Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Indirect Drawcall args #356

Open
SinsOfSeven opened this issue Feb 21, 2025 · 3 comments
Open

Indirect Drawcall args #356

SinsOfSeven opened this issue Feb 21, 2025 · 3 comments

Comments

@SinsOfSeven
Copy link

SinsOfSeven commented Feb 21, 2025

I've been experimenting with 3dmigoto's extended use cases, I created this block to get a better understanding of replacing less common cases after we ran into some things using Indirect draws, I wanted to get a more formal understanding.

; This should be the same as `draw = from_caller`
if draw_type == 0
    ; Invalid Draw
elif draw_type == 1
    ; Draw()
    draw = vertex_count, first_vertex
elif draw_type == 2
    ; DrawIndexed()
    drawindexed = index_count, first_index, vertex_count
elif draw_type == 3
    ; DrawInstanced()
    drawinstanced = vertex_count, instance_count, first_vertex, first_index
elif draw_type == 4
    ; DrawIndexedInstanced()
    drawindexedinstanced = index_count, instance_count, first_index, first_vertex, first_instance
elif draw_type == 5
    ; DrawInstancedIndirect()
    drawinstancedindirect = this, indirect_offset
elif draw_type == 6
    ; DrawIndexedInstancedIndirect()
    drawindexedinstancedindirect = this, indirect_offset
elif draw_type == 7
    ; DrawAuto()
    draw = auto
elif draw_type == 8
    ; Dispatch()
    dispatch = thread_group_count_x, thread_group_count_y, thread_group_count_z
elif draw_type == 9
    ; DispatchIndirect()
    dispatchindirect = this, indirect_offset
endif

I learned that Indirect calls use a buffer to pass their draw parameters, which is pretty powerful, because you can generate them. I was able to successfully use drawindexedinstancedindirect in 3dm, but I won't lie, the offset did give me a bit of trouble when it wasn't using the Stride property of the resource directly.
https://learn.microsoft.com/en-us/windows/win32/api/d3d11/nf-d3d11-id3d11devicecontext-drawindexedinstancedindirect

[TextureOverrideCelestiaHead]
hash = 5fdf1ade
vb0 = ResourceCelestia
vb0 = ResourceCelestia.Rotated unless_null
$active = 1
handling = skip
match_first_index = 0
ib = ResourceCelestiaHeadIB
ps-t0 = ResourceCelestiaHeadDiffuse
; CelestiaHead
	if !$galaxy
		; Icosphere.001 (62914)
		;drawindexed = 127512, 0, 0
		drawindexedinstancedindirect = ResourceTest, 20*0
		; Roundcube.001 (3360)
		; drawindexed = 3600, 127512, 0
		drawindexedinstancedindirect = ResourceTest, 20*1
	else
		; moon (597893)
		;drawindexed = 2313216, 131112, 0
		drawindexedinstancedindirect = ResourceTest, 20*2
	endif
	
[ResourceTest]
type = Buffer
data = R32_UINT 127512 1 0 0 0   3600 1 127512 0 0   2313216 1 131112 0 0
; index_count, instance_count, first_index, first_vertex, first_instance

Do you know if it's even possible to dump the Indirect Args buffers used in the Indirect draw calls? The only place I can find where we can even access the originals is with the this keyword during an indirect draw instruction. I believe they are hashed, they are referenced in the dumps. I thought maybe something akin to this block, but I don't imagine it would do what I want.

[ShaderOverride_HasIndirect]
; This is a Vertex Shader using an indirect buffer generated by a Compute Shader
hash = 
if DRAW_TYPE == 5 || DRAW_TYPE == 6 || DRAW_TYPE == 9
    ; Attempting to dump Indirect Args
    dump = this buf txt desc
endif

Here is a line from the dump, so maybe I should find a hash I can dump.
000114 DrawIndexedInstancedIndirect(pBufferForArgs:0x000001B3D58C44B0, AlignedByteOffsetForArgs:420) hash=5c38bf06

