Skip to content

Texture streaming - #34

Open
jiayev wants to merge 13 commits into
Gistix:mainfrom
jiayev:texturestreaming
Open

Texture streaming#34
jiayev wants to merge 13 commits into
Gistix:mainfrom
jiayev:texturestreaming

Conversation

@jiayev

@jiayev jiayev commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

Summary by CodeRabbit

  • New Features
    • Added experimental texture streaming controls (mode selection, memory budget, and max mip-bias limit).
    • Added texture memory statistics logging to the public renderer API.
    • Introduced resident-mipmap behavior to reduce streamed texture memory usage.
    • Added low-discrepancy ray-tracing sample generation support for BSDF sampling.
  • Improvements
    • Enhanced ray-tracing randomness and cosine-hemisphere sampling consistency.
    • Improved texture upload completion handling to reduce idle blocking during streaming.

@coderabbitai

coderabbitai Bot commented Jun 18, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 18812b72-1258-4407-81b8-5548ae3433c3

📥 Commits

Reviewing files that changed from the base of the PR and between 5af1cfc and 2acb8af.

📒 Files selected for processing (3)
  • src/API.cpp
  • src/Hooks.cpp
  • src/Types/Settings.h
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/API.cpp
  • src/Types/Settings.h
  • src/Hooks.cpp

📝 Walkthrough

Walkthrough

Adds configurable texture streaming with resident-mip tracking, asynchronous D3D12 uploads, and memory statistics logging. It also adds deterministic Sobol/Owen sampling utilities and routes pre-generated samples into ray-traced BSDF sampling.

Changes

Texture Streaming Pipeline

Layer / File(s) Summary
Streaming configuration contract
src/Types/Settings.h
Adds texture streaming modes, memory budgets, and maximum mip-bias settings.
Resident mip allocation and upload flow
src/Hooks.cpp
Computes streaming mip bias, allocates resident texture subsets for D3D11/D3D12, adjusts uploads and SRVs, and tracks asynchronous D3D12 upload completion.
Texture metadata and memory accounting
src/Core/TextureManager.h, src/Core/TextureManager.cpp, src/Core/Texture.h
Stores resident mip metadata, consumes registered offsets during descriptor creation, records mip levels, and logs total and streamed texture memory.
Memory statistics API export
src/API.h, src/API.cpp
Exports LogTextureMemoryStats() and routes it through the scene texture manager.

Deterministic Ray-Tracing Sampling

Layer / File(s) Summary
Sample generator implementation
shaders/raytracing/include/SampleGenerators.hlsli
Adds hash, Sobol, Owen-scrambled, uniform, and low-discrepancy sample generators.
Common random and hemisphere sampling
shaders/raytracing/include/Common.hlsli
Integrates the generators, updates random seeding, and adds sample-based cosine hemisphere sampling.
BSDF pre-generated sample consumption
shaders/raytracing/include/Materials/BSDF.hlsli
Updates BSDF and hair-tint sampling to consume pre-generated samples.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant TextureHook
  participant TextureManager
  participant D3D12Renderer
  participant TextureStatsAPI

  TextureHook->>TextureManager: Register resident mip offset
  TextureHook->>D3D12Renderer: Submit resident-mip upload with event query
  D3D12Renderer-->>TextureHook: Report upload completion
  TextureHook->>TextureManager: Create descriptor with resident metadata
  TextureStatsAPI->>TextureManager: LogMemoryStats()
Loading

Possibly related PRs

Poem

