Skip to content

Commit 1d552ce

Browse files
committed
v1.3.9: D3D11 compute effects actually run now
Fixed - DispatchUserD3D11Compute early-returned when isCompiled() was false, but for D3D11 compute the bytecode is only populated inside that function. Chicken-and-egg meant Channel / Luminance / Chromaticity Statistics never compiled or dispatched. Removed the early-out. - D3D11ComputeRunner::CompileShader threw away the bytecode blob after creating the shader object; now caches it and exposes it via GetCompiledBytecode() so the host can persist it on the def for cbuffer reflection. - cbuffer pack code wrote raw float bit patterns into uint/int/bool slots. Now reflects each variable's D3D_SHADER_VARIABLE_TYPE and converts before memcpy, so if (Units == 1) works as written. - Analysis-only compute nodes froze their output fields after frame 1 because their own properties never change. Added topological dirty propagation pre-pass in both Evaluate and RenderFrame so video-frame updates flow through analysis nodes the same frame they're decoded. - Effect Designer LoadDefinition didn't restore the Output Type selector or the typed analysis-field rows. Refactored the field-row builder into a reusable helper and taught LoadDefinition to use it.
1 parent ef13322 commit 1d552ce

8 files changed

Lines changed: 210 additions & 63 deletions

CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,15 @@ Format follows [Keep a Changelog](https://keepachangelog.com/).
55

66
## [Unreleased]
77

8+
## [1.3.9] - 2026-05-05
9+
10+
### Fixed
11+
- **D3D11 compute effects (Channel / Luminance / Chromaticity Statistics) never compiled or dispatched.** `DispatchUserD3D11Compute` started with `if (!node.customEffect->isCompiled()) return;` — but `isCompiled()` checks `compiledBytecode.empty()`, and for D3D11 compute the bytecode is only ever populated *inside* this function via the lazy `runner->CompileShader` path. Chicken-and-egg: every call early-returned, no bytecode was ever stored, the shader never ran, and analysis-output fields stayed empty forever. Removed the early-out for that case so the runner can compile on first dispatch and persist the bytecode back to `def.compiledBytecode` for the cbuffer reflection pass.
12+
- **`D3D11ComputeRunner::CompileShader` discarded the compiled bytecode blob** after creating the `ID3D11ComputeShader` object, leaving `def.compiledBytecode` empty for D3D11 compute effects forever. Cached the blob in `m_bytecode` and exposed it via `GetCompiledBytecode()`; `DispatchUserD3D11Compute` now copies it onto `node.customEffect->compiledBytecode` after a successful compile so the cbuffer-reflection pass that packs user properties has something to reflect against.
13+
- **D3D11 compute cbuffers received raw float bit patterns where shaders declared `uint`/`int`/`bool`.** The Properties panel stores enum-style fields (e.g. `Units`, `ClipSource`) as `float`, but the HLSL declares `cbuffer { uint Units; ... }`. Doing `memcpy(dest, &floatVal, 4)` writes `0x3F800000` (1,065,353,216) into the uint slot, so `if (Units == 1)` was never true and conditional code paths were dead. The pack code now reflects each variable's `D3D_SHADER_VARIABLE_TYPE` and converts via `static_cast<uint32_t>` / `static_cast<int32_t>` based on the declared HLSL type before writing.
14+
- **Analysis-only compute nodes froze their output fields after the first frame.** `needsCompute` was only true on `node->dirty || fields.empty()`, and a Statistics node's own properties don't change frame-to-frame even as the upstream video does. Added a topological dirty-propagation pre-pass at the start of `Evaluate`: any node that's dirty marks all its direct downstream consumers dirty before the eval loop runs. Same pass is also done in `RenderFrame` after `TickAndUploadVideos`, so video-frame updates correctly flow through pixel-shader chains *and* analysis-only compute nodes in the same frame they're decoded. Both passes are idempotent — no-op when nothing is dirty.
15+
- **Effect Designer "Output type" + analysis fields were blank when opening a built-in ShaderLab effect** (or any saved custom effect with typed analysis output). `LoadDefinition` populated inputs / parameters / thread-groups but never restored the `OutputTypeSelector` selection or the analysis-field rows, so opening *Channel Statistics* showed no record of its declared `Min` / `Max` / `Mean` / etc. Refactored the per-row construction in the Add-Field click handler into a reusable `AppendAnalysisFieldRow()` method, then taught `LoadDefinition` to map `def.analysisOutputType` → selector index and rebuild one row per `def.analysisFields` entry with name / type / array length restored.
16+
817
## [1.3.8] - 2026-05-05
918

1019
### Added

EffectDesignerWindow.xaml.cpp

Lines changed: 111 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -40,57 +40,7 @@ namespace winrt::ShaderLab::implementation
4040
});
4141
AddAnalysisFieldButton().Click([this](auto&&, auto&&)
4242
{
43-
auto panel = AnalysisFieldsPanel();
44-
auto row = Controls::StackPanel();
45-
row.Orientation(Controls::Orientation::Horizontal);
46-
row.Spacing(4);
47-
48-
auto nameBox = Controls::TextBox();
49-
nameBox.PlaceholderText(L"Field Name");
50-
nameBox.MinWidth(120);
51-
row.Children().Append(nameBox);
52-
53-
auto typeCombo = Controls::ComboBox();
54-
typeCombo.Items().Append(box_value(L"Float"));
55-
typeCombo.Items().Append(box_value(L"Float2"));
56-
typeCombo.Items().Append(box_value(L"Float3"));
57-
typeCombo.Items().Append(box_value(L"Float4"));
58-
typeCombo.Items().Append(box_value(L"Float Array"));
59-
typeCombo.Items().Append(box_value(L"Float2 Array"));
60-
typeCombo.Items().Append(box_value(L"Float3 Array"));
61-
typeCombo.Items().Append(box_value(L"Float4 Array"));
62-
typeCombo.SelectedIndex(0);
63-
typeCombo.MinWidth(110);
64-
row.Children().Append(typeCombo);
65-
66-
auto lenBox = Controls::NumberBox();
67-
lenBox.Header(box_value(L"Length"));
68-
lenBox.Value(0);
69-
lenBox.Minimum(0);
70-
lenBox.Maximum(4096);
71-
lenBox.Width(80);
72-
lenBox.SpinButtonPlacementMode(Controls::NumberBoxSpinButtonPlacementMode::Compact);
73-
lenBox.Visibility(Visibility::Collapsed);
74-
row.Children().Append(lenBox);
75-
76-
// Show length only for array types.
77-
typeCombo.SelectionChanged([lenBox](auto&& sender, auto&&) {
78-
auto c = sender.template as<Controls::ComboBox>();
79-
lenBox.Visibility(c.SelectedIndex() >= 4
80-
? Visibility::Visible : Visibility::Collapsed);
81-
});
82-
83-
auto removeBtn = Controls::Button();
84-
removeBtn.Content(box_value(L"\xE711"));
85-
removeBtn.FontFamily(winrt::Microsoft::UI::Xaml::Media::FontFamily(L"Segoe Fluent Icons"));
86-
removeBtn.Click([row, panel](auto&&, auto&&) {
87-
uint32_t i = 0;
88-
if (panel.Children().IndexOf(row, i))
89-
panel.Children().RemoveAt(i);
90-
});
91-
row.Children().Append(removeBtn);
92-
93-
panel.Children().Append(row);
43+
AppendAnalysisFieldRow();
9444
});
9545

