diff --git a/client/include/camera.h b/client/include/camera.h index 30c3d7b..36c41fe 100644 --- a/client/include/camera.h +++ b/client/include/camera.h @@ -4,11 +4,12 @@ #include #include "gpu.h" -// Forward declaration only (no #include "ui.h") - camera.h and ui.h are +// Forward declarations only (no #include "ui.h") - camera.h and ui.h are // mutually referential (Container owns a Camera*, Camera targets a // UIContext's texture pool) and neither needs more than a pointer to the // other's type. typedef struct UIContextStruct UIContext; +typedef struct ContainerStruct Container; // GPU mirror of the proj/view a camera pushes to its render target's push // constant; std430 layout must match shader/camera_common.glsl's Camera @@ -38,6 +39,14 @@ typedef struct CameraStruct { mat4 proj; bool has_texture_target; + // When true, texture dimensions are derived from the first display container + // at camera_display() time (and on recreate_framebuffer). Set by + // create_camera; cleared when cam:set_size(w,h) fixes explicit dimensions. + bool texture_dynamic; + // Reserved for future cyclic self-reference support (max render recursion + // depth for cameras that display themselves). Default 1 = no recursion. + uint32_t max_depth; + struct { uint32_t width, height; // Double-buffered per frame-in-flight: two separate vkQueueSubmits @@ -82,4 +91,32 @@ void camera_destroy_texture_target( UIContext* ui, Camera* camera); +// Resizes an existing texture target in-place: replaces the per-frame color +// images and depth images at the SAME texture slot indices, so descriptor +// bindings (and drawable var fields) remain valid without consuming new slots. +// Caller must ensure the GPU is idle (e.g. vkDeviceWaitIdle) before calling. +VkResult camera_recreate_texture_target( + Camera* camera, + RenderContext* gpu, + UIContext* ui, + uint32_t width, + uint32_t height); + +// Attaches camera to container as a background IMAGE drawable. Creates the +// texture target on first call (sizing from container if texture_dynamic). +// Multiple containers can display the same camera - each gets its own drawable. +VkResult camera_display( + Camera* camera, + Container* container, + RenderContext* gpu, + UIContext* ui); + +// Removes the background IMAGE drawable from container and clears its camera +// pointer. The texture target (and the camera itself) are unaffected. +VkResult camera_undisplay( + Camera* camera, + Container* container, + RenderContext* gpu, + UIContext* ui); + #endif diff --git a/client/include/draw.h b/client/include/draw.h index 097a1f6..c915e63 100644 --- a/client/include/draw.h +++ b/client/include/draw.h @@ -9,7 +9,6 @@ VkResult draw_frame( RenderContext* context, UIContext* ui, HexContext* hex, - Camera* offscreen_camera, double time); #endif diff --git a/client/include/engine.h b/client/include/engine.h index 5f16f14..dca394b 100644 --- a/client/include/engine.h +++ b/client/include/engine.h @@ -54,12 +54,6 @@ struct ClientContextStruct { EventBus events; Camera camera; - // Nullable, non-owning. Set by the app to render a second camera's scene - // into an offscreen texture each frame (see camera_init_texture_target, - // camera.h) - NULL (the zero-init default) skips that pass entirely, so - // apps that don't opt in see no behavior change. - Camera* offscreen_camera; - void* app_data; app_frame_function app_frame; app_text_callback app_text; diff --git a/client/include/ui.h b/client/include/ui.h index b6f4a0b..9b6fda9 100644 --- a/client/include/ui.h +++ b/client/include/ui.h @@ -182,10 +182,13 @@ struct ContainerStruct { uint32_t id; - // Non-owning, nullable. When set, this container's region (anchor/offset/ - // size) and slot in container_order double as the camera's viewport and - // draw order - see container_set_camera/container_screen_rect. + // Non-owning, nullable. When set, this container's slot in container_order + // determines the camera's render order - see camera_display (camera.h). Camera* camera; + // Drawable slot index of the camera's background IMAGE element, or + // UINT32_MAX when no camera is displayed. Managed by camera_display / + // camera_undisplay - do not set directly. + uint32_t camera_background_drawable; // script_env: which script's dispatch handlers (on_button etc.) run for // this container's input, 0 = no script. Set by ui.create_overlay, not by @@ -311,6 +314,17 @@ VkResult create_render_target_texture( uint32_t height, uint32_t* index); +// Replaces the image/view/sampler for an existing render-target slot with new +// ones at the given dimensions, then updates the descriptor set in-place. +// The slot index (and therefore any drawable var referencing it) is unchanged. +// Caller must vkDeviceWaitIdle or otherwise guarantee the slot is not in use. +VkResult recreate_render_target_texture( + RenderContext* gpu, + UIContext* context, + uint32_t index, + uint32_t width, + uint32_t height); + VkResult load_container( ContainerInput* container, RenderContext* gpu, diff --git a/client/script/editor_topdown.lua b/client/script/editor_topdown.lua index 8d6dffd..8c901a2 100644 --- a/client/script/editor_topdown.lua +++ b/client/script/editor_topdown.lua @@ -6,7 +6,8 @@ local overlay = ui.create_overlay{anchor = ANCHOR_TOP_RIGHT, offset = {-10, 10}, size = {250, 250}} local top_down = camera.create() -top_down:attach(overlay) +top_down:set_size(250, 250) +top_down:display(overlay) top_down:set_position(0, 0, 0) -- Just shy of straight down (pi/2) - exactly vertical is singular for -- glm_lookat against the {0,1,0} up vector (same bound editor.c's own pitch diff --git a/client/src/camera.c b/client/src/camera.c index 9e8fb94..9c98efa 100644 --- a/client/src/camera.c +++ b/client/src/camera.c @@ -3,11 +3,14 @@ #include #include +#include static vec3 up = {0, 1, 0}; VkResult create_camera(RenderContext* gpu, Camera* camera) { VkResult result; + camera->texture_dynamic = true; + camera->max_depth = 1; for(uint32_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { VK_RESULT(create_storage_buffer( gpu->allocator, @@ -89,6 +92,97 @@ void camera_destroy_texture_target(RenderContext* gpu, UIContext* ui, Camera* ca for(uint32_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { vkDestroyImageView(gpu->device, camera->texture.depth_image_view[i], NULL); vmaDestroyImage(gpu->allocator, camera->texture.depth_image[i], camera->texture.depth_image_memory[i]); + // TODO: free camera->texture.texture_slot[i] from the bindless descriptor + // pool once destroy_render_target_texture is implemented. For now the + // color slot leaks on destroy - only called from destroy_camera (shutdown). } camera->has_texture_target = false; } + +VkResult camera_recreate_texture_target( + Camera* camera, + RenderContext* gpu, + UIContext* ui, + uint32_t width, + uint32_t height) { + VkResult result; + VkExtent2D extent = {width, height}; + for(uint32_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + VK_RESULT(recreate_render_target_texture(gpu, ui, camera->texture.texture_slot[i], width, height)); + vkDestroyImageView(gpu->device, camera->texture.depth_image_view[i], NULL); + vmaDestroyImage(gpu->allocator, camera->texture.depth_image[i], camera->texture.depth_image_memory[i]); + VK_RESULT(create_depth_image( + gpu->device, gpu->depth_format, extent, gpu->allocator, + gpu->extra_graphics_pool, gpu->graphics_queue, + &camera->texture.depth_image[i], + &camera->texture.depth_image_memory[i], + &camera->texture.depth_image_view[i])); + } + camera->texture.width = width; + camera->texture.height = height; + return VK_SUCCESS; +} + +VkResult camera_display(Camera* camera, Container* container, RenderContext* gpu, UIContext* ui) { + VkResult result; + + container_set_camera(container, camera); + + if(!camera->has_texture_target) { + uint32_t w, h; + if(camera->texture_dynamic) { + VkRect2D rect; + container_screen_rect(gpu, container, &rect); + w = rect.extent.width > 0 ? rect.extent.width : 1; + h = rect.extent.height > 0 ? rect.extent.height : 1; + } else { + w = camera->texture.width > 0 ? camera->texture.width : 1; + h = camera->texture.height > 0 ? camera->texture.height : 1; + } + VK_RESULT(camera_init_texture_target(gpu, ui, camera, w, h)); + } + + GPUDrawable bg = { + .pos = {0.0f, 0.0f}, + .size = {container->data.size[0], container->data.size[1]}, + .color = { + {1.0f, 1.0f, 1.0f, 1.0f}, + {1.0f, 1.0f, 1.0f, 1.0f}, + {1.0f, 1.0f, 1.0f, 1.0f}, + {1.0f, 1.0f, 1.0f, 1.0f}, + }, + .type = DRAWABLE_TYPE_IMAGE, + .var = camera->texture.texture_slot[0], + .events = 0, + .z = -1e30f, + }; + + uint32_t bg_slot; + VK_RESULT(ui_create_drawable(container, &bg, gpu, &bg_slot)); + + // ui_create_drawable → add_transfers uploads var=texture_slot[0] to both + // frames. Override frame 1's var in-place (the coalesce in add_transfer + // rewrites the staging data without adding a new entry). + VK_RESULT(add_transfer( + &camera->texture.texture_slot[1], + container->drawables[1], + sizeof(GPUDrawable) * bg_slot + offsetof(GPUDrawable, var), + sizeof(uint32_t), + 1, + gpu)); + + container->camera_background_drawable = bg_slot; + return VK_SUCCESS; +} + +VkResult camera_undisplay(Camera* camera, Container* container, RenderContext* gpu, UIContext* ui) { + (void)camera; + (void)ui; + VkResult result = VK_SUCCESS; + if(container->camera_background_drawable != UINT32_MAX) { + result = ui_destroy_drawable(container, container->camera_background_drawable, gpu); + container->camera_background_drawable = UINT32_MAX; + } + container_set_camera(container, NULL); + return result; +} diff --git a/client/src/draw.c b/client/src/draw.c index 92aeda3..9dd1abc 100644 --- a/client/src/draw.c +++ b/client/src/draw.c @@ -1,5 +1,6 @@ #include "draw.h" #include "hex.h" +#include "camera.h" #include "vulkan/vulkan_core.h" void record_hex_draw(VkCommandBuffer command_buffer, HexContext* hex, VkDeviceAddress camera_address, double time, uint32_t frame) { @@ -32,22 +33,17 @@ static void bind_ui_pipeline(VkCommandBuffer command_buffer, UIContext* ui) { vkCmdBindDescriptorSets(command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, ui->pipeline.layout, 3, 1, &ui->textures, 0, NULL); } -// Single pass over container_order (back to front, same z-order UI has -// always used): a container with a camera attached renders that camera's -// hex scene scissored to its own region first, then every container -// (camera-attached or not) draws its normal 2D drawables via the UI -// pipeline on top - see draw_frame's single unified rendering scope, which -// is what makes interleaving these two pipelines per-container possible -// without a barrier between them. -void record_container_draw( +// UI-only pass over container_order (back to front). Camera scenes are +// rendered to offscreen textures in draw_frame before this is called; each +// camera-attached container samples its texture via a background IMAGE +// drawable created by camera_display (camera.c). +static void record_container_draw( VkCommandBuffer command_buffer, RenderContext* gpu, UIContext* ui, - HexContext* hex, - VkViewport full_viewport, - VkRect2D full_scissor, double time, uint32_t frame) { + (void)gpu; UIPushConstant push = { .time = (float)time, }; @@ -56,31 +52,6 @@ void record_container_draw( for(uint32_t o = 0; o < ui->container_order_count; o++) { Container* c = &ui->containers[ui->container_order[o]]; - - if(c->camera != NULL) { - VkRect2D rect; - container_screen_rect(gpu, c, &rect); - VkViewport camera_viewport = { - .x = (float)rect.offset.x, - .y = (float)rect.offset.y, - .width = (float)rect.extent.width, - .height = (float)rect.extent.height, - .minDepth = 0.0f, - .maxDepth = 1.0f, - }; - vkCmdSetViewport(command_buffer, 0, 1, &camera_viewport); - vkCmdSetScissor(command_buffer, 0, 1, &rect); - - record_hex_draw(command_buffer, hex, c->camera->gpu_address[frame], time, frame); - - // record_hex_draw left a hex pipeline bound (a different bind point - // state) and the viewport/scissor scoped to this container - restore - // both before this (or the next) container's UI drawables. - vkCmdSetViewport(command_buffer, 0, 1, &full_viewport); - vkCmdSetScissor(command_buffer, 0, 1, &full_scissor); - bind_ui_pipeline(command_buffer, ui); - } - push.container = c->address[frame]; vkCmdPushConstants(command_buffer, ui->pipeline.layout, VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, 0, 16, &push); vkCmdDrawIndirect(command_buffer, c->container[frame], offsetof(GPUContainer, draw), 1, 0); @@ -107,7 +78,6 @@ VkResult draw_frame( RenderContext* context, UIContext* ui, HexContext* hex, - Camera* offscreen_camera, double time) { VkResult result; @@ -223,29 +193,39 @@ VkResult draw_frame( .layerCount = 1, }; - // Targets a different image entirely (not the swapchain), so this can run - // any time after the command buffer begins - independent of the acquire - // below. The read barrier at the end is the producer-before-consumer sync - // that makes the texture safe for the unified scene pass (or anything - // else) to sample later this same frame. - if(offscreen_camera != NULL) { + // Render each camera's hex scene to its offscreen texture before the + // main scene pass. Iterate container_order to discover cameras; skip + // duplicates (same camera referenced by multiple containers). + // Cameras are independent so this order carries no semantic dependency. + for(uint32_t o = 0; o < ui->container_order_count; o++) { + Container* c = &ui->containers[ui->container_order[o]]; + Camera* cam = c->camera; + if(cam == NULL || !cam->has_texture_target) continue; + + bool already_rendered = false; + for(uint32_t p = 0; p < o; p++) { + if(ui->containers[ui->container_order[p]].camera == cam) { + already_rendered = true; + break; + } + } + if(already_rendered) continue; + uint32_t f = context->current_frame; - Texture* target = &ui->texture_slots[offscreen_camera->texture.texture_slot[f]]; + Texture* target = &ui->texture_slots[cam->texture.texture_slot[f]]; - VkViewport offscreen_viewport = { - .width = (float)offscreen_camera->texture.width, - .height = (float)offscreen_camera->texture.height, + VkExtent2D cam_extent = {cam->texture.width, cam->texture.height}; + VkViewport cam_viewport = { + .width = (float)cam_extent.width, + .height = (float)cam_extent.height, .maxDepth = 1.0f, .minDepth = 0.0f, }; - vkCmdSetViewport(command_buffer, 0, 1, &offscreen_viewport); - - VkRect2D offscreen_scissor = { - .extent = {offscreen_camera->texture.width, offscreen_camera->texture.height}, - }; - vkCmdSetScissor(command_buffer, 0, 1, &offscreen_scissor); + vkCmdSetViewport(command_buffer, 0, 1, &cam_viewport); + VkRect2D cam_scissor = {.extent = cam_extent}; + vkCmdSetScissor(command_buffer, 0, 1, &cam_scissor); - VkImageMemoryBarrier offscreen_acquire_barrier = { + VkImageMemoryBarrier cam_acquire_barrier = { .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, .srcAccessMask = 0, .dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, @@ -259,9 +239,9 @@ VkResult draw_frame( vkCmdPipelineBarrier(command_buffer, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, - 0, 0, NULL, 0, NULL, 1, &offscreen_acquire_barrier); + 0, 0, NULL, 0, NULL, 1, &cam_acquire_barrier); - VkRenderingAttachmentInfo offscreen_color_attachment = { + VkRenderingAttachmentInfo cam_color = { .sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO, .imageView = target->view, .imageLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, @@ -269,27 +249,27 @@ VkResult draw_frame( .storeOp = VK_ATTACHMENT_STORE_OP_STORE, .clearValue = {.color = {{0.0f, 0.0f, 0.0f, 0.0f}}}, }; - VkRenderingAttachmentInfo offscreen_depth_attachment = { + VkRenderingAttachmentInfo cam_depth = { .sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO, - .imageView = offscreen_camera->texture.depth_image_view[f], + .imageView = cam->texture.depth_image_view[f], .imageLayout = VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL, .loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR, .storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE, .clearValue = {.depthStencil = {1.0f, 0}}, }; - VkRenderingInfo offscreen_rendering = { + VkRenderingInfo cam_rendering = { .sType = VK_STRUCTURE_TYPE_RENDERING_INFO, - .renderArea = {{0, 0}, offscreen_scissor.extent}, + .renderArea = {{0, 0}, cam_extent}, .layerCount = 1, .colorAttachmentCount = 1, - .pColorAttachments = &offscreen_color_attachment, - .pDepthAttachment = &offscreen_depth_attachment, + .pColorAttachments = &cam_color, + .pDepthAttachment = &cam_depth, }; - vkCmdBeginRendering(command_buffer, &offscreen_rendering); - record_hex_draw(command_buffer, hex, offscreen_camera->gpu_address[f], time, f); + vkCmdBeginRendering(command_buffer, &cam_rendering); + record_hex_draw(command_buffer, hex, cam->gpu_address[f], time, f); vkCmdEndRendering(command_buffer); - VkImageMemoryBarrier offscreen_read_barrier = { + VkImageMemoryBarrier cam_read_barrier = { .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, .srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, .dstAccessMask = VK_ACCESS_SHADER_READ_BIT, @@ -303,7 +283,7 @@ VkResult draw_frame( vkCmdPipelineBarrier(command_buffer, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, - 0, 0, NULL, 0, NULL, 1, &offscreen_read_barrier); + 0, 0, NULL, 0, NULL, 1, &cam_read_barrier); } VkViewport viewport = { @@ -360,7 +340,7 @@ VkResult draw_frame( .pDepthAttachment = &depth_attachment, }; vkCmdBeginRendering(command_buffer, &scene_rendering); - record_container_draw(command_buffer, context, ui, hex, viewport, scissor, time, context->current_frame); + record_container_draw(command_buffer, context, ui, time, context->current_frame); vkCmdEndRendering(command_buffer); VkImageMemoryBarrier present_barrier = { diff --git a/client/src/editor.c b/client/src/editor.c index 9f783ed..4dc14e4 100644 --- a/client/src/editor.c +++ b/client/src/editor.c @@ -731,7 +731,7 @@ void editor_startup(ClientContext* context) { }; load_container(&main_view, &context->render, &context->ui); data->main_container = context_container(main_view.id, &context->ui); - container_set_camera(data->main_container, &context->camera); + camera_display(&context->camera, data->main_container, &context->render, &context->ui); // The editor sets the camera's initial values and computes its first // view/projection here; editor_frame_callback only recomputes them when diff --git a/client/src/editor_lua.c b/client/src/editor_lua.c index 53325eb..bf2fa0d 100644 --- a/client/src/editor_lua.c +++ b/client/src/editor_lua.c @@ -135,23 +135,78 @@ static int lua_camera_gc(lua_State* L) { return 0; } -// h:attach(overlay) - the overlay's region/z-order becomes this camera's -// viewport and draw order (see container_set_camera, ui.h) -static int lua_camera_attach(lua_State* L) { +// h:display(overlay) - renders this camera's scene into overlay as a +// background IMAGE drawable. Creates the texture target on first call. +static int lua_camera_display(lua_State* L) { CameraLuaHandle* h = check_camera(L, 1); Container* c = ui_lua_check_overlay(L, 2); - container_set_camera(c, h->camera); + ClientContext* context = current_context(L); + if(camera_display(h->camera, c, &context->render, &context->ui) != VK_SUCCESS) { + return luaL_error(L, "camera:display: failed to initialise texture target"); + } return 0; } -// h:detach(overlay) -static int lua_camera_detach(lua_State* L) { - check_camera(L, 1); +// h:undisplay(overlay) - removes the background drawable and clears the +// camera pointer from overlay. The texture target is preserved. +static int lua_camera_undisplay(lua_State* L) { + CameraLuaHandle* h = check_camera(L, 1); Container* c = ui_lua_check_overlay(L, 2); - container_set_camera(c, NULL); + ClientContext* context = current_context(L); + camera_undisplay(h->camera, c, &context->render, &context->ui); + return 0; +} + +// h:set_size(width, height) - fix the texture target size. Pass 0,0 to +// restore dynamic sizing (derives from the first display container). +static int lua_camera_set_size(lua_State* L) { + CameraLuaHandle* h = check_camera(L, 1); + uint32_t w = (uint32_t)luaL_checkinteger(L, 2); + uint32_t h_val = (uint32_t)luaL_checkinteger(L, 3); + h->camera->texture.width = w; + h->camera->texture.height = h_val; + h->camera->texture_dynamic = (w == 0 && h_val == 0); + return 0; +} + +// h:init_texture_target(width, height) - explicit texture allocation before +// display; useful when resolution control is needed before attaching. +static int lua_camera_init_texture_target(lua_State* L) { + CameraLuaHandle* h = check_camera(L, 1); + uint32_t w = (uint32_t)luaL_checkinteger(L, 2); + uint32_t h_val = (uint32_t)luaL_checkinteger(L, 3); + ClientContext* context = current_context(L); + if(camera_init_texture_target(&context->render, &context->ui, h->camera, w, h_val) != VK_SUCCESS) { + return luaL_error(L, "camera:init_texture_target: failed"); + } return 0; } +// h:get_texture_slot() -> current frame's texture slot index +static int lua_camera_get_texture_slot(lua_State* L) { + CameraLuaHandle* h = check_camera(L, 1); + if(!h->camera->has_texture_target) { + return luaL_error(L, "camera:get_texture_slot: no texture target"); + } + ClientContext* context = current_context(L); + lua_pushinteger(L, (lua_Integer)h->camera->texture.texture_slot[context->render.current_frame]); + return 1; +} + +// h:set_max_depth(n) / h:get_max_depth() -> n +// Reserved for future cyclic self-reference; currently capped at 1. +static int lua_camera_set_max_depth(lua_State* L) { + CameraLuaHandle* h = check_camera(L, 1); + h->camera->max_depth = (uint32_t)luaL_checkinteger(L, 2); + return 0; +} + +static int lua_camera_get_max_depth(lua_State* L) { + CameraLuaHandle* h = check_camera(L, 1); + lua_pushinteger(L, (lua_Integer)h->camera->max_depth); + return 1; +} + // h:set_position(x, y, z) static int lua_camera_set_position(lua_State* L) { CameraLuaHandle* h = check_camera(L, 1); @@ -256,16 +311,21 @@ void editor_lua_register(lua_State* L, ClientContext* context) { lua_setglobal(L, "camera"); static const luaL_Reg camera_methods[] = { - {"destroy", lua_camera_destroy}, - {"attach", lua_camera_attach}, - {"detach", lua_camera_detach}, - {"set_position", lua_camera_set_position}, - {"get_position", lua_camera_get_position}, - {"set_rotation", lua_camera_set_rotation}, - {"get_rotation", lua_camera_get_rotation}, - {"set_distance", lua_camera_set_distance}, - {"get_distance", lua_camera_get_distance}, - {"set_aspect", lua_camera_set_aspect}, + {"destroy", lua_camera_destroy}, + {"display", lua_camera_display}, + {"undisplay", lua_camera_undisplay}, + {"set_size", lua_camera_set_size}, + {"init_texture_target", lua_camera_init_texture_target}, + {"get_texture_slot", lua_camera_get_texture_slot}, + {"set_max_depth", lua_camera_set_max_depth}, + {"get_max_depth", lua_camera_get_max_depth}, + {"set_position", lua_camera_set_position}, + {"get_position", lua_camera_get_position}, + {"set_rotation", lua_camera_set_rotation}, + {"get_rotation", lua_camera_get_rotation}, + {"set_distance", lua_camera_set_distance}, + {"get_distance", lua_camera_get_distance}, + {"set_aspect", lua_camera_set_aspect}, {NULL, NULL}, }; luaL_newmetatable(L, CAMERA_LUA_META); diff --git a/client/src/engine.c b/client/src/engine.c index 94f47ff..c505e71 100644 --- a/client/src/engine.c +++ b/client/src/engine.c @@ -2,6 +2,8 @@ #include "ui_lua.h" #include "gpu.h" #include "draw.h" +#include "camera.h" +#include #include void framebuffer_size_callback(GLFWwindow* window, int width, int height) { @@ -211,9 +213,51 @@ int app_main(ClientContext* context) { if(context->render.framebuffer_recreated == true) { context->render.framebuffer_recreated = false; VK_RESULT(update_ui_context_resolution(&context->ui, &context->render)); + + // Recreate dynamic camera texture targets to match new container sizes. + // Color texture slots from the previous target are leaked (TODO: implement + // destroy_render_target_texture and free them here). + for(uint32_t o = 0; o < context->ui.container_order_count; o++) { + Container* c = &context->ui.containers[context->ui.container_order[o]]; + Camera* cam = c->camera; + if(cam == NULL || !cam->has_texture_target || !cam->texture_dynamic) continue; + + bool already_done = false; + for(uint32_t p = 0; p < o; p++) { + if(context->ui.containers[context->ui.container_order[p]].camera == cam) { + already_done = true; + break; + } + } + if(!already_done) { + VkRect2D rect; + container_screen_rect(&context->render, c, &rect); + uint32_t w = rect.extent.width > 0 ? rect.extent.width : 1; + uint32_t h = rect.extent.height > 0 ? rect.extent.height : 1; + + // Recreate in-place: same texture slot indices, new image dimensions. + // Slot indices (and therefore drawable var fields) are unchanged. + VK_RESULT(camera_recreate_texture_target(cam, &context->render, &context->ui, w, h)); + } + + // Update background drawable size to match the (possibly new) container + // size. Must happen for every container displaying this camera, not + // just the first one (multiple containers can share one camera). + uint32_t bg = c->camera_background_drawable; + if(bg != UINT32_MAX) { + c->drawables_buffer[bg].size[0] = c->data.size[0]; + c->drawables_buffer[bg].size[1] = c->data.size[1]; + VK_RESULT(add_transfers( + &c->drawables_buffer[bg].size, + c->drawables, + sizeof(GPUDrawable)*bg + offsetof(GPUDrawable, size), + sizeof(vec2), + &context->render)); + } + } } - VkResult result = draw_frame(&context->render, &context->ui, &context->hex, context->offscreen_camera, frame_time); + VkResult result = draw_frame(&context->render, &context->ui, &context->hex, frame_time); if(result != VK_SUCCESS) { fprintf(stderr, "draw_frame error: %s\n", string_VkResult(result)); glfwDestroyWindow(context->window); diff --git a/client/src/hex.c b/client/src/hex.c index 817230c..ea48853 100644 --- a/client/src/hex.c +++ b/client/src/hex.c @@ -150,7 +150,7 @@ VkResult create_point_pipeline( .srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA, .dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA, .colorBlendOp = VK_BLEND_OP_ADD, - .srcAlphaBlendFactor = VK_BLEND_FACTOR_ZERO, + .srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE, .dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO, .alphaBlendOp = VK_BLEND_OP_ADD, }; @@ -319,7 +319,7 @@ VkResult create_ray_pipeline( .srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA, .dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA, .colorBlendOp = VK_BLEND_OP_ADD, - .srcAlphaBlendFactor = VK_BLEND_FACTOR_ZERO, + .srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE, .dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO, .alphaBlendOp = VK_BLEND_OP_ADD, }; @@ -488,7 +488,7 @@ VkResult create_hex_highlight_pipeline( .srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA, .dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA, .colorBlendOp = VK_BLEND_OP_ADD, - .srcAlphaBlendFactor = VK_BLEND_FACTOR_ZERO, + .srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE, .dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO, .alphaBlendOp = VK_BLEND_OP_ADD, }; @@ -657,7 +657,7 @@ VkResult create_hex_pipeline( .srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA, .dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA, .colorBlendOp = VK_BLEND_OP_ADD, - .srcAlphaBlendFactor = VK_BLEND_FACTOR_ZERO, + .srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE, .dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO, .alphaBlendOp = VK_BLEND_OP_ADD, }; diff --git a/client/src/ui.c b/client/src/ui.c index efb5e25..6993f87 100644 --- a/client/src/ui.c +++ b/client/src/ui.c @@ -800,6 +800,7 @@ VkResult load_container( } c->id = input->id; + c->camera_background_drawable = UINT32_MAX; c->script_env = 0; c->overlay_ref = LUA_NOREF; context->container_order[context->container_order_count] = index; @@ -1128,6 +1129,83 @@ VkResult create_render_target_texture( return VK_SUCCESS; } +VkResult recreate_render_target_texture( + RenderContext* gpu, + UIContext* context, + uint32_t index, + uint32_t width, + uint32_t height) { + VkResult result; + Texture* slot = &context->texture_slots[index]; + + vkDestroySampler(gpu->device, slot->sampler, NULL); + vkDestroyImageView(gpu->device, slot->view, NULL); + vmaDestroyImage(gpu->allocator, slot->image, slot->image_memory); + + VkImageCreateInfo image_info = { + .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, + .usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, + .extent = {.width = width, .height = height, .depth = 1}, + .mipLevels = 1, + .arrayLayers = 1, + .format = gpu->swapchain_format.format, + .tiling = VK_IMAGE_TILING_OPTIMAL, + .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED, + .samples = VK_SAMPLE_COUNT_1_BIT, + .imageType = VK_IMAGE_TYPE_2D, + }; + VmaAllocationCreateInfo memory_info = {.usage = VMA_MEMORY_USAGE_GPU_ONLY}; + VK_RESULT(vmaCreateImage(gpu->allocator, &image_info, &memory_info, &slot->image, &slot->image_memory, NULL)); + + VkImageViewCreateInfo view_info = { + .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, + .image = slot->image, + .viewType = VK_IMAGE_VIEW_TYPE_2D, + .format = gpu->swapchain_format.format, + .subresourceRange = {.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, .layerCount = 1, .levelCount = 1}, + }; + VK_RESULT(vkCreateImageView(gpu->device, &view_info, NULL, &slot->view)); + + VkSamplerCreateInfo sampler_info = { + .sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO, + .magFilter = VK_FILTER_LINEAR, + .minFilter = VK_FILTER_LINEAR, + .addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, + .addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, + .addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, + }; + VK_RESULT(vkCreateSampler(gpu->device, &sampler_info, NULL, &slot->sampler)); + + VkDescriptorImageInfo desc_sampler_info = {.sampler = slot->sampler}; + VkDescriptorImageInfo desc_image_info = { + .imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, + .imageView = slot->view, + }; + VkWriteDescriptorSet desc_writes[] = { + { + .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, + .dstSet = context->textures, + .dstBinding = 0, + .dstArrayElement = index, + .descriptorType = VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, + .descriptorCount = 1, + .pImageInfo = &desc_image_info, + }, + { + .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, + .dstSet = context->samplers, + .dstBinding = 0, + .dstArrayElement = index, + .descriptorType = VK_DESCRIPTOR_TYPE_SAMPLER, + .descriptorCount = 1, + .pImageInfo = &desc_sampler_info, + }, + }; + vkUpdateDescriptorSets(gpu->device, sizeof(desc_writes)/sizeof(VkWriteDescriptorSet), desc_writes, 0, NULL); + + return VK_SUCCESS; +} + VkResult load_font( uint32_t index, const char* ttf_file,