Skip to content

refactor(render): normalize the filter-chain provider seam#130

Closed
K1ngst0m wants to merge 1 commit intomainfrom
dev/normalize-filter-chain
Closed

refactor(render): normalize the filter-chain provider seam#130
K1ngst0m wants to merge 1 commit intomainfrom
dev/normalize-filter-chain

Conversation

@K1ngst0m
Copy link
Copy Markdown
Collaborator

@K1ngst0m K1ngst0m commented Mar 13, 2026

  • Normalize provider resolution so render targets always link goggles-filter-chain
  • Preserve the host-versus-boundary ownership split for retarget and presentation
  • Keep backend code on the stable wrapper and public filter-chain headers
  • Add monorepo package rehearsal coverage for the consumer boundary
  • Archive and sync specs around the filter-chain consumer-boundary milestone

Summary by CodeRabbit

  • New Features

    • Added support for multiple filter-chain provider modes (in-tree, subdir, package) with a normalized target contract.
    • Introduced new public API headers for error handling, result types, filter controls, and scale mode.
    • Added package-based rehearsal workflows to test provider selection and consumption paths.
  • Documentation

    • Documented retarget vs preset reload contract for filter-chain output behavior.
    • Added specifications for filter-chain boundary ownership and public API surface isolation.

- Normalize provider resolution so render targets always link goggles-filter-chain
- Preserve the host-versus-boundary ownership split for retarget and presentation
- Keep backend code on the stable wrapper and public filter-chain headers
- Add monorepo package rehearsal coverage for the consumer boundary
- Archive and sync specs around the filter-chain consumer-boundary milestone
@K1ngst0m K1ngst0m requested a review from Copilot March 13, 2026 06:10
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 13, 2026

📝 Walkthrough

Walkthrough

This PR introduces a normalized filter-chain consumer boundary through provider-resolution logic, reorganizes public headers under the goggles/filter_chain namespace, implements CMake-based package configuration and installation workflows, and adds rehearsal presets and scripts for testing in-tree and package-based provider modes.

Changes

