fixed oversight in uf::Mesh::convert, fixed texture streaming not working in OpenGL (the graphic.material.textures weren't updated) fixed missing texture being used in VXGI for vulkan (think it was from reinitializing the texture object itself if it was aliased rather than just resetting the handles)

This commit is contained in:
ecker 2025-08-14 23:32:08 -05:00
parent 9ec35a26e6
commit ab9aa0ae13
6 changed files with 136 additions and 100 deletions

1
.gitignore vendored
View File

@ -34,6 +34,7 @@
*.app
*.spv
*.elf
*.elf.unstripped
# Logs
*.log

View File

@ -78,7 +78,7 @@
"stream": {
"tag": "worldspawn",
"player": "info_player_spawn",
"enabled": "auto",
"enabled": true, // "auto",
"radius": 32,
"every": 4
}

View File

@ -1 +1 @@
vulkan
opengl

View File

@ -158,7 +158,10 @@ namespace uf {
} bounds;
*/
uf::stl::vector<buffer_t> buffers;
uf::stl::vector<uf::stl::string> buffer_paths; // crunge, but it's better this way
// crunge, but it's better this way
uf::stl::vector<uf::stl::string> buffer_paths;
uf::stl::vector<ext::RENDERER::AttributeDescriptor> buffer_descriptors;
protected:
void _destroy( uf::Mesh::Input& input );
void _bind( bool interleaved = uf::Mesh::defaultInterleaved );
@ -300,32 +303,42 @@ namespace uf {
template<typename From, typename To>
void convert() {
// if mesh data is interleaved, skip conversion
if ( this->isInterleaved() ) {
UF_MSG_DEBUG("Downcasting/upcasting requested yet mesh is interleaved, ignoring...");
return;
}
auto fromEnum = uf::renderer::typeToEnum<From>();
auto toEnum = uf::renderer::typeToEnum<To>();
auto toEnum = uf::renderer::typeToEnum<To>();
if ( toEnum == fromEnum ) return;
for ( auto& attribute : this->vertex.attributes ) {
if ( attribute.descriptor.type == toEnum ) continue;
if ( attribute.descriptor.type != fromEnum ) continue;
float scale = (float) sizeof(To) / (float) sizeof(From);
size_t elements = this->vertex.count * attribute.descriptor.components;
size_t bytes = elements * sizeof(To);
size_t elementCount = this->vertex.count * attribute.descriptor.components;
auto& srcBuffer = this->buffers[attribute.buffer];
if ( srcBuffer.empty() ) continue;
uf::stl::vector<uint8_t> dstBuffer( srcBuffer.size() * scale );
uf::stl::vector<uint8_t> dstBuffer( elementCount * sizeof(To) );
attribute.pointer = (uint8_t*) dstBuffer.data();
attribute.length *= scale;
const From* srcPtr = (const From*) (srcBuffer.data());
To* dstPtr = (To*) (dstBuffer.data());
if ( toEnum == uf::renderer::enums::Type::USHORT )
for ( size_t i = 0; i < elementCount; ++i ) dstPtr[i] = uf::quant::quantize_f32u16(srcPtr[i]);
else if ( fromEnum == uf::renderer::enums::Type::USHORT )
for ( size_t i = 0; i < elementCount; ++i) dstPtr[i] = uf::quant::dequantize_u16f32(srcPtr[i]);
else
for ( size_t i = 0; i < elementCount; ++i ) dstPtr[i] = (To) srcPtr[i];
srcBuffer.swap( dstBuffer );
attribute.pointer = (uint8_t*) ( srcBuffer.data() );
attribute.descriptor.type = toEnum;
attribute.descriptor.size *= scale;
attribute.descriptor.size = sizeof(To) * attribute.descriptor.components;
attribute.length = sizeof(To) * elementCount;
if ( toEnum == uf::renderer::enums::Type::FLOAT ) {
switch ( attribute.descriptor.components ) {
@ -334,14 +347,16 @@ namespace uf {
case 3: attribute.descriptor.format = uf::renderer::enums::Format::R32G32B32_SFLOAT; break;
case 4: attribute.descriptor.format = uf::renderer::enums::Format::R32G32B32A32_SFLOAT; break;
}
} else if ( toEnum == uf::renderer::enums::Type::FLOAT16 ) {
}
else if ( toEnum == uf::renderer::enums::Type::FLOAT16 ) {
switch ( attribute.descriptor.components ) {
case 1: attribute.descriptor.format = uf::renderer::enums::Format::R16_SFLOAT; break;
case 2: attribute.descriptor.format = uf::renderer::enums::Format::R16G16_SFLOAT; break;
case 3: attribute.descriptor.format = uf::renderer::enums::Format::R16G16B16_SFLOAT; break;
case 4: attribute.descriptor.format = uf::renderer::enums::Format::R16G16B16A16_SFLOAT; break;
}
} else if ( toEnum == uf::renderer::enums::Type::USHORT ) {
}
else if ( toEnum == uf::renderer::enums::Type::USHORT ) {
switch ( attribute.descriptor.components ) {
case 1: attribute.descriptor.format = uf::renderer::enums::Format::R16_UINT; break;
case 2: attribute.descriptor.format = uf::renderer::enums::Format::R16G16_UINT; break;
@ -349,20 +364,6 @@ namespace uf {
case 4: attribute.descriptor.format = uf::renderer::enums::Format::R16G16B16A16_UINT; break;
}
}
From* srcPtr = (From*) (srcBuffer.data());
To* dstPtr = (To*) (dstBuffer.data());
if ( toEnum == uf::renderer::enums::Type::USHORT ) {
for ( size_t i = 0; i < elements; ++i ) dstPtr[i] = uf::quant::quantize_f32u16(srcPtr[i]);
} else if ( fromEnum == uf::renderer::enums::Type::USHORT ) {
for ( size_t i = 0; i < elements; ++i ) dstPtr[i] = uf::quant::dequantize_u16f32(srcPtr[i]);
} else {
for ( size_t i = 0; i < elements; ++i ) dstPtr[i] = srcPtr[i];
}
srcBuffer.swap( dstBuffer );
}
}
};
@ -421,10 +422,10 @@ namespace ext {
}
namespace std {
template <>
struct hash<ext::RENDERER::GraphicDescriptor> {
size_t operator()(const ext::RENDERER::GraphicDescriptor& descriptor) const { return descriptor.hash(); }
};
template <>
struct hash<ext::RENDERER::GraphicDescriptor> {
size_t operator()(const ext::RENDERER::GraphicDescriptor& descriptor) const { return descriptor.hash(); }
};
}
#undef UF_RENDERER

View File

@ -231,6 +231,7 @@ namespace {
attribute.offset = value["offset"].as(attribute.offset);\
attribute.stride = value["stride"].as(attribute.stride);\
attribute.length = value["length"].as(attribute.length);\
mesh.buffer_descriptors.emplace_back(attribute.descriptor);\
});\
}
@ -240,6 +241,8 @@ namespace {
DESERIALIZE_MESH(indirect);
mesh.buffers.reserve( json["buffers"].size() );
mesh.buffer_paths.reserve( json["buffers"].size() );
mesh.buffer_descriptors.reserve( json["buffers"].size() );
ext::json::forEach( json["buffers"], [&]( ext::json::Value& value ){
const uf::stl::string filename = value.as<uf::stl::string>();
const uf::stl::string directory = uf::io::directory( graph.name );
@ -298,7 +301,6 @@ namespace {
{
#if UF_ENV_DREAMCAST && GL_QUANTIZED_SHORT
mesh.convert<float, uint16_t>();
UF_MSG_DEBUG("Quantizing mesh to GL_QUANTIZED_SHORT");
#else
auto conversion = graph.metadata["decode"]["conversion"].as<uf::stl::string>();
if ( conversion != "" ) {
@ -314,8 +316,9 @@ namespace {
else if ( conversion == "float" ) mesh.convert<uint16_t, float>();
}
#endif
mesh.updateDescriptor();
}
mesh.updateDescriptor();
return mesh;
}

View File

@ -25,7 +25,7 @@
#if UF_USE_OPENGL
#define UF_GRAPH_SPARSE_READ_MESH 1
#else
#define UF_GRAPH_SPARSE_READ_MESH 0
#define UF_GRAPH_SPARSE_READ_MESH 1
#endif
#endif
@ -44,6 +44,40 @@ namespace {
return hash;
}
void bindTextures( uf::renderer::Graphic& graphic ) {
graphic.material.textures.clear();
auto& scene = uf::scene::getCurrentScene();
auto& storage = uf::graph::globalStorage ? uf::graph::storage : scene.getComponent<pod::Graph::Storage>();
// for ( auto& s : graph.samplers ) graphic.material.samplers.emplace_back( storage.samplers.map[s] );
// for ( auto pair : storage.samplers.map ) graphic.material.samplers.emplace_back( pair.second );
// for ( auto& key : storage.samplers.keys ) graphic.material.samplers.emplace_back( storage.samplers.map[key] );
// for ( auto& i : graph.images ) graphic.material.textures.emplace_back().aliasTexture( storage.texture2Ds.map[i] );
// for ( auto pair : storage.texture2Ds.map ) graphic.material.textures.emplace_back().aliasTexture( pair.second );
for ( auto& key : storage.texture2Ds.keys ) graphic.material.textures.emplace_back().aliasTexture( storage.texture2Ds.map[key] );
// bind scene's voxel texture
#if UF_USE_VULKAN
if ( uf::renderer::settings::pipelines::vxgi ) {
auto& scene = uf::scene::getCurrentScene();
auto& sceneTextures = scene.getComponent<pod::SceneTextures>();
for ( auto& t : sceneTextures.voxels.drawId ) graphic.material.textures.emplace_back().aliasTexture(t);
for ( auto& t : sceneTextures.voxels.instanceId ) graphic.material.textures.emplace_back().aliasTexture(t);
for ( auto& t : sceneTextures.voxels.normalX ) graphic.material.textures.emplace_back().aliasTexture(t);
for ( auto& t : sceneTextures.voxels.normalY ) graphic.material.textures.emplace_back().aliasTexture(t);
for ( auto& t : sceneTextures.voxels.radianceR ) graphic.material.textures.emplace_back().aliasTexture(t);
for ( auto& t : sceneTextures.voxels.radianceG ) graphic.material.textures.emplace_back().aliasTexture(t);
for ( auto& t : sceneTextures.voxels.radianceB ) graphic.material.textures.emplace_back().aliasTexture(t);
for ( auto& t : sceneTextures.voxels.radianceA ) graphic.material.textures.emplace_back().aliasTexture(t);
for ( auto& t : sceneTextures.voxels.count ) graphic.material.textures.emplace_back().aliasTexture(t);
for ( auto& t : sceneTextures.voxels.output ) graphic.material.textures.emplace_back().aliasTexture(t);
}
#endif
}
// lazy load animations if requested
void loadAnimation( const uf::stl::string& name ) {
auto& scene = uf::scene::getCurrentScene();
@ -344,34 +378,7 @@ void uf::graph::initializeGraphics( pod::Graph& graph, uf::Object& entity, uf::M
#endif
*/
{
// for ( auto& s : graph.samplers ) graphic.material.samplers.emplace_back( storage.samplers.map[s] );
// for ( auto pair : storage.samplers.map ) graphic.material.samplers.emplace_back( pair.second );
// for ( auto& key : storage.samplers.keys ) graphic.material.samplers.emplace_back( storage.samplers.map[key] );
// for ( auto& i : graph.images ) graphic.material.textures.emplace_back().aliasTexture( storage.texture2Ds.map[i] );
// for ( auto pair : storage.texture2Ds.map ) graphic.material.textures.emplace_back().aliasTexture( pair.second );
for ( auto& key : storage.texture2Ds.keys ) graphic.material.textures.emplace_back().aliasTexture( storage.texture2Ds.map[key] );
// bind scene's voxel texture
#if UF_USE_VULKAN
if ( uf::renderer::settings::pipelines::vxgi ) {
auto& scene = uf::scene::getCurrentScene();
auto& sceneTextures = scene.getComponent<pod::SceneTextures>();
for ( auto& t : sceneTextures.voxels.drawId ) graphic.material.textures.emplace_back().aliasTexture(t);
for ( auto& t : sceneTextures.voxels.instanceId ) graphic.material.textures.emplace_back().aliasTexture(t);
for ( auto& t : sceneTextures.voxels.normalX ) graphic.material.textures.emplace_back().aliasTexture(t);
for ( auto& t : sceneTextures.voxels.normalY ) graphic.material.textures.emplace_back().aliasTexture(t);
for ( auto& t : sceneTextures.voxels.radianceR ) graphic.material.textures.emplace_back().aliasTexture(t);
for ( auto& t : sceneTextures.voxels.radianceG ) graphic.material.textures.emplace_back().aliasTexture(t);
for ( auto& t : sceneTextures.voxels.radianceB ) graphic.material.textures.emplace_back().aliasTexture(t);
for ( auto& t : sceneTextures.voxels.radianceA ) graphic.material.textures.emplace_back().aliasTexture(t);
for ( auto& t : sceneTextures.voxels.count ) graphic.material.textures.emplace_back().aliasTexture(t);
for ( auto& t : sceneTextures.voxels.output ) graphic.material.textures.emplace_back().aliasTexture(t);
}
#endif
}
::bindTextures( graphic );
uf::stl::string root = uf::io::directory( graph.name );
size_t texture2Ds = 0;
@ -2166,21 +2173,18 @@ void uf::graph::reload( pod::Graph& graph, pod::Node& node ) {
mesh.index.count += drawCommand.indices;
}
// load mesh data
for ( auto& attribute : mesh.index.attributes ) {
if ( ranges.count(attribute.buffer) <= 0 ) {
mesh.buffers[attribute.buffer].clear();
} else {
mesh.buffers[attribute.buffer] = uf::io::readAsBuffer( mesh.buffer_paths[attribute.buffer], ranges[attribute.buffer] );
#define STREAM_MESH_DATA( N ) \
for ( auto& attribute : mesh.N.attributes ) {\
if ( ranges.count(attribute.buffer) <= 0 ) { \
mesh.buffers[attribute.buffer].clear();\
} else {\
attribute.descriptor = mesh.buffer_descriptors[attribute.buffer];\
mesh.buffers[attribute.buffer] = uf::io::readAsBuffer( mesh.buffer_paths[attribute.buffer], ranges[attribute.buffer] );\
}\
}
}
for ( auto& attribute : mesh.vertex.attributes ) {
if ( ranges.count(attribute.buffer) <= 0 ) {
mesh.buffers[attribute.buffer].clear();
} else {
mesh.buffers[attribute.buffer] = uf::io::readAsBuffer( mesh.buffer_paths[attribute.buffer], ranges[attribute.buffer] );
}
}
STREAM_MESH_DATA( index );
STREAM_MESH_DATA( vertex );
// keep the vertex data intact
#else
// disable remaining draw commands
@ -2197,13 +2201,15 @@ void uf::graph::reload( pod::Graph& graph, pod::Node& node ) {
}
}
// load mesh data
for ( auto& attribute : mesh.index.attributes ) {
mesh.buffers[attribute.buffer] = uf::io::readAsBuffer( mesh.buffer_paths[attribute.buffer] );
}
for ( auto& attribute : mesh.vertex.attributes ) {
mesh.buffers[attribute.buffer] = uf::io::readAsBuffer( mesh.buffer_paths[attribute.buffer] );
}
#define STREAM_MESH_DATA( N ) \
for ( auto& attribute : mesh.N.attributes ) {\
if ( !mesh.buffers[attribute.buffer].empty() || mesh.buffer_paths.empty() ) continue;\
attribute.descriptor = mesh.buffer_descriptors[attribute.buffer];\
mesh.buffers[attribute.buffer] = uf::io::readAsBuffer( mesh.buffer_paths[attribute.buffer] );\
}
STREAM_MESH_DATA( index );
STREAM_MESH_DATA( vertex );
#endif
if ( graph.settings.stream.textures ) {
@ -2239,13 +2245,11 @@ void uf::graph::reload( pod::Graph& graph, pod::Node& node ) {
}
// iterate through our ref counts
// to-do: figure out why this doesn't work for OpenGL (texture ID handles might be wrong, might be better to store the old texture ID handle to use it and then update to that handle)
for ( auto& [ key, count ] : textureReferences ) {
auto& texture = storage.texture2Ds[key];
auto& image = storage.images[key];
bool visible = count > 0;
// load texture
if ( visible && (!texture.generated() || texture.aliased) ) {
// load image
if ( image.getPixels().empty() ) image.open(image.getFilename(), false);
@ -2264,7 +2268,13 @@ void uf::graph::reload( pod::Graph& graph, pod::Node& node ) {
// avoids manipulating the aliased texture
if ( texture.aliased ) {
texture = {};
texture.aliased = false;
#if UF_USE_OPENGL
texture.image = 0;
#else
texture.image = {};
texture.view = {};
#endif
}
texture.sampler.descriptor.filter.min = filter;
@ -2275,7 +2285,7 @@ void uf::graph::reload( pod::Graph& graph, pod::Node& node ) {
#if UF_ENV_DREAMCAST
image.clear();
#endif
} else if ( !visible && texture.generated() ) {
} else if ( !visible && (texture.generated() && !texture.aliased) ) {
// unload image
image.clear();
// defer destruction of texture
@ -2286,20 +2296,39 @@ void uf::graph::reload( pod::Graph& graph, pod::Node& node ) {
}
}
} else { // this shouldn't be reached
// load mesh data
for ( auto& attribute : mesh.index.attributes ) {
if ( !mesh.buffers[attribute.buffer].empty() || mesh.buffer_paths.empty() ) continue;
mesh.buffers[attribute.buffer] = uf::io::readAsBuffer( mesh.buffer_paths[attribute.buffer] );
}
for ( auto& attribute : mesh.vertex.attributes ) {
if ( !mesh.buffers[attribute.buffer].empty() || mesh.buffer_paths.empty() ) continue;
mesh.buffers[attribute.buffer] = uf::io::readAsBuffer( mesh.buffer_paths[attribute.buffer] );
#define LOAD_MESH_DATA( N ) \
for ( auto& attribute : mesh.N.attributes ) {\
if ( !mesh.buffers[attribute.buffer].empty() || mesh.buffer_paths.empty() ) continue;\
mesh.buffers[attribute.buffer] = uf::io::readAsBuffer( mesh.buffer_paths[attribute.buffer] );\
}
LOAD_MESH_DATA( index );
LOAD_MESH_DATA( vertex );
}
{
#if UF_ENV_DREAMCAST && GL_QUANTIZED_SHORT
mesh.convert<float, uint16_t>();
#else
auto conversion = graph.metadata["decode"]["conversion"].as<uf::stl::string>();
if ( conversion != "" ) {
#if UF_USE_FLOAT16
if ( conversion == "float16" ) mesh.convert<float, float16>();
else if ( conversion == "float" ) mesh.convert<float16, float>();
#endif
#if UF_USE_BFLOAT16
if ( conversion == "bfloat16" ) mesh.convert<float, bfloat16>();
else if ( conversion == "float" ) mesh.convert<bfloat16, float>();
#endif
if ( conversion == "uint16_t" ) mesh.convert<float, uint16_t>();
else if ( conversion == "float" ) mesh.convert<uint16_t, float>();
}
#endif
}
mesh.updateDescriptor();
// may or may not be necessary (OpenGL might need a re-record, Vulkan seems fine for the main deferred pass but VXGI doesn't ever get to update since null textures get used sometimes)
uf::renderer::states::rebuild = true;
// uf::renderer::states::rebuild = true;
// update graphic
if ( /*(graph.metadata["renderer"]["separate"].as<bool>()) &&*/ graph.metadata["renderer"]["render"].as<bool>() ) {
@ -2310,11 +2339,13 @@ void uf::graph::reload( pod::Graph& graph, pod::Node& node ) {
if ( rebuild ) {
// uf::renderer::states::rebuild = true;
}
::bindTextures( graphic );
} else {
uf::graph::initializeGraphics( graph, entity, mesh );
}
}
// bind mesh to physics state
// to-do: figure out why the mesh just suddenly breaks when re-streamed in dreamcast (could just be the version of reactphysics)
{
auto phyziks = tag["physics"];
if ( !ext::json::isObject( phyziks ) ) phyziks = metadataJson["physics"];