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()