Cohort / File(s) Summary
CMake Build System & Provider Resolution
CMakePresets.json, cmake/GogglesFilterChainConfig.cmake.in, cmake/GogglesFilterChainProvider.cmake
Adds three new smoke-provider presets (in-tree, package-stage, package-consumer) with cache variables for provider selection. Introduces GogglesFilterChainConfig.cmake template for packaging configuration, dependency resolution (Vulkan, spdlog, slang), and exported targets. Implements goggles_resolve_filter_chain_provider() function to orchestrate provider detection, normalization, and target binding across in-tree/subdir/package modes.
Render CMake Integration
src/render/CMakeLists.txt, src/render/backend/CMakeLists.txt, src/render/chain/CMakeLists.txt, src/render/shader/CMakeLists.txt, src/render/texture/CMakeLists.txt
Replaces inline goggles-filter-chain target assembly with provider-based resolution. Adds package configuration generation and conditional installation paths. Shifts include/definition visibility from PUBLIC to PRIVATE. Adds explicit include paths for chain/include directory.
Public API Header Reorganization
src/render/chain/include/goggles/filter_chain/*, src/util/error.hpp, src/util/config.hpp, src/render/chain/filter_controls.hpp, src/render/chain/vulkan_context.hpp
Creates new canonical headers under goggles/filter_chain/ namespace: error.hpp, result.hpp, filter_controls.hpp, vulkan_context.hpp, scale_mode.hpp, and diagnostics/* wrapper headers. Moves ErrorCode, Error, Result types, and ScaleMode enum to new locations. Original utility headers now forward-include from new paths, preserving backward compatibility.
Backend Integration & API Migration
src/render/backend/filter_chain_controller.hpp, src/render/backend/filter_chain_controller.cpp, src/render/backend/vulkan_backend.cpp, src/render/backend/external_frame_importer.hpp, src/render/backend/render_output.hpp, src/render/backend/vulkan_context.hpp, src/render/backend/vulkan_debug.hpp, src/render/backend/vulkan_error.hpp, src/render/backend/vulkan_backend.hpp
Adds FilterChainController::record() method. Replaces util/error.hpp includes with goggles/filter_chain/result.hpp. Updates vulkan_backend callsites to use direct record() instead of nested filter_chain_runtime().record() accessor.
Filter Chain Runtime & Diagnostics
src/render/chain/chain_executor.cpp, src/render/chain/chain_executor.hpp, src/render/chain/chain_resources.cpp, src/render/chain/chain_resources.hpp, src/render/chain/chain_runtime.cpp, src/render/chain/chain_runtime.hpp
Adds semantic_record_key(), binding_record_key(), binding_record_state() helper functions and state tracking via m_recorded_semantic_keys/m_recorded_binding_states in ChainResources. Reworks emit_semantic_events to accept ChainResources parameter and emit diagnostics only on first observation. Conditional binding state change detection replaces unconditional recording. Updates all diagnostic includes to use goggles/filter_chain/diagnostics paths.
Remaining Filter Chain Headers
src/render/chain/api/c/goggles_filter_chain.cpp, src/render/chain/api/c/goggles_filter_chain.h, src/render/chain/api/cpp/goggles_filter_chain.hpp, src/render/chain/chain_builder.cpp, src/render/chain/chain_builder.hpp, src/render/chain/filter_pass.hpp, src/render/chain/framebuffer.hpp, src/render/chain/pass.hpp, src/render/chain/preset_parser.hpp, src/render/chain/vulkan_result.hpp
Updates include paths from util/* to goggles/filter_chain/*. Adds documentation comments to C API (GogglesChainVkContext, GogglesChainVkRecordInfo, preset_load, output_retarget_vk, record_vk) clarifying ownership and lifecycle boundaries. No functional logic changes.
Utility Build System
src/util/CMakeLists.txt
Splits logging into separate OBJECT target goggles_util_logging_obj. Expands goggles_util PUBLIC dependencies to include nonstd::expected-lite, spdlog, toml11, BS_thread_pool, Threads. Adds public include path for src/render/chain/include. Adds diagnostics subdirectory.
Task Infrastructure & Pixi Integration
pixi.toml, scripts/task/help.sh, scripts/task/rehearse-filter-chain-provider.sh
Adds new task [tasks.rehearse-filter-chain-provider] and corresponding help entry with --mode option (both/in-tree/package). Implements rehearsal script orchestrating preset-based build/test workflows for provider modes with ccache and configurable stage/consumer testing.
Specification Documents
openspec/changes/archive/2026-03-13-normalize-filter-chain-consumer-boundary/*, openspec/specs/build-system/spec.md, openspec/specs/filter-chain-c-api/spec.md, openspec/specs/filter-chain-cpp-wrapper/spec.md, openspec/specs/goggles-filter-chain/spec.md, openspec/specs/render-pipeline/spec.md
Adds comprehensive change documentation detailing normalized filter-library source selection, monorepo package rehearsal workflows, test registration separation, post-retarget output contracts, and boundary ownership clarification. Documents three provider modes, stable target naming, and state preservation during retargeting. No executable code changes.
Test Infrastructure
tests/CMakeLists.txt, tests/render/test_filter_boundary_contracts.cpp, tests/render/test_filter_chain_retarget_contract.cpp, tests/render/test_vulkan_backend_subsystem_contracts.cpp
Introduces new optional goggles_filter_chain_contract_tests executable (when not using package provider). Splits test sources into GOGGLES_HOST_TEST_SOURCES and GOGGLES_FILTER_CHAIN_CONTRACT_TEST_SOURCES. Updates contract test header assertions to reference new goggles/filter_chain paths. Adds comprehensive filter-chain retarget integration test with VulkanRuntimeFixture, preset loading, and state preservation validation.

Sequence Diagram

sequenceDiagram
    participant User as Developer/CI
    participant CMake as CMake Configure
    participant Provider as Provider Resolver
    participant Build as Build System
    participant Install as Package Install
    participant Consumer as Consumer (find_package)
    
    User->>CMake: cmake --preset smoke-provider-in-tree
    CMake->>Provider: goggles_resolve_filter_chain_provider()
    Provider->>Provider: Detect in-tree provider mode
    Provider->>Provider: Scan/normalize in-tree targets
    Provider->>Provider: Create goggles-filter-chain alias
    Provider-->>CMake: Target ready
    CMake->>Build: ninja -C build/smoke-provider-in-tree
    Build-->>User: ✓ In-tree build complete
    
    User->>CMake: cmake --preset smoke-provider-package-stage
    CMake->>Provider: goggles_resolve_filter_chain_provider()
    Provider->>Provider: Setup in-tree provider mode
    Provider-->>CMake: Target ready
    CMake->>Build: ninja -C build/smoke-provider-package-stage
    Build->>Install: cmake --install
    Install->>Install: Write GogglesFilterChainConfig.cmake
    Install-->>User: ✓ Package installed to CMAKE_INSTALL_PREFIX
    
    User->>CMake: cmake --preset smoke-provider-package-consumer
    CMake->>Consumer: find_package(goggles-filter-chain)
    Consumer->>Consumer: Load GogglesFilterChainConfig.cmake
    Consumer->>Consumer: Validate Vulkan/spdlog/slang deps
    Consumer->>Consumer: Resolve goggles-filter-chain target
    Consumer-->>CMake: Consumer target configured
    CMake->>Build: ninja -C build/smoke-provider-package-consumer
    Build-->>User: ✓ Package consumer build complete
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested labels

architecture, build-system, filter-chain, high-complexity, package-integration

Suggested reviewers

  • zhangzhousuper

Poem

🐰 Hopping through namespaces with glee,
Provider modes: in-tree, stage, consumer spree!
Filter chains normalized, boundaries clear,
Package rehearsals bringing cheer!
From util to goggles, the headers now leap, 🎉

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'refactor(render): normalize the filter-chain provider seam' accurately summarizes the main change: normalizing provider resolution and establishing a stable filter-chain target boundary across in-tree, subdir, and package provider modes.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch dev/normalize-filter-chain
📝 Coding Plan
  • Generate coding plan for human review comments

Comment @coderabbitai help to get the list of available commands and usage tips.

@qodo-code-review
Copy link
Copy Markdown

Review Summary by Qodo

Normalize filter-chain provider seam with boundary-owned public headers and diagnostic deduplication

✨ Enhancement 🧪 Tests 📝 Documentation

Grey Divider

Walkthroughs

Description
• Normalize filter-chain provider resolution by establishing boundary-owned public headers under
  goggles/filter_chain/ for error handling, filter controls, scale modes, Vulkan context, and
  diagnostics
• Move error types, result aliases, and filter control definitions from internal util/ and
  render/chain/ paths to boundary-owned public headers
• Implement diagnostic event deduplication via state tracking in chain_executor and
  chain_resources to reduce redundant warnings
• Update all internal code to use boundary-owned public headers instead of internal paths,
  maintaining stable wrapper and public filter-chain headers
• Add comprehensive test coverage for filter-chain retarget contract, boundary header validation,
  and backend subsystem contracts
• Refactor CMake configuration to normalize visibility (PUBLIC to PRIVATE) and add filter-chain
  include paths across render subsystems
• Create CMake package configuration for goggles-filter-chain with proper dependency declarations
• Add monorepo package rehearsal task for testing in-tree and package provider modes
• Archive and sync specifications documenting filter-chain consumer-boundary milestone including
  build-system, render-pipeline, C API, and C++ wrapper contracts
Diagram
flowchart LR
  A["Internal Paths<br/>util/ render/chain/"] -->|"Move to Boundary"| B["Public Headers<br/>goggles/filter_chain/"]
  B -->|"Error & Result"| C["result.hpp<br/>error.hpp"]
  B -->|"Types"| D["filter_controls.hpp<br/>vulkan_context.hpp<br/>scale_mode.hpp"]
  B -->|"Diagnostics"| E["diagnostic_event.hpp<br/>diagnostic_session.hpp"]
  F["Backend Code"] -->|"Use Boundary"| B
  G["Chain Runtime"] -->|"Use Boundary"| B
  H["Diagnostic Events"] -->|"Deduplicate via"| I["State Tracking<br/>semantic_keys<br/>binding_states"]
  J["CMake Config"] -->|"Normalize"| K["Visibility<br/>Linkage<br/>Includes"]
Loading

Grey Divider

File Changes

1. src/render/chain/chain_executor.cpp ✨ Enhancement +106/-75

Deduplication of diagnostic events via state tracking

• Added three helper functions (semantic_record_key, binding_record_key, binding_record_state)
 to generate unique keys and state strings for tracking diagnostic events
• Modified emit_semantic_events to accept ChainResources parameter and track first observations
 of semantic keys to avoid duplicate event emissions
• Updated emit_binding_diagnostics to track binding state changes and only emit diagnostic events
 when state actually changes, reducing redundant warnings
• Refactored diagnostic event emission logic to distinguish between first observation and state
 changes for both semantic and binding diagnostics

src/render/chain/chain_executor.cpp


2. tests/render/test_filter_chain_retarget_contract.cpp 🧪 Tests +216/-0

New retarget contract test coverage

• New comprehensive test file validating filter chain output retarget behavior
• Tests that retargeting preserves runtime state including stage policy, prechain resolution, and
 control values
• Includes VulkanRuntimeFixture for Vulkan device initialization and CacheDirGuard for temporary
 directory management
• Validates that retarget_output() maintains active preset and control state across format changes

tests/render/test_filter_chain_retarget_contract.cpp


3. tests/render/test_filter_boundary_contracts.cpp 🧪 Tests +59/-6

Boundary contract validation for public headers

• Updated include path from render/chain/filter_controls.hpp to
 goggles/filter_chain/filter_controls.hpp
• Added NOLINTNEXTLINE comment for pointer cast in surface_token function
• Enhanced boundary contract checks to verify canonical filter-chain headers are used instead of
 internal paths
• Added validation that backend code uses goggles/filter_chain/vulkan_context.hpp instead of
 internal chain headers
• Expanded wrapper boundary contract tests to verify C API, filter controls, and vulkan context
 header isolation

tests/render/test_filter_boundary_contracts.cpp


View more (70)
4. src/util/error.hpp Refactoring +1/-150

Error handling moved to filter-chain boundary

• Removed all error handling implementation (ErrorCode enum, Error struct, Result types, helper
 functions, and macros)
• Replaced with single include forwarding to
 ../render/chain/include/goggles/filter_chain/result.hpp
• Converts this file into a compatibility forwarder for the boundary-owned error contract

src/util/error.hpp


5. src/render/chain/include/goggles/filter_chain/result.hpp ✨ Enhancement +78/-0

Boundary-owned result and error handling types

• New file containing Result type aliases and helper functions moved from src/util/error.hpp
• Defines Result<T> and ResultPtr<T> template aliases using nonstd::expected
• Includes helper functions make_error, make_result_ptr, and make_result_ptr_error
• Contains GOGGLES_TRY, GOGGLES_MUST, and GOGGLES_ASSERT macros for error handling

src/render/chain/include/goggles/filter_chain/result.hpp


6. src/render/chain/include/goggles/filter_chain/error.hpp ✨ Enhancement +68/-0

Boundary-owned error code definitions

• New file containing ErrorCode enum and Error struct moved from src/util/error.hpp
• Defines error codes for file operations, parsing, Vulkan, shader compilation, and input
 initialization
• Includes error_code_name() function for stable string representation of error codes
• Establishes boundary-owned error contract independent of Goggles utilities

src/render/chain/include/goggles/filter_chain/error.hpp


7. src/render/chain/chain_resources.cpp ✨ Enhancement +5/-1

Diagnostic state tracking initialization

• Updated include from util/diagnostics/diagnostic_event.hpp to
 goggles/filter_chain/diagnostics/diagnostic_event.hpp
• Added initialization and cleanup of m_recorded_semantic_keys and m_recorded_binding_states in
 install() and shutdown() methods
• These new members support deduplication of diagnostic events across frames

src/render/chain/chain_resources.cpp


8. src/render/backend/vulkan_backend.cpp Refactoring +2/-2

Backend record calls through controller boundary

• Changed two calls from m_filter_chain_controller.filter_chain_runtime().record(...) to
 m_filter_chain_controller.record(...)
• Delegates record operations directly through the controller instead of accessing the runtime
 through a getter
• Simplifies the call chain and keeps backend code on the stable controller boundary

src/render/backend/vulkan_backend.cpp


9. tests/render/test_vulkan_backend_subsystem_contracts.cpp 🧪 Tests +4/-2

Backend subsystem contract header validation

• Updated include from render/chain/vulkan_context.hpp to
 goggles/filter_chain/vulkan_context.hpp
• Enhanced contract checks to verify controller uses boundary-owned headers instead of internal
 paths
• Added validation that backend code does not access filter_chain_runtime() directly
• Confirms controller exposes record() method for backend use

tests/render/test_vulkan_backend_subsystem_contracts.cpp


10. src/render/backend/filter_chain_controller.hpp Refactoring +5/-5

Controller uses boundary-owned public headers

• Reordered includes to place goggles_filter_chain.hpp after other includes
• Updated includes from internal paths to boundary-owned headers:
 goggles/filter_chain/filter_controls.hpp, goggles/filter_chain/vulkan_context.hpp,
 goggles/filter_chain/result.hpp
• Removed util/error.hpp include as error types now come from result.hpp
• Added new record() method declaration that delegates to the filter chain runtime

src/render/backend/filter_chain_controller.hpp


11. src/render/chain/chain_resources.hpp ✨ Enhancement +5/-1

Diagnostic state tracking members added

• Updated include from util/diagnostics/diagnostic_session.hpp to
 goggles/filter_chain/diagnostics/diagnostic_session.hpp
• Added includes for string and unordered_set headers
• Added two new member variables: m_recorded_semantic_keys (unordered_set) and
 m_recorded_binding_states (unordered_map) for tracking diagnostic state

src/render/chain/chain_resources.hpp


12. src/render/chain/filter_controls.hpp Refactoring +1/-41

Filter controls moved to boundary headers

• Removed all filter control definitions (FilterControlId, FilterControlStage enum,
 FilterControlDescriptor struct, helper functions)
• Replaced with single include forwarding to goggles/filter_chain/filter_controls.hpp
• Converts this file into a compatibility forwarder for the boundary-owned filter controls contract

src/render/chain/filter_controls.hpp


13. src/render/chain/include/goggles/filter_chain/filter_controls.hpp ✨ Enhancement +42/-0

Boundary-owned filter control types

• New file containing filter control definitions moved from src/render/chain/filter_controls.hpp
• Defines FilterControlId type alias, FilterControlStage enum, and FilterControlDescriptor
 struct
• Includes to_string() helper for stage values and function declarations for control ID generation
 and value clamping
• Establishes boundary-owned filter control contract

src/render/chain/include/goggles/filter_chain/filter_controls.hpp


14. src/util/config.hpp ✨ Enhancement +1/-28

Scale mode include added to config

• Added include for ../render/chain/include/goggles/filter_chain/scale_mode.hpp before error.hpp
• Maintains existing error.hpp include for backward compatibility

src/util/config.hpp


15. src/render/chain/api/c/goggles_filter_chain.cpp Refactoring +4/-4

C API uses boundary-owned public headers

• Updated includes to use boundary-owned headers:
 goggles/filter_chain/diagnostics/diagnostic_policy.hpp,
 goggles/filter_chain/diagnostics/diagnostic_sink.hpp, goggles/filter_chain/result.hpp,
 goggles/filter_chain/scale_mode.hpp
• Removed includes from util/ paths (config, diagnostics, error)
• Maintains internal includes for chain runtime, filter controls, and vulkan context

src/render/chain/api/c/goggles_filter_chain.cpp


16. src/render/chain/chain_runtime.hpp Refactoring +5/-5

Chain runtime uses boundary-owned headers

• Updated includes to use boundary-owned headers:
 goggles/filter_chain/diagnostics/diagnostic_policy.hpp,
 goggles/filter_chain/diagnostics/diagnostic_session.hpp,
 goggles/filter_chain/diagnostics/gpu_timestamp_pool.hpp, goggles/filter_chain/result.hpp,
 goggles/filter_chain/scale_mode.hpp
• Removed includes from util/ paths (config, diagnostics, error)

src/render/chain/chain_runtime.hpp


17. src/render/chain/include/goggles/filter_chain/scale_mode.hpp ✨ Enhancement +31/-0

Boundary-owned scale mode definitions

• New file defining ScaleMode enum with values: fit, fill, stretch, integer, dynamic
• Includes to_string() helper function for stable string representation of scale modes
• Establishes boundary-owned scale mode contract independent of Goggles config

src/render/chain/include/goggles/filter_chain/scale_mode.hpp


18. src/render/backend/filter_chain_controller.cpp ✨ Enhancement +8/-0

Controller record delegation method

• Added new record() method implementation that delegates to filter_chain.record(record_info)
• Method checks if filter chain is initialized and returns appropriate error if not
• Provides controller-level boundary for backend record operations

src/render/backend/filter_chain_controller.cpp


19. src/render/backend/vulkan_backend.hpp Refactoring +3/-2

Backend uses boundary-owned public headers

• Updated includes to use boundary-owned headers: goggles/filter_chain/filter_controls.hpp,
 goggles/filter_chain/result.hpp, goggles/filter_chain/scale_mode.hpp
• Removed render/chain/filter_controls.hpp and util/error.hpp includes

src/render/backend/vulkan_backend.hpp


20. src/render/chain/chain_builder.cpp Refactoring +2/-2

Chain builder uses boundary diagnostics headers

• Updated includes to use boundary-owned headers:
 goggles/filter_chain/diagnostics/compile_report.hpp,
 goggles/filter_chain/diagnostics/source_provenance.hpp
• Removed includes from util/diagnostics/ paths

src/render/chain/chain_builder.cpp


21. src/render/chain/chain_builder.hpp Refactoring +2/-2

Chain builder header uses boundary diagnostics

• Updated includes to use boundary-owned headers:
 goggles/filter_chain/diagnostics/compile_report.hpp,
 goggles/filter_chain/diagnostics/diagnostic_session.hpp
• Removed includes from util/diagnostics/ paths

src/render/chain/chain_builder.hpp


22. src/render/chain/vulkan_context.hpp Refactoring +1/-15

Vulkan context moved to boundary headers

• Removed all VulkanContext struct definition and related code
• Replaced with single include forwarding to goggles/filter_chain/vulkan_context.hpp
• Converts this file into a compatibility forwarder for the boundary-owned Vulkan context contract

src/render/chain/vulkan_context.hpp


23. src/render/chain/filter_pass.hpp Refactoring +1/-1

Filter pass uses boundary diagnostics header

• Updated include from util/diagnostics/compile_report.hpp to
 goggles/filter_chain/diagnostics/compile_report.hpp

src/render/chain/filter_pass.hpp


24. src/render/chain/api/cpp/goggles_filter_chain.hpp Refactoring +2/-2

C++ wrapper uses boundary-owned headers

• Reordered includes to place boundary-owned headers first: goggles/filter_chain/result.hpp,
 goggles_filter_chain.h
• Removed render/chain/api/c/goggles_filter_chain.h include path
• Removed util/error.hpp include

src/render/chain/api/cpp/goggles_filter_chain.hpp


25. src/render/chain/include/goggles/filter_chain/vulkan_context.hpp ✨ Enhancement +16/-0

Boundary-owned Vulkan context types

• New file containing VulkanContext struct definition moved from
 src/render/chain/vulkan_context.hpp
• Defines Vulkan device, physical device, command pool, queue, and queue family index members
• Establishes boundary-owned Vulkan context contract

src/render/chain/include/goggles/filter_chain/vulkan_context.hpp


26. src/render/chain/chain_runtime.cpp Refactoring +1/-1

Chain runtime uses boundary diagnostics header

• Updated include from util/diagnostics/log_sink.hpp to
 goggles/filter_chain/diagnostics/log_sink.hpp

src/render/chain/chain_runtime.cpp


27. src/render/chain/chain_executor.hpp Refactoring +2/-2

Chain executor uses boundary diagnostics headers

• Updated includes from util/diagnostics/ paths to goggles/filter_chain/diagnostics/ paths for
 diagnostic_session.hpp and gpu_timestamp_pool.hpp

src/render/chain/chain_executor.hpp


28. src/render/backend/render_output.hpp Refactoring +1/-1

Render output uses boundary result header

• Updated include from util/error.hpp to goggles/filter_chain/result.hpp

src/render/backend/render_output.hpp


29. src/render/backend/vulkan_context.hpp Refactoring +2/-2

Vulkan context uses boundary-owned headers

• Updated includes to use boundary-owned headers: goggles/filter_chain/result.hpp,
 goggles/filter_chain/vulkan_context.hpp
• Removed render/chain/vulkan_context.hpp and util/error.hpp includes

src/render/backend/vulkan_context.hpp


30. src/render/backend/external_frame_importer.hpp Refactoring +1/-1

Frame importer uses boundary result header

• Updated include from util/error.hpp to goggles/filter_chain/result.hpp

src/render/backend/external_frame_importer.hpp


31. src/render/shader/retroarch_preprocessor.hpp Refactoring +2/-2

Preprocessor uses boundary diagnostics headers

• Updated includes to use boundary-owned headers:
 goggles/filter_chain/diagnostics/source_provenance.hpp, goggles/filter_chain/result.hpp
• Removed util/diagnostics/source_provenance.hpp and util/error.hpp includes

src/render/shader/retroarch_preprocessor.hpp


32. src/render/shader/shader_runtime.hpp 📦 Other +2/-2
• Updated includes to use boundary-owned headers:
 goggles/filter_chain/diagnostics/compile_report.hpp, `gogg

src/render/shader/shader_runtime.hpp


33. openspec/specs/build-system/spec.md 📝 Documentation +58/-1

Build system specification with filter-chain normalization requirements

• Updated Purpose section from placeholder to define repository-managed build, provider-resolution,
 and test-registration contracts
• Added three new requirements: Normalized Filter Library Source Selection, Monorepo Package
 Rehearsal Preserves Goggles Workflows, and Filter Runtime Test Registration Split
• Each requirement includes detailed scenarios covering provider mode normalization, preset-driven
 rehearsal, and test coverage separation

openspec/specs/build-system/spec.md


34. openspec/changes/archive/2026-03-13-normalize-filter-chain-consumer-boundary/specs/build-system/spec.md 📝 Documentation +59/-0

Archive specification delta for filter-chain normalization

• Created new archive file documenting delta changes for build-system specification
• Added three new requirements: Normalized Filter Library Source Selection, Monorepo Package
 Rehearsal Preserves Goggles Workflows, and Filter Runtime Test Registration Split
• Includes detailed scenarios for provider normalization, preset-driven workflows, and test coverage
 distinction

openspec/changes/archive/2026-03-13-normalize-filter-chain-consumer-boundary/specs/build-system/spec.md


35. openspec/specs/filter-chain-cpp-wrapper/spec.md 📝 Documentation +41/-1

Filter-chain C++ wrapper specification with retarget contract

• Updated Purpose section from placeholder to define public C++ wrapper contract for
 goggles-filter-chain
• Added two new requirements: Wrapper Retarget Contract Mirrors Post-Retarget ABI and
 Extraction-safe Wrapper Public Surface
• Includes scenarios for preserving preset state during retargeting and ensuring wrapper headers
 depend only on C ABI and boundary-owned headers

openspec/specs/filter-chain-cpp-wrapper/spec.md


36. openspec/changes/archive/2026-03-13-normalize-filter-chain-consumer-boundary/specs/render-pipeline/spec.md 📝 Documentation +44/-0

Archive specification delta for render-pipeline retargeting

• Created new archive file documenting delta changes for render-pipeline specification
• Modified Swapchain Format Matching requirement to use output-retarget seam instead of preset
 reload
• Added Async Filter Lifecycle Safety requirement with scenarios for pending reload alignment and
 retarget failure handling

openspec/changes/archive/2026-03-13-normalize-filter-chain-consumer-boundary/specs/render-pipeline/spec.md


37. openspec/changes/archive/2026-03-13-normalize-filter-chain-consumer-boundary/specs/filter-chain-c-api/spec.md 📝 Documentation +43/-0

Archive specification delta for filter-chain C API boundary

• Created new archive file documenting delta changes for filter-chain C API specification
• Added Post-Retarget Output Contract requirement distinguishing retarget from preset reload
 behavior
• Added C ABI Public Surface Isolation requirement ensuring public headers exclude Goggles-internal
 dependencies

openspec/changes/archive/2026-03-13-normalize-filter-chain-consumer-boundary/specs/filter-chain-c-api/spec.md


38. openspec/changes/archive/2026-03-13-normalize-filter-chain-consumer-boundary/specs/filter-chain-cpp-wrapper/spec.md 📝 Documentation +43/-0

Archive specification delta for C++ wrapper contract

• Created new archive file documenting delta changes for filter-chain C++ wrapper specification
• Added Wrapper Retarget Contract Mirrors Post-Retarget ABI requirement with scenarios for preset
 state preservation
• Added Extraction-safe Wrapper Public Surface requirement ensuring wrapper depends only on C ABI
 and boundary-owned headers

openspec/changes/archive/2026-03-13-normalize-filter-chain-consumer-boundary/specs/filter-chain-cpp-wrapper/spec.md


39. cmake/GogglesFilterChainConfig.cmake.in ⚙️ Configuration changes +44/-0

CMake package configuration for filter-chain normalization

• Created new CMake package configuration file for goggles-filter-chain
• Defines dependencies on Vulkan, spdlog, slang, and stb_image
• Creates both GogglesFilterChain::goggles-filter-chain and normalized goggles-filter-chain
 targets with proper include directories and compile definitions

cmake/GogglesFilterChainConfig.cmake.in


40. src/util/CMakeLists.txt Refactoring +19/-1

Utility library refactoring with logging separation

• Refactored goggles_util to extract logging into separate goggles_util_logging_obj object
 library
• Added ${CMAKE_SOURCE_DIR}/src/render/chain/include to include directories for both logging
 object and main util library
• Applied clang-tidy, sanitizers, and profiling enablement to logging object library

src/util/CMakeLists.txt


41. src/render/chain/CMakeLists.txt Refactoring +3/-2

Render chain CMake visibility and linkage normalization

• Added ${CMAKE_SOURCE_DIR}/src/render/chain/include to target include directories
• Changed target_link_libraries from PUBLIC to PRIVATE for internal dependencies
• Changed target_compile_definitions from PUBLIC to PRIVATE for Vulkan configuration

src/render/chain/CMakeLists.txt


42. src/render/shader/CMakeLists.txt Refactoring +3/-2

Render shader CMake visibility and linkage normalization

• Added ${CMAKE_SOURCE_DIR}/src/render/chain/include to target include directories
• Changed target_link_libraries from PUBLIC to PRIVATE for internal dependencies
• Changed target_compile_definitions from PUBLIC to PRIVATE for Vulkan configuration

src/render/shader/CMakeLists.txt


43. src/render/texture/CMakeLists.txt Refactoring +3/-2

Render texture CMake visibility and linkage normalization

• Added ${CMAKE_SOURCE_DIR}/src/render/chain/include to target include directories
• Changed target_link_libraries from PUBLIC to PRIVATE for internal dependencies
• Changed target_compile_definitions from PUBLIC to PRIVATE for Vulkan configuration

src/render/texture/CMakeLists.txt


44. pixi.toml ⚙️ Configuration changes +5/-0

Pixi task for filter-chain provider rehearsal

• Added new task rehearse-filter-chain-provider to test in-tree and package provider modes
• Task runs scripts/task/rehearse-filter-chain-provider.sh script with dependency on _check-init

pixi.toml


45. src/render/backend/CMakeLists.txt ⚙️ Configuration changes +1/-0

Render backend CMake include path addition

• Added ${CMAKE_SOURCE_DIR}/src/render/chain/api/c to target include directories

src/render/backend/CMakeLists.txt


46. CMakePresets.json Additional files +75/-0

...

CMakePresets.json


47. cmake/GogglesFilterChainProvider.cmake Additional files +189/-0

...

cmake/GogglesFilterChainProvider.cmake


48. openspec/changes/archive/2026-03-13-normalize-filter-chain-consumer-boundary/design.md Additional files +183/-0

...

openspec/changes/archive/2026-03-13-normalize-filter-chain-consumer-boundary/design.md


49. openspec/changes/archive/2026-03-13-normalize-filter-chain-consumer-boundary/proposal.md Additional files +111/-0

...

openspec/changes/archive/2026-03-13-normalize-filter-chain-consumer-boundary/proposal.md


50. openspec/changes/archive/2026-03-13-normalize-filter-chain-consumer-boundary/specs/goggles-filter-chain/spec.md Additional files +86/-0

...

openspec/changes/archive/2026-03-13-normalize-filter-chain-consumer-boundary/specs/goggles-filter-chain/spec.md


51. openspec/changes/archive/2026-03-13-normalize-filter-chain-consumer-boundary/tasks.md Additional files +58/-0

...

openspec/changes/archive/2026-03-13-normalize-filter-chain-consumer-boundary/tasks.md


52. openspec/specs/filter-chain-c-api/spec.md Additional files +42/-2

...

openspec/specs/filter-chain-c-api/spec.md


53. openspec/specs/goggles-filter-chain/spec.md Additional files +74/-29

...

openspec/specs/goggles-filter-chain/spec.md


54. openspec/specs/render-pipeline/spec.md Additional files +28/-34

...

openspec/specs/render-pipeline/spec.md


55. scripts/task/help.sh Additional files +5/-1

...

scripts/task/help.sh


56. scripts/task/rehearse-filter-chain-provider.sh Additional files +90/-0

...

scripts/task/rehearse-filter-chain-provider.sh


57. src/render/CMakeLists.txt Additional files +62/-67

...

src/render/CMakeLists.txt


58. src/render/backend/vulkan_debug.hpp Additional files +1/-1

...

src/render/backend/vulkan_debug.hpp


59. src/render/backend/vulkan_error.hpp Additional files +1/-1

...

src/render/backend/vulkan_error.hpp


60. src/render/chain/api/c/goggles_filter_chain.h Additional files +11/-5

...

src/render/chain/api/c/goggles_filter_chain.h


61. src/render/chain/framebuffer.hpp Additional files +1/-1

...

src/render/chain/framebuffer.hpp


62. src/render/chain/include/goggles/filter_chain/diagnostics/compile_report.hpp Additional files +3/-0

...

src/render/chain/include/goggles/filter_chain/diagnostics/compile_report.hpp


63. src/render/chain/include/goggles/filter_chain/diagnostics/diagnostic_event.hpp Additional files +3/-0

...

src/render/chain/include/goggles/filter_chain/diagnostics/diagnostic_event.hpp


64. src/render/chain/include/goggles/filter_chain/diagnostics/diagnostic_policy.hpp Additional files +3/-0

...

src/render/chain/include/goggles/filter_chain/diagnostics/diagnostic_policy.hpp


65. src/render/chain/include/goggles/filter_chain/diagnostics/diagnostic_session.hpp Additional files +3/-0

...

src/render/chain/include/goggles/filter_chain/diagnostics/diagnostic_session.hpp


66. src/render/chain/include/goggles/filter_chain/diagnostics/diagnostic_sink.hpp Additional files +3/-0

...

src/render/chain/include/goggles/filter_chain/diagnostics/diagnostic_sink.hpp


67. src/render/chain/include/goggles/filter_chain/diagnostics/gpu_timestamp_pool.hpp Additional files +3/-0

...

src/render/chain/include/goggles/filter_chain/diagnostics/gpu_timestamp_pool.hpp


68. src/render/chain/include/goggles/filter_chain/diagnostics/log_sink.hpp Additional files +3/-0

...

src/render/chain/include/goggles/filter_chain/diagnostics/log_sink.hpp


69. src/render/chain/include/goggles/filter_chain/diagnostics/source_provenance.hpp Additional files +3/-0

...

src/render/chain/include/goggles/filter_chain/diagnostics/source_provenance.hpp


70. src/render/chain/pass.hpp Additional files +1/-2

...

src/render/chain/pass.hpp


71. src/render/chain/preset_parser.hpp Additional files +1/-1

...

src/render/chain/preset_parser.hpp


72. src/render/chain/vulkan_result.hpp Additional files +1/-1

...

src/render/chain/vulkan_result.hpp


73. tests/CMakeLists.txt Additional files +82/-12

...

tests/CMakeLists.txt


Grey Divider

Qodo Logo

@qodo-code-review
Copy link
Copy Markdown

qodo-code-review bot commented Mar 13, 2026

Code Review by Qodo

🐞 Bugs (3) 📘 Rule violations (0) 📎 Requirement gaps (0)

Grey Divider


Action required

1. Provider uses undefined targets 🐞 Bug ✓ Correctness
Description
The default in-tree provider path creates target goggles-filter-chain from OBJECT libraries
goggles_diagnostics and goggles_util_logging_obj, but src/CMakeLists.txt processes src/render before
src/util so those OBJECT targets do not exist when goggles_resolve_filter_chain_provider() runs,
causing CMake configure to fail.
Code

cmake/GogglesFilterChainProvider.cmake[R80-86]

+    add_library(goggles-filter-chain ${GOGGLES_CHAIN_LIBRARY_TYPE_NORMALIZED}
+        $<TARGET_OBJECTS:goggles_render_chain_obj>
+        $<TARGET_OBJECTS:goggles_render_shader_obj>
+        $<TARGET_OBJECTS:goggles_render_texture_obj>
+        $<TARGET_OBJECTS:goggles_diagnostics>
+        $<TARGET_OBJECTS:goggles_util_logging_obj>
+    )
Evidence
src/render/CMakeLists.txt calls goggles_resolve_filter_chain_provider() during configuration, which
in the in-tree mode immediately calls _goggles_filter_chain_enable_in_tree() and references
$<TARGET_OBJECTS:goggles_diagnostics> and $<TARGET_OBJECTS:goggles_util_logging_obj>. However,
src/CMakeLists.txt adds the render subdirectory before util, so those OBJECT libraries (defined
under src/util and src/util/diagnostics) have not been created yet at the time render is configured.

cmake/GogglesFilterChainProvider.cmake[75-86]
src/render/CMakeLists.txt[1-23]
src/CMakeLists.txt[3-7]
src/util/CMakeLists.txt[4-26]
src/util/diagnostics/CMakeLists.txt[6-12]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
The default `in-tree` provider path configures `goggles-filter-chain` using `$&lt;TARGET_OBJECTS:goggles_diagnostics&gt;` and `$&lt;TARGET_OBJECTS:goggles_util_logging_obj&gt;` before those targets exist (because `src/util` is added after `src/render`). This breaks CMake configure for the default provider mode.

### Issue Context
`goggles_resolve_filter_chain_provider()` is invoked inside `src/render/CMakeLists.txt`, which is evaluated while processing `add_subdirectory(render)` from `src/CMakeLists.txt`.

### Fix Focus Areas
- src/CMakeLists.txt[3-7]
- src/render/CMakeLists.txt[1-23]
- cmake/GogglesFilterChainProvider.cmake[75-86]
- src/util/CMakeLists.txt[4-26]
- src/util/diagnostics/CMakeLists.txt[6-12]

### Suggested fix approach
- Move `add_subdirectory(util)` (and any other providers of `goggles_diagnostics`) before `add_subdirectory(render)` in `src/CMakeLists.txt`, OR
- Move the call site of `goggles_resolve_filter_chain_provider()` to a point after util/diagnostics targets are created, OR
- Stop depending on util/diagnostics OBJECT targets in `_goggles_filter_chain_enable_in_tree()` and instead link to normal libraries that are already defined at that point (if applicable).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Installed header includes source path 🐞 Bug ✓ Correctness
Description
src/util/error.hpp is installed as a compatibility header but now includes result.hpp using a
source-tree relative include (../render/chain/include/...), which will not exist in the install tree
and will break consumers including <util/error.hpp>.
Code

src/util/error.hpp[R1-3]

#pragma once

-#include <cstdint>
-#include <cstdio>
-#include <cstdlib>
-#include <memory>
-#include <nonstd/expected.hpp>
-#include <source_location>
-#include <string>
-#include <utility>
-
-namespace goggles {
-
-/// @brief Error codes used by `goggles::Error`.
-enum class ErrorCode : std::uint8_t {
-    ok,
-    file_not_found,
-    file_read_failed,
-    file_write_failed,
-    parse_error,
-    invalid_config,
-    vulkan_init_failed,
-    vulkan_device_lost,
-    shader_compile_failed,
-    shader_load_failed,
-    input_init_failed,
-    invalid_data,
-    unknown_error
-};
-
-/// @brief Structured error for `Result<T>` operations.
-struct Error {
-    ErrorCode code;
-    std::string message;
-    std::source_location location;
-
-    Error(ErrorCode error_code, std::string msg,
-          std::source_location loc = std::source_location::current())
-        : code(error_code), message(std::move(msg)), location(loc) {}
-};
-
-/// @brief Project-wide fallible operation return type.
-template <typename T>
-using Result = nonstd::expected<T, Error>;
-
-/// @brief Convenience alias for `Result<std::unique_ptr<T>>`.
-template <typename T>
-using ResultPtr = Result<std::unique_ptr<T>>;
-
-template <typename T>
-[[nodiscard]] inline auto make_error(ErrorCode code, std::string message,
-                                     std::source_location loc = std::source_location::current())
-    -> Result<T> {
-    return nonstd::make_unexpected(Error{code, std::move(message), loc});
-}
-
-template <typename T>
-[[nodiscard]] inline auto make_result_ptr(std::unique_ptr<T> ptr) -> ResultPtr<T> {
-    return ResultPtr<T>{std::move(ptr)};
-}
-
-template <typename T>
-[[nodiscard]] inline auto
-make_result_ptr_error(ErrorCode code, std::string message,
-                      std::source_location loc = std::source_location::current()) -> ResultPtr<T> {
-    return nonstd::make_unexpected(Error{code, std::move(message), loc});
-}
-
-/// @brief Returns a stable string name for an `ErrorCode` value.
-[[nodiscard]] constexpr auto error_code_name(ErrorCode code) -> const char* {
-    switch (code) {
-    case ErrorCode::ok:
-        return "ok";
-    case ErrorCode::file_not_found:
-        return "file_not_found";
-    case ErrorCode::file_read_failed:
-        return "file_read_failed";
-    case ErrorCode::file_write_failed:
-        return "file_write_failed";
-    case ErrorCode::parse_error:
-        return "parse_error";
-    case ErrorCode::invalid_config:
-        return "invalid_config";
-    case ErrorCode::vulkan_init_failed:
-        return "vulkan_init_failed";
-    case ErrorCode::vulkan_device_lost:
-        return "vulkan_device_lost";
-    case ErrorCode::shader_compile_failed:
-        return "shader_compile_failed";
-    case ErrorCode::shader_load_failed:
-        return "shader_load_failed";
-    case ErrorCode::input_init_failed:
-        return "input_init_failed";
-    case ErrorCode::invalid_data:
-        return "invalid_data";
-    case ErrorCode::unknown_error:
-        return "unknown_error";
-    }
-    return "unknown";
-}
-
-} // namespace goggles
-
-// NOLINTBEGIN(cppcoreguidelines-macro-usage)
-
-/// @brief Propagates an error or returns the contained value (expression-style).
-///
-/// Similar to Rust's `?` operator. The expression must yield a `Result<T>`.
-// NOLINTNEXTLINE(bugprone-macro-parentheses)
-#define GOGGLES_TRY(expr)                                                                          \
-    ({                                                                                             \
-        auto _try_result = (expr);                                                                 \
-        if (!_try_result)                                                                          \
-            return nonstd::make_unexpected(_try_result.error());                                   \
-        std::move(_try_result).value();                                                            \
-    })
-
-/// @brief Aborts on error or returns the contained value (expression-style).
-///
-/// Use for internal invariants where failure indicates a bug.
-// NOLINTNEXTLINE(bugprone-macro-parentheses)
-#define GOGGLES_MUST(expr)                                                                         \
-    ({                                                                                             \
-        auto _must_result = (expr);                                                                \
-        if (!_must_result) {                                                                       \
-            auto& _err = _must_result.error();                                                     \
-            std::fprintf(stderr, "GOGGLES_MUST failed at %s:%u in %s\n  %s: %s\n",                 \
-                         _err.location.file_name(), _err.location.line(),                          \
-                         _err.location.function_name(), goggles::error_code_name(_err.code),       \
-                         _err.message.c_str());                                                    \
-            std::abort();                                                                          \
-        }                                                                                          \
-        std::move(_must_result).value();                                                           \
-    })
-
-/// @brief Aborts when an invariant is violated.
-// NOLINTNEXTLINE(bugprone-macro-parentheses)
-#define GOGGLES_ASSERT(condition, ...)                                                             \
-    do {                                                                                           \
-        if (!(condition)) {                                                                        \
-            std::fprintf(stderr, "GOGGLES_ASSERT failed: %s at %s:%u in %s\n", #condition,         \
-                         __FILE__, __LINE__, __func__);                                            \
-            __VA_OPT__(do {                                                                        \
-                std::fprintf(stderr, "  ");                                                        \
-                std::fprintf(stderr, __VA_ARGS__);                                                 \
-                std::fprintf(stderr, "\n");                                                        \
-            } while (false);)                                                                      \
-            std::abort();                                                                          \
-        }                                                                                          \
-    } while (false)
-
-// NOLINTEND(cppcoreguidelines-macro-usage)
+#include "../render/chain/include/goggles/filter_chain/result.hpp"
Evidence
The install step explicitly installs src/util/error.hpp into include/util for the in-tree-owned
package, but that header now references a source-only path rather than the installed canonical
public header under include/goggles/filter_chain.

src/util/error.hpp[1-3]
src/render/CMakeLists.txt[56-80]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`src/util/error.hpp` is installed to `include/util/error.hpp` but includes `result.hpp` via a source-tree relative path (`../render/chain/include/...`). That path is invalid in the installed package layout, breaking downstream builds that rely on the compatibility include.

### Issue Context
The installed canonical header is `include/goggles/filter_chain/result.hpp`, and `util/error.hpp` should be a forwarder to that public surface.

### Fix Focus Areas
- src/util/error.hpp[1-3]
- src/render/CMakeLists.txt[56-80]

### Suggested fix approach
- Replace the include in `src/util/error.hpp` with:
 - `#include &lt;goggles/filter_chain/result.hpp&gt;`
- (Optional) If you want to keep the forwarder strictly minimal, consider also adding a brief comment that it is a compatibility forwarder.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

3. Provider mode case split 🐞 Bug ⛯ Reliability
Description
goggles_resolve_filter_chain_provider lowercases GOGGLES_FILTER_CHAIN_PROVIDER so values like
'PACKAGE' are accepted, but tests/CMakeLists.txt uses case-sensitive STREQUAL "package" checks, so
the test graph/include setup can diverge from the resolved provider mode.
Code

tests/CMakeLists.txt[R66-87]

+if(GOGGLES_FILTER_CHAIN_PROVIDER STREQUAL "package")
+    foreach(prefix_dir IN LISTS CMAKE_PREFIX_PATH)
+        list(APPEND GOGGLES_FILTER_CHAIN_TEST_INCLUDE_DIRS "${prefix_dir}/include")
+    endforeach()
+else()
+    list(APPEND GOGGLES_FILTER_CHAIN_TEST_INCLUDE_DIRS
+        ${CMAKE_SOURCE_DIR}/src/render/chain/include
+        ${CMAKE_SOURCE_DIR}/src/render/chain/api/c
+        ${CMAKE_SOURCE_DIR}/src/render/chain/api/cpp
+    )
+endif()
+
+# Test executables
+add_executable(goggles_tests)
+
+target_sources(goggles_tests PRIVATE ${GOGGLES_HOST_TEST_SOURCES})
+
+if(NOT GOGGLES_FILTER_CHAIN_PROVIDER STREQUAL "package")
+    add_executable(goggles_filter_chain_contract_tests)
+    target_sources(goggles_filter_chain_contract_tests PRIVATE
+        ${GOGGLES_FILTER_CHAIN_CONTRACT_TEST_SOURCES})
+endif()
Evidence
The provider resolver uses a lowercased copy of the cache variable for behavior decisions, while
tests key off the raw cache variable value. This makes behavior inconsistent when the cache value is
not already lowercase (which the resolver explicitly supports).

cmake/GogglesFilterChainProvider.cmake[12-21]
tests/CMakeLists.txt[66-87]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
The provider module supports mixed-case values by normalizing with `string(TOLOWER ...)`, but tests branch on the raw cache variable via `STREQUAL &quot;package&quot;`. This can misconfigure which test executables are created and what include dirs are used.

### Issue Context
Provider resolution already computes a normalized `provider_mode` but does not publish it for downstream CMake.

### Fix Focus Areas
- cmake/GogglesFilterChainProvider.cmake[12-22]
- tests/CMakeLists.txt[66-87]

### Suggested fix approach
- In `goggles_resolve_filter_chain_provider()`, set something like `GOGGLES_FILTER_CHAIN_PROVIDER_MODE` (normalized) in `PARENT_SCOPE` (or cache/internal) and/or update the cache variable to the normalized value.
- Update `tests/CMakeLists.txt` to branch on the normalized variable instead of `GOGGLES_FILTER_CHAIN_PROVIDER` directly.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

ⓘ The new review experience is currently in Beta. Learn more

Grey Divider

Qodo Logo

Comment on lines +80 to +86
add_library(goggles-filter-chain ${GOGGLES_CHAIN_LIBRARY_TYPE_NORMALIZED}
$<TARGET_OBJECTS:goggles_render_chain_obj>
$<TARGET_OBJECTS:goggles_render_shader_obj>
$<TARGET_OBJECTS:goggles_render_texture_obj>
$<TARGET_OBJECTS:goggles_diagnostics>
$<TARGET_OBJECTS:goggles_util_logging_obj>
)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

1. Provider uses undefined targets 🐞 Bug ✓ Correctness

The default in-tree provider path creates target goggles-filter-chain from OBJECT libraries
goggles_diagnostics and goggles_util_logging_obj, but src/CMakeLists.txt processes src/render before
src/util so those OBJECT targets do not exist when goggles_resolve_filter_chain_provider() runs,
causing CMake configure to fail.
Agent Prompt
### Issue description
The default `in-tree` provider path configures `goggles-filter-chain` using `$<TARGET_OBJECTS:goggles_diagnostics>` and `$<TARGET_OBJECTS:goggles_util_logging_obj>` before those targets exist (because `src/util` is added after `src/render`). This breaks CMake configure for the default provider mode.

### Issue Context
`goggles_resolve_filter_chain_provider()` is invoked inside `src/render/CMakeLists.txt`, which is evaluated while processing `add_subdirectory(render)` from `src/CMakeLists.txt`.

### Fix Focus Areas
- src/CMakeLists.txt[3-7]
- src/render/CMakeLists.txt[1-23]
- cmake/GogglesFilterChainProvider.cmake[75-86]
- src/util/CMakeLists.txt[4-26]
- src/util/diagnostics/CMakeLists.txt[6-12]

### Suggested fix approach
- Move `add_subdirectory(util)` (and any other providers of `goggles_diagnostics`) before `add_subdirectory(render)` in `src/CMakeLists.txt`, OR
- Move the call site of `goggles_resolve_filter_chain_provider()` to a point after util/diagnostics targets are created, OR
- Stop depending on util/diagnostics OBJECT targets in `_goggles_filter_chain_enable_in_tree()` and instead link to normal libraries that are already defined at that point (if applicable).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines 1 to +3
#pragma once

#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <memory>
#include <nonstd/expected.hpp>
#include <source_location>
#include <string>
#include <utility>

namespace goggles {

/// @brief Error codes used by `goggles::Error`.
enum class ErrorCode : std::uint8_t {
ok,
file_not_found,
file_read_failed,
file_write_failed,
parse_error,
invalid_config,
vulkan_init_failed,
vulkan_device_lost,
shader_compile_failed,
shader_load_failed,
input_init_failed,
invalid_data,
unknown_error
};

/// @brief Structured error for `Result<T>` operations.
struct Error {
ErrorCode code;
std::string message;
std::source_location location;

Error(ErrorCode error_code, std::string msg,
std::source_location loc = std::source_location::current())
: code(error_code), message(std::move(msg)), location(loc) {}
};

/// @brief Project-wide fallible operation return type.
template <typename T>
using Result = nonstd::expected<T, Error>;

/// @brief Convenience alias for `Result<std::unique_ptr<T>>`.
template <typename T>
using ResultPtr = Result<std::unique_ptr<T>>;

template <typename T>
[[nodiscard]] inline auto make_error(ErrorCode code, std::string message,
std::source_location loc = std::source_location::current())
-> Result<T> {
return nonstd::make_unexpected(Error{code, std::move(message), loc});
}

template <typename T>
[[nodiscard]] inline auto make_result_ptr(std::unique_ptr<T> ptr) -> ResultPtr<T> {
return ResultPtr<T>{std::move(ptr)};
}

template <typename T>
[[nodiscard]] inline auto
make_result_ptr_error(ErrorCode code, std::string message,
std::source_location loc = std::source_location::current()) -> ResultPtr<T> {
return nonstd::make_unexpected(Error{code, std::move(message), loc});
}

/// @brief Returns a stable string name for an `ErrorCode` value.
[[nodiscard]] constexpr auto error_code_name(ErrorCode code) -> const char* {
switch (code) {
case ErrorCode::ok:
return "ok";
case ErrorCode::file_not_found:
return "file_not_found";
case ErrorCode::file_read_failed:
return "file_read_failed";
case ErrorCode::file_write_failed:
return "file_write_failed";
case ErrorCode::parse_error:
return "parse_error";
case ErrorCode::invalid_config:
return "invalid_config";
case ErrorCode::vulkan_init_failed:
return "vulkan_init_failed";
case ErrorCode::vulkan_device_lost:
return "vulkan_device_lost";
case ErrorCode::shader_compile_failed:
return "shader_compile_failed";
case ErrorCode::shader_load_failed:
return "shader_load_failed";
case ErrorCode::input_init_failed:
return "input_init_failed";
case ErrorCode::invalid_data:
return "invalid_data";
case ErrorCode::unknown_error:
return "unknown_error";
}
return "unknown";
}

} // namespace goggles

// NOLINTBEGIN(cppcoreguidelines-macro-usage)

/// @brief Propagates an error or returns the contained value (expression-style).
///
/// Similar to Rust's `?` operator. The expression must yield a `Result<T>`.
// NOLINTNEXTLINE(bugprone-macro-parentheses)
#define GOGGLES_TRY(expr) \
({ \
auto _try_result = (expr); \
if (!_try_result) \
return nonstd::make_unexpected(_try_result.error()); \
std::move(_try_result).value(); \
})

/// @brief Aborts on error or returns the contained value (expression-style).
///
/// Use for internal invariants where failure indicates a bug.
// NOLINTNEXTLINE(bugprone-macro-parentheses)
#define GOGGLES_MUST(expr) \
({ \
auto _must_result = (expr); \
if (!_must_result) { \
auto& _err = _must_result.error(); \
std::fprintf(stderr, "GOGGLES_MUST failed at %s:%u in %s\n %s: %s\n", \
_err.location.file_name(), _err.location.line(), \
_err.location.function_name(), goggles::error_code_name(_err.code), \
_err.message.c_str()); \
std::abort(); \
} \
std::move(_must_result).value(); \
})

/// @brief Aborts when an invariant is violated.
// NOLINTNEXTLINE(bugprone-macro-parentheses)
#define GOGGLES_ASSERT(condition, ...) \
do { \
if (!(condition)) { \
std::fprintf(stderr, "GOGGLES_ASSERT failed: %s at %s:%u in %s\n", #condition, \
__FILE__, __LINE__, __func__); \
__VA_OPT__(do { \
std::fprintf(stderr, " "); \
std::fprintf(stderr, __VA_ARGS__); \
std::fprintf(stderr, "\n"); \
} while (false);) \
std::abort(); \
} \
} while (false)

// NOLINTEND(cppcoreguidelines-macro-usage)
#include "../render/chain/include/goggles/filter_chain/result.hpp"
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

2. Installed header includes source path 🐞 Bug ✓ Correctness

src/util/error.hpp is installed as a compatibility header but now includes result.hpp using a
source-tree relative include (../render/chain/include/...), which will not exist in the install tree
and will break consumers including <util/error.hpp>.
Agent Prompt
### Issue description
`src/util/error.hpp` is installed to `include/util/error.hpp` but includes `result.hpp` via a source-tree relative path (`../render/chain/include/...`). That path is invalid in the installed package layout, breaking downstream builds that rely on the compatibility include.

### Issue Context
The installed canonical header is `include/goggles/filter_chain/result.hpp`, and `util/error.hpp` should be a forwarder to that public surface.

### Fix Focus Areas
- src/util/error.hpp[1-3]
- src/render/CMakeLists.txt[56-80]

### Suggested fix approach
- Replace the include in `src/util/error.hpp` with:
  - `#include <goggles/filter_chain/result.hpp>`
- (Optional) If you want to keep the forwarder strictly minimal, consider also adding a brief comment that it is a compatibility forwarder.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 10

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/util/CMakeLists.txt (1)

28-33: ⚠️ Potential issue | 🟠 Major

Missing PIC setting on OBJECT library when building shared.

When GOGGLES_CHAIN_LIBRARY_TYPE is SHARED, position-independent code is enabled on goggles_util, but goggles_util_logging_obj is compiled before this check. Since OBJECT library objects are embedded directly into the final library, they also need PIC enabled, otherwise linking will fail on some platforms.

Proposed fix
+if(DEFINED GOGGLES_CHAIN_LIBRARY_TYPE)
+    string(TOUPPER "${GOGGLES_CHAIN_LIBRARY_TYPE}" GOGGLES_CHAIN_LIBRARY_TYPE_NORMALIZED)
+    if(GOGGLES_CHAIN_LIBRARY_TYPE_NORMALIZED STREQUAL "SHARED")
+        set_target_properties(goggles_util_logging_obj PROPERTIES POSITION_INDEPENDENT_CODE ON)
+    endif()
+endif()
+
 add_library(goggles_util_logging_obj OBJECT
     logging.cpp
 )

Alternatively, move the existing PIC block (lines 28-33) before the OBJECT library definition and apply it to both targets.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/util/CMakeLists.txt` around lines 28 - 33, When
GOGGLES_CHAIN_LIBRARY_TYPE is SHARED you only set POSITION_INDEPENDENT_CODE on
target goggles_util but not on the OBJECT library goggles_util_logging_obj
(whose objects are embedded into the final library), causing link failures; fix
by ensuring POSITION_INDEPENDENT_CODE is set for goggles_util_logging_obj as
well when GOGGLES_CHAIN_LIBRARY_TYPE_NORMALIZED STREQUAL "SHARED" or move the
entire PIC block that checks GOGGLES_CHAIN_LIBRARY_TYPE before the OBJECT
library definition so it applies to both targets (ensure you reference the
symbols GOGGLES_CHAIN_LIBRARY_TYPE, goggles_util, goggles_util_logging_obj, and
the POSITION_INDEPENDENT_CODE property).
🧹 Nitpick comments (11)
src/util/CMakeLists.txt (1)

8-11: PUBLIC on OBJECT library has no effect for object consumption.

When goggles_util_logging_obj is consumed via $<TARGET_OBJECTS:...> (line 22), CMake does not propagate the OBJECT library's include directories or link dependencies. The PUBLIC keyword here is effectively ignored. Consider using PRIVATE to avoid misleading readers, since the actual propagation happens via goggles_util's own target_include_directories.

Suggested change
-target_include_directories(goggles_util_logging_obj PUBLIC
+target_include_directories(goggles_util_logging_obj PRIVATE
     ${CMAKE_SOURCE_DIR}/src/render/chain/include
     ${CMAKE_CURRENT_SOURCE_DIR}
 )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/util/CMakeLists.txt` around lines 8 - 11, The target_include_directories
call on the OBJECT library goggles_util_logging_obj uses PUBLIC which has no
effect for OBJECT targets and is misleading; change the scope to PRIVATE (or
remove the scope) so readers aren't misled and ensure the actual propagation is
handled on the consuming target goggles_util (which uses
$<TARGET_OBJECTS:goggles_util_logging_obj>); update the
target_include_directories(goggles_util_logging_obj ...) invocation to use
PRIVATE to clarify intent.
openspec/specs/filter-chain-c-api/spec.md (1)

94-104: Minor formatting inconsistency in scenario blocks.

The new scenario blocks use plain text for keywords (e.g., - GIVEN, - WHEN, - THEN), while existing scenarios in this spec use bold formatting (e.g., - **GIVEN**, - **WHEN**, - **THEN**). Consider aligning with the existing convention for consistency.

📝 Suggested formatting alignment
 #### Scenario: Retarget preserves source-independent runtime state
-- GIVEN a runtime has already completed preset load and is in READY state
-- WHEN the host requests output-target retargeting for a format-only change through the C boundary
-- THEN the C ABI contract SHALL preserve active preset identity and existing control state on success
-- AND the host SHALL NOT need to reload the preset to adopt the new output target
+- **GIVEN** a runtime has already completed preset load and is in READY state
+- **WHEN** the host requests output-target retargeting for a format-only change through the C boundary
+- **THEN** the C ABI contract SHALL preserve active preset identity and existing control state on success
+- **AND** the host SHALL NOT need to reload the preset to adopt the new output target

Apply the same pattern to the other new scenarios (lines 100-104, 112-117, 119-125).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@openspec/specs/filter-chain-c-api/spec.md` around lines 94 - 104, Scenario
blocks use plain keywords like "- GIVEN" instead of the spec's established bold
format; update each scenario line in the new blocks to use bolded keywords
(e.g., change "- GIVEN" to "- **GIVEN**", "- WHEN" to "- **WHEN**", "- THEN" to
"- **THEN**") so the blocks for "Retarget preserves source-independent runtime
state" and the subsequent related scenarios match the existing convention; apply
the same replacement to the other new scenario lines mentioned in the comment
(the blocks around lines 100-104, 112-117, 119-125) to keep formatting
consistent.
src/render/chain/include/goggles/filter_chain/scale_mode.hpp (1)

15-29: Consider adding an unreachable hint for safety.

The to_string function handles all enum cases correctly and has a fallback return. However, some compilers may still emit warnings about control reaching the end of a non-void function. Consider moving the fallback into a default case or adding a compiler hint if warnings arise.

♻️ Alternative with default case
 [[nodiscard]] constexpr auto to_string(ScaleMode mode) -> const char* {
     switch (mode) {
     case ScaleMode::fit:
         return "fit";
     case ScaleMode::fill:
         return "fill";
     case ScaleMode::stretch:
         return "stretch";
     case ScaleMode::integer:
         return "integer";
     case ScaleMode::dynamic:
         return "dynamic";
+    default:
+        return "unknown";
     }
-    return "unknown";
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/render/chain/include/goggles/filter_chain/scale_mode.hpp` around lines 15
- 29, The to_string(ScaleMode) switch should include an explicit default branch
(instead of falling through after the switch) to silient compiler warnings and
make intent clear: add a default case that returns "unknown" and, for safety,
add an unreachable hint (e.g., call std::unreachable()/__builtin_unreachable()
or assert(false)) after the return path so the compiler knows all enum cases are
handled; update the to_string function's switch to include this default and the
unreachable hint.
tests/render/test_filter_chain_retarget_contract.cpp (1)

142-151: Consider making CacheDirGuard non-copyable and non-movable.

This RAII guard manages a filesystem resource. Allowing copies could lead to double-deletion of the directory.

♻️ Proposed fix
 struct CacheDirGuard {
     explicit CacheDirGuard(std::filesystem::path path) : dir(std::move(path)) {}
     ~CacheDirGuard() {
         if (!dir.empty()) {
             std::filesystem::remove_all(dir);
         }
     }
+    CacheDirGuard(const CacheDirGuard&) = delete;
+    CacheDirGuard& operator=(const CacheDirGuard&) = delete;
+    CacheDirGuard(CacheDirGuard&&) = delete;
+    CacheDirGuard& operator=(CacheDirGuard&&) = delete;
 
     std::filesystem::path dir;
 };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/render/test_filter_chain_retarget_contract.cpp` around lines 142 - 151,
CacheDirGuard currently allows implicit copying/moving which can cause
double-deletion of the filesystem path; make CacheDirGuard non-copyable and
non-movable by explicitly deleting the copy constructor, copy assignment, move
constructor and move assignment (e.g. declare CacheDirGuard(const
CacheDirGuard&) = delete; CacheDirGuard& operator=(const CacheDirGuard&) =
delete; CacheDirGuard(CacheDirGuard&&) = delete; CacheDirGuard&
operator=(CacheDirGuard&&) = delete;) while keeping the explicit
CacheDirGuard(std::filesystem::path) constructor and the destructor as-is so the
RAII semantics are preserved for dir.
src/render/chain/include/goggles/filter_chain/filter_controls.hpp (1)

26-34: Consider using default case for cleaner switch handling.

The fallback return after the switch block handles invalid enum values but may trigger compiler warnings about control reaching end of non-void function in some configurations.

♻️ Proposed fix
 [[nodiscard]] inline auto to_string(FilterControlStage stage) -> const char* {
     switch (stage) {
     case FilterControlStage::prechain:
         return "prechain";
     case FilterControlStage::effect:
         return "effect";
+    default:
+        return "effect";
     }
-    return "effect";
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/render/chain/include/goggles/filter_chain/filter_controls.hpp` around
lines 26 - 34, The to_string(FilterControlStage stage) function currently ends
with a fallback return after the switch which can trigger warnings; replace that
fallback with a default case inside the switch (e.g., default: return "effect";)
so all control paths are covered, or alternatively add a default that
logs/asserts on an invalid FilterControlStage before returning "effect",
updating the switch in to_string to handle unexpected enum values directly.
src/util/error.hpp (1)

3-3: Use angle-bracket includes for consistency with the rest of the codebase.

The relative path is fragile and inconsistent. CMake already configures ${CMAKE_SOURCE_DIR}/src/render/chain/include as a public include directory for goggles_util, which enables angle-bracket includes like <goggles/filter_chain/result.hpp>. Files in src/render/ consistently use this style; src/util/ should do the same.

♻️ Proposed fix
 `#pragma` once
 
-#include "../render/chain/include/goggles/filter_chain/result.hpp"
+#include <goggles/filter_chain/result.hpp>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/util/error.hpp` at line 3, Replace the fragile relative include in
src/util/error.hpp with the project-standard angle-bracket include;
specifically, change the current relative include of
"../render/chain/include/goggles/filter_chain/result.hpp" to use
<goggles/filter_chain/result.hpp> so it relies on the CMake public include
directory and matches other files in src/render/.
src/render/chain/api/c/goggles_filter_chain.cpp (1)

277-285: Consider adding default case annotation.

The to_api_stage function handles all FilterControlStage enum values, but the fallback return GOGGLES_CHAIN_STAGE_EFFECT at line 284 is unreachable for a well-formed enum. Consider using [[unreachable]] (C++23) or a comment/assert to clarify intent and help the compiler optimize.

Optional improvement
 auto to_api_stage(FilterControlStage stage) -> goggles_chain_stage_t {
     switch (stage) {
     case FilterControlStage::prechain:
         return GOGGLES_CHAIN_STAGE_PRECHAIN;
     case FilterControlStage::effect:
         return GOGGLES_CHAIN_STAGE_EFFECT;
     }
-    return GOGGLES_CHAIN_STAGE_EFFECT;
+    // Unreachable: all enum cases handled above
+    GOGGLES_ASSERT(false, "Unknown FilterControlStage value");
+    return GOGGLES_CHAIN_STAGE_EFFECT; // Suppress warning
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/render/chain/api/c/goggles_filter_chain.cpp` around lines 277 - 285, The
fallback return in to_api_stage(FilterControlStage stage) is intended to be
unreachable; replace the bare fallback with an explicit unreachable annotation
or assert to make intent clear and aid optimization: in function to_api_stage,
after handling FilterControlStage::prechain and ::effect, either add a default
case that calls std::unreachable() (or [[unreachable]] if available) or add an
assertion like assert(false && "Unhandled FilterControlStage") before the final
return, ensuring the compiler and readers know the code path is impossible.
cmake/GogglesFilterChainProvider.cmake (2)

174-179: Use list length check instead of string comparison.

CMake lists that are empty result in an empty string, but comparing with STREQUAL "" can be fragile. Using list(LENGTH) is more idiomatic for CMake.

♻️ Proposed fix
     _goggles_filter_chain_candidate_targets(candidate_targets)
-    if(candidate_targets STREQUAL "")
+    list(LENGTH candidate_targets candidate_count)
+    if(candidate_count EQUAL 0)
         message(FATAL_ERROR
             "Filter-chain provider '${provider_mode}' did not expose a recognizable target. "
             "Expected goggles-filter-chain or a namespaced equivalent.")
     endif()
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cmake/GogglesFilterChainProvider.cmake` around lines 174 - 179, The
empty-list check for candidate_targets using "STREQUAL \"\"" is fragile; update
the logic in the _goggles_filter_chain_candidate_targets invocation to use
CMake's list length check: call list(LENGTH candidate_targets <var>) and test if
that length EQUAL 0, then call message(FATAL_ERROR ...) with the same
provider_mode message; ensure you reference the existing variable name
candidate_targets and provider_mode and replace the STREQUAL "" branch with the
list(LENGTH) check.

42-50: The BUILDSYSTEM_TARGETS check is redundant for subdirectory targets.

The if(TARGET) check at lines 35-40 already captures targets from any scope (including subdirectories added via add_subdirectory at line 167-169) using the known candidate names. The BUILDSYSTEM_TARGETS property at line 42 only contains targets created directly in the current directory—it excludes targets from subdirectories, making the regex pattern at line 44 unlikely to find additional targets beyond what the first block already found.

Consider whether the second block adds value or can be removed, since the candidate list in lines 25-33 already covers the expected naming patterns.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cmake/GogglesFilterChainProvider.cmake` around lines 42 - 50, Remove the
redundant BUILDSYSTEM_TARGETS fallback block: delete the
get_property(global_targets DIRECTORY PROPERTY BUILDSYSTEM_TARGETS) ...
list(REMOVE_DUPLICATES found_targets) set(${out_targets} "${found_targets}"
PARENT_SCOPE) sequence, since the earlier if(TARGET ...) candidate checks
already find targets (including those from add_subdirectory) and the
BUILDSYSTEM_TARGETS property only contains targets created in the current
directory; keep the existing found_targets accumulation and final out_targets
export produced by the first block (the if(TARGET) logic) so behavior is
unchanged.
cmake/GogglesFilterChainConfig.cmake.in (1)

9-17: CONDA_PREFIX dependency may limit portability.

The stb_image fallback requires CONDA_PREFIX to be set, which restricts package consumption to conda environments. If broader package distribution is planned, consider:

  1. Using find_path with standard search paths as a fallback
  2. Documenting this requirement in the package README

For monorepo rehearsal purposes as stated in the design doc, this is acceptable, but flagging for awareness if standalone package readiness becomes a goal.

💡 Suggested alternative for broader portability
 if(NOT TARGET stb_image)
-    if(NOT DEFINED ENV{CONDA_PREFIX})
-        message(FATAL_ERROR "CONDA_PREFIX must be set to consume goggles-filter-chain")
-    endif()
+    find_path(STB_IMAGE_INCLUDE_DIR
+        NAMES stb_image.h
+        PATHS
+            "$ENV{CONDA_PREFIX}/include/stb"
+            "/usr/include/stb"
+            "/usr/local/include/stb"
+    )
+    if(NOT STB_IMAGE_INCLUDE_DIR)
+        message(FATAL_ERROR "stb_image.h not found. Set STB_IMAGE_INCLUDE_DIR or ensure stb is installed.")
+    endif()
     add_library(stb_image INTERFACE IMPORTED)
     set_target_properties(stb_image PROPERTIES
-        INTERFACE_INCLUDE_DIRECTORIES "$ENV{CONDA_PREFIX}/include/stb"
+        INTERFACE_INCLUDE_DIRECTORIES "${STB_IMAGE_INCLUDE_DIR}"
     )
 endif()
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cmake/GogglesFilterChainConfig.cmake.in` around lines 9 - 17, The current
fallback that requires ENV{CONDA_PREFIX} for the stb_image INTERFACE target
(add_library(stb_image INTERFACE IMPORTED) + set_target_properties(...
INTERFACE_INCLUDE_DIRECTORIES "$ENV{CONDA_PREFIX}/include/stb")) limits
portability; change the CMake logic to first try a generic find_path for the stb
headers (use find_path to locate e.g. "stb_image.h" with common system and
project include locations), and only if find_path fails fall back to using
ENV{CONDA_PREFIX} to set INTERFACE_INCLUDE_DIRECTORIES, keeping the
add_library(stb_image INTERFACE IMPORTED) and set_target_properties usage; also
add/update README to document the CONDA_PREFIX fallback requirement so consumers
know about the environment dependency.
tests/render/test_filter_boundary_contracts.cpp (1)

22-28: Consider extracting read_text_file to a shared test utility.

This helper function is duplicated identically in test_vulkan_backend_subsystem_contracts.cpp (lines 20-26) and test_filter_chain.cpp (lines 74-80). Extracting it to a shared test utility header would reduce duplication.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/render/test_filter_boundary_contracts.cpp` around lines 22 - 28, The
helper function read_text_file is duplicated across multiple tests; extract it
into a shared test utility header (declare and implement the function once,
either inline in the header or in a small test-only source) and replace the
duplicate copies in test_filter_boundary_contracts.cpp,
test_vulkan_backend_subsystem_contracts.cpp and test_filter_chain.cpp with an
`#include` of that shared header and calls to read_text_file; ensure the signature
and behavior (returns std::optional<std::string>) remain identical and update
includes (e.g., <fstream>, <filesystem>, <optional>, <string>) in the shared
header so test files don’t need duplicated includes.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@CMakePresets.json`:
- Around line 222-232: The build preset "smoke-provider-package-stage" sets
CMAKE_INSTALL_PREFIX but doesn't run the install step, so running cmake --build
--preset smoke-provider-package-stage won't populate the install tree; update
the "smoke-provider-package-stage" build preset to include the install target
(e.g. add "targets": ["install"] or equivalent) so the configured
CMAKE_INSTALL_PREFIX is populated, and apply the same change to the other
analogous preset referenced in the comment (lines 306-309) to ensure both
package-stage presets run install.

In `@src/render/chain/chain_executor.cpp`:
- Around line 231-243: The current logic freezes semantic records on first sight
by using resources.m_recorded_semantic_keys (semantic_record_key(pass_ordinal,
member.name)) to gate session.record_semantic, which causes stale live-derived
values (e.g., semantic_value fields like FrameCount and alias-backed *Size
members) to never update after the first frame/resize; instead, either move the
presence check to only suppress duplicate warnings (i.e., keep the
first-observation gate for warning emission but always call
session.record_semantic), or implement value-based change detection: store the
last recorded semantic_value per (pass_ordinal, member.name) and compare the
current semantic_value (including FrameCount and size fields) before calling
session.record_semantic only when it has changed; update
resources.m_recorded_semantic_keys usage accordingly and ensure any alias-backed
size fields are evaluated after bindings are prepared before
comparing/recording.

In `@src/render/chain/include/goggles/filter_chain/error.hpp`:
- Line 8: The header declares types under namespace goggles but lives in
goggles/filter_chain, so update the namespace to goggles::filter_chain for the
public types (Error, ErrorCode, and any related declarations) to match the
directory boundary; change the opening namespace declaration from namespace
goggles to namespace goggles::filter_chain and update the matching closing
comment/brace accordingly so symbols are emitted as goggles::filter_chain::Error
and goggles::filter_chain::ErrorCode.
- Line 4: The public header uses std::source_location (requires C++20) but the
installed CMake config template doesn't enforce C++20 for consumers; update the
GogglesFilterChainConfig.cmake.in template so that both the exported target
(GogglesFilterChain::goggles-filter-chain) and the imported legacy target
(goggles-filter-chain) get INTERFACE_COMPILE_FEATURES "cxx_std_20" added when
the targets are defined/installed, ensuring consumers compile with C++20
compatibility for symbols like std::source_location used in
src/render/chain/include/goggles/filter_chain/error.hpp.

In `@src/render/chain/include/goggles/filter_chain/result.hpp`:
- Around line 5-9: The file is missing the <source_location> header used by
std::source_location in the factory functions make_error and
make_result_ptr_error; fix by adding an `#include` <source_location> to the
includes at the top of result.hpp so the symbols used by make_error and
make_result_ptr_error compile correctly with all compilers.

In `@src/render/shader/CMakeLists.txt`:
- Around line 25-28: The compile definitions for the target are currently
applied as PRIVATE which causes downstream consumers including slang_reflect.hpp
(exposed via target_include_directories(PUBLIC)) to be built without
VULKAN_HPP_NO_EXCEPTIONS and VULKAN_HPP_DISPATCH_LOADER_DYNAMIC=1, risking
ODR/ABI mismatches; change the call to
target_compile_definitions(goggles_render_shader_obj ...) so the definitions
VULKAN_HPP_NO_EXCEPTIONS and VULKAN_HPP_DISPATCH_LOADER_DYNAMIC=1 are PUBLIC
(not PRIVATE) so that consumers including <vulkan/vulkan.hpp> get the same macro
settings as the library.

In `@src/util/CMakeLists.txt`:
- Line 36: The util CMakeLists should not include render/chain headers because
Error, ErrorCode, and Result<T> are causing a circular dependency; extract those
types into a shared utility module (e.g., goggles_types or move them directly
into util) and update CMake and includes accordingly: create a new
target/library that defines Error, ErrorCode, and Result<T> (or move their
definitions into util headers referenced by util/config.hpp), change
render/chain and util to depend on that new target instead of render/chain, and
remove `${CMAKE_SOURCE_DIR}/src/render/chain/include` from util's include list
so the circular link is eliminated.

In `@src/util/config.hpp`:
- Line 3: The include in src/util/config.hpp currently uses a repo-relative path
to scale_mode.hpp
("../render/chain/include/goggles/filter_chain/scale_mode.hpp"); change it to
the public filter-chain include (e.g. use the stable public header path such as
"goggles/filter_chain/scale_mode.hpp" or "filter_chain/scale_mode.hpp" depending
on the project's public include root) so the header is resolved via the
installed/public include contract; make the equivalent change in
src/util/error.hpp where the same repo-relative include appears so both files
consume the public filter-chain header instead of hardcoded source-tree paths.

In `@tests/CMakeLists.txt`:
- Around line 144-146: The test target goggles_filter_chain_contract_tests is
being registered twice (via add_test and via catch_discover_tests), causing
duplicate runs and lost ASAN/label properties; fix by removing or guarding the
add_test registration when Catch.cmake discovery is used (i.e., only call
add_test if catch_discover_tests is not available) OR remove add_test and
instead pass the ASAN and label properties to catch_discover_tests using its
PROPERTIES argument so the discovered individual cases inherit the same
properties (apply to the goggles_filter_chain_contract_tests target and the
existing ASAN/label property names used elsewhere).

In `@tests/render/test_filter_chain_retarget_contract.cpp`:
- Around line 90-99: Remove the redundant assignment of
features13.dynamicRendering after the feature check and correctly build the
pNext chain so the base features struct is included: set features2.pNext to
&features13 and then assign device_info.pNext = &features2 (instead of
&features13) so the chain begins with features2 and continues to features13;
keep the existing check that returns if features13.dynamicRendering != VK_TRUE
but do not reassign it.

---

Outside diff comments:
In `@src/util/CMakeLists.txt`:
- Around line 28-33: When GOGGLES_CHAIN_LIBRARY_TYPE is SHARED you only set
POSITION_INDEPENDENT_CODE on target goggles_util but not on the OBJECT library
goggles_util_logging_obj (whose objects are embedded into the final library),
causing link failures; fix by ensuring POSITION_INDEPENDENT_CODE is set for
goggles_util_logging_obj as well when GOGGLES_CHAIN_LIBRARY_TYPE_NORMALIZED
STREQUAL "SHARED" or move the entire PIC block that checks
GOGGLES_CHAIN_LIBRARY_TYPE before the OBJECT library definition so it applies to
both targets (ensure you reference the symbols GOGGLES_CHAIN_LIBRARY_TYPE,
goggles_util, goggles_util_logging_obj, and the POSITION_INDEPENDENT_CODE
property).

---

Nitpick comments:
In `@cmake/GogglesFilterChainConfig.cmake.in`:
- Around line 9-17: The current fallback that requires ENV{CONDA_PREFIX} for the
stb_image INTERFACE target (add_library(stb_image INTERFACE IMPORTED) +
set_target_properties(... INTERFACE_INCLUDE_DIRECTORIES
"$ENV{CONDA_PREFIX}/include/stb")) limits portability; change the CMake logic to
first try a generic find_path for the stb headers (use find_path to locate e.g.
"stb_image.h" with common system and project include locations), and only if
find_path fails fall back to using ENV{CONDA_PREFIX} to set
INTERFACE_INCLUDE_DIRECTORIES, keeping the add_library(stb_image INTERFACE
IMPORTED) and set_target_properties usage; also add/update README to document
the CONDA_PREFIX fallback requirement so consumers know about the environment
dependency.

In `@cmake/GogglesFilterChainProvider.cmake`:
- Around line 174-179: The empty-list check for candidate_targets using
"STREQUAL \"\"" is fragile; update the logic in the
_goggles_filter_chain_candidate_targets invocation to use CMake's list length
check: call list(LENGTH candidate_targets <var>) and test if that length EQUAL
0, then call message(FATAL_ERROR ...) with the same provider_mode message;
ensure you reference the existing variable name candidate_targets and
provider_mode and replace the STREQUAL "" branch with the list(LENGTH) check.
- Around line 42-50: Remove the redundant BUILDSYSTEM_TARGETS fallback block:
delete the get_property(global_targets DIRECTORY PROPERTY BUILDSYSTEM_TARGETS)
... list(REMOVE_DUPLICATES found_targets) set(${out_targets} "${found_targets}"
PARENT_SCOPE) sequence, since the earlier if(TARGET ...) candidate checks
already find targets (including those from add_subdirectory) and the
BUILDSYSTEM_TARGETS property only contains targets created in the current
directory; keep the existing found_targets accumulation and final out_targets
export produced by the first block (the if(TARGET) logic) so behavior is
unchanged.

In `@openspec/specs/filter-chain-c-api/spec.md`:
- Around line 94-104: Scenario blocks use plain keywords like "- GIVEN" instead
of the spec's established bold format; update each scenario line in the new
blocks to use bolded keywords (e.g., change "- GIVEN" to "- **GIVEN**", "- WHEN"
to "- **WHEN**", "- THEN" to "- **THEN**") so the blocks for "Retarget preserves
source-independent runtime state" and the subsequent related scenarios match the
existing convention; apply the same replacement to the other new scenario lines
mentioned in the comment (the blocks around lines 100-104, 112-117, 119-125) to
keep formatting consistent.

In `@src/render/chain/api/c/goggles_filter_chain.cpp`:
- Around line 277-285: The fallback return in to_api_stage(FilterControlStage
stage) is intended to be unreachable; replace the bare fallback with an explicit
unreachable annotation or assert to make intent clear and aid optimization: in
function to_api_stage, after handling FilterControlStage::prechain and ::effect,
either add a default case that calls std::unreachable() (or [[unreachable]] if
available) or add an assertion like assert(false && "Unhandled
FilterControlStage") before the final return, ensuring the compiler and readers
know the code path is impossible.

In `@src/render/chain/include/goggles/filter_chain/filter_controls.hpp`:
- Around line 26-34: The to_string(FilterControlStage stage) function currently
ends with a fallback return after the switch which can trigger warnings; replace
that fallback with a default case inside the switch (e.g., default: return
"effect";) so all control paths are covered, or alternatively add a default that
logs/asserts on an invalid FilterControlStage before returning "effect",
updating the switch in to_string to handle unexpected enum values directly.

In `@src/render/chain/include/goggles/filter_chain/scale_mode.hpp`:
- Around line 15-29: The to_string(ScaleMode) switch should include an explicit
default branch (instead of falling through after the switch) to silient compiler
warnings and make intent clear: add a default case that returns "unknown" and,
for safety, add an unreachable hint (e.g., call
std::unreachable()/__builtin_unreachable() or assert(false)) after the return
path so the compiler knows all enum cases are handled; update the to_string
function's switch to include this default and the unreachable hint.

In `@src/util/CMakeLists.txt`:
- Around line 8-11: The target_include_directories call on the OBJECT library
goggles_util_logging_obj uses PUBLIC which has no effect for OBJECT targets and
is misleading; change the scope to PRIVATE (or remove the scope) so readers
aren't misled and ensure the actual propagation is handled on the consuming
target goggles_util (which uses $<TARGET_OBJECTS:goggles_util_logging_obj>);
update the target_include_directories(goggles_util_logging_obj ...) invocation
to use PRIVATE to clarify intent.

In `@src/util/error.hpp`:
- Line 3: Replace the fragile relative include in src/util/error.hpp with the
project-standard angle-bracket include; specifically, change the current
relative include of "../render/chain/include/goggles/filter_chain/result.hpp" to
use <goggles/filter_chain/result.hpp> so it relies on the CMake public include
directory and matches other files in src/render/.

In `@tests/render/test_filter_boundary_contracts.cpp`:
- Around line 22-28: The helper function read_text_file is duplicated across
multiple tests; extract it into a shared test utility header (declare and
implement the function once, either inline in the header or in a small test-only
source) and replace the duplicate copies in test_filter_boundary_contracts.cpp,
test_vulkan_backend_subsystem_contracts.cpp and test_filter_chain.cpp with an
`#include` of that shared header and calls to read_text_file; ensure the signature
and behavior (returns std::optional<std::string>) remain identical and update
includes (e.g., <fstream>, <filesystem>, <optional>, <string>) in the shared
header so test files don’t need duplicated includes.

In `@tests/render/test_filter_chain_retarget_contract.cpp`:
- Around line 142-151: CacheDirGuard currently allows implicit copying/moving
which can cause double-deletion of the filesystem path; make CacheDirGuard
non-copyable and non-movable by explicitly deleting the copy constructor, copy
assignment, move constructor and move assignment (e.g. declare
CacheDirGuard(const CacheDirGuard&) = delete; CacheDirGuard& operator=(const
CacheDirGuard&) = delete; CacheDirGuard(CacheDirGuard&&) = delete;
CacheDirGuard& operator=(CacheDirGuard&&) = delete;) while keeping the explicit
CacheDirGuard(std::filesystem::path) constructor and the destructor as-is so the
RAII semantics are preserved for dir.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 823c7b1d-4ff8-42a6-a89d-9a2499c6b039

📥 Commits

Reviewing files that changed from the base of the PR and between ea86e0c and b0cf218.

📒 Files selected for processing (73)
  • CMakePresets.json
  • cmake/GogglesFilterChainConfig.cmake.in
  • cmake/GogglesFilterChainProvider.cmake
  • openspec/changes/archive/2026-03-13-normalize-filter-chain-consumer-boundary/design.md
  • openspec/changes/archive/2026-03-13-normalize-filter-chain-consumer-boundary/proposal.md
  • openspec/changes/archive/2026-03-13-normalize-filter-chain-consumer-boundary/specs/build-system/spec.md
  • openspec/changes/archive/2026-03-13-normalize-filter-chain-consumer-boundary/specs/filter-chain-c-api/spec.md
  • openspec/changes/archive/2026-03-13-normalize-filter-chain-consumer-boundary/specs/filter-chain-cpp-wrapper/spec.md
  • openspec/changes/archive/2026-03-13-normalize-filter-chain-consumer-boundary/specs/goggles-filter-chain/spec.md
  • openspec/changes/archive/2026-03-13-normalize-filter-chain-consumer-boundary/specs/render-pipeline/spec.md
  • openspec/changes/archive/2026-03-13-normalize-filter-chain-consumer-boundary/tasks.md
  • openspec/specs/build-system/spec.md
  • openspec/specs/filter-chain-c-api/spec.md
  • openspec/specs/filter-chain-cpp-wrapper/spec.md
  • openspec/specs/goggles-filter-chain/spec.md
  • openspec/specs/render-pipeline/spec.md
  • pixi.toml
  • scripts/task/help.sh
  • scripts/task/rehearse-filter-chain-provider.sh
  • src/render/CMakeLists.txt
  • src/render/backend/CMakeLists.txt
  • src/render/backend/external_frame_importer.hpp
  • src/render/backend/filter_chain_controller.cpp
  • src/render/backend/filter_chain_controller.hpp
  • src/render/backend/render_output.hpp
  • src/render/backend/vulkan_backend.cpp
  • src/render/backend/vulkan_backend.hpp
  • src/render/backend/vulkan_context.hpp
  • src/render/backend/vulkan_debug.hpp
  • src/render/backend/vulkan_error.hpp
  • src/render/chain/CMakeLists.txt
  • src/render/chain/api/c/goggles_filter_chain.cpp
  • src/render/chain/api/c/goggles_filter_chain.h
  • src/render/chain/api/cpp/goggles_filter_chain.hpp
  • src/render/chain/chain_builder.cpp
  • src/render/chain/chain_builder.hpp
  • src/render/chain/chain_executor.cpp
  • src/render/chain/chain_executor.hpp
  • src/render/chain/chain_resources.cpp
  • src/render/chain/chain_resources.hpp
  • src/render/chain/chain_runtime.cpp
  • src/render/chain/chain_runtime.hpp
  • src/render/chain/filter_controls.hpp
  • src/render/chain/filter_pass.hpp
  • src/render/chain/framebuffer.hpp
  • src/render/chain/include/goggles/filter_chain/diagnostics/compile_report.hpp
  • src/render/chain/include/goggles/filter_chain/diagnostics/diagnostic_event.hpp
  • src/render/chain/include/goggles/filter_chain/diagnostics/diagnostic_policy.hpp
  • src/render/chain/include/goggles/filter_chain/diagnostics/diagnostic_session.hpp
  • src/render/chain/include/goggles/filter_chain/diagnostics/diagnostic_sink.hpp
  • src/render/chain/include/goggles/filter_chain/diagnostics/gpu_timestamp_pool.hpp
  • src/render/chain/include/goggles/filter_chain/diagnostics/log_sink.hpp
  • src/render/chain/include/goggles/filter_chain/diagnostics/source_provenance.hpp
  • src/render/chain/include/goggles/filter_chain/error.hpp
  • src/render/chain/include/goggles/filter_chain/filter_controls.hpp
  • src/render/chain/include/goggles/filter_chain/result.hpp
  • src/render/chain/include/goggles/filter_chain/scale_mode.hpp
  • src/render/chain/include/goggles/filter_chain/vulkan_context.hpp
  • src/render/chain/pass.hpp
  • src/render/chain/preset_parser.hpp
  • src/render/chain/vulkan_context.hpp
  • src/render/chain/vulkan_result.hpp
  • src/render/shader/CMakeLists.txt
  • src/render/shader/retroarch_preprocessor.hpp
  • src/render/shader/shader_runtime.hpp
  • src/render/texture/CMakeLists.txt
  • src/util/CMakeLists.txt
  • src/util/config.hpp
  • src/util/error.hpp
  • tests/CMakeLists.txt
  • tests/render/test_filter_boundary_contracts.cpp
  • tests/render/test_filter_chain_retarget_contract.cpp
  • tests/render/test_vulkan_backend_subsystem_contracts.cpp

Comment on lines +222 to +232
"name": "smoke-provider-package-stage",
"displayName": "Provider rehearsal package stage",
"description": "Build and install the filter-chain package for provider rehearsal",
"inherits": [
".debug",
".test"
],
"cacheVariables": {
"GOGGLES_FILTER_CHAIN_PROVIDER": "in-tree",
"CMAKE_INSTALL_PREFIX": "${sourceDir}/build/smoke-provider-package-root"
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, let's get the size and content of CMakePresets.json
wc -l CMakePresets.json

Repository: goggles-dev/Goggles

Length of output: 84


🏁 Script executed:

# Read the section around lines 222-232 to see the configure preset
sed -n '210,240p' CMakePresets.json

Repository: goggles-dev/Goggles

Length of output: 1092


🏁 Script executed:

# Also check the build preset and lines 306-309 for the "Also applies to" reference
sed -n '290,320p' CMakePresets.json

Repository: goggles-dev/Goggles

Length of output: 898


🏁 Script executed:

# Let's also see the overall structure of the file to understand the preset organization
grep -n '"name"' CMakePresets.json | grep -E 'smoke-provider|provider-package'

Repository: goggles-dev/Goggles

Length of output: 453


🏁 Script executed:

# Get more complete picture of the build preset structure
sed -n '305,315p' CMakePresets.json

Repository: goggles-dev/Goggles

Length of output: 328


🏁 Script executed:

# Check if there are any test presets that might build or install
sed -n '340,360p' CMakePresets.json

Repository: goggles-dev/Goggles

Length of output: 773


Add install target to the package-stage build preset.

The smoke-provider-package-stage configure preset sets CMAKE_INSTALL_PREFIX, but the build preset only builds the default target without running install. Running cmake --build --preset smoke-provider-package-stage will not populate build/smoke-provider-package-root, leaving nothing for the package-consumer preset to resolve.

Fix
    {
      "name": "smoke-provider-package-stage",
-      "configurePreset": "smoke-provider-package-stage"
+      "configurePreset": "smoke-provider-package-stage",
+      "targets": ["install"]
    },

Also applies to: 306-309

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@CMakePresets.json` around lines 222 - 232, The build preset
"smoke-provider-package-stage" sets CMAKE_INSTALL_PREFIX but doesn't run the
install step, so running cmake --build --preset smoke-provider-package-stage
won't populate the install tree; update the "smoke-provider-package-stage" build
preset to include the install target (e.g. add "targets": ["install"] or
equivalent) so the configured CMAKE_INSTALL_PREFIX is populated, and apply the
same change to the other analogous preset referenced in the comment (lines
306-309) to ensure both package-stage presets run install.

Comment on lines +231 to +243
const auto semantic_key = semantic_record_key(pass_ordinal, member.name);
const bool first_observation =
resources.m_recorded_semantic_keys.insert(semantic_key).second;

if (first_observation) {
session.record_semantic({
.pass_ordinal = pass_ordinal,
.member_name = member.name,
.classification = classification,
.value = value,
.offset = static_cast<uint32_t>(member.offset),
});
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Don't freeze semantic records on first sight.

session.record_semantic() now runs only once per pass/member, but semantic_value() carries live data (FrameCount, size semantics) and some alias-backed *Size members are only correct after bindings are prepared. After the first frame, or after a resize, the semantic ledger will keep stale values forever. If you only want to suppress duplicate warnings, keep the first-observation gate on the warning path, or switch this to value-based change detection instead of a presence set.

Minimal correctness fix
-        if (first_observation) {
-            session.record_semantic({
-                .pass_ordinal = pass_ordinal,
-                .member_name = member.name,
-                .classification = classification,
-                .value = value,
-                .offset = static_cast<uint32_t>(member.offset),
-            });
-        }
+        session.record_semantic({
+            .pass_ordinal = pass_ordinal,
+            .member_name = member.name,
+            .classification = classification,
+            .value = value,
+            .offset = static_cast<uint32_t>(member.offset),
+        });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const auto semantic_key = semantic_record_key(pass_ordinal, member.name);
const bool first_observation =
resources.m_recorded_semantic_keys.insert(semantic_key).second;
if (first_observation) {
session.record_semantic({
.pass_ordinal = pass_ordinal,
.member_name = member.name,
.classification = classification,
.value = value,
.offset = static_cast<uint32_t>(member.offset),
});
}
const auto semantic_key = semantic_record_key(pass_ordinal, member.name);
const bool first_observation =
resources.m_recorded_semantic_keys.insert(semantic_key).second;
session.record_semantic({
.pass_ordinal = pass_ordinal,
.member_name = member.name,
.classification = classification,
.value = value,
.offset = static_cast<uint32_t>(member.offset),
});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/render/chain/chain_executor.cpp` around lines 231 - 243, The current
logic freezes semantic records on first sight by using
resources.m_recorded_semantic_keys (semantic_record_key(pass_ordinal,
member.name)) to gate session.record_semantic, which causes stale live-derived
values (e.g., semantic_value fields like FrameCount and alias-backed *Size
members) to never update after the first frame/resize; instead, either move the
presence check to only suppress duplicate warnings (i.e., keep the
first-observation gate for warning emission but always call
session.record_semantic), or implement value-based change detection: store the
last recorded semantic_value per (pass_ordinal, member.name) and compare the
current semantic_value (including FrameCount and size fields) before calling
session.record_semantic only when it has changed; update
resources.m_recorded_semantic_keys usage accordingly and ensure any alias-backed
size fields are evaluated after bindings are prepared before
comparing/recording.

#pragma once

#include <cstdint>
#include <source_location>
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== C++ standard declarations in CMake files =="
fd -HI 'CMakeLists\.txt$|\.cmake$' \
  | xargs -r rg -n -C2 'CMAKE_CXX_STANDARD|cxx_std_20|target_compile_features'

echo
echo "== Usages of source_location and this header =="
rg -n -C2 'source_location|goggles/filter_chain/error\.hpp'

Repository: goggles-dev/Goggles

Length of output: 8934


🏁 Script executed:

#!/bin/bash

echo "=== Library target providing the header ==="
rg -n -B5 'goggles/filter_chain/error\.hpp' src/render/CMakeLists.txt | head -20

echo
echo "=== Target that likely exports this header ==="
rg -n -B10 -A10 'add_library.*goggles_render|add_library.*goggles_filter_chain' src/render/CMakeLists.txt

echo
echo "=== Check for PUBLIC include directories and compile features ==="
rg -n -C3 'target_include_directories.*PUBLIC|target_compile_features' src/render/CMakeLists.txt | head -40

echo
echo "=== Package config files (for consumers) ==="
fd -HI '\.cmake$' cmake/ | xargs -r cat | rg -n -C3 'CXX_STANDARD|cxx_std_20'

Repository: goggles-dev/Goggles

Length of output: 1697


🏁 Script executed:

#!/bin/bash

echo "=== Full src/render/CMakeLists.txt (goggles_render target section) ==="
sed -n '1,100p' src/render/CMakeLists.txt

echo
echo "=== Check for CMake package config or install targets ==="
rg -n 'install.*TARGET|export|GogglesConfig|goggles-config' --type cmake

echo
echo "=== Search for target_compile_features on goggles_render ==="
rg -n -C5 'target_compile_features.*goggles_render'

Repository: goggles-dev/Goggles

Length of output: 5095


🏁 Script executed:

#!/bin/bash

echo "=== GogglesFilterChainConfig.cmake.in template ==="
cat cmake/GogglesFilterChainConfig.cmake.in

echo
echo "=== src/render/chain/CMakeLists.txt ==="
cat src/render/chain/CMakeLists.txt

Repository: goggles-dev/Goggles

Length of output: 3126


Add C++20 requirement to exported CMake config for package consumers.

The header uses std::source_location (lines 4, 29, 31-32) which requires C++20. While in-tree builds are protected by CMAKE_CXX_STANDARD 20 in CompilerConfig.cmake, the installed package config (GogglesFilterChainConfig.cmake.in) does not enforce C++20 for consumers.

Add INTERFACE_COMPILE_FEATURES "cxx_std_20" to both the GogglesFilterChain::goggles-filter-chain and goggles-filter-chain imported targets in the config template to ensure package consumers enforce C++20.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/render/chain/include/goggles/filter_chain/error.hpp` at line 4, The
public header uses std::source_location (requires C++20) but the installed CMake
config template doesn't enforce C++20 for consumers; update the
GogglesFilterChainConfig.cmake.in template so that both the exported target
(GogglesFilterChain::goggles-filter-chain) and the imported legacy target
(goggles-filter-chain) get INTERFACE_COMPILE_FEATURES "cxx_std_20" added when
the targets are defined/installed, ensuring consumers compile with C++20
compatibility for symbols like std::source_location used in
src/render/chain/include/goggles/filter_chain/error.hpp.

#include <string>
#include <utility>

namespace goggles {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Namespace does not match the filter_chain public boundary.

Line 8 declares namespace goggles, but this header lives under goggles/filter_chain/.... That mismatch can break call sites expecting goggles::filter_chain::Error/ErrorCode and weakens the provider seam normalization goal.

Suggested fix
-namespace goggles {
+namespace goggles::filter_chain {
@@
-} // namespace goggles
+} // namespace goggles::filter_chain

Also applies to: 68-68

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/render/chain/include/goggles/filter_chain/error.hpp` at line 8, The
header declares types under namespace goggles but lives in goggles/filter_chain,
so update the namespace to goggles::filter_chain for the public types (Error,
ErrorCode, and any related declarations) to match the directory boundary; change
the opening namespace declaration from namespace goggles to namespace
goggles::filter_chain and update the matching closing comment/brace accordingly
so symbols are emitted as goggles::filter_chain::Error and
goggles::filter_chain::ErrorCode.

Comment on lines +5 to +9
#include <cstdio>
#include <cstdlib>
#include <memory>
#include <nonstd/expected.hpp>
#include <utility>
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Missing <source_location> header.

The factory functions make_error and make_result_ptr_error use std::source_location but the header <source_location> is not included. This will cause compilation failures on some compilers.

Proposed fix
 `#include` <cstdio>
 `#include` <cstdlib>
 `#include` <memory>
 `#include` <nonstd/expected.hpp>
+#include <source_location>
 `#include` <utility>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
#include <cstdio>
#include <cstdlib>
#include <memory>
#include <nonstd/expected.hpp>
#include <utility>
`#include` <cstdio>
`#include` <cstdlib>
`#include` <memory>
`#include` <nonstd/expected.hpp>
`#include` <source_location>
`#include` <utility>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/render/chain/include/goggles/filter_chain/result.hpp` around lines 5 - 9,
The file is missing the <source_location> header used by std::source_location in
the factory functions make_error and make_result_ptr_error; fix by adding an
`#include` <source_location> to the includes at the top of result.hpp so the
symbols used by make_error and make_result_ptr_error compile correctly with all
compilers.

Comment on lines +25 to 28
target_compile_definitions(goggles_render_shader_obj PRIVATE
VULKAN_HPP_NO_EXCEPTIONS
VULKAN_HPP_DISPATCH_LOADER_DYNAMIC=1
)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check if public headers in this target include vulkan.hpp or vulkan_hpp variants

echo "=== Public headers in render/shader ==="
fd -e hpp -e h . src/render/shader

echo ""
echo "=== Check for vulkan.hpp includes in those headers ==="
rg -n '#include.*vulkan' src/render/shader --glob '*.hpp' --glob '*.h'

echo ""
echo "=== Also check chain/include headers since they're PUBLIC ==="
rg -n '#include.*vulkan' src/render/chain/include --glob '*.hpp' --glob '*.h' 2>/dev/null || echo "Path may not exist or no matches"

Repository: goggles-dev/Goggles

Length of output: 503


🏁 Script executed:

cat -n src/render/shader/CMakeLists.txt | head -50

Repository: goggles-dev/Goggles

Length of output: 1114


🏁 Script executed:

rg -n 'public_header|install|slang_reflect' src/render/shader/CMakeLists.txt

Repository: goggles-dev/Goggles

Length of output: 86


🏁 Script executed:

rg -n 'PUBLIC' src/render/shader/CMakeLists.txt | head -20

Repository: goggles-dev/Goggles

Length of output: 124


Change compile definitions from PRIVATE to PUBLIC to prevent ODR violations.

slang_reflect.hpp is publicly exposed (via target_include_directories(PUBLIC)) and directly includes <vulkan/vulkan.hpp>. Consumers building against this library will include this header without the VULKAN_HPP_NO_EXCEPTIONS and VULKAN_HPP_DISPATCH_LOADER_DYNAMIC=1 definitions, causing struct layout and dispatch ABI mismatches. These definitions must be PUBLIC.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/render/shader/CMakeLists.txt` around lines 25 - 28, The compile
definitions for the target are currently applied as PRIVATE which causes
downstream consumers including slang_reflect.hpp (exposed via
target_include_directories(PUBLIC)) to be built without VULKAN_HPP_NO_EXCEPTIONS
and VULKAN_HPP_DISPATCH_LOADER_DYNAMIC=1, risking ODR/ABI mismatches; change the
call to target_compile_definitions(goggles_render_shader_obj ...) so the
definitions VULKAN_HPP_NO_EXCEPTIONS and VULKAN_HPP_DISPATCH_LOADER_DYNAMIC=1
are PUBLIC (not PRIVATE) so that consumers including <vulkan/vulkan.hpp> get the
same macro settings as the library.

endif()

target_include_directories(goggles_util PUBLIC
${CMAKE_SOURCE_DIR}/src/render/chain/include
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check what headers from render/chain/include are actually used by util sources

echo "=== Headers in render/chain/include ==="
fd . src/render/chain/include --type f 2>/dev/null || echo "Directory not found"

echo ""
echo "=== Includes from util sources referencing chain headers ==="
rg -n '#include.*goggles/filter_chain' src/util/ 2>/dev/null || echo "No direct includes found"

echo ""
echo "=== Check if render/chain depends on util (potential circular dependency) ==="
rg -l 'goggles_util' src/render/chain/CMakeLists.txt 2>/dev/null || echo "No dependency found in chain CMakeLists"

Repository: goggles-dev/Goggles

Length of output: 1388


🏁 Script executed:

#!/bin/bash
# Check what symbols from render/chain are actually used in util

echo "=== Content of util/error.hpp to see result.hpp usage ==="
cat -n src/util/error.hpp | head -30

echo ""
echo "=== Content of util/config.hpp to see scale_mode.hpp usage ==="
cat -n src/util/config.hpp | head -30

echo ""
echo "=== render/chain CMakeLists.txt to confirm util dependency ==="
rg -n 'goggles_util' src/render/chain/CMakeLists.txt -A 2 -B 2

Repository: goggles-dev/Goggles

Length of output: 1356


🏁 Script executed:

#!/bin/bash
# Get full content of util headers and CMakeLists.txt context

echo "=== Full util/error.hpp ==="
cat -n src/util/error.hpp

echo ""
echo "=== render/chain/include/goggles/filter_chain/result.hpp ==="
head -40 src/render/chain/include/goggles/filter_chain/result.hpp

echo ""
echo "=== render/chain/include/goggles/filter_chain/scale_mode.hpp ==="
cat -n src/render/chain/include/goggles/filter_chain/scale_mode.hpp

echo ""
echo "=== CMakeLists.txt context around line 36 ==="
sed -n '30,45p' src/util/CMakeLists.txt

Repository: goggles-dev/Goggles

Length of output: 2701


🏁 Script executed:

#!/bin/bash
# Check if the includes are actually used (dead imports?) and verify full circular dependency

echo "=== Check where Error type is defined ==="
rg 'class Error\|struct Error' --type-list

echo ""
echo "=== Check if util code actually uses Result<T> from render/chain ==="
grep -n 'Result<' src/util/error.hpp src/util/config.hpp || echo "No Result<T> usage found"

echo ""
echo "=== Check render/chain/error.hpp ==="
head -30 src/render/chain/include/goggles/filter_chain/error.hpp

echo ""
echo "=== Verify full path: does util/config.hpp actually use ScaleMode? ==="
grep -n 'ScaleMode' src/util/config.hpp || echo "No ScaleMode usage found"

Repository: goggles-dev/Goggles

Length of output: 1375


🏁 Script executed:

#!/bin/bash
# Verify the exact CMakeLists.txt line 36 that's mentioned in the review

echo "=== Full util/CMakeLists.txt to see target structure ==="
cat -n src/util/CMakeLists.txt

echo ""
echo "=== Check if Error/ErrorCode are defined in util or render/chain ==="
rg 'enum class ErrorCode\|struct Error' src/util/ src/render/chain/include/

Repository: goggles-dev/Goggles

Length of output: 1986


🏁 Script executed:

#!/bin/bash
# Check if Error/ErrorCode are defined elsewhere or only in render/chain

echo "=== Check util directory structure ==="
find src/util -type f -name "*.hpp" -o -name "*.cpp" | sort

echo ""
echo "=== Search for Error/ErrorCode definitions in entire codebase ==="
rg 'enum class ErrorCode' --type-list 2>/dev/null
rg 'enum class ErrorCode'
rg 'struct Error \{'

Repository: goggles-dev/Goggles

Length of output: 1582


Resolve circular dependency: util module should not depend on render/chain headers.

Adding ${CMAKE_SOURCE_DIR}/src/render/chain/include to util creates a circular dependency—util includes Error/ErrorCode and Result types from render/chain, while render/chain links against goggles_util. This inverts standard layering where render components depend on utilities, not vice versa.

The root issue is that Error and ErrorCode are defined in render/chain but are fundamental infrastructure types needed by util (see util/config.hpp using Result). Extract Error, ErrorCode, and Result to a dedicated utility types module (or move them to util directly) that both util and render/chain can depend on without circularity.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/util/CMakeLists.txt` at line 36, The util CMakeLists should not include
render/chain headers because Error, ErrorCode, and Result<T> are causing a
circular dependency; extract those types into a shared utility module (e.g.,
goggles_types or move them directly into util) and update CMake and includes
accordingly: create a new target/library that defines Error, ErrorCode, and
Result<T> (or move their definitions into util headers referenced by
util/config.hpp), change render/chain and util to depend on that new target
instead of render/chain, and remove
`${CMAKE_SOURCE_DIR}/src/render/chain/include` from util's include list so the
circular link is eliminated.

@@ -1,5 +1,6 @@
#pragma once

#include "../render/chain/include/goggles/filter_chain/scale_mode.hpp"
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify there are no remaining source-tree-relative includes to filter_chain public headers.
rg -nP '#include\s+"(\.\./)+render/chain/include/goggles/filter_chain/[^"]+"' src tests
# Expected: no matches

Repository: goggles-dev/Goggles

Length of output: 243


Use the public filter-chain include path, not a repo-relative path.

Line 3 hardcodes a source-tree layout and can fail in package/provider modes. This should consume the stable public header contract instead. The same issue exists in src/util/error.hpp:3.

Suggested fix
-#include "../render/chain/include/goggles/filter_chain/scale_mode.hpp"
+#include <goggles/filter_chain/scale_mode.hpp>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
#include "../render/chain/include/goggles/filter_chain/scale_mode.hpp"
`#include` <goggles/filter_chain/scale_mode.hpp>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/util/config.hpp` at line 3, The include in src/util/config.hpp currently
uses a repo-relative path to scale_mode.hpp
("../render/chain/include/goggles/filter_chain/scale_mode.hpp"); change it to
the public filter-chain include (e.g. use the stable public header path such as
"goggles/filter_chain/scale_mode.hpp" or "filter_chain/scale_mode.hpp" depending
on the project's public include root) so the header is resolved via the
installed/public include contract; make the equivalent change in
src/util/error.hpp where the same repo-relative include appears so both files
consume the public filter-chain header instead of hardcoded source-tree paths.

Comment on lines +144 to +146
if(TARGET goggles_filter_chain_contract_tests)
add_test(NAME goggles_filter_chain_contract_tests COMMAND goggles_filter_chain_contract_tests)
endif()
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# First, find and read the CMakeLists.txt file
find . -path "*/tests/CMakeLists.txt" -type f

