From 4ba9e704e525f73c46d5eec0bc145f02920d5f6c Mon Sep 17 00:00:00 2001 From: Travis Vroman Date: Mon, 30 May 2022 20:33:09 -0400 Subject: [PATCH] 059 Render Targets and Configurable Renderpasses (#61) --- engine/src/renderer/renderer_backend.c | 10 + engine/src/renderer/renderer_frontend.c | 200 +++++++- engine/src/renderer/renderer_frontend.h | 50 +- engine/src/renderer/renderer_types.inl | 167 ++++++- engine/src/renderer/vulkan/vulkan_backend.c | 443 ++++++++++++------ engine/src/renderer/vulkan/vulkan_backend.h | 142 +----- engine/src/renderer/vulkan/vulkan_device.c | 7 + .../src/renderer/vulkan/vulkan_renderpass.c | 176 ------- .../src/renderer/vulkan/vulkan_renderpass.h | 83 ---- engine/src/renderer/vulkan/vulkan_swapchain.c | 18 +- engine/src/renderer/vulkan/vulkan_types.inl | 39 +- engine/src/systems/shader_system.c | 6 +- testbed/src/game.c | 2 +- 13 files changed, 745 insertions(+), 598 deletions(-) delete mode 100644 engine/src/renderer/vulkan/vulkan_renderpass.c delete mode 100644 engine/src/renderer/vulkan/vulkan_renderpass.h diff --git a/engine/src/renderer/renderer_backend.c b/engine/src/renderer/renderer_backend.c index 4cb58e5d..3cf2a0dd 100644 --- a/engine/src/renderer/renderer_backend.c +++ b/engine/src/renderer/renderer_backend.c @@ -37,6 +37,16 @@ b8 renderer_backend_create(renderer_backend_type type, renderer_backend* out_ren out_renderer_backend->texture_map_acquire_resources = vulkan_renderer_texture_map_acquire_resources; out_renderer_backend->texture_map_release_resources = vulkan_renderer_texture_map_release_resources; + out_renderer_backend->render_target_create = vulkan_renderer_render_target_create; + out_renderer_backend->render_target_destroy = vulkan_renderer_render_target_destroy; + + out_renderer_backend->renderpass_create = vulkan_renderpass_create; + out_renderer_backend->renderpass_destroy = vulkan_renderpass_destroy; + out_renderer_backend->renderpass_get = vulkan_renderer_renderpass_get; + out_renderer_backend->window_attachment_get = vulkan_renderer_window_attachment_get; + out_renderer_backend->depth_attachment_get = vulkan_renderer_depth_attachment_get; + out_renderer_backend->window_attachment_index_get = vulkan_renderer_window_attachment_index_get; + return true; } diff --git a/engine/src/renderer/renderer_frontend.c b/engine/src/renderer/renderer_frontend.c index 4f4b7d2e..86ddf9c4 100644 --- a/engine/src/renderer/renderer_frontend.c +++ b/engine/src/renderer/renderer_frontend.c @@ -31,10 +31,28 @@ typedef struct renderer_system_state { u32 material_shader_id; u32 ui_shader_id; u32 render_mode; + // The number of render targets. Typically lines up with the amount of swapchain images. + u8 window_render_target_count; + // The current window framebuffer width. + u32 framebuffer_width; + // The current window framebuffer height. + u32 framebuffer_height; + + // A pointer to the world renderpass. TODO: Configurable via views. + renderpass* world_renderpass; + // A pointer to the UI renderpass. TODO: Configurable via views. + renderpass* ui_renderpass; + // Indicates if the window is currently being resized. + b8 resizing; + // The current number of frames since the last resize operation.' + // Only set if resizing = true. Otherwise 0. + u8 frames_since_resize; } renderer_system_state; static renderer_system_state* state_ptr; +void regenerate_render_targets(); + b8 renderer_on_event(u16 code, void* sender, void* listener_inst, event_context context) { switch (code) { case EVENT_CODE_SET_RENDER_MODE: { @@ -75,6 +93,12 @@ b8 renderer_system_initialize(u64* memory_requirement, void* state, const char* } state_ptr = state; + // Default framebuffer size. Overridden when window is created. + state_ptr->framebuffer_width = 1280; + state_ptr->framebuffer_height = 720; + state_ptr->resizing = false; + state_ptr->frames_since_resize = 0; + // TODO: make this configurable. renderer_backend_create(RENDERER_BACKEND_TYPE_VULKAN, &state_ptr->backend); state_ptr->backend.frame_number = 0; @@ -82,8 +106,56 @@ b8 renderer_system_initialize(u64* memory_requirement, void* state, const char* event_register(EVENT_CODE_SET_RENDER_MODE, state, renderer_on_event); + renderer_backend_config renderer_config = {}; + renderer_config.application_name = application_name; + renderer_config.on_rendertarget_refresh_required = regenerate_render_targets; + + // Renderpasses. TODO: read config from file. + renderer_config.renderpass_count = 2; + const char* world_renderpass_name = "Renderpass.Builtin.World"; + const char* ui_renderpass_name = "Renderpass.Builtin.UI"; + renderpass_config pass_configs[2]; + pass_configs[0].name = world_renderpass_name; + pass_configs[0].prev_name = 0; + pass_configs[0].next_name = ui_renderpass_name; + pass_configs[0].render_area = (vec4){0, 0, 1280, 720}; + pass_configs[0].clear_colour = (vec4){0.0f, 0.0f, 0.2f, 1.0f}; + pass_configs[0].clear_flags = RENDERPASS_CLEAR_COLOUR_BUFFER_FLAG | RENDERPASS_CLEAR_DEPTH_BUFFER_FLAG | RENDERPASS_CLEAR_STENCIL_BUFFER_FLAG; + + pass_configs[1].name = ui_renderpass_name; + pass_configs[1].prev_name = world_renderpass_name; + pass_configs[1].next_name = 0; + pass_configs[1].render_area = (vec4){0, 0, 1280, 720}; + pass_configs[1].clear_colour = (vec4){0.0f, 0.0f, 0.2f, 1.0f}; + pass_configs[1].clear_flags = RENDERPASS_CLEAR_NONE_FLAG; + + renderer_config.pass_configs = pass_configs; + // Initialize the backend. - CRITICAL_INIT(state_ptr->backend.initialize(&state_ptr->backend, application_name), "Renderer backend failed to initialize. Shutting down."); + CRITICAL_INIT(state_ptr->backend.initialize(&state_ptr->backend, &renderer_config, &state_ptr->window_render_target_count), "Renderer backend failed to initialize. Shutting down."); + + // TODO: Will know how to get these when we define views. + state_ptr->world_renderpass = state_ptr->backend.renderpass_get(world_renderpass_name); + state_ptr->world_renderpass->render_target_count = state_ptr->window_render_target_count; + state_ptr->world_renderpass->targets = kallocate(sizeof(render_target) * state_ptr->window_render_target_count, MEMORY_TAG_ARRAY); + + state_ptr->ui_renderpass = state_ptr->backend.renderpass_get(ui_renderpass_name); + state_ptr->ui_renderpass->render_target_count = state_ptr->window_render_target_count; + state_ptr->ui_renderpass->targets = kallocate(sizeof(render_target) * state_ptr->window_render_target_count, MEMORY_TAG_ARRAY); + + regenerate_render_targets(); + + // Update the main/world renderpass dimensions. + state_ptr->world_renderpass->render_area.x = 0; + state_ptr->world_renderpass->render_area.y = 0; + state_ptr->world_renderpass->render_area.z = state_ptr->framebuffer_width; + state_ptr->world_renderpass->render_area.w = state_ptr->framebuffer_height; + + // Also update the UI renderpass dimensions. + state_ptr->ui_renderpass->render_area.x = 0; + state_ptr->ui_renderpass->render_area.y = 0; + state_ptr->ui_renderpass->render_area.z = state_ptr->framebuffer_width; + state_ptr->ui_renderpass->render_area.w = state_ptr->framebuffer_height; // Shaders resource config_resource; @@ -126,6 +198,12 @@ b8 renderer_system_initialize(u64* memory_requirement, void* state, const char* void renderer_system_shutdown(void* state) { if (state_ptr) { + // Destroy render targets. + for (u8 i = 0; i < state_ptr->window_render_target_count; ++i) { + state_ptr->backend.render_target_destroy(&state_ptr->world_renderpass->targets[i], true); + state_ptr->backend.render_target_destroy(&state_ptr->ui_renderpass->targets[i], true); + } + state_ptr->backend.shutdown(&state_ptr->backend); } state_ptr = 0; @@ -133,9 +211,12 @@ void renderer_system_shutdown(void* state) { void renderer_on_resized(u16 width, u16 height) { if (state_ptr) { - state_ptr->projection = mat4_perspective(deg_to_rad(45.0f), width / (f32)height, state_ptr->near_clip, state_ptr->far_clip); - state_ptr->ui_projection = mat4_orthographic(0, (f32)width, (f32)height, 0, -100.f, 100.0f); // Intentionally flipped on y axis. - state_ptr->backend.resized(&state_ptr->backend, width, height); + // Flag as resizing and store the change, but wait to regenerate. + state_ptr->resizing = true; + state_ptr->framebuffer_width = width; + state_ptr->framebuffer_height = height; + // Also reset the frame count since the last resize operation. + state_ptr->frames_since_resize = 0; } else { KWARN("renderer backend does not exist to accept resize: %i %i", width, height); } @@ -144,10 +225,42 @@ void renderer_on_resized(u16 width, u16 height) { b8 renderer_draw_frame(render_packet* packet) { state_ptr->backend.frame_number++; + // Make sure the window is not currently being resized by waiting a designated + // number of frames after the last resize operation before performing the backend updates. + if (state_ptr->resizing) { + state_ptr->frames_since_resize++; + + // If the required number of frames have passed since the resize, go ahead and perform the actual updates. + if (state_ptr->frames_since_resize >= 30) { + f32 width = state_ptr->framebuffer_width; + f32 height = state_ptr->framebuffer_height; + state_ptr->projection = mat4_perspective(deg_to_rad(45.0f), width / (f32)height, state_ptr->near_clip, state_ptr->far_clip); + state_ptr->ui_projection = mat4_orthographic(0, (f32)width, (f32)height, 0, -100.f, 100.0f); // Intentionally flipped on y axis. + state_ptr->backend.resized(&state_ptr->backend, width, height); + + state_ptr->frames_since_resize = 0; + state_ptr->resizing = false; + } else { + // Skip rendering the frame and try again next time. + return true; + } + } + + // TODO: views + // Update the main/world renderpass dimensions. + state_ptr->world_renderpass->render_area.z = state_ptr->framebuffer_width; + state_ptr->world_renderpass->render_area.w = state_ptr->framebuffer_height; + + // Also update the UI renderpass dimensions. + state_ptr->ui_renderpass->render_area.z = state_ptr->framebuffer_width; + state_ptr->ui_renderpass->render_area.w = state_ptr->framebuffer_height; + // If the begin frame returned successfully, mid-frame operations may continue. if (state_ptr->backend.begin_frame(&state_ptr->backend, packet->delta_time)) { + u8 attachment_index = state_ptr->backend.window_attachment_index_get(); + // World renderpass - if (!state_ptr->backend.begin_renderpass(&state_ptr->backend, BUILTIN_RENDERPASS_WORLD)) { + if (!state_ptr->backend.begin_renderpass(&state_ptr->backend, state_ptr->world_renderpass, &state_ptr->world_renderpass->targets[attachment_index])) { KERROR("backend.begin_renderpass -> BUILTIN_RENDERPASS_WORLD failed. Application shutting down..."); return false; } @@ -191,14 +304,14 @@ b8 renderer_draw_frame(render_packet* packet) { state_ptr->backend.draw_geometry(packet->geometries[i]); } - if (!state_ptr->backend.end_renderpass(&state_ptr->backend, BUILTIN_RENDERPASS_WORLD)) { + if (!state_ptr->backend.end_renderpass(&state_ptr->backend, state_ptr->world_renderpass)) { KERROR("backend.end_renderpass -> BUILTIN_RENDERPASS_WORLD failed. Application shutting down..."); return false; } // End world renderpass // UI renderpass - if (!state_ptr->backend.begin_renderpass(&state_ptr->backend, BUILTIN_RENDERPASS_UI)) { + if (!state_ptr->backend.begin_renderpass(&state_ptr->backend, state_ptr->ui_renderpass, &state_ptr->ui_renderpass->targets[attachment_index])) { KERROR("backend.begin_renderpass -> BUILTIN_RENDERPASS_UI failed. Application shutting down..."); return false; } @@ -241,7 +354,7 @@ b8 renderer_draw_frame(render_packet* packet) { state_ptr->backend.draw_geometry(packet->ui_geometries[i]); } - if (!state_ptr->backend.end_renderpass(&state_ptr->backend, BUILTIN_RENDERPASS_UI)) { + if (!state_ptr->backend.end_renderpass(&state_ptr->backend, state_ptr->ui_renderpass)) { KERROR("backend.end_renderpass -> BUILTIN_RENDERPASS_UI failed. Application shutting down..."); return false; } @@ -284,7 +397,6 @@ void renderer_texture_resize(texture* t, u32 new_width, u32 new_height) { state_ptr->backend.texture_resize(t, new_width, new_height); } - b8 renderer_create_geometry(geometry* geometry, u32 vertex_size, u32 vertex_count, const void* vertices, u32 index_size, u32 index_count, const void* indices) { return state_ptr->backend.create_geometry(geometry, vertex_size, vertex_count, vertices, index_size, index_count, indices); } @@ -293,23 +405,12 @@ void renderer_destroy_geometry(geometry* geometry) { state_ptr->backend.destroy_geometry(geometry); } -b8 renderer_renderpass_id(const char* name, u8* out_renderpass_id) { - // TODO: HACK: Need dynamic renderpasses instead of hardcoding them. - if (strings_equali("Renderpass.Builtin.World", name)) { - *out_renderpass_id = BUILTIN_RENDERPASS_WORLD; - return true; - } else if (strings_equali("Renderpass.Builtin.UI", name)) { - *out_renderpass_id = BUILTIN_RENDERPASS_UI; - return true; - } - - KERROR("renderer_renderpass_id: No renderpass named '%s'.", name); - *out_renderpass_id = INVALID_ID_U8; - return false; +renderpass* renderer_renderpass_get(const char* name) { + return state_ptr->backend.renderpass_get(name); } -b8 renderer_shader_create(shader* s, u8 renderpass_id, u8 stage_count, const char** stage_filenames, shader_stage* stages) { - return state_ptr->backend.shader_create(s, renderpass_id, stage_count, stage_filenames, stages); +b8 renderer_shader_create(shader* s, renderpass* pass, u8 stage_count, const char** stage_filenames, shader_stage* stages) { + return state_ptr->backend.shader_create(s, pass, stage_count, stage_filenames, stages); } void renderer_shader_destroy(shader* s) { @@ -358,4 +459,53 @@ b8 renderer_texture_map_acquire_resources(struct texture_map* map) { void renderer_texture_map_release_resources(struct texture_map* map) { state_ptr->backend.texture_map_release_resources(map); -} \ No newline at end of file +} + +void renderer_render_target_create(u8 attachment_count, texture** attachments, renderpass* pass, u32 width, u32 height, render_target* out_target) { + state_ptr->backend.render_target_create(attachment_count, attachments, pass, width, height, out_target); +} + +void renderer_render_target_destroy(render_target* target, b8 free_internal_memory) { + state_ptr->backend.render_target_destroy(target, free_internal_memory); +} + +void renderer_renderpass_create(renderpass* out_renderpass, f32 depth, u32 stencil, b8 has_prev_pass, b8 has_next_pass) { + state_ptr->backend.renderpass_create(out_renderpass, depth, stencil, has_prev_pass, has_next_pass); +} + +void renderer_renderpass_destroy(renderpass* pass) { + state_ptr->backend.renderpass_destroy(pass); +} + +void regenerate_render_targets() { + // Create render targets for each. TODO: Should be configurable. + for (u8 i = 0; i < state_ptr->window_render_target_count; ++i) { + // Destroy the old first if they exist. + state_ptr->backend.render_target_destroy(&state_ptr->world_renderpass->targets[i], false); + state_ptr->backend.render_target_destroy(&state_ptr->ui_renderpass->targets[i], false); + + texture* window_target_texture = state_ptr->backend.window_attachment_get(i); + texture* depth_target_texture = state_ptr->backend.depth_attachment_get(); + + // World render targets. + texture* attachments[2] = {window_target_texture, depth_target_texture}; + state_ptr->backend.render_target_create( + 2, + attachments, + state_ptr->world_renderpass, + state_ptr->framebuffer_width, + state_ptr->framebuffer_height, + &state_ptr->world_renderpass->targets[i]); + + // UI render targets + texture* ui_attachments[1] = {window_target_texture}; + state_ptr->backend.render_target_create( + 1, + ui_attachments, + state_ptr->ui_renderpass, + state_ptr->framebuffer_width, + state_ptr->framebuffer_height, + &state_ptr->ui_renderpass->targets[i]); + + } +} diff --git a/engine/src/renderer/renderer_frontend.h b/engine/src/renderer/renderer_frontend.h index a2c8543e..24bb17c9 100644 --- a/engine/src/renderer/renderer_frontend.h +++ b/engine/src/renderer/renderer_frontend.h @@ -127,25 +127,24 @@ b8 renderer_create_geometry(geometry* geometry, u32 vertex_size, u32 vertex_coun void renderer_destroy_geometry(geometry* geometry); /** - * @brief Obtains the identifier of the renderpass with the given name. + * @brief Obtains a pointer to the renderpass with the given name. * * @param name The name of the renderpass whose identifier to obtain. - * @param out_renderpass_id A pointer to hold the renderpass id. - * @return True if found; otherwise false. + * @return A pointer to a renderpass if found; otherwise 0. */ -b8 renderer_renderpass_id(const char* name, u8* out_renderpass_id); +renderpass* renderer_renderpass_get(const char* name); /** * @brief Creates internal shader resources using the provided parameters. * * @param s A pointer to the shader. - * @param renderpass_id The identifier of the renderpass to be associated with the shader. + * @param pass A pointer to the renderpass to be associated with the shader. * @param stage_count The total number of stages. * @param stage_filenames An array of shader stage filenames to be loaded. Should align with stages array. * @param stages A array of shader_stages indicating what render stages (vertex, fragment, etc.) used in this shader. * @return b8 True on success; otherwise false. */ -b8 renderer_shader_create(struct shader* s, u8 renderpass_id, u8 stage_count, const char** stage_filenames, shader_stage* stages); +b8 renderer_shader_create(struct shader* s, renderpass* pass, u8 stage_count, const char** stage_filenames, shader_stage* stages); /** * @brief Destroys the given shader and releases any resources held by it. @@ -248,3 +247,42 @@ b8 renderer_texture_map_acquire_resources(struct texture_map* map); * @param map A pointer to the texture map to release resources from. */ void renderer_texture_map_release_resources(struct texture_map* map); + +/** + * @brief Creates a new render target using the provided data. + * + * @param attachment_count The number of attachments (texture pointers). + * @param attachments An array of attachments (texture pointers). + * @param renderpass A pointer to the renderpass the render target is associated with. + * @param width The width of the render target in pixels. + * @param height The height of the render target in pixels. + * @param out_target A pointer to hold the newly created render target. + */ +void renderer_render_target_create(u8 attachment_count, texture** attachments, renderpass* pass, u32 width, u32 height, render_target* out_target); + +/** + * @brief Destroys the provided render target. + * + * @param target A pointer to the render target to be destroyed. + * @param free_internal_memory Indicates if internal memory should be freed. + */ +void renderer_render_target_destroy(render_target* target, b8 free_internal_memory); + +/** + * @brief Creates a new renderpass. + * + * @param out_renderpass A pointer to the generic renderpass. + * @param depth The depth clear amount. + * @param stencil The stencil clear value. + * @param clear_flags The combined clear flags indicating what kind of clear should take place. + * @param has_prev_pass Indicates if there is a previous renderpass. + * @param has_next_pass Indicates if there is a next renderpass. + */ +void renderer_renderpass_create(renderpass* out_renderpass, f32 depth, u32 stencil, b8 has_prev_pass, b8 has_next_pass); + +/** + * @brief Destroys the given renderpass. + * + * @param pass A pointer to the renderpass to be destroyed. + */ +void renderer_renderpass_destroy(renderpass* pass); diff --git a/engine/src/renderer/renderer_types.inl b/engine/src/renderer/renderer_types.inl index 8586ebdb..4f735d8d 100644 --- a/engine/src/renderer/renderer_types.inl +++ b/engine/src/renderer/renderer_types.inl @@ -21,17 +21,91 @@ typedef struct geometry_render_data { geometry* geometry; } geometry_render_data; -typedef enum builtin_renderpass { - BUILTIN_RENDERPASS_WORLD = 0x01, - BUILTIN_RENDERPASS_UI = 0x02 -} builtin_renderpass; - typedef enum renderer_debug_view_mode { RENDERER_VIEW_MODE_DEFAULT = 0, RENDERER_VIEW_MODE_LIGHTING = 1, RENDERER_VIEW_MODE_NORMALS = 2 } renderer_debug_view_mode; +/** @brief Represents a render target, which is used for rendering to a texture or set of textures. */ +typedef struct render_target { + /** @brief Indicates if this render target should be updated on window resize. */ + b8 sync_to_window_size; + /** @brief The number of attachments */ + u8 attachment_count; + /** @brief An array of attachments (pointers to textures). */ + struct texture** attachments; + /** @brief The renderer API internal framebuffer object. */ + void* internal_framebuffer; +} render_target; + +/** + * @brief The types of clearing to be done on a renderpass. + * Can be combined together for multiple clearing functions. + */ +typedef enum renderpass_clear_flag { + /** @brief No clearing shoudl be done. */ + RENDERPASS_CLEAR_NONE_FLAG = 0x0, + /** @brief Clear the colour buffer. */ + RENDERPASS_CLEAR_COLOUR_BUFFER_FLAG = 0x1, + /** @brief Clear the depth buffer. */ + RENDERPASS_CLEAR_DEPTH_BUFFER_FLAG = 0x2, + /** @brief Clear the stencil buffer. */ + RENDERPASS_CLEAR_STENCIL_BUFFER_FLAG = 0x4 +} renderpass_clear_flag; + +typedef struct renderpass_config { + /** @brief The name of this renderpass. */ + const char* name; + /** @brief The name of the previous renderpass. */ + const char* prev_name; + /** @brief The name of the next renderpass. */ + const char* next_name; + /** @brief The current render area of the renderpass. */ + vec4 render_area; + /** @brief The clear colour used for this renderpass. */ + vec4 clear_colour; + + /** @brief The clear flags for this renderpass. */ + u8 clear_flags; +} renderpass_config; + +/** + * @brief Represents a generic renderpass. + */ +typedef struct renderpass { + /** @brief The id of the renderpass */ + u16 id; + + /** @brief The current render area of the renderpass. */ + vec4 render_area; + /** @brief The clear colour used for this renderpass. */ + vec4 clear_colour; + + /** @brief The clear flags for this renderpass. */ + u8 clear_flags; + /** @brief The number of render targets for this renderpass. */ + u8 render_target_count; + /** @brief An array of render targets used by this renderpass. */ + render_target* targets; + + /** @brief Internal renderpass data */ + void* internal_data; +} renderpass; + +/** @brief The generic configuration for a renderer backend. */ +typedef struct renderer_backend_config { + /** @brief The name of the application */ + const char* application_name; + /** @brief The number of pointers to renderpasses. */ + u16 renderpass_count; + /** @brief An array configurations for renderpasses. Will be initialized on the backend automatically. */ + renderpass_config* pass_configs; + /** @brief A callback that will be made when the backend requires a refresh/regeneration of the render targets. */ + void (*on_rendertarget_refresh_required)(); +} renderer_backend_config; + + /** * @brief A generic "interface" for the backend. The renderer backend * is what is responsible for making calls to the graphics API such as @@ -46,10 +120,11 @@ typedef struct renderer_backend { * @brief Initializes the backend. * * @param backend A pointer to the generic backend interface. - * @param application_name The name of the application. + * @param config A pointer to configuration to be used when initializing the backend. + * @param out_window_render_target_count A pointer to hold how many render targets are needed for renderpasses targeting the window. * @return True if initialized successfully; otherwise false. */ - b8 (*initialize)(struct renderer_backend* backend, const char* application_name); + b8 (*initialize)(struct renderer_backend* backend, const renderer_backend_config* config, u8* out_window_render_target_count); /** * @brief Shuts the renderer backend down. @@ -93,19 +168,28 @@ typedef struct renderer_backend { * @brief Begins a renderpass with the given id. * * @param backend A pointer to the generic backend interface. - * @param renderpass_id The identifier of the renderpass to begin. + * @param pass A pointer to the renderpass to begin. + * @param target A pointer to the render target to be used. * @return True on success; otherwise false. */ - b8 (*begin_renderpass)(struct renderer_backend* backend, u8 renderpass_id); + b8 (*begin_renderpass)(struct renderer_backend* backend, renderpass* pass, render_target* target); /** * @brief Ends a renderpass with the given id. * * @param backend A pointer to the generic backend interface. - * @param renderpass_id The identifier of the renderpass to end. + * @param pass A pointer to the renderpass to end. * @return True on success; otherwise false. */ - b8 (*end_renderpass)(struct renderer_backend* backend, u8 renderpass_id); + b8 (*end_renderpass)(struct renderer_backend* backend, renderpass* pass); + + /** + * @brief Obtains a pointer to a renderpass using the provided name. + * + * @param name The renderpass name. + * @return A pointer to a renderpass, if found; otherwise 0. + */ + renderpass* (*renderpass_get)(const char* name); /** * @brief Draws the given geometry. Should only be called inside a renderpass, within a frame. @@ -186,13 +270,13 @@ typedef struct renderer_backend { * @brief Creates internal shader resources using the provided parameters. * * @param s A pointer to the shader. - * @param renderpass_id The identifier of the renderpass to be associated with the shader. + * @param pass A pointer to the renderpass to be associated with the shader. * @param stage_count The total number of stages. * @param stage_filenames An array of shader stage filenames to be loaded. Should align with stages array. * @param stages A array of shader_stages indicating what render stages (vertex, fragment, etc.) used in this shader. * @return b8 True on success; otherwise false. */ - b8 (*shader_create)(struct shader* shader, u8 renderpass_id, u8 stage_count, const char** stage_filenames, shader_stage* stages); + b8 (*shader_create)(struct shader* shader, renderpass* pass, u8 stage_count, const char** stage_filenames, shader_stage* stages); /** * @brief Destroys the given shader and releases any resources held by it. @@ -296,6 +380,63 @@ typedef struct renderer_backend { */ void (*texture_map_release_resources)(struct texture_map* map); + /** + * @brief Creates a new render target using the provided data. + * + * @param attachment_count The number of attachments (texture pointers). + * @param attachments An array of attachments (texture pointers). + * @param renderpass A pointer to the renderpass the render target is associated with. + * @param width The width of the render target in pixels. + * @param height The height of the render target in pixels. + * @param out_target A pointer to hold the newly created render target. + */ + void (*render_target_create)(u8 attachment_count, texture** attachments, renderpass* pass, u32 width, u32 height, render_target* out_target); + + /** + * @brief Destroys the provided render target. + * + * @param target A pointer to the render target to be destroyed. + * @param free_internal_memory Indicates if internal memory should be freed. + */ + void (*render_target_destroy)(render_target* target, b8 free_internal_memory); + + /** + * @brief Creates a new renderpass. + * + * @param out_renderpass A pointer to the generic renderpass. + * @param depth The depth clear amount. + * @param stencil The stencil clear value. + * @param clear_flags The combined clear flags indicating what kind of clear should take place. + * @param has_prev_pass Indicates if there is a previous renderpass. + * @param has_next_pass Indicates if there is a next renderpass. + */ + void (*renderpass_create)(renderpass* out_renderpass, f32 depth, u32 stencil, b8 has_prev_pass, b8 has_next_pass); + + /** + * @brief Destroys the given renderpass. + * + * @param pass A pointer to the renderpass to be destroyed. + */ + void (*renderpass_destroy)(renderpass* pass); + + /** + * @brief Attempts to get the window render target at the given index. + * + * @param index The index of the attachment to get. Must be within the range of window render target count. + * @return A pointer to a texture attachment if successful; otherwise 0. + */ + texture* (*window_attachment_get)(u8 index); + + /** + * @brief Returns a pointer to the main depth texture target. + */ + texture* (*depth_attachment_get)(); + + /** + * @brief Returns the current window attachment index. + */ + u8 (*window_attachment_index_get)(); + } renderer_backend; typedef struct render_packet { diff --git a/engine/src/renderer/vulkan/vulkan_backend.c b/engine/src/renderer/vulkan/vulkan_backend.c index 8be048ba..289c9e6b 100644 --- a/engine/src/renderer/vulkan/vulkan_backend.c +++ b/engine/src/renderer/vulkan/vulkan_backend.c @@ -4,7 +4,6 @@ #include "vulkan_platform.h" #include "vulkan_device.h" #include "vulkan_swapchain.h" -#include "vulkan_renderpass.h" #include "vulkan_command_buffer.h" #include "vulkan_utils.h" #include "vulkan_buffer.h" @@ -14,7 +13,6 @@ #include "core/logger.h" #include "core/kstring.h" #include "core/kmemory.h" -#include "core/application.h" #include "containers/darray.h" @@ -29,8 +27,6 @@ // static Vulkan context static vulkan_context context; -static u32 cached_framebuffer_width = 0; -static u32 cached_framebuffer_height = 0; VKAPI_ATTR VkBool32 VKAPI_CALL vk_debug_callback( VkDebugUtilsMessageSeverityFlagBitsEXT message_severity, @@ -42,7 +38,6 @@ i32 find_memory_index(u32 type_filter, u32 property_flags); b8 create_buffers(vulkan_context* context); void create_command_buffers(renderer_backend* backend); -void regenerate_framebuffers(); b8 recreate_swapchain(renderer_backend* backend); b8 create_module(vulkan_shader* shader, vulkan_shader_stage_config config, vulkan_shader_stage* shader_stage); @@ -76,23 +71,25 @@ void free_data_range(vulkan_buffer* buffer, u64 offset, u64 size) { } } -b8 vulkan_renderer_backend_initialize(renderer_backend* backend, const char* application_name) { +b8 vulkan_renderer_backend_initialize(renderer_backend* backend, const renderer_backend_config* config, u8* out_window_render_target_count) { // Function pointers context.find_memory_index = find_memory_index; // TODO: custom allocator. context.allocator = 0; - application_get_framebuffer_size(&cached_framebuffer_width, &cached_framebuffer_height); - context.framebuffer_width = (cached_framebuffer_width != 0) ? cached_framebuffer_width : 800; - context.framebuffer_height = (cached_framebuffer_height != 0) ? cached_framebuffer_height : 600; - cached_framebuffer_width = 0; - cached_framebuffer_height = 0; + context.on_rendertarget_refresh_required = config->on_rendertarget_refresh_required; + + // Just set some default values for the framebuffer for now. + // It doesn't really matyer what these are because they will be + // overridden, but are needed for swapchain creation. + context.framebuffer_width = 800; + context.framebuffer_height = 600; // Setup Vulkan instance. VkApplicationInfo app_info = {VK_STRUCTURE_TYPE_APPLICATION_INFO}; app_info.apiVersion = VK_API_VERSION_1_2; - app_info.pApplicationName = application_name; + app_info.pApplicationName = config->application_name; app_info.applicationVersion = VK_MAKE_VERSION(1, 0, 0); app_info.pEngineName = "Kohi Engine"; app_info.engineVersion = VK_MAKE_VERSION(1, 0, 0); @@ -206,30 +203,56 @@ b8 vulkan_renderer_backend_initialize(renderer_backend* backend, const char* app context.framebuffer_height, &context.swapchain); - // World render pass - vulkan_renderpass_create( - &context, - &context.main_renderpass, - (vec4){0, 0, context.framebuffer_width, context.framebuffer_height}, - (vec4){0.0f, 0.0f, 0.2f, 1.0f}, - 1.0f, - 0, - RENDERPASS_CLEAR_COLOUR_BUFFER_FLAG | RENDERPASS_CLEAR_DEPTH_BUFFER_FLAG | RENDERPASS_CLEAR_STENCIL_BUFFER_FLAG, - false, true); - - // UI renderpass - vulkan_renderpass_create( - &context, - &context.ui_renderpass, - (vec4){0, 0, context.framebuffer_width, context.framebuffer_height}, - (vec4){0.0f, 0.0f, 0.0f, 0.0f}, - 1.0f, - 0, - RENDERPASS_CLEAR_NONE_FLAG, - true, false); + // Save off the number of images we have as the number of render targets needed. + *out_window_render_target_count = context.swapchain.image_count; + + // Hold registered renderpasses. + for (u32 i = 0; i < VULKAN_MAX_REGISTERED_RENDERPASSES; ++i) { + context.registered_passes[i].id = INVALID_ID_U16; + } + + // The renderpass table will be a lookup of array indices. Start off every index with an invalid id. + context.renderpass_table_block = kallocate(sizeof(u32) * VULKAN_MAX_REGISTERED_RENDERPASSES, MEMORY_TAG_RENDERER); + hashtable_create(sizeof(u32), VULKAN_MAX_REGISTERED_RENDERPASSES, context.renderpass_table_block, false, &context.renderpass_table); + u32 value = INVALID_ID; + hashtable_fill(&context.renderpass_table, &value); + + // Renderpasses + for (u32 i = 0; i < config->renderpass_count; ++i) { + // TODO: move to a function for reusability. + // Make sure there are no collisions with the name first. + u32 id = INVALID_ID; + hashtable_get(&context.renderpass_table, config->pass_configs[i].name, &id); + if (id != INVALID_ID) { + KERROR("Collision with renderpass named '%s'. Initialization failed.", config->pass_configs[i].name); + return false; + } + // Snip up a new id. + for (u32 j = 0; j < VULKAN_MAX_REGISTERED_RENDERPASSES; ++j) { + if (context.registered_passes[j].id == INVALID_ID_U16) { + // Found one. + context.registered_passes[j].id = j; + id = j; + break; + } + } + + // Verify we got an id + if (id == INVALID_ID) { + KERROR("No space was found for a new renderpass. Increase VULKAN_MAX_REGISTERED_RENDERPASSES. Initialization failed."); + return false; + } + + // Setup the renderpass. + context.registered_passes[id].clear_flags = config->pass_configs[i].clear_flags; + context.registered_passes[id].clear_colour = config->pass_configs[i].clear_colour; + context.registered_passes[id].render_area = config->pass_configs[i].render_area; - // Regenerate swapchain and world framebuffers - regenerate_framebuffers(); + vulkan_renderpass_create(&context.registered_passes[id], 1.0f, 0, config->pass_configs[i].prev_name != 0, config->pass_configs[i].next_name != 0); + + // Update the table with the new id. + hashtable_set(&context.renderpass_table, config->pass_configs[i].name, &id); + } // Create command buffers. create_command_buffers(backend); @@ -314,15 +337,18 @@ void vulkan_renderer_backend_shutdown(renderer_backend* backend) { darray_destroy(context.graphics_command_buffers); context.graphics_command_buffers = 0; - // Destroy framebuffers + // Destroy render targets for (u32 i = 0; i < context.swapchain.image_count; ++i) { - vkDestroyFramebuffer(context.device.logical_device, context.world_framebuffers[i], context.allocator); - vkDestroyFramebuffer(context.device.logical_device, context.swapchain.framebuffers[i], context.allocator); + vulkan_renderer_render_target_destroy(&context.world_render_targets[i], true); + vulkan_renderer_render_target_destroy(&context.swapchain.render_targets[i], true); } // Renderpasses - vulkan_renderpass_destroy(&context, &context.ui_renderpass); - vulkan_renderpass_destroy(&context, &context.main_renderpass); + for (u32 i = 0; i < VULKAN_MAX_REGISTERED_RENDERPASSES; ++i) { + if (context.registered_passes[i].id != INVALID_ID_U16) { + vulkan_renderpass_destroy(&context.registered_passes[i]); + } + } // Swapchain vulkan_swapchain_destroy(&context, &context.swapchain); @@ -352,8 +378,8 @@ void vulkan_renderer_backend_shutdown(renderer_backend* backend) { void vulkan_renderer_backend_on_resized(renderer_backend* backend, u16 width, u16 height) { // Update the "framebuffer size generation", a counter which indicates when the // framebuffer size has been updated. - cached_framebuffer_width = width; - cached_framebuffer_height = height; + context.framebuffer_width = width; + context.framebuffer_height = height; context.framebuffer_size_generation++; KINFO("Vulkan renderer backend->resized: w/h/gen: %i/%i/%llu", width, height, context.framebuffer_size_generation); @@ -435,14 +461,6 @@ b8 vulkan_renderer_backend_begin_frame(renderer_backend* backend, f32 delta_time vkCmdSetViewport(command_buffer->handle, 0, 1, &viewport); vkCmdSetScissor(command_buffer->handle, 0, 1, &scissor); - // Update the main/world renderpass dimensions. - context.main_renderpass.render_area.z = context.framebuffer_width; - context.main_renderpass.render_area.w = context.framebuffer_height; - - // Also update the UI renderpass dimensions. - context.ui_renderpass.render_area.z = context.framebuffer_width; - context.ui_renderpass.render_area.w = context.framebuffer_height; - return true; } @@ -512,51 +530,71 @@ b8 vulkan_renderer_backend_end_frame(renderer_backend* backend, f32 delta_time) return true; } -b8 vulkan_renderer_begin_renderpass(struct renderer_backend* backend, u8 renderpass_id) { - vulkan_renderpass* renderpass = 0; - VkFramebuffer framebuffer = 0; +b8 vulkan_renderer_begin_renderpass(struct renderer_backend* backend, renderpass* pass, render_target* target) { vulkan_command_buffer* command_buffer = &context.graphics_command_buffers[context.image_index]; - // Choose a renderpass based on ID. - switch (renderpass_id) { - case BUILTIN_RENDERPASS_WORLD: - renderpass = &context.main_renderpass; - framebuffer = context.world_framebuffers[context.image_index]; - break; - case BUILTIN_RENDERPASS_UI: - renderpass = &context.ui_renderpass; - framebuffer = context.swapchain.framebuffers[context.image_index]; - break; - default: - KERROR("vulkan_renderer_begin_renderpass called on unrecognized renderpass id: %#02x", renderpass_id); - return false; + // Begin the render pass. + vulkan_renderpass* internal_data = pass->internal_data; + + VkRenderPassBeginInfo begin_info = {VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO}; + begin_info.renderPass = internal_data->handle; + begin_info.framebuffer = target->internal_framebuffer; + begin_info.renderArea.offset.x = pass->render_area.x; + begin_info.renderArea.offset.y = pass->render_area.y; + begin_info.renderArea.extent.width = pass->render_area.z; + begin_info.renderArea.extent.height = pass->render_area.w; + + begin_info.clearValueCount = 0; + begin_info.pClearValues = 0; + + VkClearValue clear_values[2]; + kzero_memory(clear_values, sizeof(VkClearValue) * 2); + b8 do_clear_colour = (pass->clear_flags & RENDERPASS_CLEAR_COLOUR_BUFFER_FLAG) != 0; + if (do_clear_colour) { + kcopy_memory(clear_values[begin_info.clearValueCount].color.float32, pass->clear_colour.elements, sizeof(f32) * 4); + begin_info.clearValueCount++; } - // Begin the render pass. - vulkan_renderpass_begin(command_buffer, renderpass, framebuffer); + b8 do_clear_depth = (pass->clear_flags & RENDERPASS_CLEAR_DEPTH_BUFFER_FLAG) != 0; + if (do_clear_depth) { + kcopy_memory(clear_values[begin_info.clearValueCount].color.float32, pass->clear_colour.elements, sizeof(f32) * 4); + clear_values[begin_info.clearValueCount].depthStencil.depth = internal_data->depth; + + b8 do_clear_stencil = (pass->clear_flags & RENDERPASS_CLEAR_STENCIL_BUFFER_FLAG) != 0; + clear_values[begin_info.clearValueCount].depthStencil.stencil = do_clear_stencil ? internal_data->stencil : 0; + begin_info.clearValueCount++; + } + + begin_info.pClearValues = begin_info.clearValueCount > 0 ? clear_values : 0; + + vkCmdBeginRenderPass(command_buffer->handle, &begin_info, VK_SUBPASS_CONTENTS_INLINE); + command_buffer->state = COMMAND_BUFFER_STATE_IN_RENDER_PASS; return true; } -b8 vulkan_renderer_end_renderpass(struct renderer_backend* backend, u8 renderpass_id) { - vulkan_renderpass* renderpass = 0; +b8 vulkan_renderer_end_renderpass(struct renderer_backend* backend, renderpass* pass) { vulkan_command_buffer* command_buffer = &context.graphics_command_buffers[context.image_index]; + // End the renderpass. + vkCmdEndRenderPass(command_buffer->handle); + command_buffer->state = COMMAND_BUFFER_STATE_RECORDING; + return true; +} - // Choose a renderpass based on ID. - switch (renderpass_id) { - case BUILTIN_RENDERPASS_WORLD: - renderpass = &context.main_renderpass; - break; - case BUILTIN_RENDERPASS_UI: - renderpass = &context.ui_renderpass; - break; - default: - KERROR("vulkan_renderer_end_renderpass called on unrecognized renderpass id: %#02x", renderpass_id); - return false; +renderpass* vulkan_renderer_renderpass_get(const char* name) { + if (!name || name[0] == 0) { + KERROR("vulkan_renderer_renderpass_get requires a name. Nothing will be returned."); + return 0; } - vulkan_renderpass_end(command_buffer, renderpass); - return true; + u32 id = INVALID_ID; + hashtable_get(&context.renderpass_table, name, &id); + if (id == INVALID_ID) { + KWARN("There is no registered renderpass named '%s'.", name); + return 0; + } + + return &context.registered_passes[id]; } VKAPI_ATTR VkBool32 VKAPI_CALL vk_debug_callback( @@ -623,35 +661,6 @@ void create_command_buffers(renderer_backend* backend) { KDEBUG("Vulkan command buffers created."); } -void regenerate_framebuffers() { - u32 image_count = context.swapchain.image_count; - for (u32 i = 0; i < image_count; ++i) { - vulkan_image* image = (vulkan_image*)context.swapchain.render_textures[i]->internal_data; - VkImageView world_attachments[2] = {image->view, context.swapchain.depth_attachment.view}; - VkFramebufferCreateInfo framebuffer_create_info = {VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO}; - framebuffer_create_info.renderPass = context.main_renderpass.handle; - framebuffer_create_info.attachmentCount = 2; - framebuffer_create_info.pAttachments = world_attachments; - framebuffer_create_info.width = context.framebuffer_width; - framebuffer_create_info.height = context.framebuffer_height; - framebuffer_create_info.layers = 1; - - VK_CHECK(vkCreateFramebuffer(context.device.logical_device, &framebuffer_create_info, context.allocator, &context.world_framebuffers[i])); - - // Swapchain framebuffers (UI pass). Outputs to swapchain images - VkImageView ui_attachments[1] = {image->view}; - VkFramebufferCreateInfo sc_framebuffer_create_info = {VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO}; - sc_framebuffer_create_info.renderPass = context.ui_renderpass.handle; - sc_framebuffer_create_info.attachmentCount = 1; - sc_framebuffer_create_info.pAttachments = ui_attachments; - sc_framebuffer_create_info.width = context.framebuffer_width; - sc_framebuffer_create_info.height = context.framebuffer_height; - sc_framebuffer_create_info.layers = 1; - - VK_CHECK(vkCreateFramebuffer(context.device.logical_device, &sc_framebuffer_create_info, context.allocator, &context.swapchain.framebuffers[i])); - } -} - b8 recreate_swapchain(renderer_backend* backend) { // If already being recreated, do not try again. if (context.recreating_swapchain) { @@ -685,18 +694,10 @@ b8 recreate_swapchain(renderer_backend* backend) { vulkan_swapchain_recreate( &context, - cached_framebuffer_width, - cached_framebuffer_height, + context.framebuffer_width, + context.framebuffer_height, &context.swapchain); - // Sync the framebuffer size with the cached sizes. - context.framebuffer_width = cached_framebuffer_width; - context.framebuffer_height = cached_framebuffer_height; - context.main_renderpass.render_area.z = context.framebuffer_width; - context.main_renderpass.render_area.w = context.framebuffer_height; - cached_framebuffer_width = 0; - cached_framebuffer_height = 0; - // Update framebuffer size generation. context.framebuffer_size_last_generation = context.framebuffer_size_generation; @@ -705,27 +706,11 @@ b8 recreate_swapchain(renderer_backend* backend) { vulkan_command_buffer_free(&context, context.device.graphics_command_pool, &context.graphics_command_buffers[i]); } - // Framebuffers. - for (u32 i = 0; i < context.swapchain.image_count; ++i) { - vkDestroyFramebuffer(context.device.logical_device, context.world_framebuffers[i], context.allocator); - vkDestroyFramebuffer(context.device.logical_device, context.swapchain.framebuffers[i], context.allocator); + // Tell the renderer that a refresh is required. + if (context.on_rendertarget_refresh_required) { + context.on_rendertarget_refresh_required(); } - // Update the main/world renderpass dimensions. - context.main_renderpass.render_area.x = 0; - context.main_renderpass.render_area.y = 0; - context.main_renderpass.render_area.z = context.framebuffer_width; - context.main_renderpass.render_area.w = context.framebuffer_height; - - // Also update the UI renderpass dimensions. - context.ui_renderpass.render_area.x = 0; - context.ui_renderpass.render_area.y = 0; - context.ui_renderpass.render_area.z = context.framebuffer_width; - context.ui_renderpass.render_area.w = context.framebuffer_height; - - // Regenerate swapchain and world framebuffers - regenerate_framebuffers(); - create_command_buffers(backend); // Clear the recreating flag. @@ -1078,12 +1063,9 @@ const u32 BINDING_INDEX_UBO = 0; // The index of the image sampler binding. const u32 BINDING_INDEX_SAMPLER = 1; -b8 vulkan_renderer_shader_create(shader* shader, u8 renderpass_id, u8 stage_count, const char** stage_filenames, shader_stage* stages) { +b8 vulkan_renderer_shader_create(shader* shader, renderpass* pass, u8 stage_count, const char** stage_filenames, shader_stage* stages) { shader->internal_data = kallocate(sizeof(vulkan_shader), MEMORY_TAG_RENDERER); - // TODO: dynamic renderpasses - vulkan_renderpass* renderpass = renderpass_id == 1 ? &context.main_renderpass : &context.ui_renderpass; - // Translate stages VkShaderStageFlags vk_stages[VULKAN_SHADER_MAX_STAGES]; for (u8 i = 0; i < stage_count; ++i) { @@ -1115,7 +1097,7 @@ b8 vulkan_renderer_shader_create(shader* shader, u8 renderpass_id, u8 stage_coun // Take a copy of the pointer to the context. vulkan_shader* out_shader = (vulkan_shader*)shader->internal_data; - out_shader->renderpass = renderpass; + out_shader->renderpass = pass->internal_data; // Build out the configuration. out_shader->config.max_descriptor_set_count = max_descriptor_allocate_count; @@ -1830,4 +1812,173 @@ b8 create_module(vulkan_shader* shader, vulkan_shader_stage_config config, vulka shader_stage->shader_stage_create_info.pName = "main"; return true; +} + +void vulkan_renderpass_create(renderpass* out_renderpass, f32 depth, u32 stencil, b8 has_prev_pass, b8 has_next_pass) { + out_renderpass->internal_data = kallocate(sizeof(vulkan_renderpass), MEMORY_TAG_RENDERER); + vulkan_renderpass* internal_data = (vulkan_renderpass*)out_renderpass->internal_data; + internal_data->has_prev_pass = has_prev_pass; + internal_data->has_next_pass = has_next_pass; + + internal_data->depth = depth; + internal_data->stencil = stencil; + + // Main subpass + VkSubpassDescription subpass = {}; + subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; + + // Attachments TODO: make this configurable. + u32 attachment_description_count = 0; + VkAttachmentDescription attachment_descriptions[2]; + + // Color attachment + b8 do_clear_colour = (out_renderpass->clear_flags & RENDERPASS_CLEAR_COLOUR_BUFFER_FLAG) != 0; + VkAttachmentDescription color_attachment; + color_attachment.format = context.swapchain.image_format.format; // TODO: configurable + color_attachment.samples = VK_SAMPLE_COUNT_1_BIT; + color_attachment.loadOp = do_clear_colour ? VK_ATTACHMENT_LOAD_OP_CLEAR : VK_ATTACHMENT_LOAD_OP_LOAD; + color_attachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE; + color_attachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + color_attachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + // If coming from a previous pass, should already be VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL. Otherwise undefined. + color_attachment.initialLayout = has_prev_pass ? VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL : VK_IMAGE_LAYOUT_UNDEFINED; + + // If going to another pass, use VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL. Otherwise VK_IMAGE_LAYOUT_PRESENT_SRC_KHR. + color_attachment.finalLayout = has_next_pass ? VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL : VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; // Transitioned to after the render pass + color_attachment.flags = 0; + + attachment_descriptions[attachment_description_count] = color_attachment; + attachment_description_count++; + + VkAttachmentReference color_attachment_reference; + color_attachment_reference.attachment = 0; // Attachment description array index + color_attachment_reference.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + + subpass.colorAttachmentCount = 1; + subpass.pColorAttachments = &color_attachment_reference; + + // Depth attachment, if there is one + b8 do_clear_depth = (out_renderpass->clear_flags & RENDERPASS_CLEAR_DEPTH_BUFFER_FLAG) != 0; + if (do_clear_depth) { + VkAttachmentDescription depth_attachment = {}; + depth_attachment.format = context.device.depth_format; + depth_attachment.samples = VK_SAMPLE_COUNT_1_BIT; + depth_attachment.loadOp = do_clear_depth ? VK_ATTACHMENT_LOAD_OP_CLEAR : VK_ATTACHMENT_LOAD_OP_LOAD; + depth_attachment.storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + depth_attachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + depth_attachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + depth_attachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + depth_attachment.finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; + + attachment_descriptions[attachment_description_count] = depth_attachment; + attachment_description_count++; + + // Depth attachment reference + VkAttachmentReference depth_attachment_reference; + depth_attachment_reference.attachment = 1; + depth_attachment_reference.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; + + // TODO: other attachment types (input, resolve, preserve) + + // Depth stencil data. + subpass.pDepthStencilAttachment = &depth_attachment_reference; + } else { + kzero_memory(&attachment_descriptions[attachment_description_count], sizeof(VkAttachmentDescription)); + subpass.pDepthStencilAttachment = 0; + } + + // Input from a shader + subpass.inputAttachmentCount = 0; + subpass.pInputAttachments = 0; + + // Attachments used for multisampling colour attachments + subpass.pResolveAttachments = 0; + + // Attachments not used in this subpass, but must be preserved for the next. + subpass.preserveAttachmentCount = 0; + subpass.pPreserveAttachments = 0; + + // Render pass dependencies. TODO: make this configurable. + VkSubpassDependency dependency; + dependency.srcSubpass = VK_SUBPASS_EXTERNAL; + dependency.dstSubpass = 0; + dependency.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + dependency.srcAccessMask = 0; + dependency.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + dependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; + dependency.dependencyFlags = 0; + + // Render pass create. + VkRenderPassCreateInfo render_pass_create_info = {VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO}; + render_pass_create_info.attachmentCount = attachment_description_count; + render_pass_create_info.pAttachments = attachment_descriptions; + render_pass_create_info.subpassCount = 1; + render_pass_create_info.pSubpasses = &subpass; + render_pass_create_info.dependencyCount = 1; + render_pass_create_info.pDependencies = &dependency; + render_pass_create_info.pNext = 0; + render_pass_create_info.flags = 0; + + VK_CHECK(vkCreateRenderPass(context.device.logical_device, &render_pass_create_info, context.allocator, &internal_data->handle)); +} + +void vulkan_renderpass_destroy(renderpass* pass) { + if (pass && pass->internal_data) { + vulkan_renderpass* internal_data = pass->internal_data; + vkDestroyRenderPass(context.device.logical_device, internal_data->handle, context.allocator); + internal_data->handle = 0; + kfree(internal_data, sizeof(vulkan_renderpass), MEMORY_TAG_RENDERER); + pass->internal_data = 0; + } +} + +void vulkan_renderer_render_target_create(u8 attachment_count, texture** attachments, renderpass* pass, u32 width, u32 height, render_target* out_target) { + // Max number of attachments + VkImageView attachment_views[32]; + for (u32 i = 0; i < attachment_count; ++i) { + attachment_views[i] = ((vulkan_image*)attachments[i]->internal_data)->view; + } + + // Take a copy of the attachments and count. + out_target->attachment_count = attachment_count; + if (!out_target->attachments) { + out_target->attachments = kallocate(sizeof(texture*) * attachment_count, MEMORY_TAG_ARRAY); + } + kcopy_memory(out_target->attachments, attachments, sizeof(texture*) * attachment_count); + + VkFramebufferCreateInfo framebuffer_create_info = {VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO}; + framebuffer_create_info.renderPass = ((vulkan_renderpass*)pass->internal_data)->handle; + framebuffer_create_info.attachmentCount = attachment_count; + framebuffer_create_info.pAttachments = attachment_views; + framebuffer_create_info.width = width; + framebuffer_create_info.height = height; + framebuffer_create_info.layers = 1; + + VK_CHECK(vkCreateFramebuffer(context.device.logical_device, &framebuffer_create_info, context.allocator, (VkFramebuffer*)&out_target->internal_framebuffer)); +} +void vulkan_renderer_render_target_destroy(render_target* target, b8 free_internal_memory) { + if (target && target->internal_framebuffer) { + vkDestroyFramebuffer(context.device.logical_device, (VkFramebuffer)target->internal_framebuffer, context.allocator); + target->internal_framebuffer = 0; + if (free_internal_memory) { + kfree(target->attachments, sizeof(texture*) * target->attachment_count, MEMORY_TAG_ARRAY); + target->attachments = 0; + target->attachment_count = 0; + } + } +} + +texture* vulkan_renderer_window_attachment_get(u8 index) { + if (index >= context.swapchain.image_count) { + KFATAL("Attempting to get attachment index out of range: %d. Attachment count: %d", index, context.swapchain.image_count); + return 0; + } + + return context.swapchain.render_textures[index]; +} +texture* vulkan_renderer_depth_attachment_get() { + return context.swapchain.depth_texture; +} +u8 vulkan_renderer_window_attachment_index_get() { + return (u8)context.image_index; } \ No newline at end of file diff --git a/engine/src/renderer/vulkan/vulkan_backend.h b/engine/src/renderer/vulkan/vulkan_backend.h index d86fe436..a69d95fa 100644 --- a/engine/src/renderer/vulkan/vulkan_backend.h +++ b/engine/src/renderer/vulkan/vulkan_backend.h @@ -19,147 +19,25 @@ struct shader; struct shader_uniform; -/** - * @brief Initializes the Vulkan backend. - * - * @param backend A pointer to the generic backend interface. - * @param application_name The name of the application. - * @return True if initialized successfully; otherwise false. - */ -b8 vulkan_renderer_backend_initialize(renderer_backend* backend, const char* application_name); - -/** - * @brief Shuts the Vulkan renderer backend down. - * - * @param backend A pointer to the generic backend interface. - */ +b8 vulkan_renderer_backend_initialize(renderer_backend* backend, const renderer_backend_config* config, u8* out_window_render_target_count); void vulkan_renderer_backend_shutdown(renderer_backend* backend); - -/** - * @brief Handles window resizes. - * - * @param backend A pointer to the generic backend interface. - * @param width The new window width. - * @param height The new window height. - */ void vulkan_renderer_backend_on_resized(renderer_backend* backend, u16 width, u16 height); - -/** - * @brief Performs setup routines required at the start of a frame. - * @note A false result does not necessarily indicate failure. It can also specify that - * the backend is simply not in a state capable of drawing a frame at the moment, and - * that it should be attempted again on the next loop. End frame does not need to (and - * should not) be called if this is the case. - * @param backend A pointer to the generic backend interface. - * @param delta_time The time in seconds since the last frame. - * @return True if successful; otherwise false. - */ b8 vulkan_renderer_backend_begin_frame(renderer_backend* backend, f32 delta_time); - -/** - * @brief Performs routines required to draw a frame, such as presentation. Should only be called - * after a successful return of begin_frame. - * - * @param backend A pointer to the generic backend interface. - * @param delta_time The time in seconds since the last frame. - * @return True on success; otherwise false. - */ b8 vulkan_renderer_backend_end_frame(renderer_backend* backend, f32 delta_time); +b8 vulkan_renderer_begin_renderpass(struct renderer_backend* backend, renderpass* pass, render_target* target); +b8 vulkan_renderer_end_renderpass(struct renderer_backend* backend, renderpass* pass); +renderpass* vulkan_renderer_renderpass_get(const char* name); -/** - * @brief Begins a renderpass with the given id. - * - * @param backend A pointer to the generic backend interface. - * @param renderpass_id The identifier of the renderpass to begin. - * @return True on success; otherwise false. - */ -b8 vulkan_renderer_begin_renderpass(struct renderer_backend* backend, u8 renderpass_id); - -/** - * @brief Ends a renderpass with the given id. - * - * @param backend A pointer to the generic backend interface. - * @param renderpass_id The identifier of the renderpass to end. - * @return True on success; otherwise false. - */ -b8 vulkan_renderer_end_renderpass(struct renderer_backend* backend, u8 renderpass_id); - -/** - * @brief Draws the given geometry. Should only be called inside a renderpass, within a frame. - * - * @param data The render data of the geometry to be drawn. - */ void vulkan_renderer_draw_geometry(geometry_render_data data); - -/** - * @brief Creates a Vulkan-specific texture, acquiring internal resources as needed. - * - * @param pixels The raw image data used for the texture. - * @param texture A pointer to the texture to hold the resources. - */ void vulkan_renderer_texture_create(const u8* pixels, texture* texture); - -/** - * @brief Destroys the given texture, releasing internal resources. - * - * @param texture A pointer to the texture to be destroyed. - */ void vulkan_renderer_texture_destroy(texture* texture); - -/** - * @brief Creates a new writeable texture with no data written to it. - * - * @param t A pointer to the texture to hold the resources. - */ void vulkan_renderer_texture_create_writeable(texture* t); - -/** - * @brief Resizes a texture. There is no check at this level to see if the - * texture is writeable. Internal resources are destroyed and re-created at - * the new resolution. Data is lost and would need to be reloaded. - * - * @param t A pointer to the texture to be resized. - * @param new_width The new width in pixels. - * @param new_height The new height in pixels. - */ void vulkan_renderer_texture_resize(texture* t, u32 new_width, u32 new_height); - -/** - * @brief Writes the given data to the provided texture. - * NOTE: At this level, this can either be a writeable or non-writeable texture because - * this also handles the initial texture load. The texture system itself should be - * responsible for blocking write requests to non-writeable textures. - * - * @param t A pointer to the texture to be written to. - * @param offset The offset in bytes from the beginning of the data to be written. - * @param size The number of bytes to be written. - * @param pixels The raw image data to be written. - */ void vulkan_renderer_texture_write_data(texture* t, u32 offset, u32 size, const u8* pixels); - -/** - * @brief Creates Vulkan-specific internal resources for the given geometry using - * the data provided. - * - * @param geometry A pointer to the geometry to be created. - * @param vertex_size The size of a single vertex. - * @param vertex_count The total number of vertices. - * @param vertices An array of vertices. - * @param index_size The size of an individual index. - * @param index_count The total number of indices. - * @param indices An array of indices. - * @return True on success; otherwise false. - */ b8 vulkan_renderer_create_geometry(geometry* geometry, u32 vertex_size, u32 vertex_count, const void* vertices, u32 index_size, u32 index_count, const void* indices); - -/** - * @brief Destroys the given geometry, releasing internal resources. - * - * @param geometry A pointer to the geometry to be destroyed. - */ void vulkan_renderer_destroy_geometry(geometry* geometry); -b8 vulkan_renderer_shader_create(struct shader* shader, u8 renderpass_id, u8 stage_count, const char** stage_filenames, shader_stage* stages); +b8 vulkan_renderer_shader_create(struct shader* shader, renderpass* pass, u8 stage_count, const char** stage_filenames, shader_stage* stages); void vulkan_renderer_shader_destroy(struct shader* shader); b8 vulkan_renderer_shader_initialize(struct shader* shader); @@ -174,3 +52,13 @@ b8 vulkan_renderer_set_uniform(struct shader* frontend_shader, struct shader_uni b8 vulkan_renderer_texture_map_acquire_resources(texture_map* map); void vulkan_renderer_texture_map_release_resources(texture_map* map); + +void vulkan_renderpass_create(renderpass* out_renderpass, f32 depth, u32 stencil, b8 has_prev_pass, b8 has_next_pass); +void vulkan_renderpass_destroy(renderpass* pass); + +void vulkan_renderer_render_target_create(u8 attachment_count, texture** attachments, renderpass* pass, u32 width, u32 height, render_target* out_target); +void vulkan_renderer_render_target_destroy(render_target* target, b8 free_internal_memory); + +texture* vulkan_renderer_window_attachment_get(u8 index); +texture* vulkan_renderer_depth_attachment_get(); +u8 vulkan_renderer_window_attachment_index_get(); diff --git a/engine/src/renderer/vulkan/vulkan_device.c b/engine/src/renderer/vulkan/vulkan_device.c index 708c962a..f06ac0f4 100644 --- a/engine/src/renderer/vulkan/vulkan_device.c +++ b/engine/src/renderer/vulkan/vulkan_device.c @@ -258,6 +258,11 @@ b8 vulkan_device_detect_depth_format(vulkan_device* device) { VK_FORMAT_D32_SFLOAT_S8_UINT, VK_FORMAT_D24_UNORM_S8_UINT}; + u8 sizes[3] = { + 4, + 4, + 3}; + u32 flags = VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT; for (u64 i = 0; i < candidate_count; ++i) { VkFormatProperties properties; @@ -265,9 +270,11 @@ b8 vulkan_device_detect_depth_format(vulkan_device* device) { if ((properties.linearTilingFeatures & flags) == flags) { device->depth_format = candidates[i]; + device->depth_channel_count = sizes[i]; return true; } else if ((properties.optimalTilingFeatures & flags) == flags) { device->depth_format = candidates[i]; + device->depth_channel_count = sizes[i]; return true; } } diff --git a/engine/src/renderer/vulkan/vulkan_renderpass.c b/engine/src/renderer/vulkan/vulkan_renderpass.c deleted file mode 100644 index 2a947204..00000000 --- a/engine/src/renderer/vulkan/vulkan_renderpass.c +++ /dev/null @@ -1,176 +0,0 @@ -#include "vulkan_renderpass.h" - -#include "core/kmemory.h" - -void vulkan_renderpass_create( - vulkan_context* context, - vulkan_renderpass* out_renderpass, - vec4 render_area, - vec4 clear_colour, - f32 depth, - u32 stencil, - u8 clear_flags, - b8 has_prev_pass, - b8 has_next_pass) { - out_renderpass->clear_flags = clear_flags; - out_renderpass->render_area = render_area; - out_renderpass->clear_colour = clear_colour; - out_renderpass->has_prev_pass = has_prev_pass; - out_renderpass->has_next_pass = has_next_pass; - - out_renderpass->depth = depth; - out_renderpass->stencil = stencil; - - // Main subpass - VkSubpassDescription subpass = {}; - subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; - - // Attachments TODO: make this configurable. - u32 attachment_description_count = 0; - VkAttachmentDescription attachment_descriptions[2]; - - // Color attachment - b8 do_clear_colour = (out_renderpass->clear_flags & RENDERPASS_CLEAR_COLOUR_BUFFER_FLAG) != 0; - VkAttachmentDescription color_attachment; - color_attachment.format = context->swapchain.image_format.format; // TODO: configurable - color_attachment.samples = VK_SAMPLE_COUNT_1_BIT; - color_attachment.loadOp = do_clear_colour ? VK_ATTACHMENT_LOAD_OP_CLEAR : VK_ATTACHMENT_LOAD_OP_LOAD; - color_attachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE; - color_attachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; - color_attachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; - // If coming from a previous pass, should already be VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL. Otherwise undefined. - color_attachment.initialLayout = has_prev_pass ? VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL : VK_IMAGE_LAYOUT_UNDEFINED; - - // If going to another pass, use VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL. Otherwise VK_IMAGE_LAYOUT_PRESENT_SRC_KHR. - color_attachment.finalLayout = has_next_pass ? VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL : VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; // Transitioned to after the render pass - color_attachment.flags = 0; - - attachment_descriptions[attachment_description_count] = color_attachment; - attachment_description_count++; - - VkAttachmentReference color_attachment_reference; - color_attachment_reference.attachment = 0; // Attachment description array index - color_attachment_reference.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; - - subpass.colorAttachmentCount = 1; - subpass.pColorAttachments = &color_attachment_reference; - - // Depth attachment, if there is one - b8 do_clear_depth = (out_renderpass->clear_flags & RENDERPASS_CLEAR_DEPTH_BUFFER_FLAG) != 0; - if (do_clear_depth) { - VkAttachmentDescription depth_attachment = {}; - depth_attachment.format = context->device.depth_format; - depth_attachment.samples = VK_SAMPLE_COUNT_1_BIT; - depth_attachment.loadOp = do_clear_depth ? VK_ATTACHMENT_LOAD_OP_CLEAR : VK_ATTACHMENT_LOAD_OP_LOAD; - depth_attachment.storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; - depth_attachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; - depth_attachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; - depth_attachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; - depth_attachment.finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; - - attachment_descriptions[attachment_description_count] = depth_attachment; - attachment_description_count++; - - // Depth attachment reference - VkAttachmentReference depth_attachment_reference; - depth_attachment_reference.attachment = 1; - depth_attachment_reference.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; - - // TODO: other attachment types (input, resolve, preserve) - - // Depth stencil data. - subpass.pDepthStencilAttachment = &depth_attachment_reference; - } else { - kzero_memory(&attachment_descriptions[attachment_description_count], sizeof(VkAttachmentDescription)); - subpass.pDepthStencilAttachment = 0; - } - - // Input from a shader - subpass.inputAttachmentCount = 0; - subpass.pInputAttachments = 0; - - // Attachments used for multisampling colour attachments - subpass.pResolveAttachments = 0; - - // Attachments not used in this subpass, but must be preserved for the next. - subpass.preserveAttachmentCount = 0; - subpass.pPreserveAttachments = 0; - - // Render pass dependencies. TODO: make this configurable. - VkSubpassDependency dependency; - dependency.srcSubpass = VK_SUBPASS_EXTERNAL; - dependency.dstSubpass = 0; - dependency.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; - dependency.srcAccessMask = 0; - dependency.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; - dependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; - dependency.dependencyFlags = 0; - - // Render pass create. - VkRenderPassCreateInfo render_pass_create_info = {VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO}; - render_pass_create_info.attachmentCount = attachment_description_count; - render_pass_create_info.pAttachments = attachment_descriptions; - render_pass_create_info.subpassCount = 1; - render_pass_create_info.pSubpasses = &subpass; - render_pass_create_info.dependencyCount = 1; - render_pass_create_info.pDependencies = &dependency; - render_pass_create_info.pNext = 0; - render_pass_create_info.flags = 0; - - VK_CHECK(vkCreateRenderPass( - context->device.logical_device, - &render_pass_create_info, - context->allocator, - &out_renderpass->handle)); -} - -void vulkan_renderpass_destroy(vulkan_context* context, vulkan_renderpass* renderpass) { - if (renderpass && renderpass->handle) { - vkDestroyRenderPass(context->device.logical_device, renderpass->handle, context->allocator); - renderpass->handle = 0; - } -} - -void vulkan_renderpass_begin( - vulkan_command_buffer* command_buffer, - vulkan_renderpass* renderpass, - VkFramebuffer frame_buffer) { - VkRenderPassBeginInfo begin_info = {VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO}; - begin_info.renderPass = renderpass->handle; - begin_info.framebuffer = frame_buffer; - begin_info.renderArea.offset.x = renderpass->render_area.x; - begin_info.renderArea.offset.y = renderpass->render_area.y; - begin_info.renderArea.extent.width = renderpass->render_area.z; - begin_info.renderArea.extent.height = renderpass->render_area.w; - - begin_info.clearValueCount = 0; - begin_info.pClearValues = 0; - - VkClearValue clear_values[2]; - kzero_memory(clear_values, sizeof(VkClearValue) * 2); - b8 do_clear_colour = (renderpass->clear_flags & RENDERPASS_CLEAR_COLOUR_BUFFER_FLAG) != 0; - if (do_clear_colour) { - kcopy_memory(clear_values[begin_info.clearValueCount].color.float32, renderpass->clear_colour.elements, sizeof(f32) * 4); - begin_info.clearValueCount++; - } - - b8 do_clear_depth = (renderpass->clear_flags & RENDERPASS_CLEAR_DEPTH_BUFFER_FLAG) != 0; - if (do_clear_depth) { - kcopy_memory(clear_values[begin_info.clearValueCount].color.float32, renderpass->clear_colour.elements, sizeof(f32) * 4); - clear_values[begin_info.clearValueCount].depthStencil.depth = renderpass->depth; - - b8 do_clear_stencil = (renderpass->clear_flags & RENDERPASS_CLEAR_STENCIL_BUFFER_FLAG) != 0; - clear_values[begin_info.clearValueCount].depthStencil.stencil = do_clear_stencil ? renderpass->stencil : 0; - begin_info.clearValueCount++; - } - - begin_info.pClearValues = begin_info.clearValueCount > 0 ? clear_values : 0; - - vkCmdBeginRenderPass(command_buffer->handle, &begin_info, VK_SUBPASS_CONTENTS_INLINE); - command_buffer->state = COMMAND_BUFFER_STATE_IN_RENDER_PASS; -} - -void vulkan_renderpass_end(vulkan_command_buffer* command_buffer, vulkan_renderpass* renderpass) { - vkCmdEndRenderPass(command_buffer->handle); - command_buffer->state = COMMAND_BUFFER_STATE_RECORDING; -} \ No newline at end of file diff --git a/engine/src/renderer/vulkan/vulkan_renderpass.h b/engine/src/renderer/vulkan/vulkan_renderpass.h deleted file mode 100644 index 568f0d7a..00000000 --- a/engine/src/renderer/vulkan/vulkan_renderpass.h +++ /dev/null @@ -1,83 +0,0 @@ -/** - * @file vulkan_renderpass.h - * @author Travis Vroman (travis@kohiengine.com) - * @brief This file contains the implementation of a Vulkan renderpass, which defines - * attachments, potential clearing, and render area for the next drawing commands. - * @version 1.0 - * @date 2022-01-11 - * - * @copyright Kohi Game Engine is Copyright (c) Travis Vroman 2021-2022 - * - */ - -#pragma once - -#include "vulkan_types.inl" - -/** - * @brief The types of clearing to be done on a renderpass. - * Can be combined together for multiple clearing functions. - */ -typedef enum renderpass_clear_flag { - /** @brief No clearing shoudl be done. */ - RENDERPASS_CLEAR_NONE_FLAG = 0x0, - /** @brief Clear the colour buffer. */ - RENDERPASS_CLEAR_COLOUR_BUFFER_FLAG = 0x1, - /** @brief Clear the depth buffer. */ - RENDERPASS_CLEAR_DEPTH_BUFFER_FLAG = 0x2, - /** @brief Clear the stencil buffer. */ - RENDERPASS_CLEAR_STENCIL_BUFFER_FLAG = 0x4 -} renderpass_clear_flag; - -/** - * @brief Creates a new renderpass. - * - * @param context A pointer to the Vulkan context. - * @param out_renderpass A pointer to hold the newly-created renderpass. - * @param render_area A rectangle to represent the render area. x and y are position, - * z is width and w is height. - * @param clear_colour The colour to be used when clearing the colour buffer (RGBA, 0.0-1.0 range). - * @param depth The depth clear amount. - * @param stencil The stencil clear value. - * @param clear_flags The combined clear flags indicating what kind of clear should take place. - * @param has_prev_pass Indicates if there is a previous renderpass. - * @param has_next_pass Indicates if there is a next renderpass. - */ -void vulkan_renderpass_create( - vulkan_context* context, - vulkan_renderpass* out_renderpass, - vec4 render_area, - vec4 clear_colour, - f32 depth, - u32 stencil, - u8 clear_flags, - b8 has_prev_pass, - b8 has_next_pass); - -/** - * @brief Destroys the given renderpass. - * - * @param context A pointer to the Vulkan context. - * @param renderpass A pointer to the renderpass to be destroyed. - */ -void vulkan_renderpass_destroy(vulkan_context* context, vulkan_renderpass* renderpass); - -/** - * @brief Begins the given renderpass. - * - * @param command_buffer A pointer to the command buffer to be used. - * @param renderpass A pointer to the renderpass to begin. - * @param frame_buffer The framebuffer to be used for the being of the renderpass. - */ -void vulkan_renderpass_begin( - vulkan_command_buffer* command_buffer, - vulkan_renderpass* renderpass, - VkFramebuffer frame_buffer); - -/** - * @brief Ends the given renderpass. - * - * @param command_buffer A pointer to the command buffer to be used. - * @param renderpass A pointer to the renderpass to end. - */ -void vulkan_renderpass_end(vulkan_command_buffer* command_buffer, vulkan_renderpass* renderpass); diff --git a/engine/src/renderer/vulkan/vulkan_swapchain.c b/engine/src/renderer/vulkan/vulkan_swapchain.c index 35436df4..3b210262 100644 --- a/engine/src/renderer/vulkan/vulkan_swapchain.c +++ b/engine/src/renderer/vulkan/vulkan_swapchain.c @@ -248,6 +248,7 @@ void create(vulkan_context* context, u32 width, u32 height, vulkan_swapchain* sw } // Create depth image and its view. + vulkan_image* image = kallocate(sizeof(texture), MEMORY_TAG_TEXTURE); vulkan_image_create( context, VK_IMAGE_TYPE_2D, @@ -259,14 +260,27 @@ void create(vulkan_context* context, u32 width, u32 height, vulkan_swapchain* sw VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, true, VK_IMAGE_ASPECT_DEPTH_BIT, - &swapchain->depth_attachment); + image); + + // Wrap it in a texture. + context->swapchain.depth_texture = texture_system_wrap_internal( + "__kohi_default_depth_texture__", + swapchain_extent.width, + swapchain_extent.height, + context->device.depth_channel_count, + false, + true, + false, + image); KINFO("Swapchain created successfully."); } void destroy(vulkan_context* context, vulkan_swapchain* swapchain) { vkDeviceWaitIdle(context->device.logical_device); - vulkan_image_destroy(context, &swapchain->depth_attachment); + vulkan_image_destroy(context, (vulkan_image*)swapchain->depth_texture->internal_data); + kfree(swapchain->depth_texture->internal_data, sizeof(vulkan_image), MEMORY_TAG_TEXTURE); + swapchain->depth_texture->internal_data = 0; // Only destroy the views, not the images, since those are owned by the swapchain and are thus // destroyed when it is. diff --git a/engine/src/renderer/vulkan/vulkan_types.inl b/engine/src/renderer/vulkan/vulkan_types.inl index fb079ca8..7c257c61 100644 --- a/engine/src/renderer/vulkan/vulkan_types.inl +++ b/engine/src/renderer/vulkan/vulkan_types.inl @@ -114,6 +114,8 @@ typedef struct vulkan_device { /** @brief The chosen supported depth format. */ VkFormat depth_format; + /** @brief The chosen depth format's number of channels.*/ + u8 depth_channel_count; } vulkan_device; /** @@ -157,18 +159,12 @@ typedef struct vulkan_renderpass { /** @brief The internal renderpass handle. */ VkRenderPass handle; /** @brief The current render area of the renderpass. */ - vec4 render_area; - /** @brief The clear colour used for this renderpass. */ - vec4 clear_colour; /** @brief The depth clear value. */ f32 depth; /** @brief The stencil clear value. */ u32 stencil; - /** @brief The clear flags for this renderpass. */ - u8 clear_flags; - /** @brief Indicates if there is a previous renderpass. */ b8 has_prev_pass; /** @brief Indicates if there is a next renderpass. */ @@ -197,11 +193,14 @@ typedef struct vulkan_swapchain { /** @brief An array of pointers to render targets, which contain swapchain images. */ texture** render_textures; - /** @brief The depth image attachment. */ - vulkan_image depth_attachment; + /** @brief The depth texture. */ + texture* depth_texture; - /** @brief Framebuffers used for on-screen rendering, one per frame */ - VkFramebuffer framebuffers[3]; + /** + * @brief Render targets used for on-screen rendering, one per frame. + * The images contained in these are created and owned by the swapchain. + * */ + render_target render_targets[3]; } vulkan_swapchain; /** @@ -457,6 +456,8 @@ typedef struct vulkan_shader { } vulkan_shader; +#define VULKAN_MAX_REGISTERED_RENDERPASSES 31 + /** * @brief The overall Vulkan context for the backend. Holds and maintains * global renderer backend state, Vulkan instance, etc. @@ -495,11 +496,11 @@ typedef struct vulkan_context { /** @brief The swapchain. */ vulkan_swapchain swapchain; - /** @brief The main world renderpass. */ - vulkan_renderpass main_renderpass; + void* renderpass_table_block; + hashtable renderpass_table; - /** @brief The UI renderpass. */ - vulkan_renderpass ui_renderpass; + /** @brief Registered renderpasses. */ + renderpass registered_passes[VULKAN_MAX_REGISTERED_RENDERPASSES]; /** @brief The object vertex buffer, used to hold geometry vertices. */ vulkan_buffer object_vertex_buffer; @@ -535,8 +536,8 @@ typedef struct vulkan_context { /** @brief The A collection of loaded geometries. @todo TODO: make dynamic */ vulkan_geometry_data geometries[VULKAN_MAX_GEOMETRY_COUNT]; - /** @brief Framebuffers used for world rendering. @note One per frame. */ - VkFramebuffer world_framebuffers[3]; + /** @brief Render targets used for world rendering. @note One per frame. */ + render_target world_render_targets[3]; /** * @brief A function pointer to find a memory index of the given type and with the given properties. @@ -546,4 +547,10 @@ typedef struct vulkan_context { */ i32 (*find_memory_index)(u32 type_filter, u32 property_flags); + /** + * @brief A pointer to a function to be called when the backend requires + * rendertargets to be refreshed/regenerated. + */ + void (*on_rendertarget_refresh_required)(); + } vulkan_context; diff --git a/engine/src/systems/shader_system.c b/engine/src/systems/shader_system.c index da2177e4..b1f0d042 100644 --- a/engine/src/systems/shader_system.c +++ b/engine/src/systems/shader_system.c @@ -151,13 +151,13 @@ b8 shader_system_create(const shader_config* config) { out_shader->push_constant_stride = 128; out_shader->push_constant_size = 0; - u8 renderpass_id = INVALID_ID_U8; - if (!renderer_renderpass_id(config->renderpass_name, &renderpass_id)) { + renderpass* pass = renderer_renderpass_get(config->renderpass_name); + if (!pass) { KERROR("Unable to find renderpass '%s'", config->renderpass_name); return false; } - if (!renderer_shader_create(out_shader, renderpass_id, config->stage_count, (const char**)config->stage_filenames, config->stages)) { + if (!renderer_shader_create(out_shader, pass, config->stage_count, (const char**)config->stage_filenames, config->stages)) { KERROR("Error creating shader."); return false; } diff --git a/testbed/src/game.c b/testbed/src/game.c index 164880b5..a5180fc4 100644 --- a/testbed/src/game.c +++ b/testbed/src/game.c @@ -43,7 +43,7 @@ b8 game_initialize(game* game_inst) { game_state* state = (game_state*)game_inst->state; - state->camera_position = (vec3){0, 0, 30.0f}; + state->camera_position = (vec3){10.5f, 5.0f, 9.5f}; state->camera_euler = vec3_zero(); state->view = mat4_translation(state->camera_position);