Skip to content

Introduce pre-chain stage with area downsampling pass#60

Merged
K1ngst0m merged 3 commits intomainfrom
dev/add-prechain-stage
Jan 21, 2026
Merged

Introduce pre-chain stage with area downsampling pass#60
K1ngst0m merged 3 commits intomainfrom
dev/add-prechain-stage

Conversation

@K1ngst0m
Copy link
Copy Markdown
Collaborator

@K1ngst0m K1ngst0m commented Jan 21, 2026

User description

Why

The --app-width / --app-height CLI options currently only work with WSI proxy mode. Users need a way to control the source resolution fed into the RetroArch filter chain regardless of capture mode. A pre-chain downsampling stage enables resolution control for shader effects that benefit from lower-res input (CRT simulation, pixel art upscalers) without requiring WSI proxy overhead.

What Changes

  • Add downsample.frag.slang shader in shaders/internal/ using area filtering for high-quality downsampling
  • Introduce PreChain infrastructure in FilterChain to run internal passes before RetroArch shader passes
  • Add DownsamplePass as the first pre-chain pass when --app-width/--app-height are specified
  • Reuse existing FilterPass/Framebuffer infrastructure for pre-chain passes
  • Change --app-width/--app-height semantics: these options now set the source resolution for the filter chain input (applies to all capture modes, not just WSI proxy)
  • Store configured source resolution in Config::Capture for use by FilterChain

Impact

  • Affected specs: render-pipeline
  • Affected code:
    • shaders/internal/downsample.frag.slang (new)
    • src/render/chain/filter_chain.hpp - add pre-chain pass storage
    • src/render/chain/filter_chain.cpp - implement pre-chain recording
    • src/app/cli.cpp - update option descriptions
    • src/util/config.hpp - add source resolution fields

PR Type

Enhancement


Description

  • Introduce extensible pre-chain pass infrastructure in FilterChain

  • Add DownsamplePass with area filtering for resolution control

  • Extend --app-width/--app-height to work in all capture modes

  • Support single-dimension specification with aspect-ratio preservation

  • Propagate source resolution through Config and RenderSettings


Diagram Walkthrough

flowchart LR
  CLI["CLI Options<br/>--app-width/height"]
  Config["Config::Render<br/>source_width/height"]
  RenderSettings["RenderSettings<br/>source_width/height"]
  VulkanBackend["VulkanBackend<br/>m_source_resolution"]
  FilterChain["FilterChain<br/>m_prechain_passes"]
  DownsamplePass["DownsamplePass<br/>Area Filter"]
  Output["Output to<br/>RetroArch Chain"]
  
  CLI -->|"parse"| Config
  Config -->|"propagate"| RenderSettings
  RenderSettings -->|"pass to create"| VulkanBackend
  VulkanBackend -->|"pass to create"| FilterChain
  FilterChain -->|"instantiate"| DownsamplePass
  DownsamplePass -->|"output becomes<br/>original_view"| Output
Loading

File Walkthrough

Relevant files
Enhancement
11 files
downsample_pass.hpp
New DownsamplePass class header definition                             
+61/-0   
downsample_pass.cpp
Implement area-filter downsampling pass logic                       
+344/-0 
filter_chain.hpp
Add pre-chain pass and framebuffer vectors                             
+18/-1   
filter_chain.cpp
Implement generic pre-chain recording infrastructure         
+165/-18
vulkan_backend.hpp
Add source_resolution field to RenderSettings                       
+3/-0     
vulkan_backend.cpp
Propagate source resolution through backend initialization
+8/-4     
application.cpp
Pass source resolution to RenderSettings                                 
+2/-0     
cli.cpp
Update CLI option descriptions and remove validation         
+4/-12   
main.cpp
Parse and propagate source resolution from CLI                     
+6/-0     
config.hpp
Add source_width/height fields to Config::Render                 
+2/-0     
downsample.frag.slang
New area-filter downsampling shader implementation             
+73/-0   
Formatting
1 files
vulkan_debug.cpp
Update debug log prefix from Vulkan to VVL                             
+4/-4     
Configuration changes
1 files
CMakeLists.txt
Add downsample_pass.cpp to build targets                                 
+1/-0     
Documentation
3 files
proposal.md
Document pre-chain and downsample feature proposal             
+38/-0   
spec.md
Define requirements for pre-chain and downsample                 
+93/-0   
tasks.md
Track implementation tasks for pre-chain feature                 
+41/-0   

