diff --git a/CMakeLists.txt b/CMakeLists.txt index 079a8f222c..7cbe623fb3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -830,7 +830,14 @@ if(ANDROID) set(nativeExtra ${nativeExtra} ${NativeAppSource}) endif() -set(THIN3D_PLATFORMS ext/native/thin3d/thin3d_gl.cpp) +set(THIN3D_PLATFORMS ext/native/thin3d/thin3d_gl.cpp + ext/native/thin3d/GLRenderManager.cpp + ext/native/thin3d/GLRenderManager.h + ext/native/thin3d/GLQueueRunner.cpp + ext/native/thin3d/GLQueueRunner.h + ext/native/thin3d/DataFormatGL.cpp + ext/native/thin3d/DataFormatGL.h) + set(THIN3D_PLATFORMS ${THIN3D_PLATFORMS} ext/native/thin3d/thin3d_vulkan.cpp ext/native/thin3d/VulkanRenderManager.cpp @@ -887,8 +894,6 @@ add_library(native STATIC ext/native/file/vfs.h ext/native/file/zip_read.cpp ext/native/file/zip_read.h - ext/native/gfx/GLStateCache.cpp - ext/native/gfx/GLStateCache.h ext/native/gfx/gl_common.h ext/native/gfx/gl_debug_log.cpp ext/native/gfx/gl_debug_log.h diff --git a/Common/GraphicsContext.h b/Common/GraphicsContext.h index c8495b04be..66afcd94da 100644 --- a/Common/GraphicsContext.h +++ b/Common/GraphicsContext.h @@ -10,6 +10,9 @@ class GraphicsContext { public: virtual ~GraphicsContext() {} + virtual bool InitFromRenderThread(std::string *errorMessage) { return true; } + virtual void ShutdownFromRenderThread() {} + virtual void Shutdown() = 0; virtual void SwapInterval(int interval) = 0; @@ -25,6 +28,11 @@ public: // Needs casting to the appropriate type, unfortunately. Should find a better solution.. virtual void *GetAPIContext() { return nullptr; } + // Called from the render thread from threaded backends. + virtual void ThreadStart() {} + virtual bool ThreadFrame() { return true; } + virtual void ThreadEnd() {} + virtual Draw::DrawContext *GetDrawContext() = 0; }; diff --git a/Common/Vulkan/VulkanContext.cpp b/Common/Vulkan/VulkanContext.cpp index 6f3cfb5d55..8854970e9d 100644 --- a/Common/Vulkan/VulkanContext.cpp +++ b/Common/Vulkan/VulkanContext.cpp @@ -1090,22 +1090,22 @@ const char *VulkanResultToString(VkResult res) { } void VulkanDeleteList::Take(VulkanDeleteList &del) { - assert(cmdPools_.size() == 0); - assert(descPools_.size() == 0); - assert(modules_.size() == 0); - assert(buffers_.size() == 0); - assert(bufferViews_.size() == 0); - assert(images_.size() == 0); - assert(imageViews_.size() == 0); - assert(deviceMemory_.size() == 0); - assert(samplers_.size() == 0); - assert(pipelines_.size() == 0); - assert(pipelineCaches_.size() == 0); - assert(renderPasses_.size() == 0); - assert(framebuffers_.size() == 0); - assert(pipelineLayouts_.size() == 0); - assert(descSetLayouts_.size() == 0); - assert(callbacks_.size() == 0); + assert(cmdPools_.empty()); + assert(descPools_.empty()); + assert(modules_.empty()); + assert(buffers_.empty()); + assert(bufferViews_.empty()); + assert(images_.empty()); + assert(imageViews_.empty()); + assert(deviceMemory_.empty()); + assert(samplers_.empty()); + assert(pipelines_.empty()); + assert(pipelineCaches_.empty()); + assert(renderPasses_.empty()); + assert(framebuffers_.empty()); + assert(pipelineLayouts_.empty()); + assert(descSetLayouts_.empty()); + assert(callbacks_.empty()); cmdPools_ = std::move(del.cmdPools_); descPools_ = std::move(del.descPools_); modules_ = std::move(del.modules_); diff --git a/Common/Vulkan/VulkanMemory.cpp b/Common/Vulkan/VulkanMemory.cpp index 0950bc8596..1410d985a1 100644 --- a/Common/Vulkan/VulkanMemory.cpp +++ b/Common/Vulkan/VulkanMemory.cpp @@ -75,9 +75,8 @@ bool VulkanPushBuffer::AddBuffer() { return false; } - buf_ = buffers_.size(); - buffers_.resize(buf_ + 1); - buffers_[buf_] = info; + buffers_.push_back(info); + buf_ = buffers_.size() - 1; return true; } diff --git a/Core/Core.h b/Core/Core.h index 98262f7beb..9adbd6356e 100644 --- a/Core/Core.h +++ b/Core/Core.h @@ -24,11 +24,15 @@ class GraphicsContext; // called from emu thread void UpdateRunLoop(); + void Core_Run(GraphicsContext *ctx); void Core_Stop(); void Core_ErrorPause(); // For platforms that don't call Core_Run void Core_SetGraphicsContext(GraphicsContext *ctx); + +void Core_RunRenderThreadFrame(); + // called from gui void Core_EnableStepping(bool step); void Core_DoSingleStep(); diff --git a/Core/System.cpp b/Core/System.cpp index cc41c39313..772f00e877 100644 --- a/Core/System.cpp +++ b/Core/System.cpp @@ -81,12 +81,7 @@ ParamSFOData g_paramSFO; static GlobalUIState globalUIState; static CoreParameter coreParameter; static FileLoader *loadedFile; -static std::thread *cpuThread = nullptr; -static std::thread::id cpuThreadID; -static std::mutex cpuThreadLock; -static std::condition_variable cpuThreadCond; -static std::condition_variable cpuThreadReplyCond; -static u64 cpuThreadUntil; + bool audioInitialized; bool coreCollectDebugStats = false; @@ -142,45 +137,6 @@ void Audio_Shutdown() { } } -bool IsOnSeparateCPUThread() { - if (cpuThread != nullptr) { - return cpuThreadID == std::this_thread::get_id(); - } else { - return false; - } -} - -void CPU_SetStateNoLock(CPUThreadState to) { - cpuThreadState = to; - cpuThreadCond.notify_one(); - cpuThreadReplyCond.notify_one(); -} - -void CPU_SetState(CPUThreadState to) { - std::lock_guard guard(cpuThreadLock); - CPU_SetStateNoLock(to); -} - -bool CPU_NextState(CPUThreadState from, CPUThreadState to) { - std::lock_guard guard(cpuThreadLock); - if (cpuThreadState == from) { - CPU_SetStateNoLock(to); - return true; - } else { - return false; - } -} - -bool CPU_NextStateNot(CPUThreadState from, CPUThreadState to) { - std::lock_guard guard(cpuThreadLock); - if (cpuThreadState != from) { - CPU_SetStateNoLock(to); - return true; - } else { - return false; - } -} - bool CPU_IsReady() { if (coreState == CORE_POWERUP) return false; @@ -195,13 +151,6 @@ bool CPU_HasPendingAction() { return cpuThreadState != CPU_THREAD_RUNNING; } -void CPU_WaitStatus(std::condition_variable &cond, bool (*pred)()) { - std::unique_lock guard(cpuThreadLock); - while (!pred()) { - cond.wait(guard); - } -} - void CPU_Shutdown(); void CPU_Init() { @@ -278,7 +227,6 @@ void CPU_Init() { if (!LoadFile(&loadedFile, &coreParameter.errorString)) { CPU_Shutdown(); coreParameter.fileToStart = ""; - CPU_SetState(CPU_THREAD_NOT_RUNNING); return; } @@ -321,52 +269,6 @@ void UpdateLoadedFile(FileLoader *fileLoader) { loadedFile = fileLoader; } -void CPU_RunLoop() { - setCurrentThreadName("CPU"); - - if (CPU_NextState(CPU_THREAD_PENDING, CPU_THREAD_STARTING)) { - CPU_Init(); - CPU_NextState(CPU_THREAD_STARTING, CPU_THREAD_RUNNING); - } else if (!CPU_NextState(CPU_THREAD_RESUME, CPU_THREAD_RUNNING)) { - ERROR_LOG(CPU, "CPU thread in unexpected state: %d", cpuThreadState); - return; - } - - while (cpuThreadState != CPU_THREAD_SHUTDOWN) - { - CPU_WaitStatus(cpuThreadCond, &CPU_HasPendingAction); - switch (cpuThreadState) { - case CPU_THREAD_EXECUTE: - mipsr4k.RunLoopUntil(cpuThreadUntil); - CPU_NextState(CPU_THREAD_EXECUTE, CPU_THREAD_RUNNING); - break; - - // These are fine, just keep looping. - case CPU_THREAD_RUNNING: - case CPU_THREAD_SHUTDOWN: - break; - - case CPU_THREAD_QUIT: - // Just leave the thread, CPU is switching off thread. - CPU_SetState(CPU_THREAD_NOT_RUNNING); - return; - - default: - ERROR_LOG(CPU, "CPU thread in unexpected state: %d", cpuThreadState); - // Begin shutdown, otherwise we'd just spin on this bad state. - CPU_SetState(CPU_THREAD_SHUTDOWN); - break; - } - } - - if (coreState != CORE_ERROR) { - coreState = CORE_POWERDOWN; - } - - CPU_Shutdown(); - CPU_SetState(CPU_THREAD_NOT_RUNNING); -} - void Core_UpdateState(CoreState newState) { if ((coreState == CORE_RUNNING || coreState == CORE_NEXTFRAME) && newState != CORE_RUNNING) coreStatePending = true; @@ -384,11 +286,6 @@ void Core_UpdateDebugStats(bool collectStats) { gpuStats.ResetFrame(); } -void System_Wake() { - // Ping the threads so they check coreState. - CPU_NextStateNot(CPU_THREAD_NOT_RUNNING, CPU_THREAD_SHUTDOWN); -} - // Ugly! static bool pspIsInited = false; static bool pspIsIniting = false; @@ -415,17 +312,7 @@ bool PSP_InitStart(const CoreParameter &coreParam, std::string *error_string) { coreParameter.errorString = ""; pspIsIniting = true; - // Keeping this around because we might need it in the future. - const bool separateCPUThread = false; - if (separateCPUThread) { - Core_ListenShutdown(System_Wake); - CPU_SetState(CPU_THREAD_PENDING); - cpuThread = new std::thread(&CPU_RunLoop); - cpuThreadID = cpuThread->get_id(); - cpuThread->detach(); - } else { - CPU_Init(); - } + CPU_Init(); *error_string = coreParameter.errorString; bool success = coreParameter.fileToStart != ""; @@ -461,11 +348,6 @@ bool PSP_InitUpdate(std::string *error_string) { bool PSP_Init(const CoreParameter &coreParam, std::string *error_string) { PSP_InitStart(coreParam, error_string); - // For a potential resurrection of separate CPU thread later. - if (false) { - CPU_WaitStatus(cpuThreadReplyCond, &CPU_IsReady); - } - while (!PSP_InitUpdate(error_string)) sleep_ms(10); return pspIsInited; @@ -496,15 +378,7 @@ void PSP_Shutdown() { if (coreState == CORE_RUNNING) Core_UpdateState(CORE_ERROR); Core_NotifyShutdown(); - if (cpuThread != nullptr) { - CPU_NextStateNot(CPU_THREAD_NOT_RUNNING, CPU_THREAD_SHUTDOWN); - CPU_WaitStatus(cpuThreadReplyCond, &CPU_IsShutdown); - delete cpuThread; - cpuThread = 0; - cpuThreadID = std::thread::id(); - } else { - CPU_Shutdown(); - } + CPU_Shutdown(); GPU_Shutdown(); g_paramSFO.Clear(); host->SetWindowTitle(0); @@ -534,45 +408,7 @@ void PSP_RunLoopUntil(u64 globalticks) { return; } - // We no longer allow a separate CPU thread but if we add a render queue - // to GL we're gonna need it. - bool useCPUThread = false; - if (useCPUThread && cpuThread == nullptr) { - // Need to start the cpu thread. - Core_ListenShutdown(System_Wake); - CPU_SetState(CPU_THREAD_RESUME); - cpuThread = new std::thread(&CPU_RunLoop); - cpuThreadID = cpuThread->get_id(); - cpuThread->detach(); - // Probably needs to tell the gpu that it will need to queue up its output - // on another thread. - CPU_WaitStatus(cpuThreadReplyCond, &CPU_IsReady); - } else if (!useCPUThread && cpuThread != nullptr) { - CPU_SetState(CPU_THREAD_QUIT); - CPU_WaitStatus(cpuThreadReplyCond, &CPU_IsShutdown); - delete cpuThread; - cpuThread = nullptr; - cpuThreadID = std::thread::id(); - } - - if (cpuThread != nullptr) { - cpuThreadUntil = globalticks; - if (CPU_NextState(CPU_THREAD_RUNNING, CPU_THREAD_EXECUTE)) { - // The CPU doesn't actually respect cpuThreadUntil well, especially when skipping frames. - // TODO: Something smarter? Or force CPU to bail periodically? - while (!CPU_IsReady()) { - // Have the GPU do stuff here. - if (coreState != CORE_RUNNING) { - CPU_WaitStatus(cpuThreadReplyCond, &CPU_IsReady); - } - } - } else { - ERROR_LOG(CPU, "Unable to execute CPU run loop, unexpected state: %d", cpuThreadState); - } - } else { - mipsr4k.RunLoopUntil(globalticks); - } - + mipsr4k.RunLoopUntil(globalticks); gpu->CleanupBeforeUI(); } diff --git a/Core/System.h b/Core/System.h index 9cc4cfdc89..6017e88cc5 100644 --- a/Core/System.h +++ b/Core/System.h @@ -78,8 +78,6 @@ void Core_UpdateDebugStats(bool collectStats); void Audio_Init(); void Audio_Shutdown(); - -bool IsOnSeparateCPUThread(); bool IsAudioInitialised(); void UpdateLoadedFile(FileLoader *fileLoader); diff --git a/GPU/Common/DrawEngineCommon.h b/GPU/Common/DrawEngineCommon.h index 961e5dad5e..24599a094b 100644 --- a/GPU/Common/DrawEngineCommon.h +++ b/GPU/Common/DrawEngineCommon.h @@ -170,6 +170,7 @@ protected: colStride = 4; } virtual void SendDataToShader(const float *pos, const float *tex, const float *col, int size, bool hasColor, bool hasTexCoords) = 0; + virtual void EndFrame() {} }; TessellationDataTransfer *tessDataTransfer; }; diff --git a/GPU/Common/FramebufferCommon.cpp b/GPU/Common/FramebufferCommon.cpp index 94072a85b2..3f93329d22 100644 --- a/GPU/Common/FramebufferCommon.cpp +++ b/GPU/Common/FramebufferCommon.cpp @@ -208,6 +208,7 @@ void FramebufferManagerCommon::SetNumExtraFBOs(int num) { extraFBOs_.push_back(fbo); } currentRenderVfb_ = 0; + // TODO: Should probably not do this bind. if (num != 0) draw_->BindFramebufferAsRenderTarget(nullptr, { Draw::RPAction::KEEP, Draw::RPAction::KEEP, Draw::RPAction::KEEP }); } @@ -608,6 +609,8 @@ void FramebufferManagerCommon::NotifyRenderFramebufferSwitched(VirtualFramebuffe // 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, Draw::RPAction::CLEAR }); + // GLES resets the blend state on clears. + gstate_c.Dirty(DIRTY_BLEND_STATE); } else { draw_->BindFramebufferAsRenderTarget(vfb->fbo, { Draw::RPAction::KEEP, Draw::RPAction::KEEP, Draw::RPAction::KEEP }); } @@ -718,8 +721,6 @@ void FramebufferManagerCommon::DrawPixels(VirtualFramebuffer *vfb, int dstX, int float u0 = 0.0f, u1 = 1.0f; float v0 = 0.0f, v1 = 1.0f; - MakePixelTexture(srcPixels, srcPixelFormat, srcStride, width, height, u1, v1); - if (useBufferedRendering_ && vfb && vfb->fbo) { draw_->BindFramebufferAsRenderTarget(vfb->fbo, { Draw::RPAction::KEEP, Draw::RPAction::KEEP, Draw::RPAction::KEEP }); SetViewport2D(0, 0, vfb->renderWidth, vfb->renderHeight); @@ -735,6 +736,8 @@ void FramebufferManagerCommon::DrawPixels(VirtualFramebuffer *vfb, int dstX, int } DisableState(); + MakePixelTexture(srcPixels, srcPixelFormat, srcStride, width, height, u1, v1); + DrawTextureFlags flags = (vfb || g_Config.iBufFilter == SCALE_LINEAR) ? DRAWTEX_LINEAR : DRAWTEX_NEAREST; Bind2DShader(); DrawActiveTexture(dstX, dstY, width, height, vfb->bufferWidth, vfb->bufferHeight, u0, v0, u1, v1, ROTATION_LOCKED_HORIZONTAL, flags); @@ -851,6 +854,7 @@ void FramebufferManagerCommon::CopyDisplayToOutput() { if (useBufferedRendering_) { draw_->BindFramebufferAsRenderTarget(nullptr, { Draw::RPAction::CLEAR, Draw::RPAction::CLEAR, Draw::RPAction::CLEAR }); } + gstate_c.Dirty(DIRTY_BLEND_STATE); return; } @@ -920,6 +924,7 @@ void FramebufferManagerCommon::CopyDisplayToOutput() { SetViewport2D(0, 0, pixelWidth_, pixelHeight_); draw_->SetScissorRect(0, 0, pixelWidth_, pixelHeight_); DrawFramebufferToOutput(Memory::GetPointer(displayFramebufPtr_), displayFormat_, displayStride_, true); + gstate_c.Dirty(DIRTY_BLEND_STATE); return; } } else { @@ -929,6 +934,7 @@ void FramebufferManagerCommon::CopyDisplayToOutput() { // 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, Draw::RPAction::CLEAR }); } + gstate_c.Dirty(DIRTY_BLEND_STATE); return; } } @@ -1031,13 +1037,6 @@ void FramebufferManagerCommon::CopyDisplayToOutput() { SetViewport2D(0, 0, pixelWidth_, pixelHeight_); DrawActiveTexture(x, y, w, h, (float)pixelWidth_, (float)pixelHeight_, u0, v0, u1, v1, uvRotation, flags); } - - /* - if (gl_extensions.GLES3 && glInvalidateFramebuffer != nullptr) { - draw_->BindFramebufferAsRenderTarget(extraFBOs_[0], { Draw::RPAction::CLEAR, 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::RPAction::CLEAR }); draw_->BindFramebufferAsTexture(vfb->fbo, 0, Draw::FB_COLOR_BIT, 0); @@ -1070,6 +1069,9 @@ void FramebufferManagerCommon::CopyDisplayToOutput() { else if (useBufferedRendering_) { WARN_LOG(FRAMEBUF, "Current VFB lacks an FBO: %08x", vfb->fb_address); } + + // This may get called mid-draw if the game uses an immediate flip. + gstate_c.Dirty(DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_BLEND_STATE); } void FramebufferManagerCommon::DecimateFBOs() { @@ -1191,6 +1193,8 @@ void FramebufferManagerCommon::ResizeFramebufFBO(VirtualFramebuffer *vfb, int w, INFO_LOG(FRAMEBUF, "Resizing FBO for %08x : %d x %d x %d", vfb->fb_address, w, h, vfb->format); if (vfb->fbo) { draw_->BindFramebufferAsRenderTarget(vfb->fbo, { Draw::RPAction::CLEAR, Draw::RPAction::CLEAR, Draw::RPAction::CLEAR }); + // GLES resets the blend state on clears. + gstate_c.Dirty(DIRTY_BLEND_STATE); if (!skipCopy && !g_Config.bDisableSlowFramebufEffects) { BlitFramebuffer(vfb, 0, 0, &old, 0, 0, std::min((u16)oldWidth, std::min(vfb->bufferWidth, vfb->width)), std::min((u16)oldHeight, std::min(vfb->height, vfb->bufferHeight)), 0); } @@ -1201,6 +1205,8 @@ void FramebufferManagerCommon::ResizeFramebufFBO(VirtualFramebuffer *vfb, int w, } } else { draw_->BindFramebufferAsRenderTarget(vfb->fbo, { Draw::RPAction::CLEAR, Draw::RPAction::CLEAR, Draw::RPAction::CLEAR }); + // GLES resets the blend state on clears. + gstate_c.Dirty(DIRTY_BLEND_STATE); } if (!vfb->fbo) { @@ -1936,9 +1942,8 @@ bool FramebufferManagerCommon::GetFramebuffer(u32 fb_address, int fb_stride, GEB buffer.Allocate(w, h, GE_FORMAT_8888, flipY, true); bool retval = draw_->CopyFramebufferToMemorySync(bound, Draw::FB_COLOR_BIT, 0, 0, w, h, Draw::DataFormat::R8G8B8A8_UNORM, buffer.GetData(), w); gpuStats.numReadbacks++; - // Vulkan requires us to re-apply all dynamic state for each command buffer, and the above will cause us to start a new cmdbuf. - // So let's dirty the things that are involved in Vulkan dynamic state. Readbacks are not frequent so this won't hurt other backends. - gstate_c.Dirty(DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_BLEND_STATE | DIRTY_DEPTHSTENCIL_STATE); + // After a readback we'll have flushed and started over, need to dirty a bunch of things to be safe. + gstate_c.Dirty(DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_BLEND_STATE | DIRTY_DEPTHSTENCIL_STATE | DIRTY_RASTER_STATE | DIRTY_TEXTURE_IMAGE | DIRTY_TEXTURE_PARAMS); // We may have blitted to a temp FBO. RebindFramebuffer(); return retval; @@ -1972,6 +1977,8 @@ bool FramebufferManagerCommon::GetDepthbuffer(u32 fb_address, int fb_stride, u32 } // No need to free on failure, that's the caller's job (it likely will reuse a buffer.) bool retval = draw_->CopyFramebufferToMemorySync(vfb->fbo, Draw::FB_DEPTH_BIT, 0, 0, w, h, Draw::DataFormat::D32F, buffer.GetData(), w); + // After a readback we'll have flushed and started over, need to dirty a bunch of things to be safe. + gstate_c.Dirty(DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_BLEND_STATE | DIRTY_DEPTHSTENCIL_STATE | DIRTY_RASTER_STATE | DIRTY_TEXTURE_IMAGE | DIRTY_TEXTURE_PARAMS); // That may have unbound the framebuffer, rebind to avoid crashes when debugging. RebindFramebuffer(); return retval; diff --git a/GPU/Common/FramebufferCommon.h b/GPU/Common/FramebufferCommon.h index 78005a1126..9ab59a4b0b 100644 --- a/GPU/Common/FramebufferCommon.h +++ b/GPU/Common/FramebufferCommon.h @@ -206,7 +206,7 @@ public: return vfb; } } - virtual void RebindFramebuffer(); + void RebindFramebuffer(); std::vector GetFramebufferList(); void CopyDisplayToOutput(); @@ -222,9 +222,9 @@ public: bool NotifyBlockTransferBefore(u32 dstBasePtr, int dstStride, int dstX, int dstY, u32 srcBasePtr, int srcStride, int srcX, int srcY, int w, int h, int bpp, u32 skipDrawReason); void NotifyBlockTransferAfter(u32 dstBasePtr, int dstStride, int dstX, int dstY, u32 srcBasePtr, int srcStride, int srcX, int srcY, int w, int h, int bpp, u32 skipDrawReason); - virtual void ReadFramebufferToMemory(VirtualFramebuffer *vfb, bool sync, int x, int y, int w, int h); + void ReadFramebufferToMemory(VirtualFramebuffer *vfb, bool sync, int x, int y, int w, int h); - virtual void DownloadFramebufferForClut(u32 fb_address, u32 loadBytes); + void DownloadFramebufferForClut(u32 fb_address, u32 loadBytes); void DrawFramebufferToOutput(const u8 *srcPixels, GEBufferFormat srcPixelFormat, int srcStride, bool applyPostShader); void DrawPixels(VirtualFramebuffer *vfb, int dstX, int dstY, const u8 *srcPixels, GEBufferFormat srcPixelFormat, int srcStride, int width, int height); diff --git a/GPU/Common/GPUStateUtils.cpp b/GPU/Common/GPUStateUtils.cpp index 6060449807..d1091b9a3e 100644 --- a/GPU/Common/GPUStateUtils.cpp +++ b/GPU/Common/GPUStateUtils.cpp @@ -576,17 +576,11 @@ void ConvertViewportAndScissor(bool useBufferedRendering, float renderWidth, flo // This is a bit of a hack as the render buffer isn't always that size // We always scissor on non-buffered so that clears don't spill outside the frame. - if (useBufferedRendering && scissorX1 == 0 && scissorY1 == 0 - && scissorX2 >= (int)gstate_c.curRTWidth - && scissorY2 >= (int)gstate_c.curRTHeight) { - out.scissorEnable = false; - } else { - out.scissorEnable = true; - out.scissorX = renderX + displayOffsetX + scissorX1 * renderWidthFactor; - out.scissorY = renderY + displayOffsetY + scissorY1 * renderHeightFactor; - out.scissorW = (scissorX2 - scissorX1) * renderWidthFactor; - out.scissorH = (scissorY2 - scissorY1) * renderHeightFactor; - } + out.scissorEnable = true; + out.scissorX = renderX + displayOffsetX + scissorX1 * renderWidthFactor; + out.scissorY = renderY + displayOffsetY + scissorY1 * renderHeightFactor; + out.scissorW = (scissorX2 - scissorX1) * renderWidthFactor; + out.scissorH = (scissorY2 - scissorY1) * renderHeightFactor; int curRTWidth = gstate_c.curRTWidth; int curRTHeight = gstate_c.curRTHeight; diff --git a/GPU/Common/TextureCacheCommon.cpp b/GPU/Common/TextureCacheCommon.cpp index 1485a333a3..75f9a5d14c 100644 --- a/GPU/Common/TextureCacheCommon.cpp +++ b/GPU/Common/TextureCacheCommon.cpp @@ -383,7 +383,7 @@ void TextureCacheCommon::SetTexture(bool force) { // Exponential backoff up to 512 frames. Textures are often reused. if (entry->numFrames > 32) { // Also, try to add some "randomness" to avoid rehashing several textures the same frame. - entry->framesUntilNextFullHash = std::min(512, entry->numFrames) + (entry->textureName & 15); + entry->framesUntilNextFullHash = std::min(512, entry->numFrames) + (((intptr_t)(entry->textureName) >> 12) & 15); } else { entry->framesUntilNextFullHash = entry->numFrames; } diff --git a/GPU/Common/TextureCacheCommon.h b/GPU/Common/TextureCacheCommon.h index 31aa6eeb97..1bc16a2541 100644 --- a/GPU/Common/TextureCacheCommon.h +++ b/GPU/Common/TextureCacheCommon.h @@ -87,6 +87,8 @@ struct SamplerCacheKey { } }; +class GLRTexture; + // TODO: Shrink this struct. There is some fluff. struct TexCacheEntry { ~TexCacheEntry() { @@ -132,7 +134,7 @@ struct TexCacheEntry { u16 dim; u16 bufw; union { - u32 textureName; + GLRTexture *textureName; void *texturePtr; CachedTextureVulkan *vkTex; }; @@ -146,16 +148,8 @@ struct TexCacheEntry { u32 framesUntilNextFullHash; u32 fullhash; u32 cluthash; - float lodBias; u16 maxSeenV; - // Cache the current filter settings so we can avoid setting it again. - // (OpenGL ES 2.0 madness where filter settings are attached to each texture. Unused in other backends). - u8 magFilt; - u8 minFilt; - bool sClamp; - bool tClamp; - TexStatus GetHashStatus() { return TexStatus(status & STATUS_MASK); } diff --git a/GPU/D3D11/FramebufferManagerD3D11.cpp b/GPU/D3D11/FramebufferManagerD3D11.cpp index 6da4b477c1..c45ec45cd5 100644 --- a/GPU/D3D11/FramebufferManagerD3D11.cpp +++ b/GPU/D3D11/FramebufferManagerD3D11.cpp @@ -459,15 +459,6 @@ void FramebufferManagerD3D11::BindPostShader(const PostShaderUniforms &uniforms) context_->PSSetConstantBuffers(0, 1, &postConstants_); } -void FramebufferManagerD3D11::RebindFramebuffer() { - if (currentRenderVfb_ && currentRenderVfb_->fbo) { - draw_->BindFramebufferAsRenderTarget(currentRenderVfb_->fbo, { Draw::RPAction::KEEP, Draw::RPAction::KEEP, Draw::RPAction::KEEP }); - } else { - // Should this even happen? - draw_->BindFramebufferAsRenderTarget(nullptr, { Draw::RPAction::KEEP, Draw::RPAction::KEEP, Draw::RPAction::KEEP }); - } -} - void FramebufferManagerD3D11::ReformatFramebufferFrom(VirtualFramebuffer *vfb, GEBufferFormat old) { if (!useBufferedRendering_ || !vfb->fbo) { return; diff --git a/GPU/D3D11/FramebufferManagerD3D11.h b/GPU/D3D11/FramebufferManagerD3D11.h index 508e995e52..d951d60e7f 100644 --- a/GPU/D3D11/FramebufferManagerD3D11.h +++ b/GPU/D3D11/FramebufferManagerD3D11.h @@ -59,8 +59,6 @@ public: virtual bool NotifyStencilUpload(u32 addr, int size, bool skipZero = false) override; - virtual void RebindFramebuffer() override; - // TODO: Remove ID3D11Buffer *GetDynamicQuadBuffer() { return quadBuffer_; diff --git a/GPU/D3D11/StateMappingD3D11.cpp b/GPU/D3D11/StateMappingD3D11.cpp index 002a118fa4..ee76b37322 100644 --- a/GPU/D3D11/StateMappingD3D11.cpp +++ b/GPU/D3D11/StateMappingD3D11.cpp @@ -462,4 +462,8 @@ void DrawEngineD3D11::ApplyDrawStateLate(bool applyStencilRef, uint8_t stencilRe context_->OMSetDepthStencilState(depthStencilState_, applyStencilRef ? stencilRef : dynState_.stencilRef); } gstate_c.Clean(DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_DEPTHSTENCIL_STATE | DIRTY_RASTER_STATE | DIRTY_BLEND_STATE); + + // Must dirty blend state here so we re-copy next time. Example: Lunar's spell effects. + if (fboTexBound_) + gstate_c.Dirty(DIRTY_BLEND_STATE); } diff --git a/GPU/D3D11/TextureCacheD3D11.cpp b/GPU/D3D11/TextureCacheD3D11.cpp index 425d8b738b..fc12d417db 100644 --- a/GPU/D3D11/TextureCacheD3D11.cpp +++ b/GPU/D3D11/TextureCacheD3D11.cpp @@ -775,16 +775,18 @@ bool TextureCacheD3D11::DecodeTexture(u8 *output, const GPUgstate &state) { } bool TextureCacheD3D11::GetCurrentTextureDebug(GPUDebugBuffer &buffer, int level) { - ApplyTexture(); SetTexture(false); if (!nextTexture_) return false; + // Apply texture may need to rebuild the texture if we're about to render, or bind a framebuffer. + TexCacheEntry *entry = nextTexture_; + ApplyTexture(); + // TODO: Centralize. - if (nextTexture_->framebuffer) { - VirtualFramebuffer *vfb = nextTexture_->framebuffer; - bool flipY = GetGPUBackend() == GPUBackend::OPENGL && g_Config.iRenderingMode != FB_NON_BUFFERED_MODE; - buffer.Allocate(vfb->bufferWidth, vfb->bufferHeight, GPU_DBG_FORMAT_8888, flipY); + if (entry->framebuffer) { + VirtualFramebuffer *vfb = entry->framebuffer; + buffer.Allocate(vfb->bufferWidth, vfb->bufferHeight, GPU_DBG_FORMAT_8888, false); bool retval = draw_->CopyFramebufferToMemorySync(vfb->fbo, Draw::FB_COLOR_BIT, 0, 0, vfb->bufferWidth, vfb->bufferHeight, Draw::DataFormat::R8G8B8A8_UNORM, buffer.GetData(), vfb->bufferWidth); // Vulkan requires us to re-apply all dynamic state for each command buffer, and the above will cause us to start a new cmdbuf. // So let's dirty the things that are involved in Vulkan dynamic state. Readbacks are not frequent so this won't hurt other backends. @@ -794,7 +796,7 @@ bool TextureCacheD3D11::GetCurrentTextureDebug(GPUDebugBuffer &buffer, int level return retval; } - ID3D11Texture2D *texture = (ID3D11Texture2D *)nextTexture_->texturePtr; + ID3D11Texture2D *texture = (ID3D11Texture2D *)entry->texturePtr; if (!texture) return false; diff --git a/GPU/Directx9/TextureCacheDX9.cpp b/GPU/Directx9/TextureCacheDX9.cpp index 123eebd0ff..53e69baa56 100644 --- a/GPU/Directx9/TextureCacheDX9.cpp +++ b/GPU/Directx9/TextureCacheDX9.cpp @@ -171,7 +171,6 @@ void TextureCacheDX9::UpdateSamplingParams(TexCacheEntry &entry, bool force) { dxstate.texMaxMipLevel.set(0); dxstate.texMipLodBias.set(0.0f); } - entry.lodBias = lodBias; } else { dxstate.texMaxMipLevel.set(0); dxstate.texMipLodBias.set(0.0f); diff --git a/GPU/GLES/DepalettizeShaderGLES.cpp b/GPU/GLES/DepalettizeShaderGLES.cpp index 71ef33d4c2..49e1b36f3c 100644 --- a/GPU/GLES/DepalettizeShaderGLES.cpp +++ b/GPU/GLES/DepalettizeShaderGLES.cpp @@ -23,7 +23,6 @@ #include "Core/Reporting.h" #include "DepalettizeShaderGLES.h" #include "GPU/GLES/TextureCacheGLES.h" -#include "ext/native/gfx/GLStateCache.h" #include "GPU/Common/DepalettizeShaderCommon.h" #ifdef _WIN32 @@ -58,38 +57,8 @@ static const char *depalVShader300 = " gl_Position = a_position;\n" "}\n"; - -static bool CheckShaderCompileSuccess(GLuint shader, const char *code) { - GLint success; - glGetShaderiv(shader, GL_COMPILE_STATUS, &success); - if (!success) { -#define MAX_INFO_LOG_SIZE 2048 - GLchar infoLog[MAX_INFO_LOG_SIZE]; - GLsizei len; - glGetShaderInfoLog(shader, MAX_INFO_LOG_SIZE, &len, infoLog); - infoLog[len] = '\0'; -#ifdef __ANDROID__ - ELOG("Error in shader compilation! %s\n", infoLog); - ELOG("Shader source:\n%s\n", (const char *)code); -#endif - ERROR_LOG(G3D, "Error in shader compilation!\n"); - ERROR_LOG(G3D, "Info log: %s\n", infoLog); - ERROR_LOG(G3D, "Shader source:\n%s\n", (const char *)code); -#ifdef SHADERLOG - OutputDebugStringUTF8(infoLog); -#endif - shader = 0; - return false; - } else { - DEBUG_LOG(G3D, "Compiled shader:\n%s\n", (const char *)code); -#ifdef SHADERLOG - OutputDebugStringUTF8(code); -#endif - return true; - } -} - -DepalShaderCacheGLES::DepalShaderCacheGLES() { +DepalShaderCacheGLES::DepalShaderCacheGLES(Draw::DrawContext *draw) { + render_ = (GLRenderManager *)draw->GetNativeObject(Draw::NativeObject::RENDER_MANAGER); // Pre-build the vertex program useGL3_ = gl_extensions.GLES3 || gl_extensions.VersionGEThan(3, 3); @@ -102,25 +71,12 @@ DepalShaderCacheGLES::~DepalShaderCacheGLES() { } bool DepalShaderCacheGLES::CreateVertexShader() { - if (vertexShaderFailed_) { - return false; - } - - vertexShader_ = glCreateShader(GL_VERTEX_SHADER); - glShaderSource(vertexShader_, 1, useGL3_ ? &depalVShader300 : &depalVShader100, 0); - glCompileShader(vertexShader_); - - if (!CheckShaderCompileSuccess(vertexShader_, useGL3_ ? depalVShader300 : depalVShader100)) { - glDeleteShader(vertexShader_); - vertexShader_ = 0; - // Don't try to recompile. - vertexShaderFailed_ = true; - } - - return !vertexShaderFailed_; + std::string src(useGL3_ ? depalVShader300 : depalVShader100); + vertexShader_ = render_->CreateShader(GL_VERTEX_SHADER, src, "depal"); + return true; } -GLuint DepalShaderCacheGLES::GetClutTexture(GEPaletteFormat clutFormat, const u32 clutHash, u32 *rawClut) { +GLRTexture *DepalShaderCacheGLES::GetClutTexture(GEPaletteFormat clutFormat, const u32 clutHash, u32 *rawClut) { u32 clutId = GetClutID(clutFormat, clutHash); auto oldtex = texCache_.find(clutId); @@ -133,18 +89,13 @@ GLuint DepalShaderCacheGLES::GetClutTexture(GEPaletteFormat clutFormat, const u3 int texturePixels = clutFormat == GE_CMODE_32BIT_ABGR8888 ? 256 : 512; DepalTexture *tex = new DepalTexture(); - glGenTextures(1, &tex->texture); - glBindTexture(GL_TEXTURE_2D, tex->texture); + tex->texture = render_->CreateTexture(GL_TEXTURE_2D); GLuint components = dstFmt == GL_UNSIGNED_SHORT_5_6_5 ? GL_RGB : GL_RGBA; - GLuint components2 = components; - glTexImage2D(GL_TEXTURE_2D, 0, components, texturePixels, 1, 0, components2, dstFmt, (void *)rawClut); - - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + uint8_t *clutCopy = new uint8_t[1024]; + memcpy(clutCopy, rawClut, 1024); + render_->TextureImage(tex->texture, 0, texturePixels, 1, components, components2, dstFmt, clutCopy, false); tex->lastFrame = gpuStats.numFlips; texCache_[clutId] = tex; @@ -153,20 +104,20 @@ GLuint DepalShaderCacheGLES::GetClutTexture(GEPaletteFormat clutFormat, const u3 void DepalShaderCacheGLES::Clear() { for (auto shader = cache_.begin(); shader != cache_.end(); ++shader) { - glDeleteShader(shader->second->fragShader); + render_->DeleteShader(shader->second->fragShader); if (shader->second->program) { - glDeleteProgram(shader->second->program); + render_->DeleteProgram(shader->second->program); } delete shader->second; } cache_.clear(); for (auto tex = texCache_.begin(); tex != texCache_.end(); ++tex) { - glDeleteTextures(1, &tex->second->texture); + render_->DeleteTexture(tex->second->texture); delete tex->second; } texCache_.clear(); if (vertexShader_) { - glDeleteShader(vertexShader_); + render_->DeleteShader(vertexShader_); vertexShader_ = 0; } } @@ -174,7 +125,7 @@ void DepalShaderCacheGLES::Clear() { void DepalShaderCacheGLES::Decimate() { for (auto tex = texCache_.begin(); tex != texCache_.end(); ) { if (tex->second->lastFrame + DEPAL_TEXTURE_OLD_AGE < gpuStats.numFlips) { - glDeleteTextures(1, &tex->second->texture); + render_->DeleteTexture(tex->second->texture); delete tex->second; texCache_.erase(tex++); } else { @@ -188,10 +139,14 @@ DepalShader *DepalShaderCacheGLES::GetDepalettizeShader(uint32_t clutMode, GEBuf auto shader = cache_.find(id); if (shader != cache_.end()) { + DepalShader *depal = shader->second; + // If compile failed previously, try to recover. + if (depal->fragShader->failed || vertexShader_->failed) + return nullptr; return shader->second; } - if (vertexShader_ == 0) { + if (!vertexShader_) { if (!CreateVertexShader()) { // The vertex shader failed, no need to bother trying the fragment. return nullptr; @@ -201,64 +156,33 @@ DepalShader *DepalShaderCacheGLES::GetDepalettizeShader(uint32_t clutMode, GEBuf char *buffer = new char[2048]; GenerateDepalShader(buffer, pixelFormat, useGL3_ ? GLSL_300 : GLSL_140); - - GLuint fragShader = glCreateShader(GL_FRAGMENT_SHADER); - - const char *buf = buffer; - glShaderSource(fragShader, 1, &buf, 0); - glCompileShader(fragShader); - - CheckShaderCompileSuccess(fragShader, buffer); - - GLuint program = glCreateProgram(); - glAttachShader(program, vertexShader_); - glAttachShader(program, fragShader); - glBindAttribLocation(program, 0, "a_position"); - glBindAttribLocation(program, 1, "a_texcoord0"); - - glLinkProgram(program); - glUseProgram(program); - - GLint u_tex = glGetUniformLocation(program, "tex"); - GLint u_pal = glGetUniformLocation(program, "pal"); - - glUniform1i(u_tex, 0); - glUniform1i(u_pal, 3); + std::string src(buffer); + GLRShader *fragShader = render_->CreateShader(GL_FRAGMENT_SHADER, src, "depal"); DepalShader *depal = new DepalShader(); + + std::vector semantics; + semantics.push_back({ 0, "a_position" }); + semantics.push_back({ 1, "a_texcoord0" }); + + std::vector queries; + queries.push_back({ &depal->u_tex, "tex" }); + queries.push_back({ &depal->u_pal, "pal" }); + + std::vector initializer; + initializer.push_back({ &depal->u_tex, 0, 0 }); + initializer.push_back({ &depal->u_pal, 0, 3 }); + + std::vector shaders{ vertexShader_, fragShader }; + + GLRProgram *program = render_->CreateProgram(shaders, semantics, queries, initializer, false); + depal->program = program; depal->fragShader = fragShader; depal->code = buffer; cache_[id] = depal; - GLint linkStatus = GL_FALSE; - glGetProgramiv(program, GL_LINK_STATUS, &linkStatus); - if (linkStatus != GL_TRUE) { - GLint bufLength = 0; - glGetProgramiv(program, GL_INFO_LOG_LENGTH, &bufLength); - if (bufLength) { - char* errorbuf = new char[bufLength]; - glGetProgramInfoLog(program, bufLength, NULL, errorbuf); -#ifdef SHADERLOG - OutputDebugStringUTF8(buffer); - OutputDebugStringUTF8(errorbuf); -#endif - ERROR_LOG(G3D, "Could not link program:\n %s \n\n %s", errorbuf, buf); - delete[] errorbuf; // we're dead! - } - - // Since it failed, let's mark it in the cache so we don't keep retrying. - // That will only make it slower. - depal->program = 0; - - // We will delete the shader later in Clear(). - glDeleteProgram(program); - } else { - depal->a_position = glGetAttribLocation(program, "a_position"); - depal->a_texcoord0 = glGetAttribLocation(program, "a_texcoord0"); - } - delete[] buffer; return depal->program ? depal : nullptr; } diff --git a/GPU/GLES/DepalettizeShaderGLES.h b/GPU/GLES/DepalettizeShaderGLES.h index a32acb2cb3..6fd9749fec 100644 --- a/GPU/GLES/DepalettizeShaderGLES.h +++ b/GPU/GLES/DepalettizeShaderGLES.h @@ -19,34 +19,36 @@ #include "Common/CommonTypes.h" #include "gfx/gl_common.h" +#include "thin3d/thin3d.h" +#include "thin3d/GLRenderManager.h" #include "GPU/ge_constants.h" #include "GPU/Common/ShaderCommon.h" #include "GPU/Common/DepalettizeShaderCommon.h" class DepalShader { public: - GLuint program; - GLuint fragShader; - GLint a_position; - GLint a_texcoord0; + GLRProgram *program; + GLRShader *fragShader; + GLint u_tex; + GLint u_pal; std::string code; }; class DepalTexture { public: - GLuint texture; + GLRTexture *texture; int lastFrame; }; // Caches both shaders and palette textures. class DepalShaderCacheGLES : public DepalShaderCacheCommon { public: - DepalShaderCacheGLES(); + DepalShaderCacheGLES(Draw::DrawContext *draw); ~DepalShaderCacheGLES(); // This also uploads the palette and binds the correct texture. DepalShader *GetDepalettizeShader(uint32_t clutMode, GEBufferFormat pixelFormat); - GLuint GetClutTexture(GEPaletteFormat clutFormat, const u32 clutHash, u32 *rawClut); + GLRTexture *GetClutTexture(GEPaletteFormat clutFormat, const u32 clutHash, u32 *rawClut); void Clear(); void Decimate(); std::vector DebugGetShaderIDs(DebugShaderType type); @@ -55,9 +57,10 @@ public: private: bool CreateVertexShader(); + GLRenderManager *render_; bool useGL3_; bool vertexShaderFailed_; - GLuint vertexShader_; + GLRShader *vertexShader_; std::map cache_; std::map texCache_; }; diff --git a/GPU/GLES/DrawEngineGLES.cpp b/GPU/GLES/DrawEngineGLES.cpp index 7cba85b02b..77717aea19 100644 --- a/GPU/GLES/DrawEngineGLES.cpp +++ b/GPU/GLES/DrawEngineGLES.cpp @@ -83,7 +83,6 @@ #include "GPU/Common/SplineCommon.h" #include "GPU/Common/VertexDecoderCommon.h" #include "GPU/Common/SoftwareTransformCommon.h" -#include "ext/native/gfx/GLStateCache.h" #include "GPU/GLES/FragmentTestCacheGLES.h" #include "GPU/GLES/StateMappingGLES.h" #include "GPU/GLES/TextureCacheGLES.h" @@ -91,7 +90,7 @@ #include "GPU/GLES/ShaderManagerGLES.h" #include "GPU/GLES/GPU_GLES.h" -extern const GLuint glprim[8] = { +const GLuint glprim[8] = { GL_POINTS, GL_LINES, GL_LINE_STRIP, @@ -115,7 +114,8 @@ enum { enum { VAI_KILL_AGE = 120, VAI_UNRELIABLE_KILL_AGE = 240, VAI_UNRELIABLE_KILL_MAX = 4 }; -DrawEngineGLES::DrawEngineGLES() : vai_(256) { +DrawEngineGLES::DrawEngineGLES(Draw::DrawContext *draw) : vai_(256), draw_(draw), inputLayoutMap_(16) { + render_ = (GLRenderManager *)draw_->GetNativeObject(Draw::NativeObject::RENDER_MANAGER); decOptions_.expandAllWeightsToFloat = false; decOptions_.expand8BitNormalsToFloat = false; @@ -133,7 +133,7 @@ DrawEngineGLES::DrawEngineGLES() : vai_(256) { InitDeviceObjects(); - tessDataTransfer = new TessellationDataTransferGLES(gl_extensions.VersionGEThan(3, 0, 0)); + tessDataTransfer = new TessellationDataTransferGLES(render_); } DrawEngineGLES::~DrawEngineGLES() { @@ -145,17 +145,6 @@ DrawEngineGLES::~DrawEngineGLES() { delete tessDataTransfer; } -void DrawEngineGLES::RestoreVAO() { - if (sharedVao_ != 0) { - glBindVertexArray(sharedVao_); - } else if (gstate_c.Supports(GPU_SUPPORTS_VAO)) { - // Note: this is here because, InitDeviceObjects() is called before GPU_SUPPORTS_VAO is setup. - // So, this establishes it if Supports() returns true and there isn't one yet. - glGenVertexArrays(1, &sharedVao_); - glBindVertexArray(sharedVao_); - } -} - void DrawEngineGLES::DeviceLost() { DestroyDeviceObjects(); } @@ -165,35 +154,64 @@ void DrawEngineGLES::DeviceRestore() { } void DrawEngineGLES::InitDeviceObjects() { - if (bufferNameCache_.empty()) { - bufferNameCache_.resize(VERTEXCACHE_NAME_CACHE_SIZE); - glGenBuffers(VERTEXCACHE_NAME_CACHE_SIZE, &bufferNameCache_[0]); - bufferNameCacheSize_ = 0; + for (int i = 0; i < GLRenderManager::MAX_INFLIGHT_FRAMES; i++) { + frameData_[i].pushVertex = new GLPushBuffer(render_, GL_ARRAY_BUFFER, 1024 * 1024); + frameData_[i].pushIndex = new GLPushBuffer(render_, GL_ELEMENT_ARRAY_BUFFER, 256 * 1024); - if (gstate_c.Supports(GPU_SUPPORTS_VAO)) { - glGenVertexArrays(1, &sharedVao_); - } else { - sharedVao_ = 0; - } - } else { - ERROR_LOG(G3D, "Device objects already initialized!"); + render_->RegisterPushBuffer(i, frameData_[i].pushVertex); + render_->RegisterPushBuffer(i, frameData_[i].pushIndex); } + + int vertexSize = sizeof(TransformedVertex); + std::vector entries; + entries.push_back({ ATTR_POSITION, 4, GL_FLOAT, GL_FALSE, vertexSize, 0 }); + entries.push_back({ ATTR_TEXCOORD, 3, GL_FLOAT, GL_FALSE, vertexSize, offsetof(TransformedVertex, u) }); + entries.push_back({ ATTR_COLOR0, 4, GL_UNSIGNED_BYTE, GL_TRUE, vertexSize, offsetof(TransformedVertex, color0) }); + entries.push_back({ ATTR_COLOR1, 3, GL_UNSIGNED_BYTE, GL_TRUE, vertexSize, offsetof(TransformedVertex, color1) }); + softwareInputLayout_ = render_->CreateInputLayout(entries); } void DrawEngineGLES::DestroyDeviceObjects() { - ClearTrackedVertexArrays(); - if (!bufferNameCache_.empty()) { - glstate.arrayBuffer.unbind(); - glstate.elementArrayBuffer.unbind(); - glDeleteBuffers((GLsizei)bufferNameCache_.size(), &bufferNameCache_[0]); - bufferNameCache_.clear(); - bufferNameInfo_.clear(); - freeSizedBuffers_.clear(); - bufferNameCacheSize_ = 0; - if (sharedVao_ != 0) { - glDeleteVertexArrays(1, &sharedVao_); - } + // Beware: this could be called twice in a row, sometimes. + for (int i = 0; i < GLRenderManager::MAX_INFLIGHT_FRAMES; i++) { + if (!frameData_[i].pushVertex && !frameData_[i].pushIndex) + continue; + + render_->UnregisterPushBuffer(i, frameData_[i].pushVertex); + render_->UnregisterPushBuffer(i, frameData_[i].pushIndex); + frameData_[i].pushVertex->Destroy(); + frameData_[i].pushIndex->Destroy(); + delete frameData_[i].pushVertex; + delete frameData_[i].pushIndex; + frameData_[i].pushVertex = nullptr; + frameData_[i].pushIndex = nullptr; } + + ClearTrackedVertexArrays(); + + if (softwareInputLayout_) + render_->DeleteInputLayout(softwareInputLayout_); + softwareInputLayout_ = nullptr; +} + +void DrawEngineGLES::ClearInputLayoutMap() { + inputLayoutMap_.Iterate([&](const uint32_t &key, GLRInputLayout *il) { + render_->DeleteInputLayout(il); + }); + inputLayoutMap_.Clear(); +} + +void DrawEngineGLES::BeginFrame() { + FrameData &frameData = frameData_[render_->GetCurFrame()]; + frameData.pushIndex->Begin(); + frameData.pushVertex->Begin(); +} + +void DrawEngineGLES::EndFrame() { + FrameData &frameData = frameData_[render_->GetCurFrame()]; + frameData.pushIndex->End(); + frameData.pushVertex->End(); + tessDataTransfer->EndFrame(); } struct GlTypeInfo { @@ -220,24 +238,40 @@ static const GlTypeInfo GLComp[] = { {GL_UNSIGNED_SHORT, 4, GL_TRUE},// DEC_U16_4, }; -static inline void VertexAttribSetup(int attrib, int fmt, int stride, u8 *ptr) { +static inline void VertexAttribSetup(int attrib, int fmt, int stride, int offset, std::vector &entries) { if (fmt) { const GlTypeInfo &type = GLComp[fmt]; - glVertexAttribPointer(attrib, type.count, type.type, type.normalized, stride, ptr); + GLRInputLayout::Entry entry; + entry.offset = offset; + entry.location = attrib; + entry.normalized = type.normalized; + entry.type = type.type; + entry.stride = stride; + entry.count = type.count; + entries.push_back(entry); } } // TODO: Use VBO and get rid of the vertexData pointers - with that, we will supply only offsets -static void SetupDecFmtForDraw(LinkedShader *program, const DecVtxFormat &decFmt, u8 *vertexData) { - CHECK_GL_ERROR_IF_DEBUG(); - VertexAttribSetup(ATTR_W1, decFmt.w0fmt, decFmt.stride, vertexData + decFmt.w0off); - VertexAttribSetup(ATTR_W2, decFmt.w1fmt, decFmt.stride, vertexData + decFmt.w1off); - VertexAttribSetup(ATTR_TEXCOORD, decFmt.uvfmt, decFmt.stride, vertexData + decFmt.uvoff); - VertexAttribSetup(ATTR_COLOR0, decFmt.c0fmt, decFmt.stride, vertexData + decFmt.c0off); - VertexAttribSetup(ATTR_COLOR1, decFmt.c1fmt, decFmt.stride, vertexData + decFmt.c1off); - VertexAttribSetup(ATTR_NORMAL, decFmt.nrmfmt, decFmt.stride, vertexData + decFmt.nrmoff); - VertexAttribSetup(ATTR_POSITION, decFmt.posfmt, decFmt.stride, vertexData + decFmt.posoff); - CHECK_GL_ERROR_IF_DEBUG(); +GLRInputLayout *DrawEngineGLES::SetupDecFmtForDraw(LinkedShader *program, const DecVtxFormat &decFmt) { + uint32_t key = decFmt.id; + GLRInputLayout *inputLayout = inputLayoutMap_.Get(key); + if (inputLayout) { + return inputLayout; + } + + std::vector entries; + VertexAttribSetup(ATTR_W1, decFmt.w0fmt, decFmt.stride, decFmt.w0off, entries); + VertexAttribSetup(ATTR_W2, decFmt.w1fmt, decFmt.stride, decFmt.w1off, entries); + VertexAttribSetup(ATTR_TEXCOORD, decFmt.uvfmt, decFmt.stride, decFmt.uvoff, entries); + VertexAttribSetup(ATTR_COLOR0, decFmt.c0fmt, decFmt.stride, decFmt.c0off, entries); + VertexAttribSetup(ATTR_COLOR1, decFmt.c1fmt, decFmt.stride, decFmt.c1off, entries); + VertexAttribSetup(ATTR_NORMAL, decFmt.nrmfmt, decFmt.stride, decFmt.nrmoff, entries); + VertexAttribSetup(ATTR_POSITION, decFmt.posfmt, decFmt.stride, decFmt.posoff, entries); + + inputLayout = render_->CreateInputLayout(entries); + inputLayoutMap_.Insert(key, inputLayout); + return inputLayout; } void DrawEngineGLES::SubmitPrim(void *verts, void *inds, GEPrimitiveType prim, int vertexCount, u32 vertType, int *bytesRead) { @@ -304,14 +338,25 @@ void DrawEngineGLES::SubmitPrim(void *verts, void *inds, GEPrimitiveType prim, i } } +void DrawEngineGLES::DecodeVertsToPushBuffer(GLPushBuffer *push, uint32_t *bindOffset, GLRBuffer **buf) { + u8 *dest = decoded; + + // Figure out how much pushbuffer space we need to allocate. + if (push) { + int vertsToDecode = ComputeNumVertsToDecode(); + dest = (u8 *)push->Push(vertsToDecode * dec_->GetDecVtxFmt().stride, bindOffset, buf); + } + DecodeVerts(dest); +} + void DrawEngineGLES::MarkUnreliable(VertexArrayInfo *vai) { vai->status = VertexArrayInfo::VAI_UNRELIABLE; if (vai->vbo) { - FreeBuffer(vai->vbo); + render_->DeleteBuffer(vai->vbo); vai->vbo = 0; } if (vai->ebo) { - FreeBuffer(vai->ebo); + render_->DeleteBuffer(vai->ebo); vai->ebo = 0; } } @@ -351,99 +396,43 @@ void DrawEngineGLES::DecimateTrackedVertexArrays() { vai_.Maintain(); } -GLuint DrawEngineGLES::AllocateBuffer(size_t sz) { - GLuint unused = 0; - - auto freeMatch = freeSizedBuffers_.find(sz); - if (freeMatch != freeSizedBuffers_.end()) { - unused = freeMatch->second; - _assert_(!bufferNameInfo_[unused].used); - - freeSizedBuffers_.erase(freeMatch); - } else { - for (GLuint buf : bufferNameCache_) { - const BufferNameInfo &info = bufferNameInfo_[buf]; - if (info.used) { - continue; - } - - // Just pick the first unused one, we'll have to resize it. - unused = buf; - - // Let's also remove from the free list, if it's there. - if (info.sz != 0) { - auto range = freeSizedBuffers_.equal_range(info.sz); - for (auto it = range.first; it != range.second; ++it) { - if (it->second == buf) { - // It will only be once, so remove and bail. - freeSizedBuffers_.erase(it); - break; - } - } - } - break; - } - } - - if (unused == 0) { - size_t oldSize = bufferNameCache_.size(); - bufferNameCache_.resize(oldSize + VERTEXCACHE_NAME_CACHE_SIZE); - glGenBuffers(VERTEXCACHE_NAME_CACHE_SIZE, &bufferNameCache_[oldSize]); - - unused = bufferNameCache_[oldSize]; - } - - BufferNameInfo &info = bufferNameInfo_[unused]; - - // Record the change in size. - bufferNameCacheSize_ += sz - info.sz; - info.sz = sz; - info.used = true; - return unused; -} - -void DrawEngineGLES::FreeBuffer(GLuint buf) { - // We can reuse buffers by setting new data on them, so let's actually keep it. - auto it = bufferNameInfo_.find(buf); - if (it != bufferNameInfo_.end()) { - it->second.used = false; - it->second.lastFrame = gpuStats.numFlips; - - if (it->second.sz != 0) { - freeSizedBuffers_.insert(std::make_pair(it->second.sz, buf)); - } - } else { - ERROR_LOG(G3D, "Unexpected buffer freed (%d) but not tracked", buf); - } -} - void DrawEngineGLES::FreeVertexArray(VertexArrayInfo *vai) { if (vai->vbo) { - FreeBuffer(vai->vbo); - vai->vbo = 0; + render_->DeleteBuffer(vai->vbo); + vai->vbo = nullptr; } if (vai->ebo) { - FreeBuffer(vai->ebo); - vai->ebo = 0; + render_->DeleteBuffer(vai->ebo); + vai->ebo = nullptr; } } void DrawEngineGLES::DoFlush() { PROFILE_THIS_SCOPE("flush"); - CHECK_GL_ERROR_IF_DEBUG(); + FrameData &frameData = frameData_[render_->GetCurFrame()]; + gpuStats.numFlushes++; gpuStats.numTrackedVertexArrays = (int)vai_.size(); + bool textureNeedsApply = false; + if (gstate_c.IsDirty(DIRTY_TEXTURE_IMAGE | DIRTY_TEXTURE_PARAMS) && !gstate.isModeClear() && gstate.isTextureMapEnabled()) { + textureCache_->SetTexture(); + gstate_c.Clean(DIRTY_TEXTURE_IMAGE | DIRTY_TEXTURE_PARAMS); + textureNeedsApply = true; + } + GEPrimitiveType prim = prevPrim_; - ApplyDrawState(prim); - CHECK_GL_ERROR_IF_DEBUG(); VShaderID vsid; Shader *vshader = shaderManager_->ApplyVertexShader(prim, lastVType_, &vsid); + GLRBuffer *vertexBuffer = nullptr; + GLRBuffer *indexBuffer = nullptr; + uint32_t vertexBufferOffset = 0; + uint32_t indexBufferOffset = 0; + if (vshader->UseHWTransform()) { - GLuint vbo = 0, ebo = 0; int vertexCount = 0; bool useElements = true; @@ -453,6 +442,9 @@ void DrawEngineGLES::DoFlush() { if (g_Config.bSoftwareSkinning && (lastVType_ & GE_VTYPE_WEIGHT_MASK)) useCache = false; + // TEMPORARY + useCache = false; + if (useCache) { u32 id = dcid_ ^ gstate.getUVGenMode(); // This can have an effect on which UV decoder we need to use! And hence what the decoded data will look like. See #9263 VertexArrayInfo *vai = vai_.Get(id); @@ -534,31 +526,27 @@ void DrawEngineGLES::DoFlush() { _dbg_assert_msg_(G3D, gstate_c.vertBounds.minV >= gstate_c.vertBounds.maxV, "Should not have checked UVs when caching."); size_t vsz = dec_->GetDecVtxFmt().stride * indexGen.MaxIndex(); - vai->vbo = AllocateBuffer(vsz); - glstate.arrayBuffer.bind(vai->vbo); - glBufferData(GL_ARRAY_BUFFER, vsz, decoded, GL_STATIC_DRAW); + vai->vbo = render_->CreateBuffer(GL_ARRAY_BUFFER, vsz, GL_STATIC_DRAW); + render_->BufferSubdata(vai->vbo, 0, vsz, decoded); // If there's only been one primitive type, and it's either TRIANGLES, LINES or POINTS, // there is no need for the index buffer we built. We can then use glDrawArrays instead // for a very minor speed boost. if (useElements) { size_t esz = sizeof(short) * indexGen.VertexCount(); - vai->ebo = AllocateBuffer(esz); - glstate.elementArrayBuffer.bind(vai->ebo); - glBufferData(GL_ELEMENT_ARRAY_BUFFER, esz, (GLvoid *)decIndex, GL_STATIC_DRAW); + vai->ebo = render_->CreateBuffer(GL_ARRAY_BUFFER, esz, GL_STATIC_DRAW); + render_->BufferSubdata(vai->ebo, 0, esz, (uint8_t *)decIndex, false); } else { vai->ebo = 0; - glstate.elementArrayBuffer.bind(vai->ebo); + render_->BindIndexBuffer(vai->ebo); } } else { gpuStats.numCachedDrawCalls++; - glstate.arrayBuffer.bind(vai->vbo); - glstate.elementArrayBuffer.bind(vai->ebo); useElements = vai->ebo ? true : false; gpuStats.numCachedVertsDrawn += vai->numVerts; gstate_c.vertexFullAlpha = vai->flags & VAI_FLAG_VERTEXFULLALPHA; } - vbo = vai->vbo; - ebo = vai->ebo; + vertexBuffer = vai->vbo; + indexBuffer = vai->ebo; vertexCount = vai->numVerts; prim = static_cast(vai->prim); break; @@ -573,10 +561,8 @@ void DrawEngineGLES::DoFlush() { } gpuStats.numCachedDrawCalls++; gpuStats.numCachedVertsDrawn += vai->numVerts; - vbo = vai->vbo; - ebo = vai->ebo; - glstate.arrayBuffer.bind(vbo); - glstate.elementArrayBuffer.bind(ebo); + vertexBuffer = vai->vbo; + indexBuffer = vai->ebo; vertexCount = vai->numVerts; prim = static_cast(vai->prim); @@ -597,7 +583,15 @@ void DrawEngineGLES::DoFlush() { vai->lastFrame = gpuStats.numFlips; } else { - DecodeVerts(decoded); + if (g_Config.bSoftwareSkinning && (lastVType_ & GE_VTYPE_WEIGHT_MASK)) { + // If software skinning, we've already predecoded into "decoded". So push that content. + size_t size = decodedVerts_ * dec_->GetDecVtxFmt().stride; + u8 *dest = (u8 *)frameData.pushVertex->Push(size, &vertexBufferOffset, &vertexBuffer); + memcpy(dest, decoded, size); + } else { + // Decode directly into the pushbuffer + DecodeVertsToPushBuffer(frameData.pushVertex, &vertexBufferOffset, &vertexBuffer); + } rotateVBO: gpuStats.numUncachedVertsDrawn += indexGen.VertexCount(); @@ -606,9 +600,6 @@ rotateVBO: if (!useElements && indexGen.PureCount()) { vertexCount = indexGen.PureCount(); } - glstate.arrayBuffer.unbind(); - glstate.elementArrayBuffer.unbind(); - prim = indexGen.Prim(); } @@ -620,26 +611,27 @@ rotateVBO: gstate_c.vertexFullAlpha = gstate_c.vertexFullAlpha && ((hasColor && (gstate.materialupdate & 1)) || gstate.getMaterialAmbientA() == 255) && (!gstate.isLightingEnabled() || gstate.getAmbientA() == 255); } - ApplyDrawStateLate(); - - if (gstate_c.Supports(GPU_SUPPORTS_VAO) && vbo == 0) { - vbo = BindBuffer(decoded, dec_->GetDecVtxFmt().stride * indexGen.MaxIndex()); - if (useElements) { - ebo = BindElementBuffer(decIndex, sizeof(short) * indexGen.VertexCount()); - } - } + if (textureNeedsApply) + textureCache_->ApplyTexture(); + // Need to ApplyDrawState after ApplyTexture because depal can launch a render pass and that wrecks the state. + ApplyDrawState(prim); + ApplyDrawStateLate(false, 0); + LinkedShader *program = shaderManager_->ApplyFragmentShader(vsid, vshader, lastVType_, prim); - SetupDecFmtForDraw(program, dec_->GetDecVtxFmt(), vbo ? 0 : decoded); - + GLRInputLayout *inputLayout = SetupDecFmtForDraw(program, dec_->GetDecVtxFmt()); + render_->BindVertexBuffer(inputLayout, vertexBuffer, vertexBufferOffset); if (useElements) { + if (!indexBuffer) { + indexBufferOffset = (uint32_t)frameData.pushIndex->Push(decIndex, sizeof(uint16_t) * indexGen.VertexCount(), &indexBuffer); + render_->BindIndexBuffer(indexBuffer); + } if (gstate_c.bezier || gstate_c.spline) - // Instanced rendering for instanced tessellation - glDrawElementsInstanced(glprim[prim], vertexCount, GL_UNSIGNED_SHORT, ebo ? 0 : (GLvoid*)decIndex, numPatches); + render_->DrawIndexed(glprim[prim], vertexCount, GL_UNSIGNED_SHORT, (GLvoid*)(intptr_t)indexBufferOffset, numPatches); else - glDrawElements(glprim[prim], vertexCount, GL_UNSIGNED_SHORT, ebo ? 0 : (GLvoid*)decIndex); + render_->DrawIndexed(glprim[prim], vertexCount, GL_UNSIGNED_SHORT, (GLvoid*)(intptr_t)indexBufferOffset); } else { - glDrawArrays(glprim[prim], 0, vertexCount); + render_->Draw(glprim[prim], 0, vertexCount); } } else { DecodeVerts(decoded); @@ -683,40 +675,30 @@ rotateVBO: prim, vertexCount, dec_->VertexType(), inds, GE_VTYPE_IDX_16BIT, dec_->GetDecVtxFmt(), maxIndex, drawBuffer, numTrans, drawIndexed, ¶ms, &result); - ApplyDrawStateLate(); + + if (textureNeedsApply) + textureCache_->ApplyTexture(); + + ApplyDrawState(prim); + ApplyDrawStateLate(result.setStencil, result.stencilValue); LinkedShader *program = shaderManager_->ApplyFragmentShader(vsid, vshader, lastVType_, prim); if (result.action == SW_DRAW_PRIMITIVES) { - if (result.setStencil) { - glstate.stencilFunc.set(GL_ALWAYS, result.stencilValue, 255); - } const int vertexSize = sizeof(transformed[0]); bool doTextureProjection = gstate.getUVGenMode() == GE_TEXMAP_TEXTURE_MATRIX; - const uint8_t *bufferStart = (const uint8_t *)drawBuffer; - if (gstate_c.Supports(GPU_SUPPORTS_VAO)) { - bufferStart = 0; - BindBuffer(drawBuffer, vertexSize * maxIndex); - if (drawIndexed) { - BindElementBuffer(inds, sizeof(short) * numTrans); - inds = 0; - } - } else { - glstate.arrayBuffer.unbind(); - glstate.elementArrayBuffer.unbind(); - } - - glVertexAttribPointer(ATTR_POSITION, 4, GL_FLOAT, GL_FALSE, vertexSize, bufferStart); - int attrMask = program->attrMask; - if (attrMask & (1 << ATTR_TEXCOORD)) glVertexAttribPointer(ATTR_TEXCOORD, doTextureProjection ? 3 : 2, GL_FLOAT, GL_FALSE, vertexSize, bufferStart + offsetof(TransformedVertex, u)); - if (attrMask & (1 << ATTR_COLOR0)) glVertexAttribPointer(ATTR_COLOR0, 4, GL_UNSIGNED_BYTE, GL_TRUE, vertexSize, bufferStart + offsetof(TransformedVertex, color0)); - if (attrMask & (1 << ATTR_COLOR1)) glVertexAttribPointer(ATTR_COLOR1, 3, GL_UNSIGNED_BYTE, GL_TRUE, vertexSize, bufferStart + offsetof(TransformedVertex, color1)); if (drawIndexed) { - glDrawElements(glprim[prim], numTrans, GL_UNSIGNED_SHORT, inds); + vertexBufferOffset = (uint32_t)frameData.pushVertex->Push(drawBuffer, maxIndex * sizeof(TransformedVertex), &vertexBuffer); + indexBufferOffset = (uint32_t)frameData.pushIndex->Push(inds, sizeof(uint16_t) * numTrans, &indexBuffer); + render_->BindVertexBuffer(softwareInputLayout_, vertexBuffer, vertexBufferOffset); + render_->BindIndexBuffer(indexBuffer); + render_->DrawIndexed(glprim[prim], numTrans, GL_UNSIGNED_SHORT, (void *)(intptr_t)indexBufferOffset); } else { - glDrawArrays(glprim[prim], 0, numTrans); + vertexBufferOffset = (uint32_t)frameData.pushVertex->Push(drawBuffer, numTrans * sizeof(TransformedVertex), &vertexBuffer); + render_->BindVertexBuffer(softwareInputLayout_, vertexBuffer, vertexBufferOffset); + render_->Draw(glprim[prim], 0, numTrans); } } else if (result.action == SW_CLEAR) { u32 clearColor = result.color; @@ -735,26 +717,14 @@ rotateVBO: framebufferManager_->SetDepthUpdated(); } - // Note that scissor may still apply while clearing. Turn off other tests for the clear. - glstate.stencilTest.disable(); - glstate.stencilMask.set(0xFF); - glstate.depthTest.disable(); - GLbitfield target = 0; + // Without this, we will clear RGB when clearing stencil, which breaks games. + uint8_t rgbaMask = (colorMask ? 7 : 0) | (alphaMask ? 8 : 0); if (colorMask || alphaMask) target |= GL_COLOR_BUFFER_BIT; if (alphaMask) target |= GL_STENCIL_BUFFER_BIT; if (depthMask) target |= GL_DEPTH_BUFFER_BIT; - glstate.colorMask.set(colorMask, colorMask, colorMask, alphaMask); - glClearColor(col[0], col[1], col[2], col[3]); -#ifdef USING_GLES2 - glClearDepthf(clearDepth); -#else - glClearDepth(clearDepth); -#endif - // Stencil takes alpha. - glClearStencil(clearColor >> 24); - glClear(target); + render_->Clear(clearColor, clearDepth, clearColor >> 24, target, rgbaMask); framebufferManager_->SetColorUpdated(gstate_c.skipDrawReason); int scissorX1 = gstate.getScissorX1(); @@ -766,6 +736,7 @@ rotateVBO: if (g_Config.bBlockTransferGPU && (gstate_c.featureFlags & GPU_USE_CLEAR_RAM_HACK) && colorMask && (alphaMask || gstate.FrameBufFormat() == GE_FORMAT_565)) { framebufferManager_->ApplyClearToMemory(scissorX1, scissorY1, scissorX2, scissorY2, clearColor); } + gstate_c.Dirty(DIRTY_BLEND_STATE); // Make sure the color mask gets re-applied. } } @@ -791,104 +762,6 @@ rotateVBO: #ifndef MOBILE_DEVICE host->GPUNotifyDraw(); #endif - CHECK_GL_ERROR_IF_DEBUG(); -} - -GLuint DrawEngineGLES::BindBuffer(const void *p, size_t sz) { - // Get a new buffer each time we need one. - GLuint buf = AllocateBuffer(sz); - glstate.arrayBuffer.bind(buf); - - // These aren't used more than once per frame, so let's use GL_STREAM_DRAW. - glBufferData(GL_ARRAY_BUFFER, sz, p, GL_STREAM_DRAW); - buffersThisFrame_.push_back(buf); - - return buf; -} - -GLuint DrawEngineGLES::BindBuffer(const void *p1, size_t sz1, const void *p2, size_t sz2) { - GLuint buf = AllocateBuffer(sz1 + sz2); - glstate.arrayBuffer.bind(buf); - - glBufferData(GL_ARRAY_BUFFER, sz1 + sz2, nullptr, GL_STREAM_DRAW); - glBufferSubData(GL_ARRAY_BUFFER, 0, sz1, p1); - glBufferSubData(GL_ARRAY_BUFFER, sz1, sz2, p2); - buffersThisFrame_.push_back(buf); - - return buf; -} - -GLuint DrawEngineGLES::BindElementBuffer(const void *p, size_t sz) { - GLuint buf = AllocateBuffer(sz); - glstate.elementArrayBuffer.bind(buf); - glBufferData(GL_ELEMENT_ARRAY_BUFFER, sz, p, GL_STREAM_DRAW); - buffersThisFrame_.push_back(buf); - - return buf; -} - -void DrawEngineGLES::DecimateBuffers() { - for (GLuint buf : buffersThisFrame_) { - FreeBuffer(buf); - } - buffersThisFrame_.clear(); - - if (--bufferDecimationCounter_ <= 0) { - bufferDecimationCounter_ = VERTEXCACHE_DECIMATION_INTERVAL; - } else { - return; - } - - // Let's not keep too many around, will eat up memory. - // First check if there's any to free, and only check if it seems somewhat full. - bool hasOld = false; - if (bufferNameCacheSize_ > VERTEXCACHE_NAME_CACHE_FULL_BYTES) { - for (GLuint buf : bufferNameCache_) { - const BufferNameInfo &info = bufferNameInfo_[buf]; - const int age = gpuStats.numFlips - info.lastFrame; - if (!info.used && age > VERTEXCACHE_NAME_CACHE_MAX_AGE) { - hasOld = true; - break; - } - } - } - - if (hasOld) { - // Okay, it is. Let's rebuild the array. - std::vector toFree; - std::vector toKeep; - - toKeep.reserve(bufferNameCache_.size()); - - for (size_t i = 0, n = bufferNameCache_.size(); i < n; ++i) { - const GLuint buf = bufferNameCache_[i]; - const BufferNameInfo &info = bufferNameInfo_[buf]; - const int age = gpuStats.numFlips - info.lastFrame; - if (!info.used && age > VERTEXCACHE_NAME_CACHE_MAX_AGE) { - toFree.push_back(buf); - bufferNameCacheSize_ -= bufferNameInfo_[buf].sz; - bufferNameInfo_.erase(buf); - - // If we've removed all we want to this round, keep the rest and abort. - if (toFree.size() >= VERTEXCACHE_NAME_DECIMATION_MAX && i + 1 < bufferNameCache_.size()) { - toKeep.insert(toKeep.end(), bufferNameCache_.begin() + i + 1, bufferNameCache_.end()); - break; - } - } else { - toKeep.push_back(buf); - } - } - - if (!toFree.empty()) { - bufferNameCache_ = toKeep; - // TODO: Rebuild? - freeSizedBuffers_.clear(); - - glstate.arrayBuffer.unbind(); - glstate.elementArrayBuffer.unbind(); - glDeleteBuffers((GLsizei)toFree.size(), &toFree[0]); - } - } } bool DrawEngineGLES::IsCodePtrVertexDecoder(const u8 *ptr) const { @@ -896,100 +769,45 @@ bool DrawEngineGLES::IsCodePtrVertexDecoder(const u8 *ptr) const { } void DrawEngineGLES::TessellationDataTransferGLES::SendDataToShader(const float *pos, const float *tex, const float *col, int size, bool hasColor, bool hasTexCoords) { -#ifndef USING_GLES2 - if (isAllowTexture1D_) { - // Position - glActiveTexture(GL_TEXTURE4); - glBindTexture(GL_TEXTURE_1D, data_tex[0]); - glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - if (prevSize < size) { - glTexImage1D(GL_TEXTURE_1D, 0, GL_RGBA32F, size, 0, GL_RGBA, GL_FLOAT, (GLfloat*)pos); - prevSize = size; - } else { - glTexSubImage1D(GL_TEXTURE_1D, 0, 0, size, GL_RGBA, GL_FLOAT, (GLfloat*)pos); - } + // Removed the 1D texture support, it's unlikely to be relevant for performance. + if (data_tex[0]) + renderManager_->DeleteTexture(data_tex[0]); + uint8_t *pos_data = new uint8_t[size * sizeof(float) * 4]; + memcpy(pos_data, pos, size * sizeof(float) * 4); + data_tex[0] = renderManager_->CreateTexture(GL_TEXTURE_2D); + renderManager_->TextureImage(data_tex[0], 0, size, 1, GL_RGBA32F, GL_RGBA, GL_FLOAT, pos_data, false); + renderManager_->FinalizeTexture(data_tex[0], 0, false); + renderManager_->BindTexture(4, data_tex[0]); - // Texcoords - if (hasTexCoords) { - glActiveTexture(GL_TEXTURE5); - glBindTexture(GL_TEXTURE_1D, data_tex[1]); - glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - if (prevSizeTex < size) { - glTexImage1D(GL_TEXTURE_1D, 0, GL_RGBA32F, size, 0, GL_RGBA, GL_FLOAT, (GLfloat*)tex); - prevSizeTex = size; - } else { - glTexSubImage1D(GL_TEXTURE_1D, 0, 0, size, GL_RGBA, GL_FLOAT, (GLfloat*)tex); - } - } + // Texcoords + if (hasTexCoords) { + if (data_tex[1]) + renderManager_->DeleteTexture(data_tex[1]); + uint8_t *tex_data = new uint8_t[size * sizeof(float) * 4]; + memcpy(tex_data, pos, size * sizeof(float) * 4); + data_tex[1] = renderManager_->CreateTexture(GL_TEXTURE_2D); + renderManager_->TextureImage(data_tex[1], 0, size, 1, GL_RGBA32F, GL_RGBA, GL_FLOAT, tex_data, false); + renderManager_->FinalizeTexture(data_tex[1], 0, false); + renderManager_->BindTexture(5, data_tex[1]); + } - // Color - glActiveTexture(GL_TEXTURE6); - glBindTexture(GL_TEXTURE_1D, data_tex[2]); - glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - int sizeColor = hasColor ? size : 1; - if (prevSizeCol < sizeColor) { - glTexImage1D(GL_TEXTURE_1D, 0, GL_RGBA32F, sizeColor, 0, GL_RGBA, GL_FLOAT, (GLfloat*)col); - prevSizeCol = sizeColor; - } else { - glTexSubImage1D(GL_TEXTURE_1D, 0, 0, sizeColor, GL_RGBA, GL_FLOAT, (GLfloat*)col); - } - } else -#endif - { - // Position - glActiveTexture(GL_TEXTURE4); - glBindTexture(GL_TEXTURE_2D, data_tex[0]); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - if (prevSize < size) { - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, size, 1, 0, GL_RGBA, GL_FLOAT, (GLfloat*)pos); - prevSize = size; - } else { - glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, size, 1, GL_RGBA, GL_FLOAT, (GLfloat*)pos); - } + if (data_tex[2]) + renderManager_->DeleteTexture(data_tex[2]); + data_tex[2] = renderManager_->CreateTexture(GL_TEXTURE_2D); + int sizeColor = hasColor ? size : 1; + uint8_t *col_data = new uint8_t[sizeColor * sizeof(float) * 4]; + memcpy(col_data, col, sizeColor * sizeof(float) * 4); - // Texcoords - if (hasTexCoords) { - glActiveTexture(GL_TEXTURE5); - glBindTexture(GL_TEXTURE_2D, data_tex[1]); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - if (prevSizeTex < size) { - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, size, 1, 0, GL_RGBA, GL_FLOAT, (GLfloat*)tex); - prevSizeTex = size; - } else { - glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, size, 1, GL_RGBA, GL_FLOAT, (GLfloat*)tex); - } - } + renderManager_->TextureImage(data_tex[2], 0, sizeColor, 1, GL_RGBA32F, GL_RGBA, GL_FLOAT, col_data, false); + renderManager_->FinalizeTexture(data_tex[2], 0, false); + renderManager_->BindTexture(6, data_tex[2]); +} - // Color - glActiveTexture(GL_TEXTURE6); - glBindTexture(GL_TEXTURE_2D, data_tex[2]); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - int sizeColor = hasColor ? size : 1; - if (prevSizeCol < sizeColor) { - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, sizeColor, 1, 0, GL_RGBA, GL_FLOAT, (GLfloat*)col); - prevSizeCol = sizeColor; - } else { - glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, sizeColor, 1, GL_RGBA, GL_FLOAT, (GLfloat*)col); +void DrawEngineGLES::TessellationDataTransferGLES::EndFrame() { + for (int i = 0; i < 3; i++) { + if (data_tex[i]) { + renderManager_->DeleteTexture(data_tex[i]); + data_tex[i] = nullptr; } } - glActiveTexture(GL_TEXTURE0); - CHECK_GL_ERROR_IF_DEBUG(); } diff --git a/GPU/GLES/DrawEngineGLES.h b/GPU/GLES/DrawEngineGLES.h index 037758d670..7fa9f1e816 100644 --- a/GPU/GLES/DrawEngineGLES.h +++ b/GPU/GLES/DrawEngineGLES.h @@ -28,6 +28,7 @@ #include "GPU/Common/GPUStateUtils.h" #include "GPU/GLES/FragmentShaderGeneratorGLES.h" #include "gfx/gl_common.h" +#include "thin3d/GLRenderManager.h" class LinkedShader; class ShaderManagerGLES; @@ -60,8 +61,8 @@ class VertexArrayInfo { public: VertexArrayInfo() { status = VAI_NEW; - vbo = 0; - ebo = 0; + vbo = nullptr; + ebo = nullptr; prim = GE_PRIM_INVALID; numDraws = 0; numFrames = 0; @@ -81,8 +82,8 @@ public: ReliableHashType hash; u32 minihash; - u32 vbo; - u32 ebo; + GLRBuffer *vbo; + GLRBuffer *ebo; // Precalculated parameter for drawRangeElements u16 numVerts; @@ -101,7 +102,7 @@ public: // Handles transform, lighting and drawing. class DrawEngineGLES : public DrawEngineCommon { public: - DrawEngineGLES(); + DrawEngineGLES(Draw::DrawContext *draw); virtual ~DrawEngineGLES(); void SubmitPrim(void *verts, void *inds, GEPrimitiveType prim, int vertexCount, u32 vertType, int *bytesRead); @@ -118,7 +119,6 @@ public: void SetFragmentTestCache(FragmentTestCacheGLES *testCache) { fragmentTestCache_ = testCache; } - void RestoreVAO(); void DeviceLost(); void DeviceRestore(); @@ -126,6 +126,10 @@ public: void ClearTrackedVertexArrays() override; void DecimateTrackedVertexArrays(); + void BeginFrame(); + void EndFrame(); + + // So that this can be inlined void Flush() { if (!numDrawCalls) @@ -136,7 +140,7 @@ public: void FinishDeferred() { if (!numDrawCalls) return; - DecodeVerts(decoded); + DoFlush(); } bool IsCodePtrVertexDecoder(const u8 *ptr) const; @@ -146,10 +150,14 @@ public: SubmitPrim(verts, inds, prim, vertexCount, vertType, bytesRead); } - GLuint BindBuffer(const void *p, size_t sz); - GLuint BindBuffer(const void *p1, size_t sz1, const void *p2, size_t sz2); - GLuint BindElementBuffer(const void *p, size_t sz); - void DecimateBuffers(); + GLPushBuffer *GetPushVertexBuffer() { + return frameData_[render_->GetCurFrame()].pushVertex; + } + GLPushBuffer *GetPushIndexBuffer() { + return frameData_[render_->GetCurFrame()].pushIndex; + } + + void ClearInputLayoutMap(); private: void InitDeviceObjects(); @@ -157,53 +165,51 @@ private: void DoFlush(); void ApplyDrawState(int prim); - void ApplyDrawStateLate(); + void ApplyDrawStateLate(bool setStencil, int stencilValue); void ResetShaderBlending(); - GLuint AllocateBuffer(size_t sz); - void FreeBuffer(GLuint buf); + GLRInputLayout *SetupDecFmtForDraw(LinkedShader *program, const DecVtxFormat &decFmt); + + void DecodeVertsToPushBuffer(GLPushBuffer *push, uint32_t *bindOffset, GLRBuffer **buf); + void FreeVertexArray(VertexArrayInfo *vai); void MarkUnreliable(VertexArrayInfo *vai); + struct FrameData { + GLPushBuffer *pushVertex; + GLPushBuffer *pushIndex; + }; + FrameData frameData_[GLRenderManager::MAX_INFLIGHT_FRAMES]; + PrehashMap vai_; - // Vertex buffer objects - // Element buffer objects - struct BufferNameInfo { - BufferNameInfo() : sz(0), used(false), lastFrame(0) {} + DenseHashMap inputLayoutMap_; - size_t sz; - bool used; - int lastFrame; - }; - std::vector bufferNameCache_; - std::multimap freeSizedBuffers_; - std::unordered_map bufferNameInfo_; - std::vector buffersThisFrame_; - size_t bufferNameCacheSize_ = 0; - GLuint sharedVao_ = 0; + GLRInputLayout *softwareInputLayout_ = nullptr; + GLRenderManager *render_; // Other ShaderManagerGLES *shaderManager_ = nullptr; TextureCacheGLES *textureCache_ = nullptr; FramebufferManagerGLES *framebufferManager_ = nullptr; FragmentTestCacheGLES *fragmentTestCache_ = nullptr; + Draw::DrawContext *draw_; int bufferDecimationCounter_ = 0; // Hardware tessellation class TessellationDataTransferGLES : public TessellationDataTransfer { private: - int data_tex[3]; - bool isAllowTexture1D_; + GLRTexture *data_tex[3]{}; + GLRenderManager *renderManager_; public: - TessellationDataTransferGLES(bool isAllowTexture1D) : TessellationDataTransfer(), data_tex(), isAllowTexture1D_(isAllowTexture1D) { - glGenTextures(3, (GLuint*)data_tex); - } + TessellationDataTransferGLES(GLRenderManager *renderManager) + : renderManager_(renderManager) { } ~TessellationDataTransferGLES() { - glDeleteTextures(3, (GLuint*)data_tex); + EndFrame(); } void SendDataToShader(const float *pos, const float *tex, const float *col, int size, bool hasColor, bool hasTexCoords) override; + void EndFrame() override; // Queues textures for deletion. }; }; diff --git a/GPU/GLES/FragmentTestCacheGLES.cpp b/GPU/GLES/FragmentTestCacheGLES.cpp index e49f336e13..0ad098a499 100644 --- a/GPU/GLES/FragmentTestCacheGLES.cpp +++ b/GPU/GLES/FragmentTestCacheGLES.cpp @@ -15,6 +15,7 @@ // Official git repository and contact information can be found at // https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. +#include "thin3d/thin3d.h" #include "gfx/gl_debug_log.h" #include "Core/Config.h" #include "GPU/GLES/FragmentTestCacheGLES.h" @@ -26,16 +27,15 @@ static const int FRAGTEST_TEXTURE_OLD_AGE = 307; static const int FRAGTEST_DECIMATION_INTERVAL = 113; -FragmentTestCacheGLES::FragmentTestCacheGLES() : textureCache_(NULL), lastTexture_(0), decimationCounter_(0) { - scratchpad_ = new u8[256 * 4]; +FragmentTestCacheGLES::FragmentTestCacheGLES(Draw::DrawContext *draw) { + render_ = (GLRenderManager *)draw->GetNativeObject(Draw::NativeObject::RENDER_MANAGER); } FragmentTestCacheGLES::~FragmentTestCacheGLES() { Clear(); - delete [] scratchpad_; } -void FragmentTestCacheGLES::BindTestTexture(GLenum unit) { +void FragmentTestCacheGLES::BindTestTexture(int slot) { if (!g_Config.bFragmentTestCache) { return; } @@ -51,17 +51,12 @@ void FragmentTestCacheGLES::BindTestTexture(GLenum unit) { const auto cached = cache_.find(id); if (cached != cache_.end()) { cached->second.lastFrame = gpuStats.numFlips; - GLuint tex = cached->second.texture; + GLRTexture *tex = cached->second.texture; if (tex == lastTexture_) { // Already bound, hurray. return; } - CHECK_GL_ERROR_IF_DEBUG(); - glActiveTexture(unit); - glBindTexture(GL_TEXTURE_2D, tex); - // Always return to the default. - glActiveTexture(GL_TEXTURE0); - CHECK_GL_ERROR_IF_DEBUG(); + render_->BindTexture(slot, tex); lastTexture_ = tex; return; } @@ -79,13 +74,9 @@ void FragmentTestCacheGLES::BindTestTexture(GLenum unit) { const GEComparison funcs[4] = {gstate.getColorTestFunction(), gstate.getColorTestFunction(), gstate.getColorTestFunction(), gstate.getAlphaTestFunction()}; const bool valid[4] = {gstate.isColorTestEnabled(), gstate.isColorTestEnabled(), gstate.isColorTestEnabled(), gstate.isAlphaTestEnabled()}; - glActiveTexture(unit); - // This will necessarily bind the texture. - const GLuint tex = CreateTestTexture(funcs, refs, masks, valid); - // Always return to the default. - glActiveTexture(GL_TEXTURE0); + GLRTexture *tex = CreateTestTexture(funcs, refs, masks, valid); lastTexture_ = tex; - + render_->BindTexture(slot, tex); FragmentTestTexture item; item.lastFrame = gpuStats.numFlips; item.texture = tex; @@ -106,7 +97,8 @@ FragmentTestID FragmentTestCacheGLES::GenerateTestID() const { return id; } -GLuint FragmentTestCacheGLES::CreateTestTexture(const GEComparison funcs[4], const u8 refs[4], const u8 masks[4], const bool valid[4]) { +GLRTexture *FragmentTestCacheGLES::CreateTestTexture(const GEComparison funcs[4], const u8 refs[4], const u8 masks[4], const bool valid[4]) { + u8 *data = new u8[256 * 4]; // TODO: Might it be better to use GL_ALPHA for simple textures? // TODO: Experiment with 4-bit/etc. textures. @@ -142,26 +134,19 @@ GLuint FragmentTestCacheGLES::CreateTestTexture(const GEComparison funcs[4], con break; } } - scratchpad_[color * 4 + i] = res ? 0xFF : 0; + data[color * 4 + i] = res ? 0xFF : 0; } } - GLuint tex = textureCache_->AllocTextureName(); - glBindTexture(GL_TEXTURE_2D, tex); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 256, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, scratchpad_); - - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - + GLRTexture *tex = render_->CreateTexture(GL_TEXTURE_2D); + render_->TextureImage(tex, 0, 256, 1, GL_RGBA, GL_RGBA, GL_UNSIGNED_BYTE, data); return tex; } void FragmentTestCacheGLES::Clear(bool deleteThem) { if (deleteThem) { for (auto tex = cache_.begin(); tex != cache_.end(); ++tex) { - glDeleteTextures(1, &tex->second.texture); + render_->DeleteTexture(tex->second.texture); } } cache_.clear(); @@ -172,7 +157,7 @@ void FragmentTestCacheGLES::Decimate() { if (--decimationCounter_ <= 0) { for (auto tex = cache_.begin(); tex != cache_.end(); ) { if (tex->second.lastFrame + FRAGTEST_TEXTURE_OLD_AGE < gpuStats.numFlips) { - glDeleteTextures(1, &tex->second.texture); + render_->DeleteTexture(tex->second.texture); cache_.erase(tex++); } else { ++tex; diff --git a/GPU/GLES/FragmentTestCacheGLES.h b/GPU/GLES/FragmentTestCacheGLES.h index 106258e6d0..043b236fe6 100644 --- a/GPU/GLES/FragmentTestCacheGLES.h +++ b/GPU/GLES/FragmentTestCacheGLES.h @@ -19,7 +19,6 @@ #include #include "Common/CommonTypes.h" -#include "ext/native/gfx/GLStateCache.h" #include "GPU/GLES/TextureCacheGLES.h" #include "GPU/ge_constants.h" @@ -53,33 +52,33 @@ struct FragmentTestID { }; struct FragmentTestTexture { - GLuint texture; + GLRTexture *texture; int lastFrame; }; class FragmentTestCacheGLES { public: - FragmentTestCacheGLES(); + FragmentTestCacheGLES(Draw::DrawContext *draw); ~FragmentTestCacheGLES(); void SetTextureCache(TextureCacheGLES *tc) { textureCache_ = tc; } - void BindTestTexture(GLenum unit); + void BindTestTexture(int slot); void Clear(bool deleteThem = true); void Decimate(); private: - GLuint CreateTestTexture(const GEComparison funcs[4], const u8 refs[4], const u8 masks[4], const bool valid[4]); + GLRTexture *CreateTestTexture(const GEComparison funcs[4], const u8 refs[4], const u8 masks[4], const bool valid[4]); FragmentTestID GenerateTestID() const; + GLRenderManager *render_; TextureCacheGLES *textureCache_; std::map cache_; - u8 *scratchpad_; - GLuint lastTexture_; - int decimationCounter_; + GLRTexture *lastTexture_ = nullptr; + int decimationCounter_ = 0; }; diff --git a/GPU/GLES/FramebufferManagerGLES.cpp b/GPU/GLES/FramebufferManagerGLES.cpp index 20eb4aaf8c..42015c1169 100644 --- a/GPU/GLES/FramebufferManagerGLES.cpp +++ b/GPU/GLES/FramebufferManagerGLES.cpp @@ -42,7 +42,6 @@ #include "GPU/Common/TextureDecoder.h" #include "GPU/Common/FramebufferCommon.h" #include "GPU/Debugger/Stepping.h" -#include "ext/native/gfx/GLStateCache.h" #include "GPU/GLES/FramebufferManagerGLES.h" #include "GPU/GLES/TextureCacheGLES.h" #include "GPU/GLES/DrawEngineGLES.h" @@ -82,17 +81,6 @@ const int MAX_PBO = 2; void ConvertFromRGBA8888(u8 *dst, const u8 *src, u32 dstStride, u32 srcStride, u32 width, u32 height, GEBufferFormat format); void FramebufferManagerGLES::DisableState() { - glstate.blend.disable(); - glstate.cullFace.disable(); - glstate.depthTest.disable(); - glstate.scissorTest.disable(); - glstate.stencilTest.disable(); -#if !defined(USING_GLES2) - glstate.colorLogicOp.disable(); -#endif - glstate.colorMask.set(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); - glstate.stencilMask.set(0xFF); - gstate_c.Dirty(DIRTY_BLEND_STATE | DIRTY_RASTER_STATE | DIRTY_DEPTHSTENCIL_STATE | DIRTY_VIEWPORTSCISSOR_STATE); } @@ -102,13 +90,20 @@ void FramebufferManagerGLES::CompileDraw2DProgram() { static std::string vs_code, fs_code; vs_code = ApplyGLSLPrelude(basic_vs, GL_VERTEX_SHADER); fs_code = ApplyGLSLPrelude(tex_fs, GL_FRAGMENT_SHADER); - draw2dprogram_ = glsl_create_source(vs_code.c_str(), fs_code.c_str(), &errorString); - if (!draw2dprogram_) { - ERROR_LOG_REPORT(G3D, "Failed to compile draw2dprogram! This shouldn't happen.\n%s", errorString.c_str()); - } else { - glsl_bind(draw2dprogram_); - glUniform1i(draw2dprogram_->sampler0, 0); - } + std::vector shaders; + shaders.push_back(render_->CreateShader(GL_VERTEX_SHADER, vs_code, "draw2d")); + shaders.push_back(render_->CreateShader(GL_FRAGMENT_SHADER, fs_code, "draw2d")); + + std::vector queries; + queries.push_back({ &u_draw2d_tex, "u_tex" }); + std::vector initializers; + initializers.push_back({ &u_draw2d_tex, 0 }); + std::vector semantics; + semantics.push_back({ 0, "a_position" }); + semantics.push_back({ 1, "a_texcoord0" }); + draw2dprogram_ = render_->CreateProgram(shaders, semantics, queries, initializers, false); + for (auto shader : shaders) + render_->DeleteShader(shader); CompilePostShader(); } } @@ -155,7 +150,26 @@ void FramebufferManagerGLES::CompilePostShader() { } if (!translationFailed) { - postShaderProgram_ = glsl_create_source(vshader.c_str(), fshader.c_str(), &errorString); + SetNumExtraFBOs(1); + + std::vector shaders; + shaders.push_back(render_->CreateShader(GL_VERTEX_SHADER, vshader, "postshader")); + shaders.push_back(render_->CreateShader(GL_VERTEX_SHADER, fshader, "postshader")); + std::vector queries; + queries.push_back({ &u_postShaderTex, "tex" }); + queries.push_back({ &deltaLoc_, "u_texelDelta" }); + queries.push_back({ &pixelDeltaLoc_, "u_pixelDelta" }); + queries.push_back({ &timeLoc_, "u_time" }); + queries.push_back({ &videoLoc_, "u_video" }); + + std::vector inits; + inits.push_back({ &u_postShaderTex, 0, 0 }); + postShaderProgram_ = render_->CreateProgram(shaders, {}, queries, inits, false); + render_->SetUniformI1(&u_postShaderTex, 0); + + for (auto iter : shaders) { + render_->DeleteShader(iter); + } } else { ERROR_LOG(FRAMEBUF, "Failed to translate post shader!"); } @@ -193,24 +207,16 @@ void FramebufferManagerGLES::CompilePostShader() { } usePostShader_ = false; } else { - glsl_bind(postShaderProgram_); - glUniform1i(postShaderProgram_->sampler0, 0); - SetNumExtraFBOs(1); - deltaLoc_ = glsl_uniform_loc(postShaderProgram_, "u_texelDelta"); - pixelDeltaLoc_ = glsl_uniform_loc(postShaderProgram_, "u_pixelDelta"); - timeLoc_ = glsl_uniform_loc(postShaderProgram_, "u_time"); - videoLoc_ = glsl_uniform_loc(postShaderProgram_, "u_video"); usePostShader_ = true; } } else { postShaderProgram_ = nullptr; usePostShader_ = false; } - glsl_unbind(); } void FramebufferManagerGLES::Bind2DShader() { - glsl_bind(draw2dprogram_); + render_->BindProgram(draw2dprogram_); } void FramebufferManagerGLES::BindPostShader(const PostShaderUniforms &uniforms) { @@ -218,35 +224,25 @@ void FramebufferManagerGLES::BindPostShader(const PostShaderUniforms &uniforms) if (!postShaderProgram_) { CompileDraw2DProgram(); } - - glsl_bind(postShaderProgram_); + render_->BindProgram(postShaderProgram_); if (deltaLoc_ != -1) - glUniform2f(deltaLoc_, uniforms.texelDelta[0], uniforms.texelDelta[1]); + render_->SetUniformF(&deltaLoc_, 2, uniforms.texelDelta); if (pixelDeltaLoc_ != -1) - glUniform2f(pixelDeltaLoc_, uniforms.pixelDelta[0], uniforms.pixelDelta[1]); + render_->SetUniformF(&pixelDeltaLoc_, 2, uniforms.pixelDelta); if (timeLoc_ != -1) - glUniform4fv(timeLoc_, 1, uniforms.time); + render_->SetUniformF(&timeLoc_, 4, uniforms.time); if (videoLoc_ != -1) - glUniform1f(videoLoc_, uniforms.video); + render_->SetUniformF(&videoLoc_, 1, &uniforms.video); } -FramebufferManagerGLES::FramebufferManagerGLES(Draw::DrawContext *draw) : +FramebufferManagerGLES::FramebufferManagerGLES(Draw::DrawContext *draw, GLRenderManager *render) : FramebufferManagerCommon(draw), - drawPixelsTex_(0), - drawPixelsTexFormat_(GE_FORMAT_INVALID), - convBuf_(nullptr), - videoLoc_(-1), - timeLoc_(-1), - pixelDeltaLoc_(-1), - deltaLoc_(-1), - textureCacheGL_(nullptr), - shaderManagerGL_(nullptr), - pixelBufObj_(nullptr), - currentPBO_(0) + render_(render) { needBackBufferYSwap_ = true; needGLESRebinds_ = true; CreateDeviceObjects(); + render_ = (GLRenderManager *)draw_->GetNativeObject(Draw::NativeObject::RENDER_MANAGER); } void FramebufferManagerGLES::Init() { @@ -273,23 +269,33 @@ void FramebufferManagerGLES::SetDrawEngine(DrawEngineGLES *td) { void FramebufferManagerGLES::CreateDeviceObjects() { CompileDraw2DProgram(); + + std::vector entries; + entries.push_back({ 0, 3, GL_FLOAT, GL_FALSE, sizeof(Simple2DVertex), offsetof(Simple2DVertex, pos) }); + entries.push_back({ 1, 2, GL_FLOAT, GL_FALSE, sizeof(Simple2DVertex), offsetof(Simple2DVertex, uv) }); + simple2DInputLayout_ = render_->CreateInputLayout(entries); } void FramebufferManagerGLES::DestroyDeviceObjects() { + if (simple2DInputLayout_) { + render_->DeleteInputLayout(simple2DInputLayout_); + simple2DInputLayout_ = nullptr; + } + if (draw2dprogram_) { - glsl_destroy(draw2dprogram_); + render_->DeleteProgram(draw2dprogram_); draw2dprogram_ = nullptr; } if (postShaderProgram_) { - glsl_destroy(postShaderProgram_); + render_->DeleteProgram(postShaderProgram_); postShaderProgram_ = nullptr; } if (drawPixelsTex_) { - glDeleteTextures(1, &drawPixelsTex_); + render_->DeleteTexture(drawPixelsTex_); drawPixelsTex_ = 0; } if (stencilUploadProgram_) { - glsl_destroy(stencilUploadProgram_); + render_->DeleteProgram(stencilUploadProgram_); stencilUploadProgram_ = nullptr; } } @@ -297,12 +303,6 @@ void FramebufferManagerGLES::DestroyDeviceObjects() { FramebufferManagerGLES::~FramebufferManagerGLES() { DestroyDeviceObjects(); - if (pixelBufObj_) { - for (int i = 0; i < MAX_PBO; i++) { - glDeleteBuffers(1, &pixelBufObj_[i].handle); - } - delete[] pixelBufObj_; - } delete [] convBuf_; } @@ -317,95 +317,68 @@ void FramebufferManagerGLES::MakePixelTexture(const u8 *srcPixels, GEBufferForma } } - if (drawPixelsTex_ && (drawPixelsTexFormat_ != srcPixelFormat || drawPixelsTexW_ != texWidth || drawPixelsTexH_ != height)) { - glDeleteTextures(1, &drawPixelsTex_); - drawPixelsTex_ = 0; + if (drawPixelsTex_) { + render_->DeleteTexture(drawPixelsTex_); } - if (!drawPixelsTex_) { - drawPixelsTex_ = textureCacheGL_->AllocTextureName(); - drawPixelsTexW_ = texWidth; - drawPixelsTexH_ = height; + drawPixelsTex_ = render_->CreateTexture(GL_TEXTURE_2D); + drawPixelsTexW_ = texWidth; + drawPixelsTexH_ = height; - // Initialize backbuffer texture for DrawPixels - glBindTexture(GL_TEXTURE_2D, drawPixelsTex_); - glPixelStorei(GL_UNPACK_ALIGNMENT, 1); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, texWidth, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0); - drawPixelsTexFormat_ = srcPixelFormat; - } else { - glBindTexture(GL_TEXTURE_2D, drawPixelsTex_); - } + drawPixelsTexFormat_ = srcPixelFormat; // TODO: We can just change the texture format and flip some bits around instead of this. // Could share code with the texture cache perhaps. - bool useConvBuf = false; - if (srcPixelFormat != GE_FORMAT_8888 || srcStride != texWidth) { - useConvBuf = true; - u32 neededSize = texWidth * height * 4; - if (!convBuf_ || convBufSize_ < neededSize) { - delete [] convBuf_; - convBuf_ = new u8[neededSize]; - convBufSize_ = neededSize; - } - for (int y = 0; y < height; y++) { - switch (srcPixelFormat) { - case GE_FORMAT_565: - { - const u16 *src = (const u16 *)srcPixels + srcStride * y; - u8 *dst = convBuf_ + 4 * texWidth * y; - ConvertRGBA565ToRGBA8888((u32 *)dst, src, width); - } - break; - - case GE_FORMAT_5551: - { - const u16 *src = (const u16 *)srcPixels + srcStride * y; - u8 *dst = convBuf_ + 4 * texWidth * y; - ConvertRGBA5551ToRGBA8888((u32 *)dst, src, width); - } - break; - - case GE_FORMAT_4444: - { - const u16 *src = (const u16 *)srcPixels + srcStride * y; - u8 *dst = convBuf_ + 4 * texWidth * y; - ConvertRGBA4444ToRGBA8888((u32 *)dst, src, width); - } - break; - - case GE_FORMAT_8888: - { - const u8 *src = srcPixels + srcStride * 4 * y; - u8 *dst = convBuf_ + 4 * texWidth * y; - memcpy(dst, src, 4 * width); - } - break; - - case GE_FORMAT_INVALID: - _dbg_assert_msg_(G3D, false, "Invalid pixelFormat passed to DrawPixels()."); - break; + u32 neededSize = texWidth * height * 4; + u8 *convBuf = new u8[neededSize]; + for (int y = 0; y < height; y++) { + switch (srcPixelFormat) { + case GE_FORMAT_565: + { + const u16 *src = (const u16 *)srcPixels + srcStride * y; + u8 *dst = convBuf + 4 * texWidth * y; + ConvertRGBA565ToRGBA8888((u32 *)dst, src, width); } + break; + + case GE_FORMAT_5551: + { + const u16 *src = (const u16 *)srcPixels + srcStride * y; + u8 *dst = convBuf + 4 * texWidth * y; + ConvertRGBA5551ToRGBA8888((u32 *)dst, src, width); + } + break; + + case GE_FORMAT_4444: + { + const u16 *src = (const u16 *)srcPixels + srcStride * y; + u8 *dst = convBuf + 4 * texWidth * y; + ConvertRGBA4444ToRGBA8888((u32 *)dst, src, width); + } + break; + + case GE_FORMAT_8888: + { + const u8 *src = srcPixels + srcStride * 4 * y; + u8 *dst = convBuf + 4 * texWidth * y; + memcpy(dst, src, 4 * width); + } + break; + + case GE_FORMAT_INVALID: + _dbg_assert_msg_(G3D, false, "Invalid pixelFormat passed to DrawPixels()."); + break; } } + render_->TextureImage(drawPixelsTex_, 0, texWidth, height, GL_RGBA, GL_RGBA, GL_UNSIGNED_BYTE, convBuf, false); + render_->FinalizeTexture(drawPixelsTex_, 0, false); - // Try to skip uploading the unnecessary parts. - if (gstate_c.Supports(GPU_SUPPORTS_UNPACK_SUBIMAGE) && width != texWidth) { - glPixelStorei(GL_UNPACK_ROW_LENGTH, texWidth); - glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, useConvBuf ? convBuf_ : srcPixels); - glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); - } else { - glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, texWidth, height, GL_RGBA, GL_UNSIGNED_BYTE, useConvBuf ? convBuf_ : srcPixels); - } - CHECK_GL_ERROR_IF_DEBUG(); + // TODO: Return instead? + render_->BindTexture(0, drawPixelsTex_); } void FramebufferManagerGLES::SetViewport2D(int x, int y, int w, int h) { - glstate.viewport.set(x, y, w, h); + render_->SetViewport({ (float)x, (float)y, (float)w, (float)h, 0.0f, 1.0f }); } // x, y, w, h are relative coordinates against destW/destH, which is not very intuitive. @@ -418,7 +391,7 @@ void FramebufferManagerGLES::DrawActiveTexture(float x, float y, float w, float u0,v1, }; - static const GLushort indices[4] = {0,1,3,2}; + static const GLushort indices[4] = { 0,1,3,2 }; if (uvRotation != ROTATION_LOCKED_HORIZONTAL) { float temp[8]; @@ -437,9 +410,9 @@ void FramebufferManagerGLES::DrawActiveTexture(float x, float y, float w, float float pos[12] = { x,y,0, - x+w,y,0, - x+w,y+h,0, - x,y+h,0 + x + w,y,0, + x + w,y + h,0, + x,y + h,0 }; float invDestW = 1.0f / (destW * 0.5f); @@ -451,42 +424,27 @@ void FramebufferManagerGLES::DrawActiveTexture(float x, float y, float w, float // Upscaling postshaders doesn't look well with linear if (flags & DRAWTEX_LINEAR) { - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + render_->SetTextureSampler(GL_CLAMP_TO_EDGE, GL_CLAMP_TO_EDGE, GL_LINEAR, GL_LINEAR, 0.0f); } else { - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + render_->SetTextureSampler(GL_CLAMP_TO_EDGE, GL_CLAMP_TO_EDGE, GL_NEAREST, GL_NEAREST, 0.0f); } - const GLSLProgram *program = glsl_get_program(); - if (!program) { - ERROR_LOG(FRAMEBUF, "Trying to DrawActiveTexture() without a program"); - return; - } + Simple2DVertex verts[4]; + memcpy(verts[0].pos, &pos[0], 12); + memcpy(verts[1].pos, &pos[3], 12); + memcpy(verts[3].pos, &pos[6], 12); + memcpy(verts[2].pos, &pos[9], 12); + memcpy(verts[0].uv, &texCoords[0], 8); + memcpy(verts[1].uv, &texCoords[2], 8); + memcpy(verts[3].uv, &texCoords[4], 8); + memcpy(verts[2].uv, &texCoords[6], 8); - glEnableVertexAttribArray(program->a_position); - glEnableVertexAttribArray(program->a_texcoord0); - if (gstate_c.Supports(GPU_SUPPORTS_VAO)) { - drawEngineGL_->BindBuffer(pos, sizeof(pos), texCoords, sizeof(texCoords)); - drawEngineGL_->BindElementBuffer(indices, sizeof(indices)); - glVertexAttribPointer(program->a_position, 3, GL_FLOAT, GL_FALSE, 12, 0); - glVertexAttribPointer(program->a_texcoord0, 2, GL_FLOAT, GL_FALSE, 8, (void *)sizeof(pos)); - glDrawElements(GL_TRIANGLE_STRIP, 4, GL_UNSIGNED_SHORT, 0); - } else { - glstate.arrayBuffer.unbind(); - glstate.elementArrayBuffer.unbind(); - glVertexAttribPointer(program->a_position, 3, GL_FLOAT, GL_FALSE, 12, pos); - glVertexAttribPointer(program->a_texcoord0, 2, GL_FLOAT, GL_FALSE, 8, texCoords); - glDrawElements(GL_TRIANGLE_STRIP, 4, GL_UNSIGNED_SHORT, indices); - } - glDisableVertexAttribArray(program->a_position); - glDisableVertexAttribArray(program->a_texcoord0); -} - -void FramebufferManagerGLES::RebindFramebuffer() { - FramebufferManagerCommon::RebindFramebuffer(); - if (g_Config.iRenderingMode == FB_NON_BUFFERED_MODE) - glstate.viewport.restore(); + uint32_t bindOffset; + GLRBuffer *buffer; + void *dest = drawEngineGL_->GetPushVertexBuffer()->Push(sizeof(verts), &bindOffset, &buffer); + memcpy(dest, verts, sizeof(verts)); + render_->BindVertexBuffer(simple2DInputLayout_, buffer, bindOffset); + render_->Draw(GL_TRIANGLE_STRIP, 0, 4); } void FramebufferManagerGLES::ReformatFramebufferFrom(VirtualFramebuffer *vfb, GEBufferFormat old) { @@ -505,6 +463,7 @@ void FramebufferManagerGLES::ReformatFramebufferFrom(VirtualFramebuffer *vfb, GE if (old == GE_FORMAT_565) { draw_->BindFramebufferAsRenderTarget(vfb->fbo, { Draw::RPAction::CLEAR, Draw::RPAction::CLEAR, Draw::RPAction::CLEAR }); + gstate_c.Dirty(DIRTY_BLEND_STATE); } else { draw_->BindFramebufferAsRenderTarget(vfb->fbo, { Draw::RPAction::KEEP, Draw::RPAction::KEEP, Draw::RPAction::KEEP }); } @@ -527,17 +486,15 @@ void FramebufferManagerGLES::BlitFramebufferDepth(VirtualFramebuffer *src, Virtu if (gstate_c.Supports(GPU_SUPPORTS_ARB_FRAMEBUFFER_BLIT | GPU_SUPPORTS_NV_FRAMEBUFFER_BLIT)) { // Let's only do this if not clearing depth. - glstate.scissorTest.force(false); draw_->BlitFramebuffer(src->fbo, 0, 0, w, h, dst->fbo, 0, 0, w, h, Draw::FB_DEPTH_BIT, Draw::FB_BLIT_NEAREST); dst->last_frame_depth_updated = gpuStats.numFlips; - glstate.scissorTest.restore(); } } } void FramebufferManagerGLES::BindFramebufferAsColorTexture(int stage, VirtualFramebuffer *framebuffer, int flags) { if (!framebuffer->fbo || !useBufferedRendering_) { - glBindTexture(GL_TEXTURE_2D, 0); + render_->BindTexture(stage, nullptr); gstate_c.skipDrawReason |= SKIPDRAW_BAD_FB_TEXTURE; return; } @@ -565,48 +522,6 @@ void FramebufferManagerGLES::BindFramebufferAsColorTexture(int stage, VirtualFra } } -void FramebufferManagerGLES::ReadFramebufferToMemory(VirtualFramebuffer *vfb, bool sync, int x, int y, int w, int h) { - PROFILE_THIS_SCOPE("gpu-readback"); - if (sync) { - // flush async just in case when we go for synchronous update - // Doesn't actually pack when sent a null argument. - PackFramebufferAsync_(nullptr); - } - - if (vfb) { - // We'll pseudo-blit framebuffers here to get a resized version of vfb. - VirtualFramebuffer *nvfb = FindDownloadTempBuffer(vfb); - OptimizeDownloadRange(vfb, x, y, w, h); - BlitFramebuffer(nvfb, x, y, vfb, x, y, w, h, 0); - - // PackFramebufferSync_() - Synchronous pixel data transfer using glReadPixels - // PackFramebufferAsync_() - Asynchronous pixel data transfer using glReadPixels with PBOs - - if (gl_extensions.IsGLES) { - PackFramebufferSync_(nvfb, x, y, w, h); - } else { - // TODO: Can we fall back to sync without these? - if (gl_extensions.ARB_pixel_buffer_object && gstate_c.Supports(GPU_SUPPORTS_OES_TEXTURE_NPOT)) { - if (!sync) { - PackFramebufferAsync_(nvfb); - } else { - PackFramebufferSync_(nvfb, x, y, w, h); - } - } - } - - textureCacheGL_->ForgetLastTexture(); - RebindFramebuffer(); - } -} - -void FramebufferManagerGLES::DownloadFramebufferForClut(u32 fb_address, u32 loadBytes) { - PROFILE_THIS_SCOPE("gpu-readback"); - // Flush async just in case. - PackFramebufferAsync_(nullptr); - FramebufferManagerCommon::DownloadFramebufferForClut(fb_address, loadBytes); -} - bool FramebufferManagerGLES::CreateDownloadTempBuffer(VirtualFramebuffer *nvfb) { // When updating VRAM, it need to be exact format. if (!gstate_c.Supports(GPU_PREFER_CPU_DOWNLOAD)) { @@ -641,12 +556,10 @@ 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::RPAction::DONT_CARE, 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::RPAction::CLEAR, Draw::RPAction::CLEAR, Draw::RPAction::CLEAR }); + gstate_c.Dirty(DIRTY_BLEND_STATE); } - CHECK_GL_ERROR_IF_DEBUG(); } void FramebufferManagerGLES::BlitFramebuffer(VirtualFramebuffer *dst, int dstX, int dstY, VirtualFramebuffer *src, int srcX, int srcY, int w, int h, int bpp) { @@ -699,12 +612,10 @@ void FramebufferManagerGLES::BlitFramebuffer(VirtualFramebuffer *dst, int dstX, const bool yOverlap = src == dst && srcY2 > dstY1 && srcY1 < dstY2; if (sameSize && sameDepth && srcInsideBounds && dstInsideBounds && !(xOverlap && yOverlap)) { draw_->CopyFramebufferImage(src->fbo, 0, srcX1, srcY1, 0, dst->fbo, 0, dstX1, dstY1, 0, dstX2 - dstX1, dstY2 - dstY1, 1, Draw::FB_COLOR_BIT); - CHECK_GL_ERROR_IF_DEBUG(); return; } } - glstate.scissorTest.force(false); 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 { @@ -714,39 +625,21 @@ void FramebufferManagerGLES::BlitFramebuffer(VirtualFramebuffer *dst, int dstX, // Make sure our 2D drawing program is ready. Compiles only if not already compiled. CompileDraw2DProgram(); - glstate.viewport.force(0, 0, dst->renderWidth, dst->renderHeight); - glstate.blend.force(false); - glstate.cullFace.force(false); - glstate.depthTest.force(false); - glstate.stencilTest.force(false); -#if !defined(USING_GLES2) - glstate.colorLogicOp.force(false); -#endif - glstate.colorMask.force(true, true, true, true); - glstate.stencilMask.force(0xFF); + render_->SetViewport({ 0, 0, (float)dst->renderWidth, (float)dst->renderHeight, 0, 1.0f }); + render_->SetStencilDisabled(); + render_->SetDepth(false, false, GL_ALWAYS); + render_->SetNoBlendAndMask(0xF); // 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; - glsl_bind(draw2dprogram_); + render_->BindProgram(draw2dprogram_); DrawActiveTexture(dstX1, dstY1, w * dstXFactor, h, dst->bufferWidth, dst->bufferHeight, srcX1 / srcW, srcY1 / srcH, srcX2 / srcW, srcY2 / srcH, ROTATION_LOCKED_HORIZONTAL, DRAWTEX_NEAREST); - glBindTexture(GL_TEXTURE_2D, 0); textureCacheGL_->ForgetLastTexture(); - glstate.viewport.restore(); - glstate.blend.restore(); - glstate.cullFace.restore(); - glstate.depthTest.restore(); - glstate.stencilTest.restore(); -#if !defined(USING_GLES2) - glstate.colorLogicOp.restore(); -#endif - glstate.colorMask.restore(); - glstate.stencilMask.restore(); } - glstate.scissorTest.restore(); - CHECK_GL_ERROR_IF_DEBUG(); + gstate_c.Dirty(DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_BLEND_STATE | DIRTY_RASTER_STATE); } // TODO: SSE/NEON @@ -806,191 +699,6 @@ void ConvertFromRGBA8888(u8 *dst, const u8 *src, u32 dstStride, u32 srcStride, u } } -void FramebufferManagerGLES::PackFramebufferAsync_(VirtualFramebuffer *vfb) { - CHECK_GL_ERROR_IF_DEBUG(); - GLubyte *packed = 0; - bool unbind = false; - const u8 nextPBO = (currentPBO_ + 1) % MAX_PBO; - const bool useCPU = gstate_c.Supports(GPU_PREFER_CPU_DOWNLOAD); - - // We'll prepare two PBOs to switch between readying and reading - if (!pixelBufObj_) { - if (!vfb) { - // This call is just to flush the buffers. We don't have any yet, - // so there's nothing to do. - return; - } - - GLuint pbos[MAX_PBO]; - glGenBuffers(MAX_PBO, pbos); - - pixelBufObj_ = new AsyncPBO[MAX_PBO]; - for (int i = 0; i < MAX_PBO; i++) { - pixelBufObj_[i].handle = pbos[i]; - pixelBufObj_[i].maxSize = 0; - pixelBufObj_[i].reading = false; - } - } - - // Receive previously requested data from a PBO - AsyncPBO &pbo = pixelBufObj_[nextPBO]; - if (pbo.reading) { - glBindBuffer(GL_PIXEL_PACK_BUFFER, pbo.handle); -#ifdef USING_GLES2 - // Not on desktop GL 2.x... - packed = (GLubyte *)glMapBufferRange(GL_PIXEL_PACK_BUFFER, 0, pbo.size, GL_MAP_READ_BIT); -#else - packed = (GLubyte *)glMapBuffer(GL_PIXEL_PACK_BUFFER, GL_READ_ONLY); -#endif - - if (packed) { - DEBUG_LOG(FRAMEBUF, "Reading PBO to memory , bufSize = %u, packed = %p, fb_address = %08x, stride = %u, pbo = %u", - pbo.size, packed, pbo.fb_address, pbo.stride, nextPBO); - - if (useCPU) { - u8 *dst = Memory::GetPointer(pbo.fb_address); - ConvertFromRGBA8888(dst, packed, pbo.stride, pbo.stride, pbo.stride, pbo.height, pbo.format); - } else { - // We don't need to convert, GPU already did (or should have) - Memory::MemcpyUnchecked(pbo.fb_address, packed, pbo.size); - } - - pbo.reading = false; - } - - glUnmapBuffer(GL_PIXEL_PACK_BUFFER); - unbind = true; - } - - // Order packing/readback of the framebuffer - if (vfb) { - bool reverseOrder = gstate_c.Supports(GPU_PREFER_REVERSE_COLOR_ORDER); - Draw::DataFormat dataFmt = Draw::DataFormat::UNDEFINED; - switch (vfb->format) { - case GE_FORMAT_4444: - dataFmt = (reverseOrder ? Draw::DataFormat::A4R4G4B4_UNORM_PACK16 : Draw::DataFormat::B4G4R4A4_UNORM_PACK16); - break; - case GE_FORMAT_5551: - dataFmt = (reverseOrder ? Draw::DataFormat::A1R5G5B5_UNORM_PACK16 : Draw::DataFormat::B5G5R5A1_UNORM_PACK16); - break; - case GE_FORMAT_565: - dataFmt = (reverseOrder ? Draw::DataFormat::R5G6B5_UNORM_PACK16 : Draw::DataFormat::B5G6R5_UNORM_PACK16); - break; - case GE_FORMAT_8888: - dataFmt = Draw::DataFormat::R8G8B8A8_UNORM; - break; - }; - - if (useCPU) { - dataFmt = Draw::DataFormat::R8G8B8A8_UNORM; - } - - int pixelSize = (int)DataFormatSizeInBytes(dataFmt); - int align = pixelSize; - - // If using the CPU, we need 4 bytes per pixel always. - u32 bufSize = vfb->fb_stride * vfb->height * pixelSize; - u32 fb_address = (0x04000000) | vfb->fb_address; - - if (!vfb->fbo) { - ERROR_LOG_REPORT_ONCE(vfbfbozero, SCEGE, "PackFramebufferAsync_: vfb->fbo == 0"); - return; - } - - glBindBuffer(GL_PIXEL_PACK_BUFFER, pixelBufObj_[currentPBO_].handle); - - if (pixelBufObj_[currentPBO_].maxSize < bufSize) { - // We reserve a buffer big enough to fit all those pixels - glBufferData(GL_PIXEL_PACK_BUFFER, bufSize, NULL, GL_DYNAMIC_READ); - pixelBufObj_[currentPBO_].maxSize = bufSize; - } - - // TODO: This is a hack since PBOs have not been implemented in Thin3D yet (and maybe shouldn't? maybe should do this internally?) - draw_->CopyFramebufferToMemorySync(vfb->fbo, Draw::FB_COLOR_BIT, 0, 0, vfb->fb_stride, vfb->height, dataFmt, nullptr, vfb->fb_stride); - - unbind = true; - - pixelBufObj_[currentPBO_].fb_address = fb_address; - pixelBufObj_[currentPBO_].size = bufSize; - pixelBufObj_[currentPBO_].stride = vfb->fb_stride; - pixelBufObj_[currentPBO_].height = vfb->height; - pixelBufObj_[currentPBO_].format = vfb->format; - pixelBufObj_[currentPBO_].reading = true; - } - - currentPBO_ = nextPBO; - - if (unbind) { - glBindBuffer(GL_PIXEL_PACK_BUFFER, 0); - } - CHECK_GL_ERROR_IF_DEBUG(); -} - -void FramebufferManagerGLES::PackFramebufferSync_(VirtualFramebuffer *vfb, int x, int y, int w, int h) { - if (!vfb->fbo) { - ERROR_LOG_REPORT_ONCE(vfbfbozero, SCEGE, "PackFramebufferSync_: vfb->fbo == 0"); - return; - } - - int possibleH = std::max(vfb->height - y, 0); - if (h > possibleH) { - h = possibleH; - } - - bool convert = vfb->format != GE_FORMAT_8888; - const int dstBpp = vfb->format == GE_FORMAT_8888 ? 4 : 2; - const int packWidth = std::min(vfb->fb_stride, std::min(x + w, (int)vfb->width)); - - // Pixel size always 4 here because we always request RGBA8888 - u32 bufSize = packWidth * h * 4; - u32 fb_address = 0x04000000 | vfb->fb_address; - - if (gl_extensions.IsGLES && !gl_extensions.GLES3 && packWidth != vfb->fb_stride && h != 1) { - // Need to use a temp buffer, since GLES2 doesn't support GL_PACK_ROW_LENGTH. - convert = true; - } - - int dstByteOffset = y * vfb->fb_stride * dstBpp; - u8 *dst = Memory::GetPointer(fb_address + dstByteOffset); - - u8 *packed = nullptr; - if (!convert) { - packed = (u8 *)dst; - } else { - // End result may be 16-bit but we are reading 32-bit, so there may not be enough space at fb_address - if (!convBuf_ || convBufSize_ < bufSize) { - delete [] convBuf_; - convBuf_ = new u8[bufSize]; - convBufSize_ = bufSize; - } - packed = convBuf_; - } - - if (packed) { - DEBUG_LOG(FRAMEBUF, "Reading framebuffer to mem, bufSize = %u, fb_address = %08x", bufSize, fb_address); - // Avoid reading the part between width and stride, if possible. - int packStride = convert || h == 1 ? packWidth : vfb->fb_stride; - draw_->CopyFramebufferToMemorySync(vfb->fbo, Draw::FB_COLOR_BIT, 0, y, packWidth, h, Draw::DataFormat::R8G8B8A8_UNORM, packed, packStride); - if (convert) { - ConvertFromRGBA8888(dst, packed, vfb->fb_stride, packStride, packWidth, h, vfb->format); - } - } - - // TODO: Move this into Thin3d. - if (gl_extensions.GLES3 && glInvalidateFramebuffer != nullptr) { -#ifdef USING_GLES2 - // GLES3 doesn't support using GL_READ_FRAMEBUFFER here. - draw_->BindFramebufferAsRenderTarget(vfb->fbo, { Draw::RPAction::DONT_CARE, Draw::RPAction::DONT_CARE, Draw::RPAction::DONT_CARE }); - const GLenum target = GL_FRAMEBUFFER; -#else - const GLenum target = GL_READ_FRAMEBUFFER; -#endif - GLenum attachments[3] = { GL_COLOR_ATTACHMENT0, GL_DEPTH_ATTACHMENT, GL_STENCIL_ATTACHMENT }; - glInvalidateFramebuffer(target, 3, attachments); - } - CHECK_GL_ERROR_IF_DEBUG(); -} - void FramebufferManagerGLES::PackDepthbuffer(VirtualFramebuffer *vfb, int x, int y, int w, int h) { if (!vfb->fbo) { ERROR_LOG_REPORT_ONCE(vfbfbozero, SCEGE, "PackDepthbuffer: vfb->fbo == 0"); @@ -1031,26 +739,9 @@ void FramebufferManagerGLES::PackDepthbuffer(VirtualFramebuffer *vfb, int x, int } } } - CHECK_GL_ERROR_IF_DEBUG(); } void FramebufferManagerGLES::EndFrame() { - CHECK_GL_ERROR_IF_DEBUG(); - - // Let's explicitly invalidate any temp FBOs used during this frame. - if (gl_extensions.GLES3 && glInvalidateFramebuffer != nullptr) { - for (auto temp : tempFBOs_) { - if (temp.second.last_frame_used < gpuStats.numFlips) { - continue; - } - - draw_->BindFramebufferAsRenderTarget(temp.second.fbo, { Draw::RPAction::DONT_CARE, 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_->BindFramebufferAsRenderTarget(nullptr, { Draw::RPAction::KEEP , Draw::RPAction::KEEP, Draw::RPAction::KEEP }); - } - CHECK_GL_ERROR_IF_DEBUG(); } void FramebufferManagerGLES::DeviceLost() { @@ -1064,7 +755,6 @@ void FramebufferManagerGLES::DeviceRestore(Draw::DrawContext *draw) { } void FramebufferManagerGLES::DestroyAllFBOs() { - CHECK_GL_ERROR_IF_DEBUG(); currentRenderVfb_ = 0; displayFramebuf_ = 0; prevDisplayFramebuf_ = 0; @@ -1091,25 +781,17 @@ void FramebufferManagerGLES::DestroyAllFBOs() { SetNumExtraFBOs(0); DisableState(); - CHECK_GL_ERROR_IF_DEBUG(); } void FramebufferManagerGLES::Resized() { FramebufferManagerCommon::Resized(); + render_->Resize(PSP_CoreParameter().pixelWidth, PSP_CoreParameter().pixelHeight); if (UpdateSize()) { DestroyAllFBOs(); } -#ifndef USING_GLES2 - if (g_Config.iInternalResolution == 0) { - glLineWidth(std::max(1, (int)(renderWidth_ / 480))); - glPointSize(std::max(1.0f, (float)(renderWidth_ / 480.f))); - } else { - glLineWidth(g_Config.iInternalResolution); - glPointSize((float)g_Config.iInternalResolution); - } -#endif + // render_->SetLineWidth(renderWidth_ / 480.0f); } bool FramebufferManagerGLES::GetOutputFramebuffer(GPUDebugBuffer &buffer) { diff --git a/GPU/GLES/FramebufferManagerGLES.h b/GPU/GLES/FramebufferManagerGLES.h index 94c87ae982..c3f0594637 100644 --- a/GPU/GLES/FramebufferManagerGLES.h +++ b/GPU/GLES/FramebufferManagerGLES.h @@ -29,28 +29,16 @@ #include "Core/Config.h" #include "GPU/GPUCommon.h" #include "GPU/Common/FramebufferCommon.h" +#include "thin3d/GLRenderManager.h" struct GLSLProgram; class TextureCacheGLES; class DrawEngineGLES; class ShaderManagerGLES; -// Simple struct for asynchronous PBO readbacks -struct AsyncPBO { - uint32_t handle; - u32 maxSize; - - u32 fb_address; - u32 stride; - u32 height; - u32 size; - GEBufferFormat format; - bool reading; -}; - class FramebufferManagerGLES : public FramebufferManagerCommon { public: - FramebufferManagerGLES(Draw::DrawContext *draw); + FramebufferManagerGLES(Draw::DrawContext *draw, GLRenderManager *render); ~FramebufferManagerGLES(); void SetTextureCache(TextureCacheGLES *tc); @@ -66,7 +54,6 @@ public: void EndFrame(); void Resized() override; void DeviceLost(); - void SetLineWidth(); void ReformatFramebufferFrom(VirtualFramebuffer *vfb, GEBufferFormat old) override; void BlitFramebufferDepth(VirtualFramebuffer *src, VirtualFramebuffer *dst) override; @@ -74,14 +61,9 @@ public: // For use when texturing from a framebuffer. May create a duplicate if target. void BindFramebufferAsColorTexture(int stage, VirtualFramebuffer *framebuffer, int flags); - // Reads a rectangular subregion of a framebuffer to the right position in its backing memory. - void ReadFramebufferToMemory(VirtualFramebuffer *vfb, bool sync, int x, int y, int w, int h) override; - void DownloadFramebufferForClut(u32 fb_address, u32 loadBytes) override; - bool NotifyStencilUpload(u32 addr, int size, bool skipZero = false) override; bool GetOutputFramebuffer(GPUDebugBuffer &buffer) override; - virtual void RebindFramebuffer() override; void DeviceRestore(Draw::DrawContext *draw); @@ -103,35 +85,44 @@ private: void Bind2DShader() override; void BindPostShader(const PostShaderUniforms &uniforms) override; void CompileDraw2DProgram(); - void DestroyDraw2DProgram(); void CompilePostShader(); - void PackFramebufferAsync_(VirtualFramebuffer *vfb); // Not used under ES currently - void PackFramebufferSync_(VirtualFramebuffer *vfb, int x, int y, int w, int h) override; void PackDepthbuffer(VirtualFramebuffer *vfb, int x, int y, int w, int h); + GLRenderManager *render_; + // Used by DrawPixels - unsigned int drawPixelsTex_; - GEBufferFormat drawPixelsTexFormat_; - int drawPixelsTexW_; - int drawPixelsTexH_; + GLRTexture *drawPixelsTex_ = nullptr; + GEBufferFormat drawPixelsTexFormat_ = GE_FORMAT_INVALID; + int drawPixelsTexW_ = 0; + int drawPixelsTexH_ = 0; - u8 *convBuf_; - u32 convBufSize_; - GLSLProgram *draw2dprogram_ = nullptr; - GLSLProgram *postShaderProgram_ = nullptr; - GLSLProgram *stencilUploadProgram_ = nullptr; - int plainColorLoc_; - int videoLoc_; - int timeLoc_; - int pixelDeltaLoc_; - int deltaLoc_; + u8 *convBuf_ = nullptr; + u32 convBufSize_ = 0; + GLRProgram *draw2dprogram_ = nullptr; + GLRProgram *postShaderProgram_ = nullptr; - TextureCacheGLES *textureCacheGL_; - ShaderManagerGLES *shaderManagerGL_; - DrawEngineGLES *drawEngineGL_; + GLRProgram *stencilUploadProgram_ = nullptr; + int u_stencilUploadTex = -1; + int u_stencilValue = -1; + int u_postShaderTex = -1; + + // Cached uniform locs + int u_draw2d_tex = -1; - // Not used under ES currently. - AsyncPBO *pixelBufObj_; //this isn't that large - u8 currentPBO_; + int plainColorLoc_ = -1; + int videoLoc_ = -1; + int timeLoc_ = -1; + int pixelDeltaLoc_ = -1; + int deltaLoc_ = -1; + + TextureCacheGLES *textureCacheGL_ = nullptr; + ShaderManagerGLES *shaderManagerGL_ = nullptr; + DrawEngineGLES *drawEngineGL_ = nullptr; + + struct Simple2DVertex { + float pos[3]; + float uv[2]; + }; + GLRInputLayout *simple2DInputLayout_ = nullptr; }; diff --git a/GPU/GLES/GPU_GLES.cpp b/GPU/GLES/GPU_GLES.cpp index b6f112a0ae..4ca8419772 100644 --- a/GPU/GLES/GPU_GLES.cpp +++ b/GPU/GLES/GPU_GLES.cpp @@ -36,7 +36,6 @@ #include "GPU/GeDisasm.h" #include "GPU/Common/FramebufferCommon.h" -#include "ext/native/gfx/GLStateCache.h" #include "GPU/GLES/ShaderManagerGLES.h" #include "GPU/GLES/GPU_GLES.h" #include "GPU/GLES/FramebufferManagerGLES.h" @@ -79,12 +78,14 @@ static const GLESCommandTableEntry commandTable[] = { GPU_GLES::CommandInfo GPU_GLES::cmdInfo_[256]; GPU_GLES::GPU_GLES(GraphicsContext *gfxCtx, Draw::DrawContext *draw) -: GPUCommon(gfxCtx, draw) { +: GPUCommon(gfxCtx, draw), drawEngine_(draw), fragmentTestCache_(draw), depalShaderCache_(draw) { UpdateVsyncInterval(true); CheckGPUFeatures(); - shaderManagerGL_ = new ShaderManagerGLES(); - framebufferManagerGL_ = new FramebufferManagerGLES(draw); + GLRenderManager *render = (GLRenderManager *)draw->GetNativeObject(Draw::NativeObject::RENDER_MANAGER); + + shaderManagerGL_ = new ShaderManagerGLES(render); + framebufferManagerGL_ = new FramebufferManagerGLES(draw, render); framebufferManager_ = framebufferManagerGL_; textureCacheGL_ = new TextureCacheGLES(draw); textureCache_ = textureCacheGL_; @@ -158,10 +159,6 @@ GPU_GLES::GPU_GLES(GraphicsContext *gfxCtx, Draw::DrawContext *draw) // Update again after init to be sure of any silly driver problems. UpdateVsyncInterval(true); - // Some of our defaults are different from hw defaults, let's assert them. - // We restore each frame anyway, but here is convenient for tests. - glstate.Restore(); - drawEngine_.RestoreVAO(); textureCacheGL_->NotifyConfigChanged(); // Load shader cache. @@ -187,6 +184,10 @@ GPU_GLES::GPU_GLES(GraphicsContext *gfxCtx, Draw::DrawContext *draw) } GPU_GLES::~GPU_GLES() { + GLRenderManager *render = (GLRenderManager *)draw_->GetNativeObject(Draw::NativeObject::RENDER_MANAGER); + render->Wipe(); + render->WaitUntilQueueIdle(); + framebufferManagerGL_->DestroyAllFBOs(); shaderManagerGL_->ClearCache(true); depalShaderCache_.Clear(); @@ -298,9 +299,6 @@ void GPU_GLES::CheckGPUFeatures() { if (gl_extensions.OES_texture_npot) features |= GPU_SUPPORTS_OES_TEXTURE_NPOT; - if (gl_extensions.EXT_unpack_subimage) - features |= GPU_SUPPORTS_UNPACK_SUBIMAGE; - if (gl_extensions.EXT_blend_minmax) features |= GPU_SUPPORTS_BLEND_MINMAX; @@ -322,8 +320,7 @@ void GPU_GLES::CheckGPUFeatures() { if (instanceRendering) features |= GPU_SUPPORTS_INSTANCE_RENDERING; - int maxVertexTextureImageUnits; - glGetIntegerv(GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS, &maxVertexTextureImageUnits); + int maxVertexTextureImageUnits = gl_extensions.maxVertexTextureUnits; if (maxVertexTextureImageUnits >= 3) // At least 3 for hardware tessellation features |= GPU_SUPPORTS_VERTEX_TEXTURE_FETCH; @@ -368,30 +365,24 @@ bool GPU_GLES::IsReady() { return shaderManagerGL_->ContinuePrecompile(); } -// Let's avoid passing nulls into snprintf(). -static const char *GetGLStringAlways(GLenum name) { - const GLubyte *value = glGetString(name); - if (!value) - return "?"; - return (const char *)value; -} - // Needs to be called on GPU thread, not reporting thread. void GPU_GLES::BuildReportingInfo() { - const char *glVendor = GetGLStringAlways(GL_VENDOR); - const char *glRenderer = GetGLStringAlways(GL_RENDERER); - const char *glVersion = GetGLStringAlways(GL_VERSION); - const char *glSlVersion = GetGLStringAlways(GL_SHADING_LANGUAGE_VERSION); - const char *glExtensions = nullptr; + GLRenderManager *render = (GLRenderManager *)draw_->GetNativeObject(Draw::NativeObject::RENDER_MANAGER); + + std::string glVendor = render->GetGLString(GL_VENDOR); + std::string glRenderer = render->GetGLString(GL_RENDERER); + std::string glVersion = render->GetGLString(GL_VERSION); + std::string glSlVersion = render->GetGLString(GL_SHADING_LANGUAGE_VERSION); + std::string glExtensions; if (gl_extensions.VersionGEThan(3, 0)) { - glExtensions = g_all_gl_extensions.c_str(); + glExtensions = g_all_gl_extensions; } else { - glExtensions = GetGLStringAlways(GL_EXTENSIONS); + glExtensions = render->GetGLString(GL_EXTENSIONS); } char temp[16384]; - snprintf(temp, sizeof(temp), "%s (%s %s), %s (extensions: %s)", glVersion, glVendor, glRenderer, glSlVersion, glExtensions); + snprintf(temp, sizeof(temp), "%s (%s %s), %s (extensions: %s)", glVersion.c_str(), glVendor.c_str(), glRenderer.c_str(), glSlVersion.c_str(), glExtensions.c_str()); reportingPrimaryInfo_ = glVendor; reportingFullInfo_ = temp; @@ -401,6 +392,10 @@ void GPU_GLES::BuildReportingInfo() { void GPU_GLES::DeviceLost() { ILOG("GPU_GLES: DeviceLost"); + // Simply drop all caches and textures. + // FBOs appear to survive? Or no? + // TransformDraw has registered as a GfxResourceHolder. + drawEngine_.ClearInputLayoutMap(); shaderManagerGL_->ClearCache(false); textureCacheGL_->Clear(false); fragmentTestCache_.Clear(false); @@ -429,14 +424,6 @@ void GPU_GLES::Reinitialize() { } void GPU_GLES::InitClear() { - bool useNonBufferedRendering = g_Config.iRenderingMode == FB_NON_BUFFERED_MODE; - if (useNonBufferedRendering) { - glstate.depthWrite.set(GL_TRUE); - glstate.colorMask.set(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); - glClearColor(0,0,0,1); - glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); - } - glstate.viewport.set(0, 0, PSP_CoreParameter().pixelWidth, PSP_CoreParameter().pixelHeight); } void GPU_GLES::BeginHostFrame() { @@ -449,6 +436,12 @@ void GPU_GLES::BeginHostFrame() { shaderManagerGL_->DirtyShader(); textureCacheGL_->NotifyConfigChanged(); } + + drawEngine_.BeginFrame(); +} + +void GPU_GLES::EndHostFrame() { + drawEngine_.EndFrame(); } inline void GPU_GLES::UpdateVsyncInterval(bool force) { @@ -489,8 +482,6 @@ void GPU_GLES::UpdateCmdInfo() { } void GPU_GLES::ReapplyGfxState() { - drawEngine_.RestoreVAO(); - glstate.Restore(); GPUCommon::ReapplyGfxState(); } @@ -500,7 +491,6 @@ void GPU_GLES::BeginFrame() { textureCacheGL_->StartFrame(); drawEngine_.DecimateTrackedVertexArrays(); - drawEngine_.DecimateBuffers(); depalShaderCache_.Decimate(); fragmentTestCache_.Decimate(); @@ -551,9 +541,6 @@ void GPU_GLES::CopyDisplayToOutput() { shaderManagerGL_->DirtyLastShader(); - glstate.depthWrite.set(GL_TRUE); - glstate.colorMask.set(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); - framebufferManagerGL_->CopyDisplayToOutput(); framebufferManagerGL_->EndFrame(); @@ -762,8 +749,6 @@ void GPU_GLES::ClearShaderCache() { void GPU_GLES::CleanupBeforeUI() { // Clear any enabled vertex arrays. shaderManagerGL_->DirtyLastShader(); - glstate.arrayBuffer.bind(0); - glstate.elementArrayBuffer.bind(0); } void GPU_GLES::DoState(PointerWrap &p) { diff --git a/GPU/GLES/GPU_GLES.h b/GPU/GLES/GPU_GLES.h index f99fe6d3e3..4378f166a7 100644 --- a/GPU/GLES/GPU_GLES.h +++ b/GPU/GLES/GPU_GLES.h @@ -80,6 +80,7 @@ public: std::string DebugGetShaderString(std::string id, DebugShaderType shader, DebugShaderStringType stringType) override; void BeginHostFrame() override; + void EndHostFrame() override; protected: void FastRunLoop(DisplayList &list) override; diff --git a/GPU/GLES/ShaderManagerGLES.cpp b/GPU/GLES/ShaderManagerGLES.cpp index 11ee8d736d..bb06521ea6 100644 --- a/GPU/GLES/ShaderManagerGLES.cpp +++ b/GPU/GLES/ShaderManagerGLES.cpp @@ -26,6 +26,8 @@ #include "base/logging.h" #include "base/timeutil.h" #include "gfx/gl_debug_log.h" +#include "gfx_es2/gpu_features.h" +#include "thin3d/GLRenderManager.h" #include "i18n/i18n.h" #include "math/math_util.h" #include "math/lin/matrix4x4.h" @@ -38,13 +40,12 @@ #include "GPU/Math3D.h" #include "GPU/GPUState.h" #include "GPU/ge_constants.h" -#include "ext/native/gfx/GLStateCache.h" #include "GPU/GLES/ShaderManagerGLES.h" #include "GPU/GLES/DrawEngineGLES.h" #include "FramebufferManagerGLES.h" -Shader::Shader(const ShaderID &id, const char *code, uint32_t glShaderType, bool useHWTransform, uint32_t attrMask, uint64_t uniformMask) - : failed_(false), useHWTransform_(useHWTransform), attrMask_(attrMask), uniformMask_(uniformMask) { +Shader::Shader(GLRenderManager *render, const char *code, const std::string &desc, uint32_t glShaderType, bool useHWTransform, uint32_t attrMask, uint64_t uniformMask) + : render_(render), failed_(false), useHWTransform_(useHWTransform), attrMask_(attrMask), uniformMask_(uniformMask) { PROFILE_THIS_SCOPE("shadercomp"); isFragment_ = glShaderType == GL_FRAGMENT_SHADER; source_ = code; @@ -55,244 +56,153 @@ Shader::Shader(const ShaderID &id, const char *code, uint32_t glShaderType, bool printf("%s\n", code); #endif #endif - shader = glCreateShader(glShaderType); - glShaderSource(shader, 1, &code, 0); - glCompileShader(shader); - GLint success = 0; - glGetShaderiv(shader, GL_COMPILE_STATUS, &success); - if (!success) { -#define MAX_INFO_LOG_SIZE 2048 - GLchar infoLog[MAX_INFO_LOG_SIZE]; - GLsizei len; - glGetShaderInfoLog(shader, MAX_INFO_LOG_SIZE, &len, infoLog); - infoLog[len] = '\0'; -#ifdef __ANDROID__ - ELOG("Error in shader compilation! %s\n", infoLog); - ELOG("Shader source:\n%s\n", (const char *)code); -#endif - - std::string desc = GetShaderString(SHADER_STRING_SHORT_DESC, id); - ERROR_LOG(G3D, "Error in shader compilation for: %s", desc.c_str()); - ERROR_LOG(G3D, "Info log: %s", infoLog); - ERROR_LOG(G3D, "Shader source:\n%s\n", (const char *)code); - Reporting::ReportMessage("Error in shader compilation: info: %s\n%s\n%s", infoLog, desc.c_str(), (const char *)code); -#ifdef SHADERLOG - OutputDebugStringUTF8(infoLog); -#endif - failed_ = true; - shader = 0; - } else { - DEBUG_LOG(G3D, "Compiled shader:\n%s\n", (const char *)code); - } - CHECK_GL_ERROR_IF_DEBUG(); + shader = render->CreateShader(glShaderType, source_, desc); } Shader::~Shader() { - if (shader) - glDeleteShader(shader); + render_->DeleteShader(shader); } -LinkedShader::LinkedShader(VShaderID VSID, Shader *vs, FShaderID FSID, Shader *fs, bool useHWTransform, bool preloading) - : useHWTransform_(useHWTransform) { +LinkedShader::LinkedShader(GLRenderManager *render, VShaderID VSID, Shader *vs, FShaderID FSID, Shader *fs, bool useHWTransform, bool preloading) + : render_(render), useHWTransform_(useHWTransform) { PROFILE_THIS_SCOPE("shaderlink"); - program = glCreateProgram(); vs_ = vs; - glAttachShader(program, vs->shader); - glAttachShader(program, fs->shader); - // Bind attribute locations to fixed locations so that they're - // the same in all shaders. We use this later to minimize the calls to - // glEnableVertexAttribArray and glDisableVertexAttribArray. - glBindAttribLocation(program, ATTR_POSITION, "position"); - glBindAttribLocation(program, ATTR_TEXCOORD, "texcoord"); - glBindAttribLocation(program, ATTR_NORMAL, "normal"); - glBindAttribLocation(program, ATTR_W1, "w1"); - glBindAttribLocation(program, ATTR_W2, "w2"); - glBindAttribLocation(program, ATTR_COLOR0, "color0"); - glBindAttribLocation(program, ATTR_COLOR1, "color1"); + std::vector shaders; + shaders.push_back(vs->shader); + shaders.push_back(fs->shader); -#if !defined(USING_GLES2) - if (gstate_c.Supports(GPU_SUPPORTS_DUALSOURCE_BLEND)) { - // Dual source alpha - glBindFragDataLocationIndexed(program, 0, 0, "fragColor0"); - glBindFragDataLocationIndexed(program, 0, 1, "fragColor1"); - } else if (gl_extensions.VersionGEThan(3, 3, 0)) { - glBindFragDataLocation(program, 0, "fragColor0"); - } -#elif !defined(IOS) - if (gl_extensions.GLES3) { - if (gstate_c.Supports(GPU_SUPPORTS_DUALSOURCE_BLEND)) { - glBindFragDataLocationIndexedEXT(program, 0, 0, "fragColor0"); - glBindFragDataLocationIndexedEXT(program, 0, 1, "fragColor1"); - } - } -#endif - glLinkProgram(program); + std::vector semantics; + semantics.push_back({ ATTR_POSITION, "position" }); + semantics.push_back({ ATTR_TEXCOORD, "texcoord" }); + semantics.push_back({ ATTR_NORMAL, "normal" }); + semantics.push_back({ ATTR_W1, "w1" }); + semantics.push_back({ ATTR_W2, "w2" }); + semantics.push_back({ ATTR_COLOR0, "color0" }); + semantics.push_back({ ATTR_COLOR1, "color1" }); - GLint linkStatus = GL_FALSE; - glGetProgramiv(program, GL_LINK_STATUS, &linkStatus); - if (linkStatus != GL_TRUE) { - GLint bufLength = 0; - glGetProgramiv(program, GL_INFO_LOG_LENGTH, &bufLength); - if (bufLength) { - char* buf = new char[bufLength]; - glGetProgramInfoLog(program, bufLength, NULL, buf); -#ifdef __ANDROID__ - ELOG("Could not link program:\n %s", buf); -#endif - ERROR_LOG(G3D, "Could not link program:\n %s", buf); + std::vector queries; + queries.push_back({ &u_tex, "tex" }); + queries.push_back({ &u_proj, "u_proj" }); + queries.push_back({ &u_proj_through, "u_proj_through" }); - std::string vs_desc = vs->GetShaderString(SHADER_STRING_SHORT_DESC, VSID); - std::string fs_desc = fs->GetShaderString(SHADER_STRING_SHORT_DESC, FSID); - std::string vs_source = vs->GetShaderString(SHADER_STRING_SOURCE_CODE, VSID); - std::string fs_source = fs->GetShaderString(SHADER_STRING_SOURCE_CODE, FSID); + queries.push_back({ &u_proj, "u_proj" }); + queries.push_back({ &u_proj_through, "u_proj_through" }); + queries.push_back({ &u_texenv, "u_texenv" }); + queries.push_back({ &u_fogcolor, "u_fogcolor" }); + queries.push_back({ &u_fogcoef, "u_fogcoef" }); + queries.push_back({ &u_alphacolorref, "u_alphacolorref" }); + queries.push_back({ &u_alphacolormask, "u_alphacolormask" }); + queries.push_back({ &u_stencilReplaceValue, "u_stencilReplaceValue" }); + queries.push_back({ &u_testtex, "testtex" }); - ERROR_LOG(G3D, "VS desc:\n%s", vs_desc.c_str()); - ERROR_LOG(G3D, "FS desc:\n%s", fs_desc.c_str()); - ERROR_LOG(G3D, "VS:\n%s\n", vs_source.c_str()); - ERROR_LOG(G3D, "FS:\n%s\n", fs_source.c_str()); - if (preloading) { - Reporting::ReportMessage("Error in shader program link during preload: info: %s\nfs: %s\n%s\nvs: %s\n%s", buf, fs_desc.c_str(), fs_source.c_str(), vs_desc.c_str(), vs_source.c_str()); - } else { - Reporting::ReportMessage("Error in shader program link: info: %s\nfs: %s\n%s\nvs: %s\n%s", buf, fs_desc.c_str(), fs_source.c_str(), vs_desc.c_str(), vs_source.c_str()); - } -#ifdef SHADERLOG - OutputDebugStringUTF8(buf); - OutputDebugStringUTF8(vs_source.c_str()); - OutputDebugStringUTF8(fs_source.c_str()); -#endif - delete [] buf; // we're dead! - } - // Prevent a buffer overflow. - numBones = 0; - // Avoid weird attribute enables. - attrMask = 0; - availableUniforms = 0; - return; - } - - INFO_LOG(G3D, "Linked shader: vs %i fs %i", (int)vs->shader, (int)fs->shader); - - u_tex = glGetUniformLocation(program, "tex"); - u_proj = glGetUniformLocation(program, "u_proj"); - u_proj_through = glGetUniformLocation(program, "u_proj_through"); - u_texenv = glGetUniformLocation(program, "u_texenv"); - u_fogcolor = glGetUniformLocation(program, "u_fogcolor"); - u_fogcoef = glGetUniformLocation(program, "u_fogcoef"); - u_alphacolorref = glGetUniformLocation(program, "u_alphacolorref"); - u_alphacolormask = glGetUniformLocation(program, "u_alphacolormask"); - u_stencilReplaceValue = glGetUniformLocation(program, "u_stencilReplaceValue"); - u_testtex = glGetUniformLocation(program, "testtex"); - - u_fbotex = glGetUniformLocation(program, "fbotex"); - u_blendFixA = glGetUniformLocation(program, "u_blendFixA"); - u_blendFixB = glGetUniformLocation(program, "u_blendFixB"); - u_fbotexSize = glGetUniformLocation(program, "u_fbotexSize"); + queries.push_back({ &u_fbotex, "fbotex" }); + queries.push_back({ &u_blendFixA, "u_blendFixA" }); + queries.push_back({ &u_blendFixB, "u_blendFixB" }); + queries.push_back({ &u_fbotexSize, "u_fbotexSize" }); // Transform - u_view = glGetUniformLocation(program, "u_view"); - u_world = glGetUniformLocation(program, "u_world"); - u_texmtx = glGetUniformLocation(program, "u_texmtx"); + queries.push_back({ &u_view, "u_view" }); + queries.push_back({ &u_world, "u_world" }); + queries.push_back({ &u_texmtx, "u_texmtx" }); + if (VSID.Bit(VS_BIT_ENABLE_BONES)) numBones = TranslateNumBones(VSID.Bits(VS_BIT_BONES, 3) + 1); else numBones = 0; - u_depthRange = glGetUniformLocation(program, "u_depthRange"); + queries.push_back({ &u_depthRange, "u_depthRange" }); #ifdef USE_BONE_ARRAY - u_bone = glGetUniformLocation(program, "u_bone"); + queries.push_back({ &u_bone, "u_bone" }); #else + static const char * const boneNames[8] = { "u_bone0", "u_bone1", "u_bone2", "u_bone3", "u_bone4", "u_bone5", "u_bone6", "u_bone7", }; for (int i = 0; i < 8; i++) { - char name[10]; - sprintf(name, "u_bone%i", i); - u_bone[i] = glGetUniformLocation(program, name); + queries.push_back({ &u_bone[i], boneNames[i] }); } #endif // Lighting, texturing - u_ambient = glGetUniformLocation(program, "u_ambient"); - u_matambientalpha = glGetUniformLocation(program, "u_matambientalpha"); - u_matdiffuse = glGetUniformLocation(program, "u_matdiffuse"); - u_matspecular = glGetUniformLocation(program, "u_matspecular"); - u_matemissive = glGetUniformLocation(program, "u_matemissive"); - u_uvscaleoffset = glGetUniformLocation(program, "u_uvscaleoffset"); - u_texclamp = glGetUniformLocation(program, "u_texclamp"); - u_texclampoff = glGetUniformLocation(program, "u_texclampoff"); + queries.push_back({ &u_ambient, "u_ambient" }); + queries.push_back({ &u_matambientalpha, "u_matambientalpha" }); + queries.push_back({ &u_matdiffuse, "u_matdiffuse" }); + queries.push_back({ &u_matspecular, "u_matspecular" }); + queries.push_back({ &u_matemissive, "u_matemissive" }); + queries.push_back({ &u_uvscaleoffset, "u_uvscaleoffset" }); + queries.push_back({ &u_texclamp, "u_texclamp" }); + queries.push_back({ &u_texclampoff, "u_texclampoff" }); for (int i = 0; i < 4; i++) { - char temp[64]; - sprintf(temp, "u_lightpos%i", i); - u_lightpos[i] = glGetUniformLocation(program, temp); - sprintf(temp, "u_lightdir%i", i); - u_lightdir[i] = glGetUniformLocation(program, temp); - sprintf(temp, "u_lightatt%i", i); - u_lightatt[i] = glGetUniformLocation(program, temp); - sprintf(temp, "u_lightangle%i", i); - u_lightangle[i] = glGetUniformLocation(program, temp); - sprintf(temp, "u_lightspotCoef%i", i); - u_lightspotCoef[i] = glGetUniformLocation(program, temp); - sprintf(temp, "u_lightambient%i", i); - u_lightambient[i] = glGetUniformLocation(program, temp); - sprintf(temp, "u_lightdiffuse%i", i); - u_lightdiffuse[i] = glGetUniformLocation(program, temp); - sprintf(temp, "u_lightspecular%i", i); - u_lightspecular[i] = glGetUniformLocation(program, temp); + static const char * const lightPosNames[4] = { "u_lightpos0", "u_lightpos1", "u_lightpos2", "u_lightpos3", }; + queries.push_back({ &u_lightpos[i], lightPosNames[i] }); + static const char * const lightdir_names[4] = { "u_lightdir0", "u_lightdir1", "u_lightdir2", "u_lightdir3", }; + queries.push_back({ &u_lightdir[i], lightdir_names[i] }); + static const char * const lightatt_names[4] = { "u_lightatt0", "u_lightatt1", "u_lightatt2", "u_lightatt3", }; + queries.push_back({ &u_lightatt[i], lightatt_names[i] }); + static const char * const lightangle_names[4] = { "u_lightangle0", "u_lightangle1", "u_lightangle2", "u_lightangle3", }; + queries.push_back({ &u_lightangle[i], lightangle_names[i] }); + + static const char * const lightspotCoef_names[4] = { "u_lightspotCoef0", "u_lightspotCoef1", "u_lightspotCoef2", "u_lightspotCoef3", }; + queries.push_back({ &u_lightspotCoef[i], lightspotCoef_names[i] }); + static const char * const lightambient_names[4] = { "u_lightambient0", "u_lightambient1", "u_lightambient2", "u_lightambient3", }; + queries.push_back({ &u_lightambient[i], lightambient_names[i] }); + static const char * const lightdiffuse_names[4] = { "u_lightdiffuse0", "u_lightdiffuse1", "u_lightdiffuse2", "u_lightdiffuse3", }; + queries.push_back({ &u_lightdiffuse[i], lightdiffuse_names[i] }); + static const char * const lightspecular_names[4] = { "u_lightspecular0", "u_lightspecular1", "u_lightspecular2", "u_lightspecular3", }; + queries.push_back({ &u_lightspecular[i], lightspecular_names[i] }); } // We need to fetch these unconditionally, gstate_c.spline or bezier will not be set if we // create this shader at load time from the shader cache. - u_tess_pos_tex = glGetUniformLocation(program, "u_tess_pos_tex"); - u_tess_tex_tex = glGetUniformLocation(program, "u_tess_tex_tex"); - u_tess_col_tex = glGetUniformLocation(program, "u_tess_col_tex"); - u_spline_count_u = glGetUniformLocation(program, "u_spline_count_u"); - u_spline_count_v = glGetUniformLocation(program, "u_spline_count_v"); - u_spline_type_u = glGetUniformLocation(program, "u_spline_type_u"); - u_spline_type_v = glGetUniformLocation(program, "u_spline_type_v"); + queries.push_back({ &u_tess_pos_tex, "u_tess_pos_tex" }); + queries.push_back({ &u_tess_tex_tex, "u_tess_tex_tex" }); + queries.push_back({ &u_tess_col_tex, "u_tess_col_tex" }); + queries.push_back({ &u_spline_count_u, "u_spline_count_u" }); + queries.push_back({ &u_spline_count_v, "u_spline_count_v" }); + queries.push_back({ &u_spline_type_u, "u_spline_type_u" }); + queries.push_back({ &u_spline_type_v, "u_spline_type_v" }); attrMask = vs->GetAttrMask(); availableUniforms = vs->GetUniformMask() | fs->GetUniformMask(); - glUseProgram(program); + std::vector initialize; + initialize.push_back({ &u_tex, 0, 0 }); + initialize.push_back({ &u_fbotex, 0, 1 }); + initialize.push_back({ &u_testtex, 0, 2 }); + initialize.push_back({ &u_tess_pos_tex, 0, 4 }); // Texture unit 4 + initialize.push_back({ &u_tess_tex_tex, 0, 5 }); // Texture unit 5 + initialize.push_back({ &u_tess_col_tex, 0, 6 }); // Texture unit 6 - // Default uniform values - glUniform1i(u_tex, 0); - glUniform1i(u_fbotex, 1); - glUniform1i(u_testtex, 2); - - if (u_tess_pos_tex != -1) - glUniform1i(u_tess_pos_tex, 4); // Texture unit 4 - if (u_tess_tex_tex != -1) - glUniform1i(u_tess_tex_tex, 5); // Texture unit 5 - if (u_tess_col_tex != -1) - glUniform1i(u_tess_col_tex, 6); // Texture unit 6 + program = render->CreateProgram(shaders, semantics, queries, initialize, gstate_c.featureFlags & GPU_SUPPORTS_DUALSOURCE_BLEND); // The rest, use the "dirty" mechanism. dirtyUniforms = DIRTY_ALL_UNIFORMS; - CHECK_GL_ERROR_IF_DEBUG(); } LinkedShader::~LinkedShader() { - // Shaders are automatically detached by glDeleteProgram. - glDeleteProgram(program); + render_->DeleteProgram(program); } // Utility -static void SetColorUniform3(int uniform, u32 color) { - float f[4]; - Uint8x4ToFloat4(f, color); - glUniform3fv(uniform, 1, f); +static inline void SetFloatUniform(GLRenderManager *render, GLint *uniform, float value) { + render->SetUniformF(uniform, 1, &value); } -static void SetColorUniform3Alpha(int uniform, u32 color, u8 alpha) { +static inline void SetColorUniform3(GLRenderManager *render, GLint *uniform, u32 color) { + float f[4]; + Uint8x4ToFloat4(f, color); + render->SetUniformF(uniform, 3, f); +} + +static void SetColorUniform3Alpha(GLRenderManager *render, GLint *uniform, u32 color, u8 alpha) { float f[4]; Uint8x3ToFloat4_AlphaUint8(f, color, alpha); - glUniform4fv(uniform, 1, f); + render->SetUniformF(uniform, 4, f); } // This passes colors unscaled (e.g. 0 - 255 not 0 - 1.) -static void SetColorUniform3Alpha255(int uniform, u32 color, u8 alpha) { +static void SetColorUniform3Alpha255(GLRenderManager *render, GLint *uniform, u32 color, u8 alpha) { if (gl_extensions.gpuVendor == GPU_VENDOR_IMGTEC) { const float col[4] = { (float)((color & 0xFF) >> 0) * (1.0f / 255.0f), @@ -300,7 +210,7 @@ static void SetColorUniform3Alpha255(int uniform, u32 color, u8 alpha) { (float)((color & 0xFF0000) >> 16) * (1.0f / 255.0f), (float)alpha * (1.0f / 255.0f) }; - glUniform4fv(uniform, 1, col); + render->SetUniformF(uniform, 4, col); } else { const float col[4] = { (float)((color & 0xFF) >> 0), @@ -308,44 +218,44 @@ static void SetColorUniform3Alpha255(int uniform, u32 color, u8 alpha) { (float)((color & 0xFF0000) >> 16), (float)alpha }; - glUniform4fv(uniform, 1, col); + render->SetUniformF(uniform, 4, col); } } -static void SetColorUniform3iAlpha(int uniform, u32 color, u8 alpha) { +static void SetColorUniform3iAlpha(GLRenderManager *render, GLint *uniform, u32 color, u8 alpha) { const int col[4] = { (int)((color & 0xFF) >> 0), (int)((color & 0xFF00) >> 8), (int)((color & 0xFF0000) >> 16), (int)alpha, }; - glUniform4iv(uniform, 1, col); + render->SetUniformI(uniform, 4, col); } -static void SetColorUniform3ExtraFloat(int uniform, u32 color, float extra) { +static void SetColorUniform3ExtraFloat(GLRenderManager *render, GLint *uniform, u32 color, float extra) { const float col[4] = { ((color & 0xFF)) / 255.0f, ((color & 0xFF00) >> 8) / 255.0f, ((color & 0xFF0000) >> 16) / 255.0f, extra }; - glUniform4fv(uniform, 1, col); + render->SetUniformF(uniform, 4, col); } -static void SetFloat24Uniform3(int uniform, const uint32_t data[3]) { +static void SetFloat24Uniform3(GLRenderManager *render, GLint *uniform, const uint32_t data[3]) { float f[4]; ExpandFloat24x3ToFloat4(f, data); - glUniform3fv(uniform, 1, f); + render->SetUniformF(uniform, 3, f); } -static void SetFloatUniform4(int uniform, float data[4]) { - glUniform4fv(uniform, 1, data); +static void SetFloatUniform4(GLRenderManager *render, GLint *uniform, float data[4]) { + render->SetUniformF(uniform, 4, data); } -static void SetMatrix4x3(int uniform, const float *m4x3) { +static void SetMatrix4x3(GLRenderManager *render, GLint *uniform, const float *m4x3) { float m4x4[16]; ConvertMatrix4x3To4x4(m4x4, m4x3); - glUniformMatrix4fv(uniform, 1, GL_FALSE, m4x4); + render->SetUniformM4x4(uniform, m4x4); } static inline void ScaleProjMatrix(Matrix4x4 &in) { @@ -359,34 +269,12 @@ static inline void ScaleProjMatrix(Matrix4x4 &in) { in.translateAndScale(trans, scale); } -void LinkedShader::use(const VShaderID &VSID, LinkedShader *previous) { - glUseProgram(program); - int enable, disable; - if (previous) { - enable = attrMask & ~previous->attrMask; - disable = (~attrMask) & previous->attrMask; - } else { - enable = attrMask; - disable = ~attrMask; - } - for (int i = 0; i < ATTR_COUNT; i++) { - if (enable & (1 << i)) - glEnableVertexAttribArray(i); - else if (disable & (1 << i)) - glDisableVertexAttribArray(i); - } +void LinkedShader::use(const ShaderID &VSID) { + render_->BindProgram(program); + // Note that we no longer track attr masks here - we do it for the input layouts instead. } -void LinkedShader::stop() { - for (int i = 0; i < ATTR_COUNT; i++) { - if (attrMask & (1 << i)) - glDisableVertexAttribArray(i); - } -} - -void LinkedShader::UpdateUniforms(u32 vertType, const VShaderID &vsid) { - CHECK_GL_ERROR_IF_DEBUG(); - +void LinkedShader::UpdateUniforms(u32 vertType, const ShaderID &vsid) { u64 dirty = dirtyUniforms & availableUniforms; dirtyUniforms = 0; if (!dirty) @@ -448,7 +336,7 @@ void LinkedShader::UpdateUniforms(u32 vertType, const VShaderID &vsid) { ScaleProjMatrix(flippedMatrix); - glUniformMatrix4fv(u_proj, 1, GL_FALSE, flippedMatrix.m); + render_->SetUniformM4x4(&u_proj, flippedMatrix.m); } if (dirty & DIRTY_PROJTHROUGHMATRIX) { @@ -459,19 +347,19 @@ void LinkedShader::UpdateUniforms(u32 vertType, const VShaderID &vsid) { } else { proj_through.setOrtho(0.0f, gstate_c.curRTWidth, gstate_c.curRTHeight, 0.0f, 0.0f, 1.0f); } - glUniformMatrix4fv(u_proj_through, 1, GL_FALSE, proj_through.getReadPtr()); + render_->SetUniformM4x4(&u_proj_through, proj_through.getReadPtr()); } if (dirty & DIRTY_TEXENV) { - SetColorUniform3(u_texenv, gstate.texenvcolor); + SetColorUniform3(render_, &u_texenv, gstate.texenvcolor); } if (dirty & DIRTY_ALPHACOLORREF) { - SetColorUniform3Alpha255(u_alphacolorref, gstate.getColorTestRef(), gstate.getAlphaTestRef() & gstate.getAlphaTestMask()); + SetColorUniform3Alpha255(render_, &u_alphacolorref, gstate.getColorTestRef(), gstate.getAlphaTestRef() & gstate.getAlphaTestMask()); } if (dirty & DIRTY_ALPHACOLORMASK) { - SetColorUniform3iAlpha(u_alphacolormask, gstate.colortestmask, gstate.getAlphaTestMask()); + SetColorUniform3iAlpha(render_, &u_alphacolormask, gstate.colortestmask, gstate.getAlphaTestMask()); } if (dirty & DIRTY_FOGCOLOR) { - SetColorUniform3(u_fogcolor, gstate.fogcolor); + SetColorUniform3(render_, &u_fogcolor, gstate.fogcolor); } if (dirty & DIRTY_FOGCOEF) { float fogcoef[2] = { @@ -494,7 +382,7 @@ void LinkedShader::UpdateUniforms(u32 vertType, const VShaderID &vsid) { ERROR_LOG_REPORT_ONCE(fognan, G3D, "Unhandled fog NaN/INF combo: %f %f", fogcoef[0], fogcoef[1]); } #endif - glUniform2fv(u_fogcoef, 1, fogcoef); + render_->SetUniformF(&u_fogcoef, 2, fogcoef); } if (dirty & DIRTY_UVSCALEOFFSET) { @@ -518,7 +406,7 @@ void LinkedShader::UpdateUniforms(u32 vertType, const VShaderID &vsid) { uvscaleoff[2] = 0.0f; uvscaleoff[3] = 0.0f; } - glUniform4fv(u_uvscaleoffset, 1, uvscaleoff); + render_->SetUniformF(&u_uvscaleoffset, 4, uvscaleoff); } if ((dirty & DIRTY_TEXCLAMP) && u_texclamp != -1) { @@ -540,21 +428,21 @@ void LinkedShader::UpdateUniforms(u32 vertType, const VShaderID &vsid) { gstate_c.curTextureXOffset * invW, gstate_c.curTextureYOffset * invH, }; - glUniform4fv(u_texclamp, 1, texclamp); + render_->SetUniformF(&u_texclamp, 4, texclamp); if (u_texclampoff != -1) { - glUniform2fv(u_texclampoff, 1, texclampoff); + render_->SetUniformF(&u_texclampoff, 2, texclampoff); } } // Transform if (dirty & DIRTY_WORLDMATRIX) { - SetMatrix4x3(u_world, gstate.worldMatrix); + SetMatrix4x3(render_, &u_world, gstate.worldMatrix); } if (dirty & DIRTY_VIEWMATRIX) { - SetMatrix4x3(u_view, gstate.viewMatrix); + SetMatrix4x3(render_, &u_view, gstate.viewMatrix); } if (dirty & DIRTY_TEXMATRIX) { - SetMatrix4x3(u_texmtx, gstate.tgenMatrix); + SetMatrix4x3(render_, &u_texmtx, gstate.tgenMatrix); } if ((dirty & DIRTY_DEPTHRANGE) && u_depthRange != -1) { // Since depth is [-1, 1] mapping to [minz, maxz], this is easyish. @@ -580,53 +468,27 @@ void LinkedShader::UpdateUniforms(u32 vertType, const VShaderID &vsid) { } float data[4] = { viewZScale, viewZCenter, viewZCenter, viewZInvScale }; - SetFloatUniform4(u_depthRange, data); + SetFloatUniform4(render_, &u_depthRange, data); } if (dirty & DIRTY_STENCILREPLACEVALUE) { - glUniform1f(u_stencilReplaceValue, (float)gstate.getStencilTestRef() * (1.0f / 255.0f)); + float f = (float)gstate.getStencilTestRef() * (1.0f / 255.0f); + render_->SetUniformF(&u_stencilReplaceValue, 1, &f); } - // TODO: Could even set all bones in one go if they're all dirty. -#ifdef USE_BONE_ARRAY - if (u_bone != -1) { - float allBones[8 * 16]; - - bool allDirty = true; - for (int i = 0; i < numBones; i++) { - if (dirty & (DIRTY_BONEMATRIX0 << i)) { - ConvertMatrix4x3To4x4(allBones + 16 * i, gstate.boneMatrix + 12 * i); - } else { - allDirty = false; - } - } - if (allDirty) { - // Set them all with one call - glUniformMatrix4fv(u_bone, numBones, GL_FALSE, allBones); - } else { - // Set them one by one. Could try to coalesce two in a row etc but too lazy. - for (int i = 0; i < numBones; i++) { - if (dirty & (DIRTY_BONEMATRIX0 << i)) { - glUniformMatrix4fv(u_bone + i, 1, GL_FALSE, allBones + 16 * i); - } - } - } - } -#else float bonetemp[16]; for (int i = 0; i < numBones; i++) { if (dirty & (DIRTY_BONEMATRIX0 << i)) { ConvertMatrix4x3To4x4(bonetemp, gstate.boneMatrix + 12 * i); - glUniformMatrix4fv(u_bone[i], 1, GL_FALSE, bonetemp); + render_->SetUniformM4x4(&u_bone[i], bonetemp); } } -#endif if (dirty & DIRTY_SHADERBLEND) { if (u_blendFixA != -1) { - SetColorUniform3(u_blendFixA, gstate.getFixA()); + SetColorUniform3(render_, &u_blendFixA, gstate.getFixA()); } if (u_blendFixB != -1) { - SetColorUniform3(u_blendFixB, gstate.getFixB()); + SetColorUniform3(render_, &u_blendFixB, gstate.getFixB()); } const float fbotexSize[2] = { @@ -634,25 +496,25 @@ void LinkedShader::UpdateUniforms(u32 vertType, const VShaderID &vsid) { 1.0f / (float)gstate_c.curRTRenderHeight, }; if (u_fbotexSize != -1) { - glUniform2fv(u_fbotexSize, 1, fbotexSize); + render_->SetUniformF(&u_fbotexSize, 2, fbotexSize); } } // Lighting if (dirty & DIRTY_AMBIENT) { - SetColorUniform3Alpha(u_ambient, gstate.ambientcolor, gstate.getAmbientA()); + SetColorUniform3Alpha(render_, &u_ambient, gstate.ambientcolor, gstate.getAmbientA()); } if (dirty & DIRTY_MATAMBIENTALPHA) { - SetColorUniform3Alpha(u_matambientalpha, gstate.materialambient, gstate.getMaterialAmbientA()); + SetColorUniform3Alpha(render_, &u_matambientalpha, gstate.materialambient, gstate.getMaterialAmbientA()); } if (dirty & DIRTY_MATDIFFUSE) { - SetColorUniform3(u_matdiffuse, gstate.materialdiffuse); + SetColorUniform3(render_, &u_matdiffuse, gstate.materialdiffuse); } if (dirty & DIRTY_MATEMISSIVE) { - SetColorUniform3(u_matemissive, gstate.materialemissive); + SetColorUniform3(render_, &u_matemissive, gstate.materialemissive); } if (dirty & DIRTY_MATSPECULAR) { - SetColorUniform3ExtraFloat(u_matspecular, gstate.materialspecular, getFloat24(gstate.materialspecularcoef)); + SetColorUniform3ExtraFloat(render_, &u_matspecular, gstate.materialspecular, getFloat24(gstate.materialspecularcoef)); } for (int i = 0; i < 4; i++) { @@ -668,34 +530,33 @@ void LinkedShader::UpdateUniforms(u32 vertType, const VShaderID &vsid) { else len = 1.0f / len; float vec[3] = { x * len, y * len, z * len }; - glUniform3fv(u_lightpos[i], 1, vec); + render_->SetUniformF(&u_lightpos[i], 3, vec); } else { - SetFloat24Uniform3(u_lightpos[i], &gstate.lpos[i * 3]); + SetFloat24Uniform3(render_, &u_lightpos[i], &gstate.lpos[i * 3]); } - if (u_lightdir[i] != -1) SetFloat24Uniform3(u_lightdir[i], &gstate.ldir[i * 3]); - if (u_lightatt[i] != -1) SetFloat24Uniform3(u_lightatt[i], &gstate.latt[i * 3]); - if (u_lightangle[i] != -1) glUniform1f(u_lightangle[i], getFloat24(gstate.lcutoff[i])); - if (u_lightspotCoef[i] != -1) glUniform1f(u_lightspotCoef[i], getFloat24(gstate.lconv[i])); - if (u_lightambient[i] != -1) SetColorUniform3(u_lightambient[i], gstate.lcolor[i * 3]); - if (u_lightdiffuse[i] != -1) SetColorUniform3(u_lightdiffuse[i], gstate.lcolor[i * 3 + 1]); - if (u_lightspecular[i] != -1) SetColorUniform3(u_lightspecular[i], gstate.lcolor[i * 3 + 2]); + if (u_lightdir[i] != -1) SetFloat24Uniform3(render_, &u_lightdir[i], &gstate.ldir[i * 3]); + if (u_lightatt[i] != -1) SetFloat24Uniform3(render_, &u_lightatt[i], &gstate.latt[i * 3]); + if (u_lightangle[i] != -1) SetFloatUniform(render_, &u_lightangle[i], getFloat24(gstate.lcutoff[i])); + if (u_lightspotCoef[i] != -1) SetFloatUniform(render_, &u_lightspotCoef[i], getFloat24(gstate.lconv[i])); + if (u_lightambient[i] != -1) SetColorUniform3(render_, &u_lightambient[i], gstate.lcolor[i * 3]); + if (u_lightdiffuse[i] != -1) SetColorUniform3(render_, &u_lightdiffuse[i], gstate.lcolor[i * 3 + 1]); + if (u_lightspecular[i] != -1) SetColorUniform3(render_, &u_lightspecular[i], gstate.lcolor[i * 3 + 2]); } } if (dirty & DIRTY_BEZIERSPLINE) { - glUniform1i(u_spline_count_u, gstate_c.spline_count_u); + render_->SetUniformI1(&u_spline_count_u, gstate_c.spline_count_u); if (u_spline_count_v != -1) - glUniform1i(u_spline_count_v, gstate_c.spline_count_v); + render_->SetUniformI1(&u_spline_count_v, gstate_c.spline_count_v); if (u_spline_type_u != -1) - glUniform1i(u_spline_type_u, gstate_c.spline_type_u); + render_->SetUniformI1(&u_spline_type_u, gstate_c.spline_type_u); if (u_spline_type_v != -1) - glUniform1i(u_spline_type_v, gstate_c.spline_type_v); + render_->SetUniformI1(&u_spline_type_v, gstate_c.spline_type_v); } - CHECK_GL_ERROR_IF_DEBUG(); } -ShaderManagerGLES::ShaderManagerGLES() - : lastShader_(nullptr), shaderSwitchDirtyUniforms_(0), diskCacheDirty_(false), fsCache_(16), vsCache_(16) { +ShaderManagerGLES::ShaderManagerGLES(GLRenderManager *render) + : render_(render), lastShader_(nullptr), shaderSwitchDirtyUniforms_(0), diskCacheDirty_(false), fsCache_(16), vsCache_(16) { codeBuffer_ = new char[16384]; lastFSID_.set_invalid(); lastVSID_.set_invalid(); @@ -737,8 +598,6 @@ void ShaderManagerGLES::DirtyShader() { } void ShaderManagerGLES::DirtyLastShader() { // disables vertex arrays - if (lastShader_) - lastShader_->stop(); lastShader_ = nullptr; lastVShaderSame_ = false; } @@ -748,7 +607,8 @@ Shader *ShaderManagerGLES::CompileFragmentShader(FShaderID FSID) { if (!GenerateFragmentShader(FSID, codeBuffer_, &uniformMask)) { return nullptr; } - return new Shader(FSID, codeBuffer_, GL_FRAGMENT_SHADER, false, 0, uniformMask); + std::string desc = FragmentShaderDesc(FSID); + return new Shader(render_, codeBuffer_, desc, GL_FRAGMENT_SHADER, false, 0, uniformMask); } Shader *ShaderManagerGLES::CompileVertexShader(VShaderID VSID) { @@ -756,7 +616,8 @@ Shader *ShaderManagerGLES::CompileVertexShader(VShaderID VSID) { uint32_t attrMask; uint64_t uniformMask; GenerateVertexShader(VSID, codeBuffer_, &attrMask, &uniformMask); - return new Shader(VSID, codeBuffer_, GL_VERTEX_SHADER, useHWTransform, attrMask, uniformMask); + std::string desc = VertexShaderDesc(VSID); + return new Shader(render_, codeBuffer_, desc, GL_VERTEX_SHADER, useHWTransform, attrMask, uniformMask); } Shader *ShaderManagerGLES::ApplyVertexShader(int prim, u32 vertType, VShaderID *VSID) { @@ -806,7 +667,7 @@ Shader *ShaderManagerGLES::ApplyVertexShader(int prim, u32 vertType, VShaderID * uint32_t attrMask; uint64_t uniformMask; GenerateVertexShader(vsidTemp, codeBuffer_, &attrMask, &uniformMask); - vs = new Shader(vsidTemp, codeBuffer_, GL_VERTEX_SHADER, false, attrMask, uniformMask); + vs = new Shader(render_, codeBuffer_, VertexShaderDesc(vsidTemp), GL_VERTEX_SHADER, false, attrMask, uniformMask); } vsCache_.Insert(*VSID, vs); @@ -860,12 +721,12 @@ LinkedShader *ShaderManagerGLES::ApplyFragmentShader(VShaderID VSID, Shader *vs, _dbg_assert_(G3D, FSID.Bit(FS_BIT_FLATSHADE) == VSID.Bit(VS_BIT_FLATSHADE)); // Check if we can link these. - ls = new LinkedShader(VSID, vs, FSID, fs, vs->UseHWTransform()); - ls->use(VSID, lastShader_); + ls = new LinkedShader(render_, VSID, vs, FSID, fs, vs->UseHWTransform()); + ls->use(VSID); const LinkedShaderCacheEntry entry(vs, fs, ls); linkedShaderCache_.push_back(entry); } else { - ls->use(VSID, lastShader_); + ls->use(VSID); } ls->UpdateUniforms(vertType, VSID); @@ -1083,7 +944,7 @@ bool ShaderManagerGLES::ContinuePrecompile(float sliceTime) { Shader *vs = vsCache_.Get(vsid); Shader *fs = fsCache_.Get(fsid); if (vs && fs) { - LinkedShader *ls = new LinkedShader(vsid, vs, fsid, fs, vs->UseHWTransform(), true); + LinkedShader *ls = new LinkedShader(render_, vsid, vs, fsid, fs, vs->UseHWTransform(), true); LinkedShaderCacheEntry entry(vs, fs, ls); linkedShaderCache_.push_back(entry); } diff --git a/GPU/GLES/ShaderManagerGLES.h b/GPU/GLES/ShaderManagerGLES.h index fe9c0226da..77b94f9cd2 100644 --- a/GPU/GLES/ShaderManagerGLES.h +++ b/GPU/GLES/ShaderManagerGLES.h @@ -21,6 +21,7 @@ #include "base/basictypes.h" #include "Common/Hashmaps.h" +#include "thin3d/GLRenderManager.h" #include "GPU/Common/ShaderCommon.h" #include "GPU/Common/ShaderId.h" #include "GPU/GLES/VertexShaderGeneratorGLES.h" @@ -43,18 +44,18 @@ enum { class LinkedShader { public: - LinkedShader(VShaderID VSID, Shader *vs, FShaderID FSID, Shader *fs, bool useHWTransform, bool preloading = false); + LinkedShader(GLRenderManager *render, VShaderID VSID, Shader *vs, FShaderID FSID, Shader *fs, bool useHWTransform, bool preloading = false); ~LinkedShader(); - void use(const VShaderID &VSID, LinkedShader *previous); - void stop(); - void UpdateUniforms(u32 vertType, const VShaderID &VSID); + void use(const ShaderID &VSID); + void UpdateUniforms(u32 vertType, const ShaderID &VSID); + GLRenderManager *render_; Shader *vs_; // Set to false if the VS failed, happens on Mali-400 a lot for complex shaders. bool useHWTransform_; - uint32_t program = 0; + GLRProgram *program; uint64_t availableUniforms; uint64_t dirtyUniforms = 0; @@ -124,12 +125,12 @@ public: class Shader { public: - Shader(const ShaderID &id, const char *code, uint32_t glShaderType, bool useHWTransform, uint32_t attrMask, uint64_t uniformMask); + Shader(GLRenderManager *render, const char *code, const std::string &desc, uint32_t glShaderType, bool useHWTransform, uint32_t attrMask, uint64_t uniformMask); ~Shader(); - uint32_t shader; + GLRShader *shader; bool Failed() const { return failed_; } - bool UseHWTransform() const { return useHWTransform_; } // only relevant for vtx shaders + bool UseHWTransform() const { return useHWTransform_; } // only relevant for vtx shaders std::string GetShaderString(DebugShaderStringType type, ShaderID id) const; @@ -137,6 +138,7 @@ public: uint64_t GetUniformMask() const { return uniformMask_; } private: + GLRenderManager *render_; std::string source_; bool failed_; bool useHWTransform_; @@ -147,7 +149,7 @@ private: class ShaderManagerGLES : public ShaderManagerCommon { public: - ShaderManagerGLES(); + ShaderManagerGLES(GLRenderManager *render); ~ShaderManagerGLES(); void ClearCache(bool deleteThem); // TODO: deleteThem currently not respected @@ -186,6 +188,7 @@ private: }; typedef std::vector LinkedShaderCache; + GLRenderManager *render_; LinkedShaderCache linkedShaderCache_; bool lastVShaderSame_; diff --git a/GPU/GLES/StateMappingGLES.cpp b/GPU/GLES/StateMappingGLES.cpp index 195c21d1b9..caa6ad2472 100644 --- a/GPU/GLES/StateMappingGLES.cpp +++ b/GPU/GLES/StateMappingGLES.cpp @@ -23,6 +23,7 @@ #include "StateMappingGLES.h" #include "profiler/profiler.h" #include "gfx/gl_debug_log.h" +#include "thin3d/GLRenderManager.h" #include "GPU/Math3D.h" #include "GPU/GPUState.h" @@ -31,7 +32,6 @@ #include "Core/Config.h" #include "Core/Reporting.h" #include "GPU/GLES/GPU_GLES.h" -#include "ext/native/gfx/GLStateCache.h" #include "GPU/GLES/ShaderManagerGLES.h" #include "GPU/GLES/TextureCacheGLES.h" #include "GPU/GLES/FramebufferManagerGLES.h" @@ -123,18 +123,14 @@ static const GLushort logicOps[] = { inline void DrawEngineGLES::ResetShaderBlending() { if (fboTexBound_) { - glActiveTexture(GL_TEXTURE1); - glBindTexture(GL_TEXTURE_2D, 0); - glActiveTexture(GL_TEXTURE0); + GLRenderManager *renderManager = (GLRenderManager *)draw_->GetNativeObject(Draw::NativeObject::RENDER_MANAGER); + renderManager->BindTexture(1, nullptr); fboTexBound_ = false; } } void DrawEngineGLES::ApplyDrawState(int prim) { - if (gstate_c.IsDirty(DIRTY_TEXTURE_IMAGE | DIRTY_TEXTURE_PARAMS) && !gstate.isModeClear() && gstate.isTextureMapEnabled()) { - textureCache_->SetTexture(); - gstate_c.Clean(DIRTY_TEXTURE_IMAGE | DIRTY_TEXTURE_PARAMS); - } + GLRenderManager *renderManager = (GLRenderManager *)draw_->GetNativeObject(Draw::NativeObject::RENDER_MANAGER); if (!gstate_c.IsDirty(DIRTY_BLEND_STATE | DIRTY_DEPTHSTENCIL_STATE | DIRTY_RASTER_STATE | DIRTY_VIEWPORTSCISSOR_STATE)) { // Nothing to do, let's early-out @@ -163,17 +159,10 @@ void DrawEngineGLES::ApplyDrawState(int prim) { gstate_c.SetAllowShaderBlend(!g_Config.bDisableSlowFramebufEffects); if (gstate.isModeClear()) { - glstate.blend.disable(); // Color Test bool colorMask = gstate.isClearModeColorMask(); bool alphaMask = gstate.isClearModeAlphaMask(); - glstate.colorMask.set(colorMask, colorMask, colorMask, alphaMask); -#ifndef USING_GLES2 - if (gstate_c.Supports(GPU_SUPPORTS_LOGIC_OP)) { - // Logic Ops - glstate.colorLogicOp.disable(); - } -#endif + renderManager->SetNoBlendAndMask((colorMask ? 7 : 0) | (alphaMask ? 8 : 0)); } else { // Do the large chunks of state conversion. We might be able to hide these two behind a dirty-flag each, // to avoid recomputing heavy stuff unnecessarily every draw call. @@ -184,6 +173,21 @@ void DrawEngineGLES::ApplyDrawState(int prim) { if (ApplyShaderBlending()) { // We may still want to do something about stencil -> alpha. ApplyStencilReplaceAndLogicOp(blendState.replaceAlphaWithStencil, blendState); + + // We copy the framebuffer here, as doing so will wipe any blend state if we do it later. + if (fboTexNeedBind_) { + // Note that this is positions, not UVs, that we need the copy from. + // TODO: If the device doesn't support blit, this will corrupt the currently applied texture. + framebufferManager_->BindFramebufferAsColorTexture(1, framebufferManager_->GetCurrentRenderVFB(), BINDFBCOLOR_MAY_COPY); + // If we are rendering at a higher resolution, linear is probably best for the dest color. + renderManager->SetTextureSampler(GL_CLAMP_TO_EDGE, GL_CLAMP_TO_EDGE, GL_LINEAR, GL_LINEAR, 0.0f); + fboTexBound_ = true; + fboTexNeedBind_ = false; + + framebufferManager_->RebindFramebuffer(); + // Must dirty blend state here so we re-copy next time. Example: Lunar's spell effects. + gstate_c.Dirty(DIRTY_BLEND_STATE); + } } else { // Until next time, force it off. ResetShaderBlending(); @@ -194,11 +198,6 @@ void DrawEngineGLES::ApplyDrawState(int prim) { } if (blendState.enabled) { - glstate.blend.enable(); - glstate.blendEquationSeparate.set(glBlendEqLookup[(size_t)blendState.eqColor], glBlendEqLookup[(size_t)blendState.eqAlpha]); - glstate.blendFuncSeparate.set( - glBlendFactorLookup[(size_t)blendState.srcColor], glBlendFactorLookup[(size_t)blendState.dstColor], - glBlendFactorLookup[(size_t)blendState.srcAlpha], glBlendFactorLookup[(size_t)blendState.dstAlpha]); if (blendState.dirtyShaderBlend) { gstate_c.Dirty(DIRTY_SHADERBLEND); } @@ -210,10 +209,8 @@ void DrawEngineGLES::ApplyDrawState(int prim) { (float)((color & 0xFF0000) >> 16) * (1.0f / 255.0f), (float)((color & 0xFF000000) >> 24) * (1.0f / 255.0f), }; - glstate.blendColor.set(col); + renderManager->SetBlendFactor(col); } - } else { - glstate.blend.disable(); } // PSP color/alpha mask is per bit but we can only support per byte. @@ -235,19 +232,20 @@ void DrawEngineGLES::ApplyDrawState(int prim) { WARN_LOG_REPORT_ONCE(amask, G3D, "Unsupported alpha/stencil mask: %02x", abits); } #endif - - glstate.colorMask.set(rmask, gmask, bmask, amask); + int mask = (int)rmask | ((int)gmask << 1) | ((int)bmask << 2) | ((int)amask << 3); + if (blendState.enabled) { + renderManager->SetBlendAndMask(mask, blendState.enabled, + glBlendFactorLookup[(size_t)blendState.srcColor], glBlendFactorLookup[(size_t)blendState.dstColor], + glBlendFactorLookup[(size_t)blendState.srcAlpha], glBlendFactorLookup[(size_t)blendState.dstAlpha], + glBlendEqLookup[(size_t)blendState.eqColor], glBlendEqLookup[(size_t)blendState.eqAlpha]); + } else { + renderManager->SetNoBlendAndMask(mask); + } #ifndef USING_GLES2 if (gstate_c.Supports(GPU_SUPPORTS_LOGIC_OP)) { - // TODO: Make this dynamic - // Logic Ops - if (gstate.isLogicOpEnabled() && gstate.getLogicOp() != GE_LOGIC_COPY) { - glstate.colorLogicOp.enable(); - glstate.logicOp.set(logicOps[gstate.getLogicOp()]); - } else { - glstate.colorLogicOp.disable(); - } + renderManager->SetLogicOp(gstate.isLogicOpEnabled() && gstate.getLogicOp() != GE_LOGIC_COPY, + logicOps[gstate.getLogicOp()]); } #endif } @@ -257,26 +255,18 @@ void DrawEngineGLES::ApplyDrawState(int prim) { gstate_c.Clean(DIRTY_RASTER_STATE); // Dither - if (gstate.isDitherEnabled()) { - glstate.dither.enable(); - glstate.dither.set(true); - } else { - glstate.dither.disable(); - } + bool dither = gstate.isDitherEnabled(); + bool cullEnable; + GLenum cullMode = cullingMode[gstate.getCullMode() ^ !useBufferedRendering]; if (gstate.isModeClear()) { // Culling - glstate.cullFace.disable(); + cullEnable = false; } else { // Set cull - bool cullEnabled = !gstate.isModeThrough() && prim != GE_PRIM_RECTANGLES && gstate.isCullEnabled(); - if (cullEnabled) { - glstate.cullFace.enable(); - glstate.cullFaceMode.set(cullingMode[gstate.getCullMode() ^ !useBufferedRendering]); - } else { - glstate.cullFace.disable(); - } + cullEnable = !gstate.isModeThrough() && prim != GE_PRIM_RECTANGLES && gstate.isCullEnabled(); } + renderManager->SetRaster(cullEnable, GL_CCW, cullMode, dither); } if (gstate_c.IsDirty(DIRTY_DEPTHSTENCIL_STATE)) { @@ -284,50 +274,27 @@ void DrawEngineGLES::ApplyDrawState(int prim) { bool enableStencilTest = !g_Config.bDisableStencilTest; if (gstate.isModeClear()) { // Depth Test - glstate.depthTest.enable(); - glstate.depthFunc.set(GL_ALWAYS); - glstate.depthWrite.set(gstate.isClearModeDepthMask() ? GL_TRUE : GL_FALSE); if (gstate.isClearModeDepthMask()) { framebufferManager_->SetDepthUpdated(); } - - // Stencil Test - if (gstate.isClearModeAlphaMask() && enableStencilTest) { - glstate.stencilTest.enable(); - glstate.stencilOp.set(GL_REPLACE, GL_REPLACE, GL_REPLACE); - // TODO: In clear mode, the stencil value is set to the alpha value of the vertex. - // A normal clear will be 2 points, the second point has the color. - // We should set "ref" to that value instead of 0. - // In case of clear rectangles, we set it again once we know what the color is. - glstate.stencilFunc.set(GL_ALWAYS, 255, 0xFF); - glstate.stencilMask.set(0xFF); - } else { - glstate.stencilTest.disable(); - } + renderManager->SetStencilFunc(gstate.isClearModeAlphaMask() && enableStencilTest, GL_ALWAYS, 0xFF, 0xFF); + renderManager->SetStencilOp(0xFF, GL_REPLACE, GL_REPLACE, GL_REPLACE); + renderManager->SetDepth(true, gstate.isClearModeDepthMask() ? true : false, GL_ALWAYS); } else { // Depth Test - if (gstate.isDepthTestEnabled()) { - glstate.depthTest.enable(); - glstate.depthFunc.set(compareOps[gstate.getDepthTestFunction()]); - glstate.depthWrite.set(gstate.isDepthWriteEnabled() ? GL_TRUE : GL_FALSE); - if (gstate.isDepthWriteEnabled()) { - framebufferManager_->SetDepthUpdated(); - } - } else { - glstate.depthTest.disable(); + renderManager->SetDepth(gstate.isDepthTestEnabled(), gstate.isDepthWriteEnabled(), compareOps[gstate.getDepthTestFunction()]); + if (gstate.isDepthTestEnabled() && gstate.isDepthWriteEnabled()) { + framebufferManager_->SetDepthUpdated(); } GenericStencilFuncState stencilState; ConvertStencilFuncState(stencilState); - // Stencil Test if (stencilState.enabled) { - glstate.stencilTest.enable(); - glstate.stencilFunc.set(compareOps[stencilState.testFunc], stencilState.testRef, stencilState.testMask); - glstate.stencilOp.set(stencilOps[stencilState.sFail], stencilOps[stencilState.zFail], stencilOps[stencilState.zPass]); - glstate.stencilMask.set(stencilState.writeMask); + renderManager->SetStencilFunc(stencilState.enabled, compareOps[stencilState.testFunc], stencilState.testRef, stencilState.testMask); + renderManager->SetStencilOp(stencilState.writeMask, stencilOps[stencilState.sFail], stencilOps[stencilState.zFail], stencilOps[stencilState.zPass]); } else { - glstate.stencilTest.disable(); + renderManager->SetStencilDisabled(); } } } @@ -340,21 +307,11 @@ void DrawEngineGLES::ApplyDrawState(int prim) { framebufferManager_->GetTargetBufferWidth(), framebufferManager_->GetTargetBufferHeight(), vpAndScissor); - if (vpAndScissor.scissorEnable) { - glstate.scissorTest.enable(); - if (!useBufferedRendering) { - vpAndScissor.scissorY = PSP_CoreParameter().pixelHeight - vpAndScissor.scissorH - vpAndScissor.scissorY; - } - glstate.scissorRect.set(vpAndScissor.scissorX, vpAndScissor.scissorY, vpAndScissor.scissorW, vpAndScissor.scissorH); - } else { - glstate.scissorTest.disable(); - } - - if (!useBufferedRendering) { - vpAndScissor.viewportY = PSP_CoreParameter().pixelHeight - vpAndScissor.viewportH - vpAndScissor.viewportY; - } - glstate.viewport.set(vpAndScissor.viewportX, vpAndScissor.viewportY, vpAndScissor.viewportW, vpAndScissor.viewportH); - glstate.depthRange.set(vpAndScissor.depthRangeMin, vpAndScissor.depthRangeMax); + renderManager->SetScissor(GLRect2D{ vpAndScissor.scissorX, vpAndScissor.scissorY, vpAndScissor.scissorW, vpAndScissor.scissorH }); + renderManager->SetViewport({ + vpAndScissor.viewportX, vpAndScissor.viewportY, + vpAndScissor.viewportW, vpAndScissor.viewportH, + vpAndScissor.depthRangeMin, vpAndScissor.depthRangeMax }); if (vpAndScissor.dirtyProj) { gstate_c.Dirty(DIRTY_PROJMATRIX); @@ -363,40 +320,19 @@ void DrawEngineGLES::ApplyDrawState(int prim) { gstate_c.Dirty(DIRTY_DEPTHRANGE); } } - CHECK_GL_ERROR_IF_DEBUG(); } -void DrawEngineGLES::ApplyDrawStateLate() { +void DrawEngineGLES::ApplyDrawStateLate(bool setStencil, int stencilValue) { + if (setStencil) { + render_->SetStencilFunc(GL_TRUE, GL_ALWAYS, stencilValue, 255); + } + // 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()) { - if (fboTexNeedBind_) { - CHECK_GL_ERROR_IF_DEBUG(); - // Note that this is positions, not UVs, that we need the copy from. - framebufferManager_->BindFramebufferAsColorTexture(1, framebufferManager_->GetCurrentRenderVFB(), BINDFBCOLOR_MAY_COPY); - framebufferManager_->RebindFramebuffer(); - CHECK_GL_ERROR_IF_DEBUG(); - - glActiveTexture(GL_TEXTURE1); - // If we are rendering at a higher resolution, linear is probably best for the dest color. - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glActiveTexture(GL_TEXTURE0); - fboTexBound_ = true; - fboTexNeedBind_ = false; - CHECK_GL_ERROR_IF_DEBUG(); - } - CHECK_GL_ERROR_IF_DEBUG(); - - // Apply the texture after the FBO tex, since it might unbind the texture. - // TODO: Could use a separate texture unit to be safer? - textureCache_->ApplyTexture(); - CHECK_GL_ERROR_IF_DEBUG(); - // Apply last, once we know the alpha params of the texture. if (gstate.isAlphaTestEnabled() || gstate.isColorTestEnabled()) { - fragmentTestCache_->BindTestTexture(GL_TEXTURE2); + fragmentTestCache_->BindTestTexture(2); } - CHECK_GL_ERROR_IF_DEBUG(); } } diff --git a/GPU/GLES/StencilBufferGLES.cpp b/GPU/GLES/StencilBufferGLES.cpp index 01dfcf254e..e86aad078f 100644 --- a/GPU/GLES/StencilBufferGLES.cpp +++ b/GPU/GLES/StencilBufferGLES.cpp @@ -17,7 +17,6 @@ #include "gfx_es2/glsl_program.h" #include "Core/Reporting.h" -#include "ext/native/gfx/GLStateCache.h" #include "GPU/Common/StencilCommon.h" #include "GPU/GLES/FramebufferManagerGLES.h" #include "GPU/GLES/ShaderManagerGLES.h" @@ -111,12 +110,10 @@ bool FramebufferManagerGLES::NotifyStencilUpload(u32 addr, int size, bool skipZe } // Let's not bother with the shader if it's just zero. - glstate.scissorTest.disable(); - glstate.colorMask.set(GL_FALSE, GL_FALSE, GL_FALSE, GL_TRUE); - glClearColor(0, 0, 0, 0); - glClearStencil(0); - glClear(GL_STENCIL_BUFFER_BIT | GL_COLOR_BUFFER_BIT); - + if (dstBuffer->fbo) { + draw_->BindFramebufferAsRenderTarget(dstBuffer->fbo, { Draw::RPAction::KEEP, Draw::RPAction::KEEP, Draw::RPAction::CLEAR }); + } + render_->Clear(0, 0, 0, GL_STENCIL_BUFFER_BIT | GL_COLOR_BUFFER_BIT, 0x8); gstate_c.Dirty(DIRTY_BLEND_STATE | DIRTY_VIEWPORTSCISSOR_STATE); return true; } @@ -126,26 +123,26 @@ bool FramebufferManagerGLES::NotifyStencilUpload(u32 addr, int size, bool skipZe static std::string vs_code, fs_code; vs_code = ApplyGLSLPrelude(stencil_vs, GL_VERTEX_SHADER); fs_code = ApplyGLSLPrelude(stencil_fs, GL_FRAGMENT_SHADER); - stencilUploadProgram_ = glsl_create_source(vs_code.c_str(), fs_code.c_str(), &errorString); + std::vector shaders; + shaders.push_back(render_->CreateShader(GL_VERTEX_SHADER, vs_code, "stencil")); + shaders.push_back(render_->CreateShader(GL_FRAGMENT_SHADER, fs_code, "stencil")); + std::vector queries; + queries.push_back({ &u_stencilUploadTex, "tex" }); + queries.push_back({ &u_stencilValue, "u_stencilValue" }); + std::vector inits; + inits.push_back({ &u_stencilUploadTex, 0, 0 }); + stencilUploadProgram_ = render_->CreateProgram(shaders, {}, queries, inits, false); + for (auto iter : shaders) { + render_->DeleteShader(iter); + } if (!stencilUploadProgram_) { ERROR_LOG_REPORT(G3D, "Failed to compile stencilUploadProgram! This shouldn't happen.\n%s", errorString.c_str()); - } else { - glsl_bind(stencilUploadProgram_); } - - GLint u_tex = glsl_uniform_loc(stencilUploadProgram_, "tex"); - glUniform1i(u_tex, 0); - } else { - glsl_bind(stencilUploadProgram_); } shaderManagerGL_->DirtyLastShader(); DisableState(); - glstate.colorMask.set(GL_FALSE, GL_FALSE, GL_FALSE, GL_TRUE); - glstate.stencilTest.enable(); - glstate.stencilOp.set(GL_REPLACE, GL_REPLACE, GL_REPLACE); - gstate_c.Dirty(DIRTY_BLEND_STATE | DIRTY_DEPTHSTENCIL_STATE); bool useBlit = gstate_c.Supports(GPU_SUPPORTS_ARB_FRAMEBUFFER_BLIT | GPU_SUPPORTS_NV_FRAMEBUFFER_BLIT); @@ -164,43 +161,42 @@ bool FramebufferManagerGLES::NotifyStencilUpload(u32 addr, int size, bool skipZe } else if (dstBuffer->fbo) { draw_->BindFramebufferAsRenderTarget(dstBuffer->fbo, { Draw::RPAction::KEEP, Draw::RPAction::KEEP, Draw::RPAction::CLEAR }); } - glViewport(0, 0, w, h); - gstate_c.Dirty(DIRTY_VIEWPORTSCISSOR_STATE); + render_->SetViewport({ 0, 0, (float)w, (float)h, 0.0f, 1.0f }); float u1 = 1.0f; float v1 = 1.0f; MakePixelTexture(src, dstBuffer->format, dstBuffer->fb_stride, dstBuffer->bufferWidth, dstBuffer->bufferHeight, u1, v1); textureCacheGL_->ForgetLastTexture(); - glClearStencil(0); - glClear(GL_STENCIL_BUFFER_BIT); + // We must bind the program after starting the render pass, and set the color mask after clearing. + render_->Clear(0, 0, 0, GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT, 0x8); + render_->SetStencilFunc(GL_TRUE, GL_ALWAYS, 0xFF, 0xFF); + render_->BindProgram(stencilUploadProgram_); + render_->SetNoBlendAndMask(0x8); - glstate.stencilFunc.set(GL_ALWAYS, 0xFF, 0xFF); - - GLint u_stencilValue = glsl_uniform_loc(stencilUploadProgram_, "u_stencilValue"); for (int i = 1; i < values; i += i) { if (!(usedBits & i)) { // It's already zero, let's skip it. continue; } if (dstBuffer->format == GE_FORMAT_4444) { - glstate.stencilMask.set((i << 4) | i); - glUniform1f(u_stencilValue, i * (16.0f / 255.0f)); + render_->SetStencilOp((i << 4) | i, GL_REPLACE, GL_REPLACE, GL_REPLACE); + render_->SetUniformF1(&u_stencilValue, i * (16.0f / 255.0f)); } else if (dstBuffer->format == GE_FORMAT_5551) { - glstate.stencilMask.set(0xFF); - glUniform1f(u_stencilValue, i * (128.0f / 255.0f)); + render_->SetStencilOp(0xFF, GL_REPLACE, GL_REPLACE, GL_REPLACE); + render_->SetUniformF1(&u_stencilValue, i * (128.0f / 255.0f)); } else { - glstate.stencilMask.set(i); - glUniform1f(u_stencilValue, i * (1.0f / 255.0f)); + render_->SetStencilOp(i, GL_REPLACE, GL_REPLACE, GL_REPLACE); + render_->SetUniformF1(&u_stencilValue, i * (1.0f / 255.0f)); } DrawActiveTexture(0, 0, dstBuffer->width, dstBuffer->height, dstBuffer->bufferWidth, dstBuffer->bufferHeight, 0.0f, 0.0f, u1, v1, ROTATION_LOCKED_HORIZONTAL, DRAWTEX_NEAREST); } - glstate.stencilMask.set(0xFF); if (useBlit) { draw_->BlitFramebuffer(blitFBO, 0, 0, w, h, dstBuffer->fbo, 0, 0, dstBuffer->renderWidth, dstBuffer->renderHeight, Draw::FB_STENCIL_BIT, Draw::FB_BLIT_NEAREST); } + gstate_c.Dirty(DIRTY_BLEND_STATE | DIRTY_DEPTHSTENCIL_STATE | DIRTY_VIEWPORTSCISSOR_STATE); RebindFramebuffer(); return true; } diff --git a/GPU/GLES/TextureCacheGLES.cpp b/GPU/GLES/TextureCacheGLES.cpp index f9daf9faef..59eb3184c1 100644 --- a/GPU/GLES/TextureCacheGLES.cpp +++ b/GPU/GLES/TextureCacheGLES.cpp @@ -23,6 +23,7 @@ #include "i18n/i18n.h" #include "math/math_util.h" #include "profiler/profiler.h" +#include "thin3d/GLRenderManager.h" #include "Common/ColorConv.h" #include "Core/Config.h" @@ -31,7 +32,6 @@ #include "Core/Reporting.h" #include "GPU/ge_constants.h" #include "GPU/GPUState.h" -#include "ext/native/gfx/GLStateCache.h" #include "GPU/GLES/TextureCacheGLES.h" #include "GPU/GLES/FramebufferManagerGLES.h" #include "GPU/GLES/FragmentShaderGeneratorGLES.h" @@ -44,24 +44,23 @@ #include #endif -#ifndef GL_UNPACK_ROW_LENGTH -#define GL_UNPACK_ROW_LENGTH 0x0CF2 -#endif - -#define TEXCACHE_NAME_CACHE_SIZE 16 - TextureCacheGLES::TextureCacheGLES(Draw::DrawContext *draw) : TextureCacheCommon(draw) { timesInvalidatedAllThisFrame_ = 0; - lastBoundTexture = INVALID_TEX; + lastBoundTexture = nullptr; + render_ = (GLRenderManager *)draw_->GetNativeObject(Draw::NativeObject::RENDER_MANAGER); - glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &maxAnisotropyLevel); SetupTextureDecoder(); nextTexture_ = nullptr; + std::vector entries; + entries.push_back({ 0, 3, GL_FLOAT, GL_FALSE, 20, 0 }); + entries.push_back({ 1, 2, GL_FLOAT, GL_FALSE, 20, 12 }); + shadeInputLayout_ = render_->CreateInputLayout(entries); } TextureCacheGLES::~TextureCacheGLES() { + render_->DeleteInputLayout(shadeInputLayout_); Clear(true); } @@ -71,23 +70,17 @@ void TextureCacheGLES::SetFramebufferManager(FramebufferManagerGLES *fbManager) } void TextureCacheGLES::ReleaseTexture(TexCacheEntry *entry, bool delete_them) { - DEBUG_LOG(G3D, "Deleting texture %i", entry->textureName); + DEBUG_LOG(G3D, "Deleting texture %08x", entry->addr); if (delete_them) { - if (entry->textureName != 0) { - glDeleteTextures(1, &entry->textureName); + if (entry->textureName) { + render_->DeleteTexture(entry->textureName); } } - entry->textureName = 0; + entry->textureName = nullptr; } void TextureCacheGLES::Clear(bool delete_them) { TextureCacheCommon::Clear(delete_them); - if (delete_them) { - if (!nameCache_.empty()) { - glDeleteTextures((GLsizei)nameCache_.size(), &nameCache_[0]); - nameCache_.clear(); - } - } } GLenum getClutDestFormat(GEPaletteFormat format) { @@ -122,7 +115,6 @@ static const GLuint MagFiltGL[2] = { // This should not have to be done per texture! OpenGL is silly yo void TextureCacheGLES::UpdateSamplingParams(TexCacheEntry &entry, bool force) { - CHECK_GL_ERROR_IF_DEBUG(); int minFilt; int magFilt; bool sClamp; @@ -133,59 +125,37 @@ void TextureCacheGLES::UpdateSamplingParams(TexCacheEntry &entry, bool force) { GetSamplingParams(minFilt, magFilt, sClamp, tClamp, lodBias, maxLevel, entry.addr, mode); if (gstate_c.Supports(GPU_SUPPORTS_TEXTURE_LOD_CONTROL)) { + float minLod = 0.0f; + float maxLod = 0.0f; if (maxLevel != 0) { // TODO: What about a swap of autoMip mode? - if (force || entry.lodBias != lodBias) { + if (true) { if (mode == GE_TEXLEVEL_MODE_AUTO) { -#ifndef USING_GLES2 - // Sigh, LOD_BIAS is not even in ES 3.0.. but we could do it in the shader via texture()... - glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_LOD_BIAS, lodBias); -#endif - glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_LOD, 0); - glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_LOD, (float)maxLevel); + minLod = 0.0f; + maxLod = (float)maxLevel; } else if (mode == GE_TEXLEVEL_MODE_CONST) { - glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_LOD, std::max(0.0f, std::min((float)maxLevel, lodBias))); - glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_LOD, std::max(0.0f, std::min((float)maxLevel, lodBias))); + minLod = std::max(0.0f, std::min((float)maxLevel, lodBias)); + maxLod = std::max(0.0f, std::min((float)maxLevel, lodBias)); } else { // mode == GE_TEXLEVEL_MODE_SLOPE) { // It's incorrect to use the slope as a bias. Instead it should be passed // into the shader directly as an explicit lod level, with the bias on top. For now, we just kill the // lodBias in this mode, working around #9772. #ifndef USING_GLES2 - glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_LOD_BIAS, 0.0f); + lodBias = 0.0f; #endif - glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_LOD, 0); - glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_LOD, (float)maxLevel); + minLod = 0.0f; + maxLod = (float)maxLevel; } - entry.lodBias = lodBias; } } else { - glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_LOD, 0.0f); - glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_LOD, 0.0f); + minLod = 0.0f; + maxLod = 0.0f; } + render_->SetTextureLod(minLod, maxLod, lodBias); } - if (force || entry.minFilt != minFilt) { - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, MinFiltGL[minFilt]); - entry.minFilt = minFilt; - } - if (force || entry.magFilt != magFilt) { - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, MagFiltGL[magFilt]); - entry.magFilt = magFilt; - } - - if (entry.framebuffer) { - WARN_LOG_REPORT_ONCE(wrongFramebufAttach, G3D, "Framebuffer still attached in UpdateSamplingParams()?"); - } - - if (force || entry.sClamp != sClamp) { - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, sClamp ? GL_CLAMP_TO_EDGE : GL_REPEAT); - entry.sClamp = sClamp; - } - if (force || entry.tClamp != tClamp) { - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, tClamp ? GL_CLAMP_TO_EDGE : GL_REPEAT); - entry.tClamp = tClamp; - } - CHECK_GL_ERROR_IF_DEBUG(); + float aniso = 0.0f; + render_->SetTextureSampler(sClamp ? GL_CLAMP_TO_EDGE : GL_REPEAT, tClamp ? GL_CLAMP_TO_EDGE : GL_REPEAT, MagFiltGL[magFilt], MinFiltGL[minFilt], aniso); } void TextureCacheGLES::SetFramebufferSamplingParams(u16 bufferWidth, u16 bufferHeight) { @@ -199,8 +169,8 @@ void TextureCacheGLES::SetFramebufferSamplingParams(u16 bufferWidth, u16 bufferH minFilt &= 1; // framebuffers can't mipmap. - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, MinFiltGL[minFilt]); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, MagFiltGL[magFilt]); + float aniso = 0.0f; + render_->SetTextureSampler(sClamp ? GL_CLAMP_TO_EDGE : GL_REPEAT, tClamp ? GL_CLAMP_TO_EDGE : GL_REPEAT, MagFiltGL[magFilt], MinFiltGL[minFilt], aniso); // Often the framebuffer will not match the texture size. We'll wrap/clamp in the shader in that case. // This happens whether we have OES_texture_npot or not. @@ -209,9 +179,6 @@ void TextureCacheGLES::SetFramebufferSamplingParams(u16 bufferWidth, u16 bufferH if (w != bufferWidth || h != bufferHeight) { return; } - - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, sClamp ? GL_CLAMP_TO_EDGE : GL_REPEAT); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, tClamp ? GL_CLAMP_TO_EDGE : GL_REPEAT); } static void ConvertColors(void *dstBuf, const void *srcBuf, GLuint dstFmt, int numPixels) { @@ -240,6 +207,19 @@ void TextureCacheGLES::StartFrame() { InvalidateLastTexture(); timesInvalidatedAllThisFrame_ = 0; + GLRenderManager *renderManager = (GLRenderManager *)draw_->GetNativeObject(Draw::NativeObject::RENDER_MANAGER); + if (!lowMemoryMode_ && renderManager->SawOutOfMemory()) { + lowMemoryMode_ = true; + decimationCounter_ = 0; + + I18NCategory *err = GetI18NCategory("Error"); + if (standardScaleFactor_ > 1) { + host->NotifyUserMessage(err->T("Warning: Video memory FULL, reducing upscaling and switching to slow caching mode"), 2.0f); + } else { + host->NotifyUserMessage(err->T("Warning: Video memory FULL, switching to slow caching mode"), 2.0f); + } + } + if (texelsScaledThisFrame_) { // INFO_LOG(G3D, "Scaled %i texels", texelsScaledThisFrame_); } @@ -336,39 +316,26 @@ bool SetDebugTexture() { #endif void TextureCacheGLES::BindTexture(TexCacheEntry *entry) { - CHECK_GL_ERROR_IF_DEBUG(); if (entry->textureName != lastBoundTexture) { - glBindTexture(GL_TEXTURE_2D, entry->textureName); + render_->BindTexture(0, entry->textureName); lastBoundTexture = entry->textureName; } - CHECK_GL_ERROR_IF_DEBUG(); UpdateSamplingParams(*entry, false); - CHECK_GL_ERROR_IF_DEBUG(); } void TextureCacheGLES::Unbind() { - glBindTexture(GL_TEXTURE_2D, 0); + render_->BindTexture(0, nullptr); InvalidateLastTexture(); } class TextureShaderApplier { public: struct Pos { - Pos(float x_, float y_, float z_) : x(x_), y(y_), z(z_) { - } - Pos() { - } - float x; float y; float z; }; struct UV { - UV(float u_, float v_) : u(u_), v(v_) { - } - UV() { - } - float u; float v; }; @@ -411,66 +378,44 @@ public: const float top = v1 * invHalfHeight - 1.0f; const float bottom = v2 * invHalfHeight - 1.0f; // Points are: BL, BR, TR, TL. - pos_[0] = Pos(left, bottom, -1.0f); - pos_[1] = Pos(right, bottom, -1.0f); - pos_[2] = Pos(right, top, -1.0f); - pos_[3] = Pos(left, top, -1.0f); + pos_[0] = Pos{ left, bottom, -1.0f }; + pos_[1] = Pos{ right, bottom, -1.0f }; + pos_[2] = Pos{ right, top, -1.0f }; + pos_[3] = Pos{ left, top, -1.0f }; // And also the UVs, same order. const float uvleft = u1 * invWidth; const float uvright = u2 * invWidth; const float uvtop = v1 * invHeight; const float uvbottom = v2 * invHeight; - uv_[0] = UV(uvleft, uvbottom); - uv_[1] = UV(uvright, uvbottom); - uv_[2] = UV(uvright, uvtop); - uv_[3] = UV(uvleft, uvtop); + uv_[0] = UV{ uvleft, uvbottom }; + uv_[1] = UV{ uvright, uvbottom }; + uv_[2] = UV{ uvright, uvtop }; + uv_[3] = UV{ uvleft, uvtop }; } } - void Use(DrawEngineGLES *transformDraw) { - glUseProgram(shader_->program); - - // Restore will rebind all of the state below. - if (gstate_c.Supports(GPU_SUPPORTS_VAO)) { - static const GLubyte indices[4] = { 0, 1, 3, 2 }; - transformDraw->BindBuffer(pos_, sizeof(pos_), uv_, sizeof(uv_)); - transformDraw->BindElementBuffer(indices, sizeof(indices)); - } else { - glBindBuffer(GL_ARRAY_BUFFER, 0); - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); + void Use(GLRenderManager *render, DrawEngineGLES *transformDraw, GLRInputLayout *inputLayout) { + render->BindProgram(shader_->program); + struct SimpleVertex { + float pos[3]; + float uv[2]; + }; + uint32_t bindOffset; + GLRBuffer *bindBuffer; + SimpleVertex *verts = (SimpleVertex *)transformDraw->GetPushVertexBuffer()->Push(sizeof(SimpleVertex) * 4, &bindOffset, &bindBuffer); + int order[4] = { 0 ,1, 3, 2 }; + for (int i = 0; i < 4; i++) { + memcpy(verts[i].pos, &pos_[order[i]], sizeof(Pos)); + memcpy(verts[i].uv, &uv_[order[i]], sizeof(UV)); } - glEnableVertexAttribArray(shader_->a_position); - glEnableVertexAttribArray(shader_->a_texcoord0); + render->BindVertexBuffer(inputLayout, bindBuffer, bindOffset); } - void Shade() { + void Shade(GLRenderManager *render) { static const GLubyte indices[4] = { 0, 1, 3, 2 }; - - glstate.blend.force(false); - glstate.colorMask.force(true, true, true, true); - glstate.scissorTest.force(false); - glstate.cullFace.force(false); - glstate.depthTest.force(false); - glstate.stencilTest.force(false); -#if !defined(USING_GLES2) - glstate.colorLogicOp.force(false); -#endif - glViewport(0, 0, renderW_, renderH_); - - if (gstate_c.Supports(GPU_SUPPORTS_VAO)) { - glVertexAttribPointer(shader_->a_position, 3, GL_FLOAT, GL_FALSE, 12, 0); - glVertexAttribPointer(shader_->a_texcoord0, 2, GL_FLOAT, GL_FALSE, 8, (void *)sizeof(pos_)); - glDrawElements(GL_TRIANGLE_STRIP, 4, GL_UNSIGNED_BYTE, 0); - } else { - glVertexAttribPointer(shader_->a_position, 3, GL_FLOAT, GL_FALSE, 12, pos_); - glVertexAttribPointer(shader_->a_texcoord0, 2, GL_FLOAT, GL_FALSE, 8, uv_); - glDrawElements(GL_TRIANGLE_STRIP, 4, GL_UNSIGNED_BYTE, indices); - } - glDisableVertexAttribArray(shader_->a_position); - glDisableVertexAttribArray(shader_->a_texcoord0); - - glstate.Restore(); + render->SetViewport(GLRViewport{ 0, 0, (float)renderW_, (float)renderH_, 0.0f, 1.0f }); + render->Draw(GL_TRIANGLE_STRIP, 0, 4); } protected: @@ -491,31 +436,27 @@ void TextureCacheGLES::ApplyTextureFramebuffer(TexCacheEntry *entry, VirtualFram } if (depal) { const GEPaletteFormat clutFormat = gstate.getClutPaletteFormat(); - GLuint clutTexture = depalShaderCache_->GetClutTexture(clutFormat, clutHash_, clutBuf_); + GLRTexture *clutTexture = depalShaderCache_->GetClutTexture(clutFormat, clutHash_, clutBuf_); Draw::Framebuffer *depalFBO = framebufferManagerGL_->GetTempFBO(framebuffer->renderWidth, framebuffer->renderHeight, Draw::FBO_8888); draw_->BindFramebufferAsRenderTarget(depalFBO, { Draw::RPAction::DONT_CARE, Draw::RPAction::DONT_CARE, Draw::RPAction::DONT_CARE }); shaderManager_->DirtyLastShader(); TextureShaderApplier shaderApply(depal, framebuffer->bufferWidth, framebuffer->bufferHeight, framebuffer->renderWidth, framebuffer->renderHeight); shaderApply.ApplyBounds(gstate_c.vertBounds, gstate_c.curTextureXOffset, gstate_c.curTextureYOffset); - shaderApply.Use(drawEngine_); - - glActiveTexture(GL_TEXTURE3); - glBindTexture(GL_TEXTURE_2D, clutTexture); - glActiveTexture(GL_TEXTURE0); + shaderApply.Use(render_, drawEngine_, shadeInputLayout_); framebufferManagerGL_->BindFramebufferAsColorTexture(0, framebuffer, BINDFBCOLOR_SKIP_COPY); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + render_->BindTexture(3, clutTexture); + render_->SetTextureSampler(GL_CLAMP_TO_EDGE, GL_CLAMP_TO_EDGE, GL_NEAREST, GL_NEAREST, 0.0f); - shaderApply.Shade(); + shaderApply.Shade(render_); draw_->BindFramebufferAsTexture(depalFBO, 0, Draw::FB_COLOR_BIT, 0); const u32 bytesPerColor = clutFormat == GE_CMODE_32BIT_ABGR8888 ? sizeof(u32) : sizeof(u16); const u32 clutTotalColors = clutMaxBytes_ / bytesPerColor; - TexCacheEntry::TexStatus alphaStatus = CheckAlpha(clutBuf_, getClutDestFormat(clutFormat), clutTotalColors, clutTotalColors, 1); + TexCacheEntry::TexStatus alphaStatus = CheckAlpha((const uint8_t *)clutBuf_, getClutDestFormat(clutFormat), clutTotalColors, clutTotalColors, 1); gstate_c.SetTextureFullAlpha(alphaStatus == TexCacheEntry::STATUS_ALPHA_FULL); } else { entry->status &= ~TexCacheEntry::STATUS_DEPALETTIZE; @@ -528,9 +469,10 @@ void TextureCacheGLES::ApplyTextureFramebuffer(TexCacheEntry *entry, VirtualFram framebufferManagerGL_->RebindFramebuffer(); SetFramebufferSamplingParams(framebuffer->bufferWidth, framebuffer->bufferHeight); - CHECK_GL_ERROR_IF_DEBUG(); - InvalidateLastTexture(); + + // Since we started/ended render passes, might need these. + gstate_c.Dirty(DIRTY_BLEND_STATE | DIRTY_DEPTHSTENCIL_STATE | DIRTY_RASTER_STATE | DIRTY_VIEWPORTSCISSOR_STATE); } ReplacedTextureFormat FromGLESFormat(GLenum fmt) { @@ -555,6 +497,10 @@ GLenum ToGLESFormat(ReplacedTextureFormat fmt) { void TextureCacheGLES::BuildTexture(TexCacheEntry *const entry, bool replaceImages) { entry->status &= ~TexCacheEntry::STATUS_ALPHA_MASK; + // Never replace images in-place - there's no such thing, drivers have to fake it anyway, at least if + // the image has been in use within the last frame or two. + replaceImages = false; + // For the estimate, we assume cluts always point to 8888 for simplicity. cacheSizeEstimate_ += EstimateTexMemoryUsage(entry); @@ -566,7 +512,7 @@ void TextureCacheGLES::BuildTexture(TexCacheEntry *const entry, bool replaceImag // Always generate a texture name unless it's a framebuffer, we might need it if the texture is replaced later. if (!replaceImages) { if (!entry->textureName) { - entry->textureName = AllocTextureName(); + entry->textureName = render_->CreateTexture(GL_TEXTURE_2D); } } @@ -630,8 +576,8 @@ void TextureCacheGLES::BuildTexture(TexCacheEntry *const entry, bool replaceImag if (replaced.GetSize(0, w, h)) { if (replaceImages) { // Since we're replacing the texture, we can't replace the image inside. - glDeleteTextures(1, &entry->textureName); - entry->textureName = AllocTextureName(); + render_->DeleteTexture(entry->textureName); + entry->textureName = render_->CreateTexture(GL_TEXTURE_2D); replaceImages = false; } @@ -663,31 +609,9 @@ void TextureCacheGLES::BuildTexture(TexCacheEntry *const entry, bool replaceImag } } - glBindTexture(GL_TEXTURE_2D, entry->textureName); + // glBindTexture(GL_TEXTURE_2D, entry->textureName); lastBoundTexture = entry->textureName; - - // Disabled this due to issue #6075: https://github.com/hrydgard/ppsspp/issues/6075 - // This breaks Dangan Ronpa 2 with mipmapping enabled. Why? No idea, it shouldn't. - // glTexStorage2D probably has few benefits for us anyway. - if (false && gl_extensions.GLES3 && maxLevel > 0) { - // glTexStorage2D requires the use of sized formats. - GLenum actualFmt = replaced.Valid() ? ToGLESFormat(replaced.Format(0)) : dstFmt; - GLenum storageFmt = GL_RGBA8; - switch (actualFmt) { - case GL_UNSIGNED_BYTE: storageFmt = GL_RGBA8; break; - case GL_UNSIGNED_SHORT_5_6_5: storageFmt = GL_RGB565; break; - case GL_UNSIGNED_SHORT_4_4_4_4: storageFmt = GL_RGBA4; break; - case GL_UNSIGNED_SHORT_5_5_5_1: storageFmt = GL_RGB5_A1; break; - default: - ERROR_LOG(G3D, "Unknown dstfmt %i", (int)actualFmt); - break; - } - // TODO: This may cause bugs, since it hard-sets the texture w/h, and we might try to reuse it later with a different size. - glTexStorage2D(GL_TEXTURE_2D, maxLevel + 1, storageFmt, w * scaleFactor, h * scaleFactor); - // Make sure we don't use glTexImage2D after glTexStorage2D. - replaceImages = true; - } - + // GLES2 doesn't have support for a "Max lod" which is critical as PSP games often // don't specify mips all the way down. As a result, we either need to manually generate // the bottom few levels or rely on OpenGL's autogen mipmaps instead, which might not @@ -702,33 +626,35 @@ void TextureCacheGLES::BuildTexture(TexCacheEntry *const entry, bool replaceImag LoadTextureLevel(*entry, replaced, 0, replaceImages, scaleFactor, dstFmt); // Mipmapping only enable when texture scaling disable + int texMaxLevel = 0; + bool genMips = false; if (maxLevel > 0 && scaleFactor == 1) { if (gstate_c.Supports(GPU_SUPPORTS_TEXTURE_LOD_CONTROL)) { if (badMipSizes) { // WARN_LOG(G3D, "Bad mipmap for texture sized %dx%dx%d - autogenerating", w, h, (int)format); if (canAutoGen) { - glGenerateMipmap(GL_TEXTURE_2D); + genMips = true; } else { - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0); + texMaxLevel = 0; maxLevel = 0; } } else { for (int i = 1; i <= maxLevel; i++) { LoadTextureLevel(*entry, replaced, i, replaceImages, scaleFactor, dstFmt); } - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, maxLevel); + texMaxLevel = maxLevel; } } else { // Avoid PowerVR driver bug if (canAutoGen && w > 1 && h > 1 && !(h > w && (gl_extensions.bugs & BUG_PVR_GENMIPMAP_HEIGHT_GREATER))) { // Really! only seems to fail if height > width // NOTICE_LOG(G3D, "Generating mipmap for texture sized %dx%d%d", w, h, (int)format); - glGenerateMipmap(GL_TEXTURE_2D); + genMips = true; } else { maxLevel = 0; } } } else if (gstate_c.Supports(GPU_SUPPORTS_TEXTURE_LOD_CONTROL)) { - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0); + texMaxLevel = 0; } if (maxLevel == 0) { @@ -740,32 +666,12 @@ void TextureCacheGLES::BuildTexture(TexCacheEntry *const entry, bool replaceImag entry->SetAlphaStatus(TexCacheEntry::TexStatus(replaced.AlphaStatus())); } - if (gstate_c.Supports(GPU_SUPPORTS_ANISOTROPY)) { - int aniso = 1 << g_Config.iAnisotropyLevel; - float anisotropyLevel = (float) aniso > maxAnisotropyLevel ? maxAnisotropyLevel : (float) aniso; - if (anisotropyLevel > 1.0f) { - glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, anisotropyLevel); - } - } + render_->FinalizeTexture(entry->textureName, texMaxLevel, genMips); // This will rebind it, but that's okay. + // Need to actually bind it now - it might only have gotten bound in the init phase. + render_->BindTexture(0, entry->textureName); UpdateSamplingParams(*entry, true); - - //glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); - glPixelStorei(GL_UNPACK_ALIGNMENT, 1); - //glPixelStorei(GL_PACK_ROW_LENGTH, 0); - glPixelStorei(GL_PACK_ALIGNMENT, 1); - CHECK_GL_ERROR_IF_DEBUG(); -} - -u32 TextureCacheGLES::AllocTextureName() { - if (nameCache_.empty()) { - nameCache_.resize(TEXCACHE_NAME_CACHE_SIZE); - glGenTextures(TEXCACHE_NAME_CACHE_SIZE, &nameCache_[0]); - } - u32 name = nameCache_.back(); - nameCache_.pop_back(); - return name; } GLenum TextureCacheGLES::GetDestFormat(GETextureFormat format, GEPaletteFormat clutFormat) const { @@ -790,7 +696,7 @@ GLenum TextureCacheGLES::GetDestFormat(GETextureFormat format, GEPaletteFormat c } } -void *TextureCacheGLES::DecodeTextureLevelOld(GETextureFormat format, GEPaletteFormat clutformat, int level, GLenum dstFmt, int scaleFactor, int *bufwout) { +u8 *TextureCacheGLES::DecodeTextureLevelOld(GETextureFormat format, GEPaletteFormat clutformat, int level, GLenum dstFmt, int scaleFactor, int *bufwout) { void *finalBuf = nullptr; u32 texaddr = gstate.getTextureAddress(level); int bufw = GetTextureBufw(level, texaddr, format); @@ -808,26 +714,26 @@ void *TextureCacheGLES::DecodeTextureLevelOld(GETextureFormat format, GEPaletteF decPitch = bufw * pixelSize; } - tmpTexBufRearrange_.resize(std::max(w, bufw) * h); - DecodeTextureLevel((u8 *)tmpTexBufRearrange_.data(), decPitch, format, clutformat, texaddr, level, bufw, true, false, false); - return tmpTexBufRearrange_.data(); + uint8_t *texBuf = new uint8_t[std::max(w, bufw) * h * pixelSize]; + DecodeTextureLevel(texBuf, decPitch, format, clutformat, texaddr, level, bufw, true, false, false); + return texBuf; } -TexCacheEntry::TexStatus TextureCacheGLES::CheckAlpha(const u32 *pixelData, GLenum dstFmt, int stride, int w, int h) { +TexCacheEntry::TexStatus TextureCacheGLES::CheckAlpha(const uint8_t *pixelData, GLenum dstFmt, int stride, int w, int h) { CheckAlphaResult res; switch (dstFmt) { case GL_UNSIGNED_SHORT_4_4_4_4: - res = CheckAlphaABGR4444Basic(pixelData, stride, w, h); + res = CheckAlphaABGR4444Basic((const uint32_t *)pixelData, stride, w, h); break; case GL_UNSIGNED_SHORT_5_5_5_1: - res = CheckAlphaABGR1555Basic(pixelData, stride, w, h); + res = CheckAlphaABGR1555Basic((const uint32_t *)pixelData, stride, w, h); break; case GL_UNSIGNED_SHORT_5_6_5: // Never has any alpha. res = CHECKALPHA_FULL; break; default: - res = CheckAlphaRGBA8888Basic(pixelData, stride, w, h); + res = CheckAlphaRGBA8888Basic((const uint32_t *)pixelData, stride, w, h); break; } @@ -838,9 +744,7 @@ void TextureCacheGLES::LoadTextureLevel(TexCacheEntry &entry, ReplacedTexture &r int w = gstate.getTextureWidth(level); int h = gstate.getTextureHeight(level); bool useUnpack = false; - u32 *pixelData; - - CHECK_GL_ERROR_IF_DEBUG(); + uint8_t *pixelData; // TODO: only do this once u32 texByteAlign = 1; @@ -850,10 +754,10 @@ void TextureCacheGLES::LoadTextureLevel(TexCacheEntry &entry, ReplacedTexture &r if (replaced.GetSize(level, w, h)) { PROFILE_THIS_SCOPE("replacetex"); - tmpTexBufRearrange_.resize(w * h); int bpp = replaced.Format(level) == ReplacedTextureFormat::F_8888 ? 4 : 2; - replaced.Load(level, tmpTexBufRearrange_.data(), bpp * w); - pixelData = tmpTexBufRearrange_.data(); + uint8_t *rearrange = new uint8_t[w * h * bpp]; + replaced.Load(level, rearrange, bpp * w); + pixelData = rearrange; dstFmt = ToGLESFormat(replaced.Format(level)); @@ -863,20 +767,14 @@ void TextureCacheGLES::LoadTextureLevel(TexCacheEntry &entry, ReplacedTexture &r GEPaletteFormat clutformat = gstate.getClutPaletteFormat(); int bufw; - void *finalBuf = DecodeTextureLevelOld(GETextureFormat(entry.format), clutformat, level, dstFmt, scaleFactor, &bufw); - if (finalBuf == NULL) { + uint8_t *finalBuf = DecodeTextureLevelOld(GETextureFormat(entry.format), clutformat, level, dstFmt, scaleFactor, &bufw); + if (!finalBuf) { return; } - // Can restore these and remove the fixup at the end of DecodeTextureLevel on desktop GL and GLES 3. - if (scaleFactor == 1 && gstate_c.Supports(GPU_SUPPORTS_UNPACK_SUBIMAGE) && w != bufw) { - glPixelStorei(GL_UNPACK_ROW_LENGTH, bufw); - useUnpack = true; - } - // Textures are always aligned to 16 bytes bufw, so this could safely be 4 always. texByteAlign = dstFmt == GL_UNSIGNED_BYTE ? 4 : 2; - pixelData = (u32 *)finalBuf; + pixelData = finalBuf; // We check before scaling since scaling shouldn't invent alpha from a full alpha texture. if ((entry.status & TexCacheEntry::STATUS_CHANGE_FREQUENT) == 0) { @@ -886,8 +784,12 @@ void TextureCacheGLES::LoadTextureLevel(TexCacheEntry &entry, ReplacedTexture &r entry.SetAlphaStatus(TexCacheEntry::STATUS_ALPHA_UNKNOWN); } - if (scaleFactor > 1) - scaler.Scale(pixelData, dstFmt, w, h, scaleFactor); + if (scaleFactor > 1) { + uint8_t *rearrange = new uint8_t[w * scaleFactor * h * scaleFactor * 4]; + scaler.ScaleAlways((u32 *)rearrange, (u32 *)pixelData, dstFmt, w, h, scaleFactor); + pixelData = rearrange; + delete [] finalBuf; + } if (replacer_.Enabled()) { ReplacedTextureDecodeInfo replacedInfo; @@ -904,56 +806,20 @@ void TextureCacheGLES::LoadTextureLevel(TexCacheEntry &entry, ReplacedTexture &r } } - glPixelStorei(GL_UNPACK_ALIGNMENT, texByteAlign); - - CHECK_GL_ERROR_IF_DEBUG(); - GLuint components = dstFmt == GL_UNSIGNED_SHORT_5_6_5 ? GL_RGB : GL_RGBA; GLuint components2 = components; if (replaceImages) { PROFILE_THIS_SCOPE("repltex"); - glTexSubImage2D(GL_TEXTURE_2D, level, 0, 0, w, h, components2, dstFmt, pixelData); + Crash(); + // glTexSubImage2D(GL_TEXTURE_2D, level, 0, 0, w, h, components2, dstFmt, pixelData); } else { PROFILE_THIS_SCOPE("loadtex"); - // Avoid misleading errors in texture upload, these are common. - GLenum err = glGetError(); - if (err) { - WARN_LOG(G3D, "Got an error BEFORE texture upload: %08x (%s)", err, GLEnumToString(err).c_str()); - } if (IsFakeMipmapChange()) - glTexImage2D(GL_TEXTURE_2D, 0, components, w, h, 0, components2, dstFmt, pixelData); + render_->TextureImage(entry.textureName, 0, w, h, components, components2, dstFmt, pixelData); else - glTexImage2D(GL_TEXTURE_2D, level, components, w, h, 0, components2, dstFmt, pixelData); - if (!lowMemoryMode_) { - // TODO: We really, really should avoid calling glGetError. - GLenum err = glGetError(); - if (err == GL_OUT_OF_MEMORY) { - WARN_LOG_REPORT(G3D, "Texture cache ran out of GPU memory; switching to low memory mode"); - lowMemoryMode_ = true; - decimationCounter_ = 0; - Decimate(); - // Try again, now that we've cleared out textures in lowMemoryMode_. - glTexImage2D(GL_TEXTURE_2D, level, components, w, h, 0, components2, dstFmt, pixelData); - - I18NCategory *err = GetI18NCategory("Error"); - if (scaleFactor > 1) { - host->NotifyUserMessage(err->T("Warning: Video memory FULL, reducing upscaling and switching to slow caching mode"), 2.0f); - } else { - host->NotifyUserMessage(err->T("Warning: Video memory FULL, switching to slow caching mode"), 2.0f); - } - } else if (err != GL_NO_ERROR) { - // We checked the err anyway, might as well log if there is one. - WARN_LOG(G3D, "Got an error in texture upload: %08x (%s) (components=%s components2=%s dstFmt=%s w=%d h=%d level=%d)", - err, GLEnumToString(err).c_str(), GLEnumToString(components).c_str(), GLEnumToString(components2).c_str(), GLEnumToString(dstFmt).c_str(), - w, h, level); - } - } - } - - if (useUnpack) { - glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); + render_->TextureImage(entry.textureName, level, w, h, components, components2, dstFmt, pixelData); } } @@ -1046,19 +912,40 @@ bool TextureCacheGLES::GetCurrentTextureDebug(GPUDebugBuffer &buffer, int level) } SetTexture(true); + if (!nextTexture_) + return false; + + // Apply texture may need to rebuild the texture if we're about to render, or bind a framebuffer. + TexCacheEntry *entry = nextTexture_; ApplyTexture(); + + // TODO: Centralize? + if (entry->framebuffer) { + VirtualFramebuffer *vfb = entry->framebuffer; + buffer.Allocate(vfb->bufferWidth, vfb->bufferHeight, GPU_DBG_FORMAT_8888, false); + bool retval = draw_->CopyFramebufferToMemorySync(vfb->fbo, Draw::FB_COLOR_BIT, 0, 0, vfb->bufferWidth, vfb->bufferHeight, Draw::DataFormat::R8G8B8A8_UNORM, buffer.GetData(), vfb->bufferWidth); + // Vulkan requires us to re-apply all dynamic state for each command buffer, and the above will cause us to start a new cmdbuf. + // So let's dirty the things that are involved in Vulkan dynamic state. Readbacks are not frequent so this won't hurt other backends. + gstate_c.Dirty(DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_BLEND_STATE | DIRTY_DEPTHSTENCIL_STATE); + // We may have blitted to a temp FBO. + framebufferManager_->RebindFramebuffer(); + return retval; + } + + GLRenderManager *renderManager = (GLRenderManager *)draw_->GetNativeObject(Draw::NativeObject::RENDER_MANAGER); + + // Not a framebuffer, so let's assume these are right. int w = gstate.getTextureWidth(level); int h = gstate.getTextureHeight(level); - glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &w); - glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &h); if (level != 0) { gstate = saved; } buffer.Allocate(w, h, GE_FORMAT_8888, false); - glPixelStorei(GL_PACK_ALIGNMENT, 4); - glGetTexImage(GL_TEXTURE_2D, 0, GL_RGBA, GL_UNSIGNED_BYTE, buffer.GetData()); + renderManager->CopyImageToMemorySync(entry->textureName, level, 0, 0, w, h, Draw::DataFormat::R8G8B8A8_UNORM, (uint8_t *)buffer.GetData(), w); + gstate_c.Dirty(DIRTY_TEXTURE_IMAGE | DIRTY_TEXTURE_PARAMS); + framebufferManager_->RebindFramebuffer(); return true; #else diff --git a/GPU/GLES/TextureCacheGLES.h b/GPU/GLES/TextureCacheGLES.h index 9e894e2476..2d415c599a 100644 --- a/GPU/GLES/TextureCacheGLES.h +++ b/GPU/GLES/TextureCacheGLES.h @@ -21,6 +21,7 @@ #include "gfx_es2/gpu_features.h" #include "gfx/gl_common.h" +#include "thin3d/GLRenderManager.h" #include "GPU/GPUInterface.h" #include "GPU/GPUState.h" #include "GPU/GLES/TextureScalerGLES.h" @@ -31,6 +32,7 @@ class FramebufferManagerGLES; class DepalShaderCacheGLES; class ShaderManagerGLES; class DrawEngineGLES; +class GLRTexture; class TextureCacheGLES : public TextureCacheCommon { public: @@ -52,17 +54,15 @@ public: } void ForgetLastTexture() override { - lastBoundTexture = INVALID_TEX; + lastBoundTexture = nullptr; gstate_c.Dirty(DIRTY_TEXTURE_PARAMS); } void InvalidateLastTexture(TexCacheEntry *entry = nullptr) override { if (!entry || entry->textureName == lastBoundTexture) { - lastBoundTexture = INVALID_TEX; + lastBoundTexture = nullptr; } } - u32 AllocTextureName(); - // Only used by Qt UI? bool DecodeTexture(u8 *output, const GPUgstate &state); @@ -80,25 +80,29 @@ private: void UpdateSamplingParams(TexCacheEntry &entry, bool force); void LoadTextureLevel(TexCacheEntry &entry, ReplacedTexture &replaced, int level, bool replaceImages, int scaleFactor, GLenum dstFmt); GLenum GetDestFormat(GETextureFormat format, GEPaletteFormat clutFormat) const; - void *DecodeTextureLevelOld(GETextureFormat format, GEPaletteFormat clutformat, int level, GLenum dstFmt, int scaleFactor, int *bufw = 0); - TexCacheEntry::TexStatus CheckAlpha(const u32 *pixelData, GLenum dstFmt, int stride, int w, int h); + + // Caller owns the returned buffer. Delete[]. + u8 *DecodeTextureLevelOld(GETextureFormat format, GEPaletteFormat clutformat, int level, GLenum dstFmt, int scaleFactor, int *bufw = 0); + + TexCacheEntry::TexStatus CheckAlpha(const uint8_t *pixelData, GLenum dstFmt, int stride, int w, int h); void UpdateCurrentClut(GEPaletteFormat clutFormat, u32 clutBase, bool clutIndexIsSimple) override; void ApplyTextureFramebuffer(TexCacheEntry *entry, VirtualFramebuffer *framebuffer) override; void BuildTexture(TexCacheEntry *const entry, bool replaceImages) override; - std::vector nameCache_; + GLRenderManager *render_; TextureScalerGLES scaler; - u32 lastBoundTexture; - float maxAnisotropyLevel; + GLRTexture *lastBoundTexture; FramebufferManagerGLES *framebufferManagerGL_; DepalShaderCacheGLES *depalShaderCache_; ShaderManagerGLES *shaderManager_; DrawEngineGLES *drawEngine_; + GLRInputLayout *shadeInputLayout_; + enum { INVALID_TEX = -1 }; }; diff --git a/GPU/GLES/VertexShaderGeneratorGLES.cpp b/GPU/GLES/VertexShaderGeneratorGLES.cpp index 1517d58643..f9de2d312b 100644 --- a/GPU/GLES/VertexShaderGeneratorGLES.cpp +++ b/GPU/GLES/VertexShaderGeneratorGLES.cpp @@ -107,7 +107,6 @@ void GenerateVertexShader(const VShaderID &id, char *buffer, uint32_t *attrMask, const char *attribute = "attribute"; const char * const * boneWeightDecl = boneWeightAttrDecl; const char *texelFetch = NULL; - bool isAllowTexture1D = false; bool highpFog = false; bool highpTexcoord = false; @@ -135,13 +134,11 @@ void GenerateVertexShader(const VShaderID &id, char *buffer, uint32_t *attrMask, glslES30 = true; WRITE(p, "#version 330\n"); texelFetch = "texelFetch"; - isAllowTexture1D = true; } else if (gl_extensions.VersionGEThan(3, 0, 0)) { WRITE(p, "#version 130\n"); if (gl_extensions.EXT_gpu_shader4) { WRITE(p, "#extension GL_EXT_gpu_shader4 : enable\n"); texelFetch = "texelFetch"; - isAllowTexture1D = true; } } else { WRITE(p, "#version 110\n"); @@ -374,10 +371,9 @@ void GenerateVertexShader(const VShaderID &id, char *buffer, uint32_t *attrMask, if (doBezier || doSpline) { *uniformMask |= DIRTY_BEZIERSPLINE; - const char *sampler = !isAllowTexture1D ? "sampler2D" : "sampler1D"; - WRITE(p, "uniform %s u_tess_pos_tex;\n", sampler); - WRITE(p, "uniform %s u_tess_tex_tex;\n", sampler); - WRITE(p, "uniform %s u_tess_col_tex;\n", sampler); + WRITE(p, "uniform sampler2D u_tess_pos_tex;\n"); + WRITE(p, "uniform sampler2D u_tess_tex_tex;\n"); + WRITE(p, "uniform sampler2D u_tess_col_tex;\n"); WRITE(p, "uniform int u_spline_count_u;\n"); @@ -502,12 +498,11 @@ void GenerateVertexShader(const VShaderID &id, char *buffer, uint32_t *attrMask, WRITE(p, " for (int i = 0; i < 4; i++) {\n"); WRITE(p, " for (int j = 0; j < 4; j++) {\n"); WRITE(p, " int index = (i + v%s) * u_spline_count_u + (j + u%s);\n", doBezier ? " * 3" : "", doBezier ? " * 3" : ""); - const char *index = !isAllowTexture1D ? "ivec2(index, 0)" : "index"; - WRITE(p, " _pos[i * 4 + j] = %s(u_tess_pos_tex, %s, 0).xyz;\n", texelFetch, index); + WRITE(p, " _pos[i * 4 + j] = %s(u_tess_pos_tex, ivec2(index, 0), 0).xyz;\n", texelFetch); if (doTexture && hasTexcoord && hasTexcoordTess) - WRITE(p, " _tex[i * 4 + j] = %s(u_tess_tex_tex, %s, 0).xy;\n", texelFetch, index); + WRITE(p, " _tex[i * 4 + j] = %s(u_tess_tex_tex, ivec2(index, 0), 0).xy;\n", texelFetch); if (hasColor && hasColorTess) - WRITE(p, " _col[i * 4 + j] = %s(u_tess_col_tex, %s, 0).rgba;\n", texelFetch, index); + WRITE(p, " _col[i * 4 + j] = %s(u_tess_col_tex, ivec2(index, 0), 0).rgba;\n", texelFetch); WRITE(p, " }\n"); WRITE(p, " }\n"); WRITE(p, " vec2 tess_pos = position.xy;\n"); @@ -536,7 +531,7 @@ void GenerateVertexShader(const VShaderID &id, char *buffer, uint32_t *attrMask, if (hasColorTess) WRITE(p, " vec4 col = tess_sample(_col, weights);\n"); else - WRITE(p, " vec4 col = %s(u_tess_col_tex, %s, 0).rgba;\n", texelFetch, !isAllowTexture1D ? "ivec2(0, 0)" : "0"); + WRITE(p, " vec4 col = %s(u_tess_col_tex, ivec2(0, 0), 0).rgba;\n", texelFetch); } if (hasNormal) { // Curved surface is probably always need to compute normal(not sampling from control points) diff --git a/GPU/Vulkan/DrawEngineVulkan.cpp b/GPU/Vulkan/DrawEngineVulkan.cpp index a11e02d27e..6143778ddd 100644 --- a/GPU/Vulkan/DrawEngineVulkan.cpp +++ b/GPU/Vulkan/DrawEngineVulkan.cpp @@ -847,6 +847,10 @@ void DrawEngineVulkan::DoFlush() { ApplyDrawStateLate(renderManager, false, 0, pipeline->useBlendConstant); gstate_c.Clean(DIRTY_BLEND_STATE | DIRTY_DEPTHSTENCIL_STATE | DIRTY_RASTER_STATE | DIRTY_VIEWPORTSCISSOR_STATE); lastPipeline_ = pipeline; + + // Must dirty blend state here so we re-copy next time. Example: Lunar's spell effects. + if (fboTexBound_) + gstate_c.Dirty(DIRTY_BLEND_STATE); } lastPrim_ = prim; @@ -947,6 +951,10 @@ void DrawEngineVulkan::DoFlush() { ApplyDrawStateLate(renderManager, result.setStencil, result.stencilValue, pipeline->useBlendConstant); gstate_c.Clean(DIRTY_BLEND_STATE | DIRTY_DEPTHSTENCIL_STATE | DIRTY_RASTER_STATE | DIRTY_VIEWPORTSCISSOR_STATE); lastPipeline_ = pipeline; + + // Must dirty blend state here so we re-copy next time. Example: Lunar's spell effects. + if (fboTexBound_) + gstate_c.Dirty(DIRTY_BLEND_STATE); } lastPrim_ = prim; diff --git a/GPU/Vulkan/StencilBufferVulkan.cpp b/GPU/Vulkan/StencilBufferVulkan.cpp index 8ee4666583..febe32c61c 100644 --- a/GPU/Vulkan/StencilBufferVulkan.cpp +++ b/GPU/Vulkan/StencilBufferVulkan.cpp @@ -38,11 +38,15 @@ layout (location = 0) in vec2 v_texcoord0; layout (location = 0) out vec4 fragColor0; void main() { - vec4 index = texture(tex, v_texcoord0); - int indexBits = int(floor(index.a * 255.99)) & 0xFF; - if ((indexBits & u_stencilValue) == 0) - discard; - fragColor0 = index.aaaa; + if (u_stencilValue == 0) { + fragColor0 = vec4(0.0); + } else { + vec4 index = texture(tex, v_texcoord0); + int indexBits = int(floor(index.a * 255.99)) & 0xFF; + if ((indexBits & u_stencilValue) == 0) + discard; + fragColor0 = index.aaaa; + } } )"; @@ -116,18 +120,6 @@ bool FramebufferManagerVulkan::NotifyStencilUpload(u32 addr, int size, bool skip VulkanRenderManager *renderManager = (VulkanRenderManager *)draw_->GetNativeObject(Draw::NativeObject::RENDER_MANAGER); - if (usedBits == 0) { - if (skipZero) { - // Common when creating buffers, it's already 0. We're done. - return false; - } - - // TODO: Find a nice way to clear alpha here too. - draw_->BindFramebufferAsRenderTarget(dstBuffer->fbo, { Draw::RPAction::KEEP, Draw::RPAction::KEEP, Draw::RPAction::CLEAR }); - gstate_c.Dirty(DIRTY_BLEND_STATE | DIRTY_RASTER_STATE | DIRTY_VIEWPORTSCISSOR_STATE); - return true; - } - shaderManagerVulkan_->DirtyLastShader(); textureCacheVulkan_->ForgetLastTexture(); @@ -150,26 +142,34 @@ bool FramebufferManagerVulkan::NotifyStencilUpload(u32 addr, int size, bool skip VkDescriptorSet descSet = vulkan2D_->GetDescriptorSet(overrideImageView_, nearestSampler_, VK_NULL_HANDLE, VK_NULL_HANDLE); + // Note: Even with skipZero, we don't necessarily start framebuffers at 0 in Vulkan. Clear anyway. + // Not an actual clear, because we need to draw to alpha only as well. + uint32_t value = 0; + renderManager->PushConstants(vulkan2D_->GetPipelineLayout(), VK_SHADER_STAGE_VERTEX_BIT|VK_SHADER_STAGE_FRAGMENT_BIT, 0, 4, &value); + renderManager->SetStencilParams(0xFF, 0xFF, 0x00); + renderManager->Draw(vulkan2D_->GetPipelineLayout(), descSet, 0, nullptr, VK_NULL_HANDLE, 0, 3); // full screen triangle + for (int i = 1; i < values; i += i) { if (!(usedBits & i)) { // It's already zero, let's skip it. continue; } - // These feel a little backwards : Mask is the bits that are going to be written, while value - // is the "mask" that will be tested against. - uint8_t mask = 0; + + // These are the stencil bits that will be written. We discard when the bit doesn't match. + uint8_t writeMask = 0; + // This is the value to test the texture alpha against in the shader. uint32_t value = 0; if (dstBuffer->format == GE_FORMAT_4444) { - mask = i | (i << 4); + writeMask = i | (i << 4); value = i * 16; } else if (dstBuffer->format == GE_FORMAT_5551) { - mask = 0xFF; + writeMask = 0xFF; value = i * 128; } else { - mask = i; + writeMask = i; value = i; } - renderManager->SetStencilParams(mask, 0xFF, 0xFF); + renderManager->SetStencilParams(writeMask, 0xFF, 0xFF); // Need to specify both VERTEX and FRAGMENT bits here since that's what we set up in the pipeline layout, and we need // that for the post shaders. There's probably not really a cost to this. renderManager->PushConstants(vulkan2D_->GetPipelineLayout(), VK_SHADER_STAGE_VERTEX_BIT|VK_SHADER_STAGE_FRAGMENT_BIT, 0, 4, &value); diff --git a/GPU/Vulkan/TextureCacheVulkan.cpp b/GPU/Vulkan/TextureCacheVulkan.cpp index fd7aaa6bc0..73085778f1 100644 --- a/GPU/Vulkan/TextureCacheVulkan.cpp +++ b/GPU/Vulkan/TextureCacheVulkan.cpp @@ -784,16 +784,18 @@ void TextureCacheVulkan::LoadTextureLevel(TexCacheEntry &entry, uint8_t *writePt } bool TextureCacheVulkan::GetCurrentTextureDebug(GPUDebugBuffer &buffer, int level) { - ApplyTexture(); SetTexture(false); if (!nextTexture_) return false; + // Apply texture may need to rebuild the texture if we're about to render, or bind a framebuffer. + TexCacheEntry *entry = nextTexture_; + ApplyTexture(); + // TODO: Centralize? - if (nextTexture_->framebuffer) { - VirtualFramebuffer *vfb = nextTexture_->framebuffer; - bool flipY = GetGPUBackend() == GPUBackend::OPENGL && g_Config.iRenderingMode != FB_NON_BUFFERED_MODE; - buffer.Allocate(vfb->bufferWidth, vfb->bufferHeight, GPU_DBG_FORMAT_8888, flipY); + if (entry->framebuffer) { + VirtualFramebuffer *vfb = entry->framebuffer; + buffer.Allocate(vfb->bufferWidth, vfb->bufferHeight, GPU_DBG_FORMAT_8888, false); bool retval = draw_->CopyFramebufferToMemorySync(vfb->fbo, Draw::FB_COLOR_BIT, 0, 0, vfb->bufferWidth, vfb->bufferHeight, Draw::DataFormat::R8G8B8A8_UNORM, buffer.GetData(), vfb->bufferWidth); // Vulkan requires us to re-apply all dynamic state for each command buffer, and the above will cause us to start a new cmdbuf. // So let's dirty the things that are involved in Vulkan dynamic state. Readbacks are not frequent so this won't hurt other backends. @@ -803,9 +805,9 @@ bool TextureCacheVulkan::GetCurrentTextureDebug(GPUDebugBuffer &buffer, int leve return retval; } - if (!nextTexture_->vkTex || !nextTexture_->vkTex->texture_) + if (!entry->vkTex || !entry->vkTex->texture_) return false; - VulkanTexture *texture = nextTexture_->vkTex->texture_; + VulkanTexture *texture = entry->vkTex->texture_; VulkanRenderManager *renderManager = (VulkanRenderManager *)draw_->GetNativeObject(Draw::NativeObject::RENDER_MANAGER); GPUDebugBufferFormat bufferFormat; diff --git a/Qt/Debugger/debugger_memorytex.cpp b/Qt/Debugger/debugger_memorytex.cpp index 51af9915c5..f077daaaf7 100644 --- a/Qt/Debugger/debugger_memorytex.cpp +++ b/Qt/Debugger/debugger_memorytex.cpp @@ -1,5 +1,4 @@ #include "debugger_memorytex.h" -#include "gfx/GLStateCache.h" #include "gfx/gl_common.h" #include "ui_debugger_memorytex.h" #include "Core/MemMap.h" diff --git a/Qt/QtMain.cpp b/Qt/QtMain.cpp index 3030f64354..ea364b08a5 100644 --- a/Qt/QtMain.cpp +++ b/Qt/QtMain.cpp @@ -29,6 +29,7 @@ #include "QtMain.h" #include "gfx_es2/gpu_features.h" #include "math/math_util.h" +#include "thread/threadutil.h" #include @@ -188,31 +189,64 @@ static int mainInternal(QApplication &a) return a.exec(); } +void MainUI::EmuThreadFunc() { + ILOG("In emu thread"); + setCurrentThreadName("Emu"); + + // There's no real requirement that NativeInit happen on this thread. + // We just call the update/render loop here. + emuThreadState = (int)EmuThreadState::RUNNING; + while (emuThreadState != (int)EmuThreadState::QUIT_REQUESTED) { + #ifdef SDL + SDL_PumpEvents(); + #endif + updateAccelerometer(); + time_update(); + UpdateRunLoop(); + } + emuThreadState = (int)EmuThreadState::STOPPED; +} + +void MainUI::EmuThreadStart() { + emuThreadState = (int)EmuThreadState::START_REQUESTED; + emuThread = std::thread([&]() { this->EmuThreadFunc(); } ); +} + +void MainUI::EmuThreadStop() { + emuThreadState = (int)EmuThreadState::QUIT_REQUESTED; + emuThread.join(); + emuThread = std::thread(); +} + MainUI::MainUI(QWidget *parent): - QGLWidget(parent) + QGLWidget(parent) { - setAttribute(Qt::WA_AcceptTouchEvents); + emuThreadState = (int)EmuThreadState::DISABLED; + setAttribute(Qt::WA_AcceptTouchEvents); #if QT_VERSION < QT_VERSION_CHECK(5, 0, 0) - setAttribute(Qt::WA_LockLandscapeOrientation); + setAttribute(Qt::WA_LockLandscapeOrientation); #endif #if defined(MOBILE_DEVICE) - acc = new QAccelerometer(this); - acc->start(); + acc = new QAccelerometer(this); + acc->start(); #endif - setFocus(); - setFocusPolicy(Qt::StrongFocus); - startTimer(16); + setFocus(); + setFocusPolicy(Qt::StrongFocus); + startTimer(16); } MainUI::~MainUI() { + if (useThread_) { + EmuThreadStop(); + } #if defined(MOBILE_DEVICE) - delete acc; + delete acc; #endif - NativeShutdownGraphics(); - graphicsContext->Shutdown(); - delete graphicsContext; - graphicsContext = nullptr; + NativeShutdownGraphics(); + graphicsContext->Shutdown(); + delete graphicsContext; + graphicsContext = nullptr; } QString MainUI::InputBoxGetQString(QString title, QString defaultValue) @@ -330,18 +364,36 @@ void MainUI::initializeGL() if (gl_extensions.IsCoreContext) glGetError(); #endif + ILOG("Initializing graphics context"); graphicsContext = new QtDummyGraphicsContext(); NativeInitGraphics(graphicsContext); + + // OpenGL uses a background thread to do the main processing and only renders on the gl thread. + useThread_ = g_Config.iGPUBackend == (int)GPUBackend::OPENGL; + + if (useThread_) { + ILOG("Using thread, starting emu thread"); + EmuThreadStart(); + + graphicsContext->ThreadStart(); + } else { + ILOG("Not using thread, backend=%d", (int)g_Config.iGPUBackend); + } } void MainUI::paintGL() { -#ifdef SDL - SDL_PumpEvents(); -#endif - updateAccelerometer(); - time_update(); - UpdateRunLoop(); + if (useThread_) { + graphicsContext->ThreadFrame(); + // Do the rest in EmuThreadFunc + } else { + #ifdef SDL + SDL_PumpEvents(); + #endif + updateAccelerometer(); + time_update(); + UpdateRunLoop(); + } } void MainUI::updateAccelerometer() @@ -454,14 +506,17 @@ int main(int argc, char *argv[]) #endif savegame_dir += "/"; external_dir += "/"; - + bool fullscreenCLI=false; for (int i = 1; i < argc; i++) { if (!strcmp(argv[i],"--fullscreen")) fullscreenCLI=true; } NativeInit(argc, (const char **)argv, savegame_dir.c_str(), external_dir.c_str(), nullptr, fullscreenCLI); - + + // TODO: Support other backends than GL, like Vulkan, in the Qt backend. + g_Config.iGPUBackend = (int)GPUBackend::OPENGL; + int ret = mainInternal(a); NativeShutdownGraphics(); diff --git a/Qt/QtMain.h b/Qt/QtMain.h index f4f1b3c5c2..8f0538f7c6 100644 --- a/Qt/QtMain.h +++ b/Qt/QtMain.h @@ -19,6 +19,8 @@ QTM_USE_NAMESPACE #endif #include +#include +#include #include "base/display.h" #include "base/logging.h" @@ -37,6 +39,7 @@ QTM_USE_NAMESPACE #include "Core/Core.h" #include "Core/Config.h" #include "Core/System.h" +#include "thin3d/GLRenderManager.h" // Input void SimulateGamepad(); @@ -47,21 +50,48 @@ public: CheckGLExtensions(); draw_ = Draw::T3DCreateGLContext(); SetGPUBackend(GPUBackend::OPENGL); + renderManager_ = (GLRenderManager *)draw_->GetNativeObject(Draw::NativeObject::RENDER_MANAGER); bool success = draw_->CreatePresets(); assert(success); } + ~QtDummyGraphicsContext() { delete draw_; + draw_ = nullptr; + renderManager_ = nullptr; } Draw::DrawContext *GetDrawContext() override { return draw_; } + + void ThreadStart() override { + renderManager_->ThreadStart(); + } + + bool ThreadFrame() override { + return renderManager_->ThreadFrame(); + } + + void ThreadEnd() override { + renderManager_->ThreadEnd(); + } + private: - Draw::DrawContext *draw_; + Draw::DrawContext *draw_ = nullptr; + GLRenderManager *renderManager_ = nullptr; }; -//GUI +enum class EmuThreadState { + DISABLED, + START_REQUESTED, + RUNNING, + QUIT_REQUESTED, + STOPPED, +}; + + +// GUI, thread manager class MainUI : public QGLWidget { Q_OBJECT @@ -87,6 +117,10 @@ protected: void updateAccelerometer(); + void EmuThreadFunc(); + void EmuThreadStart(); + void EmuThreadStop(); + private: QtDummyGraphicsContext *graphicsContext; @@ -94,6 +128,11 @@ private: #if defined(MOBILE_DEVICE) QAccelerometer* acc; #endif + + std::thread emuThread; + std::atomic emuThreadState; + + bool useThread_ = false; }; extern MainUI* emugl; diff --git a/SDL/SDLMain.cpp b/SDL/SDLMain.cpp index acc5cc0e10..a3a15581e0 100644 --- a/SDL/SDLMain.cpp +++ b/SDL/SDLMain.cpp @@ -14,9 +14,11 @@ SDLJoystick *joystick = NULL; #include #endif +#include #include #include #include +#include #include "base/display.h" #include "base/logging.h" @@ -32,6 +34,8 @@ SDLJoystick *joystick = NULL; #include "util/text/utf8.h" #include "util/text/parsers.h" #include "math/math_util.h" +#include "thin3d/GLRenderManager.h" +#include "thread/threadutil.h" #include "Common/Vulkan/VulkanContext.h" #include "Common/Vulkan/VulkanDebug.h" #include "math.h" @@ -198,6 +202,8 @@ void EGL_Close() { } #endif +class GLRenderManager; + class SDLGLGraphicsContext : public DummyGraphicsContext { public: SDLGLGraphicsContext() { @@ -218,21 +224,30 @@ public: } void SwapBuffers() override { -#ifdef USING_EGL - eglSwapBuffers(g_eglDisplay, g_eglSurface); -#else - SDL_GL_SwapWindow(window_); -#endif + renderManager_->Swap(); } Draw::DrawContext *GetDrawContext() override { return draw_; } + void ThreadStart() override { + renderManager_->ThreadStart(); + } + + bool ThreadFrame() override { + return renderManager_->ThreadFrame(); + } + + void ThreadEnd() override { + renderManager_->ThreadEnd(); + } + private: Draw::DrawContext *draw_ = nullptr; SDL_Window *window_; SDL_GLContext glContext = nullptr; + GLRenderManager *renderManager_ = nullptr; }; // Returns 0 on success. @@ -340,9 +355,17 @@ int SDLGLGraphicsContext::Init(SDL_Window *&window, int x, int y, int mode, std: // Finally we can do the regular initialization. CheckGLExtensions(); draw_ = Draw::T3DCreateGLContext(); + renderManager_ = (GLRenderManager *)draw_->GetNativeObject(Draw::NativeObject::RENDER_MANAGER); SetGPUBackend(GPUBackend::OPENGL); bool success = draw_->CreatePresets(); assert(success); + renderManager_->SetSwapFunction([&]() { +#ifdef USING_EGL + eglSwapBuffers(g_eglDisplay, g_eglSurface); +#else + SDL_GL_SwapWindow(window_); +#endif + }); window_ = window; return 0; } @@ -691,6 +714,40 @@ void ToggleFullScreenIfFlagSet(SDL_Window *window) { } } +enum class EmuThreadState { + DISABLED, + START_REQUESTED, + RUNNING, + QUIT_REQUESTED, + STOPPED, +}; + +static std::thread emuThread; +static std::atomic emuThreadState((int)EmuThreadState::DISABLED); + +static void EmuThreadFunc() { + setCurrentThreadName("Emu"); + + // There's no real requirement that NativeInit happen on this thread. + // We just call the update/render loop here. + emuThreadState = (int)EmuThreadState::RUNNING; + while (emuThreadState != (int)EmuThreadState::QUIT_REQUESTED) { + UpdateRunLoop(); + } + emuThreadState = (int)EmuThreadState::STOPPED; +} + +static void EmuThreadStart() { + emuThreadState = (int)EmuThreadState::START_REQUESTED; + emuThread = std::thread(&EmuThreadFunc); +} + +static void EmuThreadStop() { + emuThreadState = (int)EmuThreadState::QUIT_REQUESTED; + emuThread.join(); + emuThread = std::thread(); +} + #ifdef _WIN32 #undef main #endif @@ -888,6 +945,11 @@ int main(int argc, char *argv[]) { } } + // Since we render from the main thread, there's nothing done here, but we call it to avoid confusion. + if (!graphicsContext->InitFromRenderThread(&error_message)) { + printf("Init from thread error: '%s'\n", error_message.c_str()); + } + SDL_SetWindowTitle(window, (app_name_nice + " " + PPSSPP_GIT_VERSION).c_str()); #ifdef MOBILE_DEVICE @@ -934,6 +996,11 @@ int main(int argc, char *argv[]) { int framecount = 0; bool mouseDown = false; + if (GetGPUBackend() == GPUBackend::OPENGL) { + EmuThreadStart(); + } + graphicsContext->ThreadStart(); + while (true) { SDL_Event event; while (SDL_PollEvent(&event)) { @@ -1101,7 +1168,9 @@ int main(int argc, char *argv[]) { if (g_QuitRequested) break; const uint8_t *keys = SDL_GetKeyboardState(NULL); - UpdateRunLoop(); + if (emuThreadState == (int)EmuThreadState::DISABLED) { + UpdateRunLoop(); + } if (g_QuitRequested) break; #if !defined(MOBILE_DEVICE) @@ -1118,12 +1187,22 @@ int main(int argc, char *argv[]) { // glsl_refresh(); // auto-reloads modified GLSL shaders once per second. } + if (emuThreadState != (int)EmuThreadState::DISABLED) { + if (!graphicsContext->ThreadFrame()) + break; + } graphicsContext->SwapBuffers(); ToggleFullScreenIfFlagSet(window); time_update(); framecount++; } + + graphicsContext->ThreadEnd(); + graphicsContext->ShutdownFromRenderThread(); + + EmuThreadStop(); + delete joystick; NativeShutdownGraphics(); graphicsContext->Shutdown(); diff --git a/UI/EmuScreen.cpp b/UI/EmuScreen.cpp index 5aad74ca76..2f6b18213e 100644 --- a/UI/EmuScreen.cpp +++ b/UI/EmuScreen.cpp @@ -255,7 +255,7 @@ void EmuScreen::bootComplete() { #if !PPSSPP_PLATFORM(UWP) if (GetGPUBackend() == GPUBackend::OPENGL) { - const char *renderer = (const char*)glGetString(GL_RENDERER); + const char *renderer = gl_extensions.model; if (strstr(renderer, "Chainfire3D") != 0) { osm.Show(sc->T("Chainfire3DWarning", "WARNING: Chainfire3D detected, may cause problems"), 10.0f, 0xFF30a0FF, -1, true); } else if (strstr(renderer, "GLTools") != 0) { diff --git a/UI/GameSettingsScreen.cpp b/UI/GameSettingsScreen.cpp index 34fec613b6..6b63263d7d 100644 --- a/UI/GameSettingsScreen.cpp +++ b/UI/GameSettingsScreen.cpp @@ -63,10 +63,6 @@ #include "Windows/W32Util/ShellUtil.h" #endif -#if !PPSSPP_PLATFORM(UWP) -#include "gfx/gl_common.h" -#endif - extern bool VulkanMayBeAvailable(); GameSettingsScreen::GameSettingsScreen(std::string gamePath, std::string gameID, bool editThenRestore) @@ -85,8 +81,7 @@ bool CheckSupportInstancedTessellationGLES() { return true; #else // TODO: Make work with non-GL backends - int maxVertexTextureImageUnits; - glGetIntegerv(GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS, &maxVertexTextureImageUnits); + int maxVertexTextureImageUnits = gl_extensions.maxVertexTextureUnits; bool vertexTexture = maxVertexTextureImageUnits >= 3; // At least 3 for hardware tessellation bool canUseInstanceID = gl_extensions.EXT_draw_instanced || gl_extensions.ARB_draw_instanced; @@ -100,7 +95,7 @@ bool CheckSupportInstancedTessellationGLES() { #endif } -bool IsBackendSupportHWTess() { +bool DoesBackendSupportHWTess() { switch (GetGPUBackend()) { case GPUBackend::OPENGL: return CheckSupportInstancedTessellationGLES(); @@ -353,7 +348,7 @@ void GameSettingsScreen::CreateViews() { settingInfo_->Show(gr->T("HardwareTessellation Tip", "Uses hardware to make curves, always uses a fixed quality"), e.v); return UI::EVENT_CONTINUE; }); - tessHWEnable_ = IsBackendSupportHWTess() && !g_Config.bSoftwareRendering && g_Config.bHardwareTransform; + tessHWEnable_ = DoesBackendSupportHWTess() && !g_Config.bSoftwareRendering && g_Config.bHardwareTransform; tessellationHW->SetEnabledPtr(&tessHWEnable_); // In case we're going to add few other antialiasing option like MSAA in the future. @@ -798,13 +793,13 @@ UI::EventReturn GameSettingsScreen::OnSoftwareRendering(UI::EventParams &e) { postProcEnable_ = !g_Config.bSoftwareRendering && (g_Config.iRenderingMode != FB_NON_BUFFERED_MODE); resolutionEnable_ = !g_Config.bSoftwareRendering && (g_Config.iRenderingMode != FB_NON_BUFFERED_MODE); bloomHackEnable_ = !g_Config.bSoftwareRendering && (g_Config.iInternalResolution != 1); - tessHWEnable_ = IsBackendSupportHWTess() && !g_Config.bSoftwareRendering && g_Config.bHardwareTransform; + tessHWEnable_ = DoesBackendSupportHWTess() && !g_Config.bSoftwareRendering && g_Config.bHardwareTransform; return UI::EVENT_DONE; } UI::EventReturn GameSettingsScreen::OnHardwareTransform(UI::EventParams &e) { vtxCacheEnable_ = !g_Config.bSoftwareRendering && g_Config.bHardwareTransform; - tessHWEnable_ = IsBackendSupportHWTess() && !g_Config.bSoftwareRendering && g_Config.bHardwareTransform; + tessHWEnable_ = DoesBackendSupportHWTess() && !g_Config.bSoftwareRendering && g_Config.bHardwareTransform; return UI::EVENT_DONE; } diff --git a/Windows/EmuThread.cpp b/Windows/EmuThread.cpp index 87ae5f1d17..c48094bf79 100644 --- a/Windows/EmuThread.cpp +++ b/Windows/EmuThread.cpp @@ -10,6 +10,7 @@ #include "Common/Log.h" #include "Common/StringUtils.h" +#include "Common/GraphicsContext.h" #include "Windows/EmuThread.h" #include "Windows/W32Util/Misc.h" #include "Windows/MainWindow.h" @@ -27,8 +28,21 @@ static std::mutex emuThreadLock; static std::thread emuThread; static std::atomic emuThreadState; +static std::mutex renderThreadLock; +static std::thread renderThread; +static std::atomic renderThreadReady; + +static bool useRenderThread; +static bool renderThreadFailed; +static bool renderThreadSucceeded; +static std::string g_error_message; + extern std::vector GetWideCmdLine(); +class GraphicsContext; + +static GraphicsContext *g_graphicsContext; + enum EmuThreadStatus : int { THREAD_NONE = 0, THREAD_INIT, @@ -38,10 +52,15 @@ enum EmuThreadStatus : int { }; void EmuThreadFunc(); +void RenderThreadFunc(); -void EmuThread_Start() { +void EmuThread_Start(bool separateRenderThread) { std::lock_guard guard(emuThreadLock); emuThread = std::thread(&EmuThreadFunc); + useRenderThread = separateRenderThread; + if (useRenderThread) { + renderThread = std::thread(&RenderThreadFunc); + } } void EmuThread_Stop() { @@ -56,6 +75,9 @@ void EmuThread_Stop() { Core_Stop(); Core_WaitInactive(800); emuThread.join(); + if (useRenderThread) { + renderThread.join(); + } PostMessage(MainWindow::GetHWND(), MainWindow::WM_USER_UPDATE_UI, 0, 0); } @@ -64,6 +86,32 @@ bool EmuThread_Ready() { return emuThreadState == THREAD_CORE_LOOP; } +void RenderThreadFunc() { + setCurrentThreadName("Render"); + renderThreadFailed = false; + renderThreadSucceeded = false; + while (!g_graphicsContext) { + sleep_ms(10); + continue; + } + + std::string error_message; + if (!g_graphicsContext->InitFromRenderThread(&error_message)) { + g_error_message = error_message; + renderThreadFailed = true; + return; + } else { + renderThreadSucceeded = true; + } + + g_graphicsContext->ThreadStart(); + while (g_graphicsContext->ThreadFrame()) { + continue; + } + g_graphicsContext->ThreadEnd(); + g_graphicsContext->ShutdownFromRenderThread(); +} + void EmuThreadFunc() { emuThreadState = THREAD_INIT; @@ -87,10 +135,25 @@ void EmuThreadFunc() { host->UpdateUI(); - GraphicsContext *graphicsContext = nullptr; - std::string error_string; - if (!host->InitGraphics(&error_string, &graphicsContext)) { + bool success = host->InitGraphics(&error_string, &g_graphicsContext); + + if (success) { + if (!useRenderThread) { + // This is also the render thread. + success = g_graphicsContext->InitFromRenderThread(&error_string); + } else { + while (!renderThreadFailed && !renderThreadSucceeded) { + sleep_ms(10); + } + success = renderThreadSucceeded; + if (!success) { + error_string = g_error_message; + } + } + } + + if (!success) { // Before anything: are we restarting right now? if (performingRestart) { // Okay, switching graphics didn't work out. Probably a driver bug - fallback to restart. @@ -139,7 +202,7 @@ void EmuThreadFunc() { exit(1); } - NativeInitGraphics(graphicsContext); + NativeInitGraphics(g_graphicsContext); NativeResized(); INFO_LOG(BOOT, "Done."); @@ -162,17 +225,18 @@ void EmuThreadFunc() { // This way they can load a new game. if (!Core_IsActive()) UpdateUIState(UISTATE_MENU); - Core_Run(graphicsContext); + Core_Run(g_graphicsContext); } shutdown: emuThreadState = THREAD_SHUTDOWN; NativeShutdownGraphics(); + if (!useRenderThread) + g_graphicsContext->ShutdownFromRenderThread(); // NativeShutdown deletes the graphics context through host->ShutdownGraphics(). NativeShutdown(); emuThreadState = THREAD_END; } - diff --git a/Windows/EmuThread.h b/Windows/EmuThread.h index ed1a9df08d..d232fefb14 100644 --- a/Windows/EmuThread.h +++ b/Windows/EmuThread.h @@ -17,6 +17,6 @@ #pragma once -void EmuThread_Start(); +void EmuThread_Start(bool separateRenderThread); void EmuThread_Stop(); bool EmuThread_Ready(); diff --git a/Windows/GPU/WindowsGLContext.cpp b/Windows/GPU/WindowsGLContext.cpp index 3b9707ca4c..53cf6580f8 100644 --- a/Windows/GPU/WindowsGLContext.cpp +++ b/Windows/GPU/WindowsGLContext.cpp @@ -21,6 +21,7 @@ #include "gfx/gl_common.h" #include "gfx/gl_debug_log.h" #include "gfx_es2/gpu_features.h" +#include "thin3d/GLRenderManager.h" #include "GL/gl.h" #include "GL/wglew.h" #include "Core/Config.h" @@ -34,7 +35,7 @@ #include "Windows/GPU/WindowsGLContext.h" void WindowsGLContext::SwapBuffers() { - ::SwapBuffers(hDC); + renderManager_->Swap(); // Used during fullscreen switching to prevent rendering. if (pauseRequested) { @@ -46,10 +47,6 @@ void WindowsGLContext::SwapBuffers() { } pauseRequested = false; } - - // According to some sources, doing this *after* swapbuffers can reduce frame latency - // at a large performance cost. So let's not. - // glFinish(); } void WindowsGLContext::Pause() { @@ -96,7 +93,7 @@ void FormatDebugOutputARB(char outStr[], size_t outStrSize, GLenum source, GLenu case GL_DEBUG_SOURCE_APPLICATION_ARB: sourceFmt = "APPLICATION"; break; case GL_DEBUG_SOURCE_OTHER_ARB: sourceFmt = "OTHER"; break; } - _snprintf(sourceStr, 32, sourceFmt, source); + snprintf(sourceStr, sizeof(sourceStr), sourceFmt, source); char typeStr[32]; const char *typeFmt = "UNDEFINED(0x%04X)"; @@ -108,30 +105,34 @@ void FormatDebugOutputARB(char outStr[], size_t outStrSize, GLenum source, GLenu case GL_DEBUG_TYPE_PERFORMANCE_ARB: typeFmt = "PERFORMANCE"; break; case GL_DEBUG_TYPE_OTHER_ARB: typeFmt = "OTHER"; break; } - _snprintf(typeStr, 32, typeFmt, type); + snprintf(typeStr, sizeof(typeStr), typeFmt, type); char severityStr[32]; const char *severityFmt = "UNDEFINED"; - switch(severity) - { - case GL_DEBUG_SEVERITY_HIGH_ARB: severityFmt = "HIGH"; break; + switch (severity) { + case GL_DEBUG_SEVERITY_HIGH_ARB: severityFmt = "HIGH"; break; case GL_DEBUG_SEVERITY_MEDIUM_ARB: severityFmt = "MEDIUM"; break; case GL_DEBUG_SEVERITY_LOW_ARB: severityFmt = "LOW"; break; } - _snprintf(severityStr, 32, severityFmt, severity); - - _snprintf(outStr, outStrSize, "OpenGL: %s [source=%s type=%s severity=%s id=%d]", msg, sourceStr, typeStr, severityStr, id); + snprintf(severityStr, sizeof(severityStr), severityFmt, severity); + snprintf(outStr, outStrSize, "OpenGL: %s [source=%s type=%s severity=%s id=%d]\n", msg, sourceStr, typeStr, severityStr, id); } void DebugCallbackARB(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar *message, GLvoid *userParam) { (void)length; - FILE *outFile = (FILE*)userParam; - char finalMessage[256]; - FormatDebugOutputARB(finalMessage, 256, source, type, id, severity, message); + FILE *outFile = (FILE *)userParam; + char finalMessage[1024]; + FormatDebugOutputARB(finalMessage, sizeof(finalMessage), source, type, id, severity, message); OutputDebugStringA(finalMessage); + // Truncate the \n before passing to our log functions. + size_t len = strlen(finalMessage); + if (len) { + finalMessage[len - 1] = '\0'; + } + switch (type) { case GL_DEBUG_TYPE_ERROR_ARB: case GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR_ARB: @@ -153,8 +154,15 @@ void DebugCallbackARB(GLenum source, GLenum type, GLuint id, GLenum severity, bool WindowsGLContext::Init(HINSTANCE hInst, HWND window, std::string *error_message) { glslang::InitializeProcess(); + + hInst_ = hInst; + hWnd_ = window; + *error_message = "ok"; + return true; +} + +bool WindowsGLContext::InitFromRenderThread(std::string *error_message) { *error_message = "ok"; - hWnd = window; GLuint PixelFormat; // TODO: Change to use WGL_ARB_pixel_format instead @@ -179,14 +187,14 @@ bool WindowsGLContext::Init(HINSTANCE hInst, HWND window, std::string *error_mes 0, 0, 0 // Layer Masks Ignored }; - hDC = GetDC(hWnd); + hDC = GetDC(hWnd_); if (!hDC) { *error_message = "Failed to get a device context."; return false; // Return FALSE } - if (!(PixelFormat = ChoosePixelFormat(hDC, &pfd))) { + if (!(PixelFormat = ChoosePixelFormat(hDC, &pfd))) { *error_message = "Can't find a suitable PixelFormat."; return false; } @@ -196,7 +204,7 @@ bool WindowsGLContext::Init(HINSTANCE hInst, HWND window, std::string *error_mes return false; } - if (!(hRC = wglCreateContext(hDC))) { + if (!(hRC = wglCreateContext(hDC))) { *error_message = "Can't create a GL rendering context."; return false; } @@ -225,7 +233,7 @@ bool WindowsGLContext::Init(HINSTANCE hInst, HWND window, std::string *error_mes HDC dc = GetDC(NULL); u32 colour_depth = GetDeviceCaps(dc, BITSPIXEL); ReleaseDC(NULL, dc); - if (colour_depth != 32){ + if (colour_depth != 32) { MessageBox(0, L"Please switch your display to 32-bit colour mode", L"OpenGL Error", MB_OK); ExitProcess(1); } @@ -239,7 +247,7 @@ bool WindowsGLContext::Init(HINSTANCE hInst, HWND window, std::string *error_mes std::wstring title = ConvertUTF8ToWString(err->T("OpenGLDriverError", "OpenGL driver error")); std::wstring combined = versionDetected + error; - bool yes = IDYES == MessageBox(hWnd, combined.c_str(), title.c_str(), MB_ICONERROR | MB_YESNO); + bool yes = IDYES == MessageBox(hWnd_, combined.c_str(), title.c_str(), MB_ICONERROR | MB_YESNO); if (yes) { // Change the config to D3D and restart. @@ -362,8 +370,10 @@ bool WindowsGLContext::Init(HINSTANCE hInst, HWND window, std::string *error_mes CheckGLExtensions(); draw_ = Draw::T3DCreateGLContext(); + renderManager_ = (GLRenderManager *)draw_->GetNativeObject(Draw::NativeObject::RENDER_MANAGER); SetGPUBackend(GPUBackend::OPENGL); bool success = draw_->CreatePresets(); // if we get this far, there will always be a GLSL compiler capable of compiling these. + renderManager_->SetSwapFunction([&]() {::SwapBuffers(hDC); }); assert(success); CHECK_GL_ERROR_IF_DEBUG(); return true; // Success @@ -376,6 +386,12 @@ void WindowsGLContext::SwapInterval(int interval) { } void WindowsGLContext::Shutdown() { + if (renderManager_) + renderManager_->StopThread(); + glslang::FinalizeProcess(); +} + +void WindowsGLContext::ShutdownFromRenderThread() { delete draw_; draw_ = nullptr; CloseHandle(pauseEvent); @@ -383,26 +399,37 @@ void WindowsGLContext::Shutdown() { if (hRC) { // Are we able to release the DC and RC contexts? if (!wglMakeCurrent(NULL,NULL)) { - MessageBox(NULL,L"Release of DC and RC failed.", L"SHUTDOWN ERROR",MB_OK | MB_ICONINFORMATION); + MessageBox(NULL, L"Release of DC and RC failed.", L"SHUTDOWN ERROR", MB_OK | MB_ICONINFORMATION); } // Are we able to delete the RC? if (!wglDeleteContext(hRC)) { - MessageBox(NULL,L"Release rendering context failed.", L"SHUTDOWN ERROR",MB_OK | MB_ICONINFORMATION); + MessageBox(NULL, L"Release rendering context failed.", L"SHUTDOWN ERROR", MB_OK | MB_ICONINFORMATION); } hRC = NULL; } - if (hDC && !ReleaseDC(hWnd,hDC)) { + if (hDC && !ReleaseDC(hWnd_, hDC)) { DWORD err = GetLastError(); if (err != ERROR_DC_NOT_FOUND) { - MessageBox(NULL,L"Release device context failed.", L"SHUTDOWN ERROR",MB_OK | MB_ICONINFORMATION); + MessageBox(NULL, L"Release device context failed.", L"SHUTDOWN ERROR", MB_OK | MB_ICONINFORMATION); } hDC = NULL; } - hWnd = NULL; - glslang::FinalizeProcess(); + hWnd_ = NULL; } void WindowsGLContext::Resize() { } + +void WindowsGLContext::ThreadStart() { + renderManager_->ThreadStart(); +} + +bool WindowsGLContext::ThreadFrame() { + return renderManager_->ThreadFrame(); +} + +void WindowsGLContext::ThreadEnd() { + renderManager_->ThreadEnd(); +} diff --git a/Windows/GPU/WindowsGLContext.h b/Windows/GPU/WindowsGLContext.h index 3fe2f9f4a8..38c9494947 100644 --- a/Windows/GPU/WindowsGLContext.h +++ b/Windows/GPU/WindowsGLContext.h @@ -7,9 +7,15 @@ namespace Draw { class DrawContext; } +class GLRenderManager; + class WindowsGLContext : public WindowsGraphicsContext { public: bool Init(HINSTANCE hInst, HWND window, std::string *error_message) override; + + bool InitFromRenderThread(std::string *errorMessage) override; + void ShutdownFromRenderThread() override; + void Shutdown() override; void SwapInterval(int interval) override; void SwapBuffers() override; @@ -18,16 +24,22 @@ public: // not the rendering thread or CPU thread. void Pause() override; void Resume() override; - void Resize() override; + void ThreadStart() override; + void ThreadEnd() override; + bool ThreadFrame() override; + Draw::DrawContext *GetDrawContext() override { return draw_; } private: + bool renderThread_; Draw::DrawContext *draw_; + GLRenderManager *renderManager_; + HINSTANCE hInst_; HDC hDC; // Private GDI Device Context HGLRC hRC; // Permanent Rendering Context - HWND hWnd; // Holds Our Window Handle + HWND hWnd_; // Holds Our Window Handle volatile bool pauseRequested; volatile bool resumeRequested; HANDLE pauseEvent; diff --git a/Windows/MainWindow.cpp b/Windows/MainWindow.cpp index f56ade7bce..a3c45e3567 100644 --- a/Windows/MainWindow.cpp +++ b/Windows/MainWindow.cpp @@ -917,7 +917,7 @@ namespace MainWindow EmuThread_Stop(); coreState = CORE_POWERUP; ResetUIState(); - EmuThread_Start(); + EmuThread_Start(false); InputDevice::BeginPolling(); break; diff --git a/Windows/main.cpp b/Windows/main.cpp index 4d6a28c849..66c09110ba 100644 --- a/Windows/main.cpp +++ b/Windows/main.cpp @@ -529,8 +529,10 @@ int WINAPI WinMain(HINSTANCE _hInstance, HINSTANCE hPrevInstance, LPSTR szCmdLin MainWindow::Minimize(); } - // Emu thread is always running! - EmuThread_Start(); + // Emu thread (and render thread, if any) is always running! + // Only OpenGL uses an externally managed render thread (due to GL's single-threaded context design). Vulkan + // manages its own render thread. + EmuThread_Start(g_Config.iGPUBackend == (int)GPUBackend::OPENGL); InputDevice::BeginPolling(); HACCEL hAccelTable = LoadAccelerators(_hInstance, (LPCTSTR)IDR_ACCELS); diff --git a/android/jni/AndroidEGLContext.cpp b/android/jni/AndroidEGLContext.cpp index 117c431d80..68438cce21 100644 --- a/android/jni/AndroidEGLContext.cpp +++ b/android/jni/AndroidEGLContext.cpp @@ -7,13 +7,16 @@ #include "GL/GLInterface/EGLAndroid.h" #include "Core/System.h" -bool AndroidEGLGraphicsContext::Init(ANativeWindow *wnd, int backbufferWidth, int backbufferHeight, int backbufferFormat, int androidVersion) { +bool AndroidEGLGraphicsContext::InitFromRenderThread(ANativeWindow *wnd, int desiredBackbufferSizeX, int desiredBackbufferSizeY, int backbufferFormat, int androidVersion) { + ILOG("AndroidEGLGraphicsContext::Init()"); wnd_ = wnd; gl = HostGL_CreateGLInterface(); if (!gl) { ELOG("ERROR: Failed to create GL interface"); return false; } + int backbufferWidth = desiredBackbufferSizeX; + int backbufferHeight = desiredBackbufferSizeY; ILOG("EGL interface created. Desired backbuffer size: %dx%d", backbufferWidth, backbufferHeight); // Apparently we still have to set this through Java through setFixedSize on the bufferHolder for it to take effect... diff --git a/android/jni/AndroidEGLContext.h b/android/jni/AndroidEGLContext.h index efeafecf80..fd86bc6cfe 100644 --- a/android/jni/AndroidEGLContext.h +++ b/android/jni/AndroidEGLContext.h @@ -6,7 +6,7 @@ class AndroidEGLGraphicsContext : public AndroidGraphicsContext { public: AndroidEGLGraphicsContext() : draw_(nullptr), wnd_(nullptr), gl(nullptr) {} - bool Init(ANativeWindow *wnd, int desiredBackbufferSizeX, int desiredBackbufferSizeY, int backbufferFormat, int androidVersion) override; + bool InitFromRenderThread(ANativeWindow *wnd, int desiredBackbufferSizeX, int desiredBackbufferSizeY, int backbufferFormat, int androidVersion) override; void Shutdown() override; void SwapBuffers() override; void SwapInterval(int interval) override {} diff --git a/android/jni/AndroidGraphicsContext.h b/android/jni/AndroidGraphicsContext.h index 027be92505..589cddc97d 100644 --- a/android/jni/AndroidGraphicsContext.h +++ b/android/jni/AndroidGraphicsContext.h @@ -19,5 +19,7 @@ enum { class AndroidGraphicsContext : public GraphicsContext { public: - virtual bool Init(ANativeWindow *wnd, int desiredBackbufferSizeX, int desiredBackbufferSizeY, int backbufferFormat, int androidVersion) = 0; + // This is different than the base class function since on + // Android (EGL, Vulkan) we do have all this info on the render thread. + virtual bool InitFromRenderThread(ANativeWindow *wnd, int desiredBackbufferSizeX, int desiredBackbufferSizeY, int backbufferFormat, int androidVersion) = 0; }; diff --git a/android/jni/AndroidJavaGLContext.cpp b/android/jni/AndroidJavaGLContext.cpp index 751f9d7539..c201c9fb10 100644 --- a/android/jni/AndroidJavaGLContext.cpp +++ b/android/jni/AndroidJavaGLContext.cpp @@ -6,16 +6,25 @@ #include "Core/System.h" AndroidJavaEGLGraphicsContext::AndroidJavaEGLGraphicsContext() { + SetGPUBackend(GPUBackend::OPENGL); +} + +bool AndroidJavaEGLGraphicsContext::InitFromRenderThread(ANativeWindow *wnd, int desiredBackbufferSizeX, int desiredBackbufferSizeY, int backbufferFormat, int androidVersion) { + ILOG("AndroidJavaEGLGraphicsContext::InitFromRenderThread"); CheckGLExtensions(); draw_ = Draw::T3DCreateGLContext(); - SetGPUBackend(GPUBackend::OPENGL); + renderManager_ = (GLRenderManager *)draw_->GetNativeObject(Draw::NativeObject::RENDER_MANAGER); bool success = draw_->CreatePresets(); - assert(success); + return success; +} + +void AndroidJavaEGLGraphicsContext::ShutdownFromRenderThread() { + ILOG("AndroidJavaEGLGraphicsContext::Shutdown"); + renderManager_ = nullptr; // owned by draw_. + delete draw_; + draw_ = nullptr; } void AndroidJavaEGLGraphicsContext::Shutdown() { - ILOG("AndroidJavaEGLGraphicsContext::Shutdown"); - delete draw_; - draw_ = nullptr; - NativeShutdownGraphics(); + // TODO } diff --git a/android/jni/AndroidJavaGLContext.h b/android/jni/AndroidJavaGLContext.h index 36e443e51d..c6e11910e7 100644 --- a/android/jni/AndroidJavaGLContext.h +++ b/android/jni/AndroidJavaGLContext.h @@ -1,14 +1,25 @@ #pragma once +#include +#include +#include + #include "AndroidGraphicsContext.h" +#include "thin3d/GLRenderManager.h" // Doesn't do much. Just to fit in. -class AndroidJavaEGLGraphicsContext : public GraphicsContext { +class AndroidJavaEGLGraphicsContext : public AndroidGraphicsContext { public: AndroidJavaEGLGraphicsContext(); ~AndroidJavaEGLGraphicsContext() { delete draw_; } + + // This performs the actual initialization, + bool InitFromRenderThread(ANativeWindow *wnd, int desiredBackbufferSizeX, int desiredBackbufferSizeY, int backbufferFormat, int androidVersion) override; + + void ShutdownFromRenderThread() override; + void Shutdown() override; void SwapBuffers() override {} void SwapInterval(int interval) override {} @@ -16,7 +27,21 @@ public: Draw::DrawContext *GetDrawContext() override { return draw_; } + + void ThreadStart() override { + renderManager_->ThreadStart(); + } + + bool ThreadFrame() override { + return renderManager_->ThreadFrame(); + } + + void ThreadEnd() override { + renderManager_->ThreadEnd(); + } + private: - Draw::DrawContext *draw_; + Draw::DrawContext *draw_ = nullptr; + GLRenderManager *renderManager_ = nullptr; }; diff --git a/android/jni/AndroidVulkanContext.cpp b/android/jni/AndroidVulkanContext.cpp index 04373c874d..b067d7f533 100644 --- a/android/jni/AndroidVulkanContext.cpp +++ b/android/jni/AndroidVulkanContext.cpp @@ -60,7 +60,8 @@ static VKAPI_ATTR VkBool32 VKAPI_CALL Vulkan_Dbg(VkDebugReportFlagsEXT msgFlags, loglevel = ANDROID_LOG_WARN; } - __android_log_print(loglevel, APP_NAME, "[%s] %s Code %d : %s", pLayerPrefix, ObjTypeToString(objType), msgCode, pMsg); + __android_log_print(loglevel, APP_NAME, "[%s] %s Code %d : %s", + pLayerPrefix, ObjTypeToString(objType), msgCode, pMsg); // false indicates that layer should not bail-out of an // API call that had validation failures. This may mean that the @@ -75,7 +76,7 @@ AndroidVulkanContext::~AndroidVulkanContext() { g_Vulkan = nullptr; } -bool AndroidVulkanContext::Init(ANativeWindow *wnd, int desiredBackbufferSizeX, int desiredBackbufferSizeY, int backbufferFormat, int androidVersion) { +bool AndroidVulkanContext::InitFromRenderThread(ANativeWindow *wnd, int desiredBackbufferSizeX, int desiredBackbufferSizeY, int backbufferFormat, int androidVersion) { ILOG("AndroidVulkanContext::Init"); init_glslang(); diff --git a/android/jni/AndroidVulkanContext.h b/android/jni/AndroidVulkanContext.h index 8c48e09c92..e43041d3dc 100644 --- a/android/jni/AndroidVulkanContext.h +++ b/android/jni/AndroidVulkanContext.h @@ -11,7 +11,7 @@ public: AndroidVulkanContext() : draw_(nullptr) {} ~AndroidVulkanContext(); - bool Init(ANativeWindow *wnd, int desiredBackbufferSizeX, int desiredBackbufferSizeY, int backbufferFormat, int androidVersion) override; + bool InitFromRenderThread(ANativeWindow *wnd, int desiredBackbufferSizeX, int desiredBackbufferSizeY, int backbufferFormat, int androidVersion) override; void Shutdown() override; void SwapInterval(int interval) override; void SwapBuffers() override; diff --git a/android/jni/app-android.cpp b/android/jni/app-android.cpp index 05306c22be..2d0731d711 100644 --- a/android/jni/app-android.cpp +++ b/android/jni/app-android.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include #include @@ -50,9 +51,20 @@ #include "app-android.h" -JNIEnv *jniEnvMain; -JNIEnv *jniEnvGraphics; -JavaVM *javaVM; +bool useCPUThread = true; + +enum class EmuThreadState { + DISABLED, + START_REQUESTED, + RUNNING, + QUIT_REQUESTED, + STOPPED, +}; + +static std::thread emuThread; +static std::atomic emuThreadState((int)EmuThreadState::DISABLED); + +void UpdateRunLoopAndroid(JNIEnv *env); static AndroidAudioState *g_audioState; @@ -97,6 +109,12 @@ static int backbuffer_format; // Android PixelFormat enum static int desiredBackbufferSizeX; static int desiredBackbufferSizeY; +// Cache the class loader so we can use it from native threads. Required for TextAndroid. +JavaVM* gJvm = nullptr; +static jobject gClassLoader; +static jmethodID gFindClassMethod; + + static jmethodID postCommand; static jobject nativeActivity; static volatile bool exitRenderLoop; @@ -114,7 +132,66 @@ static bool javaGL = true; static std::string library_path; static std::map permissions; -GraphicsContext *graphicsContext; +AndroidGraphicsContext *graphicsContext; + +JNIEnv* getEnv() { + JNIEnv *env; + int status = gJvm->GetEnv((void**)&env, JNI_VERSION_1_6); + if(status < 0) { + status = gJvm->AttachCurrentThread(&env, NULL); + if(status < 0) { + return nullptr; + } + } + return env; +} + +jclass findClass(const char* name) { + return static_cast(getEnv()->CallObjectMethod(gClassLoader, gFindClassMethod, getEnv()->NewStringUTF(name))); +} + +JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *pjvm, void *reserved) { + ILOG("JNI_OnLoad"); + gJvm = pjvm; // cache the JavaVM pointer + auto env = getEnv(); + //replace with one of your classes in the line below + auto randomClass = env->FindClass("org/ppsspp/ppsspp/NativeActivity"); + jclass classClass = env->GetObjectClass(randomClass); + auto classLoaderClass = env->FindClass("java/lang/ClassLoader"); + auto getClassLoaderMethod = env->GetMethodID(classClass, "getClassLoader", + "()Ljava/lang/ClassLoader;"); + gClassLoader = env->NewGlobalRef(env->CallObjectMethod(randomClass, getClassLoaderMethod)); + gFindClassMethod = env->GetMethodID(classLoaderClass, "findClass", + "(Ljava/lang/String;)Ljava/lang/Class;"); + return JNI_VERSION_1_6; +} + +static void EmuThreadFunc() { + JNIEnv *env; + gJvm->AttachCurrentThread(&env, nullptr); + + setCurrentThreadName("Emu"); + + // There's no real requirement that NativeInit happen on this thread. + // We just call the update/render loop here. + emuThreadState = (int)EmuThreadState::RUNNING; + while (emuThreadState != (int)EmuThreadState::QUIT_REQUESTED) { + UpdateRunLoopAndroid(env); + } + emuThreadState = (int)EmuThreadState::STOPPED; + gJvm->DetachCurrentThread(); +} + +static void EmuThreadStart(JNIEnv *env) { + emuThreadState = (int)EmuThreadState::START_REQUESTED; + emuThread = std::thread(&EmuThreadFunc); +} + +static void EmuThreadStop() { + emuThreadState = (int)EmuThreadState::QUIT_REQUESTED; + emuThread.join(); + emuThread = std::thread(); +} static void ProcessFrameCommands(JNIEnv *env); @@ -225,7 +302,6 @@ std::string GetJavaString(JNIEnv *env, jstring jstr) { extern "C" void Java_org_ppsspp_ppsspp_NativeActivity_registerCallbacks(JNIEnv *env, jobject obj) { nativeActivity = env->NewGlobalRef(obj); postCommand = env->GetMethodID(env->GetObjectClass(obj), "postCommand", "(Ljava/lang/String;Ljava/lang/String;)V"); - ILOG("Got method ID to postCommand: %p", postCommand); } extern "C" void Java_org_ppsspp_ppsspp_NativeActivity_unregisterCallbacks(JNIEnv *env, jobject obj) { @@ -265,9 +341,6 @@ extern "C" void Java_org_ppsspp_ppsspp_NativeApp_init (JNIEnv *env, jclass, jstring jmodel, jint jdeviceType, jstring jlangRegion, jstring japkpath, jstring jdataDir, jstring jexternalDir, jstring jlibraryDir, jstring jcacheDir, jstring jshortcutParam, jint jAndroidVersion, jstring jboard) { - jniEnvMain = env; - env->GetJavaVM(&javaVM); - setCurrentThreadName("androidInit"); // Makes sure we get early permission grants. @@ -323,8 +396,7 @@ extern "C" void Java_org_ppsspp_ppsspp_NativeApp_init if (shortcut_param.empty()) { const char *argv[2] = {app_name.c_str(), 0}; NativeInit(1, argv, user_data_path.c_str(), externalDir.c_str(), cacheDir.c_str()); - } - else { + } else { const char *argv[3] = {app_name.c_str(), shortcut_param.c_str(), 0}; NativeInit(2, argv, user_data_path.c_str(), externalDir.c_str(), cacheDir.c_str()); } @@ -332,6 +404,33 @@ extern "C" void Java_org_ppsspp_ppsspp_NativeApp_init // Now that we've loaded config, set javaGL. javaGL = NativeQueryConfig("androidJavaGL") == "true"; +retry: + switch (g_Config.iGPUBackend) { + case (int)GPUBackend::OPENGL: + ILOG("NativeApp.init() -- creating OpenGL context"); + useCPUThread = true; + if (javaGL) { + graphicsContext = new AndroidJavaEGLGraphicsContext(); + } else { + graphicsContext = new AndroidEGLGraphicsContext(); + } + break; + case (int)GPUBackend::VULKAN: + ILOG("NativeApp.init() -- creating Vulkan context"); + useCPUThread = false; // The Vulkan render manager manages its own thread. + graphicsContext = new AndroidVulkanContext(); + break; + default: + ELOG("NativeApp.init(): iGPUBackend %d not supported. Switching to OpenGL.", (int)g_Config.iGPUBackend); + g_Config.iGPUBackend = (int)GPUBackend::OPENGL; + goto retry; + // Crash(); + } + + if (useCPUThread) { + ILOG("NativeApp.init() - launching emu thread"); + EmuThreadStart(env); + } ILOG("NativeApp.init() -- end"); } @@ -381,9 +480,13 @@ extern "C" void Java_org_ppsspp_ppsspp_NativeApp_pause(JNIEnv *, jclass) { } extern "C" void Java_org_ppsspp_ppsspp_NativeApp_shutdown(JNIEnv *, jclass) { + if (useCPUThread) + EmuThreadStop(); + ILOG("NativeApp.shutdown() -- begin"); if (renderer_inited) { ILOG("Shutting down renderer"); + // This will be from the wrong thread? :/ graphicsContext->Shutdown(); delete graphicsContext; graphicsContext = nullptr; @@ -401,32 +504,22 @@ extern "C" void Java_org_ppsspp_ppsspp_NativeApp_shutdown(JNIEnv *, jclass) { // JavaEGL extern "C" void Java_org_ppsspp_ppsspp_NativeRenderer_displayInit(JNIEnv * env, jobject obj) { - // Need to get the local JNI env for the graphics thread. Used later in draw_text_android. - int res = javaVM->GetEnv((void **)&jniEnvGraphics, JNI_VERSION_1_6); - if (res != JNI_OK) { - ELOG("GetEnv failed: %d", res); - } - - if (javaGL && !graphicsContext) { - graphicsContext = new AndroidJavaEGLGraphicsContext(); - } else if (!graphicsContext) { - _assert_msg_(G3D, false, "No graphics context in displayInit?"); - } - + // We should be running on the render thread here. + std::string errorMessage; if (renderer_inited) { ILOG("NativeApp.displayInit() restoring"); - NativeShutdownGraphics(); - delete graphicsContext; + graphicsContext->ThreadEnd(); + graphicsContext->ShutdownFromRenderThread(); - graphicsContext = new AndroidJavaEGLGraphicsContext(); - NativeInitGraphics(graphicsContext); + graphicsContext->InitFromRenderThread(nullptr, 0, 0, 0, 0); + graphicsContext->ThreadStart(); ILOG("Restored."); } else { ILOG("NativeApp.displayInit() first time"); - NativeInitGraphics(graphicsContext); + graphicsContext->InitFromRenderThread(nullptr, 0, 0, 0, 0); + graphicsContext->ThreadStart(); renderer_inited = true; } - NativeMessageReceived("recreateviews", ""); } @@ -470,29 +563,20 @@ extern "C" void JNICALL Java_org_ppsspp_ppsspp_NativeApp_backbufferResize(JNIEnv } } - -// JavaEGL -extern "C" void Java_org_ppsspp_ppsspp_NativeRenderer_displayRender(JNIEnv *env, jobject obj) { - static bool hasSetThreadName = false; - if (!hasSetThreadName) { - hasSetThreadName = true; - setCurrentThreadName("AndroidRender"); +void UpdateRunLoopAndroid(JNIEnv *env) { + // Wait for render loop to get started. + if (!renderer_inited) { + ILOG("Runloop: Waiting for displayInit"); + while (!renderer_inited) { + sleep_ms(20); + } + NativeInitGraphics(graphicsContext); } - if (renderer_inited) { - NativeUpdate(); + NativeUpdate(); - NativeRender(graphicsContext); - time_update(); - } else { - ELOG("BAD: Ended up in nativeRender even though app has quit.%s", ""); - // Shouldn't really get here. Let's draw magenta. - // TODO: Should we have GL here? - glDepthMask(GL_TRUE); - glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); - glClearColor(1.0, 0.0, 1.0f, 1.0f); - glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); - } + NativeRender(graphicsContext); + time_update(); std::lock_guard guard(frameCommandLock); if (!nativeActivity) { @@ -504,6 +588,21 @@ extern "C" void Java_org_ppsspp_ppsspp_NativeRenderer_displayRender(JNIEnv *env, ProcessFrameCommands(env); } +extern "C" void Java_org_ppsspp_ppsspp_NativeRenderer_displayRender(JNIEnv *env, jobject obj) { + static bool hasSetThreadName = false; + if (!hasSetThreadName) { + hasSetThreadName = true; + setCurrentThreadName("AndroidRender"); + } + + if (useCPUThread) { + // This is the "GPU thread". + graphicsContext->ThreadFrame(); + } else { + UpdateRunLoopAndroid(env); + } +} + void System_AskForPermission(SystemPermission permission) { switch (permission) { case SYSTEM_PERMISSION_STORAGE: @@ -768,18 +867,14 @@ static void ProcessFrameCommands(JNIEnv *env) { } extern "C" bool JNICALL Java_org_ppsspp_ppsspp_NativeActivity_runEGLRenderLoop(JNIEnv *env, jobject obj, jobject _surf) { + // Needed for Vulkan, even if we're not using the old EGL path. + exitRenderLoop = false; // This is up here to prevent race conditions, in case we pause during init. renderLoopRunning = true; ANativeWindow *wnd = ANativeWindow_fromSurface(env, _surf); - // Need to get the local JNI env for the graphics thread. Used later in draw_text_android. - int res = javaVM->GetEnv((void **)&jniEnvGraphics, JNI_VERSION_1_6); - if (res != JNI_OK) { - ELOG("GetEnv failed: %d", res); - } - WLOG("runEGLRenderLoop. display_xres=%d display_yres=%d", display_xres, display_yres); if (wnd == nullptr) { @@ -793,14 +888,8 @@ retry: bool vulkan = g_Config.iGPUBackend == (int)GPUBackend::VULKAN; int tries = 0; - AndroidGraphicsContext *graphicsContext; - if (vulkan) { - graphicsContext = new AndroidVulkanContext(); - } else { - graphicsContext = new AndroidEGLGraphicsContext(); - } - if (!graphicsContext->Init(wnd, desiredBackbufferSizeX, desiredBackbufferSizeY, backbuffer_format, androidVersion)) { + if (!graphicsContext->InitFromRenderThread(wnd, desiredBackbufferSizeX, desiredBackbufferSizeY, backbuffer_format, androidVersion)) { ELOG("Failed to initialize graphics context."); if (!exitRenderLoop && (vulkan && tries < 2)) { @@ -850,7 +939,6 @@ retry: graphicsContext = nullptr; renderLoopRunning = false; WLOG("Render loop function exited."); - jniEnvGraphics = nullptr; return true; } diff --git a/android/jni/app-android.h b/android/jni/app-android.h index 04a849b1bc..49d0dab435 100644 --- a/android/jni/app-android.h +++ b/android/jni/app-android.h @@ -6,8 +6,7 @@ #include -extern JNIEnv *jniEnvMain; -extern JNIEnv *jniEnvGraphics; -extern JavaVM *javaVM; +jclass findClass(const char* name); +JNIEnv* getEnv(); #endif diff --git a/android/src/org/ppsspp/ppsspp/NativeSurfaceView.java b/android/src/org/ppsspp/ppsspp/NativeSurfaceView.java index 2d53328573..1e13dba115 100644 --- a/android/src/org/ppsspp/ppsspp/NativeSurfaceView.java +++ b/android/src/org/ppsspp/ppsspp/NativeSurfaceView.java @@ -47,7 +47,7 @@ public class NativeSurfaceView extends SurfaceView implements SensorEventListene Log.i(TAG, "MOGA initialized"); mController.setListener(this, new Handler()); } catch (Exception e) { - Log.i(TAG, "Moga failed to initialize"); + // Ignore. } } diff --git a/ext/native/Android.mk b/ext/native/Android.mk index 5656512d81..78a75db9d4 100644 --- a/ext/native/Android.mk +++ b/ext/native/Android.mk @@ -75,7 +75,6 @@ LOCAL_SRC_FILES :=\ gfx_es2/draw_buffer.cpp.arm \ gfx_es2/draw_text.cpp.arm \ gfx_es2/draw_text_android.cpp.arm \ - gfx/GLStateCache.cpp.arm \ gfx/gl_debug_log.cpp \ gfx/texture_atlas.cpp \ image/zim_load.cpp \ @@ -84,8 +83,11 @@ LOCAL_SRC_FILES :=\ thin3d/thin3d.cpp \ thin3d/thin3d_gl.cpp \ thin3d/thin3d_vulkan.cpp \ + thin3d/GLRenderManager.cpp \ + thin3d/GLQueueRunner.cpp \ thin3d/VulkanRenderManager.cpp \ thin3d/VulkanQueueRunner.cpp \ + thin3d/DataFormatGL.cpp \ ui/view.cpp \ ui/viewgroup.cpp \ ui/ui.cpp \ diff --git a/ext/native/gfx/GLStateCache.cpp b/ext/native/gfx/GLStateCache.cpp deleted file mode 100644 index b4edffc1aa..0000000000 --- a/ext/native/gfx/GLStateCache.cpp +++ /dev/null @@ -1,52 +0,0 @@ -#include - -#include "base/logging.h" - -#include "gfx/GLStateCache.h" - -OpenGLState glstate; - -int OpenGLState::state_count = 0; - -void OpenGLState::Restore() { - int count = 0; - - blend.restore(); count++; - blendEquationSeparate.restore(); count++; - blendFuncSeparate.restore(); count++; - blendColor.restore(); count++; - - scissorTest.restore(); count++; - scissorRect.restore(); count++; - - cullFace.restore(); count++; - cullFaceMode.restore(); count++; - frontFace.restore(); count++; - - depthTest.restore(); count++; - depthRange.restore(); count++; - depthFunc.restore(); count++; - depthWrite.restore(); count++; - - colorMask.restore(); count++; - viewport.restore(); count++; - - stencilTest.restore(); count++; - stencilOp.restore(); count++; - stencilFunc.restore(); count++; - stencilMask.restore(); count++; - - dither.restore(); count++; - -#if !defined(USING_GLES2) - colorLogicOp.restore(); count++; - logicOp.restore(); count++; -#endif - - arrayBuffer.restore(); count++; - elementArrayBuffer.restore(); count++; - - if (count != state_count) { - FLOG("OpenGLState::Restore is missing some states"); - } -} diff --git a/ext/native/gfx/GLStateCache.h b/ext/native/gfx/GLStateCache.h deleted file mode 100644 index e46133709f..0000000000 --- a/ext/native/gfx/GLStateCache.h +++ /dev/null @@ -1,283 +0,0 @@ -#pragma once - -#include -#include // for memcmp - -#include "gfx/gl_common.h" -#include "gfx_es2/gpu_features.h" - -// OpenGL state cache. -// Probably only really worth it in rendering cores on weak mobile hardware. -class OpenGLState { -private: - template - class BoolState { - private: - bool v; - public: - BoolState() : v(init) { - OpenGLState::state_count++; - } - - inline void set(bool value) { - if (value != v) { - v = value; - restore(); - } - } - inline void force(bool value) { - bool old = v; - set(value); - v = old; - } - inline void enable() { - set(true); - } - inline void disable() { - set(false); - } - operator bool() const { - return isset(); - } - inline bool isset() { - return v; - } - void restore() { - if (v) - glEnable(cap); - else - glDisable(cap); - } - }; - -#define STATE1(func, p1type, p1def) \ - class SavedState1_##func { \ - p1type p1; \ - public: \ - SavedState1_##func() : p1(p1def) { \ - OpenGLState::state_count++; \ - } \ - void set(p1type newp1) { \ - if (newp1 != p1) { \ - p1 = newp1; \ - restore(); \ - } \ - } \ - void force(p1type newp1) { \ - p1type old = p1; \ - set(newp1); \ - p1 = old; \ - } \ - inline void restore() { \ - func(p1); \ - } \ - } - -#define STATE2(func, p1type, p2type, p1def, p2def) \ - class SavedState2_##func { \ - p1type p1; \ - p2type p2; \ - public: \ - SavedState2_##func() : p1(p1def), p2(p2def) { \ - OpenGLState::state_count++; \ - } \ - inline void set(p1type newp1, p2type newp2) { \ - if (newp1 != p1 || newp2 != p2) { \ - p1 = newp1; \ - p2 = newp2; \ - restore(); \ - } \ - } \ - void force(p1type newp1, p2type newp2) { \ - p1type old1 = p1; \ - p2type old2 = p2; \ - set(newp1, newp2); \ - p1 = old1; \ - p2 = old2; \ - } \ - inline void restore() { \ - func(p1, p2); \ - } \ - } - -#define STATE3(func, p1type, p2type, p3type, p1def, p2def, p3def) \ - class SavedState3_##func { \ - p1type p1; \ - p2type p2; \ - p3type p3; \ - public: \ - SavedState3_##func() : p1(p1def), p2(p2def), p3(p3def) { \ - OpenGLState::state_count++; \ - } \ - inline void set(p1type newp1, p2type newp2, p3type newp3) { \ - if (newp1 != p1 || newp2 != p2 || newp3 != p3) { \ - p1 = newp1; \ - p2 = newp2; \ - p3 = newp3; \ - restore(); \ - } \ - } \ - void force(p1type newp1, p2type newp2, p3type newp3) { \ - p1type old1 = p1; \ - p2type old2 = p2; \ - p3type old3 = p3; \ - set(newp1, newp2, newp3); \ - p1 = old1; \ - p2 = old2; \ - p3 = old3; \ - } \ - inline void restore() { \ - func(p1, p2, p3); \ - } \ - } - - #define STATE4(func, p1type, p2type, p3type, p4type, p1def, p2def, p3def, p4def) \ - class SavedState4_##func { \ - p1type p1; \ - p2type p2; \ - p3type p3; \ - p4type p4; \ - public: \ - SavedState4_##func() : p1(p1def), p2(p2def), p3(p3def), p4(p4def) { \ - OpenGLState::state_count++; \ - } \ - inline void set(p1type newp1, p2type newp2, p3type newp3, p4type newp4) { \ - if (newp1 != p1 || newp2 != p2 || newp3 != p3 || newp4 != p4) { \ - p1 = newp1; \ - p2 = newp2; \ - p3 = newp3; \ - p4 = newp4; \ - restore(); \ - } \ - } \ - void force(p1type newp1, p2type newp2, p3type newp3, p4type newp4) { \ - p1type old1 = p1; \ - p2type old2 = p2; \ - p3type old3 = p3; \ - p4type old4 = p4; \ - set(newp1, newp2, newp3, newp4); \ - p1 = old1; \ - p2 = old2; \ - p3 = old3; \ - p4 = old4; \ - } \ - inline void restore() { \ - func(p1, p2, p3, p4); \ - } \ - } - -#define STATEFLOAT4(func, def) \ - class SavedState4_##func { \ - float p[4]; \ - public: \ - SavedState4_##func() { \ - for (int i = 0; i < 4; i++) {p[i] = def;} \ - OpenGLState::state_count++; \ - } \ - inline void set(const float v[4]) { \ - if (memcmp(p, v, sizeof(float) * 4)) { \ - memcpy(p, v, sizeof(float) * 4); \ - restore(); \ - } \ - } \ - void force(const float v[4]) { \ - float old[4]; \ - memcpy(old, p, sizeof(float) * 4); \ - set(v); \ - memcpy(p, old, sizeof(float) * 4); \ - } \ - inline void restore() { \ - func(p[0], p[1], p[2], p[3]); \ - } \ - } - -#define STATEBIND(func, target) \ - class SavedBind_##func_##target { \ - GLuint val_; \ - public: \ - SavedBind_##func_##target() { \ - val_ = 0; \ - OpenGLState::state_count++; \ - } \ - inline void bind(GLuint val) { \ - if (val_ != val) { \ - val_ = val; \ - restore(); \ - } \ - } \ - inline void force(GLuint val) { \ - GLuint old = val_; \ - bind(val); \ - val_ = old; \ - } \ - inline void unbind() { \ - bind(0); \ - } \ - inline void restore() { \ - func(target, val_); \ - } \ - } - -public: - static int state_count; - OpenGLState() {} - void Restore(); - - // When adding a state here, don't forget to add it to OpenGLState::Restore() too - - // Blending - BoolState blend; - STATE4(glBlendFuncSeparate, GLenum, GLenum, GLenum, GLenum, GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) blendFuncSeparate; - - // On OpenGL ES, using minmax blend requires glBlendEquationEXT (in theory at least but I don't think it's true in practice) - STATE2(glBlendEquationSeparate, GLenum, GLenum, GL_FUNC_ADD, GL_FUNC_ADD) blendEquationSeparate; - STATEFLOAT4(glBlendColor, 1.0f) blendColor; - - // Logic Ops. Not available on OpenGL ES at all. -#if !defined(USING_GLES2) - BoolState colorLogicOp; - STATE1(glLogicOp, GLenum, GL_COPY) logicOp; -#endif - - // Dither - BoolState dither; - - // Cull Face - BoolState cullFace; - STATE1(glCullFace, GLenum, GL_FRONT) cullFaceMode; - STATE1(glFrontFace, GLenum, GL_CCW) frontFace; - - // Depth Test - BoolState depthTest; -#if defined(USING_GLES2) - STATE2(glDepthRangef, float, float, 0.f, 1.f) depthRange; -#else - STATE2(glDepthRange, double, double, 0.0, 1.0) depthRange; -#endif - STATE1(glDepthFunc, GLenum, GL_LESS) depthFunc; - STATE1(glDepthMask, GLboolean, GL_TRUE) depthWrite; - - // Color Mask - STATE4(glColorMask, bool, bool, bool, bool, true, true, true, true) colorMask; - - // Viewport - STATE4(glViewport, GLint, GLint, GLsizei, GLsizei, 0, 0, 128, 128) viewport; - - // Scissor Test - BoolState scissorTest; - STATE4(glScissor, GLint, GLint, GLsizei, GLsizei, 0, 0, 128, 128) scissorRect; - - // Stencil Test - BoolState stencilTest; - STATE3(glStencilOp, GLenum, GLenum, GLenum, GL_KEEP, GL_KEEP, GL_KEEP) stencilOp; - STATE3(glStencilFunc, GLenum, GLint, GLuint, GL_ALWAYS, 0, 0xFF) stencilFunc; - STATE1(glStencilMask, GLuint, 0xFF) stencilMask; - - STATEBIND(glBindBuffer, GL_ARRAY_BUFFER) arrayBuffer; - STATEBIND(glBindBuffer, GL_ELEMENT_ARRAY_BUFFER) elementArrayBuffer; -}; - -#undef STATE1 -#undef STATE2 - -extern OpenGLState glstate; diff --git a/ext/native/gfx_es2/draw_text_android.cpp b/ext/native/gfx_es2/draw_text_android.cpp index 68138ee9a4..1bc3bc6639 100644 --- a/ext/native/gfx_es2/draw_text_android.cpp +++ b/ext/native/gfx_es2/draw_text_android.cpp @@ -16,9 +16,9 @@ #include TextDrawerAndroid::TextDrawerAndroid(Draw::DrawContext *draw) : TextDrawer(draw) { - env_ = jniEnvGraphics; + env_ = getEnv(); const char *textRendererClassName = "org/ppsspp/ppsspp/TextRenderer"; - jclass localClass = env_->FindClass(textRendererClassName); + jclass localClass = findClass(textRendererClassName); cls_textRenderer = reinterpret_cast(env_->NewGlobalRef(localClass)); ILOG("cls_textRender: %p", cls_textRenderer); if (cls_textRenderer) { diff --git a/ext/native/gfx_es2/gpu_features.cpp b/ext/native/gfx_es2/gpu_features.cpp index c758645fc9..69ffa7c897 100644 --- a/ext/native/gfx_es2/gpu_features.cpp +++ b/ext/native/gfx_es2/gpu_features.cpp @@ -393,6 +393,8 @@ void CheckGLExtensions() { } #endif + glGetIntegerv(GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS, &gl_extensions.maxVertexTextureUnits); + // This is probably a waste of time, implementations lie. if (gl_extensions.IsGLES || strstr(extString, "GL_ARB_ES2_compatibility") || gl_extensions.VersionGEThan(4, 1)) { const GLint precisions[6] = { diff --git a/ext/native/gfx_es2/gpu_features.h b/ext/native/gfx_es2/gpu_features.h index 2f405546b8..b12b406eee 100644 --- a/ext/native/gfx_es2/gpu_features.h +++ b/ext/native/gfx_es2/gpu_features.h @@ -96,6 +96,8 @@ struct GLExtensions { int range[2][6][2]; // [vs,fs][lowf,mediumf,highf,lowi,mediumi,highi][min,max] int precision[2][6]; // [vs,fs][lowf...] + int maxVertexTextureUnits; + // greater-or-equal than bool VersionGEThan(int major, int minor, int sub = 0); }; diff --git a/ext/native/native.vcxproj b/ext/native/native.vcxproj index 9ccbc1e644..ebb1517ea2 100644 --- a/ext/native/native.vcxproj +++ b/ext/native/native.vcxproj @@ -232,7 +232,6 @@ - @@ -241,6 +240,9 @@ + + + @@ -691,7 +693,6 @@ - @@ -699,6 +700,9 @@ + + + diff --git a/ext/native/native.vcxproj.filters b/ext/native/native.vcxproj.filters index bec0f42cee..e0507e183a 100644 --- a/ext/native/native.vcxproj.filters +++ b/ext/native/native.vcxproj.filters @@ -302,9 +302,6 @@ gfx - - gfx - thin3d @@ -332,6 +329,15 @@ base + + thin3d + + + thin3d + + + thin3d + @@ -769,9 +775,6 @@ gfx - - gfx - thin3d @@ -799,6 +802,15 @@ ui + + thin3d + + + thin3d + + + thin3d + @@ -871,4 +883,4 @@ {06c6305a-a646-485b-85b9-645a24dd6553} - + \ No newline at end of file diff --git a/ext/native/thin3d/DataFormatGL.cpp b/ext/native/thin3d/DataFormatGL.cpp new file mode 100644 index 0000000000..5d48f15868 --- /dev/null +++ b/ext/native/thin3d/DataFormatGL.cpp @@ -0,0 +1,89 @@ +#include "thin3d/DataFormatGL.h" +#include "base/logging.h" +#include "Common/Log.h" + +namespace Draw { + +// TODO: Also output storage format (GL_RGBA8 etc) for modern GL usage. +bool Thin3DFormatToFormatAndType(DataFormat fmt, GLuint &internalFormat, GLuint &format, GLuint &type, int &alignment) { + alignment = 4; + switch (fmt) { + case DataFormat::R8G8B8A8_UNORM: + internalFormat = GL_RGBA; + format = GL_RGBA; + type = GL_UNSIGNED_BYTE; + break; + + case DataFormat::D32F: + internalFormat = GL_DEPTH_COMPONENT; + format = GL_DEPTH_COMPONENT; + type = GL_FLOAT; + break; + +#ifndef USING_GLES2 + case DataFormat::S8: + internalFormat = GL_STENCIL_INDEX; + format = GL_STENCIL_INDEX; + type = GL_UNSIGNED_BYTE; + alignment = 1; + break; +#endif + + case DataFormat::R8G8B8_UNORM: + internalFormat = GL_RGB; + format = GL_RGB; + type = GL_UNSIGNED_BYTE; + alignment = 1; + break; + + case DataFormat::B4G4R4A4_UNORM_PACK16: + internalFormat = GL_RGBA; + format = GL_RGBA; + type = GL_UNSIGNED_SHORT_4_4_4_4; + alignment = 2; + break; + + case DataFormat::B5G6R5_UNORM_PACK16: + internalFormat = GL_RGB; + format = GL_RGB; + type = GL_UNSIGNED_SHORT_5_6_5; + alignment = 2; + break; + + case DataFormat::B5G5R5A1_UNORM_PACK16: + internalFormat = GL_RGBA; + format = GL_RGBA; + type = GL_UNSIGNED_SHORT_5_5_5_1; + alignment = 2; + break; + +#ifndef USING_GLES2 + case DataFormat::A4R4G4B4_UNORM_PACK16: + internalFormat = GL_RGBA; + format = GL_RGBA; + type = GL_UNSIGNED_SHORT_4_4_4_4_REV; + alignment = 2; + break; + + case DataFormat::R5G6B5_UNORM_PACK16: + internalFormat = GL_RGB; + format = GL_RGB; + type = GL_UNSIGNED_SHORT_5_6_5_REV; + alignment = 2; + break; + + case DataFormat::A1R5G5B5_UNORM_PACK16: + internalFormat = GL_RGBA; + format = GL_RGBA; + type = GL_UNSIGNED_SHORT_1_5_5_5_REV; + alignment = 2; + break; +#endif + + default: + return false; + } + return true; +} + +} diff --git a/ext/native/thin3d/DataFormatGL.h b/ext/native/thin3d/DataFormatGL.h new file mode 100644 index 0000000000..28b48f60ff --- /dev/null +++ b/ext/native/thin3d/DataFormatGL.h @@ -0,0 +1,10 @@ +#pragma once + +#include "gfx/gl_common.h" +#include "thin3d/DataFormat.h" + +namespace Draw { + +bool Thin3DFormatToFormatAndType(DataFormat fmt, GLuint &internalFormat, GLuint &format, GLuint &type, int &alignment); + +} diff --git a/ext/native/thin3d/GLQueueRunner.cpp b/ext/native/thin3d/GLQueueRunner.cpp new file mode 100644 index 0000000000..9fd394c8be --- /dev/null +++ b/ext/native/thin3d/GLQueueRunner.cpp @@ -0,0 +1,1299 @@ +#include "Core/Reporting.h" +#include "GLQueueRunner.h" +#include "GLRenderManager.h" +#include "DataFormatGL.h" +#include "base/logging.h" +#include "gfx/gl_common.h" +#include "gfx/gl_debug_log.h" +#include "gfx_es2/gpu_features.h" +#include "math/dataconv.h" + +#define TEXCACHE_NAME_CACHE_SIZE 16 + +#ifdef IOS +extern void bindDefaultFBO(); +#endif + +// Workaround for Retroarch. Simply declare +// extern GLuint g_defaultFBO; +// and set is as appropriate. Can adjust the variables in ext/native/base/display.h as +// appropriate. +GLuint g_defaultFBO = 0; + +void GLQueueRunner::CreateDeviceObjects() { + glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &maxAnisotropyLevel_); + if (gl_extensions.ARB_vertex_array_object) { + glGenVertexArrays(1, &globalVAO_); + } + + // An eternal optimist. + sawOutOfMemory_ = false; + + // Populate some strings from the GL thread. + auto populate = [&](int name) { + const GLubyte *value = glGetString(name); + if (!value) + glStrings_[name] = "?"; + else + glStrings_[name] = (const char *)value; + }; + populate(GL_VENDOR); + populate(GL_RENDERER); + populate(GL_VERSION); + populate(GL_SHADING_LANGUAGE_VERSION); + populate(GL_EXTENSIONS); // TODO: Not OK to query this in core profile! +} + +void GLQueueRunner::DestroyDeviceObjects() { + if (!nameCache_.empty()) { + glDeleteTextures((GLsizei)nameCache_.size(), &nameCache_[0]); + nameCache_.clear(); + } + if (gl_extensions.ARB_vertex_array_object) { + glDeleteVertexArrays(1, &globalVAO_); + } + delete[] readbackBuffer_; + readbackBufferSize_ = 0; + delete[] tempBuffer_; + tempBufferSize_ = 0; +} + +void GLQueueRunner::RunInitSteps(const std::vector &steps) { + glActiveTexture(GL_TEXTURE0); + GLuint boundTexture = (GLuint)-1; + bool allocatedTextures = false; + + for (int i = 0; i < steps.size(); i++) { + const GLRInitStep &step = steps[i]; + switch (step.stepType) { + case GLRInitStepType::CREATE_TEXTURE: + { + GLRTexture *tex = step.create_texture.texture; + glGenTextures(1, &tex->texture); + glBindTexture(tex->target, tex->texture); + boundTexture = tex->texture; + break; + } + case GLRInitStepType::CREATE_BUFFER: + { + GLRBuffer *buffer = step.create_buffer.buffer; + glGenBuffers(1, &buffer->buffer); + glBindBuffer(buffer->target_, buffer->buffer); + glBufferData(buffer->target_, step.create_buffer.size, nullptr, step.create_buffer.usage); + break; + } + case GLRInitStepType::BUFFER_SUBDATA: + { + GLRBuffer *buffer = step.buffer_subdata.buffer; + glBindBuffer(GL_ARRAY_BUFFER, buffer->buffer); + glBufferSubData(GL_ARRAY_BUFFER, step.buffer_subdata.offset, step.buffer_subdata.size, step.buffer_subdata.data); + if (step.buffer_subdata.deleteData) + delete[] step.buffer_subdata.data; + break; + } + case GLRInitStepType::CREATE_PROGRAM: + { + GLRProgram *program = step.create_program.program; + program->program = glCreateProgram(); + _assert_msg_(G3D, step.create_program.num_shaders > 0, "Can't create a program with zero shaders"); + for (int i = 0; i < step.create_program.num_shaders; i++) { + _dbg_assert_msg_(G3D, step.create_program.shaders[i]->shader, "Can't create a program with a null shader"); + glAttachShader(program->program, step.create_program.shaders[i]->shader); + } + + for (auto iter : program->semantics_) { + glBindAttribLocation(program->program, iter.location, iter.attrib); + } + +#if !defined(USING_GLES2) + if (step.create_program.support_dual_source) { + // Dual source alpha + glBindFragDataLocationIndexed(program->program, 0, 0, "fragColor0"); + glBindFragDataLocationIndexed(program->program, 0, 1, "fragColor1"); + } else if (gl_extensions.VersionGEThan(3, 3, 0)) { + glBindFragDataLocation(program->program, 0, "fragColor0"); + } +#elif !defined(IOS) + if (gl_extensions.GLES3 && step.create_program.support_dual_source) { + glBindFragDataLocationIndexedEXT(program->program, 0, 0, "fragColor0"); + glBindFragDataLocationIndexedEXT(program->program, 0, 1, "fragColor1"); + } +#endif + glLinkProgram(program->program); + + GLint linkStatus = GL_FALSE; + glGetProgramiv(program->program, GL_LINK_STATUS, &linkStatus); + if (linkStatus != GL_TRUE) { + GLint bufLength = 0; + glGetProgramiv(program->program, GL_INFO_LOG_LENGTH, &bufLength); + if (bufLength) { + char *buf = new char[bufLength]; + glGetProgramInfoLog(program->program, bufLength, nullptr, buf); + + // TODO: Could be other than vs/fs. Also, we're assuming order here... + const char *vsDesc = step.create_program.shaders[0]->desc.c_str(); + const char *fsDesc = step.create_program.num_shaders > 1 ? step.create_program.shaders[1]->desc.c_str() : nullptr; + const char *vsCode = step.create_program.shaders[0]->code.c_str(); + const char *fsCode = step.create_program.num_shaders > 1 ? step.create_program.shaders[1]->code.c_str() : nullptr; + Reporting::ReportMessage("Error in shader program link: info: %s\nfs: %s\n%s\nvs: %s\n%s", buf, fsDesc, fsCode, vsDesc, vsCode); + + ELOG("Could not link program:\n %s", buf); + ERROR_LOG(G3D, "VS desc:\n%s", vsDesc); + ERROR_LOG(G3D, "FS desc:\n%s", fsDesc); + ERROR_LOG(G3D, "VS:\n%s\n", vsCode); + ERROR_LOG(G3D, "FS:\n%s\n", fsCode); + +#ifdef _WIN32 + OutputDebugStringUTF8(buf); + OutputDebugStringUTF8(vsCode); + if (fsCode) + OutputDebugStringUTF8(fsCode); +#endif + delete[] buf; + } else { + ELOG("Could not link program with %d shaders for unknown reason:", step.create_program.num_shaders); + } + break; + } + + glUseProgram(program->program); + + // Query all the uniforms. + for (int i = 0; i < program->queries_.size(); i++) { + auto &x = program->queries_[i]; + assert(x.name); + *x.dest = glGetUniformLocation(program->program, x.name); + } + + // Run initializers. + for (int i = 0; i < program->initialize_.size(); i++) { + auto &init = program->initialize_[i]; + GLint uniform = *init.uniform; + if (uniform != -1) { + switch (init.type) { + case 0: + glUniform1i(uniform, init.value); + } + } + } + break; + } + case GLRInitStepType::CREATE_SHADER: + { + GLuint shader = glCreateShader(step.create_shader.stage); + step.create_shader.shader->shader = shader; + const char *code = step.create_shader.code; + glShaderSource(shader, 1, &code, nullptr); + glCompileShader(shader); + GLint success = 0; + glGetShaderiv(shader, GL_COMPILE_STATUS, &success); + if (!success) { +#define MAX_INFO_LOG_SIZE 2048 + GLchar infoLog[MAX_INFO_LOG_SIZE]; + GLsizei len = 0; + glGetShaderInfoLog(shader, MAX_INFO_LOG_SIZE, &len, infoLog); + infoLog[len] = '\0'; +#ifdef __ANDROID__ + ELOG("Error in shader compilation! %s\n", infoLog); + ELOG("Shader source:\n%s\n", (const char *)code); +#endif + ERROR_LOG(G3D, "Error in shader compilation for: %s", step.create_shader.shader->desc.c_str()); + ERROR_LOG(G3D, "Info log: %s", infoLog); + ERROR_LOG(G3D, "Shader source:\n%s\n", (const char *)code); + Reporting::ReportMessage("Error in shader compilation: info: %s\n%s\n%s", infoLog, step.create_shader.shader->desc.c_str(), (const char *)code); +#ifdef SHADERLOG + OutputDebugStringUTF8(infoLog); +#endif + step.create_shader.shader->valid = false; + step.create_shader.shader->failed = true; + } + // Before we throw away the code, attach it to the shader for debugging. + step.create_shader.shader->code = code; + delete[] step.create_shader.code; + step.create_shader.shader->valid = true; + break; + } + case GLRInitStepType::CREATE_INPUT_LAYOUT: + { + GLRInputLayout *layout = step.create_input_layout.inputLayout; + // Nothing to do unless we want to create vertexbuffer objects (GL 4.5) + break; + } + case GLRInitStepType::CREATE_FRAMEBUFFER: + { + boundTexture = (GLuint)-1; + InitCreateFramebuffer(step); + allocatedTextures = true; + break; + } + case GLRInitStepType::TEXTURE_IMAGE: + { + GLRTexture *tex = step.texture_image.texture; + CHECK_GL_ERROR_IF_DEBUG(); + if (boundTexture != tex->texture) { + glBindTexture(tex->target, tex->texture); + boundTexture = tex->texture; + } + if (!step.texture_image.data) + Crash(); + // For things to show in RenderDoc, need to split into glTexImage2D(..., nullptr) and glTexSubImage. + glTexImage2D(tex->target, step.texture_image.level, step.texture_image.internalFormat, step.texture_image.width, step.texture_image.height, 0, step.texture_image.format, step.texture_image.type, step.texture_image.data); + allocatedTextures = true; + delete[] step.texture_image.data; + CHECK_GL_ERROR_IF_DEBUG(); + tex->wrapS = GL_CLAMP_TO_EDGE; + tex->wrapT = GL_CLAMP_TO_EDGE; + tex->magFilter = step.texture_image.linearFilter ? GL_LINEAR : GL_NEAREST; + tex->minFilter = step.texture_image.linearFilter ? GL_LINEAR : GL_NEAREST; + glTexParameteri(tex->target, GL_TEXTURE_WRAP_S, tex->wrapS); + glTexParameteri(tex->target, GL_TEXTURE_WRAP_T, tex->wrapT); + glTexParameteri(tex->target, GL_TEXTURE_MAG_FILTER, tex->magFilter); + glTexParameteri(tex->target, GL_TEXTURE_MIN_FILTER, tex->minFilter); + break; + } + case GLRInitStepType::TEXTURE_FINALIZE: + { + GLRTexture *tex = step.texture_finalize.texture; + if (boundTexture != tex->texture) { + glBindTexture(tex->target, tex->texture); + boundTexture = tex->texture; + } + glTexParameteri(tex->target, GL_TEXTURE_MAX_LEVEL, step.texture_finalize.maxLevel); + tex->maxLod = (float)step.texture_finalize.maxLevel; + if (step.texture_finalize.genMips) { + glGenerateMipmap(tex->target); + } + break; + } + default: + Crash(); + break; + } + } + + // TODO: Use GL_KHR_no_error or a debug callback, where supported? + if (allocatedTextures) { + // Users may use replacements or scaling, with high render resolutions, and run out of VRAM. + // This detects that, rather than looking like PPSSPP is broken. + // Calling glGetError() isn't great, but at the end of init shouldn't be too bad... + GLenum err = glGetError(); + if (err == GL_OUT_OF_MEMORY) { + WARN_LOG_REPORT(G3D, "GL ran out of GPU memory; switching to low memory mode"); + sawOutOfMemory_ = true; + } else if (err != GL_NO_ERROR) { + // We checked the err anyway, might as well log if there is one. + WARN_LOG(G3D, "Got an error after init: %08x (%s)", err, GLEnumToString(err).c_str()); + } + } +} + +void GLQueueRunner::InitCreateFramebuffer(const GLRInitStep &step) { + GLRFramebuffer *fbo = step.create_framebuffer.framebuffer; + +#ifndef USING_GLES2 + if (!gl_extensions.ARB_framebuffer_object && gl_extensions.EXT_framebuffer_object) { + fbo_ext_create(step); + } else if (!gl_extensions.ARB_framebuffer_object) { + return; + } + // If GLES2, we have basic FBO support and can just proceed. +#endif + CHECK_GL_ERROR_IF_DEBUG(); + + // Color texture is same everywhere + glGenFramebuffers(1, &fbo->handle); + glGenTextures(1, &fbo->color_texture.texture); + fbo->color_texture.target = GL_TEXTURE_2D; + fbo->color_texture.maxLod = 0.0f; + + // Create the surfaces. + glBindTexture(GL_TEXTURE_2D, fbo->color_texture.texture); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, fbo->width, fbo->height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL); + + fbo->color_texture.wrapS = GL_CLAMP_TO_EDGE; + fbo->color_texture.wrapT = GL_CLAMP_TO_EDGE; + fbo->color_texture.magFilter = GL_LINEAR; + fbo->color_texture.minFilter = GL_LINEAR; + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, fbo->color_texture.wrapS); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, fbo->color_texture.wrapT); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, fbo->color_texture.magFilter); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, fbo->color_texture.minFilter); + + if (gl_extensions.IsGLES) { + if (gl_extensions.OES_packed_depth_stencil) { + ILOG("Creating %i x %i FBO using DEPTH24_STENCIL8", fbo->width, fbo->height); + // Standard method + fbo->stencil_buffer = 0; + fbo->z_buffer = 0; + // 24-bit Z, 8-bit stencil combined + glGenRenderbuffers(1, &fbo->z_stencil_buffer); + glBindRenderbuffer(GL_RENDERBUFFER, fbo->z_stencil_buffer); + glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8_OES, fbo->width, fbo->height); + + // Bind it all together + glBindFramebuffer(GL_FRAMEBUFFER, fbo->handle); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, fbo->color_texture.texture, 0); + glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, fbo->z_stencil_buffer); + glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, fbo->z_stencil_buffer); + } else { + ILOG("Creating %i x %i FBO using separate stencil", fbo->width, fbo->height); + // TEGRA + fbo->z_stencil_buffer = 0; + // 16/24-bit Z, separate 8-bit stencil + glGenRenderbuffers(1, &fbo->z_buffer); + glBindRenderbuffer(GL_RENDERBUFFER, fbo->z_buffer); + // Don't forget to make sure fbo_standard_z_depth() matches. + glRenderbufferStorage(GL_RENDERBUFFER, gl_extensions.OES_depth24 ? GL_DEPTH_COMPONENT24 : GL_DEPTH_COMPONENT16, fbo->width, fbo->height); + + // 8-bit stencil buffer + glGenRenderbuffers(1, &fbo->stencil_buffer); + glBindRenderbuffer(GL_RENDERBUFFER, fbo->stencil_buffer); + glRenderbufferStorage(GL_RENDERBUFFER, GL_STENCIL_INDEX8, fbo->width, fbo->height); + + // Bind it all together + glBindFramebuffer(GL_FRAMEBUFFER, fbo->handle); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, fbo->color_texture.texture, 0); + glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, fbo->z_buffer); + glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, fbo->stencil_buffer); + } + } else { + fbo->stencil_buffer = 0; + fbo->z_buffer = 0; + // 24-bit Z, 8-bit stencil + glGenRenderbuffers(1, &fbo->z_stencil_buffer); + glBindRenderbuffer(GL_RENDERBUFFER, fbo->z_stencil_buffer); + glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, fbo->width, fbo->height); + + // Bind it all together + glBindFramebuffer(GL_FRAMEBUFFER, fbo->handle); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, fbo->color_texture.texture, 0); + glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, fbo->z_stencil_buffer); + glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, fbo->z_stencil_buffer); + } + + GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER); + switch (status) { + case GL_FRAMEBUFFER_COMPLETE: + // ILOG("Framebuffer verified complete."); + break; + case GL_FRAMEBUFFER_UNSUPPORTED: + ELOG("GL_FRAMEBUFFER_UNSUPPORTED"); + break; + case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT: + ELOG("GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT "); + break; + default: + FLOG("Other framebuffer error: %i", status); + break; + } + + // Unbind state we don't need + glBindRenderbuffer(GL_RENDERBUFFER, 0); + glBindTexture(GL_TEXTURE_2D, 0); + CHECK_GL_ERROR_IF_DEBUG(); + + currentDrawHandle_ = fbo->handle; + currentReadHandle_ = fbo->handle; +} + +void GLQueueRunner::RunSteps(const std::vector &steps) { + for (int i = 0; i < steps.size(); i++) { + const GLRStep &step = *steps[i]; + switch (step.stepType) { + case GLRStepType::RENDER: + PerformRenderPass(step); + break; + case GLRStepType::COPY: + PerformCopy(step); + break; + case GLRStepType::BLIT: + PerformBlit(step); + break; + case GLRStepType::READBACK: + PerformReadback(step); + break; + case GLRStepType::READBACK_IMAGE: + PerformReadbackImage(step); + break; + default: + Crash(); + break; + } + delete steps[i]; + } +} + +void GLQueueRunner::LogSteps(const std::vector &steps) { + for (int i = 0; i < steps.size(); i++) { + const GLRStep &step = *steps[i]; + switch (step.stepType) { + case GLRStepType::RENDER: + LogRenderPass(step); + break; + case GLRStepType::COPY: + LogCopy(step); + break; + case GLRStepType::BLIT: + LogBlit(step); + break; + case GLRStepType::READBACK: + LogReadback(step); + break; + case GLRStepType::READBACK_IMAGE: + LogReadbackImage(step); + break; + default: + Crash(); + break; + } + delete steps[i]; + } +} + +// TODO: Improve these. +void GLQueueRunner::LogRenderPass(const GLRStep &pass) { + ILOG("RenderPass (%d commands)", (int)pass.commands.size()); +} + +void GLQueueRunner::LogCopy(const GLRStep &pass) { + ILOG("Copy"); +} + +void GLQueueRunner::LogBlit(const GLRStep &pass) { + ILOG("Blit"); +} + +void GLQueueRunner::LogReadback(const GLRStep &pass) { + ILOG("Readback"); +} + +void GLQueueRunner::LogReadbackImage(const GLRStep &pass) { + ILOG("ReadbackImage"); +} + +void GLQueueRunner::PerformBlit(const GLRStep &step) { + // 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, step.blit.dst->handle); + fbo_bind_fb_target(true, step.blit.src->handle); + + int srcX1 = step.blit.srcRect.x; + int srcY1 = step.blit.srcRect.y; + int srcX2 = step.blit.srcRect.x + step.blit.srcRect.w; + int srcY2 = step.blit.srcRect.y + step.blit.srcRect.h; + int dstX1 = step.blit.dstRect.x; + int dstY1 = step.blit.dstRect.y; + int dstX2 = step.blit.dstRect.x + step.blit.dstRect.w; + int dstY2 = step.blit.dstRect.y + step.blit.dstRect.h; + + if (gl_extensions.GLES3 || gl_extensions.ARB_framebuffer_object) { + glBlitFramebuffer(srcX1, srcY1, srcX2, srcY2, dstX1, dstY1, dstX2, dstY2, step.blit.aspectMask, step.blit.filter ? GL_LINEAR : GL_NEAREST); + CHECK_GL_ERROR_IF_DEBUG(); +#if defined(USING_GLES2) && defined(__ANDROID__) // We only support this extension on Android, it's not even available on PC. + } else if (gl_extensions.NV_framebuffer_blit) { + glBlitFramebufferNV(srcX1, srcY1, srcX2, srcY2, dstX1, dstY1, dstX2, dstY2, step.blit.aspectMask, step.blit.filter ? GL_LINEAR : GL_NEAREST); + CHECK_GL_ERROR_IF_DEBUG(); +#endif // defined(USING_GLES2) && defined(__ANDROID__) + } else { + ERROR_LOG(G3D, "GLQueueRunner: Tried to blit without the capability"); + } +} + +void GLQueueRunner::PerformRenderPass(const GLRStep &step) { + // Don't execute empty renderpasses. + if (step.commands.empty()) { + // Nothing to do. + return; + } + + PerformBindFramebufferAsRenderTarget(step); + + glDisable(GL_DEPTH_TEST); + glDisable(GL_STENCIL_TEST); + glDisable(GL_BLEND); + glDisable(GL_CULL_FACE); + glDisable(GL_DITHER); + glEnable(GL_SCISSOR_TEST); +#ifndef USING_GLES2 + glDisable(GL_COLOR_LOGIC_OP); +#endif + + /* +#ifndef USING_GLES2 + if (g_Config.iInternalResolution == 0) { + glLineWidth(std::max(1, (int)(renderWidth_ / 480))); + glPointSize(std::max(1.0f, (float)(renderWidth_ / 480.f))); + } else { + glLineWidth(g_Config.iInternalResolution); + glPointSize((float)g_Config.iInternalResolution); + } +#endif + */ + + if (gl_extensions.ARB_vertex_array_object) { + glBindVertexArray(globalVAO_); + } + + GLRFramebuffer *fb = step.render.framebuffer; + GLRProgram *curProgram = nullptr; + int activeSlot = 0; + glActiveTexture(GL_TEXTURE0 + activeSlot); + + // State filtering tracking. + int attrMask = 0; + int colorMask = -1; + int depthMask = -1; + int depthFunc = -1; + int logicOp = -1; + GLuint curArrayBuffer = (GLuint)-1; + GLuint curElemArrayBuffer = (GLuint)-1; + bool depthEnabled = false; + bool blendEnabled = false; + bool cullEnabled = false; + bool ditherEnabled = false; + bool logicEnabled = false; + GLuint blendEqColor = (GLuint)-1; + GLuint blendEqAlpha = (GLuint)-1; + + GLRTexture *curTex[8]{}; + + auto &commands = step.commands; + for (const auto &c : commands) { + switch (c.cmd) { + case GLRRenderCommand::DEPTH: + if (c.depth.enabled) { + if (!depthEnabled) { + glEnable(GL_DEPTH_TEST); + depthEnabled = true; + } + if (c.depth.write != depthMask) { + glDepthMask(c.depth.write); + depthMask = c.depth.write; + } + if (c.depth.func != depthFunc) { + glDepthFunc(c.depth.func); + depthFunc = c.depth.func; + } + } else if (!c.depth.enabled && depthEnabled) { + glDisable(GL_DEPTH_TEST); + depthEnabled = false; + } + break; + case GLRRenderCommand::STENCILFUNC: + if (c.stencilFunc.enabled) { + glEnable(GL_STENCIL_TEST); + glStencilFunc(c.stencilFunc.func, c.stencilFunc.ref, c.stencilFunc.compareMask); + } else { + glDisable(GL_STENCIL_TEST); + } + break; + case GLRRenderCommand::STENCILOP: + glStencilOp(c.stencilOp.sFail, c.stencilOp.zFail, c.stencilOp.pass); + glStencilMask(c.stencilOp.writeMask); + break; + case GLRRenderCommand::BLEND: + if (c.blend.enabled) { + if (!blendEnabled) { + glEnable(GL_BLEND); + blendEnabled = true; + } + if (blendEqColor != c.blend.funcColor || blendEqAlpha != c.blend.funcAlpha) { + glBlendEquationSeparate(c.blend.funcColor, c.blend.funcAlpha); + blendEqColor = c.blend.funcColor; + blendEqAlpha = c.blend.funcAlpha; + } + glBlendFuncSeparate(c.blend.srcColor, c.blend.dstColor, c.blend.srcAlpha, c.blend.dstAlpha); + } else if (!c.blend.enabled && blendEnabled) { + glDisable(GL_BLEND); + blendEnabled = false; + } + if (c.blend.mask != colorMask) { + glColorMask(c.blend.mask & 1, (c.blend.mask >> 1) & 1, (c.blend.mask >> 2) & 1, (c.blend.mask >> 3) & 1); + colorMask = c.blend.mask; + } + break; + case GLRRenderCommand::LOGICOP: +#ifndef USING_GLES2 + if (c.logic.enabled) { + if (!logicEnabled) { + glEnable(GL_COLOR_LOGIC_OP); + logicEnabled = true; + } + if (logicOp != c.logic.logicOp) { + glLogicOp(c.logic.logicOp); + } + } else if (!c.logic.enabled && logicEnabled) { + glDisable(GL_COLOR_LOGIC_OP); + logicEnabled = false; + } +#endif + break; + case GLRRenderCommand::CLEAR: + glDisable(GL_SCISSOR_TEST); + if (c.clear.colorMask != colorMask) { + glColorMask(c.clear.colorMask & 1, (c.clear.colorMask >> 1) & 1, (c.clear.colorMask >> 2) & 1, (c.clear.colorMask >> 3) & 1); + colorMask = c.clear.colorMask; + } + if (c.clear.clearMask & GL_COLOR_BUFFER_BIT) { + float color[4]; + Uint8x4ToFloat4(color, c.clear.clearColor); + glClearColor(color[0], color[1], color[2], color[3]); + } + if (c.clear.clearMask & GL_DEPTH_BUFFER_BIT) { +#if defined(USING_GLES2) + glClearDepthf(c.clear.clearZ); +#else + glClearDepth(c.clear.clearZ); +#endif + } + if (c.clear.clearMask & GL_STENCIL_BUFFER_BIT) { + glClearStencil(c.clear.clearStencil); + } + glClear(c.clear.clearMask); + glEnable(GL_SCISSOR_TEST); + break; + case GLRRenderCommand::INVALIDATE: + { + GLenum attachments[3]; + int count = 0; + if (c.clear.clearMask & GL_COLOR_BUFFER_BIT) + attachments[count++] = GL_COLOR_ATTACHMENT0; + if (c.clear.clearMask & GL_DEPTH_BUFFER_BIT) + attachments[count++] = GL_DEPTH_ATTACHMENT; + if (c.clear.clearMask & GL_STENCIL_BUFFER_BIT) + attachments[count++] = GL_STENCIL_BUFFER_BIT; + glInvalidateFramebuffer(GL_FRAMEBUFFER, count, attachments); + break; + } + case GLRRenderCommand::BLENDCOLOR: + glBlendColor(c.blendColor.color[0], c.blendColor.color[1], c.blendColor.color[2], c.blendColor.color[3]); + break; + case GLRRenderCommand::VIEWPORT: + { + float y = c.viewport.vp.y; + if (!curFB_) + y = curFBHeight_ - y - c.viewport.vp.h; + + // TODO: Support FP viewports through glViewportArrays + glViewport((GLint)c.viewport.vp.x, (GLint)y, (GLsizei)c.viewport.vp.w, (GLsizei)c.viewport.vp.h); +#if !defined(USING_GLES2) + glDepthRange(c.viewport.vp.minZ, c.viewport.vp.maxZ); +#else + glDepthRangef(c.viewport.vp.minZ, c.viewport.vp.maxZ); +#endif + break; + } + case GLRRenderCommand::SCISSOR: + { + int y = c.scissor.rc.y; + if (!curFB_) + y = curFBHeight_ - y - c.scissor.rc.h; + glScissor(c.scissor.rc.x, y, c.scissor.rc.w, c.scissor.rc.h); + break; + } + case GLRRenderCommand::UNIFORM4F: + { + int loc = c.uniform4.loc ? *c.uniform4.loc : -1; + if (c.uniform4.name) { + loc = curProgram->GetUniformLoc(c.uniform4.name); + } + if (loc >= 0) { + switch (c.uniform4.count) { + case 1: + glUniform1f(loc, c.uniform4.v[0]); + break; + case 2: + glUniform2fv(loc, 1, c.uniform4.v); + break; + case 3: + glUniform3fv(loc, 1, c.uniform4.v); + break; + case 4: + glUniform4fv(loc, 1, c.uniform4.v); + break; + } + } + break; + } + case GLRRenderCommand::UNIFORM4I: + { + int loc = c.uniform4.loc ? *c.uniform4.loc : -1; + if (c.uniform4.name) { + loc = curProgram->GetUniformLoc(c.uniform4.name); + } + if (loc >= 0) { + switch (c.uniform4.count) { + case 1: + glUniform1iv(loc, 1, (GLint *)&c.uniform4.v[0]); + break; + case 2: + glUniform2iv(loc, 1, (GLint *)c.uniform4.v); + break; + case 3: + glUniform3iv(loc, 1, (GLint *)c.uniform4.v); + break; + case 4: + glUniform4iv(loc, 1, (GLint *)c.uniform4.v); + break; + } + } + break; + } + case GLRRenderCommand::UNIFORMMATRIX: + { + int loc = c.uniform4.loc ? *c.uniform4.loc : -1; + if (c.uniform4.name) { + loc = curProgram->GetUniformLoc(c.uniform4.name); + } + if (loc >= 0) { + glUniformMatrix4fv(loc, 1, false, c.uniformMatrix4.m); + } + break; + } + case GLRRenderCommand::BINDTEXTURE: + { + GLint slot = c.texture.slot; + if (slot != activeSlot) { + glActiveTexture(GL_TEXTURE0 + slot); + activeSlot = slot; + } + if (c.texture.texture) { + if (curTex[slot] != c.texture.texture) { + glBindTexture(c.texture.texture->target, c.texture.texture->texture); + curTex[slot] = c.texture.texture; + } + } else { + glBindTexture(GL_TEXTURE_2D, 0); // Which target? Well we only use this one anyway... + curTex[slot] = nullptr; + } + break; + } + case GLRRenderCommand::BIND_FB_TEXTURE: + { + GLint slot = c.bind_fb_texture.slot; + if (slot != activeSlot) { + glActiveTexture(GL_TEXTURE0 + slot); + activeSlot = slot; + } + if (c.bind_fb_texture.aspect == GL_COLOR_BUFFER_BIT) { + if (curTex[slot] != &c.bind_fb_texture.framebuffer->color_texture) + glBindTexture(GL_TEXTURE_2D, c.bind_fb_texture.framebuffer->color_texture.texture); + curTex[slot] = &c.bind_fb_texture.framebuffer->color_texture; + } else { + // TODO: Depth texturing? + curTex[slot] = nullptr; + } + break; + } + case GLRRenderCommand::BINDPROGRAM: + { + if (curProgram != c.program.program) { + glUseProgram(c.program.program->program); + curProgram = c.program.program; + } + break; + } + case GLRRenderCommand::BIND_VERTEX_BUFFER: + { + // TODO: Add fast path for glBindVertexBuffer + GLRInputLayout *layout = c.bindVertexBuffer.inputLayout; + GLuint buf = c.bindVertexBuffer.buffer ? c.bindVertexBuffer.buffer->buffer : 0; + if (buf != curArrayBuffer) { + glBindBuffer(GL_ARRAY_BUFFER, buf); + curArrayBuffer = buf; + } + int enable = layout->semanticsMask_ & ~attrMask; + int disable = (~layout->semanticsMask_) & attrMask; + for (int i = 0; i < 7; i++) { // SEM_MAX + if (enable & (1 << i)) { + glEnableVertexAttribArray(i); + } + if (disable & (1 << i)) { + glDisableVertexAttribArray(i); + } + } + attrMask = layout->semanticsMask_; + for (size_t i = 0; i < layout->entries.size(); i++) { + auto &entry = layout->entries[i]; + glVertexAttribPointer(entry.location, entry.count, entry.type, entry.normalized, entry.stride, (const void *)(c.bindVertexBuffer.offset + entry.offset)); + } + break; + } + case GLRRenderCommand::BIND_BUFFER: + { + if (c.bind_buffer.target == GL_ARRAY_BUFFER) { + Crash(); + } else if (c.bind_buffer.target == GL_ELEMENT_ARRAY_BUFFER) { + GLuint buf = c.bind_buffer.buffer ? c.bind_buffer.buffer->buffer : 0; + if (buf != curElemArrayBuffer) { + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buf); + curElemArrayBuffer = buf; + } + } else { + GLuint buf = c.bind_buffer.buffer ? c.bind_buffer.buffer->buffer : 0; + glBindBuffer(c.bind_buffer.target, buf); + } + break; + } + case GLRRenderCommand::GENMIPS: + // TODO: Should we include the texture handle in the command? + // Also, should this not be an init command? + glGenerateMipmap(GL_TEXTURE_2D); + break; + case GLRRenderCommand::DRAW: + glDrawArrays(c.draw.mode, c.draw.first, c.draw.count); + break; + case GLRRenderCommand::DRAW_INDEXED: + if (c.drawIndexed.instances == 1) { + glDrawElements(c.drawIndexed.mode, c.drawIndexed.count, c.drawIndexed.indexType, c.drawIndexed.indices); + } else { + glDrawElementsInstanced(c.drawIndexed.mode, c.drawIndexed.count, c.drawIndexed.indexType, c.drawIndexed.indices, c.drawIndexed.instances); + } + break; + case GLRRenderCommand::TEXTURESAMPLER: + { + GLRTexture *tex = curTex[activeSlot]; + if (!tex) { + break; + } + if (tex->wrapS != c.textureSampler.wrapS) { + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, c.textureSampler.wrapS); + tex->wrapS = c.textureSampler.wrapS; + } + if (tex->wrapT != c.textureSampler.wrapT) { + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, c.textureSampler.wrapT); + tex->wrapT = c.textureSampler.wrapT; + } + if (tex->magFilter != c.textureSampler.magFilter) { + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, c.textureSampler.magFilter); + tex->magFilter = c.textureSampler.magFilter; + } + if (tex->minFilter != c.textureSampler.minFilter) { + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, c.textureSampler.minFilter); + tex->minFilter = c.textureSampler.minFilter; + } + if (tex->anisotropy != c.textureSampler.anisotropy) { + if (c.textureSampler.anisotropy != 0.0f) { + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, c.textureSampler.anisotropy); + } + tex->anisotropy = c.textureSampler.anisotropy; + } + break; + } + case GLRRenderCommand::TEXTURELOD: + { + GLRTexture *tex = curTex[activeSlot]; + if (!tex) { + break; + } +#ifndef USING_GLES2 + if (tex->lodBias != c.textureLod.lodBias) { + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_LOD_BIAS, c.textureLod.lodBias); + tex->lodBias = c.textureLod.lodBias; + } +#endif + if (tex->minLod != c.textureLod.minLod) { + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_LOD, c.textureLod.minLod); + tex->minLod = c.textureLod.minLod; + } + if (tex->maxLod != c.textureLod.maxLod) { + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_LOD, c.textureLod.maxLod); + tex->maxLod = c.textureLod.maxLod; + } + break; + } + case GLRRenderCommand::RASTER: + if (c.raster.cullEnable) { + if (!cullEnabled) { + glEnable(GL_CULL_FACE); + cullEnabled = true; + } + glFrontFace(c.raster.frontFace); + glCullFace(c.raster.cullFace); + } else if (!c.raster.cullEnable && cullEnabled) { + glDisable(GL_CULL_FACE); + cullEnabled = false; + } + if (c.raster.ditherEnable) { + if (!ditherEnabled) { + glEnable(GL_DITHER); + ditherEnabled = true; + } + } else if (!c.raster.ditherEnable && ditherEnabled) { + glDisable(GL_DITHER); + ditherEnabled = false; + } + break; + default: + Crash(); + break; + } + } + + for (int i = 0; i < 7; i++) { + if (attrMask & (1 << i)) { + glDisableVertexAttribArray(i); + } + } + + if (activeSlot != 0) + glActiveTexture(GL_TEXTURE0); + + // Wipe out the current state. + glBindBuffer(GL_ARRAY_BUFFER, 0); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); + if (gl_extensions.ARB_vertex_array_object) { + glBindVertexArray(0); + } + glDisable(GL_SCISSOR_TEST); + glDisable(GL_DEPTH_TEST); + glDisable(GL_STENCIL_TEST); + glDisable(GL_BLEND); + glDisable(GL_CULL_FACE); +#ifndef USING_GLES2 + glDisable(GL_COLOR_LOGIC_OP); +#endif + glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); +} + +void GLQueueRunner::PerformCopy(const GLRStep &step) { + GLuint srcTex = 0; + GLuint dstTex = 0; + GLuint target = GL_TEXTURE_2D; + + const GLRect2D &srcRect = step.copy.srcRect; + const GLOffset2D &dstPos = step.copy.dstPos; + + GLRFramebuffer *src = step.copy.src; + GLRFramebuffer *dst = step.copy.dst; + + int srcLevel = 0; + int dstLevel = 0; + int srcZ = 0; + int dstZ = 0; + int depth = 1; + + switch (step.copy.aspectMask) { + case GL_COLOR_BUFFER_BIT: + srcTex = src->color_texture.texture; + dstTex = dst->color_texture.texture; + break; + case GL_DEPTH_BUFFER_BIT: + // TODO: Support depth copies. + _assert_msg_(G3D, false, "Depth copies not yet supported - soon"); + target = GL_RENDERBUFFER; + /* + srcTex = src->depth.texture; + dstTex = src->depth.texture; + */ + break; + } + + _dbg_assert_(G3D, srcTex); + _dbg_assert_(G3D, dstTex); + +#if defined(USING_GLES2) +#ifndef IOS + glCopyImageSubDataOES( + srcTex, target, srcLevel, srcRect.x, srcRect.y, srcZ, + dstTex, target, dstLevel, dstPos.x, dstPos.y, dstZ, + srcRect.w, srcRect.h, depth); +#endif +#else + if (gl_extensions.ARB_copy_image) { + glCopyImageSubData( + srcTex, target, srcLevel, srcRect.x, srcRect.y, srcZ, + dstTex, target, dstLevel, dstPos.x, dstPos.y, dstZ, + srcRect.w, srcRect.h, depth); + } else if (gl_extensions.NV_copy_image) { + // Older, pre GL 4.x NVIDIA cards. + glCopyImageSubDataNV( + srcTex, target, srcLevel, srcRect.x, srcRect.y, srcZ, + dstTex, target, dstLevel, dstPos.x, dstPos.y, dstZ, + srcRect.w, srcRect.h, depth); + } +#endif +} + +void GLQueueRunner::PerformReadback(const GLRStep &pass) { + using namespace Draw; + + GLRFramebuffer *fb = pass.readback.src; + + fbo_bind_fb_target(true, fb ? fb->handle : 0); + + // Reads from the "bound for read" framebuffer. + if (gl_extensions.GLES3 || !gl_extensions.IsGLES) + glReadBuffer(GL_COLOR_ATTACHMENT0); + + CHECK_GL_ERROR_IF_DEBUG(); + + // Always read back in 8888 format. + const GLuint internalFormat = GL_RGBA; + const GLuint format = GL_RGBA; + const GLuint type = GL_UNSIGNED_BYTE; + const int srcAlignment = 4; + int dstAlignment = (int)DataFormatSizeInBytes(pass.readback.dstFormat); + + int pixelStride = pass.readback.srcRect.w; + // Apply the correct alignment. + glPixelStorei(GL_PACK_ALIGNMENT, srcAlignment); + if (!gl_extensions.IsGLES || gl_extensions.GLES3) { + // Some drivers seem to require we specify this. See #8254. + glPixelStorei(GL_PACK_ROW_LENGTH, pixelStride); + } + + GLRect2D rect = pass.readback.srcRect; + + bool convert = pass.readback.dstFormat != DataFormat::R8G8B8A8_UNORM; + + int tempSize = srcAlignment * rect.w * rect.h; + int readbackSize = dstAlignment * rect.w * rect.h; + if (convert && tempSize > tempBufferSize_) { + delete[] tempBuffer_; + tempBuffer_ = new uint8_t[tempSize]; + tempBufferSize_ = tempSize; + } + if (readbackSize > readbackBufferSize_) { + delete[] readbackBuffer_; + readbackBuffer_ = new uint8_t[readbackSize]; + readbackBufferSize_ = readbackSize; + } + + glReadPixels(rect.x, rect.y, rect.w, rect.h, format, type, convert ? tempBuffer_ : readbackBuffer_); + #ifdef DEBUG_READ_PIXELS + LogReadPixelsError(glGetError()); + #endif + if (!gl_extensions.IsGLES || gl_extensions.GLES3) { + glPixelStorei(GL_PACK_ROW_LENGTH, 0); + } + if (convert) { + ConvertFromRGBA8888(readbackBuffer_, tempBuffer_, pixelStride, pixelStride, rect.w, rect.h, pass.readback.dstFormat); + } + + CHECK_GL_ERROR_IF_DEBUG(); +} + +void GLQueueRunner::PerformReadbackImage(const GLRStep &pass) { + GLRTexture *tex = pass.readback_image.texture; + + glBindTexture(GL_TEXTURE_2D, tex->texture); + + CHECK_GL_ERROR_IF_DEBUG(); + +#ifndef USING_GLES2 + int pixelStride = pass.readback_image.srcRect.w; + glPixelStorei(GL_PACK_ALIGNMENT, 4); + + GLRect2D rect = pass.readback.srcRect; + + int size = 4 * rect.w * rect.h; + if (size > readbackBufferSize_) { + delete[] readbackBuffer_; + readbackBuffer_ = new uint8_t[size]; + readbackBufferSize_ = size; + } + + glPixelStorei(GL_PACK_ALIGNMENT, 4); + glGetTexImage(GL_TEXTURE_2D, pass.readback_image.mipLevel, GL_RGBA, GL_UNSIGNED_BYTE, readbackBuffer_); +#endif + + CHECK_GL_ERROR_IF_DEBUG(); +} + +void GLQueueRunner::PerformBindFramebufferAsRenderTarget(const GLRStep &pass) { + if (pass.render.framebuffer) { + curFBWidth_ = pass.render.framebuffer->width; + curFBHeight_ = pass.render.framebuffer->height; + } else { + curFBWidth_ = targetWidth_; + curFBHeight_ = targetHeight_; + } + + curFB_ = pass.render.framebuffer; + if (curFB_) { + // 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, curFB_->handle); + } else { + fbo_unbind(); + // Backbuffer is now bound. + } +} + +void GLQueueRunner::CopyReadbackBuffer(int width, int height, Draw::DataFormat srcFormat, Draw::DataFormat destFormat, int pixelStride, uint8_t *pixels) { + // TODO: Maybe move data format conversion here, and always read back 8888. Drivers + // don't usually provide very optimized conversion implementations, though some do. + // Just need to be careful about dithering, which may break Danganronpa. + int bpp = (int)Draw::DataFormatSizeInBytes(destFormat); + for (int y = 0; y < height; y++) { + memcpy(pixels + y * pixelStride * bpp, readbackBuffer_ + y * width * bpp, width * bpp); + } +} + +GLuint GLQueueRunner::AllocTextureName() { + if (nameCache_.empty()) { + nameCache_.resize(TEXCACHE_NAME_CACHE_SIZE); + glGenTextures(TEXCACHE_NAME_CACHE_SIZE, &nameCache_[0]); + } + u32 name = nameCache_.back(); + nameCache_.pop_back(); + return name; +} + +// On PC, we always use GL_DEPTH24_STENCIL8. +// On Android, we try to use what's available. + +#ifndef USING_GLES2 +void GLQueueRunner::fbo_ext_create(const GLRInitStep &step) { + GLRFramebuffer *fbo = step.create_framebuffer.framebuffer; + + // Color texture is same everywhere + glGenFramebuffersEXT(1, &fbo->handle); + glGenTextures(1, &fbo->color_texture.texture); + + // Create the surfaces. + glBindTexture(GL_TEXTURE_2D, fbo->color_texture.texture); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, fbo->width, fbo->height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL); + + fbo->color_texture.target = GL_TEXTURE_2D; + fbo->color_texture.wrapS = GL_CLAMP_TO_EDGE; + fbo->color_texture.wrapT = GL_CLAMP_TO_EDGE; + fbo->color_texture.magFilter = GL_LINEAR; + fbo->color_texture.minFilter = GL_LINEAR; + fbo->color_texture.maxLod = 0.0f; + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, fbo->color_texture.wrapS); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, fbo->color_texture.wrapT); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, fbo->color_texture.magFilter); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, fbo->color_texture.minFilter); + + fbo->stencil_buffer = 0; + fbo->z_buffer = 0; + // 24-bit Z, 8-bit stencil + glGenRenderbuffersEXT(1, &fbo->z_stencil_buffer); + glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, fbo->z_stencil_buffer); + glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, GL_DEPTH_STENCIL_EXT, fbo->width, fbo->height); + // glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, GL_DEPTH24_STENCIL8, width, height); + + // Bind it all together + glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fbo->handle); + glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, fbo->color_texture.texture, 0); + glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_RENDERBUFFER_EXT, fbo->z_stencil_buffer); + glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_STENCIL_ATTACHMENT_EXT, GL_RENDERBUFFER_EXT, fbo->z_stencil_buffer); + + GLenum status = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT); + switch (status) { + case GL_FRAMEBUFFER_COMPLETE_EXT: + // ILOG("Framebuffer verified complete."); + break; + case GL_FRAMEBUFFER_UNSUPPORTED_EXT: + ELOG("GL_FRAMEBUFFER_UNSUPPORTED"); + break; + case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT_EXT: + ELOG("GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT "); + break; + default: + FLOG("Other framebuffer error: %i", status); + break; + } + // Unbind state we don't need + glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, 0); + glBindTexture(GL_TEXTURE_2D, 0); + + currentDrawHandle_ = fbo->handle; + currentReadHandle_ = fbo->handle; +} +#endif + +GLenum GLQueueRunner::fbo_get_fb_target(bool read, GLuint **cached) { + bool supportsBlit = gl_extensions.ARB_framebuffer_object; + if (gl_extensions.IsGLES) { + supportsBlit = (gl_extensions.GLES3 || gl_extensions.NV_framebuffer_blit); + } + + // Note: GL_FRAMEBUFFER_EXT and GL_FRAMEBUFFER have the same value, same with _NV. + if (supportsBlit) { + if (read) { + *cached = ¤tReadHandle_; + return GL_READ_FRAMEBUFFER; + } else { + *cached = ¤tDrawHandle_; + return GL_DRAW_FRAMEBUFFER; + } + } else { + *cached = ¤tDrawHandle_; + return GL_FRAMEBUFFER; + } +} + +void GLQueueRunner::fbo_bind_fb_target(bool read, GLuint name) { + CHECK_GL_ERROR_IF_DEBUG(); + GLuint *cached; + GLenum target = fbo_get_fb_target(read, &cached); + if (*cached != name) { + if (gl_extensions.ARB_framebuffer_object || gl_extensions.IsGLES) { + glBindFramebuffer(target, name); + } else { +#ifndef USING_GLES2 + glBindFramebufferEXT(target, name); +#endif + } + *cached = name; + } + CHECK_GL_ERROR_IF_DEBUG(); +} + +void GLQueueRunner::fbo_unbind() { + CHECK_GL_ERROR_IF_DEBUG(); +#ifndef USING_GLES2 + if (gl_extensions.ARB_framebuffer_object || gl_extensions.IsGLES) { + glBindFramebuffer(GL_FRAMEBUFFER, g_defaultFBO); + } else if (gl_extensions.EXT_framebuffer_object) { + glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, g_defaultFBO); + } +#else + glBindFramebuffer(GL_FRAMEBUFFER, g_defaultFBO); +#endif + +#ifdef IOS + bindDefaultFBO(); +#endif + + currentDrawHandle_ = 0; + currentReadHandle_ = 0; + CHECK_GL_ERROR_IF_DEBUG(); +} + +GLRFramebuffer::~GLRFramebuffer() { + if (gl_extensions.ARB_framebuffer_object || gl_extensions.IsGLES) { + if (handle) { + glBindFramebuffer(GL_FRAMEBUFFER, handle); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0); + glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, 0); + glBindFramebuffer(GL_FRAMEBUFFER, g_defaultFBO); + glDeleteFramebuffers(1, &handle); + } + if (z_stencil_buffer) + glDeleteRenderbuffers(1, &z_stencil_buffer); + if (z_buffer) + glDeleteRenderbuffers(1, &z_buffer); + if (stencil_buffer) + glDeleteRenderbuffers(1, &stencil_buffer); + } else if (gl_extensions.EXT_framebuffer_object) { +#ifndef USING_GLES2 + if (handle) { + glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, handle); + glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0); + glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER_EXT, 0); + glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, g_defaultFBO); + glDeleteFramebuffersEXT(1, &handle); + } + if (z_stencil_buffer) + glDeleteRenderbuffers(1, &z_stencil_buffer); + if (z_buffer) + glDeleteRenderbuffers(1, &z_buffer); + if (stencil_buffer) + glDeleteRenderbuffers(1, &stencil_buffer); +#endif + } +} diff --git a/ext/native/thin3d/GLQueueRunner.h b/ext/native/thin3d/GLQueueRunner.h new file mode 100644 index 0000000000..8d50e0ca44 --- /dev/null +++ b/ext/native/thin3d/GLQueueRunner.h @@ -0,0 +1,392 @@ +#pragma once + +#include +#include +#include + +#include "gfx/gl_common.h" +#include "thin3d/DataFormat.h" + +struct GLRViewport { + float x, y, w, h, minZ, maxZ; +}; + +struct GLRect2D { + int x, y, w, h; +}; + +struct GLOffset2D { + int x, y; +}; + + +class GLRShader; +class GLRTexture; +class GLRBuffer; +class GLRFramebuffer; +class GLRProgram; +class GLRInputLayout; + +enum class GLRRenderCommand : uint8_t { + DEPTH, + STENCILFUNC, + STENCILOP, + BLEND, + BLENDCOLOR, + LOGICOP, + UNIFORM4I, + UNIFORM4F, + UNIFORMMATRIX, + TEXTURESAMPLER, + TEXTURELOD, + VIEWPORT, + SCISSOR, + RASTER, + CLEAR, + INVALIDATE, + BINDPROGRAM, + BINDTEXTURE, + BIND_FB_TEXTURE, + BIND_VERTEX_BUFFER, + BIND_BUFFER, + GENMIPS, + DRAW, + DRAW_INDEXED, + PUSH_CONSTANTS, +}; + +// TODO: Bloated since the biggest struct decides the size. Will need something more efficient (separate structs with shared +// type field, smashed right after each other?) +// Also, all GLenums are really only 16 bits. +struct GLRRenderData { + GLRRenderCommand cmd; + union { + struct { + GLboolean enabled; + GLenum srcColor; + GLenum dstColor; + GLenum srcAlpha; + GLenum dstAlpha; + GLenum funcColor; + GLenum funcAlpha; + int mask; + } blend; + struct { + float color[4]; + } blendColor; + struct { + GLboolean enabled; + GLenum logicOp; + } logic; + struct { + GLboolean enabled; + GLboolean write; + GLenum func; + } depth; + struct { + GLboolean enabled; + GLenum func; + uint8_t ref; + uint8_t compareMask; + } stencilFunc; + struct { + GLenum sFail; + GLenum zFail; + GLenum pass; + uint8_t writeMask; + } stencilOp; // also write mask + struct { + GLenum mode; // primitive + GLint buffer; + GLint first; + GLint count; + } draw; + struct { + GLenum mode; // primitive + GLint count; + GLint instances; + GLint indexType; + void *indices; + } drawIndexed; + struct { + const char *name; // if null, use loc + GLint *loc; // NOTE: This is a pointer so we can immediately use things that are "queried" during program creation. + GLint count; + float v[4]; + } uniform4; + struct { + const char *name; // if null, use loc + GLint *loc; + float m[16]; + } uniformMatrix4; + struct { + uint32_t clearColor; + float clearZ; + int clearStencil; + int clearMask; // VK_IMAGE_ASPECT_COLOR_BIT etc + int colorMask; // Like blend, but for the clear. + } clear; + struct { + int slot; + GLRTexture *texture; + } texture; + struct { + int slot; + GLRFramebuffer *framebuffer; + int aspect; + } bind_fb_texture; + struct { + GLRBuffer *buffer; + GLuint target; + } bind_buffer; + struct { + GLRProgram *program; + } program; + struct { + GLRInputLayout *inputLayout; + GLRBuffer *buffer; + size_t offset; + } bindVertexBuffer; + struct { + GLenum wrapS; + GLenum wrapT; + GLenum magFilter; + GLenum minFilter; // also includes mip. GL... + float anisotropy; + } textureSampler; + struct { + float minLod; + float maxLod; + float lodBias; + } textureLod; + struct { + GLRViewport vp; + } viewport; + struct { + GLRect2D rc; + } scissor; + struct { + GLboolean cullEnable; + GLenum frontFace; + GLenum cullFace; + GLboolean ditherEnable; + } raster; + }; +}; + +// Unlike in Vulkan, we can't create stuff on the main thread, but need to +// defer this too. A big benefit will be that we'll be able to do all creation +// at the start of the frame. +enum class GLRInitStepType : uint8_t { + CREATE_TEXTURE, + CREATE_SHADER, + CREATE_PROGRAM, + CREATE_BUFFER, + CREATE_INPUT_LAYOUT, + CREATE_FRAMEBUFFER, + + TEXTURE_IMAGE, + TEXTURE_FINALIZE, + BUFFER_SUBDATA, +}; + +struct GLRInitStep { + GLRInitStep(GLRInitStepType _type) : stepType(_type) {} + GLRInitStepType stepType; + union { + struct { + GLRTexture *texture; + GLenum target; + } create_texture; + struct { + GLRShader *shader; + // This char arrays needs to be allocated with new[]. + char *code; + GLuint stage; + } create_shader; + struct { + GLRProgram *program; + GLRShader *shaders[3]; + int num_shaders; + bool support_dual_source; + } create_program; + struct { + GLRBuffer *buffer; + int size; + GLuint usage; + } create_buffer; + struct { + GLRInputLayout *inputLayout; + } create_input_layout; + struct { + GLRFramebuffer *framebuffer; + } create_framebuffer; + struct { + GLRBuffer *buffer; + int offset; + int size; + uint8_t *data; // owned, delete[]-d + bool deleteData; + } buffer_subdata; + struct { + GLRTexture *texture; + GLenum internalFormat; + GLenum format; + GLenum type; + int level; + int width; + int height; + bool linearFilter; + uint8_t *data; // owned, delete[]-d + } texture_image; + struct { + GLRTexture *texture; + int maxLevel; + bool genMips; + } texture_finalize; + }; +}; + +enum class GLRStepType : uint8_t { + RENDER, + COPY, + BLIT, + READBACK, + READBACK_IMAGE, +}; + +enum class GLRRenderPassAction { + DONT_CARE, + CLEAR, + KEEP, +}; + +class GLRFramebuffer; + +enum { + GLR_ASPECT_COLOR = 1, + GLR_ASPECT_DEPTH = 2, + GLR_ASPECT_STENCIL = 3, +}; + +struct GLRStep { + GLRStep(GLRStepType _type) : stepType(_type) {} + GLRStepType stepType; + std::vector commands; + union { + struct { + GLRFramebuffer *framebuffer; + int numDraws; + } render; + struct { + GLRFramebuffer *src; + GLRFramebuffer *dst; + GLRect2D srcRect; + GLOffset2D dstPos; + int aspectMask; + } copy; + struct { + GLRFramebuffer *src; + GLRFramebuffer *dst; + GLRect2D srcRect; + GLRect2D dstRect; + int aspectMask; + GLboolean filter; + } blit; + struct { + int aspectMask; + GLRFramebuffer *src; + GLRect2D srcRect; + Draw::DataFormat dstFormat; + } readback; + struct { + GLRTexture *texture; + GLRect2D srcRect; + int mipLevel; + } readback_image; + }; +}; + +class GLQueueRunner { +public: + GLQueueRunner() {} + + void RunInitSteps(const std::vector &steps); + + void RunSteps(const std::vector &steps); + void LogSteps(const std::vector &steps); + + void CreateDeviceObjects(); + void DestroyDeviceObjects(); + + inline int RPIndex(GLRRenderPassAction color, GLRRenderPassAction depth) { + return (int)depth * 3 + (int)color; + } + + void CopyReadbackBuffer(int width, int height, Draw::DataFormat srcFormat, Draw::DataFormat destFormat, int pixelStride, uint8_t *pixels); + + void Resize(int width, int height) { + targetWidth_ = width; + targetHeight_ = height; + } + + bool SawOutOfMemory() { + return sawOutOfMemory_; + } + + std::string GetGLString(int name) const { + auto it = glStrings_.find(name); + return it != glStrings_.end() ? it->second : ""; + } + +private: + void InitCreateFramebuffer(const GLRInitStep &step); + + void PerformBindFramebufferAsRenderTarget(const GLRStep &pass); + void PerformRenderPass(const GLRStep &pass); + void PerformCopy(const GLRStep &pass); + void PerformBlit(const GLRStep &pass); + void PerformReadback(const GLRStep &pass); + void PerformReadbackImage(const GLRStep &pass); + + void LogRenderPass(const GLRStep &pass); + void LogCopy(const GLRStep &pass); + void LogBlit(const GLRStep &pass); + void LogReadback(const GLRStep &pass); + void LogReadbackImage(const GLRStep &pass); + + void fbo_ext_create(const GLRInitStep &step); + void fbo_bind_fb_target(bool read, GLuint name); + GLenum fbo_get_fb_target(bool read, GLuint **cached); + void fbo_unbind(); + + GLRFramebuffer *curFB_ = nullptr; + + GLuint globalVAO_ = 0; + + int curFBWidth_ = 0; + int curFBHeight_ = 0; + int targetWidth_ = 0; + int targetHeight_ = 0; + + // Readback buffer. Currently we only support synchronous readback, so we only really need one. + // We size it generously. + uint8_t *readbackBuffer_ = nullptr; + int readbackBufferSize_ = 0; + // Temp buffer for color conversion + uint8_t *tempBuffer_ = nullptr; + int tempBufferSize_ = 0; + + float maxAnisotropyLevel_ = 0.0f; + + // Framebuffer state? + GLuint currentDrawHandle_ = 0; + GLuint currentReadHandle_ = 0; + + GLuint AllocTextureName(); + // Texture name cache. Ripped straight from TextureCacheGLES. + std::vector nameCache_; + std::unordered_map glStrings_; + + bool sawOutOfMemory_ = false; +}; diff --git a/ext/native/thin3d/GLRenderManager.cpp b/ext/native/thin3d/GLRenderManager.cpp new file mode 100644 index 0000000000..abd267aee1 --- /dev/null +++ b/ext/native/thin3d/GLRenderManager.cpp @@ -0,0 +1,622 @@ +#include + +#include "GLRenderManager.h" +#include "gfx_es2/gpu_features.h" +#include "thread/threadutil.h" +#include "base/logging.h" +#include "GPU/GPUState.h" + +#if 0 // def _DEBUG +#define VLOG ILOG +#else +#define VLOG(...) +#endif + +void GLDeleter::Perform() { + for (auto shader : shaders) { + delete shader; + } + shaders.clear(); + for (auto program : programs) { + delete program; + } + programs.clear(); + for (auto buffer : buffers) { + delete buffer; + } + buffers.clear(); + for (auto texture : textures) { + delete texture; + } + textures.clear(); + for (auto inputLayout : inputLayouts) { + delete inputLayout; + } + inputLayouts.clear(); + for (auto framebuffer : framebuffers) { + delete framebuffer; + } + framebuffers.clear(); +} + +GLRenderManager::GLRenderManager() { + for (int i = 0; i < MAX_INFLIGHT_FRAMES; i++) { + + } + + if (!useThread_) { + // The main thread is also the render thread. + ThreadStart(); + } +} + +GLRenderManager::~GLRenderManager() { + for (int i = 0; i < MAX_INFLIGHT_FRAMES; i++) { + + } + + if (!useThread_) { + // The main thread is also the render thread. + ThreadEnd(); + } +} + +void GLRenderManager::ThreadStart() { + queueRunner_.CreateDeviceObjects(); + threadFrame_ = threadInitFrame_; +} + +void GLRenderManager::ThreadEnd() { + // Wait for any shutdown to complete in StopThread(). + std::unique_lock lock(mutex_); + queueRunner_.DestroyDeviceObjects(); + VLOG("PULL: Quitting"); +} + +bool GLRenderManager::ThreadFrame() { + std::unique_lock lock(mutex_); + if (!run_) + return false; + + // In case of syncs or other partial completion, we keep going until we complete a frame. + do { + if (nextFrame) { + threadFrame_++; + if (threadFrame_ >= MAX_INFLIGHT_FRAMES) + threadFrame_ = 0; + } + FrameData &frameData = frameData_[threadFrame_]; + { + std::unique_lock lock(frameData.pull_mutex); + while (!frameData.readyForRun && run_) { + VLOG("PULL: Waiting for frame[%d].readyForRun", threadFrame_); + frameData.pull_condVar.wait(lock); + } + if (!frameData.readyForRun && !run_) { + // This means we're out of frames to render and run_ is false, so bail. + return false; + } + VLOG("PULL: frame[%d].readyForRun = false", threadFrame_); + frameData.readyForRun = false; + // Previously we had a quick exit here that avoided calling Run() if run_ was suddenly false, + // but that created a race condition where frames could end up not finished properly on resize etc. + + // Only increment next time if we're done. + nextFrame = frameData.type == GLRRunType::END; + assert(frameData.type == GLRRunType::END || frameData.type == GLRRunType::SYNC); + } + VLOG("PULL: Running frame %d", threadFrame_); + if (firstFrame) { + ILOG("Running first frame (%d)", threadFrame_); + firstFrame = false; + } + Run(threadFrame_); + VLOG("PULL: Finished frame %d", threadFrame_); + } while (!nextFrame); + return true; +} + +void GLRenderManager::StopThread() { + // Since we don't control the thread directly, this will only pause the thread. + + if (useThread_ && run_) { + run_ = false; + for (int i = 0; i < MAX_INFLIGHT_FRAMES; i++) { + auto &frameData = frameData_[i]; + { + std::unique_lock lock(frameData.push_mutex); + frameData.push_condVar.notify_all(); + } + { + std::unique_lock lock(frameData.pull_mutex); + frameData.pull_condVar.notify_all(); + } + } + + // Wait until we've definitely stopped the threadframe. + std::unique_lock lock(mutex_); + + ILOG("GL submission thread paused. Frame=%d", curFrame_); + + // Eat whatever has been queued up for this frame if anything. + Wipe(); + + // Wait for any fences to finish and be resignaled, so we don't have sync issues. + // Also clean out any queued data, which might refer to things that might not be valid + // when we restart... + for (int i = 0; i < MAX_INFLIGHT_FRAMES; i++) { + auto &frameData = frameData_[i]; + if (frameData.readyForRun || frameData.steps.size() != 0) { + Crash(); + } + frameData.readyForRun = false; + frameData.readyForSubmit = false; + for (size_t i = 0; i < frameData.steps.size(); i++) { + delete frameData.steps[i]; + } + frameData.steps.clear(); + frameData.initSteps.clear(); + + std::unique_lock lock(frameData.push_mutex); + while (!frameData.readyForFence) { + VLOG("PUSH: Waiting for frame[%d].readyForFence = 1 (stop)", i); + frameData.push_condVar.wait(lock); + } + } + } else { + ILOG("GL submission thread was already paused."); + } +} + +void GLRenderManager::BindFramebufferAsRenderTarget(GLRFramebuffer *fb, GLRRenderPassAction color, GLRRenderPassAction depth, GLRRenderPassAction stencil, uint32_t clearColor, float clearDepth, uint8_t clearStencil) { + assert(insideFrame_); + // Eliminate dupes. + if (steps_.size() && steps_.back()->render.framebuffer == fb && steps_.back()->stepType == GLRStepType::RENDER) { + if (color != GLRRenderPassAction::CLEAR && depth != GLRRenderPassAction::CLEAR && stencil != GLRRenderPassAction::CLEAR) { + // We don't move to a new step, this bind was unnecessary and we can safely skip it. + return; + } + } + if (curRenderStep_ && curRenderStep_->commands.size() == 0) { + VLOG("Empty render step. Usually happens after uploading pixels.."); + } + + GLRStep *step = new GLRStep{ GLRStepType::RENDER }; + // This is what queues up new passes, and can end previous ones. + step->render.framebuffer = fb; + step->render.numDraws = 0; + steps_.push_back(step); + + GLuint clearMask = 0; + GLRRenderData data; + data.cmd = GLRRenderCommand::CLEAR; + if (color == GLRRenderPassAction::CLEAR) { + clearMask |= GL_COLOR_BUFFER_BIT; + data.clear.clearColor = clearColor; + } + if (depth == GLRRenderPassAction::CLEAR) { + clearMask |= GL_DEPTH_BUFFER_BIT; + data.clear.clearZ = clearDepth; + } + if (stencil == GLRRenderPassAction::CLEAR) { + clearMask |= GL_STENCIL_BUFFER_BIT; + data.clear.clearStencil = clearStencil; + } + if (clearMask) { + data.clear.clearMask = clearMask; + data.clear.colorMask = 0xF; + step->commands.push_back(data); + } + + curRenderStep_ = step; + + // Every step clears this state. + gstate_c.Dirty(DIRTY_BLEND_STATE | DIRTY_DEPTHSTENCIL_STATE | DIRTY_RASTER_STATE); +} + +void GLRenderManager::BindFramebufferAsTexture(GLRFramebuffer *fb, int binding, int aspectBit, int attachment) { + _dbg_assert_(G3D, curRenderStep_ && curRenderStep_->stepType == GLRStepType::RENDER); + GLRRenderData data{ GLRRenderCommand::BIND_FB_TEXTURE }; + data.bind_fb_texture.slot = binding; + data.bind_fb_texture.framebuffer = fb; + data.bind_fb_texture.aspect = aspectBit; + curRenderStep_->commands.push_back(data); +} + +void GLRenderManager::CopyFramebuffer(GLRFramebuffer *src, GLRect2D srcRect, GLRFramebuffer *dst, GLOffset2D dstPos, int aspectMask) { + GLRStep *step = new GLRStep{ GLRStepType::COPY }; + step->copy.srcRect = srcRect; + step->copy.dstPos = dstPos; + step->copy.src = src; + step->copy.dst = dst; + step->copy.aspectMask = aspectMask; + steps_.push_back(step); + + // Every step clears this state. + gstate_c.Dirty(DIRTY_BLEND_STATE | DIRTY_DEPTHSTENCIL_STATE | DIRTY_RASTER_STATE); +} + +void GLRenderManager::BlitFramebuffer(GLRFramebuffer *src, GLRect2D srcRect, GLRFramebuffer *dst, GLRect2D dstRect, int aspectMask, bool filter) { + GLRStep *step = new GLRStep{ GLRStepType::BLIT }; + step->blit.srcRect = srcRect; + step->blit.dstRect = dstRect; + step->blit.src = src; + step->blit.dst = dst; + step->blit.aspectMask = aspectMask; + step->blit.filter = filter; + steps_.push_back(step); + + // Every step clears this state. + gstate_c.Dirty(DIRTY_BLEND_STATE | DIRTY_DEPTHSTENCIL_STATE | DIRTY_RASTER_STATE); +} + +bool GLRenderManager::CopyFramebufferToMemorySync(GLRFramebuffer *src, int aspectBits, int x, int y, int w, int h, Draw::DataFormat destFormat, uint8_t *pixels, int pixelStride) { + GLRStep *step = new GLRStep{ GLRStepType::READBACK }; + step->readback.src = src; + step->readback.srcRect = { x, y, w, h }; + step->readback.aspectMask = aspectBits; + step->readback.dstFormat = destFormat; + steps_.push_back(step); + + // Every step clears this state. + gstate_c.Dirty(DIRTY_BLEND_STATE | DIRTY_DEPTHSTENCIL_STATE | DIRTY_RASTER_STATE); + + curRenderStep_ = nullptr; + FlushSync(); + + Draw::DataFormat srcFormat; + if (aspectBits & GL_COLOR_BUFFER_BIT) { + srcFormat = Draw::DataFormat::R8G8B8A8_UNORM; + } else if (aspectBits & GL_STENCIL_BUFFER_BIT) { + // Copies from stencil are always S8. + srcFormat = Draw::DataFormat::S8; + } else if (aspectBits & GL_DEPTH_BUFFER_BIT) { + // TODO: Do this properly. + srcFormat = Draw::DataFormat::D24_S8; + } else { + _assert_(false); + } + queueRunner_.CopyReadbackBuffer(w, h, srcFormat, destFormat, pixelStride, pixels); + return true; +} + +void GLRenderManager::CopyImageToMemorySync(GLRTexture *texture, int mipLevel, int x, int y, int w, int h, Draw::DataFormat destFormat, uint8_t *pixels, int pixelStride) { + GLRStep *step = new GLRStep{ GLRStepType::READBACK_IMAGE }; + step->readback_image.texture = texture; + step->readback_image.mipLevel = mipLevel; + step->readback_image.srcRect = { x, y, w, h }; + steps_.push_back(step); + + // Every step clears this state. + gstate_c.Dirty(DIRTY_BLEND_STATE | DIRTY_DEPTHSTENCIL_STATE | DIRTY_RASTER_STATE); + + curRenderStep_ = nullptr; + FlushSync(); + + queueRunner_.CopyReadbackBuffer(w, h, Draw::DataFormat::R8G8B8A8_UNORM, destFormat, pixelStride, pixels); +} + +void GLRenderManager::BeginFrame() { + VLOG("BeginFrame"); + + int curFrame = GetCurFrame(); + FrameData &frameData = frameData_[curFrame]; + + // Make sure the very last command buffer from the frame before the previous has been fully executed. + if (useThread_) { + std::unique_lock lock(frameData.push_mutex); + while (!frameData.readyForFence) { + VLOG("PUSH: Waiting for frame[%d].readyForFence = 1", curFrame); + frameData.push_condVar.wait(lock); + } + frameData.readyForFence = false; + frameData.readyForSubmit = true; + } + + VLOG("PUSH: Fencing %d", curFrame); + + + // vkWaitForFences(device, 1, &frameData.fence, true, UINT64_MAX); + // vkResetFences(device, 1, &frameData.fence); + // glFenceSync(...) + + // Must be after the fence - this performs deletes. + VLOG("PUSH: BeginFrame %d", curFrame); + if (!run_) { + WLOG("BeginFrame while !run_!"); + } + + // vulkan_->BeginFrame(); + // In GL, we have to do deletes on the submission thread. + + insideFrame_ = true; +} + +void GLRenderManager::Finish() { + curRenderStep_ = nullptr; + int curFrame = GetCurFrame(); + FrameData &frameData = frameData_[curFrame]; + if (!useThread_) { + frameData.steps = std::move(steps_); + steps_.clear(); + frameData.initSteps = std::move(initSteps_); + initSteps_.clear(); + frameData.type = GLRRunType::END; + Run(curFrame); + } else { + std::unique_lock lock(frameData.pull_mutex); + VLOG("PUSH: Frame[%d].readyForRun = true", curFrame); + frameData.steps = std::move(steps_); + steps_.clear(); + frameData.initSteps = std::move(initSteps_); + initSteps_.clear(); + frameData.readyForRun = true; + frameData.type = GLRRunType::END; + frameData.pull_condVar.notify_all(); + } + + curFrame_++; + if (curFrame_ >= MAX_INFLIGHT_FRAMES) + curFrame_ = 0; + + insideFrame_ = false; +} + +void GLRenderManager::BeginSubmitFrame(int frame) { + FrameData &frameData = frameData_[frame]; + if (!frameData.hasBegun) { + frameData.hasBegun = true; + } +} + +void GLRenderManager::Submit(int frame, bool triggerFence) { + FrameData &frameData = frameData_[frame]; + + // In GL, submission happens automatically in Run(). + + // When !triggerFence, we notify after syncing with Vulkan. + + if (useThread_ && triggerFence) { + VLOG("PULL: Frame %d.readyForFence = true", frame); + + std::unique_lock lock(frameData.push_mutex); + assert(frameData.readyForSubmit); + frameData.readyForFence = true; + frameData.readyForSubmit = false; + frameData.push_condVar.notify_all(); + } +} + +void GLRenderManager::EndSubmitFrame(int frame) { + FrameData &frameData = frameData_[frame]; + frameData.hasBegun = false; + + Submit(frame, true); + + if (!frameData.skipSwap) { + if (swapFunction_) { + swapFunction_(); + } + } else { + frameData.skipSwap = false; + } +} + +void GLRenderManager::Run(int frame) { + BeginSubmitFrame(frame); + + FrameData &frameData = frameData_[frame]; + + // Delete stuff from the last round. + frameData.deleter_prev.Perform(); + + auto &stepsOnThread = frameData_[frame].steps; + auto &initStepsOnThread = frameData_[frame].initSteps; + // queueRunner_.LogSteps(stepsOnThread); + queueRunner_.RunInitSteps(initStepsOnThread); + queueRunner_.RunSteps(stepsOnThread); + stepsOnThread.clear(); + initStepsOnThread.clear(); + + switch (frameData.type) { + case GLRRunType::END: + frameData.deleter_prev.Take(frameData.deleter); + EndSubmitFrame(frame); + break; + + case GLRRunType::SYNC: + EndSyncFrame(frame); + break; + + default: + assert(false); + } + + VLOG("PULL: Finished running frame %d", frame); +} + +void GLRenderManager::FlushSync() { + // Need to flush any pushbuffers to VRAM before submitting draw calls. + for (auto iter : frameData_[curFrame_].activePushBuffers) { + iter->Flush(); + } + + // TODO: Reset curRenderStep_? + int curFrame = curFrame_; + FrameData &frameData = frameData_[curFrame]; + if (!useThread_) { + frameData.initSteps = std::move(initSteps_); + initSteps_.clear(); + frameData.steps = std::move(steps_); + steps_.clear(); + frameData.type = GLRRunType::SYNC; + Run(curFrame); + } else { + std::unique_lock lock(frameData.pull_mutex); + VLOG("PUSH: Frame[%d].readyForRun = true (sync)", curFrame); + frameData.initSteps = std::move(initSteps_); + initSteps_.clear(); + frameData.steps = std::move(steps_); + steps_.clear(); + frameData.readyForRun = true; + assert(frameData.readyForFence == false); + frameData.type = GLRRunType::SYNC; + frameData.pull_condVar.notify_all(); + } + + if (useThread_) { + std::unique_lock lock(frameData.push_mutex); + // Wait for the flush to be hit, since we're syncing. + while (!frameData.readyForFence) { + VLOG("PUSH: Waiting for frame[%d].readyForFence = 1 (sync)", curFrame); + frameData.push_condVar.wait(lock); + } + frameData.readyForFence = false; + frameData.readyForSubmit = true; + } +} + +void GLRenderManager::EndSyncFrame(int frame) { + FrameData &frameData = frameData_[frame]; + Submit(frame, false); + + // This is brutal! Should probably wait for a fence instead, not that it'll matter much since we'll + // still stall everything. + glFinish(); + + // At this point we can resume filling the command buffers for the current frame since + // we know the device is idle - and thus all previously enqueued command buffers have been processed. + // No need to switch to the next frame number. + + if (useThread_) { + std::unique_lock lock(frameData.push_mutex); + frameData.readyForFence = true; + frameData.readyForSubmit = true; + frameData.push_condVar.notify_all(); + } +} + +void GLRenderManager::Wipe() { + initSteps_.clear(); + for (auto step : steps_) { + delete step; + } + steps_.clear(); +} + +void GLRenderManager::WaitUntilQueueIdle() { + if (!useThread_) { + // Must be idle, nothing to do. + return; + } + + // Just wait for all frames to be ready. + for (int i = 0; i < MAX_INFLIGHT_FRAMES; i++) { + FrameData &frameData = frameData_[i]; + + std::unique_lock lock(frameData.push_mutex); + // Ignore unsubmitted frames. + while (!frameData.readyForFence && !frameData.readyForSubmit) { + VLOG("PUSH: Waiting for frame[%d].readyForFence = 1 (wait idle)", i); + frameData.push_condVar.wait(lock); + } + } +} + +GLPushBuffer::GLPushBuffer(GLRenderManager *render, GLuint target, size_t size) : render_(render), target_(target), size_(size) { + bool res = AddBuffer(); + assert(res); +} + +GLPushBuffer::~GLPushBuffer() { + assert(buffers_.empty()); +} + +void GLPushBuffer::Map() { + assert(!writePtr_); + // TODO: Even a good old glMapBuffer could actually work well here. + // VkResult res = vkMapMemory(device_, buffers_[buf_].deviceMemory, 0, size_, 0, (void **)(&writePtr_)); + writePtr_ = buffers_[buf_].deviceMemory; + assert(writePtr_); +} + +void GLPushBuffer::Unmap() { + assert(writePtr_); + // Here we should simply upload everything to the buffers. + // Might be worth trying with size_ instead of offset_, so the driver can replace the whole buffer. + // At least if it's close. + render_->BufferSubdata(buffers_[buf_].buffer, 0, offset_, buffers_[buf_].deviceMemory, false); + writePtr_ = nullptr; +} + +void GLPushBuffer::Flush() { + render_->BufferSubdata(buffers_[buf_].buffer, 0, offset_, buffers_[buf_].deviceMemory, false); + // Here we will submit all the draw calls, with the already known buffer and offsets. + // Might as well reset the write pointer here and start over the current buffer. + writePtr_ = buffers_[buf_].deviceMemory; + offset_ = 0; +} + +bool GLPushBuffer::AddBuffer() { + BufInfo info; + info.deviceMemory = new uint8_t[size_]; + info.buffer = render_->CreateBuffer(target_, size_, GL_DYNAMIC_DRAW); + buf_ = buffers_.size(); + buffers_.resize(buf_ + 1); + buffers_[buf_] = info; + return true; +} + +void GLPushBuffer::Destroy() { + for (BufInfo &info : buffers_) { + render_->DeleteBuffer(info.buffer); + delete[] info.deviceMemory; + } + buffers_.clear(); +} + +void GLPushBuffer::NextBuffer(size_t minSize) { + // First, unmap the current memory. + Unmap(); + + buf_++; + if (buf_ >= buffers_.size() || minSize > size_) { + // Before creating the buffer, adjust to the new size_ if necessary. + while (size_ < minSize) { + size_ <<= 1; + } + + bool res = AddBuffer(); + assert(res); + if (!res) { + // Let's try not to crash at least? + buf_ = 0; + } + } + + // Now, move to the next buffer and map it. + offset_ = 0; + Map(); +} + +void GLPushBuffer::Defragment() { + if (buffers_.size() <= 1) { + return; + } + + // Okay, we have more than one. Destroy them all and start over with a larger one. + size_t newSize = size_ * buffers_.size(); + Destroy(); + + size_ = newSize; + bool res = AddBuffer(); + assert(res); +} + +size_t GLPushBuffer::GetTotalSize() const { + size_t sum = 0; + if (buffers_.size() > 1) + sum += size_ * (buffers_.size() - 1); + sum += offset_; + return sum; +} diff --git a/ext/native/thin3d/GLRenderManager.h b/ext/native/thin3d/GLRenderManager.h new file mode 100644 index 0000000000..239ff0f3c0 --- /dev/null +++ b/ext/native/thin3d/GLRenderManager.h @@ -0,0 +1,842 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "gfx/gl_common.h" +#include "math/dataconv.h" +#include "Common/Log.h" +#include "GLQueueRunner.h" + +class GLRInputLayout; +class GLPushBuffer; + +class GLRTexture { +public: + ~GLRTexture() { + if (texture) { + glDeleteTextures(1, &texture); + } + } + GLuint texture = 0; + // Could also trust OpenGL defaults I guess.. + GLenum target = 0xFFFF; + GLenum wrapS = 0xFFFF; + GLenum wrapT = 0xFFFF; + GLenum magFilter = 0xFFFF; + GLenum minFilter = 0xFFFF; + float anisotropy = -100000.0f; + float minLod = -100000.0f; + float maxLod = 100000.0f; + float lodBias = 0.0f; +}; + +class GLRFramebuffer { +public: + GLRFramebuffer(int _width, int _height, bool z_stencil) + : width(_width), height(_height), z_stencil_(z_stencil) { + } + + ~GLRFramebuffer(); + + int numShadows = 1; // TODO: Support this. + + GLuint handle = 0; + GLRTexture color_texture; + GLuint z_stencil_buffer = 0; // Either this is set, or the two below. + GLuint z_buffer = 0; + GLuint stencil_buffer = 0; + + int width; + int height; + GLuint colorDepth; + + bool z_stencil_; +}; + +// We need to create some custom heap-allocated types so we can forward things that need to be created on the GL thread, before +// they've actually been created. + +class GLRShader { +public: + ~GLRShader() { + if (shader) { + glDeleteShader(shader); + } + } + GLuint shader = 0; + bool valid = false; + // Warning: Won't know until a future frame. + bool failed = false; + std::string desc; + std::string code; +}; + +class GLRProgram { +public: + ~GLRProgram() { + if (program) { + glDeleteProgram(program); + } + } + struct Semantic { + int location; + const char *attrib; + }; + + struct UniformLocQuery { + GLint *dest; + const char *name; + }; + + struct Initializer { + GLint *uniform; + int type; + int value; + }; + + GLuint program = 0; + std::vector semantics_; + std::vector queries_; + std::vector initialize_; + + struct UniformInfo { + int loc_; + }; + + // Must ONLY be called from GLQueueRunner! + // Also it's pretty slow... + int GetUniformLoc(const char *name) { + auto iter = uniformCache_.find(std::string(name)); + int loc = -1; + if (iter != uniformCache_.end()) { + loc = iter->second.loc_; + } else { + loc = glGetUniformLocation(program, name); + UniformInfo info; + info.loc_ = loc; + uniformCache_[name] = info; + } + return loc; + } + std::unordered_map uniformCache_; +}; + +class GLRBuffer { +public: + GLRBuffer(GLuint target, size_t size) : target_(target), size_((int)size) {} + ~GLRBuffer() { + if (buffer) { + glDeleteBuffers(1, &buffer); + } + } + GLuint buffer = 0; + GLuint target_; + int size_; +private: +}; + +enum class GLRRunType { + END, + SYNC, +}; + +// Synchronize this externally if needed, no mutex anymore. +class GLDeleter { +public: + void Perform(); + + void Take(GLDeleter &other) { + _assert_msg_(G3D, shaders.empty() && programs.empty() && buffers.empty() && textures.empty() && inputLayouts.empty() && framebuffers.empty(), "Deleter already has stuff"); + shaders = std::move(other.shaders); + programs = std::move(other.programs); + buffers = std::move(other.buffers); + textures = std::move(other.textures); + inputLayouts = std::move(other.inputLayouts); + framebuffers = std::move(other.framebuffers); + other.shaders.clear(); + other.programs.clear(); + other.buffers.clear(); + other.textures.clear(); + other.inputLayouts.clear(); + other.framebuffers.clear(); + } + + std::vector shaders; + std::vector programs; + std::vector buffers; + std::vector textures; + std::vector inputLayouts; + std::vector framebuffers; +}; + +class GLRInputLayout { +public: + struct Entry { + int location; + int count; + GLenum type; + GLboolean normalized; + int stride; + intptr_t offset; + }; + std::vector entries; + int semanticsMask_ = 0; +}; + +class GLRenderManager { +public: + GLRenderManager(); + ~GLRenderManager(); + + void ThreadStart(); + void ThreadEnd(); + bool ThreadFrame(); // Returns false to request exiting the loop. + + // Makes sure that the GPU has caught up enough that we can start writing buffers of this frame again. + void BeginFrame(); + // Can run on a different thread! + void Finish(); + void Run(int frame); + + // Zaps queued up commands. Use if you know there's a risk you've queued up stuff that has already been deleted. Can happen during in-game shutdown. + void Wipe(); + + // Wait until no frames are pending. Use during shutdown before freeing pointers. + void WaitUntilQueueIdle(); + + // Creation commands. These were not needed in Vulkan since there we can do that on the main thread. + GLRTexture *CreateTexture(GLenum target) { + GLRInitStep step{ GLRInitStepType::CREATE_TEXTURE }; + step.create_texture.texture = new GLRTexture(); + step.create_texture.texture->target = target; + initSteps_.push_back(step); + return step.create_texture.texture; + } + + GLRBuffer *CreateBuffer(GLuint target, size_t size, GLuint usage) { + GLRInitStep step{ GLRInitStepType::CREATE_BUFFER }; + step.create_buffer.buffer = new GLRBuffer(target, size); + step.create_buffer.size = (int)size; + step.create_buffer.usage = usage; + initSteps_.push_back(step); + return step.create_buffer.buffer; + } + + GLRShader *CreateShader(GLuint stage, const std::string &code, const std::string &desc) { + GLRInitStep step{ GLRInitStepType::CREATE_SHADER }; + step.create_shader.shader = new GLRShader(); + step.create_shader.shader->desc = desc; + step.create_shader.stage = stage; + step.create_shader.code = new char[code.size() + 1]; + memcpy(step.create_shader.code, code.data(), code.size() + 1); + initSteps_.push_back(step); + return step.create_shader.shader; + } + + GLRFramebuffer *CreateFramebuffer(int width, int height, bool z_stencil) { + GLRInitStep step{ GLRInitStepType::CREATE_FRAMEBUFFER }; + step.create_framebuffer.framebuffer = new GLRFramebuffer(width, height, z_stencil); + initSteps_.push_back(step); + return step.create_framebuffer.framebuffer; + } + + // Can't replace uniform initializers with direct calls to SetUniform() etc because there might + // not be an active render pass. + GLRProgram *CreateProgram( + std::vector shaders, std::vector semantics, std::vector queries, + std::vector initalizers, bool supportDualSource) { + GLRInitStep step{ GLRInitStepType::CREATE_PROGRAM }; + assert(shaders.size() <= ARRAY_SIZE(step.create_program.shaders)); + step.create_program.program = new GLRProgram(); + step.create_program.program->semantics_ = semantics; + step.create_program.program->queries_ = queries; + step.create_program.program->initialize_ = initalizers; + step.create_program.support_dual_source = supportDualSource; + _assert_msg_(G3D, shaders.size() > 0, "Can't create a program with zero shaders"); + for (int i = 0; i < shaders.size(); i++) { + step.create_program.shaders[i] = shaders[i]; + } +#ifdef _DEBUG + for (auto &iter : queries) { + assert(iter.name); + } + for (auto &sem : semantics) { + assert(sem.attrib); + } +#endif + step.create_program.num_shaders = (int)shaders.size(); + initSteps_.push_back(step); + return step.create_program.program; + } + + GLRInputLayout *CreateInputLayout(std::vector &entries) { + GLRInitStep step{ GLRInitStepType::CREATE_INPUT_LAYOUT }; + step.create_input_layout.inputLayout = new GLRInputLayout(); + step.create_input_layout.inputLayout->entries = entries; + for (auto &iter : step.create_input_layout.inputLayout->entries) { + step.create_input_layout.inputLayout->semanticsMask_ |= 1 << iter.location; + } + initSteps_.push_back(step); + return step.create_input_layout.inputLayout; + } + + void DeleteShader(GLRShader *shader) { + frameData_[curFrame_].deleter.shaders.push_back(shader); + } + void DeleteProgram(GLRProgram *program) { + frameData_[curFrame_].deleter.programs.push_back(program); + } + void DeleteBuffer(GLRBuffer *buffer) { + frameData_[curFrame_].deleter.buffers.push_back(buffer); + } + void DeleteTexture(GLRTexture *texture) { + frameData_[curFrame_].deleter.textures.push_back(texture); + } + void DeleteInputLayout(GLRInputLayout *inputLayout) { + frameData_[curFrame_].deleter.inputLayouts.push_back(inputLayout); + } + void DeleteFramebuffer(GLRFramebuffer *framebuffer) { + frameData_[curFrame_].deleter.framebuffers.push_back(framebuffer); + } + + void BindFramebufferAsRenderTarget(GLRFramebuffer *fb, GLRRenderPassAction color, GLRRenderPassAction depth, GLRRenderPassAction stencil, uint32_t clearColor, float clearDepth, uint8_t clearStencil); + void BindFramebufferAsTexture(GLRFramebuffer *fb, int binding, int aspectBit, int attachment); + bool CopyFramebufferToMemorySync(GLRFramebuffer *src, int aspectBits, int x, int y, int w, int h, Draw::DataFormat destFormat, uint8_t *pixels, int pixelStride); + void CopyImageToMemorySync(GLRTexture *texture, int mipLevel, int x, int y, int w, int h, Draw::DataFormat destFormat, uint8_t *pixels, int pixelStride); + + void CopyFramebuffer(GLRFramebuffer *src, GLRect2D srcRect, GLRFramebuffer *dst, GLOffset2D dstPos, int aspectMask); + void BlitFramebuffer(GLRFramebuffer *src, GLRect2D srcRect, GLRFramebuffer *dst, GLRect2D dstRect, int aspectMask, bool filter); + + // Takes ownership of data if deleteData = true. + void BufferSubdata(GLRBuffer *buffer, size_t offset, size_t size, uint8_t *data, bool deleteData = true) { + // TODO: Maybe should be a render command instead of an init command? When possible it's better as + // an init command, that's for sure. + GLRInitStep step{ GLRInitStepType::BUFFER_SUBDATA }; + _dbg_assert_(G3D, offset >= 0); + _dbg_assert_(G3D, offset <= buffer->size_ - size); + step.buffer_subdata.buffer = buffer; + step.buffer_subdata.offset = (int)offset; + step.buffer_subdata.size = (int)size; + step.buffer_subdata.data = data; + step.buffer_subdata.deleteData = deleteData; + initSteps_.push_back(step); + } + + // Takes ownership over the data pointer and delete[]-s it. + void TextureImage(GLRTexture *texture, int level, int width, int height, GLenum internalFormat, GLenum format, GLenum type, uint8_t *data, bool linearFilter = false) { + GLRInitStep step{ GLRInitStepType::TEXTURE_IMAGE }; + step.texture_image.texture = texture; + step.texture_image.data = data; + step.texture_image.internalFormat = internalFormat; + step.texture_image.format = format; + step.texture_image.type = type; + step.texture_image.level = level; + step.texture_image.width = width; + step.texture_image.height = height; + step.texture_image.linearFilter = linearFilter; + initSteps_.push_back(step); + } + + void FinalizeTexture(GLRTexture *texture, int maxLevels, bool genMips) { + GLRInitStep step{ GLRInitStepType::TEXTURE_FINALIZE }; + step.texture_finalize.texture = texture; + step.texture_finalize.maxLevel = maxLevels; + step.texture_finalize.genMips = genMips; + initSteps_.push_back(step); + } + + void BindTexture(int slot, GLRTexture *tex) { + _dbg_assert_(G3D, curRenderStep_ && curRenderStep_->stepType == GLRStepType::RENDER); + GLRRenderData data{ GLRRenderCommand::BINDTEXTURE }; + _dbg_assert_(G3D, slot < 16); + data.texture.slot = slot; + data.texture.texture = tex; + curRenderStep_->commands.push_back(data); + } + + void BindProgram(GLRProgram *program) { + _dbg_assert_(G3D, curRenderStep_ && curRenderStep_->stepType == GLRStepType::RENDER); + GLRRenderData data{ GLRRenderCommand::BINDPROGRAM }; + _dbg_assert_(G3D, program != nullptr); + data.program.program = program; + curRenderStep_->commands.push_back(data); + } + + void BindPixelPackBuffer(GLRBuffer *buffer) { // Want to support an offset but can't in ES 2.0. We supply an offset when binding the buffers instead. + _dbg_assert_(G3D, curRenderStep_ && curRenderStep_->stepType == GLRStepType::RENDER); + GLRRenderData data{ GLRRenderCommand::BIND_BUFFER }; + data.bind_buffer.buffer = buffer; + data.bind_buffer.target = GL_PIXEL_PACK_BUFFER; + curRenderStep_->commands.push_back(data); + } + + void BindIndexBuffer(GLRBuffer *buffer) { // Want to support an offset but can't in ES 2.0. We supply an offset when binding the buffers instead. + _dbg_assert_(G3D, curRenderStep_ && curRenderStep_->stepType == GLRStepType::RENDER); + GLRRenderData data{ GLRRenderCommand::BIND_BUFFER}; + data.bind_buffer.buffer = buffer; + data.bind_buffer.target = GL_ELEMENT_ARRAY_BUFFER; + curRenderStep_->commands.push_back(data); + } + + void BindVertexBuffer(GLRInputLayout *inputLayout, GLRBuffer *buffer, size_t offset) { + _dbg_assert_(G3D, curRenderStep_ && curRenderStep_->stepType == GLRStepType::RENDER); + assert(inputLayout); + GLRRenderData data{ GLRRenderCommand::BIND_VERTEX_BUFFER }; + data.bindVertexBuffer.inputLayout = inputLayout; + data.bindVertexBuffer.offset = offset; + data.bindVertexBuffer.buffer = buffer; + curRenderStep_->commands.push_back(data); + } + + void SetDepth(bool enabled, bool write, GLenum func) { + _dbg_assert_(G3D, curRenderStep_ && curRenderStep_->stepType == GLRStepType::RENDER); + GLRRenderData data{ GLRRenderCommand::DEPTH }; + data.depth.enabled = enabled; + data.depth.write = write; + data.depth.func = func; + curRenderStep_->commands.push_back(data); + } + + void SetViewport(const GLRViewport &vp) { + _dbg_assert_(G3D, curRenderStep_ && curRenderStep_->stepType == GLRStepType::RENDER); + GLRRenderData data{ GLRRenderCommand::VIEWPORT }; + data.viewport.vp = vp; + curRenderStep_->commands.push_back(data); + } + + void SetScissor(const GLRect2D &rc) { + _dbg_assert_(G3D, curRenderStep_ && curRenderStep_->stepType == GLRStepType::RENDER); + GLRRenderData data{ GLRRenderCommand::SCISSOR }; + data.scissor.rc = rc; + curRenderStep_->commands.push_back(data); + } + + void SetUniformI(GLint *loc, int count, const int *udata) { + _dbg_assert_(G3D, curRenderStep_ && curRenderStep_->stepType == GLRStepType::RENDER); + GLRRenderData data{ GLRRenderCommand::UNIFORM4I }; + data.uniform4.loc = loc; + data.uniform4.count = count; + memcpy(data.uniform4.v, udata, sizeof(int) * count); + curRenderStep_->commands.push_back(data); + } + + void SetUniformI1(GLint *loc, int udata) { + _dbg_assert_(G3D, curRenderStep_ && curRenderStep_->stepType == GLRStepType::RENDER); + GLRRenderData data{ GLRRenderCommand::UNIFORM4I }; + data.uniform4.loc = loc; + data.uniform4.count = 1; + memcpy(data.uniform4.v, &udata, sizeof(udata)); + curRenderStep_->commands.push_back(data); + } + + void SetUniformF(GLint *loc, int count, const float *udata) { + _dbg_assert_(G3D, curRenderStep_ && curRenderStep_->stepType == GLRStepType::RENDER); + GLRRenderData data{ GLRRenderCommand::UNIFORM4F }; + data.uniform4.loc = loc; + data.uniform4.count = count; + memcpy(data.uniform4.v, udata, sizeof(float) * count); + curRenderStep_->commands.push_back(data); + } + + void SetUniformF1(GLint *loc, const float udata) { + _dbg_assert_(G3D, curRenderStep_ && curRenderStep_->stepType == GLRStepType::RENDER); + GLRRenderData data{ GLRRenderCommand::UNIFORM4F }; + data.uniform4.loc = loc; + data.uniform4.count = 1; + memcpy(data.uniform4.v, &udata, sizeof(float)); + curRenderStep_->commands.push_back(data); + } + + void SetUniformF(const char *name, int count, const float *udata) { + _dbg_assert_(G3D, curRenderStep_ && curRenderStep_->stepType == GLRStepType::RENDER); + GLRRenderData data{ GLRRenderCommand::UNIFORM4F }; + data.uniform4.name = name; + data.uniform4.count = count; + memcpy(data.uniform4.v, udata, sizeof(float) * count); + curRenderStep_->commands.push_back(data); + } + + void SetUniformM4x4(GLint *loc, const float *udata) { + _dbg_assert_(G3D, curRenderStep_ && curRenderStep_->stepType == GLRStepType::RENDER); + GLRRenderData data{ GLRRenderCommand::UNIFORMMATRIX }; + data.uniformMatrix4.loc = loc; + memcpy(data.uniformMatrix4.m, udata, sizeof(float) * 16); + curRenderStep_->commands.push_back(data); + } + + void SetUniformM4x4(const char *name, const float *udata) { + _dbg_assert_(G3D, curRenderStep_ && curRenderStep_->stepType == GLRStepType::RENDER); + GLRRenderData data{ GLRRenderCommand::UNIFORMMATRIX }; + data.uniformMatrix4.name = name; + memcpy(data.uniformMatrix4.m, udata, sizeof(float) * 16); + curRenderStep_->commands.push_back(data); + } + + void SetBlendAndMask(int colorMask, bool blendEnabled, GLenum srcColor, GLenum dstColor, GLenum srcAlpha, GLenum dstAlpha, GLenum funcColor, GLenum funcAlpha) { + _dbg_assert_(G3D, curRenderStep_ && curRenderStep_->stepType == GLRStepType::RENDER); + GLRRenderData data{ GLRRenderCommand::BLEND }; + data.blend.mask = colorMask; + data.blend.enabled = blendEnabled; + data.blend.srcColor = srcColor; + data.blend.dstColor = dstColor; + data.blend.srcAlpha = srcAlpha; + data.blend.dstAlpha = dstAlpha; + data.blend.funcColor = funcColor; + data.blend.funcAlpha = funcAlpha; + curRenderStep_->commands.push_back(data); + } + + void SetNoBlendAndMask(int colorMask) { + _dbg_assert_(G3D, curRenderStep_ && curRenderStep_->stepType == GLRStepType::RENDER); + GLRRenderData data{ GLRRenderCommand::BLEND }; + data.blend.mask = colorMask; + data.blend.enabled = false; + curRenderStep_->commands.push_back(data); + } + +#ifndef USING_GLES2 + void SetLogicOp(bool enabled, GLenum logicOp) { + _dbg_assert_(G3D, curRenderStep_ && curRenderStep_->stepType == GLRStepType::RENDER); + GLRRenderData data{ GLRRenderCommand::LOGICOP }; + data.logic.enabled = enabled; + data.logic.logicOp = logicOp; + curRenderStep_->commands.push_back(data); + } +#endif + + void SetStencilFunc(bool enabled, GLenum func, uint8_t refValue, uint8_t compareMask) { + _dbg_assert_(G3D, curRenderStep_ && curRenderStep_->stepType == GLRStepType::RENDER); + GLRRenderData data{ GLRRenderCommand::STENCILFUNC }; + data.stencilFunc.enabled = enabled; + data.stencilFunc.func = func; + data.stencilFunc.ref = refValue; + data.stencilFunc.compareMask = compareMask; + curRenderStep_->commands.push_back(data); + } + + void SetStencilOp(uint8_t writeMask, GLenum sFail, GLenum zFail, GLenum pass) { + _dbg_assert_(G3D, curRenderStep_ && curRenderStep_->stepType == GLRStepType::RENDER); + GLRRenderData data{ GLRRenderCommand::STENCILOP }; + data.stencilOp.writeMask = writeMask; + data.stencilOp.sFail = sFail; + data.stencilOp.zFail = zFail; + data.stencilOp.pass = pass; + curRenderStep_->commands.push_back(data); + } + + void SetStencilDisabled() { + _dbg_assert_(G3D, curRenderStep_ && curRenderStep_->stepType == GLRStepType::RENDER); + GLRRenderData data; + data.cmd = GLRRenderCommand::STENCILFUNC; + data.stencilFunc.enabled = false; + curRenderStep_->commands.push_back(data); + } + + void SetBlendFactor(const float color[4]) { + _dbg_assert_(G3D, curRenderStep_ && curRenderStep_->stepType == GLRStepType::RENDER); + GLRRenderData data{ GLRRenderCommand::BLENDCOLOR }; + CopyFloat4(data.blendColor.color, color); + curRenderStep_->commands.push_back(data); + } + + void SetRaster(GLboolean cullEnable, GLenum frontFace, GLenum cullFace, GLboolean ditherEnable) { + _dbg_assert_(G3D, curRenderStep_ && curRenderStep_->stepType == GLRStepType::RENDER); + GLRRenderData data{ GLRRenderCommand::RASTER }; + data.raster.cullEnable = cullEnable; + data.raster.frontFace = frontFace; + data.raster.cullFace = cullFace; + data.raster.ditherEnable = ditherEnable; + curRenderStep_->commands.push_back(data); + } + + // Modifies the current texture as per GL specs, not global state. + void SetTextureSampler(GLenum wrapS, GLenum wrapT, GLenum magFilter, GLenum minFilter, float anisotropy) { + _dbg_assert_(G3D, curRenderStep_ && curRenderStep_->stepType == GLRStepType::RENDER); + GLRRenderData data{ GLRRenderCommand::TEXTURESAMPLER }; + data.textureSampler.wrapS = wrapS; + data.textureSampler.wrapT = wrapT; + data.textureSampler.magFilter = magFilter; + data.textureSampler.minFilter = minFilter; + data.textureSampler.anisotropy = anisotropy; + curRenderStep_->commands.push_back(data); + } + + void SetTextureLod(float minLod, float maxLod, float lodBias) { + _dbg_assert_(G3D, curRenderStep_ && curRenderStep_->stepType == GLRStepType::RENDER); + GLRRenderData data{ GLRRenderCommand::TEXTURELOD}; + data.textureLod.minLod = minLod; + data.textureLod.maxLod = maxLod; + data.textureLod.lodBias = lodBias; + curRenderStep_->commands.push_back(data); + } + + void Clear(uint32_t clearColor, float clearZ, int clearStencil, int clearMask, int colorMask = 0xF) { + _dbg_assert_(G3D, curRenderStep_ && curRenderStep_->stepType == GLRStepType::RENDER); + GLRRenderData data{ GLRRenderCommand::CLEAR }; + data.clear.clearMask = clearMask; + data.clear.clearColor = clearColor; + data.clear.clearZ = clearZ; + data.clear.clearStencil = clearStencil; + data.clear.colorMask = colorMask; + curRenderStep_->commands.push_back(data); + } + + void Invalidate(int invalidateMask) { + _dbg_assert_(G3D, curRenderStep_ && curRenderStep_->stepType == GLRStepType::RENDER); + GLRRenderData data{ GLRRenderCommand::INVALIDATE }; + data.clear.clearMask = invalidateMask; + curRenderStep_->commands.push_back(data); + } + + void Draw(GLenum mode, int first, int count) { + _dbg_assert_(G3D, curRenderStep_ && curRenderStep_->stepType == GLRStepType::RENDER); + GLRRenderData data{ GLRRenderCommand::DRAW }; + data.draw.mode = mode; + data.draw.first = first; + data.draw.count = count; + data.draw.buffer = 0; + curRenderStep_->commands.push_back(data); + curRenderStep_->render.numDraws++; + } + + void DrawIndexed(GLenum mode, int count, GLenum indexType, void *indices, int instances = 1) { + _dbg_assert_(G3D, curRenderStep_ && curRenderStep_->stepType == GLRStepType::RENDER); + GLRRenderData data{ GLRRenderCommand::DRAW_INDEXED }; + data.drawIndexed.mode = mode; + data.drawIndexed.count = count; + data.drawIndexed.indexType = indexType; + data.drawIndexed.instances = instances; + data.drawIndexed.indices = indices; + curRenderStep_->commands.push_back(data); + curRenderStep_->render.numDraws++; + } + + enum { MAX_INFLIGHT_FRAMES = 3 }; + + int GetCurFrame() const { + return curFrame_; + } + + void Resize(int width, int height) { + targetWidth_ = width; + targetHeight_ = height; + queueRunner_.Resize(width, height); + } + + // When using legacy functionality for push buffers (glBufferData), we need to flush them + // before actually making the glDraw* calls. It's best if the render manager handles that. + void RegisterPushBuffer(int frame, GLPushBuffer *buffer) { + frameData_[frame].activePushBuffers.insert(buffer); + } + void UnregisterPushBuffer(int frame, GLPushBuffer *buffer) { + auto iter = frameData_[frame].activePushBuffers.find(buffer); + _assert_(iter != frameData_[frame].activePushBuffers.end()); + frameData_[frame].activePushBuffers.erase(iter); + } + + void SetSwapFunction(std::function swapFunction) { + swapFunction_ = swapFunction; + } + + void Swap() { + if (!useThread_ && swapFunction_) { + swapFunction_(); + } + } + + void StopThread(); + + bool SawOutOfMemory() { + return queueRunner_.SawOutOfMemory(); + } + + // Only supports a common subset. + std::string GetGLString(int name) const { + return queueRunner_.GetGLString(name); + } + +private: + void BeginSubmitFrame(int frame); + void EndSubmitFrame(int frame); + void Submit(int frame, bool triggerFence); + + // Bad for performance but sometimes necessary for synchronous CPU readbacks (screenshots and whatnot). + void FlushSync(); + void EndSyncFrame(int frame); + + // Per-frame data, round-robin so we can overlap submission with execution of the previous frame. + struct FrameData { + std::mutex push_mutex; + std::condition_variable push_condVar; + + std::mutex pull_mutex; + std::condition_variable pull_condVar; + + bool readyForFence = true; + bool readyForRun = false; + bool readyForSubmit = false; + bool skipSwap = false; + GLRRunType type = GLRRunType::END; + + // GLuint fence; For future AZDO stuff? + std::vector steps; + std::vector initSteps; + + // Swapchain. + bool hasBegun = false; + uint32_t curSwapchainImage = -1; + + std::mutex deleter_mutex; + GLDeleter deleter; + GLDeleter deleter_prev; + + std::set activePushBuffers; + }; + + FrameData frameData_[MAX_INFLIGHT_FRAMES]; + + // Submission time state + bool insideFrame_ = false; + GLRStep *curRenderStep_ = nullptr; + std::vector steps_; + std::vector initSteps_; + + // Execution time state + bool run_ = true; + // Thread is managed elsewhere, and should call ThreadFrame. + std::mutex mutex_; + int threadInitFrame_ = 0; + GLQueueRunner queueRunner_; + + // Thread state + int threadFrame_; + + bool nextFrame = false; + bool firstFrame = true; + + bool useThread_ = true; + + int curFrame_ = 0; + + std::function swapFunction_; + + int targetWidth_ = 0; + int targetHeight_ = 0; +}; + + +// Similar to VulkanPushBuffer but uses really stupid tactics - collect all the data in RAM then do a big +// memcpy/buffer upload at the end. This can however be optimized with glBufferStorage on chips that support that +// for massive boosts. +class GLPushBuffer { +public: + struct BufInfo { + GLRBuffer *buffer; + uint8_t *deviceMemory; + }; + +public: + GLPushBuffer(GLRenderManager *render, GLuint target, size_t size); + ~GLPushBuffer(); + + void Destroy(); + + void Reset() { offset_ = 0; } + + // Needs context in case of defragment. + void Begin() { + buf_ = 0; + offset_ = 0; + // Note: we must defrag because some buffers may be smaller than size_. + Defragment(); + Map(); + assert(writePtr_); + } + + void BeginNoReset() { + Map(); + } + + void End() { + Unmap(); + } + + void Map(); + void Unmap(); + + // When using the returned memory, make sure to bind the returned vkbuf. + // This will later allow for handling overflow correctly. + size_t Allocate(size_t numBytes, GLRBuffer **vkbuf) { + size_t out = offset_; + if (offset_ + ((numBytes + 3) & ~3) >= size_) { + NextBuffer(numBytes); + out = offset_; + offset_ += (numBytes + 3) & ~3; + } else { + offset_ += (numBytes + 3) & ~3; // Round up to 4 bytes. + } + *vkbuf = buffers_[buf_].buffer; + return out; + } + + // Returns the offset that should be used when binding this buffer to get this data. + size_t Push(const void *data, size_t size, GLRBuffer **vkbuf) { + assert(writePtr_); + size_t off = Allocate(size, vkbuf); + memcpy(writePtr_ + off, data, size); + return off; + } + + uint32_t PushAligned(const void *data, size_t size, int align, GLRBuffer **vkbuf) { + assert(writePtr_); + offset_ = (offset_ + align - 1) & ~(align - 1); + size_t off = Allocate(size, vkbuf); + memcpy(writePtr_ + off, data, size); + return (uint32_t)off; + } + + size_t GetOffset() const { + return offset_; + } + + // "Zero-copy" variant - you can write the data directly as you compute it. + // Recommended. + void *Push(size_t size, uint32_t *bindOffset, GLRBuffer **vkbuf) { + assert(writePtr_); + size_t off = Allocate(size, vkbuf); + *bindOffset = (uint32_t)off; + return writePtr_ + off; + } + void *PushAligned(size_t size, uint32_t *bindOffset, GLRBuffer **vkbuf, int align) { + assert(writePtr_); + offset_ = (offset_ + align - 1) & ~(align - 1); + size_t off = Allocate(size, vkbuf); + *bindOffset = (uint32_t)off; + return writePtr_ + off; + } + + size_t GetTotalSize() const; + + void Flush(); + +private: + bool AddBuffer(); + void NextBuffer(size_t minSize); + void Defragment(); + + GLRenderManager *render_; + std::vector buffers_; + size_t buf_ = 0; + size_t offset_ = 0; + size_t size_ = 0; + uint8_t *writePtr_ = nullptr; + GLuint target_; +}; diff --git a/ext/native/thin3d/VulkanQueueRunner.cpp b/ext/native/thin3d/VulkanQueueRunner.cpp index e693dc834a..5cd4008386 100644 --- a/ext/native/thin3d/VulkanQueueRunner.cpp +++ b/ext/native/thin3d/VulkanQueueRunner.cpp @@ -391,7 +391,7 @@ void VulkanQueueRunner::PerformRenderPass(const VKRStep &step, VkCommandBuffer c } // Don't execute empty renderpasses. - if (step.commands.empty() && step.render.color == VKRRenderPassAction::KEEP && step.render.depthStencil == VKRRenderPassAction::KEEP) { + if (step.commands.empty() && step.render.color == VKRRenderPassAction::KEEP && step.render.depth == VKRRenderPassAction::KEEP && step.render.stencil == VKRRenderPassAction::KEEP) { // Nothing to do. return; } @@ -610,12 +610,12 @@ void VulkanQueueRunner::PerformBindFramebufferAsRenderTarget(const VKRStep &step fb->depth.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; } - renderPass = GetRenderPass(step.render.color, step.render.depthStencil, step.render.depthStencil); + renderPass = GetRenderPass(step.render.color, step.render.depth, step.render.stencil); if (step.render.color == VKRRenderPassAction::CLEAR) { Uint8x4ToFloat4(clearVal[0].color.float32, step.render.clearColor); numClearVals = 1; } - if (step.render.depthStencil == VKRRenderPassAction::CLEAR) { + if (step.render.depth == VKRRenderPassAction::CLEAR || step.render.stencil == VKRRenderPassAction::CLEAR) { clearVal[1].depthStencil.depth = step.render.clearDepth; clearVal[1].depthStencil.stencil = step.render.clearStencil; numClearVals = 2; diff --git a/ext/native/thin3d/VulkanQueueRunner.h b/ext/native/thin3d/VulkanQueueRunner.h index 1049a5bb80..b52b150fa3 100644 --- a/ext/native/thin3d/VulkanQueueRunner.h +++ b/ext/native/thin3d/VulkanQueueRunner.h @@ -108,7 +108,8 @@ struct VKRStep { struct { VKRFramebuffer *framebuffer; VKRRenderPassAction color; - VKRRenderPassAction depthStencil; + VKRRenderPassAction depth; + VKRRenderPassAction stencil; uint32_t clearColor; float clearDepth; int clearStencil; diff --git a/ext/native/thin3d/VulkanRenderManager.cpp b/ext/native/thin3d/VulkanRenderManager.cpp index ab9bbafc22..ef262e9dc1 100644 --- a/ext/native/thin3d/VulkanRenderManager.cpp +++ b/ext/native/thin3d/VulkanRenderManager.cpp @@ -389,16 +389,16 @@ VkCommandBuffer VulkanRenderManager::GetInitCmd() { return frameData_[curFrame].initCmd; } -void VulkanRenderManager::BindFramebufferAsRenderTarget(VKRFramebuffer *fb, VKRRenderPassAction color, VKRRenderPassAction depth, uint32_t clearColor, float clearDepth, uint8_t clearStencil) { +void VulkanRenderManager::BindFramebufferAsRenderTarget(VKRFramebuffer *fb, VKRRenderPassAction color, VKRRenderPassAction depth, VKRRenderPassAction stencil, uint32_t clearColor, float clearDepth, uint8_t clearStencil) { assert(insideFrame_); // Eliminate dupes. if (steps_.size() && steps_.back()->render.framebuffer == fb && steps_.back()->stepType == VKRStepType::RENDER) { - if (color != VKRRenderPassAction::CLEAR && depth != VKRRenderPassAction::CLEAR) { + if (color != VKRRenderPassAction::CLEAR && depth != VKRRenderPassAction::CLEAR && stencil != VKRRenderPassAction::CLEAR) { // We don't move to a new step, this bind was unnecessary and we can safely skip it. return; } } - if (curRenderStep_ && curRenderStep_->commands.size() == 0 && curRenderStep_->render.color == VKRRenderPassAction::KEEP && curRenderStep_->render.depthStencil == VKRRenderPassAction::KEEP) { + if (curRenderStep_ && curRenderStep_->commands.size() == 0 && curRenderStep_->render.color == VKRRenderPassAction::KEEP && curRenderStep_->render.depth == VKRRenderPassAction::KEEP && curRenderStep_->render.stencil == VKRRenderPassAction::KEEP) { // Can trivially kill the last empty render step. assert(steps_.back() == curRenderStep_); delete steps_.back(); @@ -413,7 +413,8 @@ void VulkanRenderManager::BindFramebufferAsRenderTarget(VKRFramebuffer *fb, VKRR // This is what queues up new passes, and can end previous ones. step->render.framebuffer = fb; step->render.color = color; - step->render.depthStencil = depth; + step->render.depth= depth; + step->render.stencil = stencil; step->render.clearColor = clearColor; step->render.clearDepth = clearDepth; step->render.clearStencil = clearStencil; @@ -617,7 +618,8 @@ void VulkanRenderManager::Clear(uint32_t clearColor, float clearZ, int clearSten curRenderStep_->render.clearDepth = clearZ; curRenderStep_->render.clearStencil = clearStencil; curRenderStep_->render.color = (clearMask & VK_IMAGE_ASPECT_COLOR_BIT) ? VKRRenderPassAction::CLEAR : VKRRenderPassAction::KEEP; - curRenderStep_->render.depthStencil = (clearMask & (VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT)) ? VKRRenderPassAction::CLEAR : VKRRenderPassAction::KEEP; + curRenderStep_->render.depth = (clearMask & VK_IMAGE_ASPECT_DEPTH_BIT) ? VKRRenderPassAction::CLEAR : VKRRenderPassAction::KEEP; + curRenderStep_->render.stencil = (clearMask & VK_IMAGE_ASPECT_STENCIL_BIT) ? VKRRenderPassAction::CLEAR : VKRRenderPassAction::KEEP; } else { VkRenderData data{ VKRRenderCommand::CLEAR }; data.clear.clearColor = clearColor; diff --git a/ext/native/thin3d/VulkanRenderManager.h b/ext/native/thin3d/VulkanRenderManager.h index 322738d44f..19e5cc8cd0 100644 --- a/ext/native/thin3d/VulkanRenderManager.h +++ b/ext/native/thin3d/VulkanRenderManager.h @@ -99,7 +99,7 @@ public: // Zaps queued up commands. Use if you know there's a risk you've queued up stuff that has already been deleted. Can happen during in-game shutdown. void Wipe(); - void BindFramebufferAsRenderTarget(VKRFramebuffer *fb, VKRRenderPassAction color, VKRRenderPassAction depth, uint32_t clearColor, float clearDepth, uint8_t clearStencil); + void BindFramebufferAsRenderTarget(VKRFramebuffer *fb, VKRRenderPassAction color, VKRRenderPassAction depth, VKRRenderPassAction stencil, uint32_t clearColor, float clearDepth, uint8_t clearStencil); VkImageView BindFramebufferAsTexture(VKRFramebuffer *fb, int binding, int aspectBit, int attachment); bool CopyFramebufferToMemorySync(VKRFramebuffer *src, int aspectBits, int x, int y, int w, int h, Draw::DataFormat destFormat, uint8_t *pixels, int pixelStride); void CopyImageToMemorySync(VkImage image, int mipLevel, int x, int y, int w, int h, Draw::DataFormat destFormat, uint8_t *pixels, int pixelStride); diff --git a/ext/native/thin3d/thin3d.h b/ext/native/thin3d/thin3d.h index 9c993727d2..46f6aaf38e 100644 --- a/ext/native/thin3d/thin3d.h +++ b/ext/native/thin3d/thin3d.h @@ -589,7 +589,10 @@ public: // color must be 0, for now. virtual void BindFramebufferAsTexture(Framebuffer *fbo, int binding, FBChannel channelBit, int attachment) = 0; - virtual uintptr_t GetFramebufferAPITexture(Framebuffer *fbo, int channelBits, int attachment) = 0; + // deprecated + virtual uintptr_t GetFramebufferAPITexture(Framebuffer *fbo, int channelBits, int attachment) { + return 0; + } virtual void GetFramebufferDimensions(Framebuffer *fbo, int *w, int *h) = 0; @@ -632,7 +635,7 @@ public: 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) { + virtual void SetTargetSize(int w, int h) { targetWidth_ = w; targetHeight_ = h; } diff --git a/ext/native/thin3d/thin3d_gl.cpp b/ext/native/thin3d/thin3d_gl.cpp index 95f9f6532a..b831e199a0 100644 --- a/ext/native/thin3d/thin3d_gl.cpp +++ b/ext/native/thin3d/thin3d_gl.cpp @@ -9,23 +9,15 @@ #include "math/dataconv.h" #include "math/lin/matrix4x4.h" #include "thin3d/thin3d.h" +#include "thin3d/DataFormatGL.h" #include "gfx/gl_common.h" #include "gfx/gl_debug_log.h" -#include "gfx/GLStateCache.h" #include "gfx_es2/gpu_features.h" -#ifdef IOS -extern void bindDefaultFBO(); -#endif +#include "thin3d/GLRenderManager.h" // #define DEBUG_READ_PIXELS 1 -// Workaround for Retroarch. Simply declare -// extern GLuint g_defaultFBO; -// and set is as appropriate. Can adjust the variables in ext/native/base/display.h as -// appropriate. -GLuint g_defaultFBO = 0; - namespace Draw { static const unsigned short compToGL[] = { @@ -170,29 +162,11 @@ public: bool enabled; GLuint eqCol, eqAlpha; GLuint srcCol, srcAlpha, dstCol, dstAlpha; - bool logicEnabled; - GLuint logicOp; int colorMask; // uint32_t fixedColor; - void Apply() { - if (enabled) { - glEnable(GL_BLEND); - glBlendEquationSeparate(eqCol, eqAlpha); - glBlendFuncSeparate(srcCol, dstCol, srcAlpha, dstAlpha); - } else { - glDisable(GL_BLEND); - } - glColorMask(colorMask & 1, (colorMask >> 1) & 1, (colorMask >> 2) & 1, (colorMask >> 3) & 1); - -#if !defined(USING_GLES2) - if (logicEnabled) { - glEnable(GL_COLOR_LOGIC_OP); - glLogicOp(logicOp); - } else { - glDisable(GL_COLOR_LOGIC_OP); - } -#endif + void Apply(GLRenderManager *render) { + render->SetBlendAndMask(colorMask, enabled, srcCol, dstCol, srcAlpha, dstAlpha, eqCol, eqAlpha); } }; @@ -212,7 +186,7 @@ public: bool depthWriteEnabled; GLuint depthComp; // TODO: Two-sided - GLboolean stencilEnabled; + bool stencilEnabled; GLuint stencilFail; GLuint stencilZFail; GLuint stencilPass; @@ -221,36 +195,17 @@ public: uint8_t stencilCompareMask; uint8_t stencilWriteMask; - void Apply() { - if (depthTestEnabled) { - glEnable(GL_DEPTH_TEST); - glDepthFunc(depthComp); - glDepthMask(depthWriteEnabled); - } else { - glDisable(GL_DEPTH_TEST); - } - if (stencilEnabled) { - glEnable(GL_STENCIL_TEST); - glStencilOpSeparate(GL_FRONT_AND_BACK, stencilFail, stencilZFail, stencilPass); - glStencilFuncSeparate(GL_FRONT_AND_BACK, stencilCompareOp, stencilReference, stencilCompareMask); - glStencilMaskSeparate(GL_FRONT_AND_BACK, stencilWriteMask); - } else { - glDisable(GL_STENCIL_TEST); - } + void Apply(GLRenderManager *render) { + render->SetDepth(depthTestEnabled, depthWriteEnabled, depthComp); + render->SetStencilFunc(stencilEnabled, stencilCompareOp, stencilReference, stencilCompareMask); + render->SetStencilOp(stencilWriteMask, stencilFail, stencilZFail, stencilPass); } }; class OpenGLRasterState : public RasterState { public: - void Apply() { - glEnable(GL_SCISSOR_TEST); - if (!cullEnable) { - glDisable(GL_CULL_FACE); - return; - } - glEnable(GL_CULL_FACE); - glFrontFace(frontFace); - glCullFace(cullMode); + void Apply(GLRenderManager *render) { + render->SetRaster(cullEnable, frontFace, cullMode, false); } GLboolean cullEnable; @@ -275,17 +230,18 @@ GLuint ShaderStageToOpenGL(ShaderStage stage) { class OpenGLShaderModule : public ShaderModule { public: - OpenGLShaderModule(ShaderStage stage) : stage_(stage) { + OpenGLShaderModule(GLRenderManager *render, ShaderStage stage) : render_(render), stage_(stage) { + ILOG("Shader module created (%p)", this); glstage_ = ShaderStageToOpenGL(stage); } ~OpenGLShaderModule() { if (shader_) - glDeleteShader(shader_); + render_->DeleteShader(shader_); } - bool Compile(ShaderLanguage language, const uint8_t *data, size_t dataSize); - GLuint GetShader() const { + bool Compile(GLRenderManager *render, ShaderLanguage language, const uint8_t *data, size_t dataSize); + GLRShader *GetShader() const { return shader_; } const std::string &GetSource() const { return source_; } @@ -298,19 +254,17 @@ public: } private: + GLRenderManager *render_; ShaderStage stage_; ShaderLanguage language_; - GLuint shader_ = 0; + GLRShader *shader_ = nullptr; GLuint glstage_ = 0; bool ok_ = false; std::string source_; // So we can recompile in case of context loss. }; -bool OpenGLShaderModule::Compile(ShaderLanguage language, const uint8_t *data, size_t dataSize) { +bool OpenGLShaderModule::Compile(GLRenderManager *render, ShaderLanguage language, const uint8_t *data, size_t dataSize) { source_ = std::string((const char *)data); - shader_ = glCreateShader(glstage_); - language_ = language; - std::string temp; // Add the prelude on automatically. if (glstage_ == GL_FRAGMENT_SHADER || glstage_ == GL_VERTEX_SHADER) { @@ -318,59 +272,35 @@ bool OpenGLShaderModule::Compile(ShaderLanguage language, const uint8_t *data, s source_ = temp.c_str(); } - const char *code = source_.c_str(); - glShaderSource(shader_, 1, &code, nullptr); - glCompileShader(shader_); - GLint success = 0; - glGetShaderiv(shader_, GL_COMPILE_STATUS, &success); - if (!success) { -#define MAX_INFO_LOG_SIZE 2048 - GLchar infoLog[MAX_INFO_LOG_SIZE]; - GLsizei len = 0; - glGetShaderInfoLog(shader_, MAX_INFO_LOG_SIZE, &len, infoLog); - infoLog[len] = '\0'; - glDeleteShader(shader_); - shader_ = 0; - ILOG("%s Shader compile error:\n%s", glstage_ == GL_FRAGMENT_SHADER ? "Fragment" : "Vertex", infoLog); - } - ok_ = success != 0; - return ok_; + shader_ = render->CreateShader(glstage_, source_, "thin3d"); + return true; } class OpenGLInputLayout : public InputLayout { public: + OpenGLInputLayout(GLRenderManager *render) : render_(render) {} ~OpenGLInputLayout(); - void Apply(const void *base = nullptr); - void Unapply(); - void Compile(); + void Compile(const InputLayoutDesc &desc); bool RequiresBuffer() { - return id_ != 0; + return false; } - InputLayoutDesc desc; - - int semanticsMask_; // Fast way to check what semantics to enable/disable. - int stride_; - GLuint id_; - bool needsEnable_; - intptr_t lastBase_; -}; - -struct UniformInfo { - int loc_; + GLRInputLayout *inputLayout_; + int stride; +private: + GLRenderManager *render_; }; class OpenGLPipeline : public Pipeline { public: - OpenGLPipeline() { - program_ = 0; + OpenGLPipeline(GLRenderManager *render) : render_(render) { } ~OpenGLPipeline() { for (auto &iter : shaders) { iter->Release(); } - glDeleteProgram(program_); + render_->DeleteProgram(program_); if (depthStencil) depthStencil->Release(); if (blend) blend->Release(); if (raster) raster->Release(); @@ -379,8 +309,6 @@ public: bool LinkShaders(); - int GetUniformLoc(const char *name); - bool RequiresBuffer() override { return inputLayout->RequiresBuffer(); } @@ -395,9 +323,9 @@ public: // TODO: Optimize by getting the locations first and putting in a custom struct UniformBufferDesc dynamicUniforms; - GLuint program_; + GLRProgram *program_ = nullptr; private: - std::map uniformCache_; + GLRenderManager *render_; }; class OpenGLFramebuffer; @@ -408,6 +336,11 @@ public: OpenGLContext(); virtual ~OpenGLContext(); + void SetTargetSize(int w, int h) override { + DrawContext::SetTargetSize(w, h); + renderManager_.Resize(w, h); + } + const DeviceCaps &GetDeviceCaps() const override { return caps_; } @@ -432,6 +365,9 @@ public: Buffer *CreateBuffer(size_t size, uint32_t usageFlags) override; Framebuffer *CreateFramebuffer(const FramebufferDesc &desc) override; + void BeginFrame() override; + void EndFrame() override; + void UpdateBuffer(Buffer *buffer, const uint8_t *data, size_t offset, size_t size, UpdateBufferFlags flags) override; void CopyFramebufferImage(Framebuffer *src, int level, int x, int y, int z, Framebuffer *dst, int dstLevel, int dstX, int dstY, int dstZ, int width, int height, int depth, int channelBits) override; @@ -443,8 +379,6 @@ public: // color must be 0, for now. void BindFramebufferAsTexture(Framebuffer *fbo, int binding, FBChannel channelBit, int attachment) override; - uintptr_t GetFramebufferAPITexture(Framebuffer *fbo, int channelBits, int attachment) override; - void GetFramebufferDimensions(Framebuffer *fbo, int *w, int *h) override; void BindSamplerStates(int start, int count, SamplerState **states) override { @@ -458,26 +392,16 @@ public: } void SetScissorRect(int left, int top, int width, int height) override { - int y = top; - if (!curFB_) { - // We render "upside down" to the backbuffer since GL is silly. - y = targetHeight_ - (top + height); - } - glstate.scissorRect.set(left, y, width, height); + renderManager_.SetScissor({ left, top, width, height }); } void SetViewports(int count, Viewport *viewports) override { - // TODO: Use glViewportArrayv. - glViewport((GLint)viewports[0].TopLeftX, (GLint)viewports[0].TopLeftY, (GLsizei)viewports[0].Width, (GLsizei)viewports[0].Height); -#if defined(USING_GLES2) - glDepthRangef(viewports[0].MinDepth, viewports[0].MaxDepth); -#else - glDepthRange(viewports[0].MinDepth, viewports[0].MaxDepth); -#endif + // Same structure, different name. + renderManager_.SetViewport((GLRViewport &)*viewports); } void SetBlendFactor(float color[4]) override { - glBlendColor(color[0], color[1], color[2], color[3]); + renderManager_.SetBlendFactor(color); } void BindTextures(int start, int count, Texture **textures) override; @@ -511,7 +435,7 @@ public: } else { return "OpenGL"; } - case VENDORSTRING: return (const char *)glGetString(GL_VENDOR); + case VENDORSTRING: return renderManager_.GetGLString(GL_VENDOR); case VENDOR: switch (caps_.vendor) { case GPUVendor::VENDOR_AMD: return "VENDOR_AMD"; @@ -526,26 +450,29 @@ public: return "VENDOR_UNKNOWN"; } break; - case DRIVER: return (const char *)glGetString(GL_RENDERER); - case SHADELANGVERSION: return (const char *)glGetString(GL_SHADING_LANGUAGE_VERSION); - case APIVERSION: return (const char *)glGetString(GL_VERSION); + case DRIVER: return renderManager_.GetGLString(GL_RENDERER); + case SHADELANGVERSION: return renderManager_.GetGLString(GL_SHADING_LANGUAGE_VERSION); + case APIVERSION: return renderManager_.GetGLString(GL_VERSION); default: return "?"; } } uintptr_t GetNativeObject(NativeObject obj) override { - return 0; + switch (obj) { + case NativeObject::RENDER_MANAGER: + return (uintptr_t)&renderManager_; + default: + return 0; + } } void HandleEvent(Event ev, int width, int height, void *param1, void *param2) override {} private: - OpenGLFramebuffer *fbo_ext_create(const FramebufferDesc &desc); - void fbo_bind_fb_target(bool read, GLuint name); - GLenum fbo_get_fb_target(bool read, GLuint **cached); - void fbo_unbind(); void ApplySamplers(); + GLRenderManager renderManager_; + std::vector boundSamplers_; OpenGLTexture *boundTextures_[8]{}; int maxTextures_ = 0; @@ -557,11 +484,13 @@ private: int curVBufferOffsets_[4]{}; OpenGLBuffer *curIBuffer_ = nullptr; int curIBufferOffset_ = 0; - OpenGLFramebuffer *curFB_; - // Framebuffer state - GLuint currentDrawHandle_ = 0; - GLuint currentReadHandle_ = 0; + // Frames in flight is not such a strict concept as with Vulkan until we start using glBufferStorage and fences. + // But might as well have the structure ready, and can't hurt to rotate buffers. + struct FrameData { + GLPushBuffer *push; + }; + FrameData frameData_[GLRenderManager::MAX_INFLIGHT_FRAMES]; }; OpenGLContext::OpenGLContext() { @@ -590,17 +519,37 @@ OpenGLContext::OpenGLContext() { default: caps_.vendor = GPUVendor::VENDOR_UNKNOWN; break; + } + for (int i = 0; i < GLRenderManager::MAX_INFLIGHT_FRAMES; i++) { + frameData_[i].push = new GLPushBuffer(&renderManager_, GL_ARRAY_BUFFER, 64 * 1024); + renderManager_.RegisterPushBuffer(i, frameData_[i].push); } } OpenGLContext::~OpenGLContext() { + for (int i = 0; i < GLRenderManager::MAX_INFLIGHT_FRAMES; i++) { + renderManager_.UnregisterPushBuffer(i, frameData_[i].push); + frameData_[i].push->Destroy(); + delete frameData_[i].push; + } boundSamplers_.clear(); } +void OpenGLContext::BeginFrame() { + renderManager_.BeginFrame(); + FrameData &frameData = frameData_[renderManager_.GetCurFrame()]; + frameData.push->Begin(); +} + +void OpenGLContext::EndFrame() { + FrameData &frameData = frameData_[renderManager_.GetCurFrame()]; + frameData.push->End(); // upload the data! + renderManager_.Finish(); +} + InputLayout *OpenGLContext::CreateInputLayout(const InputLayoutDesc &desc) { - OpenGLInputLayout *fmt = new OpenGLInputLayout(); - fmt->desc = desc; - fmt->Compile(); + OpenGLInputLayout *fmt = new OpenGLInputLayout(&renderManager_); + fmt->Compile(desc); return fmt; } @@ -628,7 +577,7 @@ inline bool isPowerOf2(int n) { class OpenGLTexture : public Texture { public: - OpenGLTexture(const TextureDesc &desc); + OpenGLTexture(GLRenderManager *render, const TextureDesc &desc); ~OpenGLTexture(); bool HasMips() const { @@ -638,17 +587,15 @@ public: return canWrap_; } TextureType GetType() const { return type_; } - void Bind() { - glBindTexture(target_, tex_); + void Bind(int stage) { + render_->BindTexture(stage, tex_); } - void AutoGenMipmaps(); - private: void SetImageData(int x, int y, int z, int width, int height, int depth, int level, int stride, const uint8_t *data); - GLuint tex_ = 0; - GLuint target_ = 0; + GLRenderManager *render_; + GLRTexture *tex_; DataFormat format_; TextureType type_; @@ -657,7 +604,7 @@ private: bool canWrap_; }; -OpenGLTexture::OpenGLTexture(const TextureDesc &desc) { +OpenGLTexture::OpenGLTexture(GLRenderManager *render, const TextureDesc &desc) : render_(render) { generatedMips_ = false; canWrap_ = true; width_ = desc.width; @@ -665,16 +612,14 @@ OpenGLTexture::OpenGLTexture(const TextureDesc &desc) { depth_ = desc.depth; format_ = desc.format; type_ = desc.type; - target_ = TypeToTarget(desc.type); + GLenum target = TypeToTarget(desc.type); + tex_ = render->CreateTexture(target); + canWrap_ = isPowerOf2(width_) && isPowerOf2(height_); mipLevels_ = desc.mipLevels; if (!desc.initData.size()) return; - glActiveTexture(GL_TEXTURE0 + 0); - glGenTextures(1, &tex_); - glBindTexture(target_, tex_); - int level = 0; for (auto data : desc.initData) { SetImageData(0, 0, 0, width_, height_, depth_, level, 0, data); @@ -684,141 +629,38 @@ OpenGLTexture::OpenGLTexture(const TextureDesc &desc) { } mipLevels_ = desc.generateMips ? desc.mipLevels : level; -#ifdef USING_GLES2 - if (gl_extensions.GLES3) { - glTexParameteri(target_, GL_TEXTURE_MAX_LEVEL, mipLevels_ - 1); - } -#else - glTexParameteri(target_, GL_TEXTURE_MAX_LEVEL, mipLevels_ - 1); -#endif - glTexParameteri(target_, GL_TEXTURE_MIN_FILTER, mipLevels_ > 1 ? GL_LINEAR_MIPMAP_LINEAR : GL_LINEAR); - glTexParameteri(target_, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - + bool genMips = false; if ((int)desc.initData.size() < desc.mipLevels && desc.generateMips) { ILOG("Generating mipmaps"); - AutoGenMipmaps(); + // Assumes the texture is bound for editing + genMips = true; + generatedMips_ = true; } + render->FinalizeTexture(tex_, mipLevels_, genMips); - // Unbind. - glBindTexture(target_, 0); } OpenGLTexture::~OpenGLTexture() { if (tex_) { - glDeleteTextures(1, &tex_); + render_->DeleteTexture(tex_); tex_ = 0; generatedMips_ = false; } } -void OpenGLTexture::AutoGenMipmaps() { - if (!generatedMips_) { - glBindTexture(target_, tex_); - glGenerateMipmap(target_); - generatedMips_ = true; - } -} - class OpenGLFramebuffer : public Framebuffer { public: - OpenGLFramebuffer() {} - ~OpenGLFramebuffer(); + OpenGLFramebuffer(GLRenderManager *render) : render_(render) {} + ~OpenGLFramebuffer() { + render_->DeleteFramebuffer(framebuffer); + } - GLuint handle = 0; - GLuint color_texture = 0; - GLuint z_stencil_buffer = 0; // Either this is set, or the two below. - GLuint z_buffer = 0; - GLuint stencil_buffer = 0; + GLRenderManager *render_; + GLRFramebuffer *framebuffer; - int width; - int height; FBColorDepth colorDepth; }; - -// TODO: Also output storage format (GL_RGBA8 etc) for modern GL usage. -static bool Thin3DFormatToFormatAndType(DataFormat fmt, GLuint &internalFormat, GLuint &format, GLuint &type, int &alignment) { - alignment = 4; - switch (fmt) { - case DataFormat::R8G8B8A8_UNORM: - internalFormat = GL_RGBA; - format = GL_RGBA; - type = GL_UNSIGNED_BYTE; - break; - - case DataFormat::D32F: - internalFormat = GL_DEPTH_COMPONENT; - format = GL_DEPTH_COMPONENT; - type = GL_FLOAT; - break; - -#ifndef USING_GLES2 - case DataFormat::S8: - internalFormat = GL_STENCIL_INDEX; - format = GL_STENCIL_INDEX; - type = GL_UNSIGNED_BYTE; - alignment = 1; - break; -#endif - - case DataFormat::R8G8B8_UNORM: - internalFormat = GL_RGB; - format = GL_RGB; - type = GL_UNSIGNED_BYTE; - alignment = 1; - break; - - case DataFormat::B4G4R4A4_UNORM_PACK16: - internalFormat = GL_RGBA; - format = GL_RGBA; - type = GL_UNSIGNED_SHORT_4_4_4_4; - alignment = 2; - break; - - case DataFormat::B5G6R5_UNORM_PACK16: - internalFormat = GL_RGB; - format = GL_RGB; - type = GL_UNSIGNED_SHORT_5_6_5; - alignment = 2; - break; - - case DataFormat::B5G5R5A1_UNORM_PACK16: - internalFormat = GL_RGBA; - format = GL_RGBA; - type = GL_UNSIGNED_SHORT_5_5_5_1; - alignment = 2; - break; - -#ifndef USING_GLES2 - case DataFormat::A4R4G4B4_UNORM_PACK16: - internalFormat = GL_RGBA; - format = GL_RGBA; - type = GL_UNSIGNED_SHORT_4_4_4_4_REV; - alignment = 2; - break; - - case DataFormat::R5G6B5_UNORM_PACK16: - internalFormat = GL_RGB; - format = GL_RGB; - type = GL_UNSIGNED_SHORT_5_6_5_REV; - alignment = 2; - break; - - case DataFormat::A1R5G5B5_UNORM_PACK16: - internalFormat = GL_RGBA; - format = GL_RGBA; - type = GL_UNSIGNED_SHORT_1_5_5_5_REV; - alignment = 2; - break; -#endif - - default: - ELOG("Thin3d GL: Unsupported texture format %d", (int)fmt); - return false; - } - return true; -} - void OpenGLTexture::SetImageData(int x, int y, int z, int width, int height, int depth, int level, int stride, const uint8_t *data) { if (width != width_ || height != height_ || depth != depth_) { // When switching to texStorage we need to handle this correctly. @@ -835,18 +677,16 @@ void OpenGLTexture::SetImageData(int x, int y, int z, int width, int height, int return; } - CHECK_GL_ERROR_IF_DEBUG(); - switch (target_) { - case GL_TEXTURE_2D: - glTexImage2D(GL_TEXTURE_2D, level, internalFormat, width_, height_, 0, format, type, data); - break; - default: - ELOG("Thin3D GL: Targets other than GL_TEXTURE_2D not yet supported"); - break; - } - CHECK_GL_ERROR_IF_DEBUG(); -} + if (stride == 0) + stride = width; + // Make a copy of data with stride eliminated. + uint8_t *texData = new uint8_t[width * height * alignment]; + for (int y = 0; y < height; y++) { + memcpy(texData + y * width * alignment, data + y * stride * alignment, width * alignment); + } + render_->TextureImage(tex_, level, width, height, internalFormat, format, type, texData); +} #ifdef DEBUG_READ_PIXELS // TODO: Make more generic. @@ -886,65 +726,20 @@ static void LogReadPixelsError(GLenum error) { bool OpenGLContext::CopyFramebufferToMemorySync(Framebuffer *src, int channelBits, int x, int y, int w, int h, Draw::DataFormat dataFormat, void *pixels, int pixelStride) { OpenGLFramebuffer *fb = (OpenGLFramebuffer *)src; - fbo_bind_fb_target(true, fb ? fb->handle : 0); - - // Reads from the "bound for read" framebuffer. - if (gl_extensions.GLES3 || !gl_extensions.IsGLES) - glReadBuffer(GL_COLOR_ATTACHMENT0); - - CHECK_GL_ERROR_IF_DEBUG(); - - GLuint internalFormat; - GLuint format; - GLuint type; - int alignment; - if (!Thin3DFormatToFormatAndType(dataFormat, internalFormat, format, type, alignment)) { - assert(false); - } - // Apply the correct alignment. - glPixelStorei(GL_PACK_ALIGNMENT, alignment); - if (!gl_extensions.IsGLES || gl_extensions.GLES3) { - // Even if not required, some drivers seem to require we specify this. See #8254. - glPixelStorei(GL_PACK_ROW_LENGTH, pixelStride); - } - - glReadPixels(x, y, w, h, format, type, pixels); -#ifdef DEBUG_READ_PIXELS - LogReadPixelsError(glGetError()); -#endif - - if (!gl_extensions.IsGLES || gl_extensions.GLES3) { - glPixelStorei(GL_PACK_ROW_LENGTH, 0); - } - CHECK_GL_ERROR_IF_DEBUG(); + GLuint aspect = 0; + if (channelBits & FB_COLOR_BIT) + aspect |= GL_COLOR_BUFFER_BIT; + if (channelBits & FB_DEPTH_BIT) + aspect |= GL_DEPTH_BUFFER_BIT; + if (channelBits & FB_STENCIL_BIT) + aspect |= GL_STENCIL_BUFFER_BIT; + renderManager_.CopyFramebufferToMemorySync(fb->framebuffer, aspect, x, y, w, h, dataFormat, (uint8_t *)pixels, pixelStride); return true; } Texture *OpenGLContext::CreateTexture(const TextureDesc &desc) { - return new OpenGLTexture(desc); -} - -OpenGLInputLayout::~OpenGLInputLayout() { - if (id_) { - glDeleteVertexArrays(1, &id_); - } -} - -void OpenGLInputLayout::Compile() { - int semMask = 0; - for (int i = 0; i < (int)desc.attributes.size(); i++) { - semMask |= 1 << desc.attributes[i].location; - } - semanticsMask_ = semMask; - - if (gl_extensions.ARB_vertex_array_object && gl_extensions.IsCoreContext) { - glGenVertexArrays(1, &id_); - } else { - id_ = 0; - } - needsEnable_ = true; - lastBase_ = -1; + return new OpenGLTexture(&renderManager_, desc); } DepthStencilState *OpenGLContext::CreateDepthStencilState(const DepthStencilStateDesc &desc) { @@ -972,10 +767,6 @@ BlendState *OpenGLContext::CreateBlendState(const BlendStateDesc &desc) { bs->eqAlpha = blendEqToGL[(int)desc.eqAlpha]; bs->srcAlpha = blendFactorToGL[(int)desc.srcAlpha]; bs->dstAlpha = blendFactorToGL[(int)desc.dstAlpha]; -#ifndef USING_GLES2 - bs->logicEnabled = desc.logicEnabled; - bs->logicOp = logicOpToGL[(int)desc.logicOp]; -#endif bs->colorMask = desc.colorMask; return bs; } @@ -1025,28 +816,22 @@ RasterState *OpenGLContext::CreateRasterState(const RasterStateDesc &desc) { class OpenGLBuffer : public Buffer { public: - OpenGLBuffer(size_t size, uint32_t flags) { - glGenBuffers(1, &buffer_); + OpenGLBuffer(GLRenderManager *render, size_t size, uint32_t flags) : render_(render) { target_ = (flags & BufferUsageFlag::INDEXDATA) ? GL_ELEMENT_ARRAY_BUFFER : GL_ARRAY_BUFFER; usage_ = 0; if (flags & BufferUsageFlag::DYNAMIC) usage_ = GL_STREAM_DRAW; else usage_ = GL_STATIC_DRAW; + buffer_ = render->CreateBuffer(target_, size, usage_); totalSize_ = size; - glBindBuffer(target_, buffer_); - glBufferData(target_, size, NULL, usage_); } ~OpenGLBuffer() override { - glDeleteBuffers(1, &buffer_); + render_->DeleteBuffer(buffer_); } - void Bind(int offset) { - // TODO: Can't support offset using ES 2.0 - glBindBuffer(target_, buffer_); - } - - GLuint buffer_; + GLRenderManager *render_; + GLRBuffer *buffer_; GLuint target_; GLuint usage_; @@ -1054,26 +839,29 @@ public: }; Buffer *OpenGLContext::CreateBuffer(size_t size, uint32_t usageFlags) { - return new OpenGLBuffer(size, usageFlags); + return new OpenGLBuffer(&renderManager_, size, usageFlags); } void OpenGLContext::UpdateBuffer(Buffer *buffer, const uint8_t *data, size_t offset, size_t size, UpdateBufferFlags flags) { OpenGLBuffer *buf = (OpenGLBuffer *)buffer; - buf->Bind(0); if (size + offset > buf->totalSize_) { Crash(); } + + uint8_t *dataCopy = new uint8_t[size]; + memcpy(dataCopy, data, size); // if (flags & UPDATE_DISCARD) we could try to orphan the buffer using glBufferData. - glBufferSubData(buf->target_, offset, size, data); + // But we're much better off using separate buffers per FrameData... + renderManager_.BufferSubdata(buf->buffer_, offset, size, dataCopy); } Pipeline *OpenGLContext::CreateGraphicsPipeline(const PipelineDesc &desc) { if (!desc.shaders.size()) { ELOG("Pipeline requires at least one shader"); - return NULL; + return nullptr; } - OpenGLPipeline *pipeline = new OpenGLPipeline(); + OpenGLPipeline *pipeline = new OpenGLPipeline(&renderManager_); for (auto iter : desc.shaders) { iter->AddRef(); pipeline->shaders.push_back(static_cast(iter)); @@ -1103,16 +891,14 @@ void OpenGLContext::BindTextures(int start, int count, Texture **textures) { maxTextures_ = std::max(maxTextures_, start + count); for (int i = start; i < start + count; i++) { OpenGLTexture *glTex = static_cast(textures[i]); - glActiveTexture(GL_TEXTURE0 + i); if (!glTex) { boundTextures_[i] = 0; - glBindTexture(GL_TEXTURE_2D, 0); + renderManager_.BindTexture(i, nullptr); continue; } - glTex->Bind(); + glTex->Bind(i); boundTextures_[i] = glTex; } - glActiveTexture(GL_TEXTURE0); } void OpenGLContext::ApplySamplers() { @@ -1122,31 +908,25 @@ void OpenGLContext::ApplySamplers() { const OpenGLTexture *tex = boundTextures_[i]; if (!tex) continue; + GLenum wrapS; + GLenum wrapT; if (tex->CanWrap()) { - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, samp->wrapU); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, samp->wrapV); -#ifndef USING_GLES2 - if (tex->GetType() == TextureType::LINEAR3D) - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_R, samp->wrapW); -#endif + wrapS = samp->wrapU; + wrapT = samp->wrapV; } else { - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - } - - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, samp->magFilt); - if (tex->HasMips()) { - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, samp->mipMinFilt); - } else { - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, samp->minFilt); + wrapS = GL_CLAMP_TO_EDGE; + wrapT = GL_CLAMP_TO_EDGE; } + GLenum magFilt = samp->magFilt; + GLenum minFilt = tex->HasMips() ? samp->mipMinFilt : samp->minFilt; + renderManager_.SetTextureSampler(wrapS, wrapT, magFilt, minFilt, 0.0f); } } } ShaderModule *OpenGLContext::CreateShaderModule(ShaderStage stage, ShaderLanguage language, const uint8_t *data, size_t dataSize) { - OpenGLShaderModule *shader = new OpenGLShaderModule(stage); - if (shader->Compile(language, data, dataSize)) { + OpenGLShaderModule *shader = new OpenGLShaderModule(&renderManager_, stage); + if (shader->Compile(&renderManager_, language, data, dataSize)) { return shader; } else { shader->Release(); @@ -1155,76 +935,30 @@ ShaderModule *OpenGLContext::CreateShaderModule(ShaderStage stage, ShaderLanguag } bool OpenGLPipeline::LinkShaders() { - program_ = glCreateProgram(); + std::vector linkShaders; for (auto iter : shaders) { - glAttachShader(program_, iter->GetShader()); + linkShaders.push_back(iter->GetShader()); } - + std::vector semantics; // Bind all the common vertex data points. Mismatching ones will be ignored. - glBindAttribLocation(program_, SEM_POSITION, "Position"); - glBindAttribLocation(program_, SEM_COLOR0, "Color0"); - glBindAttribLocation(program_, SEM_TEXCOORD0, "TexCoord0"); - glBindAttribLocation(program_, SEM_NORMAL, "Normal"); - glBindAttribLocation(program_, SEM_TANGENT, "Tangent"); - glBindAttribLocation(program_, SEM_BINORMAL, "Binormal"); - glLinkProgram(program_); - - GLint linkStatus = GL_FALSE; - glGetProgramiv(program_, GL_LINK_STATUS, &linkStatus); - if (linkStatus != GL_TRUE) { - GLint bufLength = 0; - glGetProgramiv(program_, GL_INFO_LOG_LENGTH, &bufLength); - if (bufLength) { - char* buf = new char[bufLength]; - glGetProgramInfoLog(program_, bufLength, NULL, buf); - ELOG("Could not link program:\n %s", buf); - // We've thrown out the source at this point. Might want to do something about that. -#ifdef _WIN32 - OutputDebugStringUTF8(buf); -#endif - delete[] buf; - } else { - ELOG("Could not link program with %d shaders for unknown reason:", (int)shaders.size()); - } - return false; - } - - // Auto-initialize samplers. - glUseProgram(program_); - for (int i = 0; i < 4; i++) { - char temp[256]; - sprintf(temp, "Sampler%i", i); - int samplerLoc = GetUniformLoc(temp); - if (samplerLoc != -1) { - glUniform1i(samplerLoc, i); - } - } - - // Here we could (using glGetAttribLocation) save a bitmask about which pieces of vertex data are used in the shader - // and then AND it with the vertex format bitmask later... + semantics.push_back({ SEM_POSITION, "Position" }); + semantics.push_back({ SEM_COLOR0, "Color0" }); + semantics.push_back({ SEM_TEXCOORD0, "TexCoord0" }); + semantics.push_back({ SEM_NORMAL, "Normal" }); + semantics.push_back({ SEM_TANGENT, "Tangent" }); + semantics.push_back({ SEM_BINORMAL, "Binormal" }); + std::vector queries; + std::vector initialize; + program_ = render_->CreateProgram(linkShaders, semantics, queries, initialize, false); return true; } -int OpenGLPipeline::GetUniformLoc(const char *name) { - auto iter = uniformCache_.find(name); - int loc = -1; - if (iter != uniformCache_.end()) { - loc = iter->second.loc_; - } else { - loc = glGetUniformLocation(program_, name); - UniformInfo info; - info.loc_ = loc; - uniformCache_[name] = info; - } - return loc; -} - void OpenGLContext::BindPipeline(Pipeline *pipeline) { curPipeline_ = (OpenGLPipeline *)pipeline; - curPipeline_->blend->Apply(); - curPipeline_->depthStencil->Apply(); - curPipeline_->raster->Apply(); - glUseProgram(curPipeline_->program_); + curPipeline_->blend->Apply(&renderManager_); + curPipeline_->depthStencil->Apply(&renderManager_); + curPipeline_->raster->Apply(&renderManager_); + renderManager_.BindProgram(curPipeline_->program_); } void OpenGLContext::UpdateDynamicUniformBuffer(const void *ub, size_t size) { @@ -1233,55 +967,47 @@ void OpenGLContext::UpdateDynamicUniformBuffer(const void *ub, size_t size) { } for (auto &uniform : curPipeline_->dynamicUniforms.uniforms) { - GLuint loc = curPipeline_->GetUniformLoc(uniform.name); - if (loc == -1) - Crash(); const float *data = (const float *)((uint8_t *)ub + uniform.offset); switch (uniform.type) { case UniformType::FLOAT4: - glUniform1fv(loc, 4, data); + renderManager_.SetUniformF(uniform.name, 4, data); break; case UniformType::MATRIX4X4: - glUniformMatrix4fv(loc, 1, false, data); + renderManager_.SetUniformM4x4(uniform.name, data); break; } } } void OpenGLContext::Draw(int vertexCount, int offset) { - curVBuffers_[0]->Bind(curVBufferOffsets_[0]); - curPipeline_->inputLayout->Apply(); + _dbg_assert_msg_(G3D, curVBuffers_[0], "Can't call Draw without a vertex buffer"); ApplySamplers(); - - glDrawArrays(curPipeline_->prim, offset, vertexCount); - - curPipeline_->inputLayout->Unapply(); - glBindBuffer(GL_ARRAY_BUFFER, 0); + renderManager_.BindVertexBuffer(curPipeline_->inputLayout->inputLayout_, curVBuffers_[0]->buffer_, curVBufferOffsets_[0]); + renderManager_.Draw(curPipeline_->prim, offset, vertexCount); } void OpenGLContext::DrawIndexed(int vertexCount, int offset) { - curVBuffers_[0]->Bind(curVBufferOffsets_[0]); - curPipeline_->inputLayout->Apply(); + _dbg_assert_msg_(G3D, curVBuffers_[0], "Can't call DrawIndexed without a vertex buffer"); + _dbg_assert_msg_(G3D, curIBuffer_, "Can't call DrawIndexed without an index buffer"); ApplySamplers(); - // Note: ibuf binding is stored in the VAO, so call this after binding the fmt. - curIBuffer_->Bind(curIBufferOffset_); - - glDrawElements(curPipeline_->prim, vertexCount, GL_UNSIGNED_INT, (const void *)(size_t)offset); - - curPipeline_->inputLayout->Unapply(); - glBindBuffer(GL_ARRAY_BUFFER, 0); - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); + renderManager_.BindVertexBuffer(curPipeline_->inputLayout->inputLayout_, curVBuffers_[0]->buffer_, curVBufferOffsets_[0]); + renderManager_.BindIndexBuffer(curIBuffer_->buffer_); + renderManager_.DrawIndexed(curPipeline_->prim, vertexCount, GL_UNSIGNED_INT, (void *)(intptr_t)curIBufferOffset_); } void OpenGLContext::DrawUP(const void *vdata, int vertexCount) { - curPipeline_->inputLayout->Apply(vdata); + int stride = curPipeline_->inputLayout->stride; + size_t dataSize = stride * vertexCount; + + FrameData &frameData = frameData_[renderManager_.GetCurFrame()]; + + GLRBuffer *buf; + size_t offset = frameData.push->Push(vdata, dataSize, &buf); + ApplySamplers(); - glBindBuffer(GL_ARRAY_BUFFER, 0); - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); - glDrawArrays(curPipeline_->prim, 0, vertexCount); - - curPipeline_->inputLayout->Unapply(); + renderManager_.BindVertexBuffer(curPipeline_->inputLayout->inputLayout_, buf, offset); + renderManager_.Draw(curPipeline_->prim, 0, vertexCount); } void OpenGLContext::Clear(int mask, uint32_t colorval, float depthVal, int stencilVal) { @@ -1289,544 +1015,135 @@ void OpenGLContext::Clear(int mask, uint32_t colorval, float depthVal, int stenc Uint8x4ToFloat4(col, colorval); GLuint glMask = 0; if (mask & FBChannel::FB_COLOR_BIT) { - glClearColor(col[0], col[1], col[2], col[3]); glMask |= GL_COLOR_BUFFER_BIT; } if (mask & FBChannel::FB_DEPTH_BIT) { -#if defined(USING_GLES2) - glClearDepthf(depthVal); -#else - glClearDepth(depthVal); -#endif glMask |= GL_DEPTH_BUFFER_BIT; } if (mask & FBChannel::FB_STENCIL_BIT) { - glClearStencil(stencilVal); glMask |= GL_STENCIL_BUFFER_BIT; } - glClear(glMask); + renderManager_.Clear(colorval, depthVal, stencilVal, glMask); } DrawContext *T3DCreateGLContext() { return new OpenGLContext(); } -void OpenGLInputLayout::Apply(const void *base) { - if (id_ != 0) { - glBindVertexArray(id_); - } - - if (needsEnable_ || id_ == 0) { - for (int i = 0; i < SEM_MAX; i++) { - if (semanticsMask_ & (1 << i)) { - glEnableVertexAttribArray(i); - } - } - if (id_ != 0) { - needsEnable_ = false; - } - } - - intptr_t b = (intptr_t)base; - if (b != lastBase_) { - for (size_t i = 0; i < desc.attributes.size(); i++) { - GLsizei stride = (GLsizei)desc.bindings[desc.attributes[i].binding].stride; - switch (desc.attributes[i].format) { - case DataFormat::R32G32_FLOAT: - glVertexAttribPointer(desc.attributes[i].location, 2, GL_FLOAT, GL_FALSE, stride, (void *)(b + (intptr_t)desc.attributes[i].offset)); - break; - case DataFormat::R32G32B32_FLOAT: - glVertexAttribPointer(desc.attributes[i].location, 3, GL_FLOAT, GL_FALSE, stride, (void *)(b + (intptr_t)desc.attributes[i].offset)); - break; - case DataFormat::R32G32B32A32_FLOAT: - glVertexAttribPointer(desc.attributes[i].location, 4, GL_FLOAT, GL_FALSE, stride, (void *)(b + (intptr_t)desc.attributes[i].offset)); - break; - case DataFormat::R8G8B8A8_UNORM: - glVertexAttribPointer(desc.attributes[i].location, 4, GL_UNSIGNED_BYTE, GL_TRUE, stride, (void *)(b + (intptr_t)desc.attributes[i].offset)); - break; - case DataFormat::UNDEFINED: - default: - ELOG("Thin3DGLVertexFormat: Invalid or unknown component type applied."); - break; - } - } - if (id_ != 0) { - lastBase_ = b; - } - } +OpenGLInputLayout::~OpenGLInputLayout() { } -void OpenGLInputLayout::Unapply() { - if (id_ == 0) { - for (int i = 0; i < (int)SEM_MAX; i++) { - if (semanticsMask_ & (1 << i)) { - glDisableVertexAttribArray(i); - } +void OpenGLInputLayout::Compile(const InputLayoutDesc &desc) { + int semMask = 0; + + // TODO: This is only accurate if there's only one stream. But whatever, for now we + // never use multiple streams anyway. + stride = (GLsizei)desc.bindings[0].stride; + + std::vector entries; + for (auto &attr : desc.attributes) { + GLRInputLayout::Entry entry; + entry.location = attr.location; + entry.stride = (GLsizei)desc.bindings[attr.binding].stride; + entry.offset = attr.offset; + switch (attr.format) { + case DataFormat::R32G32_FLOAT: + entry.count = 2; + entry.type = GL_FLOAT; + entry.normalized = GL_FALSE; + break; + case DataFormat::R32G32B32_FLOAT: + entry.count = 3; + entry.type = GL_FLOAT; + entry.normalized = GL_FALSE; + break; + case DataFormat::R32G32B32A32_FLOAT: + entry.count = 4; + entry.type = GL_FLOAT; + entry.normalized = GL_FALSE; + break; + case DataFormat::R8G8B8A8_UNORM: + entry.count = 4; + entry.type = GL_UNSIGNED_BYTE; + entry.normalized = GL_TRUE; + break; + case DataFormat::UNDEFINED: + default: + ELOG("Thin3DGLVertexFormat: Invalid or unknown component type applied."); + break; } - } else { - glBindVertexArray(0); + + entries.push_back(entry); } + inputLayout_ = render_->CreateInputLayout(entries); } -// On PC, we always use GL_DEPTH24_STENCIL8. -// On Android, we try to use what's available. - -#ifndef USING_GLES2 -OpenGLFramebuffer *OpenGLContext::fbo_ext_create(const FramebufferDesc &desc) { - OpenGLFramebuffer *fbo = new OpenGLFramebuffer(); - fbo->width = desc.width; - fbo->height = desc.height; - fbo->colorDepth = desc.colorDepth; - - // Color texture is same everywhere - glGenFramebuffersEXT(1, &fbo->handle); - glGenTextures(1, &fbo->color_texture); - - // Create the surfaces. - glBindTexture(GL_TEXTURE_2D, fbo->color_texture); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - - // TODO: We could opt to only create 16-bit render targets on slow devices. For later. - switch (fbo->colorDepth) { - case FBO_8888: - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, fbo->width, fbo->height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL); - break; - case FBO_4444: - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, fbo->width, fbo->height, 0, GL_RGBA, GL_UNSIGNED_SHORT_4_4_4_4, NULL); - break; - case FBO_5551: - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, fbo->width, fbo->height, 0, GL_RGBA, GL_UNSIGNED_SHORT_5_5_5_1, NULL); - break; - case FBO_565: - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, fbo->width, fbo->height, 0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, NULL); - break; - } - - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - - fbo->stencil_buffer = 0; - fbo->z_buffer = 0; - // 24-bit Z, 8-bit stencil - glGenRenderbuffersEXT(1, &fbo->z_stencil_buffer); - glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, fbo->z_stencil_buffer); - glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, GL_DEPTH_STENCIL_EXT, fbo->width, fbo->height); - //glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, GL_DEPTH24_STENCIL8, width, height); - - // Bind it all together - glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fbo->handle); - glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, fbo->color_texture, 0); - glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_RENDERBUFFER_EXT, fbo->z_stencil_buffer); - glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_STENCIL_ATTACHMENT_EXT, GL_RENDERBUFFER_EXT, fbo->z_stencil_buffer); - - GLenum status = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT); - switch (status) { - case GL_FRAMEBUFFER_COMPLETE_EXT: - // ILOG("Framebuffer verified complete."); - break; - case GL_FRAMEBUFFER_UNSUPPORTED_EXT: - ELOG("GL_FRAMEBUFFER_UNSUPPORTED"); - break; - case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT_EXT: - ELOG("GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT "); - break; - default: - FLOG("Other framebuffer error: %i", status); - break; - } - // Unbind state we don't need - glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, 0); - glBindTexture(GL_TEXTURE_2D, 0); - - currentDrawHandle_ = fbo->handle; - currentReadHandle_ = fbo->handle; - return fbo; -} -#endif - Framebuffer *OpenGLContext::CreateFramebuffer(const FramebufferDesc &desc) { CheckGLExtensions(); -#ifndef USING_GLES2 - if (!gl_extensions.ARB_framebuffer_object && gl_extensions.EXT_framebuffer_object) { - return fbo_ext_create(desc); - } else if (!gl_extensions.ARB_framebuffer_object) { - return nullptr; - } - // If GLES2, we have basic FBO support and can just proceed. -#endif - CHECK_GL_ERROR_IF_DEBUG(); - - OpenGLFramebuffer *fbo = new OpenGLFramebuffer(); - fbo->width = desc.width; - fbo->height = desc.height; - fbo->colorDepth = desc.colorDepth; - - // Color texture is same everywhere - glGenFramebuffers(1, &fbo->handle); - glGenTextures(1, &fbo->color_texture); - - // Create the surfaces. - glBindTexture(GL_TEXTURE_2D, fbo->color_texture); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - - // TODO: We could opt to only create 16-bit render targets on slow devices. For later. - switch (fbo->colorDepth) { - case FBO_8888: - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, fbo->width, fbo->height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL); - break; - case FBO_4444: - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, fbo->width, fbo->height, 0, GL_RGBA, GL_UNSIGNED_SHORT_4_4_4_4, NULL); - break; - case FBO_5551: - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, fbo->width, fbo->height, 0, GL_RGBA, GL_UNSIGNED_SHORT_5_5_5_1, NULL); - break; - case FBO_565: - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, fbo->width, fbo->height, 0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, NULL); - break; - } - - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - - if (gl_extensions.IsGLES) { - if (gl_extensions.OES_packed_depth_stencil) { - ILOG("Creating %i x %i FBO using DEPTH24_STENCIL8", fbo->width, fbo->height); - // Standard method - fbo->stencil_buffer = 0; - fbo->z_buffer = 0; - // 24-bit Z, 8-bit stencil combined - glGenRenderbuffers(1, &fbo->z_stencil_buffer); - glBindRenderbuffer(GL_RENDERBUFFER, fbo->z_stencil_buffer); - glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8_OES, fbo->width, fbo->height); - - // Bind it all together - glBindFramebuffer(GL_FRAMEBUFFER, fbo->handle); - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, fbo->color_texture, 0); - glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, fbo->z_stencil_buffer); - glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, fbo->z_stencil_buffer); - } else { - ILOG("Creating %i x %i FBO using separate stencil", fbo->width, fbo->height); - // TEGRA - fbo->z_stencil_buffer = 0; - // 16/24-bit Z, separate 8-bit stencil - glGenRenderbuffers(1, &fbo->z_buffer); - glBindRenderbuffer(GL_RENDERBUFFER, fbo->z_buffer); - // Don't forget to make sure fbo_standard_z_depth() matches. - glRenderbufferStorage(GL_RENDERBUFFER, gl_extensions.OES_depth24 ? GL_DEPTH_COMPONENT24 : GL_DEPTH_COMPONENT16, fbo->width, fbo->height); - - // 8-bit stencil buffer - glGenRenderbuffers(1, &fbo->stencil_buffer); - glBindRenderbuffer(GL_RENDERBUFFER, fbo->stencil_buffer); - glRenderbufferStorage(GL_RENDERBUFFER, GL_STENCIL_INDEX8, fbo->width, fbo->height); - - // Bind it all together - glBindFramebuffer(GL_FRAMEBUFFER, fbo->handle); - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, fbo->color_texture, 0); - glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, fbo->z_buffer); - glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, fbo->stencil_buffer); - } - } else { - fbo->stencil_buffer = 0; - fbo->z_buffer = 0; - // 24-bit Z, 8-bit stencil - glGenRenderbuffers(1, &fbo->z_stencil_buffer); - glBindRenderbuffer(GL_RENDERBUFFER, fbo->z_stencil_buffer); - glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, fbo->width, fbo->height); - - // Bind it all together - glBindFramebuffer(GL_FRAMEBUFFER, fbo->handle); - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, fbo->color_texture, 0); - glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, fbo->z_stencil_buffer); - glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, fbo->z_stencil_buffer); - } - - GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER); - switch (status) { - case GL_FRAMEBUFFER_COMPLETE: - // ILOG("Framebuffer verified complete."); - break; - case GL_FRAMEBUFFER_UNSUPPORTED: - ELOG("GL_FRAMEBUFFER_UNSUPPORTED"); - break; - case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT: - ELOG("GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT "); - break; - default: - FLOG("Other framebuffer error: %i", status); - break; - } - - // Unbind state we don't need - glBindRenderbuffer(GL_RENDERBUFFER, 0); - glBindTexture(GL_TEXTURE_2D, 0); - CHECK_GL_ERROR_IF_DEBUG(); - - currentDrawHandle_ = fbo->handle; - currentReadHandle_ = fbo->handle; + OpenGLFramebuffer *fbo = new OpenGLFramebuffer(&renderManager_); + fbo->framebuffer = renderManager_.CreateFramebuffer(desc.width, desc.height, desc.z_stencil); return fbo; } -GLenum OpenGLContext::fbo_get_fb_target(bool read, GLuint **cached) { - bool supportsBlit = gl_extensions.ARB_framebuffer_object; - if (gl_extensions.IsGLES) { - supportsBlit = (gl_extensions.GLES3 || gl_extensions.NV_framebuffer_blit); - } - - // Note: GL_FRAMEBUFFER_EXT and GL_FRAMEBUFFER have the same value, same with _NV. - if (supportsBlit) { - if (read) { - *cached = ¤tReadHandle_; - return GL_READ_FRAMEBUFFER; - } else { - *cached = ¤tDrawHandle_; - return GL_DRAW_FRAMEBUFFER; - } - } else { - *cached = ¤tDrawHandle_; - return GL_FRAMEBUFFER; - } -} - -void OpenGLContext::fbo_bind_fb_target(bool read, GLuint name) { - GLuint *cached; - GLenum target = fbo_get_fb_target(read, &cached); - if (*cached != name) { - if (gl_extensions.ARB_framebuffer_object || gl_extensions.IsGLES) { - glBindFramebuffer(target, name); - } else { -#ifndef USING_GLES2 - glBindFramebufferEXT(target, name); -#endif - } - *cached = name; - } -} - -void OpenGLContext::fbo_unbind() { -#ifndef USING_GLES2 - if (gl_extensions.ARB_framebuffer_object || gl_extensions.IsGLES) { - glBindFramebuffer(GL_FRAMEBUFFER, g_defaultFBO); - } else if (gl_extensions.EXT_framebuffer_object) { - glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, g_defaultFBO); - } -#else - glBindFramebuffer(GL_FRAMEBUFFER, g_defaultFBO); -#endif - -#ifdef IOS - bindDefaultFBO(); -#endif - - currentDrawHandle_ = 0; - currentReadHandle_ = 0; -} - void OpenGLContext::BindFramebufferAsRenderTarget(Framebuffer *fbo, const RenderPassInfo &rp) { - CHECK_GL_ERROR_IF_DEBUG(); - curFB_ = (OpenGLFramebuffer *)fbo; - 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) { -#ifdef USING_GLES2 - glClearDepthf(rp.clearDepth); -#else - glClearDepth(rp.clearDepth); -#endif - clearFlags |= GL_DEPTH_BUFFER_BIT; - glstate.depthWrite.force(GL_TRUE); - } - if (rp.stencil == RPAction::CLEAR) { - glClearStencil(rp.clearStencil); - clearFlags |= GL_STENCIL_BUFFER_BIT; - 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(); - } - if (rp.stencil == RPAction::CLEAR) { - glstate.stencilFunc.restore(); - glstate.stencilMask.restore(); - } - CHECK_GL_ERROR_IF_DEBUG(); + OpenGLFramebuffer *fb = (OpenGLFramebuffer *)fbo; + GLRRenderPassAction color = (GLRRenderPassAction)rp.color; + GLRRenderPassAction depth = (GLRRenderPassAction)rp.depth; + GLRRenderPassAction stencil = (GLRRenderPassAction)rp.stencil; + + renderManager_.BindFramebufferAsRenderTarget(fb ? fb->framebuffer : nullptr, color, depth, stencil, rp.clearColor, rp.clearDepth, rp.clearStencil); } void OpenGLContext::CopyFramebufferImage(Framebuffer *fbsrc, int srcLevel, int srcX, int srcY, int srcZ, Framebuffer *fbdst, int dstLevel, int dstX, int dstY, int dstZ, int width, int height, int depth, int channelBits) { OpenGLFramebuffer *src = (OpenGLFramebuffer *)fbsrc; OpenGLFramebuffer *dst = (OpenGLFramebuffer *)fbdst; - GLuint srcTex = 0; - GLuint dstTex = 0; - GLuint target = GL_TEXTURE_2D; - switch (channelBits) { - case FB_COLOR_BIT: - srcTex = src->color_texture; - dstTex = dst->color_texture; - break; - case FB_DEPTH_BIT: - target = GL_RENDERBUFFER; - srcTex = src->z_buffer ? src->z_buffer : src->z_stencil_buffer; - dstTex = dst->z_buffer ? dst->z_buffer : dst->z_stencil_buffer; - break; + + int aspect = 0; + if (channelBits & FB_COLOR_BIT) { + aspect |= GL_COLOR_BUFFER_BIT; + } else if (channelBits & (FB_STENCIL_BIT | FB_DEPTH_BIT)) { + if (channelBits & FB_DEPTH_BIT) + aspect |= GL_DEPTH_BUFFER_BIT; + if (channelBits & FB_STENCIL_BIT) + aspect |= GL_STENCIL_BUFFER_BIT; } -#if defined(USING_GLES2) -#ifndef IOS - glCopyImageSubDataOES( - srcTex, target, srcLevel, srcX, srcY, srcZ, - dstTex, target, dstLevel, dstX, dstY, dstZ, - width, height, depth); -#endif -#else - if (gl_extensions.ARB_copy_image) { - glCopyImageSubData( - srcTex, target, srcLevel, srcX, srcY, srcZ, - dstTex, target, dstLevel, dstX, dstY, dstZ, - width, height, depth); - } else if (gl_extensions.NV_copy_image) { - // Older, pre GL 4.x NVIDIA cards. - glCopyImageSubDataNV( - srcTex, target, srcLevel, srcX, srcY, srcZ, - dstTex, target, dstLevel, dstX, dstY, dstZ, - width, height, depth); - } -#endif - CHECK_GL_ERROR_IF_DEBUG(); + renderManager_.CopyFramebuffer(src->framebuffer, GLRect2D{ srcX, srcY, width, height }, dst->framebuffer, GLOffset2D{ dstX, dstY }, aspect); } bool OpenGLContext::BlitFramebuffer(Framebuffer *fbsrc, int srcX1, int srcY1, int srcX2, int srcY2, Framebuffer *fbdst, int dstX1, int dstY1, int dstX2, int dstY2, int channels, FBBlitFilter linearFilter) { OpenGLFramebuffer *src = (OpenGLFramebuffer *)fbsrc; OpenGLFramebuffer *dst = (OpenGLFramebuffer *)fbdst; - GLuint bits = 0; + GLuint aspect = 0; if (channels & FB_COLOR_BIT) - bits |= GL_COLOR_BUFFER_BIT; + aspect |= GL_COLOR_BUFFER_BIT; if (channels & FB_DEPTH_BIT) - bits |= GL_DEPTH_BUFFER_BIT; + aspect |= GL_DEPTH_BUFFER_BIT; if (channels & FB_STENCIL_BIT) - bits |= GL_STENCIL_BUFFER_BIT; - // 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(); -#if defined(USING_GLES2) && defined(__ANDROID__) // We only support this extension on Android, it's not even available on PC. - return true; - } else if (gl_extensions.NV_framebuffer_blit) { - glBlitFramebufferNV(srcX1, srcY1, srcX2, srcY2, dstX1, dstY1, dstX2, dstY2, bits, linearFilter == FB_BLIT_LINEAR ? GL_LINEAR : GL_NEAREST); - CHECK_GL_ERROR_IF_DEBUG(); -#endif // defined(USING_GLES2) && defined(__ANDROID__) - return true; - } else { - return false; - } -} + aspect |= GL_STENCIL_BUFFER_BIT; -uintptr_t OpenGLContext::GetFramebufferAPITexture(Framebuffer *fbo, int channelBits, int attachment) { - OpenGLFramebuffer *fb = (OpenGLFramebuffer *)fbo; - switch (channelBits) { - case FB_COLOR_BIT: return (uintptr_t)fb->color_texture; - case FB_DEPTH_BIT: return (uintptr_t)(fb->z_buffer ? fb->z_buffer : fb->z_stencil_buffer); - default: - return 0; - } + renderManager_.BlitFramebuffer(src->framebuffer, GLRect2D{ srcX1, srcY1, srcX2 - srcX1, srcY2 - srcY1 }, dst->framebuffer, GLRect2D{ dstX1, dstY1, dstX2 - dstX1, dstY2 - dstY1 }, aspect, linearFilter == FB_BLIT_LINEAR); + return true; } void OpenGLContext::BindFramebufferAsTexture(Framebuffer *fbo, int binding, FBChannel channelBit, int color) { OpenGLFramebuffer *fb = (OpenGLFramebuffer *)fbo; - if (!fb) - return; - if (binding != 0) - glActiveTexture(GL_TEXTURE0 + binding); - switch (channelBit) { - case FB_DEPTH_BIT: - glBindTexture(GL_TEXTURE_2D, fb->z_buffer ? fb->z_buffer : fb->z_stencil_buffer); - case FB_COLOR_BIT: - default: - glBindTexture(GL_TEXTURE_2D, fb->color_texture); - break; - } - glActiveTexture(GL_TEXTURE0); -} -OpenGLFramebuffer::~OpenGLFramebuffer() { - CHECK_GL_ERROR_IF_DEBUG(); - if (gl_extensions.ARB_framebuffer_object || gl_extensions.IsGLES) { - if (handle) { - glBindFramebuffer(GL_FRAMEBUFFER, handle); - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0); - glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, 0); - glBindFramebuffer(GL_FRAMEBUFFER, g_defaultFBO); - glDeleteFramebuffers(1, &handle); - } - if (z_stencil_buffer) - glDeleteRenderbuffers(1, &z_stencil_buffer); - if (z_buffer) - glDeleteRenderbuffers(1, &z_buffer); - if (stencil_buffer) - glDeleteRenderbuffers(1, &stencil_buffer); - } else if (gl_extensions.EXT_framebuffer_object) { -#ifndef USING_GLES2 - if (handle) { - glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, handle); - glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0); - glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER_EXT, 0); - glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, g_defaultFBO); - glDeleteFramebuffersEXT(1, &handle); - } - if (z_stencil_buffer) - glDeleteRenderbuffers(1, &z_stencil_buffer); - if (z_buffer) - glDeleteRenderbuffers(1, &z_buffer); - if (stencil_buffer) - glDeleteRenderbuffers(1, &stencil_buffer); -#endif - } - - glDeleteTextures(1, &color_texture); + GLuint aspect = 0; + if (channelBit & FB_COLOR_BIT) + aspect |= GL_COLOR_BUFFER_BIT; + if (channelBit & FB_DEPTH_BIT) + aspect |= GL_DEPTH_BUFFER_BIT; + if (channelBit & FB_STENCIL_BIT) + aspect |= GL_STENCIL_BUFFER_BIT; + renderManager_.BindFramebufferAsTexture(fb->framebuffer, binding, aspect, color); } void OpenGLContext::GetFramebufferDimensions(Framebuffer *fbo, int *w, int *h) { OpenGLFramebuffer *fb = (OpenGLFramebuffer *)fbo; if (fb) { - *w = fb->width; - *h = fb->height; + *w = fb->framebuffer->width; + *h = fb->framebuffer->height; } else { *w = targetWidth_; *h = targetHeight_; diff --git a/ext/native/thin3d/thin3d_vulkan.cpp b/ext/native/thin3d/thin3d_vulkan.cpp index ce89f21498..0fa38ecdf1 100644 --- a/ext/native/thin3d/thin3d_vulkan.cpp +++ b/ext/native/thin3d/thin3d_vulkan.cpp @@ -1338,8 +1338,9 @@ void VKContext::BindFramebufferAsRenderTarget(Framebuffer *fbo, const RenderPass VKFramebuffer *fb = (VKFramebuffer *)fbo; VKRRenderPassAction color = (VKRRenderPassAction)rp.color; VKRRenderPassAction depth = (VKRRenderPassAction)rp.depth; + VKRRenderPassAction stencil = (VKRRenderPassAction)rp.stencil; - renderManager_.BindFramebufferAsRenderTarget(fb ? fb->GetFB() : nullptr, color, depth, rp.clearColor, rp.clearDepth, rp.clearStencil); + renderManager_.BindFramebufferAsRenderTarget(fb ? fb->GetFB() : nullptr, color, depth, stencil, rp.clearColor, rp.clearDepth, rp.clearStencil); curFramebuffer_ = fb; } diff --git a/ext/native/thread/threadutil.cpp b/ext/native/thread/threadutil.cpp index fcd050e98f..403f8cadd9 100644 --- a/ext/native/thread/threadutil.cpp +++ b/ext/native/thread/threadutil.cpp @@ -12,7 +12,7 @@ #include "base/logging.h" #include "thread/threadutil.h" -#ifdef __ANDROID__ +#if defined(__ANDROID__) || defined(__APPLE__) || (defined(__GLIBC__) && defined(_GNU_SOURCE)) #include #endif @@ -96,10 +96,12 @@ void setCurrentThreadName(const char* threadName) { {} #else -#if defined(__ANDROID__) +#if defined(__ANDROID__) || (defined(__GLIBC__) && defined(_GNU_SOURCE)) pthread_setname_np(pthread_self(), threadName); +#elif defined(__APPLE__) + pthread_setname_np(threadName); // #else -// pthread_setname_np(thread_name); +// pthread_setname_np(threadName); #endif // Do nothing diff --git a/ios/ViewController.mm b/ios/ViewController.mm index 2ec8d7e7a7..a214286791 100644 --- a/ios/ViewController.mm +++ b/ios/ViewController.mm @@ -16,6 +16,7 @@ #include "net/resolve.h" #include "ui/screen.h" #include "thin3d/thin3d.h" +#include "thin3d/GLRenderManager.h" #include "input/keycodes.h" #include "gfx_es2/gpu_features.h" @@ -30,23 +31,36 @@ #define IS_IPAD() ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad) #define IS_IPHONE() ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPhone) -class IOSDummyGraphicsContext : public DummyGraphicsContext { +class IOSGraphicsContext : public DummyGraphicsContext { public: - IOSDummyGraphicsContext() { + IOSGraphicsContext() { CheckGLExtensions(); draw_ = Draw::T3DCreateGLContext(); + renderManager_ = (GLRenderManager *)draw_->GetNativeObject(Draw::NativeObject::RENDER_MANAGER); SetGPUBackend(GPUBackend::OPENGL); bool success = draw_->CreatePresets(); assert(success); } - ~IOSDummyGraphicsContext() { + ~IOSGraphicsContext() { delete draw_; } Draw::DrawContext *GetDrawContext() override { return draw_; } + void ThreadStart() override { + renderManager_->ThreadStart(); + } + + bool ThreadFrame() override { + return renderManager_->ThreadFrame(); + } + + void ThreadEnd() override { + renderManager_->ThreadEnd(); + } private: Draw::DrawContext *draw_; + GLRenderManager *renderManager_; }; static float dp_xscale = 1.0f; @@ -55,6 +69,7 @@ static float dp_yscale = 1.0f; static double lastSelectPress = 0.0f; static double lastStartPress = 0.0f; static bool simulateAnalog = false; +static bool threadEnabled = true; __unsafe_unretained static ViewController* sharedViewController; static GraphicsContext *graphicsContext; @@ -153,9 +168,10 @@ static GraphicsContext *graphicsContext; pixel_in_dps_x = (float)pixel_xres / (float)dp_xres; pixel_in_dps_y = (float)pixel_yres / (float)dp_yres; - graphicsContext = new IOSDummyGraphicsContext(); - + graphicsContext = new IOSGraphicsContext(); + NativeInitGraphics(graphicsContext); + graphicsContext->ThreadStart(); dp_xscale = (float)dp_xres / (float)pixel_xres; dp_yscale = (float)dp_yres / (float)pixel_yres; @@ -172,6 +188,14 @@ static GraphicsContext *graphicsContext; } } #endif + + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{ + while (threadEnabled) { + NativeUpdate(); + NativeRender(graphicsContext); + time_update(); + } + }); } - (void)didReceiveMemoryWarning @@ -195,6 +219,8 @@ static GraphicsContext *graphicsContext; self.gameController = nil; } #endif + threadEnabled = false; + graphicsContext->ThreadEnd(); NativeShutdownGraphics(); graphicsContext->Shutdown(); delete graphicsContext; @@ -216,9 +242,7 @@ static GraphicsContext *graphicsContext; - (void)glkView:(GLKView *)view drawInRect:(CGRect)rect { - NativeUpdate(); - NativeRender(graphicsContext); - time_update(); + graphicsContext->ThreadFrame(); } - (void)touchX:(float)x y:(float)y code:(int)code pointerId:(int)pointerId