#define PVR_REGS_FOR_RENDERER // see hw/pvr/pvr_regs.h /* Created on: Nov 7, 2019 Copyright 2019 flyinghead This file is part of Flycast. Flycast is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. Flycast is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Flycast. If not, see . */ #include "oit_drawer.h" #include "../rtt_read.h" #include "rend/last_picture.h" #include "hw/pvr/pvr_mem.h" #include /* The translucent modifier volumes' draws change the per-pixel lists from * their shaders (oit_trmodvol.frag), and nothing orders what the shaders of * two draws do to the same fragment but a barrier between them: a GPU runs * the draws of a subpass at once, in any order, where a software device * runs them one after the other. They go through here. Built with * RACE_TEST (tools/rend/speckle_test.sh; never in a release) the draws * between two barriers are recorded a triangle at a time, the draws' * triangles taken in turn, the last draw's first: one draw's shaders run * while another's are half done, as on a GPU, and a picture that changes * then depends on an order nothing keeps. */ namespace { struct ListDraws { OITDrawer& drawer; const vk::CommandBuffer& cmd; PsoCull& cull; #ifdef RACE_TEST struct Held { vk::Pipeline pipeline; u32 cullIndex; vk::Rect2D scissor; u32 vertices, first; }; Held held[256]; u32 count = 0; #endif ListDraws(OITDrawer& drawer, const vk::CommandBuffer& cmd, PsoCull& cull) : drawer(drawer), cmd(cmd), cull(cull) {} ~ListDraws() { Flush(); } // @cullIndex: PsoCullIndex()'s void Draw(vk::Pipeline pipeline, u32 cullIndex, const vk::Rect2D& scissor, u32 vertices, u32 first) { #ifdef RACE_TEST if (count == ARRAY_SIZE(held)) Flush(); held[count++] = { pipeline, cullIndex, scissor, vertices, first }; #else Record(pipeline, cullIndex, scissor, vertices, first); #endif } /* what the draws before did to the lists is done before the draws after start */ void Barrier() { Flush(); vk::MemoryBarrier barrier(vk::AccessFlagBits::eShaderWrite, vk::AccessFlagBits::eShaderRead); cmd.pipelineBarrier(vk::PipelineStageFlagBits::eFragmentShader, vk::PipelineStageFlagBits::eFragmentShader, vk::DependencyFlagBits::eByRegion, barrier, nullptr, nullptr); } void Flush() { #ifdef RACE_TEST for (u32 v = 0, more = count; more != 0; v += 3) { more = 0; for (u32 i = count; i-- > 0; ) if (v < held[i].vertices) { Record(held[i].pipeline, held[i].cullIndex, held[i].scissor, 3, held[i].first + v); more++; } } count = 0; #endif } private: void Record(vk::Pipeline pipeline, u32 cullIndex, const vk::Rect2D& scissor, u32 vertices, u32 first) { drawer.SetScissor(cmd, scissor); cmd.bindPipeline(vk::PipelineBindPoint::eGraphics, pipeline); cull.Set(cullIndex); cmd.draw(vertices, 1, first, 0); } }; } void OITDrawer::DrawPoly(const vk::CommandBuffer& cmdBuffer, u32 listType, bool autosort, Pass pass, const PolyParam& poly, u32 first, u32 count) { vk::Rect2D scissorRect; TileClipping tileClip = SetTileClip(poly.tileclip, scissorRect); if (tileClip == TileClipping::Outside) SetScissor(cmdBuffer, scissorRect); else SetScissor(cmdBuffer, baseScissor); float trilinearAlpha = 1.f; if (poly.tsp.FilterMode > 1 && poly.pcw.Texture && listType != ListType_Punch_Through && poly.tcw.MipMapped == 1) { trilinearAlpha = 0.25 * (poly.tsp.MipMapD & 0x3); if (poly.tsp.FilterMode == 2) // Trilinear pass A trilinearAlpha = 1.0 - trilinearAlpha; } bool twoVolumes = poly.tsp1.full != (u32)-1 || poly.tcw1.full != (u32)-1; const bool keepAll = twoVolumes || poly.tsp.SrcSelect; bool palette = BaseTextureCacheData::IsGpuHandledPaletted(poly.tsp, poly.tcw); float palette_index = 0.f; if (palette) { if (poly.tcw.PixelFmt == PixelPal4) palette_index = (float(poly.tcw.PalSelect << 4) + 0.5f) / 1024.f; else palette_index = (float((poly.tcw.PalSelect >> 4) << 8) + 0.5f) / 1024.f; } OITDescriptorSets::PushConstants pushConstants = { { (float)scissorRect.offset.x, (float)scissorRect.offset.y, (float)scissorRect.offset.x + (float)scissorRect.extent.width, (float)scissorRect.offset.y + (float)scissorRect.extent.height }, // The per-pixel pass drops a fragment whose blend would leave the // pixel as it is. It cannot tell that when the source is the second // accumulation buffer, or when the polygon has a second volume and // which blend applies is known only when the lists are put // together: those get a blend (8) that none of its cases take. { keepAll ? 8 : (int)poly.tsp.SrcInstr, keepAll ? 8 : (int)poly.tsp.DstInstr, 0, 0 }, trilinearAlpha, // (a frame decoded on the GPU numbers its strips by their draws: see TA_GPU_INSTANCE) listType == ListType_Translucent && !pvrrc.gpu ? (int)(&poly - pvrrc.global_param_tr.head()) : 0, palette_index, }; if (twoVolumes) { pushConstants.blend_mode1[0] = (int)poly.tsp1.SrcInstr; pushConstants.blend_mode1[1] = (int)poly.tsp1.DstInstr; pushConstants.blend_mode1[2] = 0; pushConstants.blend_mode1[3] = 0; pushConstants.shading_instr0 = poly.tsp.ShadInstr; pushConstants.shading_instr1 = poly.tsp1.ShadInstr; pushConstants.fog_control0 = poly.tsp.FogCtrl; pushConstants.fog_control1 = poly.tsp1.FogCtrl; pushConstants.use_alpha0 = poly.tsp.UseAlpha; pushConstants.use_alpha1 = poly.tsp1.UseAlpha; pushConstants.ignore_tex_alpha0 = poly.tsp.IgnoreTexA; pushConstants.ignore_tex_alpha1 = poly.tsp1.IgnoreTexA; } const OITPsoChoice pso = pipelineManager->GetPipeline(listType, autosort, poly, pass); if (pso.pipeline == VK_NULL_HANDLE) return; // (the general pipeline is told the polygon's key too: pso.h) pushConstants.key = (int)(pso.key & ((1u << OIT_SC_BITS) - 1)); cmdBuffer.pushConstants(pipelineManager->GetPipelineLayout(), vk::ShaderStageFlagBits::eFragment, 0, pso.general ? (u32)sizeof(pushConstants) : (u32)OIT_PUSH_KEY_OFFSET, &pushConstants); bool needTexture = poly.pcw.Texture; vk::DescriptorSet texSet; if (needTexture) texSet = GetCurrentDescSet().SetTexture(poly.texid, poly.tsp, poly.texid1, poly.tsp1); cmdBuffer.bindPipeline(vk::PipelineBindPoint::eGraphics, vk::Pipeline(pso.pipeline)); if (pso.general) pipelineManager->SetDynamicState(cmdBuffer, pso.key); if (needTexture) GetCurrentDescSet().BindPerPolyDescriptorSets(cmdBuffer, texSet); DrawIndices(cmdBuffer, listType, first, count); } void OITDrawer::DrawList(const vk::CommandBuffer& cmdBuffer, u32 listType, bool sortTriangles, Pass pass, const List& polys, u32 first, u32 last) { const PolyParam *pp_end = polys.head() + last; for (const PolyParam *pp = polys.head() + first; pp != pp_end; pp++) if (pp->count > 2) DrawPoly(cmdBuffer, listType, sortTriangles, pass, *pp, pp->first, pp->count); } template void OITDrawer::DrawModifierVolumes(const vk::CommandBuffer& cmdBuffer, int first, int count, const ModifierVolumeParam *modVolParams) { if (count == 0 || ModVolTriangles() == 0) return; vk::Buffer buffer = GetMainBuffer(0)->buffer.get(); BindModVolTriangles(cmdBuffer, buffer, offsets.modVolOffset); SetScissor(cmdBuffer, baseScissor); const ModifierVolumeParam* params = &modVolParams[first]; int mod_base = -1; vk::Pipeline pipeline; PsoCull cull(cmdBuffer, pipelineManager->CullSetter()); ListDraws listDraws(*this, cmdBuffer, cull); for (int cmv = 0; cmv < count; cmv++) { const ModifierVolumeParam& param = params[cmv]; u32 mvFirst, mvCount; mv_clip_range_of(pvrrc, modVolParams, param, mvFirst, mvCount); if (mvCount == 0) continue; u32 mv_mode = param.isp.DepthMode; // clipped like a polygon, to the rectangle it was sent under if it asked to be vk::Rect2D scissor = baseScissor; { vk::Rect2D clipRect; if (SetTileClip(param.tileclip, clipRect) == TileClipping::Outside) scissor = clipRect; } verify(mvFirst + mvCount <= ModVolTriangles()); if (mod_base == -1) mod_base = mvFirst; if (!param.isp.VolumeLast && mv_mode > 0) { // OR'ing (open volume or quad) if (Translucent) pipeline = pipelineManager->GetTrModifierVolumePipeline(ModVolMode::Or, param.isp.CullMode); else pipeline = pipelineManager->GetModifierVolumePipeline(ModVolMode::Or, param.isp.CullMode); } else { // XOR'ing (closed volume) if (Translucent) pipeline = pipelineManager->GetTrModifierVolumePipeline(ModVolMode::Xor, param.isp.CullMode); else pipeline = pipelineManager->GetModifierVolumePipeline(ModVolMode::Xor, param.isp.CullMode); } if (Translucent) listDraws.Draw(pipeline, PsoCullIndex(param.isp.CullMode), scissor, mvCount * 3, mvFirst * 3); else { SetScissor(cmdBuffer, scissor); cmdBuffer.bindPipeline(vk::PipelineBindPoint::eGraphics, pipeline); cull.Set(PsoCullIndex(param.isp.CullMode)); cmdBuffer.draw(mvCount * 3, 1, mvFirst * 3, 0); } if (mv_mode == 1 || mv_mode == 2) { //Sum the area if (Translucent) { listDraws.Barrier(); pipeline = pipelineManager->GetTrModifierVolumePipeline(mv_mode == 1 ? ModVolMode::Inclusion : ModVolMode::Exclusion, param.isp.CullMode); listDraws.Draw(pipeline, PsoCullIndex(param.isp.CullMode), scissor, (mvFirst + mvCount - mod_base) * 3, mod_base * 3); /* ...and done before the next volume's draws start: this * draw clears the stencil bit the next volume's toggle, and * sets the fragments it finds inside from what it read */ listDraws.Barrier(); } else { pipeline = pipelineManager->GetModifierVolumePipeline(mv_mode == 1 ? ModVolMode::Inclusion : ModVolMode::Exclusion, param.isp.CullMode); cmdBuffer.bindPipeline(vk::PipelineBindPoint::eGraphics, pipeline); cull.Set(PsoCullIndex(param.isp.CullMode)); cmdBuffer.draw((mvFirst + mvCount - mod_base) * 3, 1, mod_base * 3, 0); } mod_base = -1; } } listDraws.Flush(); SetScissor(cmdBuffer, baseScissor); BindVertices(cmdBuffer); } void OITDrawer::UploadMainBuffer(const OITDescriptorSets::FragmentShaderUniforms& fragmentUniforms) { using FragmentShaderUniforms = OITDescriptorSets::FragmentShaderUniforms; /* (on the stack: nothing allocated for them) */ const void *chunks[10]; u32 chunkSizes[10]; u32 nchunks = 0; /* (a frame decoded on the GPU has its geometry and translucent polygon * parameters there; one the parse wrote where the card reads it, its * geometry there) */ const bool here = UseParsed(0); const u32 vertexBytes = pvrrc.gpu || here ? 0 : pvrrc.verts.bytes(); const u32 modVolBytes = ModVolsOnCpu() && !here ? ModVolList().bytes() : 0; const u32 indexBytes = pvrrc.gpu || here ? 0 : pvrrc.idx.bytes(); // Vertex chunks[nchunks] = (pvrrc.verts.head()); chunkSizes[nchunks++] = (vertexBytes); u32 padding = align(vertexBytes, 4); offsets.modVolOffset = vertexBytes + padding; chunks[nchunks] = (nullptr); chunkSizes[nchunks++] = (padding); // Modifier Volumes chunks[nchunks] = (ModVolList().head()); chunkSizes[nchunks++] = (modVolBytes); padding = align(offsets.modVolOffset + modVolBytes, 4); offsets.indexOffset = offsets.modVolOffset + modVolBytes + padding; chunks[nchunks] = (nullptr); chunkSizes[nchunks++] = (padding); // Index chunks[nchunks] = (pvrrc.idx.head()); chunkSizes[nchunks++] = (indexBytes); // Fragment uniform buffer (the vertex one is its own: WriteVertexUniforms()) u32 indexSize = indexBytes; padding = align(offsets.indexOffset + indexSize, std::max(4, (int)GetContext()->GetUniformBufferAlignment())); offsets.fragmentUniformOffset = offsets.indexOffset + indexSize + padding; chunks[nchunks] = (nullptr); chunkSizes[nchunks++] = (padding); chunks[nchunks] = (&fragmentUniforms); chunkSizes[nchunks++] = (sizeof(fragmentUniforms)); // Translucent poly params padding = align(offsets.fragmentUniformOffset + sizeof(FragmentShaderUniforms), std::max(4, (int)GetContext()->GetStorageBufferAlignment())); offsets.polyParamsOffset = offsets.fragmentUniformOffset + sizeof(FragmentShaderUniforms) + padding; chunks[nchunks] = (nullptr); chunkSizes[nchunks++] = (padding); /* (written below, straight into the buffer: two words a translucent * polygon, or the one the validation layers want when there is none) */ const u32 trPolys = pvrrc.gpu ? 0 : (u32)pvrrc.global_param_tr.used(); offsets.polyParamsSize = (trPolys != 0 ? trPolys * 2 : 1) * 4; chunks[nchunks] = (nullptr); chunkSizes[nchunks++] = (offsets.polyParamsSize); u32 totalSize = offsets.polyParamsOffset + offsets.polyParamsSize; BufferData *buffer = GetMainBuffer(totalSize); buffer->upload(nchunks, chunkSizes, chunks); SetGeometry(buffer->buffer.get()); if (here) offsets.indexOffset = parsed.indices; u32 *trPolyParams = (u32 *)((u8 *)buffer->MapMemory() + offsets.polyParamsOffset); if (trPolys == 0) trPolyParams[0] = 0; else { const PolyParam *pp = pvrrc.global_param_tr.head(); for (u32 i = 0; i < trPolys; i++, pp++) { trPolyParams[i * 2] = (pp->tsp.full & 0xffff00c0) | ((pp->isp.full >> 16) & 0xe400) | ((pp->pcw.full >> 7) & 1); trPolyParams[i * 2 + 1] = pp->tsp1.full; } } } bool OITDrawer::Draw(const Texture *fogTexture, const Texture *paletteTexture) { vk::CommandBuffer cmdBuffer = NewFrame(); // (the attachments drawn into: this drawer's, or the screen's drawer's - see OITTextureDrawer::ShareAttachments()) OITDrawer *const att = attachmentsOf; if (att->needDepthTransition) { att->needDepthTransition = false; // Not convinced that this is really needed but it makes validation layers happy if (att->upscaled) for (const auto& attachment : att->depthAttachments) setImageLayout(cmdBuffer, attachment->GetImage(), GetContext()->GetDepthFormat(), 1, vk::ImageLayout::eUndefined, vk::ImageLayout::eDepthStencilReadOnlyOptimal); else setImageLayout(cmdBuffer, att->depthAttachment->GetImage(), GetContext()->GetDepthFormat(), 1, vk::ImageLayout::eUndefined, vk::ImageLayout::eDepthStencilReadOnlyOptimal); } OITDescriptorSets::FragmentShaderUniforms fragUniforms = MakeFragmentUniforms(); fragUniforms.shade_scale_factor = FPU_SHAD_SCALE.scale_factor / 256.f; currentScissor = vk::Rect2D(); oitBuffers->OnNewFrame(cmdBuffer); // (the GPU's decode has done it to a frame it made) if (!pvrrc.gpu) SetProvokingVertices(); // Upload vertex and index buffers UploadMainBuffer(fragUniforms); quadBuffer->Update(); /* Translucent polygons that are not sorted are drawn in the colour * subpass and write depth there. A frame that has any is drawn with the * render passes that allow that: see RenderPasses::GetRenderPass(). */ bool depthWrites = false; { int tr_count = 0; for (int i = 0; i < pvrrc.render_passes.used(); i++) { const RenderPass& pass = pvrrc.render_passes.head()[i]; if (!pass.autosort && pass.tr_count > tr_count) depthWrites = true; tr_count = pass.tr_count; } } // Update per-frame descriptor set and bind it const vk::Buffer mainBuffer = GetMainBuffer(0)->buffer.get(); GetCurrentDescSet().UpdateUniforms(vertexUniformBuffer, mainBuffer, offsets.fragmentUniformOffset, fogTexture->GetImageView(), offsets.polyParamsOffset, offsets.polyParamsSize, att->Depth()->GetStencilView(), att->Depth()->GetImageView(), paletteTexture->GetImageView(), RenderPasses::DepthInputLayout(depthWrites), pvrrc.gpu ? gpuDraw->trParams : vk::Buffer(), pvrrc.gpu ? gpuDraw->trParamsSize : 0); GetCurrentDescSet().BindPerFrameDescriptorSets(cmdBuffer); /* SSAA: the samples' colour, one image for every render pass, read in * the general layout; without, the two that take turns */ const bool ssaa = pipelineManager->GetSamples() > 1; if (ssaa) GetCurrentDescSet().UpdateColorInputDescSet(0, att->colorAttachments[0]->GetImageView(), vk::ImageLayout::eGeneral); else { GetCurrentDescSet().UpdateColorInputDescSet(0, att->colorAttachments[0]->GetImageView()); GetCurrentDescSet().UpdateColorInputDescSet(1, att->colorAttachments[1]->GetImageView()); } oitBuffers->BindDescriptorSet(cmdBuffer, pipelineManager->GetPipelineLayout(), 3); // Reset per-poly descriptor set pool GetCurrentDescSet().Reset(); // Bind vertex and index buffers BindGeometry(cmdBuffer, offsets.indexOffset); // Make sure to push constants even if not used OITDescriptorSets::PushConstants pushConstants = { }; cmdBuffer.pushConstants(pipelineManager->GetPipelineLayout(), vk::ShaderStageFlagBits::eFragment, 0, pushConstants); /* A framebuffer the game has not drawn to lately starts as the border * colour, which is what the OpenGL renderers clear it to (never a * render to a texture: that is not such a framebuffer); any other * starts black. */ const vk::ClearColorValue clear_color(pvrrc.clearFramebuffer ? std::array{ VO_BORDER_COL.Red / 255.f, VO_BORDER_COL.Green / 255.f, VO_BORDER_COL.Blue / 255.f, 1.f } : std::array{ 0.f, 0.f, 0.f, 1.f }); const std::array clear_colors = { clear_color, clear_color, vk::ClearDepthStencilValue{ 0.f, 0 }, }; /* In bands, when the frames before had more translucent fragments than * there is room for: the bands are drawn one after the other, each with * all of the frame's render passes. The picture is cleared in the * first band only (RenderPasses::GetRenderPass(), @resumed), and the * last band's final render pass is the whole picture's, as the frame's * final render pass always is: what is drawn after it (the screen's * VMUs, the texture's copy) is drawn over the whole of it. */ bands = oitBuffers->ChooseBands(commandPool->GetIndex(), pvrrc.isRTT); const u32 height = std::max(viewport.extent.height, 1u); const u32 bandHeight = (height + std::min(bands, height) - 1) / std::min(bands, height); const u32 bandCount = (height + bandHeight - 1) / bandHeight; oitBuffers->SetBands(bandCount); for (u32 bandIndex = 0; bandIndex < bandCount; bandIndex++) { const bool lastBand = bandIndex == bandCount - 1; if (bandCount > 1) { const u32 top = bandIndex * bandHeight; band = vk::Rect2D(vk::Offset2D(viewport.offset.x, viewport.offset.y + (s32)top), vk::Extent2D(viewport.extent.width, std::min(bandHeight, height - top))); currentScissor = vk::Rect2D(); if (bandIndex > 0) // (the band before ended with the quad's) BindGeometry(cmdBuffer, offsets.indexOffset); } RenderPass previous_pass = {}; for (int render_pass = 0; render_pass < pvrrc.render_passes.used(); render_pass++) { const RenderPass& current_pass = pvrrc.render_passes.head()[render_pass]; DEBUG_LOG(RENDERER, "Render pass %d OP %d PT %d TR %d MV %d TrMV %d autosort %d", render_pass + 1, current_pass.op_count - previous_pass.op_count, current_pass.pt_count - previous_pass.pt_count, current_pass.tr_count - previous_pass.tr_count, current_pass.mvo_count - previous_pass.mvo_count, current_pass.mvo_tr_count - previous_pass.mvo_tr_count, current_pass.autosort); // Reset the pixel counter oitBuffers->ResetPixelCounter(cmdBuffer); const bool initialPass = render_pass == 0; const bool finalPass = render_pass == pvrrc.render_passes.used() - 1; vk::Framebuffer targetFramebuffer; if (ssaa) // (the samples carry on to the next render pass; the picture is written by the last) targetFramebuffer = GetFinalFramebuffer(); else if (!finalPass) targetFramebuffer = *att->tempFramebuffers[att->upscaled ? GetCurrentImage() : 0][(pvrrc.render_passes.used() - 1 - render_pass) % 2]; else targetFramebuffer = GetFinalFramebuffer(); cmdBuffer.beginRenderPass( vk::RenderPassBeginInfo(pipelineManager->GetRenderPass(initialPass, finalPass, depthWrites, bandIndex > 0), targetFramebuffer, bandCount > 1 && !(finalPass && lastBand) ? band : viewport, clear_colors.size(), clear_colors.data()), vk::SubpassContents::eInline); // Depth + stencil subpass DrawList(cmdBuffer, ListType_Opaque, false, Pass::Depth, pvrrc.global_param_op, previous_pass.op_count, current_pass.op_count); DrawList(cmdBuffer, ListType_Punch_Through, false, Pass::Depth, pvrrc.global_param_pt, previous_pass.pt_count, current_pass.pt_count); DrawModifierVolumes(cmdBuffer, previous_pass.mvo_count, current_pass.mvo_count - previous_pass.mvo_count, pvrrc.global_param_mvo.head()); // Color subpass cmdBuffer.nextSubpass(vk::SubpassContents::eInline); /* A render that covers only part of the screen leaves the rest as * it was: the last picture goes in first, under everything, and * what this frame binds is bound again after it. */ if (render_pass == 0 && DrawLastPicture(cmdBuffer)) { GetCurrentDescSet().BindPerFrameDescriptorSets(cmdBuffer); oitBuffers->BindDescriptorSet(cmdBuffer, pipelineManager->GetPipelineLayout(), 3); BindGeometry(cmdBuffer, offsets.indexOffset); cmdBuffer.pushConstants(pipelineManager->GetPipelineLayout(), vk::ShaderStageFlagBits::eFragment, 0, pushConstants); SetScissor(cmdBuffer, baseScissor); } // OP + PT DrawList(cmdBuffer, ListType_Opaque, false, Pass::Color, pvrrc.global_param_op, previous_pass.op_count, current_pass.op_count); DrawList(cmdBuffer, ListType_Punch_Through, false, Pass::Color, pvrrc.global_param_pt, previous_pass.pt_count, current_pass.pt_count); // TR /* (In the first frame after the per-pixel lists are made, too: they * start empty - OITBuffers::OnNewFrame - and that frame's translucent * polygons are drawn like any other's. They used to be left out of * it, and where a game draws one frame and then waits, they stayed * missing. Upstream: the background of the progress window in * Fighting Force 2.) */ if (current_pass.autosort) DrawList(cmdBuffer, ListType_Translucent, true, Pass::OIT, pvrrc.global_param_tr, previous_pass.tr_count, current_pass.tr_count); else DrawList(cmdBuffer, ListType_Translucent, false, Pass::Color, pvrrc.global_param_tr, previous_pass.tr_count, current_pass.tr_count); // Final subpass (SSAA: the translucent modifier volumes' one) cmdBuffer.nextSubpass(vk::SubpassContents::eInline); GetCurrentDescSet().BindColorInputDescSet(cmdBuffer, ssaa ? 0 : (pvrrc.render_passes.used() - 1 - render_pass) % 2); SetScissor(cmdBuffer, baseScissor); { // Tr modifier volumes if (GetContext()->GetVendorID() != VENDOR_QUALCOMM) // Adreno bug { if (current_pass.mv_op_tr_shared) DrawModifierVolumes(cmdBuffer, previous_pass.mvo_count, current_pass.mvo_count - previous_pass.mvo_count, pvrrc.global_param_mvo.head()); else DrawModifierVolumes(cmdBuffer, previous_pass.mvo_tr_count, current_pass.mvo_tr_count - previous_pass.mvo_tr_count, pvrrc.global_param_mvo_tr.head()); } /* The whole picture is put together here, not the part the game * clips its drawing to: what is outside that has to be written * too, or it is left as it was in whichever of the picture's * images this frame goes to - two or three frames old, and a * different one every frame. (Upstream: the loading screen * before a fight in Soul Calibur blinking.) */ SetScissor(cmdBuffer, viewport); if (!ssaa) { vk::Pipeline pipeline = pipelineManager->GetFinalPipeline(); cmdBuffer.bindPipeline(vk::PipelineBindPoint::eGraphics, pipeline); quadBuffer->Bind(cmdBuffer); quadBuffer->Draw(cmdBuffer); } } SetScissor(cmdBuffer, viewport); if (ssaa) { if (!finalPass) { // each sample put together, into the samples the next render pass draws over... cmdBuffer.bindPipeline(vk::PipelineBindPoint::eGraphics, pipelineManager->GetFinalPipeline(true)); quadBuffer->Bind(cmdBuffer); quadBuffer->Draw(cmdBuffer); // ...which sees the translucent depth too BindGeometry(cmdBuffer, offsets.indexOffset); DrawList(cmdBuffer, ListType_Translucent, current_pass.autosort, Pass::Depth, pvrrc.global_param_tr, previous_pass.tr_count, current_pass.tr_count); } // The picture put together and resolved, by the last render pass, all of it cmdBuffer.nextSubpass(vk::SubpassContents::eInline); SetScissor(cmdBuffer, viewport); quadBuffer->Bind(cmdBuffer); if (finalPass) { cmdBuffer.setViewport(0, vk::Viewport(0.f, 0.f, (float)viewport.extent.width, (float)viewport.extent.height, 0.f, 1.f)); cmdBuffer.bindPipeline(vk::PipelineBindPoint::eGraphics, pipelineManager->GetFinalPipeline(false, true)); quadBuffer->Draw(cmdBuffer); } } // Clear vk::MemoryBarrier memoryBarrier(vk::AccessFlagBits::eShaderRead, vk::AccessFlagBits::eShaderWrite); cmdBuffer.pipelineBarrier(vk::PipelineStageFlagBits::eFragmentShader, vk::PipelineStageFlagBits::eFragmentShader, vk::DependencyFlagBits::eByRegion, 1, &memoryBarrier, 0, nullptr, 0, nullptr); vk::Pipeline pipeline = pipelineManager->GetClearPipeline(); cmdBuffer.bindPipeline(vk::PipelineBindPoint::eGraphics, pipeline); quadBuffer->Draw(cmdBuffer); if (!finalPass && ssaa) { // (the next render pass draws from the frame's geometry, not the quad) BindGeometry(cmdBuffer, offsets.indexOffset); cmdBuffer.endRenderPass(); oitBuffers->CountPass(cmdBuffer); } else if (!finalPass) { // Re-bind vertex and index buffers BindGeometry(cmdBuffer, offsets.indexOffset); // Tr depth-only pass DrawList(cmdBuffer, ListType_Translucent, current_pass.autosort, Pass::Depth, pvrrc.global_param_tr, previous_pass.tr_count, current_pass.tr_count); cmdBuffer.endRenderPass(); oitBuffers->CountPass(cmdBuffer); } else if (!lastBand) { // (the last band's is ended by EndFrame()) cmdBuffer.endRenderPass(); oitBuffers->CountPass(cmdBuffer); } previous_pass = current_pass; } } band = vk::Rect2D(); return !pvrrc.isRTT; } void OITDrawer::MakeBuffers(int width, int height) { oitBuffers->Init(width, height, commandPool); MakeAttachments(width, height); } void OITDrawer::MakeAttachments(int width, int height) { // (made again for another count of samples) const u32 samples = pipelineManager->GetSamples(); if (samples != attachmentSamples || upscaled != attachmentsUpscaled) { maxWidth = maxHeight = 0; attachmentSamples = samples; attachmentsUpscaled = upscaled; } if (width <= maxWidth && height <= maxHeight) return; maxWidth = std::max(maxWidth, width); maxHeight = std::max(maxHeight, height); if (samples > 1) { MakeSampleAttachments(); return; } for (auto& attachment : colorAttachments) { if (attachment) commandPool->DeferDelete(std::move(attachment)); attachment = std::unique_ptr( new FramebufferAttachment(GetContext()->GetPhysicalDevice(), GetContext()->GetDevice())); attachment->Init(maxWidth, maxHeight, GetColorFormat(), vk::ImageUsageFlagBits::eColorAttachment | vk::ImageUsageFlagBits::eInputAttachment); } if (depthAttachment) commandPool->DeferDelete(std::move(depthAttachment)); for (auto& attachment : depthAttachments) if (attachment) commandPool->DeferDelete(std::move(attachment)); depthAttachments.clear(); if (upscaled) { // the frame's depth, kept, sampled and copied from (the frontend reads it as it likes) for (int i = 0; i < GetContext()->GetSwapChainSize(); i++) { depthAttachments.push_back(std::unique_ptr( new FramebufferAttachment(GetContext()->GetPhysicalDevice(), GetContext()->GetDevice()))); depthAttachments.back()->Init(maxWidth, maxHeight, GetContext()->GetDepthFormat(), vk::ImageUsageFlagBits::eDepthStencilAttachment | vk::ImageUsageFlagBits::eInputAttachment | vk::ImageUsageFlagBits::eSampled | vk::ImageUsageFlagBits::eTransferSrc); } } else { depthAttachment = std::unique_ptr( new FramebufferAttachment(GetContext()->GetPhysicalDevice(), GetContext()->GetDevice())); depthAttachment->Init(maxWidth, maxHeight, GetContext()->GetDepthFormat(), vk::ImageUsageFlagBits::eDepthStencilAttachment | vk::ImageUsageFlagBits::eInputAttachment); } needDepthTransition = true; for (auto& pair : tempFramebuffers) for (auto& framebuffer : pair) if (framebuffer) commandPool->DeferDelete(std::unique_ptr(new vk::UniqueFramebuffer(std::move(framebuffer)))); tempFramebuffers.clear(); tempFramebuffers.resize(upscaled ? GetContext()->GetSwapChainSize() : 1); for (size_t i = 0; i < tempFramebuffers.size(); i++) { vk::ImageView attachments[] = { colorAttachments[1]->GetImageView(), colorAttachments[0]->GetImageView(), upscaled ? depthAttachments[i]->GetImageView() : depthAttachment->GetImageView(), }; vk::FramebufferCreateInfo createInfo(vk::FramebufferCreateFlags(), pipelineManager->GetRenderPass(true, true), ARRAY_SIZE(attachments), attachments, width, height, 1); tempFramebuffers[i][0] = GetContext()->GetDevice().createFramebufferUnique(createInfo); attachments[0] = attachments[1]; attachments[1] = colorAttachments[1]->GetImageView(); tempFramebuffers[i][1] = GetContext()->GetDevice().createFramebufferUnique(createInfo); } } void OITDrawer::DropAttachments() { for (auto& attachment : colorAttachments) if (attachment && commandPool != nullptr) commandPool->DeferDelete(std::move(attachment)); if (depthAttachment && commandPool != nullptr) commandPool->DeferDelete(std::move(depthAttachment)); for (auto& attachment : depthAttachments) if (attachment && commandPool != nullptr) commandPool->DeferDelete(std::move(attachment)); for (auto& pair : tempFramebuffers) for (auto& framebuffer : pair) if (framebuffer && commandPool != nullptr) commandPool->DeferDelete(std::unique_ptr(new vk::UniqueFramebuffer(std::move(framebuffer)))); colorAttachments[0].reset(); colorAttachments[1].reset(); depthAttachment.reset(); depthAttachments.clear(); tempFramebuffers.clear(); maxWidth = maxHeight = 0; } /* SSAA: one image of samples for the colour, one for the depth, for all * the render passes of a frame (RenderPasses::MakeSsaaRenderPass()), of * the largest size asked for. Not transient: a frame of several render * passes stores them for the next one (and a device may drop what is * stored to an attachment made transient). */ void OITDrawer::MakeSampleAttachments() { const vk::SampleCountFlagBits n = (vk::SampleCountFlagBits)pipelineManager->GetSamples(); for (auto& attachment : colorAttachments) if (attachment) commandPool->DeferDelete(std::move(attachment)); for (auto& pair : tempFramebuffers) for (auto& framebuffer : pair) if (framebuffer) commandPool->DeferDelete(std::unique_ptr(new vk::UniqueFramebuffer(std::move(framebuffer)))); tempFramebuffers.clear(); for (auto& attachment : depthAttachments) if (attachment) commandPool->DeferDelete(std::move(attachment)); depthAttachments.clear(); colorAttachments[0] = std::unique_ptr( new FramebufferAttachment(GetContext()->GetPhysicalDevice(), GetContext()->GetDevice())); colorAttachments[0]->Init(maxWidth, maxHeight, GetColorFormat(), vk::ImageUsageFlagBits::eColorAttachment | vk::ImageUsageFlagBits::eInputAttachment, true, n); if (depthAttachment) commandPool->DeferDelete(std::move(depthAttachment)); depthAttachment = std::unique_ptr( new FramebufferAttachment(GetContext()->GetPhysicalDevice(), GetContext()->GetDevice())); depthAttachment->Init(maxWidth, maxHeight, GetContext()->GetDepthFormat(), vk::ImageUsageFlagBits::eDepthStencilAttachment | vk::ImageUsageFlagBits::eInputAttachment, true, n); needDepthTransition = true; } void OITScreenDrawer::KeepPicture() { if (!havePicture) return; u8 *pixels = last_picture_keep(viewport.extent.width, viewport.extent.height); if (pixels != NULL) vk_read_picture(finalColorAttachments[GetCurrentImage()]->GetImage(), GetContext()->GetColorFormat(), viewport.extent.width, viewport.extent.height, pixels); } void OITScreenDrawer::MakeFramebuffers() { viewport.offset.x = 0; viewport.offset.y = 0; viewport.extent = GetContext()->GetViewPort(); SetScreenSize(viewport.extent.width, viewport.extent.height); /* (renders to textures draw into the attachments too, when they can: * as large as those need, TextureDrawer::ShareExtent()) */ { const vk::Extent2D size = SharesAttachments() ? TextureDrawer::ShareExtent(viewport.extent) : viewport.extent; /* (the lists' heads as large as those renders too: made while * the game runs, for the first of them, otherwise) */ const vk::Extent2D heads = TextureDrawer::ShareExtent(viewport.extent); InitBuffers(heads.width, heads.height); MakeAttachments(size.width, size.height); } // (kept until the frames that could still use them have gone by: see the per-triangle screen drawer) if (commandPool != nullptr) { // the image with the last frame stays, as the picture the next render starts from if (carriedPicture) commandPool->DeferDelete(std::move(carriedPicture)); if (havePicture && (size_t)GetCurrentImage() < finalColorAttachments.size() && finalColorAttachments[GetCurrentImage()]) { carriedPicture = std::move(finalColorAttachments[GetCurrentImage()]); restoredPicture = carriedPicture->GetImageView(); } for (auto& framebuffer : framebuffers) if (framebuffer) commandPool->DeferDelete(std::unique_ptr(new vk::UniqueFramebuffer(std::move(framebuffer)))); for (auto& attachment : finalColorAttachments) if (attachment) commandPool->DeferDelete(std::move(attachment)); } framebuffers.clear(); finalColorAttachments.clear(); havePicture = false; while (finalColorAttachments.size() < GetContext()->GetSwapChainSize()) { finalColorAttachments.push_back(std::unique_ptr( new FramebufferAttachment(GetContext()->GetPhysicalDevice(), GetContext()->GetDevice()))); // (copied from when the context goes, to keep the picture: rend/last_picture.h) finalColorAttachments.back()->Init(viewport.extent.width, viewport.extent.height, GetContext()->GetColorFormat(), vk::ImageUsageFlagBits::eColorAttachment | vk::ImageUsageFlagBits::eSampled | vk::ImageUsageFlagBits::eTransferSrc | (storagePicture || VulkanWriteOut::Wanted() ? vk::ImageUsageFlagBits::eStorage : vk::ImageUsageFlags()), false); vk::ImageView attachments[] = { finalColorAttachments.back()->GetImageView(), colorAttachments[0]->GetImageView(), upscaled ? depthAttachments[finalColorAttachments.size() - 1]->GetImageView() : depthAttachment->GetImageView(), }; vk::FramebufferCreateInfo createInfo(vk::FramebufferCreateFlags(), screenPipelineManager->GetRenderPass(true, true), ARRAY_SIZE(attachments), attachments, viewport.extent.width, viewport.extent.height, 1); framebuffers.push_back(GetContext()->GetDevice().createFramebufferUnique(createInfo)); } // (the chip's write-out over the pictures: ../writeout.h) { std::vector views; for (const auto& attachment : finalColorAttachments) views.push_back(attachment->GetImageView()); writeOut.Init(views, viewport.extent); } } OITTextureDrawer::Size OITTextureDrawer::GetSize() { Size s; // the frame from its first pixel, as far as the pixel clip goes (see TransformMatrix) s.origWidth = pvrrc.fb_X_CLIP.max + 1; s.origHeight = pvrrc.fb_Y_CLIP.max + 1; s.clipX = pvrrc.fb_X_CLIP.min; s.clipY = pvrrc.fb_Y_CLIP.min; s.clipWidth = s.origWidth - s.clipX; s.clipHeight = s.origHeight - s.clipY; s.upscaledWidth = s.origWidth; s.upscaledHeight = s.origHeight; s.heightPow2 = 8; while (s.heightPow2 < s.upscaledHeight) s.heightPow2 *= 2; s.widthPow2 = 8; while (s.widthPow2 < s.upscaledWidth) s.widthPow2 *= 2; if (settings.rend.RenderToTextureUpscale > 1) { s.upscaledWidth *= settings.rend.RenderToTextureUpscale; s.upscaledHeight *= settings.rend.RenderToTextureUpscale; s.widthPow2 *= settings.rend.RenderToTextureUpscale; s.heightPow2 *= settings.rend.RenderToTextureUpscale; } return s; } vk::ImageLayout OITTextureDrawer::TakeTexture(const Size& size, const vk::ImageUsageFlags& usage) { const u32 origWidth = size.origWidth, origHeight = size.origHeight; const u32 widthPow2 = size.widthPow2, heightPow2 = size.heightPow2; vk::ImageLayout colorImageCurrentLayout; /* what an earlier render left waiting in this memory, while its picture is still what it was: see rtt_read.h */ vk_rtt_supersede(textureAddr, size.clipX, size.clipY, size.clipWidth, size.clipHeight); // TexAddr : fb_rtt.TexAddr, Reserved : 0, StrideSel : 0, ScanOrder : 1 TCW tcw = { { textureAddr >> 3, 0, 0, 1 } }; switch (FB_W_CTRL.fb_packmode) { case 0: case 3: tcw.PixelFmt = Pixel1555; break; case 1: tcw.PixelFmt = Pixel565; break; case 2: tcw.PixelFmt = Pixel4444; break; } TSP tsp = {}; for (tsp.TexU = 0; tsp.TexU <= 7 && (8u << tsp.TexU) < origWidth; tsp.TexU++); for (tsp.TexV = 0; tsp.TexV <= 7 && (8u << tsp.TexV) < origHeight; tsp.TexV++); texture = textureCache->getTextureCacheData(tsp, tcw); if (texture->IsNew()) { texture->Create(); texture->SetPhysicalDevice(GetContext()->GetPhysicalDevice()); texture->SetDevice(GetContext()->GetDevice()); } else if (textureCache->IsInFlight(texture)) { texture->KeepReadOnlyView(); textureCache->DestroyLater(texture); } textureCache->SetInFlight(texture); /* A texture the game uploaded can become one it renders to, at the * same address, the same size and in this format: its image was not * made to be rendered to, nor to be copied from, and is made again. */ const vk::ImageUsageFlags renderTargetUsage = vk::ImageUsageFlagBits::eColorAttachment | vk::ImageUsageFlagBits::eSampled | vk::ImageUsageFlagBits::eTransferSrc // read back when the game touches it in video memory | storageUsage | usage; if (texture->format != vk::Format::eR8G8B8A8Unorm || texture->extent.width != widthPow2 || texture->extent.height != heightPow2 || (texture->imageUsage & renderTargetUsage) != renderTargetUsage) { texture->extent = vk::Extent2D(widthPow2, heightPow2); texture->format = vk::Format::eR8G8B8A8Unorm; texture->needsStaging = true; texture->CreateRenderTarget(renderTargetUsage); colorImageCurrentLayout = vk::ImageLayout::eUndefined; } else { colorImageCurrentLayout = vk::ImageLayout::eShaderReadOnlyOptimal; } colorImage = texture->GetImage(); viewport.offset.x = 0; viewport.offset.y = 0; viewport.extent.width = widthPow2; viewport.extent.height = heightPow2; return colorImageCurrentLayout; } vk::CommandBuffer OITTextureDrawer::NewFrame() { DEBUG_LOG(RENDERER, "RenderToTexture packmode=%d stride=%d - %d,%d -> %d,%d", FB_W_CTRL.fb_packmode, FB_W_LINESTRIDE.stride * 8, FB_X_CLIP.min, FB_Y_CLIP.min, FB_X_CLIP.max, FB_Y_CLIP.max); NewImage(); matrices.CalcMatrices(&pvrrc); textureAddr = FB_W_SOF1 & VRAM_MASK; const Size size = GetSize(); const u32 clipX = size.clipX, clipY = size.clipY; const u32 clipWidth = size.clipWidth, clipHeight = size.clipHeight; const u32 upscaledWidth = size.upscaledWidth, upscaledHeight = size.upscaledHeight; VulkanContext *context = GetContext(); vk::Device device = context->GetDevice(); vk::CommandBuffer commandBuffer = commandPool->Allocate(); commandBuffer.begin(vk::CommandBufferBeginInfo(vk::CommandBufferUsageFlagBits::eOneTimeSubmit)); vertexUniformBuffer = WriteVertexUniforms(commandBuffer, GetCurrentImage()); /* What the render draws: its own pixels, the texture's from the first * (Texture::ClearOutside()) */ InitBuffers(upscaledWidth, upscaledHeight); /* The attachments: the screen's drawer's when they are large enough - * drawn into by the screen's frames and this one by one, on the same * queue, each one's first render pass starting them anew -, or this * drawer's own; which a render larger than both has made for it. */ attachmentsOf = this; if (screen != nullptr && screen->SharesAttachments() && screen->AttachmentsCover(upscaledWidth, upscaledHeight)) { attachmentsOf = screen; drewShared++; } else { drewOwn++; if (!AttachmentsCover(upscaledWidth, upscaledHeight)) { if (!saidLarger) { saidLarger = true; WARN_LOG(RENDERER, "Render to a texture of %u x %u: attachments made for it", upscaledWidth, upscaledHeight); } } MakeAttachments(upscaledWidth, upscaledHeight); } const OITDrawer *const att = attachmentsOf; vk::ImageLayout colorImageCurrentLayout = TakeTexture(size, vk::ImageUsageFlags()); const vk::ImageView colorImageView = texture->GetImageView(); viewport.extent.width = upscaledWidth; viewport.extent.height = upscaledHeight; texture->ClearOutside(commandBuffer, upscaledWidth, upscaledHeight, colorImageCurrentLayout); setImageLayout(commandBuffer, colorImage, vk::Format::eR8G8B8A8Unorm, 1, colorImageCurrentLayout, vk::ImageLayout::eColorAttachmentOptimal); vk::ImageView imageViews[] = { colorImageView, att->ColorAttachment(0)->GetImageView(), att->DepthAttachment()->GetImageView(), }; const u64 viewSerials[] = { texture->GetViewSerial(), att->ColorAttachment(0)->GetViewSerial(), att->DepthAttachment()->GetViewSerial(), }; /* (made again only for other attachments: a texture rendered to every * frame is the same one) */ framebuffers.resize(GetContext()->GetSwapChainSize()); framebuffers[GetCurrentImage()].Get(rttPipelineManager->GetRenderPass(true, true), ARRAY_SIZE(imageViews), imageViews, viewSerials, upscaledWidth, upscaledHeight); commandBuffer.setViewport(0, vk::Viewport(0.0f, 0.0f, (float)upscaledWidth, (float)upscaledHeight, 0.0f, 1.0f)); { // only the pixel clip is written const u32 scale = settings.rend.RenderToTextureUpscale > 1 ? settings.rend.RenderToTextureUpscale : 1; baseScissor = vk::Rect2D(vk::Offset2D(clipX * scale, clipY * scale), vk::Extent2D(clipWidth * scale, clipHeight * scale)); } commandBuffer.setScissor(0, baseScissor); currentCommandBuffer = commandBuffer; return commandBuffer; } void OITTextureDrawer::EndFrame() { currentCommandBuffer.endRenderPass(); CountPass(currentCommandBuffer); currentCommandBuffer.end(); colorImage = nullptr; currentCommandBuffer = nullptr; commandPool->EndFrame(); Watch(); } void OITTextureDrawer::Watch() { texture->dirty = 0; texture->stride_read = 0; // (rendered, not read) texture->made_type = 0; libCore_vramlock_Lock(texture->sa_tex, texture->sa + texture->size - 1, texture); /* video memory gets it when something first touches it there */ vk_rtt_watch(texture, textureAddr, pvrrc.fb_X_CLIP.min, pvrrc.fb_Y_CLIP.min, pvrrc.fb_X_CLIP.max - pvrrc.fb_X_CLIP.min + 1, pvrrc.fb_Y_CLIP.max - pvrrc.fb_Y_CLIP.min + 1); } /* The compute renderer's render to a texture goes to the texture a render * of this drawer's would, which is left as one of its is: in the cache, * locked in video memory and watched */ OITTextureDrawer::ComputeTarget OITTextureDrawer::ComputeBegin() { NewImage(); textureAddr = FB_W_SOF1 & VRAM_MASK; // (the compute renderer's own barrier makes it ready to be written, from whatever it held) TakeTexture(GetSize(), vk::ImageUsageFlagBits::eStorage); texture->rttArea = 0; // (written as the compute renderer writes it: Texture::ClearOutside()) ComputeTarget t; t.image = colorImage; t.view = texture->GetImageView(); return t; } void OITTextureDrawer::ComputeEnd() { colorImage = nullptr; commandPool->EndFrame(); Watch(); } vk::CommandBuffer OITScreenDrawer::NewFrame() { NewImage(); vk::CommandBuffer commandBuffer = commandPool->Allocate(); commandBuffer.begin(vk::CommandBufferBeginInfo(vk::CommandBufferUsageFlagBits::eOneTimeSubmit)); timer.Begin(commandBuffer, commandPool->GetIndex()); matrices.CalcMatrices(&pvrrc); // the frame drawn under this frame's jitter, its strips matched for the motion vector pass if (upscaled && motionPass.Begin(GetCurrentImage())) { float jx, jy; upscaler_jitter(&jx, &jy); matrices.Jitter(jx, jy); } vertexUniformBuffer = WriteVertexUniforms(commandBuffer, GetCurrentImage()); SetBaseScissor(); commandBuffer.setScissor(0, baseScissor); commandBuffer.setViewport(0, vk::Viewport(viewport.offset.x, viewport.offset.y, viewport.extent.width, viewport.extent.height, 0.0f, 1.0f)); currentCommandBuffer = commandBuffer; return commandBuffer; }