2011-03-17 01:47:30 -04:00
|
|
|
#include "global.h"
|
|
|
|
|
#include "RageDisplay.h"
|
|
|
|
|
#include "RageTimer.h"
|
|
|
|
|
#include "RageLog.h"
|
|
|
|
|
#include "RageMath.h"
|
|
|
|
|
#include "RageUtil.h"
|
|
|
|
|
#include "RageFile.h"
|
|
|
|
|
#include "RageSurface_Save_BMP.h"
|
|
|
|
|
#include "RageSurface_Save_JPEG.h"
|
|
|
|
|
#include "RageSurface_Save_PNG.h"
|
|
|
|
|
#include "RageSurfaceUtils_Zoom.h"
|
|
|
|
|
#include "RageSurface.h"
|
|
|
|
|
#include "Preference.h"
|
|
|
|
|
#include "LocalizedString.h"
|
2017-06-18 11:55:16 -04:00
|
|
|
#include "DisplaySpec.h"
|
2011-03-17 01:47:30 -04:00
|
|
|
#include "arch/ArchHooks/ArchHooks.h"
|
|
|
|
|
|
2023-04-19 14:22:59 +02:00
|
|
|
#include <cmath>
|
2023-04-19 23:04:25 +02:00
|
|
|
#include <cstddef>
|
2023-04-20 12:34:12 +02:00
|
|
|
#include <cstdint>
|
2023-04-20 19:02:13 +02:00
|
|
|
#include <vector>
|
|
|
|
|
|
2023-04-19 14:22:59 +02:00
|
|
|
|
2011-03-17 01:47:30 -04:00
|
|
|
// Statistics stuff
|
|
|
|
|
RageTimer g_LastCheckTimer;
|
|
|
|
|
int g_iNumVerts;
|
|
|
|
|
int g_iFPS, g_iVPF, g_iCFPS;
|
|
|
|
|
|
|
|
|
|
int RageDisplay::GetFPS() const { return g_iFPS; }
|
|
|
|
|
int RageDisplay::GetVPF() const { return g_iVPF; }
|
|
|
|
|
int RageDisplay::GetCumFPS() const { return g_iCFPS; }
|
|
|
|
|
|
|
|
|
|
static int g_iFramesRenderedSinceLastCheck,
|
|
|
|
|
g_iFramesRenderedSinceLastReset,
|
|
|
|
|
g_iVertsRenderedSinceLastCheck,
|
|
|
|
|
g_iNumChecksSinceLastReset;
|
|
|
|
|
static RageTimer g_LastFrameEndedAt( RageZeroTimer );
|
|
|
|
|
|
|
|
|
|
struct Centering
|
|
|
|
|
{
|
|
|
|
|
Centering( int iTranslateX = 0, int iTranslateY = 0, int iAddWidth = 0, int iAddHeight = 0 ):
|
|
|
|
|
m_iTranslateX( iTranslateX ),
|
|
|
|
|
m_iTranslateY( iTranslateY ),
|
|
|
|
|
m_iAddWidth( iAddWidth ),
|
|
|
|
|
m_iAddHeight( iAddHeight ) { }
|
|
|
|
|
|
|
|
|
|
int m_iTranslateX, m_iTranslateY, m_iAddWidth, m_iAddHeight;
|
|
|
|
|
};
|
|
|
|
|
|
2022-07-10 18:28:56 +03:00
|
|
|
static std::vector<Centering> g_CenteringStack( 1, Centering(0, 0, 0, 0) );
|
2011-03-17 01:47:30 -04:00
|
|
|
|
2019-06-22 12:35:38 -07:00
|
|
|
RageDisplay* DISPLAY = nullptr; // global and accessible from anywhere in our program
|
2011-03-17 01:47:30 -04:00
|
|
|
|
2024-07-15 05:12:23 -07:00
|
|
|
Preference<bool> LOG_FPS( "LogFPS", false );
|
2011-03-17 01:47:30 -04:00
|
|
|
Preference<float> g_fFrameLimitPercent( "FrameLimitPercent", 0.0f );
|
|
|
|
|
|
2013-02-21 03:18:28 -05:00
|
|
|
static const char *RagePixelFormatNames[] = {
|
2011-03-17 01:47:30 -04:00
|
|
|
"RGBA8",
|
|
|
|
|
"BGRA8",
|
|
|
|
|
"RGBA4",
|
|
|
|
|
"RGB5A1",
|
|
|
|
|
"RGB5",
|
|
|
|
|
"RGB8",
|
|
|
|
|
"PAL",
|
|
|
|
|
"BGR8",
|
|
|
|
|
"A1BGR5",
|
|
|
|
|
"X1RGB5",
|
|
|
|
|
};
|
2013-02-21 03:18:28 -05:00
|
|
|
XToString( RagePixelFormat );
|
2011-03-17 01:47:30 -04:00
|
|
|
|
|
|
|
|
/* bNeedReloadTextures is set to true if the device was re-created and we need
|
2023-04-19 14:22:59 +02:00
|
|
|
* to reload textures. On failure, an error message is returned.
|
2011-03-17 01:47:30 -04:00
|
|
|
* XXX: the renderer itself should probably be the one to try fallback modes */
|
|
|
|
|
static LocalizedString SETVIDEOMODE_FAILED ( "RageDisplay", "SetVideoMode failed:" );
|
|
|
|
|
RString RageDisplay::SetVideoMode( VideoModeParams p, bool &bNeedReloadTextures )
|
|
|
|
|
{
|
|
|
|
|
RString err;
|
2022-07-10 18:28:56 +03:00
|
|
|
std::vector<RString> vs;
|
2011-03-17 01:47:30 -04:00
|
|
|
|
2011-07-20 21:24:31 -04:00
|
|
|
if( (err = this->TryVideoMode(p,bNeedReloadTextures)) == "" )
|
2011-03-17 01:47:30 -04:00
|
|
|
return RString();
|
|
|
|
|
LOG->Trace( "TryVideoMode failed: %s", err.c_str() );
|
|
|
|
|
vs.push_back( err );
|
|
|
|
|
|
|
|
|
|
// fall back to settings that will most likely work
|
|
|
|
|
p.bpp = 16;
|
2011-07-20 21:24:31 -04:00
|
|
|
if( (err = this->TryVideoMode(p,bNeedReloadTextures)) == "" )
|
2011-03-17 01:47:30 -04:00
|
|
|
return RString();
|
|
|
|
|
vs.push_back( err );
|
|
|
|
|
|
2023-04-19 14:22:59 +02:00
|
|
|
// "Intel(R) 82810E Graphics Controller" won't accept a 16 bpp surface if
|
2011-03-17 01:47:30 -04:00
|
|
|
// the desktop is 32 bpp, so try 32 bpp as well.
|
|
|
|
|
p.bpp = 32;
|
2011-07-20 21:24:31 -04:00
|
|
|
if( (err = this->TryVideoMode(p,bNeedReloadTextures)) == "" )
|
2011-03-17 01:47:30 -04:00
|
|
|
return RString();
|
|
|
|
|
vs.push_back( err );
|
|
|
|
|
|
|
|
|
|
// Fall back on a known resolution good rather than 640 x 480.
|
2017-06-18 11:55:16 -04:00
|
|
|
DisplaySpecs dr;
|
|
|
|
|
this->GetDisplaySpecs(dr);
|
2011-03-17 01:47:30 -04:00
|
|
|
if( dr.empty() )
|
|
|
|
|
{
|
|
|
|
|
vs.push_back( "No display resolutions" );
|
|
|
|
|
return SETVIDEOMODE_FAILED.GetValue() + " " + join(";",vs);
|
|
|
|
|
}
|
|
|
|
|
|
2017-06-18 11:55:16 -04:00
|
|
|
DisplaySpec d = *dr.begin();
|
|
|
|
|
// Try to find DisplaySpec corresponding to requested display
|
|
|
|
|
for (const auto &candidate: dr)
|
|
|
|
|
{
|
|
|
|
|
if (candidate.currentMode() != nullptr)
|
|
|
|
|
{
|
|
|
|
|
d = candidate;
|
|
|
|
|
if (candidate.id() == p.sDisplayId)
|
|
|
|
|
{
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
p.sDisplayId = d.id();
|
|
|
|
|
const DisplayMode supported = d.currentMode() != nullptr ? *d.currentMode() : *d.supportedModes().begin();
|
|
|
|
|
p.width = supported.width;
|
|
|
|
|
p.height = supported.height;
|
2023-04-19 14:22:59 +02:00
|
|
|
p.rate = std::round(supported.refreshRate);
|
2011-07-20 21:24:31 -04:00
|
|
|
if( (err = this->TryVideoMode(p,bNeedReloadTextures)) == "" )
|
2011-03-17 01:47:30 -04:00
|
|
|
return RString();
|
|
|
|
|
vs.push_back( err );
|
|
|
|
|
|
|
|
|
|
return SETVIDEOMODE_FAILED.GetValue() + " " + join(";",vs);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void RageDisplay::ProcessStatsOnFlip()
|
|
|
|
|
{
|
|
|
|
|
g_iFramesRenderedSinceLastCheck++;
|
|
|
|
|
g_iFramesRenderedSinceLastReset++;
|
|
|
|
|
|
|
|
|
|
if( g_LastCheckTimer.PeekDeltaTime() >= 1.0f ) // update stats every 1 sec.
|
|
|
|
|
{
|
|
|
|
|
float fActualTime = g_LastCheckTimer.GetDeltaTime();
|
|
|
|
|
g_iNumChecksSinceLastReset++;
|
2023-04-19 14:22:59 +02:00
|
|
|
g_iFPS = std::lrint( g_iFramesRenderedSinceLastCheck / fActualTime );
|
2011-03-17 01:47:30 -04:00
|
|
|
g_iCFPS = g_iFramesRenderedSinceLastReset / g_iNumChecksSinceLastReset;
|
2023-04-19 14:22:59 +02:00
|
|
|
g_iCFPS = std::lrint( g_iCFPS / fActualTime );
|
2011-03-17 01:47:30 -04:00
|
|
|
g_iVPF = g_iVertsRenderedSinceLastCheck / g_iFramesRenderedSinceLastCheck;
|
|
|
|
|
g_iFramesRenderedSinceLastCheck = g_iVertsRenderedSinceLastCheck = 0;
|
|
|
|
|
if( LOG_FPS )
|
|
|
|
|
{
|
|
|
|
|
RString sStats = GetStats();
|
|
|
|
|
sStats.Replace( "\n", ", " );
|
|
|
|
|
LOG->Trace( "%s", sStats.c_str() );
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void RageDisplay::ResetStats()
|
|
|
|
|
{
|
|
|
|
|
g_iFPS = g_iVPF = 0;
|
|
|
|
|
g_iFramesRenderedSinceLastCheck = g_iFramesRenderedSinceLastReset = 0;
|
|
|
|
|
g_iNumChecksSinceLastReset = 0;
|
|
|
|
|
g_iVertsRenderedSinceLastCheck = 0;
|
|
|
|
|
g_LastCheckTimer.GetDeltaTime();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
RString RageDisplay::GetStats() const
|
|
|
|
|
{
|
|
|
|
|
RString s;
|
|
|
|
|
// If FPS == 0, we don't have stats yet.
|
|
|
|
|
if( !GetFPS() )
|
|
|
|
|
s = "-- FPS\n-- av FPS\n-- VPF";
|
|
|
|
|
|
|
|
|
|
s = ssprintf( "%i FPS\n%i av FPS\n%i VPF", GetFPS(), GetCumFPS(), GetVPF() );
|
|
|
|
|
|
2024-06-22 05:00:13 -07:00
|
|
|
// #if defined(_WIN32)
|
2011-03-17 01:47:30 -04:00
|
|
|
s += "\n"+this->GetApiDescription();
|
2012-12-09 03:03:58 -08:00
|
|
|
// #endif
|
2011-03-17 01:47:30 -04:00
|
|
|
|
|
|
|
|
return s;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool RageDisplay::BeginFrame()
|
|
|
|
|
{
|
|
|
|
|
this->SetDefaultRenderStates();
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void RageDisplay::EndFrame()
|
|
|
|
|
{
|
|
|
|
|
ProcessStatsOnFlip();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void RageDisplay::BeginConcurrentRendering()
|
|
|
|
|
{
|
|
|
|
|
this->SetDefaultRenderStates();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void RageDisplay::StatsAddVerts( int iNumVertsRendered ) { g_iVertsRenderedSinceLastCheck += iNumVertsRendered; }
|
|
|
|
|
|
|
|
|
|
/* Draw a line as a quad. GL_LINES with SmoothLines off can draw line
|
|
|
|
|
* ends at odd angles--they're forced to axis-alignment regardless of the
|
|
|
|
|
* angle of the line. */
|
|
|
|
|
void RageDisplay::DrawPolyLine(const RageSpriteVertex &p1, const RageSpriteVertex &p2, float LineWidth )
|
|
|
|
|
{
|
|
|
|
|
// soh cah toa strikes strikes again!
|
|
|
|
|
float opp = p2.p.x - p1.p.x;
|
|
|
|
|
float adj = p2.p.y - p1.p.y;
|
2023-04-19 14:22:59 +02:00
|
|
|
float hyp = std::pow(opp*opp + adj*adj, 0.5f);
|
2011-03-17 01:47:30 -04:00
|
|
|
|
|
|
|
|
float lsin = opp/hyp;
|
|
|
|
|
float lcos = adj/hyp;
|
|
|
|
|
|
|
|
|
|
RageSpriteVertex v[4];
|
|
|
|
|
|
|
|
|
|
v[0] = v[1] = p1;
|
|
|
|
|
v[2] = v[3] = p2;
|
|
|
|
|
|
|
|
|
|
float ydist = lsin * LineWidth/2;
|
|
|
|
|
float xdist = lcos * LineWidth/2;
|
|
|
|
|
|
|
|
|
|
v[0].p.x += xdist;
|
|
|
|
|
v[0].p.y -= ydist;
|
|
|
|
|
v[1].p.x -= xdist;
|
|
|
|
|
v[1].p.y += ydist;
|
|
|
|
|
v[2].p.x -= xdist;
|
|
|
|
|
v[2].p.y += ydist;
|
|
|
|
|
v[3].p.x += xdist;
|
|
|
|
|
v[3].p.y -= ydist;
|
|
|
|
|
|
|
|
|
|
this->DrawQuad(v);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void RageDisplay::DrawLineStripInternal( const RageSpriteVertex v[], int iNumVerts, float LineWidth )
|
|
|
|
|
{
|
|
|
|
|
ASSERT( iNumVerts >= 2 );
|
|
|
|
|
|
|
|
|
|
/* Draw a line strip with rounded corners using polys. This is used on
|
|
|
|
|
* cards that have strange allergic reactions to antialiased points and
|
|
|
|
|
* lines. */
|
|
|
|
|
for( int i = 0; i < iNumVerts-1; ++i )
|
|
|
|
|
DrawPolyLine(v[i], v[i+1], LineWidth);
|
|
|
|
|
|
|
|
|
|
// Join the lines with circles so we get rounded corners.
|
|
|
|
|
for( int i = 0; i < iNumVerts; ++i )
|
|
|
|
|
DrawCircle( v[i], LineWidth/2 );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void RageDisplay::DrawCircleInternal( const RageSpriteVertex &p, float radius )
|
|
|
|
|
{
|
|
|
|
|
const int subdivisions = 32;
|
|
|
|
|
RageSpriteVertex v[subdivisions+2];
|
|
|
|
|
v[0] = p;
|
|
|
|
|
|
2023-04-19 14:22:59 +02:00
|
|
|
for(int i = 0; i < subdivisions+1; ++i)
|
2011-03-17 01:47:30 -04:00
|
|
|
{
|
|
|
|
|
const float fRotation = float(i) / subdivisions * 2*PI;
|
2024-07-17 21:37:27 -07:00
|
|
|
const float fX = std::cos(fRotation) * radius;
|
|
|
|
|
const float fY = -std::sin(fRotation) * radius;
|
2011-03-17 01:47:30 -04:00
|
|
|
v[1+i] = v[0];
|
|
|
|
|
v[1+i].p.x += fX;
|
|
|
|
|
v[1+i].p.y += fY;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this->DrawFan( v, subdivisions+2 );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void RageDisplay::SetDefaultRenderStates()
|
|
|
|
|
{
|
|
|
|
|
SetLighting( false );
|
|
|
|
|
SetCullMode( CULL_NONE );
|
2023-04-19 14:22:59 +02:00
|
|
|
SetZWrite( false );
|
2011-03-17 01:47:30 -04:00
|
|
|
SetZTestMode( ZTEST_OFF );
|
|
|
|
|
SetAlphaTest( true );
|
|
|
|
|
SetBlendMode( BLEND_NORMAL );
|
|
|
|
|
SetTextureFiltering( TextureUnit_1, true );
|
|
|
|
|
SetZBias( 0 );
|
|
|
|
|
LoadMenuPerspective( 0, 640, 480, 320, 240 ); // 0 FOV = ortho
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Matrix stuff
|
|
|
|
|
class MatrixStack
|
|
|
|
|
{
|
2022-07-10 18:28:56 +03:00
|
|
|
std::vector<RageMatrix> stack;
|
2011-03-17 01:47:30 -04:00
|
|
|
public:
|
|
|
|
|
|
2011-03-19 18:26:55 -04:00
|
|
|
MatrixStack(): stack()
|
2011-03-17 01:47:30 -04:00
|
|
|
{
|
|
|
|
|
stack.resize(1);
|
|
|
|
|
LoadIdentity();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Pops the top of the stack.
|
|
|
|
|
void Pop()
|
|
|
|
|
{
|
|
|
|
|
stack.pop_back();
|
|
|
|
|
ASSERT( stack.size() > 0 ); // underflow
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Pushes the stack by one, duplicating the current matrix.
|
|
|
|
|
void Push()
|
|
|
|
|
{
|
|
|
|
|
stack.push_back( stack.back() );
|
|
|
|
|
ASSERT( stack.size() < 100 ); // overflow
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Loads identity in the current matrix.
|
|
|
|
|
void LoadIdentity()
|
|
|
|
|
{
|
|
|
|
|
RageMatrixIdentity( &stack.back() );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Loads the given matrix into the current matrix
|
|
|
|
|
void LoadMatrix( const RageMatrix& m )
|
|
|
|
|
{
|
|
|
|
|
stack.back() = m;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Right-Multiplies the given matrix to the current matrix.
|
|
|
|
|
// (transformation is about the current world origin)
|
|
|
|
|
void MultMatrix( const RageMatrix& m )
|
|
|
|
|
{
|
|
|
|
|
RageMatrixMultiply( &stack.back(), &m, &stack.back() );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Left-Multiplies the given matrix to the current matrix
|
|
|
|
|
// (transformation is about the local origin of the object)
|
|
|
|
|
void MultMatrixLocal( const RageMatrix& m )
|
|
|
|
|
{
|
|
|
|
|
RageMatrixMultiply( &stack.back(), &stack.back(), &m );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Right multiply the current matrix with the computed rotation
|
|
|
|
|
// matrix, counterclockwise about the given axis with the given angle.
|
|
|
|
|
// (rotation is about the current world origin)
|
|
|
|
|
void RotateX( float degrees )
|
|
|
|
|
{
|
|
|
|
|
RageMatrix m;
|
|
|
|
|
RageMatrixRotationX( &m, degrees );
|
|
|
|
|
MultMatrix( m );
|
|
|
|
|
}
|
|
|
|
|
void RotateY( float degrees )
|
|
|
|
|
{
|
|
|
|
|
RageMatrix m;
|
|
|
|
|
RageMatrixRotationY( &m, degrees );
|
|
|
|
|
MultMatrix( m );
|
|
|
|
|
}
|
|
|
|
|
void RotateZ( float degrees )
|
|
|
|
|
{
|
|
|
|
|
RageMatrix m;
|
|
|
|
|
RageMatrixRotationZ( &m, degrees );
|
|
|
|
|
MultMatrix( m );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Left multiply the current matrix with the computed rotation
|
|
|
|
|
// matrix. All angles are counterclockwise. (rotation is about the
|
|
|
|
|
// local origin of the object)
|
|
|
|
|
void RotateXLocal( float degrees )
|
|
|
|
|
{
|
|
|
|
|
RageMatrix m;
|
|
|
|
|
RageMatrixRotationX( &m, degrees );
|
|
|
|
|
MultMatrixLocal( m );
|
|
|
|
|
}
|
|
|
|
|
void RotateYLocal( float degrees )
|
|
|
|
|
{
|
|
|
|
|
RageMatrix m;
|
|
|
|
|
RageMatrixRotationY( &m, degrees );
|
|
|
|
|
MultMatrixLocal( m );
|
|
|
|
|
}
|
|
|
|
|
void RotateZLocal( float degrees )
|
|
|
|
|
{
|
|
|
|
|
RageMatrix m;
|
|
|
|
|
RageMatrixRotationZ( &m, degrees );
|
|
|
|
|
MultMatrixLocal( m );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Right multiply the current matrix with the computed scale
|
|
|
|
|
// matrix. (transformation is about the current world origin)
|
|
|
|
|
void Scale( float x, float y, float z )
|
|
|
|
|
{
|
|
|
|
|
RageMatrix m;
|
|
|
|
|
RageMatrixScaling( &m, x, y, z );
|
|
|
|
|
MultMatrix( m );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Left multiply the current matrix with the computed scale
|
|
|
|
|
// matrix. (transformation is about the local origin of the object)
|
|
|
|
|
void ScaleLocal( float x, float y, float z )
|
|
|
|
|
{
|
|
|
|
|
RageMatrix m;
|
|
|
|
|
RageMatrixScaling( &m, x, y, z );
|
|
|
|
|
MultMatrixLocal( m );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Right multiply the current matrix with the computed translation
|
|
|
|
|
// matrix. (transformation is about the current world origin)
|
|
|
|
|
void Translate( float x, float y, float z )
|
|
|
|
|
{
|
|
|
|
|
RageMatrix m;
|
|
|
|
|
RageMatrixTranslation( &m, x, y, z );
|
|
|
|
|
MultMatrix( m );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Left multiply the current matrix with the computed translation
|
|
|
|
|
// matrix. (transformation is about the local origin of the object)
|
|
|
|
|
void TranslateLocal( float x, float y, float z )
|
|
|
|
|
{
|
|
|
|
|
RageMatrix m;
|
|
|
|
|
RageMatrixTranslation( &m, x, y, z );
|
|
|
|
|
MultMatrixLocal( m );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SkewX( float fAmount )
|
|
|
|
|
{
|
|
|
|
|
RageMatrix m;
|
|
|
|
|
RageMatrixSkewX( &m, fAmount );
|
|
|
|
|
MultMatrixLocal( m );
|
|
|
|
|
}
|
2023-04-19 14:22:59 +02:00
|
|
|
|
2011-03-17 01:47:30 -04:00
|
|
|
void SkewY( float fAmount )
|
|
|
|
|
{
|
|
|
|
|
RageMatrix m;
|
|
|
|
|
RageMatrixSkewY( &m, fAmount );
|
|
|
|
|
MultMatrixLocal( m );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Obtain the current matrix at the top of the stack
|
|
|
|
|
const RageMatrix* GetTop() const { return &stack.back(); }
|
|
|
|
|
void SetTop( const RageMatrix &m ) { stack.back() = m; }
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static RageMatrix g_CenteringMatrix;
|
|
|
|
|
static MatrixStack g_ProjectionStack;
|
|
|
|
|
static MatrixStack g_ViewStack;
|
|
|
|
|
static MatrixStack g_WorldStack;
|
|
|
|
|
static MatrixStack g_TextureStack;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
RageDisplay::RageDisplay()
|
|
|
|
|
{
|
|
|
|
|
RageMatrixIdentity( &g_CenteringMatrix );
|
|
|
|
|
g_ProjectionStack = MatrixStack();
|
|
|
|
|
g_ViewStack = MatrixStack();
|
|
|
|
|
g_WorldStack = MatrixStack();
|
|
|
|
|
g_TextureStack = MatrixStack();
|
|
|
|
|
|
|
|
|
|
// Register with Lua.
|
|
|
|
|
{
|
|
|
|
|
Lua *L = LUA->Get();
|
|
|
|
|
lua_pushstring( L, "DISPLAY" );
|
|
|
|
|
this->PushSelf( L );
|
|
|
|
|
lua_settable( L, LUA_GLOBALSINDEX );
|
|
|
|
|
LUA->Release( L );
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
RageDisplay::~RageDisplay()
|
|
|
|
|
{
|
|
|
|
|
// Unregister with Lua.
|
|
|
|
|
LUA->UnsetGlobal( "DISPLAY" );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const RageMatrix* RageDisplay::GetCentering() const
|
|
|
|
|
{
|
|
|
|
|
return &g_CenteringMatrix;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const RageMatrix* RageDisplay::GetProjectionTop() const
|
|
|
|
|
{
|
|
|
|
|
return g_ProjectionStack.GetTop();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const RageMatrix* RageDisplay::GetViewTop() const
|
|
|
|
|
{
|
|
|
|
|
return g_ViewStack.GetTop();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const RageMatrix* RageDisplay::GetWorldTop() const
|
|
|
|
|
{
|
|
|
|
|
return g_WorldStack.GetTop();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const RageMatrix* RageDisplay::GetTextureTop() const
|
|
|
|
|
{
|
|
|
|
|
return g_TextureStack.GetTop();
|
|
|
|
|
}
|
|
|
|
|
|
2023-04-19 14:22:59 +02:00
|
|
|
void RageDisplay::PushMatrix()
|
|
|
|
|
{
|
2011-03-17 01:47:30 -04:00
|
|
|
g_WorldStack.Push();
|
|
|
|
|
}
|
|
|
|
|
|
2023-04-19 14:22:59 +02:00
|
|
|
void RageDisplay::PopMatrix()
|
|
|
|
|
{
|
2011-03-17 01:47:30 -04:00
|
|
|
g_WorldStack.Pop();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void RageDisplay::Translate( float x, float y, float z )
|
|
|
|
|
{
|
|
|
|
|
g_WorldStack.TranslateLocal(x, y, z);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void RageDisplay::TranslateWorld( float x, float y, float z )
|
|
|
|
|
{
|
|
|
|
|
g_WorldStack.Translate(x, y, z);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void RageDisplay::Scale( float x, float y, float z )
|
|
|
|
|
{
|
|
|
|
|
g_WorldStack.ScaleLocal(x, y, z);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void RageDisplay::RotateX( float deg )
|
|
|
|
|
{
|
|
|
|
|
g_WorldStack.RotateXLocal( deg );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void RageDisplay::RotateY( float deg )
|
|
|
|
|
{
|
|
|
|
|
g_WorldStack.RotateYLocal( deg );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void RageDisplay::RotateZ( float deg )
|
|
|
|
|
{
|
|
|
|
|
g_WorldStack.RotateZLocal( deg );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void RageDisplay::SkewX( float fAmount )
|
|
|
|
|
{
|
|
|
|
|
g_WorldStack.SkewX( fAmount );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void RageDisplay::SkewY( float fAmount )
|
|
|
|
|
{
|
|
|
|
|
g_WorldStack.SkewY( fAmount );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void RageDisplay::PostMultMatrix( const RageMatrix &m )
|
|
|
|
|
{
|
|
|
|
|
g_WorldStack.MultMatrix( m );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void RageDisplay::PreMultMatrix( const RageMatrix &m )
|
|
|
|
|
{
|
|
|
|
|
g_WorldStack.MultMatrixLocal( m );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void RageDisplay::LoadIdentity()
|
|
|
|
|
{
|
|
|
|
|
g_WorldStack.LoadIdentity();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2023-04-19 14:22:59 +02:00
|
|
|
void RageDisplay::TexturePushMatrix()
|
|
|
|
|
{
|
2011-03-17 01:47:30 -04:00
|
|
|
g_TextureStack.Push();
|
|
|
|
|
}
|
|
|
|
|
|
2023-04-19 14:22:59 +02:00
|
|
|
void RageDisplay::TexturePopMatrix()
|
|
|
|
|
{
|
2011-03-17 01:47:30 -04:00
|
|
|
g_TextureStack.Pop();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void RageDisplay::TextureTranslate( float x, float y )
|
|
|
|
|
{
|
|
|
|
|
g_TextureStack.TranslateLocal(x, y, 0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void RageDisplay::LoadMenuPerspective( float fovDegrees, float fWidth, float fHeight, float fVanishPointX, float fVanishPointY )
|
|
|
|
|
{
|
|
|
|
|
// fovDegrees == 0 gives ortho projection.
|
|
|
|
|
if( fovDegrees == 0 )
|
|
|
|
|
{
|
|
|
|
|
float left = 0, right = fWidth, bottom = fHeight, top = 0;
|
|
|
|
|
g_ProjectionStack.LoadMatrix( GetOrthoMatrix(left, right, bottom, top, -1000, +1000) );
|
|
|
|
|
g_ViewStack.LoadIdentity();
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
CLAMP( fovDegrees, 0.1f, 179.9f );
|
|
|
|
|
float fovRadians = fovDegrees / 180.f * PI;
|
|
|
|
|
float theta = fovRadians/2;
|
2023-04-19 14:22:59 +02:00
|
|
|
float fDistCameraFromImage = fWidth/2 / std::tan( theta );
|
2011-03-17 01:47:30 -04:00
|
|
|
|
|
|
|
|
fVanishPointX = SCALE( fVanishPointX, 0, fWidth, fWidth, 0 );
|
|
|
|
|
fVanishPointY = SCALE( fVanishPointY, 0, fHeight, fHeight, 0 );
|
|
|
|
|
|
|
|
|
|
fVanishPointX -= fWidth/2;
|
|
|
|
|
fVanishPointY -= fHeight/2;
|
|
|
|
|
|
|
|
|
|
// It's the caller's responsibility to push first.
|
|
|
|
|
g_ProjectionStack.LoadMatrix(
|
|
|
|
|
GetFrustumMatrix(
|
|
|
|
|
(fVanishPointX-fWidth/2)/fDistCameraFromImage,
|
|
|
|
|
(fVanishPointX+fWidth/2)/fDistCameraFromImage,
|
|
|
|
|
(fVanishPointY+fHeight/2)/fDistCameraFromImage,
|
|
|
|
|
(fVanishPointY-fHeight/2)/fDistCameraFromImage,
|
|
|
|
|
1,
|
|
|
|
|
fDistCameraFromImage+1000 ) );
|
|
|
|
|
|
2023-04-19 14:22:59 +02:00
|
|
|
g_ViewStack.LoadMatrix(
|
2011-03-17 01:47:30 -04:00
|
|
|
RageLookAt(
|
|
|
|
|
-fVanishPointX+fWidth/2, -fVanishPointY+fHeight/2, fDistCameraFromImage,
|
|
|
|
|
-fVanishPointX+fWidth/2, -fVanishPointY+fHeight/2, 0,
|
|
|
|
|
0.0f, 1.0f, 0.0f) );
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void RageDisplay::CameraPushMatrix()
|
|
|
|
|
{
|
|
|
|
|
g_ProjectionStack.Push();
|
|
|
|
|
g_ViewStack.Push();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void RageDisplay::CameraPopMatrix()
|
|
|
|
|
{
|
|
|
|
|
g_ProjectionStack.Pop();
|
|
|
|
|
g_ViewStack.Pop();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* gluLookAt. The result is pre-multiplied to the matrix (M = L * M) instead of
|
|
|
|
|
* post-multiplied. */
|
|
|
|
|
void RageDisplay::LoadLookAt( float fFOV, const RageVector3 &Eye, const RageVector3 &At, const RageVector3 &Up )
|
|
|
|
|
{
|
|
|
|
|
float fAspect = GetActualVideoModeParams().fDisplayAspectRatio;
|
|
|
|
|
g_ProjectionStack.LoadMatrix( GetPerspectiveMatrix(fFOV, fAspect, 1, 1000) );
|
|
|
|
|
|
|
|
|
|
// Flip the Y coordinate, so positive numbers go down.
|
|
|
|
|
g_ProjectionStack.Scale( 1, -1, 1 );
|
|
|
|
|
|
|
|
|
|
g_ViewStack.LoadMatrix( RageLookAt(Eye.x, Eye.y, Eye.z, At.x, At.y, At.z, Up.x, Up.y, Up.z) );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
RageMatrix RageDisplay::GetPerspectiveMatrix(float fovy, float aspect, float zNear, float zFar)
|
|
|
|
|
{
|
2023-04-19 14:22:59 +02:00
|
|
|
float ymax = zNear * std::tan(fovy * PI / 360.0f);
|
2011-03-17 01:47:30 -04:00
|
|
|
float ymin = -ymax;
|
|
|
|
|
float xmin = ymin * aspect;
|
|
|
|
|
float xmax = ymax * aspect;
|
|
|
|
|
|
|
|
|
|
return GetFrustumMatrix(xmin, xmax, ymin, ymax, zNear, zFar);
|
|
|
|
|
}
|
|
|
|
|
|
2013-02-21 03:18:28 -05:00
|
|
|
RageSurface *RageDisplay::CreateSurfaceFromPixfmt( RagePixelFormat pixfmt,
|
2011-03-17 01:47:30 -04:00
|
|
|
void *pixels, int width, int height, int pitch )
|
|
|
|
|
{
|
2013-02-21 03:18:28 -05:00
|
|
|
const RagePixelFormatDesc *tpf = GetPixelFormatDesc(pixfmt);
|
2011-03-17 01:47:30 -04:00
|
|
|
|
|
|
|
|
RageSurface *surf = CreateSurfaceFrom(
|
2023-04-19 14:22:59 +02:00
|
|
|
width, height, tpf->bpp,
|
2011-03-17 01:47:30 -04:00
|
|
|
tpf->masks[0], tpf->masks[1], tpf->masks[2], tpf->masks[3],
|
2023-04-20 12:34:12 +02:00
|
|
|
(std::uint8_t *) pixels, pitch );
|
2011-03-17 01:47:30 -04:00
|
|
|
|
|
|
|
|
return surf;
|
|
|
|
|
}
|
|
|
|
|
|
2013-02-21 03:18:28 -05:00
|
|
|
RagePixelFormat RageDisplay::FindPixelFormat( int iBPP, unsigned iRmask, unsigned iGmask, unsigned iBmask, unsigned iAmask, bool bRealtime )
|
2011-03-17 01:47:30 -04:00
|
|
|
{
|
2013-02-21 03:18:28 -05:00
|
|
|
RagePixelFormatDesc tmp = { iBPP, { iRmask, iGmask, iBmask, iAmask } };
|
2011-03-17 01:47:30 -04:00
|
|
|
|
2013-02-21 03:18:28 -05:00
|
|
|
FOREACH_ENUM( RagePixelFormat, iPixFmt )
|
2011-03-17 01:47:30 -04:00
|
|
|
{
|
2013-02-21 03:18:28 -05:00
|
|
|
const RagePixelFormatDesc *pf = GetPixelFormatDesc( RagePixelFormat(iPixFmt) );
|
|
|
|
|
if( !SupportsTextureFormat(RagePixelFormat(iPixFmt), bRealtime) )
|
2011-03-17 01:47:30 -04:00
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
if( memcmp(pf, &tmp, sizeof(tmp)) )
|
|
|
|
|
continue;
|
|
|
|
|
return iPixFmt;
|
|
|
|
|
}
|
|
|
|
|
|
2013-02-21 03:18:28 -05:00
|
|
|
return RagePixelFormat_Invalid;
|
2011-03-17 01:47:30 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* These convert to OpenGL's coordinate system: -1,-1 is the bottom-left,
|
|
|
|
|
* +1,+1 is the top-right, and Z goes from -1 (viewer) to +1 (distance).
|
|
|
|
|
* It's a little odd, but very well-defined. */
|
|
|
|
|
RageMatrix RageDisplay::GetOrthoMatrix( float l, float r, float b, float t, float zn, float zf )
|
|
|
|
|
{
|
|
|
|
|
RageMatrix m(
|
|
|
|
|
2/(r-l), 0, 0, 0,
|
|
|
|
|
0, 2/(t-b), 0, 0,
|
|
|
|
|
0, 0, -2/(zf-zn), 0,
|
|
|
|
|
-(r+l)/(r-l), -(t+b)/(t-b), -(zf+zn)/(zf-zn), 1 );
|
|
|
|
|
return m;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
RageMatrix RageDisplay::GetFrustumMatrix( float l, float r, float b, float t, float zn, float zf )
|
|
|
|
|
{
|
|
|
|
|
// glFrustum
|
|
|
|
|
float A = (r+l) / (r-l);
|
|
|
|
|
float B = (t+b) / (t-b);
|
|
|
|
|
float C = -1 * (zf+zn) / (zf-zn);
|
|
|
|
|
float D = -1 * (2*zf*zn) / (zf-zn);
|
|
|
|
|
RageMatrix m(
|
|
|
|
|
2*zn/(r-l), 0, 0, 0,
|
|
|
|
|
0, 2*zn/(t-b), 0, 0,
|
|
|
|
|
A, B, C, -1,
|
|
|
|
|
0, 0, D, 0 );
|
|
|
|
|
return m;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void RageDisplay::ResolutionChanged()
|
|
|
|
|
{
|
|
|
|
|
// The centering matrix depends on the resolution.
|
|
|
|
|
UpdateCentering();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void RageDisplay::CenteringPushMatrix()
|
|
|
|
|
{
|
|
|
|
|
g_CenteringStack.push_back( g_CenteringStack.back() );
|
|
|
|
|
ASSERT( g_CenteringStack.size() < 100 ); // overflow
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void RageDisplay::CenteringPopMatrix()
|
|
|
|
|
{
|
|
|
|
|
g_CenteringStack.pop_back();
|
|
|
|
|
ASSERT( g_CenteringStack.size() > 0 ); // underflow
|
|
|
|
|
UpdateCentering();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void RageDisplay::ChangeCentering( int iTranslateX, int iTranslateY, int iAddWidth, int iAddHeight )
|
|
|
|
|
{
|
|
|
|
|
g_CenteringStack.back() = Centering( iTranslateX, iTranslateY, iAddWidth, iAddHeight );
|
|
|
|
|
|
|
|
|
|
UpdateCentering();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
RageMatrix RageDisplay::GetCenteringMatrix( float fTranslateX, float fTranslateY, float fAddWidth, float fAddHeight ) const
|
|
|
|
|
{
|
|
|
|
|
// in screen space, left edge = -1, right edge = 1, bottom edge = -1. top edge = 1
|
2017-06-18 11:55:16 -04:00
|
|
|
float fWidth = (float) GetActualVideoModeParams().windowWidth;
|
|
|
|
|
float fHeight = (float) GetActualVideoModeParams().windowHeight;
|
2011-03-17 01:47:30 -04:00
|
|
|
float fPercentShiftX = SCALE( fTranslateX, 0, fWidth, 0, +2.0f );
|
|
|
|
|
float fPercentShiftY = SCALE( fTranslateY, 0, fHeight, 0, -2.0f );
|
|
|
|
|
float fPercentScaleX = SCALE( fAddWidth, 0, fWidth, 1.0f, 2.0f );
|
|
|
|
|
float fPercentScaleY = SCALE( fAddHeight, 0, fHeight, 1.0f, 2.0f );
|
|
|
|
|
|
|
|
|
|
RageMatrix m1;
|
|
|
|
|
RageMatrix m2;
|
2023-04-19 14:22:59 +02:00
|
|
|
RageMatrixTranslation(
|
|
|
|
|
&m1,
|
|
|
|
|
fPercentShiftX,
|
|
|
|
|
fPercentShiftY,
|
2011-03-17 01:47:30 -04:00
|
|
|
0 );
|
2023-04-19 14:22:59 +02:00
|
|
|
RageMatrixScaling(
|
|
|
|
|
&m2,
|
|
|
|
|
fPercentScaleX,
|
|
|
|
|
fPercentScaleY,
|
2011-03-17 01:47:30 -04:00
|
|
|
1 );
|
|
|
|
|
RageMatrix mOut;
|
|
|
|
|
RageMatrixMultiply( &mOut, &m1, &m2 );
|
|
|
|
|
return mOut;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void RageDisplay::UpdateCentering()
|
|
|
|
|
{
|
|
|
|
|
const Centering &p = g_CenteringStack.back();
|
2023-04-19 14:22:59 +02:00
|
|
|
g_CenteringMatrix = GetCenteringMatrix(
|
2011-03-17 01:47:30 -04:00
|
|
|
(float) p.m_iTranslateX, (float) p.m_iTranslateY, (float) p.m_iAddWidth, (float) p.m_iAddHeight );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool RageDisplay::SaveScreenshot( RString sPath, GraphicsFileFormat format )
|
|
|
|
|
{
|
|
|
|
|
RageTimer timer;
|
|
|
|
|
RageSurface *surface = this->CreateScreenshot();
|
|
|
|
|
// LOG->Trace( "CreateScreenshot took %f seconds", timer.GetDeltaTime() );
|
2023-04-19 14:22:59 +02:00
|
|
|
|
2019-10-04 09:00:24 -04:00
|
|
|
if (nullptr == surface)
|
|
|
|
|
{
|
|
|
|
|
LOG->Trace("CreateScreenshot failed to return a surface");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2023-04-19 14:22:59 +02:00
|
|
|
|
2011-03-17 01:47:30 -04:00
|
|
|
/* Unless we're in lossless, resize the image to 640x480. If we're saving lossy,
|
|
|
|
|
* there's no sense in saving 1280x960 screenshots, and we don't want to output
|
|
|
|
|
* screenshots in a strange (non-1) sample aspect ratio. */
|
|
|
|
|
if( format != SAVE_LOSSLESS && format != SAVE_LOSSLESS_SENSIBLE )
|
|
|
|
|
{
|
|
|
|
|
// Maintain the DAR.
|
|
|
|
|
ASSERT( GetActualVideoModeParams().fDisplayAspectRatio > 0 );
|
|
|
|
|
int iHeight = 480;
|
2023-04-19 14:22:59 +02:00
|
|
|
// This used to be lrint. However, lrint causes odd resolutions like
|
|
|
|
|
// 639x480 (4:3) and 853x480 (16:9). ceil gives correct values. -aj
|
|
|
|
|
int iWidth = std::ceil( iHeight * GetActualVideoModeParams().fDisplayAspectRatio );
|
2011-03-17 01:47:30 -04:00
|
|
|
timer.Touch();
|
|
|
|
|
RageSurfaceUtils::Zoom( surface, iWidth, iHeight );
|
|
|
|
|
// LOG->Trace( "%ix%i -> %ix%i (%.3f) in %f seconds", surface->w, surface->h, iWidth, iHeight, GetActualVideoModeParams().fDisplayAspectRatio, timer.GetDeltaTime() );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
RageFile out;
|
|
|
|
|
if( !out.Open( sPath, RageFile::WRITE ) )
|
|
|
|
|
{
|
|
|
|
|
LOG->Trace("Couldn't write %s: %s", sPath.c_str(), out.GetError().c_str() );
|
|
|
|
|
SAFE_DELETE( surface );
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool bSuccess = false;
|
|
|
|
|
timer.Touch();
|
|
|
|
|
RString strError = "";
|
|
|
|
|
switch( format )
|
|
|
|
|
{
|
|
|
|
|
case SAVE_LOSSLESS:
|
|
|
|
|
bSuccess = RageSurfaceUtils::SaveBMP( surface, out );
|
|
|
|
|
break;
|
|
|
|
|
case SAVE_LOSSLESS_SENSIBLE:
|
|
|
|
|
bSuccess = RageSurfaceUtils::SavePNG( surface, out, strError );
|
|
|
|
|
break;
|
|
|
|
|
case SAVE_LOSSY_LOW_QUAL:
|
|
|
|
|
bSuccess = RageSurfaceUtils::SaveJPEG( surface, out, false );
|
|
|
|
|
break;
|
|
|
|
|
case SAVE_LOSSY_HIGH_QUAL:
|
|
|
|
|
bSuccess = RageSurfaceUtils::SaveJPEG( surface, out, true );
|
|
|
|
|
break;
|
|
|
|
|
DEFAULT_FAIL( format );
|
|
|
|
|
}
|
|
|
|
|
// LOG->Trace( "Saving Screenshot file took %f seconds.", timer.GetDeltaTime() );
|
|
|
|
|
|
|
|
|
|
SAFE_DELETE( surface );
|
|
|
|
|
|
|
|
|
|
if( !bSuccess )
|
|
|
|
|
{
|
|
|
|
|
LOG->Trace("Couldn't write %s: %s", sPath.c_str(), out.GetError().c_str() );
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void RageDisplay::DrawQuads( const RageSpriteVertex v[], int iNumVerts )
|
|
|
|
|
{
|
|
|
|
|
ASSERT( (iNumVerts%4) == 0 );
|
|
|
|
|
|
|
|
|
|
if(!iNumVerts)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
this->DrawQuadsInternal(v,iNumVerts);
|
|
|
|
|
|
|
|
|
|
StatsAddVerts(iNumVerts);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void RageDisplay::DrawQuadStrip( const RageSpriteVertex v[], int iNumVerts )
|
|
|
|
|
{
|
|
|
|
|
ASSERT( (iNumVerts%2) == 0 );
|
|
|
|
|
|
|
|
|
|
if(iNumVerts < 4)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
this->DrawQuadStripInternal(v,iNumVerts);
|
|
|
|
|
|
|
|
|
|
StatsAddVerts(iNumVerts);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void RageDisplay::DrawFan( const RageSpriteVertex v[], int iNumVerts )
|
|
|
|
|
{
|
|
|
|
|
ASSERT( iNumVerts >= 3 );
|
|
|
|
|
|
|
|
|
|
this->DrawFanInternal(v,iNumVerts);
|
|
|
|
|
|
|
|
|
|
StatsAddVerts(iNumVerts);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void RageDisplay::DrawStrip( const RageSpriteVertex v[], int iNumVerts )
|
|
|
|
|
{
|
|
|
|
|
ASSERT( iNumVerts >= 3 );
|
|
|
|
|
|
|
|
|
|
this->DrawStripInternal(v,iNumVerts);
|
|
|
|
|
|
2023-04-19 14:22:59 +02:00
|
|
|
StatsAddVerts(iNumVerts);
|
2011-03-17 01:47:30 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void RageDisplay::DrawTriangles( const RageSpriteVertex v[], int iNumVerts )
|
|
|
|
|
{
|
|
|
|
|
if( iNumVerts == 0 )
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
ASSERT( iNumVerts >= 3 );
|
|
|
|
|
|
|
|
|
|
this->DrawTrianglesInternal(v,iNumVerts);
|
|
|
|
|
|
|
|
|
|
StatsAddVerts(iNumVerts);
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-10 18:28:56 +03:00
|
|
|
void RageDisplay::DrawCompiledGeometry( const RageCompiledGeometry *p, int iMeshIndex, const std::vector<msMesh> &vMeshes )
|
2011-03-17 01:47:30 -04:00
|
|
|
{
|
|
|
|
|
this->DrawCompiledGeometryInternal( p, iMeshIndex );
|
|
|
|
|
|
2023-04-19 14:22:59 +02:00
|
|
|
StatsAddVerts( vMeshes[iMeshIndex].Triangles.size() );
|
2011-03-17 01:47:30 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void RageDisplay::DrawLineStrip( const RageSpriteVertex v[], int iNumVerts, float LineWidth )
|
|
|
|
|
{
|
|
|
|
|
ASSERT( iNumVerts >= 2 );
|
|
|
|
|
|
|
|
|
|
this->DrawLineStripInternal( v, iNumVerts, LineWidth );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Draw a strip of:
|
|
|
|
|
*
|
|
|
|
|
* 0..1..2
|
|
|
|
|
* . /.\ .
|
|
|
|
|
* ./ . \.
|
|
|
|
|
* 3..4..5
|
|
|
|
|
* . /.\ .
|
|
|
|
|
* ./ . \.
|
|
|
|
|
* 6..7..8
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
void RageDisplay::DrawSymmetricQuadStrip( const RageSpriteVertex v[], int iNumVerts )
|
|
|
|
|
{
|
|
|
|
|
ASSERT( iNumVerts >= 3 );
|
|
|
|
|
|
|
|
|
|
if( iNumVerts < 6 )
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
this->DrawSymmetricQuadStripInternal( v, iNumVerts );
|
|
|
|
|
|
|
|
|
|
StatsAddVerts( iNumVerts );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void RageDisplay::DrawCircle( const RageSpriteVertex &v, float radius )
|
|
|
|
|
{
|
|
|
|
|
this->DrawCircleInternal( v, radius );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void RageDisplay::FrameLimitBeforeVsync( int iFPS )
|
|
|
|
|
{
|
|
|
|
|
ASSERT( iFPS != 0 );
|
|
|
|
|
|
|
|
|
|
int iDelayMicroseconds = 0;
|
|
|
|
|
if( g_fFrameLimitPercent.Get() > 0.0f && !g_LastFrameEndedAt.IsZero() )
|
|
|
|
|
{
|
|
|
|
|
float fFrameTime = g_LastFrameEndedAt.GetDeltaTime();
|
|
|
|
|
float fExpectedTime = 1.0f / iFPS;
|
|
|
|
|
|
|
|
|
|
/* This is typically used to turn some of the delay that would normally
|
|
|
|
|
* be waiting for vsync and turn it into a usleep, to make sure we give
|
|
|
|
|
* up the CPU. If we overshoot the sleep, then we'll miss the vsync,
|
|
|
|
|
* so allow tweaking the amount of time we expect a frame to take.
|
|
|
|
|
* Frame limiting is disabled by setting this to 0. */
|
|
|
|
|
fExpectedTime *= g_fFrameLimitPercent.Get();
|
|
|
|
|
float fExtraTime = fExpectedTime - fFrameTime;
|
|
|
|
|
|
|
|
|
|
iDelayMicroseconds = int(fExtraTime * 1000000);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if( !HOOKS->AppHasFocus() )
|
2022-07-10 18:28:56 +03:00
|
|
|
iDelayMicroseconds = std::max( iDelayMicroseconds, 10000 ); // give some time to other processes and threads
|
2011-03-17 01:47:30 -04:00
|
|
|
|
|
|
|
|
if( iDelayMicroseconds > 0 )
|
|
|
|
|
usleep( iDelayMicroseconds );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void RageDisplay::FrameLimitAfterVsync()
|
|
|
|
|
{
|
|
|
|
|
if( g_fFrameLimitPercent.Get() == 0.0f )
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
g_LastFrameEndedAt.Touch();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
RageCompiledGeometry::~RageCompiledGeometry()
|
|
|
|
|
{
|
|
|
|
|
m_bNeedsNormals = false;
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-10 18:28:56 +03:00
|
|
|
void RageCompiledGeometry::Set( const std::vector<msMesh> &vMeshes, bool bNeedsNormals )
|
2011-03-17 01:47:30 -04:00
|
|
|
{
|
|
|
|
|
m_bNeedsNormals = bNeedsNormals;
|
|
|
|
|
|
2023-04-19 23:04:25 +02:00
|
|
|
std::size_t totalVerts = 0;
|
|
|
|
|
std::size_t totalTriangles = 0;
|
2011-03-17 01:47:30 -04:00
|
|
|
|
|
|
|
|
m_bAnyNeedsTextureMatrixScale = false;
|
|
|
|
|
|
|
|
|
|
m_vMeshInfo.resize( vMeshes.size() );
|
|
|
|
|
for( unsigned i=0; i<vMeshes.size(); i++ )
|
|
|
|
|
{
|
|
|
|
|
const msMesh& mesh = vMeshes[i];
|
2022-07-10 18:28:56 +03:00
|
|
|
const std::vector<RageModelVertex> &Vertices = mesh.Vertices;
|
|
|
|
|
const std::vector<msTriangle> &Triangles = mesh.Triangles;
|
2011-03-17 01:47:30 -04:00
|
|
|
|
|
|
|
|
MeshInfo& meshInfo = m_vMeshInfo[i];
|
|
|
|
|
meshInfo.m_bNeedsTextureMatrixScale = false;
|
|
|
|
|
|
|
|
|
|
meshInfo.iVertexStart = totalVerts;
|
|
|
|
|
meshInfo.iVertexCount = Vertices.size();
|
|
|
|
|
meshInfo.iTriangleStart = totalTriangles;
|
|
|
|
|
meshInfo.iTriangleCount = Triangles.size();
|
|
|
|
|
|
|
|
|
|
totalVerts += Vertices.size();
|
|
|
|
|
totalTriangles += Triangles.size();
|
|
|
|
|
|
|
|
|
|
for( unsigned j = 0; j < Vertices.size(); ++j )
|
|
|
|
|
{
|
|
|
|
|
if( Vertices[j].TextureMatrixScale.x != 1.0f || Vertices[j].TextureMatrixScale.y != 1.0f )
|
|
|
|
|
{
|
|
|
|
|
meshInfo.m_bNeedsTextureMatrixScale = true;
|
|
|
|
|
m_bAnyNeedsTextureMatrixScale = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this->Allocate( vMeshes );
|
|
|
|
|
|
|
|
|
|
Change( vMeshes );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// lua start
|
|
|
|
|
#include "LuaBinding.h"
|
|
|
|
|
|
2017-06-18 11:55:16 -04:00
|
|
|
// Register with Lua.
|
|
|
|
|
static void register_REFRESH_DEFAULT(lua_State *L)
|
|
|
|
|
{
|
|
|
|
|
lua_pushstring( L, "REFRESH_DEFAULT" );
|
|
|
|
|
lua_pushinteger( L, REFRESH_DEFAULT );
|
|
|
|
|
lua_settable( L, LUA_GLOBALSINDEX);
|
|
|
|
|
}
|
|
|
|
|
REGISTER_WITH_LUA_FUNCTION( register_REFRESH_DEFAULT );
|
|
|
|
|
|
|
|
|
|
|
2023-04-19 14:22:59 +02:00
|
|
|
/** @brief Allow Lua to have access to the RageDisplay. */
|
2011-03-17 01:47:30 -04:00
|
|
|
class LunaRageDisplay: public Luna<RageDisplay>
|
|
|
|
|
{
|
|
|
|
|
public:
|
|
|
|
|
static int GetDisplayWidth( T* p, lua_State *L )
|
|
|
|
|
{
|
|
|
|
|
VideoModeParams params = p->GetActualVideoModeParams();
|
|
|
|
|
LuaHelpers::Push( L, params.width );
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int GetDisplayHeight( T* p, lua_State *L )
|
|
|
|
|
{
|
|
|
|
|
VideoModeParams params = p->GetActualVideoModeParams();
|
|
|
|
|
LuaHelpers::Push( L, params.height );
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
2023-04-19 14:22:59 +02:00
|
|
|
|
2011-10-07 21:14:39 -04:00
|
|
|
static int GetFPS( T* p, lua_State *L )
|
|
|
|
|
{
|
|
|
|
|
lua_pushnumber(L, p->GetFPS());
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
2023-04-19 14:22:59 +02:00
|
|
|
|
2011-10-07 21:14:39 -04:00
|
|
|
static int GetVPF( T* p, lua_State *L )
|
|
|
|
|
{
|
|
|
|
|
lua_pushnumber(L, p->GetVPF());
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
2023-04-19 14:22:59 +02:00
|
|
|
|
2011-10-07 21:14:39 -04:00
|
|
|
static int GetCumFPS( T* p, lua_State *L )
|
|
|
|
|
{
|
|
|
|
|
lua_pushnumber(L, p->GetCumFPS());
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
2011-03-17 01:47:30 -04:00
|
|
|
|
2017-06-18 11:55:16 -04:00
|
|
|
static int GetDisplaySpecs( T* p, lua_State *L )
|
|
|
|
|
{
|
|
|
|
|
DisplaySpecs s;
|
|
|
|
|
p->GetDisplaySpecs(s);
|
|
|
|
|
pushDisplaySpecs(L, s);
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int SupportsRenderToTexture( T* p, lua_State *L )
|
|
|
|
|
{
|
|
|
|
|
lua_pushboolean(L, p->SupportsRenderToTexture());
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int SupportsFullscreenBorderlessWindow( T* p, lua_State *L )
|
|
|
|
|
{
|
|
|
|
|
lua_pushboolean(L, p->SupportsFullscreenBorderlessWindow());
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
|
2023-04-19 14:22:59 +02:00
|
|
|
LunaRageDisplay()
|
2011-03-17 01:47:30 -04:00
|
|
|
{
|
|
|
|
|
ADD_METHOD( GetDisplayWidth );
|
|
|
|
|
ADD_METHOD( GetDisplayHeight );
|
2011-10-07 21:14:39 -04:00
|
|
|
ADD_METHOD( GetFPS );
|
|
|
|
|
ADD_METHOD( GetVPF );
|
|
|
|
|
ADD_METHOD( GetCumFPS );
|
2017-06-18 11:55:16 -04:00
|
|
|
ADD_METHOD( GetDisplaySpecs );
|
|
|
|
|
ADD_METHOD( SupportsRenderToTexture );
|
2019-03-28 15:05:07 -07:00
|
|
|
ADD_METHOD( SupportsFullscreenBorderlessWindow );
|
2011-03-17 01:47:30 -04:00
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
LUA_REGISTER_CLASS( RageDisplay )
|
|
|
|
|
// lua end
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Copyright (c) 2001-2004 Chris Danford, Glenn Maynard
|
|
|
|
|
* 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.
|
|
|
|
|
*/
|
|
|
|
|
|