A rabbit hops through mips so neat,
While shaders sample paths discrete.
Hashes twirl and textures stream,
Queries retire in a glowing beam.
“Memory logs!” the bunny sings,
As Sobol sprinkles sampling wings.

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

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.
Title check ❓ Inconclusive The title is relevant but too generic to convey the main change beyond the broad topic of texture streaming. Use a more specific title, such as "Add texture streaming support" or "Implement streamed texture residency tracking".
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/Core/TextureManager.cpp`:
- Around line 97-103: The standardBytes counter is accumulating all textures
without excluding streamed ones, so streamed textures are being counted in both
standard and streamed categories. Modify the code to only add to standardBytes
when the texture is not a streamed texture by adding a condition that excludes
textures where residentMipOffset is greater than 0. This will ensure the
standard and streamed byte counts are mutually exclusive and the log breakdown
is accurate. Apply the same fix to the similar code block referenced at lines
112-119.
- Around line 24-31: The RegisterResidentMipOffset function in
TextureManager.cpp stores raw IUnknown pointers as keys in the
g_ResidentMipOffsets map, but these entries are only removed when GetDescriptor
consumes them. If a registered resource is destroyed without reaching descriptor
creation, the stale pointer key persists in the map. When another COM object
later reuses the same memory address, the old entry will incorrectly apply a
stale mip offset to the new object. Implement proper cleanup by registering a
release callback or using COM object lifecycle tracking (such as implementing
IUnknown::Release wrapping or using weak_ptr alternatives) to remove entries
from g_ResidentMipOffsets when resources are actually destroyed, rather than
deferring cleanup until descriptor consumption.

In `@src/Hooks.cpp`:
- Around line 113-155: The GetTextureStreamingMipBias function determines
texture streaming bias but never uses the TextureBudgetMB setting from
ExperimentalSettings, making the memory budget configuration ineffective.
Integrate TextureBudgetMB into the bias calculation by computing an estimated
memory footprint for the texture at different mip levels, then adjust the
desiredBias upward as needed to keep memory consumption within the configured
budget limit. This adjustment should happen after the initial bias is determined
by TextureStreamingMode but before the final clamping operations, ensuring the
budget constraint overrides mode-based decisions when memory limits would be
exceeded.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: a30ea6a0-e526-4e30-869d-897c421e9496

📥 Commits

Reviewing files that changed from the base of the PR and between ba5b864 and 37db002.

📒 Files selected for processing (10)
  • AGENTS.md
  • extern/RTXDI-Library
  • src/API.cpp
  • src/API.h
  • src/Core/MaterialBase.cpp
  • src/Core/Texture.h
  • src/Core/TextureManager.cpp
  • src/Core/TextureManager.h
  • src/Hooks.cpp
  • src/Types/Settings.h
💤 Files with no reviewable changes (1)
  • AGENTS.md

Comment on lines +24 to 31
void TextureManager::RegisterResidentMipOffset(IUnknown* resource, uint32_t mipOffset)
{
if (!resource || mipOffset == 0)
return;

std::scoped_lock lock(g_ResidentMipOffsetsMutex);
g_ResidentMipOffsets[resource] = mipOffset;
}

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 | ⚡ Quick win

Resident mip-offset registry can retain stale pointer entries.

Offsets are only removed on GetDescriptor consumption. If a registered resource never reaches descriptor creation, the raw-pointer key persists and can later be reused by another COM object, applying an incorrect mip offset.

Suggested mitigation
 void TextureManager::ReleaseTexture(RE::BSGraphics::Texture* texture)
 {
   if (!texture)
     return;

   IUnknown* key = nullptr;
@@
 `#endif`
     key = texture->texture;

+  {
+    std::scoped_lock lock(g_ResidentMipOffsetsMutex);
+    g_ResidentMipOffsets.erase(key);
+  }
+
   m_Textures.erase(key);
   m_NormalMaps.erase(key);
 }

Also applies to: 172-179

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/Core/TextureManager.cpp` around lines 24 - 31, The
RegisterResidentMipOffset function in TextureManager.cpp stores raw IUnknown
pointers as keys in the g_ResidentMipOffsets map, but these entries are only
removed when GetDescriptor consumes them. If a registered resource is destroyed
without reaching descriptor creation, the stale pointer key persists in the map.
When another COM object later reuses the same memory address, the old entry will
incorrectly apply a stale mip offset to the new object. Implement proper cleanup
by registering a release callback or using COM object lifecycle tracking (such
as implementing IUnknown::Release wrapping or using weak_ptr alternatives) to
remove entries from g_ResidentMipOffsets when resources are actually destroyed,
rather than deferring cleanup until descriptor consumption.

Comment on lines +97 to +103
standardBytes += texture->size;

if (texture->residentMipOffset > 0) {
streamedTextures++;
streamedBytes += texture->size;
}
}

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 | ⚡ Quick win

standard memory in the log currently includes streamed textures.

This makes the breakdown non-exclusive and easy to misread. Either rename the label or log non-streamed bytes explicitly.

Suggested fix
- uint64_t standardBytes = 0;
+ uint64_t standardBytes = 0;
+ uint64_t nonStreamedBytes = 0;
@@
   standardBytes += texture->size;

   if (texture->residentMipOffset > 0) {
     streamedTextures++;
     streamedBytes += texture->size;
+  } else {
+    nonStreamedBytes += texture->size;
   }
@@
- "TextureManager - RT texture memory: total={} MiB, standard={} MiB ({} textures), normal={} MiB ({} textures), streamed={} MiB ({} textures)",
+ "TextureManager - RT texture memory: total={} MiB, standard={} MiB ({} textures), normal={} MiB ({} textures), streamed={} MiB ({} textures)",
   (standardBytes + normalBytes) / (1024 * 1024),
-  standardBytes / (1024 * 1024),
+  nonStreamedBytes / (1024 * 1024),

Also applies to: 112-119

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/Core/TextureManager.cpp` around lines 97 - 103, The standardBytes counter
is accumulating all textures without excluding streamed ones, so streamed
textures are being counted in both standard and streamed categories. Modify the
code to only add to standardBytes when the texture is not a streamed texture by
adding a condition that excludes textures where residentMipOffset is greater
than 0. This will ensure the standard and streamed byte counts are mutually
exclusive and the log breakdown is accurate. Apply the same fix to the similar
code block referenced at lines 112-119.

