Skip to content

Instantly share code, notes, and snippets.

Created May 22, 2017 22:05
Show Gist options
  • Save anonymous/2edc9e9d52a93c126ff486cfb4a2c65b to your computer and use it in GitHub Desktop.
Save anonymous/2edc9e9d52a93c126ff486cfb4a2c65b to your computer and use it in GitHub Desktop.
D3D12 creation logic (with bonus rant on error handling)
// This function exists solely to make debugging D3D errors easier.
// (single point of failure for breakpoints!)
static HRESULT d3d_check( HRESULT hr )
{
if ( FAILED( hr ) )
{
hr = hr; // put a breakpoint here
}
return hr;
}
// Now we have a bunch of functions like this.
// The pertinent detail being that all of these take a HRESULT *
// output parameter and just are no-ops returning a NULL pointer
// when the HRESULT is already in a failed state.
static ID3D12Resource * create_buffer( ID3D12Device *dev, D3D12_HEAP_TYPE heap, UINT64 size, D3D12_RESOURCE_FLAGS flags, D3D12_RESOURCE_STATES states, HRESULT *hr )
{
D3D12_HEAP_PROPERTIES heap_props = { heap };
D3D12_RESOURCE_DESC desc;
ID3D12Resource * resource = NULL;
init_buffer_desc( &desc, size );
desc.Flags = flags;
if ( SUCCEEDED( *hr ) )
*hr = d3d_check( dev->CreateCommittedResource( &heap_props, D3D12_HEAP_FLAG_NONE, &desc, states, NULL, IID_PPV_ARGS( &resource ) ) );
return resource;
}
static ID3D12Resource * create_tex2d( ID3D12Device *dev, D3D12_HEAP_TYPE heap, U32 width, U32 height, U16 nmips, DXGI_FORMAT format, D3D12_RESOURCE_FLAGS flags, D3D12_RESOURCE_STATES states, HRESULT *hr )
{
D3D12_HEAP_PROPERTIES heap_props = { heap };
D3D12_RESOURCE_DESC desc;
ID3D12Resource * resource = NULL;
init_tex2d_desc( &desc, width, height, nmips, format );
desc.Flags = flags;
if ( SUCCEEDED( *hr ) )
*hr = d3d_check( dev->CreateCommittedResource( &heap_props, D3D12_HEAP_FLAG_NONE, &desc, states, NULL, IID_PPV_ARGS( &resource ) ) );
return resource;
}
// Convenience function to create buffers in our default setup for UAV usage
static ID3D12Resource * create_buffer_for_uav( ID3D12Device * dev, UINT64 size, HRESULT * hr )
{
return create_buffer( dev, D3D12_HEAP_TYPE_DEFAULT, size, D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS,
D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE, hr );
}
// ... and with helper functions like that, you can structure resource creation
// code like this:
BINKSHADERSD3D12GPU * Create_Bink_shaders( BINKCREATESHADERSD3D12 * create )
{
UINT i;
HRESULT hr = S_OK;
if ( create == 0 || create->device == 0 || create->fence == 0 || create->gpu_mode_command_queue == 0 )
return 0;
BINKSHADERSD3D12GPU * shaders;
shaders = (BINKSHADERSD3D12GPU *)BinkUtilMalloc( sizeof(BINKSHADERSD3D12GPU) );
if ( shaders == 0 )
return 0;
// zero the fields in "shaders"
// ---- then start creating things:
shaders->fence_event = CreateEvent( NULL, 0,0, NULL ); // autoreset event starting non-signaled
if ( !shaders->fence_event )
hr = E_FAIL;
// Decode fence
if ( SUCCEEDED( hr ) )
hr = d3d_check( device->CreateFence( 0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS( &shaders->decode_fence ) ) );
// [...] and create even more things
// DC predict root signature
{
// PER_PLANE_GRP_CONSTANT
static D3D12_DESCRIPTOR_RANGE const range_dc_predict_tab_ppgc[] = {
{ D3D12_DESCRIPTOR_RANGE_TYPE_UAV, 1, 0, 0, PPGC_DC_OUT_UAV_PRED }, // u0 (dc_out_uav_pred)
};
// PER_PLANE_AND_UPLOAD_SLOT
static D3D12_DESCRIPTOR_RANGE const range_dc_predict_tab_ppaus[] = {
{ D3D12_DESCRIPTOR_RANGE_TYPE_CBV, 1, 0, 0, PPAUS_FRAME_CONSTS_CBV }, // b0 (frame consts CBV)
};
D3D12_ROOT_PARAMETER root_params[2];
root_params[0] = root_param_table( range_dc_predict_tab_ppgc ); // slot 0
root_params[1] = root_param_table( range_dc_predict_tab_ppaus ); // slot 1
// 2 slot total
shaders->root_sig[ ROOT_SIG_DC_PREDICT ] = create_root_sig( device, 2, root_params, 1, static_smps, D3D12_ROOT_SIGNATURE_FLAG_NONE, &hr );
}
// [..] so many things to create...
// Create draw pipelines
{
// Clean out all the stuff from the graphics pipeline state that we don't use
D3D12_GRAPHICS_PIPELINE_STATE_DESC gpdesc = create->prototype;
// <fill out the fields in gpdesc>
// Create the various shader permutations
for ( i = 0 ; i < DRAW_STATE_COUNT; ++i )
{
static const struct DrawStateDesc
{
CompiledShader const * pshader;
BOOL blend;
D3D12_BLEND src_blend;
D3D12_BLEND dst_blend;
} states[ DRAW_STATE_COUNT ] = {
#define T(name, pshader, blend, srcBlend, dstBlend) { pshader, blend, srcBlend, dstBlend },
DRAW_STATE_LIST
#undef T
};
DrawStateDesc const * state = &states[i];
// Pixel shader
gpdesc.PS = get_bytecode( state->pshader );
// Blending state
// RenderTargetWriteMask uses user-provided setting!
gpdesc.BlendState.RenderTarget[0].BlendEnable = state->blend;
gpdesc.BlendState.RenderTarget[0].LogicOpEnable = FALSE;
gpdesc.BlendState.RenderTarget[0].SrcBlend = state->src_blend;
gpdesc.BlendState.RenderTarget[0].DestBlend = state->dst_blend;
gpdesc.BlendState.RenderTarget[0].BlendOp = D3D12_BLEND_OP_ADD;
gpdesc.BlendState.RenderTarget[0].SrcBlendAlpha = state->src_blend;
gpdesc.BlendState.RenderTarget[0].DestBlendAlpha = state->dst_blend;
gpdesc.BlendState.RenderTarget[0].BlendOpAlpha = D3D12_BLEND_OP_ADD;
if ( SUCCEEDED( hr ) )
hr = d3d_check( device->CreateGraphicsPipelineState( &gpdesc, IID_PPV_ARGS( &shaders->draw_state[i] ) ) );
}
}
// [..] create yet more things (I'm serious, the original code is about 400 lines worth of "Create" calls)
// If *any* of this failed, bail.
if ( FAILED( hr ) )
{
Free_shaders( shaders ); // this releases all non-null ptrs in "shaders" then frees "shaders" itself.
return NULL;
}
return shaders;
}
// Bonus rant on error handling:
//
// In this case there is no fine-grained error reporting, just cleanup, because there is no useful
// intermediate stage (as far as the app is concerned) between "most of this succeeded" and
// "we failed immediately". For the resulting thing to be useful, _everything_ needs to be
// initialized.
//
// That's an interesting concern API design: if you have something complex like that, it's a good
// idea to have some *logging* code for devs that allows them to quickly pinpoint what's going
// wrong, but you want to keep *error codes* and their ilk really simple. Having a giant taxonomy
// of possible errors just means that nobody is going to handle most of them.
//
// What you want to do is separate error messages/log messages (which are unstructured text and can
// have as much detail as you want) from error codes, of which there should be a small, limited
// number (less than 5, ideally).
//
// Error messages should be written to aid diagnosing/debugging the problem.
// Error *codes* should tell the app "what state am I in now?" or "what do I do next?".
//
// E.g. there's a million ways to fail to connect to some network service, and it's a good idea
// to put some detail in error/log messages, but for app-level code, generally all it cares about
// is "am I connected to the server or not?". It is reasonable to expect app code handling "I don't
// have a connection". But a lot of APIs end up with error code taxonomies like this:
//
// enum NetworkError {
// Success,
// AllOK,
// NoRouteToHost,
// ConnectionRefused,
// DNSLookupFailed,
// OutOfMemory,
// UnsupportedProtocolVersion,
// IDontKnowWhyThisHappensButMaybeBethDoes,
// HardwareFault,
// ServerBusy,
// UnknownError
// };
//
// and then any user of that API is left wondering what the subtle distinction between "success" and
// "all OK" means, what it should do on "OutOfMemory" (is the network subsystem even in a state where
// you can do a clean teardown when that error occurs, or do you basically have to exit the process
// at that point?), whether "HardwareFault" warrants extreme action like an OutOfMemory that leaves
// the subsystem in an unspecified state or can be fixed by just shutting down and re-initializing
// the connection, or what the hell it is expected to do on UnknownErrors.
//
// And, of course, with a list like that, it's likely that some new versions of the library will add
// 1 or 2 extra error codes, maybe slightly more specific than the previous categorization, and now
// app code that used to go through one path will go through a different path that's unhandled
// (because the app code for the error handling didn't get updated when the new errors were added).
//
// So at this point I believe pretty strongly in allocating error codes purely based on "what
// should the app do next?" (e.g. in this example: try again later, or close connection/reconnect,
// or "this isn't gonna fix itself, bail"), and having separate error message/logging facilities
// with more details (for when you write logs or need a message to present to the user).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment