Merge pull request #9713 from hrydgard/vulkan-improvements

Vulkan - initial implementation of buffered rendering, fixes for Mali/Android
This commit is contained in:
Henrik Rydgård 2017-05-30 09:54:17 +02:00 committed by GitHub
commit c109f849e0
49 changed files with 1439 additions and 733 deletions

View File

@ -86,6 +86,7 @@ VulkanContext::VulkanContext(const char *app_name, int app_ver, uint32_t flags)
instance_extension_names.push_back(VK_KHR_ANDROID_SURFACE_EXTENSION_NAME);
#endif
device_extension_names.push_back(VK_KHR_SWAPCHAIN_EXTENSION_NAME);
// device_extension_names.push_back(VK_NV_DEDICATED_ALLOCATION_EXTENSION_NAME);
if (flags & VULKAN_FLAG_VALIDATE) {
for (size_t i = 0; i < ARRAY_SIZE(validationLayers); i++) {
@ -215,13 +216,11 @@ void VulkanContext::QueueBeforeSurfaceRender(VkCommandBuffer cmd) {
cmdQueue_.push_back(cmd);
}
VkCommandBuffer VulkanContext::BeginSurfaceRenderPass(VkClearValue clear_values[2]) {
VkCommandBuffer VulkanContext::BeginFrame() {
FrameData *frame = &frame_[curFrame_];
// Get the index of the next available swapchain image, and a semaphore to block command buffer execution on.
// Now, I wonder if we should do this early in the frame or late? Right now we do it early, which should be fine.
VkResult res = vkAcquireNextImageKHR(device_, swap_chain_, UINT64_MAX, acquireSemaphore, VK_NULL_HANDLE, &current_buffer);
// TODO: Deal with the VK_SUBOPTIMAL_KHR and VK_ERROR_OUT_OF_DATE_KHR
// return codes
assert(res == VK_SUCCESS);
@ -238,7 +237,11 @@ VkCommandBuffer VulkanContext::BeginSurfaceRenderPass(VkClearValue clear_values[
res = vkBeginCommandBuffer(frame->cmdBuf, &begin);
TransitionFromPresent(frame->cmdBuf, swapChainBuffers[current_buffer].image);
return frame->cmdBuf;
}
VkCommandBuffer VulkanContext::BeginSurfaceRenderPass(VkClearValue clear_values[2]) {
FrameData *frame = &frame_[curFrame_];
VkRenderPassBeginInfo rp_begin = { VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO };
rp_begin.renderPass = surface_render_pass_;
rp_begin.framebuffer = framebuffers_[current_buffer];
@ -248,18 +251,18 @@ VkCommandBuffer VulkanContext::BeginSurfaceRenderPass(VkClearValue clear_values[
rp_begin.renderArea.extent.height = height_;
rp_begin.clearValueCount = 2;
rp_begin.pClearValues = clear_values;
// We don't really need to record this at this point in time, but hey, at some point we'll start this
// pass anyway so might as well do it now (although you can imagine getting away with just a stretchblt and not
// even starting a final render pass if there's nothing to overlay... hm. Uncommon though on mobile).
vkCmdBeginRenderPass(frame->cmdBuf, &rp_begin, VK_SUBPASS_CONTENTS_INLINE);
return frame->cmdBuf;
}
void VulkanContext::EndSurfaceRenderPass() {
FrameData *frame = &frame_[curFrame_];
// ILOG("VulkanContext::EndSurfaceRenderPass");
vkCmdEndRenderPass(frame->cmdBuf);
}
void VulkanContext::EndFrame() {
FrameData *frame = &frame_[curFrame_];
TransitionToPresent(frame->cmdBuf, swapChainBuffers[current_buffer].image);
VkResult res = vkEndCommandBuffer(frame->cmdBuf);
@ -285,12 +288,12 @@ void VulkanContext::EndSurfaceRenderPass() {
VkSubmitInfo submit_info = { VK_STRUCTURE_TYPE_SUBMIT_INFO };
submit_info.waitSemaphoreCount = 1;
submit_info.pWaitSemaphores = &acquireSemaphore;
VkPipelineStageFlags waitStage[1] = { VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT };
VkPipelineStageFlags waitStage[1] = { VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT };
submit_info.pWaitDstStageMask = waitStage;
submit_info.commandBufferCount = (uint32_t)cmdBufs.size();
submit_info.pCommandBuffers = cmdBufs.data();
submit_info.signalSemaphoreCount = 0;
submit_info.pSignalSemaphores = NULL;
submit_info.signalSemaphoreCount = 1;
submit_info.pSignalSemaphores = &renderingCompleteSemaphore;
res = vkQueueSubmit(gfx_queue_, 1, &submit_info, frame->fence);
assert(res == VK_SUCCESS);
@ -298,8 +301,8 @@ void VulkanContext::EndSurfaceRenderPass() {
present.swapchainCount = 1;
present.pSwapchains = &swap_chain_;
present.pImageIndices = &current_buffer;
present.pWaitSemaphores = NULL;
present.waitSemaphoreCount = 0;
present.pWaitSemaphores = &renderingCompleteSemaphore;
present.waitSemaphoreCount = 1;
present.pResults = NULL;
res = vkQueuePresentKHR(gfx_queue_, &present);
@ -942,13 +945,11 @@ void VulkanContext::InitQueue() {
vkGetDeviceQueue(device_, graphics_queue_family_index_, 0, &gfx_queue_);
ILOG("gfx_queue_: %p", gfx_queue_);
VkSemaphoreCreateInfo acquireSemaphoreCreateInfo = { VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO };
acquireSemaphoreCreateInfo.flags = 0;
res = vkCreateSemaphore(device_,
&acquireSemaphoreCreateInfo,
NULL,
&acquireSemaphore);
VkSemaphoreCreateInfo semaphoreCreateInfo = { VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO };
semaphoreCreateInfo.flags = 0;
res = vkCreateSemaphore(device_, &semaphoreCreateInfo, NULL, &acquireSemaphore);
assert(res == VK_SUCCESS);
res = vkCreateSemaphore(device_, &semaphoreCreateInfo, NULL, &renderingCompleteSemaphore);
assert(res == VK_SUCCESS);
}
@ -1063,7 +1064,9 @@ bool VulkanContext::InitSwapchain(VkCommandBuffer cmd) {
&swapchainImageCount, NULL);
assert(res == VK_SUCCESS);
VkImage* swapchainImages = (VkImage*)malloc(swapchainImageCount * sizeof(VkImage));
ILOG("Vulkan swapchain image count: %d", swapchainImageCount);
VkImage* swapchainImages = new VkImage[swapchainImageCount];
assert(swapchainImages);
res = vkGetSwapchainImagesKHR(device_, swap_chain_, &swapchainImageCount, swapchainImages);
assert(res == VK_SUCCESS);
@ -1101,8 +1104,7 @@ bool VulkanContext::InitSwapchain(VkCommandBuffer cmd) {
swapChainBuffers.push_back(sc_buffer);
assert(res == VK_SUCCESS);
}
free(swapchainImages);
delete[] swapchainImages;
current_buffer = 0;
return true;
@ -1242,6 +1244,7 @@ void VulkanContext::DestroySwapChain() {
swap_chain_ = VK_NULL_HANDLE;
swapChainBuffers.clear();
vkDestroySemaphore(device_, acquireSemaphore, NULL);
vkDestroySemaphore(device_, renderingCompleteSemaphore, NULL);
}
void VulkanContext::DestroyFramebuffers() {
@ -1300,6 +1303,9 @@ void TransitionImageLayout(VkCommandBuffer cmd, VkImage image, VkImageAspectFlag
if (old_image_layout == VK_IMAGE_LAYOUT_PRESENT_SRC_KHR) {
image_memory_barrier.srcAccessMask |= VK_ACCESS_MEMORY_READ_BIT;
}
if (old_image_layout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) {
image_memory_barrier.srcAccessMask |= VK_ACCESS_SHADER_READ_BIT;
}
if (old_image_layout == VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL) {
image_memory_barrier.srcAccessMask |= VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;

View File

@ -258,17 +258,22 @@ public:
VkCommandBuffer GetInitCommandBuffer();
VkFramebuffer GetSurfaceFramebuffer() {
return framebuffers_[current_buffer];
}
// This must only be accessed between BeginSurfaceRenderPass and EndSurfaceRenderPass.
VkCommandBuffer GetSurfaceCommandBuffer() {
return frame_[curFrame_ & 1].cmdBuf;
}
VkCommandBuffer BeginFrame();
// The surface render pass is special because it has to acquire the backbuffer, and may thus "block".
// Use the returned command buffer to enqueue commands that render to the backbuffer.
// To render to other buffers first, you can submit additional commandbuffers using QueueBeforeSurfaceRender(cmd).
VkCommandBuffer BeginSurfaceRenderPass(VkClearValue clear_values[2]);
// May eventually need the ability to break and resume the backbuffer render pass in a few rare cases.
void EndSurfaceRenderPass();
void EndFrame();
void QueueBeforeSurfaceRender(VkCommandBuffer cmd);
@ -312,6 +317,7 @@ public:
private:
VkSemaphore acquireSemaphore;
VkSemaphore renderingCompleteSemaphore;
#ifdef _WIN32
HINSTANCE connection; // hInstance - Windows Instance

View File

@ -362,6 +362,14 @@ void VulkanTexture::EndCreate() {
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
}
void VulkanTexture::TransitionForUpload() {
VkCommandBuffer cmd = vulkan_->GetInitCommandBuffer();
TransitionImageLayout(cmd, image,
VK_IMAGE_ASPECT_COLOR_BIT,
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
}
void VulkanTexture::Destroy() {
if (view != VK_NULL_HANDLE) {
vulkan_->Delete().QueueDeleteImageView(view);

View File

@ -34,6 +34,9 @@ public:
bool CreateDirect(int w, int h, int numMips, VkFormat format, VkImageLayout initialLayout, VkImageUsageFlags usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, const VkComponentMapping *mapping = nullptr);
void UploadMip(int mip, int mipWidth, int mipHeight, VkBuffer buffer, uint32_t offset, size_t rowLength); // rowLength is in pixels
void EndCreate();
void TransitionForUpload();
int GetNumMips() const { return numMips_; }
void Destroy();

View File

@ -51,6 +51,7 @@ public:
void Unmap() {
assert(writePtr_);
/*
// Should not need this since we use coherent memory.
VkMappedMemoryRange range = { VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE };
range.offset = 0;
range.size = offset_;

View File

@ -134,10 +134,6 @@ FramebufferManagerCommon::~FramebufferManagerCommon() {
void FramebufferManagerCommon::Init() {
const std::string gameId = g_paramSFO.GetValueString("DISC_ID");
// And an initial clear. We don't clear per frame as the games are supposed to handle that
// by themselves.
ClearBuffer();
BeginFrame();
}
@ -204,14 +200,9 @@ void FramebufferManagerCommon::SetNumExtraFBOs(int num) {
// No depth/stencil for post processing
Draw::Framebuffer *fbo = draw_->CreateFramebuffer({ (int)renderWidth_, (int)renderHeight_, 1, 1, false, Draw::FBO_8888 });
extraFBOs_.push_back(fbo);
// The new FBO is still bound after creation, but let's bind it anyway.
draw_->BindFramebufferAsRenderTarget(fbo);
ClearBuffer();
}
currentRenderVfb_ = 0;
draw_->BindBackbufferAsRenderTarget();
draw_->BindFramebufferAsRenderTarget(nullptr, { Draw::RPAction::KEEP, Draw::RPAction::KEEP });
}
// Heuristics to figure out the size of FBO to create.
@ -533,13 +524,13 @@ void FramebufferManagerCommon::DestroyFramebuf(VirtualFramebuffer *v) {
void FramebufferManagerCommon::NotifyRenderFramebufferCreated(VirtualFramebuffer *vfb) {
if (!useBufferedRendering_) {
draw_->BindBackbufferAsRenderTarget();
// Let's ignore rendering to targets that have not (yet) been displayed.
gstate_c.skipDrawReason |= SKIPDRAW_NON_DISPLAYED_FB;
}
textureCache_->NotifyFramebuffer(vfb->fb_address, vfb, NOTIFY_FB_CREATED);
// TODO: Is this necessary?
ClearBuffer();
// ugly...
@ -579,41 +570,6 @@ void FramebufferManagerCommon::NotifyRenderFramebufferSwitched(VirtualFramebuffe
}
textureCache_->ForgetLastTexture();
if (useBufferedRendering_) {
if (vfb->fbo) {
draw_->BindFramebufferAsRenderTarget(vfb->fbo);
} else {
// This should only happen very briefly when toggling useBufferedRendering_.
ResizeFramebufFBO(vfb, vfb->width, vfb->height, true);
}
} else {
if (vfb->fbo) {
// This should only happen very briefly when toggling useBufferedRendering_.
textureCache_->NotifyFramebuffer(vfb->fb_address, vfb, NOTIFY_FB_DESTROYED);
delete vfb->fbo;
vfb->fbo = nullptr;
}
draw_->BindBackbufferAsRenderTarget();
// Let's ignore rendering to targets that have not (yet) been displayed.
if (vfb->usageFlags & FB_USAGE_DISPLAYED_FRAMEBUFFER) {
gstate_c.skipDrawReason &= ~SKIPDRAW_NON_DISPLAYED_FB;
} else {
gstate_c.skipDrawReason |= SKIPDRAW_NON_DISPLAYED_FB;
}
}
textureCache_->NotifyFramebuffer(vfb->fb_address, vfb, NOTIFY_FB_UPDATED);
if (gl_extensions.IsGLES) {
// Some tiled mobile GPUs benefit IMMENSELY from clearing an FBO before rendering
// to it. This broke stuff before, so now it only clears on the first use of an
// FBO in a frame. This means that some games won't be able to avoid the on-some-GPUs
// performance-crushing framebuffer reloads from RAM, but we'll have to live with that.
if (vfb->last_frame_render != gpuStats.numFlips) {
ClearBuffer();
}
}
// Copy depth pixel value from the read framebuffer to the draw framebuffer
if (prevVfb && !g_Config.bDisableSlowFramebufEffects) {
if (!prevVfb->fbo || !vfb->fbo || !useBufferedRendering_ || !prevVfb->depthUpdated || isClearingDepth) {
@ -628,6 +584,42 @@ void FramebufferManagerCommon::NotifyRenderFramebufferSwitched(VirtualFramebuffe
ReformatFramebufferFrom(vfb, vfb->drawnFormat);
}
if (useBufferedRendering_) {
if (vfb->fbo) {
if (gl_extensions.IsGLES) {
// Some tiled mobile GPUs benefit IMMENSELY from clearing an FBO before rendering
// to it. This broke stuff before, so now it only clears on the first use of an
// FBO in a frame. This means that some games won't be able to avoid the on-some-GPUs
// performance-crushing framebuffer reloads from RAM, but we'll have to live with that.
if (vfb->last_frame_render != gpuStats.numFlips) {
draw_->BindFramebufferAsRenderTarget(vfb->fbo, { Draw::RPAction::CLEAR, Draw::RPAction::CLEAR });
} else {
draw_->BindFramebufferAsRenderTarget(vfb->fbo, { Draw::RPAction::KEEP, Draw::RPAction::KEEP });
}
} else {
draw_->BindFramebufferAsRenderTarget(vfb->fbo, { Draw::RPAction::KEEP, Draw::RPAction::KEEP });
}
} else {
// This should only happen very briefly when toggling useBufferedRendering_.
ResizeFramebufFBO(vfb, vfb->width, vfb->height, true);
}
} else {
if (vfb->fbo) {
// This should only happen very briefly when toggling useBufferedRendering_.
textureCache_->NotifyFramebuffer(vfb->fb_address, vfb, NOTIFY_FB_DESTROYED);
delete vfb->fbo;
vfb->fbo = nullptr;
}
// Let's ignore rendering to targets that have not (yet) been displayed.
if (vfb->usageFlags & FB_USAGE_DISPLAYED_FRAMEBUFFER) {
gstate_c.skipDrawReason &= ~SKIPDRAW_NON_DISPLAYED_FB;
} else {
gstate_c.skipDrawReason |= SKIPDRAW_NON_DISPLAYED_FB;
}
}
textureCache_->NotifyFramebuffer(vfb->fb_address, vfb, NOTIFY_FB_UPDATED);
// ugly...
if (gstate_c.curRTWidth != vfb->width || gstate_c.curRTHeight != vfb->height) {
gstate_c.Dirty(DIRTY_PROJTHROUGHMATRIX);
@ -710,8 +702,9 @@ void FramebufferManagerCommon::DrawPixels(VirtualFramebuffer *vfb, int dstX, int
MakePixelTexture(srcPixels, srcPixelFormat, srcStride, width, height, u1, v1);
if (useBufferedRendering_ && vfb && vfb->fbo) {
draw_->BindFramebufferAsRenderTarget(vfb->fbo);
draw_->BindFramebufferAsRenderTarget(vfb->fbo, { Draw::RPAction::KEEP, Draw::RPAction::KEEP });
SetViewport2D(0, 0, vfb->renderWidth, vfb->renderHeight);
draw_->SetScissorRect(0, 0, vfb->renderWidth, vfb->renderHeight);
} else {
// We are drawing to the back buffer so need to flip.
if (needBackBufferYSwap_)
@ -719,6 +712,7 @@ void FramebufferManagerCommon::DrawPixels(VirtualFramebuffer *vfb, int dstX, int
float x, y, w, h;
CenterDisplayOutputRect(&x, &y, &w, &h, 480.0f, 272.0f, (float)pixelWidth_, (float)pixelHeight_, ROTATION_LOCKED_HORIZONTAL);
SetViewport2D(x, y, w, h);
draw_->SetScissorRect(0, 0, pixelWidth_, pixelHeight_);
}
DisableState();
@ -827,21 +821,17 @@ void FramebufferManagerCommon::SetViewport2D(int x, int y, int w, int h) {
void FramebufferManagerCommon::CopyDisplayToOutput() {
DownloadFramebufferOnSwitch(currentRenderVfb_);
SetViewport2D(0, 0, pixelWidth_, pixelHeight_);
draw_->BindBackbufferAsRenderTarget();
currentRenderVfb_ = 0;
if (displayFramebufPtr_ == 0) {
DEBUG_LOG(FRAMEBUF, "Display disabled, displaying only black");
// No framebuffer to display! Clear to black.
ClearBuffer();
if (useBufferedRendering_) {
draw_->BindFramebufferAsRenderTarget(nullptr, { Draw::RPAction::CLEAR, Draw::RPAction::CLEAR });
}
return;
}
if (useBufferedRendering_) {
draw_->Clear(Draw::FB_COLOR_BIT | Draw::FB_STENCIL_BIT | Draw::FB_DEPTH_BIT, 0, 0, 0);
}
u32 offsetX = 0;
u32 offsetY = 0;
@ -900,14 +890,23 @@ void FramebufferManagerCommon::CopyDisplayToOutput() {
}
if (!vfb) {
if (useBufferedRendering_) {
// Bind and clear the backbuffer. This should be the first time during the frame that it's bound.
draw_->BindFramebufferAsRenderTarget(nullptr, { Draw::RPAction::CLEAR, Draw::RPAction::CLEAR });
}
// Just a pointer to plain memory to draw. We should create a framebuffer, then draw to it.
SetViewport2D(0, 0, pixelWidth_, pixelHeight_);
draw_->SetScissorRect(0, 0, pixelWidth_, pixelHeight_);
DrawFramebufferToOutput(Memory::GetPointer(displayFramebufPtr_), displayFormat_, displayStride_, true);
return;
}
} else {
DEBUG_LOG(FRAMEBUF, "Found no FBO to display! displayFBPtr = %08x", displayFramebufPtr_);
// No framebuffer to display! Clear to black.
ClearBuffer();
if (useBufferedRendering_) {
// Bind and clear the backbuffer. This should be the first time during the frame that it's bound.
draw_->BindFramebufferAsRenderTarget(nullptr, { Draw::RPAction::CLEAR, Draw::RPAction::CLEAR });
}
return;
}
}
@ -929,8 +928,6 @@ void FramebufferManagerCommon::CopyDisplayToOutput() {
DEBUG_LOG(FRAMEBUF, "Displaying FBO %08x", vfb->fb_address);
DisableState();
draw_->BindFramebufferAsTexture(vfb->fbo, 0, Draw::FB_COLOR_BIT, 0);
int uvRotation = useBufferedRendering_ ? g_Config.iInternalScreenRotation : ROTATION_LOCKED_HORIZONTAL;
// Output coordinates
@ -945,6 +942,10 @@ void FramebufferManagerCommon::CopyDisplayToOutput() {
float v1 = (272.0f + offsetY) / (float)vfb->bufferHeight;
if (!usePostShader_) {
draw_->BindFramebufferAsRenderTarget(nullptr, { Draw::RPAction::CLEAR, Draw::RPAction::CLEAR });
draw_->BindFramebufferAsTexture(vfb->fbo, 0, Draw::FB_COLOR_BIT, 0);
SetViewport2D(0, 0, pixelWidth_, pixelHeight_);
draw_->SetScissorRect(0, 0, pixelWidth_, pixelHeight_);
bool linearFilter = g_Config.iBufFilter == SCALE_LINEAR;
// We are doing the DrawActiveTexture call directly to the backbuffer here. Hence, we must
// flip V.
@ -966,10 +967,11 @@ void FramebufferManagerCommon::CopyDisplayToOutput() {
}
} else if (usePostShader_ && extraFBOs_.size() == 1 && !postShaderAtOutputResolution_) {
// An additional pass, post-processing shader to the extra FBO.
draw_->BindFramebufferAsRenderTarget(extraFBOs_[0]);
draw_->BindFramebufferAsRenderTarget(extraFBOs_[0], { Draw::RPAction::DONT_CARE, Draw::RPAction::DONT_CARE });
int fbo_w, fbo_h;
draw_->GetFramebufferDimensions(extraFBOs_[0], &fbo_w, &fbo_h);
SetViewport2D(0, 0, fbo_w, fbo_h);
draw_->SetScissorRect(0, 0, pixelWidth_, pixelHeight_);
shaderManager_->DirtyLastShader(); // dirty lastShader_
PostShaderUniforms uniforms{};
CalculatePostShaderUniforms(vfb->bufferWidth, vfb->bufferHeight, renderWidth_, renderHeight_, &uniforms);
@ -977,7 +979,8 @@ void FramebufferManagerCommon::CopyDisplayToOutput() {
bool linearFilter = g_Config.iBufFilter == SCALE_LINEAR;
DrawActiveTexture(0, 0, fbo_w, fbo_h, fbo_w, fbo_h, 0.0f, 0.0f, 1.0f, 1.0f, ROTATION_LOCKED_HORIZONTAL, linearFilter);
draw_->BindBackbufferAsRenderTarget();
draw_->BindFramebufferAsRenderTarget(nullptr, { Draw::RPAction::CLEAR, Draw::RPAction::CLEAR });
SetViewport2D(0, 0, pixelWidth_, pixelHeight_);
// Use the extra FBO, with applied post-processing shader, as a texture.
// fbo_bind_as_texture(extraFBOs_[0], FB_COLOR_BIT, 0);
@ -1009,11 +1012,13 @@ void FramebufferManagerCommon::CopyDisplayToOutput() {
/*
if (gl_extensions.GLES3 && glInvalidateFramebuffer != nullptr) {
draw_->BindFramebufferAsRenderTarget(extraFBOs_[0]);
draw_->BindFramebufferAsRenderTarget(extraFBOs_[0], { Draw::RPAction::CLEAR, Draw::RPAction::CLEAR });
GLenum attachments[3] = { GL_COLOR_ATTACHMENT0, GL_DEPTH_ATTACHMENT, GL_STENCIL_ATTACHMENT };
glInvalidateFramebuffer(GL_FRAMEBUFFER, 3, attachments);
}*/
} else {
draw_->BindFramebufferAsRenderTarget(nullptr, { Draw::RPAction::CLEAR, Draw::RPAction::CLEAR });
draw_->SetScissorRect(0, 0, pixelWidth_, pixelHeight_);
// We are doing the DrawActiveTexture call directly to the backbuffer here. Hence, we must
// flip V.
if (needBackBufferYSwap_)
@ -1045,9 +1050,6 @@ void FramebufferManagerCommon::CopyDisplayToOutput() {
}
void FramebufferManagerCommon::DecimateFBOs() {
if (useBufferedRendering_) {
draw_->BindBackbufferAsRenderTarget();
}
currentRenderVfb_ = 0;
for (size_t i = 0; i < vfbs_.size(); ++i) {
@ -1138,7 +1140,6 @@ void FramebufferManagerCommon::ResizeFramebufFBO(VirtualFramebuffer *vfb, u16 w,
}
textureCache_->ForgetLastTexture();
draw_->BindBackbufferAsRenderTarget();
if (!useBufferedRendering_) {
if (vfb->fbo) {
@ -1156,16 +1157,17 @@ void FramebufferManagerCommon::ResizeFramebufFBO(VirtualFramebuffer *vfb, u16 w,
if (old.fbo) {
INFO_LOG(FRAMEBUF, "Resizing FBO for %08x : %i x %i x %i", vfb->fb_address, w, h, vfb->format);
if (vfb->fbo) {
draw_->BindFramebufferAsRenderTarget(vfb->fbo);
ClearBuffer();
draw_->BindFramebufferAsRenderTarget(vfb->fbo, { Draw::RPAction::CLEAR, Draw::RPAction::CLEAR });
if (!skipCopy && !g_Config.bDisableSlowFramebufEffects) {
BlitFramebuffer(vfb, 0, 0, &old, 0, 0, std::min(vfb->bufferWidth, vfb->width), std::min(vfb->height, vfb->bufferHeight), 0);
}
}
delete old.fbo;
if (vfb->fbo) {
draw_->BindFramebufferAsRenderTarget(vfb->fbo);
if (needGLESRebinds_) {
draw_->BindFramebufferAsRenderTarget(vfb->fbo, { Draw::RPAction::KEEP, Draw::RPAction::KEEP });
}
} else {
draw_->BindFramebufferAsRenderTarget(vfb->fbo, { Draw::RPAction::CLEAR, Draw::RPAction::CLEAR });
}
if (!vfb->fbo) {
@ -1801,8 +1803,9 @@ Draw::Framebuffer *FramebufferManagerCommon::GetTempFBO(u16 w, u16 h, Draw::FBCo
Draw::Framebuffer *fbo = draw_->CreateFramebuffer({ w, h, 1, 1, false, depth });
if (!fbo)
return fbo;
draw_->BindFramebufferAsRenderTarget(fbo);
ClearBuffer(true);
// TODO: Move binding out of here!
draw_->BindFramebufferAsRenderTarget(fbo, { Draw::RPAction::CLEAR, Draw::RPAction::CLEAR });
const TempFBO info = { fbo, gpuStats.numFlips };
tempFBOs_[key] = info;
return fbo;

View File

@ -267,7 +267,7 @@ public:
virtual void Resized();
Draw::Framebuffer *GetTempFBO(u16 w, u16 h, Draw::FBColorDepth depth = Draw::FBO_8888);
Draw::Framebuffer *GetTempFBO(u16 w, u16 h, Draw::FBColorDepth colorDepth = Draw::FBO_8888);
// Debug features
virtual bool GetFramebuffer(u32 fb_address, int fb_stride, GEBufferFormat format, GPUDebugBuffer &buffer, int maxRes) = 0;
@ -375,6 +375,7 @@ protected:
// Used by post-processing shaders
std::vector<Draw::Framebuffer *> extraFBOs_;
bool needGLESRebinds_ = false;
struct TempFBO {
Draw::Framebuffer *fbo;

View File

@ -218,8 +218,6 @@ void FramebufferManagerD3D11::DisableState() {
}
void FramebufferManagerD3D11::CompilePostShader() {
SetNumExtraFBOs(0);
std::string vsSource;
std::string psSource;
@ -472,9 +470,10 @@ void FramebufferManagerD3D11::BindPostShader(const PostShaderUniforms &uniforms)
void FramebufferManagerD3D11::RebindFramebuffer() {
if (currentRenderVfb_ && currentRenderVfb_->fbo) {
draw_->BindFramebufferAsRenderTarget(currentRenderVfb_->fbo);
draw_->BindFramebufferAsRenderTarget(currentRenderVfb_->fbo, { Draw::RPAction::KEEP, Draw::RPAction::KEEP });
} else {
draw_->BindBackbufferAsRenderTarget();
// Should this even happen?
draw_->BindFramebufferAsRenderTarget(nullptr, { Draw::RPAction::KEEP, Draw::RPAction::KEEP });
}
}
@ -483,8 +482,6 @@ void FramebufferManagerD3D11::ReformatFramebufferFrom(VirtualFramebuffer *vfb, G
return;
}
draw_->BindFramebufferAsRenderTarget(vfb->fbo);
// Technically, we should at this point re-interpret the bytes of the old format to the new.
// That might get tricky, and could cause unnecessary slowness in some games.
// For now, we just clear alpha/stencil from 565, which fixes shadow issues in Kingdom Hearts.
@ -493,8 +490,10 @@ void FramebufferManagerD3D11::ReformatFramebufferFrom(VirtualFramebuffer *vfb, G
// The best way to do this may ultimately be to create a new FBO (combine with any resize?)
// and blit with a shader to that, then replace the FBO on vfb. Stencil would still be complex
// to exactly reproduce in 4444 and 8888 formats.
if (old == GE_FORMAT_565) {
draw_->BindFramebufferAsRenderTarget(vfb->fbo, { Draw::RPAction::CLEAR, Draw::RPAction::KEEP });
// TODO: There's no way this does anything useful :(
context_->OMSetDepthStencilState(stockD3D11.depthDisabledStencilWrite, 0xFF);
context_->OMSetBlendState(stockD3D11.blendStateDisabledWithColorMask[0], nullptr, 0xFFFFFFFF);
context_->RSSetState(stockD3D11.rasterStateNoCull);
@ -571,7 +570,6 @@ void FramebufferManagerD3D11::BindFramebufferAsColorTexture(int stage, VirtualFr
if (renderCopy) {
VirtualFramebuffer copyInfo = *framebuffer;
copyInfo.fbo = renderCopy;
CopyFramebufferForColorTexture(&copyInfo, framebuffer, flags);
RebindFramebuffer();
draw_->BindFramebufferAsTexture(renderCopy, stage, Draw::FB_COLOR_BIT, 0);
@ -653,8 +651,7 @@ bool FramebufferManagerD3D11::CreateDownloadTempBuffer(VirtualFramebuffer *nvfb)
return false;
}
draw_->BindFramebufferAsRenderTarget(nvfb->fbo);
ClearBuffer();
draw_->BindFramebufferAsRenderTarget(nvfb->fbo, { Draw::RPAction::CLEAR, Draw::RPAction::CLEAR });
return true;
}
@ -697,7 +694,7 @@ void FramebufferManagerD3D11::SimpleBlit(
// Unbind the texture first to avoid the D3D11 hazard check (can't set render target to things bound as textures and vice versa, not even temporarily).
draw_->BindTexture(0, nullptr);
draw_->BindFramebufferAsRenderTarget(dest);
draw_->BindFramebufferAsRenderTarget(dest, { Draw::RPAction::KEEP, Draw::RPAction::KEEP });
draw_->BindFramebufferAsTexture(src, 0, Draw::FB_COLOR_BIT, 0);
Bind2DShader();
@ -717,7 +714,9 @@ void FramebufferManagerD3D11::SimpleBlit(
void FramebufferManagerD3D11::BlitFramebuffer(VirtualFramebuffer *dst, int dstX, int dstY, VirtualFramebuffer *src, int srcX, int srcY, int w, int h, int bpp) {
if (!dst->fbo || !src->fbo || !useBufferedRendering_) {
// This can happen if they recently switched from non-buffered.
draw_->BindBackbufferAsRenderTarget();
if (useBufferedRendering_) {
draw_->BindFramebufferAsRenderTarget(nullptr, { Draw::RPAction::KEEP, Draw::RPAction::KEEP });
}
return;
}
@ -812,7 +811,6 @@ void ConvertFromRGBA8888(u8 *dst, u8 *src, u32 dstStride, u32 srcStride, u32 wid
void FramebufferManagerD3D11::PackFramebufferD3D11_(VirtualFramebuffer *vfb, int x, int y, int w, int h) {
if (!vfb->fbo) {
ERROR_LOG_REPORT_ONCE(vfbfbozero, SCEGE, "PackFramebufferD3D11_: vfb->fbo == 0");
draw_->BindBackbufferAsRenderTarget();
return;
}
@ -884,7 +882,6 @@ std::vector<FramebufferInfo> FramebufferManagerD3D11::GetFramebufferList() {
}
void FramebufferManagerD3D11::DestroyAllFBOs() {
draw_->BindBackbufferAsRenderTarget();
currentRenderVfb_ = 0;
displayFramebuf_ = 0;
prevDisplayFramebuf_ = 0;

View File

@ -194,16 +194,13 @@ bool FramebufferManagerD3D11::NotifyStencilUpload(u32 addr, int size, bool skipZ
float v1 = 1.0f;
MakePixelTexture(src, dstBuffer->format, dstBuffer->fb_stride, dstBuffer->bufferWidth, dstBuffer->bufferHeight, u1, v1);
if (dstBuffer->fbo) {
draw_->BindFramebufferAsRenderTarget(dstBuffer->fbo);
draw_->BindFramebufferAsRenderTarget(dstBuffer->fbo, { Draw::RPAction::KEEP, Draw::RPAction::CLEAR });
} else {
// something is wrong...
}
D3D11_VIEWPORT vp{ 0.0f, 0.0f, (float)w, (float)h, 0.0f, 1.0f };
context_->RSSetViewports(1, &vp);
// Zero stencil
draw_->Clear(Draw::FBChannel::FB_STENCIL_BIT, 0, 0, 0);
float fw = dstBuffer->width;
float fh = dstBuffer->height;

View File

@ -434,7 +434,7 @@ void TextureCacheD3D11::ApplyTextureFramebuffer(TexCacheEntry *entry, VirtualFra
context_->PSSetShaderResources(1, 1, &clutTexture);
framebufferManagerD3D11_->BindFramebufferAsColorTexture(0, framebuffer, BINDFBCOLOR_SKIP_COPY);
context_->PSSetSamplers(0, 1, &stockD3D11.samplerPoint2DWrap);
draw_->BindFramebufferAsRenderTarget(depalFBO);
draw_->BindFramebufferAsRenderTarget(depalFBO, { Draw::RPAction::DONT_CARE, Draw::RPAction::DONT_CARE });
shaderApply.Shade();
framebufferManagerD3D11_->RebindFramebuffer();
@ -453,7 +453,7 @@ void TextureCacheD3D11::ApplyTextureFramebuffer(TexCacheEntry *entry, VirtualFra
gstate_c.SetTextureFullAlpha(gstate.getTextureFormat() == GE_TFMT_5650);
gstate_c.SetTextureSimpleAlpha(gstate_c.textureFullAlpha);
framebufferManagerD3D11_->RebindFramebuffer();
framebufferManagerD3D11_->RebindFramebuffer(); // Probably not necessary.
}
SamplerCacheKey samplerKey;
SetFramebufferSamplingParams(framebuffer->bufferWidth, framebuffer->bufferHeight, samplerKey);

View File

@ -324,9 +324,10 @@ static const D3DVERTEXELEMENT9 g_FramebufferVertexElements[] = {
void FramebufferManagerDX9::RebindFramebuffer() {
if (currentRenderVfb_ && currentRenderVfb_->fbo) {
draw_->BindFramebufferAsRenderTarget(currentRenderVfb_->fbo);
draw_->BindFramebufferAsRenderTarget(currentRenderVfb_->fbo, { Draw::RPAction::KEEP, Draw::RPAction::KEEP });
} else {
draw_->BindBackbufferAsRenderTarget();
// Should this even happen?
draw_->BindFramebufferAsRenderTarget(nullptr, { Draw::RPAction::KEEP, Draw::RPAction::KEEP });
}
}
@ -345,7 +346,7 @@ static const D3DVERTEXELEMENT9 g_FramebufferVertexElements[] = {
return;
}
draw_->BindFramebufferAsRenderTarget(vfb->fbo);
draw_->BindFramebufferAsRenderTarget(vfb->fbo, { Draw::RPAction::CLEAR, Draw::RPAction::KEEP });
// Technically, we should at this point re-interpret the bytes of the old format to the new.
// That might get tricky, and could cause unnecessary slowness in some games.
@ -552,8 +553,7 @@ static const D3DVERTEXELEMENT9 g_FramebufferVertexElements[] = {
return false;
}
draw_->BindFramebufferAsRenderTarget(nvfb->fbo);
ClearBuffer();
draw_->BindFramebufferAsRenderTarget(nvfb->fbo, { Draw::RPAction::CLEAR, Draw::RPAction::CLEAR });
return true;
}
@ -564,7 +564,8 @@ static const D3DVERTEXELEMENT9 g_FramebufferVertexElements[] = {
void FramebufferManagerDX9::BlitFramebuffer(VirtualFramebuffer *dst, int dstX, int dstY, VirtualFramebuffer *src, int srcX, int srcY, int w, int h, int bpp) {
if (!dst->fbo || !src->fbo || !useBufferedRendering_) {
// This can happen if we recently switched from non-buffered.
draw_->BindBackbufferAsRenderTarget();
if (useBufferedRendering_)
draw_->BindFramebufferAsRenderTarget(nullptr, { Draw::RPAction::KEEP, Draw::RPAction::KEEP });
return;
}
@ -664,7 +665,6 @@ static const D3DVERTEXELEMENT9 g_FramebufferVertexElements[] = {
void FramebufferManagerDX9::PackFramebufferDirectx9_(VirtualFramebuffer *vfb, int x, int y, int w, int h) {
if (!vfb->fbo) {
ERROR_LOG_REPORT_ONCE(vfbfbozero, SCEGE, "PackFramebufferDirectx9_: vfb->fbo == 0");
draw_->BindBackbufferAsRenderTarget();
return;
}
@ -792,7 +792,6 @@ static const D3DVERTEXELEMENT9 g_FramebufferVertexElements[] = {
}
void FramebufferManagerDX9::DestroyAllFBOs() {
draw_->BindBackbufferAsRenderTarget();
currentRenderVfb_ = 0;
displayFramebuf_ = 0;
prevDisplayFramebuf_ = 0;
@ -882,8 +881,6 @@ static const D3DVERTEXELEMENT9 g_FramebufferVertexElements[] = {
}
bool FramebufferManagerDX9::GetOutputFramebuffer(GPUDebugBuffer &buffer) {
draw_->BindBackbufferAsRenderTarget();
LPDIRECT3DSURFACE9 renderTarget = nullptr;
HRESULT hr = device_->GetRenderTarget(0, &renderTarget);
bool success = false;
@ -899,7 +896,6 @@ static const D3DVERTEXELEMENT9 g_FramebufferVertexElements[] = {
}
renderTarget->Release();
}
return success;
}

View File

@ -221,7 +221,7 @@ bool FramebufferManagerDX9::NotifyStencilUpload(u32 addr, int size, bool skipZer
u16 h = dstBuffer->renderHeight;
if (dstBuffer->fbo) {
draw_->BindFramebufferAsRenderTarget(dstBuffer->fbo);
draw_->BindFramebufferAsRenderTarget(dstBuffer->fbo, { Draw::RPAction::KEEP, Draw::RPAction::CLEAR });
}
D3DVIEWPORT9 vp{ 0, 0, w, h, 0.0f, 1.0f };
device_->SetViewport(&vp);

View File

@ -421,7 +421,7 @@ void TextureCacheDX9::ApplyTextureFramebuffer(TexCacheEntry *entry, VirtualFrame
LPDIRECT3DTEXTURE9 clutTexture = depalShaderCache_->GetClutTexture(clutFormat, clutHash_, clutBuf_);
Draw::Framebuffer *depalFBO = framebufferManagerDX9_->GetTempFBO(framebuffer->renderWidth, framebuffer->renderHeight, Draw::FBO_8888);
draw_->BindFramebufferAsRenderTarget(depalFBO);
draw_->BindFramebufferAsRenderTarget(depalFBO, { Draw::RPAction::DONT_CARE, Draw::RPAction::DONT_CARE });
shaderManager_->DirtyLastShader();
float xoff = -0.5f / framebuffer->renderWidth;

View File

@ -645,6 +645,8 @@ void DrawEngineGLES::DoFlush() {
PROFILE_THIS_SCOPE("flush");
CHECK_GL_ERROR_IF_DEBUG();
gpuStats.numFlushes++;
gpuStats.numTrackedVertexArrays = (int)vai_.size();

View File

@ -234,6 +234,7 @@ FramebufferManagerGLES::FramebufferManagerGLES(Draw::DrawContext *draw) :
currentPBO_(0)
{
needBackBufferYSwap_ = true;
needGLESRebinds_ = true;
}
void FramebufferManagerGLES::Init() {
@ -450,9 +451,10 @@ void FramebufferManagerGLES::DrawActiveTexture(float x, float y, float w, float
void FramebufferManagerGLES::RebindFramebuffer() {
if (currentRenderVfb_ && currentRenderVfb_->fbo) {
draw_->BindFramebufferAsRenderTarget(currentRenderVfb_->fbo);
draw_->BindFramebufferAsRenderTarget(currentRenderVfb_->fbo, { Draw::RPAction::KEEP, Draw::RPAction::KEEP });
} else {
draw_->BindBackbufferAsRenderTarget();
// Should this even happen?
draw_->BindFramebufferAsRenderTarget(nullptr, { Draw::RPAction::KEEP, Draw::RPAction::KEEP });
}
if (g_Config.iRenderingMode == FB_NON_BUFFERED_MODE)
glstate.viewport.restore();
@ -475,8 +477,6 @@ void FramebufferManagerGLES::ReformatFramebufferFrom(VirtualFramebuffer *vfb, GE
return;
}
draw_->BindFramebufferAsRenderTarget(vfb->fbo);
// Technically, we should at this point re-interpret the bytes of the old format to the new.
// That might get tricky, and could cause unnecessary slowness in some games.
// For now, we just clear alpha/stencil from 565, which fixes shadow issues in Kingdom Hearts.
@ -487,14 +487,9 @@ void FramebufferManagerGLES::ReformatFramebufferFrom(VirtualFramebuffer *vfb, GE
// to exactly reproduce in 4444 and 8888 formats.
if (old == GE_FORMAT_565) {
glstate.scissorTest.disable();
glstate.depthWrite.set(GL_FALSE);
glstate.colorMask.set(false, false, false, true);
glstate.stencilFunc.set(GL_ALWAYS, 0, 0);
glstate.stencilMask.set(0xFF);
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
glClearStencil(0);
glClear(GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
draw_->BindFramebufferAsRenderTarget(vfb->fbo, { Draw::RPAction::CLEAR, Draw::RPAction::CLEAR });
} else {
draw_->BindFramebufferAsRenderTarget(vfb->fbo, { Draw::RPAction::KEEP, Draw::RPAction::KEEP });
}
RebindFramebuffer();
@ -655,10 +650,6 @@ bool FramebufferManagerGLES::CreateDownloadTempBuffer(VirtualFramebuffer *nvfb)
ERROR_LOG(FRAMEBUF, "Error creating GL FBO! %i x %i", nvfb->renderWidth, nvfb->renderHeight);
return false;
}
draw_->BindFramebufferAsRenderTarget(nvfb->fbo);
ClearBuffer();
glDisable(GL_DITHER); // Weird place to do this
return true;
}
@ -667,11 +658,11 @@ void FramebufferManagerGLES::UpdateDownloadTempBuffer(VirtualFramebuffer *nvfb)
// Discard the previous contents of this buffer where possible.
if (gl_extensions.GLES3 && glInvalidateFramebuffer != nullptr) {
draw_->BindFramebufferAsRenderTarget(nvfb->fbo);
draw_->BindFramebufferAsRenderTarget(nvfb->fbo, { Draw::RPAction::DONT_CARE, Draw::RPAction::DONT_CARE });
GLenum attachments[3] = { GL_COLOR_ATTACHMENT0, GL_STENCIL_ATTACHMENT, GL_DEPTH_ATTACHMENT };
glInvalidateFramebuffer(GL_FRAMEBUFFER, 3, attachments);
} else if (gl_extensions.IsGLES) {
draw_->BindFramebufferAsRenderTarget(nvfb->fbo);
draw_->BindFramebufferAsRenderTarget(nvfb->fbo, { Draw::RPAction::DONT_CARE, Draw::RPAction::DONT_CARE });
ClearBuffer();
}
CHECK_GL_ERROR_IF_DEBUG();
@ -680,7 +671,8 @@ void FramebufferManagerGLES::UpdateDownloadTempBuffer(VirtualFramebuffer *nvfb)
void FramebufferManagerGLES::BlitFramebuffer(VirtualFramebuffer *dst, int dstX, int dstY, VirtualFramebuffer *src, int srcX, int srcY, int w, int h, int bpp) {
if (!dst->fbo || !src->fbo || !useBufferedRendering_) {
// This can happen if they recently switched from non-buffered.
draw_->BindBackbufferAsRenderTarget();
if (useBufferedRendering_)
draw_->BindFramebufferAsRenderTarget(nullptr, { Draw::RPAction::KEEP, Draw::RPAction::KEEP });
return;
}
@ -735,7 +727,7 @@ void FramebufferManagerGLES::BlitFramebuffer(VirtualFramebuffer *dst, int dstX,
if (useBlit) {
draw_->BlitFramebuffer(src->fbo, srcX1, srcY1, srcX2, srcY2, dst->fbo, dstX1, dstY1, dstX2, dstY2, Draw::FB_COLOR_BIT, Draw::FB_BLIT_NEAREST);
} else {
draw_->BindFramebufferAsRenderTarget(dst->fbo);
draw_->BindFramebufferAsRenderTarget(dst->fbo, { Draw::RPAction::KEEP, Draw::RPAction::KEEP });
draw_->BindFramebufferAsTexture(src->fbo, 0, Draw::FB_COLOR_BIT, 0);
// Make sure our 2D drawing program is ready. Compiles only if not already compiled.
@ -1093,7 +1085,7 @@ void FramebufferManagerGLES::PackFramebufferSync_(VirtualFramebuffer *vfb, int x
if (gl_extensions.GLES3 && glInvalidateFramebuffer != nullptr) {
#ifdef USING_GLES2
// GLES3 doesn't support using GL_READ_FRAMEBUFFER here.
draw_->BindFramebufferAsRenderTarget(vfb->fbo);
draw_->BindFramebufferAsRenderTarget(vfb->fbo, { Draw::RPAction::DONT_CARE, Draw::RPAction::DONT_CARE });
const GLenum target = GL_FRAMEBUFFER;
#else
const GLenum target = GL_READ_FRAMEBUFFER;
@ -1161,11 +1153,11 @@ void FramebufferManagerGLES::EndFrame() {
continue;
}
draw_->BindFramebufferAsRenderTarget(temp.second.fbo);
draw_->BindFramebufferAsRenderTarget(temp.second.fbo, { Draw::RPAction::DONT_CARE, Draw::RPAction::DONT_CARE });
GLenum attachments[3] = { GL_COLOR_ATTACHMENT0, GL_STENCIL_ATTACHMENT, GL_DEPTH_ATTACHMENT };
glInvalidateFramebuffer(GL_FRAMEBUFFER, 3, attachments);
}
draw_->BindBackbufferAsRenderTarget();
draw_->BindFramebufferAsRenderTarget(nullptr, { Draw::RPAction::KEEP , Draw::RPAction::KEEP });
}
CHECK_GL_ERROR_IF_DEBUG();
}
@ -1196,7 +1188,6 @@ std::vector<FramebufferInfo> FramebufferManagerGLES::GetFramebufferList() {
void FramebufferManagerGLES::DestroyAllFBOs() {
CHECK_GL_ERROR_IF_DEBUG();
draw_->BindBackbufferAsRenderTarget();
currentRenderVfb_ = 0;
displayFramebuf_ = 0;
prevDisplayFramebuf_ = 0;
@ -1220,7 +1211,6 @@ void FramebufferManagerGLES::DestroyAllFBOs() {
}
tempFBOs_.clear();
draw_->BindBackbufferAsRenderTarget();
DisableState();
CHECK_GL_ERROR_IF_DEBUG();
}

View File

@ -660,7 +660,7 @@ void GPU_GLES::Execute_Prim(u32 op, u32 diff) {
return;
}
// This also makes skipping drawing very effective.
// This also makes skipping drawing very effective. This function can change the framebuffer.
framebufferManagerGL_->SetRenderFrameBuffer(gstate_c.IsDirty(DIRTY_FRAMEBUF), gstate_c.skipDrawReason);
if (gstate_c.skipDrawReason & (SKIPDRAW_SKIPFRAME | SKIPDRAW_NON_DISPLAYED_FB)) {
drawEngine_.SetupVertexDecoder(gstate.vertType);

View File

@ -183,9 +183,9 @@ bool FramebufferManagerGLES::NotifyStencilUpload(u32 addr, int size, bool skipZe
Draw::Framebuffer *blitFBO = nullptr;
if (useBlit) {
blitFBO = GetTempFBO(w, h, Draw::FBO_8888);
draw_->BindFramebufferAsRenderTarget(blitFBO);
draw_->BindFramebufferAsRenderTarget(blitFBO, { Draw::RPAction::DONT_CARE, Draw::RPAction::DONT_CARE });
} else if (dstBuffer->fbo) {
draw_->BindFramebufferAsRenderTarget(dstBuffer->fbo);
draw_->BindFramebufferAsRenderTarget(dstBuffer->fbo, { Draw::RPAction::KEEP, Draw::RPAction::CLEAR });
}
glViewport(0, 0, w, h);

View File

@ -483,7 +483,7 @@ void TextureCacheGLES::ApplyTextureFramebuffer(TexCacheEntry *entry, VirtualFram
const GEPaletteFormat clutFormat = gstate.getClutPaletteFormat();
GLuint clutTexture = depalShaderCache_->GetClutTexture(clutFormat, clutHash_, clutBuf_);
Draw::Framebuffer *depalFBO = framebufferManagerGL_->GetTempFBO(framebuffer->renderWidth, framebuffer->renderHeight, Draw::FBO_8888);
draw_->BindFramebufferAsRenderTarget(depalFBO);
draw_->BindFramebufferAsRenderTarget(depalFBO, { Draw::RPAction::DONT_CARE, Draw::RPAction::DONT_CARE });
shaderManager_->DirtyLastShader();
TextureShaderApplier shaderApply(depal, framebuffer->bufferWidth, framebuffer->bufferHeight, framebuffer->renderWidth, framebuffer->renderHeight);

View File

@ -21,7 +21,8 @@
<PropertyGroup Label="Globals">
<ProjectGuid>{457F45D2-556F-47BC-A31D-AFF0D15BEAED}</ProjectGuid>
<RootNamespace>GPU</RootNamespace>
<WindowsTargetPlatformVersion></WindowsTargetPlatformVersion>
<WindowsTargetPlatformVersion>
</WindowsTargetPlatformVersion>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">

View File

@ -408,9 +408,6 @@
<ClCompile Include="Vulkan\FragmentShaderGeneratorVulkan.cpp">
<Filter>Vulkan</Filter>
</ClCompile>
<ClCompile Include="Vulkan\FramebufferVulkan.cpp">
<Filter>Vulkan</Filter>
</ClCompile>
<ClCompile Include="Vulkan\GPU_Vulkan.cpp">
<Filter>Vulkan</Filter>
</ClCompile>
@ -510,5 +507,8 @@
<ClCompile Include="Common\ShaderCommon.cpp">
<Filter>Common</Filter>
</ClCompile>
<ClCompile Include="Vulkan\FramebufferVulkan.cpp">
<Filter>Vulkan</Filter>
</ClCompile>
</ItemGroup>
</Project>

View File

@ -205,7 +205,7 @@ void SoftGPU::CopyToCurrentFboFromDisplayRam(int srcwidth, int srcheight) {
u1 = 1.0f;
}
if (!hasImage) {
draw_->Clear(Draw::FB_COLOR_BIT, 0, 0, 0);
draw_->BindFramebufferAsRenderTarget(nullptr, { Draw::RPAction::CLEAR, Draw::RPAction::DONT_CARE });
return;
}
@ -236,8 +236,7 @@ void SoftGPU::CopyToCurrentFboFromDisplayRam(int srcwidth, int srcheight) {
if (GetGPUBackend() == GPUBackend::VULKAN) {
std::swap(v0, v1);
}
draw_->BindBackbufferAsRenderTarget();
draw_->Clear(Draw::FB_COLOR_BIT, 0, 0, 0);
draw_->BindFramebufferAsRenderTarget(nullptr, { Draw::RPAction::CLEAR, Draw::RPAction::DONT_CARE });
Draw::SamplerState *sampler;
if (g_Config.iBufFilter == SCALE_NEAREST) {

View File

@ -64,10 +64,10 @@ enum {
TRANSFORMED_VERTEX_BUFFER_SIZE = VERTEX_BUFFER_MAX * sizeof(TransformedVertex)
};
DrawEngineVulkan::DrawEngineVulkan(VulkanContext *vulkan)
DrawEngineVulkan::DrawEngineVulkan(VulkanContext *vulkan, Draw::DrawContext *draw)
: vulkan_(vulkan),
draw_(draw),
prevPrim_(GE_PRIM_INVALID),
lastVTypeID_(-1),
numDrawCalls(0),
vertexCountInDrawCalls(0),
curFrame_(0),
@ -149,7 +149,7 @@ void DrawEngineVulkan::InitDeviceObjects() {
VkDescriptorPoolSize dpTypes[2];
dpTypes[0].descriptorCount = 2048;
dpTypes[0].type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC;
dpTypes[1].descriptorCount = 2048;
dpTypes[1].descriptorCount = 4096;
dpTypes[1].type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
VkDescriptorPoolCreateInfo dp = { VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO };
@ -168,7 +168,6 @@ void DrawEngineVulkan::InitDeviceObjects() {
if (res == VK_SUCCESS) {
break;
}
// Let's try to reduce the counts.
assert(res == VK_ERROR_OUT_OF_HOST_MEMORY || res == VK_ERROR_OUT_OF_DEVICE_MEMORY);
dpTypes[0].descriptorCount /= 2;
@ -328,15 +327,17 @@ inline void DrawEngineVulkan::SetupVertexDecoderInternal(u32 vertType) {
const u32 vertTypeID = (vertType & 0xFFFFFF) | (gstate.getUVGenMode() << 24);
// If vtype has changed, setup the vertex decoder.
if (vertTypeID != lastVTypeID_) {
if (vertTypeID != lastVType_) {
dec_ = GetVertexDecoder(vertTypeID);
lastVTypeID_ = vertTypeID;
lastVType_ = vertTypeID;
}
if (!dec_)
Crash();
}
void DrawEngineVulkan::SubmitPrim(void *verts, void *inds, GEPrimitiveType prim, int vertexCount, u32 vertType, int *bytesRead) {
if (!indexGen.PrimCompatible(prevPrim_, prim) || numDrawCalls >= MAX_DEFERRED_DRAW_CALLS || vertexCountInDrawCalls + vertexCount > VERTEX_BUFFER_MAX)
Flush(cmd_);
Flush();
// TODO: Is this the right thing to do?
if (prim == GE_PRIM_KEEP_PREVIOUS) {
@ -375,7 +376,7 @@ void DrawEngineVulkan::SubmitPrim(void *verts, void *inds, GEPrimitiveType prim,
// Rendertarget == texture?
if (!g_Config.bDisableSlowFramebufEffects) {
gstate_c.Dirty(DIRTY_TEXTURE_PARAMS);
Flush(cmd_);
Flush();
}
}
}
@ -643,28 +644,15 @@ void DrawEngineVulkan::DirtyAllUBOs() {
gstate_c.Dirty(DIRTY_TEXTURE_IMAGE);
}
//void DrawEngineVulkan::ApplyDrawStateLate() {
/*
// At this point, we know if the vertices are full alpha or not.
// TODO: Set the nearest/linear here (since we correctly know if alpha/color tests are needed)?
if (!gstate.isModeClear()) {
// TODO: Test texture?
if (fboTexNeedBind_) {
// Note that this is positions, not UVs, that we need the copy from.
framebufferManager_->BindFramebufferAsColorTexture(1, framebufferManager_->GetCurrentRenderVFB(), BINDFBCOLOR_MAY_COPY);
// If we are rendering at a higher resolution, linear is probably best for the dest color.
fboTexBound_ = true;
fboTexNeedBind_ = false;
}
}
*/
//}
// The inline wrapper in the header checks for numDrawCalls == 0d
void DrawEngineVulkan::DoFlush(VkCommandBuffer cmd) {
void DrawEngineVulkan::DoFlush() {
gpuStats.numFlushes++;
VkCommandBuffer cmd = (VkCommandBuffer)draw_->GetNativeObject(Draw::NativeObject::RENDERPASS_COMMANDBUFFER);
VkRenderPass rp = (VkRenderPass)draw_->GetNativeObject(Draw::NativeObject::CURRENT_RENDERPASS);
if (!rp)
Crash();
FrameData *frame = &frame_[curFrame_ & 1];
bool textureNeedsApply = false;
@ -689,6 +677,8 @@ void DrawEngineVulkan::DoFlush(VkCommandBuffer cmd) {
uint32_t ibOffset = 0;
uint32_t vbOffset = 0;
VkRenderPass renderPass = (VkRenderPass)draw_->GetNativeObject(Draw::NativeObject::COMPATIBLE_RENDERPASS);
if (useHWTransform) {
// We don't detect clears in this path, so here we can switch framebuffers if necessary.
@ -706,7 +696,7 @@ void DrawEngineVulkan::DoFlush(VkCommandBuffer cmd) {
}
prim = indexGen.Prim();
bool hasColor = (lastVTypeID_ & GE_VTYPE_COL_MASK) != GE_VTYPE_COL_NONE;
bool hasColor = (lastVType_ & GE_VTYPE_COL_MASK) != GE_VTYPE_COL_NONE;
if (gstate.isModeThrough()) {
gstate_c.vertexFullAlpha = gstate_c.vertexFullAlpha && (hasColor || gstate.getMaterialAmbientA() == 255);
} else {
@ -724,28 +714,28 @@ void DrawEngineVulkan::DoFlush(VkCommandBuffer cmd) {
ConvertStateToVulkanKey(*framebufferManager_, shaderManager_, prim, pipelineKey_, dynState_);
// TODO: Dirty-flag these.
vkCmdSetScissor(cmd_, 0, 1, &dynState_.scissor);
vkCmdSetViewport(cmd_, 0, 1, &dynState_.viewport);
vkCmdSetScissor(cmd, 0, 1, &dynState_.scissor);
vkCmdSetViewport(cmd, 0, 1, &dynState_.viewport);
if (dynState_.useStencil) {
vkCmdSetStencilWriteMask(cmd_, VK_STENCIL_FRONT_AND_BACK, dynState_.stencilWriteMask);
vkCmdSetStencilCompareMask(cmd_, VK_STENCIL_FRONT_AND_BACK, dynState_.stencilCompareMask);
vkCmdSetStencilReference(cmd_, VK_STENCIL_FRONT_AND_BACK, dynState_.stencilRef);
vkCmdSetStencilWriteMask(cmd, VK_STENCIL_FRONT_AND_BACK, dynState_.stencilWriteMask);
vkCmdSetStencilCompareMask(cmd, VK_STENCIL_FRONT_AND_BACK, dynState_.stencilCompareMask);
vkCmdSetStencilReference(cmd, VK_STENCIL_FRONT_AND_BACK, dynState_.stencilRef);
}
if (dynState_.useBlendColor) {
float bc[4];
Uint8x4ToFloat4(bc, dynState_.blendColor);
vkCmdSetBlendConstants(cmd_, bc);
vkCmdSetBlendConstants(cmd, bc);
}
dirtyUniforms_ |= shaderManager_->UpdateUniforms();
shaderManager_->GetShaders(prim, lastVTypeID_, &vshader, &fshader, useHWTransform);
VulkanPipeline *pipeline = pipelineManager_->GetOrCreatePipeline(pipelineLayout_, pipelineKey_, dec_, vshader, fshader, true);
shaderManager_->GetShaders(prim, lastVType_, &vshader, &fshader, useHWTransform);
VulkanPipeline *pipeline = pipelineManager_->GetOrCreatePipeline(pipelineLayout_, renderPass, pipelineKey_, dec_, vshader, fshader, true);
if (!pipeline) {
// Already logged, let's bail out.
return;
}
vkCmdBindPipeline(cmd_, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline->pipeline); // TODO: Avoid if same as last draw.
vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline->pipeline); // TODO: Avoid if same as last draw.
UpdateUBOs(frame);
@ -754,7 +744,7 @@ void DrawEngineVulkan::DoFlush(VkCommandBuffer cmd) {
const uint32_t dynamicUBOOffsets[3] = {
baseUBOOffset, lightUBOOffset, boneUBOOffset,
};
vkCmdBindDescriptorSets(cmd_, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout_, 0, 1, &ds, 3, dynamicUBOOffsets);
vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout_, 0, 1, &ds, 3, dynamicUBOOffsets);
int stride = dec_->GetDecVtxFmt().stride;
@ -763,18 +753,18 @@ void DrawEngineVulkan::DoFlush(VkCommandBuffer cmd) {
VkBuffer ibuf;
ibOffset = (uint32_t)frame->pushIndex->Push(decIndex, 2 * indexGen.VertexCount(), &ibuf);
// TODO: Avoid rebinding vertex/index buffers if the vertex size stays the same by using the offset arguments
vkCmdBindVertexBuffers(cmd_, 0, 1, &vbuf, offsets);
vkCmdBindIndexBuffer(cmd_, ibuf, ibOffset, VK_INDEX_TYPE_UINT16);
vkCmdBindVertexBuffers(cmd, 0, 1, &vbuf, offsets);
vkCmdBindIndexBuffer(cmd, ibuf, ibOffset, VK_INDEX_TYPE_UINT16);
int numInstances = (gstate_c.bezier || gstate_c.spline) ? numPatches : 1;
vkCmdDrawIndexed(cmd_, vertexCount, numInstances, 0, 0, 0);
vkCmdDrawIndexed(cmd, vertexCount, numInstances, 0, 0, 0);
} else {
vkCmdBindVertexBuffers(cmd_, 0, 1, &vbuf, offsets);
vkCmdDraw(cmd_, vertexCount, 1, 0, 0);
vkCmdBindVertexBuffers(cmd, 0, 1, &vbuf, offsets);
vkCmdDraw(cmd, vertexCount, 1, 0, 0);
}
} else {
// Decode to "decoded"
DecodeVerts(nullptr, nullptr, nullptr);
bool hasColor = (lastVTypeID_ & GE_VTYPE_COL_MASK) != GE_VTYPE_COL_NONE;
bool hasColor = (lastVType_ & GE_VTYPE_COL_MASK) != GE_VTYPE_COL_NONE;
if (gstate.isModeThrough()) {
gstate_c.vertexFullAlpha = gstate_c.vertexFullAlpha && (hasColor || gstate.getMaterialAmbientA() == 255);
} else {
@ -826,32 +816,31 @@ void DrawEngineVulkan::DoFlush(VkCommandBuffer cmd) {
VulkanDynamicState dynState;
ConvertStateToVulkanKey(*framebufferManager_, shaderManager_, prim, pipelineKey, dynState);
// TODO: Dirty-flag these.
vkCmdSetScissor(cmd_, 0, 1, &dynState.scissor);
vkCmdSetViewport(cmd_, 0, 1, &dynState.viewport);
vkCmdSetScissor(cmd, 0, 1, &dynState.scissor);
vkCmdSetViewport(cmd, 0, 1, &dynState.viewport);
if (dynState.useStencil) {
vkCmdSetStencilWriteMask(cmd_, VK_STENCIL_FRONT_AND_BACK, dynState.stencilWriteMask);
vkCmdSetStencilCompareMask(cmd_, VK_STENCIL_FRONT_AND_BACK, dynState.stencilCompareMask);
vkCmdSetStencilWriteMask(cmd, VK_STENCIL_FRONT_AND_BACK, dynState.stencilWriteMask);
vkCmdSetStencilCompareMask(cmd, VK_STENCIL_FRONT_AND_BACK, dynState.stencilCompareMask);
}
if (result.setStencil) {
vkCmdSetStencilReference(cmd_, VK_STENCIL_FRONT_AND_BACK, result.stencilValue);
vkCmdSetStencilReference(cmd, VK_STENCIL_FRONT_AND_BACK, result.stencilValue);
} else if (dynState.useStencil) {
vkCmdSetStencilReference(cmd_, VK_STENCIL_FRONT_AND_BACK, dynState.stencilRef);
vkCmdSetStencilReference(cmd, VK_STENCIL_FRONT_AND_BACK, dynState.stencilRef);
}
if (dynState.useBlendColor) {
float bc[4];
Uint8x4ToFloat4(bc, dynState.blendColor);
vkCmdSetBlendConstants(cmd_, bc);
vkCmdSetBlendConstants(cmd, bc);
}
dirtyUniforms_ |= shaderManager_->UpdateUniforms();
shaderManager_->GetShaders(prim, lastVTypeID_, &vshader, &fshader, useHWTransform);
VulkanPipeline *pipeline = pipelineManager_->GetOrCreatePipeline(pipelineLayout_, pipelineKey, dec_, vshader, fshader, false);
shaderManager_->GetShaders(prim, lastVType_, &vshader, &fshader, useHWTransform);
VulkanPipeline *pipeline = pipelineManager_->GetOrCreatePipeline(pipelineLayout_, renderPass, pipelineKey, dec_, vshader, fshader, false);
if (!pipeline) {
// Already logged, let's bail out.
return;
}
vkCmdBindPipeline(cmd_, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline->pipeline); // TODO: Avoid if same as last draw.
vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline->pipeline); // TODO: Avoid if same as last draw.
// Even if the first draw is through-mode, make sure we at least have one copy of these uniforms buffered
UpdateUBOs(frame);
@ -860,7 +849,7 @@ void DrawEngineVulkan::DoFlush(VkCommandBuffer cmd) {
const uint32_t dynamicUBOOffsets[3] = {
baseUBOOffset, lightUBOOffset, boneUBOOffset,
};
vkCmdBindDescriptorSets(cmd_, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout_, 0, 1, &ds, 3, dynamicUBOOffsets);
vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout_, 0, 1, &ds, 3, dynamicUBOOffsets);
if (drawIndexed) {
VkBuffer vbuf, ibuf;
@ -868,21 +857,22 @@ void DrawEngineVulkan::DoFlush(VkCommandBuffer cmd) {
ibOffset = (uint32_t)frame->pushIndex->Push(inds, sizeof(short) * numTrans, &ibuf);
VkDeviceSize offsets[1] = { vbOffset };
// TODO: Avoid rebinding if the vertex size stays the same by using the offset arguments
vkCmdBindVertexBuffers(cmd_, 0, 1, &vbuf, offsets);
vkCmdBindIndexBuffer(cmd_, ibuf, ibOffset, VK_INDEX_TYPE_UINT16);
vkCmdDrawIndexed(cmd_, numTrans, 1, 0, 0, 0);
vkCmdBindVertexBuffers(cmd, 0, 1, &vbuf, offsets);
vkCmdBindIndexBuffer(cmd, ibuf, ibOffset, VK_INDEX_TYPE_UINT16);
vkCmdDrawIndexed(cmd, numTrans, 1, 0, 0, 0);
} else {
VkBuffer vbuf;
vbOffset = (uint32_t)frame->pushVertex->Push(drawBuffer, numTrans * sizeof(TransformedVertex), &vbuf);
VkDeviceSize offsets[1] = { vbOffset };
// TODO: Avoid rebinding if the vertex size stays the same by using the offset arguments
vkCmdBindVertexBuffers(cmd_, 0, 1, &vbuf, offsets);
vkCmdDraw(cmd_, numTrans, 1, 0, 0);
vkCmdBindVertexBuffers(cmd, 0, 1, &vbuf, offsets);
vkCmdDraw(cmd, numTrans, 1, 0, 0);
}
} else if (result.action == SW_CLEAR) {
// Note: we won't get here if the clear is alpha but not color, or color but not alpha.
// We let the framebuffer manager handle the clear. It can use renderpasses to optimize on tilers.
// If non-buffered though, it'll just do a plain clear.
framebufferManager_->NotifyClear(gstate.isClearModeColorMask(), gstate.isClearModeAlphaMask(), gstate.isClearModeDepthMask(), result.color, result.depth);
int scissorX1 = gstate.getScissorX1();

View File

@ -70,7 +70,7 @@ struct DrawEngineVulkanStats {
// Handles transform, lighting and drawing.
class DrawEngineVulkan : public DrawEngineCommon {
public:
DrawEngineVulkan(VulkanContext *vulkan);
DrawEngineVulkan(VulkanContext *vulkan, Draw::DrawContext *draw);
virtual ~DrawEngineVulkan();
void SubmitPrim(void *verts, void *inds, GEPrimitiveType prim, int vertexCount, u32 vertType, int *bytesRead);
@ -95,23 +95,19 @@ public:
void SetupVertexDecoderInternal(u32 vertType);
// So that this can be inlined
void Flush(VkCommandBuffer cmd) {
void Flush() {
if (!numDrawCalls)
return;
DoFlush(cmd);
DoFlush();
}
bool IsCodePtrVertexDecoder(const u8 *ptr) const;
void DispatchFlush() override { Flush(cmd_); }
void DispatchFlush() override { Flush(); }
void DispatchSubmitPrim(void *verts, void *inds, GEPrimitiveType prim, int vertexCount, u32 vertType, int *bytesRead) override {
SubmitPrim(verts, inds, prim, vertexCount, vertType, bytesRead);
}
void SetCmdBuffer(VkCommandBuffer cmd) {
cmd_ = cmd;
}
VkPipelineLayout GetPipelineLayout() const {
return pipelineLayout_;
}
@ -131,7 +127,7 @@ public:
private:
struct FrameData;
void ApplyDrawStateLate();
void ConvertStateToVulkanKey(FramebufferManagerVulkan &fbManager, ShaderManagerVulkan *shaderManager, int prim, VulkanPipelineRasterStateKey &key, VulkanDynamicState &dynState);
void InitDeviceObjects();
@ -140,12 +136,13 @@ private:
void DecodeVerts(VulkanPushBuffer *push, uint32_t *bindOffset, VkBuffer *vkbuf);
void DecodeVertsStep(u8 *dest, int &i, int &decodedVerts);
void DoFlush(VkCommandBuffer cmd);
void DoFlush();
void UpdateUBOs(FrameData *frame);
VkDescriptorSet GetDescriptorSet(VkImageView imageView, VkSampler sampler, VkBuffer base, VkBuffer light, VkBuffer bone);
VulkanContext *vulkan_;
Draw::DrawContext *draw_;
// We use a single descriptor set layout for all PSP draws.
VkDescriptorSetLayout descriptorSetLayout_;
@ -196,20 +193,12 @@ private:
u16 indexUpperBound;
};
// This is always set to the current main command buffer of the VulkanContext.
// In the future, we may support flushing mid-frame and more fine grained command buffer usage,
// but for now, let's just submit a whole frame at a time. This is not compatible with some games
// that do mid frame read-backs.
VkCommandBuffer cmd_;
// Vertex collector state
IndexGenerator indexGen;
GEPrimitiveType prevPrim_;
u32 lastVTypeID_;
TransformedVertex *transformed;
TransformedVertex *transformedExpanded;
TransformedVertex *transformed = nullptr;
TransformedVertex *transformedExpanded = nullptr;
// Other
ShaderManagerVulkan *shaderManager_ = nullptr;

View File

@ -52,8 +52,6 @@
#include "GPU/Vulkan/ShaderManagerVulkan.h"
#include "GPU/Vulkan/VulkanUtil.h"
const VkFormat framebufFormat = VK_FORMAT_B8G8R8A8_UNORM;
static const char tex_fs[] = R"(#version 400
#extension GL_ARB_separate_shader_objects : enable
#extension GL_ARB_shading_language_420pack : enable
@ -92,7 +90,6 @@ FramebufferManagerVulkan::FramebufferManagerVulkan(Draw::DrawContext *draw, Vulk
pixelBufObj_(nullptr),
currentPBO_(0),
curFrame_(0),
pipelineBasicTex_(VK_NULL_HANDLE),
pipelinePostShader_(VK_NULL_HANDLE),
vulkan2D_(vulkan) {
@ -117,74 +114,8 @@ void FramebufferManagerVulkan::SetShaderManager(ShaderManagerVulkan *sm) {
}
void FramebufferManagerVulkan::InitDeviceObjects() {
// Create a bunch of render pass objects, for normal rendering with a depth buffer,
// with and without pre-clearing of both depth/stencil and color, so 4 combos.
VkAttachmentDescription attachments[2] = {};
attachments[0].format = framebufFormat;
attachments[0].samples = VK_SAMPLE_COUNT_1_BIT;
attachments[0].loadOp = VK_ATTACHMENT_LOAD_OP_LOAD;
attachments[0].storeOp = VK_ATTACHMENT_STORE_OP_STORE;
attachments[0].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
attachments[0].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
attachments[0].initialLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
attachments[0].finalLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
attachments[0].flags = 0;
attachments[1].format = vulkan_->GetDeviceInfo().preferredDepthStencilFormat;
attachments[1].samples = VK_SAMPLE_COUNT_1_BIT;
attachments[1].loadOp = VK_ATTACHMENT_LOAD_OP_LOAD;
attachments[1].storeOp = VK_ATTACHMENT_STORE_OP_STORE;
attachments[1].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_LOAD;
attachments[1].stencilStoreOp = VK_ATTACHMENT_STORE_OP_STORE;
attachments[1].initialLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
attachments[1].finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
attachments[1].flags = 0;
VkAttachmentReference color_reference = {};
color_reference.attachment = 0;
color_reference.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
VkAttachmentReference depth_reference = {};
depth_reference.attachment = 1;
depth_reference.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
VkSubpassDescription subpass = {};
subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
subpass.flags = 0;
subpass.inputAttachmentCount = 0;
subpass.pInputAttachments = NULL;
subpass.colorAttachmentCount = 1;
subpass.pColorAttachments = &color_reference;
subpass.pResolveAttachments = NULL;
subpass.pDepthStencilAttachment = &depth_reference;
subpass.preserveAttachmentCount = 0;
subpass.pPreserveAttachments = NULL;
VkRenderPassCreateInfo rp = { VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO };
rp.attachmentCount = 2;
rp.pAttachments = attachments;
rp.subpassCount = 1;
rp.pSubpasses = &subpass;
rp.dependencyCount = 0;
rp.pDependencies = NULL;
// TODO: Maybe LOAD_OP_DONT_CARE makes sense in some situations. Additionally,
// there is often no need to store the depth buffer afterwards, although hard to know up front.
vkCreateRenderPass(vulkan_->GetDevice(), &rp, nullptr, &rpLoadColorLoadDepth_);
attachments[0].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
vkCreateRenderPass(vulkan_->GetDevice(), &rp, nullptr, &rpClearColorLoadDepth_);
attachments[1].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
vkCreateRenderPass(vulkan_->GetDevice(), &rp, nullptr, &rpClearColorClearDepth_);
attachments[0].loadOp = VK_ATTACHMENT_LOAD_OP_LOAD;
vkCreateRenderPass(vulkan_->GetDevice(), &rp, nullptr, &rpLoadColorClearDepth_);
// Initialize framedata
for (int i = 0; i < 2; i++) {
VkCommandPoolCreateInfo cp = { VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO };
cp.flags = VK_COMMAND_POOL_CREATE_TRANSIENT_BIT;
cp.queueFamilyIndex = vulkan_->GetGraphicsQueueFamilyIndex();
VkResult res = vkCreateCommandPool(vulkan_->GetDevice(), &cp, nullptr, &frameData_[i].cmdPool_);
assert(res == VK_SUCCESS);
frameData_[i].push_ = new VulkanPushBuffer(vulkan_, 64 * 1024);
}
@ -196,7 +127,9 @@ void FramebufferManagerVulkan::InitDeviceObjects() {
assert(fsBasicTex_ != VK_NULL_HANDLE);
assert(vsBasicTex_ != VK_NULL_HANDLE);
pipelineBasicTex_ = vulkan2D_.GetPipeline(pipelineCache2D_, rpClearColorClearDepth_, vsBasicTex_, fsBasicTex_);
// Prime the 2D pipeline cache.
vulkan2D_.GetPipeline(pipelineCache2D_, vulkan_->GetSurfaceRenderPass(), vsBasicTex_, fsBasicTex_);
vulkan2D_.GetPipeline(pipelineCache2D_, (VkRenderPass)draw_->GetNativeObject(Draw::NativeObject::COMPATIBLE_RENDERPASS), vsBasicTex_, fsBasicTex_);
VkSamplerCreateInfo samp = { VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO };
samp.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
@ -213,25 +146,7 @@ void FramebufferManagerVulkan::InitDeviceObjects() {
}
void FramebufferManagerVulkan::DestroyDeviceObjects() {
if (rpLoadColorLoadDepth_ != VK_NULL_HANDLE)
vulkan_->Delete().QueueDeleteRenderPass(rpLoadColorLoadDepth_);
if (rpClearColorLoadDepth_ != VK_NULL_HANDLE)
vulkan_->Delete().QueueDeleteRenderPass(rpClearColorLoadDepth_);
if (rpClearColorClearDepth_ != VK_NULL_HANDLE)
vulkan_->Delete().QueueDeleteRenderPass(rpClearColorClearDepth_);
if (rpLoadColorClearDepth_ != VK_NULL_HANDLE)
vulkan_->Delete().QueueDeleteRenderPass(rpLoadColorClearDepth_);
for (int i = 0; i < 2; i++) {
if (frameData_[i].numCommandBuffers_ > 0) {
vkFreeCommandBuffers(vulkan_->GetDevice(), frameData_[i].cmdPool_, frameData_[i].numCommandBuffers_, frameData_[i].commandBuffers_);
frameData_[i].numCommandBuffers_ = 0;
frameData_[i].totalCommandBuffers_ = 0;
}
if (frameData_[i].cmdPool_ != VK_NULL_HANDLE) {
vkDestroyCommandPool(vulkan_->GetDevice(), frameData_[i].cmdPool_, nullptr);
frameData_[i].cmdPool_ = VK_NULL_HANDLE;
}
if (frameData_[i].push_) {
frameData_[i].push_->Destroy(vulkan_);
delete frameData_[i].push_;
@ -256,50 +171,30 @@ void FramebufferManagerVulkan::DestroyDeviceObjects() {
}
void FramebufferManagerVulkan::NotifyClear(bool clearColor, bool clearAlpha, bool clearDepth, uint32_t color, float depth) {
if (!useBufferedRendering_) {
// if (!useBufferedRendering_) {
float x, y, w, h;
CenterDisplayOutputRect(&x, &y, &w, &h, 480.0f, 272.0f, (float)pixelWidth_, (float)pixelHeight_, ROTATION_LOCKED_HORIZONTAL);
VkClearValue colorValue, depthValue;
Uint8x4ToFloat4(colorValue.color.float32, color);
depthValue.depthStencil.depth = depth;
depthValue.depthStencil.stencil = (color >> 24) & 0xFF;
VkClearRect rect;
rect.baseArrayLayer = 0;
rect.layerCount = 1;
rect.rect.offset.x = x;
rect.rect.offset.y = y;
rect.rect.extent.width = w;
rect.rect.extent.height = h;
int count = 0;
VkClearAttachment attach[2];
int mask = 0;
// The Clear detection takes care of doing a regular draw instead if separate masking
// of color and alpha is needed, so we can just treat them as the same.
if (clearColor || clearAlpha) {
attach[count].aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
attach[count].clearValue = colorValue;
attach[count].colorAttachment = 0;
count++;
}
if (clearDepth) {
attach[count].aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT;
attach[count].clearValue = depthValue;
attach[count].colorAttachment = 0;
count++;
}
vkCmdClearAttachments(curCmd_, count, attach, 1, &rect);
if (clearColor || clearAlpha)
mask |= Draw::FBChannel::FB_COLOR_BIT;
if (clearDepth)
mask |= Draw::FBChannel::FB_DEPTH_BIT;
if (clearAlpha)
mask |= Draw::FBChannel::FB_STENCIL_BIT;
draw_->Clear(mask, color, depth, 0);
if (clearColor || clearAlpha) {
SetColorUpdated(gstate_c.skipDrawReason);
}
if (clearDepth) {
SetDepthUpdated();
}
} else {
//} else {
// TODO: Clever render pass magic.
}
//}
}
void FramebufferManagerVulkan::DoNotifyDraw() {
@ -345,6 +240,8 @@ void FramebufferManagerVulkan::MakePixelTexture(const u8 *srcPixels, GEBufferFor
drawPixelsTex_->CreateDirect(width, height, 1, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT);
// Initialize backbuffer texture for DrawPixels
drawPixelsTexFormat_ = srcPixelFormat;
} else {
drawPixelsTex_->TransitionForUpload();
}
// TODO: We can just change the texture format and flip some bits around instead of this.
@ -403,6 +300,8 @@ void FramebufferManagerVulkan::MakePixelTexture(const u8 *srcPixels, GEBufferFor
size_t offset = frameData_[curFrame_].push_->Push(data, width * height * 4, &buffer);
drawPixelsTex_->UploadMip(0, width, height, buffer, (uint32_t)offset, width);
drawPixelsTex_->EndCreate();
overrideImageView_ = drawPixelsTex_->GetImageView();
}
void FramebufferManagerVulkan::SetViewport2D(int x, int y, int w, int h) {
@ -413,15 +312,12 @@ void FramebufferManagerVulkan::SetViewport2D(int x, int y, int w, int h) {
vp.y = (float)y;
vp.width = (float)w;
vp.height = (float)h;
vkCmdSetViewport(curCmd_, 0, 1, &vp);
VkCommandBuffer cmd = (VkCommandBuffer)draw_->GetNativeObject(Draw::NativeObject::RENDERPASS_COMMANDBUFFER);
vkCmdSetViewport(cmd, 0, 1, &vp);
}
void FramebufferManagerVulkan::DrawActiveTexture(float x, float y, float w, float h, float destW, float destH, float u0, float v0, float u1, float v1, int uvRotation, bool linearFilter) {
// TODO
}
// x, y, w, h are relative coordinates against destW/destH, which is not very intuitive.
void FramebufferManagerVulkan::DrawTexture(VulkanTexture *texture, float x, float y, float w, float h, float destW, float destH, float u0, float v0, float u1, float v1, VkPipeline pipeline, int uvRotation) {
float texCoords[8] = {
u0,v0,
u1,v0,
@ -444,10 +340,10 @@ void FramebufferManagerVulkan::DrawTexture(VulkanTexture *texture, float x, floa
}
Vulkan2D::Vertex vtx[4] = {
{x,y,0,texCoords[0],texCoords[1]},
{x + w,y,0,texCoords[2],texCoords[3]},
{x,y + h,0,texCoords[6],texCoords[7] },
{x + w,y + h,0,texCoords[4],texCoords[5] },
{x, y, 0, texCoords[0], texCoords[1]},
{x + w, y, 0, texCoords[2], texCoords[3]},
{x, y + h, 0, texCoords[6], texCoords[7]},
{x + w, y + h, 0, texCoords[4], texCoords[5]},
};
float invDestW = 1.0f / (destW * 0.5f);
@ -457,13 +353,18 @@ void FramebufferManagerVulkan::DrawTexture(VulkanTexture *texture, float x, floa
vtx[i].y = vtx[i].y * invDestH - 1.0f;
}
draw_->FlushState();
// TODO: Should probably use draw_ directly and not go low level
VulkanPushBuffer *push = frameData_[curFrame_].push_;
VkCommandBuffer cmd = curCmd_;
VkCommandBuffer cmd = (VkCommandBuffer)draw_->GetNativeObject(Draw::NativeObject::RENDERPASS_COMMANDBUFFER);
// TODO: Choose linear or nearest appropriately, see GL impl.
vulkan2D_.BindDescriptorSet(cmd, texture->GetImageView(), linearSampler_);
vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
VkImageView view = overrideImageView_ ? overrideImageView_ : (VkImageView)draw_->GetNativeObject(Draw::NativeObject::BOUND_TEXTURE_IMAGEVIEW);
overrideImageView_ = VK_NULL_HANDLE;
vulkan2D_.BindDescriptorSet(cmd, view, linearFilter ? linearSampler_ : nearestSampler_);
vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, cur2DPipeline_);
VkBuffer vbuffer;
VkDeviceSize offset = push->Push(vtx, sizeof(vtx), &vbuffer);
vkCmdBindVertexBuffers(cmd, 0, 1, &vbuffer, &offset);
@ -471,7 +372,8 @@ void FramebufferManagerVulkan::DrawTexture(VulkanTexture *texture, float x, floa
}
void FramebufferManagerVulkan::Bind2DShader() {
VkRenderPass rp = (VkRenderPass)draw_->GetNativeObject(Draw::NativeObject::COMPATIBLE_RENDERPASS);
cur2DPipeline_ = vulkan2D_.GetPipeline(pipelineCache2D_, rp, vsBasicTex_, fsBasicTex_);
}
void FramebufferManagerVulkan::BindPostShader(const PostShaderUniforms &uniforms) {
@ -480,9 +382,10 @@ void FramebufferManagerVulkan::BindPostShader(const PostShaderUniforms &uniforms
void FramebufferManagerVulkan::RebindFramebuffer() {
if (currentRenderVfb_ && currentRenderVfb_->fbo) {
draw_->BindFramebufferAsRenderTarget(currentRenderVfb_->fbo);
draw_->BindFramebufferAsRenderTarget(currentRenderVfb_->fbo, { Draw::RPAction::KEEP, Draw::RPAction::KEEP });
} else {
draw_->BindBackbufferAsRenderTarget();
// Should this even happen?
draw_->BindFramebufferAsRenderTarget(nullptr, { Draw::RPAction::KEEP, Draw::RPAction::KEEP });
}
}
@ -500,14 +403,12 @@ int FramebufferManagerVulkan::GetLineWidth() {
}
}
// This also binds vfb as the current render target.
void FramebufferManagerVulkan::ReformatFramebufferFrom(VirtualFramebuffer *vfb, GEBufferFormat old) {
if (!useBufferedRendering_ || !vfb->fbo) {
return;
}
/*
BindFramebufferAsRenderTargetvfb->fbo);
// Technically, we should at this point re-interpret the bytes of the old format to the new.
// That might get tricky, and could cause unnecessary slowness in some games.
// For now, we just clear alpha/stencil from 565, which fixes shadow issues in Kingdom Hearts.
@ -518,30 +419,70 @@ void FramebufferManagerVulkan::ReformatFramebufferFrom(VirtualFramebuffer *vfb,
// to exactly reproduce in 4444 and 8888 formats.
if (old == GE_FORMAT_565) {
// TODO: Clear to black, set stencil to 0, don't touch depth (or maybe zap depth).
draw_->BindFramebufferAsRenderTarget(vfb->fbo, { Draw::RPAction::CLEAR, Draw::RPAction::CLEAR });
} else {
draw_->BindFramebufferAsRenderTarget(vfb->fbo, { Draw::RPAction::KEEP, Draw::RPAction::KEEP });
}
RebindFramebuffer();
*/
}
// Except for a missing rebind and silly scissor enables, identical copy of the same function in GPU_GLES - tricky parts are in thin3d.
void FramebufferManagerVulkan::BlitFramebufferDepth(VirtualFramebuffer *src, VirtualFramebuffer *dst) {
if (src->z_address == dst->z_address &&
src->z_stride != 0 && dst->z_stride != 0 &&
src->renderWidth == dst->renderWidth &&
src->renderHeight == dst->renderHeight) {
if (g_Config.bDisableSlowFramebufEffects) {
return;
}
// TODO: Let's only do this if not clearing depth.
bool matchingDepthBuffer = src->z_address == dst->z_address && src->z_stride != 0 && dst->z_stride != 0;
bool matchingSize = src->width == dst->width && src->height == dst->height;
bool matchingRenderSize = src->renderWidth == dst->renderWidth && src->renderHeight == dst->renderHeight;
VkImageCopy region = {};
region.srcSubresource.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT;
region.dstSubresource.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT;
region.extent = { dst->renderWidth, dst->renderHeight, 1 };
region.extent.depth = 1;
// vkCmdCopyImage(curCmd_, src->fbo->GetDepthStencil()->GetImage(), VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL,
// dst->fbo->GetDepthStencil()->GetImage(), VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL, 1, &region);
if (gstate_c.Supports(GPU_SUPPORTS_ANY_COPY_IMAGE) && matchingDepthBuffer && matchingRenderSize && matchingSize) {
draw_->CopyFramebufferImage(src->fbo, 0, 0, 0, 0, dst->fbo, 0, 0, 0, 0, src->renderWidth, src->renderHeight, 1, Draw::FB_DEPTH_BIT);
} else if (matchingDepthBuffer && matchingSize) {
int w = std::min(src->renderWidth, dst->renderWidth);
int h = std::min(src->renderHeight, dst->renderHeight);
if (gstate_c.Supports(GPU_SUPPORTS_ARB_FRAMEBUFFER_BLIT | GPU_SUPPORTS_NV_FRAMEBUFFER_BLIT)) {
draw_->BlitFramebuffer(src->fbo, 0, 0, w, h, dst->fbo, 0, 0, w, h, Draw::FB_DEPTH_BIT, Draw::FB_BLIT_NEAREST);
}
}
}
// If we set dst->depthUpdated here, our optimization above would be pointless.
VkImageView FramebufferManagerVulkan::BindFramebufferAsColorTexture(int stage, VirtualFramebuffer *framebuffer, int flags) {
if (!framebuffer->fbo || !useBufferedRendering_) {
gstate_c.skipDrawReason |= SKIPDRAW_BAD_FB_TEXTURE;
return VK_NULL_HANDLE;
}
// currentRenderVfb_ will always be set when this is called, except from the GE debugger.
// Let's just not bother with the copy in that case.
bool skipCopy = (flags & BINDFBCOLOR_MAY_COPY) == 0;
if (GPUStepping::IsStepping() || g_Config.bDisableSlowFramebufEffects) {
skipCopy = true;
}
// Currently rendering to this framebuffer. Need to make a copy.
if (!skipCopy && framebuffer == currentRenderVfb_) {
// ignore this case for now, doesn't work
// ILOG("Texturing from current render Vfb!");
return VK_NULL_HANDLE;
// TODO: Maybe merge with bvfbs_? Not sure if those could be packing, and they're created at a different size.
Draw::Framebuffer *renderCopy = GetTempFBO(framebuffer->renderWidth, framebuffer->renderHeight, (Draw::FBColorDepth)framebuffer->colorDepth);
if (renderCopy) {
VirtualFramebuffer copyInfo = *framebuffer;
copyInfo.fbo = renderCopy;
CopyFramebufferForColorTexture(&copyInfo, framebuffer, flags);
RebindFramebuffer();
draw_->BindFramebufferAsTexture(renderCopy, stage, Draw::FB_COLOR_BIT, 0);
} else {
draw_->BindFramebufferAsTexture(framebuffer->fbo, stage, Draw::FB_COLOR_BIT, 0);
}
return (VkImageView)draw_->GetNativeObject(Draw::NativeObject::BOUND_TEXTURE_IMAGEVIEW);
} else if (framebuffer != currentRenderVfb_) {
draw_->BindFramebufferAsTexture(framebuffer->fbo, stage, Draw::FB_COLOR_BIT, 0);
return (VkImageView)draw_->GetNativeObject(Draw::NativeObject::BOUND_TEXTURE_IMAGEVIEW);
} else {
ERROR_LOG_REPORT_ONCE(vulkanSelfTexture, G3D, "Attempting to texture from target");
// To do this safely in Vulkan, we need to use input attachments.
return VK_NULL_HANDLE;
}
}
@ -721,15 +662,13 @@ void FramebufferManagerVulkan::UpdateDownloadTempBuffer(VirtualFramebuffer *nvfb
void FramebufferManagerVulkan::BlitFramebuffer(VirtualFramebuffer *dst, int dstX, int dstY, VirtualFramebuffer *src, int srcX, int srcY, int w, int h, int bpp) {
if (!dst->fbo || !src->fbo || !useBufferedRendering_) {
// This can happen if they recently switched from non-buffered.
if (useBufferedRendering_)
draw_->BindFramebufferAsRenderTarget(nullptr, { Draw::RPAction::KEEP, Draw::RPAction::KEEP });
return;
}
// NOTE: There may be cases (like within a renderpass) where we want to
// not use a blit.
bool useBlit = true;
float srcXFactor = useBlit ? (float)src->renderWidth / (float)src->bufferWidth : 1.0f;
float srcYFactor = useBlit ? (float)src->renderHeight / (float)src->bufferHeight : 1.0f;
float srcXFactor = (float)src->renderWidth / (float)src->bufferWidth;
float srcYFactor = (float)src->renderHeight / (float)src->bufferHeight;
const int srcBpp = src->format == GE_FORMAT_8888 ? 4 : 2;
if (srcBpp != bpp && bpp != 0) {
srcXFactor = (srcXFactor * bpp) / srcBpp;
@ -739,8 +678,8 @@ void FramebufferManagerVulkan::BlitFramebuffer(VirtualFramebuffer *dst, int dstX
int srcY1 = srcY * srcYFactor;
int srcY2 = (srcY + h) * srcYFactor;
float dstXFactor = useBlit ? (float)dst->renderWidth / (float)dst->bufferWidth : 1.0f;
float dstYFactor = useBlit ? (float)dst->renderHeight / (float)dst->bufferHeight : 1.0f;
float dstXFactor = (float)dst->renderWidth / (float)dst->bufferWidth;
float dstYFactor = (float)dst->renderHeight / (float)dst->bufferHeight;
const int dstBpp = dst->format == GE_FORMAT_8888 ? 4 : 2;
if (dstBpp != bpp && bpp != 0) {
dstXFactor = (dstXFactor * bpp) / dstBpp;
@ -756,6 +695,7 @@ void FramebufferManagerVulkan::BlitFramebuffer(VirtualFramebuffer *dst, int dstX
return;
}
// BlitFramebuffer can clip, but CopyFramebufferImage is more restricted.
// In case the src goes outside, we just skip the optimization in that case.
const bool sameSize = dstX2 - dstX1 == srcX2 - srcX1 && dstY2 - dstY1 == srcY2 - srcY1;
const bool sameDepth = dst->colorDepth == src->colorDepth;
@ -764,30 +704,9 @@ void FramebufferManagerVulkan::BlitFramebuffer(VirtualFramebuffer *dst, int dstX
const bool xOverlap = src == dst && srcX2 > dstX1 && srcX1 < dstX2;
const bool yOverlap = src == dst && srcY2 > dstY1 && srcY1 < dstY2;
if (sameSize && sameDepth && srcInsideBounds && dstInsideBounds && !(xOverlap && yOverlap)) {
VkImageCopy region = {};
region.extent = { (uint32_t)(dstX2 - dstX1), (uint32_t)(dstY2 - dstY1), 1 };
/*
glCopyImageSubDataOES(
fbo_get_color_texture(src->fbo), GL_TEXTURE_2D, 0, srcX1, srcY1, 0,
fbo_get_color_texture(dst->fbo), GL_TEXTURE_2D, 0, dstX1, dstY1, 0,
dstX2 - dstX1, dstY2 - dstY1, 1);
*/
return;
}
// BindFramebufferAsRenderTargetdst->fbo);
if (useBlit) {
// fbo_bind_for_read(src->fbo);
//glBlitFramebuffer(srcX1, srcY1, srcX2, srcY2, dstX1, dstY1, dstX2, dstY2, GL_COLOR_BUFFER_BIT, GL_NEAREST);
draw_->CopyFramebufferImage(src->fbo, 0, srcX1, srcY1, 0, dst->fbo, 0, dstX1, dstY1, 0, dstX2 - dstX1, dstY2 - dstY1, 1, Draw::FB_COLOR_BIT);
} else {
// fbo_bind_color_as_texture(src->fbo, 0);
// The first four coordinates are relative to the 6th and 7th arguments of DrawActiveTexture.
// Should maybe revamp that interface.
float srcW = src->bufferWidth;
float srcH = src->bufferHeight;
// DrawActiveTexture(0, dstX1, dstY1, w * dstXFactor, h, dst->bufferWidth, dst->bufferHeight, srcX1 / srcW, srcY1 / srcH, srcX2 / srcW, srcY2 / srcH, draw2dprogram_, ROTATION_LOCKED_HORIZONTAL);
draw_->BlitFramebuffer(src->fbo, srcX1, srcY1, srcX2, srcY2, dst->fbo, dstX1, dstY1, dstX2, dstY2, Draw::FB_COLOR_BIT, Draw::FB_BLIT_NEAREST);
}
}
@ -1000,39 +919,26 @@ void FramebufferManagerVulkan::PackFramebufferSync_(VirtualFramebuffer *vfb, int
}
VkCommandBuffer FramebufferManagerVulkan::AllocFrameCommandBuffer() {
FrameData &frame = frameData_[curFrame_];
int num = frame.numCommandBuffers_;
if (!frame.commandBuffers_[num]) {
VkCommandBufferAllocateInfo cmd = { VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO };
cmd.commandBufferCount = 1;
cmd.commandPool = frame.cmdPool_;
cmd.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
vkAllocateCommandBuffers(vulkan_->GetDevice(), &cmd, &frame.commandBuffers_[num]);
frame.totalCommandBuffers_ = num + 1;
}
return frame.commandBuffers_[num];
}
void FramebufferManagerVulkan::BeginFrameVulkan() {
BeginFrame();
vulkan2D_.BeginFrame();
FrameData &frame = frameData_[curFrame_];
vkResetCommandPool(vulkan_->GetDevice(), frame.cmdPool_, 0);
frame.numCommandBuffers_ = 0;
frame.push_->Reset();
frame.push_->Begin(vulkan_);
if (!useBufferedRendering_) {
// TODO: This hackery should not be necessary. Is it? Need to check.
// We only use a single command buffer in this case.
curCmd_ = vulkan_->GetSurfaceCommandBuffer();
VkCommandBuffer cmd = vulkan_->GetSurfaceCommandBuffer();
VkRect2D scissor;
scissor.offset = { 0, 0 };
scissor.extent = { (uint32_t)pixelWidth_, (uint32_t)pixelHeight_ };
vkCmdSetScissor(curCmd_, 0, 1, &scissor);
vkCmdSetScissor(cmd, 0, 1, &scissor);
} else {
// Each render pass will set up scissor again.
}
}
@ -1111,7 +1017,9 @@ void FramebufferManagerVulkan::FlushBeforeCopy() {
// all the irrelevant state checking it'll use to decide what to do. Should
// do something more focused here.
SetRenderFrameBuffer(gstate_c.IsDirty(DIRTY_FRAMEBUF), gstate_c.skipDrawReason);
drawEngine_->Flush(curCmd_);
if (!draw_->GetNativeObject(Draw::NativeObject::CURRENT_RENDERPASS))
Crash();
drawEngine_->Flush();
}
void FramebufferManagerVulkan::Resized() {
@ -1211,22 +1119,7 @@ bool FramebufferManagerVulkan::GetStencilbuffer(u32 fb_address, int fb_stride, G
return false;
}
void FramebufferManagerVulkan::ClearBuffer(bool keepState) {
// keepState is irrelevant.
if (!currentRenderVfb_) {
return;
}
VkClearAttachment clear[2];
memset(clear, 0, sizeof(clear));
clear[0].aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
clear[1].aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT;
VkClearRect rc;
rc.baseArrayLayer = 0;
rc.layerCount = 1;
rc.rect.offset.x = 0;
rc.rect.offset.y = 0;
rc.rect.extent.width = currentRenderVfb_->bufferWidth;
rc.rect.extent.height = currentRenderVfb_->bufferHeight;
vkCmdClearAttachments(curCmd_, 2, clear, 1, &rc);
// TODO: Ideally, this should never be called.
// assert(false);
}

View File

@ -69,9 +69,7 @@ public:
drawEngine_ = td;
}
// If texture != 0, will bind it.
// x,y,w,h are relative to destW, destH which fill out the target completely.
void DrawTexture(VulkanTexture *texture, float x, float y, float w, float h, float destW, float destH, float u0, float v0, float u1, float v1, VkPipeline pipeline, int uvRotation);
void DrawActiveTexture(float x, float y, float w, float h, float destW, float destH, float u0, float v0, float u1, float v1, int uvRotation, bool linearFilter) override;
void DestroyAllFBOs();
@ -106,6 +104,7 @@ public:
bool GetOutputFramebuffer(GPUDebugBuffer &buffer) override;
virtual void RebindFramebuffer() override;
VkImageView BindFramebufferAsColorTexture(int stage, VirtualFramebuffer *framebuffer, int flags);
// VulkanFBO *GetTempFBO(u16 w, u16 h, VulkanFBOColorDepth depth = VK_FBO_8888);
@ -138,7 +137,6 @@ private:
void MakePixelTexture(const u8 *srcPixels, GEBufferFormat srcPixelFormat, int srcStride, int width, int height, float &u1, float &v1) override;
void DoNotifyDraw();
VkCommandBuffer AllocFrameCommandBuffer();
void UpdatePostShaderUniforms(int bufferWidth, int bufferHeight, int renderWidth, int renderHeight);
void PackFramebufferAsync_(VirtualFramebuffer *vfb);
@ -149,11 +147,7 @@ private:
VulkanContext *vulkan_;
// The command buffer of the current framebuffer pass being rendered to.
// One framebuffer can be used as a texturing source at multiple times in a frame,
// but then the contents have to be copied out into a new texture every time.
VkCommandBuffer curCmd_ = VK_NULL_HANDLE;
VkCommandBuffer cmdInit_ = VK_NULL_HANDLE;
// Used to keep track of command buffers here but have moved all that into Thin3D.
// Used by DrawPixels
VulkanTexture *drawPixelsTex_;
@ -173,13 +167,9 @@ private:
MAX_COMMAND_BUFFERS = 32,
};
// Commandbuffers are handled internally in thin3d, one for each framebuffer pass.
struct FrameData {
VkCommandPool cmdPool_;
// Keep track of command buffers we allocated so we can reset or free them at an appropriate point.
VkCommandBuffer commandBuffers_[MAX_COMMAND_BUFFERS];
VulkanPushBuffer *push_;
int numCommandBuffers_;
int totalCommandBuffers_;
};
FrameData frameData_[2];
@ -188,18 +178,13 @@ private:
// This gets copied to the current frame's push buffer as needed.
PostShaderUniforms postUniforms_;
// Renderpasses, all combination of preserving or clearing fb contents
VkRenderPass rpLoadColorLoadDepth_;
VkRenderPass rpClearColorLoadDepth_;
VkRenderPass rpLoadColorClearDepth_;
VkRenderPass rpClearColorClearDepth_;
VkPipelineCache pipelineCache2D_;
// Basic shaders
VkShaderModule fsBasicTex_;
VkShaderModule vsBasicTex_;
VkPipeline pipelineBasicTex_;
VkPipeline cur2DPipeline_ = VK_NULL_HANDLE;
// Postprocessing
VkPipeline pipelinePostShader_;
@ -207,6 +192,9 @@ private:
VkSampler linearSampler_;
VkSampler nearestSampler_;
// hack!
VkImageView overrideImageView_ = VK_NULL_HANDLE;
// Simple 2D drawing engine.
Vulkan2D vulkan2D_;
};

View File

@ -78,7 +78,7 @@ static const VulkanCommandTableEntry commandTable[] = {
GPU_Vulkan::GPU_Vulkan(GraphicsContext *gfxCtx, Draw::DrawContext *draw)
: GPUCommon(gfxCtx, draw),
vulkan_((VulkanContext *)gfxCtx->GetAPIContext()),
drawEngine_(vulkan_) {
drawEngine_(vulkan_, draw) {
UpdateVsyncInterval(true);
CheckGPUFeatures();
@ -197,10 +197,6 @@ void GPU_Vulkan::CheckGPUFeatures() {
}
void GPU_Vulkan::BeginHostFrame() {
if (g_Config.iRenderingMode == FB_NON_BUFFERED_MODE) {
// Draw everything directly to the backbuffer.
drawEngine_.SetCmdBuffer(vulkan_->GetSurfaceCommandBuffer());
}
drawEngine_.BeginFrame();
if (resized_) {
@ -396,7 +392,7 @@ bool GPU_Vulkan::FramebufferReallyDirty() {
void GPU_Vulkan::CopyDisplayToOutputInternal() {
// Flush anything left over.
drawEngine_.Flush(curCmd_);
drawEngine_.Flush();
shaderManagerVulkan_->DirtyLastShader();
@ -419,7 +415,7 @@ void GPU_Vulkan::FastRunLoop(DisplayList &list) {
const u32 diff = op ^ gstate.cmdmem[cmd];
// Inlined CheckFlushOp here to get rid of the dumpThisFrame_ check.
if ((cmdFlags & FLAG_FLUSHBEFORE) || (diff && (cmdFlags & FLAG_FLUSHBEFOREONCHANGE))) {
drawEngine_.Flush(curCmd_);
drawEngine_.Flush();
}
gstate.cmdmem[cmd] = op; // TODO: no need to write if diff==0...
if ((cmdFlags & FLAG_EXECUTE) || (diff && (cmdFlags & FLAG_EXECUTEONCHANGE))) {
@ -445,7 +441,7 @@ inline void GPU_Vulkan::CheckFlushOp(int cmd, u32 diff) {
if (dumpThisFrame_) {
NOTICE_LOG(G3D, "================ FLUSH ================");
}
drawEngine_.Flush(curCmd_);
drawEngine_.Flush();
}
}
@ -493,6 +489,8 @@ void GPU_Vulkan::Execute_Prim(u32 op, u32 diff) {
// This also makes skipping drawing very effective.
framebufferManager_->SetRenderFrameBuffer(gstate_c.IsDirty(DIRTY_FRAMEBUF), gstate_c.skipDrawReason);
if (!draw_->GetNativeObject(Draw::NativeObject::CURRENT_RENDERPASS))
Crash();
if (gstate_c.skipDrawReason & (SKIPDRAW_SKIPFRAME | SKIPDRAW_NON_DISPLAYED_FB)) {
drawEngine_.SetupVertexDecoder(gstate.vertType);

View File

@ -96,7 +96,7 @@ protected:
private:
void Flush() {
drawEngine_.Flush(nullptr);
drawEngine_.Flush();
}
void CheckFlushOp(int cmd, u32 diff);
void BuildReportingInfo();

View File

@ -292,9 +292,13 @@ static VulkanPipeline *CreateVulkanPipeline(VkDevice device, VkPipelineCache pip
return vulkanPipeline;
}
VulkanPipeline *PipelineManagerVulkan::GetOrCreatePipeline(VkPipelineLayout layout, const VulkanPipelineRasterStateKey &rasterKey, const VertexDecoder *vtxDec, VulkanVertexShader *vs, VulkanFragmentShader *fs, bool useHwTransform) {
VulkanPipeline *PipelineManagerVulkan::GetOrCreatePipeline(VkPipelineLayout layout, VkRenderPass renderPass, const VulkanPipelineRasterStateKey &rasterKey, const VertexDecoder *vtxDec, VulkanVertexShader *vs, VulkanFragmentShader *fs, bool useHwTransform) {
VulkanPipelineKey key;
if (!renderPass)
Crash();
key.raster = rasterKey;
key.renderPass = renderPass;
key.useHWTransform = useHwTransform;
key.vShader = vs->GetModule();
key.fShader = fs->GetModule();
@ -305,7 +309,7 @@ VulkanPipeline *PipelineManagerVulkan::GetOrCreatePipeline(VkPipelineLayout layo
}
VulkanPipeline *pipeline = CreateVulkanPipeline(
vulkan_->GetDevice(), pipelineCache_, layout, vulkan_->GetSurfaceRenderPass(),
vulkan_->GetDevice(), pipelineCache_, layout, renderPass,
rasterKey, vtxDec, vs, fs, useHwTransform);
pipelines_[key] = pipeline;
return pipeline;

View File

@ -39,13 +39,16 @@ enum class PspAttributeLocation {
struct VulkanPipelineKey {
VulkanPipelineRasterStateKey raster; // prim is included here
VkRenderPass renderPass;
bool useHWTransform;
const VertexDecoder *vtxDec;
VkShaderModule vShader;
VkShaderModule fShader;
// TODO: Probably better to use a hash function instead.
bool operator < (const VulkanPipelineKey &other) const {
if (raster < other.raster) return true; else if (other.raster < raster) return false;
if (renderPass < other.renderPass) return true; else if (other.renderPass < renderPass) return false;
if (useHWTransform < other.useHWTransform) return true; else if (other.useHWTransform < useHWTransform) return false;
if (vtxDec < other.vtxDec) return true; else if (other.vtxDec < vtxDec) return false;
if (vShader < other.vShader) return true; else if (other.vShader < vShader) return false;
@ -82,7 +85,7 @@ public:
PipelineManagerVulkan(VulkanContext *ctx);
~PipelineManagerVulkan();
VulkanPipeline *GetOrCreatePipeline(VkPipelineLayout layout, const VulkanPipelineRasterStateKey &rasterKey, const VertexDecoder *vtxDec, VulkanVertexShader *vs, VulkanFragmentShader *fs, bool useHwTransform);
VulkanPipeline *GetOrCreatePipeline(VkPipelineLayout layout, VkRenderPass renderPass, const VulkanPipelineRasterStateKey &rasterKey, const VertexDecoder *vtxDec, VulkanVertexShader *vs, VulkanFragmentShader *fs, bool useHwTransform);
int GetNumPipelines() const { return (int)pipelines_.size(); }
void Clear();

View File

@ -16,7 +16,7 @@
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
#ifdef _WIN32
#define SHADERLOG
//#define SHADERLOG
#endif
#include <map>

View File

@ -152,6 +152,9 @@ void DrawEngineVulkan::ConvertStateToVulkanKey(FramebufferManagerVulkan &fbManag
key.colorWriteMask = (colorMask ? (VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT) : 0) | (alphaMask ? VK_COLOR_COMPONENT_A_BIT : 0);
} else {
key.logicOpEnable = false;
key.logicOp = VK_LOGIC_OP_CLEAR;
// Set blend - unless we need to do it in the shader.
GenericBlendState blendState;
ConvertBlendState(blendState, gstate_c.allowShaderBlend);
@ -341,10 +344,17 @@ void DrawEngineVulkan::ConvertStateToVulkanKey(FramebufferManagerVulkan &fbManag
}
VkRect2D &scissor = dynState.scissor;
scissor.offset.x = vpAndScissor.scissorX;
scissor.offset.y = vpAndScissor.scissorY;
scissor.extent.width = vpAndScissor.scissorW;
scissor.extent.height = vpAndScissor.scissorH;
if (vpAndScissor.scissorEnable) {
scissor.offset.x = vpAndScissor.scissorX;
scissor.offset.y = vpAndScissor.scissorY;
scissor.extent.width = vpAndScissor.scissorW;
scissor.extent.height = vpAndScissor.scissorH;
} else {
scissor.offset.x = 0;
scissor.offset.y = 0;
scissor.extent.width = framebufferManager_->GetRenderWidth();
scissor.extent.height = framebufferManager_->GetRenderHeight();
}
float depthMin = vpAndScissor.depthRangeMin;
float depthMax = vpAndScissor.depthRangeMax;
@ -358,3 +368,21 @@ void DrawEngineVulkan::ConvertStateToVulkanKey(FramebufferManagerVulkan &fbManag
key.topology = primToVulkan[prim];
}
void DrawEngineVulkan::ApplyDrawStateLate() {
// At this point, we know if the vertices are full alpha or not.
// TODO: Set the nearest/linear here (since we correctly know if alpha/color tests are needed)?
if (!gstate.isModeClear()) {
// TODO: Test texture?
/*
if (fboTexNeedBind_) {
// Note that this is positions, not UVs, that we need the copy from.
framebufferManager_->BindFramebufferAsColorTexture(1, framebufferManager_->GetCurrentRenderVFB(), BINDFBCOLOR_MAY_COPY);
// If we are rendering at a higher resolution, linear is probably best for the dest color.
fboTexBound_ = true;
fboTexNeedBind_ = false;
}
*/
}
}

View File

@ -426,30 +426,21 @@ void TextureCacheVulkan::ApplyTextureFramebuffer(TexCacheEntry *entry, VirtualFr
TexCacheEntry::Status alphaStatus = CheckAlpha(clutBuf_, getClutDestFormatVulkan(clutFormat), clutTotalColors, clutTotalColors, 1);
gstate_c.SetTextureFullAlpha(alphaStatus == TexCacheEntry::STATUS_ALPHA_FULL);
gstate_c.SetTextureSimpleAlpha(alphaStatus == TexCacheEntry::STATUS_ALPHA_SIMPLE);
// imageView_ = depalFbo->getImageView().
} else {
entry->status &= ~TexCacheEntry::STATUS_DEPALETTIZE;
imageView_ = framebufferManagerVulkan_->BindFramebufferAsColorTexture(0, framebuffer, BINDFBCOLOR_MAY_COPY_WITH_UV | BINDFBCOLOR_APPLY_TEX_OFFSET);
gstate_c.SetTextureFullAlpha(gstate.getTextureFormat() == GE_TFMT_5650);
gstate_c.SetTextureSimpleAlpha(gstate_c.textureFullAlpha);
}
/*
imageView = depalFBO->GetColorImageView();
SamplerCacheKey samplerKey;
framebufferManager_->RebindFramebuffer();
SetFramebufferSamplingParams(framebuffer->bufferWidth, framebuffer->bufferHeight, samplerKey);
sampler = GetOrCreateSampler(samplerKey);
*/
if (entry->vkTex) {
SamplerCacheKey key;
UpdateSamplingParams(*entry, key);
key.mipEnable = false;
sampler_ = samplerCache_.GetOrCreateSampler(key);
InvalidateLastTexture();
}
sampler_ = samplerCache_.GetOrCreateSampler(samplerKey);
InvalidateLastTexture(entry);
}
ReplacedTextureFormat FromVulkanFormat(VkFormat fmt) {
@ -622,7 +613,7 @@ void TextureCacheVulkan::BuildTexture(TexCacheEntry *const entry, bool replaceIm
entry->vkTex = nullptr;
}
} else {
// TODO: If reusing an existing texture object, we must transition it into the correct layout.
entry->vkTex->texture_->TransitionForUpload();
}
lastBoundTexture = entry->vkTex;
@ -660,8 +651,9 @@ void TextureCacheVulkan::BuildTexture(TexCacheEntry *const entry, bool replaceIm
LoadTextureLevel(*entry, (uint8_t *)data, stride, level, scaleFactor, dstFmt);
entry->vkTex->texture_->UploadMip(0, mipWidth, mipHeight, texBuf, bufferOffset, stride / bpp);
break;
} else
} else {
LoadTextureLevel(*entry, (uint8_t *)data, stride, i, scaleFactor, dstFmt);
}
if (replacer_.Enabled()) {
replacer_.NotifyTextureDecoded(replacedInfo, data, stride, i, mipWidth, mipHeight);
}

View File

@ -217,6 +217,7 @@ VkPipeline Vulkan2D::GetPipeline(VkPipelineCache cache, VkRenderPass rp, VkShade
PipelineKey key;
key.vs = vs;
key.fs = fs;
key.rp = rp;
auto iter = pipelines_.find(key);
if (iter != pipelines_.end()) {

View File

@ -169,16 +169,6 @@ void EmuScreen::bootGame(const std::string &filename) {
break;
case GPUBackend::VULKAN:
coreParam.gpuCore = GPUCORE_VULKAN;
if (g_Config.iRenderingMode != FB_NON_BUFFERED_MODE && !g_Config.bSoftwareRendering) {
#ifdef _WIN32
if (IDYES == MessageBox(MainWindow::GetHWND(), L"The Vulkan backend is not yet compatible with buffered rendering. Switch to non-buffered (WARNING: This will cause glitches with the other backends unless you switch back)", L"Vulkan Experimental Support", MB_ICONINFORMATION | MB_YESNO)) {
g_Config.iRenderingMode = FB_NON_BUFFERED_MODE;
} else {
errorMessage_ = "Non-buffered rendering required for Vulkan";
return;
}
#endif
}
break;
#endif
}
@ -958,6 +948,42 @@ static void DrawFPS(DrawBuffer *draw2d, const Bounds &bounds) {
draw2d->SetFontScale(1.0f, 1.0f);
}
void EmuScreen::preRender() {
using namespace Draw;
DrawContext *draw = screenManager()->getDrawContext();
draw->BeginFrame();
// Here we do NOT bind the backbuffer or clear the screen, unless non-buffered.
// The emuscreen is different than the others - we really want to allow the game to render to framebuffers
// before we ever bind the backbuffer for rendering. On mobile GPUs, switching back and forth between render
// targets is a mortal sin so it's very important that we don't bind the backbuffer unnecessarily here.
// We only bind it in FramebufferManager::CopyDisplayToOutput (unless non-buffered)...
// We do, however, start the frame in other ways.
bool useBufferedRendering = g_Config.iRenderingMode != FB_NON_BUFFERED_MODE;
if (!useBufferedRendering && !g_Config.bSoftwareRendering) {
// We need to clear here already so that drawing during the frame is done on a clean slate.
DrawContext *draw = screenManager()->getDrawContext();
draw->BindFramebufferAsRenderTarget(nullptr, { RPAction::CLEAR, RPAction::CLEAR, 0xFF000000 });
Viewport viewport;
viewport.TopLeftX = 0;
viewport.TopLeftY = 0;
viewport.Width = pixel_xres;
viewport.Height = pixel_yres;
viewport.MaxDepth = 1.0;
viewport.MinDepth = 0.0;
draw->SetViewports(1, &viewport);
draw->SetTargetSize(pixel_xres, pixel_yres);
}
}
void EmuScreen::postRender() {
Draw::DrawContext *draw = screenManager()->getDrawContext();
if (!draw)
return;
draw->EndFrame();
}
void EmuScreen::render() {
using namespace Draw;
@ -979,23 +1005,6 @@ void EmuScreen::render() {
}
}
bool useBufferedRendering = g_Config.iRenderingMode != FB_NON_BUFFERED_MODE;
if (!useBufferedRendering) {
DrawContext *draw = screenManager()->getDrawContext();
draw->Clear(FBChannel::FB_COLOR_BIT | FBChannel::FB_DEPTH_BIT | FBChannel::FB_STENCIL_BIT, 0xFF000000, 0.0f, 0);
Viewport viewport;
viewport.TopLeftX = 0;
viewport.TopLeftY = 0;
viewport.Width = pixel_xres;
viewport.Height = pixel_yres;
viewport.MaxDepth = 1.0;
viewport.MinDepth = 0.0;
draw->SetViewports(1, &viewport);
draw->SetTargetSize(pixel_xres, pixel_yres);
}
PSP_BeginHostFrame();
// We just run the CPU until we get to vblank. This will quickly sync up pretty nicely.
@ -1018,6 +1027,8 @@ void EmuScreen::render() {
if (invalid_)
return;
// Here the backbuffer will always be bound.
if (!osm.IsEmpty() || g_Config.bShowDebugStats || g_Config.iShowFPSCounter || g_Config.bShowTouchControls || g_Config.bShowDeveloperMenu || g_Config.bShowAudioDebug || saveStatePreview_->GetVisibility() != UI::V_GONE || g_Config.bShowFrameProfiler) {
DrawContext *thin3d = screenManager()->getDrawContext();

View File

@ -37,6 +37,8 @@ public:
void update() override;
void render() override;
void preRender() override;
void postRender() override;
void deviceLost() override;
void deviceRestore() override;
void dialogFinished(const Screen *dialog, DialogResult result) override;

View File

@ -720,6 +720,7 @@ std::shared_ptr<GameInfo> GameInfoCache::GetInfo(Draw::DrawContext *draw, const
if (info->IsWorking()) {
// Uh oh, it's currently in process. It could mark pending = false with the wrong wantFlags.
// Let's wait it out, then queue.
// NOTE: This is bad because we're likely on the UI thread....
WaitUntilDone(info);
}

View File

@ -187,7 +187,7 @@ void GameSettingsScreen::CreateViews() {
renderingBackendChoice->HideChoice(2); // D3D11
}
#endif
#if !defined(_WIN32)
#if !defined(_WIN32) && !PPSSPP_PLATFORM(ANDROID)
// TODO: Add dynamic runtime check for Vulkan support on Android
renderingBackendChoice->HideChoice(3);
#endif

View File

@ -510,7 +510,7 @@ void LogoScreen::render() {
dc.DrawTextShadow(boot_filename.c_str(), bounds.centerX(), bounds.centerY() + 180, textColor, ALIGN_CENTER);
}
#if defined(_WIN32) && !PPSSPP_PLATFORM(UWP)
#if (defined(_WIN32) && !PPSSPP_PLATFORM(UWP)) || PPSSPP_PLATFORM(ANDROID)
// Draw the graphics API, except on UWP where it's always D3D11
dc.DrawText(screenManager()->getDrawContext()->GetInfoString(InfoField::APINAME).c_str(), bounds.centerX(), bounds.y2() - 100, textColor, ALIGN_CENTER);
#endif

View File

@ -588,6 +588,8 @@ static void UIThemeInit() {
ui_theme.popupStyle = MakeStyle(g_Config.uPopupStyleFg, g_Config.uPopupStyleBg);
}
void RenderOverlays(UIContext *dc, void *userdata);
void NativeInitGraphics(GraphicsContext *graphicsContext) {
ILOG("NativeInitGraphics");
@ -652,6 +654,7 @@ void NativeInitGraphics(GraphicsContext *graphicsContext) {
screenManager->setUIContext(uiContext);
screenManager->setDrawContext(g_draw);
screenManager->setPostRenderCallback(&RenderOverlays, nullptr);
UIBackgroundInit(*uiContext);
@ -737,7 +740,7 @@ void TakeScreenshot() {
#endif
}
void DrawDownloadsOverlay(UIContext &dc) {
void RenderOverlays(UIContext *dc, void *userdata) {
// Thin bar at the top of the screen like Chrome.
std::vector<float> progress = g_DownloadManager.GetCurrentProgress();
if (progress.empty()) {
@ -751,27 +754,34 @@ void DrawDownloadsOverlay(UIContext &dc) {
0xFF777777,
};
dc.Begin();
dc->Begin();
int h = 5;
for (size_t i = 0; i < progress.size(); i++) {
float barWidth = 10 + (dc.GetBounds().w - 10) * progress[i];
float barWidth = 10 + (dc->GetBounds().w - 10) * progress[i];
Bounds bounds(0, h * i, barWidth, h);
UI::Drawable solid(colors[i & 3]);
dc.FillRect(solid, bounds);
dc->FillRect(solid, bounds);
}
dc->End();
dc->Flush();
if (g_TakeScreenshot) {
TakeScreenshot();
}
dc.End();
dc.Flush();
}
void NativeRender(GraphicsContext *graphicsContext) {
g_GameManager.Update();
// If uitexture gets reloaded, make sure we use the latest one.
// Not sure this happens anymore now that we tear down all graphics on app switches...
uiContext->FrameSetup(uiTexture->GetTexture());
float xres = dp_xres;
float yres = dp_yres;
// Apply the UIContext bounds as a 2D transformation matrix.
// TODO: This should be moved into the draw context...
Matrix4x4 ortho;
switch (GetGPUBackend()) {
case GPUBackend::VULKAN:
@ -798,19 +808,12 @@ void NativeRender(GraphicsContext *graphicsContext) {
ui_draw2d.PushDrawMatrix(ortho);
ui_draw2d_front.PushDrawMatrix(ortho);
// All actual rendering happen in here.
screenManager->render();
if (screenManager->getUIContext()->Text()) {
screenManager->getUIContext()->Text()->OncePerFrame();
}
// At this point, the vulkan context has been "ended" already, no more drawing can be done in this frame.
// TODO: Integrate the download overlay with the screen system
DrawDownloadsOverlay(*screenManager->getUIContext());
if (g_TakeScreenshot) {
TakeScreenshot();
}
if (resized) {
resized = false;

View File

@ -124,11 +124,18 @@ static VkBool32 VKAPI_CALL Vulkan_Dbg(VkDebugReportFlagsEXT msgFlags, VkDebugRep
}
message << "[" << pLayerPrefix << "] " << ObjTypeToString(objType) << " Code " << msgCode << " : " << pMsg << "\n";
if (msgCode == 2) // Useless perf warning
return false;
// This seems like a bogus result when submitting two command buffers in one go, one creating the image, the other one using it.
if (msgCode == 6 && startsWith(pMsg, "Cannot submit cmd buffer using image"))
return false;
if (msgCode == 11)
return false;
// Silence "invalid reads of buffer data" - usually just uninitialized color buffers that will immediately get cleared due to our
// lacking clearing optimizations.
if (msgCode == 15 && objType == VK_DEBUG_REPORT_OBJECT_TYPE_DEVICE_MEMORY_EXT)
return false;
#ifdef _WIN32
std::string msg = message.str();
@ -141,7 +148,7 @@ static VkBool32 VKAPI_CALL Vulkan_Dbg(VkDebugReportFlagsEXT msgFlags, VkDebugRep
MessageBoxA(NULL, message.str().c_str(), "Alert", MB_OK);
}
} else if (msgFlags & VK_DEBUG_REPORT_WARNING_BIT_EXT) {
if (options->breakOnWarning) {
if (options->breakOnWarning && IsDebuggerPresent()) {
DebugBreak();
}
}

View File

@ -321,6 +321,10 @@ enum class NativeObject {
BACKBUFFER_COLOR_TEX,
BACKBUFFER_DEPTH_TEX,
FEATURE_LEVEL,
COMPATIBLE_RENDERPASS,
CURRENT_RENDERPASS,
RENDERPASS_COMMANDBUFFER,
BOUND_TEXTURE_IMAGEVIEW,
};
enum FBColorDepth {
@ -562,6 +566,20 @@ struct TextureDesc {
std::vector<uint8_t *> initData;
};
enum class RPAction {
DONT_CARE,
CLEAR,
KEEP,
};
struct RenderPassInfo {
RPAction color;
RPAction depth;
uint32_t clearColor;
float clearDepth;
uint8_t clearStencil;
};
class DrawContext {
public:
virtual ~DrawContext();
@ -600,16 +618,20 @@ public:
virtual bool BlitFramebuffer(Framebuffer *src, int srcX1, int srcY1, int srcX2, int srcY2, Framebuffer *dst, int dstX1, int dstY1, int dstX2, int dstY2, int channelBits, FBBlitFilter filter) = 0;
// These functions should be self explanatory.
virtual void BindFramebufferAsRenderTarget(Framebuffer *fbo) = 0;
// Binding a zero render target means binding the backbuffer.
virtual void BindFramebufferAsRenderTarget(Framebuffer *fbo, const RenderPassInfo &rp) = 0;
// color must be 0, for now.
virtual void BindFramebufferAsTexture(Framebuffer *fbo, int binding, FBChannel channelBit, int attachment) = 0;
virtual void BindFramebufferForRead(Framebuffer *fbo) = 0;
virtual void BindBackbufferAsRenderTarget() = 0;
virtual uintptr_t GetFramebufferAPITexture(Framebuffer *fbo, int channelBits, int attachment) = 0;
virtual void GetFramebufferDimensions(Framebuffer *fbo, int *w, int *h) = 0;
// Useful in OpenGL ES to give hints about framebuffers on tiler GPUs.
virtual void InvalidateFramebuffer(Framebuffer *fbo) {}
// Dynamic state
virtual void SetScissorRect(int left, int top, int width, int height) = 0;
virtual void SetViewports(int count, Viewport *viewports) = 0;
@ -636,13 +658,14 @@ public:
virtual void DrawIndexed(int vertexCount, int offset) = 0;
virtual void DrawUP(const void *vdata, int vertexCount) = 0;
// Render pass management. Default implementations here.
virtual void Begin(bool clear, uint32_t colorval, float depthVal, int stencilVal) {
Clear(0xF, colorval, depthVal, stencilVal);
}
virtual void End() {}
// Frame management (for the purposes of sync and resource management, necessary with modern APIs). Default implementations here.
virtual void BeginFrame() {}
virtual void EndFrame() {}
// This should be avoided as much as possible, in favor of clearing when binding a render target, which is native
// on Vulkan.
virtual void Clear(int mask, uint32_t colorval, float depthVal, int stencilVal) = 0;
// Necessary to correctly flip scissor rectangles etc for OpenGL.
void SetTargetSize(int w, int h) {
targetWidth_ = w;
@ -654,6 +677,16 @@ public:
virtual void HandleEvent(Event ev, int width, int height, void *param1 = nullptr, void *param2 = nullptr) = 0;
// This flushes command buffers and waits for execution at the point of the end of the last
// renderpass that wrote to the requested framebuffer. This is needed before trying to read it back
// on modern APIs like Vulkan. Ifr the framebuffer is currently being rendered to, we'll just end the render pass.
// The next draw call will automatically start up a new one.
// APIs like OpenGL won't need to implement this one.
virtual void WaitRenderCompletion(Framebuffer *fbo) {}
// Flush state like scissors etc so the caller can do its own custom drawing.
virtual void FlushState() {}
protected:
void CreatePresets();

View File

@ -58,12 +58,11 @@ public:
bool BlitFramebuffer(Framebuffer *src, int srcX1, int srcY1, int srcX2, int srcY2, Framebuffer *dst, int dstX1, int dstY1, int dstX2, int dstY2, int channelBits, FBBlitFilter filter) override;
// These functions should be self explanatory.
void BindFramebufferAsRenderTarget(Framebuffer *fbo) override;
void BindFramebufferAsRenderTarget(Framebuffer *fbo, const RenderPassInfo &rp) override;
// color must be 0, for now.
void BindFramebufferAsTexture(Framebuffer *fbo, int binding, FBChannel channelBit, int attachment) override;
void BindFramebufferForRead(Framebuffer *fbo) override;
void BindBackbufferAsRenderTarget() override;
uintptr_t GetFramebufferAPITexture(Framebuffer *fbo, int channelBit, int attachment) override;
void GetFramebufferDimensions(Framebuffer *fbo, int *w, int *h) override;
@ -1287,18 +1286,36 @@ bool D3D11DrawContext::BlitFramebuffer(Framebuffer *srcfb, int srcX1, int srcY1,
return false;
}
// These functions should be self explanatory.
void D3D11DrawContext::BindFramebufferAsRenderTarget(Framebuffer *fbo) {
void D3D11DrawContext::BindFramebufferAsRenderTarget(Framebuffer *fbo, const RenderPassInfo &rp) {
// TODO: deviceContext1 can actually discard. Useful on Windows Mobile.
D3D11Framebuffer *fb = (D3D11Framebuffer *)fbo;
if (curRenderTargetView_ == fb->colorRTView && curDepthStencilView_ == fb->depthStencilRTView) {
return;
if (fbo) {
D3D11Framebuffer *fb = (D3D11Framebuffer *)fbo;
if (curRenderTargetView_ == fb->colorRTView && curDepthStencilView_ == fb->depthStencilRTView) {
return;
}
context_->OMSetRenderTargets(1, &fb->colorRTView, fb->depthStencilRTView);
curRenderTargetView_ = fb->colorRTView;
curDepthStencilView_ = fb->depthStencilRTView;
curRTWidth_ = fb->width;
curRTHeight_ = fb->height;
} else {
if (curRenderTargetView_ == bbRenderTargetView_ && curDepthStencilView_ == bbDepthStencilView_)
return;
context_->OMSetRenderTargets(1, &bbRenderTargetView_, bbDepthStencilView_);
curRenderTargetView_ = bbRenderTargetView_;
curDepthStencilView_ = bbDepthStencilView_;
curRTWidth_ = bbWidth_;
curRTHeight_ = bbHeight_;
}
if (rp.color == RPAction::CLEAR && curRenderTargetView_) {
float cv[4]{};
if (rp.clearColor)
Uint8x4ToFloat4(cv, rp.clearColor);
context_->ClearRenderTargetView(curRenderTargetView_, cv);
}
if (rp.depth == RPAction::CLEAR && curDepthStencilView_) {
context_->ClearDepthStencilView(curDepthStencilView_, D3D11_CLEAR_DEPTH | D3D11_CLEAR_STENCIL, rp.clearDepth, rp.clearStencil);
}
context_->OMSetRenderTargets(1, &fb->colorRTView, fb->depthStencilRTView);
curRenderTargetView_ = fb->colorRTView;
curDepthStencilView_ = fb->depthStencilRTView;
curRTWidth_ = fb->width;
curRTHeight_ = fb->height;
}
// color must be 0, for now.
@ -1311,16 +1328,6 @@ void D3D11DrawContext::BindFramebufferForRead(Framebuffer *fbo) {
// This is meaningless in D3D11
}
void D3D11DrawContext::BindBackbufferAsRenderTarget() {
if (curRenderTargetView_ == bbRenderTargetView_ && curDepthStencilView_ == bbDepthStencilView_)
return;
context_->OMSetRenderTargets(1, &bbRenderTargetView_, bbDepthStencilView_);
curRenderTargetView_ = bbRenderTargetView_;
curDepthStencilView_ = bbDepthStencilView_;
curRTWidth_ = bbWidth_;
curRTHeight_ = bbHeight_;
}
uintptr_t D3D11DrawContext::GetFramebufferAPITexture(Framebuffer *fbo, int channelBit, int attachment) {
D3D11Framebuffer *fb = (D3D11Framebuffer *)fbo;
switch (channelBit) {

View File

@ -487,12 +487,11 @@ public:
bool BlitFramebuffer(Framebuffer *src, int srcX1, int srcY1, int srcX2, int srcY2, Framebuffer *dst, int dstX1, int dstY1, int dstX2, int dstY2, int channelBits, FBBlitFilter filter) override;
// These functions should be self explanatory.
void BindFramebufferAsRenderTarget(Framebuffer *fbo) override;
void BindFramebufferAsRenderTarget(Framebuffer *fbo, const RenderPassInfo &rp) override;
// color must be 0, for now.
void BindFramebufferAsTexture(Framebuffer *fbo, int binding, FBChannel channelBit, int attachment) override;
void BindFramebufferForRead(Framebuffer *fbo) override {}
void BindBackbufferAsRenderTarget() override;
uintptr_t GetFramebufferAPITexture(Framebuffer *fbo, int channelBits, int attachment) override;
void GetFramebufferDimensions(Framebuffer *fbo, int *w, int *h) override;
@ -1044,21 +1043,30 @@ D3D9Framebuffer::~D3D9Framebuffer() {
}
}
void D3D9Context::BindBackbufferAsRenderTarget() {
void D3D9Context::BindFramebufferAsRenderTarget(Framebuffer *fbo, const RenderPassInfo &rp) {
using namespace DX9;
if (fbo) {
D3D9Framebuffer *fb = (D3D9Framebuffer *)fbo;
device_->SetRenderTarget(0, fb->surf);
device_->SetDepthStencilSurface(fb->depthstencil);
} else {
device_->SetRenderTarget(0, deviceRTsurf);
device_->SetDepthStencilSurface(deviceDSsurf);
}
device_->SetRenderTarget(0, deviceRTsurf);
device_->SetDepthStencilSurface(deviceDSsurf);
dxstate.scissorRect.restore();
dxstate.viewport.restore();
}
int clearFlags = 0;
if (rp.color == RPAction::CLEAR) {
clearFlags |= D3DCLEAR_TARGET;
}
if (rp.depth == RPAction::CLEAR) {
clearFlags |= D3DCLEAR_ZBUFFER | D3DCLEAR_STENCIL;
}
if (clearFlags) {
dxstate.scissorTest.force(false);
device_->Clear(0, nullptr, clearFlags, (D3DCOLOR)SwapRB(rp.clearColor), rp.clearDepth, rp.clearStencil);
dxstate.scissorRect.restore();
}
void D3D9Context::BindFramebufferAsRenderTarget(Framebuffer *fbo) {
using namespace DX9;
D3D9Framebuffer *fb = (D3D9Framebuffer *)fbo;
device_->SetRenderTarget(0, fb->surf);
device_->SetDepthStencilSurface(fb->depthstencil);
dxstate.scissorRect.restore();
dxstate.viewport.restore();
}

View File

@ -465,12 +465,11 @@ public:
bool BlitFramebuffer(Framebuffer *src, int srcX1, int srcY1, int srcX2, int srcY2, Framebuffer *dst, int dstX1, int dstY1, int dstX2, int dstY2, int channelBits, FBBlitFilter filter) override;
// These functions should be self explanatory.
void BindFramebufferAsRenderTarget(Framebuffer *fbo) override;
void BindFramebufferAsRenderTarget(Framebuffer *fbo, const RenderPassInfo &rp) override;
// color must be 0, for now.
void BindFramebufferAsTexture(Framebuffer *fbo, int binding, FBChannel channelBit, int attachment) override;
void BindFramebufferForRead(Framebuffer *fbo) override;
void BindBackbufferAsRenderTarget() override;
uintptr_t GetFramebufferAPITexture(Framebuffer *fbo, int channelBits, int attachment) override;
void GetFramebufferDimensions(Framebuffer *fbo, int *w, int *h) override;
@ -1469,6 +1468,7 @@ Framebuffer *OpenGLContext::CreateFramebuffer(const FramebufferDesc &desc) {
FLOG("Other framebuffer error: %i", status);
break;
}
// Unbind state we don't need
glBindRenderbuffer(GL_RENDERBUFFER, 0);
glBindTexture(GL_TEXTURE_2D, 0);
@ -1534,20 +1534,49 @@ void OpenGLContext::fbo_unbind() {
currentReadHandle_ = 0;
}
void OpenGLContext::BindFramebufferAsRenderTarget(Framebuffer *fbo) {
OpenGLFramebuffer *fb = (OpenGLFramebuffer *)fbo;
// Without FBO_ARB / GLES3, this will collide with bind_for_read, but there's nothing
// in ES 2.0 that actually separate them anyway of course, so doesn't matter.
fbo_bind_fb_target(false, fb->handle);
// Always restore viewport after render target binding
// TODO: Should we set viewports this way too?
glstate.viewport.restore();
void OpenGLContext::BindFramebufferAsRenderTarget(Framebuffer *fbo, const RenderPassInfo &rp) {
CHECK_GL_ERROR_IF_DEBUG();
}
void OpenGLContext::BindBackbufferAsRenderTarget() {
CHECK_GL_ERROR_IF_DEBUG();
fbo_unbind();
if (fbo) {
OpenGLFramebuffer *fb = (OpenGLFramebuffer *)fbo;
// Without FBO_ARB / GLES3, this will collide with bind_for_read, but there's nothing
// in ES 2.0 that actually separate them anyway of course, so doesn't matter.
fbo_bind_fb_target(false, fb->handle);
// Always restore viewport after render target binding. Works around driver bugs.
glstate.viewport.restore();
} else {
fbo_unbind();
}
int clearFlags = 0;
if (rp.color == RPAction::CLEAR) {
float fc[4]{};
if (rp.clearColor) {
Uint8x4ToFloat4(fc, rp.clearColor);
}
glClearColor(fc[0], fc[1], fc[2], fc[3]);
clearFlags |= GL_COLOR_BUFFER_BIT;
glstate.colorMask.force(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
}
if (rp.depth == RPAction::CLEAR) {
glClearDepthf(rp.clearDepth);
glClearStencil(rp.clearStencil);
clearFlags |= GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT;
glstate.depthWrite.force(GL_TRUE);
glstate.stencilFunc.force(GL_ALWAYS, 0, 0);
glstate.stencilMask.force(0xFF);
}
if (clearFlags) {
glstate.scissorTest.force(false);
glClear(clearFlags);
glstate.scissorTest.restore();
}
if (rp.color == RPAction::CLEAR) {
glstate.colorMask.restore();
}
if (rp.depth == RPAction::CLEAR) {
glstate.depthWrite.restore();
glstate.stencilFunc.restore();
glstate.stencilMask.restore();
}
CHECK_GL_ERROR_IF_DEBUG();
}
@ -1609,8 +1638,10 @@ bool OpenGLContext::BlitFramebuffer(Framebuffer *fbsrc, int srcX1, int srcY1, in
bits |= GL_DEPTH_BUFFER_BIT;
if (channels & FB_STENCIL_BIT)
bits |= GL_STENCIL_BUFFER_BIT;
BindFramebufferAsRenderTarget(dst);
BindFramebufferForRead(src);
// Without FBO_ARB / GLES3, this will collide with bind_for_read, but there's nothing
// in ES 2.0 that actually separate them anyway of course, so doesn't matter.
fbo_bind_fb_target(false, dst->handle);
fbo_bind_fb_target(true, src->handle);
if (gl_extensions.GLES3 || gl_extensions.ARB_framebuffer_object) {
glBlitFramebuffer(srcX1, srcY1, srcX2, srcY2, dstX1, dstY1, dstX2, dstY2, bits, linearFilter == FB_BLIT_LINEAR ? GL_LINEAR : GL_NEAREST);
CHECK_GL_ERROR_IF_DEBUG();

File diff suppressed because it is too large Load Diff

View File

@ -125,12 +125,16 @@ void ScreenManager::render() {
backback.screen->preRender();
backback.screen->render();
stack_.back().screen->render();
if (postRenderCb_)
postRenderCb_(getUIContext(), postRenderUserdata_);
backback.screen->postRender();
break;
}
default:
stack_.back().screen->preRender();
stack_.back().screen->render();
if (postRenderCb_)
postRenderCb_(getUIContext(), postRenderUserdata_);
stack_.back().screen->postRender();
break;
}

View File

@ -92,6 +92,8 @@ enum {
LAYER_TRANSPARENT = 2,
};
typedef void(*PostRenderCallback)(UIContext *ui, void *userdata);
class ScreenManager {
public:
ScreenManager();
@ -106,6 +108,11 @@ public:
void setDrawContext(Draw::DrawContext *context) { thin3DContext_ = context; }
Draw::DrawContext *getDrawContext() { return thin3DContext_; }
void setPostRenderCallback(PostRenderCallback cb, void *userdata) {
postRenderCb_ = cb;
postRenderUserdata_ = userdata;
}
void render();
void resized();
void deviceLost();
@ -142,6 +149,9 @@ private:
UIContext *uiContext_;
Draw::DrawContext *thin3DContext_;
PostRenderCallback postRenderCb_ = nullptr;
void *postRenderUserdata_ = nullptr;
const Screen *dialogFinished_;
DialogResult dialogResult_;

View File

@ -61,11 +61,14 @@ void UIScreen::update() {
}
void UIScreen::preRender() {
using namespace Draw;
Draw::DrawContext *draw = screenManager()->getDrawContext();
if (!draw) {
return;
}
draw->Begin(true, 0xFF000000, 0.0f, 0);
draw->BeginFrame();
// Bind and clear the back buffer
draw->BindFramebufferAsRenderTarget(nullptr, { RPAction::CLEAR, RPAction::CLEAR, 0xFF000000 });
Draw::Viewport viewport;
viewport.TopLeftX = 0;
@ -83,7 +86,7 @@ void UIScreen::postRender() {
if (!draw) {
return;
}
draw->End();
draw->EndFrame();
}
void UIScreen::render() {

@ -1 +1 @@
Subproject commit e18cface3db64ccb96738dc128fe769b28fff65c
Subproject commit d02ba7407050f445edf9e908374ad4bf3b2f237b