mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-11-14 18:19:36 +01:00
Merge pull request #10411 from hrydgard/gl-render-manager
GL render manager - new faster GL multithreading
This commit is contained in:
83 files changed
+5242
-3626
No files matched your search
+8
-3
@@ -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
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
|
||||
@@ -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_);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
+4
-168
@@ -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<std::mutex> guard(cpuThreadLock);
|
||||
CPU_SetStateNoLock(to);
|
||||
}
|
||||
|
||||
bool CPU_NextState(CPUThreadState from, CPUThreadState to) {
|
||||
std::lock_guard<std::mutex> guard(cpuThreadLock);
|
||||
if (cpuThreadState == from) {
|
||||
CPU_SetStateNoLock(to);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool CPU_NextStateNot(CPUThreadState from, CPUThreadState to) {
|
||||
std::lock_guard<std::mutex> 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<std::mutex> 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();
|
||||
}
|
||||
|
||||
|
||||
@@ -78,8 +78,6 @@ void Core_UpdateDebugStats(bool collectStats);
|
||||
|
||||
void Audio_Init();
|
||||
void Audio_Shutdown();
|
||||
|
||||
bool IsOnSeparateCPUThread();
|
||||
bool IsAudioInitialised();
|
||||
|
||||
void UpdateLoadedFile(FileLoader *fileLoader);
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
@@ -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;
|
||||
|
||||
@@ -206,7 +206,7 @@ public:
|
||||
return vfb;
|
||||
}
|
||||
}
|
||||
virtual void RebindFramebuffer();
|
||||
void RebindFramebuffer();
|
||||
std::vector<FramebufferInfo> 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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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_;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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<GLRProgram::Semantic> semantics;
|
||||
semantics.push_back({ 0, "a_position" });
|
||||
semantics.push_back({ 1, "a_texcoord0" });
|
||||
|
||||
std::vector<GLRProgram::UniformLocQuery> queries;
|
||||
queries.push_back({ &depal->u_tex, "tex" });
|
||||
queries.push_back({ &depal->u_pal, "pal" });
|
||||
|
||||
std::vector<GLRProgram::Initializer> initializer;
|
||||
initializer.push_back({ &depal->u_tex, 0, 0 });
|
||||
initializer.push_back({ &depal->u_pal, 0, 3 });
|
||||
|
||||
std::vector<GLRShader *> 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;
|
||||
}
|
||||
|
||||
@@ -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<std::string> DebugGetShaderIDs(DebugShaderType type);
|
||||
@@ -55,9 +57,10 @@ public:
|
||||
private:
|
||||
bool CreateVertexShader();
|
||||
|
||||
GLRenderManager *render_;
|
||||
bool useGL3_;
|
||||
bool vertexShaderFailed_;
|
||||
GLuint vertexShader_;
|
||||
GLRShader *vertexShader_;
|
||||
std::map<u32, DepalShader *> cache_;
|
||||
std::map<u32, DepalTexture *> texCache_;
|
||||
};
|
||||
|
||||
+204
-386
@@ -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<GLRInputLayout::Entry> 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<GLRInputLayout::Entry> &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<GLRInputLayout::Entry> 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<GEPrimitiveType>(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<GEPrimitiveType>(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<GLuint> toFree;
|
||||
std::vector<GLuint> 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();
|
||||
}
|
||||
+40
-34
@@ -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<VertexArrayInfo *, nullptr> vai_;
|
||||
|
||||
// Vertex buffer objects
|
||||
// Element buffer objects
|
||||
struct BufferNameInfo {
|
||||
BufferNameInfo() : sz(0), used(false), lastFrame(0) {}
|
||||
DenseHashMap<uint32_t, GLRInputLayout *, nullptr> inputLayoutMap_;
|
||||
|
||||
size_t sz;
|
||||
bool used;
|
||||
int lastFrame;
|
||||
};
|
||||
std::vector<GLuint> bufferNameCache_;
|
||||
std::multimap<size_t, GLuint> freeSizedBuffers_;
|
||||
std::unordered_map<GLuint, BufferNameInfo> bufferNameInfo_;
|
||||
std::vector<GLuint> 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.
|
||||
};
|
||||
};
|
||||
@@ -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;
|
||||
|
||||
@@ -19,7 +19,6 @@
|
||||
|
||||
#include <map>
|
||||
#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<FragmentTestID, FragmentTestTexture> cache_;
|
||||
u8 *scratchpad_;
|
||||
GLuint lastTexture_;
|
||||
int decimationCounter_;
|
||||
GLRTexture *lastTexture_ = nullptr;
|
||||
int decimationCounter_ = 0;
|
||||
};
|
||||
+138
-456
@@ -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<GLRShader *> 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<GLRProgram::UniformLocQuery> queries;
|
||||
queries.push_back({ &u_draw2d_tex, "u_tex" });
|
||||
std::vector<GLRProgram::Initializer> initializers;
|
||||
initializers.push_back({ &u_draw2d_tex, 0 });
|
||||
std::vector<GLRProgram::Semantic> 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<GLRShader *> shaders;
|
||||
shaders.push_back(render_->CreateShader(GL_VERTEX_SHADER, vshader, "postshader"));
|
||||
shaders.push_back(render_->CreateShader(GL_VERTEX_SHADER, fshader, "postshader"));
|
||||
std::vector<GLRProgram::UniformLocQuery> 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<GLRProgram::Initializer> 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<GLRInputLayout::Entry> 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) {
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
+30
-45
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
+161
-300
@@ -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<GLRShader *> 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<GLRProgram::Semantic> 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<GLRProgram::UniformLocQuery> 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<GLRProgram::Initializer> 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);
|
||||
}
|
||||
|
||||
@@ -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<LinkedShaderCacheEntry> LinkedShaderCache;
|
||||
|
||||
GLRenderManager *render_;
|
||||
LinkedShaderCache linkedShaderCache_;
|
||||
|
||||
bool lastVShaderSame_;
|
||||
|
||||
+58
-122
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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<GLRShader *> 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<GLRProgram::UniformLocQuery> queries;
|
||||
queries.push_back({ &u_stencilUploadTex, "tex" });
|
||||
queries.push_back({ &u_stencilValue, "u_stencilValue" });
|
||||
std::vector<GLRProgram::Initializer> 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;
|
||||
}
|
||||
+147
-260
@@ -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 <emmintrin.h>
|
||||
#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<GLRInputLayout::Entry> 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
|
||||
|
||||
@@ -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<u32> 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 };
|
||||
};
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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"
|
||||
|
||||
+76
-21
@@ -29,6 +29,7 @@
|
||||
#include "QtMain.h"
|
||||
#include "gfx_es2/gpu_features.h"
|
||||
#include "math/math_util.h"
|
||||
#include "thread/threadutil.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
@@ -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();
|
||||
|
||||
+41
-2
@@ -19,6 +19,8 @@ QTM_USE_NAMESPACE
|
||||
#endif
|
||||
|
||||
#include <cassert>
|
||||
#include <atomic>
|
||||
#include <thread>
|
||||
|
||||
#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<int> emuThreadState;
|
||||
|
||||
bool useThread_ = false;
|
||||
};
|
||||
|
||||
extern MainUI* emugl;
|
||||
|
||||
+85
-6
@@ -14,9 +14,11 @@ SDLJoystick *joystick = NULL;
|
||||
#include <bcm_host.h>
|
||||
#endif
|
||||
|
||||
#include <atomic>
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
#include <cmath>
|
||||
#include <thread>
|
||||
|
||||
#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<int> 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();
|
||||
|
||||
+1
-1
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
+71
-7
@@ -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<int> emuThreadState;
|
||||
|
||||
static std::mutex renderThreadLock;
|
||||
static std::thread renderThread;
|
||||
static std::atomic<int> renderThreadReady;
|
||||
|
||||
static bool useRenderThread;
|
||||
static bool renderThreadFailed;
|
||||
static bool renderThreadSucceeded;
|
||||
static std::string g_error_message;
|
||||
|
||||
extern std::vector<std::wstring> 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<std::mutex> 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;
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -17,6 +17,6 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
void EmuThread_Start();
|
||||
void EmuThread_Start(bool separateRenderThread);
|
||||
void EmuThread_Stop();
|
||||
bool EmuThread_Ready();
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -917,7 +917,7 @@ namespace MainWindow
|
||||
EmuThread_Stop();
|
||||
coreState = CORE_POWERUP;
|
||||
ResetUIState();
|
||||
EmuThread_Start();
|
||||
EmuThread_Start(false);
|
||||
InputDevice::BeginPolling();
|
||||
break;
|
||||
|
||||
|
||||
+4
-2
@@ -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);
|
||||
|
||||
@@ -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...
|
||||
|
||||
@@ -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 {}
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
@@ -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
|
||||
}
|
||||
@@ -1,14 +1,25 @@
|
||||
#pragma once
|
||||
|
||||
#include <thread>
|
||||
#include <string>
|
||||
#include <functional>
|
||||
|
||||
#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;
|
||||
};
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
+150
-62
@@ -12,6 +12,7 @@
|
||||
#include <queue>
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
#include <atomic>
|
||||
|
||||
#include <jni.h>
|
||||
#include <android/log.h>
|
||||
@@ -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<int> 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<SystemPermission, PermissionStatus> 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<jclass>(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<std::mutex> 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;
|
||||
}
|
||||
|
||||
|
||||
@@ -6,8 +6,7 @@
|
||||
|
||||
#include <jni.h>
|
||||
|
||||
extern JNIEnv *jniEnvMain;
|
||||
extern JNIEnv *jniEnvGraphics;
|
||||
extern JavaVM *javaVM;
|
||||
jclass findClass(const char* name);
|
||||
JNIEnv* getEnv();
|
||||
|
||||
#endif
|
||||
@@ -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.
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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 \
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
#include <stdlib.h>
|
||||
|
||||
#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");
|
||||
}
|
||||
}
|
||||
@@ -1,283 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
#include <cstring> // 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<GLenum cap, bool init>
|
||||
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<GL_BLEND, false> 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<GL_COLOR_LOGIC_OP, false> colorLogicOp;
|
||||
STATE1(glLogicOp, GLenum, GL_COPY) logicOp;
|
||||
#endif
|
||||
|
||||
// Dither
|
||||
BoolState<GL_DITHER, false> dither;
|
||||
|
||||
// Cull Face
|
||||
BoolState<GL_CULL_FACE, false> cullFace;
|
||||
STATE1(glCullFace, GLenum, GL_FRONT) cullFaceMode;
|
||||
STATE1(glFrontFace, GLenum, GL_CCW) frontFace;
|
||||
|
||||
// Depth Test
|
||||
BoolState<GL_DEPTH_TEST, false> 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<GL_SCISSOR_TEST, false> scissorTest;
|
||||
STATE4(glScissor, GLint, GLint, GLsizei, GLsizei, 0, 0, 128, 128) scissorRect;
|
||||
|
||||
// Stencil Test
|
||||
BoolState<GL_STENCIL_TEST, false> 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;
|
||||
@@ -16,9 +16,9 @@
|
||||
#include <jni.h>
|
||||
|
||||
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<jclass>(env_->NewGlobalRef(localClass));
|
||||
ILOG("cls_textRender: %p", cls_textRenderer);
|
||||
if (cls_textRenderer) {
|
||||
|
||||
@@ -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] = {
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
@@ -232,7 +232,6 @@
|
||||
<ClInclude Include="file\free.h" />
|
||||
<ClInclude Include="gfx\d3d9_shader.h" />
|
||||
<ClInclude Include="gfx\d3d9_state.h" />
|
||||
<ClInclude Include="gfx\GLStateCache.h" />
|
||||
<ClInclude Include="gfx\gl_common.h" />
|
||||
<ClInclude Include="gfx\gl_debug_log.h" />
|
||||
<ClInclude Include="gfx\texture_atlas.h" />
|
||||
@@ -241,6 +240,9 @@
|
||||
<ClInclude Include="gfx_es2\draw_text_win.h" />
|
||||
<ClInclude Include="thin3d\d3d11_loader.h" />
|
||||
<ClInclude Include="thin3d\DataFormat.h" />
|
||||
<ClInclude Include="thin3d\DataFormatGL.h" />
|
||||
<ClInclude Include="thin3d\GLQueueRunner.h" />
|
||||
<ClInclude Include="thin3d\GLRenderManager.h" />
|
||||
<ClInclude Include="thin3d\VulkanQueueRunner.h" />
|
||||
<ClInclude Include="thin3d\VulkanRenderManager.h" />
|
||||
<ClInclude Include="util\text\wrap_text.h" />
|
||||
@@ -691,7 +693,6 @@
|
||||
<ClCompile Include="file\free.cpp" />
|
||||
<ClCompile Include="gfx\d3d9_shader.cpp" />
|
||||
<ClCompile Include="gfx\d3d9_state.cpp" />
|
||||
<ClCompile Include="gfx\GLStateCache.cpp" />
|
||||
<ClCompile Include="gfx\gl_debug_log.cpp" />
|
||||
<ClCompile Include="gfx\texture_atlas.cpp" />
|
||||
<ClCompile Include="gfx_es2\draw_text_android.cpp" />
|
||||
@@ -699,6 +700,9 @@
|
||||
<ClCompile Include="gfx_es2\draw_text_win.cpp" />
|
||||
<ClCompile Include="math\dataconv.cpp" />
|
||||
<ClCompile Include="thin3d\d3d11_loader.cpp" />
|
||||
<ClCompile Include="thin3d\DataFormatGL.cpp" />
|
||||
<ClCompile Include="thin3d\GLQueueRunner.cpp" />
|
||||
<ClCompile Include="thin3d\GLRenderManager.cpp" />
|
||||
<ClCompile Include="thin3d\thin3d_d3d11.cpp" />
|
||||
<ClCompile Include="thin3d\VulkanQueueRunner.cpp" />
|
||||
<ClCompile Include="thin3d\VulkanRenderManager.cpp" />
|
||||
|
||||
@@ -302,9 +302,6 @@
|
||||
<ClInclude Include="gfx\d3d9_state.h">
|
||||
<Filter>gfx</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="gfx\GLStateCache.h">
|
||||
<Filter>gfx</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="thin3d\d3d11_loader.h">
|
||||
<Filter>thin3d</Filter>
|
||||
</ClInclude>
|
||||
@@ -332,6 +329,15 @@
|
||||
<ClInclude Include="..\..\Qt\QtMain.h">
|
||||
<Filter>base</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="thin3d\GLRenderManager.h">
|
||||
<Filter>thin3d</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="thin3d\GLQueueRunner.h">
|
||||
<Filter>thin3d</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="thin3d\DataFormatGL.h">
|
||||
<Filter>thin3d</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="gfx\gl_debug_log.cpp">
|
||||
@@ -769,9 +775,6 @@
|
||||
<ClCompile Include="gfx\d3d9_state.cpp">
|
||||
<Filter>gfx</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="gfx\GLStateCache.cpp">
|
||||
<Filter>gfx</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="thin3d\d3d11_loader.cpp">
|
||||
<Filter>thin3d</Filter>
|
||||
</ClCompile>
|
||||
@@ -799,6 +802,15 @@
|
||||
<ClCompile Include="ui\ui_tween.cpp">
|
||||
<Filter>ui</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="thin3d\GLQueueRunner.cpp">
|
||||
<Filter>thin3d</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="thin3d\GLRenderManager.cpp">
|
||||
<Filter>thin3d</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="thin3d\DataFormatGL.cpp">
|
||||
<Filter>thin3d</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Filter Include="gfx">
|
||||
@@ -871,4 +883,4 @@
|
||||
<UniqueIdentifier>{06c6305a-a646-485b-85b9-645a24dd6553}</UniqueIdentifier>
|
||||
</Filter>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
</Project>
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,392 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
#include <unordered_map>
|
||||
|
||||
#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<GLRRenderData> 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<GLRInitStep> &steps);
|
||||
|
||||
void RunSteps(const std::vector<GLRStep *> &steps);
|
||||
void LogSteps(const std::vector<GLRStep *> &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<GLuint> nameCache_;
|
||||
std::unordered_map<int, std::string> glStrings_;
|
||||
|
||||
bool sawOutOfMemory_ = false;
|
||||
};
|
||||
@@ -0,0 +1,622 @@
|
||||
#include <cassert>
|
||||
|
||||
#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<std::mutex> lock(mutex_);
|
||||
queueRunner_.DestroyDeviceObjects();
|
||||
VLOG("PULL: Quitting");
|
||||
}
|
||||
|
||||
bool GLRenderManager::ThreadFrame() {
|
||||
std::unique_lock<std::mutex> 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<std::mutex> 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<std::mutex> lock(frameData.push_mutex);
|
||||
frameData.push_condVar.notify_all();
|
||||
}
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(frameData.pull_mutex);
|
||||
frameData.pull_condVar.notify_all();
|
||||
}
|
||||
}
|
||||
|
||||
// Wait until we've definitely stopped the threadframe.
|
||||
std::unique_lock<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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;
|
||||
}
|
||||
@@ -0,0 +1,842 @@
|
||||
#pragma once
|
||||
|
||||
#include <thread>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
#include <functional>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <mutex>
|
||||
#include <condition_variable>
|
||||
#include <cassert>
|
||||
|
||||
#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<Semantic> semantics_;
|
||||
std::vector<UniformLocQuery> queries_;
|
||||
std::vector<Initializer> 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<std::string, UniformInfo> 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<GLRShader *> shaders;
|
||||
std::vector<GLRProgram *> programs;
|
||||
std::vector<GLRBuffer *> buffers;
|
||||
std::vector<GLRTexture *> textures;
|
||||
std::vector<GLRInputLayout *> inputLayouts;
|
||||
std::vector<GLRFramebuffer *> framebuffers;
|
||||
};
|
||||
|
||||
class GLRInputLayout {
|
||||
public:
|
||||
struct Entry {
|
||||
int location;
|
||||
int count;
|
||||
GLenum type;
|
||||
GLboolean normalized;
|
||||
int stride;
|
||||
intptr_t offset;
|
||||
};
|
||||
std::vector<Entry> 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<GLRShader *> shaders, std::vector<GLRProgram::Semantic> semantics, std::vector<GLRProgram::UniformLocQuery> queries,
|
||||
std::vector<GLRProgram::Initializer> 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<GLRInputLayout::Entry> &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<void()> 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<GLRStep *> steps;
|
||||
std::vector<GLRInitStep> initSteps;
|
||||
|
||||
// Swapchain.
|
||||
bool hasBegun = false;
|
||||
uint32_t curSwapchainImage = -1;
|
||||
|
||||
std::mutex deleter_mutex;
|
||||
GLDeleter deleter;
|
||||
GLDeleter deleter_prev;
|
||||
|
||||
std::set<GLPushBuffer *> activePushBuffers;
|
||||
};
|
||||
|
||||
FrameData frameData_[MAX_INFLIGHT_FRAMES];
|
||||
|
||||
// Submission time state
|
||||
bool insideFrame_ = false;
|
||||
GLRStep *curRenderStep_ = nullptr;
|
||||
std::vector<GLRStep *> steps_;
|
||||
std::vector<GLRInitStep> 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<void()> 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<BufInfo> buffers_;
|
||||
size_t buf_ = 0;
|
||||
size_t offset_ = 0;
|
||||
size_t size_ = 0;
|
||||
uint8_t *writePtr_ = nullptr;
|
||||
GLuint target_;
|
||||
};
|
||||
@@ -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;
|
||||
|
||||
@@ -108,7 +108,8 @@ struct VKRStep {
|
||||
struct {
|
||||
VKRFramebuffer *framebuffer;
|
||||
VKRRenderPassAction color;
|
||||
VKRRenderPassAction depthStencil;
|
||||
VKRRenderPassAction depth;
|
||||
VKRRenderPassAction stencil;
|
||||
uint32_t clearColor;
|
||||
float clearDepth;
|
||||
int clearStencil;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
+263
-946
File diff suppressed because it is too large.
Load diff
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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 <pthread.h>
|
||||
#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
|
||||
|
||||
+32
-8
@@ -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
|
||||
|
||||
Reference in new issue
Block a user