mirror of
https://github.com/PCSX2/pcsx2.git
synced 2026-09-20 03:27:16 +02:00
Currently we're trying to infer the conversion shader based on the output format It only works if the input data is RGBA8. It might not be true in the future
1378 lines
44 KiB
C++
1378 lines
44 KiB
C++
/*
|
|
* Copyright (C) 2011-2014 Gregory hainaut
|
|
* Copyright (C) 2007-2009 Gabest
|
|
*
|
|
* This Program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2, or (at your option)
|
|
* any later version.
|
|
*
|
|
* This Program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with GNU Make; see the file COPYING. If not, write to
|
|
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA USA.
|
|
* http://www.gnu.org/copyleft/gpl.html
|
|
*
|
|
*/
|
|
|
|
#include "stdafx.h"
|
|
#include "GSDeviceOGL.h"
|
|
#include "GLState.h"
|
|
#include <fstream>
|
|
|
|
#include "res/glsl_source.h"
|
|
|
|
//#define ONLY_LINES
|
|
|
|
// TODO port those value into PerfMon API
|
|
#ifdef ENABLE_OGL_DEBUG_MEM_BW
|
|
uint64 g_real_texture_upload_byte = 0;
|
|
uint64 g_vertex_upload_byte = 0;
|
|
uint64 g_uniform_upload_byte = 0;
|
|
#endif
|
|
|
|
static const uint32 g_merge_cb_index = 10;
|
|
static const uint32 g_interlace_cb_index = 11;
|
|
static const uint32 g_shadeboost_cb_index = 12;
|
|
static const uint32 g_fx_cb_index = 14;
|
|
|
|
bool GSDeviceOGL::m_debug_gl_call = false;
|
|
int GSDeviceOGL::s_n = 0;
|
|
FILE* GSDeviceOGL::m_debug_gl_file = NULL;
|
|
|
|
GSDeviceOGL::GSDeviceOGL()
|
|
: m_free_window(false)
|
|
, m_window(NULL)
|
|
, m_fbo(0)
|
|
, m_fbo_read(0)
|
|
, m_va(NULL)
|
|
, m_shader(NULL)
|
|
{
|
|
memset(&m_merge_obj, 0, sizeof(m_merge_obj));
|
|
memset(&m_interlace, 0, sizeof(m_interlace));
|
|
memset(&m_convert, 0, sizeof(m_convert));
|
|
memset(&m_fxaa, 0, sizeof(m_fxaa));
|
|
memset(&m_shaderfx, 0, sizeof(m_shaderfx));
|
|
memset(&m_date, 0, sizeof(m_date));
|
|
memset(&m_state, 0, sizeof(m_state));
|
|
GLState::Clear();
|
|
|
|
// Reset the debug file
|
|
#ifdef ENABLE_OGL_DEBUG
|
|
m_debug_gl_file = fopen("GSdx_opengl_debug.txt","w");
|
|
#endif
|
|
|
|
m_debug_gl_call = theApp.GetConfig("debug_opengl", 0);
|
|
}
|
|
|
|
GSDeviceOGL::~GSDeviceOGL()
|
|
{
|
|
if (m_debug_gl_file) {
|
|
fclose(m_debug_gl_file);
|
|
m_debug_gl_file = NULL;
|
|
}
|
|
|
|
// If the create function wasn't called nothing to do.
|
|
if (m_shader == NULL)
|
|
return;
|
|
|
|
GL_PUSH("GSDeviceOGL destructor");
|
|
|
|
// Clean vertex buffer state
|
|
delete (m_va);
|
|
|
|
// Clean m_merge_obj
|
|
for (size_t i = 0; i < countof(m_merge_obj.ps); i++)
|
|
m_shader->Delete(m_merge_obj.ps[i]);
|
|
delete (m_merge_obj.cb);
|
|
delete (m_merge_obj.bs);
|
|
|
|
// Clean m_interlace
|
|
for (size_t i = 0; i < countof(m_interlace.ps); i++)
|
|
m_shader->Delete(m_interlace.ps[i]);
|
|
delete (m_interlace.cb);
|
|
|
|
// Clean m_convert
|
|
m_shader->Delete(m_convert.vs);
|
|
for (size_t i = 0; i < countof(m_convert.ps); i++)
|
|
m_shader->Delete(m_convert.ps[i]);
|
|
delete m_convert.dss;
|
|
delete m_convert.bs;
|
|
|
|
// Clean m_fxaa
|
|
delete m_fxaa.cb;
|
|
m_shader->Delete(m_fxaa.ps);
|
|
|
|
// Clean m_shaderfx
|
|
delete m_shaderfx.cb;
|
|
m_shader->Delete(m_shaderfx.ps);
|
|
|
|
// Clean m_date
|
|
delete m_date.dss;
|
|
delete m_date.bs;
|
|
|
|
// Clean shadeboost
|
|
delete m_shadeboost.cb;
|
|
m_shader->Delete(m_shadeboost.ps);
|
|
|
|
|
|
// Clean various opengl allocation
|
|
gl_DeleteFramebuffers(1, &m_fbo);
|
|
gl_DeleteFramebuffers(1, &m_fbo_read);
|
|
|
|
// Delete HW FX
|
|
delete m_vs_cb;
|
|
delete m_ps_cb;
|
|
gl_DeleteSamplers(1, &m_palette_ss);
|
|
m_shader->Delete(m_apitrace);
|
|
|
|
for (uint32 key = 0; key < VSSelector::size(); key++) m_shader->Delete(m_vs[key]);
|
|
m_shader->Delete(m_gs);
|
|
for (auto it = m_ps.begin(); it != m_ps.end() ; it++) m_shader->Delete(it->second);
|
|
|
|
m_ps.clear();
|
|
|
|
gl_DeleteSamplers(PSSamplerSelector::size(), m_ps_ss);
|
|
|
|
for (uint32 key = 0; key < OMDepthStencilSelector::size(); key++) delete m_om_dss[key];
|
|
|
|
for (auto it = m_om_bs.begin(); it != m_om_bs.end(); it++) delete it->second;
|
|
m_om_bs.clear();
|
|
|
|
PboPool::Destroy();
|
|
|
|
// Must be done after the destruction of all shader/program objects
|
|
delete m_shader;
|
|
m_shader = NULL;
|
|
|
|
GL_POP();
|
|
}
|
|
|
|
GSTexture* GSDeviceOGL::CreateSurface(int type, int w, int h, bool msaa, int fmt)
|
|
{
|
|
GL_PUSH("Create surface");
|
|
|
|
// A wrapper to call GSTextureOGL, with the different kind of parameter
|
|
GSTextureOGL* t = NULL;
|
|
t = new GSTextureOGL(type, w, h, fmt, m_fbo_read);
|
|
|
|
// NOTE: I'm not sure RenderTarget always need to be cleared. It could be costly for big upscale.
|
|
switch(type)
|
|
{
|
|
case GSTexture::RenderTarget:
|
|
ClearRenderTarget(t, 0);
|
|
break;
|
|
case GSTexture::DepthStencil:
|
|
ClearDepth(t, 0);
|
|
// No need to clear the stencil now.
|
|
break;
|
|
}
|
|
|
|
GL_POP();
|
|
return t;
|
|
}
|
|
|
|
GSTexture* GSDeviceOGL::FetchSurface(int type, int w, int h, bool msaa, int format)
|
|
{
|
|
return GSDevice::FetchSurface(type, w, h, false, format);
|
|
}
|
|
|
|
bool GSDeviceOGL::Create(GSWnd* wnd)
|
|
{
|
|
if (m_window == NULL) {
|
|
if (!GLLoader::check_gl_version(3, 3)) return false;
|
|
|
|
if (!GLLoader::check_gl_supported_extension()) return false;
|
|
}
|
|
|
|
GL_PUSH("GSDeviceOGL::Create");
|
|
|
|
m_window = wnd;
|
|
|
|
// ****************************************************************
|
|
// Debug helper
|
|
// ****************************************************************
|
|
#ifdef ENABLE_OGL_DEBUG
|
|
if (theApp.GetConfig("debug_opengl", 0)) {
|
|
gl_DebugMessageCallback((GLDEBUGPROC)DebugOutputToFile, NULL);
|
|
glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS_ARB);
|
|
}
|
|
#endif
|
|
|
|
// ****************************************************************
|
|
// Various object
|
|
// ****************************************************************
|
|
m_shader = new GSShaderOGL(!!theApp.GetConfig("debug_glsl_shader", 0));
|
|
|
|
gl_GenFramebuffers(1, &m_fbo);
|
|
gl_GenFramebuffers(1, &m_fbo_read);
|
|
// Always read from the first buffer
|
|
gl_BindFramebuffer(GL_READ_FRAMEBUFFER, m_fbo_read);
|
|
glReadBuffer(GL_COLOR_ATTACHMENT0);
|
|
gl_BindFramebuffer(GL_READ_FRAMEBUFFER, 0);
|
|
|
|
// ****************************************************************
|
|
// Vertex buffer state
|
|
// ****************************************************************
|
|
ASSERT(sizeof(GSVertexPT1) == sizeof(GSVertex));
|
|
GSInputLayoutOGL il_convert[] =
|
|
{
|
|
{2 , GL_FLOAT , GL_FALSE , sizeof(GSVertexPT1) , (const GLvoid*)(0) } ,
|
|
{2 , GL_FLOAT , GL_FALSE , sizeof(GSVertexPT1) , (const GLvoid*)(16) } ,
|
|
{4 , GL_UNSIGNED_BYTE , GL_TRUE , sizeof(GSVertex) , (const GLvoid*)(8) } ,
|
|
{1 , GL_FLOAT , GL_FALSE , sizeof(GSVertex) , (const GLvoid*)(12) } ,
|
|
{2 , GL_UNSIGNED_SHORT , GL_FALSE , sizeof(GSVertex) , (const GLvoid*)(16) } ,
|
|
{1 , GL_UNSIGNED_INT , GL_FALSE , sizeof(GSVertex) , (const GLvoid*)(20) } ,
|
|
{2 , GL_UNSIGNED_SHORT , GL_FALSE , sizeof(GSVertex) , (const GLvoid*)(24) } ,
|
|
{4 , GL_UNSIGNED_BYTE , GL_TRUE , sizeof(GSVertex) , (const GLvoid*)(28) } , // Only 1 byte is useful but hardware unit only support 4B
|
|
};
|
|
m_va = new GSVertexBufferStateOGL(sizeof(GSVertexPT1), il_convert, countof(il_convert));
|
|
|
|
// ****************************************************************
|
|
// Pre Generate the different sampler object
|
|
// ****************************************************************
|
|
for (uint32 key = 0; key < PSSamplerSelector::size(); key++)
|
|
m_ps_ss[key] = CreateSampler(PSSamplerSelector(key));
|
|
|
|
// ****************************************************************
|
|
// convert
|
|
// ****************************************************************
|
|
m_convert.vs = m_shader->Compile("convert.glsl", "vs_main", GL_VERTEX_SHADER, convert_glsl);
|
|
for(size_t i = 0; i < countof(m_convert.ps); i++)
|
|
m_convert.ps[i] = m_shader->Compile("convert.glsl", format("ps_main%d", i), GL_FRAGMENT_SHADER, convert_glsl);
|
|
|
|
// Note the following object are initialized to 0 so disabled.
|
|
// Note: maybe enable blend with a factor of 1
|
|
// m_convert.dss, m_convert.bs
|
|
|
|
PSSamplerSelector point;
|
|
m_convert.pt = GetSamplerID(point);
|
|
|
|
PSSamplerSelector bilinear;
|
|
bilinear.ltf = true;
|
|
m_convert.ln = GetSamplerID(bilinear);
|
|
|
|
m_convert.dss = new GSDepthStencilOGL();
|
|
m_convert.bs = new GSBlendStateOGL();
|
|
|
|
// ****************************************************************
|
|
// merge
|
|
// ****************************************************************
|
|
m_merge_obj.cb = new GSUniformBufferOGL(g_merge_cb_index, sizeof(MergeConstantBuffer));
|
|
|
|
for(size_t i = 0; i < countof(m_merge_obj.ps); i++)
|
|
m_merge_obj.ps[i] = m_shader->Compile("merge.glsl", format("ps_main%d", i), GL_FRAGMENT_SHADER, merge_glsl);
|
|
|
|
m_merge_obj.bs = new GSBlendStateOGL();
|
|
m_merge_obj.bs->EnableBlend();
|
|
m_merge_obj.bs->SetRGB(GL_FUNC_ADD, GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
|
|
|
// ****************************************************************
|
|
// interlace
|
|
// ****************************************************************
|
|
m_interlace.cb = new GSUniformBufferOGL(g_interlace_cb_index, sizeof(InterlaceConstantBuffer));
|
|
|
|
for(size_t i = 0; i < countof(m_interlace.ps); i++)
|
|
m_interlace.ps[i] = m_shader->Compile("interlace.glsl", format("ps_main%d", i), GL_FRAGMENT_SHADER, interlace_glsl);
|
|
// ****************************************************************
|
|
// Shade boost
|
|
// ****************************************************************
|
|
m_shadeboost.cb = new GSUniformBufferOGL(g_shadeboost_cb_index, sizeof(ShadeBoostConstantBuffer));
|
|
|
|
int ShadeBoost_Contrast = theApp.GetConfig("ShadeBoost_Contrast", 50);
|
|
int ShadeBoost_Brightness = theApp.GetConfig("ShadeBoost_Brightness", 50);
|
|
int ShadeBoost_Saturation = theApp.GetConfig("ShadeBoost_Saturation", 50);
|
|
std::string shade_macro = format("#define SB_SATURATION %d.0\n", ShadeBoost_Saturation)
|
|
+ format("#define SB_BRIGHTNESS %d.0\n", ShadeBoost_Brightness)
|
|
+ format("#define SB_CONTRAST %d.0\n", ShadeBoost_Contrast);
|
|
|
|
m_shadeboost.ps = m_shader->Compile("shadeboost.glsl", "ps_main", GL_FRAGMENT_SHADER, shadeboost_glsl, shade_macro);
|
|
|
|
// ****************************************************************
|
|
// rasterization configuration
|
|
// ****************************************************************
|
|
#ifdef ONLY_LINES
|
|
glLineWidth(5.0);
|
|
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
|
|
#else
|
|
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
|
|
#endif
|
|
glDisable(GL_CULL_FACE);
|
|
glEnable(GL_SCISSOR_TEST);
|
|
glDisable(GL_MULTISAMPLE);
|
|
|
|
// ****************************************************************
|
|
// DATE
|
|
// ****************************************************************
|
|
|
|
m_date.dss = new GSDepthStencilOGL();
|
|
m_date.dss->EnableStencil();
|
|
m_date.dss->SetStencil(GL_ALWAYS, GL_REPLACE);
|
|
|
|
m_date.bs = new GSBlendStateOGL();
|
|
|
|
// ****************************************************************
|
|
// Use DX coordinate convention
|
|
// ****************************************************************
|
|
|
|
|
|
// VS gl_position.z => [-1,-1]
|
|
// FS depth => [0, 1]
|
|
// because of -1 we loose lot of precision for small GS value
|
|
// This extension allow FS depth to range from -1 to 1. So
|
|
// gl_position.z could range from [0, 1]
|
|
if (GLLoader::found_GL_ARB_clip_control) {
|
|
// Change depth convention
|
|
gl_ClipControl(GL_LOWER_LEFT, GL_ZERO_TO_ONE);
|
|
}
|
|
|
|
// ****************************************************************
|
|
// HW renderer shader
|
|
// ****************************************************************
|
|
CreateTextureFX();
|
|
|
|
// ****************************************************************
|
|
// Pbo Pool allocation
|
|
// ****************************************************************
|
|
PboPool::Init();
|
|
|
|
GL_POP();
|
|
|
|
// ****************************************************************
|
|
// Finish window setup and backbuffer
|
|
// ****************************************************************
|
|
if(!GSDevice::Create(wnd))
|
|
return false;
|
|
|
|
GSVector4i rect = wnd->GetClientRect();
|
|
Reset(rect.z, rect.w);
|
|
|
|
// Basic to ensure structures are correctly packed
|
|
ASSERT(sizeof(VSSelector) == 4);
|
|
ASSERT(sizeof(PSSelector) == 4);
|
|
ASSERT(sizeof(PSSamplerSelector) == 4);
|
|
ASSERT(sizeof(OMDepthStencilSelector) == 4);
|
|
ASSERT(sizeof(OMColorMaskSelector) == 4);
|
|
ASSERT(sizeof(OMBlendSelector) == 4);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool GSDeviceOGL::Reset(int w, int h)
|
|
{
|
|
if(!GSDevice::Reset(w, h))
|
|
return false;
|
|
|
|
// Opengl allocate the backbuffer with the window. The render is done in the backbuffer when
|
|
// there isn't any FBO. Only a dummy texture is created to easily detect when the rendering is done
|
|
// in the backbuffer
|
|
m_backbuffer = new GSTextureOGL(GSTextureOGL::Backbuffer, w, h, 0, m_fbo_read);
|
|
|
|
return true;
|
|
}
|
|
|
|
void GSDeviceOGL::SetVSync(bool enable)
|
|
{
|
|
m_wnd->SetVSync(enable);
|
|
}
|
|
|
|
void GSDeviceOGL::Flip()
|
|
{
|
|
#ifdef ENABLE_OGL_DEBUG
|
|
CheckDebugLog();
|
|
#endif
|
|
|
|
m_wnd->Flip();
|
|
}
|
|
|
|
void GSDeviceOGL::BeforeDraw()
|
|
{
|
|
m_shader->UseProgram();
|
|
}
|
|
|
|
void GSDeviceOGL::AfterDraw()
|
|
{
|
|
}
|
|
|
|
void GSDeviceOGL::DrawPrimitive()
|
|
{
|
|
BeforeDraw();
|
|
m_va->DrawPrimitive();
|
|
AfterDraw();
|
|
}
|
|
|
|
void GSDeviceOGL::DrawIndexedPrimitive()
|
|
{
|
|
BeforeDraw();
|
|
m_va->DrawIndexedPrimitive();
|
|
AfterDraw();
|
|
}
|
|
|
|
void GSDeviceOGL::DrawIndexedPrimitive(int offset, int count)
|
|
{
|
|
ASSERT(offset + count <= (int)m_index.count);
|
|
|
|
BeforeDraw();
|
|
m_va->DrawIndexedPrimitive(offset, count);
|
|
AfterDraw();
|
|
}
|
|
|
|
void GSDeviceOGL::ClearRenderTarget(GSTexture* t, const GSVector4& c)
|
|
{
|
|
GSTextureOGL* T = static_cast<GSTextureOGL*>(t);
|
|
if (T->HasBeenCleaned() && !T->IsBackbuffer())
|
|
return;
|
|
|
|
GL_PUSH("Clear RT %d", T->GetID());
|
|
|
|
// TODO: check size of scissor before toggling it
|
|
glDisable(GL_SCISSOR_TEST);
|
|
|
|
uint32 old_color_mask = GLState::wrgba;
|
|
OMSetColorMaskState();
|
|
|
|
if (T->IsBackbuffer()) {
|
|
OMSetFBO(0);
|
|
|
|
// glDrawBuffer(GL_BACK); // this is the default when there is no FB
|
|
// 0 will select the first drawbuffer ie GL_BACK
|
|
gl_ClearBufferfv(GL_COLOR, 0, c.v);
|
|
} else {
|
|
OMSetFBO(m_fbo);
|
|
OMAttachRt(T);
|
|
|
|
gl_ClearBufferfv(GL_COLOR, 0, c.v);
|
|
|
|
}
|
|
|
|
OMSetColorMaskState(OMColorMaskSelector(old_color_mask));
|
|
|
|
glEnable(GL_SCISSOR_TEST);
|
|
|
|
T->WasCleaned();
|
|
|
|
GL_POP();
|
|
}
|
|
|
|
void GSDeviceOGL::ClearRenderTarget(GSTexture* t, uint32 c)
|
|
{
|
|
GSVector4 color = GSVector4::rgba32(c) * (1.0f / 255);
|
|
ClearRenderTarget(t, color);
|
|
}
|
|
|
|
void GSDeviceOGL::ClearRenderTarget_i(GSTexture* t, int32 c)
|
|
{
|
|
GSTextureOGL* T = static_cast<GSTextureOGL*>(t);
|
|
|
|
GL_PUSH("Clear RTi %d", T->GetID());
|
|
|
|
uint32 old_color_mask = GLState::wrgba;
|
|
OMSetColorMaskState();
|
|
|
|
// Keep SCISSOR_TEST enabled on purpose to reduce the size
|
|
// of clean in DATE (impact big upscaling)
|
|
int32 col[4] = {c, c, c, c};
|
|
|
|
OMSetFBO(m_fbo);
|
|
OMAttachRt(T);
|
|
|
|
gl_ClearBufferiv(GL_COLOR, 0, col);
|
|
|
|
OMSetColorMaskState(OMColorMaskSelector(old_color_mask));
|
|
|
|
GL_POP();
|
|
}
|
|
|
|
void GSDeviceOGL::ClearDepth(GSTexture* t, float c)
|
|
{
|
|
GSTextureOGL* T = static_cast<GSTextureOGL*>(t);
|
|
|
|
GL_PUSH("Clear Depth %d", T->GetID());
|
|
|
|
OMSetFBO(m_fbo);
|
|
OMAttachDs(T);
|
|
|
|
// TODO: check size of scissor before toggling it
|
|
glDisable(GL_SCISSOR_TEST);
|
|
if (GLState::depth_mask) {
|
|
gl_ClearBufferfv(GL_DEPTH, 0, &c);
|
|
} else {
|
|
glDepthMask(true);
|
|
gl_ClearBufferfv(GL_DEPTH, 0, &c);
|
|
glDepthMask(false);
|
|
}
|
|
glEnable(GL_SCISSOR_TEST);
|
|
|
|
GL_POP();
|
|
}
|
|
|
|
void GSDeviceOGL::ClearStencil(GSTexture* t, uint8 c)
|
|
{
|
|
GSTextureOGL* T = static_cast<GSTextureOGL*>(t);
|
|
|
|
GL_PUSH("Clear Stencil %d", T->GetID());
|
|
|
|
// Keep SCISSOR_TEST enabled on purpose to reduce the size
|
|
// of clean in DATE (impact big upscaling)
|
|
OMSetFBO(m_fbo);
|
|
OMAttachDs(T);
|
|
GLint color = c;
|
|
|
|
gl_ClearBufferiv(GL_STENCIL, 0, &color);
|
|
|
|
GL_POP();
|
|
}
|
|
|
|
GLuint GSDeviceOGL::CreateSampler(PSSamplerSelector sel)
|
|
{
|
|
return CreateSampler(sel.ltf, sel.tau, sel.tav);
|
|
}
|
|
|
|
GLuint GSDeviceOGL::CreateSampler(bool bilinear, bool tau, bool tav)
|
|
{
|
|
GL_PUSH("Create Sampler");
|
|
|
|
GLuint sampler;
|
|
gl_GenSamplers(1, &sampler);
|
|
if (bilinear) {
|
|
gl_SamplerParameteri(sampler, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
|
gl_SamplerParameteri(sampler, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
|
} else {
|
|
gl_SamplerParameteri(sampler, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
|
gl_SamplerParameteri(sampler, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
|
}
|
|
|
|
if (tau)
|
|
gl_SamplerParameteri(sampler, GL_TEXTURE_WRAP_S, GL_REPEAT);
|
|
else
|
|
gl_SamplerParameteri(sampler, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
|
if (tav)
|
|
gl_SamplerParameteri(sampler, GL_TEXTURE_WRAP_T, GL_REPEAT);
|
|
else
|
|
gl_SamplerParameteri(sampler, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
|
|
|
gl_SamplerParameteri(sampler, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
|
|
|
|
gl_SamplerParameterf(sampler, GL_TEXTURE_MIN_LOD, 0);
|
|
gl_SamplerParameterf(sampler, GL_TEXTURE_MAX_LOD, 6);
|
|
|
|
if (GLLoader::found_GL_EXT_texture_filter_anisotropic && !!theApp.GetConfig("AnisotropicFiltering", 0) && !theApp.GetConfig("paltex", 0)) {
|
|
int anisotropy = theApp.GetConfig("MaxAnisotropy", 1);
|
|
if (anisotropy > 1) // 1 is the default in opengl so don't do anything
|
|
gl_SamplerParameterf(sampler, GL_TEXTURE_MAX_ANISOTROPY_EXT, (float)anisotropy);
|
|
}
|
|
|
|
GL_POP();
|
|
return sampler;
|
|
}
|
|
|
|
void GSDeviceOGL::InitPrimDateTexture(GSTexture* rt)
|
|
{
|
|
const GSVector2i& rtsize = rt->GetSize();
|
|
|
|
// Create a texture to avoid the useless clean@0
|
|
if (m_date.t == NULL)
|
|
m_date.t = CreateTexture(rtsize.x, rtsize.y, GL_R32I);
|
|
|
|
// Clean with the max signed value
|
|
ClearRenderTarget_i(m_date.t, 0x7FFFFFFF);
|
|
|
|
gl_BindImageTexture(2, m_date.t->GetID(), 0, false, 0, GL_READ_WRITE, GL_R32I);
|
|
}
|
|
|
|
void GSDeviceOGL::RecycleDateTexture()
|
|
{
|
|
if (m_date.t) {
|
|
//static_cast<GSTextureOGL*>(m_date.t)->Save(format("/tmp/date_adv_%04ld.csv", s_n));
|
|
|
|
Recycle(m_date.t);
|
|
m_date.t = NULL;
|
|
}
|
|
}
|
|
|
|
void GSDeviceOGL::Barrier(GLbitfield b)
|
|
{
|
|
gl_MemoryBarrier(b);
|
|
}
|
|
|
|
/* Note: must be here because tfx_glsl is static */
|
|
GLuint GSDeviceOGL::CompileVS(VSSelector sel, int logz)
|
|
{
|
|
std::string macro = format("#define VS_BPPZ %d\n", sel.bppz)
|
|
+ format("#define VS_LOGZ %d\n", logz)
|
|
+ format("#define VS_TME %d\n", sel.tme)
|
|
+ format("#define VS_FST %d\n", sel.fst)
|
|
+ format("#define VS_WILDHACK %d\n", sel.wildhack)
|
|
;
|
|
|
|
return m_shader->Compile("tfx_vgs.glsl", "vs_main", GL_VERTEX_SHADER, tfx_vgs_glsl, macro);
|
|
}
|
|
|
|
/* Note: must be here because tfx_glsl is static */
|
|
GLuint GSDeviceOGL::CompileGS()
|
|
{
|
|
return m_shader->Compile("tfx_vgs.glsl", "gs_main", GL_GEOMETRY_SHADER, tfx_vgs_glsl, "");
|
|
}
|
|
|
|
/* Note: must be here because tfx_glsl is static */
|
|
GLuint GSDeviceOGL::CompilePS(PSSelector sel)
|
|
{
|
|
std::string macro = format("#define PS_FST %d\n", sel.fst)
|
|
+ format("#define PS_WMS %d\n", sel.wms)
|
|
+ format("#define PS_WMT %d\n", sel.wmt)
|
|
+ format("#define PS_FMT %d\n", sel.fmt)
|
|
+ format("#define PS_AEM %d\n", sel.aem)
|
|
+ format("#define PS_TFX %d\n", sel.tfx)
|
|
+ format("#define PS_TCC %d\n", sel.tcc)
|
|
+ format("#define PS_ATST %d\n", sel.atst)
|
|
+ format("#define PS_FOG %d\n", sel.fog)
|
|
+ format("#define PS_CLR1 %d\n", sel.clr1)
|
|
+ format("#define PS_FBA %d\n", sel.fba)
|
|
+ format("#define PS_AOUT %d\n", sel.aout)
|
|
+ format("#define PS_LTF %d\n", sel.ltf)
|
|
+ format("#define PS_COLCLIP %d\n", sel.colclip)
|
|
+ format("#define PS_DATE %d\n", sel.date)
|
|
+ format("#define PS_TCOFFSETHACK %d\n", sel.tcoffsethack)
|
|
//+ format("#define PS_POINT_SAMPLER %d\n", sel.point_sampler)
|
|
+ format("#define PS_BLEND %d\n", sel.blend)
|
|
+ format("#define PS_IIP %d\n", sel.iip)
|
|
;
|
|
|
|
return m_shader->Compile("tfx.glsl", "ps_main", GL_FRAGMENT_SHADER, tfx_fs_all_glsl, macro);
|
|
}
|
|
|
|
GSTexture* GSDeviceOGL::CreateRenderTarget(int w, int h, bool msaa, int format)
|
|
{
|
|
return GSDevice::CreateRenderTarget(w, h, msaa, format ? format : GL_RGBA8);
|
|
}
|
|
|
|
GSTexture* GSDeviceOGL::CreateDepthStencil(int w, int h, bool msaa, int format)
|
|
{
|
|
return GSDevice::CreateDepthStencil(w, h, msaa, format ? format : GL_DEPTH32F_STENCIL8);
|
|
}
|
|
|
|
GSTexture* GSDeviceOGL::CreateTexture(int w, int h, int format)
|
|
{
|
|
return GSDevice::CreateTexture(w, h, format ? format : GL_RGBA8);
|
|
}
|
|
|
|
GSTexture* GSDeviceOGL::CreateOffscreen(int w, int h, int format)
|
|
{
|
|
return GSDevice::CreateOffscreen(w, h, format ? format : GL_RGBA8);
|
|
}
|
|
|
|
// blit a texture into an offscreen buffer
|
|
GSTexture* GSDeviceOGL::CopyOffscreen(GSTexture* src, const GSVector4& sRect, int w, int h, int format, int ps_shader)
|
|
{
|
|
if (format == 0)
|
|
format = GL_RGBA8;
|
|
|
|
ASSERT(src);
|
|
ASSERT(format == GL_RGBA8 || format == GL_R16UI);
|
|
|
|
GSTexture* dst = CreateOffscreen(w, h, format);
|
|
|
|
GSVector4 dRect(0, 0, w, h);
|
|
|
|
StretchRect(src, sRect, dst, dRect, m_convert.ps[ps_shader]);
|
|
|
|
return dst;
|
|
}
|
|
|
|
// Copy a sub part of a texture into another
|
|
void GSDeviceOGL::CopyRect(GSTexture* sTex, GSTexture* dTex, const GSVector4i& r)
|
|
{
|
|
ASSERT(sTex && dTex);
|
|
|
|
const GLuint& sid = sTex->GetID();
|
|
const GLuint& did = dTex->GetID();
|
|
|
|
GL_PUSH("CopyRect from %d to %d", sid, did);
|
|
|
|
if (GLLoader::found_GL_ARB_copy_image) {
|
|
gl_CopyImageSubData( sid, GL_TEXTURE_2D,
|
|
0, r.x, r.y, 0,
|
|
did, GL_TEXTURE_2D,
|
|
0, r.x, r.y, 0,
|
|
r.width(), r.height(), 1);
|
|
} else {
|
|
|
|
gl_BindFramebuffer(GL_READ_FRAMEBUFFER, m_fbo_read);
|
|
|
|
gl_FramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, sid, 0);
|
|
|
|
gl_CopyTextureSubImage2D(did, GL_TEX_LEVEL_0, r.x, r.y, r.x, r.y, r.width(), r.height());
|
|
|
|
gl_BindFramebuffer(GL_READ_FRAMEBUFFER, 0);
|
|
}
|
|
|
|
GL_POP();
|
|
}
|
|
|
|
void GSDeviceOGL::StretchRect(GSTexture* sTex, const GSVector4& sRect, GSTexture* dTex, const GSVector4& dRect, int shader, bool linear)
|
|
{
|
|
StretchRect(sTex, sRect, dTex, dRect, m_convert.ps[shader], linear);
|
|
}
|
|
|
|
void GSDeviceOGL::StretchRect(GSTexture* sTex, const GSVector4& sRect, GSTexture* dTex, const GSVector4& dRect, GLuint ps, bool linear)
|
|
{
|
|
StretchRect(sTex, sRect, dTex, dRect, ps, m_convert.bs, linear);
|
|
}
|
|
|
|
void GSDeviceOGL::StretchRect(GSTexture* sTex, const GSVector4& sRect, GSTexture* dTex, const GSVector4& dRect, GLuint ps, GSBlendStateOGL* bs, bool linear)
|
|
{
|
|
if(!sTex || !dTex)
|
|
{
|
|
ASSERT(0);
|
|
return;
|
|
}
|
|
|
|
GL_PUSH("StretchRect from %d to %d", sTex->GetID(), dTex->GetID());
|
|
|
|
// ************************************
|
|
// Init
|
|
// ************************************
|
|
|
|
BeginScene();
|
|
|
|
GSVector2i ds = dTex->GetSize();
|
|
|
|
// WARNING: setup of the program must be done first. So you can setup
|
|
// 1/ subroutine uniform
|
|
// 2/ bindless texture uniform
|
|
// 3/ others uniform?
|
|
m_shader->VS(m_convert.vs);
|
|
m_shader->GS(0);
|
|
m_shader->PS(ps);
|
|
|
|
// ************************************
|
|
// om
|
|
// ************************************
|
|
|
|
OMSetDepthStencilState(m_convert.dss, 0);
|
|
OMSetBlendState(bs, 0);
|
|
OMSetRenderTargets(dTex, NULL);
|
|
OMSetColorMaskState();
|
|
|
|
// ************************************
|
|
// ia
|
|
// ************************************
|
|
|
|
|
|
// Original code from DX
|
|
float left = dRect.x * 2 / ds.x - 1.0f;
|
|
float right = dRect.z * 2 / ds.x - 1.0f;
|
|
#if 0
|
|
float top = 1.0f - dRect.y * 2 / ds.y;
|
|
float bottom = 1.0f - dRect.w * 2 / ds.y;
|
|
#else
|
|
// Opengl get some issues with the coordinate
|
|
// I flip top/bottom to fix scaling of the internal resolution
|
|
float top = -1.0f + dRect.y * 2 / ds.y;
|
|
float bottom = -1.0f + dRect.w * 2 / ds.y;
|
|
#endif
|
|
|
|
// Flip y axis only when we render in the backbuffer
|
|
// By default everything is render in the wrong order (ie dx).
|
|
// 1/ consistency between several pass rendering (interlace)
|
|
// 2/ in case some GSdx code expect thing in dx order.
|
|
// Only flipping the backbuffer is transparent (I hope)...
|
|
GSVector4 flip_sr = sRect;
|
|
if (static_cast<GSTextureOGL*>(dTex)->IsBackbuffer()) {
|
|
flip_sr.y = sRect.w;
|
|
flip_sr.w = sRect.y;
|
|
}
|
|
|
|
GSVertexPT1 vertices[] =
|
|
{
|
|
{GSVector4(left , top , 0.0f, 0.0f) , GSVector2(flip_sr.x , flip_sr.y)} ,
|
|
{GSVector4(right , top , 0.0f, 0.0f) , GSVector2(flip_sr.z , flip_sr.y)} ,
|
|
{GSVector4(left , bottom, 0.0f, 0.0f) , GSVector2(flip_sr.x , flip_sr.w)} ,
|
|
{GSVector4(right , bottom, 0.0f, 0.0f) , GSVector2(flip_sr.z , flip_sr.w)} ,
|
|
};
|
|
|
|
IASetVertexBuffer(vertices, 4);
|
|
IASetPrimitiveTopology(GL_TRIANGLE_STRIP);
|
|
|
|
// ************************************
|
|
// Texture
|
|
// ************************************
|
|
|
|
if (GLLoader::found_GL_ARB_bindless_texture) {
|
|
GLuint64 handle[2] = {static_cast<GSTextureOGL*>(sTex)->GetHandle(linear ? m_convert.ln : m_convert.pt) , 0};
|
|
m_shader->PS_ressources(handle);
|
|
} else {
|
|
PSSetShaderResource(0, sTex);
|
|
PSSetSamplerState(linear ? m_convert.ln : m_convert.pt);
|
|
}
|
|
|
|
// ************************************
|
|
// Draw
|
|
// ************************************
|
|
DrawPrimitive();
|
|
|
|
// ************************************
|
|
// End
|
|
// ************************************
|
|
|
|
EndScene();
|
|
|
|
GL_POP();
|
|
}
|
|
|
|
void GSDeviceOGL::DoMerge(GSTexture* sTex[2], GSVector4* sRect, GSTexture* dTex, GSVector4* dRect, bool slbg, bool mmod, const GSVector4& c)
|
|
{
|
|
GL_PUSH("DoMerge");
|
|
|
|
OMSetColorMaskState();
|
|
|
|
ClearRenderTarget(dTex, c);
|
|
|
|
if(sTex[1] && !slbg)
|
|
{
|
|
StretchRect(sTex[1], sRect[1], dTex, dRect[1], m_merge_obj.ps[0]);
|
|
}
|
|
|
|
if(sTex[0])
|
|
{
|
|
m_merge_obj.cb->upload(&c.v);
|
|
|
|
StretchRect(sTex[0], sRect[0], dTex, dRect[0], m_merge_obj.ps[mmod ? 1 : 0], m_merge_obj.bs);
|
|
}
|
|
|
|
GL_POP();
|
|
}
|
|
|
|
void GSDeviceOGL::DoInterlace(GSTexture* sTex, GSTexture* dTex, int shader, bool linear, float yoffset)
|
|
{
|
|
GL_PUSH("DoInterlace");
|
|
|
|
OMSetColorMaskState();
|
|
|
|
GSVector4 s = GSVector4(dTex->GetSize());
|
|
|
|
GSVector4 sRect(0, 0, 1, 1);
|
|
GSVector4 dRect(0.0f, yoffset, s.x, s.y + yoffset);
|
|
|
|
InterlaceConstantBuffer cb;
|
|
|
|
cb.ZrH = GSVector2(0, 1.0f / s.y);
|
|
cb.hH = s.y / 2;
|
|
|
|
m_interlace.cb->upload(&cb);
|
|
|
|
StretchRect(sTex, sRect, dTex, dRect, m_interlace.ps[shader], linear);
|
|
|
|
GL_POP();
|
|
}
|
|
|
|
void GSDeviceOGL::DoFXAA(GSTexture* sTex, GSTexture* dTex)
|
|
{
|
|
// Lazy compile
|
|
if (!m_fxaa.ps) {
|
|
if (!GLLoader::found_GL_ARB_gpu_shader5) { // GL4.0 extension
|
|
return;
|
|
}
|
|
|
|
std::string fxaa_macro = "#define FXAA_GLSL_130 1\n";
|
|
fxaa_macro += "#extension GL_ARB_gpu_shader5 : enable\n";
|
|
m_fxaa.ps = m_shader->Compile("fxaa.fx", "ps_main", GL_FRAGMENT_SHADER, fxaa_fx, fxaa_macro);
|
|
}
|
|
|
|
GL_PUSH("DoFxaa");
|
|
|
|
OMSetColorMaskState();
|
|
|
|
GSVector2i s = dTex->GetSize();
|
|
|
|
GSVector4 sRect(0, 0, 1, 1);
|
|
GSVector4 dRect(0, 0, s.x, s.y);
|
|
|
|
StretchRect(sTex, sRect, dTex, dRect, m_fxaa.ps, true);
|
|
|
|
GL_POP();
|
|
}
|
|
|
|
void GSDeviceOGL::DoExternalFX(GSTexture* sTex, GSTexture* dTex)
|
|
{
|
|
// Lazy compile
|
|
if (!m_shaderfx.ps) {
|
|
if (!GLLoader::found_GL_ARB_gpu_shader5) { // GL4.0 extension
|
|
return;
|
|
}
|
|
|
|
std::string config_name(theApp.GetConfig("shaderfx_conf", "dummy.ini"));
|
|
std::ifstream fconfig(config_name);
|
|
std::stringstream config;
|
|
if (fconfig.good())
|
|
config << fconfig.rdbuf();
|
|
else
|
|
fprintf(stderr, "Warning failed to load '%s'. External Shader might be wrongly configured\n", config_name.c_str());
|
|
|
|
std::string shader_name(theApp.GetConfig("shaderfx_glsl", "dummy.glsl"));
|
|
std::ifstream fshader(shader_name);
|
|
std::stringstream shader;
|
|
if (!fshader.good()) {
|
|
fprintf(stderr, "Error failed to load '%s'. External Shader will be disabled !\n", shader_name.c_str());
|
|
return;
|
|
}
|
|
shader << fshader.rdbuf();
|
|
|
|
|
|
m_shaderfx.cb = new GSUniformBufferOGL(g_fx_cb_index, sizeof(ExternalFXConstantBuffer));
|
|
m_shaderfx.ps = m_shader->Compile("Extra", "ps_main", GL_FRAGMENT_SHADER, shader.str().c_str(), config.str());
|
|
}
|
|
|
|
GL_PUSH("DoExternalFX");
|
|
|
|
OMSetColorMaskState();
|
|
|
|
GSVector2i s = dTex->GetSize();
|
|
|
|
GSVector4 sRect(0, 0, 1, 1);
|
|
GSVector4 dRect(0, 0, s.x, s.y);
|
|
|
|
ExternalFXConstantBuffer cb;
|
|
|
|
cb.xyFrame = GSVector2(s.x, s.y);
|
|
cb.rcpFrame = GSVector4(1.0f / s.x, 1.0f / s.y, 0.0f, 0.0f);
|
|
cb.rcpFrameOpt = GSVector4::zero();
|
|
|
|
m_shaderfx.cb->upload(&cb);
|
|
|
|
StretchRect(sTex, sRect, dTex, dRect, m_shaderfx.ps, true);
|
|
|
|
GL_POP();
|
|
}
|
|
|
|
void GSDeviceOGL::DoShadeBoost(GSTexture* sTex, GSTexture* dTex)
|
|
{
|
|
GL_PUSH("DoShadeBoost");
|
|
|
|
OMSetColorMaskState();
|
|
|
|
GSVector2i s = dTex->GetSize();
|
|
|
|
GSVector4 sRect(0, 0, 1, 1);
|
|
GSVector4 dRect(0, 0, s.x, s.y);
|
|
|
|
ShadeBoostConstantBuffer cb;
|
|
|
|
cb.rcpFrame = GSVector4(1.0f / s.x, 1.0f / s.y, 0.0f, 0.0f);
|
|
cb.rcpFrameOpt = GSVector4::zero();
|
|
|
|
m_shadeboost.cb->upload(&cb);
|
|
|
|
StretchRect(sTex, sRect, dTex, dRect, m_shadeboost.ps, true);
|
|
|
|
GL_POP();
|
|
}
|
|
|
|
void GSDeviceOGL::SetupDATE(GSTexture* rt, GSTexture* ds, const GSVertexPT1* vertices, bool datm)
|
|
{
|
|
GL_PUSH("DATE First Pass");
|
|
|
|
GSTexture* t = NULL;
|
|
// sfex3 (after the capcom logo), vf4 (first menu fading in), ffxii shadows, rumble roses shadows, persona4 shadows
|
|
|
|
BeginScene();
|
|
|
|
ClearStencil(ds, 0);
|
|
|
|
// WARNING: setup of the program must be done first. So you can setup
|
|
// 1/ subroutine uniform
|
|
// 2/ bindless texture uniform
|
|
// 3/ others uniform?
|
|
m_shader->VS(m_convert.vs);
|
|
m_shader->GS(0);
|
|
m_shader->PS(m_convert.ps[datm ? 2 : 3]);
|
|
|
|
// om
|
|
|
|
OMSetDepthStencilState(m_date.dss, 1);
|
|
OMSetBlendState(m_date.bs, 0);
|
|
// normally ok without any RT if GL_ARB_framebuffer_no_attachments is supported (minus driver bug)
|
|
OMSetRenderTargets(t, ds, &GLState::scissor);
|
|
OMSetColorMaskState(); // TODO: likely useless
|
|
|
|
// ia
|
|
|
|
IASetVertexBuffer(vertices, 4);
|
|
IASetPrimitiveTopology(GL_TRIANGLE_STRIP);
|
|
|
|
|
|
// Texture
|
|
|
|
if (GLLoader::found_GL_ARB_bindless_texture) {
|
|
GLuint64 handle[2] = {static_cast<GSTextureOGL*>(rt)->GetHandle(m_convert.pt) , 0};
|
|
m_shader->PS_ressources(handle);
|
|
} else {
|
|
PSSetShaderResource(0, rt);
|
|
PSSetSamplerState(m_convert.pt);
|
|
}
|
|
|
|
OMSetWriteBuffer(GL_NONE);
|
|
DrawPrimitive();
|
|
OMSetWriteBuffer();
|
|
|
|
EndScene();
|
|
|
|
GL_POP();
|
|
}
|
|
|
|
void GSDeviceOGL::EndScene()
|
|
{
|
|
m_va->EndScene();
|
|
}
|
|
|
|
void GSDeviceOGL::IASetVertexBuffer(const void* vertices, size_t count)
|
|
{
|
|
m_va->UploadVB(vertices, count);
|
|
}
|
|
|
|
void GSDeviceOGL::IASetIndexBuffer(const void* index, size_t count)
|
|
{
|
|
m_va->UploadIB(index, count);
|
|
}
|
|
|
|
void GSDeviceOGL::IASetPrimitiveTopology(GLenum topology)
|
|
{
|
|
m_va->SetTopology(topology);
|
|
}
|
|
|
|
void GSDeviceOGL::PSSetShaderResource(int i, GSTexture* sRect)
|
|
{
|
|
GLuint id = sRect->GetID();
|
|
if (GLState::tex_unit[i] != id) {
|
|
GLState::tex_unit[i] = id;
|
|
gl_BindTextureUnit(i, id);
|
|
}
|
|
}
|
|
|
|
void GSDeviceOGL::PSSetShaderResources(GSTexture* sr0, GSTexture* sr1)
|
|
{
|
|
PSSetShaderResource(0, sr0);
|
|
PSSetShaderResource(1, sr1);
|
|
}
|
|
|
|
void GSDeviceOGL::PSSetSamplerState(GLuint ss)
|
|
{
|
|
if (GLState::ps_ss != ss) {
|
|
GLState::ps_ss = ss;
|
|
gl_BindSampler(0, ss);
|
|
}
|
|
}
|
|
|
|
void GSDeviceOGL::OMAttachRt(GSTextureOGL* rt)
|
|
{
|
|
GLuint id;
|
|
if (rt) {
|
|
rt->WasAttached();
|
|
id = rt->GetID();
|
|
} else {
|
|
id = 0;
|
|
}
|
|
|
|
if (GLState::rt != id) {
|
|
GLState::rt = id;
|
|
gl_FramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, id, 0);
|
|
}
|
|
}
|
|
|
|
void GSDeviceOGL::OMAttachDs(GSTextureOGL* ds)
|
|
{
|
|
GLuint id;
|
|
if (ds) {
|
|
ds->WasAttached();
|
|
id = ds->GetID();
|
|
} else {
|
|
id = 0;
|
|
}
|
|
|
|
if (GLState::ds != id) {
|
|
GLState::ds = id;
|
|
gl_FramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, id, 0);
|
|
}
|
|
}
|
|
|
|
void GSDeviceOGL::OMSetFBO(GLuint fbo)
|
|
{
|
|
if (GLState::fbo != fbo) {
|
|
GLState::fbo = fbo;
|
|
gl_BindFramebuffer(GL_FRAMEBUFFER, fbo);
|
|
}
|
|
}
|
|
|
|
void GSDeviceOGL::OMSetWriteBuffer(GLenum buffer)
|
|
{
|
|
GLenum target[1] = {buffer};
|
|
gl_DrawBuffers(1, target);
|
|
}
|
|
|
|
void GSDeviceOGL::OMSetDepthStencilState(GSDepthStencilOGL* dss, uint8 sref)
|
|
{
|
|
// State is checkd inside the object but worst case is 11 comparaisons !
|
|
if (m_state.dss != dss) {
|
|
m_state.dss = dss;
|
|
|
|
dss->SetupDepth();
|
|
dss->SetupStencil();
|
|
}
|
|
}
|
|
|
|
void GSDeviceOGL::OMSetColorMaskState(OMColorMaskSelector sel)
|
|
{
|
|
if (sel.wrgba != GLState::wrgba) {
|
|
GLState::wrgba = sel.wrgba;
|
|
|
|
gl_ColorMaski(0, sel.wr, sel.wg, sel.wb, sel.wa);
|
|
}
|
|
}
|
|
|
|
void GSDeviceOGL::OMSetBlendState(GSBlendStateOGL* bs, float bf)
|
|
{
|
|
// State is checkd inside the object but worst case is 8 comparaisons
|
|
if (m_state.bs != bs || m_state.bf != bf)
|
|
{
|
|
m_state.bs = bs;
|
|
m_state.bf = bf;
|
|
|
|
bs->SetupBlend(bf);
|
|
}
|
|
}
|
|
|
|
void GSDeviceOGL::OMSetRenderTargets(GSTexture* rt, GSTexture* ds, const GSVector4i* scissor)
|
|
{
|
|
GSTextureOGL* RT = static_cast<GSTextureOGL*>(rt);
|
|
GSTextureOGL* DS = static_cast<GSTextureOGL*>(ds);
|
|
|
|
if (rt == NULL || !RT->IsBackbuffer()) {
|
|
OMSetFBO(m_fbo);
|
|
if (rt) {
|
|
OMAttachRt(RT);
|
|
} else {
|
|
// Note: NULL rt is only used in DATE so far.
|
|
OMAttachRt();
|
|
}
|
|
|
|
// Note: it must be done after OMSetFBO
|
|
if (ds)
|
|
OMAttachDs(DS);
|
|
else
|
|
OMAttachDs();
|
|
|
|
} else {
|
|
// Render in the backbuffer
|
|
OMSetFBO(0);
|
|
}
|
|
|
|
|
|
GSVector2i size = rt ? rt->GetSize() : ds->GetSize();
|
|
if(GLState::viewport != size)
|
|
{
|
|
GLState::viewport = size;
|
|
glViewport(0, 0, size.x, size.y);
|
|
}
|
|
|
|
GSVector4i r = scissor ? *scissor : GSVector4i(size).zwxy();
|
|
|
|
if(!GLState::scissor.eq(r))
|
|
{
|
|
GLState::scissor = r;
|
|
glScissor( r.x, r.y, r.width(), r.height() );
|
|
}
|
|
}
|
|
|
|
void GSDeviceOGL::CheckDebugLog()
|
|
{
|
|
if (!m_debug_gl_call) return;
|
|
|
|
unsigned int count = 16; // max. num. of messages that will be read from the log
|
|
int bufsize = 2048;
|
|
unsigned int sources[16] = {};
|
|
unsigned int types[16] = {};
|
|
unsigned int ids[16] = {};
|
|
unsigned int severities[16] = {};
|
|
int lengths[16] = {};
|
|
char* messageLog = new char[bufsize];
|
|
|
|
unsigned int retVal = gl_GetDebugMessageLogARB(count, bufsize, sources, types, ids, severities, lengths, messageLog);
|
|
|
|
if(retVal > 0)
|
|
{
|
|
unsigned int pos = 0;
|
|
for(unsigned int i=0; i<retVal; i++)
|
|
{
|
|
DebugOutputToFile(sources[i], types[i], ids[i], severities[i], lengths[i], &messageLog[pos], NULL);
|
|
pos += lengths[i];
|
|
}
|
|
}
|
|
|
|
delete[] messageLog;
|
|
}
|
|
|
|
// Note: used as a callback of DebugMessageCallback. Don't change the signature
|
|
void GSDeviceOGL::DebugOutputToFile(GLenum gl_source, GLenum gl_type, GLuint id, GLenum gl_severity, GLsizei gl_length, const GLchar *gl_message, const void* userParam)
|
|
{
|
|
std::string message(gl_message, gl_length);
|
|
std::string type, severity, source;
|
|
static int sev_counter = 0;
|
|
switch(gl_type) {
|
|
case GL_DEBUG_TYPE_ERROR_ARB : type = "Error"; break;
|
|
case GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR_ARB : type = "Deprecated bhv"; break;
|
|
case GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR_ARB : type = "Undefined bhv"; break;
|
|
case GL_DEBUG_TYPE_PORTABILITY_ARB : type = "Portability"; break;
|
|
case GL_DEBUG_TYPE_PERFORMANCE_ARB : type = "Perf"; break;
|
|
case GL_DEBUG_TYPE_OTHER_ARB : type = "Others"; break;
|
|
case GL_DEBUG_TYPE_PUSH_GROUP : return; // Don't print message injected by myself
|
|
case GL_DEBUG_TYPE_POP_GROUP : return; // Don't print message injected by myself
|
|
default : type = "TTT"; break;
|
|
}
|
|
switch(gl_severity) {
|
|
case GL_DEBUG_SEVERITY_HIGH_ARB : severity = "High"; sev_counter++; break;
|
|
case GL_DEBUG_SEVERITY_MEDIUM_ARB : severity = "Mid"; break;
|
|
case GL_DEBUG_SEVERITY_LOW_ARB : severity = "Low"; break;
|
|
default : severity = "Info"; break;
|
|
}
|
|
switch(gl_source) {
|
|
case GL_DEBUG_SOURCE_API_ARB : source = "API"; break;
|
|
case GL_DEBUG_SOURCE_WINDOW_SYSTEM_ARB : source = "WINDOW"; break;
|
|
case GL_DEBUG_SOURCE_SHADER_COMPILER_ARB : source = "COMPILER"; break;
|
|
case GL_DEBUG_SOURCE_THIRD_PARTY_ARB : source = "3rdparty"; break;
|
|
case GL_DEBUG_SOURCE_APPLICATION_ARB : source = "Application"; break;
|
|
case GL_DEBUG_SOURCE_OTHER_ARB : source = "Others"; break;
|
|
default : source = "???"; break;
|
|
}
|
|
|
|
#ifdef _DEBUG
|
|
// Don't spam noisy information on the terminal
|
|
if (!(gl_type == GL_DEBUG_TYPE_OTHER_ARB && gl_severity == GL_DEBUG_SEVERITY_NOTIFICATION)) {
|
|
fprintf(stderr,"Type:%s\tID:%d\tSeverity:%s\tMessage:%s\n", type.c_str(), s_n, severity.c_str(), message.c_str());
|
|
}
|
|
#endif
|
|
|
|
if (m_debug_gl_file)
|
|
fprintf(m_debug_gl_file,"Type:%s\tID:%d\tSeverity:%s\tMessage:%s\n", type.c_str(), s_n, severity.c_str(), message.c_str());
|
|
|
|
ASSERT(sev_counter < 5);
|
|
}
|
|
|
|
// (A - B) * C + D
|
|
// A: Cs/Cd/0
|
|
// B: Cs/Cd/0
|
|
// C: As/Ad/FIX
|
|
// D: Cs/Cd/0
|
|
|
|
// bogus: 0100, 0110, 0120, 0200, 0210, 0220, 1001, 1011, 1021
|
|
// tricky: 1201, 1211, 1221
|
|
|
|
// Source.rgb = float3(1, 1, 1);
|
|
// 1201 Cd*(1 + As) => Source * Dest color + Dest * Source alpha
|
|
// 1211 Cd*(1 + Ad) => Source * Dest color + Dest * Dest alpha
|
|
// 1221 Cd*(1 + F) => Source * Dest color + Dest * Factor
|
|
|
|
// Copy Dx blend table and convert it to ogl
|
|
#define D3DBLENDOP_ADD GL_FUNC_ADD
|
|
#define D3DBLENDOP_SUBTRACT GL_FUNC_SUBTRACT
|
|
#define D3DBLENDOP_REVSUBTRACT GL_FUNC_REVERSE_SUBTRACT
|
|
|
|
#define D3DBLEND_ONE GL_ONE
|
|
#define D3DBLEND_ZERO GL_ZERO
|
|
#define D3DBLEND_INVDESTALPHA GL_ONE_MINUS_DST_ALPHA
|
|
#define D3DBLEND_DESTALPHA GL_DST_ALPHA
|
|
#define D3DBLEND_DESTCOLOR GL_DST_COLOR
|
|
#define D3DBLEND_BLENDFACTOR GL_CONSTANT_COLOR
|
|
#define D3DBLEND_INVBLENDFACTOR GL_ONE_MINUS_CONSTANT_COLOR
|
|
|
|
#define D3DBLEND_SRCALPHA GL_SRC1_ALPHA
|
|
#define D3DBLEND_INVSRCALPHA GL_ONE_MINUS_SRC1_ALPHA
|
|
|
|
const GSDeviceOGL::D3D9Blend GSDeviceOGL::m_blendMapD3D9[3*3*3*3] =
|
|
{
|
|
{0, D3DBLENDOP_ADD, D3DBLEND_ONE, D3DBLEND_ZERO}, // 0000: (Cs - Cs)*As + Cs ==> Cs
|
|
{0, D3DBLENDOP_ADD, D3DBLEND_ZERO, D3DBLEND_ONE}, // 0001: (Cs - Cs)*As + Cd ==> Cd
|
|
{0, D3DBLENDOP_ADD, D3DBLEND_ZERO, D3DBLEND_ZERO}, // 0002: (Cs - Cs)*As + 0 ==> 0
|
|
{0, D3DBLENDOP_ADD, D3DBLEND_ONE, D3DBLEND_ZERO}, // 0010: (Cs - Cs)*Ad + Cs ==> Cs
|
|
{0, D3DBLENDOP_ADD, D3DBLEND_ZERO, D3DBLEND_ONE}, // 0011: (Cs - Cs)*Ad + Cd ==> Cd
|
|
{0, D3DBLENDOP_ADD, D3DBLEND_ZERO, D3DBLEND_ZERO}, // 0012: (Cs - Cs)*Ad + 0 ==> 0
|
|
{0, D3DBLENDOP_ADD, D3DBLEND_ONE, D3DBLEND_ZERO}, // 0020: (Cs - Cs)*F + Cs ==> Cs
|
|
{0, D3DBLENDOP_ADD, D3DBLEND_ZERO, D3DBLEND_ONE}, // 0021: (Cs - Cs)*F + Cd ==> Cd
|
|
{0, D3DBLENDOP_ADD, D3DBLEND_ZERO, D3DBLEND_ZERO}, // 0022: (Cs - Cs)*F + 0 ==> 0
|
|
{4, D3DBLENDOP_SUBTRACT, D3DBLEND_SRCALPHA, D3DBLEND_SRCALPHA}, //*0100: (Cs - Cd)*As + Cs ==> Cs*(As + 1) - Cd*As
|
|
{0, D3DBLENDOP_ADD, D3DBLEND_SRCALPHA, D3DBLEND_INVSRCALPHA}, // 0101: (Cs - Cd)*As + Cd ==> Cs*As + Cd*(1 - As)
|
|
{0, D3DBLENDOP_SUBTRACT, D3DBLEND_SRCALPHA, D3DBLEND_SRCALPHA}, // 0102: (Cs - Cd)*As + 0 ==> Cs*As - Cd*As
|
|
{5, D3DBLENDOP_SUBTRACT, D3DBLEND_DESTALPHA, D3DBLEND_DESTALPHA}, //*0110: (Cs - Cd)*Ad + Cs ==> Cs*(Ad + 1) - Cd*Ad
|
|
{0, D3DBLENDOP_ADD, D3DBLEND_DESTALPHA, D3DBLEND_INVDESTALPHA}, // 0111: (Cs - Cd)*Ad + Cd ==> Cs*Ad + Cd*(1 - Ad)
|
|
{0, D3DBLENDOP_SUBTRACT, D3DBLEND_DESTALPHA, D3DBLEND_DESTALPHA}, // 0112: (Cs - Cd)*Ad + 0 ==> Cs*Ad - Cd*Ad
|
|
{6, D3DBLENDOP_SUBTRACT, D3DBLEND_BLENDFACTOR, D3DBLEND_BLENDFACTOR}, //*0120: (Cs - Cd)*F + Cs ==> Cs*(F + 1) - Cd*F
|
|
{0, D3DBLENDOP_ADD, D3DBLEND_BLENDFACTOR, D3DBLEND_INVBLENDFACTOR}, // 0121: (Cs - Cd)*F + Cd ==> Cs*F + Cd*(1 - F)
|
|
{0, D3DBLENDOP_SUBTRACT, D3DBLEND_BLENDFACTOR, D3DBLEND_BLENDFACTOR}, // 0122: (Cs - Cd)*F + 0 ==> Cs*F - Cd*F
|
|
{7, D3DBLENDOP_ADD, D3DBLEND_SRCALPHA, D3DBLEND_ZERO}, //*0200: (Cs - 0)*As + Cs ==> Cs*(As + 1)
|
|
{0, D3DBLENDOP_ADD, D3DBLEND_SRCALPHA, D3DBLEND_ONE}, // 0201: (Cs - 0)*As + Cd ==> Cs*As + Cd
|
|
{0, D3DBLENDOP_ADD, D3DBLEND_SRCALPHA, D3DBLEND_ZERO}, // 0202: (Cs - 0)*As + 0 ==> Cs*As
|
|
{8, D3DBLENDOP_ADD, D3DBLEND_DESTALPHA, D3DBLEND_ZERO}, //*0210: (Cs - 0)*Ad + Cs ==> Cs*(Ad + 1)
|
|
{0, D3DBLENDOP_ADD, D3DBLEND_DESTALPHA, D3DBLEND_ONE}, // 0211: (Cs - 0)*Ad + Cd ==> Cs*Ad + Cd
|
|
{0, D3DBLENDOP_ADD, D3DBLEND_DESTALPHA, D3DBLEND_ZERO}, // 0212: (Cs - 0)*Ad + 0 ==> Cs*Ad
|
|
{9, D3DBLENDOP_ADD, D3DBLEND_BLENDFACTOR, D3DBLEND_ZERO}, //*0220: (Cs - 0)*F + Cs ==> Cs*(F + 1)
|
|
{0, D3DBLENDOP_ADD, D3DBLEND_BLENDFACTOR, D3DBLEND_ONE}, // 0221: (Cs - 0)*F + Cd ==> Cs*F + Cd
|
|
{0, D3DBLENDOP_ADD, D3DBLEND_BLENDFACTOR, D3DBLEND_ZERO}, // 0222: (Cs - 0)*F + 0 ==> Cs*F
|
|
{0, D3DBLENDOP_ADD, D3DBLEND_INVSRCALPHA, D3DBLEND_SRCALPHA}, // 1000: (Cd - Cs)*As + Cs ==> Cd*As + Cs*(1 - As)
|
|
{10,D3DBLENDOP_REVSUBTRACT, D3DBLEND_SRCALPHA, D3DBLEND_SRCALPHA}, //*1001: (Cd - Cs)*As + Cd ==> Cd*(As + 1) - Cs*As
|
|
{0, D3DBLENDOP_REVSUBTRACT, D3DBLEND_SRCALPHA, D3DBLEND_SRCALPHA}, // 1002: (Cd - Cs)*As + 0 ==> Cd*As - Cs*As
|
|
{0, D3DBLENDOP_ADD, D3DBLEND_INVDESTALPHA, D3DBLEND_DESTALPHA}, // 1010: (Cd - Cs)*Ad + Cs ==> Cd*Ad + Cs*(1 - Ad)
|
|
{11,D3DBLENDOP_REVSUBTRACT, D3DBLEND_DESTALPHA, D3DBLEND_DESTALPHA}, //*1011: (Cd - Cs)*Ad + Cd ==> Cd*(Ad + 1) - Cs*Ad
|
|
{0, D3DBLENDOP_REVSUBTRACT, D3DBLEND_DESTALPHA, D3DBLEND_DESTALPHA}, // 1012: (Cd - Cs)*Ad + 0 ==> Cd*Ad - Cs*Ad
|
|
{0, D3DBLENDOP_ADD, D3DBLEND_INVBLENDFACTOR, D3DBLEND_BLENDFACTOR}, // 1020: (Cd - Cs)*F + Cs ==> Cd*F + Cs*(1 - F)
|
|
{12,D3DBLENDOP_REVSUBTRACT, D3DBLEND_BLENDFACTOR, D3DBLEND_BLENDFACTOR},//*1021: (Cd - Cs)*F + Cd ==> Cd*(F + 1) - Cs*F
|
|
{0, D3DBLENDOP_REVSUBTRACT, D3DBLEND_BLENDFACTOR, D3DBLEND_BLENDFACTOR},// 1022: (Cd - Cs)*F + 0 ==> Cd*F - Cs*F
|
|
{0, D3DBLENDOP_ADD, D3DBLEND_ONE, D3DBLEND_ZERO}, // 1100: (Cd - Cd)*As + Cs ==> Cs
|
|
{0, D3DBLENDOP_ADD, D3DBLEND_ZERO, D3DBLEND_ONE}, // 1101: (Cd - Cd)*As + Cd ==> Cd
|
|
{0, D3DBLENDOP_ADD, D3DBLEND_ZERO, D3DBLEND_ZERO}, // 1102: (Cd - Cd)*As + 0 ==> 0
|
|
{0, D3DBLENDOP_ADD, D3DBLEND_ONE, D3DBLEND_ZERO}, // 1110: (Cd - Cd)*Ad + Cs ==> Cs
|
|
{0, D3DBLENDOP_ADD, D3DBLEND_ZERO, D3DBLEND_ONE}, // 1111: (Cd - Cd)*Ad + Cd ==> Cd
|
|
{0, D3DBLENDOP_ADD, D3DBLEND_ZERO, D3DBLEND_ZERO}, // 1112: (Cd - Cd)*Ad + 0 ==> 0
|
|
{0, D3DBLENDOP_ADD, D3DBLEND_ONE, D3DBLEND_ZERO}, // 1120: (Cd - Cd)*F + Cs ==> Cs
|
|
{0, D3DBLENDOP_ADD, D3DBLEND_ZERO, D3DBLEND_ONE}, // 1121: (Cd - Cd)*F + Cd ==> Cd
|
|
{0, D3DBLENDOP_ADD, D3DBLEND_ZERO, D3DBLEND_ZERO}, // 1122: (Cd - Cd)*F + 0 ==> 0
|
|
{0, D3DBLENDOP_ADD, D3DBLEND_ONE, D3DBLEND_SRCALPHA}, // 1200: (Cd - 0)*As + Cs ==> Cs + Cd*As
|
|
{2, D3DBLENDOP_ADD, D3DBLEND_DESTCOLOR, D3DBLEND_SRCALPHA}, //#1201: (Cd - 0)*As + Cd ==> Cd*(1 + As) // ffxii main menu background glow effect
|
|
{0, D3DBLENDOP_ADD, D3DBLEND_ZERO, D3DBLEND_SRCALPHA}, // 1202: (Cd - 0)*As + 0 ==> Cd*As
|
|
{0, D3DBLENDOP_ADD, D3DBLEND_ONE, D3DBLEND_DESTALPHA}, // 1210: (Cd - 0)*Ad + Cs ==> Cs + Cd*Ad
|
|
{2, D3DBLENDOP_ADD, D3DBLEND_DESTCOLOR, D3DBLEND_DESTALPHA}, //#1211: (Cd - 0)*Ad + Cd ==> Cd*(1 + Ad)
|
|
{0, D3DBLENDOP_ADD, D3DBLEND_ZERO, D3DBLEND_DESTALPHA}, // 1212: (Cd - 0)*Ad + 0 ==> Cd*Ad
|
|
{0, D3DBLENDOP_ADD, D3DBLEND_ONE, D3DBLEND_BLENDFACTOR}, // 1220: (Cd - 0)*F + Cs ==> Cs + Cd*F
|
|
{2, D3DBLENDOP_ADD, D3DBLEND_DESTCOLOR, D3DBLEND_BLENDFACTOR}, //#1221: (Cd - 0)*F + Cd ==> Cd*(1 + F)
|
|
{0, D3DBLENDOP_ADD, D3DBLEND_ZERO, D3DBLEND_BLENDFACTOR}, // 1222: (Cd - 0)*F + 0 ==> Cd*F
|
|
{0, D3DBLENDOP_ADD, D3DBLEND_INVSRCALPHA, D3DBLEND_ZERO}, // 2000: (0 - Cs)*As + Cs ==> Cs*(1 - As)
|
|
{0, D3DBLENDOP_REVSUBTRACT, D3DBLEND_SRCALPHA, D3DBLEND_ONE}, // 2001: (0 - Cs)*As + Cd ==> Cd - Cs*As
|
|
{0, D3DBLENDOP_REVSUBTRACT, D3DBLEND_SRCALPHA, D3DBLEND_ZERO}, // 2002: (0 - Cs)*As + 0 ==> 0 - Cs*As
|
|
{0, D3DBLENDOP_ADD, D3DBLEND_INVDESTALPHA, D3DBLEND_ZERO}, // 2010: (0 - Cs)*Ad + Cs ==> Cs*(1 - Ad)
|
|
{0, D3DBLENDOP_REVSUBTRACT, D3DBLEND_DESTALPHA, D3DBLEND_ONE}, // 2011: (0 - Cs)*Ad + Cd ==> Cd - Cs*Ad
|
|
{0, D3DBLENDOP_REVSUBTRACT, D3DBLEND_DESTALPHA, D3DBLEND_ZERO}, // 2012: (0 - Cs)*Ad + 0 ==> 0 - Cs*Ad
|
|
{0, D3DBLENDOP_ADD, D3DBLEND_INVBLENDFACTOR, D3DBLEND_ZERO}, // 2020: (0 - Cs)*F + Cs ==> Cs*(1 - F)
|
|
{0, D3DBLENDOP_REVSUBTRACT, D3DBLEND_BLENDFACTOR, D3DBLEND_ONE}, // 2021: (0 - Cs)*F + Cd ==> Cd - Cs*F
|
|
{0, D3DBLENDOP_REVSUBTRACT, D3DBLEND_BLENDFACTOR, D3DBLEND_ZERO}, // 2022: (0 - Cs)*F + 0 ==> 0 - Cs*F
|
|
{0, D3DBLENDOP_SUBTRACT, D3DBLEND_ONE, D3DBLEND_SRCALPHA}, // 2100: (0 - Cd)*As + Cs ==> Cs - Cd*As
|
|
{0, D3DBLENDOP_ADD, D3DBLEND_ZERO, D3DBLEND_INVSRCALPHA}, // 2101: (0 - Cd)*As + Cd ==> Cd*(1 - As)
|
|
{0, D3DBLENDOP_SUBTRACT, D3DBLEND_ZERO, D3DBLEND_SRCALPHA}, // 2102: (0 - Cd)*As + 0 ==> 0 - Cd*As
|
|
{0, D3DBLENDOP_SUBTRACT, D3DBLEND_ONE, D3DBLEND_DESTALPHA}, // 2110: (0 - Cd)*Ad + Cs ==> Cs - Cd*Ad
|
|
{0, D3DBLENDOP_ADD, D3DBLEND_ZERO, D3DBLEND_INVDESTALPHA}, // 2111: (0 - Cd)*Ad + Cd ==> Cd*(1 - Ad)
|
|
{0, D3DBLENDOP_SUBTRACT, D3DBLEND_ONE, D3DBLEND_DESTALPHA}, // 2112: (0 - Cd)*Ad + 0 ==> 0 - Cd*Ad
|
|
{0, D3DBLENDOP_SUBTRACT, D3DBLEND_ONE, D3DBLEND_BLENDFACTOR}, // 2120: (0 - Cd)*F + Cs ==> Cs - Cd*F
|
|
{0, D3DBLENDOP_ADD, D3DBLEND_ZERO, D3DBLEND_INVBLENDFACTOR}, // 2121: (0 - Cd)*F + Cd ==> Cd*(1 - F)
|
|
{0, D3DBLENDOP_SUBTRACT, D3DBLEND_ONE, D3DBLEND_BLENDFACTOR}, // 2122: (0 - Cd)*F + 0 ==> 0 - Cd*F
|
|
{0, D3DBLENDOP_ADD, D3DBLEND_ONE, D3DBLEND_ZERO}, // 2200: (0 - 0)*As + Cs ==> Cs
|
|
{0, D3DBLENDOP_ADD, D3DBLEND_ZERO, D3DBLEND_ONE}, // 2201: (0 - 0)*As + Cd ==> Cd
|
|
{0, D3DBLENDOP_ADD, D3DBLEND_ZERO, D3DBLEND_ZERO}, // 2202: (0 - 0)*As + 0 ==> 0
|
|
{0, D3DBLENDOP_ADD, D3DBLEND_ONE, D3DBLEND_ZERO}, // 2210: (0 - 0)*Ad + Cs ==> Cs
|
|
{0, D3DBLENDOP_ADD, D3DBLEND_ZERO, D3DBLEND_ONE}, // 2211: (0 - 0)*Ad + Cd ==> Cd
|
|
{0, D3DBLENDOP_ADD, D3DBLEND_ZERO, D3DBLEND_ZERO}, // 2212: (0 - 0)*Ad + 0 ==> 0
|
|
{0, D3DBLENDOP_ADD, D3DBLEND_ONE, D3DBLEND_ZERO}, // 2220: (0 - 0)*F + Cs ==> Cs
|
|
{0, D3DBLENDOP_ADD, D3DBLEND_ZERO, D3DBLEND_ONE}, // 2221: (0 - 0)*F + Cd ==> Cd
|
|
{0, D3DBLENDOP_ADD, D3DBLEND_ZERO, D3DBLEND_ZERO}, // 2222: (0 - 0)*F + 0 ==> 0
|
|
};
|