From 77e2b0ad07cfa69ea819e21029458265611f7d5c Mon Sep 17 00:00:00 2001 From: YaoBing Xiao Date: Mon, 23 Mar 2026 21:09:13 +0800 Subject: [PATCH] examples: add render-pass-ext exmaple --- examples/meson.build | 2 + examples/render-pass-ext/gles2/meson.build | 6 + .../render-pass-ext/gles2/shaders/meson.build | 22 ++ .../gles2/shaders/triangle.frag | 11 + .../gles2/shaders/triangle.vert | 8 + .../render-pass-ext/gles2/triangle_pass.c | 134 ++++++++ .../render-pass-ext/gles2/triangle_pass.h | 27 ++ examples/render-pass-ext/meson.build | 26 ++ examples/render-pass-ext/pixman/meson.build | 3 + .../render-pass-ext/pixman/triangle_pass.c | 108 +++++++ .../render-pass-ext/pixman/triangle_pass.h | 16 + examples/render-pass-ext/render-pass-ext.c | 206 ++++++++++++ examples/render-pass-ext/triangle_pass.c | 98 ++++++ examples/render-pass-ext/triangle_pass.h | 41 +++ examples/render-pass-ext/vulkan/meson.build | 6 + .../vulkan/shaders/meson.build | 24 ++ .../vulkan/shaders/triangle.frag | 8 + .../vulkan/shaders/triangle.vert | 15 + .../render-pass-ext/vulkan/triangle_pass.c | 299 ++++++++++++++++++ .../render-pass-ext/vulkan/triangle_pass.h | 22 ++ 20 files changed, 1082 insertions(+) create mode 100644 examples/render-pass-ext/gles2/meson.build create mode 100644 examples/render-pass-ext/gles2/shaders/meson.build create mode 100644 examples/render-pass-ext/gles2/shaders/triangle.frag create mode 100644 examples/render-pass-ext/gles2/shaders/triangle.vert create mode 100644 examples/render-pass-ext/gles2/triangle_pass.c create mode 100644 examples/render-pass-ext/gles2/triangle_pass.h create mode 100644 examples/render-pass-ext/meson.build create mode 100644 examples/render-pass-ext/pixman/meson.build create mode 100644 examples/render-pass-ext/pixman/triangle_pass.c create mode 100644 examples/render-pass-ext/pixman/triangle_pass.h create mode 100644 examples/render-pass-ext/render-pass-ext.c create mode 100644 examples/render-pass-ext/triangle_pass.c create mode 100644 examples/render-pass-ext/triangle_pass.h create mode 100644 examples/render-pass-ext/vulkan/meson.build create mode 100644 examples/render-pass-ext/vulkan/shaders/meson.build create mode 100644 examples/render-pass-ext/vulkan/shaders/triangle.frag create mode 100644 examples/render-pass-ext/vulkan/shaders/triangle.vert create mode 100644 examples/render-pass-ext/vulkan/triangle_pass.c create mode 100644 examples/render-pass-ext/vulkan/triangle_pass.h diff --git a/examples/meson.build b/examples/meson.build index 28a83cccc..524c91d2d 100644 --- a/examples/meson.build +++ b/examples/meson.build @@ -49,6 +49,8 @@ compositors = { }, } +subdir('render-pass-ext') + foreach name, info : compositors extra_src = [] foreach p : info.get('proto', []) diff --git a/examples/render-pass-ext/gles2/meson.build b/examples/render-pass-ext/gles2/meson.build new file mode 100644 index 000000000..b317d51c6 --- /dev/null +++ b/examples/render-pass-ext/gles2/meson.build @@ -0,0 +1,6 @@ +subdir('shaders') + +gles2_sources = [ + files('triangle_pass.c'), + gles2_shader_sources, +] diff --git a/examples/render-pass-ext/gles2/shaders/meson.build b/examples/render-pass-ext/gles2/shaders/meson.build new file mode 100644 index 000000000..96a52bfca --- /dev/null +++ b/examples/render-pass-ext/gles2/shaders/meson.build @@ -0,0 +1,22 @@ +custom_pass_embed = find_program('../../../../render/gles2/shaders/embed.sh', native: true) +triangle_vert = custom_target( + 'custom-render-pass-triangle-vert', + command: [custom_pass_embed, 'custom_triangle_vert'], + input: 'triangle.vert', + output: 'triangle_vert.h', + feed: true, + capture: true, +) +triangle_frag = custom_target( + 'custom-render-pass-triangle-frag', + command: [custom_pass_embed, 'custom_triangle_frag'], + input: 'triangle.frag', + output: 'triangle_frag.h', + feed: true, + capture: true, +) + +gles2_shader_sources = [ + triangle_vert, + triangle_frag, +] diff --git a/examples/render-pass-ext/gles2/shaders/triangle.frag b/examples/render-pass-ext/gles2/shaders/triangle.frag new file mode 100644 index 000000000..0b0cbc968 --- /dev/null +++ b/examples/render-pass-ext/gles2/shaders/triangle.frag @@ -0,0 +1,11 @@ +#ifdef GL_FRAGMENT_PRECISION_HIGH +precision highp float; +#else +precision mediump float; +#endif + +varying vec3 v_color; + +void main() { + gl_FragColor = vec4(v_color, 1.0); +} diff --git a/examples/render-pass-ext/gles2/shaders/triangle.vert b/examples/render-pass-ext/gles2/shaders/triangle.vert new file mode 100644 index 000000000..e0c51f3c0 --- /dev/null +++ b/examples/render-pass-ext/gles2/shaders/triangle.vert @@ -0,0 +1,8 @@ +attribute vec2 pos; +attribute vec3 color; +varying vec3 v_color; + +void main() { + gl_Position = vec4(pos, 0.0, 1.0); + v_color = color; +} diff --git a/examples/render-pass-ext/gles2/triangle_pass.c b/examples/render-pass-ext/gles2/triangle_pass.c new file mode 100644 index 000000000..d533770b5 --- /dev/null +++ b/examples/render-pass-ext/gles2/triangle_pass.c @@ -0,0 +1,134 @@ +#include "triangle_pass.h" +#include "triangle_vert.h" +#include "triangle_frag.h" + +#include +#include +#include + +#include +#include +#include + +static void triangle_render(struct wlr_render_pass *render_pass, + const struct custom_render_triangle_options *options) { + struct wlr_gles2_render_pass *wlr_gles2_pass = + wlr_gles2_render_pass_from_render_pass(render_pass); + struct render_triangle_pass *base = + wlr_gles2_pass->buffer->renderer->wlr_renderer.data; + struct gles2_render_triangle_pass *triangle_pass = + gles2_render_triangle_pass_from_pass(base); + struct wlr_gles2_renderer *renderer = wlr_gles2_pass->buffer->renderer; + + struct wlr_box box; + struct wlr_buffer *wlr_buffer = wlr_gles2_pass->buffer->buffer; + custom_render_triangle_options_get_box(options, wlr_buffer, &box); + + int width = wlr_gles2_pass->buffer->buffer->width; + int height = wlr_gles2_pass->buffer->buffer->height; + + float x0 = box.x + box.width * 0.50f; + float y0 = box.y + box.height * 0.10f; + float x1 = box.x + box.width * 0.10f; + float y1 = box.y + box.height * 0.90f; + float x2 = box.x + box.width * 0.90f; + float y2 = box.y + box.height * 0.90f; + + GLfloat verts[3][5] = { + { x0 / width * 2.0f - 1.0f, y0 / height * 2.0f - 1.0f, 1.0f, 0.1f, 0.1f }, + { x1 / width * 2.0f - 1.0f, y1 / height * 2.0f - 1.0f, 0.1f, 1.0f, 0.1f }, + { x2 / width * 2.0f - 1.0f, y2 / height * 2.0f - 1.0f, 0.1f, 0.2f, 1.0f }, + }; + + wlr_gles2_push_debug(renderer); + glDisable(GL_SCISSOR_TEST); + glDisable(GL_BLEND); + + glUseProgram(triangle_pass->shader.program); + wlr_gles_set_proj_matrix(triangle_pass->shader.proj, wlr_gles2_pass->projection_matrix, &box); + glEnableVertexAttribArray(triangle_pass->shader.pos_attrib); + glVertexAttribPointer(triangle_pass->shader.pos_attrib, 2, GL_FLOAT, GL_FALSE, + 5 * sizeof(GLfloat), verts); + glEnableVertexAttribArray(triangle_pass->shader.color_attrib); + glVertexAttribPointer(triangle_pass->shader.color_attrib, 3, GL_FLOAT, GL_FALSE, + 5 * sizeof(GLfloat), &verts[0][2]); + + glDrawArrays(GL_TRIANGLES, 0, 3); + + glDisableVertexAttribArray(triangle_pass->shader.pos_attrib); + glDisableVertexAttribArray(triangle_pass->shader.color_attrib); + wlr_gles2_pop_debug(renderer); +} + +static void triangle_destroy(struct render_triangle_pass *pass) { + struct gles2_render_triangle_pass *gles2_pass = + gles2_render_triangle_pass_from_pass(pass); + if (gles2_pass->shader.program != 0) { + glDeleteProgram(gles2_pass->shader.program); + } + + free(gles2_pass); +} + +static const struct render_triangle_pass_impl render_triangle_pass_impl = { + .destroy = triangle_destroy, + .render = triangle_render, +}; + +struct render_triangle_pass *gles2_render_triangle_pass_create( + struct wlr_renderer *wlr_renderer) { + struct gles2_render_triangle_pass *pass = calloc(1, sizeof(*pass)); + if (pass == NULL) { + wlr_log_errno(WLR_ERROR, "failed to allocate gles2_render_triangle_pass"); + return NULL; + } + + struct wlr_gles2_renderer *renderer = wlr_gles2_renderer_from_renderer(wlr_renderer); + if (!wlr_egl_make_current(renderer->egl, NULL)) { + free(pass); + return NULL; + } + + render_triangle_pass_init(&pass->base, &render_triangle_pass_impl); + wlr_gles2_push_debug(renderer); + GLuint prog; + pass->shader.program = prog = + wlr_gles2_link_program(renderer, custom_triangle_vert, custom_triangle_frag); + if (!pass->shader.program) { + goto error; + } + + pass->shader.proj = -1; + pass->shader.color_attrib = glGetAttribLocation(pass->shader.program, "color"); + pass->shader.pos_attrib = glGetAttribLocation(pass->shader.program, "pos"); + if (pass->shader.pos_attrib < 0 || pass->shader.color_attrib < 0) { + wlr_log(WLR_ERROR, "triangle shader attribute lookup failed: pos=%d color=%d", + pass->shader.pos_attrib, pass->shader.color_attrib); + goto error; + } + wlr_gles2_pop_debug(renderer); + wlr_egl_unset_current(renderer->egl); + pass->renderer = renderer; + return &pass->base; + +error: + render_triangle_pass_destroy(&pass->base); + wlr_gles2_pop_debug(renderer); + wlr_egl_unset_current(renderer->egl); + + return NULL; +} + +bool render_triangle_pass_is_gles2(const struct render_triangle_pass *triangle_pass) { + return triangle_pass->impl == &render_triangle_pass_impl; +} + +struct gles2_render_triangle_pass *gles2_render_triangle_pass_from_pass( + struct render_triangle_pass *triangle_pass) { + assert(triangle_pass->impl == &render_triangle_pass_impl); + + struct gles2_render_triangle_pass *gles2_pass = + wl_container_of(triangle_pass, gles2_pass, base); + + return gles2_pass; +} diff --git a/examples/render-pass-ext/gles2/triangle_pass.h b/examples/render-pass-ext/gles2/triangle_pass.h new file mode 100644 index 000000000..2e22339cc --- /dev/null +++ b/examples/render-pass-ext/gles2/triangle_pass.h @@ -0,0 +1,27 @@ +#ifndef GLES2_TRIANGLE_PASS_PASS_H +#define GLES2_TRIANGLE_PASS_PASS_H + +#include "../triangle_pass.h" + +#include + +#include + +struct gles2_render_triangle_pass { + struct render_triangle_pass base; + struct wlr_gles2_renderer *renderer; + struct { + GLuint program; + GLint proj; + GLint pos_attrib; + GLint color_attrib; + } shader; +}; + +struct render_triangle_pass *gles2_render_triangle_pass_create( + struct wlr_renderer *wlr_renderer); +bool render_triangle_pass_is_gles2(const struct render_triangle_pass *triangle_pass); +struct gles2_render_triangle_pass *gles2_render_triangle_pass_from_pass( + struct render_triangle_pass *triangle_pass); + +#endif // GLES2_TRIANGLE_PASS_PASS_H diff --git a/examples/render-pass-ext/meson.build b/examples/render-pass-ext/meson.build new file mode 100644 index 000000000..acbb4a74c --- /dev/null +++ b/examples/render-pass-ext/meson.build @@ -0,0 +1,26 @@ +gles2_sources = [] +pixman_sources = [] +vulkan_sources = [] + + if features.get('gles2-renderer') + subdir('gles2') + endif + +subdir('pixman') + +if features.get('vulkan-renderer') + subdir('vulkan') +endif + +executable( + 'render-pass-ext', + [ + 'triangle_pass.c', + 'render-pass-ext.c', + gles2_sources, + pixman_sources, + vulkan_sources, + ], + dependencies: [wlroots, libdrm_header], + build_by_default: get_option('examples'), +) diff --git a/examples/render-pass-ext/pixman/meson.build b/examples/render-pass-ext/pixman/meson.build new file mode 100644 index 000000000..cdcd0763f --- /dev/null +++ b/examples/render-pass-ext/pixman/meson.build @@ -0,0 +1,3 @@ +pixman_sources = [ + files('triangle_pass.c'), +] diff --git a/examples/render-pass-ext/pixman/triangle_pass.c b/examples/render-pass-ext/pixman/triangle_pass.c new file mode 100644 index 000000000..86984e2c8 --- /dev/null +++ b/examples/render-pass-ext/pixman/triangle_pass.c @@ -0,0 +1,108 @@ +#include "render/pixman.h" +#include "triangle_pass.h" + +#include +#include + +#include +#include + +static bool triangle_contains(float px, float py, + float x0, float y0, float x1, float y1, float x2, float y2, + float *w0, float *w1, float *w2) { + float denom = (y1 - y2) * (x0 - x2) + (x2 - x1) * (y0 - y2); + if (denom == 0.0f) { + return false; + } + + *w0 = ((y1 - y2) * (px - x2) + (x2 - x1) * (py - y2)) / denom; + *w1 = ((y2 - y0) * (px - x2) + (x0 - x2) * (py - y2)) / denom; + *w2 = 1.0f - *w0 - *w1; + return *w0 >= 0.0f && *w1 >= 0.0f && *w2 >= 0.0f; +} + +static void triangle_render(struct wlr_render_pass *render_pass, + const struct custom_render_triangle_options *options) { + struct wlr_pixman_render_pass *pixman_pass = + wlr_pixman_render_pass_from_render_pass(render_pass); + pixman_image_t *dst = pixman_pass->buffer->image; + int w = options->box.width; + int h = options->box.height; + if (w <= 0 || h <= 0) { + pixman_image_set_clip_region32(dst, NULL); + return; + } + + uint32_t *pixels = calloc((size_t)w * (size_t)h, sizeof(uint32_t)); + if (pixels == NULL) { + pixman_image_set_clip_region32(dst, NULL); + return; + } + + float x0 = w * 0.50f, y0 = h * 0.10f; + float x1 = w * 0.10f, y1 = h * 0.90f; + float x2 = w * 0.90f, y2 = h * 0.90f; + + for (int y = 0; y < h; y++) { + for (int x = 0; x < w; x++) { + float w0, w1, w2; + if (!triangle_contains(x + 0.5f, y + 0.5f, + x0, y0, x1, y1, x2, y2, &w0, &w1, &w2)) { + continue; + } + + uint8_t r = (uint8_t)(w0 * 255.0f); + uint8_t g = (uint8_t)(w1 * 255.0f); + uint8_t b = (uint8_t)(w2 * 255.0f); + pixels[y * w + x] = 0xFF000000u | ((uint32_t)r << 16) | + ((uint32_t)g << 8) | (uint32_t)b; + } + } + + pixman_image_t *triangle = pixman_image_create_bits(PIXMAN_a8r8g8b8, + w, h, pixels, w * sizeof(uint32_t)); + pixman_image_composite32(PIXMAN_OP_OVER, triangle, NULL, dst, + 0, 0, 0, 0, options->box.x, options->box.y, w, h); + pixman_image_unref(triangle); + pixman_image_set_clip_region32(dst, NULL); + free(pixels); +} + +static void triangle_destroy(struct render_triangle_pass *pass) { + struct pixman_render_triangle_pass *pixman_pass = + pixman_render_triangle_pass_from_pass(pass); + free(pixman_pass); +} + +static const struct render_triangle_pass_impl render_triangle_pass_impl = { + .destroy = triangle_destroy, + .render = triangle_render, +}; + +struct render_triangle_pass *pixman_render_triangle_pass_create( + struct wlr_renderer *renderer) { + struct pixman_render_triangle_pass *pass = malloc(sizeof(*pass)); + if (pass == NULL) { + wlr_log_errno(WLR_ERROR, "failed to allocate wlr_pixman_render_submit_pass"); + return NULL; + } + + render_triangle_pass_init(&pass->base, &render_triangle_pass_impl); + + return &pass->base; +} + +bool render_triangle_pass_is_pixman(const struct render_triangle_pass *triangle_pass) { + return triangle_pass->impl == &render_triangle_pass_impl; +} + +struct pixman_render_triangle_pass *pixman_render_triangle_pass_from_pass( + struct render_triangle_pass *triangle_pass) { + assert(triangle_pass->impl == &render_triangle_pass_impl); + + struct pixman_render_triangle_pass *pixman_pass = + wl_container_of(triangle_pass, pixman_pass, base); + + return pixman_pass; +} + diff --git a/examples/render-pass-ext/pixman/triangle_pass.h b/examples/render-pass-ext/pixman/triangle_pass.h new file mode 100644 index 000000000..5267799a8 --- /dev/null +++ b/examples/render-pass-ext/pixman/triangle_pass.h @@ -0,0 +1,16 @@ +#ifndef PIXMAN_TRIANGLE_PASS_PASS_H +#define PIXMAN_TRIANGLE_PASS_PASS_H + +#include "../triangle_pass.h" + +struct pixman_render_triangle_pass { + struct render_triangle_pass base; +}; + +struct render_triangle_pass *pixman_render_triangle_pass_create( + struct wlr_renderer *renderer); +bool render_triangle_pass_is_pixman(const struct render_triangle_pass *triangle_pass); +struct pixman_render_triangle_pass *pixman_render_triangle_pass_from_pass( + struct render_triangle_pass *triangle_pass); + +#endif // PIXMAN_TRIANGLE_PASS_PASS_H diff --git a/examples/render-pass-ext/render-pass-ext.c b/examples/render-pass-ext/render-pass-ext.c new file mode 100644 index 000000000..eb09abd55 --- /dev/null +++ b/examples/render-pass-ext/render-pass-ext.c @@ -0,0 +1,206 @@ +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "triangle_pass.h" + +struct sample_state { + struct wl_display *display; + struct wl_listener new_output; + struct wl_listener new_input; + struct wl_listener renderer_destroy; + struct wlr_renderer *renderer; + struct wlr_allocator *allocator; +}; + +struct sample_output { + struct sample_state *sample; + struct wlr_output *output; + struct wl_listener frame; + struct wl_listener destroy; +}; + +struct sample_keyboard { + struct sample_state *sample; + struct wlr_keyboard *wlr_keyboard; + struct wl_listener key; + struct wl_listener destroy; +}; + +static void output_frame_notify(struct wl_listener *listener, void *data) { + struct sample_output *sample_output = + wl_container_of(listener, sample_output, frame); + struct wlr_output *wlr_output = sample_output->output; + + struct wlr_output_state state; + wlr_output_state_init(&state); + struct wlr_render_pass *pass = + wlr_output_begin_render_pass(wlr_output, &state, NULL); + + int size = wlr_output->height < wlr_output->width ? + (wlr_output->height * 3) / 4 : (wlr_output->width * 3) / 4; + struct wlr_box triangle_box = { + .x = (wlr_output->width - size) / 2, + .y = (wlr_output->height - size) / 2, + .width = size, + .height = size, + }; + render_triangle_pass_add(pass, + &(struct custom_render_triangle_options){ + .box = triangle_box, + }); + + wlr_render_pass_submit(pass); + wlr_output_commit_state(wlr_output, &state); + wlr_output_state_finish(&state); +} + +static void output_remove_notify(struct wl_listener *listener, void *data) { + struct sample_output *sample_output = + wl_container_of(listener, sample_output, destroy); + wlr_log(WLR_DEBUG, "Output removed"); + wl_list_remove(&sample_output->frame.link); + wl_list_remove(&sample_output->destroy.link); + free(sample_output); +} + +static void new_output_notify(struct wl_listener *listener, void *data) { + struct wlr_output *output = data; + struct sample_state *sample = + wl_container_of(listener, sample, new_output); + + wlr_output_init_render(output, sample->allocator, sample->renderer); + sample->renderer->data = get_or_create_render_triangle_pass(sample->renderer); + struct sample_output *sample_output = calloc(1, sizeof(*sample_output)); + sample_output->output = output; + sample_output->sample = sample; + wl_signal_add(&output->events.frame, &sample_output->frame); + sample_output->frame.notify = output_frame_notify; + wl_signal_add(&output->events.destroy, &sample_output->destroy); + sample_output->destroy.notify = output_remove_notify; + + struct wlr_output_state state; + wlr_output_state_init(&state); + wlr_output_state_set_enabled(&state, true); + struct wlr_output_mode *mode = wlr_output_preferred_mode(output); + if (mode != NULL) { + wlr_output_state_set_mode(&state, mode); + } + wlr_output_commit_state(output, &state); + wlr_output_state_finish(&state); +} + +static void keyboard_key_notify(struct wl_listener *listener, void *data) { + struct sample_keyboard *keyboard = wl_container_of(listener, keyboard, key); + struct sample_state *sample = keyboard->sample; + struct wlr_keyboard_key_event *event = data; + uint32_t keycode = event->keycode + 8; + const xkb_keysym_t *syms; + int nsyms = xkb_state_key_get_syms(keyboard->wlr_keyboard->xkb_state, + keycode, &syms); + for (int i = 0; i < nsyms; i++) { + xkb_keysym_t sym = syms[i]; + if (sym == XKB_KEY_Escape) { + wl_display_terminate(sample->display); + } + } +} + +static void keyboard_destroy_notify(struct wl_listener *listener, void *data) { + struct sample_keyboard *keyboard = + wl_container_of(listener, keyboard, destroy); + wl_list_remove(&keyboard->destroy.link); + wl_list_remove(&keyboard->key.link); + free(keyboard); +} + +static void new_input_notify(struct wl_listener *listener, void *data) { + struct wlr_input_device *device = data; + struct sample_state *sample = wl_container_of(listener, sample, new_input); + switch (device->type) { + case WLR_INPUT_DEVICE_KEYBOARD:; + struct sample_keyboard *keyboard = calloc(1, sizeof(*keyboard)); + keyboard->wlr_keyboard = wlr_keyboard_from_input_device(device); + keyboard->sample = sample; + wl_signal_add(&device->events.destroy, &keyboard->destroy); + keyboard->destroy.notify = keyboard_destroy_notify; + wl_signal_add(&keyboard->wlr_keyboard->events.key, &keyboard->key); + keyboard->key.notify = keyboard_key_notify; + struct xkb_context *context = xkb_context_new(XKB_CONTEXT_NO_FLAGS); + if (!context) { + wlr_log(WLR_ERROR, "Failed to create XKB context"); + exit(1); + } + struct xkb_keymap *keymap = xkb_keymap_new_from_names(context, NULL, + XKB_KEYMAP_COMPILE_NO_FLAGS); + if (!keymap) { + wlr_log(WLR_ERROR, "Failed to create XKB keymap"); + exit(1); + } + wlr_keyboard_set_keymap(keyboard->wlr_keyboard, keymap); + xkb_keymap_unref(keymap); + xkb_context_unref(context); + break; + default: + break; + } +} + +static void renderer_destroy_notify(struct wl_listener *listener, void *data) { + struct sample_state *sample = wl_container_of(listener, sample, renderer_destroy); + render_triangle_pass_destroy(sample->renderer->data); +} + +int main(void) { + wlr_log_init(WLR_DEBUG, NULL); + struct wl_display *display = wl_display_create(); + struct sample_state state = { + .display = display, + }; + + struct wlr_backend *backend = wlr_backend_autocreate( + wl_display_get_event_loop(display), NULL); + if (backend == NULL) { + return 1; + } + + state.renderer = wlr_renderer_autocreate(backend); + if (state.renderer == NULL) { + wlr_log(WLR_ERROR, "Failed to create renderer"); + wlr_backend_destroy(backend); + return 1; + } + + wl_signal_add(&state.renderer->events.destroy, &state.renderer_destroy); + state.renderer_destroy.notify = renderer_destroy_notify; + + state.allocator = wlr_allocator_autocreate(backend, state.renderer); + if (state.allocator == NULL) { + wlr_log(WLR_ERROR, "Failed to create allocator"); + wlr_backend_destroy(backend); + return 1; + } + + wl_signal_add(&backend->events.new_output, &state.new_output); + state.new_output.notify = new_output_notify; + wl_signal_add(&backend->events.new_input, &state.new_input); + state.new_input.notify = new_input_notify; + + if (!wlr_backend_start(backend)) { + wlr_log(WLR_ERROR, "Failed to start backend"); + wlr_backend_destroy(backend); + return 1; + } + + wl_display_run(display); + wl_display_destroy(display); + return 0; +} diff --git a/examples/render-pass-ext/triangle_pass.c b/examples/render-pass-ext/triangle_pass.c new file mode 100644 index 000000000..807ae2b37 --- /dev/null +++ b/examples/render-pass-ext/triangle_pass.c @@ -0,0 +1,98 @@ +#include +#include +#include +#include +#if WLR_HAS_GLES2_RENDERER +#include +#endif +#if WLR_HAS_VULKAN_RENDERER +#include +#endif +#include +#include + +#include "triangle_pass.h" +#include "pixman/triangle_pass.h" +#if WLR_HAS_GLES2_RENDERER +#include "gles2/triangle_pass.h" +#endif +#if WLR_HAS_VULKAN_RENDERER +#include "vulkan/triangle_pass.h" +#endif + +void render_triangle_pass_init(struct render_triangle_pass *pass, + const struct render_triangle_pass_impl *impl) { + assert(impl->render); + *pass = (struct render_triangle_pass){ + .impl = impl, + }; + + wl_signal_init(&pass->events.destroy); +} + +void render_triangle_pass_destroy(struct render_triangle_pass *pass) { + if (pass == NULL) { + return; + } + + pass->impl->destroy(pass); +} + +struct render_triangle_pass *get_or_create_render_triangle_pass( + struct wlr_renderer *renderer) { + if (renderer == NULL) { + return NULL; + } + + if (renderer->data == NULL) { + struct render_triangle_pass *pass = NULL; + if (wlr_renderer_is_pixman(renderer)) { + pass = pixman_render_triangle_pass_create(renderer); + } + +#if WLR_HAS_GLES2_RENDERER + else if (wlr_renderer_is_gles2(renderer)) { + pass = gles2_render_triangle_pass_create(renderer); + } +#endif + +#if WLR_HAS_VULKAN_RENDERER + else if (wlr_renderer_is_vk(renderer)) { + pass = vk_render_triangle_pass_create(renderer); + } +#endif + + renderer->data = pass; + return pass; + } else { + return renderer->data; + } +} + +void render_triangle_pass_add(struct wlr_render_pass *render_pass, + const struct custom_render_triangle_options *options) { + struct wlr_renderer *renderer = + wlr_get_wlr_renderer_from_render_pass(render_pass); + + struct render_triangle_pass *pass = renderer->data; + if (pass == NULL) { + wlr_log(WLR_ERROR, "No triangle pass is available for this renderer"); + return; + } + return pass->impl->render(render_pass, options); +} + +void custom_render_triangle_options_get_box(const struct custom_render_triangle_options *options, + const struct wlr_buffer *buffer, struct wlr_box *box) { + if (wlr_box_empty(&options->box)) { + *box = (struct wlr_box){ + .width = buffer->width, + .height = buffer->height, + }; + + return; + } + + *box = options->box; +} + diff --git a/examples/render-pass-ext/triangle_pass.h b/examples/render-pass-ext/triangle_pass.h new file mode 100644 index 000000000..f3ecfb040 --- /dev/null +++ b/examples/render-pass-ext/triangle_pass.h @@ -0,0 +1,41 @@ +#ifndef RENDER_PASS_EXT_H +#define RENDER_PASS_EXT_H + +#include + +#include +#include + +#include + +struct custom_render_triangle_options { + struct wlr_box box; +}; + +struct render_triangle_pass; + +struct render_triangle_pass_impl { + void (*destroy)(struct render_triangle_pass *pass); + void (*render)(struct wlr_render_pass *render_pass, + const struct custom_render_triangle_options *options); +}; + +struct render_triangle_pass { + const struct render_triangle_pass_impl *impl; + struct { + struct wl_signal destroy; + } events; +}; + +void render_triangle_pass_init(struct render_triangle_pass *pass, + const struct render_triangle_pass_impl *impl); +void render_triangle_pass_destroy(struct render_triangle_pass *pass); + +struct render_triangle_pass *get_or_create_render_triangle_pass( + struct wlr_renderer *renderer); +void render_triangle_pass_add(struct wlr_render_pass *render_pass, + const struct custom_render_triangle_options *options); +void custom_render_triangle_options_get_box(const struct custom_render_triangle_options *options, + const struct wlr_buffer *buffer, struct wlr_box *box); + +#endif // RENDER_PASS_EXT_H diff --git a/examples/render-pass-ext/vulkan/meson.build b/examples/render-pass-ext/vulkan/meson.build new file mode 100644 index 000000000..aa447d8cf --- /dev/null +++ b/examples/render-pass-ext/vulkan/meson.build @@ -0,0 +1,6 @@ +subdir('shaders') + +vulkan_sources = [ + files('triangle_pass.c'), + vulkan_shader_sources, +] diff --git a/examples/render-pass-ext/vulkan/shaders/meson.build b/examples/render-pass-ext/vulkan/shaders/meson.build new file mode 100644 index 000000000..247acccab --- /dev/null +++ b/examples/render-pass-ext/vulkan/shaders/meson.build @@ -0,0 +1,24 @@ +vulkan_custom_shaders_src = [ + 'triangle.vert', + 'triangle.frag', +] + +vulkan_shader_sources = [] +foreach shader : vulkan_custom_shaders_src + if shader == 'triangle.vert' + name = 'custom_triangle_vert_data' + else + name = 'custom_triangle_frag_data' + endif + args = [glslang, '-V', '@INPUT@', '-o', '@OUTPUT@', '--vn', name] + if glslang_version.version_compare('>=11.0.0') + args += '--quiet' + endif + header = custom_target( + 'custom-render-pass-vulkan-' + shader + '_spv', + output: shader + '.h', + input: shader, + command: args, + ) + vulkan_shader_sources += [header] +endforeach diff --git a/examples/render-pass-ext/vulkan/shaders/triangle.frag b/examples/render-pass-ext/vulkan/shaders/triangle.frag new file mode 100644 index 000000000..7dd5a9656 --- /dev/null +++ b/examples/render-pass-ext/vulkan/shaders/triangle.frag @@ -0,0 +1,8 @@ +#version 450 + +layout(location = 0) in vec3 v_color; +layout(location = 0) out vec4 out_color; + +void main() { + out_color = vec4(v_color, 1.0); +} diff --git a/examples/render-pass-ext/vulkan/shaders/triangle.vert b/examples/render-pass-ext/vulkan/shaders/triangle.vert new file mode 100644 index 000000000..26e34aec9 --- /dev/null +++ b/examples/render-pass-ext/vulkan/shaders/triangle.vert @@ -0,0 +1,15 @@ +#version 450 + +// Keep push constants layout compatible with wlroots Vulkan helpers. +layout(push_constant, row_major) uniform UBO { + mat4 proj; + vec4 pos[3]; + vec4 color[3]; +} data; + +layout(location = 0) out vec3 v_color; + +void main() { + gl_Position = data.proj * vec4(data.pos[gl_VertexIndex].xy, 0.0, 1.0); + v_color = data.color[gl_VertexIndex].rgb; +} diff --git a/examples/render-pass-ext/vulkan/triangle_pass.c b/examples/render-pass-ext/vulkan/triangle_pass.c new file mode 100644 index 000000000..865eef84e --- /dev/null +++ b/examples/render-pass-ext/vulkan/triangle_pass.c @@ -0,0 +1,299 @@ +#include "render/vulkan.h" +#include "util/matrix.h" + +#include "triangle.vert.h" +#include "triangle.frag.h" +#include "triangle_pass.h" + +#include +#include +#include +#include +#include + +#include +#include + +struct custom_triangle_push_data { + float mat4[4][4]; + float pos[3][4]; + float color[3][4]; +}; + +static void custom_rect_union_add(struct rect_union *ru, pixman_box32_t box) { + if (box.x1 >= box.x2 || box.y1 >= box.y2) { + return; + } + + ru->bounding_box.x1 = ru->bounding_box.x1 < box.x1 ? ru->bounding_box.x1 : box.x1; + ru->bounding_box.y1 = ru->bounding_box.y1 < box.y1 ? ru->bounding_box.y1 : box.y1; + ru->bounding_box.x2 = ru->bounding_box.x2 > box.x2 ? ru->bounding_box.x2 : box.x2; + ru->bounding_box.y2 = ru->bounding_box.y2 > box.y2 ? ru->bounding_box.y2 : box.y2; + + if (!ru->alloc_failure) { + pixman_box32_t *entry = wl_array_add(&ru->unsorted, sizeof(*entry)); + if (entry != NULL) { + *entry = box; + } else { + ru->alloc_failure = true; + wl_array_release(&ru->unsorted); + wl_array_init(&ru->unsorted); + } + } +} + +static void render_pass_mark_box_updated(struct wlr_vk_render_pass *pass, + const struct wlr_box *box) { + if (!pass->two_pass) { + return; + } + + pixman_box32_t pixman_box = { + .x1 = box->x, + .x2 = box->x + box->width, + .y1 = box->y, + .y2 = box->y + box->height, + }; + custom_rect_union_add(&pass->updated_region, pixman_box); +} + +static bool create_graphics_pipeline(struct vk_render_triangle_pass *pass, + struct wlr_vk_render_pass *vk_pass, + VkPipelineLayout *layout_out, + VkPipeline *pipeline_out) { + VkDevice dev = pass->renderer->dev->dev; + + VkPushConstantRange push_range = { + .stageFlags = VK_SHADER_STAGE_VERTEX_BIT, + .offset = 0, + .size = sizeof(struct custom_triangle_push_data), + }; + VkPipelineLayoutCreateInfo layout_info = { + .sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO, + .pushConstantRangeCount = 1, + .pPushConstantRanges = &push_range, + }; + VkResult res = vkCreatePipelineLayout(dev, &layout_info, NULL, layout_out); + if (res != VK_SUCCESS) { + wlr_log(WLR_ERROR, "Failed to create custom Vulkan pipeline layout (VkResult=%d)", res); + return false; + } + + VkPipelineShaderStageCreateInfo shader_stages[2] = { + { + .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, + .stage = VK_SHADER_STAGE_VERTEX_BIT, + .module = pass->vert_module, + .pName = "main", + }, + { + .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, + .stage = VK_SHADER_STAGE_FRAGMENT_BIT, + .module = pass->frag_module, + .pName = "main", + }, + }; + + VkPipelineVertexInputStateCreateInfo vertex_input = { + .sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO, + }; + VkPipelineInputAssemblyStateCreateInfo input_assembly = { + .sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO, + .topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, + }; + VkPipelineViewportStateCreateInfo viewport_state = { + .sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO, + .viewportCount = 1, + .scissorCount = 1, + }; + VkPipelineRasterizationStateCreateInfo rasterization = { + .sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO, + .polygonMode = VK_POLYGON_MODE_FILL, + .cullMode = VK_CULL_MODE_NONE, + .frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE, + .lineWidth = 1.0f, + }; + VkPipelineMultisampleStateCreateInfo multisample = { + .sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO, + .rasterizationSamples = VK_SAMPLE_COUNT_1_BIT, + }; + VkPipelineColorBlendAttachmentState blend_attachment = { + .blendEnable = VK_FALSE, + .colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | + VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT, + }; + VkPipelineColorBlendStateCreateInfo blend = { + .sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO, + .attachmentCount = 1, + .pAttachments = &blend_attachment, + }; + VkDynamicState dyn_states[] = { + VK_DYNAMIC_STATE_VIEWPORT, + VK_DYNAMIC_STATE_SCISSOR, + }; + VkPipelineDynamicStateCreateInfo dynamic = { + .sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO, + .dynamicStateCount = sizeof(dyn_states) / sizeof(dyn_states[0]), + .pDynamicStates = dyn_states, + }; + + VkGraphicsPipelineCreateInfo pipeline_info = { + .sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO, + .stageCount = 2, + .pStages = shader_stages, + .pVertexInputState = &vertex_input, + .pInputAssemblyState = &input_assembly, + .pViewportState = &viewport_state, + .pRasterizationState = &rasterization, + .pMultisampleState = &multisample, + .pColorBlendState = &blend, + .pDynamicState = &dynamic, + .layout = *layout_out, + .renderPass = vk_pass->render_setup->render_pass, + .subpass = 0, + }; + + res = vkCreateGraphicsPipelines(dev, VK_NULL_HANDLE, 1, &pipeline_info, NULL, pipeline_out); + if (res != VK_SUCCESS) { + wlr_log(WLR_ERROR, "Failed to create custom Vulkan graphics pipeline (VkResult=%d)", res); + vkDestroyPipelineLayout(dev, *layout_out, NULL); + *layout_out = VK_NULL_HANDLE; + return false; + } + + return true; +} + +static void triangle_render(struct wlr_render_pass *render_pass, + const struct custom_render_triangle_options *options) { + struct wlr_vk_render_pass *vk_pass = + wlr_vk_render_pass_from_render_pass(render_pass); + struct render_triangle_pass *base = + vk_pass->render_buffer->renderer->wlr_renderer.data; + struct vk_render_triangle_pass *vk_triangle_pass = + vk_render_triangle_pass_from_pass(base); + + VkCommandBuffer cb = vk_pass->command_buffer->vk; + VkPipelineLayout pipeline_layout = VK_NULL_HANDLE; + VkPipeline pipeline = VK_NULL_HANDLE; + + struct wlr_box box; + struct wlr_buffer *wlr_buffer = vk_pass->render_buffer->wlr_buffer; + custom_render_triangle_options_get_box(options, wlr_buffer, &box); + render_pass_mark_box_updated(vk_pass, &box); + + float proj[9], matrix[9]; + wlr_matrix_identity(proj); + wlr_matrix_project_box(matrix, &box, WL_OUTPUT_TRANSFORM_NORMAL, proj); + wlr_matrix_multiply(matrix, vk_pass->projection, matrix); + + if (!create_graphics_pipeline(vk_triangle_pass, vk_pass, + &pipeline_layout, &pipeline)) { + vk_pass->failed = true; + return; + } + + struct custom_triangle_push_data push_data = { + .pos = { + { 0.5f, 0.1f, 0.0f, 0.0f }, + { 0.1f, 0.9f, 0.0f, 0.0f }, + { 0.9f, 0.9f, 0.0f, 0.0f }, + }, + }; + + const float alpha = 1.0f; + push_data.color[0][0] = wlr_color_to_linear_premult(1.0f, alpha); + push_data.color[0][1] = wlr_color_to_linear_premult(0.1f, alpha); + push_data.color[0][2] = wlr_color_to_linear_premult(0.1f, alpha); + push_data.color[1][0] = wlr_color_to_linear_premult(0.1f, alpha); + push_data.color[1][1] = wlr_color_to_linear_premult(1.0f, alpha); + push_data.color[1][2] = wlr_color_to_linear_premult(0.1f, alpha); + push_data.color[2][0] = wlr_color_to_linear_premult(0.1f, alpha); + push_data.color[2][1] = wlr_color_to_linear_premult(0.2f, alpha); + push_data.color[2][2] = wlr_color_to_linear_premult(1.0f, alpha); + wlr_encode_proj_matrix(matrix, push_data.mat4); + + vkCmdBindPipeline(cb, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline); + vkCmdPushConstants(cb, pipeline_layout, + VK_SHADER_STAGE_VERTEX_BIT, 0, sizeof(push_data), &push_data); + + VkRect2D scissor = { + .offset = { .x = 0, .y = 0 }, + .extent = { .width = wlr_buffer->width, .height = wlr_buffer->height }, + }; + vkCmdSetScissor(cb, 0, 1, &scissor); + vkCmdDraw(cb, 3, 1, 0, 0); + + VkDevice dev = vk_triangle_pass->renderer->dev->dev; + vkDestroyPipeline(dev, pipeline, NULL); + vkDestroyPipelineLayout(dev, pipeline_layout, NULL); +} + +static void triangle_destroy(struct render_triangle_pass *pass) { + struct vk_render_triangle_pass *vk_triangle_pass = + (struct vk_render_triangle_pass *)pass; + + if (vk_triangle_pass->vert_module != VK_NULL_HANDLE) { + vkDestroyShaderModule(vk_triangle_pass->renderer->dev->dev, + vk_triangle_pass->vert_module, NULL); + } + if (vk_triangle_pass->frag_module != VK_NULL_HANDLE) { + vkDestroyShaderModule(vk_triangle_pass->renderer->dev->dev, + vk_triangle_pass->frag_module, NULL); + } + + free(vk_triangle_pass); +} + +static const struct render_triangle_pass_impl render_triangle_pass_impl = { + .destroy = triangle_destroy, + .render = triangle_render, +}; + +struct render_triangle_pass *vk_render_triangle_pass_create( + struct wlr_renderer *renderer) { + struct vk_render_triangle_pass *pass = malloc(sizeof(*pass)); + if (pass == NULL) { + wlr_log_errno(WLR_ERROR, "failed to allocate vk_render_triangle_pass"); + return NULL; + } + + render_triangle_pass_init(&pass->base, &render_triangle_pass_impl); + + pass->renderer = wlr_vk_renderer_from_renderer(renderer); + VkDevice dev = pass->renderer->dev->dev; + + if (!wlr_vk_create_shader_module(dev, + custom_triangle_vert_data, + sizeof(custom_triangle_vert_data), + "custom triangle vertex", + &pass->vert_module)) { + render_triangle_pass_destroy(&pass->base); + return NULL; + } + + if (!wlr_vk_create_shader_module(dev, + custom_triangle_frag_data, + sizeof(custom_triangle_frag_data), + "custom triangle fragment", + &pass->frag_module)) { + render_triangle_pass_destroy(&pass->base); + return NULL; + } + + return &pass->base; +} + +bool render_triangle_pass_is_vk(const struct render_triangle_pass *triangle_pass) { + return triangle_pass->impl == &render_triangle_pass_impl; +} + +struct vk_render_triangle_pass *vk_render_triangle_pass_from_pass( + struct render_triangle_pass *triangle_pass) { + assert(triangle_pass->impl == &render_triangle_pass_impl); + + struct vk_render_triangle_pass *vk_pass = + wl_container_of(triangle_pass, vk_pass, base); + + return vk_pass; +} diff --git a/examples/render-pass-ext/vulkan/triangle_pass.h b/examples/render-pass-ext/vulkan/triangle_pass.h new file mode 100644 index 000000000..9d82fa50f --- /dev/null +++ b/examples/render-pass-ext/vulkan/triangle_pass.h @@ -0,0 +1,22 @@ +#ifndef CUSTOM_RENDER_PASS_VULKAN_PASS_H +#define CUSTOM_RENDER_PASS_VULKAN_PASS_H + +#include "../triangle_pass.h" + +#include +#include + +struct vk_render_triangle_pass { + struct render_triangle_pass base; + struct wlr_vk_renderer *renderer; + VkShaderModule vert_module; + VkShaderModule frag_module; +}; + +struct render_triangle_pass *vk_render_triangle_pass_create( + struct wlr_renderer *renderer); +bool render_triangle_pass_is_vk(const struct render_triangle_pass *triangle_pass); +struct vk_render_triangle_pass *vk_render_triangle_pass_from_pass( + struct render_triangle_pass *triangle_pass); + +#endif