9646
HlslEditorBox().PreviewKeyDown([this](auto&&, Input::KeyRoutedEventArgs const& args)
@@ -170,6 +120,63 @@ namespace winrt::ShaderLab::implementation
170120
panel.Children().Append(row);
171121
}
172122

123+
winrt::Microsoft::UI::Xaml::Controls::StackPanel
124+
EffectDesignerWindow::AppendAnalysisFieldRow()
125+
{
126+
auto panel = AnalysisFieldsPanel();
127+
auto row = Controls::StackPanel();
128+
row.Orientation(Controls::Orientation::Horizontal);
129+
row.Spacing(4);
130+
131+
auto nameBox = Controls::TextBox();
132+
nameBox.PlaceholderText(L"Field Name");
133+
nameBox.MinWidth(120);
134+
row.Children().Append(nameBox);
135+
136+
auto typeCombo = Controls::ComboBox();
137+
typeCombo.Items().Append(box_value(L"Float"));
138+
typeCombo.Items().Append(box_value(L"Float2"));
139+
typeCombo.Items().Append(box_value(L"Float3"));
140+
typeCombo.Items().Append(box_value(L"Float4"));
141+
typeCombo.Items().Append(box_value(L"Float Array"));
142+
typeCombo.Items().Append(box_value(L"Float2 Array"));
143+
typeCombo.Items().Append(box_value(L"Float3 Array"));
144+
typeCombo.Items().Append(box_value(L"Float4 Array"));
145+
typeCombo.SelectedIndex(0);
146+
typeCombo.MinWidth(110);
147+
row.Children().Append(typeCombo);
148+
149+
auto lenBox = Controls::NumberBox();
150+
lenBox.Header(box_value(L"Length"));
151+
lenBox.Value(0);
152+
lenBox.Minimum(0);
153+
lenBox.Maximum(4096);
154+
lenBox.Width(80);
155+
lenBox.SpinButtonPlacementMode(Controls::NumberBoxSpinButtonPlacementMode::Compact);
156+
lenBox.Visibility(Visibility::Collapsed);
157+
row.Children().Append(lenBox);
158+
159+
// Show length only for array types.
160+
typeCombo.SelectionChanged([lenBox](auto&& sender, auto&&) {
161+
auto c = sender.template as<Controls::ComboBox>();
162+
lenBox.Visibility(c.SelectedIndex() >= 4
163+
? Visibility::Visible : Visibility::Collapsed);
164+
});
165+
166+
auto removeBtn = Controls::Button();
167+
removeBtn.Content(box_value(L"\xE711"));
168+
removeBtn.FontFamily(winrt::Microsoft::UI::Xaml::Media::FontFamily(L"Segoe Fluent Icons"));
169+
removeBtn.Click([row, panel](auto&&, auto&&) {
170+
uint32_t i = 0;
171+
if (panel.Children().IndexOf(row, i))
172+
panel.Children().RemoveAt(i);
173+
});
174+
row.Children().Append(removeBtn);
175+
176+
panel.Children().Append(row);
177+
return row;
178+
}
179+
173180
void EffectDesignerWindow::OnAddParam(
174181
winrt::Windows::Foundation::IInspectable const&,
175182
winrt::Microsoft::UI::Xaml::RoutedEventArgs const&)
@@ -1236,6 +1243,59 @@ namespace winrt::ShaderLab::implementation
12361243
ThreadGroupY().Value(def.threadGroupY);
12371244
ThreadGroupZ().Value(def.threadGroupZ);
12381245
}
1246+
1247+
// Output type + analysis fields. Map AnalysisOutputType -> selector
1248+
// index (None=0, Histogram=1, Float Buffer=2, Typed Analysis=3) so
1249+
// opening a built-in ShaderLab effect (or any saved custom effect)
1250+
// shows the same output configuration the developer originally
1251+
// declared, instead of the empty default.
1252+
{
1253+
int selIdx = 0;
1254+
switch (def.analysisOutputType)
1255+
{
1256+
case ::ShaderLab::Graph::AnalysisOutputType::None: selIdx = 0; break;
1257+
case ::ShaderLab::Graph::AnalysisOutputType::Histogram: selIdx = 1; break;
1258+
case ::ShaderLab::Graph::AnalysisOutputType::FloatBuffer: selIdx = 2; break;
1259+
case ::ShaderLab::Graph::AnalysisOutputType::Typed: selIdx = 3; break;
1260+
}
1261+
OutputTypeSelector().SelectedIndex(selIdx);
1262+
}
1263+
1264+
// Repopulate the Typed Analysis field rows from the definition,
1265+
// matching the manual-add layout (TextBox, ComboBox, NumberBox).
1266+
AnalysisFieldsPanel().Children().Clear();
1267+
if (def.analysisOutputType == ::ShaderLab::Graph::AnalysisOutputType::Typed)
1268+
{
1269+
for (const auto& fd : def.analysisFields)
1270+
{
1271+
auto row = AppendAnalysisFieldRow();
1272+
1273+
if (auto nameBox = row.Children().GetAt(0).try_as<Controls::TextBox>())
1274+
nameBox.Text(winrt::hstring(fd.name));
1275+
1276+
int typeIdx = 0;
1277+
switch (fd.type)
1278+
{
1279+
case ::ShaderLab::Graph::AnalysisFieldType::Float: typeIdx = 0; break;
1280+
case ::ShaderLab::Graph::AnalysisFieldType::Float2: typeIdx = 1; break;
1281+
case ::ShaderLab::Graph::AnalysisFieldType::Float3: typeIdx = 2; break;
1282+
case ::ShaderLab::Graph::AnalysisFieldType::Float4: typeIdx = 3; break;
1283+
case ::ShaderLab::Graph::AnalysisFieldType::FloatArray: typeIdx = 4; break;
1284+
case ::ShaderLab::Graph::AnalysisFieldType::Float2Array: typeIdx = 5; break;
1285+
case ::ShaderLab::Graph::AnalysisFieldType::Float3Array: typeIdx = 6; break;
1286+
case ::ShaderLab::Graph::AnalysisFieldType::Float4Array: typeIdx = 7; break;
1287+
}
1288+
if (auto typeCombo = row.Children().GetAt(1).try_as<Controls::ComboBox>())
1289+
typeCombo.SelectedIndex(typeIdx);
1290+
1291+
if (auto lenBox = row.Children().GetAt(2).try_as<Controls::NumberBox>())
1292+
lenBox.Value(static_cast<double>(fd.arrayLength));
1293+
}
1294+
}
1295+
AnalysisFieldsSection().Visibility(
1296+
def.analysisOutputType == ::ShaderLab::Graph::AnalysisOutputType::Typed
1297+
? Visibility::Visible : Visibility::Collapsed);
1298+
12391299
// If no source exists (built-in effects that use hardcoded paths),
12401300
// generate a scaffold so the developer sees representative HLSL.
12411301
std::wstring hlslToShow = def.hlslSource;

EffectDesignerWindow.xaml.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,12 @@ namespace winrt::ShaderLab::implementation
4646
winrt::Windows::Foundation::IInspectable const& sender,
4747
winrt::Microsoft::UI::Xaml::RoutedEventArgs const& args);
4848

49+
// Append one Typed Analysis field row to AnalysisFieldsPanel and
50+
// return it. Used by both the "+ Add Field" button click handler
51+
// and LoadDefinition (when restoring fields from a saved or
52+
// built-in effect definition).
53+
winrt::Microsoft::UI::Xaml::Controls::StackPanel AppendAnalysisFieldRow();
54+
4955
// Build a CustomEffectDefinition from the current UI state.
5056
::ShaderLab::Graph::CustomEffectDefinition BuildDefinition();
5157

Package.appxmanifest

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
<Identity
1010
Name="ShaderLab"
1111
Publisher="CN=ShaderLab"
12-
Version="1.3.8.0" />
12+
Version="1.3.9.0" />
1313

1414
<mp:PhoneIdentity PhoneProductId="a1b2c3d4-e5f6-7890-abcd-ef1234567890" PhonePublisherId="00000000-0000-0000-0000-000000000000"/>
1515

Rendering/D3D11ComputeRunner.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ namespace ShaderLab::Rendering
1313
bool D3D11ComputeRunner::CompileShader(const std::string& hlslSource)
1414
{
1515
m_shader = nullptr;
16+
m_bytecode.clear();
1617
m_compileError.clear();
1718

1819
winrt::com_ptr<ID3DBlob> blob, errors;
@@ -47,6 +48,10 @@ namespace ShaderLab::Rendering
4748
return false;
4849
}
4950

51+
// Stash bytecode so callers can run D3DReflect for cbuffer layout.
52+
const auto* src = static_cast<const uint8_t*>(blob->GetBufferPointer());
53+
m_bytecode.assign(src, src + blob->GetBufferSize());
54+
5055
return true;
5156
}
5257

Rendering/D3D11ComputeRunner.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,18 @@ namespace ShaderLab::Rendering
2525

2626
// Compile a compute shader from HLSL source.
2727
// Returns true on success. Call GetCompileError() on failure.
28+
// On success, GetCompiledBytecode() returns the bytecode blob —
29+
// the caller should store it on the CustomEffectDefinition so
30+
// downstream cbuffer reflection (in DispatchUserD3D11Compute)
31+
// can pack user properties at the correct offsets.
2832
bool CompileShader(const std::string& hlslSource);
2933

3034
// Get the last compile error message.
3135
const std::wstring& GetCompileError() const { return m_compileError; }
3236

37+
// Get the compiled bytecode (empty until CompileShader succeeds).
38+
const std::vector<uint8_t>& GetCompiledBytecode() const { return m_bytecode; }
39+
3340
// Dispatch the compiled shader on an input texture.
3441
// cbufferData: user constant buffer bytes (Width/Height prepended automatically).
3542
// resultCount: number of float4 elements in the result buffer.
@@ -46,6 +53,7 @@ namespace ShaderLab::Rendering
4653
winrt::com_ptr<ID3D11Device> m_device;
4754
winrt::com_ptr<ID3D11DeviceContext> m_context;
4855
winrt::com_ptr<ID3D11ComputeShader> m_shader;
56+
std::vector<uint8_t> m_bytecode;
4957

5058
// Result buffer (RWStructuredBuffer<float4>)
5159
winrt::com_ptr<ID3D11Buffer> m_resultBuffer;

0 commit comments

Comments
 (0)