#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 "../vulkan.h" #include "../vulkan_renderer.h" #include "oit_drawer.h" #include "rend/upscaler.h" #include "oit_shaders.h" #include "oit_buffer.h" #include "rend/ssaa_pattern.h" #include "../compute_drawer.h" #include "rend/rend_method.h" class OITVulkanRenderer final : public BaseVulkanRenderer { public: bool Init() override { DEBUG_LOG(RENDERER, "OITVulkanRenderer::Init"); oitBuffers.SetCompact(rend_compute_wanted && ComputeDrawer::DeviceLacks() == nullptr); samples = SsaaSamples(); samplerManager.Init(); /* (with SSAA and the HDR output, the last pass's channel sums, for * the encode: OITBuffers::SetSums()) */ oitBuffers.SetSums(samples > 1 && GetContext()->HdrSumsReady()); oitBuffers.Init(0, 0, &texCommandPool); oitShaderManager.SetSamples(samples); // (the compute renderer's slots are the pool's: made now, BaseInit() finds them made) texCommandPool.Init(); MakeCompute(); NOTICE_LOG(RENDERER, "Vulkan per-pixel renderer: with SSAA, a translucent fragment takes the samples %s", GetContext()->postDepthCoverage ? "the depth test passed (VK_EXT_post_depth_coverage)" : "its own depth test passes, at each sample"); textureDrawer.SetCommandPool(&texCommandPool); textureDrawer.Init(&samplerManager, &rttShaderManager, &textureCache, &oitBuffers); /* (renders to a texture drawn by the compute renderer go to the * pool's images, which it then writes as storage images) */ texPool.SetTargetStorage(rend_compute_wanted && ComputeDrawer::DeviceLacks() == nullptr); textureDrawer.SetGpuDraw(taDecoder.Draw()); screenDrawer.SetStoragePicture(rend_compute_wanted); screenDrawer.Init(&samplerManager, &oitShaderManager, &oitBuffers, samples); screenDrawer.SetCommandPool(&texCommandPool); textureDrawer.ShareAttachments(&screenDrawer); screenDrawer.SetGpuDraw(taDecoder.Draw()); computeDrawer.SetGpuDraw(taDecoder.Draw()); BaseInit(screenDrawer.GetRenderPass(), screenDrawer.GetOsdSubpass()); InitOverlay(screenDrawer.OverlayPass()); textureDrawer.SetStorageTarget(texPool.TargetsStorable()); // (a quad pipeline is for one subpass: the one above is the last subpass's, this the colour one's) lastPicturePipeline.Init(&shaderManager, screenDrawer.GetRenderPass(), 1, samples); screenDrawer.SetLastPicturePipeline(&lastPicturePipeline); InitComputeMotion(); return true; } void Resize(int w, int h) override { NOTICE_LOG(RENDERER, "OIT Resize %d x %d", w, h); // what is replaced below may be in use by frames still on the GPU texCommandPool.WaitAll(); oitBuffers.SetCompact(rend_compute_wanted && ComputeDrawer::DeviceLacks() == nullptr); const u32 was = samples; samples = SsaaSamples(); oitBuffers.SetSums(samples > 1 && GetContext()->HdrSumsReady()); if (samples != was) { // (a change of SSAA: the shaders, render passes and pipelines of the other count go) oitShaderManager.SetSamples(samples); quadPipeline.Forget(); lastPicturePipeline.Forget(); } screenDrawer.Init(&samplerManager, &oitShaderManager, &oitBuffers, samples); textureDrawer.ShareAttachments(&screenDrawer); BaseInit(screenDrawer.GetRenderPass(), screenDrawer.GetOsdSubpass()); InitOverlay(screenDrawer.OverlayPass()); // (a quad pipeline is for one subpass: the one above is the last subpass's, this the colour one's) lastPicturePipeline.Init(&shaderManager, screenDrawer.GetRenderPass(), 1, samples); screenDrawer.SetLastPicturePipeline(&lastPicturePipeline); MakeCompute(); InitComputeMotion(); // what this size no longer needs is given back once the frames that used it are done GetContext()->GetHeap().TrimSoon(); } void KeepPicture() override { screenDrawer.KeepPicture(); } void SetRestoredPicture(vk::ImageView view) override { screenDrawer.SetRestoredPicture(view); } protected: /* The per-pixel lists sort the translucent polygons, by depth and then * by polygon number: a frame decoded on the GPU numbers its strips by * their draws' firstInstance, and without that it has no sorted pass */ u32 TaFlags() const override { return TA_GPU_DRAW | TA_GPU_PROVOKE | (taDecoder.HasInstance() ? TA_GPU_INSTANCE : TA_GPU_NO_SORT); } public: void Term() override { DEBUG_LOG(RENDERER, "VulkanRenderer::Term"); GetContext()->LogVramUsage(); // (the frontend lets go of the pictures before they go) GetContext()->ReleasePicture(); texCommandPool.WaitAll(); DropQuads(); computeDrawer.Term(); samplerManager.Term(); screenDrawer.Term(); lastPicturePipeline.Term(); textureDrawer.Term(); /* (after the drawers: the pipeline thread may be making pipelines * of these shaders for them until their pipelines go) */ oitShaderManager.Term(); rttShaderManager.Term(); oitBuffers.Term(); BaseVulkanRenderer::Term(); } bool RenderFrame() override { if (pvrrc.isRenderFramebuffer) return true; if (pvrrc.isRTT && (pvrrc.fb_X_CLIP.max < pvrrc.fb_X_CLIP.min || pvrrc.fb_Y_CLIP.max < pvrrc.fb_Y_CLIP.min)) { /* A pixel clip that ends before it starts holds no pixel: the * chip writes nothing. (Its size would be negative here.) */ WARN_LOG(RENDERER, "Render to texture with an empty pixel clip: %d-%d, %d-%d", pvrrc.fb_X_CLIP.min, pvrrc.fb_X_CLIP.max, pvrrc.fb_Y_CLIP.min, pvrrc.fb_Y_CLIP.max); return false; } /* Rendering Method: Compute draws the frames it can * (vulkan/compute_drawer.h) into the same pictures, and the renders * to a texture into the same textures; the others are drawn below */ if (computeDrawer.Ready() && pvrrc.isRTT) { if (!texPool.TargetsStorable()) computeDrawer.Fallback(ComputeFrame::Why_Rtt); else if (!computeDrawer.CanDraw()) computeDrawer.Fallback(ComputeFrame::Why_Decode); else if (computeDrawer.Prepare(false, false, paletteTexture.get(), OITTextureDrawer::ComputeSize())) { const OITTextureDrawer::ComputeTarget target = textureDrawer.ComputeBegin(); computeDrawer.Record(target.image, target.view, vk::ImageView()); textureDrawer.ComputeEnd(); return false; } } else if (computeDrawer.Ready()) { bool last, scaled; if (!computeDrawer.CanDraw()) computeDrawer.Fallback(ComputeFrame::Why_Decode); else if (vmus->Drawing()) computeDrawer.Fallback(ComputeFrame::Why_Osd); else if (!screenDrawer.ComputeCanStart(&last, &scaled)) computeDrawer.Fallback(ComputeFrame::Why_Picture); else if (computeDrawer.Prepare(last, scaled, paletteTexture.get())) { // (the upscaler's strips matched once the frame is this renderer's: the raster drawer matches its own) screenDrawer.ComputeMotionBegin(computeDrawer.Slot()); const OITScreenDrawer::ComputeTarget target = screenDrawer.ComputeBegin(last, scaled); computeDrawer.Record(target.image, target.view, target.last); screenDrawer.ComputeEnd(computeDrawer.Sums()); screenDrawer.ComputeMotionEnd(computeDrawer.Slot(), computeDrawer.DepthImage(), computeDrawer.DepthView()); return true; } } OITDrawer *drawer; if (pvrrc.isRTT) drawer = &textureDrawer; else drawer = &screenDrawer; SetParsed(*drawer); drawer->Draw(fogTexture.get(), paletteTexture.get()); if (!pvrrc.isRTT) { /* (the chip's write-out before what goes over the picture, which * then goes in a render pass of its own: writeout.h) */ const bool after = screenDrawer.HasWriteOut() && vmus->Drawing() && screenDrawer.WriteOutBeforeOsd(); vmus->DrawOSD(screenDrawer.GetCurrentCommandBuffer(), vk::Extent2D(screen_width, screen_height), &texCommandPool, after ? &overlayQuadPipeline : nullptr); } drawer->EndFrame(); return !pvrrc.isRTT; } private: /* The compute renderer, when it is the rendering method; then the * screen's drawer's motion vector pass over it. (Made before the * drawers when the renderer starts: its kernels are the first the * pipeline thread is given, as every frame draws with them.) */ void MakeCompute() { if (rend_compute_wanted) // (for the temporal upscaler, with the depth stored) computeDrawer.Init(GetContext()->GetViewPort(), samples, &texCommandPool, &samplerManager, upscaler_active() && samples == 1); else computeDrawer.Term(); } void InitComputeMotion() { if (rend_compute_wanted) computeDrawer.SetMotionPass(screenDrawer.ComputeMotionPass(computeDrawer.DepthViews())); else screenDrawer.ComputeMotionPass(std::vector()); } /* SSAA's samples a pixel, for the screen: as far as the device and * the video memory budget go for its attachments, after the lists */ u32 SsaaSamples() const { // (the upscaler is the anti-aliasing then, and reads the depth, which SSAA's samples are not) if (upscaler_active()) return 1; const vk::Extent2D size = GetContext()->GetViewPort(); return GetContext()->SsaaSamples(settings.rend.Ssaa, ssaa_per_sample_kib(size.width, size.height, 4 + GetContext()->GetDepthBytes()), (u32)(oitBuffers.PlannedSize(size.width, size.height) >> 10), false, "Vulkan per-pixel renderer"); } OITBuffers oitBuffers; SamplerManager samplerManager; OITShaderManager oitShaderManager; /* Textures are rendered to with one sample a pixel, SSAA or not: their * sample attachments would be made as large as such a texture can be, * more video memory than SSAA's screen attachments, and these renders' * attachments are stored between render passes */ OITShaderManager rttShaderManager; OITScreenDrawer screenDrawer; QuadPipeline lastPicturePipeline; OITTextureDrawer textureDrawer; ComputeDrawer computeDrawer; }; Renderer* rend_OITVulkan() { return new OITVulkanRenderer(); }