PSO Management in practice

Started by
5 comments, last by turanszkij 6 years, 4 months ago

I am doing a DX12 API abstraction layer for my engine. In the first step I just want a naïve straight forward implementation which wraps a DX11-like interface. The biggest roadblock right now is implementing the pipeline state objects from DX11-like sub-pipeline states like RasterizerState, BlendState, etc. I guess that in this case a call to MyDevice::SetRasterizerState(RasterizerState* myState) would not trigger an API call internally like the DX11 implementation, but instead it could make the currently bound pipeline state "dirty". If the pipeline state is dirty on the next draw call, then I could either retrieve a PSO if it already exists, or create one. The missing link for me is what data structure could I use here to track PSOs? What is a common/practical approach? Unfortunately I couldn't find a concreate example for this yet.

 

Advertisement

Not sure if this is something within your reach, but if possible I would suggest going the other way, ie. have your API expose a DX12-style PSO and emulate that on DX11. You can easily hash the DX11 rasterizer state, depth stencil state and blend state and only set them if they actually change. In my engine I always set the shaders and input layout on DX11 when switching PSO and it hasn't bitten me performance-wise yet at least. I would assume you could hash those too if you want, and only set them if they change. On DX12, setting the PSO is straightforward and Vulkan is pretty much the same if you want to go that route at some point.

1 hour ago, GuyWithBeard said:

Not sure if this is something within your reach, but if possible I would suggest going the other way, ie. have your API expose a DX12-style PSO and emulate that on DX11. You can easily hash the DX11 blend state, rasterizer state, depth stencil state and blend state and only set them if they actually change. In my engine I always set the shaders and input layout on DX11 when switching PSO and it hasn't bitten me performance-wise yet at least. I would assume you could hash those too if you want, and only set them if they change. On DX12, setting the PSO is straightforward and Vulkan is pretty much the same if you want to go that route at some point.

This is kind of what I do too, although I still have separate blend/raster/depth state objects, you have to build a PSO out of them to use them in a draw command. In D3D11 they actually hold the native state objects, and the PSO basically just holds a reference to them, this would turn around in the planned D3D12 implementation, where the separate state objects would only contain the descriptors, and the PSO would have the native stuff. It's kind of a mix of both worlds.

If you have to look up PSOs per draw, it's certainly going to be sub-optimal (perhaps even slower than D11). If you can do this when you create a draw command though, and then reuse that command every frame, you'll be in a good position. 

I simply hash the PSO descriptor, and store the PSOs in a hash map. When hashing an arbitrary structure though, you've got to be wary of padding - hash just the members individually, or init the structure with memset so you know the padding bytes contain 0's.

I actually use a fancy thread safe hash table because multiple threads can be looking up / creating PSOs at the same time. 

I've been there myself. I'm creating a library which is an abstraction layer for graphics APIs. So when I started the project I sat D3D11, D3D12 and Vulkan on a chair (each API in their own chair that is ;)) in the same table and asked them: "What are your common factors?"

The first answer was much like what you are trying to do now: A D3D11 like pipeline state manager. I actually did it, BUT, it was very inefficient. There were so many PSOs to be internally created, state changes that had to be track and the lookup was adding to latency. I worked with this model for some time but as the project grown I had to recall the APIs again and ask the same question again.

For the new answer I had to do as others suggested: I had to redesign my PSO common factor and went for a D3D12 like PSO manager. Literally I had to create an "IPipelineStateObject" interface. This new interface is natural for D3D12 and Vulkan to implement and is very easy for D3D11 to "emulate". This resulted in a much more native and efficient implementation.

Not only for PSO, but in general, if you try to get a common factor between all available APIs then you'll notice that the model leans towards a Vulkan like design.

Thanks guys. At first I started out with keeping DX11-like sub-pso bindings and trying to hash the whole PSO description with them to create a look up table, but as many of you suggested I will be rewriting the renderer to accommodate PSO creation instead in the application code. The plan is to make it the engine's responsibility to assemble PSOs. The good thing is that it has more high level knowledge of shader types and rendering paths and I hope to create every PSO up front in load time. 

Unfortunately this will trigger rewriting major parts of the rendering engine code apart from the API abstraction layer, but it seems like a better way long term because of the new graphics API designs.

This topic is closed to new replies.

Advertisement