[TextureOverride_IndirectArgs]
; This is the hash of the cs-u0 and "this" of the vertex shader with draw indirect.
hash = 5c38bf06
dump = this buf txt desc

Edit: dumping this from the shader did somehow work, the second example didn't work, likely because there is no checktextureoverride. I don't think 3dmigoto has a resource name for it.

I could try the shotgun approach and just list ib, vb0, vb1, ... vb-t0 etc but I don't think that would work.

type=Buffer byte_width=1638400 usage="DEFAULT" bind_flags="shader_resource unordered_access" cpu_access_flags=0 misc_flags="drawindirect_args" stride=0

I tried going even further down, but dumping on these conditions crashed my game.

[ShaderOverride_HasIndirect]
hash = 5039c33fa941355c
checktextureoverride = this

[TextureOverride_IndirectBuffer]
hash = 5c38bf06
dump = this buf txt desc

Here is a comparison of the log.txt, I think something bad must have happened.
Image

@DarkStarSword
Copy link
Collaborator

DarkStarSword commented Feb 22, 2025

I learned that Indirect calls use a buffer to pass their draw parameters, which is pretty powerful, because you can generate them. I was able to successfully use drawindexedinstancedindirect in 3dm, but I won't lie, the offset did give me a bit of trouble when it wasn't using the Stride property of the resource directly. https://learn.microsoft.com/en-us/windows/win32/api/d3d11/nf-d3d11-id3d11devicecontext-drawindexedinstancedindirect

Normally I think you would just use a dedicated buffer and pass an offset of 0, unless you have a single buffer containing multiple sets of arguments or are tacking the arguments at the end of another buffer. Definitely start with the simple case using offset 0 and get that working before adding more complexity though.

[ResourceTest]
type = Buffer
data = R32_UINT 127512 1 0 0 0 3600 1 127512 0 0 2313216 1 131112 0 0
; index_count, instance_count, first_index, first_vertex, first_instance

Ok, I see you are passing in several sets of draw calls in the one buffer, so you would need an offset for the 2nd (offset = number of arguments * sizeof(UINT) = 20) and 3rd (offset = 30) draw calls (or split this into several buffers). Normally if you are using indirect draw calls you would be writing these values from a compute shader on the GPU though - are these just initial values / for testing / to get the buffer the size you want (which you could do with format= and array= instead)?

You haven't specified the format= of the data in the buffer - that will prevent this from being assigned as a UAV to the computer shader as that needs to be typed (or structured). Passing the format inline with the initial data as you have done is not quite the same thing (it's kind of a hack to work around cases like structured buffers where there may be multiple formats in the same buffer, or otherwise where you want to pass raw data in a different format to the buffer format for whatever reason). Something like this:

[ResourceIndirectArgs]
type = Buffer
format = R32_UINT
array = 5

or if you do want initial data you can omit array= since that is calculated from the length of the initial data:

[ResourceIndirectArgsWithInitialData]
type = Buffer
format = R32_UINT
data = 30 1 0 0 0

Do you know if it's even possible to dump the Indirect Args buffers used in the Indirect draw calls?

If you're just dumping the one you created you can refer to it by name, e.g. dump = ResourceTest

The only place I can find where we can even access the originals is with the this keyword during an indirect draw instruction.

You would use that if you were dumping an indirect draw call argument buffer that the game had created, e.g. to dump this in DOA6:

[ShaderOverrideIndirect]
hash = fb9cfb6310b8de1f
dump = this

Note that the .txt version of the buffer will probably try to decode the data as floats, which will be wrong so it may be better to examine the .buf version with a hex editor.

I believe they are hashed

Only resources created by the game should be hashed at the moment.

