/* Created on: Oct 2, 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 "drawer.h" #include "hw/pvr/ta.h" #include "rend/transform_matrix.h" bool BaseVulkanRenderer::BaseInit(vk::RenderPass renderPass, int subpass) { texCommandPool.Init(); fbCommandPool.Init(); quadPipeline = std::make_unique(false, false); quadPipeline->Init(&shaderManager, renderPass, subpass); framebufferDrawer = std::make_unique(); framebufferDrawer->Init(quadPipeline.get()); return true; } void BaseVulkanRenderer::Term() { GetContext()->WaitIdle(); GetContext()->PresentFrame(nullptr, nullptr, vk::Extent2D(), 0); #if defined(VIDEO_ROUTING) && defined(TARGET_MAC) os_VideoRoutingTermVk(); #endif framebufferDrawer.reset(); quadPipeline.reset(); textureCache.Clear(); fogTexture = nullptr; paletteTexture = nullptr; texCommandPool.Term(); fbCommandPool.Term(); framebufferTextures.clear(); framebufferTexIndex = 0; shaderManager.term(); } BaseTextureCacheData *BaseVulkanRenderer::GetTexture(TSP tsp, TCW tcw) { Texture* tf = textureCache.getTextureCacheData(tsp, tcw); //update if needed if (tf->NeedsUpdate()) { // This kills performance when a frame is skipped and lots of texture updated each frame //if (textureCache.IsInFlight(tf, true)) // textureCache.DestroyLater(tf); tf->SetCommandBuffer(texCommandBuffer); if (!tf->Update()) { tf->SetCommandBuffer(nullptr); return nullptr; } } else if (tf->IsCustomTextureAvailable()) { tf->deferDeleteResource(&texCommandPool); tf->SetCommandBuffer(texCommandBuffer); tf->CheckCustomTexture(); } tf->SetCommandBuffer(nullptr); textureCache.SetInFlight(tf); return tf; } void BaseVulkanRenderer::Process(TA_context* ctx) { if (!ctx->rend.isRTT) { framebufferRendered = false; if (!config::EmulateFramebuffer) clearLastFrame = false; } if (resetTextureCache) { textureCache.Clear(); resetTextureCache = false; } texCommandPool.BeginFrame(); textureCache.SetCurrentIndex(texCommandPool.GetIndex()); textureCache.Cleanup(); texCommandBuffer = texCommandPool.Allocate(); texCommandBuffer.begin(vk::CommandBufferBeginInfo(vk::CommandBufferUsageFlagBits::eOneTimeSubmit)); ta_parse(ctx, true); // TODO can't update fog or palette twice in multi render CheckFogTexture(); CheckPaletteTexture(); texCommandBuffer.end(); } void BaseVulkanRenderer::ReInitOSD() { texCommandPool.Init(); fbCommandPool.Init(); } void BaseVulkanRenderer::RenderFramebuffer(const FramebufferInfo& info) { framebufferTexIndex = (framebufferTexIndex + 1) % GetContext()->GetSwapChainSize(); if (framebufferTextures.size() != GetContext()->GetSwapChainSize()) framebufferTextures.resize(GetContext()->GetSwapChainSize()); std::unique_ptr& curTexture = framebufferTextures[framebufferTexIndex]; if (!curTexture) { curTexture = std::make_unique(); curTexture->tex_type = TextureType::_8888; } fbCommandPool.BeginFrame(); vk::CommandBuffer commandBuffer = fbCommandPool.Allocate(); commandBuffer.begin(vk::CommandBufferBeginInfo(vk::CommandBufferUsageFlagBits::eOneTimeSubmit)); curTexture->SetCommandBuffer(commandBuffer); { static const float scopeColor[4] = { 0.0f, 1.0f, 0.0f, 1.0f }; CommandBufferDebugScope _(commandBuffer, "RenderFramebuffer", scopeColor); if (info.fb_r_ctrl.fb_enable == 0 || info.vo_control.blank_video == 1) { // Video output disabled u8 rgba[]{ (u8)info.vo_border_col._red, (u8)info.vo_border_col._green, (u8)info.vo_border_col._blue, 255 }; curTexture->UploadToGPU(1, 1, rgba, false); } else { PixelBuffer pb; int width; int height; ReadFramebuffer(info, pb, width, height); curTexture->UploadToGPU(width, height, (u8*)pb.data(), false); } } curTexture->SetCommandBuffer(nullptr); commandBuffer.end(); fbCommandPool.EndFrame(); framebufferRendered = true; clearLastFrame = false; } void BaseVulkanRenderer::RenderVideoRouting() { #if defined(VIDEO_ROUTING) && defined(TARGET_MAC) if (config::VideoRouting) { auto device = GetContext()->GetDevice(); auto srcImage = device.getSwapchainImagesKHR(GetContext()->GetSwapChain())[GetContext()->GetCurrentImageIndex()]; auto graphicsQueue = device.getQueue(GetContext()->GetGraphicsQueueFamilyIndex(), 0); int targetWidth = (config::VideoRoutingScale ? config::VideoRoutingVRes * settings.display.width / settings.display.height : settings.display.width); int targetHeight = (config::VideoRoutingScale ? config::VideoRoutingVRes : settings.display.height); extern void os_VideoRoutingPublishFrameTexture(const vk::Device& device, const vk::Image& image, const vk::Queue& queue, float x, float y, float w, float h); os_VideoRoutingPublishFrameTexture(device, srcImage, graphicsQueue, 0, 0, targetWidth, targetHeight); } else { os_VideoRoutingTermVk(); } #endif } void BaseVulkanRenderer::CheckFogTexture() { if (!fogTexture) { fogTexture = std::make_unique(); fogTexture->tex_type = TextureType::_8; updateFogTable = true; } if (!updateFogTable || !config::Fog) return; updateFogTable = false; u8 texData[256]; MakeFogTexture(texData); fogTexture->SetCommandBuffer(texCommandBuffer); fogTexture->UploadToGPU(128, 2, texData, false); fogTexture->SetCommandBuffer(nullptr); } void BaseVulkanRenderer::CheckPaletteTexture() { if (!paletteTexture) { paletteTexture = std::make_unique(); paletteTexture->tex_type = TextureType::_8888; } else if (!updatePalette) { return; } updatePalette = false; paletteTexture->SetCommandBuffer(texCommandBuffer); paletteTexture->UploadToGPU(1024, 1, (u8 *)palette32_ram, false); paletteTexture->SetCommandBuffer(nullptr); } bool BaseVulkanRenderer::presentFramebuffer() { if (framebufferTexIndex >= (int)framebufferTextures.size()) return false; Texture *fbTexture = framebufferTextures[framebufferTexIndex].get(); if (fbTexture == nullptr) return false; GetContext()->PresentFrame(fbTexture->GetImage(), fbTexture->GetImageView(), fbTexture->getSize(), getDCFramebufferAspectRatio()); return true; } class VulkanRenderer final : public BaseVulkanRenderer { public: bool Init() override { NOTICE_LOG(RENDERER, "VulkanRenderer::Init"); textureDrawer.Init(&samplerManager, &shaderManager, &textureCache); textureDrawer.SetCommandPool(&texCommandPool); screenDrawer.Init(&samplerManager, &shaderManager, viewport); screenDrawer.SetCommandPool(&texCommandPool); BaseInit(screenDrawer.GetRenderPass()); emulateFramebuffer = config::EmulateFramebuffer; return true; } void Term() override { DEBUG_LOG(RENDERER, "VulkanRenderer::Term"); GetContext()->WaitIdle(); texCommandPool.Term(); // make sure all in-flight buffers are returned screenDrawer.Term(); textureDrawer.Term(); samplerManager.term(); BaseVulkanRenderer::Term(); } void Process(TA_context* ctx) override { if (emulateFramebuffer != config::EmulateFramebuffer) { screenDrawer.EndRenderPass(); VulkanContext::Instance()->WaitIdle(); screenDrawer.Term(); screenDrawer.Init(&samplerManager, &shaderManager, viewport); BaseInit(screenDrawer.GetRenderPass()); emulateFramebuffer = config::EmulateFramebuffer; } else if (ctx->rend.isRTT) { screenDrawer.EndRenderPass(); } BaseVulkanRenderer::Process(ctx); } bool Render() override { try { Drawer *drawer; if (pvrrc.isRTT) drawer = &textureDrawer; else { resize(pvrrc.framebufferWidth, pvrrc.framebufferHeight); drawer = &screenDrawer; } drawer->Draw(fogTexture.get(), paletteTexture.get()); if (config::EmulateFramebuffer || pvrrc.isRTT) // delay ending the render pass in case of multi render drawer->EndRenderPass(); return !pvrrc.isRTT; } catch (const vk::SystemError& e) { // Sometimes happens when resizing the window WARN_LOG(RENDERER, "Vulkan system error %s", e.what()); return false; } } bool Present() override { if (clearLastFrame) return false; if (config::EmulateFramebuffer || framebufferRendered) return presentFramebuffer(); else return screenDrawer.PresentFrame(); } protected: void resize(int w, int h) override { if ((u32)w == viewport.width && (u32)h == viewport.height) return; BaseVulkanRenderer::resize(w, h); GetContext()->WaitIdle(); screenDrawer.Init(&samplerManager, &shaderManager, viewport); } private: SamplerManager samplerManager; ScreenDrawer screenDrawer; TextureDrawer textureDrawer; bool emulateFramebuffer = false; }; Renderer* rend_Vulkan() { return new VulkanRenderer(); } void ReInitOSD() { if (renderer != nullptr) { BaseVulkanRenderer *vkrenderer = dynamic_cast(renderer); if (vkrenderer != nullptr) vkrenderer->ReInitOSD(); } }