From c4cc0b56c083029f5e6c992bb728ccb05036a484 Mon Sep 17 00:00:00 2001 From: ayadav-macOs Date: Sun, 30 Aug 2026 22:20:40 -0400 Subject: [PATCH 1/8] fix: improve error handling in ShaderPipelineBuilder - Updated error logging in ShaderPipelineBuilder to provide clearer messages when reflection and cross-compilation fail, enhancing debugging capabilities. - Refactored error handling to ensure consistent return of error codes and messages, improving overall reliability. - Added a new test case to validate behavior when WGSL target compilation fails due to unavailable Tint support, ensuring robustness in shader pipeline testing. --- cmake/vnecmake | 2 +- src/vertexnova/sc/shader_pipeline_builder.cpp | 19 +++++++++--------- tests/shader_pipeline_builder_test.cpp | 20 +++++++++++++++++++ 3 files changed, 30 insertions(+), 11 deletions(-) diff --git a/cmake/vnecmake b/cmake/vnecmake index f35a45c..484ede2 160000 --- a/cmake/vnecmake +++ b/cmake/vnecmake @@ -1 +1 @@ -Subproject commit f35a45c259272488899b45d90fec6400b183f8da +Subproject commit 484ede2ac67b59a1981e39514d983a5e9b4ca6b2 diff --git a/src/vertexnova/sc/shader_pipeline_builder.cpp b/src/vertexnova/sc/shader_pipeline_builder.cpp index 1b74f15..0a6ae7f 100644 --- a/src/vertexnova/sc/shader_pipeline_builder.cpp +++ b/src/vertexnova/sc/shader_pipeline_builder.cpp @@ -189,7 +189,10 @@ PipelineBuildResult ShaderPipelineBuilder::build(const PipelineBuildDesc& desc) if (rr.ok()) { sw.artifact.reflection = std::move(rr.reflection); } else { - VNE_LOG_WARN << "ShaderPipelineBuilder: reflection failed (non-fatal): " << rr.error; + result.code = rr.code; + result.error = "ShaderPipelineBuilder: reflection failed: " + rr.error; + VNE_LOG_ERROR << result.error; + return result; } } @@ -220,15 +223,11 @@ PipelineBuildResult ShaderPipelineBuilder::build(const PipelineBuildDesc& desc) cc.entry_point = std::move(ccres.entry_point); sw.artifact.cross_compiled.push_back(std::move(cc)); } else { - if (target == CrossTarget::eWGSL) { - VNE_LOG_WARN << "ShaderPipelineBuilder: WGSL cross-compile failed (non-fatal): " << ccres.error; - } else { - result.code = ccres.code; - result.error = "ShaderPipelineBuilder: cross-compile to target " - + std::to_string(static_cast(target)) + " failed: " + ccres.error; - VNE_LOG_ERROR << result.error; - return result; - } + result.code = ccres.code; + result.error = "ShaderPipelineBuilder: cross-compile to target " + + std::to_string(static_cast(target)) + " failed: " + ccres.error; + VNE_LOG_ERROR << result.error; + return result; } } } diff --git a/tests/shader_pipeline_builder_test.cpp b/tests/shader_pipeline_builder_test.cpp index d3748d3..fec788b 100644 --- a/tests/shader_pipeline_builder_test.cpp +++ b/tests/shader_pipeline_builder_test.cpp @@ -231,4 +231,24 @@ TEST_F(ShaderPipelineBuilderTest, WgslLeavesVulkanSetBindingIdentity) { EXPECT_EQ(vs->reflection.bindings[0].binding, 0u); } +TEST_F(ShaderPipelineBuilderTest, WgslTargetFailsWhenTintUnavailable) { +#ifndef VNE_SC_TINT_ENABLED + auto builder = makeGlslPipelineBuilder(); + ASSERT_NE(builder, nullptr); + + PipelineBuildDesc desc; + desc.name = "wgsl_unavailable"; + desc.validate = false; + desc.use_cache = false; + desc.targets = {CrossTarget::eWGSL}; + desc.stages.push_back(makeVertexRequest()); + + auto result = builder->build(desc); + EXPECT_FALSE(result.ok()); + EXPECT_EQ(result.code, ResultCode::eUnavailable); +#else + GTEST_SKIP() << "Tint is enabled; unavailable path not exercised"; +#endif +} + } // namespace vne::sc::test From 51e5c90754edf87cec9b59f6078d670a87e9abf3 Mon Sep 17 00:00:00 2001 From: ayadav-macOs Date: Tue, 8 Sep 2026 00:14:21 -0400 Subject: [PATCH 2/8] feat: enhance shader reflection and resource handling - Updated `ReflectedResourceType` to include new resource types: `eSampled2DArray`, `eSampledCubeArray`, `eReadOnlyStorageBuffer`, and `eSampledImage3D`, improving shader reflection capabilities. - Enhanced `writeReflectedBindingInfo` and `readReflectedBindingInfo` functions to support new fields for storage format hints, access hints, and texture properties (multisampled, depth texture, dynamic offset). - Introduced a new function `imageFormatName` to map SPIR-V image formats to WGSL storage-texture format names, ensuring compatibility with WebGPU expectations. - Updated `appendBinding` logic to refine resource type determination based on image dimensionality, enhancing the accuracy of reflected resource types. - Adjusted CMake configuration to enable the Dawn Tint SPIR-V->WGSL cross-compiler by default, streamlining the build process for users. --- CMakeLists.txt | 2 +- cmake/vnecmake | 2 +- include/vertexnova/sc/sc_types.h | 20 +++++ .../sc/reflector/spirvcross_reflector.cpp | 84 ++++++++++++++++++- src/vertexnova/sc/shader_bundle.cpp | 8 ++ .../sc/shader_reflection_binary.cpp | 23 ++++- tools/compile_shaders.py | 2 + 7 files changed, 135 insertions(+), 6 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index ee0d26b..1714324 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -137,7 +137,7 @@ option(VNE_SC_GLSLANG "Enable glslang GLSL->SPIR-V front-end (Phase 1, primar option(VNE_SC_DXC "Enable DXC HLSL->SPIR-V front-end (Phase 2 - stub only)" OFF) option(VNE_SC_SLANG "Enable Slang front-end (Phase 3 - stub only)" OFF) option(VNE_SC_SPIRVTOOLS "Enable SPIRV-Tools SPIR-V validator (OFF = no-op validator)" OFF) -option(VNE_SC_TINT "Enable Dawn Tint SPIR-V->WGSL cross-compiler (slow first configure)" OFF) +option(VNE_SC_TINT "Enable Dawn Tint SPIR-V->WGSL cross-compiler (slow first configure)" ON) option(VNE_SC_JSON "Enable nlohmann/json for manifests and bundle metadata" ON) option(ENABLE_DOXYGEN "Enable Doxygen documentation builds" OFF) diff --git a/cmake/vnecmake b/cmake/vnecmake index 484ede2..f35a45c 160000 --- a/cmake/vnecmake +++ b/cmake/vnecmake @@ -1 +1 @@ -Subproject commit 484ede2ac67b59a1981e39514d983a5e9b4ca6b2 +Subproject commit f35a45c259272488899b45d90fec6400b183f8da diff --git a/include/vertexnova/sc/sc_types.h b/include/vertexnova/sc/sc_types.h index 21dfb95..b98a95e 100644 --- a/include/vertexnova/sc/sc_types.h +++ b/include/vertexnova/sc/sc_types.h @@ -185,6 +185,10 @@ struct ResourceBackendSlots { }; /// Classifies a reflected shader resource. +/// +/// @note Values must stay numerically identical to `vne::rhi::ReflectedResourceType` +/// (vnerhi/include/vertexnova/rhi/shader_reflection.h). `reflection.bin` stores the raw +/// value and vnerhi casts it straight across. enum class ReflectedResourceType : uint8_t { eUniformBuffer = 0, eStorageBuffer = 1, @@ -194,6 +198,10 @@ enum class ReflectedResourceType : uint8_t { ePushConstant = 5, eCombinedImageSampler = 6, eSampledCubemap = 7, + eSampled2DArray = 8, ///< texture2DArray / texture_2d_array + eSampledCubeArray = 9, ///< textureCubeArray / texture_cube_array + eReadOnlyStorageBuffer = 10, ///< SSBO declared readonly (WGSL `storage, read`) + eSampledImage3D = 11, ///< texture3D / texture_3d }; /// A single member of a reflected struct (e.g. a field inside a uniform block). @@ -219,6 +227,18 @@ struct ReflectedBindingInfo { ShaderStageFlags stages = ShaderStageFlags::eNone; ResourceBackendSlots slots; std::vector struct_members; ///< Non-empty for buffer types. + + // ----- Fields WebGPU needs to build a bind-group layout (reflection.bin v3) ----- + /// Storage-texture format, e.g. "rgba16float". Empty for non-storage-image bindings. + std::string storage_format_hint; + /// Storage-texture access: "read", "write", or "read_write". Empty when not applicable. + std::string storage_access_hint; + /// True for multisampled textures (`texture2DMS`, WGSL `texture_multisampled_2d`). + bool multisampled = false; + /// True for depth textures (WGSL `texture_depth_2d` and friends) -- drives the sample type. + bool depth_texture = false; + /// The buffer is re-offset per draw. Not derivable from SPIR-V; set by tooling/renderer. + bool dynamic_offset = false; }; /// Workgroup size for compute shaders. diff --git a/src/vertexnova/sc/reflector/spirvcross_reflector.cpp b/src/vertexnova/sc/reflector/spirvcross_reflector.cpp index 2745478..4c96947 100644 --- a/src/vertexnova/sc/reflector/spirvcross_reflector.cpp +++ b/src/vertexnova/sc/reflector/spirvcross_reflector.cpp @@ -57,6 +57,30 @@ std::vector reflectStructMembers(const spirv_cro return members; } +// Maps a SPIR-V image format to the WGSL storage-texture format name WebGPU expects. +[[nodiscard]] std::string imageFormatName(spv::ImageFormat fmt) { + switch (fmt) { + case spv::ImageFormatRgba32f: return "rgba32float"; + case spv::ImageFormatRgba16f: return "rgba16float"; + case spv::ImageFormatR32f: return "r32float"; + case spv::ImageFormatRgba8: return "rgba8unorm"; + case spv::ImageFormatRgba8Snorm: return "rgba8snorm"; + case spv::ImageFormatRg32f: return "rg32float"; + case spv::ImageFormatRg16f: return "rg16float"; + case spv::ImageFormatRgba32i: return "rgba32sint"; + case spv::ImageFormatRgba16i: return "rgba16sint"; + case spv::ImageFormatRgba8i: return "rgba8sint"; + case spv::ImageFormatR32i: return "r32sint"; + case spv::ImageFormatRgba32ui: return "rgba32uint"; + case spv::ImageFormatRgba16ui: return "rgba16uint"; + case spv::ImageFormatRgba8ui: return "rgba8uint"; + case spv::ImageFormatR32ui: return "r32uint"; + case spv::ImageFormatRg32i: return "rg32sint"; + case spv::ImageFormatRg32ui: return "rg32uint"; + default: return {}; + } +} + // Populate one ReflectedBindingInfo from a SPIRV-Cross resource template void appendBinding(const spirv_cross::Compiler& compiler, @@ -73,10 +97,60 @@ void appendBinding(const spirv_cross::Compiler& compiler, const spirv_cross::SPIRType& ty = compiler.get_type(res.type_id); const uint32_t array_size = ty.array.empty() ? 1u : (ty.array[0] == 0u ? 0u : ty.array[0]); + // Refine the coarse "which SPIRV-Cross list did this come from" type using the actual + // image dimensionality. WebGPU needs the exact texture kind to build a bind-group layout + // (texture_2d vs texture_2d_array vs texture_3d vs texture_cube[_array], plus depth and + // multisampled), and previously only the DimCube case was handled -- which is why vnerhi's + // WebGPU backend re-parsed the emitted WGSL instead of trusting reflection.bin. ReflectedResourceType actual_type = Type; - if constexpr (Type == ReflectedResourceType::eSampledImage) { - if (ty.image.dim == spv::DimCube) { - actual_type = ReflectedResourceType::eSampledCubemap; + std::string storage_format_hint; + std::string storage_access_hint; + bool multisampled = false; + bool depth_texture = false; + + if constexpr (Type == ReflectedResourceType::eSampledImage + || Type == ReflectedResourceType::eCombinedImageSampler) { + const bool arrayed = ty.image.arrayed; + switch (ty.image.dim) { + case spv::DimCube: + actual_type = arrayed ? ReflectedResourceType::eSampledCubeArray + : ReflectedResourceType::eSampledCubemap; + break; + case spv::Dim3D: + actual_type = ReflectedResourceType::eSampledImage3D; + break; + case spv::Dim2D: + if (arrayed) { + actual_type = ReflectedResourceType::eSampled2DArray; + } + break; + default: + break; + } + multisampled = ty.image.ms; + depth_texture = ty.image.depth; + } + + if constexpr (Type == ReflectedResourceType::eStorageImage) { + storage_format_hint = imageFormatName(ty.image.format); + const bool non_writable = compiler.has_decoration(res.id, spv::DecorationNonWritable); + const bool non_readable = compiler.has_decoration(res.id, spv::DecorationNonReadable); + if (non_writable && !non_readable) { + storage_access_hint = "read"; + } else if (non_readable && !non_writable) { + storage_access_hint = "write"; + } else { + storage_access_hint = "read_write"; + } + multisampled = ty.image.ms; + } + + if constexpr (Type == ReflectedResourceType::eStorageBuffer) { + // A readonly SSBO must be declared as `storage, read` in WGSL, and is the only storage + // buffer form WebGPU allows in a vertex stage. + const spirv_cross::Bitset flags = compiler.get_buffer_block_flags(res.id); + if (flags.get(spv::DecorationNonWritable)) { + actual_type = ReflectedResourceType::eReadOnlyStorageBuffer; } } @@ -137,6 +211,10 @@ void appendBinding(const spirv_cross::Compiler& compiler, info.array_size = array_size; info.stages = ShaderStageFlags::eNone; info.slots = slots; + info.storage_format_hint = std::move(storage_format_hint); + info.storage_access_hint = std::move(storage_access_hint); + info.multisampled = multisampled; + info.depth_texture = depth_texture; if constexpr (Type == ReflectedResourceType::eUniformBuffer || Type == ReflectedResourceType::eStorageBuffer) { try { diff --git a/src/vertexnova/sc/shader_bundle.cpp b/src/vertexnova/sc/shader_bundle.cpp index 4a8180c..405552d 100644 --- a/src/vertexnova/sc/shader_bundle.cpp +++ b/src/vertexnova/sc/shader_bundle.cpp @@ -245,6 +245,14 @@ bool writeShaderBundle(const ShaderArtifact& artifact, const std::filesystem::pa return "combined_image_sampler"; case ReflectedResourceType::eSampledCubemap: return "sampled_cubemap"; + case ReflectedResourceType::eSampled2DArray: + return "sampled_2d_array"; + case ReflectedResourceType::eSampledCubeArray: + return "sampled_cube_array"; + case ReflectedResourceType::eReadOnlyStorageBuffer: + return "readonly_storage_buffer"; + case ReflectedResourceType::eSampledImage3D: + return "sampled_image_3d"; } return "unknown"; }; diff --git a/src/vertexnova/sc/shader_reflection_binary.cpp b/src/vertexnova/sc/shader_reflection_binary.cpp index be5efd6..6b20939 100644 --- a/src/vertexnova/sc/shader_reflection_binary.cpp +++ b/src/vertexnova/sc/shader_reflection_binary.cpp @@ -15,7 +15,17 @@ namespace { -constexpr uint32_t kReflectionBinaryVersion = 2u; // bumped: BackendSlot -> ResourceBackendSlots +// reflection.bin format version, v1.0.0, packed into the single u32 the header already +// carries: 0xMMmmpp (major, minor, patch). Readers require an exact match -- a stale bundle +// fails loudly and is regenerated rather than silently half-read. +// Keep in lockstep with vnerhi's shader_bundle_loader.cpp. +// (Independent of bundle.header's own version -- see shader_bundle.cpp.) +constexpr uint32_t kReflectionBinaryVersionMajor = 1u; +constexpr uint32_t kReflectionBinaryVersionMinor = 0u; +constexpr uint32_t kReflectionBinaryVersionPatch = 0u; +constexpr uint32_t kReflectionBinaryVersion = (kReflectionBinaryVersionMajor << 16U) + | (kReflectionBinaryVersionMinor << 8U) + | kReflectionBinaryVersionPatch; constexpr uint32_t kMaxStringLen = 4096; constexpr uint32_t kMaxProgramStageCount = 16; constexpr uint32_t kMaxBindingCount = 256; @@ -226,6 +236,12 @@ void writeReflectedBindingInfo(Writer& w, const vne::sc::ReflectedBindingInfo& b for (const auto& m : b.struct_members) { writeReflectedStructMember(w, m); } + // ---- v3 ---- + w.str(b.storage_format_hint); + w.str(b.storage_access_hint); + w.boolean(b.multisampled); + w.boolean(b.depth_texture); + w.boolean(b.dynamic_offset); } bool readReflectedBindingInfo(Reader& r, vne::sc::ReflectedBindingInfo& b) { @@ -254,6 +270,11 @@ bool readReflectedBindingInfo(Reader& r, vne::sc::ReflectedBindingInfo& b) { return false; } } + b.storage_format_hint = r.str(); + b.storage_access_hint = r.str(); + b.multisampled = r.boolean(); + b.depth_texture = r.boolean(); + b.dynamic_offset = r.boolean(); return r.ok(); } diff --git a/tools/compile_shaders.py b/tools/compile_shaders.py index ca418c0..0075779 100755 --- a/tools/compile_shaders.py +++ b/tools/compile_shaders.py @@ -39,6 +39,8 @@ # --------------------------------------------------------------------------- _BUNDLE_MAGIC = b"VNSH" +# bundle.header format version. Independent of reflection.bin's own version +# (see shader_reflection_binary.cpp) -- the two are bumped separately. _BUNDLE_VERSION = 2 From 2638e55cabda49125b2fcad62569b87a0314fa5b Mon Sep 17 00:00:00 2001 From: ayadav-macOs Date: Tue, 8 Sep 2026 00:21:08 -0400 Subject: [PATCH 3/8] refactor: enhance shader artifact cache and pipeline builder logic - Introduced a schema versioning system for the shader artifact cache to manage cache entries more effectively, ensuring that incomplete entries are not treated as hits. - Updated the `serializeArtifact` and `deserializeArtifact` functions to include schema version handling, improving data integrity during cache operations. - Enhanced the `ShaderPipelineBuilder` to reject incomplete cache entries, ensuring that only fully prepared artifacts are utilized in the pipeline, thus improving reliability and performance. - Refined comments and documentation for clarity on the new schema versioning and its implications for shader artifact management. --- include/vertexnova/sc/sc_types.h | 8 +-- .../vertexnova/sc/shader_pipeline_builder.h | 3 +- .../sc/reflector/spirvcross_reflector.cpp | 58 ++++++++++++------- src/vertexnova/sc/shader_artifact_cache.cpp | 10 +++- src/vertexnova/sc/shader_pipeline_builder.cpp | 33 +++++++++-- .../sc/shader_reflection_binary.cpp | 5 +- tests/shader_pipeline_builder_test.cpp | 4 ++ 7 files changed, 87 insertions(+), 34 deletions(-) diff --git a/include/vertexnova/sc/sc_types.h b/include/vertexnova/sc/sc_types.h index b98a95e..05457e5 100644 --- a/include/vertexnova/sc/sc_types.h +++ b/include/vertexnova/sc/sc_types.h @@ -198,10 +198,10 @@ enum class ReflectedResourceType : uint8_t { ePushConstant = 5, eCombinedImageSampler = 6, eSampledCubemap = 7, - eSampled2DArray = 8, ///< texture2DArray / texture_2d_array - eSampledCubeArray = 9, ///< textureCubeArray / texture_cube_array - eReadOnlyStorageBuffer = 10, ///< SSBO declared readonly (WGSL `storage, read`) - eSampledImage3D = 11, ///< texture3D / texture_3d + eSampled2DArray = 8, ///< texture2DArray / texture_2d_array + eSampledCubeArray = 9, ///< textureCubeArray / texture_cube_array + eReadOnlyStorageBuffer = 10, ///< SSBO declared readonly (WGSL `storage, read`) + eSampledImage3D = 11, ///< texture3D / texture_3d }; /// A single member of a reflected struct (e.g. a field inside a uniform block). diff --git a/include/vertexnova/sc/shader_pipeline_builder.h b/include/vertexnova/sc/shader_pipeline_builder.h index f1d6b26..a099a49 100644 --- a/include/vertexnova/sc/shader_pipeline_builder.h +++ b/include/vertexnova/sc/shader_pipeline_builder.h @@ -72,7 +72,8 @@ struct PipelineBuildResult { * Per stage the pipeline is: * -# Cache lookup (key includes a source-level program fingerprint for MSL dense maps). * -# On miss: @ref IShaderFrontEnd::compile, optional validate, reflect, cross-compile, store. - * -# On hit: reuse the cached @ref StageArtifact (no front-end compile). + * -# On hit: reuse the cached @ref StageArtifact when it covers every requested + * target (no front-end compile); incomplete entries are rebuilt. * * When any stage misses and MSL dense program maps are enabled, one shared * @ref MetalBindingAllocator is built from the union of all stage SPIR-V diff --git a/src/vertexnova/sc/reflector/spirvcross_reflector.cpp b/src/vertexnova/sc/reflector/spirvcross_reflector.cpp index 4c96947..43b24ea 100644 --- a/src/vertexnova/sc/reflector/spirvcross_reflector.cpp +++ b/src/vertexnova/sc/reflector/spirvcross_reflector.cpp @@ -60,24 +60,42 @@ std::vector reflectStructMembers(const spirv_cro // Maps a SPIR-V image format to the WGSL storage-texture format name WebGPU expects. [[nodiscard]] std::string imageFormatName(spv::ImageFormat fmt) { switch (fmt) { - case spv::ImageFormatRgba32f: return "rgba32float"; - case spv::ImageFormatRgba16f: return "rgba16float"; - case spv::ImageFormatR32f: return "r32float"; - case spv::ImageFormatRgba8: return "rgba8unorm"; - case spv::ImageFormatRgba8Snorm: return "rgba8snorm"; - case spv::ImageFormatRg32f: return "rg32float"; - case spv::ImageFormatRg16f: return "rg16float"; - case spv::ImageFormatRgba32i: return "rgba32sint"; - case spv::ImageFormatRgba16i: return "rgba16sint"; - case spv::ImageFormatRgba8i: return "rgba8sint"; - case spv::ImageFormatR32i: return "r32sint"; - case spv::ImageFormatRgba32ui: return "rgba32uint"; - case spv::ImageFormatRgba16ui: return "rgba16uint"; - case spv::ImageFormatRgba8ui: return "rgba8uint"; - case spv::ImageFormatR32ui: return "r32uint"; - case spv::ImageFormatRg32i: return "rg32sint"; - case spv::ImageFormatRg32ui: return "rg32uint"; - default: return {}; + case spv::ImageFormatRgba32f: + return "rgba32float"; + case spv::ImageFormatRgba16f: + return "rgba16float"; + case spv::ImageFormatR32f: + return "r32float"; + case spv::ImageFormatRgba8: + return "rgba8unorm"; + case spv::ImageFormatRgba8Snorm: + return "rgba8snorm"; + case spv::ImageFormatRg32f: + return "rg32float"; + case spv::ImageFormatRg16f: + return "rg16float"; + case spv::ImageFormatRgba32i: + return "rgba32sint"; + case spv::ImageFormatRgba16i: + return "rgba16sint"; + case spv::ImageFormatRgba8i: + return "rgba8sint"; + case spv::ImageFormatR32i: + return "r32sint"; + case spv::ImageFormatRgba32ui: + return "rgba32uint"; + case spv::ImageFormatRgba16ui: + return "rgba16uint"; + case spv::ImageFormatRgba8ui: + return "rgba8uint"; + case spv::ImageFormatR32ui: + return "r32uint"; + case spv::ImageFormatRg32i: + return "rg32sint"; + case spv::ImageFormatRg32ui: + return "rg32uint"; + default: + return {}; } } @@ -113,8 +131,8 @@ void appendBinding(const spirv_cross::Compiler& compiler, const bool arrayed = ty.image.arrayed; switch (ty.image.dim) { case spv::DimCube: - actual_type = arrayed ? ReflectedResourceType::eSampledCubeArray - : ReflectedResourceType::eSampledCubemap; + actual_type = + arrayed ? ReflectedResourceType::eSampledCubeArray : ReflectedResourceType::eSampledCubemap; break; case spv::Dim3D: actual_type = ReflectedResourceType::eSampledImage3D; diff --git a/src/vertexnova/sc/shader_artifact_cache.cpp b/src/vertexnova/sc/shader_artifact_cache.cpp index 32d5ddb..52cf6f5 100644 --- a/src/vertexnova/sc/shader_artifact_cache.cpp +++ b/src/vertexnova/sc/shader_artifact_cache.cpp @@ -30,6 +30,9 @@ namespace { constexpr uint64_t kFnv1a64Prime = 0x100000001b3ULL; constexpr uint64_t kFnv1a64Offset = 0xcbf29ce484222325ULL; constexpr int kCacheKeyHexWidth = 16; +// Bumped when failure policy became fatal for reflection / cross-compile so +// stale incomplete .vnca entries are not treated as hits. +constexpr uint32_t kArtifactCacheSchemaVersion = 1; uint64_t fnv1a64(const void* data, size_t len) noexcept { uint64_t hash = kFnv1a64Offset; @@ -92,11 +95,12 @@ struct Reader { }; // StageArtifact binary format -// stage(u8) | entry_point(str) | spirv_count(u32) | spirv_data | +// schema(u32) | stage(u8) | entry_point(str) | spirv_count(u32) | spirv_data | // StageReflection | cross_count(u32) | { target(u8) | source(str) | ep(str) } * N std::string serializeArtifact(const StageArtifact& a) { Writer w; + w.u32(kArtifactCacheSchemaVersion); w.u8(static_cast(a.stage)); w.str(a.entry_point); w.u32(static_cast(a.spirv.size())); @@ -118,6 +122,10 @@ std::string serializeArtifact(const StageArtifact& a) { bool deserializeArtifact(const std::string& data, StageArtifact& out) { try { Reader r(data); + const uint32_t schema = r.u32(); + if (schema != kArtifactCacheSchemaVersion) { + return false; + } out.stage = static_cast(r.u8()); out.entry_point = r.str(); const uint32_t spirv_count = r.u32(); diff --git a/src/vertexnova/sc/shader_pipeline_builder.cpp b/src/vertexnova/sc/shader_pipeline_builder.cpp index 0a6ae7f..0ce0d8b 100644 --- a/src/vertexnova/sc/shader_pipeline_builder.cpp +++ b/src/vertexnova/sc/shader_pipeline_builder.cpp @@ -28,6 +28,25 @@ CREATE_VNE_LOGGER_CATEGORY("vne.sc.pipeline") namespace vne::sc { +namespace { + +// Reject cache entries that lack output for every requested target. +// Complements the .vnca schema version: even current-schema entries must be +// complete before Phase B (reflect / cross-compile) is skipped. +bool cachedArtifactReady(const StageArtifact& artifact, const std::vector& targets) { + if (artifact.spirv.empty()) { + return false; + } + for (const CrossTarget target : targets) { + const CrossCompiledSource* cc = artifact.findCrossCompiled(target); + if (cc == nullptr || cc->source.empty()) { + return false; + } + } + return true; +} + +} // namespace ShaderPipelineBuilder::ShaderPipelineBuilder(std::shared_ptr front_end, std::shared_ptr cross_compiler, @@ -96,11 +115,15 @@ PipelineBuildResult ShaderPipelineBuilder::build(const PipelineBuildDesc& desc) ShaderArtifactCache::makeKey(sw.req, desc.targets, desc.metal_layout, metal_program_fingerprint); auto cached = cache->lookup(sw.cache_key); if (cached.has_value()) { - VNE_LOG_DEBUG << "ShaderPipelineBuilder: cache hit for stage " << static_cast(sw.req.stage); - sw.artifact = std::move(*cached); - sw.needs_backend = false; - work.push_back(std::move(sw)); - continue; + if (cachedArtifactReady(*cached, desc.targets)) { + VNE_LOG_DEBUG << "ShaderPipelineBuilder: cache hit for stage " << static_cast(sw.req.stage); + sw.artifact = std::move(*cached); + sw.needs_backend = false; + work.push_back(std::move(sw)); + continue; + } + VNE_LOG_DEBUG << "ShaderPipelineBuilder: ignoring incomplete cache entry for stage " + << static_cast(sw.req.stage); } } diff --git a/src/vertexnova/sc/shader_reflection_binary.cpp b/src/vertexnova/sc/shader_reflection_binary.cpp index 6b20939..9084726 100644 --- a/src/vertexnova/sc/shader_reflection_binary.cpp +++ b/src/vertexnova/sc/shader_reflection_binary.cpp @@ -23,9 +23,8 @@ namespace { constexpr uint32_t kReflectionBinaryVersionMajor = 1u; constexpr uint32_t kReflectionBinaryVersionMinor = 0u; constexpr uint32_t kReflectionBinaryVersionPatch = 0u; -constexpr uint32_t kReflectionBinaryVersion = (kReflectionBinaryVersionMajor << 16U) - | (kReflectionBinaryVersionMinor << 8U) - | kReflectionBinaryVersionPatch; +constexpr uint32_t kReflectionBinaryVersion = + (kReflectionBinaryVersionMajor << 16U) | (kReflectionBinaryVersionMinor << 8U) | kReflectionBinaryVersionPatch; constexpr uint32_t kMaxStringLen = 4096; constexpr uint32_t kMaxProgramStageCount = 16; constexpr uint32_t kMaxBindingCount = 256; diff --git a/tests/shader_pipeline_builder_test.cpp b/tests/shader_pipeline_builder_test.cpp index fec788b..41a844f 100644 --- a/tests/shader_pipeline_builder_test.cpp +++ b/tests/shader_pipeline_builder_test.cpp @@ -212,6 +212,10 @@ TEST_F(ShaderPipelineBuilderTest, MultiSetMetalSlotsAgreeAcrossStages) { } TEST_F(ShaderPipelineBuilderTest, WgslLeavesVulkanSetBindingIdentity) { +#ifndef VNE_SC_TINT_ENABLED + GTEST_SKIP() << "Tint not enabled (configure with -DVNE_SC_TINT=ON)"; +#endif + auto builder = makeGlslPipelineBuilder(); ASSERT_NE(builder, nullptr); From f3ccd93c579694717e250f2e83c797e4a7569337 Mon Sep 17 00:00:00 2001 From: ayadav-macOs Date: Tue, 8 Sep 2026 00:24:27 -0400 Subject: [PATCH 4/8] fix: update CMake configuration for Dawn dependency handling - Added a new option to skip ExternalProject updates during reconfiguration to prevent issues with disconnected Git submodules. - Enhanced comments to clarify the rationale behind the changes, improving maintainability of the CMake setup. --- cmake/Dependencies.cmake | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/cmake/Dependencies.cmake b/cmake/Dependencies.cmake index c4e8199..ad71c8f 100644 --- a/cmake/Dependencies.cmake +++ b/cmake/Dependencies.cmake @@ -267,12 +267,17 @@ if(VNE_SC_TINT) # library that has negligible benefit from debug symbols. set(_vne_sc_saved_build_type "${CMAKE_BUILD_TYPE}") set(CMAKE_BUILD_TYPE "Release" CACHE STRING "" FORCE) + # Pin is fixed (chromium/6723). Skip ExternalProject update on reconfigure: + # modern Git defaults to fetch.recurseSubmodules=on-demand, and Dawn's + # DAWN_FETCH_DEPENDENCIES checkouts often lack an `origin` remote, so a + # plain `git fetch` on dawn-src fails mid-configure. FetchContent_Declare(dawn - GIT_REPOSITORY https://dawn.googlesource.com/dawn - GIT_TAG chromium/6723 - GIT_SHALLOW TRUE - GIT_SUBMODULES "" - GIT_SUBMODULES_RECURSE FALSE) + GIT_REPOSITORY https://dawn.googlesource.com/dawn + GIT_TAG chromium/6723 + GIT_SHALLOW TRUE + GIT_SUBMODULES "" + GIT_SUBMODULES_RECURSE FALSE + UPDATE_DISCONNECTED TRUE) FetchContent_MakeAvailable(dawn) _vne_sc_fix_dawn_abseil_randen_copts() set(CMAKE_BUILD_TYPE "${_vne_sc_saved_build_type}" CACHE STRING "" FORCE) From 3784154ac26ea595f5473382d787179bc7eafffa Mon Sep 17 00:00:00 2001 From: ayadav-macOs Date: Tue, 8 Sep 2026 00:29:26 -0400 Subject: [PATCH 5/8] refactor: rename GlslangProcessLifetime to GlslangProcessGuard - Updated the structure name from `GlslangProcessLifetime` to `GlslangProcessGuard` for improved clarity and consistency. - Adjusted related function names and references to reflect the new structure name, enhancing code readability and maintainability. - Ensured that the changes do not affect the existing functionality of the shader processing lifecycle. --- src/vertexnova/sc/frontend/glslang_frontend.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/vertexnova/sc/frontend/glslang_frontend.cpp b/src/vertexnova/sc/frontend/glslang_frontend.cpp index 00333ba..c7fceb3 100644 --- a/src/vertexnova/sc/frontend/glslang_frontend.cpp +++ b/src/vertexnova/sc/frontend/glslang_frontend.cpp @@ -28,7 +28,7 @@ namespace { constexpr std::streamsize kMaxShaderFileBytes = 64 * 1024 * 1024; -struct GlslangProcessLifetime { +struct GlslangProcessGuard { std::mutex mutex; int refs = 0; bool process_ok = false; @@ -57,9 +57,9 @@ struct GlslangProcessLifetime { } }; -GlslangProcessLifetime& glslangProcessLifetime() { - static GlslangProcessLifetime s_lifetime; - return s_lifetime; +GlslangProcessGuard& glslangProcessGuard() { + static GlslangProcessGuard s_guard; + return s_guard; } bool readBinaryFile(std::ifstream& file, std::string& out) { @@ -183,7 +183,7 @@ std::string buildPreamble(const std::vector& macros) { namespace vne::sc { GlslangFrontEnd::GlslangFrontEnd() - : initialized_(glslangProcessLifetime().acquire()) { + : initialized_(glslangProcessGuard().acquire()) { if (!initialized_) { VNE_LOG_ERROR << "GlslangFrontEnd: glslang::InitializeProcess() failed"; } else { @@ -193,7 +193,7 @@ GlslangFrontEnd::GlslangFrontEnd() GlslangFrontEnd::~GlslangFrontEnd() { if (initialized_) { - glslangProcessLifetime().release(); + glslangProcessGuard().release(); initialized_ = false; } } From d861c636d01989793bd0580aacede06cbd370fef Mon Sep 17 00:00:00 2001 From: ayadav-macOs Date: Tue, 8 Sep 2026 00:59:24 -0400 Subject: [PATCH 6/8] fix: address MSVC warning for Tint BlockAllocator - Added a compiler option to suppress MSVC C4324 warnings related to structure padding when compiling Tint wrappers, ensuring that the build process remains clean and warnings do not trigger errors under WARNINGS_AS_ERRORS settings. - Enhanced the CMake configuration for better compatibility with MSVC, improving the overall build experience for users on Windows platforms. --- cmake/Dependencies.cmake | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/cmake/Dependencies.cmake b/cmake/Dependencies.cmake index ad71c8f..06f9a73 100644 --- a/cmake/Dependencies.cmake +++ b/cmake/Dependencies.cmake @@ -315,6 +315,11 @@ function(vne_sc_link_tint target) message(FATAL_ERROR "vne_sc_link_tint: no libtint or tint target found from Dawn") endif() target_compile_definitions(${target} PRIVATE VNE_SC_TINT_ENABLED) + # Tint BlockAllocator uses alignas; MSVC C4324 (structure padded) is expected + # and must not fail WARNINGS_AS_ERRORS=/WX when compiling our Tint wrappers. + if(MSVC) + target_compile_options(${target} PRIVATE /wd4324) + endif() endfunction() #============================================================================== From a41a145ad3d663b78ddaa8a9a5f41dd30cf1eed0 Mon Sep 17 00:00:00 2001 From: ayadav-macOs Date: Tue, 8 Sep 2026 01:15:07 -0400 Subject: [PATCH 7/8] fix: update shader reflection binary format and enhance CI workflows - Updated the `reflection.bin` format version to v3, requiring exact version matches for deserialization and adding support for new WebGPU bind-group fields. - Enhanced CI workflows to manage memory usage during builds, particularly for the Tint cross-compiler, by capping parallel jobs and providing clearer comments on configuration options. - Improved error handling and logging in shader reflection and binding emission processes, ensuring better compatibility with new resource types and formats. - Added new resource types to shader reflection capabilities, enhancing the overall functionality and robustness of the shader management system. --- .github/workflows/build-reusable.yml | 16 +++++- .github/workflows/ci.yml | 5 +- .github/workflows/codeql.yml | 4 +- README.md | 2 +- cmake/Dependencies.cmake | 52 ++++++++++++++++--- .../vertexnova/sc/shader_reflection_binary.h | 6 +++ src/vertexnova/sc/gpu_layout_tools.cpp | 24 +++++++++ .../sc/reflector/spirvcross_reflector.cpp | 35 +++++++++++-- src/vertexnova/sc/shader_bundle.cpp | 4 ++ .../sc/shader_reflection_binary.cpp | 9 ++-- 10 files changed, 141 insertions(+), 16 deletions(-) diff --git a/.github/workflows/build-reusable.yml b/.github/workflows/build-reusable.yml index 1fb1577..8a5cc14 100644 --- a/.github/workflows/build-reusable.yml +++ b/.github/workflows/build-reusable.yml @@ -167,6 +167,14 @@ jobs: # shellcheck disable=SC2086 # intentional word-splitting for flag vars # vnesc builds static archives; CACHE_LIB_TYPE only selects the build/ dir name. + # + # Dawn/Tint OOMs GitHub-hosted runners when every matrix cell builds it. + # Default CI to Tint OFF; opt in per-job with -DVNE_SC_TINT=ON in extra_cmake_flags. + TINT_FLAG="-DVNE_SC_TINT=OFF" + if [[ "$EXTRA_FLAGS" == *VNE_SC_TINT* ]]; then + TINT_FLAG="" + fi + cmake -B "$BUILD_DIR" -S . \ -DCMAKE_BUILD_TYPE="$EFFECTIVE_BUILD_TYPE" \ -DVNE_SC_CI=ON \ @@ -174,11 +182,17 @@ jobs: -DCMAKE_EXPORT_COMPILE_COMMANDS=ON \ ${COVERAGE_FLAG} \ ${COMPILER_FLAGS} \ + ${TINT_FLAG} \ ${EXTRA_FLAGS} - name: Build shell: bash - run: cmake --build "$BUILD_DIR" --config "$EFFECTIVE_BUILD_TYPE" --parallel + run: | + # Unconstrained --parallel OOMs while compiling Dawn/Tint + Abseil + # (Linux "Killed", exit 143). Cap CI concurrency for memory headroom. + JOBS="${CMAKE_BUILD_PARALLEL_LEVEL:-2}" + echo "Building with --parallel ${JOBS}" + cmake --build "$BUILD_DIR" --config "$EFFECTIVE_BUILD_TYPE" --parallel "${JOBS}" - name: Verify coverage instrumentation (coverage builds only, Linux) if: runner.os == 'Linux' && inputs.enable_coverage diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8273f04..238d230 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -219,6 +219,8 @@ jobs: build_type: Release enable_coverage: false warnings_as_errors: true + # Sole Tint-on matrix cell (reusable defaults -DVNE_SC_TINT=OFF). + extra_cmake_flags: "-DVNE_SC_TINT=ON" lib_type: static secrets: inherit @@ -318,8 +320,9 @@ jobs: -DCMAKE_C_COMPILER=clang \ -DCMAKE_CXX_COMPILER=clang++ \ -DVNE_SC_CI=ON \ + -DVNE_SC_TINT=ON \ -DCMAKE_EXPORT_COMPILE_COMMANDS=ON - cmake --build build/static --config Release --parallel + cmake --build build/static --config Release --parallel 2 fi - name: Run clang-tidy diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 86ee429..95d709b 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -69,8 +69,10 @@ jobs: cmake -B build/static -S . \ -DCMAKE_BUILD_TYPE=Release \ -DVNE_SC_CI=ON \ + -DVNE_SC_TINT=OFF \ -DWARNINGS_AS_ERRORS=OFF - cmake --build build/static --config Release --parallel + # Cap parallelism: Dawn/Tint + Abseil OOMs under full --parallel. + cmake --build build/static --config Release --parallel 2 - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@v3 diff --git a/README.md b/README.md index d8b101d..b917993 100644 --- a/README.md +++ b/README.md @@ -258,7 +258,7 @@ A compiled `.vneshader` is a directory: | File | Contents | |------|----------| | `bundle.header` | Binary index: stage list, entry points, file names | -| `reflection.bin` | Binary `ProgramReflection` (v2 format) | +| `reflection.bin` | Binary `ProgramReflection` (v3 format) | | `manifest.json` | Human-readable index (when `VNE_SC_JSON` is enabled) | | `.spv` | SPIR-V bytecode | | `.msl` | MSL source (when `msl` target requested) | diff --git a/cmake/Dependencies.cmake b/cmake/Dependencies.cmake index 06f9a73..8821a6d 100644 --- a/cmake/Dependencies.cmake +++ b/cmake/Dependencies.cmake @@ -267,13 +267,53 @@ if(VNE_SC_TINT) # library that has negligible benefit from debug symbols. set(_vne_sc_saved_build_type "${CMAKE_BUILD_TYPE}") set(CMAKE_BUILD_TYPE "Release" CACHE STRING "" FORCE) - # Pin is fixed (chromium/6723). Skip ExternalProject update on reconfigure: - # modern Git defaults to fetch.recurseSubmodules=on-demand, and Dawn's - # DAWN_FETCH_DEPENDENCIES checkouts often lack an `origin` remote, so a - # plain `git fetch` on dawn-src fails mid-configure. + # Pin is fixed (chromium/6723). Skip ExternalProject update on reconfigure when + # that revision is already present: modern Git defaults to + # fetch.recurseSubmodules=on-demand, and Dawn's DAWN_FETCH_DEPENDENCIES + # checkouts often lack an `origin` remote, so a plain `git fetch` on dawn-src + # fails mid-configure. If the shallow checkout is missing the pin, wipe it so + # FetchContent re-clones (still without submodule recursion). + set(_vne_sc_dawn_tag "chromium/6723") + set(_vne_sc_dawn_src "${CMAKE_BINARY_DIR}/_deps/dawn-src") + if(EXISTS "${_vne_sc_dawn_src}/.git") + find_package(Git QUIET) + if(Git_FOUND) + execute_process( + COMMAND "${GIT_EXECUTABLE}" rev-parse --verify --quiet "${_vne_sc_dawn_tag}^{commit}" + WORKING_DIRECTORY "${_vne_sc_dawn_src}" + RESULT_VARIABLE _vne_sc_dawn_tag_ok + OUTPUT_QUIET + ERROR_QUIET) + set(_vne_sc_dawn_head "") + set(_vne_sc_dawn_wanted "") + if(_vne_sc_dawn_tag_ok EQUAL 0) + execute_process( + COMMAND "${GIT_EXECUTABLE}" rev-parse HEAD + WORKING_DIRECTORY "${_vne_sc_dawn_src}" + OUTPUT_VARIABLE _vne_sc_dawn_head + OUTPUT_STRIP_TRAILING_WHITESPACE + ERROR_QUIET) + execute_process( + COMMAND "${GIT_EXECUTABLE}" rev-parse "${_vne_sc_dawn_tag}^{commit}" + WORKING_DIRECTORY "${_vne_sc_dawn_src}" + OUTPUT_VARIABLE _vne_sc_dawn_wanted + OUTPUT_STRIP_TRAILING_WHITESPACE + ERROR_QUIET) + endif() + if(NOT _vne_sc_dawn_tag_ok EQUAL 0 OR NOT _vne_sc_dawn_head STREQUAL _vne_sc_dawn_wanted) + message(STATUS + "[vnesc] Dawn checkout missing or not at ${_vne_sc_dawn_tag}; " + "removing for FetchContent re-populate") + file(REMOVE_RECURSE + "${CMAKE_BINARY_DIR}/_deps/dawn-src" + "${CMAKE_BINARY_DIR}/_deps/dawn-build" + "${CMAKE_BINARY_DIR}/_deps/dawn-subbuild") + endif() + endif() + endif() FetchContent_Declare(dawn GIT_REPOSITORY https://dawn.googlesource.com/dawn - GIT_TAG chromium/6723 + GIT_TAG ${_vne_sc_dawn_tag} GIT_SHALLOW TRUE GIT_SUBMODULES "" GIT_SUBMODULES_RECURSE FALSE @@ -281,7 +321,7 @@ if(VNE_SC_TINT) FetchContent_MakeAvailable(dawn) _vne_sc_fix_dawn_abseil_randen_copts() set(CMAKE_BUILD_TYPE "${_vne_sc_saved_build_type}" CACHE STRING "" FORCE) - message(STATUS "[vnesc] Dawn/Tint -> FetchContent chromium/6723 (Release build)") + message(STATUS "[vnesc] Dawn/Tint -> FetchContent ${_vne_sc_dawn_tag} (Release build)") endif() #============================================================================== diff --git a/include/vertexnova/sc/shader_reflection_binary.h b/include/vertexnova/sc/shader_reflection_binary.h index 185c1ab..30aa671 100644 --- a/include/vertexnova/sc/shader_reflection_binary.h +++ b/include/vertexnova/sc/shader_reflection_binary.h @@ -13,6 +13,12 @@ /** * @file shader_reflection_binary.h * @brief Binary serialization for @ref StageReflection and @ref ProgramReflection. + * + * @c ProgramReflection blobs (@c reflection.bin) use format version 3.0.0 + * (@c 0x00030000). Deserialization requires an exact version match; regenerate + * stale bundles. Version 3 stores the WebGPU bind-group fields on each binding + * (@c storage_format_hint, @c storage_access_hint, @c multisampled, + * @c depth_texture, @c dynamic_offset). */ #include "sc_types.h" diff --git a/src/vertexnova/sc/gpu_layout_tools.cpp b/src/vertexnova/sc/gpu_layout_tools.cpp index 2b4da88..fa92afa 100644 --- a/src/vertexnova/sc/gpu_layout_tools.cpp +++ b/src/vertexnova/sc/gpu_layout_tools.cpp @@ -244,6 +244,18 @@ std::string emitSingleBinding(const ReflectedBindingInfo& binding) { } out << "} " << blockNameToInstance(binding.name) << ";\n\n"; break; + case ReflectedResourceType::eReadOnlyStorageBuffer: + out << "layout(std430, set = " << binding.set << ", binding = " << binding.binding + << ") readonly buffer " << binding.name << " {\n"; + if (binding.struct_members.empty()) { + out << " float data[];\n"; + } else { + for (const auto& member : binding.struct_members) { + emitMemberDecl(out, memberGlslType(member), member); + } + } + out << "} " << blockNameToInstance(binding.name) << ";\n\n"; + break; case ReflectedResourceType::eSampler: out << "layout(set = " << binding.set << ", binding = " << binding.binding << ") uniform sampler " << binding.name << ";\n\n"; @@ -252,10 +264,22 @@ std::string emitSingleBinding(const ReflectedBindingInfo& binding) { out << "layout(set = " << binding.set << ", binding = " << binding.binding << ") uniform texture2D " << binding.name << ";\n\n"; break; + case ReflectedResourceType::eSampled2DArray: + out << "layout(set = " << binding.set << ", binding = " << binding.binding + << ") uniform texture2DArray " << binding.name << ";\n\n"; + break; case ReflectedResourceType::eSampledCubemap: out << "layout(set = " << binding.set << ", binding = " << binding.binding << ") uniform textureCube " << binding.name << ";\n\n"; break; + case ReflectedResourceType::eSampledCubeArray: + out << "layout(set = " << binding.set << ", binding = " << binding.binding + << ") uniform textureCubeArray " << binding.name << ";\n\n"; + break; + case ReflectedResourceType::eSampledImage3D: + out << "layout(set = " << binding.set << ", binding = " << binding.binding << ") uniform texture3D " + << binding.name << ";\n\n"; + break; case ReflectedResourceType::eCombinedImageSampler: out << "layout(set = " << binding.set << ", binding = " << binding.binding << ") uniform sampler2D " << binding.name << ";\n\n"; diff --git a/src/vertexnova/sc/reflector/spirvcross_reflector.cpp b/src/vertexnova/sc/reflector/spirvcross_reflector.cpp index 43b24ea..44fff19 100644 --- a/src/vertexnova/sc/reflector/spirvcross_reflector.cpp +++ b/src/vertexnova/sc/reflector/spirvcross_reflector.cpp @@ -22,6 +22,7 @@ #include #include #include +#include #include #include @@ -58,6 +59,7 @@ std::vector reflectStructMembers(const spirv_cro } // Maps a SPIR-V image format to the WGSL storage-texture format name WebGPU expects. +// Throws on Unknown / unsupported formats so storage_format_hint is never empty. [[nodiscard]] std::string imageFormatName(spv::ImageFormat fmt) { switch (fmt) { case spv::ImageFormatRgba32f: @@ -66,6 +68,8 @@ std::vector reflectStructMembers(const spirv_cro return "rgba16float"; case spv::ImageFormatR32f: return "r32float"; + case spv::ImageFormatR16f: + return "r16float"; case spv::ImageFormatRgba8: return "rgba8unorm"; case spv::ImageFormatRgba8Snorm: @@ -82,6 +86,14 @@ std::vector reflectStructMembers(const spirv_cro return "rgba8sint"; case spv::ImageFormatR32i: return "r32sint"; + case spv::ImageFormatRg32i: + return "rg32sint"; + case spv::ImageFormatRg16i: + return "rg16sint"; + case spv::ImageFormatR16i: + return "r16sint"; + case spv::ImageFormatR8i: + return "r8sint"; case spv::ImageFormatRgba32ui: return "rgba32uint"; case spv::ImageFormatRgba16ui: @@ -90,12 +102,29 @@ std::vector reflectStructMembers(const spirv_cro return "rgba8uint"; case spv::ImageFormatR32ui: return "r32uint"; - case spv::ImageFormatRg32i: - return "rg32sint"; case spv::ImageFormatRg32ui: return "rg32uint"; + case spv::ImageFormatRg16ui: + return "rg16uint"; + case spv::ImageFormatR16ui: + return "r16uint"; + case spv::ImageFormatR8ui: + return "r8uint"; + case spv::ImageFormatR8: + return "r8unorm"; + case spv::ImageFormatRg8: + return "rg8unorm"; + case spv::ImageFormatR8Snorm: + return "r8snorm"; + case spv::ImageFormatRg8Snorm: + return "rg8snorm"; + case spv::ImageFormatRg8i: + return "rg8sint"; + case spv::ImageFormatRg8ui: + return "rg8uint"; default: - return {}; + throw std::runtime_error("SpirvCrossReflector: unsupported SPIR-V storage-image format (" + + std::to_string(static_cast(fmt)) + ")"); } } diff --git a/src/vertexnova/sc/shader_bundle.cpp b/src/vertexnova/sc/shader_bundle.cpp index 405552d..ee6cbe4 100644 --- a/src/vertexnova/sc/shader_bundle.cpp +++ b/src/vertexnova/sc/shader_bundle.cpp @@ -280,6 +280,7 @@ bool writeShaderBundle(const ShaderArtifact& artifact, const std::filesystem::pa switch (b.type) { case ReflectedResourceType::eUniformBuffer: case ReflectedResourceType::eStorageBuffer: + case ReflectedResourceType::eReadOnlyStorageBuffer: jm["buffer"] = b.slots.metal->buffer; break; case ReflectedResourceType::eCombinedImageSampler: @@ -288,6 +289,9 @@ bool writeShaderBundle(const ShaderArtifact& artifact, const std::filesystem::pa break; case ReflectedResourceType::eSampledImage: case ReflectedResourceType::eSampledCubemap: + case ReflectedResourceType::eSampled2DArray: + case ReflectedResourceType::eSampledCubeArray: + case ReflectedResourceType::eSampledImage3D: case ReflectedResourceType::eStorageImage: jm["texture"] = b.slots.metal->texture; break; diff --git a/src/vertexnova/sc/shader_reflection_binary.cpp b/src/vertexnova/sc/shader_reflection_binary.cpp index 9084726..cb15cf7 100644 --- a/src/vertexnova/sc/shader_reflection_binary.cpp +++ b/src/vertexnova/sc/shader_reflection_binary.cpp @@ -15,12 +15,14 @@ namespace { -// reflection.bin format version, v1.0.0, packed into the single u32 the header already +// reflection.bin format version, v3.0.0, packed into the single u32 the header already // carries: 0xMMmmpp (major, minor, patch). Readers require an exact match -- a stale bundle // fails loudly and is regenerated rather than silently half-read. +// v3 adds WebGPU bind-group fields on each binding: storage_format_hint, +// storage_access_hint, multisampled, depth_texture, dynamic_offset. // Keep in lockstep with vnerhi's shader_bundle_loader.cpp. // (Independent of bundle.header's own version -- see shader_bundle.cpp.) -constexpr uint32_t kReflectionBinaryVersionMajor = 1u; +constexpr uint32_t kReflectionBinaryVersionMajor = 3u; constexpr uint32_t kReflectionBinaryVersionMinor = 0u; constexpr uint32_t kReflectionBinaryVersionPatch = 0u; constexpr uint32_t kReflectionBinaryVersion = @@ -235,7 +237,7 @@ void writeReflectedBindingInfo(Writer& w, const vne::sc::ReflectedBindingInfo& b for (const auto& m : b.struct_members) { writeReflectedStructMember(w, m); } - // ---- v3 ---- + // ---- reflection.bin v3 WebGPU bind-group fields ---- w.str(b.storage_format_hint); w.str(b.storage_access_hint); w.boolean(b.multisampled); @@ -269,6 +271,7 @@ bool readReflectedBindingInfo(Reader& r, vne::sc::ReflectedBindingInfo& b) { return false; } } + // ---- reflection.bin v3 WebGPU bind-group fields (required; version gate is exact) ---- b.storage_format_hint = r.str(); b.storage_access_hint = r.str(); b.multisampled = r.boolean(); From fd3d3111a610c0f7eee6cf8bf9072ca1ebb5820f Mon Sep 17 00:00:00 2001 From: ayadav-macOs Date: Tue, 8 Sep 2026 01:15:39 -0400 Subject: [PATCH 8/8] fix: improve formatting in shader binding emission - Adjusted the output formatting in the `emitSingleBinding` function for various resource types to ensure consistent spacing and readability in emitted shader code. - Enhanced the clarity of the generated layout declarations for `eReadOnlyStorageBuffer`, `eSampled2DArray`, and `eSampledCubeArray`, improving overall code quality and maintainability. --- src/vertexnova/sc/gpu_layout_tools.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/vertexnova/sc/gpu_layout_tools.cpp b/src/vertexnova/sc/gpu_layout_tools.cpp index fa92afa..9aebcb3 100644 --- a/src/vertexnova/sc/gpu_layout_tools.cpp +++ b/src/vertexnova/sc/gpu_layout_tools.cpp @@ -245,8 +245,8 @@ std::string emitSingleBinding(const ReflectedBindingInfo& binding) { out << "} " << blockNameToInstance(binding.name) << ";\n\n"; break; case ReflectedResourceType::eReadOnlyStorageBuffer: - out << "layout(std430, set = " << binding.set << ", binding = " << binding.binding - << ") readonly buffer " << binding.name << " {\n"; + out << "layout(std430, set = " << binding.set << ", binding = " << binding.binding << ") readonly buffer " + << binding.name << " {\n"; if (binding.struct_members.empty()) { out << " float data[];\n"; } else { @@ -265,16 +265,16 @@ std::string emitSingleBinding(const ReflectedBindingInfo& binding) { << binding.name << ";\n\n"; break; case ReflectedResourceType::eSampled2DArray: - out << "layout(set = " << binding.set << ", binding = " << binding.binding - << ") uniform texture2DArray " << binding.name << ";\n\n"; + out << "layout(set = " << binding.set << ", binding = " << binding.binding << ") uniform texture2DArray " + << binding.name << ";\n\n"; break; case ReflectedResourceType::eSampledCubemap: out << "layout(set = " << binding.set << ", binding = " << binding.binding << ") uniform textureCube " << binding.name << ";\n\n"; break; case ReflectedResourceType::eSampledCubeArray: - out << "layout(set = " << binding.set << ", binding = " << binding.binding - << ") uniform textureCubeArray " << binding.name << ";\n\n"; + out << "layout(set = " << binding.set << ", binding = " << binding.binding << ") uniform textureCubeArray " + << binding.name << ";\n\n"; break; case ReflectedResourceType::eSampledImage3D: out << "layout(set = " << binding.set << ", binding = " << binding.binding << ") uniform texture3D "