Repository: goggles-dev/Goggles

Length of output: 85


🏁 Script executed:

# Get the file size first
wc -l tests/CMakeLists.txt

Repository: goggles-dev/Goggles

Length of output: 87


🏁 Script executed:

# Read the relevant sections of the file
# Lines 144-146 and surrounding context
sed -n '130,160p' tests/CMakeLists.txt

Repository: goggles-dev/Goggles

Length of output: 1159


🏁 Script executed:

# Read lines 177-185 and context
sed -n '165,200p' tests/CMakeLists.txt

Repository: goggles-dev/Goggles

Length of output: 1794


Double registration and missing properties on discovered contract tests.

When Catch.cmake is available, goggles_filter_chain_contract_tests is registered both via add_test() (line 144) and via catch_discover_tests() (line 196), causing the suite to run as both an aggregate executable and as individually discovered test cases. The ASAN and label properties set on the aggregate test (lines 177, 185) do not apply to the discovered individual cases. Either skip add_test() when discovery succeeds, or pass properties to catch_discover_tests() using the PROPERTIES parameter.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/CMakeLists.txt` around lines 144 - 146, The test target
goggles_filter_chain_contract_tests is being registered twice (via add_test and
via catch_discover_tests), causing duplicate runs and lost ASAN/label
properties; fix by removing or guarding the add_test registration when
Catch.cmake discovery is used (i.e., only call add_test if catch_discover_tests
is not available) OR remove add_test and instead pass the ASAN and label
properties to catch_discover_tests using its PROPERTIES argument so the
discovered individual cases inherit the same properties (apply to the
goggles_filter_chain_contract_tests target and the existing ASAN/label property
names used elsewhere).

Comment on lines +90 to +99
if (features13.dynamicRendering != VK_TRUE) {
return;
}
features13.dynamicRendering = VK_TRUE;

VkDeviceCreateInfo device_info{};
device_info.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
device_info.queueCreateInfoCount = 1u;
device_info.pQueueCreateInfos = &queue_info;
device_info.pNext = &features13;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Vulkan feature chain incorrectly constructed.

Two issues here:

  1. Line 93 redundantly sets dynamicRendering = VK_TRUE after already verifying it's true on line 90.
  2. Line 99 passes &features13 directly to pNext, but features2 contains the base VkPhysicalDeviceFeatures that should be included in the chain for complete feature enablement.
🔧 Proposed fix
         vkGetPhysicalDeviceFeatures2(m_physical_device, &features2);
         if (features13.dynamicRendering != VK_TRUE) {
             return;
         }
-        features13.dynamicRendering = VK_TRUE;
 
         VkDeviceCreateInfo device_info{};
         device_info.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
         device_info.queueCreateInfoCount = 1u;
         device_info.pQueueCreateInfos = &queue_info;
-        device_info.pNext = &features13;
+        device_info.pNext = &features2;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/render/test_filter_chain_retarget_contract.cpp` around lines 90 - 99,
Remove the redundant assignment of features13.dynamicRendering after the feature
check and correctly build the pNext chain so the base features struct is
included: set features2.pNext to &features13 and then assign device_info.pNext =
&features2 (instead of &features13) so the chain begins with features2 and
continues to features13; keep the existing check that returns if
features13.dynamicRendering != VK_TRUE but do not reassign it.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR normalizes how Goggles consumes the goggles-filter-chain library across provider modes (in-tree/subdir/package), while moving “public support” types (Result/Error, controls, scale mode, Vulkan context, diagnostics entrypoints) onto the boundary-owned include surface and expanding contract coverage around retarget vs reload semantics.