(There have been some edge cases where a resource created by 3DMigoto could inadvertently get a hash under some circumstances, but that has been a bug or side-effect of certain hooking scenarios / interactions with 3rd party libraries and not something that should be relied upon. There is some debate that perhaps we should intentionally hash our own resources, but since hashes are not guaranteed to be unique I think it's probably better to refer to them by resource name if possible and save hashes for matching game resources).

I tried going even further down, but dumping on these conditions crashed my game.

Hmm, that shouldn't have crashed - may need to dig into that.

Here is a comparison of the log.txt, I think something bad must have happened. Image

If you get an incomplete log like that you can enable unbuffered=1 under [Logging], though this will make everything run extremely slowly. If it's a game that allows a debugger to be attached then doing so will be very useful, or it's pretty common for games to write crash dumps somewhere that we can potentially use (but I don't think we kept 3DMigoto's pdb files for released builds, so it would need to be a fresh build from source).

Complete example with all the above, tested in DOA6:

; This shader is used e.g. in the pirate ship level for the coins in the hold
[ShaderOverrideIndirectParticles]
hash = fb9cfb6310b8de1f
; Demo dumping indirect argument buffer from the game with Frame Analysis:
dump = this
; Disable original draw call:
handling = skip
; Zeroing our custom indirect buffer first (for demo only):
clear = ResourceIndirectArgs
; Dump out our buffer to confirm it is zeroed:
dump = ResourceIndirectArgs
; Run a compute shader to write to the buffer:
run = CustomShaderSetupIndirectArgs
; Dump it again to verify that the compute shader has written the values we expect:
dump = ResourceIndirectArgs
; Our injected draw call using the arguments written by the compute shader:
DrawIndexedInstancedIndirect = ResourceIndirectArgs, 0

[ResourceIndirectArgs]
type = Buffer
; DrawIndexedInstanced all arguments are UINT or INT:
format = R32_UINT
; DrawIndexedInstanced takes 5 arguments:
array = 5

[ResourceIndirectArgsAlternateDemo]
type = Buffer
; This demo sets the initial data in the buffer rather than using a compute
; shader. Still need format= here otherwise there will be issues binding it as
; a UAV, but this time array= is automatically determined from the initial
; data. Note that passing the format inline in the data= is not the same as
; setting the resource format.
format = R32_UINT
data = 30 1 0 0 0

[CustomShaderSetupIndirectArgs]
cs = test.hlsl
cs-u0 = ResourceIndirectArgs
dispatch = 1,1,1
cs-u0 = null

test.hlsl:

Texture1D<float4> IniParams : register(t120);

RWBuffer<uint> indirect_args_buffer : register(u0);

[numthreads(1, 1, 1)]
void main(uint3 tid: SV_DispatchThreadID)
{
	indirect_args_buffer[0] = 30; // IndexCountPerInstance
	indirect_args_buffer[1] = 1; // InstanceCount
	indirect_args_buffer[2] = 0; // StartIndexLocation
	indirect_args_buffer[3] = 0; // BaseVertexLocation
	indirect_args_buffer[4] = 0; // StartInstanceLocation
}

@SinsOfSeven
Copy link
Author

SinsOfSeven commented Feb 22, 2025

Thanks for the in-depth breakdown. When I said it was hashed, I was referring to the one created by the game, but it seemed like there wasn't an obvious way to checktextureoverride on the hash. It's a little strange to use this keyword in the context of a [ShaderOverride] in the first place. I'll try to run the logging in the mode you suggested.

Ok, I see you are passing in several sets of draw calls in the one buffer, so you would need an offset for the 2nd (offset = number of arguments * sizeof(UINT) = 20) and 3rd (offset = 30) draw calls (or split this into several buffers). Normally if you are using indirect draw calls you would be writing these values from a compute shader on the GPU though - are these just initial values / for testing / to get the buffer the size you want (which you could do with format= and array= instead)?

These were for testing, I knew I could do it with format, but then I saw the data = R32_UNIT syntax in one of your text generators. I figured it was a way to work with structured buffers, but when I tried to use multiple data types it seemed to fail. data = R32_UINT 1 R32_FLOAT 2 but now that I'm looking at it, I believe it would probably be given as the full struct before defining the data. data = R32G32_FLOAT R32_UINT 1.0 2.0 3. Sometimes it's hard to hunt down syntax hints in the src. drawindexedinstancedindirect = this, indirect_offset was one of those cases. I really was just guessing when I tried it. The rest of this was just me trying to learn how that worked, I converted the regular drawindexed calls to validate my understanding.

I can also just dump them from the shader context using this but I wanted to extrapolate it for the sack of investigation, and I imagine it DID work, because it only crashed while dumping. I could probably debug it and see that it's working as intended otherwise. I'll give that a go today.


I've just finished with some follow-up tests, and it seems like using this in a TextureOverride when pointing to indirect args is just not possible. this = this is functional and safe inside the ShaderOverride, but causes an immediate crash in the TextureOverride. Any other ways to use this in the TextureOverride also failed. I tried moving it's contents to a custom resource, It was just that my first encounter with this behavior was only on dump. The game I was using does not allow debugging, I think I would need to properly debug this issue in a stripped down dx11 environment.

[ShaderOverride_HasIndirect]
hash = 5039c33fa941355c
; safe and functional
this = this
checktextureoverride = this

[TextureOverride_IndirectBuffer]
hash = 5c38bf06
; crashes
this = this

I had one last idea as a test case, and in a surprising twist, even this failed.

[ShaderOverride_HasIndirect]
hash = 5039c33fa941355c
if draw_type == 6
  handling = skip
  checktextureoverride = this
endif

[TextureOverride_IndirectBuffer]
hash = 5c38bf06
x101 = 1
drawindexedinstancedindirect = this, indirect_offset

There isn't any issue entering the block mind you. I used an iniparams debugger to see that it enters the block, it's only when using the this keyword from the TextureOverride that it fails. If I had to guess, it's because I'm entering the block with this which then makes a self-pointing reference or something, but I'm hardly a low-level programmer. I'm just too stubborn to quit pushing 3dmigoto around.

There probably isn't any advantage to using a TextureOverride in this case to being with, so I believe it's a moot point to continue, I'm not planning on patching this behavior, just trying to understand it. I also have no immediate need for it, and any real use of it could be done strictly from ShaderOverride or ShaderRegex. This kind of draw is typically used for partial effect systems and such, so it's totally fine.

Thanks for your awesome responses as always @DarkStarSword. I'm satisfied with my investigation. If I had to make a recommendation for resolving this behavior, it would either be to patch it so it is functional, or just warn instead. I honestly don't know which solution would take less effort, usually I would say warning is easier, but I think it might just need some minor adjustments to be able to function properly as well. Likely less than emitting a warning. But it's also an non-issue. It's not something that will be used anyways.

Feel free to close if applicable.

@StarBobis
Copy link

I have a idea and tested,it may help more people so i post it here ,hope you don't mind, if i miss something or wrong please correct my comment, thanks.

I tested in game MechaBreak and implemented a way to dump the DrawIndexedInstancedIndirect's pBufferArgs in 3DMigoto source code, by modify and add a new method same usage as dump_on_unmap:

void FrameAnalysisContext::FrameAnalysisAfterDrawIndexedInstancedIndirect(ID3D11Resource* resource) { _FrameAnalysisAfterUpdate(resource, FrameAnalysisOptions::DUMP_ON_DRAWINDEXEDINSTANCEDINDIRECT, L"drawindexedinstancedindirect"); }

Image

Image

then call it on FrameAnalysisContext::DrawIndexedInstancedIndirect

Image

this will get the pBufferForArgs buffer easily and elegant, and without need to write everysingle possible Vertex Shader that related with the call, in MechaBreak there is over 5 different VS Hash but do the same thing, ShaderOverride make things complex so i make this.

Image

by the way, if you have implemented dump_on_map feature in your 3DMigoto fork, you can also call FrameAnalysisContext::Map in FrameAnalysisContext::DrawIndexedInstancedIndirect to get the same result as FrameAnalysisAfterDrawIndexedInstancedIndirect, may save a new method by reuse dump_on_map method.

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

No branches or pull requests

3 participants