diff options
| author | Jasper St. Pierre <jstpierre@mecheye.net> | 2013-12-07 15:14:29 -0500 |
|---|---|---|
| committer | Jasper St. Pierre <jstpierre@mecheye.net> | 2013-12-31 14:03:19 -0500 |
| commit | 34692ab826abc8f8faa61bdb2280b742424528f1 (patch) | |
| tree | b0eedc645badbea316ff59c125eaf808b28777ec /Source/Core/VideoBackends/OGL/Render.cpp | |
| parent | 43e618682e8093602fc83dfa902ee7ae3fd3a4f6 (diff) | |
Remove unnecessary Src/ folders
Diffstat (limited to 'Source/Core/VideoBackends/OGL/Render.cpp')
| -rw-r--r-- | Source/Core/VideoBackends/OGL/Render.cpp | 1843 |
1 files changed, 1843 insertions, 0 deletions
diff --git a/Source/Core/VideoBackends/OGL/Render.cpp b/Source/Core/VideoBackends/OGL/Render.cpp new file mode 100644 index 0000000000..7204ab0d68 --- /dev/null +++ b/Source/Core/VideoBackends/OGL/Render.cpp @@ -0,0 +1,1843 @@ +// Copyright 2013 Dolphin Emulator Project +// Licensed under GPLv2 +// Refer to the license.txt file included. + +#include "Globals.h" +#include "Thread.h" +#include "Atomic.h" + +#include <vector> +#include <cmath> +#include <cstdio> +#include <cinttypes> + +#include "GLUtil.h" +#if defined(HAVE_WX) && HAVE_WX +#include "WxUtils.h" +#endif + +#include "FileUtil.h" + +#ifdef _WIN32 +#include <mmsystem.h> +#endif + +#include "CommonPaths.h" +#include "DriverDetails.h" +#include "VideoConfig.h" +#include "Statistics.h" +#include "ImageWrite.h" +#include "PixelEngine.h" +#include "Render.h" +#include "BPStructs.h" +#include "TextureCache.h" +#include "RasterFont.h" +#include "VertexShaderGen.h" +#include "DLCache.h" +#include "ProgramShaderCache.h" +#include "VertexShaderManager.h" +#include "VertexLoaderManager.h" +#include "VertexLoader.h" +#include "PostProcessing.h" +#include "TextureConverter.h" +#include "OnScreenDisplay.h" +#include "Timer.h" +#include "StringUtil.h" +#include "FramebufferManager.h" +#include "Fifo.h" +#include "Debugger.h" +#include "Core.h" +#include "Movie.h" +#include "BPFunctions.h" +#include "FPSCounter.h" +#include "ConfigManager.h" +#include "VertexManager.h" +#include "SamplerCache.h" +#include "StreamBuffer.h" + +#include "main.h" // Local +#ifdef _WIN32 +#include "EmuWindow.h" +#endif +#if defined _WIN32 || defined HAVE_LIBAV +#include "AVIDump.h" +#endif + +// glew1.8 doesn't define KHR_debug +#ifndef GL_DEBUG_OUTPUT +#define GL_DEBUG_OUTPUT 0x92E0 +#endif + + +void VideoConfig::UpdateProjectionHack() +{ + ::UpdateProjectionHack(g_Config.iPhackvalue, g_Config.sPhackvalue); +} + +int OSDInternalW, OSDInternalH; + +namespace OGL +{ + +enum MultisampleMode { + MULTISAMPLE_OFF, + MULTISAMPLE_2X, + MULTISAMPLE_4X, + MULTISAMPLE_8X, + MULTISAMPLE_CSAA_8X, + MULTISAMPLE_CSAA_8XQ, + MULTISAMPLE_CSAA_16X, + MULTISAMPLE_CSAA_16XQ, + MULTISAMPLE_SSAA_4X, +}; + + +VideoConfig g_ogl_config; + +// Declarations and definitions +// ---------------------------- +static int s_fps = 0; +static GLuint s_ShowEFBCopyRegions_VBO = 0; +static GLuint s_ShowEFBCopyRegions_VAO = 0; +static SHADER s_ShowEFBCopyRegions; + +static RasterFont* s_pfont = NULL; + +// 1 for no MSAA. Use s_MSAASamples > 1 to check for MSAA. +static int s_MSAASamples = 1; +static int s_MSAACoverageSamples = 0; +static int s_LastMultisampleMode = 0; + +static u32 s_blendMode; + +static bool s_vsync; + +#if defined(HAVE_WX) && HAVE_WX +static std::thread scrshotThread; +#endif + +// EFB cache related +static const u32 EFB_CACHE_RECT_SIZE = 64; // Cache 64x64 blocks. +static const u32 EFB_CACHE_WIDTH = (EFB_WIDTH + EFB_CACHE_RECT_SIZE - 1) / EFB_CACHE_RECT_SIZE; // round up +static const u32 EFB_CACHE_HEIGHT = (EFB_HEIGHT + EFB_CACHE_RECT_SIZE - 1) / EFB_CACHE_RECT_SIZE; +static bool s_efbCacheValid[2][EFB_CACHE_WIDTH * EFB_CACHE_HEIGHT]; +static std::vector<u32> s_efbCache[2][EFB_CACHE_WIDTH * EFB_CACHE_HEIGHT]; // 2 for PEEK_Z and PEEK_COLOR + +int GetNumMSAASamples(int MSAAMode) +{ + int samples; + switch (MSAAMode) + { + case MULTISAMPLE_OFF: + samples = 1; + break; + + case MULTISAMPLE_2X: + samples = 2; + break; + + case MULTISAMPLE_4X: + case MULTISAMPLE_CSAA_8X: + case MULTISAMPLE_CSAA_16X: + case MULTISAMPLE_SSAA_4X: + samples = 4; + break; + + case MULTISAMPLE_8X: + case MULTISAMPLE_CSAA_8XQ: + case MULTISAMPLE_CSAA_16XQ: + samples = 8; + break; + + default: + samples = 1; + } + + if(samples <= g_ogl_config.max_samples) return samples; + + // TODO: move this to InitBackendInfo + OSD::AddMessage(StringFromFormat("%d Anti Aliasing samples selected, but only %d supported by your GPU.", samples, g_ogl_config.max_samples), 10000); + return g_ogl_config.max_samples; +} + +int GetNumMSAACoverageSamples(int MSAAMode) +{ + int samples; + switch (g_ActiveConfig.iMultisampleMode) + { + case MULTISAMPLE_CSAA_8X: + case MULTISAMPLE_CSAA_8XQ: + samples = 8; + break; + + case MULTISAMPLE_CSAA_16X: + case MULTISAMPLE_CSAA_16XQ: + samples = 16; + break; + + default: + samples = 0; + } + if(g_ogl_config.bSupportCoverageMSAA || samples == 0) return samples; + + // TODO: move this to InitBackendInfo + OSD::AddMessage("CSAA Anti Aliasing isn't supported by your GPU.", 10000); + return 0; +} + +void ApplySSAASettings() { + // GLES3 doesn't support SSAA +#ifndef USE_GLES3 + if(g_ActiveConfig.iMultisampleMode == MULTISAMPLE_SSAA_4X) { + if(g_ogl_config.bSupportSampleShading) { + glEnable(GL_SAMPLE_SHADING_ARB); + glMinSampleShadingARB(s_MSAASamples); + } else { + // TODO: move this to InitBackendInfo + OSD::AddMessage("SSAA Anti Aliasing isn't supported by your GPU.", 10000); + } + } else if(g_ogl_config.bSupportSampleShading) { + glDisable(GL_SAMPLE_SHADING_ARB); + } +#endif +} + +void GLAPIENTRY ErrorCallback( GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const char* message, void* userParam) +{ + // GLES3 doesn't natively support this + // XXX: Include GLES2 extensions header so we can use this +#ifndef USE_GLES3 + const char *s_source; + const char *s_type; + + switch(source) + { + case GL_DEBUG_SOURCE_API_ARB: s_source = "API"; break; + case GL_DEBUG_SOURCE_WINDOW_SYSTEM_ARB: s_source = "Window System"; break; + case GL_DEBUG_SOURCE_SHADER_COMPILER_ARB: s_source = "Shader Compiler"; break; + case GL_DEBUG_SOURCE_THIRD_PARTY_ARB: s_source = "Third Party"; break; + case GL_DEBUG_SOURCE_APPLICATION_ARB: s_source = "Application"; break; + case GL_DEBUG_SOURCE_OTHER_ARB: s_source = "Other"; break; + default: s_source = "Unknown"; break; + } + switch(type) + { + case GL_DEBUG_TYPE_ERROR_ARB: s_type = "Error"; break; + case GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR_ARB: s_type = "Deprecated"; break; + case GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR_ARB: s_type = "Undefined"; break; + case GL_DEBUG_TYPE_PORTABILITY_ARB: s_type = "Portability"; break; + case GL_DEBUG_TYPE_PERFORMANCE_ARB: s_type = "Performance"; break; + case GL_DEBUG_TYPE_OTHER_ARB: s_type = "Other"; break; + default: s_type = "Unknown"; break; + } + switch(severity) + { + case GL_DEBUG_SEVERITY_HIGH_ARB: ERROR_LOG(VIDEO, "id: %x, source: %s, type: %s - %s", id, s_source, s_type, message); break; + case GL_DEBUG_SEVERITY_MEDIUM_ARB: WARN_LOG(VIDEO, "id: %x, source: %s, type: %s - %s", id, s_source, s_type, message); break; + case GL_DEBUG_SEVERITY_LOW_ARB: WARN_LOG(VIDEO, "id: %x, source: %s, type: %s - %s", id, s_source, s_type, message); break; + default: ERROR_LOG(VIDEO, "id: %x, source: %s, type: %s - %s", id, s_source, s_type, message); break; + } +#endif +} + +#ifndef USE_GLES3 +// Two small Fallbacks to avoid GL_ARB_ES2_compatibility +void GLAPIENTRY DepthRangef(GLfloat neardepth, GLfloat fardepth) +{ + glDepthRange(neardepth, fardepth); +} +void GLAPIENTRY ClearDepthf(GLfloat depthval) +{ + glClearDepth(depthval); +} +#endif + +void InitDriverInfo() +{ + std::string svendor = std::string(g_ogl_config.gl_vendor); + std::string srenderer = std::string(g_ogl_config.gl_renderer); + std::string sversion = std::string(g_ogl_config.gl_version); + DriverDetails::Vendor vendor = DriverDetails::VENDOR_UNKNOWN; + DriverDetails::Driver driver = DriverDetails::DRIVER_UNKNOWN; + double version = 0.0; + u32 family = 0; + + // Get the vendor first + if (svendor == "NVIDIA Corporation" && srenderer != "NVIDIA Tegra") + vendor = DriverDetails::VENDOR_NVIDIA; + else if (svendor == "ATI Technologies Inc." || svendor == "Advanced Micro Devices, Inc.") + vendor = DriverDetails::VENDOR_ATI; + else if (std::string::npos != sversion.find("Mesa")) + vendor = DriverDetails::VENDOR_MESA; + else if (std::string::npos != svendor.find("Intel")) + vendor = DriverDetails::VENDOR_INTEL; + else if (svendor == "ARM") + vendor = DriverDetails::VENDOR_ARM; + else if (svendor == "http://limadriver.org/") + { + vendor = DriverDetails::VENDOR_ARM; + driver = DriverDetails::DRIVER_LIMA; + } + else if (svendor == "Qualcomm") + vendor = DriverDetails::VENDOR_QUALCOMM; + else if (svendor == "Imagination Technologies") + vendor = DriverDetails::VENDOR_IMGTEC; + else if (svendor == "NVIDIA Corporation" && srenderer == "NVIDIA Tegra") + vendor = DriverDetails::VENDOR_TEGRA; + else if (svendor == "Vivante Corporation") + vendor = DriverDetails::VENDOR_VIVANTE; + + // Get device family and driver version...if we care about it + switch(vendor) + { + case DriverDetails::VENDOR_QUALCOMM: + { + if (std::string::npos != srenderer.find("Adreno (TM) 3")) + driver = DriverDetails::DRIVER_QUALCOMM_3XX; + else + driver = DriverDetails::DRIVER_QUALCOMM_2XX; + double glVersion; + sscanf(g_ogl_config.gl_version, "OpenGL ES %lg V@%lg", &glVersion, &version); + } + break; + case DriverDetails::VENDOR_ARM: + if (std::string::npos != srenderer.find("Mali-T6")) + driver = DriverDetails::DRIVER_ARM_T6XX; + else if(std::string::npos != srenderer.find("Mali-4")) + driver = DriverDetails::DRIVER_ARM_4XX; + break; + case DriverDetails::VENDOR_MESA: + { + if(svendor == "nouveau") + driver = DriverDetails::DRIVER_NOUVEAU; + else if(svendor == "Intel Open Source Technology Center") + driver = DriverDetails::DRIVER_I965; + else if(std::string::npos != srenderer.find("AMD") || std::string::npos != srenderer.find("ATI")) + driver = DriverDetails::DRIVER_R600; + + int major = 0; + int minor = 0; + int release = 0; + sscanf(g_ogl_config.gl_version, "%*s Mesa %d.%d.%d", &major, &minor, &release); + version = 100*major + 10*minor + release; + } + break; + case DriverDetails::VENDOR_INTEL: // Happens in OS X + sscanf(g_ogl_config.gl_renderer, "Intel HD Graphics %d", &family); + /* + int glmajor = 0; + int glminor = 0; + int major = 0; + int minor = 0; + int release = 0; + sscanf(g_ogl_config.gl_version, "%d.%d INTEL-%d.%d.%d", &glmajor, &glminor, &major, &minor, &release); + version = 10000*major + 1000*minor + release; + */ + break; + // We don't care about these + default: + break; + } + DriverDetails::Init(vendor, driver, version, family); +} + +// Init functions +Renderer::Renderer() +{ + OSDInternalW = 0; + OSDInternalH = 0; + + s_fps=0; + s_ShowEFBCopyRegions_VBO = 0; + s_blendMode = 0; + InitFPSCounter(); + + bool bSuccess = true; + + g_ogl_config.gl_vendor = (const char*)glGetString(GL_VENDOR); + g_ogl_config.gl_renderer = (const char*)glGetString(GL_RENDERER); + g_ogl_config.gl_version = (const char*)glGetString(GL_VERSION); + g_ogl_config.glsl_version = (const char*)glGetString(GL_SHADING_LANGUAGE_VERSION); + + InitDriverInfo(); + + // Init extension support. +#ifdef USE_GLES3 + // Set default GLES3 options + GLFunc::Init(); + WARN_LOG(VIDEO, "Running the OpenGL ES 3 backend!"); + + g_Config.backend_info.bSupportsDualSourceBlend = false; + g_Config.backend_info.bSupportsGLSLUBO = !DriverDetails::HasBug(DriverDetails::BUG_ANNIHILATEDUBOS); + g_Config.backend_info.bSupportsPrimitiveRestart = true; + g_Config.backend_info.bSupportsEarlyZ = false; + + g_ogl_config.bSupportsGLSLCache = true; + g_ogl_config.bSupportsGLPinnedMemory = false; + g_ogl_config.bSupportsGLSync = true; + g_ogl_config.bSupportsGLBaseVertex = false; + g_ogl_config.bSupportCoverageMSAA = false; // XXX: GLES3 spec has MSAA + g_ogl_config.bSupportSampleShading = false; + g_ogl_config.bSupportOGL31 = false; + g_ogl_config.bSupportViewportFloat = false; + g_ogl_config.eSupportedGLSLVersion = GLSLES3; +#else +#ifdef __APPLE__ + glewExperimental = 1; +#endif + if (glewInit() != GLEW_OK) + { + PanicAlert("glewInit() failed! Does your video card support OpenGL 2.x?"); + return; + } + + // check for the max vertex attributes + GLint numvertexattribs = 0; + glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &numvertexattribs); + if (numvertexattribs < 16) + { + PanicAlert("GPU: OGL ERROR: Number of attributes %d not enough.\n" + "GPU: Does your video card support OpenGL 2.x?", + numvertexattribs); + bSuccess = false; + } + + // check the max texture width and height + GLint max_texture_size; + glGetIntegerv(GL_MAX_TEXTURE_SIZE, (GLint *)&max_texture_size); + if (max_texture_size < 1024) + { + PanicAlert("GL_MAX_TEXTURE_SIZE too small at %i - must be at least 1024.", + max_texture_size); + bSuccess = false; + } + +#if defined(_DEBUG) || defined(DEBUGFAST) + if (GLEW_ARB_debug_output) + { + glDebugMessageControlARB(GL_DONT_CARE, GL_DONT_CARE, GL_DONT_CARE, 0, NULL, true); + glDebugMessageCallbackARB( ErrorCallback, NULL ); + glEnable( GL_DEBUG_OUTPUT ); + } +#endif + + if (!GLEW_VERSION_2_0) + { + // OpenGL 2.0 is required for all shader based drawings. There is no way to get this by extensions + PanicAlert("GPU: OGL ERROR: Does your video card support OpenGL 2.0?"); + bSuccess = false; + } + + if (!GLEW_ARB_framebuffer_object) + { + // We want the ogl3 framebuffer instead of the ogl2 one for better blitting support. + // It's also compatible with the gles3 one. + PanicAlert("GPU: ERROR: Need GL_ARB_framebuffer_object for multiple render targets.\n" + "GPU: Does your video card support OpenGL 3.0?"); + bSuccess = false; + } + + if (!GLEW_ARB_vertex_array_object) + { + // This extension is used to replace lots of pointer setting function. + // Also gles3 requires to use it. + PanicAlert("GPU: OGL ERROR: Need GL_ARB_vertex_array_object.\n" + "GPU: Does your video card support OpenGL 3.0?"); + bSuccess = false; + } + + if (!GLEW_ARB_map_buffer_range) + { + // ogl3 buffer mapping for better streaming support. + // The ogl2 one also isn't in gles3. + PanicAlert("GPU: OGL ERROR: Need GL_ARB_map_buffer_range.\n" + "GPU: Does your video card support OpenGL 3.0?"); + bSuccess = false; + } + + if (!GLEW_ARB_sampler_objects && bSuccess) + { + // Our sampler cache uses this extension. It could easyly be workaround and it's by far the + // highest requirement, but it seems that no driver lacks support for it. + PanicAlert("GPU: OGL ERROR: Need GL_ARB_sampler_objects." + "GPU: Does your video card support OpenGL 3.3?" + "Please report this issue, then there will be a workaround"); + bSuccess = false; + } + + // OpenGL 3 doesn't provide GLES like float functions for depth. + // They are in core in OpenGL 4.1, so almost every driver should support them. + // But for the oldest ones, we provide fallbacks to the old double functions. + if (!GLEW_ARB_ES2_compatibility) + { + glDepthRangef = DepthRangef; + glClearDepthf = ClearDepthf; + + } + +#define TO_BOOL(c) (0 != (c)) + + g_Config.backend_info.bSupportsDualSourceBlend = TO_BOOL(GLEW_ARB_blend_func_extended); + g_Config.backend_info.bSupportsGLSLUBO = TO_BOOL(GLEW_ARB_uniform_buffer_object); + g_Config.backend_info.bSupportsPrimitiveRestart = !DriverDetails::HasBug(DriverDetails::BUG_PRIMITIVERESTART) && + (TO_BOOL(GLEW_VERSION_3_1) || TO_BOOL(GLEW_NV_primitive_restart)); + g_Config.backend_info.bSupportsEarlyZ = TO_BOOL(GLEW_ARB_shader_image_load_store); + + g_ogl_config.bSupportsGLSLCache = TO_BOOL(GLEW_ARB_get_program_binary); + g_ogl_config.bSupportsGLPinnedMemory = TO_BOOL(GLEW_AMD_pinned_memory); + g_ogl_config.bSupportsGLSync = TO_BOOL(GLEW_ARB_sync); + g_ogl_config.bSupportsGLBaseVertex = TO_BOOL(GLEW_ARB_draw_elements_base_vertex); + g_ogl_config.bSupportCoverageMSAA = TO_BOOL(GLEW_NV_framebuffer_multisample_coverage); + g_ogl_config.bSupportSampleShading = TO_BOOL(GLEW_ARB_sample_shading); + g_ogl_config.bSupportOGL31 = TO_BOOL(GLEW_VERSION_3_1); + g_ogl_config.bSupportViewportFloat = TO_BOOL(GLEW_ARB_viewport_array); + +#undef TO_BOOL + + if(strstr(g_ogl_config.glsl_version, "1.00") || strstr(g_ogl_config.glsl_version, "1.10") || strstr(g_ogl_config.glsl_version, "1.20")) + { + PanicAlert("GPU: OGL ERROR: Need at least GLSL 1.30\n" + "GPU: Does your video card support OpenGL 3.0?\n" + "GPU: Your driver supports GLSL %s", g_ogl_config.glsl_version); + bSuccess = false; + } + else if(strstr(g_ogl_config.glsl_version, "1.30")) + { + g_ogl_config.eSupportedGLSLVersion = GLSL_130; + g_Config.backend_info.bSupportsEarlyZ = false; // layout keyword is only supported on glsl150+ + } + else if(strstr(g_ogl_config.glsl_version, "1.40")) + { + g_ogl_config.eSupportedGLSLVersion = GLSL_140; + g_Config.backend_info.bSupportsEarlyZ = false; // layout keyword is only supported on glsl150+ + } + else + { + g_ogl_config.eSupportedGLSLVersion = GLSL_150; + } +#endif + int samples; + glGetIntegerv(GL_SAMPLES, &samples); + if(samples > 1) + { + // MSAA on default framebuffer isn't working because of glBlitFramebuffer. + // It also isn't useful as we don't render anything to the default framebuffer. + // We also try to get a non-msaa fb, so this only happens when forced by the driver. + PanicAlert("MSAA on default framebuffer isn't supported.\n" + "Please avoid forcing dolphin to use MSAA by the driver.\n" + "%d samples on default framebuffer found.", samples); + bSuccess = false; + } + + if (!bSuccess) + { + // Not all needed extensions are supported, so we have to stop here. + // Else some of the next calls might crash. + return; + } + + glGetIntegerv(GL_MAX_SAMPLES, &g_ogl_config.max_samples); + if(g_ogl_config.max_samples < 1) + g_ogl_config.max_samples = 1; + + if(g_Config.backend_info.bSupportsGLSLUBO && DriverDetails::HasBug(DriverDetails::BUG_BROKENUBO)) + { + g_Config.backend_info.bSupportsGLSLUBO = false; + ERROR_LOG(VIDEO, "Buggy driver detected. Disable UBO"); + OSD::AddMessage("Major performance warning: Buggy GPU driver detected.", 20000); + OSD::AddMessage("Please either install the closed-source GPU driver or update your Mesa 3D version.", 20000); + } + + UpdateActiveConfig(); + + OSD::AddMessage(StringFromFormat("Video Info: %s, %s, %s", + g_ogl_config.gl_vendor, + g_ogl_config.gl_renderer, + g_ogl_config.gl_version), 5000); + + WARN_LOG(VIDEO,"Missing OGL Extensions: %s%s%s%s%s%s%s%s%s%s", + g_ActiveConfig.backend_info.bSupportsDualSourceBlend ? "" : "DualSourceBlend ", + g_ActiveConfig.backend_info.bSupportsGLSLUBO ? "" : "UniformBuffer ", + g_ActiveConfig.backend_info.bSupportsPrimitiveRestart ? "" : "PrimitiveRestart ", + g_ActiveConfig.backend_info.bSupportsEarlyZ ? "" : "EarlyZ ", + g_ogl_config.bSupportsGLPinnedMemory ? "" : "PinnedMemory ", + g_ogl_config.bSupportsGLSLCache ? "" : "ShaderCache ", + g_ogl_config.bSupportsGLBaseVertex ? "" : "BaseVertex ", + g_ogl_config.bSupportsGLSync ? "" : "Sync ", + g_ogl_config.bSupportCoverageMSAA ? "" : "CSAA ", + g_ogl_config.bSupportSampleShading ? "" : "SSAA " + ); + + s_LastMultisampleMode = g_ActiveConfig.iMultisampleMode; + s_MSAASamples = GetNumMSAASamples(s_LastMultisampleMode); + s_MSAACoverageSamples = GetNumMSAACoverageSamples(s_LastMultisampleMode); + ApplySSAASettings(); + + // Decide framebuffer size + s_backbuffer_width = (int)GLInterface->GetBackBufferWidth(); + s_backbuffer_height = (int)GLInterface->GetBackBufferHeight(); + + // Handle VSync on/off + s_vsync = g_ActiveConfig.IsVSync(); + GLInterface->SwapInterval(s_vsync); + + // TODO: Move these somewhere else? + FramebufferManagerBase::SetLastXfbWidth(MAX_XFB_WIDTH); + FramebufferManagerBase::SetLastXfbHeight(MAX_XFB_HEIGHT); + + UpdateDrawRectangle(s_backbuffer_width, s_backbuffer_height); + + s_LastEFBScale = g_ActiveConfig.iEFBScale; + CalculateTargetSize(s_backbuffer_width, s_backbuffer_height); + + // Because of the fixed framebuffer size we need to disable the resolution + // options while running + g_Config.bRunning = true; + + glStencilFunc(GL_ALWAYS, 0, 0); + glBlendFunc(GL_ONE, GL_ONE); + + glViewport(0, 0, GetTargetWidth(), GetTargetHeight()); // Reset The Current Viewport + + glClearColor(0.0f, 0.0f, 0.0f, 1.0f); + glClearDepthf(1.0f); + glEnable(GL_DEPTH_TEST); + glDepthFunc(GL_LEQUAL); + + glPixelStorei(GL_UNPACK_ALIGNMENT, 4); // 4-byte pixel alignment + + glDisable(GL_STENCIL_TEST); + glEnable(GL_SCISSOR_TEST); + + glScissor(0, 0, GetTargetWidth(), GetTargetHeight()); + glBlendColor(0, 0, 0, 0.5f); + glClearDepthf(1.0f); + + if(g_ActiveConfig.backend_info.bSupportsPrimitiveRestart) + { +#ifdef USE_GLES3 + glEnable(GL_PRIMITIVE_RESTART_FIXED_INDEX); +#else + if(g_ogl_config.bSupportOGL31) + { + glEnable(GL_PRIMITIVE_RESTART); + glPrimitiveRestartIndex(65535); + } + else + { + glEnableClientState(GL_PRIMITIVE_RESTART_NV); + glPrimitiveRestartIndexNV(65535); + } +#endif + } + UpdateActiveConfig(); +} + +Renderer::~Renderer() +{ + +#if defined(HAVE_WX) && HAVE_WX + if (scrshotThread.joinable()) + scrshotThread.join(); +#endif +} + +void Renderer::Shutdown() +{ + delete g_framebuffer_manager; + + g_Config.bRunning = false; + UpdateActiveConfig(); + + glDeleteBuffers(1, &s_ShowEFBCopyRegions_VBO); + glDeleteVertexArrays(1, &s_ShowEFBCopyRegions_VAO); + s_ShowEFBCopyRegions_VBO = 0; + + delete s_pfont; + s_pfont = 0; + s_ShowEFBCopyRegions.Destroy(); +} + +void Renderer::Init() +{ + // Initialize the FramebufferManager + g_framebuffer_manager = new FramebufferManager(s_target_width, s_target_height, + s_MSAASamples, s_MSAACoverageSamples); + + s_pfont = new RasterFont(); + + ProgramShaderCache::CompileShader(s_ShowEFBCopyRegions, + "ATTRIN vec2 rawpos;\n" + "ATTRIN vec3 color0;\n" + "VARYOUT vec4 c;\n" + "void main(void) {\n" + " gl_Position = vec4(rawpos, 0.0, 1.0);\n" + " c = vec4(color0, 1.0);\n" + "}\n", + "VARYIN vec4 c;\n" + "out vec4 ocol0;\n" + "void main(void) {\n" + " ocol0 = c;\n" + "}\n"); + + // creating buffers + glGenBuffers(1, &s_ShowEFBCopyRegions_VBO); + glGenVertexArrays(1, &s_ShowEFBCopyRegions_VAO); + glBindBuffer(GL_ARRAY_BUFFER, s_ShowEFBCopyRegions_VBO); + glBindVertexArray( s_ShowEFBCopyRegions_VAO ); + glEnableVertexAttribArray(SHADER_POSITION_ATTRIB); + glVertexAttribPointer(SHADER_POSITION_ATTRIB, 2, GL_FLOAT, 0, sizeof(GLfloat)*5, NULL); + glEnableVertexAttribArray(SHADER_COLOR0_ATTRIB); + glVertexAttribPointer(SHADER_COLOR0_ATTRIB, 3, GL_FLOAT, 0, sizeof(GLfloat)*5, (GLfloat*)NULL+2); +} + +// Create On-Screen-Messages +void Renderer::DrawDebugInfo() +{ + // Reset viewport for drawing text + glViewport(0, 0, GLInterface->GetBackBufferWidth(), GLInterface->GetBackBufferHeight()); + // Draw various messages on the screen, like FPS, statistics, etc. + char debugtext_buffer[8192]; + char *p = debugtext_buffer; + p[0] = 0; + + if (g_ActiveConfig.bShowFPS) + p+=sprintf(p, "FPS: %d\n", s_fps); + + if (SConfig::GetInstance().m_ShowLag) + p+=sprintf(p, "Lag: %" PRIu64 "\n", Movie::g_currentLagCount); + + if (g_ActiveConfig.bShowInputDisplay) + p+=sprintf(p, "%s", Movie::GetInputDisplay().c_str()); + +#ifndef USE_GLES3 + if (g_ActiveConfig.bShowEFBCopyRegions) + { + // Set Line Size + glLineWidth(3.0f); + + // 2*Coords + 3*Color + u32 length = stats.efb_regions.size() * sizeof(GLfloat) * (2+3)*2*6; + glBindBuffer(GL_ARRAY_BUFFER, s_ShowEFBCopyRegions_VBO); + glBufferData(GL_ARRAY_BUFFER, length, NULL, GL_STREAM_DRAW); + GLfloat *Vertices = (GLfloat*)glMapBufferRange(GL_ARRAY_BUFFER, 0, length, GL_MAP_WRITE_BIT); + + // Draw EFB copy regions rectangles + int a = 0; + GLfloat color[3] = {0.0f, 1.0f, 1.0f}; + + for (std::vector<EFBRectangle>::const_iterator it = stats.efb_regions.begin(); + it != stats.efb_regions.end(); ++it) + { + GLfloat halfWidth = EFB_WIDTH / 2.0f; + GLfloat halfHeight = EFB_HEIGHT / 2.0f; + GLfloat x = (GLfloat) -1.0f + ((GLfloat)it->left / halfWidth); + GLfloat y = (GLfloat) 1.0f - ((GLfloat)it->top / halfHeight); + GLfloat x2 = (GLfloat) -1.0f + ((GLfloat)it->right / halfWidth); + GLfloat y2 = (GLfloat) 1.0f - ((GLfloat)it->bottom / halfHeight); + + Vertices[a++] = x; + Vertices[a++] = y; + Vertices[a++] = color[0]; + Vertices[a++] = color[1]; + Vertices[a++] = color[2]; + + Vertices[a++] = x2; + Vertices[a++] = y; + Vertices[a++] = color[0]; + Vertices[a++] = color[1]; + Vertices[a++] = color[2]; + + + Vertices[a++] = x2; + Vertices[a++] = y; + Vertices[a++] = color[0]; + Vertices[a++] = color[1]; + Vertices[a++] = color[2]; + + Vertices[a++] = x2; + Vertices[a++] = y2; + Vertices[a++] = color[0]; + Vertices[a++] = color[1]; + Vertices[a++] = color[2]; + + + Vertices[a++] = x2; + Vertices[a++] = y2; + Vertices[a++] = color[0]; + Vertices[a++] = color[1]; + Vertices[a++] = color[2]; + + Vertices[a++] = x; + Vertices[a++] = y2; + Vertices[a++] = color[0]; + Vertices[a++] = color[1]; + Vertices[a++] = color[2]; + + + Vertices[a++] = x; + Vertices[a++] = y2; + Vertices[a++] = color[0]; + Vertices[a++] = color[1]; + Vertices[a++] = color[2]; + + Vertices[a++] = x; + Vertices[a++] = y; + Vertices[a++] = color[0]; + Vertices[a++] = color[1]; + Vertices[a++] = color[2]; + + + Vertices[a++] = x; + Vertices[a++] = y; + Vertices[a++] = color[0]; + Vertices[a++] = color[1]; + Vertices[a++] = color[2]; + + Vertices[a++] = x2; + Vertices[a++] = y2; + Vertices[a++] = color[0]; + Vertices[a++] = color[1]; + Vertices[a++] = color[2]; + + + Vertices[a++] = x2; + Vertices[a++] = y; + Vertices[a++] = color[0]; + Vertices[a++] = color[1]; + Vertices[a++] = color[2]; + + Vertices[a++] = x; + Vertices[a++] = y2; + Vertices[a++] = color[0]; + Vertices[a++] = color[1]; + Vertices[a++] = color[2]; + + // TO DO: build something nicer here + GLfloat temp = color[0]; + color[0] = color[1]; + color[1] = color[2]; + color[2] = temp; + } + glUnmapBuffer(GL_ARRAY_BUFFER); + + s_ShowEFBCopyRegions.Bind(); + glBindVertexArray( s_ShowEFBCopyRegions_VAO ); + glDrawArrays(GL_LINES, 0, stats.efb_regions.size() * 2*6); + + // Restore Line Size + SetLineWidth(); + + // Clear stored regions + stats.efb_regions.clear(); + } +#endif + + if (g_ActiveConfig.bOverlayStats) + p = Statistics::ToString(p); + + if (g_ActiveConfig.bOverlayProjStats) + p = Statistics::ToStringProj(p); + + // Render a shadow, and then the text. + if (p != debugtext_buffer) + { + Renderer::RenderText(debugtext_buffer, 21, 21, 0xDD000000); + Renderer::RenderText(debugtext_buffer, 20, 20, 0xFF00FFFF); + } +} + +void Renderer::RenderText(const char *text, int left, int top, u32 color) +{ + const int nBackbufferWidth = (int)GLInterface->GetBackBufferWidth(); + const int nBackbufferHeight = (int)GLInterface->GetBackBufferHeight(); + + s_pfont->printMultilineText(text, + left * 2.0f / (float)nBackbufferWidth - 1, + 1 - top * 2.0f / (float)nBackbufferHeight, + 0, nBackbufferWidth, nBackbufferHeight, color); + + GL_REPORT_ERRORD(); +} + +TargetRectangle Renderer::ConvertEFBRectangle(const EFBRectangle& rc) +{ + TargetRectangle result; + result.left = EFBToScaledX(rc.left); + result.top = EFBToScaledY(EFB_HEIGHT - rc.top); + result.right = EFBToScaledX(rc.right); + result.bottom = EFBToScaledY(EFB_HEIGHT - rc.bottom); + return result; +} + +// Function: This function handles the OpenGL glScissor() function +// ---------------------------- +// Call browser: OpcodeDecoding.cpp ExecuteDisplayList > Decode() > LoadBPReg() +// case 0x52 > SetScissorRect() +// ---------------------------- +// bpmem.scissorTL.x, y = 342x342 +// bpmem.scissorBR.x, y = 981x821 +// Renderer::GetTargetHeight() = the fixed ini file setting +// donkopunchstania - it appears scissorBR is the bottom right pixel inside the scissor box +// therefore the width and height are (scissorBR + 1) - scissorTL +void Renderer::SetScissorRect(const TargetRectangle& rc) +{ + glScissor(rc.left, rc.bottom, rc.GetWidth(), rc.GetHeight()); +} + +void Renderer::SetColorMask() +{ + // Only enable alpha channel if it's supported by the current EFB format + GLenum ColorMask = GL_FALSE, AlphaMask = GL_FALSE; + if (bpmem.alpha_test.TestResult() != AlphaTest::FAIL) + { + if (bpmem.blendmode.colorupdate) + ColorMask = GL_TRUE; + if (bpmem.blendmode.alphaupdate && (bpmem.zcontrol.pixel_format == PIXELFMT_RGBA6_Z24)) + AlphaMask = GL_TRUE; + } + glColorMask(ColorMask, ColorMask, ColorMask, AlphaMask); +} + +void ClearEFBCache() +{ + for (u32 i = 0; i < EFB_CACHE_WIDTH * EFB_CACHE_HEIGHT; ++i) + s_efbCacheValid[0][i] = false; + + for (u32 i = 0; i < EFB_CACHE_WIDTH * EFB_CACHE_HEIGHT; ++i) + s_efbCacheValid[1][i] = false; +} + +void Renderer::UpdateEFBCache(EFBAccessType type, u32 cacheRectIdx, const EFBRectangle& efbPixelRc, const TargetRectangle& targetPixelRc, const u32* data) +{ + u32 cacheType = (type == PEEK_Z ? 0 : 1); + + if (!s_efbCache[cacheType][cacheRectIdx].size()) + s_efbCache[cacheType][cacheRectIdx].resize(EFB_CACHE_RECT_SIZE * EFB_CACHE_RECT_SIZE); + + u32 targetPixelRcWidth = targetPixelRc.right - targetPixelRc.left; + u32 efbPixelRcHeight = efbPixelRc.bottom - efbPixelRc.top; + u32 efbPixelRcWidth = efbPixelRc.right - efbPixelRc.left; + + for (u32 yCache = 0; yCache < efbPixelRcHeight; ++yCache) + { + u32 yEFB = efbPixelRc.top + yCache; + u32 yPixel = (EFBToScaledY(EFB_HEIGHT - yEFB) + EFBToScaledY(EFB_HEIGHT - yEFB - 1)) / 2; + u32 yData = yPixel - targetPixelRc.bottom; + + for (u32 xCache = 0; xCache < efbPixelRcWidth; ++xCache) + { + u32 xEFB = efbPixelRc.left + xCache; + u32 xPixel = (EFBToScaledX(xEFB) + EFBToScaledX(xEFB + 1)) / 2; + u32 xData = xPixel - targetPixelRc.left; + s_efbCache[cacheType][cacheRectIdx][yCache * EFB_CACHE_RECT_SIZE + xCache] = data[yData * targetPixelRcWidth + xData]; + } + } + + s_efbCacheValid[cacheType][cacheRectIdx] = true; +} + +// This function allows the CPU to directly access the EFB. +// There are EFB peeks (which will read the color or depth of a pixel) +// and EFB pokes (which will change the color or depth of a pixel). +// +// The behavior of EFB peeks can only be modified by: +// - GX_PokeAlphaRead +// The behavior of EFB pokes can be modified by: +// - GX_PokeAlphaMode (TODO) +// - GX_PokeAlphaUpdate (TODO) +// - GX_PokeBlendMode (TODO) +// - GX_PokeColorUpdate (TODO) +// - GX_PokeDither (TODO) +// - GX_PokeDstAlpha (TODO) +// - GX_PokeZMode (TODO) +u32 Renderer::AccessEFB(EFBAccessType type, u32 x, u32 y, u32 poke_data) +{ + if (!g_ActiveConfig.bEFBAccessEnable) + return 0; + + u32 cacheRectIdx = (y / EFB_CACHE_RECT_SIZE) * EFB_CACHE_WIDTH + + (x / EFB_CACHE_RECT_SIZE); + + // Get the rectangular target region containing the EFB pixel + EFBRectangle efbPixelRc; + efbPixelRc.left = (x / EFB_CACHE_RECT_SIZE) * EFB_CACHE_RECT_SIZE; + efbPixelRc.top = (y / EFB_CACHE_RECT_SIZE) * EFB_CACHE_RECT_SIZE; + efbPixelRc.right = std::min(efbPixelRc.left + EFB_CACHE_RECT_SIZE, (u32)EFB_WIDTH); + efbPixelRc.bottom = std::min(efbPixelRc.top + EFB_CACHE_RECT_SIZE, (u32)EFB_HEIGHT); + + TargetRectangle targetPixelRc = ConvertEFBRectangle(efbPixelRc); + u32 targetPixelRcWidth = targetPixelRc.right - targetPixelRc.left; + u32 targetPixelRcHeight = targetPixelRc.top - targetPixelRc.bottom; + + // TODO (FIX) : currently, AA path is broken/offset and doesn't return the correct pixel + switch (type) + { + case PEEK_Z: + { + u32 z; + + if (!s_efbCacheValid[0][cacheRectIdx]) + { + if (s_MSAASamples > 1) + { + g_renderer->ResetAPIState(); + + // Resolve our rectangle. + FramebufferManager::GetEFBDepthTexture(efbPixelRc); + glBindFramebuffer(GL_READ_FRAMEBUFFER, FramebufferManager::GetResolvedFramebuffer()); + + g_renderer->RestoreAPIState(); + } + + u32* depthMap = new u32[targetPixelRcWidth * targetPixelRcHeight]; + + glReadPixels(targetPixelRc.left, targetPixelRc.bottom, targetPixelRcWidth, targetPixelRcHeight, + GL_DEPTH_COMPONENT, GL_UNSIGNED_INT, depthMap); + GL_REPORT_ERRORD(); + + UpdateEFBCache(type, cacheRectIdx, efbPixelRc, targetPixelRc, depthMap); + + delete[] depthMap; + } + + u32 xRect = x % EFB_CACHE_RECT_SIZE; + u32 yRect = y % EFB_CACHE_RECT_SIZE; + z = s_efbCache[0][cacheRectIdx][yRect * EFB_CACHE_RECT_SIZE + xRect]; + + // Scale the 32-bit value returned by glReadPixels to a 24-bit + // value (GC uses a 24-bit Z-buffer). + // TODO: in RE0 this value is often off by one, which causes lighting to disappear + if(bpmem.zcontrol.pixel_format == PIXELFMT_RGB565_Z16) + { + // if Z is in 16 bit format you must return a 16 bit integer + z = z >> 16; + } + else + { + z = z >> 8; + } + return z; + } + + case PEEK_COLOR: // GXPeekARGB + { + // Although it may sound strange, this really is A8R8G8B8 and not RGBA or 24-bit... + + // Tested in Killer 7, the first 8bits represent the alpha value which is used to + // determine if we're aiming at an enemy (0x80 / 0x88) or not (0x70) + // Wind Waker is also using it for the pictograph to determine the color of each pixel + + u32 color; + + if (!s_efbCacheValid[1][cacheRectIdx]) + { + if (s_MSAASamples > 1) + { + g_renderer->ResetAPIState(); + + // Resolve our rectangle. + FramebufferManager::GetEFBColorTexture(efbPixelRc); + glBindFramebuffer(GL_READ_FRAMEBUFFER, FramebufferManager::GetResolvedFramebuffer()); + + g_renderer->RestoreAPIState(); + } + + u32* colorMap = new u32[targetPixelRcWidth * targetPixelRcHeight]; + +#ifdef USE_GLES3 + // XXX: Swap colours + glReadPixels(targetPixelRc.left, targetPixelRc.bottom, targetPixelRcWidth, targetPixelRcHeight, + GL_RGBA, GL_UNSIGNED_BYTE, colorMap); +#else + glReadPixels(targetPixelRc.left, targetPixelRc.bottom, targetPixelRcWidth, targetPixelRcHeight, + GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, colorMap); +#endif + GL_REPORT_ERRORD(); + + UpdateEFBCache(type, cacheRectIdx, efbPixelRc, targetPixelRc, colorMap); + + delete[] colorMap; + } + + u32 xRect = x % EFB_CACHE_RECT_SIZE; + u32 yRect = y % EFB_CACHE_RECT_SIZE; + color = s_efbCache[1][cacheRectIdx][yRect * EFB_CACHE_RECT_SIZE + xRect]; + + // check what to do with the alpha channel (GX_PokeAlphaRead) + PixelEngine::UPEAlphaReadReg alpha_read_mode; + PixelEngine::Read16((u16&)alpha_read_mode, PE_ALPHAREAD); + + if (bpmem.zcontrol.pixel_format == PIXELFMT_RGBA6_Z24) + { + color = RGBA8ToRGBA6ToRGBA8(color); + } + else if (bpmem.zcontrol.pixel_format == PIXELFMT_RGB565_Z16) + { + color = RGBA8ToRGB565ToRGBA8(color); + } + if(bpmem.zcontrol.pixel_format != PIXELFMT_RGBA6_Z24) + { + color |= 0xFF000000; + } + if(alpha_read_mode.ReadMode == 2) return color; // GX_READ_NONE + else if(alpha_read_mode.ReadMode == 1) return (color | 0xFF000000); // GX_READ_FF + else /*if(alpha_read_mode.ReadMode == 0)*/ return (color & 0x00FFFFFF); // GX_READ_00 + } + + case POKE_COLOR: + case POKE_Z: + // TODO: Implement. One way is to draw a tiny pixel-sized rectangle at + // the exact location. Note: EFB pokes are susceptible to Z-buffering + // and perhaps blending. + //WARN_LOG(VIDEOINTERFACE, "This is probably some kind of software rendering"); + break; + + default: + break; + } + + return 0; +} + +// Called from VertexShaderManager +void Renderer::UpdateViewport() +{ + // reversed gxsetviewport(xorig, yorig, width, height, nearz, farz) + // [0] = width/2 + // [1] = height/2 + // [2] = 16777215 * (farz - nearz) + // [3] = xorig + width/2 + 342 + // [4] = yorig + height/2 + 342 + // [5] = 16777215 * farz + + int scissorXOff = bpmem.scissorOffset.x * 2; + int scissorYOff = bpmem.scissorOffset.y * 2; + + // TODO: ceil, floor or just cast to int? + float X = EFBToScaledXf(xfregs.viewport.xOrig - xfregs.viewport.wd - (float)scissorXOff); + float Y = EFBToScaledYf((float)EFB_HEIGHT - xfregs.viewport.yOrig + xfregs.viewport.ht + (float)scissorYOff); + float Width = EFBToScaledXf(2.0f * xfregs.viewport.wd); + float Height = EFBToScaledYf(-2.0f * xfregs.viewport.ht); + float GLNear = (xfregs.viewport.farZ - xfregs.viewport.zRange) / 16777216.0f; + float GLFar = xfregs.viewport.farZ / 16777216.0f; + if (Width < 0) + { + X += Width; + Width *= -1; + } + if (Height < 0) + { + Y += Height; + Height *= -1; + } + + // Update the view port + if(g_ogl_config.bSupportViewportFloat) + { + glViewportIndexedf(0, X, Y, Width, Height); + } + else + { + glViewport(ceil(X), ceil(Y), ceil(Width), ceil(Height)); + } + glDepthRangef(GLNear, GLFar); +} + +void Renderer::ClearScreen(const EFBRectangle& rc, bool colorEnable, bool alphaEnable, bool zEnable, u32 color, u32 z) +{ + ResetAPIState(); + + // color + GLboolean const + color_mask = colorEnable ? GL_TRUE : GL_FALSE, + alpha_mask = alphaEnable ? GL_TRUE : GL_FALSE; + glColorMask(color_mask, color_mask, color_mask, alpha_mask); + + glClearColor( + float((color >> 16) & 0xFF) / 255.0f, + float((color >> 8) & 0xFF) / 255.0f, + float((color >> 0) & 0xFF) / 255.0f, + float((color >> 24) & 0xFF) / 255.0f); + + // depth + glDepthMask(zEnable ? GL_TRUE : GL_FALSE); + + glClearDepthf(float(z & 0xFFFFFF) / float(0xFFFFFF)); + + // Update rect for clearing the picture + glEnable(GL_SCISSOR_TEST); + + TargetRectangle const targetRc = ConvertEFBRectangle(rc); + glScissor(targetRc.left, targetRc.bottom, targetRc.GetWidth(), targetRc.GetHeight()); + + // glColorMask/glDepthMask/glScissor affect glClear (glViewport does not) + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + RestoreAPIState(); + + ClearEFBCache(); +} + +void Renderer::ReinterpretPixelData(unsigned int convtype) +{ + if (convtype == 0 || convtype == 2) + { + FramebufferManager::ReinterpretPixelData(convtype); + } + else + { + ERROR_LOG(VIDEO, "Trying to reinterpret pixel data with unsupported conversion type %d", convtype); + } +} + +void Renderer::SetBlendMode(bool forceUpdate) +{ + // Our render target always uses an alpha channel, so we need to override the blend functions to assume a destination alpha of 1 if the render target isn't supposed to have an alpha channel + // Example: D3DBLEND_DESTALPHA needs to be D3DBLEND_ONE since the result without an alpha channel is assumed to always be 1. + bool target_has_alpha = bpmem.zcontrol.pixel_format == PIXELFMT_RGBA6_Z24; + + bool useDstAlpha = !g_ActiveConfig.bDstAlphaPass && bpmem.dstalpha.enable && bpmem.blendmode.alphaupdate && target_has_alpha; + bool useDualSource = useDstAlpha && g_ActiveConfig.backend_info.bSupportsDualSourceBlend; + + const GLenum glSrcFactors[8] = + { + GL_ZERO, + GL_ONE, + GL_DST_COLOR, + GL_ONE_MINUS_DST_COLOR, + (useDualSource) ? GL_SRC1_ALPHA : (GLenum)GL_SRC_ALPHA, + (useDualSource) ? GL_ONE_MINUS_SRC1_ALPHA : (GLenum)GL_ONE_MINUS_SRC_ALPHA, + (target_has_alpha) ? GL_DST_ALPHA : (GLenum)GL_ONE, + (target_has_alpha) ? GL_ONE_MINUS_DST_ALPHA : (GLenum)GL_ZERO + }; + const GLenum glDestFactors[8] = + { + GL_ZERO, + GL_ONE, + GL_SRC_COLOR, + GL_ONE_MINUS_SRC_COLOR, + (useDualSource) ? GL_SRC1_ALPHA : (GLenum)GL_SRC_ALPHA, + (useDualSource) ? GL_ONE_MINUS_SRC1_ALPHA : (GLenum)GL_ONE_MINUS_SRC_ALPHA, + (target_has_alpha) ? GL_DST_ALPHA : (GLenum)GL_ONE, + (target_has_alpha) ? GL_ONE_MINUS_DST_ALPHA : (GLenum)GL_ZERO + }; + + // blend mode bit mask + // 0 - blend enable + // 1 - dst alpha enabled + // 2 - reverse subtract enable (else add) + // 3-5 - srcRGB function + // 6-8 - dstRGB function + + u32 newval = useDualSource << 1; + newval |= bpmem.blendmode.subtract << 2; + + if (bpmem.blendmode.subtract) + newval |= 0x0049; // enable blending src 1 dst 1 + else if (bpmem.blendmode.blendenable) + { + newval |= 1; // enable blending + newval |= bpmem.blendmode.srcfactor << 3; + newval |= bpmem.blendmode.dstfactor << 6; + } + + u32 changes = forceUpdate ? 0xFFFFFFFF : newval ^ s_blendMode; + + if (changes & 1) + // blend enable change + (newval & 1) ? glEnable(GL_BLEND) : glDisable(GL_BLEND); + + if (changes & 4) + { + // subtract enable change + GLenum equation = newval & 4 ? GL_FUNC_REVERSE_SUBTRACT : GL_FUNC_ADD; + GLenum equationAlpha = useDualSource ? GL_FUNC_ADD : equation; + + glBlendEquationSeparate(equation, equationAlpha); + } + + if (changes & 0x1FA) + { + u32 srcidx = (newval >> 3) & 7; + u32 dstidx = (newval >> 6) & 7; + GLenum srcFactor = glSrcFactors[srcidx]; + GLenum dstFactor = glDestFactors[dstidx]; + + // adjust alpha factors + if (useDualSource) + { + srcidx = GX_BL_ONE; + dstidx = GX_BL_ZERO; + } + else + { + // we can't use GL_DST_COLOR or GL_ONE_MINUS_DST_COLOR for source in alpha channel so use their alpha equivalent instead + if (srcidx == GX_BL_DSTCLR) srcidx = GX_BL_DSTALPHA; + if (srcidx == GX_BL_INVDSTCLR) srcidx = GX_BL_INVDSTALPHA; + + // we can't use GL_SRC_COLOR or GL_ONE_MINUS_SRC_COLOR for destination in alpha channel so use their alpha equivalent instead + if (dstidx == GX_BL_SRCCLR) dstidx = GX_BL_SRCALPHA; + if (dstidx == GX_BL_INVSRCCLR) dstidx = GX_BL_INVSRCALPHA; + } + GLenum srcFactorAlpha = glSrcFactors[srcidx]; + GLenum dstFactorAlpha = glDestFactors[dstidx]; + // blend RGB change + glBlendFuncSeparate(srcFactor, dstFactor, srcFactorAlpha, dstFactorAlpha); + } + s_blendMode = newval; +} + +void DumpFrame(const std::vector<u8>& data, int w, int h) +{ +#if defined(HAVE_LIBAV) || defined(_WIN32) + if (g_ActiveConfig.bDumpFrames && !data.empty()) + { + AVIDump::AddFrame(&data[0], w, h); + } +#endif +} + +// This function has the final picture. We adjust the aspect ratio here. +void Renderer::Swap(u32 xfbAddr, u32 fbWidth, u32 fbHeight,const EFBRectangle& rc,float Gamma) +{ + static int w = 0, h = 0; + if (g_bSkipCurrentFrame || (!XFBWrited && !g_ActiveConfig.RealXFBEnabled()) || !fbWidth || !fbHeight) + { + DumpFrame(frame_data, w, h); + Core::Callback_VideoCopiedToXFB(false); + return; + } + + u32 xfbCount = 0; + const XFBSourceBase* const* xfbSourceList = FramebufferManager::GetXFBSource(xfbAddr, fbWidth, fbHeight, xfbCount); + if (g_ActiveConfig.VirtualXFBEnabled() && (!xfbSourceList || xfbCount == 0)) + { + DumpFrame(frame_data, w, h); + Core::Callback_VideoCopiedToXFB(false); + return; + } + + ResetAPIState(); + + PostProcessing::Update(s_backbuffer_width, s_backbuffer_height); + UpdateDrawRectangle(s_backbuffer_width, s_backbuffer_height); + TargetRectangle flipped_trc = GetTargetRectangle(); + + // Flip top and bottom for some reason; TODO: Fix the code to suck less? + int tmp = flipped_trc.top; + flipped_trc.top = flipped_trc.bottom; + flipped_trc.bottom = tmp; + + GL_REPORT_ERRORD(); + + // Copy the framebuffer to screen. + + const XFBSourceBase* xfbSource = NULL; + + if(g_ActiveConfig.bUseXFB) + { + // Render to the real/postprocessing buffer now. + PostProcessing::BindTargetFramebuffer(); + + // draw each xfb source + glBindFramebuffer(GL_READ_FRAMEBUFFER, FramebufferManager::GetXFBFramebuffer()); + + for (u32 i = 0; i < xfbCount; ++i) + { + xfbSource = xfbSourceList[i]; + + MathUtil::Rectangle<float> drawRc; + + if (g_ActiveConfig.bUseRealXFB) + { + drawRc.top = flipped_trc.top; + drawRc.bottom = flipped_trc.bottom; + drawRc.left = flipped_trc.left; + drawRc.right = flipped_trc.right; + } + else + { + // use virtual xfb with offset + int xfbHeight = xfbSource->srcHeight; + int xfbWidth = xfbSource->srcWidth; + int hOffset = ((s32)xfbSource->srcAddr - (s32)xfbAddr) / ((s32)fbWidth * 2); + + drawRc.top = flipped_trc.top - hOffset * flipped_trc.GetHeight() / fbHeight; + drawRc.bottom = flipped_trc.top - (hOffset + xfbHeight) * flipped_trc.GetHeight() / fbHeight; + drawRc.left = flipped_trc.left + (flipped_trc.GetWidth() - xfbWidth * flipped_trc.GetWidth() / fbWidth)/2; + drawRc.right = flipped_trc.left + (flipped_trc.GetWidth() + xfbWidth * flipped_trc.GetWidth() / fbWidth)/2; + + // The following code disables auto stretch. Kept for reference. + // scale draw area for a 1 to 1 pixel mapping with the draw target + //float vScale = (float)fbHeight / (float)flipped_trc.GetHeight(); + //float hScale = (float)fbWidth / (float)flipped_trc.GetWidth(); + //drawRc.top *= vScale; + //drawRc.bottom *= vScale; + //drawRc.left *= hScale; + //drawRc.right *= hScale; + } + // Tell the OSD Menu about the current internal resolution + OSDInternalW = xfbSource->sourceRc.GetWidth(); OSDInternalH = xfbSource->sourceRc.GetHeight(); + + MathUtil::Rectangle<float> sourceRc; + sourceRc.left = xfbSource->sourceRc.left; + sourceRc.right = xfbSource->sourceRc.right; + sourceRc.top = xfbSource->sourceRc.top; + sourceRc.bottom = xfbSource->sourceRc.bottom; + + xfbSource->Draw(sourceRc, drawRc); + } + } + else + { + TargetRectangle targetRc = ConvertEFBRectangle(rc); + + // for msaa mode, we must resolve the efb content to non-msaa + FramebufferManager::ResolveAndGetRenderTarget(rc); + + // Render to the real/postprocessing buffer now. (resolve have changed this in msaa mode) + PostProcessing::BindTargetFramebuffer(); + + // always the non-msaa fbo + GLuint fb = s_MSAASamples>1?FramebufferManager::GetResolvedFramebuffer():FramebufferManager::GetEFBFramebuffer(); + + glBindFramebuffer(GL_READ_FRAMEBUFFER, fb); + glBlitFramebuffer(targetRc.left, targetRc.bottom, targetRc.right, targetRc.top, + flipped_trc.left, flipped_trc.bottom, flipped_trc.right, flipped_trc.top, + GL_COLOR_BUFFER_BIT, GL_LINEAR); + } + + PostProcessing::BlitToScreen(); + + glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); + + // Save screenshot + if (s_bScreenshot) + { + std::lock_guard<std::mutex> lk(s_criticalScreenshot); + SaveScreenshot(s_sScreenshotName, flipped_trc); + // Reset settings + s_sScreenshotName.clear(); + s_bScreenshot = false; + } + + // Frame dumps are handled a little differently in Windows + // Frame dumping disabled entirely on GLES3 +#ifndef USE_GLES3 +#if defined _WIN32 || defined HAVE_LIBAV + if (g_ActiveConfig.bDumpFrames) + { + std::lock_guard<std::mutex> lk(s_criticalScreenshot); + if (frame_data.empty() || w != flipped_trc.GetWidth() || + h != flipped_trc.GetHeight()) + { + w = flipped_trc.GetWidth(); + h = flipped_trc.GetHeight(); + frame_data.resize(3 * w * h); + } + glPixelStorei(GL_PACK_ALIGNMENT, 1); + glReadPixels(flipped_trc.left, flipped_trc.bottom, w, h, GL_BGR, GL_UNSIGNED_BYTE, &frame_data[0]); + if (GL_REPORT_ERROR() == GL_NO_ERROR && w > 0 && h > 0) + { + if (!bLastFrameDumped) + { + #ifdef _WIN32 + bAVIDumping = AVIDump::Start(EmuWindow::GetParentWnd(), w, h); + #else + bAVIDumping = AVIDump::Start(w, h); + #endif + if (!bAVIDumping) + OSD::AddMessage("AVIDump Start failed", 2000); + else + { + OSD::AddMessage(StringFromFormat( + "Dumping Frames to \"%sframedump0.avi\" (%dx%d RGB24)", + File::GetUserPath(D_DUMPFRAMES_IDX).c_str(), w, h).c_str(), 2000); + } + } + if (bAVIDumping) + { + #ifndef _WIN32 + FlipImageData(&frame_data[0], w, h); + #endif + + AVIDump::AddFrame(&frame_data[0], w, h); + } + + bLastFrameDumped = true; + } + else + NOTICE_LOG(VIDEO, "Error reading framebuffer"); + } + else + { + if (bLastFrameDumped && bAVIDumping) + { + std::vector<u8>().swap(frame_data); + w = h = 0; + AVIDump::Stop(); + bAVIDumping = false; + OSD::AddMessage("Stop dumping frames", 2000); + } + bLastFrameDumped = false; + } +#else + if (g_ActiveConfig.bDumpFrames) + { + std::lock_guard<std::mutex> lk(s_criticalScreenshot); + std::string movie_file_name; + w = GetTargetRectangle().GetWidth(); + h = GetTargetRectangle().GetHeight(); + frame_data.resize(3 * w * h); + glPixelStorei(GL_PACK_ALIGNMENT, 1); + glReadPixels(GetTargetRectangle().left, GetTargetRectangle().bottom, w, h, GL_BGR, GL_UNSIGNED_BYTE, &frame_data[0]); + if (GL_REPORT_ERROR() == GL_NO_ERROR) + { + if (!bLastFrameDumped) + { + movie_file_name = File::GetUserPath(D_DUMPFRAMES_IDX) + "framedump.raw"; + pFrameDump.Open(movie_file_name, "wb"); + if (!pFrameDump) + OSD::AddMessage("Error opening framedump.raw for writing.", 2000); + else + { + OSD::AddMessage(StringFromFormat("Dumping Frames to \"%s\" (%dx%d RGB24)", movie_file_name.c_str(), w, h).c_str(), 2000); + } + } + if (pFrameDump) + { + FlipImageData(&frame_data[0], w, h); + pFrameDump.WriteBytes(&frame_data[0], w * 3 * h); + pFrameDump.Flush(); + } + bLastFrameDumped = true; + } + } + else + { + if (bLastFrameDumped) + pFrameDump.Close(); + bLastFrameDumped = false; + } +#endif +#endif + // Finish up the current frame, print some stats + + SetWindowSize(fbWidth, fbHeight); + + GLInterface->Update(); // just updates the render window position and the backbuffer size + + bool xfbchanged = false; + + if (FramebufferManagerBase::LastXfbWidth() != fbWidth || FramebufferManagerBase::LastXfbHeight() != fbHeight) + { + xfbchanged = true; + unsigned int const last_w = (fbWidth < 1 || fbWidth > MAX_XFB_WIDTH) ? MAX_XFB_WIDTH : fbWidth; + unsigned int const last_h = (fbHeight < 1 || fbHeight > MAX_XFB_HEIGHT) ? MAX_XFB_HEIGHT : fbHeight; + FramebufferManagerBase::SetLastXfbWidth(last_w); + FramebufferManagerBase::SetLastXfbHeight(last_h); + } + + bool WindowResized = false; + int W = (int)GLInterface->GetBackBufferWidth(); + int H = (int)GLInterface->GetBackBufferHeight(); + if (W != s_backbuffer_width || H != s_backbuffer_height || s_LastEFBScale != g_ActiveConfig.iEFBScale) + { + WindowResized = true; + s_backbuffer_width = W; + s_backbuffer_height = H; + s_LastEFBScale = g_ActiveConfig.iEFBScale; + } + + if (xfbchanged || WindowResized || (s_LastMultisampleMode != g_ActiveConfig.iMultisampleMode)) + { + UpdateDrawRectangle(s_backbuffer_width, s_backbuffer_height); + + if (CalculateTargetSize(s_backbuffer_width, s_backbuffer_height) || s_LastMultisampleMode != g_ActiveConfig.iMultisampleMode) + { + s_LastMultisampleMode = g_ActiveConfig.iMultisampleMode; + s_MSAASamples = GetNumMSAASamples(s_LastMultisampleMode); + s_MSAACoverageSamples = GetNumMSAACoverageSamples(s_LastMultisampleMode); + ApplySSAASettings(); + + delete g_framebuffer_manager; + g_framebuffer_manager = new FramebufferManager(s_target_width, s_target_height, + s_MSAASamples, s_MSAACoverageSamples); + } + } + + if (XFBWrited) + s_fps = UpdateFPSCounter(); + // --------------------------------------------------------------------- + if (!DriverDetails::HasBug(DriverDetails::BUG_BROKENSWAP)) + { + GL_REPORT_ERRORD(); + + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + DrawDebugInfo(); + DrawDebugText(); + + GL_REPORT_ERRORD(); + + // Do our OSD callbacks + OSD::DoCallbacks(OSD::OSD_ONFRAME); + OSD::DrawMessages(); + GL_REPORT_ERRORD(); + } + // Copy the rendered frame to the real window + GLInterface->Swap(); + + GL_REPORT_ERRORD(); + + // Clear framebuffer + if (!DriverDetails::HasBug(DriverDetails::BUG_BROKENSWAP)) + { + glClearColor(0, 0, 0, 0); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); + GL_REPORT_ERRORD(); + } + + if(s_vsync != g_ActiveConfig.IsVSync()) + { + s_vsync = g_ActiveConfig.IsVSync(); + GLInterface->SwapInterval(s_vsync); + } + + // Clean out old stuff from caches. It's not worth it to clean out the shader caches. + DLCache::ProgressiveCleanup(); + TextureCache::Cleanup(); + + frameCount++; + + GFX_DEBUGGER_PAUSE_AT(NEXT_FRAME, true); + + // Begin new frame + // Set default viewport and scissor, for the clear to work correctly + // New frame + stats.ResetFrame(); + + // Render to the framebuffer. + FramebufferManager::SetFramebuffer(0); + + GL_REPORT_ERRORD(); + + RestoreAPIState(); + + GL_REPORT_ERRORD(); + g_Config.iSaveTargetId = 0; + + UpdateActiveConfig(); + TextureCache::OnConfigChanged(g_ActiveConfig); + + // For testing zbuffer targets. + // Renderer::SetZBufferRender(); + // SaveTexture("tex.png", GL_TEXTURE_2D, s_FakeZTarget, + // GetTargetWidth(), GetTargetHeight()); + Core::Callback_VideoCopiedToXFB(XFBWrited || (g_ActiveConfig.bUseXFB && g_ActiveConfig.bUseRealXFB)); + XFBWrited = false; + + // Invalidate EFB cache + ClearEFBCache(); +} + +// ALWAYS call RestoreAPIState for each ResetAPIState call you're doing +void Renderer::ResetAPIState() +{ + // Gets us to a reasonably sane state where it's possible to do things like + // image copies with textured quads, etc. + glDisable(GL_SCISSOR_TEST); + glDisable(GL_DEPTH_TEST); + glDisable(GL_CULL_FACE); + glDisable(GL_BLEND); +#ifndef USE_GLES3 + glDisable(GL_COLOR_LOGIC_OP); +#endif + glDepthMask(GL_FALSE); + glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); +} + +void Renderer::RestoreAPIState() +{ + // Gets us back into a more game-like state. + glEnable(GL_SCISSOR_TEST); + SetGenerationMode(); + BPFunctions::SetScissor(); + SetColorMask(); + SetDepthMode(); + SetBlendMode(true); + SetLogicOpMode(); + UpdateViewport(); + +#ifndef USE_GLES3 + glPolygonMode(GL_FRONT_AND_BACK, g_ActiveConfig.bWireFrame ? GL_LINE : GL_FILL); +#endif + + VertexManager *vm = (OGL::VertexManager*)g_vertex_manager; + glBindBuffer(GL_ARRAY_BUFFER, vm->m_vertex_buffers); + vm->m_last_vao = 0; + + TextureCache::SetStage(); +} + +void Renderer::SetGenerationMode() +{ + // none, ccw, cw, ccw + if (bpmem.genMode.cullmode > 0) + { + // TODO: GX_CULL_ALL not supported, yet! + glEnable(GL_CULL_FACE); + glFrontFace(bpmem.genMode.cullmode == 2 ? GL_CCW : GL_CW); + } + else + glDisable(GL_CULL_FACE); +} + +void Renderer::SetDepthMode() +{ + const GLenum glCmpFuncs[8] = + { + GL_NEVER, + GL_LESS, + GL_EQUAL, + GL_LEQUAL, + GL_GREATER, + GL_NOTEQUAL, + GL_GEQUAL, + GL_ALWAYS + }; + + if (bpmem.zmode.testenable) + { + glEnable(GL_DEPTH_TEST); + glDepthMask(bpmem.zmode.updateenable ? GL_TRUE : GL_FALSE); + glDepthFunc(glCmpFuncs[bpmem.zmode.func]); + } + else + { + // if the test is disabled write is disabled too + // TODO: When PE performance metrics are being emulated via occlusion queries, we should (probably?) enable depth test with depth function ALWAYS here + glDisable(GL_DEPTH_TEST); + glDepthMask(GL_FALSE); + } +} + +void Renderer::SetLogicOpMode() +{ + + // Logic ops aren't available in GLES3/GLES2 +#ifndef USE_GLES3 + const GLenum glLogicOpCodes[16] = + { + GL_CLEAR, + GL_AND, + GL_AND_REVERSE, + GL_COPY, + GL_AND_INVERTED, + GL_NOOP, + GL_XOR, + GL_OR, + GL_NOR, + GL_EQUIV, + GL_INVERT, + GL_OR_REVERSE, + GL_COPY_INVERTED, + GL_OR_INVERTED, + GL_NAND, + GL_SET + }; + + if (bpmem.blendmode.logicopenable) + { + glEnable(GL_COLOR_LOGIC_OP); + glLogicOp(glLogicOpCodes[bpmem.blendmode.logicmode]); + } + else + { + glDisable(GL_COLOR_LOGIC_OP); + } +#endif +} + +void Renderer::SetDitherMode() +{ + if (bpmem.blendmode.dither) + glEnable(GL_DITHER); + else + glDisable(GL_DITHER); +} + +void Renderer::SetLineWidth() +{ + float fratio = xfregs.viewport.wd != 0 ? + ((float)Renderer::GetTargetWidth() / EFB_WIDTH) : 1.0f; + if (bpmem.lineptwidth.linesize > 0) + // scale by ratio of widths + glLineWidth((float)bpmem.lineptwidth.linesize * fratio / 6.0f); +#ifndef USE_GLES3 + if (bpmem.lineptwidth.pointsize > 0) + glPointSize((float)bpmem.lineptwidth.pointsize * fratio / 6.0f); +#endif +} + +void Renderer::SetSamplerState(int stage, int texindex) +{ + auto const& tex = bpmem.tex[texindex]; + auto const& tm0 = tex.texMode0[stage]; + auto const& tm1 = tex.texMode1[stage]; + + g_sampler_cache->SetSamplerState((texindex * 4) + stage, tm0, tm1); +} + +void Renderer::SetInterlacingMode() +{ + // TODO +} + +void Renderer::FlipImageData(u8 *data, int w, int h, int pixel_width) +{ + // Flip image upside down. Damn OpenGL. + for (int y = 0; y < h / 2; ++y) + { + for(int x = 0; x < w; ++x) + { + for (auto delta = 0; delta < pixel_width; ++delta) + std::swap(data[(y * w + x) * pixel_width + delta], data[((h - 1 - y) * w + x) * pixel_width + delta]); + } + } +} + +} + +namespace OGL +{ + +bool Renderer::SaveScreenshot(const std::string &filename, const TargetRectangle &back_rc) +{ + u32 W = back_rc.GetWidth(); + u32 H = back_rc.GetHeight(); + u8 *data = new u8[W * 4 * H]; + glPixelStorei(GL_PACK_ALIGNMENT, 1); + + glReadPixels(back_rc.left, back_rc.bottom, W, H, GL_RGBA, GL_UNSIGNED_BYTE, data); + + // Show failure message + if (GL_REPORT_ERROR() != GL_NO_ERROR) + { + delete[] data; + OSD::AddMessage("Error capturing or saving screenshot.", 2000); + return false; + } + + // Turn image upside down + FlipImageData(data, W, H, 4); + bool success = TextureToPng(data, W*4, filename, W, H, false); + delete[] data; + + return success; + +} + +} |