Comment thread src/Hooks.cpp
Comment on lines +113 to +155
uint32_t GetTextureStreamingMipBias(const ExperimentalSettings& settings, uint32_t width, uint32_t height, uint32_t mipLevels, DXGI_FORMAT format)
{
if (settings.TextureStreamingMode == TextureStreamingMode::Off || mipLevels <= 1)
return 0;

const uint32_t maxDimension = std::max(width, height);
if (maxDimension < 1024)
return 0;

if (IsCriticalStreamingFormat(format))
return 0;

uint32_t desiredBias = 0;
const bool alphaCapable = IsAlphaCapableFormat(format);
switch (settings.TextureStreamingMode) {
case TextureStreamingMode::Conservative:
if (alphaCapable)
return 0;

desiredBias = maxDimension >= 4096 ? 1 : 0;
break;
case TextureStreamingMode::Balanced:
if (alphaCapable)
return 0;

desiredBias = maxDimension >= 4096 ? 2 : 1;
break;
case TextureStreamingMode::Aggressive:
desiredBias = maxDimension >= 4096 ? 3 : (maxDimension >= 2048 ? 2 : 1);
if (alphaCapable)
desiredBias = std::min(desiredBias, 1u);
break;
default:
break;
}

desiredBias = std::min(desiredBias, settings.TextureMaxMipBias);

if (!IsBlockCompressedFormat(format))
desiredBias = std::min(desiredBias, 1u);

return std::min(desiredBias, mipLevels - 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 | 🟠 Major | 🏗️ Heavy lift

TextureBudgetMB is currently a no-op in streaming decisions.

The streaming bias path reads mode and max bias, but never uses ExperimentalSettings::TextureBudgetMB, so memory budget configuration doesn’t influence residency behavior.

Also applies to: 327-341

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/Hooks.cpp` around lines 113 - 155, The GetTextureStreamingMipBias
function determines texture streaming bias but never uses the TextureBudgetMB
setting from ExperimentalSettings, making the memory budget configuration
ineffective. Integrate TextureBudgetMB into the bias calculation by computing an
estimated memory footprint for the texture at different mip levels, then adjust
the desiredBias upward as needed to keep memory consumption within the
configured budget limit. This adjustment should happen after the initial bias is
determined by TextureStreamingMode but before the final clamping operations,
ensuring the budget constraint overrides mode-based decisions when memory limits
would be exceeded.

jiayev added 6 commits July 5, 2026 21:40
This reverts commit e58b6c9.

	modified:   shaders/raytracing/GlobalIllumination/RayGeneration.hlsl
	modified:   shaders/raytracing/Pathtracing/RayGeneration.hlsl
	modified:   shaders/raytracing/include/Common.hlsli
	modified:   shaders/raytracing/include/Materials/BSDF.hlsli
	new file:   shaders/raytracing/include/SampleGenerators.hlsli

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

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

⚠️ Outside diff range comments (1)
shaders/raytracing/include/Common.hlsli (1)

1-1: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Split the Random() calls before constructing the vectors. HLSL doesn’t guarantee argument evaluation order, so float2(Random(seed), Random(seed)) and the similar float4(...) / float2(...) patterns can advance the shared seed in a compiler-dependent order. Use temporaries first in shaders/raytracing/include/Common.hlsli and shaders/raytracing/include/Materials/BSDF.hlsli.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@shaders/raytracing/include/Common.hlsli` at line 1, The Random() calls used
to construct float2/float4 values can mutate the shared seed in an unspecified
order. In the random-vector generation code in Common.hlsli and BSDF.hlsli,
evaluate each Random(seed) call into separate temporaries first, then construct
the vectors from those temporaries while preserving the existing component order
and behavior.
🧹 Nitpick comments (1)
shaders/raytracing/include/Common.hlsli (1)

91-100: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Random duplicates PCGHash's formula instead of calling it; PCGHash name is now stale.

PCGHash(seed) computes Hash32(seed + kRandomStepSalt), and Random's first line repeats that identical expression rather than calling PCGHash(seed). Also, since the body is now Hash32-based rather than a PCG hash, the PCGHash name is misleading to future readers/maintainers of either function.

♻️ Proposed refactor
 float Random(inout uint seed)
 {
-    seed = Hash32(seed + kRandomStepSalt);
+    seed = PCGHash(seed);
     return Hash32ToFloat(seed);
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@shaders/raytracing/include/Common.hlsli` around lines 91 - 100, Update Random
to reuse PCGHash(seed) for its seed transformation instead of duplicating the
Hash32 expression, then rename PCGHash to a name that accurately reflects its
Hash32-based behavior and update all references consistently.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@shaders/raytracing/include/Common.hlsli`:
- Around line 148-151: Update the seed-based SampleCosineHemisphere overload to
generate the two random values in separate sequential assignments before
constructing the float2, ensuring deterministic .x/.y ordering while preserving
the existing sampling call.

In `@shaders/raytracing/include/Materials/BSDF.hlsli`:
- Around line 1130-1140: In the SampleBSDF overload that accepts randomSeed,
split each Random(randomSeed) invocation into sequential statements before
constructing preGeneratedSamples and extraSamples. Preserve the existing
six-call order and pass the resulting scalar values into the vectors so
randomSeed side effects are deterministic across compilers and targets.

---

Outside diff comments:
In `@shaders/raytracing/include/Common.hlsli`:
- Line 1: The Random() calls used to construct float2/float4 values can mutate
the shared seed in an unspecified order. In the random-vector generation code in
Common.hlsli and BSDF.hlsli, evaluate each Random(seed) call into separate
temporaries first, then construct the vectors from those temporaries while
preserving the existing component order and behavior.

---

Nitpick comments:
In `@shaders/raytracing/include/Common.hlsli`:
- Around line 91-100: Update Random to reuse PCGHash(seed) for its seed
transformation instead of duplicating the Hash32 expression, then rename PCGHash
to a name that accurately reflects its Hash32-based behavior and update all
references consistently.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: e44ba3fa-3557-45c8-a356-ce0506516256

📥 Commits

Reviewing files that changed from the base of the PR and between 37db002 and 5af1cfc.

⛔ Files ignored due to path filters (2)
  • shaders/raytracing/GlobalIllumination/RayGeneration.hlsl is excluded by !**/*.hlsl
  • shaders/raytracing/Pathtracing/RayGeneration.hlsl is excluded by !**/*.hlsl
📒 Files selected for processing (10)
  • shaders/raytracing/include/Common.hlsli
  • shaders/raytracing/include/Materials/BSDF.hlsli
  • shaders/raytracing/include/SampleGenerators.hlsli
  • src/API.cpp
  • src/API.h
  • src/Core/Texture.h
  • src/Core/TextureManager.cpp
  • src/Core/TextureManager.h
  • src/Hooks.cpp
  • src/Types/Settings.h
🚧 Files skipped from review as they are similar to previous changes (6)
  • src/Core/Texture.h
  • src/API.cpp
  • src/API.h
  • src/Core/TextureManager.h
  • src/Core/TextureManager.cpp
  • src/Hooks.cpp

Comment on lines +148 to +151
float3 SampleCosineHemisphere(inout uint seed)
{
return SampleCosineHemisphere(float2(Random(seed), Random(seed)));
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== File list =="
git ls-files 'shaders/raytracing/include/Common.hlsli' 'shaders/**/SampleGenerators.hlsli' 'shaders/**/BSDF.hlsli' || true

echo
echo "== Common.hlsli around relevant lines =="
sed -n '1,220p' shaders/raytracing/include/Common.hlsli

echo
echo "== Search for Random(seed) constructor patterns =="
rg -n "Random\\(seed\\).*Random\\(seed\\)|float2\\(Random\\(seed\\), Random\\(seed\\)\\)|SampleCosineHemisphere\\(|SampleConeUniform\\(|UniformSampleNext2D|SampleSequenceNext2D" shaders -S || true

Repository: Gistix/CreationEngineRaytracing

Length of output: 7073


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
from pathlib import Path
path = Path('shaders/raytracing/include/Common.hlsli')
text = path.read_text()
for needle in [
    'float3 SampleCosineHemisphere(inout uint seed)',
    'float3 SampleConeUniform(inout uint seed',
    'uint Random(inout uint seed)',
    'float2 UniformSampleNext2D',
    'float2 SampleSequenceNext2D',
]:
    idx = text.find(needle)
    print(f'-- {needle} --')
    if idx == -1:
        print('not found')
        continue
    start = max(0, text.rfind('\n', 0, idx-200))
    end = min(len(text), text.find('\n\n', idx+1))
    print(text[idx: end if end != -1 else idx+600])
    print()
PY

Repository: Gistix/CreationEngineRaytracing

Length of output: 534


🌐 Web query:

HLSL function argument evaluation order unspecified inout parameter Random(seed)

💡 Result:

In HLSL, the order of function argument evaluation is unspecified, as it depends on the underlying compiler and the C++ ABI implementation [1][2][3]. Similar to C++, HLSL does not define a fixed order for parameter initialization or destruction [2][4]. For inout parameters, the behavior can vary based on the specific ABI in use [2]. For instance, in compilers using MSVC compatibility modes, arguments may be emitted right-to-left, whereas other ABIs (such as Itanium) may use a reversed order [2][5]. Because this order is not guaranteed by the language specification, relying on a specific evaluation order for side effects—such as multiple modifications to the same variable within a single function call—is unsafe and can lead to non-portable or undefined behavior [2]. Regarding the Random(seed) pattern frequently seen in HLSL, it is standard practice to pass the seed by inout to maintain the state of the pseudo-random number generator (PRNG) across multiple calls [6]. The typical implementation, such as Xorshift, modifies the seed variable directly [6]: uint Random(inout uint state) { state ^= (state << 13); state ^= (state >> 17); state ^= (state << 5); return state; } Because the order of argument evaluation is unspecified, you should avoid passing the same variable to multiple inout parameters in a single function call (e.g., Func(inout x, inout x)). Doing so relies on implementation-defined behavior regarding how and when these parameters are written back to the original argument [2][7]. Instead, perform such operations sequentially in separate statements to ensure predictable results.

Citations:


Split the two Random(seed) calls
float2(Random(seed), Random(seed)) mutates the same inout seed twice inside one expression. HLSL doesn’t guarantee argument evaluation order, so which random lands in .x vs .y can vary by compiler/backend. Use two sequential assignments before calling SampleCosineHemisphere(...).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@shaders/raytracing/include/Common.hlsli` around lines 148 - 151, Update the
seed-based SampleCosineHemisphere overload to generate the two random values in
separate sequential assignments before constructing the float2, ensuring
deterministic .x/.y ordering while preserving the existing sampling call.

Comment on lines 1130 to 1140
bool SampleBSDF(const BRDFContext brdfContext, const LightingMaterialData material, const Surface surface, out BSDFSample result, inout uint randomSeed)
{
return SampleBSDF(brdfContext, material.Feature, surface, result, randomSeed);
float4 preGeneratedSamples = float4(
Random(randomSeed),
Random(randomSeed),
Random(randomSeed),
Random(randomSeed)
);
float2 extraSamples = float2(Random(randomSeed), Random(randomSeed));
return SampleBSDF(brdfContext, material.Feature, surface, result, preGeneratedSamples, extraSamples);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the relevant file and the related helper mentioned in the review.
git ls-files 'shaders/raytracing/include/Materials/BSDF.hlsli' 'shaders/**/Common.hlsli' 'shaders/**/Common.hlsli' | sed 's#^`#FILE` #'

echo
echo "== BSDF.hlsli context =="
sed -n '1120,1145p' shaders/raytracing/include/Materials/BSDF.hlsli

echo
echo "== Common.hlsli search for the same pattern =="
rg -n "Random\\(randomSeed\\)" shaders -g '*.hlsli'

echo
echo "== Common.hlsli context =="
common_file="$(rg -l "SampleCosineHemisphere\\(inout uint\\)|Random\\(randomSeed\\)" shaders -g 'Common.hlsli' | head -n 1 || true)"
if [ -n "${common_file:-}" ]; then
  sed -n '130,170p' "$common_file"
fi

Repository: Gistix/CreationEngineRaytracing

Length of output: 4541


🌐 Web query:

HLSL argument evaluation order constructor function call random seed unspecified evaluation order Microsoft documentation

💡 Result:

In HLSL, the order in which function arguments are evaluated is unspecified [1]. While HLSL specifications mandate that argument expressions are evaluated before the function call occurs [2], the language does not define a specific order for the evaluation of those expressions [1]. Because this evaluation order is unspecified, code that relies on a specific sequence of side effects among arguments (such as expressions involving a random number generator or other state-changing functions) is considered non-portable and potentially dangerous [3]. Relying on a particular order can lead to inconsistent behavior across different compilers, shader models, or targets, as different implementation backends (such as those following MSVC or Itanium C++ ABI conventions) may evaluate and initialize parameters in different orders [3][4]. To ensure predictable and correct behavior, especially when using functions that produce side effects or rely on state (like random seed updates), you should explicitly separate these operations into distinct statements before the function call [1]. Top results: [3], [2], [4], [1]

Citations:


Split the RNG calls before constructing the sample vectors. HLSL leaves argument evaluation order unspecified, so Random(randomSeed) side effects inside float4(...) and float2(...) can produce different sample sequences across compilers and targets.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@shaders/raytracing/include/Materials/BSDF.hlsli` around lines 1130 - 1140, In
the SampleBSDF overload that accepts randomSeed, split each Random(randomSeed)
invocation into sequential statements before constructing preGeneratedSamples
and extraSamples. Preserve the existing six-call order and pass the resulting
scalar values into the vectors so randomSeed side effects are deterministic
across compilers and targets.

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.

1 participant