scene/reload: implement run-time texture reload

This commit is contained in:
Zack Buhman 2026-04-18 22:57:19 -05:00
parent 352958f031
commit 9f636548db
11 changed files with 224 additions and 38 deletions

View File

@ -45,6 +45,7 @@ OBJS = \
src/tga/tga.o \
src/vulkan_helper.o \
src/collada/scene/vulkan.o \
src/collada/scene/reload.o \
src/collada/scene.o \
src/collada/node_state.o \
src/collada/animate.o

View File

@ -5,6 +5,7 @@
#include "collada/node_state.h"
#include "collada/scene/vulkan.h"
#include "collada/scene/reload.h"
namespace collada::scene {
struct state {
@ -12,6 +13,7 @@ namespace collada::scene {
node_state::state node_state;
collada::scene::vulkan vulkan;
collada::scene::reload reload;
void load_scene(types::descriptor const * const descriptor);
void draw();

View File

@ -0,0 +1,22 @@
#pragma once
#include <time.h>
#include "collada/types.h"
#include "collada/scene/vulkan.h"
namespace collada::scene {
struct reload_stat {
char * filenameTGA;
struct timespec mtime;
};
struct reload {
reload_stat * imageStats;
void load_images(types::descriptor const * const descriptor);
void stat_images(collada::types::descriptor const * const descriptor,
collada::scene::vulkan & vulkan);
void destroy_images(types::descriptor const * const descriptor);
};
}

View File

@ -148,6 +148,7 @@ namespace collada::scene {
VkImageView shadowDepthImageView);
void change_frame(VkCommandBuffer commandBuffer, uint32_t frameIndex);
void destroy_image(int i);
void destroy_all(collada::types::descriptor const * const descriptor);
//////////////////////////////////////////////////////////////////////
@ -169,6 +170,8 @@ namespace collada::scene {
void create_descriptor_sets(collada::types::descriptor const * const descriptor);
void write_descriptor_sets(collada::types::descriptor const * const descriptor);
void load_material_constants(collada::types::descriptor const * const descriptor);
void load_image_inner(VkCommandBuffer commandBuffer, VkFence fence, int i, char const * filename);
void load_image(int i, char const * filename);
void load_images(collada::types::descriptor const * const descriptor);
//////////////////////////////////////////////////////////////////////

View File

@ -1,5 +1,7 @@
#pragma once
namespace file {
void const * open(const char * r_filename, uint32_t * out_size);
void const * open(char const * filename, uint32_t * out_size);
void * openRelative(char const * filename, uint32_t * out_size);
}

View File

@ -21,6 +21,7 @@ namespace collada::scene {
vulkan.load_images(descriptor);
vulkan.write_descriptor_sets(descriptor);
vulkan.create_pipelines(descriptor);
reload.load_images(descriptor);
node_state.allocate_node_instances(descriptor->nodes, descriptor->nodes_count);
}
@ -71,11 +72,14 @@ namespace collada::scene {
animate::animate_node(node_state.node_instances[i], t);
node_state.update_node_world_transform(node_state.node_instances[i]);
}
reload.stat_images(descriptor, vulkan);
}
void state::unload_scene()
{
node_state.deallocate_node_instances(descriptor->nodes_count);
reload.destroy_images(descriptor);
}
void state::mouse_motion(int eyeIndex, int targetIndex, float xrel, float yrel, int mode)

View File

@ -0,0 +1,69 @@
#include <string.h>
#include <stdio.h>
#include <sys/stat.h>
#include <unistd.h>
#include "new.h"
#include "collada/scene/reload.h"
namespace collada::scene {
void reload::load_images(types::descriptor const * const descriptor)
{
imageStats = NewM<reload_stat>(descriptor->images_count);
for (int i = 0; i < descriptor->images_count; i++) {
char const * filename = descriptor->images[i]->uri;
size_t length = strlen(filename);
imageStats[i].filenameTGA = strndup(filename, length);
imageStats[i].filenameTGA[length - 3] = 't';
imageStats[i].filenameTGA[length - 2] = 'g';
imageStats[i].filenameTGA[length - 1] = 'a';
imageStats[i].mtime.tv_sec = 0;
imageStats[i].mtime.tv_nsec = 0;
}
}
void reload::stat_images(collada::types::descriptor const * const descriptor,
collada::scene::vulkan & vulkan)
{
bool reload = false;
for (int i = 0; i < descriptor->images_count; i++) {
off_t size = 0;
while (true) {
struct stat statbuf;
int ret = stat(imageStats[i].filenameTGA, &statbuf);
if (ret != 0)
break;
if (statbuf.st_mtim.tv_sec != imageStats[i].mtime.tv_sec || statbuf.st_mtim.tv_nsec != imageStats[i].mtime.tv_nsec) {
if (statbuf.st_size != size) {
size = statbuf.st_size;
usleep(500);
continue;
}
fprintf(stderr, "reload %s\n", imageStats[i].filenameTGA);
reload = true;
vulkan.destroy_image(i);
vulkan.load_image(i, imageStats[i].filenameTGA);
imageStats[i].mtime.tv_sec = statbuf.st_mtim.tv_sec;
imageStats[i].mtime.tv_nsec = statbuf.st_mtim.tv_nsec;
}
break;
}
}
if (reload) {
vulkan.write_descriptor_sets(descriptor);
}
}
void reload::destroy_images(types::descriptor const * const descriptor)
{
for (int i = 0; i < descriptor->images_count; i++) {
free(imageStats[i].filenameTGA);
}
free(imageStats);
}
}

View File

@ -2,6 +2,7 @@
#include <stdio.h>
#include <stdint.h>
#include <assert.h>
#include <sys/stat.h>
#include "volk/volk.h"
#include "vulkan/vk_enum_string_helper.h"
@ -79,7 +80,7 @@ inline static void vulkan_vertex_input_states(collada::types::descriptor const *
VkVertexInputBindingDescription * vertexBindingDescriptions)
{
for (int i = 0; i < descriptor->inputs_list_count; i++) {
collada::types::inputs const & inputs = descriptor->inputs_list[i];
collada::types::inputs const & inputs = descriptor->inputs_list[1];
VkVertexInputAttributeDescription * vertexAttributeDescriptions = NewM<VkVertexInputAttributeDescription>(inputs.elements_count + collada::inputs::skin_inputs.elements_count);
uint32_t stride = vulkan_load_layout(inputs,
0, // binding
@ -651,27 +652,8 @@ namespace collada::scene {
// material textures
//////////////////////////////////////////////////////////////////////
void vulkan::load_images(collada::types::descriptor const * const descriptor)
void vulkan::load_image_inner(VkCommandBuffer commandBuffer, VkFence fence, int i, char const * filename)
{
VkCommandBuffer commandBuffer{};
VkCommandBufferAllocateInfo commandBufferAllocateInfo{
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,
.commandPool = commandPool,
.commandBufferCount = 1
};
VK_CHECK(vkAllocateCommandBuffers(device, &commandBufferAllocateInfo, &commandBuffer));
VkFenceCreateInfo fenceCreateInfo{
.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO
};
VkFence fence{};
VK_CHECK(vkCreateFence(device, &fenceCreateInfo, nullptr, &fence));
// images
images = NewM<Image>(descriptor->images_count);
for (int i = 0; i < descriptor->images_count; i++) {
char const * filename = descriptor->images[i]->uri;
size_t length = strlen(filename);
if (dds::isDDSExtension(filename, length)) {
createImageFromFilenameDDS(device,
@ -701,6 +683,59 @@ namespace collada::scene {
}
}
void vulkan::load_image(int i, char const * filename)
{
VkCommandBuffer commandBuffer{};
VkCommandBufferAllocateInfo commandBufferAllocateInfo{
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,
.commandPool = commandPool,
.commandBufferCount = 1
};
VK_CHECK(vkAllocateCommandBuffers(device, &commandBufferAllocateInfo, &commandBuffer));
VkFenceCreateInfo fenceCreateInfo{
.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO
};
VkFence fence{};
VK_CHECK(vkCreateFence(device, &fenceCreateInfo, nullptr, &fence));
// load
load_image_inner(commandBuffer, fence, i, filename);
// cleanup
vkDestroyFence(device, fence, nullptr);
vkFreeCommandBuffers(device,
commandPool,
1,
&commandBuffer);
}
void vulkan::load_images(collada::types::descriptor const * const descriptor)
{
VkCommandBuffer commandBuffer{};
VkCommandBufferAllocateInfo commandBufferAllocateInfo{
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,
.commandPool = commandPool,
.commandBufferCount = 1
};
VK_CHECK(vkAllocateCommandBuffers(device, &commandBufferAllocateInfo, &commandBuffer));
VkFenceCreateInfo fenceCreateInfo{
.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO
};
VkFence fence{};
VK_CHECK(vkCreateFence(device, &fenceCreateInfo, nullptr, &fence));
// images
images = NewM<Image>(descriptor->images_count);
for (int i = 0; i < descriptor->images_count; i++) {
char const * filename = descriptor->images[i]->uri;
load_image_inner(commandBuffer, fence, i, filename);
}
// cleanup
vkDestroyFence(device, fence, nullptr);
@ -1179,13 +1214,18 @@ namespace collada::scene {
0, nullptr);
}
void vulkan::destroy_all(collada::types::descriptor const * const descriptor)
void vulkan::destroy_image(int i)
{
for (int i = 0; i < descriptor->images_count; i++) {
vkDestroyImage(device, images[i].image, nullptr);
vkDestroyImageView(device, images[i].imageView, nullptr);
vkFreeMemory(device, images[i].memory, nullptr);
}
void vulkan::destroy_all(collada::types::descriptor const * const descriptor)
{
for (int i = 0; i < descriptor->images_count; i++) {
destroy_image(i);
}
free(images);
free(shaderData.nodes);

View File

@ -3,6 +3,7 @@
#include <stdlib.h>
#include <stddef.h>
#include <string.h>
#include <errno.h>
#include "pack.h"
#include "file.h"
@ -23,9 +24,9 @@ extern "C" {
namespace file {
void const * open(const char * r_filename, uint32_t * out_size)
void const * open(const char * filename, uint32_t * out_size)
{
fprintf(stderr, "(pack) filename: %s\n", r_filename);
fprintf(stderr, "(pack) filename: %s\n", filename);
pack::header const * header = (pack::header const *)&files_pack_start[0];
if (header->magic != pack::magic_value) {
@ -35,13 +36,53 @@ namespace file {
ptrdiff_t data = (ptrdiff_t)&files_pack_start[header->header_size];
for (unsigned int i = 0; i < header->entry_count; i++) {
if (strcmp(header->entry[i].filename, r_filename) == 0) {
if (strcmp(header->entry[i].filename, filename) == 0) {
*out_size = header->entry[i].size;
return (void const *)(data + header->entry[i].offset);
}
}
fprintf(stderr, "filename not found in pack file %s\n", r_filename);
fprintf(stderr, "filename not found in pack file %s\n", filename);
exit(EXIT_FAILURE);
}
void * openRelative(char const * filename, uint32_t * out_size)
{
FILE * f = fopen(filename, "rb");
if (f == NULL) {
fprintf(stderr, "fopen(%s): %s\n", filename, strerror(errno));
return NULL;
}
int fseek_end_ret = fseek(f, 0, SEEK_END);
if (fseek_end_ret < 0) {
fprintf(stderr, "fseek(%s, SEEK_END): %s\n", filename, strerror(errno));
return NULL;
}
size_t size = ftell(f);
if (size < 0) {
fprintf(stderr, "ftell(%s): %s\n", filename, strerror(errno));
return NULL;
}
int fseek_set_ret = fseek(f, 0, SEEK_SET);
if (fseek_set_ret < 0) {
fprintf(stderr, "lseek(%s, SEEK_SET): %s\n", filename, strerror(errno));
return NULL;
}
rewind(f);
void * buf = malloc(size);
size_t read_size = fread(buf, 1, size, f);
if (read_size != size) {
fprintf(stderr, "fread(%s): %s\n", filename, strerror(errno));
return NULL;
}
*out_size = size;
return buf;
}
}

View File

@ -19,6 +19,7 @@ namespace tga {
size_t imageOffset = (sizeof (header)) + tga->idLength;
*outData = (void *)(((size_t)data) + imageOffset);
*outSize = tga->image.width * tga->image.width * bytesPerPixel;
assert(*outSize <= (size - imageOffset));
return tga;
}

View File

@ -379,7 +379,8 @@ void createImageFromFilenameTGA(VkDevice device,
VkImageView * outImageView)
{
uint32_t imageSize;
void const * imageStart = file::open(filename, &imageSize);
//void const * imageStart = file::open(filename, &imageSize);
void * imageStart = file::openRelative(filename, &imageSize);
void * imageData;
uint32_t imageDataSize;
tga::header const * tga = tga::validate(imageStart, imageSize, &imageData, &imageDataSize);
@ -415,5 +416,5 @@ void createImageFromFilenameTGA(VkDevice device,
levelCount,
&levelOffset);
// imageData is not malloc'ed, it is a pointer to file:: data, which is also not malloc'ed
free(imageStart);
}