From e0fbed4a14cc97cc7537da6643b3b13f198fd5be Mon Sep 17 00:00:00 2001 From: ecker Date: Sat, 20 Jun 2026 23:10:53 -0500 Subject: [PATCH] more fixes (chasing down demons because of forgetting to support BGR888......), theoretical HDR image saving/loading and animated textures (to-do: actually animate them) --- bin/data/scenes/sourceengine/cs_office.json | 4 +- .../scenes/sourceengine/sourceengine.json | 4 +- bin/data/shaders/common/functions.h | 61 ++- bin/data/shaders/common/structs.h | 2 +- bin/data/shaders/display/deferred/comp/comp.h | 15 + bin/data/shaders/graph/base/vert.h | 4 +- engine/inc/uf/engine/graph/mesh.inl | 12 +- engine/inc/uf/utils/image/image.h | 5 + engine/inc/uf/utils/mesh/mesh.h | 67 +-- engine/src/engine/asset/asset.cpp | 14 +- engine/src/engine/graph/animation.cpp | 10 +- engine/src/engine/graph/decode.cpp | 12 +- engine/src/engine/graph/encode.cpp | 1 + engine/src/engine/graph/graph.cpp | 17 +- engine/src/ext/texconv/texconv.cpp | 16 +- engine/src/ext/valve/bsp.cpp | 37 +- engine/src/ext/valve/common.cpp | 5 + engine/src/ext/valve/mdl.cpp | 97 +++-- engine/src/ext/valve/vtf.cpp | 406 ++++++++++++------ engine/src/ext/vulkan/texture.cpp | 53 +-- engine/src/utils/image/image.cpp | 158 +++---- 21 files changed, 577 insertions(+), 423 deletions(-) diff --git a/bin/data/scenes/sourceengine/cs_office.json b/bin/data/scenes/sourceengine/cs_office.json index af8b3c8d..398df6e8 100644 --- a/bin/data/scenes/sourceengine/cs_office.json +++ b/bin/data/scenes/sourceengine/cs_office.json @@ -1,7 +1,7 @@ { "import": "./base_sourceengine.json", "assets": [ - { "filename": "./maps/cs_office.bsp" } - // { "filename": "./maps/cs_office/graph.json" } + // { "filename": "./maps/cs_office.bsp" } + { "filename": "./maps/cs_office/graph.json" } ] } \ No newline at end of file diff --git a/bin/data/scenes/sourceengine/sourceengine.json b/bin/data/scenes/sourceengine/sourceengine.json index 0313721d..89c57145 100644 --- a/bin/data/scenes/sourceengine/sourceengine.json +++ b/bin/data/scenes/sourceengine/sourceengine.json @@ -2,7 +2,7 @@ // "import": "./rp_downtown_v2.json" // "import": "./ss2_medsci1.json" // "import": "./mds_mcdonalds.json" -// "import": "./cs_office.json" - "import": "./de_dust2.json" + "import": "./cs_office.json" +// "import": "./de_dust2.json" // "import": "./gm_construct.json" } \ No newline at end of file diff --git a/bin/data/shaders/common/functions.h b/bin/data/shaders/common/functions.h index 90b47e69..bb4baa1b 100644 --- a/bin/data/shaders/common/functions.h +++ b/bin/data/shaders/common/functions.h @@ -402,8 +402,6 @@ uvec4 uvec2_16x4( uvec2 i ) { void populateSurface( InstanceAddresses addresses, uvec3 indices ) { Triangle triangle; Vertex points[3]; - - float uvHandedness = 1.0; if ( isValidAddress(addresses.position) ) { VPos buf = VPos(nonuniformEXT(addresses.position)); #pragma unroll 3 @@ -419,31 +417,38 @@ void populateSurface( InstanceAddresses addresses, uvec3 indices ) { #pragma unroll 3 for ( uint _ = 0; _ < 3; ++_ ) points[_].st = buf.v[indices[_]]; } + + vec3 e0 = points[1].position - points[0].position; + vec3 e1 = points[2].position - points[0].position; + vec2 dUv1 = points[1].uv - points[0].uv; + vec2 dUv2 = points[2].uv - points[0].uv; + float det = (dUv1.x * dUv2.y - dUv1.y * dUv2.x); + float handedness = (det < 0.0) ? -1.0 : 1.0; + if ( isValidAddress(addresses.normal) ) { VNormal buf = VNormal(nonuniformEXT(addresses.normal)); #pragma unroll 3 for ( uint _ = 0; _ < 3; ++_ ) points[_].normal = vec3( buf.v[indices[_]*3+0], buf.v[indices[_]*3+1], buf.v[indices[_]*3+2] ); + } else { + vec3 normal = normalize(cross(e0, e1)); + #pragma unroll 3 + for ( uint _ = 0; _ < 3; ++_ ) points[_].normal = normal; } if ( isValidAddress(addresses.tangent) ) { VTangent buf = VTangent(nonuniformEXT(addresses.tangent)); #pragma unroll 3 - for ( uint _ = 0; _ < 3; ++_ ) points[_].tangent = vec3( buf.v[indices[_]*3+0], buf.v[indices[_]*3+1], buf.v[indices[_]*3+2] ); + for ( uint _ = 0; _ < 3; ++_ ) points[_].tangent = vec4( buf.v[indices[_]*4+0], buf.v[indices[_]*4+1], buf.v[indices[_]*4+2], buf.v[indices[_]*4+3] ); } else { - vec3 edge1 = points[1].position - points[0].position; - vec3 edge2 = points[2].position - points[0].position; - vec2 deltaUV1 = points[1].uv - points[0].uv; - vec2 deltaUV2 = points[2].uv - points[0].uv; - - float det = (deltaUV1.x * deltaUV2.y - deltaUV1.y * deltaUV2.x); - float r = 1.0f / det; - vec3 tangent_tri = (edge1 * deltaUV2.y - edge2 * deltaUV1.y) * r; - - uvHandedness = (det < 0.0) ? -1.0 : 1.0; + vec3 tangent = (e0 * dUv2.y - e1 * dUv1.y); + if ( abs(det) < 0.000001 ) { + tangent = abs(points[0].normal.y) < 0.999 ? vec3(0, 1, 0) : vec3(1, 0, 0); + } else { + tangent /= det; + } #pragma unroll 3 - for ( uint _ = 0; _ < 3; ++_ ) points[_].tangent = tangent_tri; + for ( uint _ = 0; _ < 3; ++_ ) points[_].tangent = vec4(tangent, handedness); } - #if BARYCENTRIC_CALCULATE { const vec3 p = vec3(inverse( surface.object.model ) * vec4(surface.position.world, 1)); @@ -470,12 +475,12 @@ void populateSurface( InstanceAddresses addresses, uvec3 indices ) { } #endif - // triangle.geomNormal = normalize(cross(points[1].position - points[0].position, points[2].position - points[0].position)); - triangle.point.position = /*triangle.*/points[0].position * surface.barycentric[0] + /*triangle.*/points[1].position * surface.barycentric[1] + /*triangle.*/points[2].position * surface.barycentric[2]; - triangle.point.normal = /*triangle.*/points[0].normal * surface.barycentric[0] + /*triangle.*/points[1].normal * surface.barycentric[1] + /*triangle.*/points[2].normal * surface.barycentric[2]; - triangle.point.uv = /*triangle.*/points[0].uv * surface.barycentric[0] + /*triangle.*/points[1].uv * surface.barycentric[1] + /*triangle.*/points[2].uv * surface.barycentric[2]; - triangle.point.st = /*triangle.*/points[0].st * surface.barycentric[0] + /*triangle.*/points[1].st * surface.barycentric[1] + /*triangle.*/points[2].st * surface.barycentric[2]; - triangle.point.tangent = /*triangle.*/points[0].tangent * surface.barycentric[0] + /*triangle.*/points[1].tangent * surface.barycentric[1] + /*triangle.*/points[2].tangent * surface.barycentric[2]; + triangle.point.position = points[0].position * surface.barycentric[0] + points[1].position * surface.barycentric[1] + points[2].position * surface.barycentric[2]; + triangle.point.normal = points[0].normal * surface.barycentric[0] + points[1].normal * surface.barycentric[1] + points[2].normal * surface.barycentric[2]; + triangle.point.uv = points[0].uv * surface.barycentric[0] + points[1].uv * surface.barycentric[1] + points[2].uv * surface.barycentric[2]; + triangle.point.st = points[0].st * surface.barycentric[0] + points[1].st * surface.barycentric[1] + points[2].st * surface.barycentric[2]; + triangle.point.tangent = points[0].tangent * surface.barycentric[0] + points[1].tangent * surface.barycentric[1] + points[2].tangent * surface.barycentric[2]; + // bind position (seems to muck with the skybox + fog) #if 0 && BARYCENTRIC_CALCULATE @@ -490,11 +495,11 @@ void populateSurface( InstanceAddresses addresses, uvec3 indices ) { // surface.normal.eye = vec3( VIEW_MATRIX * vec4(surface.normal.world, 0.0) ); } // bind tangent - if ( triangle.point.tangent != vec3(0) ) { - surface.tangent.world = normalize(vec3( surface.object.model * vec4(triangle.point.tangent, 0.0) )); + { + surface.tangent.world = vec3( surface.object.model * vec4(triangle.point.tangent.xyz, 0.0) ); surface.tangent.world = normalize(surface.tangent.world - dot(surface.tangent.world, surface.normal.world) * surface.normal.world); + vec3 bitangent = normalize(cross(surface.normal.world, surface.tangent.world)) * sign(triangle.point.tangent.w); - vec3 bitangent = normalize(vec3( surface.object.model * vec4(cross( triangle.point.normal, triangle.point.tangent ) * uvHandedness, 0.0) )); surface.tbn = mat3(surface.tangent.world, bitangent, surface.normal.world); } // bind UVs @@ -598,13 +603,7 @@ void populateSurface( InstanceAddresses addresses, uvec3 indices ) { #endif float pixelSize = abs(surface.position.eye.z) * 2.0 / (proj[1][1] * float(size.y)); - - vec3 e0 = points[1].position - points[0].position; - vec3 e1 = points[2].position - points[0].position; float geomArea = length(cross(e0, e1)); - - vec2 dUv1 = points[1].uv - points[0].uv; - vec2 dUv2 = points[2].uv - points[0].uv; float uvArea = abs(dUv1.x * dUv2.y - dUv2.x * dUv1.y); float uvPerMeter = sqrt(uvArea / max(geomArea, 0.00001)); float fallback = pixelSize * uvPerMeter; @@ -636,7 +635,7 @@ void populateSurface( uint instanceID, uint primitiveID ) { #pragma unroll 3 - for ( uint _ = 0; _ < 3; ++_ ) /*triangle.*/indices[_] += drawCommand.vertexID; + for ( uint _ = 0; _ < 3; ++_ ) indices[_] += drawCommand.vertexID; populateSurface( addresses, indices ); } diff --git a/bin/data/shaders/common/structs.h b/bin/data/shaders/common/structs.h index 664c7ca3..f25afe1c 100644 --- a/bin/data/shaders/common/structs.h +++ b/bin/data/shaders/common/structs.h @@ -303,7 +303,7 @@ struct Vertex { uint color; vec2 st; vec3 normal; - vec3 tangent; + vec4 tangent; uvec2 joints; vec4 weights; }; diff --git a/bin/data/shaders/display/deferred/comp/comp.h b/bin/data/shaders/display/deferred/comp/comp.h index a7415f59..7d8feb95 100644 --- a/bin/data/shaders/display/deferred/comp/comp.h +++ b/bin/data/shaders/display/deferred/comp/comp.h @@ -179,6 +179,21 @@ void postProcess() { } } +/* + { + const Material material = materials[surface.instance.materialID >= materials.length() ? 0 : surface.instance.materialID]; + + int cubemapIndex = -1; + if ( 0 <= surface.instance.cubemapID ) cubemapIndex = surface.instance.cubemapID; + else if ( 0 <= material.indexCubemap ) cubemapIndex = material.indexCubemap; + + if ( 0 <= cubemapIndex ) { + const Texture texture = textures[cubemapIndex]; + outFragColor.rgb = textureLod(samplerCubemaps[nonuniformEXT(texture.index)], surface.ray.direction, 0).rgb; + } + } +*/ + IMAGE_STORE( imageColor, outFragColor ); //IMAGE_STORE( imageBright, outFragBright ); IMAGE_STORE( imageMotion, vec4(outFragMotion, 0, 0) ); diff --git a/bin/data/shaders/graph/base/vert.h b/bin/data/shaders/graph/base/vert.h index f97b48a8..453ad413 100644 --- a/bin/data/shaders/graph/base/vert.h +++ b/bin/data/shaders/graph/base/vert.h @@ -10,7 +10,7 @@ layout (location = 1) in vec2 inUv; layout (location = 2) in vec4 inColor; layout (location = 3) in vec2 inSt; layout (location = 4) in vec3 inNormal; -layout (location = 5) in vec3 inTangent; +layout (location = 5) in vec4 inTangent; #if SKINNED layout (location = 6) in uvec4 inJoints; layout (location = 7) in vec4 inWeights; @@ -104,6 +104,6 @@ void main() { outSt = inSt; outColor = inColor * object.color; outNormal = normalize(vec3(model * vec4(inNormal.xyz, 0.0))); - outTangent = normalize(vec3(model * vec4(inTangent.xyz, 0.0))); + outTangent = normalize(vec3(model * vec4(inTangent))); outBitangent = normalize(vec3(model * vec4(cross( inNormal.xyz, inTangent.xyz ), 0.0))); } \ No newline at end of file diff --git a/engine/inc/uf/engine/graph/mesh.inl b/engine/inc/uf/engine/graph/mesh.inl index d4a994d0..8863417d 100644 --- a/engine/inc/uf/engine/graph/mesh.inl +++ b/engine/inc/uf/engine/graph/mesh.inl @@ -7,7 +7,7 @@ namespace uf { pod::Vector4ub color{ (uint8_t) ~0, (uint8_t) ~0, (uint8_t) ~0, (uint8_t) ~0 }; pod::Vector2f st{}; pod::Vector3f normal{}; - pod::Vector3f tangent{}; + pod::Vector4f tangent{}; static UF_API uf::stl::vector descriptor; static UF_API Base interpolate( const Base& p1, const Base& p2, float t ); @@ -18,7 +18,7 @@ namespace uf { pod::Vector4ub color{ (uint8_t) ~0, (uint8_t) ~0, (uint8_t) ~0, (uint8_t) ~0 }; pod::Vector2f st{}; pod::Vector3f normal{}; - pod::Vector3f tangent{}; + pod::Vector4f tangent{}; pod::Vector4us joints{}; pod::Vector4f weights{}; @@ -32,7 +32,7 @@ namespace uf { pod::Vector4ub color{ (uint8_t) ~0, (uint8_t) ~0, (uint8_t) ~0, (uint8_t) ~0 }; pod::Vector2f16 st{}; pod::Vector3f16 normal{}; - pod::Vector3f16 tangent{}; + pod::Vector4f16 tangent{}; static UF_API uf::stl::vector descriptor; static UF_API Base_16f interpolate( const Base_16f& p1, const Base_16f& p2, float t ); @@ -43,7 +43,7 @@ namespace uf { pod::Vector4ub color{ (uint8_t) ~0, (uint8_t) ~0, (uint8_t) ~0, (uint8_t) ~0 }; pod::Vector2f16 st{}; pod::Vector3f16 normal{}; - pod::Vector3f16 tangent{}; + pod::Vector4f16 tangent{}; pod::Vector4us joints{}; pod::Vector3f16 weights{}; @@ -57,7 +57,7 @@ namespace uf { pod::Vector4ub color{ (uint8_t) ~0, (uint8_t) ~0, (uint8_t) ~0, (uint8_t) ~0 }; pod::Vector2us st{}; pod::Vector3us normal{}; - pod::Vector3us tangent{}; + pod::Vector4us tangent{}; static UF_API uf::stl::vector descriptor; static UF_API Base_u16q interpolate( const Base_u16q& p1, const Base_u16q& p2, float t ); @@ -68,7 +68,7 @@ namespace uf { pod::Vector4ub color{ (uint8_t) ~0, (uint8_t) ~0, (uint8_t) ~0, (uint8_t) ~0 }; pod::Vector2us st{}; pod::Vector3us normal{}; - pod::Vector3us tangent{}; + pod::Vector4us tangent{}; pod::Vector4us joints{}; pod::Vector3us weights{}; diff --git a/engine/inc/uf/utils/image/image.h b/engine/inc/uf/utils/image/image.h index 9935a968..de4d0a61 100644 --- a/engine/inc/uf/utils/image/image.h +++ b/engine/inc/uf/utils/image/image.h @@ -15,6 +15,7 @@ namespace pod { size_t bpp = 8 * 4; size_t channels = 4; size_t format = 0; + size_t layers = 1; }; } @@ -34,6 +35,8 @@ namespace uf { pod::Image::pixel_t UF_API at( pod::Image&, const pod::Vector2ui& at ); uf::stl::string UF_API hash( const pod::Image& ); + void UF_API layers( pod::Image&, size_t ); + void UF_API flip( pod::Image& ); void UF_API padToPowerOfTwo( pod::Image& ); void UF_API convert( pod::Image&, const uf::stl::string&, const uf::stl::string& = "rgba" ); @@ -92,6 +95,8 @@ namespace uf { uf::stl::string getHash() const; size_t getFormat() const; + void setLayers( size_t ); + Image::pixel_t at( const pod::Vector2ui& at ); // Modifiers diff --git a/engine/inc/uf/utils/mesh/mesh.h b/engine/inc/uf/utils/mesh/mesh.h index 47b676af..abc1e207 100644 --- a/engine/inc/uf/utils/mesh/mesh.h +++ b/engine/inc/uf/utils/mesh/mesh.h @@ -795,41 +795,21 @@ void uf::mesh::tangents( uf::stl::vector& vertices ) { auto uv20 = uv2 - uv0; auto det = (uv10.x * uv20.y - uv10.y * uv20.x); - float r = 1.0f / det; + + float r = (det != 0.0f) ? (1.0f / det) : 0.0f; + auto t = (p10 * uv20.y - p20 * uv10.y) * r; auto b = (p20 * uv10.x - p10 * uv20.x) * r; for ( auto j = 0; j < 3; ++j ) { auto& n = vertices[idx[j]].normal; auto& tangent = vertices[idx[j]].tangent; - tangent = uf::vector::normalize(t - n * uf::vector::dot(n, t)); - if ( uf::vector::dot( uf::vector::cross(n, tangent), b) < 0.0f ) tangent = -tangent; + + auto t_ortho = uf::vector::normalize(t - n * uf::vector::dot(n, t)); + float w = (uf::vector::dot( uf::vector::cross(n, t_ortho), b) < 0.0f) ? -1.0f : 1.0f; + + tangent = { t_ortho.x, t_ortho.y, t_ortho.z, w }; } - /* - pod::Vector3f position[3] = { - vertices[idx[0]].position, vertices[idx[1]].position, vertices[idx[2]].position - }; - pod::Vector2f uv[3] = { - vertices[idx[0]].uv, vertices[idx[1]].uv, vertices[idx[2]].uv - }; - - pod::Vector3f dPosition[2] = { position[1] - position[0], position[2] - position[0] }; - pod::Vector2f dUV[2] = { uv[1] - uv[0], uv[2] - uv[0] }; - - float det = (dUV[0].x * dUV[1].y - dUV[0].y * dUV[1].x); - if ( det == 0.0f ) continue; - float r = 1.0f / det; - - auto t = (dPosition[0] * dUV[1].y - dPosition[1] * dUV[0].y) * r; - auto b = (dPosition[1] * dUV[0].x - dPosition[0] * dUV[1].x) * r; - - for ( auto j = 0; j < 3; ++j ) { - auto& normal = vertices[idx[j]].normal; - auto& tangent = vertices[idx[j]].tangent; - tangent = uf::vector::normalize(t - normal * uf::vector::dot(normal, t)); - if ( uf::vector::dot(uf::vector::cross(normal, tangent), b) < 0.0f ) tangent = -tangent; - } - */ } } @@ -856,36 +836,21 @@ void uf::mesh::tangents( uf::stl::vector& vertices, const uf::stl::vector& auto uv20 = uv2 - uv0; auto det = (uv10.x * uv20.y - uv10.y * uv20.x); - float r = 1.0f / det; + + float r = (det != 0.0f) ? (1.0f / det) : 0.0f; + auto t = (p10 * uv20.y - p20 * uv10.y) * r; auto b = (p20 * uv10.x - p10 * uv20.x) * r; for ( auto j = 0; j < 3; ++j ) { auto& n = vertices[idx[j]].normal; auto& tangent = vertices[idx[j]].tangent; - tangent = uf::vector::normalize(t - n * uf::vector::dot(n, t)); - if ( uf::vector::dot( uf::vector::cross(n, tangent), b) < 0.0f ) tangent = -tangent; + + auto t_ortho = uf::vector::normalize(t - n * uf::vector::dot(n, t)); + float w = (uf::vector::dot( uf::vector::cross(n, t_ortho), b) < 0.0f) ? -1.0f : 1.0f; + + tangent = { t_ortho.x, t_ortho.y, t_ortho.z, w }; } - /* - pod::Vector3f position[3] = { vertices[idx[0]].position, vertices[idx[1]].position, vertices[idx[2]].position }; - pod::Vector2f uv[3] = { vertices[idx[0]].uv, vertices[idx[1]].uv, vertices[idx[2]].uv }; - pod::Vector3f dPosition[2] = { position[1] - position[0], position[2] - position[0] }; - pod::Vector2f dUV[2] = { uv[1] - uv[0], uv[2] - uv[0] }; - - float det = (dUV[0].x * dUV[1].y - dUV[0].y * dUV[1].x); - if ( det == 0.0f ) continue; - float r = 1.0f / det; - - auto t = (dPosition[0] * dUV[1].y - dPosition[1] * dUV[0].y) * r; - auto b = (dPosition[1] * dUV[0].x - dPosition[0] * dUV[1].x) * r; - - for ( auto j = 0; j < 3; ++j ) { - auto& normal = vertices[idx[j]].normal; - auto& tangent = vertices[idx[j]].tangent; - tangent = uf::vector::normalize(t - normal * uf::vector::dot(normal, t)); - if ( uf::vector::dot(uf::vector::cross(normal, tangent), b) < 0.0f ) tangent = -tangent; - } - */ } } diff --git a/engine/src/engine/asset/asset.cpp b/engine/src/engine/asset/asset.cpp index 8ca3c900..12d0a670 100644 --- a/engine/src/engine/asset/asset.cpp +++ b/engine/src/engine/asset/asset.cpp @@ -71,13 +71,13 @@ void uf::asset::processQueue() { bool async = uf::asset::asyncQueue; // a bit buggy auto tasks = uf::thread::schedule(async ? uf::thread::asyncThreadName : uf::thread::mainThreadName, !true); - if ( !finishedJobs.empty() ) { - tasks.queue([jobs = std::move(finishedJobs)]() { - for ( auto& job : jobs ) { - uf::hooks.call( job.callback, job.payload ); - } - }); - } + if ( !finishedJobs.empty() ) { + tasks.queue([jobs = std::move(finishedJobs)]() { + for ( auto& job : jobs ) { + uf::hooks.call( job.callback, job.payload ); + } + }); + } for ( auto& job : jobs ) tasks.queue([=]{ auto callback = job.callback; auto type = job.type; diff --git a/engine/src/engine/graph/animation.cpp b/engine/src/engine/graph/animation.cpp index 7ff76e96..69ed1df7 100644 --- a/engine/src/engine/graph/animation.cpp +++ b/engine/src/engine/graph/animation.cpp @@ -152,14 +152,14 @@ void uf::graph::animate( pod::Graph& graph, const uf::stl::string& _name, float uf::stl::string name = key + _name; if ( storage.animations.map.count( name ) == 0 ) { - ::loadAnimation( graph, name ); + ::loadAnimation( graph, name ); } if ( storage.animations.map.count( name ) > 0 ) { auto& animation = storage.animations.map[name]; - if ( !animation.samplers.empty() && animation.samplers[0].inputs.empty() ) { - ::loadAnimation( graph, name ); - } + if ( !animation.samplers.empty() && animation.samplers[0].inputs.empty() ) { + ::loadAnimation( graph, name ); + } // if already playing, ignore it if ( !graph.sequence.empty() && graph.sequence.front() == name ) return; @@ -262,7 +262,7 @@ void uf::graph::updateAnimation( pod::Graph& graph, pod::Node& node ) { auto& skinName = graph.skins[node.skin]; auto& skin = storage.skins[skinName]; auto objectKeyName = ::keyedID(node.object); - auto& joints = storage.joints[objectKeyName]; + auto& joints = storage.joints[objectKeyName]; joints.resize( skin.joints.size() ); for ( size_t i = 0; i < skin.joints.size(); ++i ) { auto nodeID = skin.joints[i]; diff --git a/engine/src/engine/graph/decode.cpp b/engine/src/engine/graph/decode.cpp index 88773181..d1c5aed0 100644 --- a/engine/src/engine/graph/decode.cpp +++ b/engine/src/engine/graph/decode.cpp @@ -29,7 +29,7 @@ namespace { uf::Image image; uf::stl::string filename = ""; - size_t offset = 0, length = 0; + size_t offset = 0, length = 0, layers = json["layers"].as(1); uf::stl::string formatHint = ""; #if UF_ENV_DREAMCAST @@ -51,6 +51,7 @@ namespace { size_t channels = json["channels"].as(); auto pixels = uf::base64::decode( json["data"].as() ); image.loadFromBuffer( &pixels[0], size, bpp, channels, true ); + image.setLayers( layers ); return image; } @@ -69,6 +70,7 @@ namespace { } uf::image::open( image, buffer, formatHint, false ); + uf::image::layers( image, layers ); image.setFilename(fullPath); } @@ -76,10 +78,10 @@ namespace { } pod::Animation decodeAnimation( ext::json::Value& json, pod::Graph& graph, const uf::stl::string& animName, const uf::stl::vector& megaBuffer ) { - pod::Animation animation = {}; - animation.name = json["name"].as(animation.name); - animation.start = json["start"].as(0.0f); - animation.end = json["end"].as(1.0f); + pod::Animation animation = {}; + animation.name = json["name"].as(animation.name); + animation.start = json["start"].as(0.0f); + animation.end = json["end"].as(1.0f); uf::stl::string binPath = ""; if (json["buffer"].is()) { diff --git a/engine/src/engine/graph/encode.cpp b/engine/src/engine/graph/encode.cpp index a3d8f329..96dc0caf 100644 --- a/engine/src/engine/graph/encode.cpp +++ b/engine/src/engine/graph/encode.cpp @@ -406,6 +406,7 @@ uf::stl::string uf::graph::save( const pod::Graph& graph, const uf::stl::string& json["offset"] = offset; json["length"] = length; } + json["layers"] = image.layers; #if UF_USE_DC_TEXCONV auto converted = image.scale( {32, 32}, "nearest" ); diff --git a/engine/src/engine/graph/graph.cpp b/engine/src/engine/graph/graph.cpp index 0cde225c..b4dcd45f 100644 --- a/engine/src/engine/graph/graph.cpp +++ b/engine/src/engine/graph/graph.cpp @@ -531,7 +531,7 @@ UF_VERTEX_DESCRIPTOR(uf::graph::mesh::Base, UF_VERTEX_DESCRIPTION(uf::graph::mesh::Base, R8G8B8A8_UNORM, color) UF_VERTEX_DESCRIPTION(uf::graph::mesh::Base, R32G32_SFLOAT, st) UF_VERTEX_DESCRIPTION(uf::graph::mesh::Base, R32G32B32_SFLOAT, normal) - UF_VERTEX_DESCRIPTION(uf::graph::mesh::Base, R32G32B32_SFLOAT, tangent) + UF_VERTEX_DESCRIPTION(uf::graph::mesh::Base, R32G32B32A32_SFLOAT, tangent) ); // it'd be super sugoi if I could somehow macro this annoyance UF_VERTEX_INTERPOLATE(uf::graph::mesh::Base, { @@ -551,7 +551,7 @@ UF_VERTEX_DESCRIPTOR(uf::graph::mesh::Skinned, UF_VERTEX_DESCRIPTION(uf::graph::mesh::Skinned, R8G8B8A8_UNORM, color) UF_VERTEX_DESCRIPTION(uf::graph::mesh::Skinned, R32G32_SFLOAT, st) UF_VERTEX_DESCRIPTION(uf::graph::mesh::Skinned, R32G32B32_SFLOAT, normal) - UF_VERTEX_DESCRIPTION(uf::graph::mesh::Skinned, R32G32B32_SFLOAT, tangent) + UF_VERTEX_DESCRIPTION(uf::graph::mesh::Skinned, R32G32B32A32_SFLOAT, tangent) UF_VERTEX_DESCRIPTION(uf::graph::mesh::Skinned, R16G16B16A16_UINT, joints) UF_VERTEX_DESCRIPTION(uf::graph::mesh::Skinned, R32G32B32A32_SFLOAT, weights) ); @@ -575,7 +575,7 @@ UF_VERTEX_DESCRIPTOR(uf::graph::mesh::Base_16f, UF_VERTEX_DESCRIPTION(uf::graph::mesh::Base_16f, R8G8B8A8_UNORM, color) UF_VERTEX_DESCRIPTION(uf::graph::mesh::Base_16f, R16G16_SFLOAT, st) UF_VERTEX_DESCRIPTION(uf::graph::mesh::Base_16f, R16G16B16_SFLOAT, normal) - UF_VERTEX_DESCRIPTION(uf::graph::mesh::Base_16f, R16G16B16_SFLOAT, tangent) + UF_VERTEX_DESCRIPTION(uf::graph::mesh::Base_16f, R16G16B16A16_SFLOAT, tangent) ); UF_VERTEX_INTERPOLATE(uf::graph::mesh::Base_16f, { return t < 0.5 ? p1 : p2; @@ -587,7 +587,7 @@ UF_VERTEX_DESCRIPTOR(uf::graph::mesh::Skinned_16f, UF_VERTEX_DESCRIPTION(uf::graph::mesh::Skinned_16f, R8G8B8A8_UNORM, color) UF_VERTEX_DESCRIPTION(uf::graph::mesh::Skinned_16f, R16G16_SFLOAT, st) UF_VERTEX_DESCRIPTION(uf::graph::mesh::Skinned_16f, R16G16B16_SFLOAT, normal) - UF_VERTEX_DESCRIPTION(uf::graph::mesh::Skinned_16f, R16G16B16_SFLOAT, tangent) + UF_VERTEX_DESCRIPTION(uf::graph::mesh::Skinned_16f, R16G16B16A16_SFLOAT, tangent) UF_VERTEX_DESCRIPTION(uf::graph::mesh::Skinned_16f, R16G16B16A16_UINT, joints) UF_VERTEX_DESCRIPTION(uf::graph::mesh::Skinned_16f, R16G16B16A16_SFLOAT, weights) ); @@ -602,7 +602,7 @@ UF_VERTEX_DESCRIPTOR(uf::graph::mesh::Base_u16q, UF_VERTEX_DESCRIPTION(uf::graph::mesh::Base_u16q, R8G8B8A8_UNORM, color) UF_VERTEX_DESCRIPTION(uf::graph::mesh::Base_u16q, R16G16_UINT, st) UF_VERTEX_DESCRIPTION(uf::graph::mesh::Base_u16q, R16G16B16_UINT, normal) - UF_VERTEX_DESCRIPTION(uf::graph::mesh::Base_u16q, R16G16B16_UINT, tangent) + UF_VERTEX_DESCRIPTION(uf::graph::mesh::Base_u16q, R16G16B16A16_UINT, tangent) ); UF_VERTEX_INTERPOLATE(uf::graph::mesh::Base_u16q, { return t < 0.5 ? p1 : p2; @@ -614,7 +614,7 @@ UF_VERTEX_DESCRIPTOR(uf::graph::mesh::Skinned_u16q, UF_VERTEX_DESCRIPTION(uf::graph::mesh::Skinned_u16q, R8G8B8A8_UNORM, color) UF_VERTEX_DESCRIPTION(uf::graph::mesh::Skinned_u16q, R16G16_UINT, st) UF_VERTEX_DESCRIPTION(uf::graph::mesh::Skinned_u16q, R16G16B16_UINT, normal) - UF_VERTEX_DESCRIPTION(uf::graph::mesh::Skinned_u16q, R16G16B16_UINT, tangent) + UF_VERTEX_DESCRIPTION(uf::graph::mesh::Skinned_u16q, R16G16B16A16_UINT, tangent) UF_VERTEX_DESCRIPTION(uf::graph::mesh::Skinned_u16q, R16G16B16A16_UINT, joints) UF_VERTEX_DESCRIPTION(uf::graph::mesh::Skinned_u16q, R16G16B16A16_UINT, weights) ); @@ -946,6 +946,11 @@ void uf::graph::process( pod::Graph& graph ) { auto texName = graph.textures[material.indexAlbedo]; textureDescriptors[texName].srgb = true; } + + if ( (0 <= material.indexNormal && material.indexNormal < graph.textures.size() ) ) { + auto texName = graph.textures[material.indexNormal]; + textureDescriptors[texName].srgb = false; + } } UF_DEBUG_TIMER_MULTITRACE("Processing images..."); diff --git a/engine/src/ext/texconv/texconv.cpp b/engine/src/ext/texconv/texconv.cpp index ae87c8a3..668ef799 100644 --- a/engine/src/ext/texconv/texconv.cpp +++ b/engine/src/ext/texconv/texconv.cpp @@ -147,17 +147,17 @@ bool UF_API ext::texconv::save( const pod::Dtex& dtex, const uf::stl::string& fi } bool UF_API ext::texconv::save( const pod::Dtex& dtex, uf::stl::vector& buffer ) { size_t totalSize = dtex.imageData.size() + dtex.paletteData.size(); - buffer.reserve(buffer.size() + totalSize); + buffer.reserve(buffer.size() + totalSize); - if ( !dtex.imageData.empty() ) { - buffer.insert( buffer.end(), dtex.imageData.begin(), dtex.imageData.end() ); - } + if ( !dtex.imageData.empty() ) { + buffer.insert( buffer.end(), dtex.imageData.begin(), dtex.imageData.end() ); + } - if ( !dtex.paletteData.empty() ) { - buffer.insert( buffer.end(), dtex.paletteData.begin(), dtex.paletteData.end() ); - } + if ( !dtex.paletteData.empty() ) { + buffer.insert( buffer.end(), dtex.paletteData.begin(), dtex.paletteData.end() ); + } - return true; + return true; } // maintains original main() diff --git a/engine/src/ext/valve/bsp.cpp b/engine/src/ext/valve/bsp.cpp index a7a0e94d..4602d3ae 100644 --- a/engine/src/ext/valve/bsp.cpp +++ b/engine/src/ext/valve/bsp.cpp @@ -436,12 +436,6 @@ namespace impl { pod::Vector2f finalSt; finalSt.x = tx; finalSt.y = ty; - /* - finalSt.x = (uf::vector::dot( vBase, texInfo.lightmapVecs[0] ) + 0.5f - face.lightmapTextureMins.x) / (face.lightmapTextureSize.x + 1.0f); - finalSt.y = (uf::vector::dot( vBase, texInfo.lightmapVecs[1] ) + 0.5f - face.lightmapTextureMins.y) / (face.lightmapTextureSize.y + 1.0f); - //finalSt.x = 1.0f - finalSt.x; // ? - finalSt.y = 1.0f - finalSt.y; // ? - */ int dispIdx = info.dispVertStart + y * side + x; const auto& dVert = context.dispverts[dispIdx]; @@ -469,10 +463,14 @@ namespace impl { pod::Vector3f normal = uf::vector::normalize(uf::vector::cross(bitangent, tangent)); - // float w = (uf::vector::dot(uf::vector::cross(normal, tangent), bitangent) < 0.0f) ? -1.0f : 1.0f; + pod::Vector3f t = uf::vector::normalize( impl::convertPos( texInfo.textureVecs[0], 1.0f ) ); + pod::Vector3f b = uf::vector::normalize( impl::convertPos( texInfo.textureVecs[1], 1.0f ) ); + + t = uf::vector::normalize(t - normal * uf::vector::dot(normal, t)); + float w = (uf::vector::dot( uf::vector::cross(normal, t), b ) < 0.0f) ? -1.0f : 1.0f; meshlet.vertices[id].normal = normal; - meshlet.vertices[id].tangent = tangent = uf::vector::normalize(tangent - normal * uf::vector::dot(normal, tangent)); + meshlet.vertices[id].tangent = { t.x, t.y, t.z, w }; } } @@ -520,6 +518,7 @@ namespace impl { v.position = pos; v.color = { 1.0f, 1.0f, 1.0f, 1.0f }; v.normal = normal; + v.tangent = { 1.0f, 0.0f, 0.0f, 1.0f }; // has texture information if ( face.texinfo >= 0 && face.texinfo < context.texinfos.size() ) { @@ -539,8 +538,10 @@ namespace impl { pod::Vector3f t = uf::vector::normalize( impl::convertPos( info.textureVecs[0], 1.0f ) ); pod::Vector3f b = uf::vector::normalize( impl::convertPos( info.textureVecs[1], 1.0f ) ); - v.tangent = uf::vector::normalize(t - normal * uf::vector::dot(normal, t)); - // float w = (uf::vector::dot( uf::vector::cross(normal, t), b ) < 0.0f) ? -1.0f : 1.0f; + t = uf::vector::normalize(t - normal * uf::vector::dot(normal, t)); + float w = (uf::vector::dot( uf::vector::cross(normal, t), b ) < 0.0f) ? -1.0f : 1.0f; + + v.tangent = { t.x, t.y, t.z, w }; } }; @@ -1047,10 +1048,14 @@ void ext::valve::loadBsp( pod::Graph& graph, const uf::stl::string& filename, co uint16_t pivotVertID = pivotSurfEdge >= 0 ? context.edges[pivotSurfEdge].x : context.edges[-pivotSurfEdge].y; pod::Vector3f p0 = impl::convertPos( context.vertices[pivotVertID] ); + /* + // some faces are wrong when doing it this way const auto& plane = context.planes[face.planenum]; + pod::Vector3f faceNormal = uf::vector::normalize( impl::convertPos( plane.normal, 1.0f ) ); + if ( face.side != 0 ) faceNormal = -faceNormal; + */ pod::Vector3f faceNormal = {0.0f, 0.0f, 0.0f}; - for ( int16_t i = 1; i < face.numedges - 1; ++i ) { int32_t se1 = context.surfedges[edgeID + i]; int32_t se2 = context.surfedges[edgeID + i + 1]; @@ -1063,7 +1068,6 @@ void ext::valve::loadBsp( pod::Graph& graph, const uf::stl::string& filename, co faceNormal += uf::vector::cross(p1 - p0, p2 - p0); } - faceNormal = uf::vector::normalize(faceNormal); for ( int16_t i = 1; i < face.numedges - 1; ++i ) { @@ -1197,9 +1201,6 @@ void ext::valve::loadBsp( pod::Graph& graph, const uf::stl::string& filename, co // load materials uf::stl::vector missing_pixels = { 255, 0, 255, 255, 0, 0, 0, 255, 0, 0, 0, 255, 255, 0, 255, 255 }; for ( auto matName : graph.materials ) { - if ( matName == "" ) { - continue; - } uf::Serializer vmt; auto vmtPath = ::fmt::format("materials/{}.vmt", matName); auto vtfPath = ::fmt::format("materials/{}.vtf", matName); @@ -1256,7 +1257,7 @@ void ext::valve::loadBsp( pod::Graph& graph, const uf::stl::string& filename, co } // bumpmap if ( vmt["$ssbump"].as(0) == 1 ) { - material.indexNormal = -1; + // to-do: handle bumpmaps // normal map } else if ( vmt["$bumpmap"].is() ) { auto matName = uf::string::lowercase(vmt["$bumpmap"].as()); @@ -1270,7 +1271,9 @@ void ext::valve::loadBsp( pod::Graph& graph, const uf::stl::string& filename, co impl::addMaterial( graph, matName, textureID ); auto& image = storage.images[matName].data; - if ( ext::valve::loadVtf( image, vtfPath ) ) material.indexNormal = textureID; + if ( ext::valve::loadVtf( image, vtfPath ) ) { + material.indexNormal = textureID; + } } } // metallic/roughness/occlusion map diff --git a/engine/src/ext/valve/common.cpp b/engine/src/ext/valve/common.cpp index 9d4cc16e..18ab1a98 100644 --- a/engine/src/ext/valve/common.cpp +++ b/engine/src/ext/valve/common.cpp @@ -34,6 +34,11 @@ size_t impl::addMaterial( pod::Graph& graph, const uf::stl::string& name, int32_ material.factorMetallic = 0.0f; material.factorRoughness = 1.0f; material.factorOcclusion = 1.0f; + material.indexNormal = -1; + material.indexEmissive = -1; + material.indexMetallicRoughness = -1; + material.indexOcclusion = -1; + material.indexCubemap = -1; return materialID; } diff --git a/engine/src/ext/valve/mdl.cpp b/engine/src/ext/valve/mdl.cpp index 1a3bca65..55be29c6 100644 --- a/engine/src/ext/valve/mdl.cpp +++ b/engine/src/ext/valve/mdl.cpp @@ -320,66 +320,69 @@ bool ext::valve::loadMdl( pod::Graph& graph, const uf::stl::string& filename ) { const impl::mstudiomesh_t* mdlMeshes = (const impl::mstudiomesh_t*)((uint8_t*)&mdlModels[m] + mdlModels[m].meshindex); for ( int meshID = 0; meshID < lod0.numMeshes; ++meshID ) { - const impl::vtxMesh_t& mesh = meshes[meshID]; - const impl::mstudiomesh_t& mdlMesh = mdlMeshes[meshID]; + const impl::vtxMesh_t& mesh = meshes[meshID]; + const impl::mstudiomesh_t& mdlMesh = mdlMeshes[meshID]; - auto& meshlet = meshlets.emplace_back(); - uf::stl::unordered_map vertRemap; + auto& meshlet = meshlets.emplace_back(); + uf::stl::unordered_map vertRemap; - const impl::vtxStripGroup_t* stripGroups = (const impl::vtxStripGroup_t*)((uint8_t*)&mesh + mesh.stripGroupHeaderOffset); - for ( int sg = 0; sg < mesh.numStripGroups; ++sg ) { - const impl::vtxStripGroup_t& stripGroup = stripGroups[sg]; + const impl::vtxStripGroup_t* stripGroups = (const impl::vtxStripGroup_t*)((uint8_t*)&mesh + mesh.stripGroupHeaderOffset); + for ( int sg = 0; sg < mesh.numStripGroups; ++sg ) { + const impl::vtxStripGroup_t& stripGroup = stripGroups[sg]; - const uint16_t* indices = (const uint16_t*)((uint8_t*)&stripGroup + stripGroup.indexOffset); - const impl::vtxVertex_t* vtxVerts = (const impl::vtxVertex_t*)((uint8_t*)&stripGroup + stripGroup.vertOffset); + const uint16_t* indices = (const uint16_t*)((uint8_t*)&stripGroup + stripGroup.indexOffset); + const impl::vtxVertex_t* vtxVerts = (const impl::vtxVertex_t*)((uint8_t*)&stripGroup + stripGroup.vertOffset); - for ( int i = 0; i < stripGroup.numIndices; i += 3 ) { - uint32_t tri[3]; - for ( int j = 0; j < 3; ++j ) { - uint16_t localVertIndex = indices[i + j]; - const impl::vtxVertex_t& vtxVert = vtxVerts[localVertIndex]; - uint16_t originalVvdID = vtxVert.origMeshVertID; + for ( int i = 0; i < stripGroup.numIndices; i += 3 ) { + uint32_t tri[3]; + for ( int j = 0; j < 3; ++j ) { + uint16_t localVertIndex = indices[i + j]; + const impl::vtxVertex_t& vtxVert = vtxVerts[localVertIndex]; + uint16_t originalVvdID = vtxVert.origMeshVertID; - if ( vertRemap.find(originalVvdID) == vertRemap.end() ) { - vertRemap[originalVvdID] = meshlet.vertices.size(); - auto& vert = meshlet.vertices.emplace_back(); + if ( vertRemap.find(originalVvdID) == vertRemap.end() ) { + vertRemap[originalVvdID] = meshlet.vertices.size(); + auto& vert = meshlet.vertices.emplace_back(); - const auto& srcVert = lod0Vertices[mdlMesh.vertexoffset + originalVvdID]; + const auto& srcVert = lod0Vertices[mdlMesh.vertexoffset + originalVvdID]; - vert.position = impl::convertPos( srcVert.m_vecPosition ); - vert.normal = uf::vector::normalize( impl::convertPos( srcVert.m_vecNormal, 1.0f ) ); + vert.position = impl::convertPos( srcVert.m_vecPosition ); + vert.normal = uf::vector::normalize( impl::convertPos( srcVert.m_vecNormal, 1.0f ) ); + vert.tangent = {0.0f, 0.0f, 0.0f, 1.0f}; - if ( !lod0Tangents.empty() ) { - vert.tangent = uf::vector::normalize( impl::convertPos( lod0Tangents[mdlMesh.vertexoffset + originalVvdID], 1.0f ) ); - } + if ( !lod0Tangents.empty() ) { + auto srcTangent = lod0Tangents[mdlMesh.vertexoffset + originalVvdID]; + pod::Vector3f tangent = uf::vector::normalize( impl::convertPos( srcTangent, 1.0f ) ); + vert.tangent = { tangent.x, tangent.y, tangent.z, srcTangent.w }; // might need to -w + } - vert.uv = srcVert.m_vecTexCoord; - vert.color = {1.0f, 1.0f, 1.0f, 1.0f}; - vert.joints.x = srcVert.m_BoneWeights.numbones > 0 ? std::max(0, srcVert.m_BoneWeights.bone[0]) : 0; - vert.joints.y = srcVert.m_BoneWeights.numbones > 1 ? std::max(0, srcVert.m_BoneWeights.bone[1]) : 0; - vert.joints.z = srcVert.m_BoneWeights.numbones > 2 ? std::max(0, srcVert.m_BoneWeights.bone[2]) : 0; - vert.joints.w = 0; + vert.uv = srcVert.m_vecTexCoord; + vert.color = {1.0f, 1.0f, 1.0f, 1.0f}; + vert.joints.x = srcVert.m_BoneWeights.numbones > 0 ? std::max(0, srcVert.m_BoneWeights.bone[0]) : 0; + vert.joints.y = srcVert.m_BoneWeights.numbones > 1 ? std::max(0, srcVert.m_BoneWeights.bone[1]) : 0; + vert.joints.z = srcVert.m_BoneWeights.numbones > 2 ? std::max(0, srcVert.m_BoneWeights.bone[2]) : 0; + vert.joints.w = 0; - vert.weights.x = srcVert.m_BoneWeights.numbones > 0 ? srcVert.m_BoneWeights.weight[0] : 1.0f; - vert.weights.y = srcVert.m_BoneWeights.numbones > 1 ? srcVert.m_BoneWeights.weight[1] : 0.0f; - vert.weights.z = srcVert.m_BoneWeights.numbones > 2 ? srcVert.m_BoneWeights.weight[2] : 0.0f; - vert.weights.w = 0.0f; + vert.weights.x = srcVert.m_BoneWeights.numbones > 0 ? srcVert.m_BoneWeights.weight[0] : 1.0f; + vert.weights.y = srcVert.m_BoneWeights.numbones > 1 ? srcVert.m_BoneWeights.weight[1] : 0.0f; + vert.weights.z = srcVert.m_BoneWeights.numbones > 2 ? srcVert.m_BoneWeights.weight[2] : 0.0f; + vert.weights.w = 0.0f; - auto& bounds = meshlet.primitive.instance.bounds; - if ( vertRemap.size() == 1 ) { - bounds.min = bounds.max = vert.position; - } else { - bounds.min = uf::vector::min( bounds.min, vert.position ); - bounds.max = uf::vector::max( bounds.max, vert.position ); - } - } + auto& bounds = meshlet.primitive.instance.bounds; + if ( vertRemap.size() == 1 ) { + bounds.min = bounds.max = vert.position; + } else { + bounds.min = uf::vector::min( bounds.min, vert.position ); + bounds.max = uf::vector::max( bounds.max, vert.position ); + } + } - meshlet.indices.push_back(tri[j] = vertRemap[originalVvdID]); - } - } - } + meshlet.indices.emplace_back(tri[j] = vertRemap[originalVvdID]); + } + } + } - if ( lod0Tangents.empty() ) uf::mesh::tangents( meshlet.vertices, meshlet.indices ); + if ( lod0Tangents.empty() ) uf::mesh::tangents( meshlet.vertices, meshlet.indices ); size_t materialID = 0; uf::stl::string matName = "missing_texture"; diff --git a/engine/src/ext/valve/vtf.cpp b/engine/src/ext/valve/vtf.cpp index 4d485b0c..9d80a850 100644 --- a/engine/src/ext/valve/vtf.cpp +++ b/engine/src/ext/valve/vtf.cpp @@ -6,13 +6,80 @@ namespace impl { constexpr uint32_t IMAGE_FORMAT_RGBA8888 = 0; + constexpr uint32_t IMAGE_FORMAT_RGB888 = 2; constexpr uint32_t IMAGE_FORMAT_BGR888 = 3; + constexpr uint32_t IMAGE_FORMAT_BGR565 = 4; + constexpr uint32_t IMAGE_FORMAT_BGRX8888 = 11; constexpr uint32_t IMAGE_FORMAT_BGRA8888 = 12; constexpr uint32_t IMAGE_FORMAT_DXT1 = 13; + constexpr uint32_t IMAGE_FORMAT_DXT3 = 14; constexpr uint32_t IMAGE_FORMAT_DXT5 = 15; + constexpr uint32_t IMAGE_FORMAT_RGBA16161616F = 24; - constexpr uint32_t TEXTUREFLAGS_ENVMAP = 0x00002000; - constexpr uint32_t TEXTUREFLAGS_NORMAL = 0x00000080; + constexpr uint32_t MATERIAL_VAR_DEBUG = 0x0001; // $debug + constexpr uint32_t MATERIAL_VAR_NO_DEBUG_OVERRIDE = 0x0002; // $no_fullbright + constexpr uint32_t MATERIAL_VAR_NO_DRAW = 0x0004; // $no_draw + constexpr uint32_t MATERIAL_VAR_USE_IN_FILLRATE_MODE = 0x0008; // $use_in_fillrate_mode + constexpr uint32_t MATERIAL_VAR_VERTEXCOLOR = 0x0010; // $vertexcolor + constexpr uint32_t MATERIAL_VAR_VERTEXALPHA = 0x0020; // $vertexalpha + constexpr uint32_t MATERIAL_VAR_SELFILLUM = 0x0040; // $selfillum + constexpr uint32_t MATERIAL_VAR_ADDITIVE = 0x0080; // $additive + constexpr uint32_t MATERIAL_VAR_ALPHATEST = 0x0100; // $alphatest + constexpr uint32_t MATERIAL_VAR_MULTIPASS = 0x0200; // $multipass + constexpr uint32_t MATERIAL_VAR_ZNEARER = 0x0400; // $znearer + constexpr uint32_t MATERIAL_VAR_MODEL = 0x0800; // $model + constexpr uint32_t MATERIAL_VAR_FLAT = 0x1000; // $flat + constexpr uint32_t MATERIAL_VAR_NOCULL = 0x2000; // $nocull + constexpr uint32_t MATERIAL_VAR_NOFOG = 0x4000; // $nofog + constexpr uint32_t MATERIAL_VAR_IGNOREZ = 0x8000; // $ignorez + constexpr uint32_t MATERIAL_VAR_DECAL = 0x10000; // $decal + constexpr uint32_t MATERIAL_VAR_ENVMAPSPHERE = 0x20000; // $envmapsphere + constexpr uint32_t MATERIAL_VAR_NOALPHAMOD = 0x40000; // $noalphamod + constexpr uint32_t MATERIAL_VAR_ENVMAPCAMERASPACE = 0x80000; // $envmapcameraspace + constexpr uint32_t MATERIAL_VAR_BASEALPHAENVMAPMASK = 0x100000; // $basealphaenvmapmask + constexpr uint32_t MATERIAL_VAR_TRANSLUCENT = 0x200000; // $translucent + constexpr uint32_t MATERIAL_VAR_NORMALMAPALPHAENVMAPMASK = 0x400000; // $normalmapalphaenvmapmask + constexpr uint32_t MATERIAL_VAR_NEEDS_SOFTWARE_SKINNING = 0x800000; // $softwareskin + constexpr uint32_t MATERIAL_VAR_OPAQUETEXTURE = 0x1000000; // $opaquetexture + constexpr uint32_t MATERIAL_VAR_ENVMAPMODE = 0x2000000; // $envmapmode + constexpr uint32_t MATERIAL_VAR_SUPPRESS_DECALS = 0x4000000; // $nodecal + constexpr uint32_t MATERIAL_VAR_HALFLAMBERT = 0x8000000; // $halflambert + constexpr uint32_t MATERIAL_VAR_WIREFRAME = 0x10000000; // $wireframe + constexpr uint32_t MATERIAL_VAR_ALLOWALPHATOCOVERAGE = 0x20000000; // $allowalphatocoverage + constexpr uint32_t MATERIAL_VAR_IGNORE_ALPHA_MODULATION = 0x40000000; // + + constexpr uint32_t TEXTUREFLAGS_POINTSAMPLE = 1; + constexpr uint32_t TEXTUREFLAGS_TRILINEAR = 2; + constexpr uint32_t TEXTUREFLAGS_CLAMPS = 4; + constexpr uint32_t TEXTUREFLAGS_CLAMPT = 8; + constexpr uint32_t TEXTUREFLAGS_ANISOTROPIC = 16; + constexpr uint32_t TEXTUREFLAGS_HINT_DXT5 = 32; + constexpr uint32_t TEXTUREFLAGS_PWL_CORRECTED = 64; + constexpr uint32_t TEXTUREFLAGS_NORMAL = 128; + constexpr uint32_t TEXTUREFLAGS_NOMIP = 256; + constexpr uint32_t TEXTUREFLAGS_NOLOD = 512; + constexpr uint32_t TEXTUREFLAGS_ALL_MIPS = 1024; + constexpr uint32_t TEXTUREFLAGS_PROCEDURAL = 2048; + constexpr uint32_t TEXTUREFLAGS_ONEBITALPHA = 4096; + constexpr uint32_t TEXTUREFLAGS_EIGHTBITALPHA = 8192; + constexpr uint32_t TEXTUREFLAGS_ENVMAP = 16384; + constexpr uint32_t TEXTUREFLAGS_RENDERTARGET = 32768; + constexpr uint32_t TEXTUREFLAGS_DEPTHRENDERTARGET = 65536; + constexpr uint32_t TEXTUREFLAGS_NODEBUGOVERRIDE = 131072; + constexpr uint32_t TEXTUREFLAGS_SINGLECOPY = 262144; + constexpr uint32_t TEXTUREFLAGS_STAGING_MEMORY = 524288; + constexpr uint32_t TEXTUREFLAGS_IMMEDIATE_CLEANUP = 1048576; + constexpr uint32_t TEXTUREFLAGS_IGNORE_PICMIP = 2097152; + constexpr uint32_t TEXTUREFLAGS_UNUSED_00400000 = 4194304; + constexpr uint32_t TEXTUREFLAGS_NODEPTHBUFFER = 8388608; + constexpr uint32_t TEXTUREFLAGS_UNUSED_01000000 = 16777216; + constexpr uint32_t TEXTUREFLAGS_CLAMPU = 33554432; + constexpr uint32_t TEXTUREFLAGS_VERTEXTEXTURE = 67108864; + constexpr uint32_t TEXTUREFLAGS_SSBUMP = 134217728; + constexpr uint32_t TEXTUREFLAGS_UNUSED_10000000 = 268435456; + constexpr uint32_t TEXTUREFLAGS_BORDER = 536870912; + constexpr uint32_t TEXTUREFLAGS_STREAMABLE_COARSE = 1073741824; + constexpr uint32_t TEXTUREFLAGS_STREAMABLE_FINE = 2147483648; #pragma pack(push, 1) struct VTFHeader { @@ -35,6 +102,32 @@ namespace impl { }; #pragma pack(pop) + inline size_t getMipSize( uint32_t format, int width, int height ) { + int blocksX = (width + 3) / 4; + int blocksY = (height + 3) / 4; + + switch ( format ) { + case impl::IMAGE_FORMAT_DXT1: + return blocksX * blocksY * 8; + case impl::IMAGE_FORMAT_DXT3: + case impl::IMAGE_FORMAT_DXT5: + return blocksX * blocksY * 16; + case impl::IMAGE_FORMAT_BGR888: + case impl::IMAGE_FORMAT_RGB888: + return width * height * 3; + case impl::IMAGE_FORMAT_BGRA8888: + case impl::IMAGE_FORMAT_BGRX8888: + case impl::IMAGE_FORMAT_RGBA8888: + return width * height * 4; + case impl::IMAGE_FORMAT_BGR565: + return width * height * 2; + case impl::IMAGE_FORMAT_RGBA16161616F: + return width * height * 8; + default: + return 0; + } + }; + // to-do: cram this inside the image functions inline void decodeRGB565( uint16_t color, uint8_t& r, uint8_t& g, uint8_t& b ) { r = (uint8_t)(((color >> 11) & 0x1F) * 255 / 31); @@ -42,6 +135,32 @@ namespace impl { b = (uint8_t)((color & 0x1F) * 255 / 31); } + inline uint8_t halfTo8Bit(uint16_t h) { + uint32_t exp = (h >> 10) & 0x1F; + uint32_t mant = h & 0x3FF; + if (exp == 0) return 0; + if (exp == 31) return 255; + + exp = exp + (127 - 15); + uint32_t f = (exp << 23) | (mant << 13); + float val; + std::memcpy(&val, &f, sizeof(float)); + + return (uint8_t)(std::min(std::max(val, 0.0f), 1.0f) * 255.0f); + } + + inline float halfToFloat(uint16_t h) { + uint32_t exp = (h >> 10) & 0x1F; + uint32_t mant = h & 0x3FF; + if (exp == 0 && mant == 0) return 0.0f; + + exp = exp + (127 - 15); + uint32_t f = (exp << 23) | (mant << 13); + float val; + std::memcpy(&val, &f, sizeof(float)); + return val; + } + void decompressDXT1Block( const uint8_t* block, uint8_t* out, int x, int y, int width, int height ) { uint16_t color0 = *(const uint16_t*)(block + 0); uint16_t color1 = *(const uint16_t*)(block + 2); @@ -73,6 +192,38 @@ namespace impl { } } + void decompressDXT3Block(const uint8_t* block, uint8_t* out, int x, int y, int width, int height) { + uint16_t color0 = *(const uint16_t*)(block + 8); + uint16_t color1 = *(const uint16_t*)(block + 10); + uint32_t colorIndices = *(const uint32_t*)(block + 12); + + uint8_t r[4], g[4], b[4]; + decodeRGB565(color0, r[0], g[0], b[0]); + decodeRGB565(color1, r[1], g[1], b[1]); + + r[2] = (2 * r[0] + r[1]) / 3; g[2] = (2 * g[0] + g[1]) / 3; b[2] = (2 * b[0] + b[1]) / 3; + r[3] = (r[0] + 2 * r[1]) / 3; g[3] = (g[0] + 2 * g[1]) / 3; b[3] = (b[0] + 2 * b[1]) / 3; + + for ( int py = 0; py < 4; ++py ) { + for ( int px = 0; px < 4; ++px ) { + if (x + px >= width || y + py >= height) continue; + + uint8_t colorIdx = (colorIndices >> ((py * 4 + px) * 2)) & 0x03; + + int alphaOffset = (py * 4 + px) / 2; + int alphaShift = ((py * 4 + px) % 2) * 4; + uint8_t alpha4 = (block[alphaOffset] >> alphaShift) & 0x0F; + uint8_t alpha = (alpha4 << 4) | alpha4; + + int offset = ((y + py) * width + (x + px)) * 4; + out[offset + 0] = r[colorIdx]; + out[offset + 1] = g[colorIdx]; + out[offset + 2] = b[colorIdx]; + out[offset + 3] = alpha; + } + } + } + void decompressDXT5Block(const uint8_t* block, uint8_t* out, int x, int y, int width, int height) { uint8_t a0 = block[0]; uint8_t a1 = block[1]; @@ -155,158 +306,173 @@ bool ext::valve::loadVtf( pod::Image& image, const uf::stl::string& filename ) { const impl::VTFHeader* header = (const impl::VTFHeader*)(buffer.data()); if ( strncmp(header->signature, "VTF", 3) != 0 ) return false; - bool isCubemap = (header->flags & impl::TEXTUREFLAGS_ENVMAP) != 0; - int numFrames = std::max(1, header->frames); + switch ( header->highResImageFormat ) { + case impl::IMAGE_FORMAT_RGBA8888: + case impl::IMAGE_FORMAT_RGB888: + case impl::IMAGE_FORMAT_BGR888: + case impl::IMAGE_FORMAT_BGR565: + case impl::IMAGE_FORMAT_BGRX8888: + case impl::IMAGE_FORMAT_BGRA8888: + case impl::IMAGE_FORMAT_DXT1: + case impl::IMAGE_FORMAT_DXT3: + case impl::IMAGE_FORMAT_DXT5: + case impl::IMAGE_FORMAT_RGBA16161616F: { + break; + } + default: { + UF_MSG_ERROR("VTF '{}' has unrecognized format: 0x{:x}", filename, header->highResImageFormat); + } break; + } size_t singleFaceSize = 0; - for ( int mip = header->mipmapCount - 1; mip >= 0; --mip ) { + for ( int mip = 0; mip < header->mipmapCount; ++mip ) { int mipWidth = std::max(1, header->width >> mip); int mipHeight = std::max(1, header->height >> mip); - int blocksX = (mipWidth + 3) / 4; - int blocksY = (mipHeight + 3) / 4; - - if ( header->highResImageFormat == impl::IMAGE_FORMAT_DXT1 ) singleFaceSize += blocksX * blocksY * 8; - else if ( header->highResImageFormat == impl::IMAGE_FORMAT_DXT5 ) singleFaceSize += blocksX * blocksY * 16; - else if ( header->highResImageFormat == impl::IMAGE_FORMAT_BGRA8888 ) singleFaceSize += mipWidth * mipHeight * 4; + singleFaceSize += impl::getMipSize(header->highResImageFormat, mipWidth, mipHeight); } size_t offset = header->headerSize; if ( header->lowResImageFormat != 0xFFFFFFFF ) { - offset += std::max(1, (header->lowResImageWidth * header->lowResImageHeight) / 2); + offset += impl::getMipSize(header->lowResImageFormat, header->lowResImageWidth, header->lowResImageHeight); } - - size_t remainingBytes = buffer.size() - offset; - size_t bytesPerFace = singleFaceSize * numFrames; - int actualFaces = bytesPerFace > 0 ? (remainingBytes / bytesPerFace) : 1; - - if ( actualFaces == 6 || actualFaces == 7 ) { - isCubemap = true; - } - int numFaces = 1; - if ( isCubemap ) { - if ( actualFaces >= 6 ) { - numFaces = actualFaces; - } else { - isCubemap = false; - numFaces = 1; - } + int numFrames = std::max(1, header->frames); + if ( singleFaceSize > 0 && numFrames > 0 ) { + numFaces = (buffer.size() - offset) / (singleFaceSize * numFrames); } + bool isHDR = (header->highResImageFormat == impl::IMAGE_FORMAT_RGBA16161616F); + bool isCubemap = (header->flags & impl::TEXTUREFLAGS_ENVMAP) != 0 && numFaces >= 6; + for ( int mip = header->mipmapCount - 1; mip > 0; --mip ) { int mipWidth = std::max(1, header->width >> mip); int mipHeight = std::max(1, header->height >> mip); - int blocksX = (mipWidth + 3) / 4; - int blocksY = (mipHeight + 3) / 4; - - size_t mipSize = 0; - if ( header->highResImageFormat == impl::IMAGE_FORMAT_DXT1 ) mipSize = blocksX * blocksY * 8; - else if ( header->highResImageFormat == impl::IMAGE_FORMAT_DXT5 ) mipSize = blocksX * blocksY * 16; - else if ( header->highResImageFormat == impl::IMAGE_FORMAT_BGRA8888 ) mipSize = mipWidth * mipHeight * 4; - - offset += mipSize * numFrames * numFaces; + offset += impl::getMipSize(header->highResImageFormat, mipWidth, mipHeight) * numFrames * numFaces; } int outputFaces = isCubemap ? 6 : 1; - + int bytesPerPixel = isHDR ? 8 : 4; image.size = { header->width, header->height * outputFaces }; image.channels = 4; - image.bpp = 8 * 4; - image.pixels.resize( header->width * header->height * 4 * outputFaces ); + image.bpp = 8 * bytesPerPixel; + image.layers = numFrames; + image.format = isHDR ? uf::renderer::enums::Format::R16G16B16A16_SFLOAT : uf::renderer::enums::Format::R8G8B8A8_UNORM; + image.pixels.resize( header->width * header->height * bytesPerPixel * outputFaces * numFrames ); - int faceSizePixels = header->width * header->height; + int faceSize = header->width * header->height; int blocksX = (header->width + 3) / 4; int blocksY = (header->height + 3) / 4; const int faceMap[6] = { 4, 5, 0, 1, 2, 3 }; const int faceModes[6] = { 2, 0, 6, 1, 1, 3 }; - for ( int face = 0; face < outputFaces; ++face ) { - const uint8_t* data = buffer.data() + offset; - int mappedFace = (isCubemap && face < 6) ? faceMap[face] : face; - uint8_t* outPixels = image.pixels.data() + (mappedFace * faceSizePixels * 4); + for ( int frame = 0; frame < numFrames; ++frame ) { + for ( int face = 0; face < outputFaces; ++face ) { + const uint8_t* data = buffer.data() + offset; + int mappedFace = (isCubemap && face < 6) ? faceMap[face] : face; + size_t outOffset = (frame * outputFaces + mappedFace) * faceSize * bytesPerPixel; + uint8_t* outPixels = image.pixels.data() + outOffset; - if ( header->highResImageFormat == impl::IMAGE_FORMAT_DXT1 ) { - for ( int by = 0; by < blocksY; ++by ) { - for ( int bx = 0; bx < blocksX; ++bx ) { - impl::decompressDXT1Block(data + (by * blocksX + bx) * 8, outPixels, bx * 4, by * 4, header->width, header->height); - } - } - offset += blocksX * blocksY * 8; - } else if ( header->highResImageFormat == impl::IMAGE_FORMAT_DXT5 ) { - for ( int by = 0; by < blocksY; ++by) { - for ( int bx = 0; bx < blocksX; ++bx) { - impl::decompressDXT5Block(data + (by * blocksX + bx) * 16, outPixels, bx * 4, by * 4, header->width, header->height); - } - } - offset += blocksX * blocksY * 16; - } else if ( header->highResImageFormat == impl::IMAGE_FORMAT_BGRA8888 ) { - for ( auto i = 0; i < faceSizePixels; ++i ) { - outPixels[i * 4 + 0] = data[i * 4 + 2]; - outPixels[i * 4 + 1] = data[i * 4 + 1]; - outPixels[i * 4 + 2] = data[i * 4 + 0]; - outPixels[i * 4 + 3] = data[i * 4 + 3]; - } - offset += faceSizePixels * 4; - } - - if ( isCubemap ) { - int mode = faceModes[mappedFace]; - if ( mode == 0 ) continue; - uf::stl::vector temp(faceSizePixels * 4); - memcpy(temp.data(), outPixels, faceSizePixels * 4); - - int size = header->width; - int max = size - 1; - - for ( int y = 0; y < size; ++y ) { - for ( int x = 0; x < size; ++x ) { - int srcX = x; - int srcY = y; - - switch ( mode ) { - case 1: srcX = y; srcY = max - x; break; // 90 CW - case 2: srcX = max - x; srcY = max - y; break; // 180 - case 3: srcX = max - y; srcY = x; break; // 90 CCW - case 4: srcX = max - x; srcY = y; break; // Flip Horizontal - case 5: srcX = x; srcY = max - y; break; // Flip Vertical - case 6: srcX = y; srcY = x; break; // Transpose (Diagonal Flip) - case 7: srcX = max - y; srcY = max - x; break; // Anti-Transpose + if ( header->highResImageFormat == impl::IMAGE_FORMAT_DXT1 ) { + for ( int by = 0; by < blocksY; ++by ) { + for ( int bx = 0; bx < blocksX; ++bx ) { + impl::decompressDXT1Block(data + (by * blocksX + bx) * 8, outPixels, bx * 4, by * 4, header->width, header->height); } + } + offset += blocksX * blocksY * 8; + } else if ( header->highResImageFormat == impl::IMAGE_FORMAT_DXT3 ) { + for ( int by = 0; by < blocksY; ++by) { + for ( int bx = 0; bx < blocksX; ++bx) { + impl::decompressDXT3Block(data + (by * blocksX + bx) * 16, outPixels, bx * 4, by * 4, header->width, header->height); + } + } + offset += blocksX * blocksY * 16; - int srcIdx = (srcY * size + srcX) * 4; - int dstIdx = (y * size + x) * 4; + } else if ( header->highResImageFormat == impl::IMAGE_FORMAT_DXT5 ) { + for ( int by = 0; by < blocksY; ++by) { + for ( int bx = 0; bx < blocksX; ++bx) { + impl::decompressDXT5Block(data + (by * blocksX + bx) * 16, outPixels, bx * 4, by * 4, header->width, header->height); + } + } + offset += blocksX * blocksY * 16; + } else if ( header->highResImageFormat == impl::IMAGE_FORMAT_BGRX8888 || header->highResImageFormat == impl::IMAGE_FORMAT_RGBA8888 ) { + bool isRgba = (header->highResImageFormat == impl::IMAGE_FORMAT_RGBA8888); + for ( auto i = 0; i < faceSize; ++i ) { + outPixels[i * 4 + 0] = data[i * 4 + (isRgba ? 0 : 2)]; + outPixels[i * 4 + 1] = data[i * 4 + 1]; + outPixels[i * 4 + 2] = data[i * 4 + (isRgba ? 2 : 0)]; + outPixels[i * 4 + 3] = isRgba ? data[i * 4 + 3] : 255; + } + offset += faceSize * 4; + } else if ( header->highResImageFormat == impl::IMAGE_FORMAT_BGR888 ) { + for ( auto i = 0; i < faceSize; ++i ) { + outPixels[i * 4 + 0] = data[i * 3 + 2]; + outPixels[i * 4 + 1] = data[i * 3 + 1]; + outPixels[i * 4 + 2] = data[i * 3 + 0]; + outPixels[i * 4 + 3] = 255; + } + offset += faceSize * 3; + } else if ( header->highResImageFormat == impl::IMAGE_FORMAT_BGRA8888 ) { + for ( auto i = 0; i < faceSize; ++i ) { + outPixels[i * 4 + 0] = data[i * 4 + 2]; + outPixels[i * 4 + 1] = data[i * 4 + 1]; + outPixels[i * 4 + 2] = data[i * 4 + 0]; + outPixels[i * 4 + 3] = data[i * 4 + 3]; + } + offset += faceSize * 4; + } else if ( header->highResImageFormat == impl::IMAGE_FORMAT_RGB888 ) { + for ( auto i = 0; i < faceSize; ++i ) { + outPixels[i * 4 + 0] = data[i * 3 + 0]; + outPixels[i * 4 + 1] = data[i * 3 + 1]; + outPixels[i * 4 + 2] = data[i * 3 + 2]; + outPixels[i * 4 + 3] = 255; + } + offset += faceSize * 3; + } else if ( header->highResImageFormat == impl::IMAGE_FORMAT_BGR565 ) { + for ( auto i = 0; i < faceSize; ++i ) { + uint16_t color = *(const uint16_t*)(data + i * 2); + impl::decodeRGB565(color, outPixels[i * 4 + 0], outPixels[i * 4 + 1], outPixels[i * 4 + 2]); + outPixels[i * 4 + 3] = 255; + } + offset += faceSize * 2; + } else if ( header->highResImageFormat == impl::IMAGE_FORMAT_RGBA16161616F ) { + std::memcpy(outPixels, data, faceSize * 8); + offset += faceSize * 8; + } else { + UF_MSG_ERROR("VTF '{}' has unimplemented format: 0x{:x}", filename, header->highResImageFormat ); + } - outPixels[dstIdx + 0] = temp[srcIdx + 0]; - outPixels[dstIdx + 1] = temp[srcIdx + 1]; - outPixels[dstIdx + 2] = temp[srcIdx + 2]; - outPixels[dstIdx + 3] = temp[srcIdx + 3]; + if ( isCubemap ) { + int mode = faceModes[mappedFace]; + if ( mode == 0 ) continue; + uf::stl::vector temp(faceSize * bytesPerPixel); + std::memcpy(temp.data(), outPixels, faceSize * bytesPerPixel); + + int size = header->width; + int max = size - 1; + + for ( int y = 0; y < size; ++y ) { + for ( int x = 0; x < size; ++x ) { + int srcX = x, srcY = y; + + switch ( mode ) { + case 1: srcX = y; srcY = max - x; break; // 90 CW + case 2: srcX = max - x; srcY = max - y; break; // 180 + case 3: srcX = max - y; srcY = x; break; // 90 CCW + case 4: srcX = max - x; srcY = y; break; // Flip Horizontal + case 5: srcX = x; srcY = max - y; break; // Flip Vertical + case 6: srcX = y; srcY = x; break; // Transpose + case 7: srcX = max - y; srcY = max - x; break; // Anti-Transpose + } + + int srcIdx = (srcY * size + srcX) * bytesPerPixel; + int dstIdx = (y * size + x) * bytesPerPixel; + + std::memcpy(&outPixels[dstIdx], &temp[srcIdx], bytesPerPixel); + } } } } } - if ( (header->flags & impl::TEXTUREFLAGS_NORMAL) != 0 && header->highResImageFormat == impl::IMAGE_FORMAT_DXT5 ) { - size_t pixelCount = image.pixels.size() / 4; - for ( size_t i = 0; i < pixelCount; ++i ) { - uint8_t& r = image.pixels[i * 4 + 0]; - uint8_t& g = image.pixels[i * 4 + 1]; - uint8_t& b = image.pixels[i * 4 + 2]; - uint8_t& a = image.pixels[i * 4 + 3]; - - float x = (a / 255.0f) * 2.0f - 1.0f; - float y = (g / 255.0f) * 2.0f - 1.0f; - - y = -y; - - float z = std::sqrt(std::max(1.0f - (x * x + y * y), 0.0f)); - - r = (uint8_t)((x * 0.5f + 0.5f) * 255.0f); - g = (uint8_t)((y * 0.5f + 0.5f) * 255.0f); - b = (uint8_t)((z * 0.5f + 0.5f) * 255.0f); - - a = 255; - } - } - return true; } \ No newline at end of file diff --git a/engine/src/ext/vulkan/texture.cpp b/engine/src/ext/vulkan/texture.cpp index f17f8b61..468baad8 100644 --- a/engine/src/ext/vulkan/texture.cpp +++ b/engine/src/ext/vulkan/texture.cpp @@ -405,59 +405,41 @@ void ext::vulkan::Texture::loadFromImage( VkImageLayout layout, VkImageCreateFlags flags ) { -/* - switch ( format ) { - case enums::Format::R8_SRGB: - case enums::Format::R8G8_SRGB: - case enums::Format::R8G8B8_SRGB: - case enums::Format::R8G8B8A8_SRGB: - srgb = true; - break; - } -*/ switch ( image.getChannels() ) { // R case 1: switch ( image.getBpp() ) { - case 8: - format = srgb ? VK_FORMAT_R8_SRGB : VK_FORMAT_R8_UNORM; - break; - default: - UF_EXCEPTION("Vulkan error: unsupported BPP of {}", image.getBpp() ); - break; + case 8: format = srgb ? VK_FORMAT_R8_SRGB : VK_FORMAT_R8_UNORM; break; + case 16: format = VK_FORMAT_R16_SFLOAT; break; // Half-float + case 32: format = VK_FORMAT_R32_SFLOAT; break; // Full-float + default: UF_EXCEPTION("Vulkan error: unsupported BPP of {}", image.getBpp() ); break; } break; // RG case 2: switch ( image.getBpp() ) { - case 16: - format = srgb ? VK_FORMAT_R8G8_SRGB : VK_FORMAT_R8G8_UNORM; - break; - default: - UF_EXCEPTION("Vulkan error: unsupported BPP of {}", image.getBpp() ); - break; + case 16: format = srgb ? VK_FORMAT_R8G8_SRGB : VK_FORMAT_R8G8_UNORM; break; + case 32: format = VK_FORMAT_R16G16_SFLOAT; break; + case 64: format = VK_FORMAT_R32G32_SFLOAT; break; + default: UF_EXCEPTION("Vulkan error: unsupported BPP of {}", image.getBpp() ); break; } break; // RGB case 3: switch ( image.getBpp() ) { - case 24: - format = srgb ? VK_FORMAT_R8G8B8_SRGB : VK_FORMAT_R8G8B8_UNORM; - break; - default: - UF_EXCEPTION("Vulkan error: unsupported BPP of {}", image.getBpp() ); - break; + case 24: format = srgb ? VK_FORMAT_R8G8B8_SRGB : VK_FORMAT_R8G8B8_UNORM; break; + case 48: format = VK_FORMAT_R16G16B16_SFLOAT; break; + case 96: format = VK_FORMAT_R32G32B32_SFLOAT; break; + default: UF_EXCEPTION("Vulkan error: unsupported BPP of {}", image.getBpp() ); break; } break; // RGBA case 4: switch ( image.getBpp() ) { - case 32: - format = srgb ? VK_FORMAT_R8G8B8A8_SRGB : VK_FORMAT_R8G8B8A8_UNORM; - break; - default: - UF_EXCEPTION("Vulkan error: unsupported BPP of {}", image.getBpp() ); - break; + case 32: format = srgb ? VK_FORMAT_R8G8B8A8_SRGB : VK_FORMAT_R8G8B8A8_UNORM; break; + case 64: format = VK_FORMAT_R16G16B16A16_SFLOAT; break; // 16-bit HDR + case 128: format = VK_FORMAT_R32G32B32A32_SFLOAT; break; // 32-bit HDR + default: UF_EXCEPTION("Vulkan error: unsupported BPP of {}", image.getBpp() ); break; } break; default: @@ -465,9 +447,6 @@ void ext::vulkan::Texture::loadFromImage( break; } - // convert to power of two - //image.padToPowerOfTwo(); - this->fromBuffers( (void*) image.getPixelsPtr(), image.getPixels().size(), diff --git a/engine/src/utils/image/image.cpp b/engine/src/utils/image/image.cpp index dc3bd554..94a427cd 100644 --- a/engine/src/utils/image/image.cpp +++ b/engine/src/utils/image/image.cpp @@ -20,6 +20,32 @@ namespace { auto* bytes = static_cast(data); buffer->insert(buffer->end(), bytes, bytes + size); } + + inline uint8_t halfTo8Bit(uint16_t h) { + uint32_t exp = (h >> 10) & 0x1F; + uint32_t mant = h & 0x3FF; + if (exp == 0) return 0; + if (exp == 31) return 255; + + exp = exp + (127 - 15); + uint32_t f = (exp << 23) | (mant << 13); + float val; + std::memcpy(&val, &f, sizeof(float)); + + return (uint8_t)(std::min(std::max(val, 0.0f), 1.0f) * 255.0f); + } + + inline float halfToFloat(uint16_t h) { + uint32_t exp = (h >> 10) & 0x1F; + uint32_t mant = h & 0x3FF; + if (exp == 0 && mant == 0) return 0.0f; + + exp = exp + (127 - 15); + uint32_t f = (exp << 23) | (mant << 13); + float val; + std::memcpy(&val, &f, sizeof(float)); + return val; + } } namespace impl { @@ -142,10 +168,10 @@ bool uf::image::open( pod::Image& image, const uf::stl::vector& buffer, // palette data: buffer.data() + sizeof(header) + header.size - bool twiddled = (header.type & (1 << 26)) < 1; - bool compressed = (header.type & (1 << 30)) > 0; - bool mipmapped = (header.type & (1 << 31)) > 0; - bool strided = (header.type & (1 << 25)) > 0; + bool twiddled = (header.type & (1 << 26)) < 1; + bool compressed = (header.type & (1 << 30)) > 0; + bool mipmapped = (header.type & (1 << 31)) > 0; + bool strided = (header.type & (1 << 25)) > 0; uint32_t format = (header.type >> 27) & 0b111; width = header.width; height = header.height; @@ -173,22 +199,40 @@ bool uf::image::open( pod::Image& image, const uf::stl::vector& buffer, #endif { stbi_set_flip_vertically_on_load( flip ); - uint8_t* stbi_pixels = stbi_load_from_memory( buffer.data(), buffer.size(), &width, &height, &channelsDud, STBI_rgb_alpha ); + + if ( stbi_is_hdr_from_memory( buffer.data(), buffer.size() ) ) { + float* stbi_pixels = stbi_loadf_from_memory( buffer.data(), buffer.size(), &width, &height, &channelsDud, STBI_rgb_alpha ); + if ( !stbi_pixels ) { + UF_EXCEPTION("Image error: stb_image failed to decode HDR buffer"); + return false; + } - if ( !stbi_pixels ) { - UF_EXCEPTION("Image error: stb_image failed to decode buffer"); - return false; + size_t len = width * height * 4 * sizeof(float); + image.pixels.resize( len ); + std::memcpy( image.pixels.data(), stbi_pixels, len ); + stbi_image_free(stbi_pixels); + + bpp = 32; + channels = 4; + } else { + uint8_t* stbi_pixels = stbi_load_from_memory( buffer.data(), buffer.size(), &width, &height, &channelsDud, STBI_rgb_alpha ); + if ( !stbi_pixels ) { + UF_EXCEPTION("Image error: stb_image failed to decode buffer"); + return false; + } + + size_t len = width * height * 4; + image.pixels.resize( len ); + std::memcpy( image.pixels.data(), stbi_pixels, len ); + stbi_image_free(stbi_pixels); + + bpp = 8; + channels = 4; } - - size_t len = width * height * channels; - image.pixels.resize( len ); - memcpy( image.pixels.data(), stbi_pixels, len ); - - stbi_image_free(stbi_pixels); } image.size.x = width; - image.size.y = height; + image.size.y = height / MAX(1, image.layers); image.bpp = bpp * channels; image.channels = channels; return true; @@ -235,12 +279,25 @@ void uf::image::save( const pod::Image& image, uf::stl::vector& buffer, if ( image.pixels.empty() ) return; uint w = image.size.x; - uint h = image.size.y; + uint h = image.pixels.size() / (w * (image.bpp / 8)); auto* pixels = &image.pixels[0]; uf::stl::string extension = image.filename.empty() ? "png" : uf::io::extension( image.filename ); stbi_flip_vertically_on_write(flip); - if ( extension == "png" ) { + if ( extension == "hdr" ) { + if ( image.bpp == 128 ) { + stbi_write_hdr_to_func(stbi_buffer_write_func, &buffer, w, h, image.channels, (const float*)pixels); + } else if ( image.bpp == 64 ) { + size_t pixelCount = w * h * image.channels; + + uf::stl::vector temp32(pixelCount); + const uint16_t* halfPixels = (const uint16_t*)pixels; + for ( size_t i = 0; i < pixelCount; ++i ) temp32[i] = ::halfToFloat(halfPixels[i]); + stbi_write_hdr_to_func(stbi_buffer_write_func, &buffer, w, h, image.channels, temp32.data()); + } else { + UF_MSG_ERROR("Cannot save 8-bit image natively as HDR."); + } + } else if ( extension == "png" ) { stbi_write_png_to_func(stbi_buffer_write_func, &buffer, w, h, image.channels, pixels, w * image.channels); } else if ( extension == "jpg" || extension == "jpeg" ) { stbi_write_jpg_to_func(stbi_buffer_write_func, &buffer, w, h, image.channels, pixels, 90); // quality @@ -252,6 +309,11 @@ void uf::image::save( const pod::Image& image, std::ostream& stream ) { } +void uf::image::layers( pod::Image& image, size_t layers ) { + if ( image.layers != layers ) image.size.y /= MAX(1, layers); + image.layers = layers; +} + pod::Image::pixel_t uf::image::at( pod::Image& image, const pod::Vector2ui& at ) { size_t i = at.x * image.channels + image.size.x * image.channels * at.y; return { @@ -414,66 +476,7 @@ pod::Image uf::image::scale( const pod::Image& image, const pod::Vector2ui& size if ( filter == "linear" || filter == "bilinear" ) return impl::scaleBilinear( image, size );\ UF_EXCEPTION("unrecognized scale filter: {}", filter ); } -/* -uf::Image::Image() { - size = {0,0}; - bpp = 8; - channels = 4; - format = 0; -} -uf::Image::Image(const pod::Vector2ui& s) { - size = s; - bpp = 8; - channels = 4; - format = 0; - pixels.resize(size.x * size.y * channels); -} - -uf::Image::Image( pod::Image::container_t&& move, const pod::Vector2ui& s ) { - pixels = std::move( move ); - size = s; - bpp = 8; - channels = 4; - format = 0; -} - -uf::Image::Image( const pod::Image::container_t& copy, const pod::Vector2ui& s ) { - pixels = copy; - size = s; - bpp = 8; - channels = 4; - format = 0; -} - -uf::Image::Image( const uf::Image& copy ) { - this->copy( copy ); -} - -uf::Image::Image( uf::Image&& move ) noexcept { - //this->move( move ); - pixels = std::move( move.pixels ); - size = move.size; - bpp = move.bpp; - channels = move.channels; - format = move.format; -} - -uf::Image& uf::Image::operator=( const uf::Image& copy ) { - this->copy( copy ); - return *this; -} - -uf::Image& uf::Image::operator=( uf::Image&& move ) noexcept { - //this->move( move ); - pixels = std::move( move.pixels ); - size = move.size; - bpp = move.bpp; - channels = move.channels; - format = move.format; - return *this; -} -*/ uf::stl::string uf::Image::getFilename() const { return this->filename; } @@ -573,6 +576,9 @@ size_t uf::Image::getFormat() const { uf::stl::string uf::Image::getHash() const { return uf::image::hash( *this ); } +void uf::Image::setLayers( size_t layers ) { + return uf::image::layers( *this, layers ); +} pod::Image::pixel_t uf::Image::at( const pod::Vector2ui& at ) { return uf::image::at( *this, at ); }