Made cameras need a container to draw to, and render cameras seperately

main
noah metz 2026-08-01 19:23:51 -06:00
parent 446361f9f1
commit 9016fda882
12 changed files with 404 additions and 103 deletions

@ -4,11 +4,12 @@
#include <cglm/types.h>
#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

@ -9,7 +9,6 @@ VkResult draw_frame(
RenderContext* context,
UIContext* ui,
HexContext* hex,
Camera* offscreen_camera,
double time);
#endif

@ -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;

@ -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,

@ -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

@ -3,11 +3,14 @@
#include <cglm/cam.h>
#include <math.h>
#include <stddef.h>
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;
}

@ -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 = {

@ -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

@ -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);
@ -257,8 +312,13 @@ void editor_lua_register(lua_State* L, ClientContext* context) {
static const luaL_Reg camera_methods[] = {
{"destroy", lua_camera_destroy},
{"attach", lua_camera_attach},
{"detach", lua_camera_detach},
{"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},

@ -2,6 +2,8 @@
#include "ui_lua.h"
#include "gpu.h"
#include "draw.h"
#include "camera.h"
#include <stddef.h>
#include <stdlib.h>
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);

@ -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,
};

@ -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,