diff --git a/util/test/data/D3D11_Resource_Lifetimes/backbuffer.png b/util/test/data/D3D11_Resource_Lifetimes/backbuffer.png new file mode 100644 index 000000000..d71d6ddec Binary files /dev/null and b/util/test/data/D3D11_Resource_Lifetimes/backbuffer.png differ diff --git a/util/test/data/D3D12_Resource_Lifetimes/backbuffer.png b/util/test/data/D3D12_Resource_Lifetimes/backbuffer.png new file mode 100644 index 000000000..d71d6ddec Binary files /dev/null and b/util/test/data/D3D12_Resource_Lifetimes/backbuffer.png differ diff --git a/util/test/data/GL_Resource_Lifetimes/backbuffer.png b/util/test/data/GL_Resource_Lifetimes/backbuffer.png new file mode 100644 index 000000000..50aae01b5 Binary files /dev/null and b/util/test/data/GL_Resource_Lifetimes/backbuffer.png differ diff --git a/util/test/data/VK_Resource_Lifetimes/backbuffer.png b/util/test/data/VK_Resource_Lifetimes/backbuffer.png new file mode 100644 index 000000000..d71d6ddec Binary files /dev/null and b/util/test/data/VK_Resource_Lifetimes/backbuffer.png differ diff --git a/util/test/demos/d3d11/d3d11_resource_lifetimes.cpp b/util/test/demos/d3d11/d3d11_resource_lifetimes.cpp new file mode 100644 index 000000000..1c871817a --- /dev/null +++ b/util/test/demos/d3d11/d3d11_resource_lifetimes.cpp @@ -0,0 +1,195 @@ +/****************************************************************************** + * The MIT License (MIT) + * + * Copyright (c) 2015-2019 Baldur Karlsson + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + ******************************************************************************/ + +#include "d3d11_test.h" + +struct D3D11_Resource_Lifetimes : D3D11GraphicsTest +{ + static constexpr const char *Description = + "Test various edge-case resource lifetimes: a resource that is first dirtied within a frame " + "so needs initial contents created for it, and a resource that is created and destroyed " + "mid-frame (which also gets dirtied after use)."; + + std::string pixel = R"EOSHADER( + +struct v2f +{ + float4 pos : SV_POSITION; + float4 col : COLOR0; + float2 uv : TEXCOORD0; +}; + +Texture2D smiley : register(t0); +Texture2D checker : register(t1); +SamplerState samp : register(s0); + +cbuffer consts : register(b0) +{ + float4 flags; +}; + +float4 main(v2f IN) : SV_Target0 +{ + if(flags.x != 1.0f || flags.y != 2.0f || flags.z != 4.0f || flags.w != 8.0f) + return float4(1.0f, 0.0f, 1.0f, 1.0f); + + return smiley.Sample(samp, IN.uv * 2.0f) * checker.Sample(samp, IN.uv * 5.0f); +} + +)EOSHADER"; + + int main(int argc, char **argv) + { + // initialise, create window, create device, etc + if(!Init(argc, argv)) + return 3; + + ID3DBlobPtr vsblob = Compile(D3DDefaultVertex, "main", "vs_4_0"); + ID3DBlobPtr psblob = Compile(pixel, "main", "ps_4_0"); + + CreateDefaultInputLayout(vsblob); + + ID3D11VertexShaderPtr vs = CreateVS(vsblob); + ID3D11PixelShaderPtr ps = CreatePS(psblob); + + ID3D11BufferPtr vb = MakeBuffer().Vertex().Data(DefaultTri); + + Texture rgba8; + LoadXPM(SmileyTexture, rgba8); + + ID3D11Texture2DPtr smiley = + MakeTexture(DXGI_FORMAT_R8G8B8A8_UNORM, rgba8.width, rgba8.height).SRV(); + ID3D11ShaderResourceViewPtr smileysrv = MakeSRV(smiley); + + ctx->UpdateSubresource(smiley, 0, NULL, rgba8.data.data(), rgba8.width * sizeof(uint32_t), 0); + + ID3D11SamplerStatePtr samp = MakeSampler().Address(D3D11_TEXTURE_ADDRESS_WRAP); + + auto SetupBuf = [this]() { + const Vec4f flags = {1.0f, 2.0f, 4.0f, 8.0f}; + + ID3D11BufferPtr ret = MakeBuffer().Constant().Size(sizeof(flags)).Mappable(); + + D3D11_MAPPED_SUBRESOURCE map = Map(ret, 0, D3D11_MAP_WRITE_DISCARD); + memcpy(map.pData, &flags, sizeof(Vec4f)); + ctx->Unmap(ret, 0); + + return ret; + }; + + auto TrashBuf = [this](ID3D11BufferPtr &buf) { + D3D11_MAPPED_SUBRESOURCE map = Map(buf, 0, D3D11_MAP_WRITE_DISCARD); + memset(map.pData, 0, sizeof(Vec4f)); + ctx->Unmap(buf, 0); + + buf = NULL; + }; + + auto SetupSRV = [this]() { + ID3D11Texture2DPtr tex = MakeTexture(DXGI_FORMAT_R8G8B8A8_UNORM, 4, 4).SRV(); + ID3D11ShaderResourceViewPtr ret = MakeSRV(tex); + + const uint32_t checker[4 * 4] = { + // X X O O + 0xffffffff, 0xffffffff, 0, 0, + // X X O O + 0xffffffff, 0xffffffff, 0, 0, + // O O X X + 0, 0, 0xffffffff, 0xffffffff, + // O O X X + 0, 0, 0xffffffff, 0xffffffff, + }; + + ctx->UpdateSubresource(tex, 0, NULL, checker, 4 * sizeof(uint32_t), 0); + + return ret; + }; + + auto TrashSRV = [this](ID3D11ShaderResourceViewPtr &srv) { + ID3D11Resource *res = NULL; + srv->GetResource(&res); + + ID3D11Texture2DPtr tex = res; + res->Release(); + + const uint32_t empty[4 * 4] = {}; + + ctx->UpdateSubresource(tex, 0, NULL, empty, 4 * sizeof(uint32_t), 0); + + srv = NULL; + }; + + ID3D11ShaderResourceView *srvs[2] = {smileysrv}; + + ctx->PSSetSamplers(0, 1, &samp.GetInterfacePtr()); + + ID3D11BufferPtr cb = SetupBuf(); + ID3D11ShaderResourceViewPtr srv = SetupSRV(); + srvs[1] = srv; + while(Running()) + { + ClearRenderTargetView(bbRTV, {0.4f, 0.5f, 0.6f, 1.0f}); + + IASetVertexBuffer(vb, sizeof(DefaultA2V), 0); + ctx->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST); + ctx->IASetInputLayout(defaultLayout); + + ctx->VSSetShader(vs, NULL, 0); + ctx->PSSetShader(ps, NULL, 0); + ctx->OMSetRenderTargets(1, &bbRTV.GetInterfacePtr(), NULL); + + // render with last frame's resources + RSSetViewport({0.0f, 0.0f, 128.0f, 128.0f, 0.0f, 1.0f}); + ctx->Draw(3, 0); + + TrashBuf(cb); + TrashSRV(srv); + + // create resources mid-frame and use then trash them + cb = SetupBuf(); + srv = SetupSRV(); + srvs[1] = srv; + ctx->PSSetShaderResources(0, 2, srvs); + ctx->PSSetConstantBuffers(0, 1, &cb.GetInterfacePtr()); + RSSetViewport({128.0f, 0.0f, 128.0f, 128.0f, 0.0f, 1.0f}); + ctx->Draw(3, 0); + TrashBuf(cb); + TrashSRV(srv); + + // set up resources for next frame + cb = SetupBuf(); + srv = SetupSRV(); + + srvs[1] = srv; + ctx->PSSetShaderResources(0, 2, srvs); + ctx->PSSetConstantBuffers(0, 1, &cb.GetInterfacePtr()); + + Present(); + } + + return 0; + } +}; + +REGISTER_TEST(D3D11_Resource_Lifetimes); \ No newline at end of file diff --git a/util/test/demos/d3d12/d3d12_resource_lifetimes.cpp b/util/test/demos/d3d12/d3d12_resource_lifetimes.cpp new file mode 100644 index 000000000..fef993c01 --- /dev/null +++ b/util/test/demos/d3d12/d3d12_resource_lifetimes.cpp @@ -0,0 +1,531 @@ +/****************************************************************************** + * The MIT License (MIT) + * + * Copyright (c) 2018-2019 Baldur Karlsson + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + ******************************************************************************/ + +#include "d3d12_test.h" + +struct D3D12_Resource_Lifetimes : D3D12GraphicsTest +{ + static constexpr const char *Description = + "Test various edge-case resource lifetimes: a resource that is first dirtied within a frame " + "so needs initial contents created for it, and a resource that is created and destroyed " + "mid-frame (which also gets dirtied after use)."; + + std::string pixel = R"EOSHADER( + +struct v2f +{ + float4 pos : SV_POSITION; + float4 col : COLOR0; + float2 uv : TEXCOORD0; +}; + +Texture2D smiley : register(t0); +Texture2D checker : register(t1); +SamplerState samp : register(s0); + +cbuffer consts : register(b0) +{ + float4 flags; +}; + +float4 main(v2f IN) : SV_Target0 +{ + if(flags.x != 1.0f || flags.y != 2.0f || flags.z != 4.0f || flags.w != 8.0f) + return float4(1.0f, 0.0f, 1.0f, 1.0f); + + return smiley.Sample(samp, IN.uv * 2.0f) * checker.Sample(samp, IN.uv * 5.0f); +} + +)EOSHADER"; + + int main(int argc, char **argv) + { + // initialise, create window, create device, etc + if(!Init(argc, argv)) + return 3; + + ID3DBlobPtr vsblob = Compile(D3DDefaultVertex, "main", "vs_4_0"); + ID3DBlobPtr psblob = Compile(pixel, "main", "ps_4_0"); + + ID3D12ResourcePtr vb = MakeBuffer().Data(DefaultTri); + + D3D12_STATIC_SAMPLER_DESC samp = { + D3D12_FILTER_MIN_MAG_MIP_LINEAR, + D3D12_TEXTURE_ADDRESS_MODE_WRAP, + D3D12_TEXTURE_ADDRESS_MODE_WRAP, + D3D12_TEXTURE_ADDRESS_MODE_WRAP, + 0.0f, + 0, + D3D12_COMPARISON_FUNC_ALWAYS, + D3D12_STATIC_BORDER_COLOR_OPAQUE_WHITE, + 0.0f, + 0.0f, + 0, + 0, + D3D12_SHADER_VISIBILITY_PIXEL, + }; + + ID3D12RootSignaturePtr sig = MakeSig( + { + tableParam(D3D12_SHADER_VISIBILITY_PIXEL, D3D12_DESCRIPTOR_RANGE_TYPE_SRV, 0, 0, 1, 0), + tableParam(D3D12_SHADER_VISIBILITY_PIXEL, D3D12_DESCRIPTOR_RANGE_TYPE_SRV, 0, 1, 1, 1), + tableParam(D3D12_SHADER_VISIBILITY_PIXEL, D3D12_DESCRIPTOR_RANGE_TYPE_CBV, 0, 0, 1, 2), + }, + D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT, 1, &samp); + + ID3D12PipelineStatePtr pso = MakePSO().RootSig(sig).InputLayout().VS(vsblob).PS(psblob); + + ResourceBarrier(vb, D3D12_RESOURCE_STATE_COMMON, D3D12_RESOURCE_STATE_VERTEX_AND_CONSTANT_BUFFER); + + ID3D12ResourcePtr uploadBuf = MakeBuffer().Size(1024 * 1024).Upload(); + + Texture rgba8; + LoadXPM(SmileyTexture, rgba8); + + ID3D12ResourcePtr smiley = MakeTexture(DXGI_FORMAT_R8G8B8A8_UNORM, 48, 48) + .Mips(1) + .InitialState(D3D12_RESOURCE_STATE_COPY_DEST); + + { + D3D12_PLACED_SUBRESOURCE_FOOTPRINT layout = {}; + + D3D12_RESOURCE_DESC desc = smiley->GetDesc(); + + dev->GetCopyableFootprints(&desc, 0, 1, 0, &layout, NULL, NULL, NULL); + + byte *srcptr = (byte *)rgba8.data.data(); + byte *mapptr = NULL; + uploadBuf->Map(0, NULL, (void **)&mapptr); + + ID3D12GraphicsCommandListPtr cmd = GetCommandBuffer(); + + Reset(cmd); + + { + D3D12_TEXTURE_COPY_LOCATION dst, src; + + dst.Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX; + dst.pResource = smiley; + dst.SubresourceIndex = 0; + + byte *dstptr = mapptr + layout.Offset; + + for(UINT row = 0; row < rgba8.height; row++) + { + memcpy(dstptr, srcptr, rgba8.width * sizeof(uint32_t)); + srcptr += rgba8.width * sizeof(uint32_t); + dstptr += layout.Footprint.RowPitch; + } + + src.Type = D3D12_TEXTURE_COPY_TYPE_PLACED_FOOTPRINT; + src.pResource = uploadBuf; + src.PlacedFootprint = layout; + + // copy buffer into this array slice + cmd->CopyTextureRegion(&dst, 0, 0, 0, &src, NULL); + + // this slice now needs to be in shader-read to copy to the MSAA texture + D3D12_RESOURCE_BARRIER b = {}; + b.Transition.pResource = smiley; + b.Transition.Subresource = 0; + b.Transition.StateBefore = D3D12_RESOURCE_STATE_COPY_DEST; + b.Transition.StateAfter = D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE; + cmd->ResourceBarrier(1, &b); + } + + cmd->Close(); + + uploadBuf->Unmap(0, NULL); + + Submit({cmd}); + GPUSync(); + } + + auto SetupBuf = [this]() { + const Vec4f flags = {1.0f, 2.0f, 4.0f, 8.0f}; + + ID3D12ResourcePtr ret = MakeBuffer().Size(1024).Upload(); + + void *pData = NULL; + ret->Map(0, NULL, &pData); + memcpy(pData, &flags, sizeof(Vec4f)); + ret->Unmap(0, NULL); + + return ret; + }; + + auto TrashBuf = [this](ID3D12ResourcePtr &cb) { + const Vec4f empty = {}; + + void *pData = NULL; + cb->Map(0, NULL, &pData); + memcpy(pData, &empty, sizeof(Vec4f)); + cb->Unmap(0, NULL); + + cb = NULL; + }; + + auto SetupImg = [this, &uploadBuf]() { + const Vec4f flags = {1.0f, 2.0f, 4.0f, 8.0f}; + + ID3D12ResourcePtr ret = MakeTexture(DXGI_FORMAT_R8G8B8A8_UNORM, 4, 4) + .Mips(1) + .InitialState(D3D12_RESOURCE_STATE_COPY_DEST); + + const uint32_t checker[4 * 4] = { + // X X O O + 0xffffffff, 0xffffffff, 0, 0, + // X X O O + 0xffffffff, 0xffffffff, 0, 0, + // O O X X + 0, 0, 0xffffffff, 0xffffffff, + // O O X X + 0, 0, 0xffffffff, 0xffffffff, + }; + + { + D3D12_PLACED_SUBRESOURCE_FOOTPRINT layout = {}; + + D3D12_RESOURCE_DESC desc = ret->GetDesc(); + + dev->GetCopyableFootprints(&desc, 0, 1, 0, &layout, NULL, NULL, NULL); + + byte *srcptr = (byte *)checker; + byte *mapptr = NULL; + uploadBuf->Map(0, NULL, (void **)&mapptr); + + ID3D12GraphicsCommandListPtr cmd = GetCommandBuffer(); + + Reset(cmd); + + { + D3D12_TEXTURE_COPY_LOCATION dst, src; + + dst.Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX; + dst.pResource = ret; + dst.SubresourceIndex = 0; + + byte *dstptr = mapptr + layout.Offset; + + for(UINT row = 0; row < 4; row++) + { + memcpy(dstptr, srcptr, 4 * sizeof(uint32_t)); + srcptr += 4 * sizeof(uint32_t); + dstptr += layout.Footprint.RowPitch; + } + + src.Type = D3D12_TEXTURE_COPY_TYPE_PLACED_FOOTPRINT; + src.pResource = uploadBuf; + src.PlacedFootprint = layout; + + // copy buffer into this array slice + cmd->CopyTextureRegion(&dst, 0, 0, 0, &src, NULL); + + // this slice now needs to be in shader-read to copy to the MSAA texture + D3D12_RESOURCE_BARRIER b = {}; + b.Transition.pResource = ret; + b.Transition.Subresource = 0; + b.Transition.StateBefore = D3D12_RESOURCE_STATE_COPY_DEST; + b.Transition.StateAfter = D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE; + cmd->ResourceBarrier(1, &b); + } + + cmd->Close(); + + uploadBuf->Unmap(0, NULL); + + Submit({cmd}); + GPUSync(); + } + + return ret; + }; + + auto TrashImg = [this, &uploadBuf](ID3D12ResourcePtr &img) { + const uint32_t empty[4 * 4] = {}; + + { + D3D12_PLACED_SUBRESOURCE_FOOTPRINT layout = {}; + + D3D12_RESOURCE_DESC desc = img->GetDesc(); + + dev->GetCopyableFootprints(&desc, 0, 1, 0, &layout, NULL, NULL, NULL); + + byte *srcptr = (byte *)empty; + byte *mapptr = NULL; + uploadBuf->Map(0, NULL, (void **)&mapptr); + + ID3D12GraphicsCommandListPtr cmd = GetCommandBuffer(); + + Reset(cmd); + + { + D3D12_TEXTURE_COPY_LOCATION dst, src; + + dst.Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX; + dst.pResource = img; + dst.SubresourceIndex = 0; + + byte *dstptr = mapptr + layout.Offset; + + for(UINT row = 0; row < 4; row++) + { + memcpy(dstptr, srcptr, 4 * sizeof(uint32_t)); + srcptr += 4 * sizeof(uint32_t); + dstptr += layout.Footprint.RowPitch; + } + + src.Type = D3D12_TEXTURE_COPY_TYPE_PLACED_FOOTPRINT; + src.pResource = uploadBuf; + src.PlacedFootprint = layout; + + D3D12_RESOURCE_BARRIER b = {}; + b.Transition.pResource = img; + b.Transition.Subresource = 0; + b.Transition.StateBefore = D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE; + b.Transition.StateAfter = D3D12_RESOURCE_STATE_COPY_DEST; + cmd->ResourceBarrier(1, &b); + + // copy buffer into this array slice + cmd->CopyTextureRegion(&dst, 0, 0, 0, &src, NULL); + } + + cmd->Close(); + + uploadBuf->Unmap(0, NULL); + + Submit({cmd}); + GPUSync(); + } + + img = NULL; + }; + + auto SetupDescHeap = [this, smiley](ID3D12ResourcePtr cb, ID3D12ResourcePtr tex) { + D3D12_DESCRIPTOR_HEAP_DESC descheapdesc; + descheapdesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE; + descheapdesc.NodeMask = 1; + descheapdesc.NumDescriptors = 8; + descheapdesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV; + + ID3D12DescriptorHeapPtr descheap; + dev->CreateDescriptorHeap(&descheapdesc, __uuidof(ID3D12DescriptorHeap), (void **)&descheap); + + D3D12_CPU_DESCRIPTOR_HANDLE cpu = descheap->GetCPUDescriptorHandleForHeapStart(); + + { + D3D12_SHADER_RESOURCE_VIEW_DESC desc = {}; + desc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; + desc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING; + desc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D; + desc.Texture2D.MipLevels = 1; + + dev->CreateShaderResourceView(smiley, &desc, cpu); + } + + cpu.ptr += dev->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV); + + { + D3D12_SHADER_RESOURCE_VIEW_DESC desc = {}; + desc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; + desc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING; + desc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D; + desc.Texture2D.MipLevels = 1; + + dev->CreateShaderResourceView(tex, &desc, cpu); + } + + cpu.ptr += dev->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV); + + { + D3D12_CONSTANT_BUFFER_VIEW_DESC desc = {}; + desc.BufferLocation = cb->GetGPUVirtualAddress(); + desc.SizeInBytes = 1024; + + dev->CreateConstantBufferView(&desc, cpu); + } + + return descheap; + }; + + auto TrashDescHeap = [this](ID3D12DescriptorHeapPtr &descheap) { + D3D12_CPU_DESCRIPTOR_HANDLE cpu = descheap->GetCPUDescriptorHandleForHeapStart(); + + D3D12_SHADER_RESOURCE_VIEW_DESC srvdesc = {}; + srvdesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; + srvdesc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING; + srvdesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D; + srvdesc.Texture2D.MipLevels = 1; + + dev->CreateShaderResourceView(NULL, &srvdesc, cpu); + + cpu.ptr += dev->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV); + + dev->CreateShaderResourceView(NULL, &srvdesc, cpu); + + cpu.ptr += dev->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV); + + { + D3D12_CONSTANT_BUFFER_VIEW_DESC cbvdesc = {}; + cbvdesc.BufferLocation = 0; + cbvdesc.SizeInBytes = 1024; + + dev->CreateConstantBufferView(&cbvdesc, cpu); + } + + descheap = NULL; + }; + + ID3D12ResourcePtr cb = SetupBuf(); + ID3D12ResourcePtr img = SetupImg(); + ID3D12DescriptorHeapPtr descheap = SetupDescHeap(cb, img); + while(Running()) + { + D3D12_CPU_DESCRIPTOR_HANDLE rtv; + + // acquire and clear the backbuffer + { + ID3D12GraphicsCommandListPtr cmd = GetCommandBuffer(); + + Reset(cmd); + + ID3D12ResourcePtr bb = StartUsingBackbuffer(cmd, D3D12_RESOURCE_STATE_RENDER_TARGET); + + rtv = MakeRTV(bb).Format(DXGI_FORMAT_R8G8B8A8_UNORM_SRGB).CreateCPU(0); + + ClearRenderTargetView(cmd, rtv, {0.4f, 0.5f, 0.6f, 1.0f}); + + cmd->Close(); + + Submit({cmd}); + + GPUSync(); + } + + // render with last frame's resources + { + ID3D12GraphicsCommandListPtr cmd = GetCommandBuffer(); + + Reset(cmd); + + cmd->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST); + + IASetVertexBuffer(cmd, vb, sizeof(DefaultA2V), 0); + cmd->SetPipelineState(pso); + cmd->SetGraphicsRootSignature(sig); + + RSSetViewport(cmd, {0.0f, 0.0f, 128.0f, 128.0f, 0.0f, 1.0f}); + RSSetScissorRect(cmd, {0, 0, screenWidth, screenHeight}); + + OMSetRenderTargets(cmd, {rtv}, {}); + + cmd->SetDescriptorHeaps(1, &descheap.GetInterfacePtr()); + cmd->SetGraphicsRootDescriptorTable(0, descheap->GetGPUDescriptorHandleForHeapStart()); + cmd->SetGraphicsRootDescriptorTable(1, descheap->GetGPUDescriptorHandleForHeapStart()); + cmd->SetGraphicsRootDescriptorTable(2, descheap->GetGPUDescriptorHandleForHeapStart()); + cmd->DrawInstanced(3, 1, 0, 0); + + cmd->Close(); + + Submit({cmd}); + + GPUSync(); + + TrashBuf(cb); + TrashImg(img); + TrashDescHeap(descheap); + } + + // create resources mid-frame and use then trash them + { + cb = SetupBuf(); + img = SetupImg(); + descheap = SetupDescHeap(cb, img); + + GPUSync(); + + ID3D12GraphicsCommandListPtr cmd = GetCommandBuffer(); + + Reset(cmd); + + cmd->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST); + + IASetVertexBuffer(cmd, vb, sizeof(DefaultA2V), 0); + cmd->SetPipelineState(pso); + cmd->SetGraphicsRootSignature(sig); + + RSSetViewport(cmd, {128.0f, 0.0f, 128.0f, 128.0f, 0.0f, 1.0f}); + RSSetScissorRect(cmd, {0, 0, screenWidth, screenHeight}); + + OMSetRenderTargets(cmd, {rtv}, {}); + + cmd->SetDescriptorHeaps(1, &descheap.GetInterfacePtr()); + cmd->SetGraphicsRootDescriptorTable(0, descheap->GetGPUDescriptorHandleForHeapStart()); + cmd->SetGraphicsRootDescriptorTable(1, descheap->GetGPUDescriptorHandleForHeapStart()); + cmd->SetGraphicsRootDescriptorTable(2, descheap->GetGPUDescriptorHandleForHeapStart()); + cmd->DrawInstanced(3, 1, 0, 0); + + cmd->Close(); + + Submit({cmd}); + + GPUSync(); + + TrashBuf(cb); + TrashImg(img); + TrashDescHeap(descheap); + } + + // finish with the backbuffer + { + ID3D12GraphicsCommandListPtr cmd = GetCommandBuffer(); + + Reset(cmd); + + FinishUsingBackbuffer(cmd, D3D12_RESOURCE_STATE_RENDER_TARGET); + + cmd->Close(); + + Submit({cmd}); + + GPUSync(); + } + + // set up resources for next frame + cb = SetupBuf(); + img = SetupImg(); + descheap = SetupDescHeap(cb, img); + + Present(); + } + + TrashBuf(cb); + TrashImg(img); + TrashDescHeap(descheap); + + return 0; + } +}; + +REGISTER_TEST(D3D12_Resource_Lifetimes); \ No newline at end of file diff --git a/util/test/demos/demos.vcxproj b/util/test/demos/demos.vcxproj index a24ff95dc..db200621f 100644 --- a/util/test/demos/demos.vcxproj +++ b/util/test/demos/demos.vcxproj @@ -135,6 +135,7 @@ + @@ -149,6 +150,7 @@ + @@ -172,6 +174,7 @@ + @@ -201,6 +204,7 @@ + diff --git a/util/test/demos/demos.vcxproj.filters b/util/test/demos/demos.vcxproj.filters index 5e99fb59b..9f845e0ad 100644 --- a/util/test/demos/demos.vcxproj.filters +++ b/util/test/demos/demos.vcxproj.filters @@ -273,6 +273,18 @@ OpenGL\demos + + OpenGL\demos + + + Vulkan\demos + + + D3D11\demos + + + D3D12\demos + diff --git a/util/test/demos/gl/gl_resource_lifetimes.cpp b/util/test/demos/gl/gl_resource_lifetimes.cpp new file mode 100644 index 000000000..c8f8acb7f --- /dev/null +++ b/util/test/demos/gl/gl_resource_lifetimes.cpp @@ -0,0 +1,416 @@ +/****************************************************************************** + * The MIT License (MIT) + * + * Copyright (c) 2015-2019 Baldur Karlsson + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + ******************************************************************************/ + +#include "gl_test.h" + +struct GL_Resource_Lifetimes : OpenGLGraphicsTest +{ + static constexpr const char *Description = + "Test various edge-case resource lifetimes: a resource that is first dirtied within a frame " + "so needs initial contents created for it, and a resource that is created and destroyed " + "mid-frame (which also gets dirtied after use)."; + + std::string common = R"EOSHADER( + +#version 420 core + +#define v2f v2f_block \ +{ \ + vec4 pos; \ + vec4 col; \ + vec4 uv; \ +} + +)EOSHADER"; + + std::string vertex = R"EOSHADER( + +layout(location = 0) in vec3 Position; +layout(location = 1) in vec4 Color; +layout(location = 2) in vec2 UV; + +out gl_PerVertex { + vec4 gl_Position; +}; + +out v2f vertOut; + +void main() +{ + vertOut.pos = vec4(Position.xyz, 1); + gl_Position = vertOut.pos; + vertOut.col = Color; + vertOut.uv = vec4(UV.xy, 0, 1); +} + +)EOSHADER"; + + std::string pixel = R"EOSHADER( + +in v2f vertIn; + +layout(location = 0, index = 0) out vec4 Color; + +layout(binding = 0) uniform sampler2D checker; +layout(binding = 1) uniform sampler2D smiley; + +layout(std140) uniform constsbuf +{ + vec4 flags; +}; + +uniform vec4 flags2; + +void main() +{ + if(flags.x != 1.0f || flags.y != 2.0f || flags.z != 4.0f || flags.w != 8.0f) + { + Color = vec4(1.0f, 0.0f, 1.0f, 1.0f); + return; + } + + if(flags != flags2) + { + Color = vec4(0.5f, 0.0f, 0.5f, 1.0f); + return; + } + + Color = texture(smiley, vertIn.uv.xy * 2.0f) * texture(checker, vertIn.uv.xy * 5.0f); + Color.w = 1.0f; +} + +)EOSHADER"; + + std::string dummy = R"EOSHADER( +#version 420 core + +layout(location = 0, index = 0) out vec4 Color; + +void main() +{ + Color = vec4(1.0f, 0.0f, 0.0f, 1.0f); +} + +)EOSHADER"; + + int main(int argc, char **argv) + { + // initialise, create window, create context, etc + if(!Init(argc, argv)) + return 3; + + GLuint vb = MakeBuffer(); + glBindBuffer(GL_ARRAY_BUFFER, vb); + glBufferStorage(GL_ARRAY_BUFFER, sizeof(DefaultTri), DefaultTri, 0); + + uint16_t indices[] = {0, 1, 2}; + GLuint ib = MakeBuffer(); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ib); + glBufferStorage(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, 0); + + Texture rgba8; + LoadXPM(SmileyTexture, rgba8); + + GLuint offscreen = MakeTexture(); + glBindTexture(GL_TEXTURE_2D, offscreen); + glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, 128, 128); + + GLuint smiley = MakeTexture(); + glBindTexture(GL_TEXTURE_2D, smiley); + glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, rgba8.width, rgba8.height); + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, rgba8.width, rgba8.height, GL_RGBA, GL_UNSIGNED_BYTE, + rgba8.data.data()); + + glActiveTexture(GL_TEXTURE1); + glBindTexture(GL_TEXTURE_2D, smiley); + + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, 0); + + GLuint vsprog = MakeProgram(common + vertex, ""); + + std::string fssrc = common + pixel; + const char *fssrc_c = fssrc.c_str(); + + const char *dummysrc_c = dummy.c_str(); + + // function to set up a VAO + auto SetupVAO = [ib]() { + GLuint vao = 0; + glGenVertexArrays(1, &vao); + glBindVertexArray(vao); + + glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(DefaultA2V), (void *)(0)); + glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, sizeof(DefaultA2V), (void *)(sizeof(Vec3f))); + glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, sizeof(DefaultA2V), + (void *)(sizeof(Vec3f) + sizeof(Vec4f))); + + glEnableVertexAttribArray(0); + glEnableVertexAttribArray(1); + glEnableVertexAttribArray(2); + + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ib); + + return vao; + }; + + // function to trash it again afterwards so that we are forced to reset the state to properly + // replay + auto TrashVAO = [](GLuint vao) { + glDisableVertexAttribArray(0); + glDisableVertexAttribArray(1); + glDisableVertexAttribArray(2); + + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); + + glDeleteVertexArrays(1, &vao); + }; + + // Sampler setup and trashing + auto SetupSampler = []() { + GLuint sampler = 0; + glGenSamplers(1, &sampler); + glBindSampler(1, sampler); + + glSamplerParameteri(sampler, GL_TEXTURE_WRAP_R, GL_REPEAT); + glSamplerParameteri(sampler, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + + return sampler; + }; + + auto TrashSampler = [](GLuint sampler) { + glSamplerParameteri(sampler, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); + glSamplerParameteri(sampler, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + + glDeleteSamplers(1, &sampler); + }; + + auto SetupProgram = [fssrc_c]() { + GLuint prog = glCreateProgram(); + glProgramParameteri(prog, GL_PROGRAM_SEPARABLE, GL_TRUE); + + GLuint shad = glCreateShader(GL_FRAGMENT_SHADER); + + glShaderSource(shad, 1, &fssrc_c, NULL); + glCompileShader(shad); + + glAttachShader(prog, shad); + + glLinkProgram(prog); + + glDetachShader(prog, shad); + glDeleteShader(shad); + + glUniformBlockBinding(prog, glGetUniformBlockIndex(prog, "constsbuf"), 5); + + const Vec4f flags = {1.0f, 2.0f, 4.0f, 8.0f}; + + glProgramUniform4fv(prog, glGetUniformLocation(prog, "flags2"), 1, &flags.x); + + return prog; + }; + + auto TrashProgram = [dummysrc_c](GLuint prog) { + glUniformBlockBinding(prog, glGetUniformBlockIndex(prog, "constsbuf"), 4); + + const Vec4f empty = {}; + glProgramUniform4fv(prog, glGetUniformLocation(prog, "flags2"), 1, &empty.x); + + glDeleteProgram(prog); + }; + + // Program pipeline setup and trashing + auto SetupPipe = [vsprog](GLuint prog) { + GLuint pipe = 0; + glGenProgramPipelines(1, &pipe); + glBindProgramPipeline(pipe); + + glUseProgramStages(pipe, GL_VERTEX_SHADER_BIT, vsprog); + glUseProgramStages(pipe, GL_FRAGMENT_SHADER_BIT, prog); + + return pipe; + }; + + auto TrashPipe = [](GLuint pipe) { + glUseProgramStages(pipe, GL_VERTEX_SHADER_BIT, 0); + glUseProgramStages(pipe, GL_FRAGMENT_SHADER_BIT, 0); + + glDeleteProgramPipelines(1, &pipe); + }; + + auto SetupFBO = [offscreen]() { + GLuint fbo = 0; + glGenFramebuffers(1, &fbo); + glBindFramebuffer(GL_FRAMEBUFFER, fbo); + + glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, offscreen, 0); + GLenum col0 = GL_COLOR_ATTACHMENT0; + glDrawBuffers(1, &col0); + + return fbo; + }; + + auto TrashFBO = [](GLuint fbo) { + glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, 0, 0); + + GLenum col0 = GL_NONE; + glDrawBuffers(1, &col0); + + glDeleteFramebuffers(1, &fbo); + }; + + auto SetupTex = []() { + const uint32_t checker[4 * 4] = { + // X X O O + 0xffffffff, 0xffffffff, 0, 0, + // X X O O + 0xffffffff, 0xffffffff, 0, 0, + // O O X X + 0, 0, 0xffffffff, 0xffffffff, + // O O X X + 0, 0, 0xffffffff, 0xffffffff, + }; + + GLuint tex = 0; + glGenTextures(1, &tex); + glBindTexture(GL_TEXTURE_2D, tex); + glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, 4, 4); + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 4, 4, GL_RGBA, GL_UNSIGNED_BYTE, checker); + + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_R, GL_REPEAT); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); + + return tex; + }; + + auto TrashTex = [](GLuint tex) { + const uint32_t empty[4 * 4] = {}; + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 4, 4, GL_RGBA, GL_UNSIGNED_BYTE, empty); + + glDeleteTextures(1, &tex); + }; + + auto SetupBuf = []() { + const float empty = {}; + const Vec4f flags = {1.0f, 2.0f, 4.0f, 8.0f}; + + GLuint buf = 0; + glGenBuffers(1, &buf); + glBindBuffer(GL_UNIFORM_BUFFER, buf); + glBufferData(GL_UNIFORM_BUFFER, sizeof(empty), &empty, GL_STATIC_DRAW); + + glBufferData(GL_UNIFORM_BUFFER, sizeof(flags), &flags, GL_STATIC_DRAW); + + glBindBufferBase(GL_UNIFORM_BUFFER, 5, buf); + + return buf; + }; + + auto TrashBuf = [](GLuint buf) { + const float empty = {}; + glBufferData(GL_UNIFORM_BUFFER, sizeof(empty), &empty, GL_STATIC_DRAW); + + glDeleteBuffers(1, &buf); + }; + + glUseProgram(0); + glViewport(0, 0, 128, 128); + + GLuint fbo = SetupFBO(); + GLuint vao = SetupVAO(); + GLuint sampler = SetupSampler(); + GLuint fsprog = SetupProgram(); + GLuint pipe = SetupPipe(fsprog); + GLuint tex = SetupTex(); + GLuint buf = SetupBuf(); + while(Running()) + { + float col[] = {0.4f, 0.5f, 0.6f, 1.0f}; + glClearNamedFramebufferfv(0, GL_COLOR, 0, col); + + // render with last frame's resources + float col1[] = {0.5f, 0.1f, 0.1f, 1.0f}; + glClearBufferfv(GL_COLOR, 0, col1); + + glDrawElements(GL_TRIANGLES, 3, GL_UNSIGNED_SHORT, NULL); + glBlitNamedFramebuffer(fbo, 0, 0, 0, 128, 128, 0, 0, 128, 128, GL_COLOR_BUFFER_BIT, GL_NEAREST); + + // trash last frame's resources + TrashFBO(fbo); + TrashVAO(vao); + TrashSampler(sampler); + TrashProgram(fsprog); + TrashPipe(pipe); + TrashTex(tex); + TrashBuf(buf); + + // create resources mid-frame and use then trash them + fbo = SetupFBO(); + vao = SetupVAO(); + sampler = SetupSampler(); + fsprog = SetupProgram(); + pipe = SetupPipe(fsprog); + tex = SetupTex(); + buf = SetupBuf(); + float col2[] = {0.1f, 0.1f, 0.5f, 1.0f}; + glClearBufferfv(GL_COLOR, 0, col2); + + glDrawElements(GL_TRIANGLES, 3, GL_UNSIGNED_SHORT, NULL); + glBlitNamedFramebuffer(fbo, 0, 0, 0, 128, 128, 128, 0, 256, 128, GL_COLOR_BUFFER_BIT, + GL_NEAREST); + TrashFBO(fbo); + TrashVAO(vao); + TrashSampler(sampler); + TrashProgram(fsprog); + TrashPipe(pipe); + TrashTex(tex); + TrashBuf(buf); + + // set up resources for next frame + fbo = SetupFBO(); + vao = SetupVAO(); + sampler = SetupSampler(); + fsprog = SetupProgram(); + pipe = SetupPipe(fsprog); + tex = SetupTex(); + buf = SetupBuf(); + + Present(); + } + + // destroy resources + TrashFBO(fbo); + TrashVAO(vao); + TrashSampler(sampler); + TrashProgram(fsprog); + TrashPipe(pipe); + TrashBuf(buf); + + return 0; + } +}; + +REGISTER_TEST(GL_Resource_Lifetimes); \ No newline at end of file diff --git a/util/test/demos/gl/gl_simple_triangle.cpp b/util/test/demos/gl/gl_simple_triangle.cpp index 928295e42..7844b3d24 100644 --- a/util/test/demos/gl/gl_simple_triangle.cpp +++ b/util/test/demos/gl/gl_simple_triangle.cpp @@ -49,6 +49,10 @@ layout(location = 0) in vec3 Position; layout(location = 1) in vec4 Color; layout(location = 2) in vec2 UV; +out gl_PerVertex { + vec4 gl_Position; +}; + out v2f vertOut; void main() @@ -67,9 +71,23 @@ in v2f vertIn; layout(location = 0, index = 0) out vec4 Color; +layout(binding = 0) uniform sampler2D checker; +layout(binding = 1) uniform sampler2D smiley; + +layout(binding = 0, std140) uniform constsbuf +{ + vec4 flags; +}; + void main() { - Color = vertIn.col; + if(flags.x != 1.0f || flags.y != 2.0f || flags.z != 4.0f || flags.w != 8.0f) + { + Color = vec4(1.0f, 0.0f, 1.0f, 1.0f); + return; + } + + Color = vertIn.col * texture(smiley, vertIn.uv.xy * 2.0f) * texture(checker, vertIn.uv.xy * 5.0f); } )EOSHADER"; @@ -80,41 +98,251 @@ void main() if(!Init(argc, argv)) return 3; - GLuint vao = MakeVAO(); - glBindVertexArray(vao); - GLuint vb = MakeBuffer(); glBindBuffer(GL_ARRAY_BUFFER, vb); glBufferStorage(GL_ARRAY_BUFFER, sizeof(DefaultTri), DefaultTri, 0); - glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(DefaultA2V), (void *)(0)); - glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, sizeof(DefaultA2V), (void *)(sizeof(Vec3f))); - glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, sizeof(DefaultA2V), - (void *)(sizeof(Vec3f) + sizeof(Vec4f))); + uint16_t indices[] = {0, 1, 2}; + GLuint ib = MakeBuffer(); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ib); + glBufferStorage(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, 0); - glEnableVertexAttribArray(0); - glEnableVertexAttribArray(1); - glEnableVertexAttribArray(2); + Texture rgba8; + LoadXPM(SmileyTexture, rgba8); - GLuint program = MakeProgram(common + vertex, common + pixel); - glObjectLabel(GL_PROGRAM, program, -1, "Full program"); + GLuint offscreen = MakeTexture(); + glBindTexture(GL_TEXTURE_2D, offscreen); + glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, 128, 128); + GLuint smiley = MakeTexture(); + glBindTexture(GL_TEXTURE_2D, smiley); + glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, rgba8.width, rgba8.height); + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, rgba8.width, rgba8.height, GL_RGBA, GL_UNSIGNED_BYTE, + rgba8.data.data()); + + glActiveTexture(GL_TEXTURE1); + glBindTexture(GL_TEXTURE_2D, smiley); + + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, 0); + + GLuint vsprog = MakeProgram(common + vertex, ""); + GLuint fsprog = MakeProgram("", common + pixel); + + // function to set up a VAO + auto SetupVAO = [ib]() { + GLuint vao = 0; + glGenVertexArrays(1, &vao); + glBindVertexArray(vao); + + glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(DefaultA2V), (void *)(0)); + glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, sizeof(DefaultA2V), (void *)(sizeof(Vec3f))); + glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, sizeof(DefaultA2V), + (void *)(sizeof(Vec3f) + sizeof(Vec4f))); + + glEnableVertexAttribArray(0); + glEnableVertexAttribArray(1); + glEnableVertexAttribArray(2); + + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ib); + + return vao; + }; + + // function to trash it again afterwards so that we are forced to reset the state to properly + // replay + auto TrashVAO = [](GLuint vao) { + glDisableVertexAttribArray(0); + glDisableVertexAttribArray(1); + glDisableVertexAttribArray(2); + + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); + + glDeleteVertexArrays(1, &vao); + }; + + // Sampler setup and trashing + auto SetupSampler = []() { + GLuint sampler = 0; + glGenSamplers(1, &sampler); + glBindSampler(1, sampler); + + glSamplerParameteri(sampler, GL_TEXTURE_WRAP_R, GL_REPEAT); + glSamplerParameteri(sampler, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + + return sampler; + }; + + auto TrashSampler = [](GLuint sampler) { + glSamplerParameteri(sampler, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); + glSamplerParameteri(sampler, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + + glDeleteSamplers(1, &sampler); + }; + + // Program pipeline setup and trashing + auto SetupPipe = [vsprog, fsprog]() { + GLuint pipe = 0; + glGenProgramPipelines(1, &pipe); + glBindProgramPipeline(pipe); + + glUseProgramStages(pipe, GL_VERTEX_SHADER_BIT, vsprog); + glUseProgramStages(pipe, GL_FRAGMENT_SHADER_BIT, fsprog); + + return pipe; + }; + + auto TrashPipe = [](GLuint pipe) { + glUseProgramStages(pipe, GL_VERTEX_SHADER_BIT, 0); + glUseProgramStages(pipe, GL_FRAGMENT_SHADER_BIT, 0); + + glDeleteProgramPipelines(1, &pipe); + }; + + auto SetupFBO = [offscreen]() { + GLuint fbo = 0; + glGenFramebuffers(1, &fbo); + glBindFramebuffer(GL_FRAMEBUFFER, fbo); + + glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, offscreen, 0); + GLenum col0 = GL_COLOR_ATTACHMENT0; + glDrawBuffers(1, &col0); + + return fbo; + }; + + auto TrashFBO = [](GLuint fbo) { + glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, 0, 0); + + GLenum col0 = GL_NONE; + glDrawBuffers(1, &col0); + + glDeleteFramebuffers(1, &fbo); + }; + + auto SetupTex = []() { + const uint32_t checker[4 * 4] = { + // X X O O + 0xffffffff, 0xffffffff, 0, 0, + // X X O O + 0xffffffff, 0xffffffff, 0, 0, + // O O X X + 0, 0, 0xffffffff, 0xffffffff, + // O O X X + 0, 0, 0xffffffff, 0xffffffff, + }; + + GLuint tex = 0; + glGenTextures(1, &tex); + glBindTexture(GL_TEXTURE_2D, tex); + glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, 4, 4); + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 4, 4, GL_RGBA, GL_UNSIGNED_BYTE, checker); + + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_R, GL_REPEAT); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); + + return tex; + }; + + auto TrashTex = [](GLuint tex) { + const uint32_t empty[4 * 4] = {}; + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 4, 4, GL_RGBA, GL_UNSIGNED_BYTE, empty); + + glDeleteTextures(1, &tex); + }; + + auto SetupBuf = []() { + const float empty = {}; + const Vec4f flags = {1.0f, 2.0f, 4.0f, 8.0f}; + + GLuint buf = 0; + glGenBuffers(1, &buf); + glBindBuffer(GL_UNIFORM_BUFFER, buf); + glBufferData(GL_UNIFORM_BUFFER, sizeof(empty), &empty, GL_STATIC_DRAW); + + glBufferData(GL_UNIFORM_BUFFER, sizeof(flags), &flags, GL_STATIC_DRAW); + + glBindBufferBase(GL_UNIFORM_BUFFER, 0, buf); + + return buf; + }; + + auto TrashBuf = [](GLuint buf) { + const float empty = {}; + glBufferData(GL_UNIFORM_BUFFER, sizeof(empty), &empty, GL_STATIC_DRAW); + + glDeleteBuffers(1, &buf); + }; + + glUseProgram(0); + glViewport(0, 0, 128, 128); + + GLuint fbo = SetupFBO(); + GLuint vao = SetupVAO(); + GLuint sampler = SetupSampler(); + GLuint pipe = SetupPipe(); + GLuint tex = SetupTex(); + GLuint buf = SetupBuf(); while(Running()) { float col[] = {0.4f, 0.5f, 0.6f, 1.0f}; - glClearBufferfv(GL_COLOR, 0, col); + glClearNamedFramebufferfv(0, GL_COLOR, 0, col); - glBindVertexArray(vao); + // render with last frame's resources + float col1[] = {0.5f, 0.1f, 0.1f, 1.0f}; + glClearBufferfv(GL_COLOR, 0, col1); - glUseProgram(program); + glDrawElements(GL_TRIANGLES, 3, GL_UNSIGNED_SHORT, NULL); + glBlitNamedFramebuffer(fbo, 0, 0, 0, 128, 128, 0, 0, 128, 128, GL_COLOR_BUFFER_BIT, GL_NEAREST); - glViewport(0, 0, GLsizei(screenWidth), GLsizei(screenHeight)); + // trash last frame's resources + TrashFBO(fbo); + TrashVAO(vao); + TrashSampler(sampler); + TrashPipe(pipe); + TrashTex(tex); + TrashBuf(buf); - glDrawArrays(GL_TRIANGLES, 0, 3); + // create resources mid-frame and use then trash them + fbo = SetupFBO(); + vao = SetupVAO(); + sampler = SetupSampler(); + pipe = SetupPipe(); + tex = SetupTex(); + buf = SetupBuf(); + float col2[] = {0.1f, 0.1f, 0.5f, 1.0f}; + glClearBufferfv(GL_COLOR, 0, col2); + + glDrawElements(GL_TRIANGLES, 3, GL_UNSIGNED_SHORT, NULL); + glBlitNamedFramebuffer(fbo, 0, 0, 0, 128, 128, 128, 0, 256, 128, GL_COLOR_BUFFER_BIT, + GL_NEAREST); + TrashFBO(fbo); + TrashVAO(vao); + TrashSampler(sampler); + TrashPipe(pipe); + TrashTex(tex); + TrashBuf(buf); + + // set up resources for next frame + fbo = SetupFBO(); + vao = SetupVAO(); + sampler = SetupSampler(); + pipe = SetupPipe(); + tex = SetupTex(); + buf = SetupBuf(); Present(); } + // destroy resources + TrashFBO(fbo); + TrashVAO(vao); + TrashSampler(sampler); + TrashPipe(pipe); + TrashBuf(buf); + return 0; } }; diff --git a/util/test/demos/vk/vk_resource_lifetimes.cpp b/util/test/demos/vk/vk_resource_lifetimes.cpp new file mode 100644 index 000000000..1aa013822 --- /dev/null +++ b/util/test/demos/vk/vk_resource_lifetimes.cpp @@ -0,0 +1,570 @@ +/****************************************************************************** + * The MIT License (MIT) + * + * Copyright (c) 2018-2019 Baldur Karlsson + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + ******************************************************************************/ + +#include "vk_test.h" + +struct VK_Resource_Lifetimes : VulkanGraphicsTest +{ + static constexpr const char *Description = + "Test various edge-case resource lifetimes: a resource that is first dirtied within a frame " + "so needs initial contents created for it, and a resource that is created and destroyed " + "mid-frame (which also gets dirtied after use)."; + + std::string common = R"EOSHADER( + +#version 420 core + +struct v2f +{ + vec4 pos; + vec4 col; + vec4 uv; +}; + +)EOSHADER"; + + const std::string vertex = R"EOSHADER( + +layout(location = 0) in vec3 Position; +layout(location = 1) in vec4 Color; +layout(location = 2) in vec2 UV; + +layout(location = 0) out v2f vertOut; + +void main() +{ + vertOut.pos = vec4(Position.xyz*vec3(1,-1,1), 1); + gl_Position = vertOut.pos; + vertOut.col = Color; + vertOut.uv = vec4(UV.xy, 0, 1); +} + +)EOSHADER"; + + const std::string pixel = R"EOSHADER( + +layout(location = 0) in v2f vertIn; + +layout(location = 0, index = 0) out vec4 Color; + +layout(binding = 0) uniform sampler2D smiley; +layout(binding = 1) uniform sampler2D checker; + +layout(binding = 2, std140) uniform constsbuf +{ + vec4 flags; +}; + +void main() +{ + if(flags.x != 1.0f || flags.y != 2.0f || flags.z != 4.0f || flags.w != 8.0f) + { + Color = vec4(1.0f, 0.0f, 1.0f, 1.0f); + return; + } + + Color = texture(smiley, vertIn.uv.xy * 2.0f) * texture(checker, vertIn.uv.xy * 5.0f); +} + +)EOSHADER"; + + int main(int argc, char **argv) + { + // initialise, create window, create context, etc + if(!Init(argc, argv)) + return 3; + + VkDescriptorSetLayout setlayout = createDescriptorSetLayout(vkh::DescriptorSetLayoutCreateInfo({ + { + 0, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, VK_SHADER_STAGE_FRAGMENT_BIT, + }, + { + 1, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, VK_SHADER_STAGE_FRAGMENT_BIT, + }, + { + 2, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1, VK_SHADER_STAGE_FRAGMENT_BIT, + }, + })); + + VkPipelineLayout layout = createPipelineLayout(vkh::PipelineLayoutCreateInfo({setlayout})); + + vkh::GraphicsPipelineCreateInfo pipeCreateInfo; + + pipeCreateInfo.layout = layout; + pipeCreateInfo.renderPass = swapRenderPass; + + pipeCreateInfo.vertexInputState.vertexBindingDescriptions = {vkh::vertexBind(0, DefaultA2V)}; + pipeCreateInfo.vertexInputState.vertexAttributeDescriptions = { + vkh::vertexAttr(0, 0, DefaultA2V, pos), vkh::vertexAttr(1, 0, DefaultA2V, col), + vkh::vertexAttr(2, 0, DefaultA2V, uv), + }; + + pipeCreateInfo.stages = { + CompileShaderModule(common + vertex, ShaderLang::glsl, ShaderStage::vert, "main"), + CompileShaderModule(common + pixel, ShaderLang::glsl, ShaderStage::frag, "main"), + }; + + VkPipeline pipe = createGraphicsPipeline(pipeCreateInfo); + + AllocatedBuffer vb( + allocator, vkh::BufferCreateInfo(sizeof(DefaultTri), VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | + VK_BUFFER_USAGE_TRANSFER_DST_BIT), + VmaAllocationCreateInfo({0, VMA_MEMORY_USAGE_CPU_TO_GPU})); + + vb.upload(DefaultTri); + + Texture rgba8; + LoadXPM(SmileyTexture, rgba8); + + AllocatedImage smiley( + allocator, vkh::ImageCreateInfo(rgba8.width, rgba8.height, 0, VK_FORMAT_R8G8B8A8_UNORM, + VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT), + VmaAllocationCreateInfo({0, VMA_MEMORY_USAGE_GPU_ONLY})); + + VkImageView smileyview = createImageView( + vkh::ImageViewCreateInfo(smiley.image, VK_IMAGE_VIEW_TYPE_2D, VK_FORMAT_R8G8B8A8_UNORM)); + + AllocatedImage badimg(allocator, vkh::ImageCreateInfo(4, 4, 0, VK_FORMAT_R8G8B8A8_UNORM, + VK_IMAGE_USAGE_TRANSFER_DST_BIT | + VK_IMAGE_USAGE_SAMPLED_BIT), + VmaAllocationCreateInfo({0, VMA_MEMORY_USAGE_GPU_ONLY})); + + VkImageView badview = createImageView( + vkh::ImageViewCreateInfo(badimg.image, VK_IMAGE_VIEW_TYPE_2D, VK_FORMAT_R8G8B8A8_UNORM)); + + AllocatedBuffer uploadBuf(allocator, vkh::BufferCreateInfo(rgba8.data.size() * sizeof(uint32_t), + VK_BUFFER_USAGE_TRANSFER_SRC_BIT), + VmaAllocationCreateInfo({0, VMA_MEMORY_USAGE_CPU_TO_GPU})); + + uploadBuf.upload(rgba8.data.data(), rgba8.data.size() * sizeof(uint32_t)); + + { + VkCommandBuffer cmd = GetCommandBuffer(); + + vkBeginCommandBuffer(cmd, vkh::CommandBufferBeginInfo()); + + vkh::cmdPipelineBarrier( + cmd, { + vkh::ImageMemoryBarrier(0, VK_ACCESS_TRANSFER_WRITE_BIT, VK_IMAGE_LAYOUT_UNDEFINED, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, smiley.image), + }); + + VkBufferImageCopy copy = {}; + copy.imageExtent = {rgba8.width, rgba8.height, 1}; + copy.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + copy.imageSubresource.layerCount = 1; + + vkCmdCopyBufferToImage(cmd, uploadBuf.buffer, smiley.image, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ©); + + vkh::cmdPipelineBarrier( + cmd, { + vkh::ImageMemoryBarrier(VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, smiley.image), + vkh::ImageMemoryBarrier(0, VK_ACCESS_SHADER_READ_BIT, VK_IMAGE_LAYOUT_UNDEFINED, + VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, badimg.image), + }); + + vkEndCommandBuffer(cmd); + + Submit(99, 99, {cmd}); + + vkDeviceWaitIdle(device); + } + + Vec4f flags = {}; + AllocatedBuffer badcb(allocator, + vkh::BufferCreateInfo(sizeof(flags), VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | + VK_BUFFER_USAGE_TRANSFER_DST_BIT), + VmaAllocationCreateInfo({0, VMA_MEMORY_USAGE_CPU_TO_GPU})); + + badcb.upload(&flags, sizeof(flags)); + + VkSampler sampler = VK_NULL_HANDLE; + + VkSamplerCreateInfo sampInfo = {VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO}; + sampInfo.magFilter = VK_FILTER_LINEAR; + sampInfo.minFilter = VK_FILTER_LINEAR; + + vkCreateSampler(device, &sampInfo, NULL, &sampler); + + auto SetupBuffer = [this]() { + VkBuffer cb = VK_NULL_HANDLE; + + vkCreateBuffer(device, + vkh::BufferCreateInfo(sizeof(Vec4f), VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | + VK_BUFFER_USAGE_TRANSFER_DST_BIT), + NULL, &cb); + + return cb; + }; + + const VkPhysicalDeviceMemoryProperties *props = NULL; + vmaGetMemoryProperties(allocator, &props); + + auto SetupBufferMemory = [this, props](VkBuffer cb) { + VkMemoryRequirements mrq; + vkGetBufferMemoryRequirements(device, cb, &mrq); + + VkMemoryAllocateInfo info = {}; + info.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; + info.allocationSize = mrq.size; + + for(uint32_t i = 0; i < props->memoryTypeCount; i++) + { + if((mrq.memoryTypeBits & (1u << i)) && + (props->memoryTypes[i].propertyFlags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT)) + { + info.memoryTypeIndex = i; + break; + } + } + + VkDeviceMemory mem = VK_NULL_HANDLE; + vkAllocateMemory(device, &info, NULL, &mem); + vkBindBufferMemory(device, cb, mem, 0); + + Vec4f *data = NULL; + vkMapMemory(device, mem, 0, sizeof(Vec4f), 0, (void **)&data); + { + *data = Vec4f(1.0f, 2.0f, 4.0f, 8.0f); + } + vkUnmapMemory(device, mem); + + return mem; + }; + + auto TrashBuffer = [this](VkBuffer cb, VkDeviceMemory mem) { + Vec4f *data = NULL; + vkMapMemory(device, mem, 0, sizeof(Vec4f), 0, (void **)&data); + { + *data = Vec4f(); + } + vkUnmapMemory(device, mem); + + vkDestroyBuffer(device, cb, NULL); + vkFreeMemory(device, mem, NULL); + }; + + auto SetupImage = [this]() { + VkImage img = VK_NULL_HANDLE; + + vkCreateImage( + device, vkh::ImageCreateInfo(4, 4, 0, VK_FORMAT_R8G8B8A8_UNORM, + VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT), + NULL, &img); + + return img; + }; + + auto SetupImageMemory = [this, props, &uploadBuf](VkImage img) { + VkMemoryRequirements mrq; + vkGetImageMemoryRequirements(device, img, &mrq); + + VkMemoryAllocateInfo info = {}; + info.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; + info.allocationSize = mrq.size; + + for(uint32_t i = 0; i < props->memoryTypeCount; i++) + { + if(mrq.memoryTypeBits & (1 << i)) + { + info.memoryTypeIndex = i; + break; + } + } + + VkDeviceMemory mem = VK_NULL_HANDLE; + vkAllocateMemory(device, &info, NULL, &mem); + vkBindImageMemory(device, img, mem, 0); + + const uint32_t checker[4 * 4] = { + // X X O O + 0xffffffff, 0xffffffff, 0, 0, + // X X O O + 0xffffffff, 0xffffffff, 0, 0, + // O O X X + 0, 0, 0xffffffff, 0xffffffff, + // O O X X + 0, 0, 0xffffffff, 0xffffffff, + }; + + uploadBuf.upload(checker); + + { + VkCommandBuffer cmd = GetCommandBuffer(); + + vkBeginCommandBuffer(cmd, vkh::CommandBufferBeginInfo()); + + vkh::cmdPipelineBarrier( + cmd, + { + vkh::ImageMemoryBarrier(0, VK_ACCESS_TRANSFER_WRITE_BIT, VK_IMAGE_LAYOUT_UNDEFINED, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, img), + }); + + VkBufferImageCopy copy = {}; + copy.imageExtent = {4, 4, 1}; + copy.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + copy.imageSubresource.layerCount = 1; + + vkCmdCopyBufferToImage(cmd, uploadBuf.buffer, img, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, + ©); + + vkh::cmdPipelineBarrier( + cmd, { + vkh::ImageMemoryBarrier(VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, img), + }); + + vkEndCommandBuffer(cmd); + + Submit(99, 99, {cmd}); + + vkDeviceWaitIdle(device); + } + + return mem; + }; + + auto SetupImageView = [this](VkImage img) { + VkImageView ret; + vkCreateImageView( + device, vkh::ImageViewCreateInfo(img, VK_IMAGE_VIEW_TYPE_2D, VK_FORMAT_R8G8B8A8_UNORM), + NULL, &ret); + return ret; + }; + + auto TrashImage = [this](VkImage img, VkDeviceMemory mem, VkImageView view) { + + vkDestroyImageView(device, view, NULL); + vkDestroyImage(device, img, NULL); + vkFreeMemory(device, mem, NULL); + }; + + VkDescriptorPool descpool = VK_NULL_HANDLE; + + { + CHECK_VKR(vkCreateDescriptorPool( + device, vkh::DescriptorPoolCreateInfo(8, + { + {VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1024}, + {VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1024}, + }, + VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT), + NULL, &descpool)); + } + + auto SetupDescSet = [this, setlayout, descpool, sampler, smileyview](VkBuffer cb, + VkImageView view) { + VkDescriptorSet descset = VK_NULL_HANDLE; + + vkAllocateDescriptorSets(device, vkh::DescriptorSetAllocateInfo(descpool, {setlayout}), + &descset); + + vkh::updateDescriptorSets( + device, { + vkh::WriteDescriptorSet( + descset, 0, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, + { + vkh::DescriptorImageInfo( + smileyview, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, sampler), + }), + vkh::WriteDescriptorSet( + descset, 1, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, + { + vkh::DescriptorImageInfo( + view, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, sampler), + }), + vkh::WriteDescriptorSet(descset, 2, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, + {vkh::DescriptorBufferInfo(cb)}), + }); + + return descset; + }; + + auto TrashDescSet = [this, descpool, sampler, &badcb, badview](VkDescriptorSet descset) { + + // update with bad data + vkh::updateDescriptorSets( + device, { + vkh::WriteDescriptorSet( + descset, 0, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, + { + vkh::DescriptorImageInfo( + badview, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, sampler), + }), + vkh::WriteDescriptorSet( + descset, 1, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, + { + vkh::DescriptorImageInfo( + badview, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, sampler), + }), + vkh::WriteDescriptorSet(descset, 2, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, + {vkh::DescriptorBufferInfo(badcb.buffer)}), + }); + + // assume we are able to re-use the same descriptor pool indefinitely since we only allocate + // one set at a time. + vkFreeDescriptorSets(device, descpool, 1, &descset); + }; + + VkBuffer cb = SetupBuffer(); + VkDeviceMemory cbmem = SetupBufferMemory(cb); + VkImage img = SetupImage(); + VkDeviceMemory imgmem = SetupImageMemory(img); + VkImageView imgview = SetupImageView(img); + VkDescriptorSet descset = SetupDescSet(cb, imgview); + while(Running()) + { + // acquire and clear the backbuffer + { + VkCommandBuffer cmd = GetCommandBuffer(); + + vkBeginCommandBuffer(cmd, vkh::CommandBufferBeginInfo()); + + VkImage swapimg = + StartUsingBackbuffer(cmd, VK_ACCESS_TRANSFER_WRITE_BIT, VK_IMAGE_LAYOUT_GENERAL); + + vkCmdClearColorImage(cmd, swapimg, VK_IMAGE_LAYOUT_GENERAL, + vkh::ClearColorValue(0.4f, 0.5f, 0.6f, 1.0f), 1, + vkh::ImageSubresourceRange()); + + vkEndCommandBuffer(cmd); + + Submit(0, 4, {cmd}); + } + + // render with last frame's resources then trash them + { + VkCommandBuffer cmd = GetCommandBuffer(); + + vkBeginCommandBuffer(cmd, vkh::CommandBufferBeginInfo()); + + vkCmdBeginRenderPass( + cmd, vkh::RenderPassBeginInfo(swapRenderPass, swapFramebuffers[swapIndex], scissor), + VK_SUBPASS_CONTENTS_INLINE); + + vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, pipe); + vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, layout, 0, 1, &descset, 0, + NULL); + VkViewport view = {0, 0, 128, 128, 0, 1}; + vkCmdSetViewport(cmd, 0, 1, &view); + vkCmdSetScissor(cmd, 0, 1, &scissor); + vkh::cmdBindVertexBuffers(cmd, 0, {vb.buffer}, {0}); + vkCmdDraw(cmd, 3, 1, 0, 0); + + vkCmdEndRenderPass(cmd); + + vkEndCommandBuffer(cmd); + + Submit(1, 4, {cmd}); + + vkDeviceWaitIdle(device); + + TrashBuffer(cb, cbmem); + TrashImage(img, imgmem, imgview); + TrashDescSet(descset); + } + + // create resources mid-frame and use then trash them + { + cb = SetupBuffer(); + cbmem = SetupBufferMemory(cb); + img = SetupImage(); + imgmem = SetupImageMemory(img); + imgview = SetupImageView(img); + descset = SetupDescSet(cb, imgview); + + vkDeviceWaitIdle(device); + + VkCommandBuffer cmd = GetCommandBuffer(); + + vkBeginCommandBuffer(cmd, vkh::CommandBufferBeginInfo()); + + vkCmdBeginRenderPass( + cmd, vkh::RenderPassBeginInfo(swapRenderPass, swapFramebuffers[swapIndex], scissor), + VK_SUBPASS_CONTENTS_INLINE); + + vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, pipe); + vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, layout, 0, 1, &descset, 0, + NULL); + VkViewport view = {128, 0, 128, 128, 0, 1}; + vkCmdSetViewport(cmd, 0, 1, &view); + vkCmdSetScissor(cmd, 0, 1, &scissor); + vkh::cmdBindVertexBuffers(cmd, 0, {vb.buffer}, {0}); + vkCmdDraw(cmd, 3, 1, 0, 0); + + vkCmdEndRenderPass(cmd); + + vkEndCommandBuffer(cmd); + + Submit(2, 4, {cmd}); + + vkDeviceWaitIdle(device); + + TrashBuffer(cb, cbmem); + TrashImage(img, imgmem, imgview); + TrashDescSet(descset); + } + + // finish with the backbuffer + { + VkCommandBuffer cmd = GetCommandBuffer(); + + vkBeginCommandBuffer(cmd, vkh::CommandBufferBeginInfo()); + + FinishUsingBackbuffer(cmd, VK_ACCESS_TRANSFER_WRITE_BIT, VK_IMAGE_LAYOUT_GENERAL); + + vkEndCommandBuffer(cmd); + + Submit(3, 4, {cmd}); + } + + // set up resources for next frame + cb = SetupBuffer(); + cbmem = SetupBufferMemory(cb); + img = SetupImage(); + imgmem = SetupImageMemory(img); + imgview = SetupImageView(img); + descset = SetupDescSet(cb, imgview); + + Present(); + } + + // destroy resources + TrashBuffer(cb, cbmem); + TrashImage(img, imgmem, imgview); + TrashDescSet(descset); + + vkDestroyDescriptorPool(device, descpool, NULL); + vkDestroySampler(device, sampler, NULL); + + return 0; + } +}; + +REGISTER_TEST(VK_Resource_Lifetimes); \ No newline at end of file diff --git a/util/test/tests/D3D11/D3D11_Resource_Lifetimes.py b/util/test/tests/D3D11/D3D11_Resource_Lifetimes.py new file mode 100644 index 000000000..a176f9011 --- /dev/null +++ b/util/test/tests/D3D11/D3D11_Resource_Lifetimes.py @@ -0,0 +1,12 @@ +import renderdoc as rd +import rdtest + + +class D3D11_Resource_Lifetimes(rdtest.TestCase): + platform = 'win32' + + def get_capture(self): + return rdtest.run_and_capture("demos_x64", "D3D11_Resource_Lifetimes", 5) + + def check_capture(self): + self.check_final_backbuffer() diff --git a/util/test/tests/D3D12/D3D12_Resource_Lifetimes.py b/util/test/tests/D3D12/D3D12_Resource_Lifetimes.py new file mode 100644 index 000000000..72f1bcb67 --- /dev/null +++ b/util/test/tests/D3D12/D3D12_Resource_Lifetimes.py @@ -0,0 +1,13 @@ +import renderdoc as rd +import rdtest + + +class D3D12_Resource_Lifetimes(rdtest.TestCase): + platform = 'win32' + platform_version = 10 + + def get_capture(self): + return rdtest.run_and_capture("demos_x64", "D3D12_Resource_Lifetimes", 5) + + def check_capture(self): + self.check_final_backbuffer() diff --git a/util/test/tests/GL/GL_Resource_Lifetimes.py b/util/test/tests/GL/GL_Resource_Lifetimes.py new file mode 100644 index 000000000..5ec2b9d1a --- /dev/null +++ b/util/test/tests/GL/GL_Resource_Lifetimes.py @@ -0,0 +1,10 @@ +import renderdoc as rd +import rdtest + + +class GL_Resource_Lifetimes(rdtest.TestCase): + def get_capture(self): + return rdtest.run_and_capture("demos_x64", "GL_Resource_Lifetimes", 5) + + def check_capture(self): + self.check_final_backbuffer() diff --git a/util/test/tests/Vulkan/VK_Resource_Lifetimes.py b/util/test/tests/Vulkan/VK_Resource_Lifetimes.py new file mode 100644 index 000000000..54bb4221a --- /dev/null +++ b/util/test/tests/Vulkan/VK_Resource_Lifetimes.py @@ -0,0 +1,10 @@ +import renderdoc as rd +import rdtest + + +class VK_Resource_Lifetimes(rdtest.TestCase): + def get_capture(self): + return rdtest.run_and_capture("demos_x64", "VK_Resource_Lifetimes", 5) + + def check_capture(self): + self.check_final_backbuffer()