Changes:

  • Introduces a CMake provider-resolution module to always expose a normalized goggles-filter-chain target, plus rehearsal presets/tasks for package consumption.
  • Moves/forwards previously Goggles-internal support headers to src/render/chain/include/goggles/filter_chain/* and updates host/backend includes accordingly.
  • Splits test registration into host integration vs boundary contract executables and adds a Vulkan-backed retarget contract test.

Reviewed changes

Copilot reviewed 73 out of 73 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
tests/render/test_vulkan_backend_subsystem_contracts.cpp Updates include-path assertions to require the canonical boundary header.
tests/render/test_filter_chain_retarget_contract.cpp Adds Vulkan-backed contract test for retarget preserving runtime state.
tests/render/test_filter_boundary_contracts.cpp Updates boundary include audits and tightens host/backend access constraints.
tests/CMakeLists.txt Splits host vs contract test targets and adds provider-aware wiring.
src/util/error.hpp Converts util error header into a forwarder to boundary result.hpp.
src/util/config.hpp Moves ScaleMode to boundary-owned header and updates include.
src/util/CMakeLists.txt Splits util logging into an object library and adds boundary include dirs.
src/render/texture/CMakeLists.txt Adds boundary include dir; adjusts link/defs visibility.
src/render/shader/shader_runtime.hpp Switches to boundary-owned diagnostics + result headers.
src/render/shader/retroarch_preprocessor.hpp Switches to boundary-owned diagnostics + result headers.
src/render/shader/CMakeLists.txt Adds boundary include dir; adjusts link/defs visibility.
src/render/chain/vulkan_result.hpp Switches from util/error.hpp to boundary result.hpp.
src/render/chain/vulkan_context.hpp Makes legacy header a forwarder to boundary vulkan context.
src/render/chain/preset_parser.hpp Switches to boundary result.hpp.
src/render/chain/pass.hpp Switches to boundary scale_mode.hpp and removes util includes.
src/render/chain/include/goggles/filter_chain/vulkan_context.hpp Adds boundary-owned VulkanContext definition.
src/render/chain/include/goggles/filter_chain/scale_mode.hpp Adds boundary-owned ScaleMode + to_string.
src/render/chain/include/goggles/filter_chain/result.hpp Adds boundary-owned Result/Error helpers and TRY/MUST/ASSERT macros.
src/render/chain/include/goggles/filter_chain/filter_controls.hpp Adds boundary-owned filter control descriptors and helpers.
src/render/chain/include/goggles/filter_chain/error.hpp Adds boundary-owned Error/ErrorCode + naming.
src/render/chain/include/goggles/filter_chain/diagnostics/source_provenance.hpp Adds diagnostics forwarder header in boundary surface.
src/render/chain/include/goggles/filter_chain/diagnostics/log_sink.hpp Adds diagnostics forwarder header in boundary surface.
src/render/chain/include/goggles/filter_chain/diagnostics/gpu_timestamp_pool.hpp Adds diagnostics forwarder header in boundary surface.
src/render/chain/include/goggles/filter_chain/diagnostics/diagnostic_sink.hpp Adds diagnostics forwarder header in boundary surface.
src/render/chain/include/goggles/filter_chain/diagnostics/diagnostic_session.hpp Adds diagnostics forwarder header in boundary surface.
src/render/chain/include/goggles/filter_chain/diagnostics/diagnostic_policy.hpp Adds diagnostics forwarder header in boundary surface.
src/render/chain/include/goggles/filter_chain/diagnostics/diagnostic_event.hpp Adds diagnostics forwarder header in boundary surface.
src/render/chain/include/goggles/filter_chain/diagnostics/compile_report.hpp Adds diagnostics forwarder header in boundary surface.
src/render/chain/framebuffer.hpp Switches to boundary result.hpp.
src/render/chain/filter_pass.hpp Switches diagnostics include to boundary surface.
src/render/chain/filter_controls.hpp Makes legacy header a forwarder to boundary filter controls.
src/render/chain/chain_runtime.hpp Switches to boundary diagnostics/result/scale_mode headers.
src/render/chain/chain_runtime.cpp Switches diagnostics include to boundary surface.
src/render/chain/chain_resources.hpp Adds state to dedupe recorded diagnostic entries.
src/render/chain/chain_resources.cpp Clears new diagnostic-recording state during install/shutdown.
src/render/chain/chain_executor.hpp Switches to boundary diagnostics headers.
src/render/chain/chain_executor.cpp Dedupe semantic/binding diagnostics emission based on observed state.
src/render/chain/chain_builder.hpp Switches to boundary diagnostics headers.
src/render/chain/chain_builder.cpp Switches to boundary diagnostics headers.
src/render/chain/api/cpp/goggles_filter_chain.hpp Switches to boundary result.hpp and public C header include.
src/render/chain/api/c/goggles_filter_chain.h Documents retarget vs reload and explicit host/boundary ownership notes.
src/render/chain/api/c/goggles_filter_chain.cpp Switches to boundary diagnostics/result/scale_mode headers.
src/render/chain/CMakeLists.txt Adds boundary include dir; adjusts link/defs visibility.
src/render/backend/vulkan_error.hpp Switches to boundary result.hpp.
src/render/backend/vulkan_debug.hpp Switches to boundary result.hpp.
src/render/backend/vulkan_context.hpp Switches VulkanContext include to boundary surface.
src/render/backend/vulkan_backend.hpp Switches controls/result/scale_mode to boundary surface.
src/render/backend/vulkan_backend.cpp Routes recording through FilterChainController::record() seam.
src/render/backend/render_output.hpp Switches to boundary result.hpp.
src/render/backend/filter_chain_controller.hpp Switches to boundary headers and adds record() API.
src/render/backend/filter_chain_controller.cpp Implements record() with initialization guard.
src/render/backend/external_frame_importer.hpp Switches to boundary result.hpp.
src/render/backend/CMakeLists.txt Adds chain C API include dir for backend build.
src/render/CMakeLists.txt Integrates provider resolution; installs boundary headers + package config when owned.
scripts/task/rehearse-filter-chain-provider.sh Adds provider rehearsal workflow for in-tree + package consumer modes.
scripts/task/help.sh Documents the new rehearsal task.
pixi.toml Registers rehearse-filter-chain-provider Pixi task.
openspec/specs/render-pipeline/spec.md Updates retarget-vs-reload requirements and async lifecycle contract text.
openspec/specs/goggles-filter-chain/spec.md Updates boundary ownership and normalized provider contract requirements.
openspec/specs/filter-chain-cpp-wrapper/spec.md Defines wrapper retarget contract + extraction-safe public surface.
openspec/specs/filter-chain-c-api/spec.md Defines post-retarget output contract and public-surface isolation.
openspec/specs/build-system/spec.md Defines normalized provider selection and rehearsal workflow requirements.
openspec/changes/archive/2026-03-13-normalize-filter-chain-consumer-boundary/tasks.md Archives milestone tasks checklist for this boundary cleanup.
openspec/changes/archive/2026-03-13-normalize-filter-chain-consumer-boundary/specs/render-pipeline/spec.md Captures archived delta for render-pipeline spec.
openspec/changes/archive/2026-03-13-normalize-filter-chain-consumer-boundary/specs/goggles-filter-chain/spec.md Captures archived delta for goggles-filter-chain spec.
openspec/changes/archive/2026-03-13-normalize-filter-chain-consumer-boundary/specs/filter-chain-cpp-wrapper/spec.md Captures archived delta for C++ wrapper spec.
openspec/changes/archive/2026-03-13-normalize-filter-chain-consumer-boundary/specs/filter-chain-c-api/spec.md Captures archived delta for C ABI spec.
openspec/changes/archive/2026-03-13-normalize-filter-chain-consumer-boundary/specs/build-system/spec.md Captures archived delta for build-system spec.
openspec/changes/archive/2026-03-13-normalize-filter-chain-consumer-boundary/proposal.md Archives proposal reframing as consumer-boundary cleanup milestone.
openspec/changes/archive/2026-03-13-normalize-filter-chain-consumer-boundary/design.md Archives design describing provider seam, boundary headers, and test split.
cmake/GogglesFilterChainProvider.cmake Adds provider-resolution + target normalization for in-tree/subdir/package.
cmake/GogglesFilterChainConfig.cmake.in Adds install-package config for the filter-chain boundary library.
CMakePresets.json Adds provider rehearsal configure/build/test presets.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review. Take the survey.

Comment on lines 1 to +3
#pragma once

#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <memory>
#include <nonstd/expected.hpp>
#include <source_location>
#include <string>
#include <utility>

namespace goggles {

/// @brief Error codes used by `goggles::Error`.
enum class ErrorCode : std::uint8_t {
ok,
file_not_found,
file_read_failed,
file_write_failed,
parse_error,
invalid_config,
vulkan_init_failed,
vulkan_device_lost,
shader_compile_failed,
shader_load_failed,
input_init_failed,
invalid_data,
unknown_error
};

/// @brief Structured error for `Result<T>` operations.
struct Error {
ErrorCode code;
std::string message;
std::source_location location;

Error(ErrorCode error_code, std::string msg,
std::source_location loc = std::source_location::current())
: code(error_code), message(std::move(msg)), location(loc) {}
};

/// @brief Project-wide fallible operation return type.
template <typename T>
using Result = nonstd::expected<T, Error>;

/// @brief Convenience alias for `Result<std::unique_ptr<T>>`.
template <typename T>
using ResultPtr = Result<std::unique_ptr<T>>;

template <typename T>
[[nodiscard]] inline auto make_error(ErrorCode code, std::string message,
std::source_location loc = std::source_location::current())
-> Result<T> {
return nonstd::make_unexpected(Error{code, std::move(message), loc});
}

template <typename T>
[[nodiscard]] inline auto make_result_ptr(std::unique_ptr<T> ptr) -> ResultPtr<T> {
return ResultPtr<T>{std::move(ptr)};
}

template <typename T>
[[nodiscard]] inline auto
make_result_ptr_error(ErrorCode code, std::string message,
std::source_location loc = std::source_location::current()) -> ResultPtr<T> {
return nonstd::make_unexpected(Error{code, std::move(message), loc});
}

/// @brief Returns a stable string name for an `ErrorCode` value.
[[nodiscard]] constexpr auto error_code_name(ErrorCode code) -> const char* {
switch (code) {
case ErrorCode::ok:
return "ok";
case ErrorCode::file_not_found:
return "file_not_found";
case ErrorCode::file_read_failed:
return "file_read_failed";
case ErrorCode::file_write_failed:
return "file_write_failed";
case ErrorCode::parse_error:
return "parse_error";
case ErrorCode::invalid_config:
return "invalid_config";
case ErrorCode::vulkan_init_failed:
return "vulkan_init_failed";
case ErrorCode::vulkan_device_lost:
return "vulkan_device_lost";
case ErrorCode::shader_compile_failed:
return "shader_compile_failed";
case ErrorCode::shader_load_failed:
return "shader_load_failed";
case ErrorCode::input_init_failed:
return "input_init_failed";
case ErrorCode::invalid_data:
return "invalid_data";
case ErrorCode::unknown_error:
return "unknown_error";
}
return "unknown";
}

} // namespace goggles

// NOLINTBEGIN(cppcoreguidelines-macro-usage)

/// @brief Propagates an error or returns the contained value (expression-style).
///
/// Similar to Rust's `?` operator. The expression must yield a `Result<T>`.
// NOLINTNEXTLINE(bugprone-macro-parentheses)
#define GOGGLES_TRY(expr) \
({ \
auto _try_result = (expr); \
if (!_try_result) \
return nonstd::make_unexpected(_try_result.error()); \
std::move(_try_result).value(); \
})

/// @brief Aborts on error or returns the contained value (expression-style).
///
/// Use for internal invariants where failure indicates a bug.
// NOLINTNEXTLINE(bugprone-macro-parentheses)
#define GOGGLES_MUST(expr) \
({ \
auto _must_result = (expr); \
if (!_must_result) { \
auto& _err = _must_result.error(); \
std::fprintf(stderr, "GOGGLES_MUST failed at %s:%u in %s\n %s: %s\n", \
_err.location.file_name(), _err.location.line(), \
_err.location.function_name(), goggles::error_code_name(_err.code), \
_err.message.c_str()); \
std::abort(); \
} \
std::move(_must_result).value(); \
})

/// @brief Aborts when an invariant is violated.
// NOLINTNEXTLINE(bugprone-macro-parentheses)
#define GOGGLES_ASSERT(condition, ...) \
do { \
if (!(condition)) { \
std::fprintf(stderr, "GOGGLES_ASSERT failed: %s at %s:%u in %s\n", #condition, \
__FILE__, __LINE__, __func__); \
__VA_OPT__(do { \
std::fprintf(stderr, " "); \
std::fprintf(stderr, __VA_ARGS__); \
std::fprintf(stderr, "\n"); \
} while (false);) \
std::abort(); \
} \
} while (false)

// NOLINTEND(cppcoreguidelines-macro-usage)
#include "../render/chain/include/goggles/filter_chain/result.hpp"
Comment on lines +1 to +3
#pragma once

#include <util/diagnostics/compile_report.hpp>
Comment on lines +127 to +134
target_link_libraries(goggles-filter-chain
PUBLIC
Vulkan::Vulkan
PRIVATE
spdlog::spdlog
slang::slang
stb_image
)
Comment on lines +66 to +76
install(FILES
${CMAKE_SOURCE_DIR}/src/render/chain/include/goggles/filter_chain/diagnostics/compile_report.hpp
${CMAKE_SOURCE_DIR}/src/render/chain/include/goggles/filter_chain/diagnostics/diagnostic_event.hpp
${CMAKE_SOURCE_DIR}/src/render/chain/include/goggles/filter_chain/diagnostics/diagnostic_policy.hpp
${CMAKE_SOURCE_DIR}/src/render/chain/include/goggles/filter_chain/diagnostics/diagnostic_session.hpp
${CMAKE_SOURCE_DIR}/src/render/chain/include/goggles/filter_chain/diagnostics/diagnostic_sink.hpp
${CMAKE_SOURCE_DIR}/src/render/chain/include/goggles/filter_chain/diagnostics/gpu_timestamp_pool.hpp
${CMAKE_SOURCE_DIR}/src/render/chain/include/goggles/filter_chain/diagnostics/log_sink.hpp
${CMAKE_SOURCE_DIR}/src/render/chain/include/goggles/filter_chain/diagnostics/source_provenance.hpp
DESTINATION include/goggles/filter_chain/diagnostics
)
Comment on lines +11 to +20
set(GOGGLES_FILTER_CHAIN_CONTRACT_TEST_SOURCES
render/test_shader_runtime.cpp
render/test_retroarch_preprocessor.cpp
render/test_preset_parser.cpp
render/test_slang_reflect.cpp
render/test_filter_chain.cpp
render/test_filter_controls.cpp
render/test_filter_chain_c_api_contracts.cpp
render/test_filter_chain_retarget_contract.cpp
)
@K1ngst0m K1ngst0m closed this Mar 13, 2026
@K1ngst0m K1ngst0m deleted the dev/normalize-filter-chain branch March 13, 2026 16:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants