#include "global.h" #include "RageDisplay_OGL.h" #include "RageDisplay_OGL_Helpers.h" using namespace RageDisplay_Legacy_Helpers; #include "RageFile.h" #include "RageSurface.h" #include "RageSurfaceUtils.h" #include "RageUtil.h" #include "RageLog.h" #include "RageTextureManager.h" #include "RageMath.h" #include "RageTypes.h" #include "RageUtil.h" #include "EnumHelper.h" #include "DisplaySpec.h" #include "LocalizedString.h" #include "arch/LowLevelWindow/LowLevelWindow.h" #include #include #include #if defined(WINDOWS) #include #endif #if defined(_MSC_VER) #pragma comment(lib, "opengl32.lib") #pragma comment(lib, "glu32.lib") #endif #ifdef NO_GL_FLUSH #define glFlush() #endif // // Globals // static bool g_bReversePackedPixelsWorks = true; static bool g_bColorIndexTableWorks = true; /* OpenGL system information that generally doesn't change at runtime. */ /* Range and granularity of points and lines: */ static float g_line_range[2]; static float g_point_range[2]; /* OpenGL version * 10: */ static int g_glVersion; /* GLU version * 10: */ static int g_gluVersion; static int g_iMaxTextureUnits = 0; /* We don't actually use normals (we don't turn on lighting), there's just * no GL_T2F_C4F_V3F. */ static const GLenum RageSpriteVertexFormat = GL_T2F_C4F_N3F_V3F; /* If we support texture matrix scaling, a handle to the vertex program: */ static GLhandleARB g_bTextureMatrixShader = 0; static std::map g_mapRenderTargets; static RenderTarget *g_pCurrentRenderTarget = nullptr; static LowLevelWindow *g_pWind; static bool g_bInvertY = false; static void InvalidateObjects(); static RageDisplay::RagePixelFormatDesc PIXEL_FORMAT_DESC[NUM_RagePixelFormat] = { { /* R8G8B8A8 */ 32, { 0xFF000000, 0x00FF0000, 0x0000FF00, 0x000000FF } }, { /* B8G8R8A8 */ 32, { 0x0000FF00, 0x00FF0000, 0xFF000000, 0x000000FF } }, { /* R4G4B4A4 */ 16, { 0xF000, 0x0F00, 0x00F0, 0x000F }, }, { /* R5G5B5A1 */ 16, { 0xF800, 0x07C0, 0x003E, 0x0001 }, }, { /* R5G5B5X1 */ 16, { 0xF800, 0x07C0, 0x003E, 0x0000 }, }, { /* R8G8B8 */ 24, { 0xFF0000, 0x00FF00, 0x0000FF, 0x000000 } }, { /* Paletted */ 8, { 0,0,0,0 } /* N/A */ }, { /* B8G8R8 */ 24, { 0x0000FF, 0x00FF00, 0xFF0000, 0x000000 } }, { /* A1R5G5B5 */ 16, { 0x7C00, 0x03E0, 0x001F, 0x8000 }, }, { /* X1R5G5B5 */ 16, { 0x7C00, 0x03E0, 0x001F, 0x0000 }, } }; /* g_GLPixFmtInfo is used for both texture formats and surface formats. For example, * it's fine to ask for a RagePixelFormat_RGB5 texture, but to supply a surface matching * RagePixelFormat_RGB8. OpenGL will simply discard the extra bits. * * It's possible for a format to be supported as a texture format but not as a * surface format. For example, if packed pixels aren't supported, we can still * use GL_RGB5_A1, but we'll have to convert to a supported surface pixel format * first. It's not ideal, since we'll convert to RGBA8 and OGL will convert back, * but it works fine. */ struct GLPixFmtInfo_t { GLenum internalfmt; /* target format */ GLenum format; /* target format */ GLenum type; /* data format */ } const g_GLPixFmtInfo[NUM_RagePixelFormat] = { { /* R8G8B8A8 */ GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE, }, { /* R8G8B8A8 */ GL_RGBA8, GL_BGRA, GL_UNSIGNED_BYTE, }, { /* B4G4R4A4 */ GL_RGBA4, GL_RGBA, GL_UNSIGNED_SHORT_4_4_4_4, }, { /* B5G5R5A1 */ GL_RGB5_A1, GL_RGBA, GL_UNSIGNED_SHORT_5_5_5_1, }, { /* B5G5R5 */ GL_RGB5, GL_RGBA, GL_UNSIGNED_SHORT_5_5_5_1, }, { /* B8G8R8 */ GL_RGB8, GL_RGB, GL_UNSIGNED_BYTE, }, { /* Paletted */ GL_COLOR_INDEX8_EXT, GL_COLOR_INDEX, GL_UNSIGNED_BYTE, }, { /* B8G8R8 */ GL_RGB8, GL_BGR, GL_UNSIGNED_BYTE, }, { /* A1R5G5B5 (matches D3DFMT_A1R5G5B5) */ GL_RGB5_A1, GL_BGRA, GL_UNSIGNED_SHORT_1_5_5_5_REV, }, { /* X1R5G5B5 */ GL_RGB5, GL_BGRA, GL_UNSIGNED_SHORT_1_5_5_5_REV, } }; static void FixLittleEndian() { if constexpr (!Endian::little) { return; } static bool bInitialized = false; if (bInitialized) return; bInitialized = true; for( int i = 0; i < NUM_RagePixelFormat; ++i ) { RageDisplay::RagePixelFormatDesc &pf = PIXEL_FORMAT_DESC[i]; /* OpenGL and RageSurface handle byte formats differently; we need * to flip non-paletted masks to make them line up. */ if (g_GLPixFmtInfo[i].type != GL_UNSIGNED_BYTE || pf.bpp == 8) continue; for( int mask = 0; mask < 4; ++mask) { int m = pf.masks[mask]; switch( pf.bpp ) { case 24: m = Swap24(m); break; case 32: m = Swap32(m); break; default: FAIL_M(ssprintf("Unsupported BPP value: %i", pf.bpp)); } pf.masks[mask] = m; } } } static void TurnOffHardwareVBO() { if (GLEW_ARB_vertex_buffer_object) { glBindBufferARB(GL_ARRAY_BUFFER_ARB, 0); glBindBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB, 0); } } RageDisplay_Legacy::RageDisplay_Legacy() { LOG->Trace( "RageDisplay_Legacy::RageDisplay_Legacy()" ); LOG->MapLog("renderer", "Current renderer: OpenGL"); FixLittleEndian(); RageDisplay_Legacy_Helpers::Init(); g_pWind = nullptr; g_bTextureMatrixShader = 0; offscreenRenderTarget = nullptr; } RString GetInfoLog( GLhandleARB h ) { GLint iLength; glGetObjectParameterivARB( h, GL_OBJECT_INFO_LOG_LENGTH_ARB, &iLength ); if (!iLength) return RString(); GLcharARB *pInfoLog = new GLcharARB[iLength]; glGetInfoLogARB( h, iLength, &iLength, pInfoLog ); RString sRet = pInfoLog; delete [] pInfoLog; TrimRight( sRet ); return sRet; } GLhandleARB CompileShader( GLenum ShaderType, RString sFile, std::vector asDefines ) { /* XXX: This would not be necessary if it wasn't for the special case for Cel. */ if (ShaderType == GL_FRAGMENT_SHADER_ARB && !glewIsSupported("GL_VERSION_2_0")) { LOG->Warn("Fragment shaders not supported by driver. Some effects will not be available."); return 0; } RString sBuffer; { RageFile file; if (!file.Open(sFile)) { LOG->Warn( "Error compiling shader %s: %s", sFile.c_str(), file.GetError().c_str() ); return 0; } if (file.Read(sBuffer, file.GetFileSize()) == -1) { LOG->Warn( "Error compiling shader %s: %s", sFile.c_str(), file.GetError().c_str() ); return 0; } } LOG->Trace( "Compiling shader %s", sFile.c_str() ); GLhandleARB hShader = glCreateShaderObjectARB( ShaderType ); std::vector apData; std::vector aiLength; for (RString &s : asDefines) { s = ssprintf( "#define %s\n", s.c_str() ); apData.push_back( s.data() ); aiLength.push_back( s.size() ); } apData.push_back( "#line 1\n" ); aiLength.push_back( 8 ); apData.push_back( sBuffer.data() ); aiLength.push_back( sBuffer.size() ); glShaderSourceARB( hShader, apData.size(), &apData[0], &aiLength[0] ); glCompileShaderARB( hShader ); RString sInfo = GetInfoLog( hShader ); GLint bCompileStatus = GL_FALSE; glGetObjectParameterivARB( hShader, GL_OBJECT_COMPILE_STATUS_ARB, &bCompileStatus ); if (!bCompileStatus) { LOG->Warn( "Error compiling shader %s:\n%s", sFile.c_str(), sInfo.c_str() ); glDeleteObjectARB( hShader ); return 0; } if (!sInfo.empty()) LOG->Trace( "Messages compiling shader %s:\n%s", sFile.c_str(), sInfo.c_str() ); return hShader; } GLhandleARB LoadShader( GLenum ShaderType, RString sFile, std::vector asDefines ) { /* Vertex shaders are supported by more hardware than fragment shaders. * If this causes any trouble I will have to up the requirement for both * of them to at least GL 2.0. Regardless we need basic GLSL support. * -Colby */ if (!glewIsSupported("GL_ARB_shading_language_100 GL_ARB_shader_objects") || (ShaderType == GL_FRAGMENT_SHADER_ARB && !glewIsSupported("GL_VERSION_2_0")) || (ShaderType == GL_VERTEX_SHADER_ARB && !glewIsSupported("GL_ARB_vertex_shader"))) { LOG->Warn("%s shaders not supported by driver. Some effects will not be available.", (ShaderType == GL_FRAGMENT_SHADER_ARB) ? "Fragment" : "Vertex"); return 0; } // XXX: dumb, but I don't feel like refactoring ragedisplay for this. -Colby GLhandleARB secondaryShader = 0; if (sFile == "Data/Shaders/GLSL/Cel.vert") secondaryShader = CompileShader( GL_FRAGMENT_SHADER_ARB, "Data/Shaders/GLSL/Cel.frag", asDefines); else if (sFile == "Data/Shaders/GLSL/Shell.vert") secondaryShader = CompileShader( GL_FRAGMENT_SHADER_ARB, "Data/Shaders/GLSL/Shell.frag", asDefines); else if (sFile == "Data/Shaders/GLSL/Distance field.vert") secondaryShader = CompileShader( GL_FRAGMENT_SHADER_ARB, "Data/Shaders/GLSL/Distance field.frag", asDefines); GLhandleARB hShader = CompileShader( ShaderType, sFile, asDefines ); if (hShader == 0) return 0; GLhandleARB hProgram = glCreateProgramObjectARB(); glAttachObjectARB( hProgram, hShader ); if (secondaryShader) { glAttachObjectARB( hProgram, secondaryShader ); glDeleteObjectARB( secondaryShader ); } glDeleteObjectARB( hShader ); // Link the program. glLinkProgramARB( hProgram ); GLint bLinkStatus = false; glGetObjectParameterivARB( hProgram, GL_OBJECT_LINK_STATUS_ARB, &bLinkStatus ); if (!bLinkStatus) { LOG->Warn( "Error linking shader %s: %s", sFile.c_str(), GetInfoLog(hProgram).c_str() ); glDeleteObjectARB( hProgram ); return 0; } return hProgram; } static int g_iAttribTextureMatrixScale; static GLhandleARB g_bUnpremultiplyShader = 0; static GLhandleARB g_bColorBurnShader = 0; static GLhandleARB g_bColorDodgeShader = 0; static GLhandleARB g_bVividLightShader = 0; static GLhandleARB g_hHardMixShader = 0; static GLhandleARB g_hOverlayShader = 0; static GLhandleARB g_hScreenShader = 0; static GLhandleARB g_hYUYV422Shader = 0; static GLhandleARB g_gShellShader = 0; static GLhandleARB g_gCelShader = 0; static GLhandleARB g_gDistanceFieldShader = 0; void InitShaders() { // xxx: replace this with a ShaderManager or something that reads in // the shaders and determines shader type by file extension. -aj // argh shaders in stepmania are painful -colby std::vector asDefines; // used for scrolling textures (I think) g_bTextureMatrixShader = LoadShader( GL_VERTEX_SHADER_ARB, "Data/Shaders/GLSL/Texture matrix scaling.vert", asDefines ); // these two are for dancing characters and are both actually shader pairs g_gShellShader = LoadShader( GL_VERTEX_SHADER_ARB, "Data/Shaders/GLSL/Shell.vert", asDefines ); g_gCelShader = LoadShader( GL_VERTEX_SHADER_ARB, "Data/Shaders/GLSL/Cel.vert", asDefines ); g_gDistanceFieldShader = LoadShader( GL_VERTEX_SHADER_ARB, "Data/Shaders/GLSL/Distance field.vert", asDefines ); // effects g_bUnpremultiplyShader = LoadShader( GL_FRAGMENT_SHADER_ARB, "Data/Shaders/GLSL/Unpremultiply.frag", asDefines ); g_bColorBurnShader = LoadShader( GL_FRAGMENT_SHADER_ARB, "Data/Shaders/GLSL/Color burn.frag", asDefines ); g_bColorDodgeShader = LoadShader( GL_FRAGMENT_SHADER_ARB, "Data/Shaders/GLSL/Color dodge.frag", asDefines ); g_bVividLightShader = LoadShader( GL_FRAGMENT_SHADER_ARB, "Data/Shaders/GLSL/Vivid light.frag", asDefines ); g_hHardMixShader = LoadShader( GL_FRAGMENT_SHADER_ARB, "Data/Shaders/GLSL/Hard mix.frag", asDefines ); g_hOverlayShader = LoadShader( GL_FRAGMENT_SHADER_ARB, "Data/Shaders/GLSL/Overlay.frag", asDefines ); g_hScreenShader = LoadShader( GL_FRAGMENT_SHADER_ARB, "Data/Shaders/GLSL/Screen.frag", asDefines ); g_hYUYV422Shader = LoadShader( GL_FRAGMENT_SHADER_ARB, "Data/Shaders/GLSL/YUYV422.frag", asDefines ); // Bind attributes. if (g_bTextureMatrixShader) { FlushGLErrors(); g_iAttribTextureMatrixScale = glGetAttribLocationARB( g_bTextureMatrixShader, "TextureMatrixScale" ); if (g_iAttribTextureMatrixScale == -1) { LOG->Trace( "Scaling shader link failed: couldn't bind attribute \"TextureMatrixScale\"" ); glDeleteObjectARB( g_bTextureMatrixShader ); g_bTextureMatrixShader = 0; } else { AssertNoGLError(); /* Older Catalyst drivers seem to throw GL_INVALID_OPERATION here. */ glVertexAttrib2fARB( g_iAttribTextureMatrixScale, 1, 1 ); GLenum iError = glGetError(); if (iError == GL_INVALID_OPERATION) { LOG->Trace( "Scaling shader failed: glVertexAttrib2fARB returned GL_INVALID_OPERATION" ); glDeleteObjectARB( g_bTextureMatrixShader ); g_bTextureMatrixShader = 0; } else { ASSERT_M( iError == GL_NO_ERROR, GLToString(iError) ); } } } } static LocalizedString OBTAIN_AN_UPDATED_VIDEO_DRIVER ( "RageDisplay_Legacy", "Obtain an updated driver from your video card manufacturer." ); static LocalizedString GLDIRECT_IS_NOT_COMPATIBLE ( "RageDisplay_Legacy", "GLDirect was detected. GLDirect is not compatible with this game and should be disabled." ); RString RageDisplay_Legacy::Init( const VideoModeParams &p, bool bAllowUnacceleratedRenderer ) { g_pWind = LowLevelWindow::Create(); bool bIgnore = false; RString sError = SetVideoMode( p, bIgnore ); if (sError != "") return sError; // Log driver details g_pWind->LogDebugInformation(); LOG->Info( "OGL Vendor: %s", glGetString(GL_VENDOR) ); LOG->Info( "OGL Renderer: %s", glGetString(GL_RENDERER) ); LOG->Info( "OGL Version: %s", glGetString(GL_VERSION) ); LOG->Info( "OGL Max texture size: %i", GetMaxTextureSize() ); LOG->Info( "OGL Texture units: %i", g_iMaxTextureUnits ); LOG->Info( "GLU Version: %s", gluGetString(GLU_VERSION) ); /* Pretty-print the extension string: */ LOG->Info( "OGL Extensions:" ); { const char *szExtensionString = (const char *) glGetString(GL_EXTENSIONS); std::vector asExtensions; split( szExtensionString, " ", asExtensions ); sort( asExtensions.begin(), asExtensions.end() ); std::size_t iNextToPrint = 0; while( iNextToPrint < asExtensions.size() ) { std::size_t iLastToPrint = iNextToPrint; RString sType; for( std::size_t i = iNextToPrint; i asBits; split( asExtensions[i], "_", asBits ); RString sThisType; if (asBits.size() > 2) sThisType = join( "_", asBits.begin(), asBits.begin()+2 ); if (i > iNextToPrint && sThisType != sType) break; sType = sThisType; iLastToPrint = i; } if (iNextToPrint == iLastToPrint) { LOG->Info( " %s", asExtensions[iNextToPrint].c_str() ); ++iNextToPrint; continue; } RString sList = ssprintf( " %s: ", sType.c_str() ); while( iNextToPrint <= iLastToPrint ) { std::vector asBits; split( asExtensions[iNextToPrint], "_", asBits ); RString sShortExt = join( "_", asBits.begin()+2, asBits.end() ); sList += sShortExt; if (iNextToPrint < iLastToPrint) sList += ", "; if (iNextToPrint == iLastToPrint || sList.size() + asExtensions[iNextToPrint+1].size() > 120) { LOG->Info( "%s", sList.c_str() ); sList = " "; } ++iNextToPrint; } } } if (g_pWind->IsSoftwareRenderer(sError)) { if (!bAllowUnacceleratedRenderer) return sError + " " + OBTAIN_AN_UPDATED_VIDEO_DRIVER.GetValue() + "\n\n"; LOG->Warn( "Low-performance OpenGL renderer: %s", sError.c_str() ); } #if defined(_WINDOWS) /* GLDirect is a Direct3D wrapper for OpenGL. It's rather buggy; and if in * any case GLDirect can successfully render us, we should be able to do so * too using Direct3D directly. (If we can't, it's a bug that we can work * around--if GLDirect can do it, so can we!) */ if (!strncmp( (const char *) glGetString(GL_RENDERER), "GLDirect", 8 )) return GLDIRECT_IS_NOT_COMPATIBLE.GetValue() + "\n"; #endif /* Log this, so if people complain that the radar looks bad on their * system we can compare them: */ glGetFloatv( GL_LINE_WIDTH_RANGE, g_line_range ); glGetFloatv( GL_POINT_SIZE_RANGE, g_point_range ); return RString(); } RageDisplay_Legacy::~RageDisplay_Legacy() { delete g_pWind; } void RageDisplay_Legacy::GetDisplaySpecs(DisplaySpecs &out) const { out.clear(); g_pWind->GetDisplaySpecs(out); } static void CheckPalettedTextures() { RString sError; do { if (!GLEW_EXT_paletted_texture) { sError = "GL_EXT_paletted_texture missing"; break; } /* Check to see if paletted textures really work. */ GLenum glTexFormat = g_GLPixFmtInfo[RagePixelFormat_PAL].internalfmt; GLenum glImageFormat = g_GLPixFmtInfo[RagePixelFormat_PAL].format; GLenum glImageType = g_GLPixFmtInfo[RagePixelFormat_PAL].type; int iBits = 8; FlushGLErrors(); #define GL_CHECK_ERROR(f) \ { \ GLenum glError = glGetError(); \ if (glError != GL_NO_ERROR) { \ sError = ssprintf(f " failed (%s)", GLToString(glError).c_str() ); \ break; \ } \ } glTexImage2D( GL_PROXY_TEXTURE_2D, 0, glTexFormat, 16, 16, 0, glImageFormat, glImageType, nullptr ); GL_CHECK_ERROR( "glTexImage2D" ); GLuint iFormat = 0; glGetTexLevelParameteriv( GL_PROXY_TEXTURE_2D, 0, GLenum(GL_TEXTURE_INTERNAL_FORMAT), (GLint *) &iFormat ); GL_CHECK_ERROR( "glGetTexLevelParameteriv(GL_TEXTURE_INTERNAL_FORMAT)" ); if (iFormat != glTexFormat) { sError = ssprintf( "Expected format %s, got %s instead", GLToString(glTexFormat).c_str(), GLToString(iFormat).c_str() ); break; } GLubyte palette[256*4]; memset(palette, 0, sizeof(palette)); glColorTableEXT(GL_PROXY_TEXTURE_2D, GL_RGBA8, 256, GL_RGBA, GL_UNSIGNED_BYTE, palette); GL_CHECK_ERROR( "glColorTableEXT" ); GLint iSize = 0; glGetTexLevelParameteriv( GL_PROXY_TEXTURE_2D, 0, GLenum(GL_TEXTURE_INDEX_SIZE_EXT), &iSize ); GL_CHECK_ERROR( "glGetTexLevelParameteriv(GL_TEXTURE_INDEX_SIZE_EXT)" ); if (iBits > iSize || iSize > 8) { sError = ssprintf( "Expected %i-bit palette, got a %i-bit one instead", iBits, int(iSize) ); break; } GLint iRealWidth = 0; glGetColorTableParameterivEXT( GL_PROXY_TEXTURE_2D, GL_COLOR_TABLE_WIDTH, &iRealWidth ); GL_CHECK_ERROR( "glGetColorTableParameterivEXT(GL_COLOR_TABLE_WIDTH)" ); if (iRealWidth != 1 << iBits) { sError = ssprintf( "GL_COLOR_TABLE_WIDTH returned %i instead of %i", int(iRealWidth), 1 << iBits ); break; } GLint iRealFormat = 0; glGetColorTableParameterivEXT( GL_PROXY_TEXTURE_2D, GL_COLOR_TABLE_FORMAT, &iRealFormat ); GL_CHECK_ERROR( "glGetColorTableParameterivEXT(GL_COLOR_TABLE_FORMAT)" ); if (iRealFormat != GL_RGBA8) { sError = ssprintf( "GL_COLOR_TABLE_FORMAT returned %s instead of GL_RGBA8", GLToString(iRealFormat).c_str() ); break; } } while(0); #undef GL_CHECK_ERROR if (sError == "") return; /* If 8-bit palettes don't work, disable them entirely--don't trust 4-bit * palettes if it can't even get 8-bit ones right. */ glColorTableEXT = nullptr; glGetColorTableParameterivEXT = nullptr; LOG->Info( "Paletted textures disabled: %s.", sError.c_str() ); } static void CheckReversePackedPixels() { /* Try to create a texture. */ FlushGLErrors(); glTexImage2D( GL_PROXY_TEXTURE_2D, 0, GL_RGBA, 16, 16, 0, GL_BGRA, GL_UNSIGNED_SHORT_1_5_5_5_REV, nullptr ); const GLenum glError = glGetError(); if (glError == GL_NO_ERROR) { g_bReversePackedPixelsWorks = true; } else { g_bReversePackedPixelsWorks = false; LOG->Info( "GL_UNSIGNED_SHORT_1_5_5_5_REV failed (%s), disabled", GLToString(glError).c_str() ); } } void SetupExtensions() { const float fGLVersion = StringToFloat( (const char *) glGetString(GL_VERSION) ); g_glVersion = std::lrint( fGLVersion * 10 ); const float fGLUVersion = StringToFloat( (const char *) gluGetString(GLU_VERSION) ); g_gluVersion = std::lrint( fGLUVersion * 10 ); #ifndef HAVE_X11 // LLW_X11 needs to init GLEW early for GLX exts glewInit(); #endif g_iMaxTextureUnits = 1; if (GLEW_ARB_multitexture) glGetIntegerv( GL_MAX_TEXTURE_UNITS_ARB, (GLint *) &g_iMaxTextureUnits ); CheckPalettedTextures(); CheckReversePackedPixels(); { GLint iMaxTableSize = 0; glGetIntegerv( GL_MAX_PIXEL_MAP_TABLE, &iMaxTableSize ); if (iMaxTableSize < 256) { /* The minimum GL_MAX_PIXEL_MAP_TABLE is 32; if it's not at least 256, * we can't fit a palette in it, so we can't send paletted data as input * for a non-paletted texture. */ LOG->Info( "GL_MAX_PIXEL_MAP_TABLE is only %d", int(iMaxTableSize) ); g_bColorIndexTableWorks = false; } else { g_bColorIndexTableWorks = true; } } } bool RageDisplay_Legacy::UseOffscreenRenderTarget() { if ( !GetActualVideoModeParams().renderOffscreen || !TEXTUREMAN ) { return false; } if ( !offscreenRenderTarget ) { RenderTargetParam param; param.bWithDepthBuffer = true; param.bWithAlpha = true; param.bFloat = false; param.iWidth = GetActualVideoModeParams().width; param.iHeight = GetActualVideoModeParams().height; RageTextureID id( ssprintf( "FullscreenTexture%dx%d", param.iWidth, param.iHeight ) ); // See if we have this texture loaded already // (not GC'd yet). If it exists and we try to recreate // it, we'll get an error if ( TEXTUREMAN->IsTextureRegistered( id ) ) { offscreenRenderTarget = static_cast( TEXTUREMAN->LoadTexture( id ) ); } else { offscreenRenderTarget = new RageTextureRenderTarget( id, param ); TEXTUREMAN->RegisterTexture( id, offscreenRenderTarget ); } } return true; } void RageDisplay_Legacy::ResolutionChanged() { //LOG->Warn( "RageDisplay_Legacy::ResolutionChanged" ); /* Clear any junk that's in the framebuffer. */ if (BeginFrame()) EndFrame(); RageDisplay::ResolutionChanged(); if (offscreenRenderTarget && TEXTUREMAN) { TEXTUREMAN->UnloadTexture( offscreenRenderTarget ); offscreenRenderTarget = nullptr; } } // Return true if mode change was successful. // bNewDeviceOut is set true if a new device was created and textures // need to be reloaded. RString RageDisplay_Legacy::TryVideoMode( const VideoModeParams &p, bool &bNewDeviceOut ) { //LOG->Warn( "RageDisplay_Legacy::TryVideoMode( %d, %d, %d, %d, %d, %d )", p.windowed, p.width, p.height, p.bpp, p.rate, p.vsync ); RString err; err = g_pWind->TryVideoMode( p, bNewDeviceOut ); if (err != "") return err; // failed to set video mode /* Now that we've initialized, we can search for extensions. Do this before InvalidateObjects, * since AllocateBuffers needs it. */ SetupExtensions(); if (bNewDeviceOut) { /* We have a new OpenGL context, so we have to tell our textures that * their OpenGL texture number is invalid. */ if (TEXTUREMAN) TEXTUREMAN->InvalidateTextures(); /* Delete all render targets. They may have associated resources other than * the texture itself. */ for (std::pair &rt : g_mapRenderTargets) delete rt.second; g_mapRenderTargets.clear(); /* Recreate all vertex buffers. */ InvalidateObjects(); InitShaders(); } // I'm not sure this is correct -Colby #if defined(WINDOWS) /* Set vsync the Windows way, if we can. (What other extensions are there * to do this, for other archs?) */ if( wglewIsSupported("WGL_EXT_swap_control") ) wglSwapIntervalEXT(p.vsync); else return RString("The WGL_EXT_swap_control extension is not supported on your computer."); #endif ResolutionChanged(); return RString(); // successfully set mode } int RageDisplay_Legacy::GetMaxTextureSize() const { GLint size; glGetIntegerv( GL_MAX_TEXTURE_SIZE, &size ); return size; } bool RageDisplay_Legacy::BeginFrame() { /* We do this in here, rather than ResolutionChanged, or we won't update the * viewport for the concurrent rendering context. */ int fWidth = g_pWind->GetActualVideoModeParams().windowWidth; int fHeight = g_pWind->GetActualVideoModeParams().windowHeight; glViewport( 0, 0, fWidth, fHeight ); glClearColor( 0,0,0,0 ); SetZWrite( true ); glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT ); bool beginFrame = RageDisplay::BeginFrame(); if (beginFrame && UseOffscreenRenderTarget()) { offscreenRenderTarget->BeginRenderingTo( false ); } return beginFrame; } void RageDisplay_Legacy::EndFrame() { if (UseOffscreenRenderTarget()) { offscreenRenderTarget->FinishRenderingTo(); Sprite fullscreenSprite; // We've got a hold of this, don't want sprite deleting it when // it's deleted offscreenRenderTarget->m_iRefCount++; fullscreenSprite.SetTexture(offscreenRenderTarget); fullscreenSprite.SetHorizAlign(align_left); fullscreenSprite.SetVertAlign(align_top); CameraPushMatrix(); LoadMenuPerspective( 0, GetActualVideoModeParams().width, GetActualVideoModeParams().height, static_cast (GetActualVideoModeParams().width) / 2.f, static_cast (GetActualVideoModeParams().height) / 2.f ); fullscreenSprite.Draw(); CameraPopMatrix(); } FrameLimitBeforeVsync( g_pWind->GetActualVideoModeParams().rate ); g_pWind->SwapBuffers(); FrameLimitAfterVsync(); // Some would advise against glFinish(), ever. Those people don't realize // the degree of freedom GL hosts are permitted in queueing commands. // If left to its own devices, the host could lag behind several frames' worth // of commands. // glFlush() only forces the host to not wait to execute all commands // sent so far; it does NOT block on those commands until they finish. // glFinish() blocks. We WANT to block. Why? This puts the engine state // reflected by the next frame as close as possible to the on-screen // appearance of that frame. glFinish(); g_pWind->Update(); RageDisplay::EndFrame(); } RageSurface* RageDisplay_Legacy::CreateScreenshot() { int width = g_pWind->GetActualVideoModeParams().width; int height = g_pWind->GetActualVideoModeParams().height; RageSurface *image = nullptr; if (offscreenRenderTarget) { RageSurface *raw = GetTexture(offscreenRenderTarget->GetTexHandle()); image = CreateSurface( offscreenRenderTarget->GetImageWidth(), offscreenRenderTarget->GetImageHeight(), raw->fmt.BitsPerPixel, raw->fmt.Rmask, raw->fmt.Gmask, raw->fmt.Bmask, raw->fmt.Amask ); RageSurfaceUtils::Blit(raw, image); delete raw; } else { const RagePixelFormatDesc &desc = PIXEL_FORMAT_DESC[RagePixelFormat_RGBA8]; image = CreateSurface( width, height, desc.bpp, desc.masks[0], desc.masks[1], desc.masks[2], 0 ); DebugFlushGLErrors(); //TODO: revisit for MacOS, where backbuffer size can be less than window size glReadBuffer( GL_FRONT ); DebugAssertNoGLError(); glReadPixels( 0, 0, g_pWind->GetActualVideoModeParams().width, g_pWind->GetActualVideoModeParams().height, GL_RGBA, GL_UNSIGNED_BYTE, image->pixels ); DebugAssertNoGLError(); RageSurfaceUtils::FlipVertically( image ); } return image; } RageSurface *RageDisplay_Legacy::GetTexture( uintptr_t iTexture ) { if (iTexture == 0) return nullptr; // XXX FlushGLErrors(); glBindTexture( GL_TEXTURE_2D, static_cast(iTexture) ); GLint iHeight, iWidth, iAlphaBits; glGetTexLevelParameteriv( GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &iHeight ); glGetTexLevelParameteriv( GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &iWidth ); glGetTexLevelParameteriv( GL_TEXTURE_2D, 0, GL_TEXTURE_ALPHA_SIZE, &iAlphaBits ); int iFormat = iAlphaBits? RagePixelFormat_RGBA8:RagePixelFormat_RGB8; const RagePixelFormatDesc &desc = PIXEL_FORMAT_DESC[iFormat]; RageSurface *pImage = CreateSurface( iWidth, iHeight, desc.bpp, desc.masks[0], desc.masks[1], desc.masks[2], desc.masks[3] ); glGetTexImage( GL_TEXTURE_2D, 0, g_GLPixFmtInfo[iFormat].format, GL_UNSIGNED_BYTE, pImage->pixels ); AssertNoGLError(); return pImage; } ActualVideoModeParams RageDisplay_Legacy::GetActualVideoModeParams() const { return g_pWind->GetActualVideoModeParams(); } static void SetupVertices( const RageSpriteVertex v[], int iNumVerts ) { static float *Vertex, *Texture, *Normal; static GLubyte *Color; static int Size = 0; if (iNumVerts > Size) { Size = iNumVerts; delete [] Vertex; delete [] Color; delete [] Texture; delete [] Normal; Vertex = new float[Size*3]; Color = new GLubyte[Size*4]; Texture = new float[Size*2]; Normal = new float[Size*3]; } for( unsigned i = 0; i < unsigned(iNumVerts); ++i ) { Vertex[i*3+0] = v[i].p[0]; Vertex[i*3+1] = v[i].p[1]; Vertex[i*3+2] = v[i].p[2]; Color[i*4+0] = v[i].c.r; Color[i*4+1] = v[i].c.g; Color[i*4+2] = v[i].c.b; Color[i*4+3] = v[i].c.a; Texture[i*2+0] = v[i].t[0]; Texture[i*2+1] = v[i].t[1]; Normal[i*3+0] = v[i].n[0]; Normal[i*3+1] = v[i].n[1]; Normal[i*3+2] = v[i].n[2]; } glEnableClientState( GL_VERTEX_ARRAY ); glVertexPointer( 3, GL_FLOAT, 0, Vertex ); glEnableClientState( GL_COLOR_ARRAY ); glColorPointer( 4, GL_UNSIGNED_BYTE, 0, Color ); glEnableClientState( GL_TEXTURE_COORD_ARRAY ); glTexCoordPointer( 2, GL_FLOAT, 0, Texture ); if (GLEW_ARB_multitexture) { glClientActiveTextureARB( GL_TEXTURE1_ARB ); glEnableClientState( GL_TEXTURE_COORD_ARRAY ); glTexCoordPointer( 2, GL_FLOAT, 0, Texture ); glClientActiveTextureARB( GL_TEXTURE0_ARB ); } glEnableClientState( GL_NORMAL_ARRAY ); glNormalPointer( GL_FLOAT, 0, Normal ); } void RageDisplay_Legacy::SendCurrentMatrices() { RageMatrix projection; RageMatrixMultiply( &projection, GetCentering(), GetProjectionTop() ); if (g_bInvertY) { RageMatrix flip; RageMatrixScale( &flip, +1, -1, +1 ); RageMatrixMultiply( &projection, &flip, &projection ); } glMatrixMode( GL_PROJECTION ); glLoadMatrixf( (const float*)&projection ); // OpenGL has just "modelView", whereas D3D has "world" and "view" RageMatrix modelView; RageMatrixMultiply( &modelView, GetViewTop(), GetWorldTop() ); glMatrixMode( GL_MODELVIEW ); glLoadMatrixf( (const float*)&modelView ); glMatrixMode( GL_TEXTURE ); glLoadMatrixf( (const float*)GetTextureTop() ); } class RageCompiledGeometrySWOGL : public RageCompiledGeometry { public: void Allocate( const std::vector &vMeshes ) { /* Always allocate at least 1 entry, so &x[0] is valid. */ const unsigned int verticesCount = std::max(1u, GetTotalVertices()); const unsigned int trianglesCount = std::max(1u, GetTotalTriangles()); m_vPosition.resize(verticesCount); m_vTexture.resize(verticesCount); m_vNormal.resize(verticesCount); m_vTexMatrixScale.resize(verticesCount); m_vTriangles.resize(trianglesCount); } void Change( const std::vector &vMeshes ) { for( unsigned i=0; i &Vertices = mesh.Vertices; const std::vector &Triangles = mesh.Triangles; for( unsigned j=0; jTrace( s ); } */ mat.m[3][0] = 0; mat.m[3][1] = 0; mat.m[3][2] = 0; glMatrixMode( GL_TEXTURE ); glLoadMatrixf( (const float*)mat ); } glDrawElements( GL_TRIANGLES, meshInfo.iTriangleCount*3, GL_UNSIGNED_SHORT, &m_vTriangles[0]+meshInfo.iTriangleStart ); } protected: std::vector m_vPosition; std::vector m_vTexture; std::vector m_vNormal; std::vector m_vTriangles; std::vector m_vTexMatrixScale; }; class InvalidateObject; static std::set g_InvalidateList; class InvalidateObject { public: InvalidateObject() { g_InvalidateList.insert( this ); } virtual ~InvalidateObject() { g_InvalidateList.erase( this ); } virtual void Invalidate() = 0; }; static void InvalidateObjects() { for (InvalidateObject *it : g_InvalidateList) it->Invalidate(); } class RageCompiledGeometryHWOGL : public RageCompiledGeometrySWOGL, public InvalidateObject { protected: // vertex buffer object names GLuint m_nPositions; GLuint m_nTextureCoords; GLuint m_nNormals; GLuint m_nTriangles; GLuint m_nTextureMatrixScale; void AllocateBuffers(); void UploadData(); public: RageCompiledGeometryHWOGL(); ~RageCompiledGeometryHWOGL(); /* This is called when our OpenGL context is invalidated. */ void Invalidate(); void Allocate( const std::vector &vMeshes ); void Change( const std::vector &vMeshes ); void Draw( int iMeshIndex ) const; }; RageCompiledGeometryHWOGL::RageCompiledGeometryHWOGL() { m_nPositions = 0; m_nTextureCoords = 0; m_nNormals = 0; m_nTriangles = 0; m_nTextureMatrixScale = 0; AllocateBuffers(); } RageCompiledGeometryHWOGL::~RageCompiledGeometryHWOGL() { DebugFlushGLErrors(); glDeleteBuffersARB( 1, &m_nPositions ); DebugAssertNoGLError(); glDeleteBuffersARB( 1, &m_nTextureCoords ); DebugAssertNoGLError(); glDeleteBuffersARB( 1, &m_nNormals ); DebugAssertNoGLError(); glDeleteBuffersARB( 1, &m_nTriangles ); DebugAssertNoGLError(); glDeleteBuffersARB( 1, &m_nTextureMatrixScale ); DebugAssertNoGLError(); } void RageCompiledGeometryHWOGL::AllocateBuffers() { DebugFlushGLErrors(); if (!m_nPositions) { glGenBuffersARB( 1, &m_nPositions ); DebugAssertNoGLError(); } if (!m_nTextureCoords) { glGenBuffersARB( 1, &m_nTextureCoords ); DebugAssertNoGLError(); } if (!m_nNormals) { glGenBuffersARB( 1, &m_nNormals ); DebugAssertNoGLError(); } if (!m_nTriangles) { glGenBuffersARB( 1, &m_nTriangles ); DebugAssertNoGLError(); } if (!m_nTextureMatrixScale) { glGenBuffersARB( 1, &m_nTextureMatrixScale ); DebugAssertNoGLError(); } } void RageCompiledGeometryHWOGL::UploadData() { DebugFlushGLErrors(); glBindBufferARB(GL_ARRAY_BUFFER_ARB, m_nPositions); DebugAssertNoGLError(); glBufferDataARB( GL_ARRAY_BUFFER_ARB, GetTotalVertices()*sizeof(RageVector3), &m_vPosition[0], GL_STATIC_DRAW_ARB); DebugAssertNoGLError(); glBindBufferARB(GL_ARRAY_BUFFER_ARB, m_nTextureCoords); DebugAssertNoGLError(); glBufferDataARB( GL_ARRAY_BUFFER_ARB, GetTotalVertices()*sizeof(RageVector2), &m_vTexture[0], GL_STATIC_DRAW_ARB); DebugAssertNoGLError(); glBindBufferARB(GL_ARRAY_BUFFER_ARB, m_nNormals); DebugAssertNoGLError(); glBufferDataARB( GL_ARRAY_BUFFER_ARB, GetTotalVertices()*sizeof(RageVector3), &m_vNormal[0], GL_STATIC_DRAW_ARB); DebugAssertNoGLError(); glBindBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB, m_nTriangles); DebugAssertNoGLError(); glBufferDataARB( GL_ELEMENT_ARRAY_BUFFER_ARB, GetTotalTriangles()*sizeof(msTriangle), &m_vTriangles[0], GL_STATIC_DRAW_ARB); DebugAssertNoGLError(); if (m_bAnyNeedsTextureMatrixScale) { glBindBufferARB(GL_ARRAY_BUFFER_ARB, m_nTextureMatrixScale); DebugAssertNoGLError(); glBufferDataARB( GL_ARRAY_BUFFER_ARB, GetTotalVertices()*sizeof(RageVector2), &m_vTexMatrixScale[0], GL_STATIC_DRAW_ARB); DebugAssertNoGLError(); } } void RageCompiledGeometryHWOGL::Invalidate() { /* Our vertex buffers no longer exist. Reallocate and reupload. */ m_nPositions = 0; m_nTextureCoords = 0; m_nNormals = 0; m_nTriangles = 0; m_nTextureMatrixScale = 0; AllocateBuffers(); UploadData(); } void RageCompiledGeometryHWOGL::Allocate( const std::vector &vMeshes ) { DebugFlushGLErrors(); RageCompiledGeometrySWOGL::Allocate( vMeshes ); glBindBufferARB( GL_ARRAY_BUFFER_ARB, m_nPositions ); DebugAssertNoGLError(); glBufferDataARB( GL_ARRAY_BUFFER_ARB, GetTotalVertices()*sizeof(RageVector3), nullptr, GL_STATIC_DRAW_ARB ); DebugAssertNoGLError(); glBindBufferARB( GL_ARRAY_BUFFER_ARB, m_nTextureCoords ); DebugAssertNoGLError(); glBufferDataARB( GL_ARRAY_BUFFER_ARB, GetTotalVertices()*sizeof(RageVector2), nullptr, GL_STATIC_DRAW_ARB ); DebugAssertNoGLError(); glBindBufferARB( GL_ARRAY_BUFFER_ARB, m_nNormals ); DebugAssertNoGLError(); glBufferDataARB( GL_ARRAY_BUFFER_ARB, GetTotalVertices()*sizeof(RageVector3), nullptr, GL_STATIC_DRAW_ARB ); DebugAssertNoGLError(); glBindBufferARB( GL_ELEMENT_ARRAY_BUFFER_ARB, m_nTriangles ); DebugAssertNoGLError(); glBufferDataARB( GL_ELEMENT_ARRAY_BUFFER_ARB, GetTotalTriangles()*sizeof(msTriangle), nullptr, GL_STATIC_DRAW_ARB ); DebugAssertNoGLError(); glBindBufferARB( GL_ARRAY_BUFFER_ARB, m_nTextureMatrixScale ); DebugAssertNoGLError(); glBufferDataARB( GL_ARRAY_BUFFER_ARB, GetTotalVertices()*sizeof(RageVector2), nullptr, GL_STATIC_DRAW_ARB ); } void RageCompiledGeometryHWOGL::Change( const std::vector &vMeshes ) { RageCompiledGeometrySWOGL::Change( vMeshes ); UploadData(); } void RageCompiledGeometryHWOGL::Draw( int iMeshIndex ) const { DebugFlushGLErrors(); const MeshInfo& meshInfo = m_vMeshInfo[iMeshIndex]; if (!meshInfo.iVertexCount || !meshInfo.iTriangleCount) return; glEnableClientState(GL_VERTEX_ARRAY); DebugAssertNoGLError(); glBindBufferARB( GL_ARRAY_BUFFER_ARB, m_nPositions ); DebugAssertNoGLError(); glVertexPointer(3, GL_FLOAT, 0, nullptr ); DebugAssertNoGLError(); glDisableClientState(GL_COLOR_ARRAY); DebugAssertNoGLError(); glEnableClientState(GL_TEXTURE_COORD_ARRAY); DebugAssertNoGLError(); glBindBufferARB( GL_ARRAY_BUFFER_ARB, m_nTextureCoords ); DebugAssertNoGLError(); glTexCoordPointer(2, GL_FLOAT, 0, nullptr); DebugAssertNoGLError(); // TRICKY: Don't bind and send normals if lighting is disabled. This // will save some effort transforming these values. // XXX: We should keep track of these ourself and avoid glGet*() GLboolean bLighting; glGetBooleanv( GL_LIGHTING, &bLighting ); GLboolean bTextureGenS; glGetBooleanv( GL_TEXTURE_GEN_S, &bTextureGenS ); GLboolean bTextureGenT; glGetBooleanv( GL_TEXTURE_GEN_T, &bTextureGenT ); if (bLighting || bTextureGenS || bTextureGenT) { glEnableClientState(GL_NORMAL_ARRAY); DebugAssertNoGLError(); glBindBufferARB( GL_ARRAY_BUFFER_ARB, m_nNormals ); DebugAssertNoGLError(); glNormalPointer(GL_FLOAT, 0, nullptr); DebugAssertNoGLError(); } else { glDisableClientState(GL_NORMAL_ARRAY); DebugAssertNoGLError(); } if (meshInfo.m_bNeedsTextureMatrixScale) { if (g_bTextureMatrixShader != 0) { /* If we're using texture matrix scales, set up that buffer, too, and enable the * vertex shader. This shader doesn't support all OpenGL state, so only enable it * if we're using it. */ glEnableVertexAttribArrayARB( g_iAttribTextureMatrixScale ); DebugAssertNoGLError(); glBindBufferARB( GL_ARRAY_BUFFER_ARB, m_nTextureMatrixScale ); DebugAssertNoGLError(); glVertexAttribPointerARB( g_iAttribTextureMatrixScale, 2, GL_FLOAT, false, 0, nullptr ); DebugAssertNoGLError(); glUseProgramObjectARB( g_bTextureMatrixShader ); DebugAssertNoGLError(); } else { // Kill the texture translation. // XXX: Change me to scale the translation by the TextureTranslationScale of the first vertex. RageMatrix mat; glGetFloatv( GL_TEXTURE_MATRIX , (float*)mat ); /* for( int i=0; i<4; i++ ) { RString s; for( int j=0; j<4; j++ ) s += ssprintf( "%f ", mat.m[i][j] ); LOG->Trace( s ); } */ mat.m[3][0] = 0; mat.m[3][1] = 0; mat.m[3][2] = 0; glMatrixMode( GL_TEXTURE ); glLoadMatrixf( (const float*)mat ); DebugAssertNoGLError(); } } glBindBufferARB( GL_ELEMENT_ARRAY_BUFFER_ARB, m_nTriangles ); DebugAssertNoGLError(); #define BUFFER_OFFSET(o) ((char*)(o)) ASSERT( glDrawRangeElements != nullptr ); glDrawRangeElements( GL_TRIANGLES, meshInfo.iVertexStart, // minimum array index contained in indices meshInfo.iVertexStart+meshInfo.iVertexCount-1, // maximum array index contained in indices meshInfo.iTriangleCount*3, // number of elements to be rendered GL_UNSIGNED_SHORT, BUFFER_OFFSET(meshInfo.iTriangleStart*sizeof(msTriangle)) ); DebugAssertNoGLError(); if (meshInfo.m_bNeedsTextureMatrixScale && g_bTextureMatrixShader != 0) { glDisableVertexAttribArrayARB( g_iAttribTextureMatrixScale ); glUseProgramObjectARB( 0 ); } } RageCompiledGeometry* RageDisplay_Legacy::CreateCompiledGeometry() { if (GLEW_ARB_vertex_buffer_object) return new RageCompiledGeometryHWOGL; else return new RageCompiledGeometrySWOGL; } void RageDisplay_Legacy::DeleteCompiledGeometry( RageCompiledGeometry* p ) { delete p; } void RageDisplay_Legacy::DrawQuadsInternal( const RageSpriteVertex v[], int iNumVerts ) { TurnOffHardwareVBO(); SendCurrentMatrices(); SetupVertices( v, iNumVerts ); glDrawArrays( GL_QUADS, 0, iNumVerts ); } void RageDisplay_Legacy::DrawQuadStripInternal( const RageSpriteVertex v[], int iNumVerts ) { TurnOffHardwareVBO(); SendCurrentMatrices(); SetupVertices( v, iNumVerts ); glDrawArrays( GL_QUAD_STRIP, 0, iNumVerts ); } void RageDisplay_Legacy::DrawSymmetricQuadStripInternal( const RageSpriteVertex v[], int iNumVerts ) { int iNumPieces = (iNumVerts-3)/3; int iNumTriangles = iNumPieces*4; int iNumIndices = iNumTriangles*3; // make a temporary index buffer static std::vector vIndices; unsigned uOldSize = vIndices.size(); unsigned uNewSize = std::max(uOldSize,(unsigned)iNumIndices); vIndices.resize( uNewSize ); for( uint16_t i=(uint16_t)uOldSize/12; i<(uint16_t)iNumPieces; i++ ) { // { 1, 3, 0 } { 1, 4, 3 } { 1, 5, 4 } { 1, 2, 5 } vIndices[i*12+0] = i*3+1; vIndices[i*12+1] = i*3+3; vIndices[i*12+2] = i*3+0; vIndices[i*12+3] = i*3+1; vIndices[i*12+4] = i*3+4; vIndices[i*12+5] = i*3+3; vIndices[i*12+6] = i*3+1; vIndices[i*12+7] = i*3+5; vIndices[i*12+8] = i*3+4; vIndices[i*12+9] = i*3+1; vIndices[i*12+10] = i*3+2; vIndices[i*12+11] = i*3+5; } TurnOffHardwareVBO(); SendCurrentMatrices(); SetupVertices( v, iNumVerts ); glDrawElements( GL_TRIANGLES, iNumIndices, GL_UNSIGNED_SHORT, &vIndices[0] ); } void RageDisplay_Legacy::DrawFanInternal( const RageSpriteVertex v[], int iNumVerts ) { TurnOffHardwareVBO(); SendCurrentMatrices(); SetupVertices( v, iNumVerts ); glDrawArrays( GL_TRIANGLE_FAN, 0, iNumVerts ); } void RageDisplay_Legacy::DrawStripInternal( const RageSpriteVertex v[], int iNumVerts ) { TurnOffHardwareVBO(); SendCurrentMatrices(); SetupVertices( v, iNumVerts ); glDrawArrays( GL_TRIANGLE_STRIP, 0, iNumVerts ); } void RageDisplay_Legacy::DrawTrianglesInternal( const RageSpriteVertex v[], int iNumVerts ) { TurnOffHardwareVBO(); SendCurrentMatrices(); SetupVertices( v, iNumVerts ); glDrawArrays( GL_TRIANGLES, 0, iNumVerts ); } void RageDisplay_Legacy::DrawCompiledGeometryInternal( const RageCompiledGeometry *p, int iMeshIndex ) { TurnOffHardwareVBO(); SendCurrentMatrices(); p->Draw( iMeshIndex ); } void RageDisplay_Legacy::DrawLineStripInternal( const RageSpriteVertex v[], int iNumVerts, float fLineWidth ) { TurnOffHardwareVBO(); if (!GetActualVideoModeParams().bSmoothLines) { /* Fall back on the generic polygon-based line strip. */ RageDisplay::DrawLineStripInternal(v, iNumVerts, fLineWidth ); return; } SendCurrentMatrices(); /* Draw a nice AA'd line loop. One problem with this is that point and line * sizes don't always precisely match, which doesn't look quite right. * It's worth it for the AA, though. */ glEnable( GL_LINE_SMOOTH ); /* fLineWidth is in units relative to object space, but OpenGL line and point sizes * are in raster units (actual pixels). Scale the line width by the average ratio; * if object space is 640x480, and we have a 1280x960 window, we'll double the * width. */ { const RageMatrix* pMat = GetProjectionTop(); float fW = 2 / pMat->m[0][0]; float fH = -2 / pMat->m[1][1]; float fWidthVal = float(g_pWind->GetActualVideoModeParams().width) / fW; float fHeightVal = float(g_pWind->GetActualVideoModeParams().height) / fH; fLineWidth *= (fWidthVal + fHeightVal) / 2; } /* Clamp the width to the hardware max for both lines and points (whichever * is more restrictive). */ fLineWidth = clamp( fLineWidth, g_line_range[0], g_line_range[1] ); fLineWidth = clamp( fLineWidth, g_point_range[0], g_point_range[1] ); /* Hmm. The granularity of lines and points might be different; for example, * if lines are .5 and points are .25, we might want to snap the width to the * nearest .5, so the hardware doesn't snap them to different sizes. Does it * matter? */ glLineWidth( fLineWidth ); /* Draw the line loop: */ SetupVertices( v, iNumVerts ); glDrawArrays( GL_LINE_STRIP, 0, iNumVerts ); StatsAddVerts(iNumVerts); glDisable( GL_LINE_SMOOTH ); /* Round off the corners. This isn't perfect; the point is sometimes a little * larger than the line, causing a small bump on the edge. Not sure how to fix * that. */ glPointSize( fLineWidth ); /* Hack: if the points will all be the same, we don't want to draw * any points at all, since there's nothing to connect. That'll happen * if both scale factors in the matrix are ~0. (Actually, I think * it's true if two of the three scale factors are ~0, but we don't * use this for anything 3d at the moment anyway ...) This is needed * because points aren't scaled like regular polys--a zero-size point * will still be drawn. */ RageMatrix mat; glGetFloatv( GL_MODELVIEW_MATRIX, (float*)mat ); if (mat.m[0][0] < 1e-5 && mat.m[1][1] < 1e-5) return; glEnable( GL_POINT_SMOOTH ); SetupVertices( v, iNumVerts ); glDrawArrays( GL_POINTS, 0, iNumVerts ); StatsAddVerts(iNumVerts); glDisable( GL_POINT_SMOOTH ); } static bool SetTextureUnit( TextureUnit tu ) { // If multitexture isn't supported, ignore all textures except for 0. if (!GLEW_ARB_multitexture && tu != TextureUnit_1) return false; if ((int) tu > g_iMaxTextureUnits) return false; glActiveTextureARB( enum_add2(GL_TEXTURE0_ARB, tu) ); return true; } void RageDisplay_Legacy::ClearAllTextures() { FOREACH_ENUM( TextureUnit, i ) SetTexture( i, 0 ); // HACK: Reset the active texture to 0. // TODO: Change all texture functions to take a stage number. if (GLEW_ARB_multitexture) glActiveTextureARB(GL_TEXTURE0_ARB); } int RageDisplay_Legacy::GetNumTextureUnits() { if (GLEW_ARB_multitexture) return 1; else return g_iMaxTextureUnits; } void RageDisplay_Legacy::SetTexture( TextureUnit tu, uintptr_t iTexture ) { if (!SetTextureUnit( tu )) return; if (iTexture) { glEnable( GL_TEXTURE_2D ); glBindTexture( GL_TEXTURE_2D, static_cast(iTexture) ); } else { glDisable( GL_TEXTURE_2D ); } } void RageDisplay_Legacy::SetTextureMode( TextureUnit tu, TextureMode tm ) { if (!SetTextureUnit( tu )) return; switch( tm ) { case TextureMode_Modulate: glTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE ); break; case TextureMode_Add: glTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_ADD ); break; case TextureMode_Glow: // the below function is glowmode,brighten: if (!GLEW_ARB_texture_env_combine && !GLEW_EXT_texture_env_combine) { /* This is changing blend state, instead of texture state, which * isn't great, but it's better than doing nothing. */ glBlendFunc( GL_SRC_ALPHA, GL_ONE ); return; } // and this is glowmode,whiten: // Source color is the diffuse color only: glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE_EXT); glTexEnvi(GL_TEXTURE_ENV, GLenum(GL_COMBINE_RGB_EXT), GL_REPLACE); glTexEnvi(GL_TEXTURE_ENV, GLenum(GL_SOURCE0_RGB_EXT), GL_PRIMARY_COLOR_EXT); // Source alpha is texture alpha * diffuse alpha: glTexEnvi(GL_TEXTURE_ENV, GLenum(GL_COMBINE_ALPHA_EXT), GL_MODULATE); glTexEnvi(GL_TEXTURE_ENV, GLenum(GL_OPERAND0_ALPHA_EXT), GL_SRC_ALPHA); glTexEnvi(GL_TEXTURE_ENV, GLenum(GL_SOURCE0_ALPHA_EXT), GL_PRIMARY_COLOR_EXT); glTexEnvi(GL_TEXTURE_ENV, GLenum(GL_OPERAND1_ALPHA_EXT), GL_SRC_ALPHA); glTexEnvi(GL_TEXTURE_ENV, GLenum(GL_SOURCE1_ALPHA_EXT), GL_TEXTURE); break; default: break; } } void RageDisplay_Legacy::SetTextureFiltering( TextureUnit tu, bool b ) { glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, b ? GL_LINEAR : GL_NEAREST); GLint iMinFilter; if (b) { GLint iWidth1 = -1; GLint iWidth2 = -1; glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &iWidth1); glGetTexLevelParameteriv(GL_TEXTURE_2D, 1, GL_TEXTURE_WIDTH, &iWidth2); if (iWidth1 > 1 && iWidth2 != 0) { /* Mipmaps are enabled. */ if (g_pWind->GetActualVideoModeParams().bTrilinearFiltering) iMinFilter = GL_LINEAR_MIPMAP_LINEAR; else iMinFilter = GL_LINEAR_MIPMAP_NEAREST; } else { iMinFilter = GL_LINEAR; } } else { iMinFilter = GL_NEAREST; } glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, iMinFilter ); } void RageDisplay_Legacy::SetEffectMode( EffectMode effect ) { if (!GLEW_ARB_fragment_program || !GLEW_ARB_shading_language_100 || !GLEW_ARB_shader_objects) return; GLhandleARB hShader = 0; switch (effect) { case EffectMode_Normal: hShader = 0; break; case EffectMode_Unpremultiply: hShader = g_bUnpremultiplyShader; break; case EffectMode_ColorBurn: hShader = g_bColorBurnShader; break; case EffectMode_ColorDodge: hShader = g_bColorDodgeShader; break; case EffectMode_VividLight: hShader = g_bVividLightShader; break; case EffectMode_HardMix: hShader = g_hHardMixShader; break; case EffectMode_Overlay: hShader = g_hOverlayShader; break; case EffectMode_Screen: hShader = g_hScreenShader; break; case EffectMode_YUYV422: hShader = g_hYUYV422Shader; break; case EffectMode_DistanceField: hShader = g_gDistanceFieldShader; default: break; } DebugFlushGLErrors(); glUseProgramObjectARB( hShader ); if (hShader == 0) return; GLint iTexture1 = glGetUniformLocationARB( hShader, "Texture1" ); GLint iTexture2 = glGetUniformLocationARB( hShader, "Texture2" ); glUniform1iARB( iTexture1, 0 ); glUniform1iARB( iTexture2, 1 ); if (effect == EffectMode_YUYV422) { GLint iTextureWidthUniform = glGetUniformLocationARB( hShader, "TextureWidth" ); GLint iWidth; glGetTexLevelParameteriv( GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &iWidth ); glUniform1iARB( iTextureWidthUniform, iWidth ); } DebugAssertNoGLError(); } bool RageDisplay_Legacy::IsEffectModeSupported( EffectMode effect ) { switch( effect ) { case EffectMode_Normal: return true; case EffectMode_Unpremultiply: return g_bUnpremultiplyShader != 0; case EffectMode_ColorBurn: return g_bColorBurnShader != 0; case EffectMode_ColorDodge: return g_bColorDodgeShader != 0; case EffectMode_VividLight: return g_bVividLightShader != 0; case EffectMode_HardMix: return g_hHardMixShader != 0; case EffectMode_Overlay: return g_hOverlayShader != 0; case EffectMode_Screen: return g_hScreenShader != 0; case EffectMode_YUYV422: return g_hYUYV422Shader != 0; case EffectMode_DistanceField: return g_gDistanceFieldShader != 0; default: return false; } } void RageDisplay_Legacy::SetBlendMode( BlendMode mode ) { glEnable(GL_BLEND); if (glBlendEquation != nullptr) { if (mode == BLEND_INVERT_DEST) glBlendEquation( GL_FUNC_SUBTRACT ); else if (mode == BLEND_SUBTRACT) glBlendEquation( GL_FUNC_REVERSE_SUBTRACT ); else glBlendEquation( GL_FUNC_ADD ); } int iSourceRGB, iDestRGB; int iSourceAlpha = GL_ONE, iDestAlpha = GL_ONE_MINUS_SRC_ALPHA; switch( mode ) { case BLEND_NORMAL: iSourceRGB = GL_SRC_ALPHA; iDestRGB = GL_ONE_MINUS_SRC_ALPHA; break; case BLEND_ADD: iSourceRGB = GL_SRC_ALPHA; iDestRGB = GL_ONE; break; case BLEND_SUBTRACT: iSourceRGB = GL_SRC_ALPHA; iDestRGB = GL_ONE_MINUS_SRC_ALPHA; break; case BLEND_MODULATE: iSourceRGB = GL_ZERO; iDestRGB = GL_SRC_COLOR; break; case BLEND_COPY_SRC: iSourceRGB = GL_ONE; iDestRGB = GL_ZERO; iSourceAlpha = GL_ONE; iDestAlpha = GL_ZERO; break; case BLEND_ALPHA_MASK: iSourceRGB = GL_ZERO; iDestRGB = GL_ONE; iSourceAlpha = GL_ZERO; iDestAlpha = GL_SRC_ALPHA; break; case BLEND_ALPHA_KNOCK_OUT: iSourceRGB = GL_ZERO; iDestRGB = GL_ONE; iSourceAlpha = GL_ZERO; iDestAlpha = GL_ONE_MINUS_SRC_ALPHA; break; case BLEND_ALPHA_MULTIPLY: iSourceRGB = GL_SRC_ALPHA; iDestRGB = GL_ZERO; break; case BLEND_WEIGHTED_MULTIPLY: /* output = 2*(dst*src). 0.5,0.5,0.5 is identity; darker colors darken the image, * and brighter colors lighten the image. */ iSourceRGB = GL_DST_COLOR; iDestRGB = GL_SRC_COLOR; break; case BLEND_INVERT_DEST: /* out = src - dst. The source color should almost always be #FFFFFF, to make it "1 - dst". */ iSourceRGB = GL_ONE; iDestRGB = GL_ONE; break; case BLEND_NO_EFFECT: iSourceRGB = GL_ZERO; iDestRGB = GL_ONE; iSourceAlpha = GL_ZERO; iDestAlpha = GL_ONE; break; DEFAULT_FAIL( mode ); } if (GLEW_EXT_blend_equation_separate) glBlendFuncSeparateEXT( iSourceRGB, iDestRGB, iSourceAlpha, iDestAlpha ); else glBlendFunc( iSourceRGB, iDestRGB ); } bool RageDisplay_Legacy::IsZWriteEnabled() const { bool a; glGetBooleanv( GL_DEPTH_WRITEMASK, (unsigned char*)&a ); return a; } bool RageDisplay_Legacy::IsZTestEnabled() const { GLenum a; glGetIntegerv( GL_DEPTH_FUNC, (GLint*)&a ); return a != GL_ALWAYS; } void RageDisplay_Legacy::ClearZBuffer() { bool write = IsZWriteEnabled(); SetZWrite( true ); glClear( GL_DEPTH_BUFFER_BIT ); SetZWrite( write ); } void RageDisplay_Legacy::SetZWrite( bool b ) { glDepthMask( b ); } void RageDisplay_Legacy::SetZBias( float f ) { float fNear = SCALE( f, 0.0f, 1.0f, 0.05f, 0.0f ); float fFar = SCALE( f, 0.0f, 1.0f, 1.0f, 0.95f ); glDepthRange( fNear, fFar ); } void RageDisplay_Legacy::SetZTestMode( ZTestMode mode ) { glEnable( GL_DEPTH_TEST ); switch( mode ) { case ZTEST_OFF: glDepthFunc( GL_ALWAYS ); break; case ZTEST_WRITE_ON_PASS: glDepthFunc( GL_LEQUAL ); break; case ZTEST_WRITE_ON_FAIL: glDepthFunc( GL_GREATER ); break; default: FAIL_M(ssprintf("Invalid ZTestMode: %i", mode)); } } void RageDisplay_Legacy::SetTextureWrapping( TextureUnit tu, bool b ) { /* This should be per-texture-unit state, but it's per-texture state in OpenGl, * so we'll behave incorrectly if the same texture is used in more than one texture * unit simultaneously with different wrapping. */ SetTextureUnit( tu ); GLenum mode = b ? GL_REPEAT : GL_CLAMP_TO_EDGE; glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, mode ); glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, mode ); } void RageDisplay_Legacy::SetMaterial( const RageColor &emissive, const RageColor &ambient, const RageColor &diffuse, const RageColor &specular, float shininess ) { // TRICKY: If lighting is off, then setting the material // will have no effect. Even if lighting is off, we still // want Models to have basic color and transparency. // We can do this fake lighting by setting the vertex color. // XXX: unintended: SetLighting must be called before SetMaterial GLboolean bLighting; glGetBooleanv( GL_LIGHTING, &bLighting ); if (bLighting) { glMaterialfv( GL_FRONT, GL_EMISSION, emissive ); glMaterialfv( GL_FRONT, GL_AMBIENT, ambient ); glMaterialfv( GL_FRONT, GL_DIFFUSE, diffuse ); glMaterialfv( GL_FRONT, GL_SPECULAR, specular ); glMaterialf( GL_FRONT, GL_SHININESS, shininess ); } else { RageColor c = diffuse; c.r += emissive.r + ambient.r; c.g += emissive.g + ambient.g; c.b += emissive.b + ambient.b; glColor4fv( c ); } } void RageDisplay_Legacy::SetLighting( bool b ) { if (b) glEnable(GL_LIGHTING); else glDisable(GL_LIGHTING); } void RageDisplay_Legacy::SetLightOff( int index ) { glDisable( GL_LIGHT0+index ); } void RageDisplay_Legacy::SetLightDirectional( int index, const RageColor &ambient, const RageColor &diffuse, const RageColor &specular, const RageVector3 &dir ) { // Light coordinates are transformed by the modelview matrix, but // we are being passed in world-space coords. glPushMatrix(); glLoadIdentity(); glEnable( GL_LIGHT0+index ); glLightfv( GL_LIGHT0+index, GL_AMBIENT, ambient ); glLightfv( GL_LIGHT0+index, GL_DIFFUSE, diffuse ); glLightfv( GL_LIGHT0+index, GL_SPECULAR, specular ); float position[4] = {dir.x, dir.y, dir.z, 0}; glLightfv( GL_LIGHT0+index, GL_POSITION, position ); glPopMatrix(); } void RageDisplay_Legacy::SetCullMode( CullMode mode ) { if (mode != CULL_NONE) glEnable(GL_CULL_FACE); switch( mode ) { case CULL_BACK: glCullFace( GL_BACK ); break; case CULL_FRONT: glCullFace( GL_FRONT ); break; case CULL_NONE: glDisable( GL_CULL_FACE ); break; default: FAIL_M(ssprintf("Invalid CullMode: %i", mode)); } } const RageDisplay::RagePixelFormatDesc *RageDisplay_Legacy::GetPixelFormatDesc(RagePixelFormat pf) const { ASSERT( pf < NUM_RagePixelFormat ); return &PIXEL_FORMAT_DESC[pf]; } bool RageDisplay_Legacy::SupportsThreadedRendering() { return g_pWind->SupportsThreadedRendering(); } void RageDisplay_Legacy::BeginConcurrentRenderingMainThread() { g_pWind->BeginConcurrentRenderingMainThread(); } void RageDisplay_Legacy::EndConcurrentRenderingMainThread() { g_pWind->EndConcurrentRenderingMainThread(); } void RageDisplay_Legacy::BeginConcurrentRendering() { g_pWind->BeginConcurrentRendering(); RageDisplay::BeginConcurrentRendering(); } void RageDisplay_Legacy::EndConcurrentRendering() { g_pWind->EndConcurrentRendering(); } void RageDisplay_Legacy::DeleteTexture( uintptr_t iTexture ) { if (iTexture == 0) return; if (g_mapRenderTargets.find(iTexture) != g_mapRenderTargets.end()) { delete g_mapRenderTargets[iTexture]; g_mapRenderTargets.erase( iTexture ); return; } DebugFlushGLErrors(); glDeleteTextures( 1, reinterpret_cast(&iTexture) ); DebugAssertNoGLError(); } RagePixelFormat RageDisplay_Legacy::GetImgPixelFormat( RageSurface* &img, bool &bFreeImg, int width, int height, bool bPalettedTexture ) { RagePixelFormat pixfmt = FindPixelFormat( img->format->BitsPerPixel, img->format->Rmask, img->format->Gmask, img->format->Bmask, img->format->Amask ); /* If img is paletted, we're setting up a non-paletted texture, and color indexes * are too small, depalettize. */ bool bSupported = true; if (!bPalettedTexture && img->fmt.BytesPerPixel == 1 && !g_bColorIndexTableWorks) bSupported = false; if (pixfmt == RagePixelFormat_Invalid || !SupportsSurfaceFormat(pixfmt)) bSupported = false; if (!bSupported) { /* The source isn't in a supported, known pixel format. We need to convert * it ourself. Just convert it to RGBA8, and let OpenGL convert it back * down to whatever the actual pixel format is. This is a very slow code * path, which should almost never be used. */ pixfmt = RagePixelFormat_RGBA8; ASSERT( SupportsSurfaceFormat(pixfmt) ); const RagePixelFormatDesc *pfd = DISPLAY->GetPixelFormatDesc(pixfmt); RageSurface *imgconv = CreateSurface( img->w, img->h, pfd->bpp, pfd->masks[0], pfd->masks[1], pfd->masks[2], pfd->masks[3] ); RageSurfaceUtils::Blit( img, imgconv, width, height ); img = imgconv; bFreeImg = true; } else { bFreeImg = false; } return pixfmt; } /* If we're sending a paletted surface to a non-paletted texture, set the palette. */ void SetPixelMapForSurface( int glImageFormat, int glTexFormat, const RageSurfacePalette *palette ) { if (glImageFormat != GL_COLOR_INDEX || glTexFormat == GL_COLOR_INDEX8_EXT) { glPixelTransferi( GL_MAP_COLOR, false ); return; } GLushort buf[4][256]; memset( buf, 0, sizeof(buf) ); for( int i = 0; i < palette->ncolors; ++i ) { buf[0][i] = SCALE( palette->colors[i].r, 0, 255, 0, 65535 ); buf[1][i] = SCALE( palette->colors[i].g, 0, 255, 0, 65535 ); buf[2][i] = SCALE( palette->colors[i].b, 0, 255, 0, 65535 ); buf[3][i] = SCALE( palette->colors[i].a, 0, 255, 0, 65535 ); } DebugFlushGLErrors(); glPixelMapusv( GL_PIXEL_MAP_I_TO_R, 256, buf[0] ); glPixelMapusv( GL_PIXEL_MAP_I_TO_G, 256, buf[1] ); glPixelMapusv( GL_PIXEL_MAP_I_TO_B, 256, buf[2] ); glPixelMapusv( GL_PIXEL_MAP_I_TO_A, 256, buf[3] ); glPixelTransferi( GL_MAP_COLOR, true ); DebugAssertNoGLError(); } uintptr_t RageDisplay_Legacy::CreateTexture( RagePixelFormat pixfmt, RageSurface* pImg, bool bGenerateMipMaps ) { ASSERT( pixfmt < NUM_RagePixelFormat ); /* Find the pixel format of the surface we've been given. */ bool bFreeImg; RagePixelFormat SurfacePixFmt = GetImgPixelFormat( pImg, bFreeImg, pImg->w, pImg->h, pixfmt == RagePixelFormat_PAL ); ASSERT( SurfacePixFmt != RagePixelFormat_Invalid ); GLenum glTexFormat = g_GLPixFmtInfo[pixfmt].internalfmt; GLenum glImageFormat = g_GLPixFmtInfo[SurfacePixFmt].format; GLenum glImageType = g_GLPixFmtInfo[SurfacePixFmt].type; /* If the image is paletted, but we're not sending it to a paletted image, * set up glPixelMap. */ SetPixelMapForSurface( glImageFormat, glTexFormat, pImg->format->palette.get() ); // HACK: OpenGL 1.2 types aren't available in GLU 1.3. Don't call GLU for mip // mapping if we're using an OGL 1.2 type and don't have >= GLU 1.3. // http://pyopengl.sourceforge.net/documentation/manual/gluBuild2DMipmaps.3G.html if (bGenerateMipMaps && g_gluVersion < 13) { switch( pixfmt ) { // OpenGL 1.1 types case RagePixelFormat_RGBA8: case RagePixelFormat_RGB8: case RagePixelFormat_PAL: case RagePixelFormat_BGR8: break; // OpenGL 1.2 types default: LOG->Trace( "Can't generate mipmaps for type %s because GLU version %.1f is too old.", GLToString(glImageType).c_str(), g_gluVersion/10.f ); bGenerateMipMaps = false; break; } } SetTextureUnit( TextureUnit_1 ); // allocate OpenGL texture resource uintptr_t iTexHandle; glGenTextures( 1, reinterpret_cast(&iTexHandle) ); ASSERT( iTexHandle != 0 ); glBindTexture( GL_TEXTURE_2D, static_cast(iTexHandle) ); if (g_pWind->GetActualVideoModeParams().bAnisotropicFiltering && GLEW_EXT_texture_filter_anisotropic ) { GLfloat fLargestSupportedAnisotropy; glGetFloatv( GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &fLargestSupportedAnisotropy ); glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, fLargestSupportedAnisotropy ); } SetTextureFiltering( TextureUnit_1, true ); SetTextureWrapping( TextureUnit_1, false ); glPixelStorei( GL_UNPACK_ROW_LENGTH, pImg->pitch / pImg->format->BytesPerPixel ); if (pixfmt == RagePixelFormat_PAL) { /* The texture is paletted; set the texture palette. */ GLubyte palette[256*4]; memset( palette, 0, sizeof(palette) ); int p = 0; /* Copy the palette to the format OpenGL expects. */ for( int i = 0; i < pImg->format->palette->ncolors; ++i ) { palette[p++] = pImg->format->palette->colors[i].r; palette[p++] = pImg->format->palette->colors[i].g; palette[p++] = pImg->format->palette->colors[i].b; palette[p++] = pImg->format->palette->colors[i].a; } /* Set the palette. */ glColorTableEXT( GL_TEXTURE_2D, GL_RGBA8, 256, GL_RGBA, GL_UNSIGNED_BYTE, palette ); GLint iRealFormat = 0; glGetColorTableParameterivEXT( GL_TEXTURE_2D, GL_COLOR_TABLE_FORMAT, &iRealFormat ); ASSERT( iRealFormat == GL_RGBA8 ); } LOG->Trace( "%s (format %s, %ix%i, format %s, type %s, pixfmt %i, imgpixfmt %i)", bGenerateMipMaps? "gluBuild2DMipmaps":"glTexImage2D", GLToString(glTexFormat).c_str(), pImg->w, pImg->h, GLToString(glImageFormat).c_str(), GLToString(glImageType).c_str(), pixfmt, SurfacePixFmt ); DebugFlushGLErrors(); if (bGenerateMipMaps) { GLenum error = gluBuild2DMipmaps( GL_TEXTURE_2D, glTexFormat, pImg->w, pImg->h, glImageFormat, glImageType, pImg->pixels ); ASSERT_M( error == 0, (char *) gluErrorString(error) ); } else { glTexImage2D( GL_TEXTURE_2D, 0, glTexFormat, power_of_two(pImg->w), power_of_two(pImg->h), 0, glImageFormat, glImageType, nullptr ); if (pImg->pixels) glTexSubImage2D( GL_TEXTURE_2D, 0, 0, 0, pImg->w, pImg->h, glImageFormat, glImageType, pImg->pixels ); DebugAssertNoGLError(); } /* Sanity check: */ if (pixfmt == RagePixelFormat_PAL) { GLint iSize = 0; glGetTexLevelParameteriv( GL_TEXTURE_2D, 0, GLenum(GL_TEXTURE_INDEX_SIZE_EXT), &iSize ); if (iSize != 8) RageException::Throw( "Thought paletted textures worked, but they don't." ); } glPixelStorei( GL_UNPACK_ROW_LENGTH, 0 ); glFlush(); if (bFreeImg) delete pImg; return iTexHandle; } struct RageTextureLock_OGL: public RageTextureLock, public InvalidateObject { public: RageTextureLock_OGL() { m_iTexHandle = 0; m_iBuffer = 0; CreateObject(); } ~RageTextureLock_OGL() { ASSERT( m_iTexHandle == 0 ); // locked! glDeleteBuffersARB( 1, &m_iBuffer ); } /* This is called when our OpenGL context is invalidated. */ void Invalidate() { m_iTexHandle = 0; } void Lock( uintptr_t iTexHandle, RageSurface *pSurface ) { ASSERT( m_iTexHandle == 0 ); ASSERT( pSurface->pixels == nullptr ); CreateObject(); m_iTexHandle = iTexHandle; glBindBufferARB( GL_PIXEL_UNPACK_BUFFER_ARB, m_iBuffer ); int iSize = pSurface->h * pSurface->pitch; glBufferDataARB( GL_PIXEL_UNPACK_BUFFER_ARB, iSize, nullptr, GL_STREAM_DRAW ); void *pSurfaceMemory = glMapBufferARB( GL_PIXEL_UNPACK_BUFFER_ARB, GL_WRITE_ONLY ); pSurface->pixels = (uint8_t *) pSurfaceMemory; pSurface->pixels_owned = false; } void Unlock( RageSurface *pSurface, bool bChanged ) { glUnmapBufferARB( GL_PIXEL_UNPACK_BUFFER_ARB ); pSurface->pixels = (uint8_t *) BUFFER_OFFSET(0); if (bChanged) DISPLAY->UpdateTexture( m_iTexHandle, pSurface, 0, 0, pSurface->w, pSurface->h ); pSurface->pixels = nullptr; m_iTexHandle = 0; glBindBufferARB( GL_PIXEL_UNPACK_BUFFER_ARB, 0 ); } private: void CreateObject() { if (m_iBuffer != 0) return; DebugFlushGLErrors(); glGenBuffersARB( 1, &m_iBuffer ); DebugAssertNoGLError(); } GLuint m_iBuffer; uintptr_t m_iTexHandle; }; RageTextureLock *RageDisplay_Legacy::CreateTextureLock() { if (!GLEW_ARB_pixel_buffer_object) return nullptr; return new RageTextureLock_OGL; } void RageDisplay_Legacy::UpdateTexture( uintptr_t iTexHandle, RageSurface* pImg, int iXOffset, int iYOffset, int iWidth, int iHeight ) { glBindTexture( GL_TEXTURE_2D, static_cast(iTexHandle) ); bool bFreeImg; RagePixelFormat SurfacePixFmt = GetImgPixelFormat( pImg, bFreeImg, iWidth, iHeight, false ); glPixelStorei( GL_UNPACK_ROW_LENGTH, pImg->pitch / pImg->format->BytesPerPixel ); GLenum glImageFormat = g_GLPixFmtInfo[SurfacePixFmt].format; GLenum glImageType = g_GLPixFmtInfo[SurfacePixFmt].type; /* If the image is paletted, but we're not sending it to a paletted image, * set up glPixelMap. */ if (pImg->format->palette) { GLenum glTexFormat = 0; glGetTexLevelParameteriv( GL_PROXY_TEXTURE_2D, 0, GLenum(GL_TEXTURE_INTERNAL_FORMAT), (GLint *) &glTexFormat ); SetPixelMapForSurface( glImageFormat, glTexFormat, pImg->format->palette.get() ); } glTexSubImage2D( GL_TEXTURE_2D, 0, iXOffset, iYOffset, iWidth, iHeight, glImageFormat, glImageType, pImg->pixels ); /* Must unset PixelStore when we're done! */ glPixelStorei( GL_UNPACK_ROW_LENGTH, 0 ); glFlush(); if (bFreeImg) delete pImg; } class RenderTarget_FramebufferObject: public RenderTarget { public: RenderTarget_FramebufferObject(); ~RenderTarget_FramebufferObject(); void Create( const RenderTargetParam ¶m, int &iTextureWidthOut, int &iTextureHeightOut ); uintptr_t GetTexture() const { return m_iTexHandle; } void StartRenderingTo(); void FinishRenderingTo(); virtual bool InvertY() const { return true; } private: uintptr_t m_iFrameBufferHandle; uintptr_t m_iTexHandle; uintptr_t m_iDepthBufferHandle; }; RenderTarget_FramebufferObject::RenderTarget_FramebufferObject() { m_iFrameBufferHandle = 0; m_iTexHandle = 0; m_iDepthBufferHandle = 0; } RenderTarget_FramebufferObject::~RenderTarget_FramebufferObject() { if (m_iDepthBufferHandle) glDeleteRenderbuffersEXT( 1, reinterpret_cast(&m_iDepthBufferHandle) ); if (m_iFrameBufferHandle) glDeleteFramebuffersEXT( 1, reinterpret_cast(&m_iFrameBufferHandle) ); if (m_iTexHandle) glDeleteTextures( 1, reinterpret_cast(&m_iTexHandle) ); } void RenderTarget_FramebufferObject::Create( const RenderTargetParam ¶m, int &iTextureWidthOut, int &iTextureHeightOut ) { m_Param = param; DebugFlushGLErrors(); // Allocate OpenGL texture resource glGenTextures( 1, reinterpret_cast(&m_iTexHandle) ); ASSERT( m_iTexHandle != 0 ); int iTextureWidth = power_of_two( param.iWidth ); int iTextureHeight = power_of_two( param.iHeight ); iTextureWidthOut = iTextureWidth; iTextureHeightOut = iTextureHeight; glBindTexture( GL_TEXTURE_2D, static_cast(m_iTexHandle) ); GLenum internalformat; GLenum type = param.bWithAlpha? GL_RGBA:GL_RGB; if (param.bFloat && GLEW_ARB_texture_float) internalformat = param.bWithAlpha? GL_RGBA16F_ARB:GL_RGB16F_ARB; else internalformat = param.bWithAlpha? GL_RGBA8:GL_RGB8; glTexImage2D( GL_TEXTURE_2D, 0, internalformat, iTextureWidth, iTextureHeight, 0, type, GL_UNSIGNED_BYTE, nullptr ); DebugAssertNoGLError(); glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE ); glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE ); glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR ); glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR ); /* Create the framebuffer object. */ glGenFramebuffersEXT( 1, reinterpret_cast(&m_iFrameBufferHandle) ); ASSERT( m_iFrameBufferHandle != 0 ); /* Attach the texture to it. */ glBindFramebufferEXT( GL_FRAMEBUFFER_EXT, static_cast(m_iFrameBufferHandle) ); glFramebufferTexture2DEXT( GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, static_cast(m_iTexHandle), 0 ); DebugAssertNoGLError(); /* Attach a depth buffer, if requested. */ if (param.bWithDepthBuffer) { glGenRenderbuffersEXT( 1, reinterpret_cast(&m_iDepthBufferHandle) ); ASSERT( m_iDepthBufferHandle != 0 ); glBindRenderbufferEXT( GL_RENDERBUFFER, static_cast(m_iDepthBufferHandle) ); glRenderbufferStorageEXT( GL_RENDERBUFFER_EXT, GL_DEPTH_COMPONENT16, iTextureWidth, iTextureHeight ); glFramebufferRenderbufferEXT( GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_RENDERBUFFER_EXT, static_cast(m_iDepthBufferHandle) ); } GLenum status = glCheckFramebufferStatusEXT( GL_FRAMEBUFFER_EXT ); switch(status) { case GL_FRAMEBUFFER_COMPLETE_EXT: break; case GL_FRAMEBUFFER_UNSUPPORTED_EXT: FAIL_M( "GL_FRAMEBUFFER_UNSUPPORTED_EXT" ); break; case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT_EXT: FAIL_M( "GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT_EXT" ); break; case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT_EXT: FAIL_M( "GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT_EXT" ); break; case GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS_EXT: FAIL_M( "GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS_EXT" ); break; case GL_FRAMEBUFFER_INCOMPLETE_FORMATS_EXT: FAIL_M( "GL_FRAMEBUFFER_INCOMPLETE_FORMATS_EXT" ); break; case GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER_EXT: FAIL_M( "GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER_EXT" ); break; case GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER_EXT: FAIL_M( "GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER_EXT" ); break; default: FAIL_M(ssprintf("Unexpected GL framebuffer status: %i", status)); } glBindFramebufferEXT( GL_FRAMEBUFFER_EXT, 0 ); } void RenderTarget_FramebufferObject::StartRenderingTo() { glBindFramebufferEXT( GL_FRAMEBUFFER_EXT, static_cast(m_iFrameBufferHandle) ); } void RenderTarget_FramebufferObject::FinishRenderingTo() { glBindFramebufferEXT( GL_FRAMEBUFFER_EXT, 0 ); } bool RageDisplay_Legacy::SupportsRenderToTexture() const { return GLEW_EXT_framebuffer_object || g_pWind->SupportsRenderToTexture(); } bool RageDisplay_Legacy::SupportsFullscreenBorderlessWindow() const { // In order to support FSBW, we're going to need the LowLevelWindow implementation // to support creating a fullscreen borderless window, and we're going to need // RenderToTexture support in order to render in alternative resolutions return g_pWind->SupportsFullscreenBorderlessWindow() && SupportsRenderToTexture(); } /* * Render-to-texture can be implemented in several ways: the generic GL_ARB_pixel_buffer_object, * or platform-specifically. PBO is not available on all hardware that supports RTT, * particularly GeForce 2, but is simpler and faster when available. */ uintptr_t RageDisplay_Legacy::CreateRenderTarget( const RenderTargetParam ¶m, int &iTextureWidthOut, int &iTextureHeightOut ) { RenderTarget *pTarget; if (GLEW_EXT_framebuffer_object) pTarget = new RenderTarget_FramebufferObject; else pTarget = g_pWind->CreateRenderTarget(); pTarget->Create( param, iTextureWidthOut, iTextureHeightOut ); uintptr_t iTexture = pTarget->GetTexture(); ASSERT( g_mapRenderTargets.find(iTexture) == g_mapRenderTargets.end() ); g_mapRenderTargets[iTexture] = pTarget; return iTexture; } uintptr_t RageDisplay_Legacy::GetRenderTarget() { for( std::map::const_iterator it = g_mapRenderTargets.begin(); it != g_mapRenderTargets.end(); ++it ) if( it->second == g_pCurrentRenderTarget ) return it->first; return 0; } void RageDisplay_Legacy::SetRenderTarget( uintptr_t iTexture, bool bPreserveTexture ) { if (iTexture == 0) { g_bInvertY = false; glFrontFace( GL_CCW ); /* Pop matrixes affected by SetDefaultRenderStates. */ DISPLAY->CameraPopMatrix(); /* Reset the viewport. */ int fWidth = g_pWind->GetActualVideoModeParams().windowWidth; int fHeight = g_pWind->GetActualVideoModeParams().windowHeight; glViewport( 0, 0, fWidth, fHeight ); if (g_pCurrentRenderTarget) g_pCurrentRenderTarget->FinishRenderingTo(); g_pCurrentRenderTarget = nullptr; return; } /* If we already had a render target, disable it. */ if (g_pCurrentRenderTarget != nullptr) SetRenderTarget(0, true); /* Enable the new render target. */ ASSERT(g_mapRenderTargets.find(iTexture) != g_mapRenderTargets.end()); RenderTarget *pTarget = g_mapRenderTargets[iTexture]; pTarget->StartRenderingTo(); g_pCurrentRenderTarget = pTarget; /* Set the viewport to the size of the render target. */ glViewport(0, 0, pTarget->GetParam().iWidth, pTarget->GetParam().iHeight); /* If this render target implementation flips Y, compensate. Inverting will * switch the winding order. */ g_bInvertY = pTarget->InvertY(); if (g_bInvertY) glFrontFace(GL_CW); /* The render target may be in a different OpenGL context, so re-send * state. Push matrixes affected by SetDefaultRenderStates. */ DISPLAY->CameraPushMatrix(); SetDefaultRenderStates(); /* Clear the texture, if requested. Always set the associated state, for * consistency. */ glClearColor(0,0,0,0); SetZWrite(true); /* If bPreserveTexture is false, clear the render target. Only clear the depth * buffer if the target has one; otherwise we're clearing the real depth buffer. */ if (!bPreserveTexture) { int iBit = GL_COLOR_BUFFER_BIT; if (pTarget->GetParam().bWithDepthBuffer) iBit |= GL_DEPTH_BUFFER_BIT; glClear(iBit); } } void RageDisplay_Legacy::SetPolygonMode(PolygonMode pm) { GLenum m; switch (pm) { case POLYGON_FILL: m = GL_FILL; break; case POLYGON_LINE: m = GL_LINE; break; default: FAIL_M(ssprintf("Invalid PolygonMode: %i", pm)); } glPolygonMode(GL_FRONT_AND_BACK, m); } void RageDisplay_Legacy::SetLineWidth(float fWidth) { glLineWidth(fWidth); } RString RageDisplay_Legacy::GetTextureDiagnostics(uintptr_t iTexture) const { /* s << (bGenerateMipMaps? "gluBuild2DMipmaps":"glTexImage2D"); s << "(format " << GLToString(glTexFormat) << ", " << pImg->w << "x" << pImg->h << ", format " << GLToString(iFormat) << ", type " << GLToString(glImageType) << ", pixfmt " << pixfmt << ", imgpixfmt " << SurfacePixFmt << ")"; LOG->Trace( "%s", s.str().c_str() ); glBindTexture( GL_TEXTURE_2D, iTexture ); GLint iWidth; glGetTexLevelParameteriv( GL_TEXTURE_2D, 0, GLenum(GL_TEXTURE_WIDTH), (GLint *) &iWidth ); GLint iHeight; glGetTexLevelParameteriv( GL_TEXTURE_2D, 0, GLenum(GL_TEXTURE_HEIGHT), (GLint *) &iHeight ); GLint iFormat; glGetTexLevelParameteriv( GL_TEXTURE_2D, 0, GLenum(GL_TEXTURE_INTERNAL_FORMAT), (GLint *) &iFormat ); GL_CHECK_ERROR( "glGetTexLevelParameteriv(GL_TEXTURE_INTERNAL_FORMAT)" ); if (iFormat != glTexFormat) { sError = ssprintf( "Expected format %s, got %s instead", GLToString(glTexFormat).c_str(), GLToString(iFormat).c_str() ); break; } */ return RString(); } /* * XXX: Things like this only have to be set once per context - making * SetDefault call These kinds of functions is wasteful. -Colby */ void RageDisplay_Legacy::SetAlphaTest(bool b) { // Previously this was 0.01, rather than 0x01. glAlphaFunc(GL_GREATER, 0.00390625 /* 1/256 */); if (b) glEnable(GL_ALPHA_TEST); else glDisable(GL_ALPHA_TEST); } /* * Although we pair texture formats (eg. GL_RGB8) and surface formats * (pairs of eg. GL_RGB8,GL_UNSIGNED_SHORT_5_5_5_1), it's possible for * a format to be supported for a texture format but not a surface * format. This is abstracted, so you don't need to know about this * as a user calling CreateTexture. * * One case of this is if packed pixels aren't supported. We can still * use 16-bit color modes, but we have to send it in 32-bit. Almost * everything supports packed pixels. * * Another case of this is incomplete packed pixels support. Some implementations * neglect GL_UNSIGNED_SHORT_*_REV. */ bool RageDisplay_Legacy::SupportsSurfaceFormat(RagePixelFormat pixfmt) { switch (g_GLPixFmtInfo[pixfmt].type) { case GL_UNSIGNED_SHORT_1_5_5_5_REV: return GLEW_EXT_bgra && g_bReversePackedPixelsWorks; default: return true; } } bool RageDisplay_Legacy::SupportsTextureFormat(RagePixelFormat pixfmt, bool bRealtime) { /* If we support a pixfmt for texture formats but not for surface formats, then * we'll have to convert the texture to a supported surface format before uploading. * This is too slow for dynamic textures. */ if (bRealtime && !SupportsSurfaceFormat(pixfmt)) return false; switch (g_GLPixFmtInfo[pixfmt].format) { case GL_COLOR_INDEX: return glColorTableEXT && glGetColorTableParameterivEXT; case GL_BGR: case GL_BGRA: return !!GLEW_EXT_bgra; default: return true; } } bool RageDisplay_Legacy::SupportsPerVertexMatrixScale() { // Intel i915 on OSX 10.4.4 supports vertex programs but not hardware vertex buffers. // Our software vertex rendering doesn't support vertex programs. return glGenBuffersARB && g_bTextureMatrixShader != 0; } void RageDisplay_Legacy::SetSphereEnvironmentMapping(TextureUnit tu, bool b) { if (!SetTextureUnit(tu)) return; if (b) { glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP); glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP); glEnable(GL_TEXTURE_GEN_S); glEnable(GL_TEXTURE_GEN_T); } else { glDisable(GL_TEXTURE_GEN_S); glDisable(GL_TEXTURE_GEN_T); } } GLint iCelTexture1, iCelTexture2 = 0; void RageDisplay_Legacy::SetCelShaded( int stage ) { if (!GLEW_ARB_fragment_program && !GL_ARB_shading_language_100) return; // not supported switch (stage) { case 1: glUseProgramObjectARB(g_gShellShader); break; case 2: glUseProgramObjectARB(g_gCelShader); break; default: glUseProgramObjectARB(0); break; } } /* * Copyright (c) 2001-2011 Chris Danford, Glenn Maynard, Colby Klein * All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, and/or sell copies of the Software, and to permit persons to * whom the Software is furnished to do so, provided that the above * copyright notice(s) and this permission notice appear in all copies of * the Software and that both the above copyright notice(s) and this * permission notice appear in supporting documentation. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF * THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS * INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT * OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR * PERFORMANCE OF THIS SOFTWARE. */