From 07158ad24cfd20f1aa47f43262e3831e2d2fe960 Mon Sep 17 00:00:00 2001 From: baldurk Date: Mon, 19 Aug 2019 14:51:43 +0100 Subject: [PATCH] Fix support for triangle fans on GL/Vulkan --- docs/python_api/renderdoc/funcs.rst | 1 + qrenderdoc/Windows/BufferViewer.cpp | 15 +- renderdoc/api/replay/replay_enums.h | 16 ++ renderdoc/data/glsl/mesh.comp | 23 +-- renderdoc/data/hlsl/mesh.hlsl | 23 +-- renderdoc/driver/d3d12/d3d12_rendermesh.cpp | 13 ++ renderdoc/driver/gl/gl_debug.cpp | 188 +++++++++++-------- renderdoc/driver/gl/gl_postvs.cpp | 2 +- renderdoc/driver/vulkan/vk_debug.cpp | 157 +++++++++++----- renderdoc/driver/vulkan/vk_overlay.cpp | 14 ++ renderdoc/driver/vulkan/vk_postvs.cpp | 4 +- renderdoc/driver/vulkan/vk_rendermesh.cpp | 6 +- renderdoc/replay/entry_points.cpp | 2 +- renderdoc/replay/replay_driver.cpp | 121 +++++++++++- renderdoc/replay/replay_driver.h | 2 + util/test/demos/CMakeLists.txt | 1 + util/test/demos/demos.vcxproj | 1 + util/test/demos/demos.vcxproj.filters | 3 + util/test/demos/vk/vk_triangle_fan.cpp | 195 ++++++++++++++++++++ 19 files changed, 629 insertions(+), 158 deletions(-) create mode 100644 util/test/demos/vk/vk_triangle_fan.cpp diff --git a/docs/python_api/renderdoc/funcs.rst b/docs/python_api/renderdoc/funcs.rst index 6985aa25a..1c6eef4bf 100644 --- a/docs/python_api/renderdoc/funcs.rst +++ b/docs/python_api/renderdoc/funcs.rst @@ -62,6 +62,7 @@ Maths & Utilities .. autofunction:: renderdoc.VertexOffset .. autofunction:: renderdoc.PatchList_Count .. autofunction:: renderdoc.PatchList_Topology +.. autofunction:: renderdoc.SupportsRestart .. autofunction:: renderdoc.IsStrip .. autofunction:: renderdoc.IsD3D .. autofunction:: renderdoc.MaskForStage diff --git a/qrenderdoc/Windows/BufferViewer.cpp b/qrenderdoc/Windows/BufferViewer.cpp index 647909bcf..7c7e9cfa9 100644 --- a/qrenderdoc/Windows/BufferViewer.cpp +++ b/qrenderdoc/Windows/BufferViewer.cpp @@ -2071,7 +2071,7 @@ void BufferViewer::OnEventChanged(uint32_t eventId) ClearModels(); if(m_Ctx.CurPipelineState().IsStripRestartEnabled() && draw && - (draw->flags & DrawFlags::Indexed) && IsStrip(draw->topology)) + (draw->flags & DrawFlags::Indexed) && SupportsRestart(draw->topology)) { bufdata->vsinConfig.primRestart = m_Ctx.CurPipelineState().GetStripRestartIndex(); @@ -2091,9 +2091,6 @@ void BufferViewer::OnEventChanged(uint32_t eventId) float vpWidth = qAbs(vp.width); float vpHeight = qAbs(vp.height); - m_Config.position.allowRestart = m_Ctx.CurPipelineState().IsStripRestartEnabled(); - m_Config.position.restartIndex = m_Ctx.CurPipelineState().GetStripRestartIndex(); - m_Config.fov = ui->fovGuess->value(); m_Config.aspect = (vpWidth > 0.0f && vpHeight > 0.0f) ? (vpWidth / vpHeight) : 1.0f; m_Config.highlightVert = 0; @@ -2526,6 +2523,11 @@ void BufferViewer::UI_CalculateMeshFormats() m_VSInPosition = MeshFormat(); m_VSInSecondary = MeshFormat(); + m_VSInPosition.allowRestart = m_Ctx.CurPipelineState().IsStripRestartEnabled() && + (draw->flags & DrawFlags::Indexed) && + SupportsRestart(draw->topology); + m_VSInPosition.restartIndex = m_Ctx.CurPipelineState().GetStripRestartIndex(); + const BufferConfiguration &vsinConfig = m_ModelVSIn->getConfig(); if(!vsinConfig.columns.empty()) @@ -2629,6 +2631,9 @@ void BufferViewer::UI_CalculateMeshFormats() } } + m_PostVSPosition.allowRestart = m_VSInPosition.allowRestart; + m_PostVSPosition.restartIndex = m_VSInPosition.restartIndex; + const BufferConfiguration &gsoutConfig = m_ModelGSOut->getConfig(); m_PostGSPosition = MeshFormat(); @@ -2654,6 +2659,8 @@ void BufferViewer::UI_CalculateMeshFormats() } } + m_PostGSPosition.allowRestart = false; + m_PostGSPosition.indexByteStride = 0; if(!(draw->flags & DrawFlags::Indexed)) diff --git a/renderdoc/api/replay/replay_enums.h b/renderdoc/api/replay/replay_enums.h index b4f006f80..aaaf5278e 100644 --- a/renderdoc/api/replay/replay_enums.h +++ b/renderdoc/api/replay/replay_enums.h @@ -1812,6 +1812,22 @@ constexpr inline uint32_t PatchList_Count(Topology topology) : uint32_t(topology) - uint32_t(Topology::PatchList_1CPs) + 1; } +DOCUMENT(R"(Check whether or not this topology supports primitive restart. + +.. note:: This is almost but not quite the same as being a line/triangle strip - triangle fans + also support restart. See also :func:`IsStrip`. + +:param Topology t: The topology to check. +:return: ``True`` if it describes a topology that allows restart, ``False`` for a list. +:rtype: ``bool`` +)"); +constexpr inline bool SupportsRestart(Topology topology) +{ + return topology == Topology::LineStrip || topology == Topology::TriangleStrip || + topology == Topology::LineStrip_Adj || topology == Topology::TriangleStrip_Adj || + topology == Topology::TriangleFan; +} + DOCUMENT(R"(Check whether or not this is a strip-type topology. :param Topology t: The topology to check. diff --git a/renderdoc/data/glsl/mesh.comp b/renderdoc/data/glsl/mesh.comp index 61d2ca75f..103b65f2a 100644 --- a/renderdoc/data/glsl/mesh.comp +++ b/renderdoc/data/glsl/mesh.comp @@ -65,6 +65,9 @@ bool TriangleRayIntersect(vec3 A, vec3 B, vec3 C, vec3 RayPosition, vec3 RayDire { bool Result = false; + if(A == B || A == C || B == C) + return false; + vec3 v0v1 = B - A; vec3 v0v2 = C - A; vec3 pvec = cross(RayDirection, v0v2); @@ -161,23 +164,21 @@ void trianglePath(uint threadID) bool hit; if(meshpick.unproject == 1u) { - hit = TriangleRayIntersect(pos0.xyz / pos0.w, pos1.xyz / pos1.w, pos2.xyz / pos2.w, - meshpick.rayPos, meshpick.rayDir, - /*out*/ hitPosition); - } - else - { - hit = TriangleRayIntersect(pos0.xyz, pos1.xyz, pos2.xyz, meshpick.rayPos, meshpick.rayDir, - /*out*/ hitPosition); + pos0.xyz /= pos0.w; + pos1.xyz /= pos1.w; + pos2.xyz /= pos2.w; } + hit = TriangleRayIntersect(pos0.xyz, pos1.xyz, pos2.xyz, meshpick.rayPos, meshpick.rayDir, + /*out*/ hitPosition); + // ray hit a triangle, so return the vertex that was closest // to the triangle/ray intersection point if(hit) { - float dist0 = distance(pos0.xyz / pos0.w, hitPosition); - float dist1 = distance(pos1.xyz / pos1.w, hitPosition); - float dist2 = distance(pos2.xyz / pos2.w, hitPosition); + float dist0 = distance(pos0.xyz, hitPosition); + float dist1 = distance(pos1.xyz, hitPosition); + float dist2 = distance(pos2.xyz, hitPosition); uint result_idx = atomicAdd(pickresult.counter, 1u); diff --git a/renderdoc/data/hlsl/mesh.hlsl b/renderdoc/data/hlsl/mesh.hlsl index 0aace8d4d..fd5213166 100644 --- a/renderdoc/data/hlsl/mesh.hlsl +++ b/renderdoc/data/hlsl/mesh.hlsl @@ -144,6 +144,9 @@ bool TriangleRayIntersect(float3 A, float3 B, float3 C, float3 RayPosition, floa { bool Result = false; + if(all(A == B) || all(A == C) || all(B == C)) + return false; + float3 v0v1 = B - A; float3 v0v2 = C - A; float3 pvec = cross(RayDirection, v0v2); @@ -224,23 +227,21 @@ void trianglePath(uint threadID) bool hit; if(PickUnproject == 1) { - hit = TriangleRayIntersect(pos0.xyz / pos0.w, pos1.xyz / pos1.w, pos2.xyz / pos2.w, PickRayPos, - PickRayDir, - /*out*/ hitPosition); - } - else - { - hit = TriangleRayIntersect(pos0.xyz, pos1.xyz, pos2.xyz, PickRayPos, PickRayDir, - /*out*/ hitPosition); + pos0.xyz /= pos0.w; + pos1.xyz /= pos1.w; + pos2.xyz /= pos2.w; } + hit = TriangleRayIntersect(pos0.xyz, pos1.xyz, pos2.xyz, PickRayPos, PickRayDir, + /*out*/ hitPosition); + // ray hit a triangle, so return the vertex that was closest // to the triangle/ray intersection point if(hit) { - float dist0 = distance(pos0.xyz / pos0.w, hitPosition); - float dist1 = distance(pos1.xyz / pos1.w, hitPosition); - float dist2 = distance(pos2.xyz / pos2.w, hitPosition); + float dist0 = distance(pos0.xyz, hitPosition); + float dist1 = distance(pos1.xyz, hitPosition); + float dist2 = distance(pos2.xyz, hitPosition); uint meshVert = vertid0; if(dist1 < dist0 && dist1 < dist2) diff --git a/renderdoc/driver/d3d12/d3d12_rendermesh.cpp b/renderdoc/driver/d3d12/d3d12_rendermesh.cpp index 9ce580598..52c2347b8 100644 --- a/renderdoc/driver/d3d12/d3d12_rendermesh.cpp +++ b/renderdoc/driver/d3d12/d3d12_rendermesh.cpp @@ -80,6 +80,10 @@ MeshDisplayPipelines D3D12DebugManager::CacheMeshDisplayPipelines(const MeshForm key |= 1ULL << bit; bit++; + if(primary.allowRestart) + key |= 1ULL << bit; + bit++; + // only 64 bits, make sure they all fit RDCASSERT(bit < 64); @@ -100,6 +104,15 @@ MeshDisplayPipelines D3D12DebugManager::CacheMeshDisplayPipelines(const MeshForm pipeDesc.SampleMask = 0xFFFFFFFF; pipeDesc.SampleDesc.Count = D3D12_MSAA_SAMPLECOUNT; pipeDesc.IBStripCutValue = D3D12_INDEX_BUFFER_STRIP_CUT_VALUE_DISABLED; + + if(primary.allowRestart) + { + if(primary.indexByteStride == 2) + pipeDesc.IBStripCutValue = D3D12_INDEX_BUFFER_STRIP_CUT_VALUE_0xFFFF; + else + pipeDesc.IBStripCutValue = D3D12_INDEX_BUFFER_STRIP_CUT_VALUE_0xFFFFFFFF; + } + D3D_PRIMITIVE_TOPOLOGY topo = MakeD3DPrimitiveTopology(primary.topology); if(topo == D3D_PRIMITIVE_TOPOLOGY_POINTLIST || diff --git a/renderdoc/driver/gl/gl_debug.cpp b/renderdoc/driver/gl/gl_debug.cpp index a0fe803eb..cc676543f 100644 --- a/renderdoc/driver/gl/gl_debug.cpp +++ b/renderdoc/driver/gl/gl_debug.cpp @@ -1943,58 +1943,6 @@ uint32_t GLReplay::PickVertex(uint32_t eventId, int32_t width, int32_t height, } } - drv.glBindBufferBase(eGL_UNIFORM_BUFFER, 0, DebugData.UBOs[0]); - MeshPickUBOData *cdata = - (MeshPickUBOData *)drv.glMapBufferRange(eGL_UNIFORM_BUFFER, 0, sizeof(MeshPickUBOData), - GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_BUFFER_BIT); - - cdata->rayPos = rayPos; - cdata->rayDir = rayDir; - cdata->use_indices = cfg.position.indexByteStride ? 1U : 0U; - cdata->numVerts = cfg.position.numIndices; - bool isTriangleMesh = true; - switch(cfg.position.topology) - { - case Topology::TriangleList: - { - cdata->meshMode = MESH_TRIANGLE_LIST; - break; - } - case Topology::TriangleStrip: - { - cdata->meshMode = MESH_TRIANGLE_STRIP; - break; - } - case Topology::TriangleFan: - { - cdata->meshMode = MESH_TRIANGLE_FAN; - break; - } - case Topology::TriangleList_Adj: - { - cdata->meshMode = MESH_TRIANGLE_LIST_ADJ; - break; - } - case Topology::TriangleStrip_Adj: - { - cdata->meshMode = MESH_TRIANGLE_STRIP_ADJ; - break; - } - default: // points, lines, patchlists, unknown - { - cdata->meshMode = MESH_OTHER; - isTriangleMesh = false; - } - } - - // line/point data - cdata->unproject = cfg.position.unproject; - cdata->mvp = cfg.position.unproject ? pickMVPProj : pickMVP; - cdata->coords = Vec2f((float)x, (float)y); - cdata->viewport = Vec2f((float)width, (float)height); - - drv.glUnmapBuffer(eGL_UNIFORM_BUFFER); - GLuint ib = 0; uint32_t minIndex = 0; @@ -2007,29 +1955,45 @@ uint32_t GLReplay::PickVertex(uint32_t eventId, int32_t width, int32_t height, if(cfg.position.indexByteStride && cfg.position.indexResourceId != ResourceId()) ib = m_pDriver->GetResourceManager()->GetCurrentResource(cfg.position.indexResourceId).name; + const bool fandecode = + (cfg.position.topology == Topology::TriangleFan && cfg.position.allowRestart); + uint32_t numIndices = cfg.position.numIndices; + // We copy into our own buffers to promote to the target type (uint32) that the shader expects. // Most IBs will be 16-bit indices, most VBs will not be float4. We also apply baseVertex here if(ib) { + std::vector idxtmp; + + // if it's a triangle fan that allows restart, we'll have to unpack it. + // Allocate enough space for the list on the GPU, and enough temporary space to upcast into + // first + if(fandecode) + { + idxtmp.resize(numIndices); + + numIndices *= 3; + } + // resize up on demand - if(DebugData.pickIBBuf == 0 || DebugData.pickIBSize < cfg.position.numIndices * sizeof(uint32_t)) + if(DebugData.pickIBBuf == 0 || DebugData.pickIBSize < numIndices * sizeof(uint32_t)) { drv.glDeleteBuffers(1, &DebugData.pickIBBuf); drv.glGenBuffers(1, &DebugData.pickIBBuf); drv.glBindBuffer(eGL_SHADER_STORAGE_BUFFER, DebugData.pickIBBuf); - drv.glNamedBufferDataEXT(DebugData.pickIBBuf, cfg.position.numIndices * sizeof(uint32_t), - NULL, eGL_STREAM_DRAW); + drv.glNamedBufferDataEXT(DebugData.pickIBBuf, numIndices * sizeof(uint32_t), NULL, + eGL_STREAM_DRAW); - DebugData.pickIBSize = cfg.position.numIndices * sizeof(uint32_t); + DebugData.pickIBSize = numIndices * sizeof(uint32_t); } - byte *idxs = new byte[cfg.position.numIndices * cfg.position.indexByteStride]; - memset(idxs, 0, cfg.position.numIndices * cfg.position.indexByteStride); + byte *idxs = new byte[numIndices * cfg.position.indexByteStride]; + memset(idxs, 0, numIndices * cfg.position.indexByteStride); std::vector outidxs; - outidxs.resize(cfg.position.numIndices); + outidxs.resize(numIndices); drv.glBindBuffer(eGL_COPY_READ_BUFFER, ib); @@ -2045,6 +2009,8 @@ uint32_t GLReplay::PickVertex(uint32_t eventId, int32_t width, int32_t height, uint16_t *idxs16 = (uint16_t *)idxs; uint32_t *idxs32 = (uint32_t *)idxs; + size_t idxcount = 0; + if(cfg.position.indexByteStride == 1) { for(uint32_t i = 0; i < cfg.position.numIndices; i++) @@ -2069,11 +2035,8 @@ uint32_t GLReplay::PickVertex(uint32_t eventId, int32_t width, int32_t height, } outidxs[i] = idx; + idxcount++; } - - drv.glBindBuffer(eGL_SHADER_STORAGE_BUFFER, DebugData.pickIBBuf); - drv.glBufferSubData(eGL_SHADER_STORAGE_BUFFER, 0, cfg.position.numIndices * sizeof(uint32_t), - outidxs.data()); } else if(cfg.position.indexByteStride == 2) { @@ -2099,11 +2062,8 @@ uint32_t GLReplay::PickVertex(uint32_t eventId, int32_t width, int32_t height, } outidxs[i] = idx; + idxcount++; } - - drv.glBindBuffer(eGL_SHADER_STORAGE_BUFFER, DebugData.pickIBBuf); - drv.glBufferSubData(eGL_SHADER_STORAGE_BUFFER, 0, cfg.position.numIndices * sizeof(uint32_t), - outidxs.data()); } else { @@ -2129,12 +2089,30 @@ uint32_t GLReplay::PickVertex(uint32_t eventId, int32_t width, int32_t height, } outidxs[i] = idx; + idxcount++; + } + } + + // if it's a triangle fan that allows restart, unpack it + if(cfg.position.topology == Topology::TriangleFan && cfg.position.allowRestart) + { + // resize to how many indices were actually read + outidxs.resize(idxcount); + + // patch the index buffer + PatchTriangleFanRestartIndexBufer(outidxs, cfg.position.restartIndex); + + for(uint32_t &idx : idxtmp) + { + if(idx == cfg.position.restartIndex) + idx = 0; } - drv.glBindBuffer(eGL_SHADER_STORAGE_BUFFER, DebugData.pickIBBuf); - drv.glBufferSubData(eGL_SHADER_STORAGE_BUFFER, 0, cfg.position.numIndices * sizeof(uint32_t), - outidxs.data()); + numIndices = (uint32_t)outidxs.size(); } + + drv.glBindBuffer(eGL_SHADER_STORAGE_BUFFER, DebugData.pickIBBuf); + drv.glBufferSubData(eGL_SHADER_STORAGE_BUFFER, 0, numIndices * sizeof(uint32_t), outidxs.data()); } // unpack and linearise the data @@ -2177,6 +2155,61 @@ uint32_t GLReplay::PickVertex(uint32_t eventId, int32_t width, int32_t height, drv.glBufferSubData(eGL_SHADER_STORAGE_BUFFER, 0, (maxIndex + 1) * sizeof(Vec4f), vbData.data()); } + drv.glBindBufferBase(eGL_UNIFORM_BUFFER, 0, DebugData.UBOs[0]); + MeshPickUBOData *cdata = + (MeshPickUBOData *)drv.glMapBufferRange(eGL_UNIFORM_BUFFER, 0, sizeof(MeshPickUBOData), + GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_BUFFER_BIT); + + cdata->rayPos = rayPos; + cdata->rayDir = rayDir; + cdata->use_indices = cfg.position.indexByteStride ? 1U : 0U; + cdata->numVerts = numIndices; + bool isTriangleMesh = true; + switch(cfg.position.topology) + { + case Topology::TriangleList: + { + cdata->meshMode = MESH_TRIANGLE_LIST; + break; + } + case Topology::TriangleStrip: + { + cdata->meshMode = MESH_TRIANGLE_STRIP; + break; + } + case Topology::TriangleFan: + { + if(fandecode) + cdata->meshMode = MESH_TRIANGLE_LIST; + else + cdata->meshMode = MESH_TRIANGLE_FAN; + break; + } + case Topology::TriangleList_Adj: + { + cdata->meshMode = MESH_TRIANGLE_LIST_ADJ; + break; + } + case Topology::TriangleStrip_Adj: + { + cdata->meshMode = MESH_TRIANGLE_STRIP_ADJ; + break; + } + default: // points, lines, patchlists, unknown + { + cdata->meshMode = MESH_OTHER; + isTriangleMesh = false; + } + } + + // line/point data + cdata->unproject = cfg.position.unproject; + cdata->mvp = cfg.position.unproject ? pickMVPProj : pickMVP; + cdata->coords = Vec2f((float)x, (float)y); + cdata->viewport = Vec2f((float)width, (float)height); + + drv.glUnmapBuffer(eGL_UNIFORM_BUFFER); + uint32_t reset[4] = {}; drv.glBindBufferBase(eGL_SHADER_STORAGE_BUFFER, 0, DebugData.pickResultBuf); drv.glBufferSubData(eGL_SHADER_STORAGE_BUFFER, 0, sizeof(uint32_t) * 4, &reset); @@ -2194,6 +2227,8 @@ uint32_t GLReplay::PickVertex(uint32_t eventId, int32_t width, int32_t height, drv.glBindBuffer(eGL_COPY_READ_BUFFER, DebugData.pickResultBuf); drv.glGetBufferSubData(eGL_COPY_READ_BUFFER, 0, sizeof(uint32_t), &numResults); + uint32_t ret = ~0U; + if(numResults > 0) { if(isTriangleMesh) @@ -2226,7 +2261,7 @@ uint32_t GLReplay::PickVertex(uint32_t eventId, int32_t width, int32_t height, drv.glUnmapNamedBufferEXT(DebugData.pickResultBuf); - return closest->vertid; + ret = closest->vertid; } else { @@ -2265,11 +2300,18 @@ uint32_t GLReplay::PickVertex(uint32_t eventId, int32_t width, int32_t height, drv.glUnmapNamedBufferEXT(DebugData.pickResultBuf); - return closest->vertid; + ret = closest->vertid; } } - return ~0U; + if(fandecode) + { + // undo the triangle list expansion + if(ret > 2) + ret = (ret + 3) / 3 + 1; + } + + return ret; } void GLReplay::PickPixel(ResourceId texture, uint32_t x, uint32_t y, uint32_t sliceFace, diff --git a/renderdoc/driver/gl/gl_postvs.cpp b/renderdoc/driver/gl/gl_postvs.cpp index adb6ce02b..ebe6165dd 100644 --- a/renderdoc/driver/gl/gl_postvs.cpp +++ b/renderdoc/driver/gl/gl_postvs.cpp @@ -730,7 +730,7 @@ void GLReplay::InitPostVSBuffers(uint32_t eventId) uint32_t stripRestartValue32 = 0; - if(IsStrip(drawcall->topology) && rs.Enabled[GLRenderState::eEnabled_PrimitiveRestart]) + if(SupportsRestart(drawcall->topology) && rs.Enabled[GLRenderState::eEnabled_PrimitiveRestart]) { stripRestartValue32 = rs.Enabled[GLRenderState::eEnabled_PrimitiveRestartFixedIndex] ? ~0U diff --git a/renderdoc/driver/vulkan/vk_debug.cpp b/renderdoc/driver/vulkan/vk_debug.cpp index 1d518fcbe..ed25c7cba 100644 --- a/renderdoc/driver/vulkan/vk_debug.cpp +++ b/renderdoc/driver/vulkan/vk_debug.cpp @@ -1049,55 +1049,10 @@ uint32_t VulkanReplay::PickVertex(uint32_t eventId, int32_t w, int32_t h, const } } - MeshPickUBOData *ubo = (MeshPickUBOData *)m_VertexPick.UBO.Map(); + const bool fandecode = + (cfg.position.topology == Topology::TriangleFan && cfg.position.allowRestart); - ubo->rayPos = rayPos; - ubo->rayDir = rayDir; - ubo->use_indices = cfg.position.indexByteStride ? 1U : 0U; - ubo->numVerts = cfg.position.numIndices; - bool isTriangleMesh = true; - - switch(cfg.position.topology) - { - case Topology::TriangleList: - { - ubo->meshMode = MESH_TRIANGLE_LIST; - break; - }; - case Topology::TriangleStrip: - { - ubo->meshMode = MESH_TRIANGLE_STRIP; - break; - }; - case Topology::TriangleFan: - { - ubo->meshMode = MESH_TRIANGLE_FAN; - break; - }; - case Topology::TriangleList_Adj: - { - ubo->meshMode = MESH_TRIANGLE_LIST_ADJ; - break; - }; - case Topology::TriangleStrip_Adj: - { - ubo->meshMode = MESH_TRIANGLE_STRIP_ADJ; - break; - }; - default: // points, lines, patchlists, unknown - { - ubo->meshMode = MESH_OTHER; - isTriangleMesh = false; - }; - } - - // line/point data - ubo->unproject = cfg.position.unproject; - ubo->mvp = cfg.position.unproject ? pickMVPProj : pickMVP; - ubo->coords = Vec2f((float)x, (float)y); - ubo->viewport = Vec2f((float)w, (float)h); - - m_VertexPick.UBO.Unmap(); + uint32_t numIndices = cfg.position.numIndices; bytebuf idxs; @@ -1116,8 +1071,20 @@ uint32_t VulkanReplay::PickVertex(uint32_t eventId, int32_t w, int32_t h, const if(!idxs.empty()) { + std::vector idxtmp; + + // if it's a triangle fan that allows restart, we'll have to unpack it. + // Allocate enough space for the list on the GPU, and enough temporary space to upcast into + // first + if(fandecode) + { + idxtmp.resize(numIndices); + + numIndices *= 3; + } + // resize up on demand - if(m_VertexPick.IBSize < cfg.position.numIndices * sizeof(uint32_t)) + if(m_VertexPick.IBSize < numIndices * sizeof(uint32_t)) { if(m_VertexPick.IBSize > 0) { @@ -1125,7 +1092,7 @@ uint32_t VulkanReplay::PickVertex(uint32_t eventId, int32_t w, int32_t h, const m_VertexPick.IBUpload.Destroy(); } - m_VertexPick.IBSize = cfg.position.numIndices * sizeof(uint32_t); + m_VertexPick.IBSize = numIndices * sizeof(uint32_t); m_VertexPick.IB.Create(m_pDriver, dev, m_VertexPick.IBSize, 1, GPUBuffer::eGPUBufferGPULocal | GPUBuffer::eGPUBufferSSBO); @@ -1133,12 +1100,19 @@ uint32_t VulkanReplay::PickVertex(uint32_t eventId, int32_t w, int32_t h, const } uint32_t *outidxs = (uint32_t *)m_VertexPick.IBUpload.Map(); + uint32_t *mappedPtr = outidxs; memset(outidxs, 0, m_VertexPick.IBSize); + // if we're decoding a fan, we write into our temporary vector first + if(fandecode) + outidxs = idxtmp.data(); + uint16_t *idxs16 = (uint16_t *)&idxs[0]; uint32_t *idxs32 = (uint32_t *)&idxs[0]; + size_t idxcount = 0; + if(cfg.position.indexByteStride == 2) { size_t bufsize = idxs.size() / 2; @@ -1165,6 +1139,7 @@ uint32_t VulkanReplay::PickVertex(uint32_t eventId, int32_t w, int32_t h, const } outidxs[i] = idx; + idxcount++; } } else @@ -1188,9 +1163,31 @@ uint32_t VulkanReplay::PickVertex(uint32_t eventId, int32_t w, int32_t h, const maxIndex = RDCMAX(idx, maxIndex); outidxs[i] = idx; + idxcount++; } } + // if it's a triangle fan that allows restart, unpack it + if(cfg.position.topology == Topology::TriangleFan && cfg.position.allowRestart) + { + // resize to how many indices were actually read + idxtmp.resize(idxcount); + + // patch the index buffer + PatchTriangleFanRestartIndexBufer(idxtmp, cfg.position.restartIndex); + + for(uint32_t &idx : idxtmp) + { + if(idx == cfg.position.restartIndex) + idx = 0; + } + + numIndices = (uint32_t)idxtmp.size(); + + // now copy the decoded list to the GPU + memcpy(mappedPtr, idxtmp.data(), idxtmp.size() * sizeof(uint32_t)); + } + m_VertexPick.IBUpload.Unmap(); } @@ -1235,6 +1232,59 @@ uint32_t VulkanReplay::PickVertex(uint32_t eventId, int32_t w, int32_t h, const m_VertexPick.VBUpload.Unmap(); } + MeshPickUBOData *ubo = (MeshPickUBOData *)m_VertexPick.UBO.Map(); + + ubo->rayPos = rayPos; + ubo->rayDir = rayDir; + ubo->use_indices = cfg.position.indexByteStride ? 1U : 0U; + ubo->numVerts = numIndices; + bool isTriangleMesh = true; + + switch(cfg.position.topology) + { + case Topology::TriangleList: + { + ubo->meshMode = MESH_TRIANGLE_LIST; + break; + }; + case Topology::TriangleStrip: + { + ubo->meshMode = MESH_TRIANGLE_STRIP; + break; + }; + case Topology::TriangleFan: + { + if(fandecode) + ubo->meshMode = MESH_TRIANGLE_LIST; + else + ubo->meshMode = MESH_TRIANGLE_FAN; + break; + }; + case Topology::TriangleList_Adj: + { + ubo->meshMode = MESH_TRIANGLE_LIST_ADJ; + break; + }; + case Topology::TriangleStrip_Adj: + { + ubo->meshMode = MESH_TRIANGLE_STRIP_ADJ; + break; + }; + default: // points, lines, patchlists, unknown + { + ubo->meshMode = MESH_OTHER; + isTriangleMesh = false; + }; + } + + // line/point data + ubo->unproject = cfg.position.unproject; + ubo->mvp = cfg.position.unproject ? pickMVPProj : pickMVP; + ubo->coords = Vec2f((float)x, (float)y); + ubo->viewport = Vec2f((float)w, (float)h); + + m_VertexPick.UBO.Unmap(); + VkDescriptorBufferInfo ibInfo = {}; VkDescriptorBufferInfo vbInfo = {}; @@ -1425,6 +1475,13 @@ uint32_t VulkanReplay::PickVertex(uint32_t eventId, int32_t w, int32_t h, const VkMarkerRegion::End(); + if(fandecode) + { + // undo the triangle list expansion + if(ret > 2) + ret = (ret + 3) / 3 + 1; + } + return ret; } diff --git a/renderdoc/driver/vulkan/vk_overlay.cpp b/renderdoc/driver/vulkan/vk_overlay.cpp index 7fd03c0b7..0b73ece6c 100644 --- a/renderdoc/driver/vulkan/vk_overlay.cpp +++ b/renderdoc/driver/vulkan/vk_overlay.cpp @@ -2114,8 +2114,22 @@ ResourceId VulkanReplay::RenderOverlay(ResourceId texid, CompType typeHint, Floa VkPipelineInputAssemblyStateCreateInfo ia = { VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO}; + + // most topologies are decomposed into triangle lists on output ia.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; + if(pipeCreateInfo.pInputAssemblyState->topology == VK_PRIMITIVE_TOPOLOGY_POINT_LIST) + ia.topology = VK_PRIMITIVE_TOPOLOGY_POINT_LIST; + else if(pipeCreateInfo.pInputAssemblyState->topology == VK_PRIMITIVE_TOPOLOGY_LINE_LIST || + pipeCreateInfo.pInputAssemblyState->topology == + VK_PRIMITIVE_TOPOLOGY_LINE_LIST_WITH_ADJACENCY || + pipeCreateInfo.pInputAssemblyState->topology == VK_PRIMITIVE_TOPOLOGY_LINE_STRIP || + pipeCreateInfo.pInputAssemblyState->topology == + VK_PRIMITIVE_TOPOLOGY_LINE_STRIP_WITH_ADJACENCY) + ia.topology = VK_PRIMITIVE_TOPOLOGY_LINE_LIST; + else if(pipeCreateInfo.pInputAssemblyState->topology == VK_PRIMITIVE_TOPOLOGY_TRIANGLE_FAN) + ia.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_FAN; + VkVertexInputBindingDescription binds[] = { // primary {0, 0, VK_VERTEX_INPUT_RATE_VERTEX}, diff --git a/renderdoc/driver/vulkan/vk_postvs.cpp b/renderdoc/driver/vulkan/vk_postvs.cpp index 4a289721b..fcfa76417 100644 --- a/renderdoc/driver/vulkan/vk_postvs.cpp +++ b/renderdoc/driver/vulkan/vk_postvs.cpp @@ -1608,8 +1608,8 @@ void VulkanReplay::FetchVSOut(uint32_t eventId) if(drawcall->flags & DrawFlags::Indexed) { - const bool restart = - pipeCreateInfo.pInputAssemblyState->primitiveRestartEnable && IsStrip(drawcall->topology); + const bool restart = pipeCreateInfo.pInputAssemblyState->primitiveRestartEnable && + SupportsRestart(drawcall->topology); bytebuf idxdata; std::vector indices; uint8_t *idx8 = NULL; diff --git a/renderdoc/driver/vulkan/vk_rendermesh.cpp b/renderdoc/driver/vulkan/vk_rendermesh.cpp index 3ca99fc84..f5c54ffac 100644 --- a/renderdoc/driver/vulkan/vk_rendermesh.cpp +++ b/renderdoc/driver/vulkan/vk_rendermesh.cpp @@ -83,6 +83,10 @@ MeshDisplayPipelines VulkanDebugManager::CacheMeshDisplayPipelines(VkPipelineLay key |= 1ULL << bit; bit++; + if(primary.allowRestart) + key |= 1ULL << bit; + bit++; + // only 64 bits, make sure they all fit RDCASSERT(bit < 64); @@ -138,7 +142,7 @@ MeshDisplayPipelines VulkanDebugManager::CacheMeshDisplayPipelines(VkPipelineLay false, }; - ia.primitiveRestartEnable = primary.allowRestart && IsStrip(primary.topology); + ia.primitiveRestartEnable = primary.allowRestart && SupportsRestart(primary.topology); VkRect2D scissor = {{0, 0}, {16384, 16384}}; diff --git a/renderdoc/replay/entry_points.cpp b/renderdoc/replay/entry_points.cpp index 0110c974b..6d522b970 100644 --- a/renderdoc/replay/entry_points.cpp +++ b/renderdoc/replay/entry_points.cpp @@ -143,13 +143,13 @@ extern "C" RENDERDOC_API uint32_t RENDERDOC_CC RENDERDOC_VertexOffset(Topology t case Topology::LineLoop: case Topology::TriangleStrip: case Topology::LineStrip_Adj: + case Topology::TriangleFan: // for strips, each new vertex creates a new primitive return primitive; case Topology::TriangleStrip_Adj: // triangle strip with adjacency is a special case as every other // vert is purely for adjacency so it's doubled return primitive * 2; - case Topology::TriangleFan: RDCERR("Cannot get VertexOffset for triangle fan!"); break; } return primitive * RENDERDOC_NumVerticesPerPrimitive(topology); diff --git a/renderdoc/replay/replay_driver.cpp b/renderdoc/replay/replay_driver.cpp index 6cdc4a95c..fa4d0dab6 100644 --- a/renderdoc/replay/replay_driver.cpp +++ b/renderdoc/replay/replay_driver.cpp @@ -259,6 +259,86 @@ void PatchLineStripIndexBuffer(const DrawcallDescription *draw, uint8_t *idx8, u #undef IDX_VALUE } +void PatchTriangleFanRestartIndexBufer(std::vector &patchedIndices, uint32_t restartIndex) +{ + if(patchedIndices.empty()) + return; + + std::vector newIndices; + + uint32_t firstIndex = patchedIndices[0]; + + size_t i = 1; + // while we have at least two indices left + while(i + 1 < patchedIndices.size()) + { + uint32_t a = patchedIndices[i]; + uint32_t b = patchedIndices[i + 1]; + + if(a != restartIndex && b != restartIndex) + { + // no restart, add primitive + newIndices.push_back(firstIndex); + newIndices.push_back(a); + newIndices.push_back(b); + + i++; + continue; + } + else if(b == restartIndex) + { + // we've already added the last triangle before the restart in the previous iteration, just + // continue so we hit the a == restartIndex case below + i++; + } + else if(a == restartIndex) + { + // new first index is b + firstIndex = b; + // skip both the restartIndex value and the first index, and begin at the next real index (if + // it exists) + i += 2; + + uint32_t next[2] = {b, b}; + + // if this is the last vertex, the triangle will be degenerate + if(i < patchedIndices.size()) + next[0] = patchedIndices[i]; + if(i + 1 < patchedIndices.size()) + next[1] = patchedIndices[i + 1]; + + // output 3 dummy degenerate triangles so vertex ID mapping is easy + // we rotate the triangles so the important vertex is last in each. + newIndices.push_back(restartIndex); + newIndices.push_back(restartIndex); + newIndices.push_back(restartIndex); + + if(1) + { + newIndices.push_back(restartIndex); + newIndices.push_back(restartIndex); + newIndices.push_back(restartIndex); + + newIndices.push_back(restartIndex); + newIndices.push_back(restartIndex); + newIndices.push_back(restartIndex); + } + else + { + newIndices.push_back(next[0]); + newIndices.push_back(next[1]); + newIndices.push_back(firstIndex); + + newIndices.push_back(next[1]); + newIndices.push_back(firstIndex); + newIndices.push_back(next[0]); + } + } + } + + newIndices.swap(patchedIndices); +} + void StandardFillCBufferVariable(uint32_t dataOffset, const bytebuf &data, ShaderVariable &outvar, uint32_t matStride) { @@ -509,7 +589,8 @@ FloatVector HighlightCache::InterpretVertex(const byte *data, uint32_t vert, con vert = indices[vert]; - if(IsStrip(cfg.position.topology)) + if(SupportsRestart(cfg.position.topology) && cfg.position.topology != Topology::TriangleFan && + cfg.position.allowRestart) { if((cfg.position.indexByteStride == 1 && vert == 0xff) || (cfg.position.indexByteStride == 2 && vert == 0xffff) || @@ -625,6 +706,8 @@ void HighlightCache::CacheHighlightingData(uint32_t eventId, const MeshDisplay & newKey = inthash(cfg.position.vertexByteStride, newKey); newKey = inthash(cfg.position.indexResourceId, newKey); newKey = inthash(cfg.position.vertexResourceId, newKey); + newKey = inthash((uint64_t)cfg.position.allowRestart, newKey); + newKey = inthash((uint64_t)cfg.position.restartIndex, newKey); if(cacheKey != newKey) { @@ -687,7 +770,7 @@ void HighlightCache::CacheHighlightingData(uint32_t eventId, const MeshDisplay & maxIndex += add; uint32_t primRestart = 0; - if(IsStrip(cfg.position.topology)) + if(SupportsRestart(cfg.position.topology) && cfg.position.allowRestart) { if(cfg.position.indexByteStride == 1) primRestart = 0xff; @@ -719,6 +802,13 @@ void HighlightCache::CacheHighlightingData(uint32_t eventId, const MeshDisplay & driver->GetBufferData(cfg.position.vertexResourceId, cfg.position.vertexByteOffset, (maxIndex + 1) * cfg.position.vertexByteStride, vertexData); + + // if it's a fan AND it uses primitive restart, decompose it into a triangle list because the + // restart changes the central vertex + if(cfg.position.topology == Topology::TriangleFan && cfg.position.allowRestart) + { + PatchTriangleFanRestartIndexBufer(indices, cfg.position.restartIndex); + } } } @@ -735,10 +825,20 @@ bool HighlightCache::FetchHighlightPositions(const MeshDisplay &cfg, FloatVector uint32_t idx = cfg.highlightVert; Topology meshtopo = cfg.position.topology; + // if it's a fan AND it uses primitive restart, it was decomposed into a triangle list + if(meshtopo == Topology::TriangleFan && cfg.position.allowRestart) + { + meshtopo = Topology::TriangleList; + + // due to triangle fan expansion the index we need to look up is adjusted + if(idx > 2) + idx = (idx - 1) * 3 - 1; + } + activeVertex = InterpretVertex(data, idx, cfg, dataEnd, true, valid); uint32_t primRestart = 0; - if(IsStrip(meshtopo)) + if(SupportsRestart(meshtopo) && cfg.position.allowRestart) { if(cfg.position.indexByteStride == 1) primRestart = 0xff; @@ -834,6 +934,19 @@ bool HighlightCache::FetchHighlightPositions(const MeshDisplay &cfg, FloatVector activePrim.push_back(InterpretVertex(data, v + 0, cfg, dataEnd, true, valid)); activePrim.push_back(InterpretVertex(data, v + 1, cfg, dataEnd, true, valid)); } + else if(meshtopo == Topology::TriangleFan) + { + // find first vert in primitive. In fans a vert isn't + // in only one primitive, so we pick the first primitive + // it's in. This means the first N points are in the first + // primitive, and thereafter each point is in the next primitive + uint32_t v = RDCMAX(idx, 2U) - 1; + + // first vert in the whole fan + activePrim.push_back(InterpretVertex(data, 0, cfg, dataEnd, true, valid)); + activePrim.push_back(InterpretVertex(data, v + 0, cfg, dataEnd, true, valid)); + activePrim.push_back(InterpretVertex(data, v + 1, cfg, dataEnd, true, valid)); + } else if(meshtopo == Topology::TriangleStrip) { // find first vert in primitive. In strips a vert isn't @@ -1029,7 +1142,7 @@ bool HighlightCache::FetchHighlightPositions(const MeshDisplay &cfg, FloatVector } else if(meshtopo >= Topology::PatchList) { - uint32_t dim = PatchList_Count(cfg.position.topology); + uint32_t dim = PatchList_Count(meshtopo); uint32_t v0 = uint32_t(idx / dim) * dim; diff --git a/renderdoc/replay/replay_driver.h b/renderdoc/replay/replay_driver.h index b80267a57..4aad9234b 100644 --- a/renderdoc/replay/replay_driver.h +++ b/renderdoc/replay/replay_driver.h @@ -244,6 +244,8 @@ void SetupDrawcallPointers(std::vector &drawcallTable, void PatchLineStripIndexBuffer(const DrawcallDescription *draw, uint8_t *idx8, uint16_t *idx16, uint32_t *idx32, std::vector &patchedIndices); +void PatchTriangleFanRestartIndexBufer(std::vector &patchedIndices, uint32_t restartIndex); + uint64_t CalcMeshOutputSize(uint64_t curSize, uint64_t requiredOutput); void StandardFillCBufferVariable(uint32_t dataOffset, const bytebuf &data, ShaderVariable &outvar, diff --git a/util/test/demos/CMakeLists.txt b/util/test/demos/CMakeLists.txt index 8716f5173..d67e1a10d 100644 --- a/util/test/demos/CMakeLists.txt +++ b/util/test/demos/CMakeLists.txt @@ -25,6 +25,7 @@ set(VULKAN_SRC vk/vk_simple_triangle.cpp vk/vk_spec_constants.cpp vk/vk_structured_buffer_nested.cpp + vk/vk_triangle_fan.cpp vk/vk_vertex_attr_zoo.cpp vk/vk_video_textures.cpp vk/vk_vs_max_desc_set.cpp) diff --git a/util/test/demos/demos.vcxproj b/util/test/demos/demos.vcxproj index 269efc5bb..759aefcf8 100644 --- a/util/test/demos/demos.vcxproj +++ b/util/test/demos/demos.vcxproj @@ -214,6 +214,7 @@ + diff --git a/util/test/demos/demos.vcxproj.filters b/util/test/demos/demos.vcxproj.filters index 9fca1c2be..b8bd35a9e 100644 --- a/util/test/demos/demos.vcxproj.filters +++ b/util/test/demos/demos.vcxproj.filters @@ -321,6 +321,9 @@ Vulkan\demos + + Vulkan\demos + diff --git a/util/test/demos/vk/vk_triangle_fan.cpp b/util/test/demos/vk/vk_triangle_fan.cpp new file mode 100644 index 000000000..375bf1cc2 --- /dev/null +++ b/util/test/demos/vk/vk_triangle_fan.cpp @@ -0,0 +1,195 @@ +/****************************************************************************** + * 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" + +TEST(VK_Triangle_Fan, VulkanGraphicsTest) +{ + static constexpr const char *Description = + "Draws a triangle fan with primitive restart to test different edge cases."; + + 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; + +void main() +{ + Color = vertIn.col; +} + +)EOSHADER"; + + int main() + { + // initialise, create window, create context, etc + if(!Init()) + return 3; + + VkPipelineLayout layout = createPipelineLayout(vkh::PipelineLayoutCreateInfo()); + + vkh::GraphicsPipelineCreateInfo pipeCreateInfo; + + pipeCreateInfo.layout = layout; + pipeCreateInfo.renderPass = mainWindow->rp; + + pipeCreateInfo.inputAssemblyState.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_FAN; + pipeCreateInfo.inputAssemblyState.primitiveRestartEnable = VK_FALSE; + + 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); + + pipeCreateInfo.inputAssemblyState.primitiveRestartEnable = VK_TRUE; + + VkPipeline restartpipe = createGraphicsPipeline(pipeCreateInfo); + + const DefaultA2V verts[] = { + // centre A + {Vec3f(0.0f, 0.0f, 0.0f), Vec4f(0.0f, 0.0f, 0.0f, 1.0f), Vec2f(0.0f, 0.0f)}, + + // fan A + {Vec3f(-0.8f, 0.0f, 0.0f), Vec4f(1.0f, 0.0f, 0.0f, 1.0f), Vec2f(0.0f, 1.0f)}, + {Vec3f(-0.5f, 0.3f, 0.0f), Vec4f(1.0f, 1.0f, 0.0f, 1.0f), Vec2f(0.0f, 1.0f)}, + {Vec3f(0.0f, 0.5f, 0.0f), Vec4f(0.0f, 1.0f, 0.0f, 1.0f), Vec2f(0.0f, 1.0f)}, + {Vec3f(0.5f, 0.3f, 0.0f), Vec4f(0.0f, 1.0f, 1.0f, 1.0f), Vec2f(0.0f, 1.0f)}, + {Vec3f(0.8f, 0.0f, 0.0f), Vec4f(0.0f, 0.0f, 1.0f, 1.0f), Vec2f(0.0f, 1.0f)}, + + // centre B + {Vec3f(0.0f, -0.75f, 0.0f), Vec4f(1.0f, 1.0f, 1.0f, 1.0f), Vec2f(0.0f, 0.0f)}, + + // fan B + {Vec3f(-0.8f, 0.0f - 0.75f, 0.0f), Vec4f(1.0f, 0.0f, 0.0f, 1.0f), Vec2f(0.0f, 1.0f)}, + {Vec3f(-0.5f, 0.3f - 0.75f, 0.0f), Vec4f(1.0f, 1.0f, 0.0f, 1.0f), Vec2f(0.0f, 1.0f)}, + {Vec3f(0.0f, 0.5f - 0.75f, 0.0f), Vec4f(0.0f, 1.0f, 0.0f, 1.0f), Vec2f(0.0f, 1.0f)}, + {Vec3f(0.5f, 0.3f - 0.75f, 0.0f), Vec4f(0.0f, 1.0f, 1.0f, 1.0f), Vec2f(0.0f, 1.0f)}, + {Vec3f(0.8f, 0.0f - 0.75f, 0.0f), Vec4f(0.0f, 0.0f, 1.0f, 1.0f), Vec2f(0.0f, 1.0f)}, + }; + + AllocatedBuffer vb(allocator, + vkh::BufferCreateInfo(sizeof(verts), VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | + VK_BUFFER_USAGE_TRANSFER_DST_BIT), + VmaAllocationCreateInfo({0, VMA_MEMORY_USAGE_CPU_TO_GPU})); + + vb.upload(verts); + + uint32_t indices[13] = {0, 1, 2, 3, 4, 5, ~0U, 6, 7, 8, 9, 10, 11}; + AllocatedBuffer ib(allocator, + vkh::BufferCreateInfo(sizeof(indices), VK_BUFFER_USAGE_INDEX_BUFFER_BIT | + VK_BUFFER_USAGE_TRANSFER_DST_BIT), + VmaAllocationCreateInfo({0, VMA_MEMORY_USAGE_CPU_TO_GPU})); + + ib.upload(indices); + + while(Running()) + { + 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()); + + vkCmdBeginRenderPass( + cmd, vkh::RenderPassBeginInfo(mainWindow->rp, mainWindow->GetFB(), mainWindow->scissor), + VK_SUBPASS_CONTENTS_INLINE); + + VkViewport v = mainWindow->viewport; + v.width /= 2; + + vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, restartpipe); + vkCmdSetViewport(cmd, 0, 1, &v); + vkCmdSetScissor(cmd, 0, 1, &mainWindow->scissor); + vkh::cmdBindVertexBuffers(cmd, 0, {vb.buffer}, {0}); + vkCmdBindIndexBuffer(cmd, ib.buffer, 0, VK_INDEX_TYPE_UINT32); + vkCmdDrawIndexed(cmd, 13, 1, 0, 0, 0); + + v.x += v.width; + vkCmdSetViewport(cmd, 0, 1, &v); + + vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, pipe); + + vkCmdDrawIndexed(cmd, 6, 1, 0, 0, 0); + + vkCmdEndRenderPass(cmd); + + FinishUsingBackbuffer(cmd, VK_ACCESS_TRANSFER_WRITE_BIT, VK_IMAGE_LAYOUT_GENERAL); + + vkEndCommandBuffer(cmd); + + Submit(0, 1, {cmd}); + + Present(); + } + + return 0; + } +}; + +REGISTER_TEST();