fixes for streaming in meshes/textures (sped up scene loading because I was thrashing I/O with re-walking a gunzip file constantly the moment i swapped to storing all mesh data inside one file......), some other things I don't remember since it was a week ago
This commit is contained in:
parent
ad360a351d
commit
2a02e08383
@ -105,8 +105,8 @@ struct Bounds {
|
||||
|
||||
struct LOD {
|
||||
uint indices;
|
||||
uint vertexID;
|
||||
uint indexID;
|
||||
uint vertexID;
|
||||
uint vertices;
|
||||
};
|
||||
|
||||
|
||||
@ -26,7 +26,7 @@ layout (binding = 0) uniform Camera {
|
||||
Viewport viewport[6];
|
||||
} camera;
|
||||
|
||||
layout (std140, binding = 1) readonly buffer DrawCommands {
|
||||
layout (std430, binding = 1) readonly buffer DrawCommands {
|
||||
DrawCommand drawCommands[];
|
||||
};
|
||||
layout (std140, binding = 2) readonly buffer Instances {
|
||||
|
||||
@ -10,7 +10,7 @@ layout (local_size_x = 64, local_size_y = 1, local_size_z = 1) in;
|
||||
#define COMPUTE 1
|
||||
#define QUERY_MIPMAPS 1
|
||||
#define DEPTH_BIAS 0.00005
|
||||
#define FRUSTUM_CULLING 1
|
||||
#define FRUSTUM_CULLING 0
|
||||
#define OCCLUSION_CULLING 0 // currently whack
|
||||
#define LODS 1
|
||||
#define MAX_LODS 4
|
||||
@ -111,6 +111,8 @@ void main() {
|
||||
|
||||
const DrawCommand drawCommand = drawCommands[gID];
|
||||
if ( drawCommand.indices == 0 || drawCommand.vertices == 0 ) return;
|
||||
// if ( drawCommand.instances > 1 ) return;
|
||||
|
||||
|
||||
const Instance instance = instances[drawCommand.instanceID];
|
||||
const Object object = objects[instance.objectID];
|
||||
@ -181,6 +183,9 @@ void main() {
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#if FRUSTUM_CULLING || OCCLUSION_CULLING
|
||||
drawCommands[gID].instances = isVisible ? 1 : 0;
|
||||
#endif
|
||||
#if LODS
|
||||
if ( isVisible ) {
|
||||
vec3 viewCenter = (camera.viewport[0].view * vec4(worldCenter, 1.0)).xyz;
|
||||
@ -189,6 +194,7 @@ void main() {
|
||||
float projectedSize = (worldRadius * P11) / max(dist, 0.001);
|
||||
|
||||
uint lodLevel = 0;
|
||||
/*
|
||||
if ( projectedSize < 0.20 ) lodLevel = 1;
|
||||
if ( projectedSize < 0.08 ) lodLevel = 2;
|
||||
if ( projectedSize < 0.02 ) lodLevel = 3;
|
||||
@ -196,8 +202,9 @@ void main() {
|
||||
while ( lodLevel > 0 && lodMetadata[drawCommand.instanceID].levels[lodLevel].indices == 0 ) {
|
||||
lodLevel--;
|
||||
}
|
||||
*/
|
||||
|
||||
LOD lod = lodMetadata[drawCommand.instanceID].levels[lodLevel];
|
||||
LOD lod = lodMetadata[gID].levels[lodLevel];
|
||||
|
||||
if ( lod.indices > 0 ) {
|
||||
drawCommands[gID].indices = lod.indices;
|
||||
@ -207,5 +214,4 @@ void main() {
|
||||
}
|
||||
}
|
||||
#endif
|
||||
drawCommands[gID].instances = isVisible ? 1 : 0;
|
||||
}
|
||||
@ -21,7 +21,7 @@ uvec4 uvec2_16x4( uvec2 i ) {
|
||||
|
||||
layout (push_constant) uniform SkinningPush {
|
||||
uint jointID;
|
||||
uint vertexOffset;
|
||||
uint triangleCount;
|
||||
} push;
|
||||
|
||||
layout (std140, binding = 0) readonly buffer Joints {
|
||||
@ -45,18 +45,18 @@ layout (binding = 4) buffer VertexOutputPosition {
|
||||
void main() {
|
||||
const uint i = gl_GlobalInvocationID.x;
|
||||
|
||||
if ( i * 3 >= verticesInPos.length() || i * 3 >= verticesOutPos.length() ) return;
|
||||
if ( i >= push.triangleCount || i >= push.triangleCount ) return;
|
||||
|
||||
const vec3 inPos = vec3( verticesInPos[i * 3 + 0], verticesInPos[i * 3 + 1], verticesInPos[i * 3 + 2] );
|
||||
const uvec4 inJoints = uvec2_16x4(verticesInJoints[i]);
|
||||
const vec4 inWeights = verticesInWeights[i];
|
||||
|
||||
const mat4 skinned = inWeights.x * joints[push.jointID + int(inJoints.x)]
|
||||
+ inWeights.y * joints[push.jointID + int(inJoints.y)]
|
||||
+ inWeights.z * joints[push.jointID + int(inJoints.z)]
|
||||
+ inWeights.w * joints[push.jointID + int(inJoints.w)];
|
||||
vec4 inPos4 = vec4(inPos, 1.0);
|
||||
vec3 outPos = (joints[push.jointID + int(inJoints.x)] * inPos4).xyz * inWeights.x
|
||||
+ (joints[push.jointID + int(inJoints.y)] * inPos4).xyz * inWeights.y
|
||||
+ (joints[push.jointID + int(inJoints.z)] * inPos4).xyz * inWeights.z
|
||||
+ (joints[push.jointID + int(inJoints.w)] * inPos4).xyz * inWeights.w;
|
||||
|
||||
const vec3 outPos = vec3(skinned * vec4(inPos, 1));
|
||||
verticesOutPos[i * 3 + 0] = outPos[0];
|
||||
verticesOutPos[i * 3 + 1] = outPos[1];
|
||||
verticesOutPos[i * 3 + 2] = outPos[2];
|
||||
|
||||
@ -10,6 +10,7 @@
|
||||
namespace ext {
|
||||
namespace meshopt {
|
||||
bool UF_API optimize( uf::Mesh&, float simplify = 1.0f, size_t = SIZE_MAX, bool verbose = false );
|
||||
bool UF_API simplify( uf::Mesh&, float simplify = 1.0f );
|
||||
|
||||
uf::stl::vector<float> computeLODs( size_t count, size_t maxLODs = 4, size_t minIndices = 3 );
|
||||
uf::stl::vector<pod::LODMetadata> UF_API generateLODs( uf::Mesh&, const uf::stl::vector<float>&, bool verbose = false );
|
||||
|
||||
@ -26,6 +26,7 @@ namespace ext {
|
||||
bool UF_API decompressFromFile( uf::stl::vector<uint8_t>&, const uf::stl::string& filename, size_t start, size_t len );
|
||||
bool UF_API decompressFromFile( uf::stl::vector<uint8_t>&, const uf::stl::string& filename, const uf::stl::vector<pod::Range>& ranges );
|
||||
bool UF_API decompressFromMemory( uf::stl::vector<uint8_t>&, const void*, size_t, size_t );
|
||||
bool UF_API decompressScatter( const uf::stl::string& filename, uf::stl::vector<pod::ScatterRequest>& requests );
|
||||
|
||||
size_t UF_API compressToFile( const uf::stl::string&, const void*, size_t );
|
||||
bool UF_API directory( const uf::stl::vector<uint8_t>& buffer, uf::stl::unordered_map<uf::stl::string, pod::ZipEntry>& entries );
|
||||
|
||||
@ -10,6 +10,11 @@ namespace pod {
|
||||
size_t start;
|
||||
size_t len;
|
||||
};
|
||||
struct ScatterRequest {
|
||||
size_t start;
|
||||
size_t len;
|
||||
uint8_t* dest;
|
||||
};
|
||||
}
|
||||
|
||||
namespace uf {
|
||||
@ -54,6 +59,8 @@ namespace uf {
|
||||
return buffer;
|
||||
}
|
||||
|
||||
bool UF_API readScatter( const uf::stl::string& filename, uf::stl::vector<pod::ScatterRequest>& requests );
|
||||
|
||||
size_t UF_API write( const uf::stl::string& filename, const void*, size_t = SIZE_MAX );
|
||||
template<typename T> inline size_t write( const uf::stl::string& filename, const uf::stl::vector<T>& buffer, size_t size = SIZE_MAX ) {
|
||||
return write( filename, buffer.data(), std::min( buffer.size(), size ) );
|
||||
|
||||
@ -25,6 +25,8 @@ namespace pod {
|
||||
|
||||
std::function<bool(pod::Mount&, const uf::stl::string&, size_t, size_t, uf::stl::vector<uint8_t>&)> readRange;
|
||||
std::function<bool(pod::Mount&, const uf::stl::string&, const uf::stl::vector<pod::Range>&, uf::stl::vector<uint8_t>&)> readRanges;
|
||||
|
||||
std::function<bool(pod::Mount&, const uf::stl::string&, size_t, std::function<bool(const uint8_t* data, size_t size)>)> stream;
|
||||
};
|
||||
}
|
||||
|
||||
@ -47,6 +49,8 @@ namespace uf {
|
||||
|
||||
bool UF_API readRange( const uf::stl::string& path, size_t start, size_t len, uf::stl::vector<uint8_t>& buffer );
|
||||
bool UF_API readRanges( const uf::stl::string& path, const uf::stl::vector<pod::Range>& ranges, uf::stl::vector<uint8_t>& buffer );
|
||||
|
||||
bool UF_API stream( const uf::stl::string& path, size_t chunkSize, std::function<bool(const uint8_t* data, size_t size)> callback );
|
||||
|
||||
pod::Mount UF_API createDiskMount( const uf::stl::string& uri, int priority = 0 );
|
||||
uf::stl::string UF_API resolveBase( const uf::stl::string& path );
|
||||
|
||||
@ -261,6 +261,7 @@ namespace uf {
|
||||
uf::Mesh alias() const;
|
||||
uf::Mesh expand();
|
||||
void interleave();
|
||||
void prune( const uf::stl::vector<uf::stl::string>& keep );
|
||||
|
||||
void updateDescriptor();
|
||||
|
||||
@ -389,6 +390,8 @@ namespace uf {
|
||||
|
||||
srcBuffer.swap( dstBuffer );
|
||||
|
||||
attribute.offset = 0;
|
||||
attribute.stride = sizeof(To) * attribute.descriptor.components;
|
||||
attribute.pointer = (uint8_t*) ( srcBuffer.data() );
|
||||
attribute.descriptor.type = toEnum;
|
||||
attribute.descriptor.size = sizeof(To) * attribute.descriptor.components;
|
||||
@ -417,6 +420,8 @@ namespace uf {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
updateDescriptor();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@ -12,7 +12,6 @@
|
||||
#include <uf/utils/io/fmt.h>
|
||||
|
||||
#define UF_GRAPH_LOAD_MULTITHREAD 0
|
||||
#define UF_GRAPH_EXTENDED 1
|
||||
|
||||
#if UF_ENV_DREAMCAST
|
||||
#define UF_DEBUG_TIMER_MULTITRACE_START(...) UF_TIMER_MULTITRACE_START(__VA_ARGS__)
|
||||
@ -25,12 +24,20 @@
|
||||
#endif
|
||||
|
||||
namespace {
|
||||
struct PendingImage {
|
||||
uf::stl::string name;
|
||||
uf::stl::vector<uint8_t> buffer;
|
||||
uf::stl::string extension;
|
||||
size_t layers;
|
||||
};
|
||||
|
||||
size_t deduceFormat( const uf::stl::string& format ) {
|
||||
if ( format == "ARGB4444" ) return uf::renderer::enums::Format::R4G4B4A4_UNORM_PACK16;
|
||||
if ( format == "RGB565" ) return uf::renderer::enums::Format::R5G6B5_UNORM_PACK16;
|
||||
return 0;
|
||||
}
|
||||
uf::Image decodeImage( ext::json::Value& json, pod::Graph& graph, const uf::stl::string& imageName ) {
|
||||
|
||||
uf::Image decodeImage( ext::json::Value& json, pod::Graph& graph, const uf::stl::string& imageName, uf::stl::unordered_map<uf::stl::string, uf::stl::vector<pod::ScatterRequest>>& scatterMap, uf::stl::vector<PendingImage>& pendingImages ) {
|
||||
uf::Image image;
|
||||
|
||||
uf::stl::string filename = "";
|
||||
@ -82,24 +89,27 @@ namespace {
|
||||
auto& storage = uf::graph::getStorage(graph);
|
||||
graph.streams.images[imageName] = { fullPath, offset, length };
|
||||
} else {
|
||||
uf::stl::vector<uint8_t> buffer;
|
||||
if ( length > 0 ) {
|
||||
uf::io::readAsBuffer( buffer, fullPath, offset, length );
|
||||
} else {
|
||||
uf::io::readAsBuffer( buffer, fullPath );
|
||||
}
|
||||
pendingImages.push_back({ imageName, {}, extension, layers });
|
||||
auto& pending = pendingImages.back();
|
||||
|
||||
uf::image::open( image, buffer, extension, false );
|
||||
uf::image::layers( image, layers );
|
||||
size_t readLen = length > 0 ? length : uf::io::size( fullPath );
|
||||
if ( readLen > 0 ) {
|
||||
pending.buffer.resize(readLen);
|
||||
scatterMap[fullPath].push_back({
|
||||
offset,
|
||||
readLen,
|
||||
pending.buffer.data()
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
image.setFilename( fullPath );
|
||||
image.setFormat( format );
|
||||
|
||||
return image;
|
||||
}
|
||||
|
||||
pod::Animation decodeAnimation( ext::json::Value& json, pod::Graph& graph, const uf::stl::string& animName, const uf::stl::vector<uint8_t>& megaBuffer ) {
|
||||
pod::Animation decodeAnimation( ext::json::Value& json, pod::Graph& graph, const uf::stl::string& animName ) {
|
||||
pod::Animation animation = {};
|
||||
animation.name = json["name"].as(animation.name);
|
||||
animation.start = json["start"].as<float>(0.0f);
|
||||
@ -113,7 +123,7 @@ namespace {
|
||||
auto& storage = uf::graph::getStorage(graph);
|
||||
auto& animStream = graph.streams.animations[animName];
|
||||
|
||||
ext::json::forEach( json["samplers"], [&]( ext::json::Value& value ){
|
||||
ext::json::forEach( json["samplers"], [&]( ext::json::Value& value ){
|
||||
auto& sampler = animation.samplers.emplace_back();
|
||||
sampler.interpolator = value["interpolator"].as(sampler.interpolator);
|
||||
|
||||
@ -131,13 +141,17 @@ namespace {
|
||||
sStream.outputs = { binPath, outputsOffset, outputsLen };
|
||||
animStream.samplers.emplace_back(sStream);
|
||||
} else {
|
||||
if (inputsLen > 0 && !megaBuffer.empty()) {
|
||||
if ( inputsLen > 0 ) {
|
||||
uf::stl::vector<uint8_t> temp;
|
||||
uf::io::readAsBuffer(temp, binPath, inputsOffset, inputsLen);
|
||||
sampler.inputs.resize(inputsCount);
|
||||
memcpy(sampler.inputs.data(), megaBuffer.data() + inputsOffset, inputsLen);
|
||||
memcpy(sampler.inputs.data(), temp.data(), inputsLen);
|
||||
}
|
||||
if (outputsLen > 0 && !megaBuffer.empty()) {
|
||||
if ( outputsLen > 0 ) {
|
||||
uf::stl::vector<uint8_t> temp;
|
||||
uf::io::readAsBuffer(temp, binPath, outputsOffset, outputsLen);
|
||||
sampler.outputs.resize(outputsCount);
|
||||
memcpy(sampler.outputs.data(), megaBuffer.data() + outputsOffset, outputsLen);
|
||||
memcpy(sampler.outputs.data(), temp.data(), outputsLen);
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -152,7 +166,7 @@ namespace {
|
||||
return animation;
|
||||
}
|
||||
|
||||
pod::Skin decodeSkin( ext::json::Value& json, pod::Graph& graph, const uf::stl::string& skinName, const uf::stl::vector<uint8_t>& megaBuffer ) {
|
||||
pod::Skin decodeSkin( ext::json::Value& json, pod::Graph& graph, const uf::stl::string& skinName ) {
|
||||
pod::Skin skin;
|
||||
|
||||
skin.name = json["name"].as(skin.name);
|
||||
@ -162,7 +176,7 @@ namespace {
|
||||
skin.joints.emplace_back( value.as<int32_t>() );
|
||||
});
|
||||
|
||||
if (json["inverseBindMatrices"].isObject()) {
|
||||
if ( json["inverseBindMatrices"].isObject() ) {
|
||||
auto& invJson = json["inverseBindMatrices"];
|
||||
size_t count = invJson["count"].as<size_t>();
|
||||
size_t offset = invJson["offset"].as<size_t>();
|
||||
@ -175,9 +189,11 @@ namespace {
|
||||
if ( graph.settings.stream.enabled ) {
|
||||
skinStream.inverseBindMatrices = { binPath, offset, length };
|
||||
} else {
|
||||
if (length > 0 && !megaBuffer.empty()) {
|
||||
if ( length > 0 ) {
|
||||
uf::stl::vector<uint8_t> temp;
|
||||
uf::io::readAsBuffer(temp, binPath, offset, length);
|
||||
skin.inverseBindMatrices.resize(count);
|
||||
memcpy(skin.inverseBindMatrices.data(), megaBuffer.data() + offset, length);
|
||||
memcpy(skin.inverseBindMatrices.data(), temp.data(), length);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -185,7 +201,7 @@ namespace {
|
||||
return skin;
|
||||
}
|
||||
|
||||
uf::Mesh decodeMesh( ext::json::Value& json, pod::Graph& graph, const uf::stl::string& meshName, const uf::stl::vector<uint8_t>& megaBuffer ) {
|
||||
uf::Mesh decodeMesh( ext::json::Value& json, pod::Graph& graph, const uf::stl::string& meshName, uf::stl::unordered_map<uf::stl::string, uf::stl::vector<pod::ScatterRequest>>& scatterMap ) {
|
||||
uf::Mesh mesh;
|
||||
|
||||
#define DESERIALIZE_MESH(N) {\
|
||||
@ -220,129 +236,49 @@ namespace {
|
||||
auto& meshStream = graph.streams.meshes[meshName];
|
||||
|
||||
mesh.buffers.reserve( json["buffers"].size() );
|
||||
bool deferred = graph.settings.stream.enabled;
|
||||
|
||||
uf::stl::vector<pod::StreamRegion> localRegions;
|
||||
localRegions.reserve( json["buffers"].size() );
|
||||
|
||||
bool defered = true; // graph.settings.stream.enabled;
|
||||
ext::json::forEach( json["buffers"], [&]( ext::json::Value& value ){
|
||||
uf::stl::string filename;
|
||||
size_t offset = 0, length = 0;
|
||||
|
||||
if (value.isObject()) {
|
||||
if ( value.isObject() ) {
|
||||
filename = value["filename"].as<uf::stl::string>();
|
||||
offset = value["offset"].as<size_t>();
|
||||
length = value["length"].as<size_t>();
|
||||
} else {
|
||||
filename = value.as<uf::stl::string>();
|
||||
}
|
||||
|
||||
|
||||
uf::stl::string fullPath = uf::io::directory( graph.name ) + "/" + filename;
|
||||
pod::StreamRegion region = { fullPath, offset, length };
|
||||
|
||||
if ( defered ) {
|
||||
mesh.buffers.emplace_back();
|
||||
meshStream.buffers.push_back(region);
|
||||
} else {
|
||||
uf::stl::vector<uint8_t> buf;
|
||||
if (length > 0 && !megaBuffer.empty()) {
|
||||
buf.assign(megaBuffer.begin() + offset, megaBuffer.begin() + offset + length);
|
||||
} else if (length > 0) {
|
||||
uf::io::readAsBuffer(buf, fullPath, offset, length);
|
||||
} else {
|
||||
uf::io::readAsBuffer(buf, fullPath);
|
||||
}
|
||||
mesh.buffers.emplace_back(std::move(buf));
|
||||
localRegions.push_back(region);
|
||||
}
|
||||
mesh.buffers.emplace_back();
|
||||
meshStream.buffers.emplace_back(pod::StreamRegion{ fullPath, offset, length });
|
||||
});
|
||||
|
||||
auto getRegion = [&](size_t bufferIdx) -> pod::StreamRegion {
|
||||
if ( defered ) return meshStream.buffers[bufferIdx];
|
||||
return localRegions[bufferIdx];
|
||||
auto queue = [&]( auto& attributes ) {
|
||||
for ( auto& attr : attributes ) {
|
||||
if ( !mesh.buffers[attr.buffer].empty() ) continue;
|
||||
|
||||
auto region = meshStream.buffers[attr.buffer];
|
||||
if ( region.length == 0 ) continue;
|
||||
|
||||
mesh.buffers[attr.buffer].resize(region.length);
|
||||
scatterMap[region.filename].push_back({
|
||||
region.offset,
|
||||
region.length,
|
||||
mesh.buffers[attr.buffer].data()
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
for ( size_t i = 0; i < mesh.instance.attributes.size(); ++i ) {
|
||||
auto& attr = mesh.instance.attributes[i];
|
||||
if ( !mesh.buffers[attr.buffer].empty() ) continue;
|
||||
auto region = getRegion(attr.buffer);
|
||||
uf::io::readAsBuffer(mesh.buffers[attr.buffer], region.filename, region.offset, region.length);
|
||||
}
|
||||
for ( size_t i = 0; i < mesh.indirect.attributes.size(); ++i ) {
|
||||
auto& attr = mesh.indirect.attributes[i];
|
||||
if ( !mesh.buffers[attr.buffer].empty() ) continue;
|
||||
auto region = getRegion(attr.buffer);
|
||||
uf::io::readAsBuffer(mesh.buffers[attr.buffer], region.filename, region.offset, region.length);
|
||||
queue( mesh.instance.attributes );
|
||||
queue( mesh.indirect.attributes );
|
||||
if ( !deferred ) {
|
||||
queue( mesh.vertex.attributes );
|
||||
queue( mesh.index.attributes );
|
||||
}
|
||||
|
||||
{
|
||||
uf::stl::vector<uf::stl::string> attributesKept = ext::json::vector<uf::stl::string>(graph.metadata["decode"]["attributes"]);
|
||||
|
||||
uf::stl::vector<size_t> deadAttributes;
|
||||
uf::stl::vector<int32_t> deadBuffers;
|
||||
|
||||
for ( size_t i = 0; i < mesh.vertex.attributes.size(); ++i ) {
|
||||
auto& attribute = mesh.vertex.attributes[i];
|
||||
if ( std::find( attributesKept.begin(), attributesKept.end(), attribute.descriptor.name ) != attributesKept.end() ) continue;
|
||||
|
||||
deadAttributes.push_back(i);
|
||||
deadBuffers.push_back(attribute.buffer);
|
||||
}
|
||||
|
||||
std::sort(deadAttributes.rbegin(), deadAttributes.rend());
|
||||
std::sort(deadBuffers.rbegin(), deadBuffers.rend());
|
||||
|
||||
for ( auto idx : deadAttributes ) {
|
||||
mesh.vertex.attributes.erase(mesh.vertex.attributes.begin() + idx);
|
||||
}
|
||||
|
||||
for ( auto bufID : deadBuffers ) {
|
||||
mesh.buffers.erase(mesh.buffers.begin() + bufID);
|
||||
|
||||
if ( graph.settings.stream.enabled ) {
|
||||
meshStream.buffers.erase(meshStream.buffers.begin() + bufID);
|
||||
} else {
|
||||
localRegions.erase(localRegions.begin() + bufID);
|
||||
}
|
||||
}
|
||||
|
||||
auto remap_input = [&](uf::Mesh::Input& input) {
|
||||
for (auto& attr : input.attributes) {
|
||||
int32_t shift = 0;
|
||||
for (int32_t db : deadBuffers) {
|
||||
if (attr.buffer > db) shift++;
|
||||
}
|
||||
attr.buffer -= shift;
|
||||
}
|
||||
};
|
||||
|
||||
remap_input(mesh.vertex);
|
||||
remap_input(mesh.index);
|
||||
remap_input(mesh.instance);
|
||||
remap_input(mesh.indirect);
|
||||
}
|
||||
|
||||
// if ( graph.metadata["renderer"]["separate"].as<bool>() )
|
||||
{
|
||||
#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();
|
||||
return mesh;
|
||||
}
|
||||
@ -396,13 +332,15 @@ void uf::graph::load( pod::Graph& graph, const uf::stl::string& filename, const
|
||||
if ( !graph.storage ) graph.storage = new pod::Graph::Storage();
|
||||
auto& storage = uf::graph::getStorage( graph ); // will just fetch the above
|
||||
|
||||
#if 0
|
||||
if ( !ext::json::isArray(graph.metadata["decode"]["attributes"]) ) {
|
||||
#if 0 && UF_USE_OPENGL
|
||||
#if UF_USE_OPENGL
|
||||
graph.metadata["decode"]["attributes"] = uf::stl::vector<uf::stl::string>({ "position", "uv", "st" });
|
||||
#else
|
||||
graph.metadata["decode"]["attributes"] = uf::stl::vector<uf::stl::string>({ "position", "color", "uv", "st", "normal", "tangent", "joints", "weights" });
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
// failsafes
|
||||
if ( graph.metadata["stream"]["enabled"].is<uf::stl::string>() && graph.metadata["stream"]["enabled"].as<uf::stl::string>() == "auto" ) {
|
||||
@ -543,7 +481,7 @@ void uf::graph::load( pod::Graph& graph, const uf::stl::string& filename, const
|
||||
uf::stl::vector<uint8_t> ioBuf;
|
||||
pod::Primitive* allPrimitives = nullptr;
|
||||
|
||||
if (uf::io::readAsBuffer(ioBuf, directory + binName)) {
|
||||
if ( uf::io::readAsBuffer(ioBuf, directory + binName) ) {
|
||||
allPrimitives = reinterpret_cast<pod::Primitive*>(ioBuf.data());
|
||||
}
|
||||
|
||||
@ -557,7 +495,7 @@ void uf::graph::load( pod::Graph& graph, const uf::stl::string& filename, const
|
||||
|
||||
bool hasOffset = !value["offset"].isNull();
|
||||
|
||||
if (allPrimitives && hasOffset) {
|
||||
if ( allPrimitives && hasOffset ) {
|
||||
size_t count = value["count"].as<size_t>();
|
||||
size_t offsetBytes = value["offset"].as<size_t>();
|
||||
size_t startIndex = offsetBytes / sizeof(pod::Primitive);
|
||||
@ -574,14 +512,34 @@ void uf::graph::load( pod::Graph& graph, const uf::stl::string& filename, const
|
||||
tasks.queue([&]{
|
||||
UF_DEBUG_TIMER_MULTITRACE("Reading images...");
|
||||
graph.images.reserve( serializer["images"].size() );
|
||||
|
||||
uf::stl::vector<PendingImage> pendingImages;
|
||||
pendingImages.reserve( serializer["images"].size() );
|
||||
uf::stl::unordered_map<uf::stl::string, uf::stl::vector<pod::ScatterRequest>> scatterMap;
|
||||
|
||||
ext::json::forEach( serializer["images"], [&]( ext::json::Value& value ){
|
||||
auto name = key + value["name"].as<uf::stl::string>();
|
||||
|
||||
UF_DEBUG_TIMER_MULTITRACE("Reading image={}", name);
|
||||
storage.images[name] = {
|
||||
.data = decodeImage( value, graph, name ),
|
||||
.data = decodeImage( value, graph, name, scatterMap, pendingImages ),
|
||||
};
|
||||
graph.images.emplace_back(name);
|
||||
});
|
||||
|
||||
for ( auto& [filename, requests] : scatterMap ) {
|
||||
uf::io::readScatter( filename, requests );
|
||||
}
|
||||
|
||||
for ( auto& pending : pendingImages ) {
|
||||
auto& image = storage.images[pending.name].data;
|
||||
if ( !pending.buffer.empty() ) {
|
||||
uf::image::open( image, pending.buffer, pending.extension, false );
|
||||
uf::image::layers( image, pending.layers );
|
||||
|
||||
pending.buffer.clear();
|
||||
}
|
||||
}
|
||||
UF_DEBUG_TIMER_MULTITRACE("Read images");
|
||||
});
|
||||
|
||||
@ -589,35 +547,38 @@ void uf::graph::load( pod::Graph& graph, const uf::stl::string& filename, const
|
||||
UF_DEBUG_TIMER_MULTITRACE("Reading meshes...");
|
||||
graph.meshes.reserve( serializer["meshes"].size() );
|
||||
|
||||
uf::stl::vector<uint8_t> megaBuffer;
|
||||
bool bufferAttempted = false;
|
||||
|
||||
#if UF_USE_OPENGL
|
||||
bool preferInterleaved = true;
|
||||
bool preferMinified = true;
|
||||
#else
|
||||
bool preferInterleaved = false;
|
||||
bool preferMinified = false;
|
||||
#endif
|
||||
|
||||
if ( graph.settings.stream.enabled ) preferInterleaved = false;
|
||||
if ( graph.settings.stream.enabled ) preferMinified = false;
|
||||
|
||||
uf::stl::unordered_map<uf::stl::string, uf::stl::vector<pod::ScatterRequest>> scatterMap;
|
||||
uf::stl::vector<uf::stl::string> meshesToMinify;
|
||||
|
||||
ext::json::forEach( serializer["meshes"], [&]( ext::json::Value& value ){
|
||||
auto name = key + value["name"].as<uf::stl::string>();
|
||||
|
||||
bool hasInterleavedAsset = value["interleaved"].isObject();
|
||||
ext::json::Value& json = ( preferInterleaved && hasInterleavedAsset ) ? value["interleaved"] : value;
|
||||
bool hasMinifiedAsset = value["min"].isObject();
|
||||
ext::json::Value& json = ( preferMinified && hasMinifiedAsset ) ? value["min"] : value;
|
||||
|
||||
if ( !bufferAttempted && json["buffers"].size() > 0 && json["buffers"][0].isObject() ) {
|
||||
uf::stl::string binName = json["buffers"][0]["filename"].as<uf::stl::string>();
|
||||
uf::io::readAsBuffer( megaBuffer, directory + "/" + binName );
|
||||
bufferAttempted = true;
|
||||
}
|
||||
|
||||
auto& mesh = (storage.meshes[name] = decodeMesh( json, graph, name, megaBuffer ));
|
||||
if ( preferInterleaved && !hasInterleavedAsset && !graph.settings.stream.enabled ) {
|
||||
mesh.interleave();
|
||||
}
|
||||
storage.meshes[name] = decodeMesh( json, graph, name, scatterMap );
|
||||
graph.meshes.emplace_back(name);
|
||||
|
||||
if ( preferMinified && !hasMinifiedAsset && !graph.settings.stream.enabled ) {
|
||||
meshesToMinify.emplace_back( name );
|
||||
}
|
||||
});
|
||||
for ( auto& [filename, requests] : scatterMap ) uf::io::readScatter( filename, requests );
|
||||
for ( const auto& name : meshesToMinify ) {
|
||||
auto& mesh = storage.meshes[name];
|
||||
mesh.prune( { "position", "uv", "st" } );
|
||||
mesh.convert<float, uint16_t>();
|
||||
mesh.interleave();
|
||||
}
|
||||
|
||||
UF_DEBUG_TIMER_MULTITRACE("Read meshes");
|
||||
});
|
||||
|
||||
@ -625,21 +586,11 @@ void uf::graph::load( pod::Graph& graph, const uf::stl::string& filename, const
|
||||
UF_DEBUG_TIMER_MULTITRACE("Reading animation information...");
|
||||
auto& animNode = serializer["animations"];
|
||||
|
||||
uf::stl::vector<uint8_t> megaBuffer;
|
||||
bool bufferAttempted = false;
|
||||
|
||||
if (animNode.isObject()) {
|
||||
storage.animations.map.reserve( animNode.size() );
|
||||
ext::json::forEach( animNode, [&]( const uf::stl::string& rawName, ext::json::Value& value ){
|
||||
auto name = key + rawName;
|
||||
|
||||
if (!bufferAttempted && value["buffer"].is<uf::stl::string>()) {
|
||||
uf::stl::string binName = value["buffer"].as<uf::stl::string>();
|
||||
uf::io::readAsBuffer(megaBuffer, directory + binName);
|
||||
bufferAttempted = true;
|
||||
}
|
||||
|
||||
storage.animations[name] = decodeAnimation( value, graph, name, megaBuffer );
|
||||
storage.animations[name] = decodeAnimation( value, graph, name );
|
||||
graph.animations.emplace_back(name);
|
||||
});
|
||||
}
|
||||
@ -651,7 +602,7 @@ void uf::graph::load( pod::Graph& graph, const uf::stl::string& filename, const
|
||||
json.readFromFile( path );
|
||||
auto name = key + json["name"].as<uf::stl::string>();
|
||||
|
||||
storage.animations[name] = decodeAnimation( json, graph, name, megaBuffer );
|
||||
storage.animations[name] = decodeAnimation( json, graph, name );
|
||||
graph.animations.emplace_back(name);
|
||||
});
|
||||
}
|
||||
@ -662,19 +613,9 @@ void uf::graph::load( pod::Graph& graph, const uf::stl::string& filename, const
|
||||
UF_DEBUG_TIMER_MULTITRACE("Reading skinning information...");
|
||||
graph.skins.reserve( serializer["skins"].size() );
|
||||
|
||||
uf::stl::vector<uint8_t> megaBuffer;
|
||||
bool bufferAttempted = false;
|
||||
|
||||
ext::json::forEach( serializer["skins"], [&]( ext::json::Value& value ){
|
||||
auto name = key + value["name"].as<uf::stl::string>();
|
||||
|
||||
if (!bufferAttempted && value["inverseBindMatrices"].isObject()) {
|
||||
uf::stl::string binName = value["inverseBindMatrices"]["buffer"].as<uf::stl::string>();
|
||||
uf::io::readAsBuffer(megaBuffer, directory + binName);
|
||||
bufferAttempted = true;
|
||||
}
|
||||
|
||||
storage.skins[name] = decodeSkin( value, graph, name, megaBuffer );
|
||||
storage.skins[name] = decodeSkin( value, graph, name );
|
||||
graph.skins.emplace_back(name);
|
||||
});
|
||||
UF_DEBUG_TIMER_MULTITRACE("Read skins");
|
||||
|
||||
@ -330,10 +330,10 @@ uf::stl::string uf::graph::save( const pod::Graph& graph, const uf::stl::string&
|
||||
ext::json::reserve( serializer["meshes"], graph.meshes.size() );
|
||||
|
||||
uf::stl::vector<uint8_t> meshesBuffer;
|
||||
uf::stl::vector<uint8_t> interleavedBuffer;
|
||||
uf::stl::vector<uint8_t> minBuffer;
|
||||
|
||||
uf::stl::string binName = "meshes." + (settings.compression == "none" ? "bin" : settings.compression);
|
||||
uf::stl::string interleavedBinName = "meshes.interleaved." + (settings.compression == "none" ? "bin" : settings.compression);
|
||||
uf::stl::string minBinName = "meshes.min." + (settings.compression == "none" ? "bin" : settings.compression);
|
||||
|
||||
for ( auto& name : graph.meshes ) {
|
||||
auto& mesh = storage.meshes.map.at(name);
|
||||
@ -341,10 +341,15 @@ uf::stl::string uf::graph::save( const pod::Graph& graph, const uf::stl::string&
|
||||
auto json = encode(mesh, settings, graph, meshesBuffer, binName);
|
||||
json["name"] = name;
|
||||
|
||||
uf::Mesh interleavedMesh = mesh.copy();
|
||||
interleavedMesh.interleave();
|
||||
// to-do: properly flag this
|
||||
if ( true ) {
|
||||
uf::Mesh minMesh = mesh.copy();
|
||||
minMesh.prune( { "position", "uv", "st" } );
|
||||
minMesh.convert<float, uint16_t>();
|
||||
minMesh.interleave();
|
||||
|
||||
json["interleaved"] = encode(interleavedMesh, settings, graph, interleavedBuffer, interleavedBinName);;
|
||||
json["min"] = encode( minMesh, settings, graph, minBuffer, minBinName );
|
||||
}
|
||||
|
||||
serializer["meshes"].emplace_back(json);
|
||||
}
|
||||
@ -352,8 +357,8 @@ uf::stl::string uf::graph::save( const pod::Graph& graph, const uf::stl::string&
|
||||
if ( !meshesBuffer.empty() ) {
|
||||
uf::io::write(directory + "/" + binName, meshesBuffer);
|
||||
}
|
||||
if ( !interleavedBuffer.empty() ) {
|
||||
uf::io::write(directory + "/" + interleavedBinName, interleavedBuffer);
|
||||
if ( !minBuffer.empty() ) {
|
||||
uf::io::write(directory + "/" + minBinName, minBuffer);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@ -30,6 +30,13 @@
|
||||
// to-do: fix LOD1+ breaking
|
||||
|
||||
namespace {
|
||||
struct PendingTexture {
|
||||
uf::stl::string key;
|
||||
uf::stl::string formatHint;
|
||||
uf::stl::vector<uint8_t> buffer;
|
||||
bool needsUpload;
|
||||
};
|
||||
|
||||
struct TextureDescriptor {
|
||||
bool srgb = false;
|
||||
size_t layers = 1;
|
||||
@ -220,6 +227,7 @@ namespace {
|
||||
graphic.descriptor.bind.width = graphic.descriptor.inputs.indirect.count;
|
||||
graphic.descriptor.bind.height = 1;
|
||||
graphic.descriptor.bind.depth = 1;
|
||||
graphic.descriptor.bind.point = VK_PIPELINE_BIND_POINT_COMPUTE;
|
||||
|
||||
// compute shader
|
||||
auto& shader = graphic.material.getShader("compute", uf::renderer::settings::pipelines::names::culling);
|
||||
@ -310,10 +318,6 @@ namespace {
|
||||
// to-do: segregate out buffer updating code
|
||||
if ( uf::renderer::settings::pipelines::rt && mesh.vertex.count ) {
|
||||
if ( graphMetadataJson["renderer"]["skinned"].as<bool>() ) {
|
||||
struct PushConstant {
|
||||
uint32_t jointID;
|
||||
};
|
||||
|
||||
uf::stl::string compShaderFilename = graphMetadataJson["shaders"]["skinning"]["compute"].as<uf::stl::string>("/graph/skinning/skinning.comp.spv"); {
|
||||
compShaderFilename = entity.resolveURI( compShaderFilename, root );
|
||||
}
|
||||
@ -364,13 +368,15 @@ namespace {
|
||||
|
||||
auto& shader = graphic.material.getShader("compute", "skinning");
|
||||
|
||||
struct SkinningPush {
|
||||
struct PushConstant {
|
||||
uint32_t jointID;
|
||||
uint32_t vertexCount;
|
||||
};
|
||||
|
||||
auto& pushConstant = shader.pushConstants.front().get<SkinningPush>();
|
||||
auto& pushConstant = shader.pushConstants.front().get<PushConstant>();
|
||||
pushConstant = {
|
||||
.jointID = (uint32_t) primitives.front().instance.jointID
|
||||
.jointID = (uint32_t) primitives.front().instance.jointID,
|
||||
.vertexCount = (uint32_t) mesh.vertex.count,
|
||||
};
|
||||
|
||||
// bind buffers
|
||||
@ -2113,6 +2119,7 @@ void uf::graph::reload( pod::Graph& graph, pod::Node& node ) {
|
||||
auto& meshStream = graph.streams.meshes[meshName];
|
||||
auto& primitives = storage.primitives.map[graph.primitives[node.mesh]];
|
||||
|
||||
bool isStreamable = false;
|
||||
float radius = graph.settings.stream.radius;
|
||||
float radiusSquared = radius * radius;
|
||||
|
||||
@ -2122,7 +2129,17 @@ void uf::graph::reload( pod::Graph& graph, pod::Node& node ) {
|
||||
}
|
||||
|
||||
// disable if not tagged for streaming
|
||||
if ( graph.settings.stream.world != -1 && node.index != graph.settings.stream.world ) {
|
||||
if ( node.index == graph.settings.stream.world ) {
|
||||
isStreamable = true;
|
||||
}/* else if ( ext::json::isObject(tag) && tag.has("stream") ) {
|
||||
auto& streamTag = tag["stream"];
|
||||
if ( streamTag.has("enabled") && streamTag["enabled"].as<bool>() ) {
|
||||
isStreamable = true;
|
||||
radius = streamTag["radius"].as<float>(radius);
|
||||
}
|
||||
}*/
|
||||
|
||||
if ( !isStreamable ) {
|
||||
radius = 0;
|
||||
}
|
||||
|
||||
@ -2163,7 +2180,6 @@ void uf::graph::reload( pod::Graph& graph, pod::Node& node ) {
|
||||
found = true;
|
||||
|
||||
int8_t lodLevel = 0;
|
||||
#if 0
|
||||
// deduce a simple ratio [0.0 to 1.0] of how far we are into the streaming radius
|
||||
float distRatio = distanceSquared / radiusSquared;
|
||||
if ( distRatio > 0.6f ) lodLevel = 3;
|
||||
@ -2173,15 +2189,14 @@ void uf::graph::reload( pod::Graph& graph, pod::Node& node ) {
|
||||
while ( lodLevel > 0 && primitive.lod.levels[lodLevel].indices == 0 ) {
|
||||
lodLevel--;
|
||||
}
|
||||
#endif
|
||||
|
||||
queuedLODs[drawID] = lodLevel;
|
||||
}
|
||||
}
|
||||
|
||||
// insert closest primitive if all are out of range (because of cringe logic)
|
||||
if ( !found ) {
|
||||
queuedLODs[closestDrawID] = 0;
|
||||
if ( !found /*&& node.index == graph.settings.stream.world*/ ) {
|
||||
queuedLODs[closestDrawID] = 3;
|
||||
}
|
||||
|
||||
// bail if no update is detected
|
||||
@ -2196,110 +2211,112 @@ void uf::graph::reload( pod::Graph& graph, pod::Node& node ) {
|
||||
|
||||
// read from disk
|
||||
#if UF_GRAPH_SPARSE_READ_MESH
|
||||
// needs to be dequantized first, naively copying the descriptor settings just doesn't work
|
||||
{
|
||||
#if UF_ENV_DREAMCAST && GL_QUANTIZED_SHORT
|
||||
mesh.convert<uint16_t, float>();
|
||||
#else
|
||||
auto conversion = graphMetadataJson["decode"]["conversion"].as<uf::stl::string>();
|
||||
if ( conversion != "" ) {
|
||||
#if UF_USE_FLOAT16
|
||||
if ( conversion == "float16" ) mesh.convert<float16, float>();
|
||||
else if ( conversion == "float" ) mesh.convert<float, float16>();
|
||||
#endif
|
||||
#if UF_USE_BFLOAT16
|
||||
if ( conversion == "bfloat16" ) mesh.convert<bfloat16, float>();
|
||||
else if ( conversion == "float" ) mesh.convert<float, bfloat16>();
|
||||
#endif
|
||||
if ( conversion == "uint16_t" ) mesh.convert<uint16_t, float>();
|
||||
else if ( conversion == "float" ) mesh.convert<float, uint16_t>();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
uint32_t currentVertexCount = 0;
|
||||
uint32_t currentIndexCount = 0;
|
||||
|
||||
// reset counts
|
||||
mesh.vertex.count = 0;
|
||||
mesh.index.count = 0;
|
||||
struct ActiveDraw {
|
||||
size_t drawID;
|
||||
int8_t lodLevel;
|
||||
uint32_t fileVertexID;
|
||||
};
|
||||
|
||||
for (size_t drawID = 0; drawID < queuedLODs.size(); ++drawID) {
|
||||
static thread_local uf::stl::unordered_map<size_t, uf::stl::vector<pod::Range>> batchedRanges; batchedRanges.clear();
|
||||
static thread_local uf::stl::unordered_map<size_t, size_t> bufferSizes; bufferSizes.clear();
|
||||
static thread_local uf::stl::unordered_map<uf::stl::string, uf::stl::vector<pod::ScatterRequest>> scatterMap; scatterMap.clear();
|
||||
static thread_local uf::stl::unordered_map<size_t, size_t> bufferWriteOffsets; bufferWriteOffsets.clear();
|
||||
static thread_local uf::stl::unordered_map<size_t, uf::stl::vector<uint8_t>> newBuffers; newBuffers.clear();
|
||||
|
||||
STATIC_THREAD_LOCAL(uf::stl::vector<ActiveDraw>, activeDraws);
|
||||
activeDraws.reserve(queuedLODs.size());
|
||||
|
||||
for ( size_t drawID = 0; drawID < queuedLODs.size(); ++drawID ) {
|
||||
auto lodLevel = queuedLODs[drawID];
|
||||
auto& primitive = primitives[drawID];
|
||||
auto& drawCommand = drawCommands[drawID];
|
||||
|
||||
// reset from LOD0
|
||||
//primitives[drawID].drawCommand.instances = 1;
|
||||
primitives[drawID].drawCommand.indices = primitives[drawID].lod.levels[0].indices;
|
||||
primitives[drawID].drawCommand.indexID = primitives[drawID].lod.levels[0].indexID;
|
||||
primitives[drawID].drawCommand.vertexID = primitives[drawID].lod.levels[0].vertexID;
|
||||
primitives[drawID].drawCommand.vertices = primitives[drawID].lod.levels[0].vertices;
|
||||
|
||||
// copy from primitive
|
||||
drawCommand = primitive.drawCommand;
|
||||
|
||||
// disable draw call
|
||||
if ( lodLevel < 0 ) {
|
||||
//drawCommand.instances = 0;
|
||||
if ( lodLevel >= 0 ) {
|
||||
auto& lod = primitives[drawID].lod.levels[lodLevel];
|
||||
activeDraws.emplace_back(ActiveDraw{drawID, lodLevel, lod.vertexID});
|
||||
} else {
|
||||
auto& drawCommand = drawCommands[drawID];
|
||||
drawCommand.vertices = 0;
|
||||
drawCommand.indices = 0;
|
||||
drawCommand.vertexID = 0;
|
||||
drawCommand.indexID = 0;
|
||||
continue;
|
||||
primitives[drawID].drawCommand = drawCommand;
|
||||
}
|
||||
|
||||
auto& lod = primitive.lod.levels[lodLevel];
|
||||
|
||||
// queue up ranges to read from disk using LOD bounds
|
||||
for (auto& attribute : mesh.index.attributes) {
|
||||
auto stride = attribute.stride > 0 ? attribute.stride : attribute.descriptor.size;
|
||||
if (ranges[attribute.buffer].empty() || ranges[attribute.buffer].back().start != lod.indexID * stride) {
|
||||
ranges[attribute.buffer].emplace_back(pod::Range{
|
||||
lod.indexID * stride,
|
||||
lod.indices * stride,
|
||||
});
|
||||
}
|
||||
}
|
||||
for (auto& attribute : mesh.vertex.attributes) {
|
||||
auto stride = attribute.stride > 0 ? attribute.stride : attribute.descriptor.size;
|
||||
if (ranges[attribute.buffer].empty() || ranges[attribute.buffer].back().start != lod.vertexID * stride) {
|
||||
ranges[attribute.buffer].emplace_back(pod::Range{
|
||||
lod.vertexID * stride,
|
||||
lod.vertices * stride,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// reset draw call and remap to local compacted buffers
|
||||
drawCommand.vertices = lod.vertices;
|
||||
drawCommand.indices = lod.indices;
|
||||
drawCommand.vertexID = mesh.vertex.count;
|
||||
drawCommand.indexID = mesh.index.count;
|
||||
|
||||
// synchronize primitive
|
||||
primitives[drawID].drawCommand = drawCommands[drawID];
|
||||
|
||||
// increment remap indices
|
||||
mesh.vertex.count += drawCommand.vertices;
|
||||
mesh.index.count += drawCommand.indices;
|
||||
}
|
||||
|
||||
#define STREAM_MESH_DATA( N ) \
|
||||
for ( auto& attribute : mesh.N.attributes ) {\
|
||||
if ( processedBuffers.count(attribute.buffer) ) continue; \
|
||||
processedBuffers.insert(attribute.buffer); \
|
||||
auto& region = meshStream.buffers[attribute.buffer];\
|
||||
if ( ranges.count(attribute.buffer) <= 0 || region.length == 0 ) { \
|
||||
mesh.buffers[attribute.buffer].clear();\
|
||||
} else {\
|
||||
auto adjustedRanges = ranges[attribute.buffer];\
|
||||
for (auto& r : adjustedRanges) {\
|
||||
r.start += region.offset;\
|
||||
}\
|
||||
uf::io::readAsBuffer( mesh.buffers[attribute.buffer], region.filename, adjustedRanges );\
|
||||
}\
|
||||
std::sort(activeDraws.begin(), activeDraws.end(), [](const ActiveDraw& a, const ActiveDraw& b) {
|
||||
return a.fileVertexID < b.fileVertexID;
|
||||
});
|
||||
|
||||
for ( auto& active : activeDraws ) {
|
||||
auto& lod = primitives[active.drawID].lod.levels[active.lodLevel];
|
||||
for ( auto& attr : mesh.index.attributes ) {
|
||||
size_t stride = attr.stride > 0 ? attr.stride : attr.descriptor.size;
|
||||
bufferSizes[attr.buffer] += lod.indices * stride;
|
||||
}
|
||||
for ( auto& attr : mesh.vertex.attributes ) {
|
||||
size_t stride = attr.stride > 0 ? attr.stride : attr.descriptor.size;
|
||||
bufferSizes[attr.buffer] += lod.vertices * stride;
|
||||
}
|
||||
}
|
||||
|
||||
for ( auto& [b, size] : bufferSizes ) {
|
||||
newBuffers[b].resize(size);
|
||||
}
|
||||
|
||||
for ( auto& active : activeDraws ) {
|
||||
auto& primitive = primitives[active.drawID];
|
||||
auto& lod = primitive.lod.levels[active.lodLevel];
|
||||
auto& drawCommand = drawCommands[active.drawID];
|
||||
|
||||
drawCommand.vertices = lod.vertices;
|
||||
drawCommand.indices = lod.indices;
|
||||
drawCommand.vertexID = currentVertexCount;
|
||||
drawCommand.indexID = currentIndexCount;
|
||||
primitive.drawCommand = drawCommand;
|
||||
|
||||
for ( auto& attr : mesh.index.attributes ) {
|
||||
size_t stride = attr.stride > 0 ? attr.stride : attr.descriptor.size;
|
||||
auto& region = meshStream.buffers[attr.buffer];
|
||||
size_t readBytes = lod.indices * stride;
|
||||
|
||||
scatterMap[region.filename].push_back({
|
||||
region.offset + attr.offset + (lod.indexID * stride),
|
||||
readBytes,
|
||||
newBuffers[attr.buffer].data() + bufferWriteOffsets[attr.buffer]
|
||||
});
|
||||
bufferWriteOffsets[attr.buffer] += readBytes;
|
||||
}
|
||||
|
||||
STREAM_MESH_DATA( index );
|
||||
STREAM_MESH_DATA( vertex );
|
||||
for ( auto& attr : mesh.vertex.attributes ) {
|
||||
size_t stride = attr.stride > 0 ? attr.stride : attr.descriptor.size;
|
||||
auto& region = meshStream.buffers[attr.buffer];
|
||||
size_t readBytes = lod.vertices * stride;
|
||||
|
||||
scatterMap[region.filename].push_back({
|
||||
region.offset + attr.offset + (lod.vertexID * stride),
|
||||
readBytes,
|
||||
newBuffers[attr.buffer].data() + bufferWriteOffsets[attr.buffer]
|
||||
});
|
||||
bufferWriteOffsets[attr.buffer] += readBytes;
|
||||
}
|
||||
|
||||
currentVertexCount += lod.vertices;
|
||||
currentIndexCount += lod.indices;
|
||||
}
|
||||
|
||||
for ( auto& [filename, requests] : scatterMap ) {
|
||||
uf::io::readScatter(filename, requests);
|
||||
}
|
||||
|
||||
mesh.vertex.count = currentVertexCount;
|
||||
mesh.index.count = currentIndexCount;
|
||||
|
||||
for ( auto& [b, buf] : newBuffers ) mesh.buffers[b] = std::move(buf);
|
||||
|
||||
for ( auto& attr : mesh.vertex.attributes ) attr.offset = 0;
|
||||
for ( auto& attr : mesh.index.attributes ) attr.offset = 0;
|
||||
|
||||
// keep the vertex data intact
|
||||
#else
|
||||
// disable remaining draw commands
|
||||
@ -2369,6 +2386,10 @@ void uf::graph::reload( pod::Graph& graph, pod::Node& node ) {
|
||||
}
|
||||
|
||||
uf::stl::unordered_map<uf::stl::string, TextureDescriptor> textureDescriptors;
|
||||
uf::stl::vector<PendingTexture> pendingTextures;
|
||||
uf::stl::unordered_map<uf::stl::string, uf::stl::vector<pod::ScatterRequest>> textureScatterMap;
|
||||
pendingTextures.reserve( textureDescriptors.size() );
|
||||
|
||||
for ( size_t drawID = 0; drawID < primitives.size(); ++drawID ) {
|
||||
auto& primitive = primitives[drawID];
|
||||
auto& instance = primitive.instance;
|
||||
@ -2396,58 +2417,26 @@ void uf::graph::reload( pod::Graph& graph, pod::Node& node ) {
|
||||
if ( visible && (!texture.generated() || texture.aliased) ) {
|
||||
meshUpdated = true;
|
||||
|
||||
// load image
|
||||
pendingTextures.push_back({ key, "", {}, true });
|
||||
auto& pending = pendingTextures.back();
|
||||
|
||||
if ( image.getPixels().empty() ) {
|
||||
auto& imgStream = graph.streams.images[key];
|
||||
uf::stl::vector<uint8_t> buf;
|
||||
|
||||
if (imgStream.buffer.length > 0) {
|
||||
uf::io::readAsBuffer(buf, imgStream.buffer.filename, imgStream.buffer.offset, imgStream.buffer.length);
|
||||
} else {
|
||||
uf::io::readAsBuffer(buf, imgStream.buffer.filename);
|
||||
}
|
||||
|
||||
uf::stl::string formatHint = uf::io::extension(image.getFilename());
|
||||
if (imgStream.buffer.filename.find(".dtex") != uf::stl::string::npos) formatHint = "dtex";
|
||||
pending.formatHint = formatHint;
|
||||
|
||||
uf::image::open(image, buf, formatHint, false);
|
||||
|
||||
// to-do: check against format instead
|
||||
if ( key == "lightmap_atlas" ) {
|
||||
::convertLightmap( image );
|
||||
size_t readLen = imgStream.buffer.length > 0 ? imgStream.buffer.length : uf::io::size(imgStream.buffer.filename);
|
||||
if (readLen > 0) {
|
||||
pending.buffer.resize(readLen);
|
||||
textureScatterMap[imgStream.buffer.filename].push_back({
|
||||
imgStream.buffer.offset,
|
||||
readLen,
|
||||
pending.buffer.data()
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
auto filter = uf::renderer::enums::Filter::LINEAR;
|
||||
auto tag = ext::json::find( key, graphMetadataJson["tags"] );
|
||||
if ( !ext::json::isObject( tag ) ) {
|
||||
tag["renderer"] = graphMetadataJson["renderer"];
|
||||
}
|
||||
if ( tag["renderer"]["filter"].is<uf::stl::string>() ) {
|
||||
const auto mode = uf::string::lowercase( tag["renderer"]["filter"].as<uf::stl::string>("linear") );
|
||||
if ( mode == "linear" ) filter = uf::renderer::enums::Filter::LINEAR;
|
||||
else if ( mode == "nearest" ) filter = uf::renderer::enums::Filter::NEAREST;
|
||||
}
|
||||
|
||||
if ( texture.aliased ) {
|
||||
texture.aliased = false;
|
||||
#if UF_USE_OPENGL
|
||||
texture.image = 0;
|
||||
#else
|
||||
texture.image = {};
|
||||
texture.view = {};
|
||||
#endif
|
||||
}
|
||||
|
||||
texture.sampler.descriptor.filter.min = filter;
|
||||
texture.sampler.descriptor.filter.mag = filter;
|
||||
texture.layers = descriptor.layers;
|
||||
texture.srgb = descriptor.srgb;
|
||||
|
||||
texture.loadFromImage( image );
|
||||
#if UF_ENV_DREAMCAST
|
||||
image.clear();
|
||||
#endif
|
||||
} else if ( !visible && (texture.generated() && !texture.aliased) ) {
|
||||
meshUpdated = true;
|
||||
image.clear();
|
||||
@ -2455,32 +2444,64 @@ void uf::graph::reload( pod::Graph& graph, pod::Node& node ) {
|
||||
texture.aliasTexture(uf::renderer::Texture2D::empty);
|
||||
}
|
||||
}
|
||||
|
||||
for ( auto& [filename, requests] : textureScatterMap ) {
|
||||
uf::io::readScatter( filename, requests );
|
||||
}
|
||||
|
||||
for ( auto& pending : pendingTextures ) {
|
||||
if ( !pending.needsUpload ) continue;
|
||||
|
||||
auto& image = storage.images[pending.key].data;
|
||||
auto& texture = storage.images[pending.key].handle;
|
||||
auto& descriptor = textureDescriptors[pending.key];
|
||||
|
||||
if ( !pending.buffer.empty() ) {
|
||||
uf::image::open( image, pending.buffer, pending.formatHint, false );
|
||||
|
||||
if ( pending.key == "lightmap_atlas" ) {
|
||||
::convertLightmap( image );
|
||||
}
|
||||
pending.buffer.clear();
|
||||
}
|
||||
|
||||
auto filter = uf::renderer::enums::Filter::LINEAR;
|
||||
auto tag = ext::json::find( pending.key, graphMetadataJson["tags"] );
|
||||
if ( !ext::json::isObject( tag ) ) {
|
||||
tag["renderer"] = graphMetadataJson["renderer"];
|
||||
}
|
||||
if ( tag["renderer"]["filter"].is<uf::stl::string>() ) {
|
||||
const auto mode = uf::string::lowercase( tag["renderer"]["filter"].as<uf::stl::string>("linear") );
|
||||
if ( mode == "linear" ) filter = uf::renderer::enums::Filter::LINEAR;
|
||||
else if ( mode == "nearest" ) filter = uf::renderer::enums::Filter::NEAREST;
|
||||
}
|
||||
|
||||
if ( texture.aliased ) {
|
||||
texture.aliased = false;
|
||||
#if UF_USE_OPENGL
|
||||
texture.image = 0;
|
||||
#else
|
||||
texture.image = {};
|
||||
texture.view = {};
|
||||
#endif
|
||||
}
|
||||
|
||||
texture.sampler.descriptor.filter.min = filter;
|
||||
texture.sampler.descriptor.filter.mag = filter;
|
||||
texture.layers = descriptor.layers;
|
||||
texture.srgb = descriptor.srgb;
|
||||
|
||||
texture.loadFromImage( image );
|
||||
|
||||
#if UF_ENV_DREAMCAST
|
||||
image.clear();
|
||||
#endif
|
||||
}
|
||||
#undef INCREMENT_TEXTURE_REFCOUNT
|
||||
}
|
||||
|
||||
if ( !meshUpdated ) return;
|
||||
|
||||
// in the event streamed in mesh data from any pathway isn't already converted
|
||||
{
|
||||
#if UF_ENV_DREAMCAST && GL_QUANTIZED_SHORT
|
||||
mesh.convert<float, uint16_t>();
|
||||
#else
|
||||
auto conversion = graphMetadataJson["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();
|
||||
|
||||
// necessary for OpenGL because recorded descriptors have invalidated pointers
|
||||
|
||||
@ -177,6 +177,107 @@ uf::stl::vector<float> ext::meshopt::computeLODs( size_t count, size_t maxLODs,
|
||||
return factors;
|
||||
}
|
||||
|
||||
// basically just optimize with a simplify factor anyways
|
||||
bool ext::meshopt::simplify( uf::Mesh& mesh, float simplifyFactor ) {
|
||||
if ( simplifyFactor >= 1.0f || simplifyFactor <= 0.0f ) return false;
|
||||
|
||||
mesh.updateDescriptor();
|
||||
const auto& views = mesh.buffer_views;
|
||||
if ( views.empty() ) return false;
|
||||
|
||||
pod::DrawCommand* drawCommands = mesh.indirect.count > 0 ? (pod::DrawCommand*)mesh.getBuffer(mesh.indirect).data() : nullptr;
|
||||
const uint8_t* srcIndexData = mesh.index.count > 0 ? mesh.getBuffer(mesh.index).data() : nullptr;
|
||||
|
||||
uf::stl::vector<uint32_t> outIndices;
|
||||
uf::stl::vector<uf::stl::vector<uint8_t>> outVertices(mesh.vertex.attributes.size());
|
||||
|
||||
int posAttrIdx = -1;
|
||||
for ( size_t i = 0; i < mesh.vertex.attributes.size(); ++i ) {
|
||||
if ( mesh.vertex.attributes[i].descriptor.name == "position" ) {
|
||||
posAttrIdx = i; break;
|
||||
}
|
||||
}
|
||||
if ( posAttrIdx == -1 ) return false;
|
||||
|
||||
for ( size_t viewIdx = 0; viewIdx < views.size(); ++viewIdx ) {
|
||||
auto& view = views[viewIdx];
|
||||
uint32_t cmdIdx = view.indirectIndex;
|
||||
|
||||
uint32_t srcVertexOffset = view.vertex.first;
|
||||
uint32_t srcVertexCount = view.vertex.count;
|
||||
uint32_t srcIndexOffset = view.index.first;
|
||||
uint32_t srcIndexCount = view.index.count;
|
||||
|
||||
if ( srcIndexCount == 0 ) continue;
|
||||
|
||||
uf::stl::vector<uint32_t> localIndices(srcIndexCount);
|
||||
if ( srcIndexData ) {
|
||||
for ( size_t i = 0; i < srcIndexCount; ++i ) {
|
||||
localIndices[i] = readIndex(srcIndexData, srcIndexOffset + i, mesh.index.size);
|
||||
}
|
||||
} else {
|
||||
for ( size_t i = 0; i < srcIndexCount; ++i ) localIndices[i] = i;
|
||||
}
|
||||
|
||||
auto& posAttr = mesh.vertex.attributes[posAttrIdx];
|
||||
const float* srcPositions = (const float*)((const uint8_t*)posAttr.pointer + srcVertexOffset * posAttr.stride);
|
||||
|
||||
uf::stl::vector<uint32_t> simplifiedIndices(srcIndexCount);
|
||||
float targetError = FLT_MAX;
|
||||
float realError = 0.0f;
|
||||
|
||||
size_t optimizedIndexCount = meshopt_simplify(
|
||||
simplifiedIndices.data(), localIndices.data(), srcIndexCount,
|
||||
srcPositions, srcVertexCount, posAttr.stride,
|
||||
srcIndexCount * simplifyFactor, targetError, meshopt_SimplifyLockBorder, &realError
|
||||
);
|
||||
simplifiedIndices.resize(optimizedIndexCount);
|
||||
|
||||
uf::stl::vector<uint32_t> fetchRemap(srcVertexCount);
|
||||
size_t finalVertexCount = meshopt_optimizeVertexFetchRemap(fetchRemap.data(), simplifiedIndices.data(), optimizedIndexCount, srcVertexCount);
|
||||
meshopt_remapIndexBuffer(simplifiedIndices.data(), simplifiedIndices.data(), optimizedIndexCount, fetchRemap.data());
|
||||
|
||||
uint32_t newVertexOffset = outVertices[0].size() / mesh.vertex.attributes[0].stride;
|
||||
uint32_t newIndexOffset = outIndices.size();
|
||||
|
||||
for ( uint32_t idx : simplifiedIndices ) {
|
||||
outIndices.push_back(idx);
|
||||
}
|
||||
|
||||
for ( size_t a = 0; a < mesh.vertex.attributes.size(); ++a ) {
|
||||
auto& attr = mesh.vertex.attributes[a];
|
||||
const uint8_t* srcBase = (const uint8_t*)attr.pointer + srcVertexOffset * attr.stride;
|
||||
|
||||
uf::stl::vector<uint8_t> remapped(finalVertexCount * attr.stride);
|
||||
meshopt_remapVertexBuffer(remapped.data(), srcBase, srcVertexCount, attr.stride, fetchRemap.data());
|
||||
|
||||
outVertices[a].insert(outVertices[a].end(), remapped.begin(), remapped.end());
|
||||
}
|
||||
|
||||
if ( drawCommands ) {
|
||||
drawCommands[cmdIdx].indexID = newIndexOffset;
|
||||
drawCommands[cmdIdx].indices = optimizedIndexCount;
|
||||
drawCommands[cmdIdx].vertexID = newVertexOffset;
|
||||
drawCommands[cmdIdx].vertices = finalVertexCount;
|
||||
}
|
||||
}
|
||||
|
||||
mesh.index.count = outIndices.size();
|
||||
mesh.resizeIndices(mesh.index.count);
|
||||
uint8_t* dstIdx = mesh.getBuffer(mesh.index).data();
|
||||
for ( size_t i = 0; i < outIndices.size(); ++i ) writeIndex(dstIdx, i, mesh.index.size, outIndices[i]);
|
||||
|
||||
mesh.vertex.count = outVertices[0].size() / mesh.vertex.attributes[0].stride;
|
||||
for ( size_t a = 0; a < mesh.vertex.attributes.size(); ++a ) {
|
||||
auto& attr = mesh.vertex.attributes[a];
|
||||
mesh.buffers[attr.buffer].swap(outVertices[a]);
|
||||
attr.pointer = mesh.buffers[attr.buffer].data();
|
||||
}
|
||||
|
||||
mesh.updateDescriptor();
|
||||
return true;
|
||||
}
|
||||
|
||||
uf::stl::vector<pod::LODMetadata> ext::meshopt::generateLODs( uf::Mesh& mesh, const uf::stl::vector<float>& lodFactors, bool verbose ) {
|
||||
uf::stl::vector<pod::LODMetadata> lodMetadata;
|
||||
mesh.updateDescriptor();
|
||||
|
||||
@ -142,6 +142,39 @@ namespace {
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void remapAttribute( uint8_t*& ptr, size_t& stride, GLenum& type, uf::stl::vector<float>& remapBuffer, size_t compCount, uf::renderer::enums::Type::type_t enumType, size_t vertexCount ) {
|
||||
remapBuffer.resize( vertexCount * compCount );
|
||||
size_t inStride = stride > 0 ? stride : (compCount * sizeof(uint16_t));
|
||||
for ( size_t v = 0; v < vertexCount; ++v ) {
|
||||
uint8_t* basePtr = ptr + (v * inStride);
|
||||
for ( size_t c = 0; c < compCount; ++c ) {
|
||||
size_t outIdx = v * compCount + c;
|
||||
switch ( enumType ) {
|
||||
case uf::renderer::enums::Type::SHORT:
|
||||
case uf::renderer::enums::Type::USHORT:
|
||||
remapBuffer[outIdx] = uf::quant::dequantize( ((uint16_t*)basePtr)[c] );
|
||||
break;
|
||||
#if UF_USE_FLOAT16
|
||||
case uf::renderer::enums::Type::HALF:
|
||||
remapBuffer[outIdx] = ((float16*)basePtr)[c];
|
||||
break;
|
||||
#endif
|
||||
#if UF_USE_BFLOAT16
|
||||
case uf::renderer::enums::Type::BFLOAT:
|
||||
remapBuffer[outIdx] = ((bfloat16*)basePtr)[c];
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
remapBuffer[outIdx] = 0.0f;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
ptr = (uint8_t*) remapBuffer.data();
|
||||
type = GL_FLOAT;
|
||||
stride = 0;
|
||||
};
|
||||
}
|
||||
size_t ext::opengl::CommandBuffer::preallocate = 8;
|
||||
void ext::opengl::CommandBuffer::initialize( Device& device ) {
|
||||
@ -501,37 +534,21 @@ void ext::opengl::CommandBuffer::drawIndexed( const ext::opengl::CommandBuffer::
|
||||
uint8_t* vertexPtr = drawInfo.attributes.position.pointer ? (static_cast<uint8_t*>(drawInfo.attributes.position.pointer) + drawInfo.attributes.position.stride * drawInfo.descriptor.inputs.vertex.first) : NULL;
|
||||
|
||||
auto vertexStride = drawInfo.attributes.position.stride;
|
||||
auto normalStride = drawInfo.attributes.normal.stride;
|
||||
auto uvStride = drawInfo.attributes.uv.stride;
|
||||
auto stStride = drawInfo.attributes.st.stride;
|
||||
|
||||
STATIC_THREAD_LOCAL(uf::stl::vector<float>, vertexBufferRemap);
|
||||
STATIC_THREAD_LOCAL(uf::stl::vector<float>, normalBufferRemap);
|
||||
STATIC_THREAD_LOCAL(uf::stl::vector<float>, uvBufferRemap);
|
||||
STATIC_THREAD_LOCAL(uf::stl::vector<float>, stBufferRemap);
|
||||
|
||||
// my copy of GLdc is already patched to handle these without needing a preprocessed buffer
|
||||
#if !UF_ENV_DREAMCAST
|
||||
if ( vertexType != GL_FLOAT ) {
|
||||
vertexBufferRemap.resize( drawInfo.descriptor.inputs.vertex.count * 3 );
|
||||
for ( size_t i = 0; i < drawInfo.descriptor.inputs.vertex.count * 3; ++i ) {
|
||||
switch ( drawInfo.attributes.position.descriptor.type ) {
|
||||
case uf::renderer::enums::Type::SHORT:
|
||||
case uf::renderer::enums::Type::USHORT:
|
||||
vertexBufferRemap[i] = uf::quant::dequantize( ((uint16_t*) vertexPtr)[i] );
|
||||
break;
|
||||
#if UF_USE_FLOAT16
|
||||
case uf::renderer::enums::Type::HALF:
|
||||
vertexBufferRemap[i] = ((float16*) vertexPtr)[i];
|
||||
break;
|
||||
#endif
|
||||
#if UF_USE_BFLOAT16
|
||||
case uf::renderer::enums::Type::BFLOAT:
|
||||
vertexBufferRemap[i] = ((bfloat16*) vertexPtr)[i];
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
vertexBufferRemap[i] = vertexPtr[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
vertexPtr = (uint8_t*) vertexBufferRemap.data();
|
||||
vertexType = GL_FLOAT;
|
||||
vertexStride = 0;
|
||||
}
|
||||
if ( vertexPtr && vertexType != GL_FLOAT ) ::remapAttribute( vertexPtr, vertexStride, vertexType, vertexBufferRemap, drawInfo.attributes.position.descriptor.components, drawInfo.attributes.position.descriptor.type, drawInfo.descriptor.inputs.vertex.count );
|
||||
if ( normalPtr && normalType != GL_FLOAT ) ::remapAttribute( normalPtr, normalStride, normalType, normalBufferRemap, drawInfo.attributes.normal.descriptor.components, drawInfo.attributes.normal.descriptor.type, drawInfo.descriptor.inputs.vertex.count );
|
||||
if ( uvPtr && uvType != GL_FLOAT ) ::remapAttribute( uvPtr, uvStride, uvType, uvBufferRemap, drawInfo.attributes.uv.descriptor.components, drawInfo.attributes.uv.descriptor.type, drawInfo.descriptor.inputs.vertex.count );
|
||||
if ( stPtr && stType != GL_FLOAT ) ::remapAttribute( stPtr, stStride, stType, stBufferRemap, drawInfo.attributes.st.descriptor.components, drawInfo.attributes.st.descriptor.type, drawInfo.descriptor.inputs.vertex.count );
|
||||
#endif
|
||||
|
||||
if ( drawInfo.attributes.normal.pointer ) {
|
||||
@ -539,7 +556,7 @@ void ext::opengl::CommandBuffer::drawIndexed( const ext::opengl::CommandBuffer::
|
||||
GL_ERROR_CHECK(glEnableClientState(GL_NORMAL_ARRAY));
|
||||
::shadowState.normalArrayEnabled = true;
|
||||
}
|
||||
GL_ERROR_CHECK(glNormalPointer(normalType, drawInfo.attributes.normal.stride, normalPtr));
|
||||
GL_ERROR_CHECK(glNormalPointer(normalType, normalStride, normalPtr));
|
||||
} else if ( ::shadowState.normalArrayEnabled ) {
|
||||
GL_ERROR_CHECK(glDisableClientState(GL_NORMAL_ARRAY));
|
||||
::shadowState.normalArrayEnabled = false;
|
||||
@ -579,7 +596,7 @@ void ext::opengl::CommandBuffer::drawIndexed( const ext::opengl::CommandBuffer::
|
||||
::shadowState.boundTexture0 = drawInfo.textures.primary.image;
|
||||
}
|
||||
|
||||
GL_ERROR_CHECK(glTexCoordPointer(2, uvType, drawInfo.attributes.uv.stride, uvPtr));
|
||||
GL_ERROR_CHECK(glTexCoordPointer(2, uvType, uvStride, uvPtr));
|
||||
GL_ERROR_CHECK(glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, drawInfo.attributes.color.pointer ? GL_MODULATE : GL_REPLACE));
|
||||
} else {
|
||||
if ( ::shadowState.tex0Enabled ) {
|
||||
@ -618,7 +635,7 @@ void ext::opengl::CommandBuffer::drawIndexed( const ext::opengl::CommandBuffer::
|
||||
}
|
||||
|
||||
GL_ERROR_CHECK(glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE));
|
||||
GL_ERROR_CHECK(glTexCoordPointer(2, stType, drawInfo.attributes.st.stride, stPtr));
|
||||
GL_ERROR_CHECK(glTexCoordPointer(2, stType, stStride, stPtr));
|
||||
} else {
|
||||
if ( ::shadowState.tex1Enabled ) {
|
||||
GL_ERROR_CHECK(glClientActiveTexture(GL_TEXTURE1));
|
||||
|
||||
@ -420,12 +420,19 @@ void ext::vulkan::Pipeline::record( const Graphic& graphic, VkCommandBuffer comm
|
||||
|
||||
RenderMode& renderMode = ext::vulkan::getRenderMode(descriptor.renderMode, true);
|
||||
|
||||
// to-do: properly dispatch the bind point to the pipeline because for some reason it is not
|
||||
VkPipelineBindPoint bindPoint = (VkPipelineBindPoint) descriptor.bind.point;
|
||||
|
||||
bool bound = false;
|
||||
for ( auto* shader : shaders ) {
|
||||
// compute shaders
|
||||
if ( shader->descriptor.stage == VK_SHADER_STAGE_COMPUTE_BIT ) {
|
||||
if ( descriptor.bind.point == VK_PIPELINE_BIND_POINT_COMPUTE ) bound = true;
|
||||
else continue;
|
||||
if ( descriptor.bind.point == VK_PIPELINE_BIND_POINT_COMPUTE ) {
|
||||
bound = true;
|
||||
} else {
|
||||
bindPoint = VK_PIPELINE_BIND_POINT_COMPUTE;
|
||||
continue;
|
||||
}
|
||||
// raytrace shaders
|
||||
} else if (
|
||||
shader->descriptor.stage == VK_SHADER_STAGE_RAYGEN_BIT_KHR ||
|
||||
@ -434,12 +441,20 @@ void ext::vulkan::Pipeline::record( const Graphic& graphic, VkCommandBuffer comm
|
||||
shader->descriptor.stage == VK_SHADER_STAGE_ANY_HIT_BIT_KHR ||
|
||||
shader->descriptor.stage == VK_SHADER_STAGE_INTERSECTION_BIT_KHR
|
||||
) {
|
||||
if ( descriptor.bind.point == VK_PIPELINE_BIND_POINT_RAY_TRACING_KHR ) bound = true;
|
||||
else continue;
|
||||
if ( descriptor.bind.point == VK_PIPELINE_BIND_POINT_RAY_TRACING_KHR ) {
|
||||
bound = true;
|
||||
} else {
|
||||
bindPoint = VK_PIPELINE_BIND_POINT_RAY_TRACING_KHR;
|
||||
continue;
|
||||
}
|
||||
// anything else
|
||||
} else {
|
||||
if ( descriptor.bind.point == VK_PIPELINE_BIND_POINT_GRAPHICS ) bound = true;
|
||||
else continue;
|
||||
if ( descriptor.bind.point == VK_PIPELINE_BIND_POINT_GRAPHICS ) {
|
||||
bound = true;
|
||||
} else {
|
||||
bindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// automatically bind to our default push constants
|
||||
@ -459,9 +474,8 @@ void ext::vulkan::Pipeline::record( const Graphic& graphic, VkCommandBuffer comm
|
||||
}
|
||||
}
|
||||
}
|
||||
// Bind the rendering pipeline
|
||||
// The pipeline (state object) contains all states of the rendering pipeline, binding it will set all the states specified at pipeline creation time
|
||||
vkCmdBindPipeline(commandBuffer, (VkPipelineBindPoint) descriptor.bind.point, pipeline);
|
||||
|
||||
vkCmdBindPipeline(commandBuffer, bindPoint, pipeline);
|
||||
}
|
||||
void ext::vulkan::Pipeline::destroy() {
|
||||
if ( aliased ) return;
|
||||
|
||||
@ -847,6 +847,20 @@ void ext::vulkan::DeferredRenderMode::createCommandBuffers( const uf::stl::vecto
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
VkMemoryBarrier barrier = {};
|
||||
barrier.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER;
|
||||
barrier.srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT;
|
||||
barrier.dstAccessMask = VK_ACCESS_INDIRECT_COMMAND_READ_BIT | VK_ACCESS_SHADER_READ_BIT;
|
||||
|
||||
vkCmdPipelineBarrier(
|
||||
commandBuffer,
|
||||
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
|
||||
VK_PIPELINE_STAGE_DRAW_INDIRECT_BIT | VK_PIPELINE_STAGE_VERTEX_SHADER_BIT,
|
||||
0, 1, &barrier, 0, nullptr, 0, nullptr
|
||||
);
|
||||
}
|
||||
|
||||
// pre-renderpass commands
|
||||
VK_COMMAND_BUFFER_CALLBACK( CALLBACK_BEGIN, commandBuffer, frame, {
|
||||
device->UF_CHECKPOINT_MARK( commandBuffer, pod::Checkpoint::GENERIC, "callback[begin]" );
|
||||
|
||||
@ -26,34 +26,28 @@ bool ext::zlib::decompressFromFile( uf::stl::vector<uint8_t>& buffer, const uf::
|
||||
z_stream strm{};
|
||||
if (inflateInit2(&strm, 15 + 32) != Z_OK) return false;
|
||||
|
||||
size_t offset = 0;
|
||||
uint8_t outBuffer[ext::zlib::bufferSize];
|
||||
|
||||
while (offset < fileSize) {
|
||||
size_t bytesToRead = std::min(ext::zlib::bufferSize, fileSize - offset);
|
||||
uf::stl::vector<uint8_t> temp;
|
||||
if (!uf::vfs::readRange(filename, offset, bytesToRead, temp)) break;
|
||||
|
||||
strm.avail_in = (uInt)temp.size();
|
||||
strm.next_in = temp.data();
|
||||
offset += temp.size();
|
||||
bool success = uf::vfs::stream(filename, ext::zlib::bufferSize, [&](const uint8_t* data, size_t size) -> bool {
|
||||
strm.avail_in = (uInt)size;
|
||||
strm.next_in = (Bytef*)data;
|
||||
|
||||
do {
|
||||
strm.avail_out = sizeof(outBuffer);
|
||||
strm.next_out = outBuffer;
|
||||
int ret = inflate(&strm, Z_NO_FLUSH);
|
||||
if (ret == Z_STREAM_ERROR || ret == Z_DATA_ERROR || ret == Z_MEM_ERROR) {
|
||||
UF_MSG_ERROR("Zlib: inflate error on file: {}", filename);
|
||||
inflateEnd(&strm);
|
||||
return false;
|
||||
}
|
||||
if ( ret < 0 && ret != Z_BUF_ERROR ) return false;
|
||||
|
||||
size_t have = sizeof(outBuffer) - strm.avail_out;
|
||||
buffer.insert(buffer.end(), outBuffer, outBuffer + have);
|
||||
|
||||
} while (strm.avail_out == 0);
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
inflateEnd(&strm);
|
||||
return true;
|
||||
return success;
|
||||
}
|
||||
|
||||
bool ext::zlib::decompressFromFile( uf::stl::vector<uint8_t>& buffer, const uf::stl::string& filename, size_t start, size_t len ) {
|
||||
@ -63,34 +57,24 @@ bool ext::zlib::decompressFromFile( uf::stl::vector<uint8_t>& buffer, const uf::
|
||||
z_stream strm{};
|
||||
if (inflateInit2(&strm, 15 + 32) != Z_OK) return false;
|
||||
|
||||
size_t offset = 0;
|
||||
size_t uncompressedOffset = 0;
|
||||
uint8_t outBuffer[ext::zlib::bufferSize];
|
||||
|
||||
while (offset < fileSize) {
|
||||
size_t bytesToRead = std::min(ext::zlib::bufferSize, fileSize - offset);
|
||||
uf::stl::vector<uint8_t> temp;
|
||||
if (!uf::vfs::readRange(filename, offset, bytesToRead, temp)) break;
|
||||
|
||||
strm.avail_in = (uInt)temp.size();
|
||||
strm.next_in = temp.data();
|
||||
offset += temp.size();
|
||||
bool success = uf::vfs::stream(filename, ext::zlib::bufferSize, [&](const uint8_t* data, size_t size) -> bool {
|
||||
strm.avail_in = (uInt)size;
|
||||
strm.next_in = (Bytef*)data;
|
||||
|
||||
do {
|
||||
strm.avail_out = sizeof(outBuffer);
|
||||
strm.next_out = outBuffer;
|
||||
int ret = inflate(&strm, Z_NO_FLUSH);
|
||||
if (ret == Z_STREAM_ERROR || ret == Z_DATA_ERROR || ret == Z_MEM_ERROR) {
|
||||
inflateEnd(&strm);
|
||||
return false;
|
||||
}
|
||||
size_t have = sizeof(outBuffer) - strm.avail_out;
|
||||
if ( ret < 0 && ret != Z_BUF_ERROR ) return false;
|
||||
|
||||
// Calculate if this chunk overlaps with our requested range
|
||||
size_t have = sizeof(outBuffer) - strm.avail_out;
|
||||
size_t chunkStart = uncompressedOffset;
|
||||
size_t chunkEnd = uncompressedOffset + have;
|
||||
|
||||
if (chunkEnd > start && chunkStart < start + len) {
|
||||
if ( chunkEnd > start && chunkStart < start + len ) {
|
||||
size_t copyStart = (chunkStart < start) ? (start - chunkStart) : 0;
|
||||
size_t copyLen = std::min(have - copyStart, (start + len) - (chunkStart + copyStart));
|
||||
buffer.insert(buffer.end(), outBuffer + copyStart, outBuffer + copyStart + copyLen);
|
||||
@ -98,16 +82,18 @@ bool ext::zlib::decompressFromFile( uf::stl::vector<uint8_t>& buffer, const uf::
|
||||
|
||||
uncompressedOffset += have;
|
||||
|
||||
// If we've reached the end of the requested length, we can abort early!
|
||||
if (uncompressedOffset >= start + len) {
|
||||
inflateEnd(&strm);
|
||||
return true;
|
||||
}
|
||||
if ( uncompressedOffset >= start + len ) return false;
|
||||
|
||||
} while (strm.avail_out == 0);
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
inflateEnd(&strm);
|
||||
return true;
|
||||
|
||||
if ( !success && uncompressedOffset >= start + len ) return true;
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
bool ext::zlib::decompressFromFile( uf::stl::vector<uint8_t>& buffer, const uf::stl::string& filename, const uf::stl::vector<pod::Range>& ranges ) {
|
||||
@ -122,34 +108,24 @@ bool ext::zlib::decompressFromFile( uf::stl::vector<uint8_t>& buffer, const uf::
|
||||
z_stream strm{};
|
||||
if (inflateInit2(&strm, 15 + 32) != Z_OK) return false;
|
||||
|
||||
size_t offset = 0;
|
||||
size_t uncompressedOffset = 0;
|
||||
uint8_t outBuffer[ext::zlib::bufferSize];
|
||||
size_t currentRangeIdx = 0;
|
||||
uint8_t outBuffer[ext::zlib::bufferSize];
|
||||
|
||||
while (offset < fileSize && currentRangeIdx < sortedRanges.size()) {
|
||||
size_t bytesToRead = std::min(ext::zlib::bufferSize, fileSize - offset);
|
||||
uf::stl::vector<uint8_t> temp;
|
||||
if (!uf::vfs::readRange(filename, offset, bytesToRead, temp)) break;
|
||||
|
||||
strm.avail_in = (uInt)temp.size();
|
||||
strm.next_in = temp.data();
|
||||
offset += temp.size();
|
||||
bool success = uf::vfs::stream(filename, ext::zlib::bufferSize, [&](const uint8_t* data, size_t size) -> bool {
|
||||
strm.avail_in = (uInt)size;
|
||||
strm.next_in = (Bytef*)data;
|
||||
|
||||
do {
|
||||
strm.avail_out = sizeof(outBuffer);
|
||||
strm.next_out = outBuffer;
|
||||
int ret = inflate(&strm, Z_NO_FLUSH);
|
||||
if (ret < 0 && ret != Z_BUF_ERROR) {
|
||||
inflateEnd(&strm);
|
||||
return false;
|
||||
}
|
||||
size_t have = sizeof(outBuffer) - strm.avail_out;
|
||||
if ( ret < 0 && ret != Z_BUF_ERROR ) return false;
|
||||
|
||||
size_t have = sizeof(outBuffer) - strm.avail_out;
|
||||
size_t chunkStart = uncompressedOffset;
|
||||
size_t chunkEnd = uncompressedOffset + have;
|
||||
|
||||
// Check all remaining ranges against this chunk
|
||||
for (size_t i = currentRangeIdx; i < sortedRanges.size(); ++i) {
|
||||
const auto& r = sortedRanges[i];
|
||||
if (chunkEnd > r.start && chunkStart < r.start + r.len) {
|
||||
@ -158,15 +134,19 @@ bool ext::zlib::decompressFromFile( uf::stl::vector<uint8_t>& buffer, const uf::
|
||||
buffer.insert(buffer.end(), outBuffer + copyStart, outBuffer + copyStart + copyLen);
|
||||
}
|
||||
if (chunkEnd >= r.start + r.len) {
|
||||
currentRangeIdx = i + 1; // Move past completed ranges
|
||||
currentRangeIdx = i + 1;
|
||||
}
|
||||
}
|
||||
uncompressedOffset += have;
|
||||
if ( currentRangeIdx >= sortedRanges.size() ) return false;
|
||||
|
||||
} while (strm.avail_out == 0);
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
inflateEnd(&strm);
|
||||
return true;
|
||||
return success;
|
||||
}
|
||||
|
||||
bool ext::zlib::decompressFromMemory( uf::stl::vector<uint8_t>& dst, const void* src, size_t size, size_t usize ) {
|
||||
@ -188,6 +168,60 @@ bool ext::zlib::decompressFromMemory( uf::stl::vector<uint8_t>& dst, const void*
|
||||
return (ret == Z_STREAM_END || ret == Z_OK);
|
||||
}
|
||||
|
||||
bool ext::zlib::decompressScatter( const uf::stl::string& filename, uf::stl::vector<pod::ScatterRequest>& requests ) {
|
||||
if ( requests.empty() ) return true;
|
||||
|
||||
std::sort(requests.begin(), requests.end(), [](const pod::ScatterRequest& a, const pod::ScatterRequest& b) {
|
||||
return a.start < b.start;
|
||||
});
|
||||
|
||||
z_stream strm{};
|
||||
if ( inflateInit2(&strm, 15 + 32) != Z_OK ) return false;
|
||||
|
||||
size_t uncompressedOffset = 0;
|
||||
size_t currentReqIdx = 0;
|
||||
uint8_t outBuffer[ext::zlib::bufferSize];
|
||||
|
||||
bool success = uf::vfs::stream(filename, ext::zlib::bufferSize, [&](const uint8_t* data, size_t size) -> bool {
|
||||
strm.avail_in = (uInt)size;
|
||||
strm.next_in = (Bytef*)data;
|
||||
|
||||
do {
|
||||
strm.avail_out = sizeof(outBuffer);
|
||||
strm.next_out = outBuffer;
|
||||
int ret = inflate(&strm, Z_NO_FLUSH);
|
||||
if ( ret < 0 && ret != Z_BUF_ERROR ) return false;
|
||||
|
||||
size_t have = sizeof(outBuffer) - strm.avail_out;
|
||||
size_t chunkStart = uncompressedOffset;
|
||||
size_t chunkEnd = uncompressedOffset + have;
|
||||
|
||||
for ( size_t i = currentReqIdx; i < requests.size(); ++i ) {
|
||||
auto& req = requests[i];
|
||||
if ( chunkEnd > req.start && chunkStart < req.start + req.len ) {
|
||||
size_t copyStart = (chunkStart < req.start) ? (req.start - chunkStart) : 0;
|
||||
size_t copyLen = std::min(have - copyStart, (req.start + req.len) - (chunkStart + copyStart));
|
||||
|
||||
size_t destOffset = (chunkStart + copyStart) - req.start;
|
||||
std::memcpy(req.dest + destOffset, outBuffer + copyStart, copyLen);
|
||||
}
|
||||
if ( chunkEnd >= req.start + req.len && i == currentReqIdx ) {
|
||||
currentReqIdx++;
|
||||
}
|
||||
}
|
||||
|
||||
uncompressedOffset += have;
|
||||
if ( currentReqIdx >= requests.size() ) return false;
|
||||
|
||||
} while (strm.avail_out == 0);
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
inflateEnd(&strm);
|
||||
return success;
|
||||
}
|
||||
|
||||
size_t ext::zlib::compressToFile( const uf::stl::string& filename, const void* data, size_t size ) {
|
||||
z_stream strm{};
|
||||
// 31 means gzip format
|
||||
|
||||
@ -170,6 +170,40 @@ bool uf::io::readAsBuffer( uf::stl::vector<uint8_t>& buffer, const uf::stl::str
|
||||
return true;
|
||||
}
|
||||
|
||||
bool uf::io::readScatter( const uf::stl::string& filename, uf::stl::vector<pod::ScatterRequest>& requests ) {
|
||||
#if UF_ENV_DREAMCAST
|
||||
const size_t THRESHOLD = 2 * 1024 * 1024;
|
||||
#else
|
||||
const size_t THRESHOLD = 16 * 1024 * 1024;
|
||||
#endif
|
||||
|
||||
uf::stl::string extension = uf::io::extension(filename);
|
||||
bool isZlib = (extension == "gz");
|
||||
size_t fileSize = uf::io::size(filename);
|
||||
|
||||
if ( 0 < fileSize && fileSize <= THRESHOLD ) {
|
||||
uf::stl::vector<uint8_t> fullBuffer;
|
||||
if ( isZlib ) ext::zlib::decompressFromFile(fullBuffer, filename);
|
||||
else uf::vfs::read(filename, fullBuffer);
|
||||
|
||||
for ( auto& req : requests ) {
|
||||
if ( req.start + req.len <= fullBuffer.size() ) {
|
||||
std::memcpy(req.dest, fullBuffer.data() + req.start, req.len);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if ( isZlib ) return ext::zlib::decompressScatter(filename, requests);
|
||||
|
||||
for ( auto& req : requests ) {
|
||||
uf::stl::vector<uint8_t> temp;
|
||||
uf::vfs::readRange( filename, req.start, req.len, temp );
|
||||
std::memcpy(req.dest, temp.data(), temp.size());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
size_t uf::io::write( const uf::stl::string& filename, const void* buffer, size_t size ) {
|
||||
uf::stl::string extension = uf::io::extension( filename );
|
||||
if ( extension == "gz" || extension == "lz4" ) return uf::io::compress( filename, buffer, size );
|
||||
|
||||
@ -94,6 +94,22 @@ namespace {
|
||||
buffer.resize(currentOffset);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool vfs_stream( pod::Mount& mount, const uf::stl::string& file, size_t chunkSize, std::function<bool(const uint8_t* data, size_t size)> callback ) {
|
||||
uf::stl::string path = mount.path + file;
|
||||
std::ifstream is(path, std::ios::binary);
|
||||
if ( !is.is_open() ) return false;
|
||||
|
||||
uf::stl::vector<uint8_t> buffer(chunkSize);
|
||||
while ( is.good() ) {
|
||||
is.read((char*)buffer.data(), chunkSize);
|
||||
size_t bytesRead = is.gcount();
|
||||
if ( bytesRead == 0 ) break;
|
||||
|
||||
if ( !callback(buffer.data(), bytesRead) ) break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
pod::Mount uf::vfs::createDiskMount( const uf::stl::string& uri, int priority) {
|
||||
@ -113,7 +129,8 @@ pod::Mount uf::vfs::createDiskMount( const uf::stl::string& uri, int priority) {
|
||||
.write = ::vfs_write,
|
||||
.mkdir = ::vfs_mkdir,
|
||||
.readRange = ::vfs_readRange,
|
||||
.readRanges = ::vfs_readRanges
|
||||
.readRanges = ::vfs_readRanges,
|
||||
.stream = ::vfs_stream
|
||||
};
|
||||
}
|
||||
|
||||
@ -277,6 +294,7 @@ bool uf::vfs::readRange( const uf::stl::string& path, size_t start, size_t len,
|
||||
|
||||
uf::stl::vector<uint8_t> fullBuffer;
|
||||
if ( !mount.read( mount, relative, fullBuffer ) ) continue;
|
||||
UF_MSG_DEBUG("hitting fallback: {}", path);
|
||||
|
||||
if ( start < fullBuffer.size() ) {
|
||||
size_t actualLen = std::min(len, fullBuffer.size() - start);
|
||||
@ -302,6 +320,7 @@ bool uf::vfs::readRanges( const uf::stl::string& path, const uf::stl::vector<pod
|
||||
|
||||
uf::stl::vector<uint8_t> fullBuffer;
|
||||
if ( !mount.read( mount, relative, fullBuffer ) ) continue;
|
||||
UF_MSG_DEBUG("hitting fallback: {}", path);
|
||||
|
||||
size_t totalBytes = 0;
|
||||
for ( const auto& r : ranges ) {
|
||||
@ -324,6 +343,35 @@ bool uf::vfs::readRanges( const uf::stl::string& path, const uf::stl::vector<pod
|
||||
}
|
||||
return false;
|
||||
}
|
||||
bool uf::vfs::stream( const uf::stl::string& path, size_t chunkSize, std::function<bool(const uint8_t* data, size_t size)> callback ) {
|
||||
uf::stl::string prefix, relative;
|
||||
uf::io::splitUri(path, prefix, relative);
|
||||
for ( auto& mount : mounts ) {
|
||||
if ( prefix.empty() && mount.priority < 0 ) continue;
|
||||
if ( prefix.empty() || mount.prefix == prefix ) {
|
||||
if ( !mount.exists( mount, relative ) ) continue;
|
||||
if ( mount.stream ) return mount.stream( mount, relative, chunkSize, callback );
|
||||
|
||||
if ( !mount.read ) continue;
|
||||
UF_MSG_DEBUG("hitting fallback: {}", path);
|
||||
|
||||
uf::stl::vector<uint8_t> fullBuffer;
|
||||
if ( !mount.read( mount, relative, fullBuffer ) ) continue;
|
||||
|
||||
size_t offset = 0;
|
||||
size_t totalSize = fullBuffer.size();
|
||||
|
||||
while ( offset < totalSize ) {
|
||||
size_t currentChunkSize = std::min(chunkSize, totalSize - offset);
|
||||
if ( !callback(fullBuffer.data() + offset, currentChunkSize) ) break;
|
||||
offset += currentChunkSize;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
uf::stl::string uf::vfs::resolveBase( const uf::stl::string& path ) {
|
||||
uf::stl::string prefix, relative;
|
||||
|
||||
@ -288,6 +288,48 @@ void uf::Mesh::interleave() {
|
||||
updateDescriptor();
|
||||
}
|
||||
|
||||
void uf::Mesh::prune( const uf::stl::vector<uf::stl::string>& keep ) {
|
||||
uf::stl::vector<size_t> deadAttributes;
|
||||
uf::stl::vector<int32_t> deadBuffers;
|
||||
|
||||
for ( size_t i = 0; i < vertex.attributes.size(); ++i ) {
|
||||
auto& attribute = vertex.attributes[i];
|
||||
if ( std::find( keep.begin(), keep.end(), attribute.descriptor.name ) != keep.end() ) continue;
|
||||
|
||||
deadAttributes.push_back(i);
|
||||
deadBuffers.push_back(attribute.buffer);
|
||||
}
|
||||
|
||||
std::sort(deadAttributes.rbegin(), deadAttributes.rend());
|
||||
std::sort(deadBuffers.rbegin(), deadBuffers.rend());
|
||||
|
||||
for ( auto idx : deadAttributes ) {
|
||||
vertex.attributes.erase(vertex.attributes.begin() + idx);
|
||||
}
|
||||
|
||||
for ( auto bufID : deadBuffers ) {
|
||||
buffers.erase(buffers.begin() + bufID);
|
||||
}
|
||||
|
||||
//
|
||||
auto remap_input = [&](uf::Mesh::Input& input) {
|
||||
for (auto& attr : input.attributes) {
|
||||
int32_t shift = 0;
|
||||
for (int32_t db : deadBuffers) {
|
||||
if (attr.buffer > db) shift++;
|
||||
}
|
||||
attr.buffer -= shift;
|
||||
}
|
||||
};
|
||||
|
||||
remap_input(vertex);
|
||||
remap_input(index);
|
||||
remap_input(instance);
|
||||
remap_input(indirect);
|
||||
|
||||
updateDescriptor();
|
||||
}
|
||||
|
||||
void uf::Mesh::clearAttribute( uf::Mesh::Input& input, const uf::Mesh::Attribute& attribute ) {
|
||||
for ( size_t i = 0; i < input.attributes.size(); ++i ) if ( input.attributes[i].descriptor == attribute.descriptor ) return clearAttribute( input, i );
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user