Summary by CodeRabbit

  • New Features

    • Pre-chain downsampling added to the render pipeline with area filtering for improved scaling quality and performance.
    • Configure source resolution using --app-width / --app-height; single-dimension input preserves aspect ratio and is supported at startup.
  • Tests

    • CLI tests updated to allow single-dimension app width/height parsing.
  • Chores

    • Render/config storage updated so configured source resolution is applied at launch.
  • Style

    • Vulkan log prefix shortened for clearer diagnostics.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Jan 21, 2026

Caution

Review failed

The pull request is closed.

📝 Walkthrough

Walkthrough

Adds an optional pre-chain downsampling stage to the render pipeline. A new DownsamplePass and area-filter shader run before the RetroArch passes when a source resolution is configured; FilterChain and backend/config/CLI are extended to create, record, and thread pre-chain passes and framebuffers into the main chain.

Changes

Cohort / File(s) Summary
Shader & Pass Implementation
shaders/internal/downsample.frag.slang, src/render/chain/downsample_pass.hpp, src/render/chain/downsample_pass.cpp, src/render/chain/CMakeLists.txt
New area-weighted downsample fragment shader and a Vulkan DownsamplePass with sampler/descriptor/pipeline creation, push-constants, per-frame descriptor updates and record path; build list updated.
FilterChain Pre-Chain Infrastructure
src/render/chain/filter_chain.hpp, src/render/chain/filter_chain.cpp
Added m_source_resolution, pre-chain vectors (m_prechain_passes, m_prechain_framebuffers), changed create() signature to accept source resolution, added ensure_prechain_passes() and record_prechain(), integrated pre-chain execution and cleanup into recording flow.
Configuration & Backend Plumbing
src/util/config.hpp, src/render/backend/vulkan_backend.hpp, src/render/backend/vulkan_backend.cpp
Added Config::Render::source_width/height and RenderSettings::source_width/height; VulkanBackend stores m_source_resolution and forwards it into FilterChain creation and shader-reload paths.
CLI & Application Integration
src/app/cli.cpp, src/app/application.cpp, src/app/main.cpp, tests/app/test_cli.cpp
CLI/help updated to treat --app-width/--app-height as source resolution (single-dimension allowed); main applies CLI overrides into config.render; tests updated to accept single-dimension parsing.
Backend Debug & Logging
src/render/backend/vulkan_debug.cpp
Log prefix changed from "[Vulkan]" to "[VVL]" in Vulkan debug callback messages.
Specs & Tasks
openspec/changes/add-prechain-downsample/*
Proposal, spec, and tasks documenting pre-chain design, downsample behavior, CLI semantics (aspect-ratio fallback), and testing plan.

Sequence Diagram

sequenceDiagram
    participant App as Application
    participant Backend as VulkanBackend
    participant Chain as FilterChain
    participant Pass as DownsamplePass
    participant GPU as Vulkan GPU

    App->>App: Parse CLI (--app-width/--app-height)
    App->>Backend: init_filter_chain(source_resolution)
    Backend->>Chain: create(..., source_resolution)
    Chain->>Chain: store m_source_resolution
    alt source_resolution non-zero
        Chain->>Pass: create(DownsamplePass)
        Pass->>GPU: Allocate sampler & descriptors
        Pass->>GPU: Create pipeline layout & graphics pipeline
        Chain->>Chain: add pass to m_prechain_passes
    end

    rect rgba(100,150,200,0.5)
    Note over Chain,GPU: Per-frame rendering
    App->>Chain: record(cmd_buffer, frame_index)
    Chain->>Chain: ensure_prechain_passes(captured_extent)
    Chain->>Chain: record_prechain(original_view, original_extent, frame_index)
    Chain->>Pass: record(cmd, PassContext)
    Pass->>GPU: Bind descriptor with source texture
    Pass->>GPU: Push downsample constants
    Pass->>GPU: Draw area-filtered downsample
    Pass-->>Chain: Return downsampled image view/extent
    Chain->>Chain: Use prechain output as original_view for RetroArch passes
    Chain->>GPU: Record downstream passes with downsampled input
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

Poem

🐰 Hops through shaders, soft and spry,

I shrink each frame without a cry,
A pre-chain pass, a gentle art,
Pixels tumble, trimmed apart,
Thump-thump—rendering, neat and spry.

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 6.06% 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 accurately and concisely summarizes the main change: introducing a pre-chain stage with area downsampling capability to the render pipeline.

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

✨ Finishing touches
  • 📝 Generate docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

…downsample

- Add m_prechain_passes/m_prechain_framebuffers vectors to FilterChain
- Create DownsamplePass class with area filter algorithm
- Propagate source_width/source_height through Config and RenderSettings
- Update CLI semantics: --app-width/--app-height set filter chain input resolution
- Support lazy single-dimension resolution with aspect-ratio preservation
@qodo-code-review
Copy link
Copy Markdown

qodo-code-review bot commented Jan 21, 2026

PR Compliance Guide 🔍

Below is a summary of compliance checks for this PR:

Security Compliance
Out-of-bounds indexing

Description: update_descriptor() indexes m_descriptor_sets[frame_index] without validating that
frame_index < m_num_sync_indices, which could cause out-of-bounds access and a crash
(denial-of-service) if an unexpected/invalid frame index is ever passed to the pass.
downsample_pass.cpp [63-78]

Referred Code
void DownsamplePass::update_descriptor(uint32_t frame_index, vk::ImageView source_view) {
    vk::DescriptorImageInfo image_info{};
    image_info.sampler = *m_sampler;
    image_info.imageView = source_view;
    image_info.imageLayout = vk::ImageLayout::eShaderReadOnlyOptimal;

    vk::WriteDescriptorSet write{};
    write.dstSet = m_descriptor_sets[frame_index];
    write.dstBinding = 0;
    write.dstArrayElement = 0;
    write.descriptorCount = 1;
    write.descriptorType = vk::DescriptorType::eCombinedImageSampler;
    write.pImageInfo = &image_info;

    m_device.updateDescriptorSets(write, {});
}
Ticket Compliance
🎫 No ticket provided
  • Create ticket/issue
Codebase Duplication Compliance
Codebase context is not defined

Follow the guide to enable codebase context checks.

Custom Compliance
🟢
Generic: Comprehensive Audit Trails

Objective: To create a detailed and reliable record of critical system actions for security analysis
and compliance.

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Meaningful Naming and Self-Documenting Code

Objective: Ensure all identifiers clearly express their purpose and intent, making code
self-documenting

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Secure Error Handling

Objective: To prevent the leakage of sensitive system information through error messages while
providing sufficient detail for internal debugging.

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Security-First Input Validation and Data Handling

Objective: Ensure all data inputs are validated, sanitized, and handled securely to prevent
vulnerabilities

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

🔴
Generic: Robust Error Handling and Edge Case Management

Objective: Ensure comprehensive error handling that provides meaningful context and graceful
degradation

Status: 🏷️
Zero division risk: Aspect-ratio computation divides by captured_extent.width/height without guarding against
zero-sized captured frames, which can crash or produce invalid resolutions.

Referred Code
vk::Extent2D target_resolution = m_source_resolution;
if (target_resolution.width == 0) {
    target_resolution.width =
        static_cast<uint32_t>(std::round(static_cast<float>(target_resolution.height) *
                                         static_cast<float>(captured_extent.width) /
                                         static_cast<float>(captured_extent.height)));
    target_resolution.width = std::max(1U, target_resolution.width);
} else if (target_resolution.height == 0) {
    target_resolution.height =
        static_cast<uint32_t>(std::round(static_cast<float>(target_resolution.width) *
                                         static_cast<float>(captured_extent.height) /
                                         static_cast<float>(captured_extent.width)));
    target_resolution.height = std::max(1U, target_resolution.height);

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Secure Logging Practices

Objective: To ensure logs are useful for debugging and auditing without exposing sensitive
information like PII, PHI, or cardholder data.

Status: 🏷️
Unstructured log format: New log lines are plain-text (not clearly structured/JSON), so it is unclear whether the
logging backend enforces structured logging for auditing as required.

Referred Code
    GOGGLES_LOG_INFO("FilterChain pre-chain enabled: {}x{}", source_resolution.width,
                     source_resolution.height);
} else if (source_resolution.width > 0 || source_resolution.height > 0) {
    GOGGLES_LOG_INFO("FilterChain pre-chain pending: width={}, height={}",
                     source_resolution.width, source_resolution.height);
}

Learn more about managing compliance generic rules or creating your own custom rules

  • Update
Compliance status legend 🟢 - Fully Compliant
🟡 - Partial Compliant
🔴 - Not Compliant
⚪ - Requires Further Human Verification
🏷️ - Compliance label

@qodo-code-review
Copy link
Copy Markdown

qodo-code-review bot commented Jan 21, 2026

PR Code Suggestions ✨

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
Possible issue
Improve downsampling quality for large ratios

Modify the downsampling shader to use a strided loop instead of truncating the
sampling area for large downscaling ratios. This improves image quality by
ensuring the entire source pixel area is considered.

shaders/internal/downsample.frag.slang [38-65]

 // Limit sample count for performance (max 8x8 = 64 samples)
 int max_samples = 8;
-int2 sample_range = min(i_max - i_min, int2(max_samples, max_samples));
-i_max = i_min + sample_range;
+int2 full_range = i_max - i_min;
+int2 step = max(1, full_range / max_samples);
+float2 step_weight_multiplier = float2(step);
 
 float4 accum = float4(0.0, 0.0, 0.0, 0.0);
 float total_weight = 0.0;
 
 // Area filter: weight each source pixel by its coverage
-for (int y = i_min.y; y < i_max.y; y++) {
-    for (int x = i_min.x; x < i_max.x; x++) {
+for (int y = i_min.y; y < i_max.y; y += step.y) {
+    for (int x = i_min.x; x < i_max.x; x += step.x) {
         // Calculate this pixel's contribution (overlap with the box)
         float2 pixel_min = float2(x, y);
-        ...
+        float2 pixel_max = float2(x + step.x, y + step.y);
+
+        float2 overlap_min = max(pixel_min, box_min);
+        float2 overlap_max = min(pixel_max, box_max);
+        float2 overlap_size = max(overlap_max - overlap_min, float2(0.0, 0.0));
+
         float weight = overlap_size.x * overlap_size.y;
         if (weight > 0.0) {
             // Sample at pixel center
             float2 sample_uv = (float2(x, y) + 0.5) * pc.source_size.zw;
             accum += source_texture.Sample(sample_uv) * weight;
             total_weight += weight;
         }
     }
 }

[To ensure code accuracy, apply this suggestion manually]

Suggestion importance[1-10]: 8

__

Why: The suggestion correctly identifies a flaw in the downsampling shader's logic that causes incorrect rendering for large downscaling ratios by truncating the sample area, and proposes a valid fix using a strided loop that improves output quality.

Medium
Prevent division by zero during initialization

In ensure_prechain_passes, add checks to prevent division-by-zero when
captured_extent.width or captured_extent.height is zero during aspect ratio
calculation.

src/render/chain/filter_chain.cpp [738-779]

 auto FilterChain::ensure_prechain_passes(vk::Extent2D captured_extent) -> Result<void> {
     if (!m_prechain_passes.empty() && !m_prechain_framebuffers.empty()) {
         return {};
     }
 
     if (m_source_resolution.width == 0 && m_source_resolution.height == 0) {
         return {};
     }
 
     vk::Extent2D target_resolution = m_source_resolution;
     if (target_resolution.width == 0) {
+        if (captured_extent.height == 0) {
+            return {}; // Avoid division by zero
+        }
         target_resolution.width =
             static_cast<uint32_t>(std::round(static_cast<float>(target_resolution.height) *
                                              static_cast<float>(captured_extent.width) /
                                              static_cast<float>(captured_extent.height)));
         target_resolution.width = std::max(1U, target_resolution.width);
     } else if (target_resolution.height == 0) {
+        if (captured_extent.width == 0) {
+            return {}; // Avoid division by zero
+        }
         target_resolution.height =
             static_cast<uint32_t>(std::round(static_cast<float>(target_resolution.width) *
                                              static_cast<float>(captured_extent.height) /
                                              static_cast<float>(captured_extent.width)));
         target_resolution.height = std::max(1U, target_resolution.height);
     }
 
     m_source_resolution = target_resolution;
 
     DownsamplePassConfig downsample_config{
         .target_format = vk::Format::eR8G8B8A8Unorm,
         .num_sync_indices = m_num_sync_indices,
         .shader_dir = m_shader_dir,
     };
     m_prechain_passes.push_back(
         GOGGLES_TRY(DownsamplePass::create(m_vk_ctx, *m_shader_runtime, downsample_config)));
 
     m_prechain_framebuffers.push_back(GOGGLES_TRY(Framebuffer::create(
         m_vk_ctx.device, m_vk_ctx.physical_device, vk::Format::eR8G8B8A8Unorm, target_resolution)));
 
     GOGGLES_LOG_INFO("FilterChain pre-chain initialized (aspect-ratio): {}x{} (from {}x{})",
                      target_resolution.width, target_resolution.height, captured_extent.width,
                      captured_extent.height);
     return {};
 }
  • Apply / Chat
Suggestion importance[1-10]: 8

__

Why: This suggestion correctly identifies a potential division-by-zero crash in ensure_prechain_passes when calculating aspect ratio with a zero-dimension captured frame and provides a necessary check to prevent it.

Medium
Fix pre-chain barrier access flags

In record_prechain, fix the pre-barrier by setting srcAccessMask to empty when
transitioning the image layout from eUndefined to avoid an invalid access flag.

src/render/chain/filter_chain.cpp [363-367]

 vk::ImageMemoryBarrier pre_barrier{};
-pre_barrier.srcAccessMask = vk::AccessFlagBits::eShaderRead;
+pre_barrier.srcAccessMask = {};  // no prior access on undefined layout
 pre_barrier.dstAccessMask = vk::AccessFlagBits::eColorAttachmentWrite;
 pre_barrier.oldLayout = vk::ImageLayout::eUndefined;
 pre_barrier.newLayout = vk::ImageLayout::eColorAttachmentOptimal;
  • Apply / Chat
Suggestion importance[1-10]: 7

__

Why: The suggestion correctly identifies that the srcAccessMask for an image layout transition from eUndefined should be empty, not eShaderRead. This is a valid Vulkan validation layer fix that improves correctness.

Medium
General
Ensure last source extent is correct

In the record function, update m_last_source_extent with
effective_original_extent after the pre-chain processing. This ensures it
accurately reflects the source dimensions for the main filter chain.

src/render/chain/filter_chain.cpp [413-564]

 void FilterChain::record(vk::CommandBuffer cmd, vk::Image original_image,
                          vk::ImageView original_view, vk::Extent2D original_extent,
                          vk::ImageView swapchain_view, vk::Extent2D viewport_extent,
                          uint32_t frame_index, ScaleMode scale_mode, uint32_t integer_scale) {
     GOGGLES_PROFILE_FUNCTION();
 
     m_last_scale_mode = scale_mode;
     m_last_integer_scale = integer_scale;
-    m_last_source_extent = original_extent;
 
     GOGGLES_MUST(ensure_prechain_passes(original_extent));
 
     auto [effective_original_view, effective_original_extent] =
         record_prechain(cmd, original_view, original_extent, frame_index);
+
+    m_last_source_extent = effective_original_extent;
 
     GOGGLES_MUST(ensure_frame_history(effective_original_extent));
 
     if (m_passes.empty() || m_bypass_enabled.load(std::memory_order_relaxed)) {
 ...
     if (m_frame_history.is_initialized()) {
         auto history_image = !m_prechain_framebuffers.empty()
                                  ? m_prechain_framebuffers.back()->image()
                                  : original_image;
         m_frame_history.push(cmd, history_image, effective_original_extent);
     }
 
     copy_feedback_framebuffers(cmd);
     m_frame_count++;
 }

[To ensure code accuracy, apply this suggestion manually]

Suggestion importance[1-10]: 6

__

Why: The suggestion correctly points out that m_last_source_extent should be updated with the post-pre-chain effective_original_extent for logical consistency, preventing potential bugs if other code relies on this member variable.

Low
Clarify new CLI semantics

Update the CLI help footer to clarify that --app-width and --app-height can be
used individually to control the source resolution for the new pre-chain
downsampling.

src/app/cli.cpp [124-130]

 app.footer(R"(Usage:
   goggles --detach
   goggles [options] -- <app> [app_args...]
 
 Notes:
   - Default mode (no --detach) launches the target app with capture + input forwarding enabled.
-  - '--' is required before <app> to avoid app args (e.g. '--config') being parsed as Goggles options.)");
+  - '--' is required before <app> to avoid app args (e.g. '--config') being parsed as Goggles options.
+  - `--app-width`/`--app-height` now control source resolution and may be specified alone)");

[To ensure code accuracy, apply this suggestion manually]

Suggestion importance[1-10]: 4

__

Why: The suggestion correctly identifies that the CLI help text is missing information about the new behavior of --app-width/--app-height. Updating the documentation improves user experience and discoverability.

Low
Log only complete resolutions

In run_app, modify the logging statement to only print the source resolution
when both width and height are non-zero, avoiding misleading logs like "Source
resolution: 640x0".

src/app/main.cpp [375-380]

 if (cli_opts.app_width != 0 || cli_opts.app_height != 0) {
     config.render.source_width = cli_opts.app_width;
     config.render.source_height = cli_opts.app_height;
-    GOGGLES_LOG_INFO("Source resolution: {}x{}", config.render.source_width,
-                     config.render.source_height);
+    if (config.render.source_width != 0 && config.render.source_height != 0) {
+        GOGGLES_LOG_INFO("Source resolution: {}x{}", config.render.source_width,
+                         config.render.source_height);
+    }
 }
  • Apply / Chat
Suggestion importance[1-10]: 3

__

Why: The suggestion correctly points out that logging a resolution with a zero dimension can be misleading. While a minor issue, conditionalizing the log message improves clarity.

Low
  • Update

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: 2

🤖 Fix all issues with AI agents
In `@openspec/changes/add-prechain-downsample/specs/render-pipeline/spec.md`:
- Around line 80-86: The scenario example's computed width is off by one
compared to the implementation: update the spec to match the code's rounding
behavior (std::round) by changing the expected width from 854 to 853 (since
round(480 * 1920 / 1080) = round(853.333...) = 853), or explicitly note that
implementations using std::round will produce 853; reference the implementation
in filter_chain.cpp and the use of std::round when making this change.

In `@src/render/chain/downsample_pass.cpp`:
- Around line 85-93: The code computes inverses for source and target dimensions
in DownsamplePushConstants
(pc.source_inv_width/pc.source_inv_height/pc.target_inv_width/pc.target_inv_height)
without checking for zero; add a defensive guard around ctx.source_extent and
ctx.output_extent width/height: if a dimension is zero (or extremely close to
zero) set the corresponding inverse to 0.0f (or 1.0f/epsilon) and/or
early-return/skip the downsample dispatch, otherwise compute 1.0f/width as
before; update all uses of pc.* to assume these safe values.
🧹 Nitpick comments (3)
shaders/internal/downsample.frag.slang (1)

38-41: Consider centering the sample window for extreme downsampling ratios.

When the source region exceeds 8×8 pixels, the current approach truncates from the end (i_max = i_min + sample_range), which may skip high-overlap pixels at the upper boundary. For extreme downsampling ratios, centering the 8×8 window on the box midpoint could improve quality:

     int max_samples = 8;
     int2 sample_range = min(i_max - i_min, int2(max_samples, max_samples));
-    i_max = i_min + sample_range;
+    int2 excess = (i_max - i_min) - sample_range;
+    i_min = i_min + excess / 2;
+    i_max = i_min + sample_range;

This is a minor quality improvement for edge cases with large scale factors.

src/app/main.cpp (1)

375-380: Consider clarifying the log message for single-dimension specification.

When only one dimension is specified (e.g., --app-width 640), the log will show Source resolution: 640x0, which may confuse users. Consider logging only the specified dimension or indicating that the other will be computed:

     if (cli_opts.app_width != 0 || cli_opts.app_height != 0) {
         config.render.source_width = cli_opts.app_width;
         config.render.source_height = cli_opts.app_height;
-        GOGGLES_LOG_INFO("Source resolution: {}x{}", config.render.source_width,
-                         config.render.source_height);
+        if (config.render.source_width != 0 && config.render.source_height != 0) {
+            GOGGLES_LOG_INFO("Source resolution: {}x{}", config.render.source_width,
+                             config.render.source_height);
+        } else if (config.render.source_width != 0) {
+            GOGGLES_LOG_INFO("Source width: {} (height from aspect ratio)",
+                             config.render.source_width);
+        } else {
+            GOGGLES_LOG_INFO("Source height: {} (width from aspect ratio)",
+                             config.render.source_height);
+        }
     }

This is a minor UX improvement for log clarity.

src/util/config.hpp (1)

67-68: Consider adding documentation comments for the new fields.

The other fields in Config::Render are self-explanatory, but the semantics of source_width/source_height (pre-chain downsampling target, 0 = disabled) could benefit from brief documentation:

         uint32_t integer_scale = 0;
+        uint32_t source_width = 0;  ///< Target width for pre-chain downsampling (0 = disabled)
+        uint32_t source_height = 0; ///< Target height for pre-chain downsampling (0 = disabled)
-        uint32_t source_width = 0;
-        uint32_t source_height = 0;

@K1ngst0m K1ngst0m force-pushed the dev/add-prechain-stage branch from 6189d83 to 7506faf Compare January 21, 2026 11:25
@K1ngst0m K1ngst0m merged commit c8be1f9 into main Jan 21, 2026
1 of 2 checks passed
@K1ngst0m K1ngst0m deleted the dev/add-prechain-stage branch January 21, 2026 11:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant