<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Shijian's blog]]></title><description><![CDATA[Game development]]></description><link>https://blog.catoxliu.net</link><generator>RSS for Node</generator><lastBuildDate>Tue, 15 Sep 2026 03:03:29 GMT</lastBuildDate><atom:link href="https://blog.catoxliu.net/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Texture binding bug for Unreal Engine on Mac/IOS (Metal)]]></title><description><![CDATA[As a recent change in our Vertex Factory, we add a texture binding there along with some other structured buffers. Everything works perfectly fine on Windows but failed startup on MacOS with the error]]></description><link>https://blog.catoxliu.net/texture-binding-bug-for-unreal-engine-on-mac-ios-metal</link><guid isPermaLink="true">https://blog.catoxliu.net/texture-binding-bug-for-unreal-engine-on-mac-ios-metal</guid><category><![CDATA[unreal-engine]]></category><category><![CDATA[Rendering]]></category><category><![CDATA[macOS]]></category><category><![CDATA[iOS]]></category><dc:creator><![CDATA[sj]]></dc:creator><pubDate>Fri, 05 Jun 2026 04:13:56 GMT</pubDate><content:encoded><![CDATA[<p>As a recent change in our Vertex Factory, we add a texture binding there along with some other structured buffers. Everything works perfectly fine on Windows but failed startup on MacOS with the error "Shader TDepthOnlyVS with vertex factory MyVertexFactory never set texture at BaseIndex 0."</p>
<p>At the very beginning, I thought it's a regression issue we had before that <a href="https://blog.catoxliu.net/custom-render-passes-for-unreal-engine-on-ios#:~:text=Which%20is%20very%20wired,uint%3E%20in%20my%20shader.">a buffer is wrongly interpret as texture on Mac</a>. But a quick check on the compiled shader shows nothing is wrong, the only texture(0) there is the new texture we added. And the debugger shows nothing wrong here, no nullptr, no silence failure and no skip logics. When I checked all the possibilities, I realised this could be a bug of the Engine itself, I have to dig deeper.</p>
<p>First start from where it should be supposed to bind the texture:</p>
<pre><code class="language-cpp">ShaderBindings.AddTexture(
	MyTexture2D,
	MyTexture2DSampler,
	TStaticSamplerState&lt;SF_Bilinear&gt;::GetRHI(),
	UserData-&gt;MyTexture2D);
</code></pre>
<p>and follow that step by step it goes here:</p>
<pre><code class="language-cpp">inline void WriteBindingTexture(FRHITexture* Value, uint32 BaseIndex)
{
	int32 FoundIndex = FindSortedArrayBaseIndex(MakeArrayView(ParameterMapInfo.SRVs), BaseIndex);
	if (FoundIndex &gt;= 0)
	{
        GetSRVStart()[FoundIndex] = Value;
    }
}
</code></pre>
<p>And the BaseIndex here is 0, and the metal shader is "[[texture(0)]]", seems all good.</p>
<p>But then I notice that for the structured buffer</p>
<pre><code class="language-cpp">ShaderBindings.Add(MyBuffer, UserData-&gt;MyBuffer);
</code></pre>
<p>it calls this function</p>
<pre><code class="language-cpp">inline void WriteBindingSRV(FRHIShaderResourceView* Value, uint32 BaseIndex)
{
	int32 FoundIndex = FindSortedArrayBaseIndex(MakeArrayView(ParameterMapInfo.SRVs), BaseIndex);
	if (FoundIndex &gt;= 0)
	{
        uint32 TypeByteIndex = FoundIndex / 8;
        uint32 TypeBitIndex = FoundIndex % 8;
        GetSRVTypeStart()[TypeByteIndex] |= 1 &lt;&lt; TypeBitIndex;
        GetSRVStart()[FoundIndex] = Value;
    }
}
</code></pre>
<p>and for my very first structured buffer there, the BaseIndex is also 0! I double checked to make sure that they get exactly the same FoundIndex here, so the latter one is basically overwriting the former one. This is absolute wrong, but I still not sure one thing. Why even the AddTexture happened later than Add buffer, it still report no texture binding?</p>
<p>The answer is in the <code>FMeshDrawShaderBindings::SetShaderBindings</code></p>
<pre><code class="language-cpp">for (uint32 SRVIndex = 0; SRVIndex &lt; NumSRVs; SRVIndex++)
{
	FShaderResourceParameterInfo Parameter = SRVParameters[SRVIndex];

	uint32 TypeByteIndex = SRVIndex / 8;
	uint32 TypeBitIndex = SRVIndex % 8;

	if (SRVType[TypeByteIndex] &amp; (1 &lt;&lt; TypeBitIndex))
	{
		FRHIShaderResourceView* SRV = (FRHIShaderResourceView*)SRVBindings[SRVIndex];
		SetSrvParameter(BatchedParameters, Parameter, SRV);
	}
	else
	{
		FRHITexture* Texture = (FRHITexture*)SRVBindings[SRVIndex];
		SetTextureParameter(BatchedParameters, Parameter, Texture);
	}
}
</code></pre>
<p>It's because the SRVType bit flag, even the AddTexture is called later, because it didn't change the bit flag, in the bindings it still think it's a buffer!</p>
<p>Ok, so now we know the cause is because of the <strong>BaseIndex collision</strong>, but why and how could it only affect my vertex factory, and I have all other shaders work fine. Then I realized that for the vertex factory, we're using the old way to binding the shader parameters, and for all other shaders, use <code>SHADER_USE_PARAMETER_STRUCT</code> which calls <code>FShaderParameterBindings</code>. Resources are recorded as <code>{BaseIndex, ByteOffset, BaseType}</code> in <code>ResourceParameters[]</code> _ keyed by <strong>name</strong>, and then consumed in <code>RHISetShaderParametersShared</code>.</p>
<p>Before moving on, I realized that there's a work around for this situation, put the texture into a uniform buffer to avoid the BaseIndex collision. And in fact that's what the Virtual Heightfield Mesh plugin does for their vertex factory and works fine.</p>
<p>While since I'm already here I would like to figure out why there's a BaseIndex collision in the first place and try a fix if possible. And after some learning I know that on D3D, the structured buffer and texture shared the same descriptor table "t#" (shader resource views), but on Metal, [[buffer(N)]] and [[texture(N)]] are separate namespaces.</p>
<p>And then in MetalCompileShaderSPIRV, After SPIR-V reflection, resources are sorted into typed lists:</p>
<pre><code class="language-plaintext">ReflectionBindings.SBufferSRVs     structured buffers  (uses BufferIndices)
ReflectionBindings.TextureSRVs     textures            (uses TextureIndices)
</code></pre>
<p>But <strong>both</strong> call the same output function:</p>
<pre><code class="language-cpp">// SBufferSRVs loop (line 1108):
CCHeaderWriter.WriteSRV(UTF8_TO_TCHAR(Binding-&gt;name), Index);  // Index = 0, 1, 2...

// TextureSRVs loop (line 1214):
CCHeaderWriter.WriteSRV(UTF8_TO_TCHAR(Binding-&gt;name), Index);  // Index = 0 //collides!
</code></pre>
<p><code>WriteSRV</code> is the <strong>loss point</strong>. It concatenates into a single string <code>Strings.SRVs</code> with no type tag:</p>
<pre><code class="language-cpp">// HlslccHeaderWriter.cpp
void FHlslccHeaderWriter::WriteSRV(const TCHAR* ResourceName, uint32 BindingIndex, uint32 Count)
{
    MetaDataPrintf(Strings.SRVs, TEXT("%s(%u:%u)"), ResourceName, BindingIndex, Count);
}
</code></pre>
<p>And then in MetalShaderCompiler.cpp, Parsing Without Type Awareness</p>
<p>The annotation is printed as:</p>
<blockquote>
<p>@Samplers: MyBuffer(0:1),...,MyTexture2D(0:1)</p>
</blockquote>
<p>The <code>CCHeader</code> parser reads this into <code>FSampler</code> structs &lt;80&gt;&lt;94&gt; which have <strong>no type field</strong>:</p>
<pre><code class="language-cpp">struct FSampler {
    FString Name;
    int32 Offset;     // slot number from annotation
    int32 Count;       // bind count
    TArray&lt;FString&gt; SamplerStates;  // only for combined image samplers
    // no "is this a buffer or texture?" field
};
</code></pre>
<p>Then the same handler processes everything identically:</p>
<pre><code class="language-cpp">// MetalShaderCompiler.cpp:526
for (auto&amp; Sampler : CCHeader.Samplers)
    HandleReflectedShaderResource(Sampler.Name, Sampler.Offset, Sampler.Count, Output);
//  AddParameterAllocation(Name, BindOffset=0, BaseIndex=Offset, ...)
</code></pre>
<p>By this point, <code>MyBuffer</code> (structured buffer) and <code>MyTexture2D</code> (texture) are indistinguishable both <code>{BufferIndex=0, BaseIndex=0}</code>.</p>
<p>So now we have the full picture, and then the question is how to fix it. In the first look, it seems that a simple fix made in SPIR-V cross-compiler by shifting the annotation index for textures past buffer slots, so every resource gets a unique <code>BaseIndex</code> in the ParameterMap. The it broke everything else because the slot in the shader won't changed.</p>
<p>Then I noticed that the <code>BufferIndex</code> and `FSampler::SamplerStates` are never used here, so we can change the annotation format + parsing. Add <code>BufferIndex</code> disambiguation through the annotation (<code>@SRV_Buffers:</code> vs <code>@SRV_Textures:</code>) and pass distinct <code>BindOffset</code> values. But that is not enough, even with different <code>BufferIndex</code>, <code>FindSortedArrayBaseIndex</code> still returns the same slot. Because:</p>
<pre><code class="language-cpp">  // Shader.h _ only compares BaseIndex
  bool operator&lt;(const FShaderResourceParameterInfo&amp; Rhs) const {
      return BaseIndex &lt; Rhs.BaseIndex;  // ignores BufferIndex!
}
</code></pre>
<p>To make BufferIndex work as the disambiguator, we'd also need to change <code>operator&lt;</code> and <code>FindSortedArrayBaseIndex</code> to compare <code>BufferIndex</code>. That touches Shader.h, Shader.cpp, and MeshDrawShaderBindings.h _ three files. Which seems too a bit too risky for the project.</p>
<p>Finally I realized there could be an easy low-risk fix, just in <code>WriteBindingTexture</code> and <code>WriteBindingSRV</code>, do a check to make sure the <code>FoundIndex</code> isn't bounding to other resource yet:</p>
<pre><code class="language-cpp">	inline void WriteBindingTexture(FRHITexture* Value, uint32 BaseIndex)
	{
		int32 FoundIndex = FindSortedArrayBaseIndex(MakeArrayView(ParameterMapInfo.SRVs), BaseIndex);
		if (FoundIndex &gt;= 0)
		{
#if PLATFORM_IOS || PLATFORM_MAC
			// Metal flattens buffer and texture SRVs into one sorted array, so multiple entries
			// can share the same BaseIndex. Skip already-written slots to avoid overwriting.
			const FShaderResourceParameterInfo * RESTRICT SRVParams = ParameterMapInfo.SRVs.GetData();
			const uint32 NumSRVs = ParameterMapInfo.SRVs.Num();
			while ((uint32)FoundIndex &lt; NumSRVs &amp;&amp; SRVParams[FoundIndex].BaseIndex == BaseIndex &amp;&amp; GetSRVStart()[FoundIndex] != nullptr)
			{
				FoundIndex++;
			}
			if ((uint32)FoundIndex &lt; NumSRVs &amp;&amp; SRVParams[FoundIndex].BaseIndex == BaseIndex)
			{
				GetSRVStart()[FoundIndex] = Value;
			}
#else
			GetSRVStart()[FoundIndex] = Value;
#endif
		}
</code></pre>
<p>And because the structured buffer having a type bit flag while texture doesn't, it means the order (which one take the first 0 BaseIndex) doesn't matter!</p>
<p>Finally I get my vertex factory working again on Mac and IOS : )</p>
]]></content:encoded></item><item><title><![CDATA[Start building a local workflow using Pi.dev]]></title><description><![CDATA[Pi.dev is exactly the coding agent I want to try and use in real work, simple and powerful. So I start to build a local workflow around it.
Safety first, I'm not confident enough to run Pi on my main ]]></description><link>https://blog.catoxliu.net/start-building-a-local-workflow-using-pi-dev</link><guid isPermaLink="true">https://blog.catoxliu.net/start-building-a-local-workflow-using-pi-dev</guid><dc:creator><![CDATA[sj]]></dc:creator><pubDate>Tue, 19 May 2026 04:35:39 GMT</pubDate><content:encoded><![CDATA[<p><a href="http://Pi.dev">Pi.dev</a> is exactly the coding agent I want to try and use in real work, simple and powerful. So I start to build a local workflow around it.</p>
<p>Safety first, I'm not confident enough to run Pi on my main PC which has access to all my private info and so on, and I'm lucky to have a spare machine to host it.</p>
<p>Also I don't want it to access the company git server directly (well, even I want I have no auth to do it :). So a gitea server host in my local docker and Pi will only work against that.</p>
<p>And before we starting the real job, I set several rules about the project and also let it read the Unreal official C++ coding standard and make them part of the promotion. Since I have lots of powershell scripts ready to use for the project, it makes things much easier to start.</p>
<p>One interesting thing is that while I tried using "pi install" it failed due to some conflict about the NVM for Windows issue. As before, I google for a quick solution or work around, but failed. So instead, I simply asked Pi to fix the issue by itself, and it's done in one shot. Pretty impressive and give me more confidence to rely on it to accomplish complex stuffs.</p>
<p>After that, I asked Pi to create a PR once it finished the job, and also register a webhook for any comments on its own PR and try to resolve the reviews automatically. There're some back and forth, but nothing really blocked me.</p>
<p>Well after a short afternoon, now I have an agent intern who can do some simple jobs as requested and I could just review and approve on my gitea. Very interesting and can't wait to explore more.</p>
]]></content:encoded></item><item><title><![CDATA[Missing Scene Depth for Custom Vertex Factories in Unreal Engine]]></title><description><![CDATA[As last time I started using the vertex factory to consume the instance buffer by drawing indexed primitive with indirect buffer, some issues have been found which is interesting (struggling).
Firstly]]></description><link>https://blog.catoxliu.net/missing-scene-depth-for-custom-vertex-factories-in-unreal-engine</link><guid isPermaLink="true">https://blog.catoxliu.net/missing-scene-depth-for-custom-vertex-factories-in-unreal-engine</guid><category><![CDATA[unreal-engine]]></category><dc:creator><![CDATA[sj]]></dc:creator><pubDate>Wed, 25 Feb 2026 05:01:59 GMT</pubDate><content:encoded><![CDATA[<p>As last time I started using the vertex factory to consume the instance buffer by drawing indexed primitive with indirect buffer, some issues have been found which is interesting (struggling).</p>
<p>Firstly, it's the missing scene depth for the generated mesh, even the material is set to opaque and as simple as possible. Without the depth there, the overlap meshes will have z-fighting all the time which is unacceptable.</p>
<p>I checked and play around with the vertex factory shader, the FPrimitiveViewRelevance and FMeshBatch configurations and nothing works.</p>
<p>Then I tried the material itself, and do find a workaround for it, setting a small number to the "Pixel Depth Offset" seems force it render to scene depth again. Which gives me a clue, so I follow it up in the render pipeline. So without the "Pixel Depth Offset", the vertex factory's indirect draw will disappear in <code>PrePass DDM_AllOpaque</code> 's depth pass. it's because the engine default shader <code>DepthOnlyVertexShader</code> is used in this case and for our vertex factory shader map, it's missing, and then the <code>DepthRendering::GetDepthPassShaders</code> will early return false.</p>
<p>Once find the root cause, the fix is easy, just make sure add the default shader in the vertex factory permutation using <code>MaterialParameters.bIsSpecialEngineMaterial</code></p>
<pre><code class="language-cpp">bool FVertexFactory::ShouldCompilePermutation(const FVertexFactoryShaderPermutationParameters&amp; Parameters)
{
	return MyVertexFactoryPermutation() || Parameters.MaterialParameters.bIsSpecialEngineMaterial;
}
</code></pre>
<p>Then the next issue is while moving the camera around or turn on some vertex animation for the vertex factory generated mesh, part of the mesh will become black and some yellow text behind it saying:</p>
<p><mark class="bg-yellow-200 dark:bg-yellow-500/30">"Your scene contains a skydome mesh with a sky material, but it does not cover that part of the screen..."</mark></p>
<p>not my game, image from internet:</p>
<img src="https://cdn.hashnode.com/uploads/covers/626dfd3d454011df6e86c25d/b8aab285-76a4-4b30-b5f9-307ca805917f.png" alt="" style="display:block;margin:0 auto" />

<p>So I believe it's some corrupted buffer for my vertex factory, and I noticed that without the scene depth buffer fix, it's not showing up. After reading the compiled shader I realised that the default <code>DepthOnlyVertexShader</code> still needs call the vertex factory <code>GetVertexFactoryIntermediates</code>, which consume the instance buffer. But the instance buffer is written by a compute shader after the PrePass there. So a classical race condition : )</p>
<p>Then moving that compute shader pass before the PrePass get everyhing works as expected with the vertex factory now.</p>
]]></content:encoded></item><item><title><![CDATA[Custom Render Passes for Unreal Engine on iOS]]></title><description><![CDATA[Once I get the custom render pass for Unreal working fine on Windows, I started the painful journey to make it working on mobile (iOS to be specific).
To simplify, the custom render pass has two parts, the first one is computing shader to generate th...]]></description><link>https://blog.catoxliu.net/custom-render-passes-for-unreal-engine-on-ios</link><guid isPermaLink="true">https://blog.catoxliu.net/custom-render-passes-for-unreal-engine-on-ios</guid><category><![CDATA[unreal-engine]]></category><category><![CDATA[iOS]]></category><category><![CDATA[Rendering]]></category><dc:creator><![CDATA[sj]]></dc:creator><pubDate>Mon, 16 Feb 2026 04:14:10 GMT</pubDate><content:encoded><![CDATA[<p>Once I get the custom render pass for Unreal working fine on Windows, I started the painful journey to make it working on mobile (iOS to be specific).</p>
<p>To simplify, the custom render pass has two parts, the first one is computing shader to generate the instance data in the pre render base pass and then draw these instances in the base pass. For destop platform, it’s very straight forward to add the two custom passes into <code>FDeferredShadingSceneRenderer::Render</code>.</p>
<p>For the mobile platform, the compute pass added into <code>FMobileSceneRenderer::Render</code> and draw pass added into <code>FMobileSceneRenderer::RenderDeferredMultiPass</code>, which works as expected in Unreal editor preview on both Windows and Mac.</p>
<p>But while testing on iOS, it’s not working. Going into the GPU debug in XCode shows that the compute pass is working fine but the draw pass is never called. And then I learnt that for iOS, it’s using <code>FMobileSceneRenderer::RenderDeferredSinglePass</code>.</p>
<p>But adding the draw pass there after the <code>SceneColorRendering</code> pass is still not working on device and GPU debug shows that the draw pass is correctly run and the SceneColor/GBuffer result is as expected. The problem is that it’s too late, because SceneColor/GBuffer is already consumed for back buffer in the <code>SceneColorRendering</code> pass previously (yeah, it’s called for <code>RenderDeferredSinglePass</code> for a reason : ). So the correct way is to make the draw pass a subpass inside the <code>SceneColorRendering</code> pass. Or a better solution is to use unreal vertex factory for drawing our instances, more powerful and flexible, and also it will be handled by <code>FMobileBasePassMeshPassProcessor</code>, which then will be part of the subpass there.</p>
<p>A quick test with a simple vertex factory shows promising result, so I start to fully implement all our features into the vertex factory and then I start getting a crash error while starting the game.</p>
<blockquote>
<p>[UE] Ensure condition failed: Texture [File:./Runtime/Renderer/Private/MeshPassProcessor.cpp] [Line: 828] Shader TBasePassVSFNoLightMapPolicy with vertex factory FMyCustomVertexFactory never set texture at BaseIndex 0. This can cause GPU hangs, depending on how the shader uses it.</p>
</blockquote>
<p>Which is very wired because I don’t have any texture parameters in my shader. But surprisingly, after checking the compiled shader file for METAL, it turns out somehow my <code>Buffer&lt;uint&gt; ReserveBuffer</code> is translated to <code>texture_buffer ReservedBuffer [[texture(0)]]</code>! Some searching saying this is expected behavior, so for now my fix is to use StructuredBuffer&lt;uint&gt; in my shader.</p>
<p>Now, no crash or errors, but one feature is not working correctly on iOS. And I notice the data structure of this feature is not aligned with 16-bytes.</p>
<pre><code class="lang-cpp"> <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">FMyDataStructure</span> 
{</span>
    FVector3f Pos;
    FVector3f UpVector;
    <span class="hljs-keyword">float</span> Height;
    <span class="hljs-keyword">float</span> Radius;
}
</code></pre>
<p>So change it into</p>
<pre><code class="lang-cpp"><span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">FMyDataStructure</span> 
{</span>
    FVector4f PosAndHeight;    <span class="hljs-comment">// .xyz = Pos, .w = Height</span>
    FVector4f UpVectorAndRadius; <span class="hljs-comment">// .xyz = UpVector, .w = Radius</span>
};
</code></pre>
<p>And now everything works on iOS : )</p>
]]></content:encoded></item><item><title><![CDATA[Flickering issue with Indexed Indirect Draw]]></title><description><![CDATA[In previous post I have the procedual grass rendering working with the IndexedIndirectDraw, but while testing it, I find there’re flickering issue sometimes. And after playing with it for a while I ca]]></description><link>https://blog.catoxliu.net/flickering-issue-with-indexed-indirect-draw</link><guid isPermaLink="true">https://blog.catoxliu.net/flickering-issue-with-indexed-indirect-draw</guid><category><![CDATA[unreal-engine]]></category><category><![CDATA[GPU]]></category><category><![CDATA[NVIDIA]]></category><dc:creator><![CDATA[sj]]></dc:creator><pubDate>Tue, 20 Jan 2026 02:33:33 GMT</pubDate><content:encoded><![CDATA[<p>In <a href="https://hashnode.com/post/cmk3ahsli000002jvdpxja7om">previous post</a> I have the procedual grass rendering working with the IndexedIndirectDraw, but while testing it, I find there’re flickering issue sometimes. And after playing with it for a while I can create an edge case with specific number of the size of my instance buffer, which making over 1/3 of the grasses flickering constantly.</p>
<p>Now I have a perfect test case, and I start debugging it closely. The first thing I noticed is that in the RenderDoc, and in the mesh viewer of my VS pass shows the output SV_Position is shown as NaN (Not a Number) for the almost 1/3 of the instance buffer. The interesting part is that if I debug it step by step, everything just works fine as expected and we got a solid position there. Then I learnt that RenderDoc debug shader is a “simulation” on CPU side, and it cannot guarantee the result match what’s on GPU.</p>
<p>Then I thought it could be some sync issue of the instance buffer or the indirect arguments buffer, wasting a day working on it trying different buffer setup, explicit buffer transition and memory barrieers, no luck.</p>
<p>Ruling out the sync issue, I spent another day to verifying my compute pass to make sure all the data is generated correctly without any surprises.</p>
<p>As I almost gave up, I asked AI if my structure of the instance buffer is cursed : ( but surprisingly, it notice the culprit immediately : )</p>
<p>The real trouble began with a struct that looked like this:</p>
<pre><code class="language-cpp">struct FData {
    float3 Position;
    float Width;
    // ... other floats ...
    int Payload;
    float4 ControlPos0; // &lt;-- The Saboteur
};
</code></pre>
<p>The structure is 92 bytes in total.</p>
<p>And according to google, it seems that (for NVIDIA specifically), a <code>float4</code> isn't just four floats—it is a <strong>16-byte vector entity</strong>.</p>
<p>And this seems to be the root cause that some of the instance buffer will read "invalid data".</p>
<p><strong>The Fix: Manual Padding is Your Friend</strong></p>
<p>In fact, in latest <a href="https://github.com/EpicGames/UnrealEngine/blob/0b917fe1ab67ca45e1233a866c92e791fc451ef8/Engine/Source/Runtime/D3D12RHI/Private/D3D12Buffer.cpp#L420">Unreal Engine</a> (5.7), they add a new variable "RHI_RAW_VIEW_ALIGNMENT" to check if your structured buffer stride is <code>LeastCommonMultiplier</code> of it while creating the buffer.</p>
<p>So for my case, to fix it, we had to add a simple <code>float Padding;</code> after our <code>int Payload;</code>, we manually aligned to the 16-byte boundary. And then the flickering issue goes away.</p>
<p><strong>The ByteAddressBuffer Escape Hatch</strong></p>
<p>And some search result shows there's other work around for this, but I didn't test it.</p>
<p>If you’re tired of fighting with alignment, there is the <strong>ByteAddressBuffer</strong>. It treats memory as raw bytes, skipping the "smart" auto-padding of Structured Buffers. It's more manual, but it's the ultimate way to ensure what you send is exactly what you get.</p>
]]></content:encoded></item><item><title><![CDATA[An issue with indexed indirect draw in Unreal]]></title><description><![CDATA[Long story short, I’m working on a shader for an indexed indirect draw in Unreal. And I had a VS-To-PS struct here as simple as:
struct VertexOutput
{
  float4 Position : SV_POSITION;
}

And this works just perfect fine.
But things get buggy once I a...]]></description><link>https://blog.catoxliu.net/an-issue-with-indexed-indirect-draw-in-unreal</link><guid isPermaLink="true">https://blog.catoxliu.net/an-issue-with-indexed-indirect-draw-in-unreal</guid><category><![CDATA[unreal-engine]]></category><category><![CDATA[shader]]></category><dc:creator><![CDATA[sj]]></dc:creator><pubDate>Wed, 07 Jan 2026 00:38:01 GMT</pubDate><content:encoded><![CDATA[<p>Long story short, I’m working on a shader for an indexed indirect draw in Unreal. And I had a VS-To-PS struct here as simple as:</p>
<pre><code class="lang-cpp"><span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">VertexOutput</span>
{</span>
  float4 Position : SV_POSITION;
}
</code></pre>
<p>And this works just perfect fine.</p>
<p>But things get buggy once I add another variable into the struct, which is still very simple</p>
<pre><code class="lang-cpp"><span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">VertexOutput</span>
{</span>
  float4 Position : SV_POSITION;
  float4 Test : TEXCOORD0;
}
</code></pre>
<p>The render result shows that the position data is still right, but “test” data is always default.</p>
<p>I have no idea what’s wrong so goes RenderDoc to check step by step, and everything seems just working until the PS stage as the input from VS is all default.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1767743714477/c7c20212-637d-4ed3-af14-eb99d5b30145.png" alt class="image--center mx-auto" /></p>
<p>But I double check the output of the VS which is definitely fine, and also, the render result position is right.</p>
<p>Drop all the shaders into AI and ask why it happened, and it can hardly understand the real issue, other than giving possible solutions. So I simplified the question as “what possible causes for a PS only get default output from VS?”, and then I can narrow it down to the “VS-To-PS struct mismatch“. While my struct is so simple I cannot tell how it could mismatch, I suddenly realize it could be the unreal shader complier.</p>
<p>Get into the RenderDoc again and check the VS there, and my guess is correct, in the compiled VS shader, the SV_POSITION is reordered to last position in the struct, but no changes for PS shader. That’s why I get a mismatch here.</p>
<p>A quick search in the Unreal Engine source code shows it’s a bug introduced a while ago which only affect indexed indirect draw and fixed later for the shader compiler, and I’m the unlucky guy who is not using the latest version…</p>
<p>Again this is another disappointing experience using AI while coding with shader in Unreal, another case is months ago while I turned on shader debugging the editor cannot start due to shader validation failed:</p>
<pre><code class="lang-cpp"><span class="hljs-function">Get a shader compilation error when I disable shader optimize <span class="hljs-keyword">for</span> current <span class="hljs-title">project</span> <span class="hljs-params">(Unreal <span class="hljs-number">5.3</span>)</span>
5 Shader compiler errors compiling global shaders <span class="hljs-keyword">for</span> platform PCD3D_SM6:
Shader debug info dumped to: "/Saved/ShaderDebugInfo/PCD3D_SM6/Global/FShadingFurnaceTestPassPS/0"
Engine/Shaders/Private/ShadingFurnaceTest.<span class="hljs-title">usf</span><span class="hljs-params">()</span>: Shader FShadingFurnaceTestPassPS, Permutation 0, VF None:
    error: validation errors
Engine/Binaries/Win64/<span class="hljs-params">(<span class="hljs-number">6395</span>,<span class="hljs-number">6</span>)</span>: Shader FShadingFurnaceTestPassPS, Permutation 0, VF None:
    zzz.<span class="hljs-title">dxil</span><span class="hljs-params">(<span class="hljs-number">6395</span>,<span class="hljs-number">6</span>)</span>: error: Instructions should <span class="hljs-keyword">not</span> read uninitialized value.
/Engine/Shaders/Private/ShadingFurnaceTest.<span class="hljs-title">usf</span><span class="hljs-params">()</span>: Shader FShadingFurnaceTestPassPS, Permutation 0, VF None:
    note: at 'br i1 undef, label %1664, label %1699' in block '#89' of function 'MainPS'.
/Engine/Shaders/Private/ShadingFurnaceTest.<span class="hljs-title">usf</span><span class="hljs-params">()</span>: Shader FShadingFurnaceTestPassPS, Permutation 0, VF None:
    Validation failed.
/Engine/Shaders/Private/ShadingFurnaceTest.<span class="hljs-title">usf</span><span class="hljs-params">()</span>: Shader FShadingFurnaceTestPassPS, Permutation 0, VF None:
    D3DCompileToDxil failed. Error code: Unspecified <span class="hljs-title">error</span> <span class="hljs-params">(<span class="hljs-number">0x80004005</span>)</span>.</span>
</code></pre>
<p>I think this is an easy one, so I give the shader file and error message to AI and it tried several times and cannot fix it… Then I have to let it explain the shader line by line and ask if the variable is properly initialized, still no luck, I highlight that any custom struct has to be initialized explicitly, and then it finally find the fix:</p>
<pre><code class="lang-cpp">-                FDeferredLightData LightData;
+                FDeferredLightData LightData = (FDeferredLightData)<span class="hljs-number">0</span>;
</code></pre>
<p>But this time I’m not that frustrated, I have found a way to “work” with AI now and it does help me to get more efficient : )</p>
]]></content:encoded></item><item><title><![CDATA[Check your MSVC version if you having errors when using new feature in C++ 20]]></title><description><![CDATA[Getting this error while compiling an open-source code.
error C2039: 'format_string': is not a member of 'std'
Google results will tell you to make sure you're using C++20 flag in the compiler since it's a new feature. I'm pretty sure it's not the is...]]></description><link>https://blog.catoxliu.net/check-your-msvc-version-if-you-having-errors-when-using-new-feature-in-c-20</link><guid isPermaLink="true">https://blog.catoxliu.net/check-your-msvc-version-if-you-having-errors-when-using-new-feature-in-c-20</guid><category><![CDATA[C++]]></category><category><![CDATA[C2039]]></category><dc:creator><![CDATA[sj]]></dc:creator><pubDate>Thu, 29 Jun 2023 02:28:38 GMT</pubDate><content:encoded><![CDATA[<p>Getting this error while compiling an open-source code.</p>
<p><code>error C2039: 'format_string': is not a member of 'std'</code></p>
<p>Google results will tell you to make sure you're using C++20 flag in the compiler since it's a new feature. I'm pretty sure it's not the issue since <code>std::format</code> itself works fine in the code. But I double checked anyway and try all solutions I could find online (big mistake, trying to be lazy : ). I checked all the dependencies and VS packages needed. I even re-install Visual Studio 2022.</p>
<p>After wasting half a day (and a good sleep) without any progress, I started to be serious about this error.</p>
<p>So back to the error, what's this <code>std::format_string</code>. According to <a target="_blank" href="https://en.cppreference.com/w/cpp/utility/format/basic_format_string">std::basic_format_string, std::format_string, std::wformat_string -</a> <a target="_blank" href="http://cppreference.com">cppreference.com</a>, it's a new feature in C++ 20 standard library and should be in the &lt;format&gt; header file. (Yes, I do explicitly include this header file in the source code and nothing changed).</p>
<p>The definition of it is:</p>
<pre><code class="lang-cpp"><span class="hljs-keyword">template</span>&lt; class... Args &gt;
<span class="hljs-keyword">using</span> format_string = basic_format_string&lt;<span class="hljs-keyword">char</span>, <span class="hljs-built_in">std</span>::<span class="hljs-keyword">type_identity_t</span>&lt;Args&gt;...&gt;;
</code></pre>
<p>Then I open the format header in the MSVC and try to search for it. Of course no exact match, so I just tried the patterns like "basic_format_string". Finally, I find this:</p>
<pre><code class="lang-cpp"><span class="hljs-keyword">template</span> &lt;class... _Args&gt;
<span class="hljs-keyword">using</span> _Fmt_string = _Basic_format_string&lt;<span class="hljs-keyword">char</span>, <span class="hljs-keyword">type_identity_t</span>&lt;_Args&gt;...&gt;;
</code></pre>
<p>Surprise, surprise, MSVC has their own naming different from the standard library : )</p>
<p>Then changing the <code>std::format_string</code> to <code>std:_Fmt_string</code> fix the compilation errors. But why I'm getting this while it seems to work fine for others? I realise it's the MSVC version I'm using, it's 14.34.31933 even though the latest one is 14.36.32532.</p>
<p>Then I reinstall MSVC again and this time to ensure I have the latest version of MSVC installed. Then in 14.36.32532, the format header change it to <code>std::format_string</code> and now the original source code is compiled without any issues.</p>
<p>Lesson learnt : )</p>
]]></content:encoded></item><item><title><![CDATA[Unreal Engine Build Graph Copy issue]]></title><description><![CDATA[Since there're not too much useful info in Unreal Engine documentation, I have to dig this up by myself
ERROR: Source file 'C:\workspace\Binaries\Win64\UnrealEditor.target' is not under 'C:\workspace\UnrealEngine'
       while executing task <Copy Fi...]]></description><link>https://blog.catoxliu.net/unreal-engine-build-graph-copy-issue</link><guid isPermaLink="true">https://blog.catoxliu.net/unreal-engine-build-graph-copy-issue</guid><category><![CDATA[unreal-engine]]></category><dc:creator><![CDATA[sj]]></dc:creator><pubDate>Fri, 29 Jul 2022 01:29:51 GMT</pubDate><content:encoded><![CDATA[<p>Since there're not too much useful info in <a target="_blank" href="https://docs.unrealengine.com/4.27/en-US/ProductionPipelines/BuildTools/AutomationTool/BuildGraph/ScriptAnatomy/Tasks/">Unreal Engine documentation</a>, I have to dig this up by myself</p>
<pre><code>ERROR: Source file <span class="hljs-string">'C:\workspace\Binaries\Win64\UnrealEditor.target'</span> <span class="hljs-keyword">is</span> not under <span class="hljs-string">'C:\workspace\UnrealEngine'</span>
       <span class="hljs-keyword">while</span> executing task <span class="hljs-operator">&lt;</span>Copy Files<span class="hljs-operator">=</span><span class="hljs-string">"#InternalBinaries"</span> From<span class="hljs-operator">=</span><span class="hljs-string">"C:\workspace\UnrealEngine"</span> To<span class="hljs-operator">=</span><span class="hljs-string">"C:\workspace\UnrealEngine\LocalBuilds\ArchiveForUGS\Staging"</span> Overwrite<span class="hljs-operator">=</span><span class="hljs-string">"True"</span> ErrorIfNotFound<span class="hljs-operator">=</span><span class="hljs-string">"False"</span> <span class="hljs-operator">/</span><span class="hljs-operator">&gt;</span>
       at Engine<span class="hljs-operator">/</span>Build<span class="hljs-operator">/</span>Graph<span class="hljs-operator">/</span>Examples<span class="hljs-operator">/</span>CustomBuildEditorAndTools.xml(<span class="hljs-number">151</span>)
       (see C:\workspace\UnrealEngine\Engine\Programs\AutomationTool\Saved\Logs\Log.txt <span class="hljs-keyword">for</span> full exception trace)
</code></pre><p>So this is the line causing the error</p>
<pre><code><span class="hljs-operator">&lt;</span>Tag Files<span class="hljs-operator">=</span><span class="hljs-string">"#OutputFiles"</span> Except<span class="hljs-operator">=</span><span class="hljs-string">".../Intermediate/..."</span> With<span class="hljs-operator">=</span><span class="hljs-string">"#ArchiveFiles"</span><span class="hljs-operator">/</span><span class="hljs-operator">&gt;</span>
<span class="hljs-operator">&lt;</span>Tag Files<span class="hljs-operator">=</span><span class="hljs-string">"#ArchiveFiles"</span> Except<span class="hljs-operator">=</span><span class="hljs-string">"*.pdb"</span> With<span class="hljs-operator">=</span><span class="hljs-string">"#ArchiveBinaries"</span><span class="hljs-operator">/</span><span class="hljs-operator">&gt;</span>
<span class="hljs-operator">&lt;</span>Copy Files<span class="hljs-operator">=</span><span class="hljs-string">"#ArchiveBinaries"</span> From<span class="hljs-operator">=</span><span class="hljs-string">"$(RootDir)"</span> To<span class="hljs-operator">=</span><span class="hljs-string">"$(ArchiveStagingDir)"</span><span class="hljs-operator">/</span><span class="hljs-operator">&gt;</span>
</code></pre><p>The <code>#ArchiveBinaries</code> are simply all the filtered files output from the compiling. <code>$(RootDir)</code> is the engine folder (C:\workspace\UnrealEngine) and <code>$(ArchiveStagingDir)</code> is some target folder that doesn't matter. The copy should remain the relative strcutre here, saying <code>C:\workspace\path\to\file</code> should be copied to <code>$(ArchiveStagingDir)\path\to\file</code></p>
<p>And the error is basically saying that some files is not under the <code>RootDir</code> and can't be copied with relative structure remaining, which makes sense, because you can't have something like <code>$(ArchiveStagingDir)\..\path\to\file</code>.</p>
<p>And the reason why we get files not under the <code>RootDir</code> is simple, because we don't put our game folder as a subfolder of Unreal Engine source. Once the reason is clear, the solution is simple and straight forward:</p>
<pre><code>&lt;<span class="hljs-keyword">Option</span> <span class="hljs-type">Name</span>="MyRootDir" DefaultValue="$(RootDir)\..\" Description ="The root dirctory contain both Engine and Game folders"/&gt;
&lt;<span class="hljs-keyword">Copy</span> Files="#ArchiveBinaries" <span class="hljs-keyword">From</span>="$(MyRootDir)" <span class="hljs-keyword">To</span>="$(ArchiveStagingDir)"/&gt;
</code></pre><p>What we learn today, Unreal want you to put your game under Engine folder if you use the source code, but you can choose to not listen to them :)</p>
]]></content:encoded></item><item><title><![CDATA[auto and auto& nightmare]]></title><description><![CDATA[I'm new to Unreal Engine editor, and I have the task to add some customer menu into an existing context menu. So I look at some docs and read the source code, with new UE5, it's pretty simple and straight forward
UToolMenu* SourceControlMenu = UToolM...]]></description><link>https://blog.catoxliu.net/auto-and-auto-reference-nightmare</link><guid isPermaLink="true">https://blog.catoxliu.net/auto-and-auto-reference-nightmare</guid><category><![CDATA[C++]]></category><category><![CDATA[unreal-engine]]></category><dc:creator><![CDATA[sj]]></dc:creator><pubDate>Fri, 29 Jul 2022 01:01:17 GMT</pubDate><content:encoded><![CDATA[<p>I'm new to Unreal Engine editor, and I have the task to add some customer menu into an existing context menu. So I look at some docs and read the source code, with new UE5, it's pretty simple and straight forward</p>
<pre><code>UToolMenu<span class="hljs-operator">*</span> SourceControlMenu <span class="hljs-operator">=</span> UToolMenus::Get()<span class="hljs-operator">-</span><span class="hljs-operator">&gt;</span>ExtendMenu(<span class="hljs-string">"ID_OF_MENU"</span>);
FToolMenuSection<span class="hljs-operator">&amp;</span> Section <span class="hljs-operator">=</span> SourceControlMenu<span class="hljs-operator">-</span><span class="hljs-operator">&gt;</span>AddSection(<span class="hljs-string">"MySection"</span>);
Section.AddEntry(<span class="hljs-comment">//my custom menu here//);</span>
</code></pre><p>So I jump to this work and made the first mistake, I didn't copy paste code and think I'm smart to ignore unreal style guide to just do it in my way.</p>
<pre><code>auto SourceControlMenu <span class="hljs-operator">=</span> UToolMenus::Get()<span class="hljs-operator">-</span><span class="hljs-operator">&gt;</span>ExtendMenu(<span class="hljs-string">"ID_OF_MENU"</span>);
auto Section <span class="hljs-operator">=</span> SourceControlMenu<span class="hljs-operator">-</span><span class="hljs-operator">&gt;</span>AddSection(<span class="hljs-string">"MySection"</span>);
Section.AddEntry(<span class="hljs-comment">//my custom menu here//);</span>
</code></pre><p>Compile it, pass in the first try, and run it, breakpoint hitted, it runs my code correctly without any issues, cool, seems work done.</p>
<p>But wait, where is my menu, it's not showing up on the UI. What's going wrong here?</p>
<p>...</p>
<p>After a bad sleep following several hours debugging, searching and asking around online and pulling my hairs, I just realise it's a so simple mistake I'm making here.
Looking at the function again</p>
<pre><code><span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">FToolMenuSection</span></span>;
FToolMenuSection&amp; UToolMenu::AddSection(...)
</code></pre><p>So the return type is a reference, and since it's a struct, then you can implicitly copy the value through a struct reference, so what I'm doing there is making a copy of the section.</p>
<p>I know this is not right to some people, as I quote "I would totally expect the auto to be of the same type of the return type i.e. a reference to FToolMenuSection" (Maybe that's why Unreal suggest to not use auto if possible)</p>
<p>To make it clear:</p>
<pre><code>FToolMenuSection<span class="hljs-operator">&amp;</span> OriginSection <span class="hljs-operator">=</span> SourceControlMenu<span class="hljs-operator">-</span><span class="hljs-operator">&gt;</span>AddSection(<span class="hljs-string">"MySection"</span>);
auto CopiedSection <span class="hljs-operator">=</span> OriginSection; <span class="hljs-comment">//it's not the original section.</span>
<span class="hljs-comment">// Type of OriginSection is FToolMenuSection&amp;</span>
<span class="hljs-comment">// Type of CopiedSection is FToolMenuSection</span>
</code></pre><p>So I'm adding my custom menu into a local variable section which is disposed right after my function... </p>
<p>Besides the mistake I made here, is there a way to protect us from this? yes, there is, you can explicitly delete the constructor of copying from reference</p>
<pre><code><span class="hljs-keyword">struct</span> <span class="hljs-title">FToolMenuSection</span>
{
   FToolMenuSection(FToolMenuSection<span class="hljs-operator">&amp;</span> ) <span class="hljs-operator">=</span> <span class="hljs-keyword">delete</span>;
   FToolMenuSection(FToolMenuSection<span class="hljs-operator">&amp;</span><span class="hljs-operator">&amp;</span> ) <span class="hljs-operator">=</span> <span class="hljs-keyword">delete</span>;
}
FToolMenuSection<span class="hljs-operator">&amp;</span> OriginSection <span class="hljs-operator">=</span> SourceControlMenu<span class="hljs-operator">-</span><span class="hljs-operator">&gt;</span>AddSection(<span class="hljs-string">"MySection"</span>);
auto CopiedSection <span class="hljs-operator">=</span> OriginSection; <span class="hljs-comment">//compiler errors here, don't know how to copy from a reference here</span>
</code></pre><p>And I tried to do this for Unreal Editor, but can't. Since in the TArray implementation, they need insert fucntion working with reference which needs the copy by reference.</p>
<p>So what I learn today. Always copy paste code where you can :). And maybe follow the style guideline where you can ~</p>
]]></content:encoded></item><item><title><![CDATA[How to automatically update a "private" vscode extension]]></title><description><![CDATA[The right way is to publish it into official market, but if you can't wait or don't want to, here we go:
Generally speaking, this could be as simple as:

a regular task to check the server.
Download the extension package if newer version available.
I...]]></description><link>https://blog.catoxliu.net/how-to-automatically-update-a-private-vscode-extension</link><guid isPermaLink="true">https://blog.catoxliu.net/how-to-automatically-update-a-private-vscode-extension</guid><category><![CDATA[vscode extensions]]></category><category><![CDATA[vscode]]></category><category><![CDATA[Visual Studio Code]]></category><dc:creator><![CDATA[sj]]></dc:creator><pubDate>Tue, 10 May 2022 12:55:05 GMT</pubDate><content:encoded><![CDATA[<h3 id="heading-the-right-way-is-to-publish-it-into-official-market-but-if-you-cant-wait-or-dont-want-to-here-we-go">The right way is to publish it into official market, but if you can't wait or don't want to, here we go:</h3>
<p>Generally speaking, this could be as simple as:</p>
<ol>
<li>a regular task to check the server.</li>
<li>Download the extension package if newer version available.</li>
<li>Install that extension package.</li>
</ol>
<p>You can have all kinds of different impelementations for step 1 or 2, I wouldn't go into details here.</p>
<p>The challenge part is step 3.
Previously I just ask my colleagues to manually install the vsix file I shared with them, since it's just several simple clicks on UI. But after 3 releases (bugs/feature request) in a week, this process becomes a bit unacceptable. I have to figure out a way to upgrade it automatically to prevent the fragmentations and also make other's life a bit happier.</p>
<p><strong>So the first question is can we install it through a script/cli?</strong> Well, the answer is not that obvious to me (due to my poor google skills), but hey, vscode is opensource, all the answers are already there in the code. So I go to github and quickly go through the issues (always assume somebody else may have the very same issue before), and I find someone report a bug of this command:</p>
<pre><code>code <span class="hljs-operator">-</span><span class="hljs-operator">-</span>install<span class="hljs-operator">-</span>extension path_to_extension_vsix <span class="hljs-operator">-</span><span class="hljs-operator">-</span>force
</code></pre><p>wait, I don't know that and also, it's very clear then it's doable. Everything follows up is simple and boring:</p>
<ul>
<li>a lot more help info through <code>code --help</code>.</li>
<li>searching through the code by "install-extension"</li>
<li>ha, cli command: "<strong>workbench.extensions.installExtension</strong>" (in fact, it's right there in the <a target="_blank" href="https://code.visualstudio.com/api/references/commands">offical documentation</a> :))</li>
<li>basically just call this vs command in the extension code with the vsix file URI, it's done.</li>
</ul>
<p>Can't believe I go through such a long way (again, poor google skills), but a lot of other stuffs learnt.</p>
]]></content:encoded></item></channel></rss>