// Should be scale free. void DecodeToTexture(u32 xfbAddr, int srcWidth, int srcHeight, GLuint destTexture) { u8* srcAddr = Memory::GetPointer(xfbAddr); if (!srcAddr) { WARN_LOG(VIDEO, "Tried to decode from invalid memory address"); return; } g_renderer->ResetAPIState(); // reset any game specific settings // switch to texture converter frame buffer // attach destTexture as color destination FramebufferManager::SetFramebuffer(s_texConvFrameBuffer[1]); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, destTexture, 0); GL_REPORT_FBO_ERROR(); // activate source texture // set srcAddr as data for source texture glActiveTexture(GL_TEXTURE0+9); glBindTexture(GL_TEXTURE_2D, s_srcTexture); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, srcWidth / 2, srcHeight, 0, GL_BGRA, GL_UNSIGNED_BYTE, srcAddr); glViewport(0, 0, srcWidth, srcHeight); s_yuyvToRgbProgram.Bind(); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); FramebufferManager::SetFramebuffer(0); g_renderer->RestoreAPIState(); GL_REPORT_ERRORD(); }
TextureCache::TCacheEntryBase* TextureCache::CreateRenderTargetTexture( unsigned int scaled_tex_w, unsigned int scaled_tex_h) { TCacheEntry *const entry = new TCacheEntry; glActiveTexture(GL_TEXTURE0+9); glBindTexture(GL_TEXTURE_2D, entry->texture); GL_REPORT_ERRORD(); const GLenum gl_format = GL_RGBA, gl_iformat = GL_RGBA, gl_type = GL_UNSIGNED_BYTE; glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0); glTexImage2D(GL_TEXTURE_2D, 0, gl_iformat, scaled_tex_w, scaled_tex_h, 0, gl_format, gl_type, nullptr); glBindTexture(GL_TEXTURE_2D, 0); glGenFramebuffers(1, &entry->framebuffer); FramebufferManager::SetFramebuffer(entry->framebuffer); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, entry->texture, 0); GL_REPORT_FBO_ERROR(); SetStage(); GL_REPORT_ERRORD(); return entry; }
void XFBSource::CopyEFB(float Gamma) { // Copy EFB data to XFB and restore render target again #if 0 if (m_msaaSamples <= 1) #else if (!s_bHaveFramebufferBlit) #endif { // Just copy the EFB directly. glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, FramebufferManager::GetEFBFramebuffer()); glBindTexture(GL_TEXTURE_RECTANGLE_ARB, texture); glCopyTexImage2D(GL_TEXTURE_RECTANGLE_ARB, 0, 4, 0, 0, texWidth, texHeight, 0); glBindTexture(GL_TEXTURE_RECTANGLE_ARB, 0); } else { // OpenGL cannot copy directly from a multisampled framebuffer, so use // EXT_framebuffer_blit. glBindFramebufferEXT(GL_READ_FRAMEBUFFER_EXT, FramebufferManager::GetEFBFramebuffer()); glBindFramebufferEXT(GL_DRAW_FRAMEBUFFER_EXT, FramebufferManager::GetXFBFramebuffer()); // Bind texture. glFramebufferTexture2DEXT(GL_DRAW_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_RECTANGLE_ARB, texture, 0); GL_REPORT_FBO_ERROR(); glBlitFramebufferEXT( 0, 0, texWidth, texHeight, 0, 0, texWidth, texHeight, GL_COLOR_BUFFER_BIT, GL_NEAREST ); // Unbind texture. glFramebufferTexture2DEXT(GL_DRAW_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_RECTANGLE_ARB, 0, 0); // Return to EFB. glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, FramebufferManager::GetEFBFramebuffer()); } }
void XFBSource::CopyEFB(float Gamma) { g_renderer->ResetAPIState(); // Copy EFB data to XFB and restore render target again glBindFramebuffer(GL_READ_FRAMEBUFFER, FramebufferManager::GetEFBFramebuffer()); glBindFramebuffer(GL_DRAW_FRAMEBUFFER, FramebufferManager::GetXFBFramebuffer()); // Bind texture. glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0); GL_REPORT_FBO_ERROR(); glBlitFramebuffer( 0, 0, texWidth, texHeight, 0, 0, texWidth, texHeight, GL_COLOR_BUFFER_BIT, GL_NEAREST ); // Return to EFB. FramebufferManager::SetFramebuffer(0); g_renderer->RestoreAPIState(); }
FramebufferManager::FramebufferManager(int targetWidth, int targetHeight, int msaaSamples, int msaaCoverageSamples) { m_efbFramebuffer = 0; m_efbColor = 0; m_efbDepth = 0; m_resolvedFramebuffer = 0; m_resolvedColorTexture = 0; m_resolvedDepthTexture = 0; m_xfbFramebuffer = 0; m_targetWidth = targetWidth; m_targetHeight = targetHeight; m_msaaSamples = msaaSamples; m_msaaCoverageSamples = msaaCoverageSamples; // The EFB can be set to different pixel formats by the game through the // BPMEM_ZCOMPARE register (which should probably have a different name). // They are: // - 24-bit RGB (8-bit components) with 24-bit Z // - 24-bit RGBA (6-bit components) with 24-bit Z // - Multisampled 16-bit RGB (5-6-5 format) with 16-bit Z // We only use one EFB format here: 32-bit ARGB with 24-bit Z. // Multisampling depends on user settings. // The distinction becomes important for certain operations, i.e. the // alpha channel should be ignored if the EFB does not have one. // Create EFB target. glGenFramebuffersEXT(1, &m_efbFramebuffer); if (m_msaaSamples <= 1) { // EFB targets will be textures in non-MSAA mode. GLuint glObj[2]; glGenTextures(2, glObj); m_efbColor = glObj[0]; m_efbDepth = glObj[1]; glBindTexture(GL_TEXTURE_RECTANGLE_ARB, m_efbColor); glTexImage2D(GL_TEXTURE_RECTANGLE_ARB, 0, GL_RGBA8, m_targetWidth, m_targetHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL); glBindTexture(GL_TEXTURE_RECTANGLE_ARB, m_efbDepth); glTexImage2D(GL_TEXTURE_RECTANGLE_ARB, 0, GL_DEPTH_COMPONENT24, m_targetWidth, m_targetHeight, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_BYTE, NULL); glBindTexture(GL_TEXTURE_RECTANGLE_ARB, 0); // Bind target textures to the EFB framebuffer. glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m_efbFramebuffer); glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_RECTANGLE_ARB, m_efbColor, 0); glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_TEXTURE_RECTANGLE_ARB, m_efbDepth, 0); GL_REPORT_FBO_ERROR(); } else { // EFB targets will be renderbuffers in MSAA mode (required by OpenGL). // Resolve targets will be created to transfer EFB to RAM textures. // XFB framebuffer will be created to transfer EFB to XFB texture. // Create EFB target renderbuffers. GLuint glObj[2]; glGenRenderbuffersEXT(2, glObj); m_efbColor = glObj[0]; m_efbDepth = glObj[1]; glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, m_efbColor); if (m_msaaCoverageSamples) glRenderbufferStorageMultisampleCoverageNV(GL_RENDERBUFFER_EXT, m_msaaCoverageSamples, m_msaaSamples, GL_RGBA8, m_targetWidth, m_targetHeight); else glRenderbufferStorageMultisampleEXT(GL_RENDERBUFFER_EXT, m_msaaSamples, GL_RGBA8, m_targetWidth, m_targetHeight); glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, m_efbDepth); if (m_msaaCoverageSamples) glRenderbufferStorageMultisampleCoverageNV(GL_RENDERBUFFER_EXT, m_msaaCoverageSamples, m_msaaSamples, GL_DEPTH_COMPONENT24, m_targetWidth, m_targetHeight); else glRenderbufferStorageMultisampleEXT(GL_RENDERBUFFER_EXT, m_msaaSamples, GL_DEPTH_COMPONENT24, m_targetWidth, m_targetHeight); glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, 0); // Bind target renderbuffers to EFB framebuffer. glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m_efbFramebuffer); glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_RENDERBUFFER_EXT, m_efbColor); glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_RENDERBUFFER_EXT, m_efbDepth); GL_REPORT_FBO_ERROR(); // Create resolved targets for transferring multisampled EFB to texture. glGenFramebuffersEXT(1, &m_resolvedFramebuffer); glGenTextures(2, glObj); m_resolvedColorTexture = glObj[0]; m_resolvedDepthTexture = glObj[1]; glBindTexture(GL_TEXTURE_RECTANGLE_ARB, m_resolvedColorTexture); glTexImage2D(GL_TEXTURE_RECTANGLE_ARB, 0, GL_RGBA8, m_targetWidth, m_targetHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL); glBindTexture(GL_TEXTURE_RECTANGLE_ARB, m_resolvedDepthTexture); glTexImage2D(GL_TEXTURE_RECTANGLE_ARB, 0, GL_DEPTH_COMPONENT24, m_targetWidth, m_targetHeight, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_BYTE, NULL); glBindTexture(GL_TEXTURE_RECTANGLE_ARB, 0); // Bind resolved textures to resolved framebuffer. glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m_resolvedFramebuffer); glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_RECTANGLE_ARB, m_resolvedColorTexture, 0); glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_TEXTURE_RECTANGLE_ARB, m_resolvedDepthTexture, 0); GL_REPORT_FBO_ERROR(); // Return to EFB framebuffer. glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m_efbFramebuffer); } // Create XFB framebuffer; targets will be created elsewhere. glGenFramebuffersEXT(1, &m_xfbFramebuffer); // EFB framebuffer is currently bound, make sure to clear its alpha value to 1.f glViewport(0, 0, m_targetWidth, m_targetHeight); glScissor(0, 0, m_targetWidth, m_targetHeight); glClearColor(0.f, 0.f, 0.f, 1.f); glClearDepth(1.0); glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT); }
FramebufferManager::FramebufferManager(int targetWidth, int targetHeight, int msaaSamples) { m_efbFramebuffer = 0; m_xfbFramebuffer = 0; m_efbColor = 0; m_efbDepth = 0; m_efbColorSwap = 0; m_resolvedFramebuffer = 0; m_resolvedColorTexture = 0; m_resolvedDepthTexture = 0; m_targetWidth = targetWidth; m_targetHeight = targetHeight; m_msaaSamples = msaaSamples; // The EFB can be set to different pixel formats by the game through the // BPMEM_ZCOMPARE register (which should probably have a different name). // They are: // - 24-bit RGB (8-bit components) with 24-bit Z // - 24-bit RGBA (6-bit components) with 24-bit Z // - Multisampled 16-bit RGB (5-6-5 format) with 16-bit Z // We only use one EFB format here: 32-bit ARGB with 24-bit Z. // Multisampling depends on user settings. // The distinction becomes important for certain operations, i.e. the // alpha channel should be ignored if the EFB does not have one. glActiveTexture(GL_TEXTURE0 + 9); GLuint glObj[3]; glGenTextures(3, glObj); m_efbColor = glObj[0]; m_efbDepth = glObj[1]; m_efbColorSwap = glObj[2]; // OpenGL MSAA textures are a different kind of texture type and must be allocated // with a different function, so we create them separately. if (m_msaaSamples <= 1) { m_textureType = GL_TEXTURE_2D; glBindTexture(m_textureType, m_efbColor); glTexParameteri(m_textureType, GL_TEXTURE_MAX_LEVEL, 0); glTexParameteri(m_textureType, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(m_textureType, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glTexImage2D(m_textureType, 0, GL_RGBA, m_targetWidth, m_targetHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr); glBindTexture(m_textureType, m_efbDepth); glTexParameteri(m_textureType, GL_TEXTURE_MAX_LEVEL, 0); glTexParameteri(m_textureType, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(m_textureType, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glTexImage2D(m_textureType, 0, GL_DEPTH_COMPONENT24, m_targetWidth, m_targetHeight, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_INT, nullptr); glBindTexture(m_textureType, m_efbColorSwap); glTexParameteri(m_textureType, GL_TEXTURE_MAX_LEVEL, 0); glTexParameteri(m_textureType, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(m_textureType, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glTexImage2D(m_textureType, 0, GL_RGBA, m_targetWidth, m_targetHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr); } else { m_textureType = GL_TEXTURE_2D_MULTISAMPLE; glBindTexture(m_textureType, m_efbColor); glTexImage2DMultisample(m_textureType, m_msaaSamples, GL_RGBA, m_targetWidth, m_targetHeight, false); glBindTexture(m_textureType, m_efbDepth); glTexImage2DMultisample(m_textureType, m_msaaSamples, GL_DEPTH_COMPONENT24, m_targetWidth, m_targetHeight, false); glBindTexture(m_textureType, m_efbColorSwap); glTexImage2DMultisample(m_textureType, m_msaaSamples, GL_RGBA, m_targetWidth, m_targetHeight, false); glBindTexture(m_textureType, 0); // Although we are able to access the multisampled texture directly, we don't do it everywhere. // The old way is to "resolve" this multisampled texture by copying it into a non-sampled texture. // This would lead to an unneeded copy of the EFB, so we are going to avoid it. // But as this job isn't done right now, we do need that texture for resolving: glGenTextures(2, glObj); m_resolvedColorTexture = glObj[0]; m_resolvedDepthTexture = glObj[1]; glBindTexture(GL_TEXTURE_2D, m_resolvedColorTexture); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, m_targetWidth, m_targetHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr); glBindTexture(GL_TEXTURE_2D, m_resolvedDepthTexture); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT24, m_targetWidth, m_targetHeight, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_INT, nullptr); // Bind resolved textures to resolved framebuffer. glGenFramebuffers(1, &m_resolvedFramebuffer); glBindFramebuffer(GL_FRAMEBUFFER, m_resolvedFramebuffer); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, m_resolvedColorTexture, 0); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, m_resolvedDepthTexture, 0); GL_REPORT_FBO_ERROR(); } // Create XFB framebuffer; targets will be created elsewhere. glGenFramebuffers(1, &m_xfbFramebuffer); // Bind target textures to EFB framebuffer. glGenFramebuffers(1, &m_efbFramebuffer); glBindFramebuffer(GL_FRAMEBUFFER, m_efbFramebuffer); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, m_textureType, m_efbColor, 0); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, m_textureType, m_efbDepth, 0); GL_REPORT_FBO_ERROR(); // EFB framebuffer is currently bound, make sure to clear its alpha value to 1.f glViewport(0, 0, m_targetWidth, m_targetHeight); glScissor(0, 0, m_targetWidth, m_targetHeight); glClearColor(0.f, 0.f, 0.f, 1.f); glClearDepthf(1.0f); glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT); // reinterpret pixel format char vs[] = "void main(void) {\n" " vec2 rawpos = vec2(gl_VertexID&1, gl_VertexID&2);\n" " gl_Position = vec4(rawpos*2.0-1.0, 0.0, 1.0);\n" "}\n"; // The way to sample the EFB is based on the on the current configuration. // As we use the same sampling way for both interpreting shaders, the sampling // shader are generated first: std::string sampler; if (m_msaaSamples <= 1) { // non-msaa, so just fetch the pixel sampler = "SAMPLER_BINDING(9) uniform sampler2D samp9;\n" "vec4 sampleEFB(ivec2 pos) {\n" " return texelFetch(samp9, pos, 0);\n" "}\n"; } else if (g_ogl_config.bSupportSampleShading) { // msaa + sample shading available, so just fetch the sample // This will lead to sample shading, but it's the only way to not loose // the values of each sample. sampler = "SAMPLER_BINDING(9) uniform sampler2DMS samp9;\n" "vec4 sampleEFB(ivec2 pos) {\n" " return texelFetch(samp9, pos, gl_SampleID);\n" "}\n"; } else { // msaa without sample shading: calculate the mean value of the pixel std::stringstream samples; samples << m_msaaSamples; sampler = "SAMPLER_BINDING(9) uniform sampler2DMS samp9;\n" "vec4 sampleEFB(ivec2 pos) {\n" " vec4 color = vec4(0.0, 0.0, 0.0, 0.0);\n" " for(int i=0; i<" + samples.str() + "; i++)\n" " color += texelFetch(samp9, pos, i);\n" " return color / " + samples.str() + ";\n" "}\n"; } std::string ps_rgba6_to_rgb8 = sampler + "out vec4 ocol0;\n" "void main()\n" "{\n" " ivec4 src6 = ivec4(round(sampleEFB(ivec2(gl_FragCoord.xy)) * 63.f));\n" " ivec4 dst8;\n" " dst8.r = (src6.r << 2) | (src6.g >> 4);\n" " dst8.g = ((src6.g & 0xF) << 4) | (src6.b >> 2);\n" " dst8.b = ((src6.b & 0x3) << 6) | src6.a;\n" " dst8.a = 255;\n" " ocol0 = float4(dst8) / 255.f;\n" "}"; std::string ps_rgb8_to_rgba6 = sampler + "out vec4 ocol0;\n" "void main()\n" "{\n" " ivec4 src8 = ivec4(round(sampleEFB(ivec2(gl_FragCoord.xy)) * 255.f));\n" " ivec4 dst6;\n" " dst6.r = src8.r >> 2;\n" " dst6.g = ((src8.r & 0x3) << 4) | (src8.g >> 4);\n" " dst6.b = ((src8.g & 0xF) << 2) | (src8.b >> 6);\n" " dst6.a = src8.b & 0x3F;\n" " ocol0 = float4(dst6) / 63.f;\n" "}"; ProgramShaderCache::CompileShader(m_pixel_format_shaders[0], vs, ps_rgb8_to_rgba6.c_str()); ProgramShaderCache::CompileShader(m_pixel_format_shaders[1], vs, ps_rgba6_to_rgb8.c_str()); }
// Should be scale free. void DecodeToTexture(u32 xfbAddr, int srcWidth, int srcHeight, GLuint destTexture) { u8* srcAddr = Memory::GetPointer(xfbAddr); if (!srcAddr) { WARN_LOG(VIDEO, "Tried to decode from invalid memory address"); return; } int srcFmtWidth = srcWidth / 2; g_renderer->ResetAPIState(); // reset any game specific settings // switch to texture converter frame buffer // attach destTexture as color destination FramebufferManager::SetFramebuffer(s_texConvFrameBuffer); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, destTexture, 0); GL_REPORT_FBO_ERROR(); // activate source texture // set srcAddr as data for source texture glActiveTexture(GL_TEXTURE0+9); glBindTexture(getFbType(), s_srcTexture); // TODO: make this less slow. (How?) if ((GLsizei)s_srcTextureWidth == (GLsizei)srcFmtWidth && (GLsizei)s_srcTextureHeight == (GLsizei)srcHeight) { glTexSubImage2D(getFbType(), 0,0,0,s_srcTextureWidth, s_srcTextureHeight, GL_BGRA, GL_UNSIGNED_BYTE, srcAddr); } else { glTexImage2D(getFbType(), 0, GL_RGBA8, (GLsizei)srcFmtWidth, (GLsizei)srcHeight, 0, GL_BGRA, GL_UNSIGNED_BYTE, srcAddr); s_srcTextureWidth = (GLsizei)srcFmtWidth; s_srcTextureHeight = (GLsizei)srcHeight; } glViewport(0, 0, srcWidth, srcHeight); s_yuyvToRgbProgram.Bind(); GL_REPORT_ERRORD(); if(s_cached_srcHeight != srcHeight || s_cached_srcWidth != srcWidth) { GLfloat vertices[] = { 1.f, -1.f, (float)srcFmtWidth, (float)srcHeight, 1.f, 1.f, (float)srcFmtWidth, 0.f, -1.f, -1.f, 0.f, (float)srcHeight, -1.f, 1.f, 0.f, 0.f }; glBindBuffer(GL_ARRAY_BUFFER, s_decode_VBO ); glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat)*4*4, vertices, GL_STREAM_DRAW); s_cached_srcHeight = srcHeight; s_cached_srcWidth = srcWidth; } glBindVertexArray( s_decode_VAO ); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); GL_REPORT_ERRORD(); VertexShaderManager::SetViewportChanged(); FramebufferManager::SetFramebuffer(0); g_renderer->RestoreAPIState(); GL_REPORT_ERRORD(); }