2015-05-24 04:55:12 +00:00
|
|
|
// Copyright 2008 Dolphin Emulator Project
|
2015-05-17 23:08:10 +00:00
|
|
|
// Licensed under GPLv2+
|
2013-04-18 03:29:41 +00:00
|
|
|
// Refer to the license.txt file included.
|
2009-02-23 07:23:34 +00:00
|
|
|
|
2017-01-23 16:20:20 +00:00
|
|
|
#include "VideoBackends/OGL/Render.h"
|
|
|
|
|
2014-08-17 03:00:57 +00:00
|
|
|
#include <algorithm>
|
2014-02-17 10:18:15 +00:00
|
|
|
#include <cinttypes>
|
2009-02-23 07:23:34 +00:00
|
|
|
#include <cmath>
|
2009-03-28 21:07:16 +00:00
|
|
|
#include <cstdio>
|
2015-05-08 13:28:29 +00:00
|
|
|
#include <memory>
|
2014-03-12 19:33:41 +00:00
|
|
|
#include <string>
|
2017-04-09 19:05:24 +00:00
|
|
|
#include <tuple>
|
2014-02-17 10:18:15 +00:00
|
|
|
#include <vector>
|
|
|
|
|
|
|
|
#include "Common/Atomic.h"
|
2016-01-02 20:01:12 +00:00
|
|
|
#include "Common/CommonTypes.h"
|
2015-09-18 16:40:00 +00:00
|
|
|
#include "Common/GL/GLInterfaceBase.h"
|
|
|
|
#include "Common/GL/GLUtil.h"
|
2014-12-28 20:46:00 +00:00
|
|
|
#include "Common/Logging/LogManager.h"
|
2016-06-24 08:43:46 +00:00
|
|
|
#include "Common/MathUtil.h"
|
2017-02-01 04:29:29 +00:00
|
|
|
#include "Common/MsgHandler.h"
|
2016-06-24 08:43:46 +00:00
|
|
|
#include "Common/StringUtil.h"
|
2014-02-17 10:18:15 +00:00
|
|
|
|
2017-05-18 12:59:38 +00:00
|
|
|
#include "Core/Config/GraphicsSettings.h"
|
2014-02-17 10:18:15 +00:00
|
|
|
#include "Core/Core.h"
|
|
|
|
|
2014-11-13 22:26:49 +00:00
|
|
|
#include "VideoBackends/OGL/BoundingBox.h"
|
2014-02-17 10:18:15 +00:00
|
|
|
#include "VideoBackends/OGL/FramebufferManager.h"
|
2017-04-23 04:44:34 +00:00
|
|
|
#include "VideoBackends/OGL/OGLTexture.h"
|
2014-02-17 10:18:15 +00:00
|
|
|
#include "VideoBackends/OGL/PostProcessing.h"
|
|
|
|
#include "VideoBackends/OGL/ProgramShaderCache.h"
|
|
|
|
#include "VideoBackends/OGL/RasterFont.h"
|
|
|
|
#include "VideoBackends/OGL/SamplerCache.h"
|
|
|
|
#include "VideoBackends/OGL/TextureCache.h"
|
|
|
|
#include "VideoBackends/OGL/VertexManager.h"
|
|
|
|
|
|
|
|
#include "VideoCommon/BPFunctions.h"
|
|
|
|
#include "VideoCommon/DriverDetails.h"
|
2016-01-13 20:14:20 +00:00
|
|
|
#include "VideoCommon/IndexGenerator.h"
|
2014-02-17 10:18:15 +00:00
|
|
|
#include "VideoCommon/OnScreenDisplay.h"
|
|
|
|
#include "VideoCommon/PixelEngine.h"
|
2016-12-29 17:03:22 +00:00
|
|
|
#include "VideoCommon/RenderState.h"
|
2017-07-20 07:10:02 +00:00
|
|
|
#include "VideoCommon/ShaderGenCommon.h"
|
2014-02-17 10:18:15 +00:00
|
|
|
#include "VideoCommon/VertexShaderManager.h"
|
2017-01-23 16:20:20 +00:00
|
|
|
#include "VideoCommon/VideoBackendBase.h"
|
2014-02-17 10:18:15 +00:00
|
|
|
#include "VideoCommon/VideoConfig.h"
|
2017-02-01 15:56:13 +00:00
|
|
|
#include "VideoCommon/XFMemory.h"
|
2009-02-23 07:23:34 +00:00
|
|
|
|
2011-01-29 20:16:51 +00:00
|
|
|
void VideoConfig::UpdateProjectionHack()
|
|
|
|
{
|
2017-06-11 13:37:18 +00:00
|
|
|
::UpdateProjectionHack(g_Config.phack);
|
2011-01-29 20:16:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
namespace OGL
|
|
|
|
{
|
2013-03-25 14:14:24 +00:00
|
|
|
VideoConfig g_ogl_config;
|
2011-01-29 20:16:51 +00:00
|
|
|
|
|
|
|
// Declarations and definitions
|
|
|
|
// ----------------------------
|
2015-12-22 23:47:20 +00:00
|
|
|
static std::unique_ptr<RasterFont> s_raster_font;
|
2009-02-23 07:23:34 +00:00
|
|
|
|
2009-03-08 19:19:51 +00:00
|
|
|
// 1 for no MSAA. Use s_MSAASamples > 1 to check for MSAA.
|
|
|
|
static int s_MSAASamples = 1;
|
2017-06-07 11:30:39 +00:00
|
|
|
static u32 s_last_multisamples = 1;
|
2014-12-28 17:35:23 +00:00
|
|
|
static bool s_last_stereo_mode = false;
|
2014-10-29 14:42:37 +00:00
|
|
|
|
2013-03-30 21:17:39 +00:00
|
|
|
static bool s_vsync;
|
|
|
|
|
2011-08-22 04:15:02 +00:00
|
|
|
// EFB cache related
|
2016-06-24 08:43:46 +00:00
|
|
|
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
|
2012-12-07 22:34:38 +00:00
|
|
|
static const u32 EFB_CACHE_HEIGHT = (EFB_HEIGHT + EFB_CACHE_RECT_SIZE - 1) / EFB_CACHE_RECT_SIZE;
|
2011-08-22 04:15:02 +00:00
|
|
|
static bool s_efbCacheValid[2][EFB_CACHE_WIDTH * EFB_CACHE_HEIGHT];
|
2014-06-05 08:12:05 +00:00
|
|
|
static bool s_efbCacheIsCleared = false;
|
2016-06-24 08:43:46 +00:00
|
|
|
static std::vector<u32>
|
2017-01-23 07:51:46 +00:00
|
|
|
s_efbCache[2][EFB_CACHE_WIDTH * EFB_CACHE_HEIGHT]; // 2 for PeekZ and PeekColor
|
2009-07-29 03:11:35 +00:00
|
|
|
|
2016-06-24 08:43:46 +00:00
|
|
|
static void APIENTRY ErrorCallback(GLenum source, GLenum type, GLuint id, GLenum severity,
|
|
|
|
GLsizei length, const char* message, const void* userParam)
|
2013-04-04 15:37:16 +00:00
|
|
|
{
|
2016-06-24 08:43:46 +00:00
|
|
|
const char* s_source;
|
|
|
|
const char* s_type;
|
|
|
|
|
2017-04-27 17:59:25 +00:00
|
|
|
// Performance - DualCore driver performance warning:
|
|
|
|
// DualCore application thread syncing with server thread
|
|
|
|
if (id == 0x200b0)
|
|
|
|
return;
|
|
|
|
|
2016-06-24 08:43:46 +00:00
|
|
|
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(HOST_GPU, "id: %x, source: %s, type: %s - %s", id, s_source, s_type, message);
|
|
|
|
break;
|
|
|
|
case GL_DEBUG_SEVERITY_MEDIUM_ARB:
|
|
|
|
WARN_LOG(HOST_GPU, "id: %x, source: %s, type: %s - %s", id, s_source, s_type, message);
|
|
|
|
break;
|
|
|
|
case GL_DEBUG_SEVERITY_LOW_ARB:
|
2016-09-24 23:06:47 +00:00
|
|
|
DEBUG_LOG(HOST_GPU, "id: %x, source: %s, type: %s - %s", id, s_source, s_type, message);
|
2016-06-24 08:43:46 +00:00
|
|
|
break;
|
|
|
|
case GL_DEBUG_SEVERITY_NOTIFICATION:
|
|
|
|
DEBUG_LOG(HOST_GPU, "id: %x, source: %s, type: %s - %s", id, s_source, s_type, message);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
ERROR_LOG(HOST_GPU, "id: %x, source: %s, type: %s - %s", id, s_source, s_type, message);
|
|
|
|
break;
|
|
|
|
}
|
2013-04-04 15:37:16 +00:00
|
|
|
}
|
|
|
|
|
2013-06-25 16:14:41 +00:00
|
|
|
// Two small Fallbacks to avoid GL_ARB_ES2_compatibility
|
2016-01-01 19:58:19 +00:00
|
|
|
static void APIENTRY DepthRangef(GLfloat neardepth, GLfloat fardepth)
|
2013-06-25 16:14:41 +00:00
|
|
|
{
|
2016-06-24 08:43:46 +00:00
|
|
|
glDepthRange(neardepth, fardepth);
|
2013-06-25 16:14:41 +00:00
|
|
|
}
|
2016-01-01 19:58:19 +00:00
|
|
|
static void APIENTRY ClearDepthf(GLfloat depthval)
|
2013-06-25 16:14:41 +00:00
|
|
|
{
|
2016-06-24 08:43:46 +00:00
|
|
|
glClearDepth(depthval);
|
2013-06-25 16:14:41 +00:00
|
|
|
}
|
|
|
|
|
2014-07-08 12:29:26 +00:00
|
|
|
static void InitDriverInfo()
|
2013-06-11 13:33:56 +00:00
|
|
|
{
|
2016-06-24 08:43:46 +00:00
|
|
|
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;
|
|
|
|
DriverDetails::Family family = DriverDetails::Family::UNKNOWN;
|
|
|
|
double version = 0.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:
|
|
|
|
{
|
|
|
|
driver = DriverDetails::DRIVER_QUALCOMM;
|
|
|
|
double glVersion;
|
|
|
|
sscanf(g_ogl_config.gl_version, "OpenGL ES %lg V@%lg", &glVersion, &version);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case DriverDetails::VENDOR_ARM:
|
|
|
|
// Currently the Mali-T line has two families in it.
|
|
|
|
// Mali-T6xx and Mali-T7xx
|
|
|
|
// These two families are similar enough that they share bugs in their drivers.
|
|
|
|
//
|
|
|
|
// Mali drivers provide no way to explicitly find out what video driver is running.
|
|
|
|
// This is similar to how we can't find the Nvidia driver version in Windows.
|
|
|
|
// Good thing is that ARM introduces a new video driver about once every two years so we can
|
|
|
|
// find the driver version by the features it exposes.
|
|
|
|
// r2p0 - No OpenGL ES 3.0 support (We don't support this)
|
|
|
|
// r3p0 - OpenGL ES 3.0 support
|
|
|
|
// r4p0 - Supports 'GL_EXT_shader_pixel_local_storage' extension.
|
|
|
|
|
|
|
|
driver = DriverDetails::DRIVER_ARM;
|
|
|
|
if (GLExtensions::Supports("GL_EXT_shader_pixel_local_storage"))
|
|
|
|
version = 400;
|
|
|
|
else
|
|
|
|
version = 300;
|
|
|
|
break;
|
|
|
|
case DriverDetails::VENDOR_MESA:
|
|
|
|
{
|
|
|
|
if (svendor == "nouveau")
|
|
|
|
{
|
|
|
|
driver = DriverDetails::DRIVER_NOUVEAU;
|
|
|
|
}
|
|
|
|
else if (svendor == "Intel Open Source Technology Center")
|
|
|
|
{
|
|
|
|
driver = DriverDetails::DRIVER_I965;
|
|
|
|
if (srenderer.find("Sandybridge") != std::string::npos)
|
|
|
|
family = DriverDetails::Family::INTEL_SANDY;
|
|
|
|
else if (srenderer.find("Ivybridge") != std::string::npos)
|
|
|
|
family = DriverDetails::Family::INTEL_IVY;
|
|
|
|
}
|
|
|
|
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 (Core Profile) Mesa %d.%d.%d", &major, &minor, &release);
|
|
|
|
version = 100 * major + 10 * minor + release;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case DriverDetails::VENDOR_INTEL: // Happens in OS X/Windows
|
|
|
|
{
|
|
|
|
u32 market_name;
|
|
|
|
sscanf(g_ogl_config.gl_renderer, "Intel HD Graphics %d", &market_name);
|
|
|
|
switch (market_name)
|
|
|
|
{
|
|
|
|
case 2000:
|
|
|
|
case 3000:
|
|
|
|
family = DriverDetails::Family::INTEL_SANDY;
|
|
|
|
break;
|
|
|
|
case 2500:
|
|
|
|
case 4000:
|
|
|
|
family = DriverDetails::Family::INTEL_IVY;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
family = DriverDetails::Family::UNKNOWN;
|
|
|
|
break;
|
|
|
|
};
|
2014-09-04 23:03:53 +00:00
|
|
|
#ifdef _WIN32
|
2016-06-24 08:43:46 +00:00
|
|
|
int glmajor = 0;
|
|
|
|
int glminor = 0;
|
|
|
|
int major = 0;
|
|
|
|
int minor = 0;
|
|
|
|
int release = 0;
|
|
|
|
int revision = 0;
|
|
|
|
// Example version string: '4.3.0 - Build 10.18.10.3907'
|
|
|
|
sscanf(g_ogl_config.gl_version, "%d.%d.0 - Build %d.%d.%d.%d", &glmajor, &glminor, &major,
|
|
|
|
&minor, &release, &revision);
|
|
|
|
version = 100000000 * major + 1000000 * minor + 10000 * release + revision;
|
|
|
|
version /= 10000;
|
2014-09-04 23:03:53 +00:00
|
|
|
#endif
|
2016-06-24 08:43:46 +00:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
case DriverDetails::VENDOR_NVIDIA:
|
|
|
|
{
|
|
|
|
int glmajor = 0;
|
|
|
|
int glminor = 0;
|
|
|
|
int glrelease = 0;
|
|
|
|
int major = 0;
|
|
|
|
int minor = 0;
|
|
|
|
// TODO: this is known to be broken on Windows
|
|
|
|
// Nvidia seems to have removed their driver version from this string, so we can't get it.
|
|
|
|
// hopefully we'll never have to workaround Nvidia bugs
|
|
|
|
sscanf(g_ogl_config.gl_version, "%d.%d.%d NVIDIA %d.%d", &glmajor, &glminor, &glrelease, &major,
|
|
|
|
&minor);
|
|
|
|
version = 100 * major + minor;
|
|
|
|
}
|
|
|
|
break;
|
2017-09-02 18:25:12 +00:00
|
|
|
case DriverDetails::VENDOR_IMGTEC:
|
|
|
|
{
|
|
|
|
// Example version string:
|
|
|
|
// "OpenGL ES 3.2 build 1.9@4850625"
|
|
|
|
// Ends up as "109.4850625" - "1.9" being the branch, "4850625" being the build's change ID
|
|
|
|
// The change ID only makes sense to compare within a branch
|
|
|
|
driver = DriverDetails::DRIVER_IMGTEC;
|
|
|
|
double gl_version;
|
|
|
|
int major, minor, change;
|
|
|
|
constexpr double change_scale = 10000000;
|
|
|
|
sscanf(g_ogl_config.gl_version, "OpenGL ES %lg build %d.%d@%d", &gl_version, &major, &minor,
|
|
|
|
&change);
|
|
|
|
version = 100 * major + minor;
|
|
|
|
if (change >= change_scale)
|
|
|
|
{
|
|
|
|
ERROR_LOG(VIDEO, "Version changeID overflow - change:%d scale:%f", change, change_scale);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
version += static_cast<double>(change) / change_scale;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
2016-06-24 08:43:46 +00:00
|
|
|
// We don't care about these
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
2016-08-13 05:16:31 +00:00
|
|
|
DriverDetails::Init(DriverDetails::API_OPENGL, vendor, driver, version, family);
|
2013-06-11 13:33:56 +00:00
|
|
|
}
|
|
|
|
|
2011-05-07 22:00:05 +00:00
|
|
|
// Init functions
|
|
|
|
Renderer::Renderer()
|
2017-03-04 06:40:08 +00:00
|
|
|
: ::Renderer(static_cast<int>(std::max(GLInterface->GetBackBufferWidth(), 1u)),
|
|
|
|
static_cast<int>(std::max(GLInterface->GetBackBufferHeight(), 1u)))
|
2011-05-07 22:00:05 +00:00
|
|
|
{
|
2016-06-24 08:43:46 +00:00
|
|
|
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);
|
|
|
|
|
|
|
|
InitDriverInfo();
|
|
|
|
|
|
|
|
if (GLInterface->GetMode() == GLInterfaceMode::MODE_OPENGL)
|
|
|
|
{
|
|
|
|
if (!GLExtensions::Supports("GL_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 (!GLExtensions::Supports("GL_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 (!GLExtensions::Supports("GL_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 (!GLExtensions::Supports("GL_ARB_uniform_buffer_object"))
|
|
|
|
{
|
|
|
|
// ubo allow us to keep the current constants on shader switches
|
|
|
|
// we also can stream them much nicer and pack into it whatever we want to
|
|
|
|
PanicAlert("GPU: OGL ERROR: Need GL_ARB_uniform_buffer_object.\n"
|
|
|
|
"GPU: Does your video card support OpenGL 3.1?");
|
|
|
|
bSuccess = false;
|
|
|
|
}
|
2016-10-29 22:56:18 +00:00
|
|
|
else if (DriverDetails::HasBug(DriverDetails::BUG_BROKEN_UBO))
|
2016-06-24 08:43:46 +00:00
|
|
|
{
|
|
|
|
PanicAlert(
|
|
|
|
"Buggy GPU driver detected.\n"
|
|
|
|
"Please either install the closed-source GPU driver or update your Mesa 3D version.");
|
|
|
|
bSuccess = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!GLExtensions::Supports("GL_ARB_sampler_objects"))
|
|
|
|
{
|
|
|
|
// 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.\n"
|
|
|
|
"GPU: Does your video card support OpenGL 3.3?");
|
|
|
|
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 (!GLExtensions::Supports("GL_ARB_ES2_compatibility"))
|
|
|
|
{
|
|
|
|
glDepthRangef = DepthRangef;
|
|
|
|
glClearDepthf = ClearDepthf;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Copy the GPU name to g_Config, so Analytics can see it.
|
|
|
|
g_Config.backend_info.AdapterName = g_ogl_config.gl_renderer;
|
|
|
|
|
|
|
|
g_Config.backend_info.bSupportsDualSourceBlend =
|
2016-10-27 19:59:12 +00:00
|
|
|
(GLExtensions::Supports("GL_ARB_blend_func_extended") ||
|
2016-10-29 15:24:10 +00:00
|
|
|
GLExtensions::Supports("GL_EXT_blend_func_extended"));
|
2016-06-24 08:43:46 +00:00
|
|
|
g_Config.backend_info.bSupportsPrimitiveRestart =
|
2016-10-29 22:56:18 +00:00
|
|
|
!DriverDetails::HasBug(DriverDetails::BUG_PRIMITIVE_RESTART) &&
|
2016-06-24 08:43:46 +00:00
|
|
|
((GLExtensions::Version() >= 310) || GLExtensions::Supports("GL_NV_primitive_restart"));
|
OGL: implement Bounding Box on systems w/o SSBO
This commit should have zero performance effect if SSBOs are supported.
If they aren't (e.g. on all Macs), this commit alters FramebufferManager
to attach a new stencil buffer and VertexManager to draw to it when
bounding box is active. `BBoxRead` gets the pixel data from the buffer
and dumbly loops through it to find the bounding box.
This patch can run Paper Mario: The Thousand-Year Door at almost full
speed (50–60 FPS) without Dual-Core enabled for all common bounding
box-using actions I tested (going through pipes, Plane Mode, Paper
Mode, Prof. Frankly's gate, combat, walking around the overworld, etc.)
on my computer (macOS 10.12.3, 2.8 GHz Intel Core i7, 16 GB 1600 MHz
DDR3, and Intel Iris 1536 MB).
A few more demanding scenes (e.g. the self-building bridge on the way
to Petalburg) slow to ~15% of their speed without this patch (though
they don't run quite at full speed even on master). The slowdown is
caused almost solely by `glReadPixels` in `OGL::BoundingBox::Get`.
Other implementation ideas:
- Use a stencil buffer that's separate from the depth buffer. This would
require ARB_texture_stencil8 / OpenGL 4.4, which isn't available on
macOS.
- Use `glGetTexImage` instead of `glReadPixels`. This is ~5 FPS slower
on my computer, presumably because it has to transfer the entire
combined depth-stencil buffer instead of only the stencil data.
Getting only stencil data from `glGetTexImage` requires
ARB_texture_stencil8 / OpenGL 4.4, which (again) is not available on
macOS.
- Don't use a PBO, and use `glReadPixels` synchronously. This has no
visible performance effect on my computer, and is theoretically
slower.
2017-03-05 23:34:30 +00:00
|
|
|
g_Config.backend_info.bSupportsBBox = true;
|
|
|
|
g_Config.backend_info.bSupportsFragmentStoresAndAtomics =
|
2016-06-24 08:43:46 +00:00
|
|
|
GLExtensions::Supports("GL_ARB_shader_storage_buffer_object");
|
|
|
|
g_Config.backend_info.bSupportsGSInstancing = GLExtensions::Supports("GL_ARB_gpu_shader5");
|
|
|
|
g_Config.backend_info.bSupportsSSAA = GLExtensions::Supports("GL_ARB_gpu_shader5") &&
|
|
|
|
GLExtensions::Supports("GL_ARB_sample_shading");
|
|
|
|
g_Config.backend_info.bSupportsGeometryShaders =
|
|
|
|
GLExtensions::Version() >= 320 &&
|
2016-10-29 22:56:18 +00:00
|
|
|
!DriverDetails::HasBug(DriverDetails::BUG_BROKEN_GEOMETRY_SHADERS);
|
2016-06-24 08:43:46 +00:00
|
|
|
g_Config.backend_info.bSupportsPaletteConversion =
|
|
|
|
GLExtensions::Supports("GL_ARB_texture_buffer_object") ||
|
|
|
|
GLExtensions::Supports("GL_OES_texture_buffer") ||
|
|
|
|
GLExtensions::Supports("GL_EXT_texture_buffer");
|
|
|
|
g_Config.backend_info.bSupportsClipControl = GLExtensions::Supports("GL_ARB_clip_control");
|
|
|
|
g_ogl_config.bSupportsCopySubImage =
|
|
|
|
(GLExtensions::Supports("GL_ARB_copy_image") || GLExtensions::Supports("GL_NV_copy_image") ||
|
|
|
|
GLExtensions::Supports("GL_EXT_copy_image") ||
|
|
|
|
GLExtensions::Supports("GL_OES_copy_image")) &&
|
2016-10-29 22:56:18 +00:00
|
|
|
!DriverDetails::HasBug(DriverDetails::BUG_BROKEN_COPYIMAGE);
|
2017-10-21 14:49:40 +00:00
|
|
|
g_ogl_config.bSupportsTextureSubImage = GLExtensions::Supports("ARB_get_texture_sub_image");
|
2016-06-24 08:43:46 +00:00
|
|
|
|
|
|
|
// Desktop OpenGL supports the binding layout if it supports 420pack
|
|
|
|
// OpenGL ES 3.1 supports it implicitly without an extension
|
|
|
|
g_Config.backend_info.bSupportsBindingLayout =
|
|
|
|
GLExtensions::Supports("GL_ARB_shading_language_420pack");
|
|
|
|
|
2016-08-05 20:31:34 +00:00
|
|
|
// Clip distance support is useless without a method to clamp the depth range
|
2016-08-17 18:12:44 +00:00
|
|
|
g_Config.backend_info.bSupportsDepthClamp = GLExtensions::Supports("GL_ARB_depth_clamp");
|
2016-08-05 20:31:34 +00:00
|
|
|
|
2017-07-20 05:25:24 +00:00
|
|
|
// Desktop OpenGL supports bitfield manulipation and dynamic sampler indexing if it supports
|
|
|
|
// shader5. OpenGL ES 3.1 supports it implicitly without an extension
|
|
|
|
g_Config.backend_info.bSupportsBitfield = GLExtensions::Supports("GL_ARB_gpu_shader5");
|
|
|
|
g_Config.backend_info.bSupportsDynamicSamplerIndexing =
|
|
|
|
GLExtensions::Supports("GL_ARB_gpu_shader5");
|
|
|
|
|
2016-06-24 08:43:46 +00:00
|
|
|
g_ogl_config.bSupportsGLSLCache = GLExtensions::Supports("GL_ARB_get_program_binary");
|
|
|
|
g_ogl_config.bSupportsGLPinnedMemory = GLExtensions::Supports("GL_AMD_pinned_memory");
|
|
|
|
g_ogl_config.bSupportsGLSync = GLExtensions::Supports("GL_ARB_sync");
|
|
|
|
g_ogl_config.bSupportsGLBaseVertex = GLExtensions::Supports("GL_ARB_draw_elements_base_vertex") ||
|
|
|
|
GLExtensions::Supports("GL_EXT_draw_elements_base_vertex") ||
|
|
|
|
GLExtensions::Supports("GL_OES_draw_elements_base_vertex");
|
|
|
|
g_ogl_config.bSupportsGLBufferStorage = GLExtensions::Supports("GL_ARB_buffer_storage") ||
|
|
|
|
GLExtensions::Supports("GL_EXT_buffer_storage");
|
|
|
|
g_ogl_config.bSupportsMSAA = GLExtensions::Supports("GL_ARB_texture_multisample");
|
|
|
|
g_ogl_config.bSupportViewportFloat = GLExtensions::Supports("GL_ARB_viewport_array");
|
|
|
|
g_ogl_config.bSupportsDebug =
|
|
|
|
GLExtensions::Supports("GL_KHR_debug") || GLExtensions::Supports("GL_ARB_debug_output");
|
2016-11-27 08:14:53 +00:00
|
|
|
g_ogl_config.bSupportsTextureStorage = GLExtensions::Supports("GL_ARB_texture_storage");
|
|
|
|
g_ogl_config.bSupports3DTextureStorageMultisample =
|
2016-06-24 08:43:46 +00:00
|
|
|
GLExtensions::Supports("GL_ARB_texture_storage_multisample") ||
|
|
|
|
GLExtensions::Supports("GL_OES_texture_storage_multisample_2d_array");
|
2016-11-27 08:14:53 +00:00
|
|
|
g_ogl_config.bSupports2DTextureStorageMultisample =
|
2016-06-24 08:43:46 +00:00
|
|
|
GLExtensions::Supports("GL_ARB_texture_storage_multisample");
|
2016-11-27 08:14:56 +00:00
|
|
|
g_ogl_config.bSupportsImageLoadStore = GLExtensions::Supports("GL_ARB_shader_image_load_store");
|
2016-06-24 08:43:46 +00:00
|
|
|
g_ogl_config.bSupportsConservativeDepth = GLExtensions::Supports("GL_ARB_conservative_depth");
|
|
|
|
g_ogl_config.bSupportsAniso = GLExtensions::Supports("GL_EXT_texture_filter_anisotropic");
|
2016-11-27 08:14:56 +00:00
|
|
|
g_Config.backend_info.bSupportsComputeShaders = GLExtensions::Supports("GL_ARB_compute_shader");
|
2017-04-16 10:34:02 +00:00
|
|
|
g_Config.backend_info.bSupportsST3CTextures =
|
|
|
|
GLExtensions::Supports("GL_EXT_texture_compression_s3tc");
|
2017-07-27 12:00:04 +00:00
|
|
|
g_Config.backend_info.bSupportsBPTCTextures =
|
|
|
|
GLExtensions::Supports("GL_ARB_texture_compression_bptc");
|
2016-06-24 08:43:46 +00:00
|
|
|
|
|
|
|
if (GLInterface->GetMode() == GLInterfaceMode::MODE_OPENGLES3)
|
|
|
|
{
|
|
|
|
g_ogl_config.SupportedESPointSize =
|
|
|
|
GLExtensions::Supports("GL_OES_geometry_point_size") ?
|
|
|
|
1 :
|
|
|
|
GLExtensions::Supports("GL_EXT_geometry_point_size") ? 2 : 0;
|
|
|
|
g_ogl_config.SupportedESTextureBuffer = GLExtensions::Supports("VERSION_GLES_3_2") ?
|
2018-01-05 18:01:18 +00:00
|
|
|
EsTexbufType::TexbufCore :
|
2016-06-24 08:43:46 +00:00
|
|
|
GLExtensions::Supports("GL_OES_texture_buffer") ?
|
2018-01-05 18:01:18 +00:00
|
|
|
EsTexbufType::TexbufOes :
|
2016-06-24 08:43:46 +00:00
|
|
|
GLExtensions::Supports("GL_EXT_texture_buffer") ?
|
2018-01-05 18:01:18 +00:00
|
|
|
EsTexbufType::TexbufExt :
|
|
|
|
EsTexbufType::TexbufNone;
|
2016-06-24 08:43:46 +00:00
|
|
|
|
|
|
|
g_ogl_config.bSupportsGLSLCache = true;
|
|
|
|
g_ogl_config.bSupportsGLSync = true;
|
|
|
|
|
2016-08-12 11:55:35 +00:00
|
|
|
// TODO: Implement support for GL_EXT_clip_cull_distance when there is an extension for
|
|
|
|
// depth clamping.
|
2016-08-05 20:31:34 +00:00
|
|
|
g_Config.backend_info.bSupportsDepthClamp = false;
|
|
|
|
|
2017-10-26 05:44:39 +00:00
|
|
|
if (GLExtensions::Supports("GL_EXT_shader_framebuffer_fetch"))
|
|
|
|
{
|
2018-01-05 18:01:18 +00:00
|
|
|
g_ogl_config.SupportedFramebufferFetch = EsFbFetchType::FbFetchExt;
|
2017-10-26 05:44:39 +00:00
|
|
|
}
|
|
|
|
else if (GLExtensions::Supports("GL_ARM_shader_framebuffer_fetch"))
|
|
|
|
{
|
2018-01-05 18:01:18 +00:00
|
|
|
g_ogl_config.SupportedFramebufferFetch = EsFbFetchType::FbFetchArm;
|
2017-10-26 05:44:39 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2018-01-05 18:01:18 +00:00
|
|
|
g_ogl_config.SupportedFramebufferFetch = EsFbFetchType::FbFetchNone;
|
2017-10-26 05:44:39 +00:00
|
|
|
}
|
|
|
|
g_Config.backend_info.bSupportsFramebufferFetch =
|
2018-01-05 18:01:18 +00:00
|
|
|
g_ogl_config.SupportedFramebufferFetch != EsFbFetchType::FbFetchNone;
|
2017-10-26 05:44:39 +00:00
|
|
|
|
2016-08-17 23:20:24 +00:00
|
|
|
if (GLExtensions::Version() == 300)
|
2016-06-24 08:43:46 +00:00
|
|
|
{
|
2018-01-05 18:01:18 +00:00
|
|
|
g_ogl_config.eSupportedGLSLVersion = GlslEs300;
|
2016-06-24 08:43:46 +00:00
|
|
|
g_ogl_config.bSupportsAEP = false;
|
2016-11-27 08:14:53 +00:00
|
|
|
g_ogl_config.bSupportsTextureStorage = true;
|
2016-06-24 08:43:46 +00:00
|
|
|
g_Config.backend_info.bSupportsGeometryShaders = false;
|
|
|
|
}
|
2016-08-17 23:20:24 +00:00
|
|
|
else if (GLExtensions::Version() == 310)
|
2016-06-24 08:43:46 +00:00
|
|
|
{
|
2018-01-05 18:01:18 +00:00
|
|
|
g_ogl_config.eSupportedGLSLVersion = GlslEs310;
|
2016-06-24 08:43:46 +00:00
|
|
|
g_ogl_config.bSupportsAEP = GLExtensions::Supports("GL_ANDROID_extension_pack_es31a");
|
|
|
|
g_Config.backend_info.bSupportsBindingLayout = true;
|
2016-11-27 08:14:56 +00:00
|
|
|
g_ogl_config.bSupportsImageLoadStore = true;
|
2016-06-24 08:43:46 +00:00
|
|
|
g_Config.backend_info.bSupportsGeometryShaders = g_ogl_config.bSupportsAEP;
|
2016-11-27 08:14:56 +00:00
|
|
|
g_Config.backend_info.bSupportsComputeShaders = true;
|
2016-06-24 08:43:46 +00:00
|
|
|
g_Config.backend_info.bSupportsGSInstancing =
|
|
|
|
g_Config.backend_info.bSupportsGeometryShaders && g_ogl_config.SupportedESPointSize > 0;
|
|
|
|
g_Config.backend_info.bSupportsSSAA = g_ogl_config.bSupportsAEP;
|
2017-03-05 23:17:54 +00:00
|
|
|
g_Config.backend_info.bSupportsFragmentStoresAndAtomics = true;
|
2016-06-24 08:43:46 +00:00
|
|
|
g_ogl_config.bSupportsMSAA = true;
|
2016-11-27 08:14:53 +00:00
|
|
|
g_ogl_config.bSupportsTextureStorage = true;
|
|
|
|
g_ogl_config.bSupports2DTextureStorageMultisample = true;
|
2017-07-20 05:25:24 +00:00
|
|
|
g_Config.backend_info.bSupportsBitfield = true;
|
|
|
|
g_Config.backend_info.bSupportsDynamicSamplerIndexing = g_ogl_config.bSupportsAEP;
|
2017-11-11 03:55:00 +00:00
|
|
|
if (g_ActiveConfig.stereo_mode != StereoMode::Off && g_ActiveConfig.iMultisamples > 1 &&
|
2016-11-27 08:14:53 +00:00
|
|
|
!g_ogl_config.bSupports3DTextureStorageMultisample)
|
2016-06-24 08:43:46 +00:00
|
|
|
{
|
|
|
|
// GLES 3.1 can't support stereo rendering and MSAA
|
|
|
|
OSD::AddMessage("MSAA Stereo rendering isn't supported by your GPU.", 10000);
|
2017-06-07 11:30:39 +00:00
|
|
|
Config::SetCurrent(Config::GFX_MSAA, UINT32_C(1));
|
2016-06-24 08:43:46 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2018-01-05 18:01:18 +00:00
|
|
|
g_ogl_config.eSupportedGLSLVersion = GlslEs320;
|
2016-06-24 08:43:46 +00:00
|
|
|
g_ogl_config.bSupportsAEP = GLExtensions::Supports("GL_ANDROID_extension_pack_es31a");
|
|
|
|
g_Config.backend_info.bSupportsBindingLayout = true;
|
2016-11-27 08:14:56 +00:00
|
|
|
g_ogl_config.bSupportsImageLoadStore = true;
|
2016-06-24 08:43:46 +00:00
|
|
|
g_Config.backend_info.bSupportsGeometryShaders = true;
|
2016-11-27 08:14:56 +00:00
|
|
|
g_Config.backend_info.bSupportsComputeShaders = true;
|
2016-06-24 08:43:46 +00:00
|
|
|
g_Config.backend_info.bSupportsGSInstancing = g_ogl_config.SupportedESPointSize > 0;
|
|
|
|
g_Config.backend_info.bSupportsPaletteConversion = true;
|
|
|
|
g_Config.backend_info.bSupportsSSAA = true;
|
2017-03-05 23:17:54 +00:00
|
|
|
g_Config.backend_info.bSupportsFragmentStoresAndAtomics = true;
|
2016-06-24 08:43:46 +00:00
|
|
|
g_ogl_config.bSupportsCopySubImage = true;
|
|
|
|
g_ogl_config.bSupportsGLBaseVertex = true;
|
|
|
|
g_ogl_config.bSupportsDebug = true;
|
|
|
|
g_ogl_config.bSupportsMSAA = true;
|
2016-11-27 08:14:53 +00:00
|
|
|
g_ogl_config.bSupportsTextureStorage = true;
|
|
|
|
g_ogl_config.bSupports2DTextureStorageMultisample = true;
|
|
|
|
g_ogl_config.bSupports3DTextureStorageMultisample = true;
|
2017-07-20 05:25:24 +00:00
|
|
|
g_Config.backend_info.bSupportsBitfield = true;
|
|
|
|
g_Config.backend_info.bSupportsDynamicSamplerIndexing = true;
|
2016-06-24 08:43:46 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2016-08-17 23:20:24 +00:00
|
|
|
if (GLExtensions::Version() < 300)
|
2016-06-24 08:43:46 +00:00
|
|
|
{
|
|
|
|
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",
|
2016-08-17 23:20:24 +00:00
|
|
|
(const char*)glGetString(GL_SHADING_LANGUAGE_VERSION));
|
2016-06-24 08:43:46 +00:00
|
|
|
bSuccess = false;
|
|
|
|
}
|
2016-08-17 23:20:24 +00:00
|
|
|
else if (GLExtensions::Version() == 300)
|
2016-06-24 08:43:46 +00:00
|
|
|
{
|
2018-01-05 18:01:18 +00:00
|
|
|
g_ogl_config.eSupportedGLSLVersion = Glsl130;
|
2016-11-27 08:14:56 +00:00
|
|
|
g_ogl_config.bSupportsImageLoadStore = false; // layout keyword is only supported on glsl150+
|
2016-06-24 08:43:46 +00:00
|
|
|
g_ogl_config.bSupportsConservativeDepth =
|
|
|
|
false; // layout keyword is only supported on glsl150+
|
|
|
|
g_Config.backend_info.bSupportsGeometryShaders =
|
|
|
|
false; // geometry shaders are only supported on glsl150+
|
|
|
|
}
|
2016-08-17 23:20:24 +00:00
|
|
|
else if (GLExtensions::Version() == 310)
|
2016-06-24 08:43:46 +00:00
|
|
|
{
|
2018-01-05 18:01:18 +00:00
|
|
|
g_ogl_config.eSupportedGLSLVersion = Glsl140;
|
2016-11-27 08:14:56 +00:00
|
|
|
g_ogl_config.bSupportsImageLoadStore = false; // layout keyword is only supported on glsl150+
|
2016-06-24 08:43:46 +00:00
|
|
|
g_ogl_config.bSupportsConservativeDepth =
|
|
|
|
false; // layout keyword is only supported on glsl150+
|
|
|
|
g_Config.backend_info.bSupportsGeometryShaders =
|
|
|
|
false; // geometry shaders are only supported on glsl150+
|
|
|
|
}
|
2016-08-17 23:20:24 +00:00
|
|
|
else if (GLExtensions::Version() == 320)
|
2016-06-24 08:43:46 +00:00
|
|
|
{
|
2018-01-05 18:01:18 +00:00
|
|
|
g_ogl_config.eSupportedGLSLVersion = Glsl150;
|
2016-06-24 08:43:46 +00:00
|
|
|
}
|
2016-08-17 23:20:24 +00:00
|
|
|
else if (GLExtensions::Version() == 330)
|
2016-06-24 08:43:46 +00:00
|
|
|
{
|
2018-01-05 18:01:18 +00:00
|
|
|
g_ogl_config.eSupportedGLSLVersion = Glsl330;
|
2016-06-24 08:43:46 +00:00
|
|
|
}
|
2016-11-27 08:14:56 +00:00
|
|
|
else if (GLExtensions::Version() >= 430)
|
|
|
|
{
|
|
|
|
// TODO: We should really parse the GL_SHADING_LANGUAGE_VERSION token.
|
2018-01-05 18:01:18 +00:00
|
|
|
g_ogl_config.eSupportedGLSLVersion = Glsl430;
|
2016-11-27 08:14:56 +00:00
|
|
|
g_ogl_config.bSupportsTextureStorage = true;
|
|
|
|
g_ogl_config.bSupportsImageLoadStore = true;
|
|
|
|
g_Config.backend_info.bSupportsSSAA = true;
|
|
|
|
|
|
|
|
// Compute shaders are core in GL4.3.
|
|
|
|
g_Config.backend_info.bSupportsComputeShaders = true;
|
2017-10-21 14:49:40 +00:00
|
|
|
if (GLExtensions::Version() >= 450)
|
|
|
|
g_ogl_config.bSupportsTextureSubImage = true;
|
2016-11-27 08:14:56 +00:00
|
|
|
}
|
2016-06-24 08:43:46 +00:00
|
|
|
else
|
|
|
|
{
|
2018-01-05 18:01:18 +00:00
|
|
|
g_ogl_config.eSupportedGLSLVersion = Glsl400;
|
2016-06-24 08:43:46 +00:00
|
|
|
g_Config.backend_info.bSupportsSSAA = true;
|
2016-11-27 08:14:56 +00:00
|
|
|
|
|
|
|
if (GLExtensions::Version() == 420)
|
|
|
|
{
|
|
|
|
// Texture storage and shader image load/store are core in GL4.2.
|
|
|
|
g_ogl_config.bSupportsTextureStorage = true;
|
|
|
|
g_ogl_config.bSupportsImageLoadStore = true;
|
|
|
|
}
|
2016-06-24 08:43:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Desktop OpenGL can't have the Android Extension Pack
|
|
|
|
g_ogl_config.bSupportsAEP = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Either method can do early-z tests. See PixelShaderGen for details.
|
|
|
|
g_Config.backend_info.bSupportsEarlyZ =
|
2016-11-27 08:14:56 +00:00
|
|
|
g_ogl_config.bSupportsImageLoadStore || g_ogl_config.bSupportsConservativeDepth;
|
2016-06-24 08:43:46 +00:00
|
|
|
|
2017-03-31 11:55:26 +00:00
|
|
|
glGetIntegerv(GL_MAX_SAMPLES, &g_ogl_config.max_samples);
|
|
|
|
if (g_ogl_config.max_samples < 1 || !g_ogl_config.bSupportsMSAA)
|
|
|
|
g_ogl_config.max_samples = 1;
|
|
|
|
|
2016-11-27 08:15:00 +00:00
|
|
|
// We require texel buffers, image load store, and compute shaders to enable GPU texture decoding.
|
|
|
|
// If the driver doesn't expose the extensions, but supports GL4.3/GLES3.1, it will still be
|
|
|
|
// enabled in the version check below.
|
|
|
|
g_Config.backend_info.bSupportsGPUTextureDecoding =
|
|
|
|
g_Config.backend_info.bSupportsPaletteConversion &&
|
|
|
|
g_Config.backend_info.bSupportsComputeShaders && g_ogl_config.bSupportsImageLoadStore;
|
|
|
|
|
2016-06-24 08:43:46 +00:00
|
|
|
if (g_ogl_config.bSupportsDebug)
|
|
|
|
{
|
|
|
|
if (GLExtensions::Supports("GL_KHR_debug"))
|
|
|
|
{
|
|
|
|
glDebugMessageControl(GL_DONT_CARE, GL_DONT_CARE, GL_DONT_CARE, 0, nullptr, true);
|
|
|
|
glDebugMessageCallback(ErrorCallback, nullptr);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
glDebugMessageControlARB(GL_DONT_CARE, GL_DONT_CARE, GL_DONT_CARE, 0, nullptr, true);
|
|
|
|
glDebugMessageCallbackARB(ErrorCallback, nullptr);
|
|
|
|
}
|
|
|
|
if (LogManager::GetInstance()->IsEnabled(LogTypes::HOST_GPU, LogTypes::LERROR))
|
|
|
|
glEnable(GL_DEBUG_OUTPUT);
|
|
|
|
else
|
|
|
|
glDisable(GL_DEBUG_OUTPUT);
|
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
|
|
|
g_Config.VerifyValidity();
|
|
|
|
UpdateActiveConfig();
|
|
|
|
|
2017-07-20 07:10:02 +00:00
|
|
|
// Since we modify the config here, we need to update the last host bits, it may have changed.
|
|
|
|
m_last_host_config_bits = ShaderHostConfig::GetCurrent().bits;
|
|
|
|
|
2016-06-24 08:43:46 +00:00
|
|
|
OSD::AddMessage(StringFromFormat("Video Info: %s, %s, %s", g_ogl_config.gl_vendor,
|
|
|
|
g_ogl_config.gl_renderer, g_ogl_config.gl_version),
|
|
|
|
5000);
|
|
|
|
|
2017-03-14 00:02:13 +00:00
|
|
|
WARN_LOG(VIDEO, "Missing OGL Extensions: %s%s%s%s%s%s%s%s%s%s%s%s%s%s",
|
2016-06-24 08:43:46 +00:00
|
|
|
g_ActiveConfig.backend_info.bSupportsDualSourceBlend ? "" : "DualSourceBlend ",
|
|
|
|
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.bSupportsGLBufferStorage ? "" : "BufferStorage ",
|
|
|
|
g_ogl_config.bSupportsGLSync ? "" : "Sync ", g_ogl_config.bSupportsMSAA ? "" : "MSAA ",
|
|
|
|
g_ActiveConfig.backend_info.bSupportsSSAA ? "" : "SSAA ",
|
|
|
|
g_ActiveConfig.backend_info.bSupportsGSInstancing ? "" : "GSInstancing ",
|
|
|
|
g_ActiveConfig.backend_info.bSupportsClipControl ? "" : "ClipControl ",
|
2016-08-05 20:31:34 +00:00
|
|
|
g_ogl_config.bSupportsCopySubImage ? "" : "CopyImageSubData ",
|
2017-03-14 00:02:13 +00:00
|
|
|
g_ActiveConfig.backend_info.bSupportsDepthClamp ? "" : "DepthClamp ");
|
2016-06-24 08:43:46 +00:00
|
|
|
|
|
|
|
s_last_multisamples = g_ActiveConfig.iMultisamples;
|
|
|
|
s_MSAASamples = s_last_multisamples;
|
|
|
|
|
2017-11-11 03:55:00 +00:00
|
|
|
s_last_stereo_mode = g_ActiveConfig.stereo_mode != StereoMode::Off;
|
2016-06-24 08:43:46 +00:00
|
|
|
|
|
|
|
// Handle VSync on/off
|
|
|
|
s_vsync = g_ActiveConfig.IsVSync();
|
2016-10-29 22:56:18 +00:00
|
|
|
if (!DriverDetails::HasBug(DriverDetails::BUG_BROKEN_VSYNC))
|
2016-06-24 08:43:46 +00:00
|
|
|
GLInterface->SwapInterval(s_vsync);
|
|
|
|
|
|
|
|
// Because of the fixed framebuffer size we need to disable the resolution
|
|
|
|
// options while running
|
|
|
|
|
OGL: implement Bounding Box on systems w/o SSBO
This commit should have zero performance effect if SSBOs are supported.
If they aren't (e.g. on all Macs), this commit alters FramebufferManager
to attach a new stencil buffer and VertexManager to draw to it when
bounding box is active. `BBoxRead` gets the pixel data from the buffer
and dumbly loops through it to find the bounding box.
This patch can run Paper Mario: The Thousand-Year Door at almost full
speed (50–60 FPS) without Dual-Core enabled for all common bounding
box-using actions I tested (going through pipes, Plane Mode, Paper
Mode, Prof. Frankly's gate, combat, walking around the overworld, etc.)
on my computer (macOS 10.12.3, 2.8 GHz Intel Core i7, 16 GB 1600 MHz
DDR3, and Intel Iris 1536 MB).
A few more demanding scenes (e.g. the self-building bridge on the way
to Petalburg) slow to ~15% of their speed without this patch (though
they don't run quite at full speed even on master). The slowdown is
caused almost solely by `glReadPixels` in `OGL::BoundingBox::Get`.
Other implementation ideas:
- Use a stencil buffer that's separate from the depth buffer. This would
require ARB_texture_stencil8 / OpenGL 4.4, which isn't available on
macOS.
- Use `glGetTexImage` instead of `glReadPixels`. This is ~5 FPS slower
on my computer, presumably because it has to transfer the entire
combined depth-stencil buffer instead of only the stencil data.
Getting only stencil data from `glGetTexImage` requires
ARB_texture_stencil8 / OpenGL 4.4, which (again) is not available on
macOS.
- Don't use a PBO, and use `glReadPixels` synchronously. This has no
visible performance effect on my computer, and is theoretically
slower.
2017-03-05 23:34:30 +00:00
|
|
|
// The stencil is used for bounding box emulation when SSBOs are not available
|
|
|
|
glDisable(GL_STENCIL_TEST);
|
|
|
|
glStencilFunc(GL_ALWAYS, 1, 0xFF);
|
|
|
|
glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);
|
2016-06-24 08:43:46 +00:00
|
|
|
|
OGL: implement Bounding Box on systems w/o SSBO
This commit should have zero performance effect if SSBOs are supported.
If they aren't (e.g. on all Macs), this commit alters FramebufferManager
to attach a new stencil buffer and VertexManager to draw to it when
bounding box is active. `BBoxRead` gets the pixel data from the buffer
and dumbly loops through it to find the bounding box.
This patch can run Paper Mario: The Thousand-Year Door at almost full
speed (50–60 FPS) without Dual-Core enabled for all common bounding
box-using actions I tested (going through pipes, Plane Mode, Paper
Mode, Prof. Frankly's gate, combat, walking around the overworld, etc.)
on my computer (macOS 10.12.3, 2.8 GHz Intel Core i7, 16 GB 1600 MHz
DDR3, and Intel Iris 1536 MB).
A few more demanding scenes (e.g. the self-building bridge on the way
to Petalburg) slow to ~15% of their speed without this patch (though
they don't run quite at full speed even on master). The slowdown is
caused almost solely by `glReadPixels` in `OGL::BoundingBox::Get`.
Other implementation ideas:
- Use a stencil buffer that's separate from the depth buffer. This would
require ARB_texture_stencil8 / OpenGL 4.4, which isn't available on
macOS.
- Use `glGetTexImage` instead of `glReadPixels`. This is ~5 FPS slower
on my computer, presumably because it has to transfer the entire
combined depth-stencil buffer instead of only the stencil data.
Getting only stencil data from `glGetTexImage` requires
ARB_texture_stencil8 / OpenGL 4.4, which (again) is not available on
macOS.
- Don't use a PBO, and use `glReadPixels` synchronously. This has no
visible performance effect on my computer, and is theoretically
slower.
2017-03-05 23:34:30 +00:00
|
|
|
// Reset The Current Viewport
|
|
|
|
glViewport(0, 0, GetTargetWidth(), GetTargetHeight());
|
2016-06-24 08:43:46 +00:00
|
|
|
if (g_ActiveConfig.backend_info.bSupportsClipControl)
|
|
|
|
glClipControl(GL_LOWER_LEFT, GL_ZERO_TO_ONE);
|
|
|
|
|
|
|
|
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
|
|
|
|
glClearDepthf(1.0f);
|
|
|
|
glEnable(GL_DEPTH_TEST);
|
|
|
|
glDepthFunc(GL_LEQUAL);
|
2016-08-05 20:31:34 +00:00
|
|
|
if (g_ActiveConfig.backend_info.bSupportsDepthClamp)
|
|
|
|
{
|
|
|
|
glEnable(GL_CLIP_DISTANCE0);
|
2016-08-12 13:06:26 +00:00
|
|
|
glEnable(GL_CLIP_DISTANCE1);
|
2016-08-05 20:31:34 +00:00
|
|
|
glEnable(GL_DEPTH_CLAMP);
|
|
|
|
}
|
2016-06-24 08:43:46 +00:00
|
|
|
|
|
|
|
glPixelStorei(GL_UNPACK_ALIGNMENT, 4); // 4-byte pixel alignment
|
|
|
|
|
|
|
|
glEnable(GL_SCISSOR_TEST);
|
|
|
|
glScissor(0, 0, GetTargetWidth(), GetTargetHeight());
|
OGL: implement Bounding Box on systems w/o SSBO
This commit should have zero performance effect if SSBOs are supported.
If they aren't (e.g. on all Macs), this commit alters FramebufferManager
to attach a new stencil buffer and VertexManager to draw to it when
bounding box is active. `BBoxRead` gets the pixel data from the buffer
and dumbly loops through it to find the bounding box.
This patch can run Paper Mario: The Thousand-Year Door at almost full
speed (50–60 FPS) without Dual-Core enabled for all common bounding
box-using actions I tested (going through pipes, Plane Mode, Paper
Mode, Prof. Frankly's gate, combat, walking around the overworld, etc.)
on my computer (macOS 10.12.3, 2.8 GHz Intel Core i7, 16 GB 1600 MHz
DDR3, and Intel Iris 1536 MB).
A few more demanding scenes (e.g. the self-building bridge on the way
to Petalburg) slow to ~15% of their speed without this patch (though
they don't run quite at full speed even on master). The slowdown is
caused almost solely by `glReadPixels` in `OGL::BoundingBox::Get`.
Other implementation ideas:
- Use a stencil buffer that's separate from the depth buffer. This would
require ARB_texture_stencil8 / OpenGL 4.4, which isn't available on
macOS.
- Use `glGetTexImage` instead of `glReadPixels`. This is ~5 FPS slower
on my computer, presumably because it has to transfer the entire
combined depth-stencil buffer instead of only the stencil data.
Getting only stencil data from `glGetTexImage` requires
ARB_texture_stencil8 / OpenGL 4.4, which (again) is not available on
macOS.
- Don't use a PBO, and use `glReadPixels` synchronously. This has no
visible performance effect on my computer, and is theoretically
slower.
2017-03-05 23:34:30 +00:00
|
|
|
glBlendFunc(GL_ONE, GL_ONE);
|
2016-06-24 08:43:46 +00:00
|
|
|
glBlendColor(0, 0, 0, 0.5f);
|
|
|
|
glClearDepthf(1.0f);
|
|
|
|
|
|
|
|
if (g_ActiveConfig.backend_info.bSupportsPrimitiveRestart)
|
|
|
|
{
|
|
|
|
if (GLInterface->GetMode() == GLInterfaceMode::MODE_OPENGLES3)
|
|
|
|
{
|
|
|
|
glEnable(GL_PRIMITIVE_RESTART_FIXED_INDEX);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if (GLExtensions::Version() >= 310)
|
|
|
|
{
|
|
|
|
glEnable(GL_PRIMITIVE_RESTART);
|
|
|
|
glPrimitiveRestartIndex(65535);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
glEnableClientState(GL_PRIMITIVE_RESTART_NV);
|
|
|
|
glPrimitiveRestartIndexNV(65535);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2016-01-13 20:14:20 +00:00
|
|
|
IndexGenerator::Init();
|
|
|
|
|
2016-06-24 08:43:46 +00:00
|
|
|
UpdateActiveConfig();
|
|
|
|
ClearEFBCache();
|
2009-02-23 07:23:34 +00:00
|
|
|
}
|
|
|
|
|
2017-08-07 04:05:42 +00:00
|
|
|
Renderer::~Renderer() = default;
|
2013-01-25 19:39:19 +00:00
|
|
|
|
|
|
|
void Renderer::Shutdown()
|
2010-06-05 01:38:22 +00:00
|
|
|
{
|
2018-01-26 05:09:07 +00:00
|
|
|
::Renderer::Shutdown();
|
2016-06-24 08:43:46 +00:00
|
|
|
g_framebuffer_manager.reset();
|
2013-10-29 05:23:17 +00:00
|
|
|
|
2016-06-24 08:43:46 +00:00
|
|
|
UpdateActiveConfig();
|
2013-10-29 05:23:17 +00:00
|
|
|
|
2016-06-24 08:43:46 +00:00
|
|
|
s_raster_font.reset();
|
|
|
|
m_post_processor.reset();
|
2014-12-04 07:24:58 +00:00
|
|
|
|
2016-06-24 08:43:46 +00:00
|
|
|
OpenGL_DeleteAttributelessVAO();
|
2013-01-25 19:39:19 +00:00
|
|
|
}
|
2009-09-03 19:24:16 +00:00
|
|
|
|
2013-01-25 19:39:19 +00:00
|
|
|
void Renderer::Init()
|
|
|
|
{
|
2016-06-24 08:43:46 +00:00
|
|
|
// Initialize the FramebufferManager
|
2017-03-09 23:33:10 +00:00
|
|
|
g_framebuffer_manager = std::make_unique<FramebufferManager>(
|
|
|
|
m_target_width, m_target_height, s_MSAASamples, BoundingBox::NeedsStencilBuffer());
|
2013-10-29 05:23:17 +00:00
|
|
|
|
2016-06-24 08:43:46 +00:00
|
|
|
m_post_processor = std::make_unique<OpenGLPostProcessing>();
|
|
|
|
s_raster_font = std::make_unique<RasterFont>();
|
2013-10-29 05:23:17 +00:00
|
|
|
|
2016-06-24 08:43:46 +00:00
|
|
|
OpenGL_CreateAttributelessVAO();
|
2010-09-28 02:15:02 +00:00
|
|
|
}
|
|
|
|
|
2017-09-30 06:25:36 +00:00
|
|
|
std::unique_ptr<AbstractTexture> Renderer::CreateTexture(const TextureConfig& config)
|
|
|
|
{
|
|
|
|
return std::make_unique<OGLTexture>(config);
|
|
|
|
}
|
|
|
|
|
2017-10-21 14:49:40 +00:00
|
|
|
std::unique_ptr<AbstractStagingTexture> Renderer::CreateStagingTexture(StagingTextureType type,
|
|
|
|
const TextureConfig& config)
|
|
|
|
{
|
|
|
|
return OGLStagingTexture::Create(type, config);
|
|
|
|
}
|
|
|
|
|
2014-03-12 19:33:41 +00:00
|
|
|
void Renderer::RenderText(const std::string& text, int left, int top, u32 color)
|
2010-09-28 02:15:02 +00:00
|
|
|
{
|
2016-12-03 10:35:14 +00:00
|
|
|
u32 backbuffer_width = std::max(GLInterface->GetBackBufferWidth(), 1u);
|
|
|
|
u32 backbuffer_height = std::max(GLInterface->GetBackBufferHeight(), 1u);
|
2011-11-30 03:50:31 +00:00
|
|
|
|
2016-12-03 10:35:14 +00:00
|
|
|
s_raster_font->printMultilineText(text, left * 2.0f / static_cast<float>(backbuffer_width) - 1.0f,
|
|
|
|
1.0f - top * 2.0f / static_cast<float>(backbuffer_height), 0,
|
|
|
|
backbuffer_width, backbuffer_height, color);
|
2010-09-28 02:15:02 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
TargetRectangle Renderer::ConvertEFBRectangle(const EFBRectangle& rc)
|
|
|
|
{
|
2016-06-24 08:43:46 +00:00
|
|
|
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;
|
2010-09-28 02:15:02 +00:00
|
|
|
}
|
|
|
|
|
2018-01-21 12:12:32 +00:00
|
|
|
void Renderer::SetScissorRect(const MathUtil::Rectangle<int>& rc)
|
2009-02-23 07:23:34 +00:00
|
|
|
{
|
2018-01-21 12:12:32 +00:00
|
|
|
glScissor(rc.left, rc.bottom, rc.GetWidth(), rc.GetHeight());
|
2009-02-23 07:23:34 +00:00
|
|
|
}
|
|
|
|
|
2011-08-22 04:15:02 +00:00
|
|
|
void ClearEFBCache()
|
|
|
|
{
|
2016-06-24 08:43:46 +00:00
|
|
|
if (!s_efbCacheIsCleared)
|
|
|
|
{
|
|
|
|
s_efbCacheIsCleared = true;
|
|
|
|
memset(s_efbCacheValid, 0, sizeof(s_efbCacheValid));
|
|
|
|
}
|
2011-08-22 04:15:02 +00:00
|
|
|
}
|
|
|
|
|
2016-06-24 08:43:46 +00:00
|
|
|
void Renderer::UpdateEFBCache(EFBAccessType type, u32 cacheRectIdx, const EFBRectangle& efbPixelRc,
|
|
|
|
const TargetRectangle& targetPixelRc, const void* data)
|
2011-08-22 04:15:02 +00:00
|
|
|
{
|
2017-01-23 07:51:46 +00:00
|
|
|
const u32 cacheType = (type == EFBAccessType::PeekZ ? 0 : 1);
|
2016-06-24 08:43:46 +00:00
|
|
|
|
|
|
|
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;
|
|
|
|
u32 value;
|
2017-01-23 07:51:46 +00:00
|
|
|
if (type == EFBAccessType::PeekZ)
|
2016-06-24 08:43:46 +00:00
|
|
|
{
|
|
|
|
float* ptr = (float*)data;
|
|
|
|
value = MathUtil::Clamp<u32>((u32)(ptr[yData * targetPixelRcWidth + xData] * 16777216.0f),
|
|
|
|
0, 0xFFFFFF);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
u32* ptr = (u32*)data;
|
|
|
|
value = ptr[yData * targetPixelRcWidth + xData];
|
|
|
|
}
|
|
|
|
s_efbCache[cacheType][cacheRectIdx][yCache * EFB_CACHE_RECT_SIZE + xCache] = value;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
s_efbCacheValid[cacheType][cacheRectIdx] = true;
|
|
|
|
s_efbCacheIsCleared = false;
|
2011-08-22 04:15:02 +00:00
|
|
|
}
|
|
|
|
|
2010-10-24 19:52:52 +00:00
|
|
|
// 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:
|
2014-02-17 04:51:41 +00:00
|
|
|
// - GX_PokeAlphaRead
|
2010-10-24 19:52:52 +00:00
|
|
|
// The behavior of EFB pokes can be modified by:
|
2014-02-17 04:51:41 +00:00
|
|
|
// - GX_PokeAlphaMode (TODO)
|
|
|
|
// - GX_PokeAlphaUpdate (TODO)
|
|
|
|
// - GX_PokeBlendMode (TODO)
|
|
|
|
// - GX_PokeColorUpdate (TODO)
|
|
|
|
// - GX_PokeDither (TODO)
|
|
|
|
// - GX_PokeDstAlpha (TODO)
|
|
|
|
// - GX_PokeZMode (TODO)
|
2010-10-22 19:40:05 +00:00
|
|
|
u32 Renderer::AccessEFB(EFBAccessType type, u32 x, u32 y, u32 poke_data)
|
2010-09-28 02:15:02 +00:00
|
|
|
{
|
2016-06-24 08:43:46 +00:00
|
|
|
u32 cacheRectIdx = (y / EFB_CACHE_RECT_SIZE) * EFB_CACHE_WIDTH + (x / EFB_CACHE_RECT_SIZE);
|
|
|
|
|
|
|
|
EFBRectangle efbPixelRc;
|
|
|
|
|
2017-01-23 07:51:46 +00:00
|
|
|
if (type == EFBAccessType::PeekColor || type == EFBAccessType::PeekZ)
|
2016-06-24 08:43:46 +00:00
|
|
|
{
|
|
|
|
// Get the rectangular target region containing the EFB pixel
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
efbPixelRc.left = x;
|
|
|
|
efbPixelRc.top = y;
|
|
|
|
efbPixelRc.right = x + 1;
|
|
|
|
efbPixelRc.bottom = y + 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
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)
|
|
|
|
{
|
2017-01-23 07:51:46 +00:00
|
|
|
case EFBAccessType::PeekZ:
|
2016-06-24 08:43:46 +00:00
|
|
|
{
|
|
|
|
if (!s_efbCacheValid[0][cacheRectIdx])
|
|
|
|
{
|
|
|
|
if (s_MSAASamples > 1)
|
|
|
|
{
|
2016-08-31 18:19:56 +00:00
|
|
|
ResetAPIState();
|
2016-06-24 08:43:46 +00:00
|
|
|
|
|
|
|
// Resolve our rectangle.
|
|
|
|
FramebufferManager::GetEFBDepthTexture(efbPixelRc);
|
|
|
|
glBindFramebuffer(GL_READ_FRAMEBUFFER, FramebufferManager::GetResolvedFramebuffer());
|
|
|
|
|
2016-08-31 18:19:56 +00:00
|
|
|
RestoreAPIState();
|
2016-06-24 08:43:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
std::unique_ptr<float[]> depthMap(new float[targetPixelRcWidth * targetPixelRcHeight]);
|
|
|
|
|
|
|
|
glReadPixels(targetPixelRc.left, targetPixelRc.bottom, targetPixelRcWidth,
|
|
|
|
targetPixelRcHeight, GL_DEPTH_COMPONENT, GL_FLOAT, depthMap.get());
|
|
|
|
|
|
|
|
UpdateEFBCache(type, cacheRectIdx, efbPixelRc, targetPixelRc, depthMap.get());
|
|
|
|
}
|
|
|
|
|
|
|
|
u32 xRect = x % EFB_CACHE_RECT_SIZE;
|
|
|
|
u32 yRect = y % EFB_CACHE_RECT_SIZE;
|
|
|
|
u32 z = s_efbCache[0][cacheRectIdx][yRect * EFB_CACHE_RECT_SIZE + xRect];
|
|
|
|
|
|
|
|
// if Z is in 16 bit format you must return a 16 bit integer
|
|
|
|
if (bpmem.zcontrol.pixel_format == PEControl::RGB565_Z16)
|
|
|
|
z = z >> 8;
|
|
|
|
|
|
|
|
return z;
|
|
|
|
}
|
|
|
|
|
2017-01-23 07:51:46 +00:00
|
|
|
case EFBAccessType::PeekColor: // GXPeekARGB
|
2016-06-24 08:43:46 +00:00
|
|
|
{
|
|
|
|
// 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
|
|
|
|
if (!s_efbCacheValid[1][cacheRectIdx])
|
|
|
|
{
|
|
|
|
if (s_MSAASamples > 1)
|
|
|
|
{
|
2016-08-31 18:19:56 +00:00
|
|
|
ResetAPIState();
|
2016-06-24 08:43:46 +00:00
|
|
|
|
|
|
|
// Resolve our rectangle.
|
|
|
|
FramebufferManager::GetEFBColorTexture(efbPixelRc);
|
|
|
|
glBindFramebuffer(GL_READ_FRAMEBUFFER, FramebufferManager::GetResolvedFramebuffer());
|
|
|
|
|
2016-08-31 18:19:56 +00:00
|
|
|
RestoreAPIState();
|
2016-06-24 08:43:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
std::unique_ptr<u32[]> colorMap(new u32[targetPixelRcWidth * targetPixelRcHeight]);
|
|
|
|
|
|
|
|
if (GLInterface->GetMode() == GLInterfaceMode::MODE_OPENGLES3)
|
|
|
|
// XXX: Swap colours
|
|
|
|
glReadPixels(targetPixelRc.left, targetPixelRc.bottom, targetPixelRcWidth,
|
|
|
|
targetPixelRcHeight, GL_RGBA, GL_UNSIGNED_BYTE, colorMap.get());
|
|
|
|
else
|
|
|
|
glReadPixels(targetPixelRc.left, targetPixelRc.bottom, targetPixelRcWidth,
|
|
|
|
targetPixelRcHeight, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, colorMap.get());
|
|
|
|
|
|
|
|
UpdateEFBCache(type, cacheRectIdx, efbPixelRc, targetPixelRc, colorMap.get());
|
|
|
|
}
|
|
|
|
|
|
|
|
u32 xRect = x % EFB_CACHE_RECT_SIZE;
|
|
|
|
u32 yRect = y % EFB_CACHE_RECT_SIZE;
|
|
|
|
u32 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::GetAlphaReadMode();
|
|
|
|
|
|
|
|
if (bpmem.zcontrol.pixel_format == PEControl::RGBA6_Z24)
|
|
|
|
{
|
|
|
|
color = RGBA8ToRGBA6ToRGBA8(color);
|
|
|
|
}
|
|
|
|
else if (bpmem.zcontrol.pixel_format == PEControl::RGB565_Z16)
|
|
|
|
{
|
|
|
|
color = RGBA8ToRGB565ToRGBA8(color);
|
|
|
|
}
|
|
|
|
if (bpmem.zcontrol.pixel_format != PEControl::RGBA6_Z24)
|
|
|
|
{
|
|
|
|
color |= 0xFF000000;
|
|
|
|
}
|
|
|
|
if (alpha_read_mode.ReadMode == 2)
|
|
|
|
{
|
|
|
|
// GX_READ_NONE
|
|
|
|
return color;
|
|
|
|
}
|
|
|
|
else if (alpha_read_mode.ReadMode == 1)
|
|
|
|
{
|
|
|
|
// GX_READ_FF
|
|
|
|
return (color | 0xFF000000);
|
|
|
|
}
|
|
|
|
else /*if(alpha_read_mode.ReadMode == 0)*/
|
|
|
|
{
|
|
|
|
// GX_READ_00
|
|
|
|
return (color & 0x00FFFFFF);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
2010-09-28 02:15:02 +00:00
|
|
|
}
|
|
|
|
|
2015-12-19 14:34:56 +00:00
|
|
|
void Renderer::PokeEFB(EFBAccessType type, const EfbPokeData* points, size_t num_points)
|
2015-05-01 15:47:52 +00:00
|
|
|
{
|
2016-06-24 08:43:46 +00:00
|
|
|
FramebufferManager::PokeEFB(type, points, num_points);
|
2015-05-01 15:47:52 +00:00
|
|
|
}
|
|
|
|
|
2014-11-13 22:26:49 +00:00
|
|
|
u16 Renderer::BBoxRead(int index)
|
|
|
|
{
|
2016-06-24 08:43:46 +00:00
|
|
|
int swapped_index = index;
|
|
|
|
if (index >= 2)
|
|
|
|
swapped_index ^= 1; // swap 2 and 3 for top/bottom
|
|
|
|
|
|
|
|
// Here we get the min/max value of the truncated position of the upscaled and swapped
|
|
|
|
// framebuffer.
|
|
|
|
// So we have to correct them to the unscaled EFB sizes.
|
|
|
|
int value = BoundingBox::Get(swapped_index);
|
|
|
|
|
|
|
|
if (index < 2)
|
|
|
|
{
|
|
|
|
// left/right
|
2017-03-04 06:42:21 +00:00
|
|
|
value = value * EFB_WIDTH / m_target_width;
|
2016-06-24 08:43:46 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// up/down -- we have to swap up and down
|
2017-03-04 06:42:21 +00:00
|
|
|
value = value * EFB_HEIGHT / m_target_height;
|
2016-06-24 08:43:46 +00:00
|
|
|
value = EFB_HEIGHT - value - 1;
|
|
|
|
}
|
|
|
|
if (index & 1)
|
|
|
|
value++; // fix max values to describe the outer border
|
|
|
|
|
|
|
|
return value;
|
2014-11-13 22:26:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void Renderer::BBoxWrite(int index, u16 _value)
|
|
|
|
{
|
2016-06-24 08:43:46 +00:00
|
|
|
int value = _value; // u16 isn't enough to multiply by the efb width
|
|
|
|
if (index & 1)
|
|
|
|
value--;
|
|
|
|
if (index < 2)
|
|
|
|
{
|
2017-03-04 06:42:21 +00:00
|
|
|
value = value * m_target_width / EFB_WIDTH;
|
2016-06-24 08:43:46 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
index ^= 1; // swap 2 and 3 for top/bottom
|
|
|
|
value = EFB_HEIGHT - value - 1;
|
2017-03-04 06:42:21 +00:00
|
|
|
value = value * m_target_height / EFB_HEIGHT;
|
2016-06-24 08:43:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
BoundingBox::Set(index, value);
|
2014-11-13 22:26:49 +00:00
|
|
|
}
|
|
|
|
|
2018-01-21 12:04:15 +00:00
|
|
|
void Renderer::SetViewport(float x, float y, float width, float height, float near_depth,
|
|
|
|
float far_depth)
|
2010-09-28 02:15:02 +00:00
|
|
|
{
|
2018-01-21 12:04:15 +00:00
|
|
|
// The x/y parameters here assume a upper-left origin. glViewport takes an offset from the
|
|
|
|
// lower-left of the framebuffer, so we must set y to the distance from the lower-left.
|
|
|
|
y = static_cast<float>(m_target_height) - y - height;
|
2016-06-24 08:43:46 +00:00
|
|
|
if (g_ogl_config.bSupportViewportFloat)
|
|
|
|
{
|
2018-01-21 12:04:15 +00:00
|
|
|
glViewportIndexedf(0, x, y, width, height);
|
2016-06-24 08:43:46 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2018-01-21 12:04:15 +00:00
|
|
|
auto iceilf = [](float f) { return static_cast<GLint>(std::ceil(f)); };
|
|
|
|
glViewport(iceilf(x), iceilf(y), iceilf(width), iceilf(height));
|
2017-02-24 14:16:28 +00:00
|
|
|
}
|
2016-08-06 11:30:32 +00:00
|
|
|
|
2018-01-21 12:04:15 +00:00
|
|
|
glDepthRangef(near_depth, far_depth);
|
2010-09-28 02:15:02 +00:00
|
|
|
}
|
|
|
|
|
2016-06-24 08:43:46 +00:00
|
|
|
void Renderer::ClearScreen(const EFBRectangle& rc, bool colorEnable, bool alphaEnable, bool zEnable,
|
|
|
|
u32 color, u32 z)
|
2009-07-15 00:51:24 +00:00
|
|
|
{
|
2016-06-24 08:43:46 +00:00
|
|
|
ResetAPIState();
|
2009-07-15 00:51:24 +00:00
|
|
|
|
2016-06-24 08:43:46 +00:00
|
|
|
// 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);
|
2009-07-15 00:51:24 +00:00
|
|
|
|
2016-06-24 08:43:46 +00:00
|
|
|
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);
|
2011-11-30 03:50:31 +00:00
|
|
|
|
2016-06-24 08:43:46 +00:00
|
|
|
// depth
|
|
|
|
glDepthMask(zEnable ? GL_TRUE : GL_FALSE);
|
2009-07-15 00:51:24 +00:00
|
|
|
|
2016-06-24 08:43:46 +00:00
|
|
|
glClearDepthf(float(z & 0xFFFFFF) / 16777216.0f);
|
2011-11-30 03:50:31 +00:00
|
|
|
|
2016-06-24 08:43:46 +00:00
|
|
|
// Update rect for clearing the picture
|
|
|
|
glEnable(GL_SCISSOR_TEST);
|
2011-11-30 03:50:31 +00:00
|
|
|
|
2016-06-24 08:43:46 +00:00
|
|
|
TargetRectangle const targetRc = ConvertEFBRectangle(rc);
|
|
|
|
glScissor(targetRc.left, targetRc.bottom, targetRc.GetWidth(), targetRc.GetHeight());
|
2009-07-15 00:51:24 +00:00
|
|
|
|
2016-06-24 08:43:46 +00:00
|
|
|
// glColorMask/glDepthMask/glScissor affect glClear (glViewport does not)
|
|
|
|
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
2010-12-28 16:16:27 +00:00
|
|
|
|
2016-06-24 08:43:46 +00:00
|
|
|
RestoreAPIState();
|
2011-08-22 04:15:02 +00:00
|
|
|
|
2016-06-24 08:43:46 +00:00
|
|
|
ClearEFBCache();
|
2009-07-15 00:51:24 +00:00
|
|
|
}
|
2010-08-10 01:08:22 +00:00
|
|
|
|
2016-06-24 08:43:46 +00:00
|
|
|
void Renderer::BlitScreen(TargetRectangle src, TargetRectangle dst, GLuint src_texture,
|
|
|
|
int src_width, int src_height)
|
2014-12-20 16:10:58 +00:00
|
|
|
{
|
2017-04-21 13:33:12 +00:00
|
|
|
OpenGLPostProcessing* post_processor = static_cast<OpenGLPostProcessing*>(m_post_processor.get());
|
2017-11-11 03:55:00 +00:00
|
|
|
if (g_ActiveConfig.stereo_mode == StereoMode::SBS ||
|
|
|
|
g_ActiveConfig.stereo_mode == StereoMode::TAB)
|
2016-06-24 08:43:46 +00:00
|
|
|
{
|
|
|
|
TargetRectangle leftRc, rightRc;
|
|
|
|
|
|
|
|
// Top-and-Bottom mode needs to compensate for inverted vertical screen coordinates.
|
2017-11-11 03:55:00 +00:00
|
|
|
if (g_ActiveConfig.stereo_mode == StereoMode::TAB)
|
2017-04-09 19:05:24 +00:00
|
|
|
std::tie(rightRc, leftRc) = ConvertStereoRectangle(dst);
|
2016-06-24 08:43:46 +00:00
|
|
|
else
|
2017-04-09 19:05:24 +00:00
|
|
|
std::tie(leftRc, rightRc) = ConvertStereoRectangle(dst);
|
2016-06-24 08:43:46 +00:00
|
|
|
|
2017-04-21 13:33:12 +00:00
|
|
|
post_processor->BlitFromTexture(src, leftRc, src_texture, src_width, src_height, 0);
|
|
|
|
post_processor->BlitFromTexture(src, rightRc, src_texture, src_width, src_height, 1);
|
2016-06-24 08:43:46 +00:00
|
|
|
}
|
2017-11-11 03:55:00 +00:00
|
|
|
else if (g_ActiveConfig.stereo_mode == StereoMode::QuadBuffer)
|
2017-06-26 10:32:09 +00:00
|
|
|
{
|
|
|
|
glDrawBuffer(GL_BACK_LEFT);
|
|
|
|
post_processor->BlitFromTexture(src, dst, src_texture, src_width, src_height, 0);
|
|
|
|
|
|
|
|
glDrawBuffer(GL_BACK_RIGHT);
|
|
|
|
post_processor->BlitFromTexture(src, dst, src_texture, src_width, src_height, 1);
|
|
|
|
|
|
|
|
glDrawBuffer(GL_BACK);
|
|
|
|
}
|
2016-06-24 08:43:46 +00:00
|
|
|
else
|
|
|
|
{
|
2017-04-21 13:33:12 +00:00
|
|
|
post_processor->BlitFromTexture(src, dst, src_texture, src_width, src_height, 0);
|
2016-06-24 08:43:46 +00:00
|
|
|
}
|
2014-12-20 16:10:58 +00:00
|
|
|
}
|
|
|
|
|
2010-12-27 21:56:20 +00:00
|
|
|
void Renderer::ReinterpretPixelData(unsigned int convtype)
|
|
|
|
{
|
2016-06-24 08:43:46 +00:00
|
|
|
if (convtype == 0 || convtype == 2)
|
|
|
|
{
|
|
|
|
FramebufferManager::ReinterpretPixelData(convtype);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
ERROR_LOG(VIDEO, "Trying to reinterpret pixel data with unsupported conversion type %d",
|
|
|
|
convtype);
|
|
|
|
}
|
2010-12-27 21:56:20 +00:00
|
|
|
}
|
|
|
|
|
2017-04-29 15:00:45 +00:00
|
|
|
void Renderer::SetBlendingState(const BlendingState& state)
|
2009-06-26 08:57:53 +00:00
|
|
|
{
|
2016-12-29 17:03:22 +00:00
|
|
|
bool useDualSource =
|
2017-04-17 13:11:58 +00:00
|
|
|
state.usedualsrc && g_ActiveConfig.backend_info.bSupportsDualSourceBlend &&
|
2016-12-29 17:03:22 +00:00
|
|
|
(!DriverDetails::HasBug(DriverDetails::BUG_BROKEN_DUAL_SOURCE_BLENDING) || state.dstalpha);
|
2017-10-26 05:44:39 +00:00
|
|
|
// Only use shader blend if we need to and we don't support dual-source blending directly
|
|
|
|
bool useShaderBlend = !useDualSource && state.usedualsrc && state.dstalpha &&
|
|
|
|
g_ActiveConfig.backend_info.bSupportsFramebufferFetch;
|
2016-10-29 15:24:10 +00:00
|
|
|
|
2017-10-26 05:44:39 +00:00
|
|
|
if (useShaderBlend)
|
2016-06-24 08:43:46 +00:00
|
|
|
{
|
2017-10-26 05:44:39 +00:00
|
|
|
glDisable(GL_BLEND);
|
2016-06-24 08:43:46 +00:00
|
|
|
}
|
2016-12-29 17:03:22 +00:00
|
|
|
else
|
2016-06-24 08:43:46 +00:00
|
|
|
{
|
2017-10-26 05:44:39 +00:00
|
|
|
const GLenum src_factors[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,
|
|
|
|
GL_DST_ALPHA,
|
|
|
|
GL_ONE_MINUS_DST_ALPHA};
|
|
|
|
const GLenum dst_factors[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,
|
|
|
|
GL_DST_ALPHA,
|
|
|
|
GL_ONE_MINUS_DST_ALPHA};
|
|
|
|
|
|
|
|
if (state.blendenable)
|
|
|
|
{
|
|
|
|
glEnable(GL_BLEND);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
glDisable(GL_BLEND);
|
|
|
|
}
|
2016-06-24 08:43:46 +00:00
|
|
|
|
2017-10-26 05:44:39 +00:00
|
|
|
// Always call glBlendEquationSeparate and glBlendFuncSeparate, even when
|
|
|
|
// GL_BLEND is disabled, as a workaround for some bugs (possibly graphics
|
|
|
|
// driver issues?). See https://bugs.dolphin-emu.org/issues/10120 : "Sonic
|
|
|
|
// Adventure 2 Battle: graphics crash when loading first Dark level"
|
|
|
|
GLenum equation = state.subtract ? GL_FUNC_REVERSE_SUBTRACT : GL_FUNC_ADD;
|
|
|
|
GLenum equationAlpha = state.subtractAlpha ? GL_FUNC_REVERSE_SUBTRACT : GL_FUNC_ADD;
|
|
|
|
glBlendEquationSeparate(equation, equationAlpha);
|
|
|
|
glBlendFuncSeparate(src_factors[state.srcfactor], dst_factors[state.dstfactor],
|
|
|
|
src_factors[state.srcfactoralpha], dst_factors[state.dstfactoralpha]);
|
|
|
|
}
|
2017-03-01 05:29:31 +00:00
|
|
|
|
2016-12-29 17:03:22 +00:00
|
|
|
const GLenum logic_op_codes[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};
|
2016-06-24 08:43:46 +00:00
|
|
|
|
2016-12-29 17:03:22 +00:00
|
|
|
if (GLInterface->GetMode() != GLInterfaceMode::MODE_OPENGL)
|
2016-06-24 08:43:46 +00:00
|
|
|
{
|
2016-12-29 17:03:22 +00:00
|
|
|
// Logic ops aren't available in GLES3
|
2016-06-24 08:43:46 +00:00
|
|
|
}
|
2016-12-29 17:03:22 +00:00
|
|
|
else if (state.logicopenable)
|
2016-06-24 08:43:46 +00:00
|
|
|
{
|
2016-12-29 17:03:22 +00:00
|
|
|
glEnable(GL_COLOR_LOGIC_OP);
|
|
|
|
glLogicOp(logic_op_codes[state.logicmode]);
|
2016-06-24 08:43:46 +00:00
|
|
|
}
|
2016-12-29 17:03:22 +00:00
|
|
|
else
|
2016-06-24 08:43:46 +00:00
|
|
|
{
|
2016-12-29 17:03:22 +00:00
|
|
|
glDisable(GL_COLOR_LOGIC_OP);
|
2016-06-24 08:43:46 +00:00
|
|
|
}
|
2016-12-29 17:03:22 +00:00
|
|
|
|
|
|
|
glColorMask(state.colorupdate, state.colorupdate, state.colorupdate, state.alphaupdate);
|
2009-06-26 08:57:53 +00:00
|
|
|
}
|
|
|
|
|
2009-06-29 07:30:48 +00:00
|
|
|
// This function has the final picture. We adjust the aspect ratio here.
|
2017-10-01 16:19:29 +00:00
|
|
|
void Renderer::SwapImpl(AbstractTexture* texture, const EFBRectangle& xfb_region, u64 ticks,
|
|
|
|
float Gamma)
|
2009-02-28 16:33:59 +00:00
|
|
|
{
|
2016-06-24 08:43:46 +00:00
|
|
|
if (g_ogl_config.bSupportsDebug)
|
|
|
|
{
|
|
|
|
if (LogManager::GetInstance()->IsEnabled(LogTypes::HOST_GPU, LogTypes::LERROR))
|
|
|
|
glEnable(GL_DEBUG_OUTPUT);
|
|
|
|
else
|
|
|
|
glDisable(GL_DEBUG_OUTPUT);
|
|
|
|
}
|
|
|
|
|
2017-05-29 22:02:09 +00:00
|
|
|
auto* xfb_texture = static_cast<OGLTexture*>(texture);
|
2016-06-24 08:43:46 +00:00
|
|
|
|
2017-09-29 05:31:08 +00:00
|
|
|
TargetRectangle sourceRc = xfb_region;
|
|
|
|
sourceRc.top = xfb_region.GetHeight();
|
2017-05-29 22:02:09 +00:00
|
|
|
sourceRc.bottom = 0;
|
2016-06-24 08:43:46 +00:00
|
|
|
|
|
|
|
ResetAPIState();
|
|
|
|
|
2016-11-10 13:31:44 +00:00
|
|
|
UpdateDrawRectangle();
|
2016-06-24 08:43:46 +00:00
|
|
|
TargetRectangle flipped_trc = GetTargetRectangle();
|
|
|
|
|
|
|
|
// Flip top and bottom for some reason; TODO: Fix the code to suck less?
|
|
|
|
std::swap(flipped_trc.top, flipped_trc.bottom);
|
|
|
|
|
2017-11-23 06:53:44 +00:00
|
|
|
// Do our OSD callbacks
|
|
|
|
OSD::DoCallbacks(OSD::CallbackType::OnFrame);
|
2016-06-24 08:43:46 +00:00
|
|
|
|
2017-11-23 06:53:44 +00:00
|
|
|
// Skip screen rendering when running in headless mode.
|
|
|
|
if (!IsHeadless())
|
|
|
|
{
|
2018-01-26 06:22:22 +00:00
|
|
|
// Clear the framebuffer before drawing anything.
|
2017-11-23 06:53:44 +00:00
|
|
|
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
2018-01-26 06:22:22 +00:00
|
|
|
glClearColor(0, 0, 0, 0);
|
|
|
|
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
|
|
|
|
|
|
|
// Copy the framebuffer to screen.
|
2017-11-23 06:53:44 +00:00
|
|
|
BlitScreen(sourceRc, flipped_trc, xfb_texture->GetRawTexIdentifier(),
|
|
|
|
xfb_texture->GetConfig().width, xfb_texture->GetConfig().height);
|
2016-06-24 08:43:46 +00:00
|
|
|
|
2018-01-26 06:22:22 +00:00
|
|
|
// Render OSD messages.
|
|
|
|
glViewport(0, 0, m_backbuffer_width, m_backbuffer_height);
|
2017-11-23 06:53:44 +00:00
|
|
|
glEnable(GL_BLEND);
|
|
|
|
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
|
|
|
DrawDebugText();
|
|
|
|
OSD::DrawMessages();
|
2017-09-09 02:44:25 +00:00
|
|
|
|
2018-01-26 06:22:22 +00:00
|
|
|
// Swap the back and front buffers, presenting the image.
|
2017-11-23 06:53:44 +00:00
|
|
|
GLInterface->Swap();
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// Since we're not swapping in headless mode, ensure all commands are sent to the GPU.
|
|
|
|
// Otherwise the driver could batch several frames togehter.
|
|
|
|
glFlush();
|
|
|
|
}
|
2016-06-24 08:43:46 +00:00
|
|
|
|
2017-09-09 02:44:25 +00:00
|
|
|
#ifdef ANDROID
|
|
|
|
// Handle surface changes on Android.
|
|
|
|
if (m_surface_needs_change.IsSet())
|
|
|
|
{
|
|
|
|
GLInterface->UpdateHandle(m_new_surface_handle);
|
|
|
|
GLInterface->UpdateSurface();
|
2017-11-23 06:53:44 +00:00
|
|
|
m_surface_handle = m_new_surface_handle;
|
2017-09-09 02:44:25 +00:00
|
|
|
m_new_surface_handle = nullptr;
|
|
|
|
m_surface_needs_change.Clear();
|
|
|
|
m_surface_changed.Set();
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
GLInterface->Update();
|
|
|
|
|
|
|
|
// Was the size changed since the last frame?
|
2016-12-03 10:35:14 +00:00
|
|
|
bool window_resized = false;
|
|
|
|
int window_width = static_cast<int>(std::max(GLInterface->GetBackBufferWidth(), 1u));
|
|
|
|
int window_height = static_cast<int>(std::max(GLInterface->GetBackBufferHeight(), 1u));
|
2017-03-04 06:42:27 +00:00
|
|
|
if (window_width != m_backbuffer_width || window_height != m_backbuffer_height)
|
2016-06-24 08:43:46 +00:00
|
|
|
{
|
2016-12-03 10:35:14 +00:00
|
|
|
window_resized = true;
|
2017-03-04 06:42:21 +00:00
|
|
|
m_backbuffer_width = window_width;
|
|
|
|
m_backbuffer_height = window_height;
|
2016-06-24 08:43:46 +00:00
|
|
|
}
|
2016-12-03 10:35:14 +00:00
|
|
|
|
|
|
|
bool target_size_changed = CalculateTargetSize();
|
2017-03-09 23:33:10 +00:00
|
|
|
bool stencil_buffer_enabled =
|
|
|
|
static_cast<FramebufferManager*>(g_framebuffer_manager.get())->HasStencilBuffer();
|
|
|
|
|
|
|
|
bool fb_needs_update = target_size_changed ||
|
|
|
|
s_last_multisamples != g_ActiveConfig.iMultisamples ||
|
|
|
|
stencil_buffer_enabled != BoundingBox::NeedsStencilBuffer() ||
|
2017-11-11 03:55:00 +00:00
|
|
|
s_last_stereo_mode != (g_ActiveConfig.stereo_mode != StereoMode::Off);
|
2017-03-09 23:33:10 +00:00
|
|
|
|
2017-05-29 22:02:09 +00:00
|
|
|
if (window_resized || fb_needs_update)
|
2016-06-24 08:43:46 +00:00
|
|
|
{
|
2016-11-10 13:31:44 +00:00
|
|
|
UpdateDrawRectangle();
|
2017-03-09 23:33:10 +00:00
|
|
|
}
|
|
|
|
if (fb_needs_update)
|
|
|
|
{
|
2017-11-11 03:55:00 +00:00
|
|
|
s_last_stereo_mode = g_ActiveConfig.stereo_mode != StereoMode::Off;
|
2017-03-09 23:33:10 +00:00
|
|
|
s_last_multisamples = g_ActiveConfig.iMultisamples;
|
|
|
|
s_MSAASamples = s_last_multisamples;
|
2016-06-24 08:43:46 +00:00
|
|
|
|
2017-03-09 23:33:10 +00:00
|
|
|
if (s_MSAASamples > 1 && s_MSAASamples > g_ogl_config.max_samples)
|
2016-06-24 08:43:46 +00:00
|
|
|
{
|
2017-03-09 23:33:10 +00:00
|
|
|
s_MSAASamples = g_ogl_config.max_samples;
|
|
|
|
OSD::AddMessage(
|
|
|
|
StringFromFormat("%d Anti Aliasing samples selected, but only %d supported by your GPU.",
|
|
|
|
s_last_multisamples, g_ogl_config.max_samples),
|
|
|
|
10000);
|
2016-06-24 08:43:46 +00:00
|
|
|
}
|
2017-03-09 23:33:10 +00:00
|
|
|
|
|
|
|
g_framebuffer_manager.reset();
|
|
|
|
g_framebuffer_manager = std::make_unique<FramebufferManager>(
|
|
|
|
m_target_width, m_target_height, s_MSAASamples, BoundingBox::NeedsStencilBuffer());
|
|
|
|
BoundingBox::SetTargetSizeChanged(m_target_width, m_target_height);
|
2016-06-24 08:43:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (s_vsync != g_ActiveConfig.IsVSync())
|
|
|
|
{
|
|
|
|
s_vsync = g_ActiveConfig.IsVSync();
|
2016-10-29 22:56:18 +00:00
|
|
|
if (!DriverDetails::HasBug(DriverDetails::BUG_BROKEN_VSYNC))
|
2016-06-24 08:43:46 +00:00
|
|
|
GLInterface->SwapInterval(s_vsync);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Clean out old stuff from caches. It's not worth it to clean out the shader caches.
|
2016-09-06 22:57:58 +00:00
|
|
|
g_texture_cache->Cleanup(frameCount);
|
2017-07-20 05:25:29 +00:00
|
|
|
ProgramShaderCache::RetrieveAsyncShaders();
|
2016-06-24 08:43:46 +00:00
|
|
|
|
|
|
|
// Render to the framebuffer.
|
|
|
|
FramebufferManager::SetFramebuffer(0);
|
|
|
|
|
|
|
|
RestoreAPIState();
|
|
|
|
|
|
|
|
g_Config.iSaveTargetId = 0;
|
|
|
|
|
2017-09-09 08:30:15 +00:00
|
|
|
int old_anisotropy = g_ActiveConfig.iMaxAnisotropy;
|
2016-06-24 08:43:46 +00:00
|
|
|
UpdateActiveConfig();
|
2016-09-06 22:57:58 +00:00
|
|
|
g_texture_cache->OnConfigChanged(g_ActiveConfig);
|
2016-06-24 08:43:46 +00:00
|
|
|
|
2017-09-09 08:30:15 +00:00
|
|
|
if (old_anisotropy != g_ActiveConfig.iMaxAnisotropy)
|
|
|
|
g_sampler_cache->Clear();
|
|
|
|
|
2017-06-24 09:20:34 +00:00
|
|
|
// Invalidate shader cache when the host config changes.
|
2017-07-20 07:10:02 +00:00
|
|
|
if (CheckForHostConfigChanges())
|
2017-06-24 09:20:34 +00:00
|
|
|
ProgramShaderCache::Reload();
|
|
|
|
|
2016-06-24 08:43:46 +00:00
|
|
|
// For testing zbuffer targets.
|
|
|
|
// Renderer::SetZBufferRender();
|
|
|
|
// SaveTexture("tex.png", GL_TEXTURE_2D, s_FakeZTarget,
|
|
|
|
// GetTargetWidth(), GetTargetHeight());
|
|
|
|
|
|
|
|
// Invalidate EFB cache
|
|
|
|
ClearEFBCache();
|
2009-09-13 17:46:33 +00:00
|
|
|
}
|
2009-06-07 12:06:15 +00:00
|
|
|
|
2016-12-12 07:49:38 +00:00
|
|
|
void Renderer::DrawEFB(GLuint framebuffer, const TargetRectangle& target_rc,
|
2017-05-29 22:02:09 +00:00
|
|
|
const TargetRectangle& source_rc)
|
2016-11-10 12:56:22 +00:00
|
|
|
{
|
|
|
|
// for msaa mode, we must resolve the efb content to non-msaa
|
|
|
|
GLuint tex = FramebufferManager::ResolveAndGetRenderTarget(source_rc);
|
2016-12-12 07:49:38 +00:00
|
|
|
glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
|
2017-05-29 22:02:09 +00:00
|
|
|
BlitScreen(source_rc, target_rc, tex, m_target_width, m_target_height);
|
2016-11-10 12:56:22 +00:00
|
|
|
}
|
|
|
|
|
2010-09-28 02:15:02 +00:00
|
|
|
// ALWAYS call RestoreAPIState for each ResetAPIState call you're doing
|
|
|
|
void Renderer::ResetAPIState()
|
2009-02-23 07:23:34 +00:00
|
|
|
{
|
2016-06-24 08:43:46 +00:00
|
|
|
// 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);
|
|
|
|
if (GLInterface->GetMode() == GLInterfaceMode::MODE_OPENGL)
|
|
|
|
glDisable(GL_COLOR_LOGIC_OP);
|
2016-08-11 19:03:56 +00:00
|
|
|
if (g_ActiveConfig.backend_info.bSupportsDepthClamp)
|
2016-08-12 13:06:26 +00:00
|
|
|
{
|
2016-08-11 19:03:56 +00:00
|
|
|
glDisable(GL_CLIP_DISTANCE0);
|
2016-08-12 13:06:26 +00:00
|
|
|
glDisable(GL_CLIP_DISTANCE1);
|
|
|
|
}
|
2016-06-24 08:43:46 +00:00
|
|
|
glDepthMask(GL_FALSE);
|
|
|
|
glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
|
2010-09-28 02:15:02 +00:00
|
|
|
}
|
2009-02-23 07:23:34 +00:00
|
|
|
|
2010-09-28 02:15:02 +00:00
|
|
|
void Renderer::RestoreAPIState()
|
|
|
|
{
|
2016-06-24 08:43:46 +00:00
|
|
|
// Gets us back into a more game-like state.
|
|
|
|
glEnable(GL_SCISSOR_TEST);
|
2016-08-11 19:03:56 +00:00
|
|
|
if (g_ActiveConfig.backend_info.bSupportsDepthClamp)
|
2016-08-12 13:06:26 +00:00
|
|
|
{
|
2016-08-11 19:03:56 +00:00
|
|
|
glEnable(GL_CLIP_DISTANCE0);
|
2016-08-12 13:06:26 +00:00
|
|
|
glEnable(GL_CLIP_DISTANCE1);
|
|
|
|
}
|
2017-04-30 08:07:57 +00:00
|
|
|
BPFunctions::SetGenerationMode();
|
2016-06-24 08:43:46 +00:00
|
|
|
BPFunctions::SetScissor();
|
2018-01-21 12:04:15 +00:00
|
|
|
BPFunctions::SetViewport();
|
2017-04-30 05:54:45 +00:00
|
|
|
BPFunctions::SetDepthMode();
|
2017-04-29 15:00:45 +00:00
|
|
|
BPFunctions::SetBlendMode();
|
2016-06-24 08:43:46 +00:00
|
|
|
|
2017-07-20 05:25:29 +00:00
|
|
|
ProgramShaderCache::BindLastVertexFormat();
|
2016-06-24 08:43:46 +00:00
|
|
|
const VertexManager* const vm = static_cast<VertexManager*>(g_vertex_manager.get());
|
2017-09-02 22:05:49 +00:00
|
|
|
glBindBuffer(GL_ARRAY_BUFFER, vm->GetVertexBufferHandle());
|
2010-09-28 02:15:02 +00:00
|
|
|
}
|
2009-03-16 07:54:44 +00:00
|
|
|
|
2017-04-30 08:07:57 +00:00
|
|
|
void Renderer::SetRasterizationState(const RasterizationState& state)
|
2010-09-28 02:15:02 +00:00
|
|
|
{
|
2016-06-24 08:43:46 +00:00
|
|
|
// none, ccw, cw, ccw
|
2017-04-30 08:07:57 +00:00
|
|
|
if (state.cullmode != GenMode::CULL_NONE)
|
2016-06-24 08:43:46 +00:00
|
|
|
{
|
|
|
|
// TODO: GX_CULL_ALL not supported, yet!
|
|
|
|
glEnable(GL_CULL_FACE);
|
2017-04-30 08:07:57 +00:00
|
|
|
glFrontFace(state.cullmode == GenMode::CULL_FRONT ? GL_CCW : GL_CW);
|
2016-06-24 08:43:46 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
glDisable(GL_CULL_FACE);
|
|
|
|
}
|
2010-09-28 02:15:02 +00:00
|
|
|
}
|
2009-03-16 07:54:44 +00:00
|
|
|
|
2017-04-30 05:54:45 +00:00
|
|
|
void Renderer::SetDepthState(const DepthState& state)
|
2010-09-28 02:15:02 +00:00
|
|
|
{
|
2016-06-24 08:43:46 +00:00
|
|
|
const GLenum glCmpFuncs[8] = {GL_NEVER, GL_LESS, GL_EQUAL, GL_LEQUAL,
|
|
|
|
GL_GREATER, GL_NOTEQUAL, GL_GEQUAL, GL_ALWAYS};
|
|
|
|
|
2017-04-30 05:54:45 +00:00
|
|
|
if (state.testenable)
|
2016-06-24 08:43:46 +00:00
|
|
|
{
|
|
|
|
glEnable(GL_DEPTH_TEST);
|
2017-04-30 05:54:45 +00:00
|
|
|
glDepthMask(state.updateenable ? GL_TRUE : GL_FALSE);
|
|
|
|
glDepthFunc(glCmpFuncs[state.func]);
|
2016-06-24 08:43:46 +00:00
|
|
|
}
|
|
|
|
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);
|
|
|
|
}
|
2010-09-28 02:15:02 +00:00
|
|
|
}
|
2009-10-22 20:22:50 +00:00
|
|
|
|
2018-01-21 13:13:25 +00:00
|
|
|
void Renderer::SetTexture(u32 index, const AbstractTexture* texture)
|
|
|
|
{
|
|
|
|
if (m_bound_textures[index] == texture)
|
|
|
|
return;
|
|
|
|
|
|
|
|
glActiveTexture(GL_TEXTURE0 + index);
|
|
|
|
glBindTexture(GL_TEXTURE_2D_ARRAY,
|
|
|
|
texture ? static_cast<const OGLTexture*>(texture)->GetRawTexIdentifier() : 0);
|
|
|
|
m_bound_textures[index] = texture;
|
|
|
|
}
|
|
|
|
|
2017-09-09 08:30:15 +00:00
|
|
|
void Renderer::SetSamplerState(u32 index, const SamplerState& state)
|
2010-09-28 02:15:02 +00:00
|
|
|
{
|
2017-09-09 08:30:15 +00:00
|
|
|
g_sampler_cache->SetSamplerState(index, state);
|
2009-02-28 16:33:59 +00:00
|
|
|
}
|
2010-08-10 01:08:22 +00:00
|
|
|
|
2018-01-21 13:13:25 +00:00
|
|
|
void Renderer::UnbindTexture(const AbstractTexture* texture)
|
|
|
|
{
|
|
|
|
for (size_t i = 0; i < m_bound_textures.size(); i++)
|
|
|
|
{
|
|
|
|
if (m_bound_textures[i] != texture)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
glActiveTexture(static_cast<GLenum>(GL_TEXTURE0 + i));
|
|
|
|
glBindTexture(GL_TEXTURE_2D_ARRAY, 0);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2010-09-28 02:15:02 +00:00
|
|
|
void Renderer::SetInterlacingMode()
|
2009-02-23 07:23:34 +00:00
|
|
|
{
|
2016-06-24 08:43:46 +00:00
|
|
|
// TODO
|
2009-02-23 07:23:34 +00:00
|
|
|
}
|
2016-08-13 12:08:53 +00:00
|
|
|
|
|
|
|
void Renderer::ChangeSurface(void* new_surface_handle)
|
|
|
|
{
|
|
|
|
// Win32 polls the window size when redrawing, X11 runs an event loop in another thread.
|
|
|
|
// This is only necessary for Android at this point, although handling resizes here
|
|
|
|
// would be more efficient than polling.
|
|
|
|
#ifdef ANDROID
|
2017-03-04 06:42:21 +00:00
|
|
|
m_new_surface_handle = new_surface_handle;
|
|
|
|
m_surface_needs_change.Set();
|
|
|
|
m_surface_changed.Wait();
|
2016-08-13 12:08:53 +00:00
|
|
|
#endif
|
|
|
|
}
|
2010-12-20 17:06:39 +00:00
|
|
|
}
|