I could have sworn that this D3D11 renderer of mine was working before. The OpenGL version of this renderer works fine (mostly because I've been confined to MacOS for too long, causing my D3D11 renderer to fall behind) but even though I've followed the tutorials almost exactly, the code just isn't working. I've tried to use Visual Studio's debugging feature, but I couldn't find any helpful information within it (that or I'm just blind since I've never used it before until now).
Now, I really hate to just dump code on you all, but there's more than enough to go through, so I'll try to keep it limited to the relevant parts.
This is the main source file, so you can see in order what is being done, what is being called, etc.
CKeDemoApplication::CKeDemoApplication()
{
std::string dxvs =
"float4 vs_main( float4 Pos : POSITION ) : SV_POSITION\n"
"{\n"
" return Pos;\n"
"}";
std::string dxps =
"float4 ps_main( float4 Pos : SV_POSITION ) : SV_Target\n"
"{\n"
" return float4( 1.0f, 1.0f, 0.0f, 1.0f );\n"
"}";
std::string glvs =
"#version 150\n"
"in vec3 in_pos;\n"
"out vec4 out_colour;\n"
"void main( void )\n"
"{\n"
" gl_Position = vec4( in_pos.xyz, 1.0 );\n"
" out_colour = vec4( 1, 1, 1, 1 );\n"
"}";
std::string glfs =
"#version 150\n"
"out vec4 colour;\n"
"in vec4 out_colour;\n"
"void main(void)\n"
"{\n"
"colour = out_colour;\n"
"}";
/*
* Initialize Kunai Engine
*/
KeInitialize();
/*
* Initialize a basic core OpenGL 3.x device
*/
KeRenderDeviceDesc rddesc;
ZeroMemory( &rddesc, sizeof( KeRenderDeviceDesc ) );
rddesc.width = 640;
rddesc.height = 480;
rddesc.colour_bpp = 32;
rddesc.depth_bpp = 24;
rddesc.stencil_bpp = 8;
rddesc.fullscreen = No;
rddesc.buffer_count = 2;
rddesc.device_type = KE_RENDERDEVICE_D3D11;
bool ret = KeCreateWindowAndDevice( &rddesc, &m_pRenderDevice );
if( !ret )
{
DISPDBG( KE_ERROR, "Error initializing render device!" );
}
/*
* Initialize GPU program and geometry buffer
*/
KeVertexAttribute va[] =
{
{ KE_VA_POSITION, 3, KE_FLOAT, No, sizeof(float)*3, 0 },
{ -1, 0, 0, 0, 0 }
};
nv::vec3f vd[] =
{
nv::vec3f( -1.0f, -1.0f, 0.0f ),
nv::vec3f( 1.0f, -1.0f, 0.0f ),
nv::vec3f( 0.0f, 1.0f, 0.0f ),
};
if( rddesc.device_type == KE_RENDERDEVICE_D3D11 )
m_pRenderDevice->CreateProgram( dxvs.c_str(), dxps.c_str(), NULL, NULL, va, &m_pProgram );
else
m_pRenderDevice->CreateProgram( glvs.c_str(), glfs.c_str(), NULL, NULL, va, &m_pProgram );
m_pRenderDevice->CreateGeometryBuffer( &vd, sizeof(nv::vec3f)*3, NULL, 0, 0, KE_USAGE_STATIC_WRITE, va, &m_pGB );
}
CKeDemoApplication::~CKeDemoApplication()
{
if( m_pGB )
m_pGB->Destroy();
if( m_pProgram )
m_pProgram->Destroy();
KeDestroyWindowAndDevice( m_pRenderDevice );
m_pRenderDevice = NULL;
KeUninitialize();
}
void CKeDemoApplication::Run()
{
m_pRenderDevice->SetProgram( m_pProgram );
m_pRenderDevice->SetGeometryBuffer( m_pGB );
m_pRenderDevice->SetTexture( 0, NULL );
while( !KeQuitRequested() )
{
KeProcessEvents();
float green[4] = { 0.0f, 0.5f, 0.0f, 1.0 };
m_pRenderDevice->SetClearColourFV( green );
m_pRenderDevice->SetClearDepth( 1.0f );
m_pRenderDevice->SetClearStencil(0);
m_pRenderDevice->Clear( KE_COLOUR_BUFFER | KE_DEPTH_BUFFER /*| KE_STENCIL_BUFFER*/ );
m_pRenderDevice->DrawVertices( KE_TRIANGLES, sizeof(nv::vec3f), 0, 3 );
m_pRenderDevice->Swap();
}
}
For those that want to see my initialization routine:
bool IKeDirect3D11RenderDevice::PVT_InitializeDirect3DWin32()
{
/* Initialize Direct3D11 */
uint32_t flags = D3D11_CREATE_DEVICE_BGRA_SUPPORT;
D3D_FEATURE_LEVEL feature_levels[] =
{
D3D_FEATURE_LEVEL_12_1,
D3D_FEATURE_LEVEL_12_0,
D3D_FEATURE_LEVEL_11_1,
D3D_FEATURE_LEVEL_11_0,
D3D_FEATURE_LEVEL_10_1,
D3D_FEATURE_LEVEL_10_0,
D3D_FEATURE_LEVEL_9_3,
D3D_FEATURE_LEVEL_9_2,
D3D_FEATURE_LEVEL_9_1
};
int feature_level_count = ARRAYSIZE( feature_levels );
#ifdef _DEBUG
flags = D3D11_CREATE_DEVICE_DEBUG;
#endif
ZeroMemory( &swapchain_desc, sizeof( swapchain_desc ) );
swapchain_desc.BufferCount = device_desc->buffer_count;
swapchain_desc.BufferDesc.Width = device_desc->width;
swapchain_desc.BufferDesc.Height = device_desc->height;
swapchain_desc.BufferDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
swapchain_desc.BufferDesc.RefreshRate.Numerator = device_desc->refresh_rate;
swapchain_desc.BufferDesc.RefreshRate.Denominator = 1;
swapchain_desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
swapchain_desc.OutputWindow = GetActiveWindow();
swapchain_desc.SampleDesc.Count = 1;
swapchain_desc.SampleDesc.Quality = 0;
swapchain_desc.Windowed = !device_desc->fullscreen;
HRESULT hr = D3D11CreateDeviceAndSwapChain( NULL, D3D_DRIVER_TYPE_HARDWARE, NULL, flags, feature_levels, feature_level_count,
D3D11_SDK_VERSION, &swapchain_desc, &dxgi_swap_chain, &d3ddevice, &feature_level, &d3ddevice_context );
#ifdef _DEBUG
/* If we are requesting a debug device, and we fail to get it, try again without the debug flag. */
if( hr == DXGI_ERROR_SDK_COMPONENT_MISSING )
{
DISPDBG( KE_WARNING, "Attempting to re-create the Direct3D device without debugging capabilities..." );
flags &= ~D3D11_CREATE_DEVICE_DEBUG;
hr = D3D11CreateDeviceAndSwapChain( NULL, D3D_DRIVER_TYPE_HARDWARE, NULL, flags, feature_levels, feature_level_count,
D3D11_SDK_VERSION, &swapchain_desc, &dxgi_swap_chain, &d3ddevice, &feature_level, &d3ddevice_context );
}
#endif
D3D_DISPDBG_RB( KE_ERROR, "Error creating Direct3D11 device and swapchain!", hr );
/* Create our render target view */
ID3D11Texture2D* back_buffer = NULL;
hr = dxgi_swap_chain->GetBuffer( 0, __uuidof( ID3D11Texture2D ), ( LPVOID* )&back_buffer );
D3D_DISPDBG_RB( KE_ERROR, "Error getting back buffer!", hr );
hr = d3ddevice->CreateRenderTargetView( back_buffer, NULL, &d3d_render_target_view );
back_buffer->Release();
D3D_DISPDBG_RB( KE_ERROR, "Error creating render target view!", hr );
/* Create our depth stencil view */
D3D11_TEXTURE2D_DESC depthdesc;
depthdesc.Width = device_desc->width;
depthdesc.Height = device_desc->height;
depthdesc.MipLevels = 1;
depthdesc.ArraySize = 1;
depthdesc.Format = DXGI_FORMAT_D24_UNORM_S8_UINT;
depthdesc.SampleDesc.Count = 1;
depthdesc.SampleDesc.Quality = 0;
depthdesc.Usage = D3D11_USAGE_DEFAULT;
depthdesc.BindFlags = D3D11_BIND_DEPTH_STENCIL;
depthdesc.CPUAccessFlags = 0;
depthdesc.MiscFlags = 0;
hr = d3ddevice->CreateTexture2D( &depthdesc, NULL, &d3d_depth_stencil_buffer );
D3D_DISPDBG_RB( KE_ERROR, "Error creating depth stencil buffer!", hr );
D3D11_DEPTH_STENCIL_VIEW_DESC dsvdesc = {};
dsvdesc.Format = DXGI_FORMAT_D24_UNORM_S8_UINT; /* TODO: Do not hardcode this... */
dsvdesc.ViewDimension = D3D11_DSV_DIMENSION_TEXTURE2D;
dsvdesc.Texture2D.MipSlice = 0;
hr = d3ddevice->CreateDepthStencilView( d3d_depth_stencil_buffer, &dsvdesc, &d3d_depth_stencil_view );
D3D_DISPDBG_RB( KE_ERROR, "Error creating depth stencil view!", hr );
/* Set render target and depth stencil */
d3ddevice_context->OMSetRenderTargets( 1, &d3d_render_target_view.GetInterfacePtr(), d3d_depth_stencil_view );
/* Setup the viewport */
D3D11_VIEWPORT vp;
vp.Width = (FLOAT) device_desc->width;
vp.Height = (FLOAT) device_desc->height;
vp.MinDepth = 0.0f;
vp.MaxDepth = 1.0f;
vp.TopLeftX = 0;
vp.TopLeftY = 0;
d3ddevice_context->RSSetViewports( 1, &vp );
/* Get DXGI output */
if( FAILED( hr = dxgi_swap_chain->GetContainingOutput( &dxgi_output ) ) )
{
DISPDBG( KE_WARNING, "IDXGISwapChain::GetContainingOutput returned (0x" << hr << ")" );
dxgi_output = nullptr;
}
return S_OK;
}
Going down the initialization routine, here's the code for creating shaders and geometry buffers:
bool IKeDirect3D11RenderDevice::CreateProgram( const char* vertex_shader, const char* fragment_shader, const char* geometry_shader, const char* tesselation_shader, KeVertexAttribute* vertex_attributes, IKeGpuProgram** gpu_program )
{
D3D11_INPUT_ELEMENT_DESC* layout = NULL;
int layout_size = 0;
DXGI_FORMAT fmt;
DWORD shader_flags = D3DCOMPILE_ENABLE_STRICTNESS;
#ifdef _DEBUG
shader_flags |= D3DCOMPILE_DEBUG;
#endif
/* Allocate new GPU program */
*gpu_program = new IKeDirect3D11GpuProgram;
IKeDirect3D11GpuProgram* gp = static_cast<IKeDirect3D11GpuProgram*>( *gpu_program );
/* Create Direct3D compatible vertex layout */
while( vertex_attributes[layout_size].index != -1 )
layout_size++;
layout = new D3D11_INPUT_ELEMENT_DESC[layout_size];
if( layout )
{
for( int i = 0; i < layout_size; i++ )
{
if( vertex_attributes[i].type == KE_FLOAT && vertex_attributes[i].size == 1 )
fmt = DXGI_FORMAT_R32_FLOAT;
if( vertex_attributes[i].type == KE_FLOAT && vertex_attributes[i].size == 2 )
fmt = DXGI_FORMAT_R32G32_FLOAT;
if( vertex_attributes[i].type == KE_FLOAT && vertex_attributes[i].size == 3 )
fmt = DXGI_FORMAT_R32G32B32_FLOAT;
if( vertex_attributes[i].type == KE_FLOAT && vertex_attributes[i].size == 4 )
fmt = DXGI_FORMAT_R32G32B32A32_FLOAT;
if( !strcmp( "POSITION", semantic_list[vertex_attributes[i].index].name ) )
layout[i].SemanticName = "POSITION";
if( !strcmp( "COLOR", semantic_list[vertex_attributes[i].index].name ) )
layout[i].SemanticName = "COLOR";
layout[i].SemanticIndex = semantic_list[vertex_attributes[i].index].index;
layout[i].Format = fmt;
layout[i].InputSlot = 0; /* TODO */
layout[i].AlignedByteOffset = vertex_attributes[i].offset;
layout[i].InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA;
layout[i].InstanceDataStepRate = 0; /* TODO */
}
/* Initialize vertex shader */
/* TODO: Auto detect highest shader version */
CD3D10Blob* blob_shader = NULL;
CD3D10Blob* blob_error = NULL;
HRESULT hr = D3DCompile( vertex_shader, strlen( vertex_shader ) + 1, "vs_main", NULL, NULL, "vs_main",
"vs_4_0", shader_flags, 0, &blob_shader, &blob_error );
if( FAILED( hr ) )
{
if( blob_error != NULL )
{
DISPDBG( KE_ERROR, "Error compiling vertex shader source!\n" << (char*)blob_error->GetBufferPointer() << "\n" );
delete[] layout;
blob_error = 0;
gp->Destroy();
}
return false;
}
hr = d3ddevice->CreateVertexShader( blob_shader->GetBufferPointer(), blob_shader->GetBufferSize(), NULL, &gp->vs );
if( FAILED( hr ) )
{
delete[] layout;
blob_shader = 0;
gp->Destroy();
DISPDBG( KE_ERROR, "Error creating vertex shader!\n" );
}
/* Create input layout */
hr = d3ddevice->CreateInputLayout( layout, layout_size, blob_shader->GetBufferPointer(), blob_shader->GetBufferSize(), &gp->il );
blob_shader = 0;
delete[] layout;
if( FAILED( hr ) )
{
gp->Destroy();
DISPDBG( KE_ERROR, "Error creating input layout!\n" );
}
/* Create pixel shader */
hr = D3DCompile( fragment_shader, strlen( fragment_shader ) + 1, "ps_main", NULL, NULL, "ps_main",
"ps_4_0", shader_flags, 0, &blob_shader, &blob_error );
if( FAILED( hr ) )
{
if( blob_error != NULL )
{
DISPDBG( KE_ERROR, "Error compiling pixel shader source!\n" << (char*)blob_error->GetBufferPointer() << "\n" );
blob_error = 0;
gp->Destroy();
}
return false;
}
hr = d3ddevice->CreatePixelShader( blob_shader->GetBufferPointer(), blob_shader->GetBufferSize(), NULL, &gp->ps );
if( FAILED( hr ) )
{
blob_shader = 0;
gp->Destroy();
DISPDBG( KE_ERROR, "Error creating pixel shader!\n" );
}
blob_shader = 0;
/* TODO: Geometry, Hull, Compute and Domain shaders */
gp->hs = NULL;
gp->gs = NULL;
gp->cs = NULL;
gp->ds = NULL;
}
#if 1
/* Copy vertex attributes */
int va_size = 0;
while( vertex_attributes[va_size].index != -1 )
va_size++;
gp->va = new KeVertexAttribute[va_size+1];
memmove( gp->va, vertex_attributes, sizeof( KeVertexAttribute ) * (va_size+1) );
#endif
return true;
}
/*
* Name: IKeDirect3D11RenderDevice::create_geometry_buffer
* Desc: Creates a geometry buffer based on the vertex and index data given. Vertex and index
* buffers are encapsulated into one interface for easy management, however, index data
* input is completely optional. Interleaved vertex data is also supported.
*/
bool IKeDirect3D11RenderDevice::CreateGeometryBuffer( void* vertex_data, uint32_t vertex_data_size, void* index_data, uint32_t index_data_size, uint32_t index_data_type, uint32_t flags, KeVertexAttribute* va, IKeGeometryBuffer** geometry_buffer )
{
HRESULT hr = S_OK;
/* Sanity check(s) */
if( !geometry_buffer )
DISPDBG_RB( KE_ERROR, "Invalid interface pointer!" );
//if( !vertex_attributes )
// return false;
if( !vertex_data_size )
DISPDBG_RB( KE_ERROR, "(vertex_data_size == 0) condition is currently not allowed..." ); /* Temporary? */
*geometry_buffer = new IKeDirect3D11GeometryBuffer;
IKeDirect3D11GeometryBuffer* gb = static_cast<IKeDirect3D11GeometryBuffer*>( *geometry_buffer );
gb->stride = 0;
/* Create a vertex buffer */
D3D11_BUFFER_DESC bd;
ZeroMemory( &bd, sizeof(bd) );
bd.Usage = D3D11_USAGE_DEFAULT;
bd.ByteWidth = vertex_data_size;
bd.BindFlags = D3D11_BIND_VERTEX_BUFFER;
bd.CPUAccessFlags = 0; /* TODO */
D3D11_SUBRESOURCE_DATA id;
ZeroMemory( &id, sizeof(id) );
id.pSysMem = vertex_data;
hr = d3ddevice->CreateBuffer( &bd, &id, &gb->vb );
if( FAILED( hr ) )
{
delete (*geometry_buffer);
D3D_DISPDBG_RB( KE_ERROR, "Error creating vertex buffer!", hr );
}
/* Create index buffer, if desired. */
gb->ib = NULL;
if( index_data_size )
{
ZeroMemory( &bd, sizeof(bd) );
bd.Usage = D3D11_USAGE_DEFAULT;
bd.ByteWidth = index_data_size;
bd.BindFlags = D3D11_BIND_INDEX_BUFFER;
ZeroMemory( &id, sizeof(id) );
id.pSysMem = index_data;
hr = d3ddevice->CreateBuffer( &bd, &id, &gb->ib );
if( FAILED( hr ) )
{
delete (*geometry_buffer);
D3D_DISPDBG_RB( KE_ERROR, "Error creating index buffer!", hr );
}
gb->index_type = index_data_type;
}
else
{
gb->index_type = 0;
}
return true;
}
So that's the end of the initialization stuff, let's take a look the relevant stuff that makes it draw.
/*
* Name: IKeDirect3D11RenderDevice::set_program
* Desc: Sets the GPU program. If NULL, the GPU program is set to 0.
*/
void IKeDirect3D11RenderDevice::SetProgram( IKeGpuProgram* gpu_program )
{
IKeDirect3D11GpuProgram* gp = static_cast<IKeDirect3D11GpuProgram*>( gpu_program );
/* Set input layout */
if(gp)
d3ddevice_context->IASetInputLayout( gp->il );
else
d3ddevice_context->IASetInputLayout( NULL );
/* Set shaders */
if(gp)
{
d3ddevice_context->VSSetShader( gp->vs, NULL, 0 );
d3ddevice_context->PSSetShader( gp->ps, NULL, 0 );
d3ddevice_context->GSSetShader( gp->gs, NULL, 0 );
d3ddevice_context->HSSetShader( gp->hs, NULL, 0 );
d3ddevice_context->DSSetShader( gp->ds, NULL, 0 );
d3ddevice_context->CSSetShader( gp->cs, NULL, 0 );
}
else
{
d3ddevice_context->VSSetShader( NULL, NULL, 0 );
d3ddevice_context->PSSetShader( NULL, NULL, 0 );
d3ddevice_context->GSSetShader( NULL, NULL, 0 );
d3ddevice_context->HSSetShader( NULL, NULL, 0 );
d3ddevice_context->DSSetShader( NULL, NULL, 0 );
d3ddevice_context->CSSetShader( NULL, NULL, 0 );
}
}
/*
* Name: IKeDirect3D11RenderDevice::set_vertex_buffer
* Desc: Sets the current geometry buffer to be used when rendering. Internally, binds the
* vertex array object. If NULL, then sets the current vertex array object to 0.
*/
void IKeDirect3D11RenderDevice::SetGeometryBuffer( IKeGeometryBuffer* geometry_buffer )
{
current_geometrybuffer = geometry_buffer; /* We'll come back to this in a minute */
}
void IKeDirect3D11RenderDevice::Clear( uint32_t buffers )
{
if( buffers & KE_COLOUR_BUFFER )
d3ddevice_context->ClearRenderTargetView( d3d_render_target_view, clear_colour );
D3D11_CLEAR_FLAG flags = 0;
if( buffers & KE_DEPTH_BUFFER ) flags |= D3D11_CLEAR_DEPTH;
if( buffers & KE_STENCIL_BUFFER ) flags |= D3D11_CLEAR_STENCIL;
if( flags && d3d_depth_stencil_view != nullptr )
d3ddevice_context->ClearDepthStencilView( d3d_depth_stencil_view, flags, clear_depth, clear_stencil );
}
/*
* Name: IKeDirect3D11RenderDevice::draw_vertices
* Desc: Draws vertices from the current vertex buffer
*/
void IKeDirect3D11RenderDevice::DrawVertices( uint32_t primtype, uint32_t stride, int first, int count )
{
IKeDirect3D11GeometryBuffer* gb = static_cast<IKeDirect3D11GeometryBuffer*>(current_geometrybuffer);
IKeDirect3D11GpuProgram* gp = static_cast<IKeDirect3D11GpuProgram*>(current_gpu_program);
uint32_t offset = 0; /* TODO: Allow user to specify this */
d3ddevice_context->IASetVertexBuffers( 0, 1, &gb->vb.GetInterfacePtr(), &stride, &offset );
d3ddevice_context->IASetPrimitiveTopology( primitive_types[primtype] );
d3ddevice_context->Draw( count, first );
}
/*
* Name: IKeDirect3D11RenderDevice::swap
* Desc: Swaps the double buffer.
*/
void IKeDirect3D11RenderDevice::Swap()
{
HRESULT hr = dxgi_swap_chain->Present( swap_interval, 0 );
if( FAILED( hr ) )
DISPDBG( KE_ERROR, "IDXGISwapChain::Present(): Error = 0x" << hr << "\n" );
}
Okay, so that should be everything in order. I was following the Microsoft tutorials (Lesson 2) from the SDK at the time to help me get started on basics and initialization. I followed it almost to the letter but it's still not rendering anything but a blank screen. The entire thing (including this sample project) is on github if you want/need it:
https://github.com/blueshogun96/KunaiEngine/blob/master/source/KeDirect3D11/KeDirect3D11RenderDevice.h
https://github.com/blueshogun96/KunaiEngine/blob/master/source/KeDirect3D11/KeDirect3D11RenderDevice.cpp
https://github.com/blueshogun96/KunaiEngine/tree/master/templates/win32 <- Template project
Just a word of warning, if you try to build the template project, it will take a few minutes, as the entire engine is fairly large (and getting larger). I'm also prepared for any critique on the overall renderer design since there's much room for improvement and a ton of stuff I haven't gotten a chance to touch on the Direct3D side. Any ideas? Thanks.
Shogun