Merge pull request #578 from DanielGibson/soft-particles

Implement Soft Particles (and capturing depth buffer), based on old The Dark Mod code
This commit is contained in:
Daniel Gibson 2024-07-25 04:27:35 +02:00 committed by GitHub
commit d3b2794c24
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
22 changed files with 787 additions and 87 deletions

View file

@ -14,6 +14,11 @@ Note: Numbers starting with a "#" like #330 refer to the bugreport with that num
It can also be opened while in the game, which then is paused (if Single Player) but still visible,
so the effect of most graphics settings can be seen immediately.
Needs SDL2 and C++11.
* "Soft" Particles (that don't "cut" into geometry but fade smoothly), based on code from The Dark Mod
2.04. Can be enabled/disabled with `r_useSoftParticles`, is applied automatically for all appropriate
particles (view-aligned, using additive or alpha blending and not too small)
* `r_enableDepthCapture`: Enable capturing depth buffer to texture, needed for the soft particles.
Can be used in custom materials by using the `"_currentDepth"` texture
* Replaced dependency on (external) zlib with integrated [miniz](https://github.com/richgel999/miniz)
* HighDPI/Retina support
* Allow inverted mouse look (horizontally, vertically or both) with `m_invertLook`
@ -28,7 +33,15 @@ Note: Numbers starting with a "#" like #330 refer to the bugreport with that num
downscaling by OpenAL's output limiter
* If `r_windowResizable` is set, the dhewm3 window (when in windowed mode..) can be freely resized.
Needs SDL2; with 2.0.5 and newer it's applied immediately, otherwise when creating the window.
* If switching between fullscreen and windowed mode or similar changes causes issues (like
[here](https://github.com/dhewm/dhewm3/issues/587#issuecomment-2205807989)), you can set
`r_vidRestartAlwaysFull 1`, so (again) a full `vid_restart` is done, instead of the partial one
which *usually* suffices for just changing the resolution or fullscreen state. If you run into that
issue (probably a driver bug), you'll probably also want to set `r_windowResizable 0`, because
resizing the window that way also triggered the bug, and in that case no `vid_restart` is done at all
* Fixed screenshots when using native Wayland (`SDL_VIDEODRIVER=wayland`)
* If you enter the `map` command in the console, without any arguments, the current map name is printed
* Support OpenGL debug contexts and messages (`GL_ARB_debug_output`). Can be enabled with `r_glDebugContext 1`. Changing that CVar requires a `vid_restart` (or set it as startup argument)
1.5.3 (2024-03-29)
------------------------------------------------------------------------

View file

@ -210,6 +210,11 @@ This can be configured with the following CVars:
at the end of each frame. Needed at least when using Wayland.
`1`: do this, `0`: don't do it, `-1`: let dhewm3 decide (default)
- `r_useSoftParticles` Soften particle transitions when player walks through them or they cross solid geometry.
Needs r_enableDepthCapture. Can slow down rendering! `1`: enable (default), `0`: disable
- `r_enableDepthCapture` Enable capturing depth buffer to texture. `0`: disable, `1`: enable,
`-1`: enable automatically (if soft particles are enabled; the default).
This can be used in custom materials with the "_currentDepth" texture.
- `r_useCarmacksReverse` Use Z-Fail ("Carmack's Reverse") when rendering shadows (default `1`)
- `r_useStencilOpSeparate` Use glStencilOpSeparate() (if available) when rendering shadow (default `1`)
- `r_scaleMenusTo43` Render full-screen menus in 4:3 by adding black bars on the left/right if necessary (default `1`)

View file

@ -322,9 +322,9 @@ if(D3_COMPILER_IS_GCC_OR_CLANG)
set(CMAKE_C_FLAGS_DEBUG "-g -ggdb -D_DEBUG -O0")
set(CMAKE_C_FLAGS_DEBUGALL "-g -ggdb -D_DEBUG")
set(CMAKE_C_FLAGS_PROFILE "-g -ggdb -D_DEBUG -O1 -fno-omit-frame-pointer")
set(CMAKE_C_FLAGS_RELEASE "-O2 -fno-math-errno -fno-trapping-math -fomit-frame-pointer")
set(CMAKE_C_FLAGS_RELWITHDEBINFO "-g -ggdb -O2 -fno-math-errno -fno-trapping-math -fno-omit-frame-pointer")
set(CMAKE_C_FLAGS_MINSIZEREL "-Os -fno-math-errno -fno-trapping-math -fomit-frame-pointer")
set(CMAKE_C_FLAGS_RELEASE "-O2 -fno-math-errno -fno-trapping-math -ffinite-math-only -fomit-frame-pointer")
set(CMAKE_C_FLAGS_RELWITHDEBINFO "-g -ggdb -O2 -fno-math-errno -fno-trapping-math -ffinite-math-only -fno-omit-frame-pointer")
set(CMAKE_C_FLAGS_MINSIZEREL "-Os -fno-math-errno -fno-trapping-math -ffinite-math-only -fomit-frame-pointer")
set(CMAKE_CXX_FLAGS_DEBUGALL ${CMAKE_C_FLAGS_DEBUGALL})
set(CMAKE_CXX_FLAGS_PROFILE ${CMAKE_C_FLAGS_PROFILE})

View file

@ -409,6 +409,13 @@ idParticleStage *idDeclParticle::ParseParticleStage( idLexer &src ) {
stage->gravity = src.ParseFloat();
continue;
}
if ( !token.Icmp( "softeningRadius" ) ) { // #3878 soft particles
common->Warning( "Particle %s from %s has stage with \"softeningRadius\" attribute, which is currently ignored (we soften all suitable particles)\n",
this->GetName(), src.GetFileName() );
// DG: disable this for now to avoid breaking the game ABI
//stage->softeningRadius = src.ParseFloat();
continue;
}
src.Error( "unknown token %s\n", token.c_str() );
}
@ -733,6 +740,9 @@ idParticleStage::idParticleStage( void ) {
hidden = false;
boundsExpansion = 0.0f;
bounds.Clear();
// DG: disable softeningRadius for now to avoid breaking the game ABI
// (will always behave like if softeningRadius = -2.0f)
//softeningRadius = -2.0f; // -2 means "auto" - #3878 soft particles
}
/*
@ -806,6 +816,8 @@ void idParticleStage::Default() {
randomDistribution = true;
entityColor = false;
cycleMsec = ( particleLife + deadTime ) * 1000;
// DG: disable softeningRadius for now to avoid breaking game ABI
//softeningRadius = -2.0f; // -2 means "auto" - #3878 soft particles
}
/*

View file

@ -193,6 +193,18 @@ public:
float boundsExpansion; // user tweak to fix poorly calculated bounds
idBounds bounds; // derived
/* Soft particles -- SteveL #3878
-2.0 is the value at initialization, meaning no user specification: "auto".
-1.0 means no change to old system: suppress soft particles, but allow modelDepthhack if specified.
0 means disable all softening for this stage, including modelDepthHack.
+ve value means apply soft particle effect, allowing overdraw up to the specified depth.
This is more flexible even when not using soft particles, as modelDepthHack
can be turned off for specific stages to stop them poking through walls.
*/
// DG: disable this for now because it breaks the game DLL's ABI (re-enable in dhewm3 1.6.0 or 2.0.0)
// (this header is part of the SDK)
//float softeningRadius;
};

View file

@ -1142,6 +1142,14 @@ static void InitBindingEntries()
bindingEntries.Append( BindingEntry( bet ) );
}
// player.def defines, in player_base, used by player_doommarine and player_doommarine_mp (and player_doommarine_ctf),
// "def_weapon0" "weapon_fists", "def_weapon1" "weapon_pistol" etc
// => get all those definitions (up to MAX_WEAPONS=16) from Player, and then
// get the entities for the corresponding keys ("weapon_fists" etc),
// which should have an entry like "inv_name" "Pistol" (could also be #str_00100207 though!)
// hardcorps uses: idCVar pm_character("pm_character", "0", CVAR_GAME | CVAR_BOOL, "Change Player character. 1 = Scarlet. 0 = Doom Marine");
// but I guess (hope) they use the same weapons..
const idDict* playerDict = GetEntityDefDict( "player_doommarine" );
const idDict* playerDictMP = GetEntityDefDict( "player_doommarine_mp" );
bool impulse27used = false;
@ -1209,15 +1217,6 @@ static void InitBindingEntries()
idStr impName = idStr::Format( "_impulse%d", i );
bindingEntries.Append( BindingEntry( impName, impName ) );
}
// player.def defines, in player_base, used by player_doommarine and player_doommarine_mp (and player_doommarine_ctf),
// "def_weapon0" "weapon_fists", "def_weapon1" "weapon_pistol" etc
// => get all those definitions (up to MAX_WEAPONS=16) from Player, and then
// get the entities for the corresponding keys ("weapon_fists" etc),
// which should have an entry like "inv_name" "Pistol" (could also be #str_00100207 though!)
// hardcorps uses: idCVar pm_character("pm_character", "0", CVAR_GAME | CVAR_BOOL, "Change Player character. 1 = Scarlet. 0 = Doom Marine");
// but I guess (hope) they use the same weapons..
}
// this initialization should be done every time the bindings tab is opened,
@ -1666,8 +1665,31 @@ static CVarOption videoOptionsImmediately[] = {
} ),
CVarOption( "r_screenshotPngCompression", "Compression level for PNG screenshots", OT_INT, 0, 9 ),
CVarOption( "r_screenshotJpgQuality", "Quality level for JPG screenshots", OT_INT, 1, 100 ),
CVarOption( "r_useSoftParticles", []( idCVar& cvar ) {
bool enable = cvar.GetBool();
if ( ImGui::Checkbox( "Use Soft Particles", &enable ) ) {
cvar.SetBool( enable );
if ( enable && r_enableDepthCapture.GetInteger() == 0 ) {
r_enableDepthCapture.SetInteger(-1);
D3::ImGuiHooks::ShowWarningOverlay( "Capturing the Depth Buffer was disabled.\nEnabled it because soft particles need it!" );
}
}
AddCVarOptionTooltips( cvar );
} ),
CVarOption( "Advanced Options" ),
CVarOption( "r_enableDepthCapture", []( idCVar& cvar ) {
int sel = idMath::ClampInt( -1, 1, cvar.GetInteger() ) + 1; // +1 for -1..1 to 0..2
if ( ImGui::Combo( "Capture Depth Buffer to Texture", &sel, "Auto (enable if needed for Soft Particles)\0Disabled\0Always Enabled\0" ) ) {
--sel; // back to -1..1 from 0..2
cvar.SetInteger( sel );
if ( sel == 0 && r_useSoftParticles.GetBool() ) {
r_useSoftParticles.SetBool( false );
D3::ImGuiHooks::ShowWarningOverlay( "You disabled capturing the Depth Buffer.\nDisabling Soft Particles because they need the depth buffer texture." );
}
}
AddCVarOptionTooltips( cvar );
}),
CVarOption( "r_skipNewAmbient", "Disable High Quality Special Effects", OT_BOOL ),
CVarOption( "r_shadows", "Enable Shadows", OT_BOOL ),
CVarOption( "r_skipSpecular", "Disable Specular", OT_BOOL ),
@ -2262,7 +2284,7 @@ void DrawGameOptionsMenu()
playerNameIso[40] = '\0'; // limit to 40 chars, like the original menu
ui_nameVar->SetString( playerNameIso );
// update the playerNameBuf to reflect the name as it is now: limited to 40 chars
// and possibly containing '?' from non-translatable unicode chars
// and possibly containing '!' from non-translatable unicode chars
D3_ISO8859_1toUTF8( ui_nameVar->GetString(), playerNameBuf, sizeof(playerNameBuf) );
} else {
D3::ImGuiHooks::ShowWarningOverlay( "Player Name way too long (max 40 chars) or contains invalid UTF-8 encoding!" );

View file

@ -1911,7 +1911,7 @@ char * D3_UTF8toISO8859_1( const char *utf8str, char *isobuf, int isobufLen, cha
else if ( invalidChar != 0 )
buffer[i++] = invalidChar;
} else if ((*str & 0xf0) == 0xe0) {
// Unicode character between 0x0800 and 0xFFF => way out of range for ISO8859-1
// Unicode character between 0x0800 and 0xFFFF => way out of range for ISO8859-1
// so just validate and skip the input bytes
if (*str == 0xe0 && (str[1] < 0xa0 || str[1] > 0xbf)) return NULL;
if (*str == 0xed && str[1] > 0x9f) return NULL; // str[1] < 0x80 is checked below

View file

@ -183,7 +183,8 @@ public:
void CopyFramebuffer( int x, int y, int width, int height, bool useOversizedBuffer );
void CopyDepthbuffer( int x, int y, int width, int height );
void CopyDepthbuffer( int x, int y, int width, int height, bool useOversizedBuffer );
void UploadScratch( const byte *pic, int width, int height );
@ -417,6 +418,9 @@ public:
idImage * specular2DTableImage; // 2D intensity texture with our specular function with variable specularity
idImage * borderClampImage; // white inside, black outside
idImage * currentDepthImage; // #3877. Allow shaders to access scene depth
//--------------------------------------------------------
idImage * AllocImage( const char *name );

View file

@ -388,6 +388,19 @@ static void R_RGBA8Image( idImage *image ) {
TF_DEFAULT, false, TR_REPEAT, TD_HIGH_QUALITY );
}
static void R_DepthImage( idImage *image ) {
byte data[DEFAULT_SIZE][DEFAULT_SIZE][4];
memset( data, 0, sizeof( data ) );
data[0][0][0] = 16;
data[0][0][1] = 32;
data[0][0][2] = 48;
data[0][0][3] = 96;
image->GenerateImage( (byte *)data, DEFAULT_SIZE, DEFAULT_SIZE,
TF_NEAREST, false, TR_CLAMP, TD_HIGH_QUALITY );
}
#if 0
static void R_RGB8Image( idImage *image ) {
byte data[DEFAULT_SIZE][DEFAULT_SIZE][4];
@ -1993,6 +2006,7 @@ void idImageManager::Init() {
accumImage = ImageFromFunction("_accum", R_RGBA8Image );
scratchCubeMapImage = ImageFromFunction("_scratchCubeMap", makeNormalizeVectorCubeMap );
currentRenderImage = ImageFromFunction("_currentRender", R_RGBA8Image );
currentDepthImage = ImageFromFunction( "_currentDepth", R_DepthImage ); // #3877. Allow shaders to access scene depth
cmdSystem->AddCommand( "reloadImages", R_ReloadImages_f, CMD_FL_RENDERER, "reloads images" );
cmdSystem->AddCommand( "listImages", R_ListImages_f, CMD_FL_RENDERER, "lists images" );

View file

@ -1898,34 +1898,38 @@ CopyDepthbuffer
This should just be part of copyFramebuffer once we have a proper image type field
====================
*/
void idImage::CopyDepthbuffer( int x, int y, int imageWidth, int imageHeight ) {
Bind();
void idImage::CopyDepthbuffer( int x, int y, int imageWidth, int imageHeight, bool useOversizedBuffer )
{
this->Bind();
// if the size isn't a power of 2, the image must be increased in size
int potWidth, potHeight;
potWidth = MakePowerOfTwo( imageWidth );
potHeight = MakePowerOfTwo( imageHeight );
if ( uploadWidth != potWidth || uploadHeight != potHeight ) {
GetDownsize( imageWidth, imageHeight );
GetDownsize( potWidth, potHeight );
// Ensure we are reading from the back buffer:
qglReadBuffer( GL_BACK );
// only resize if the current dimensions can't hold it at all,
// otherwise subview renderings could thrash this
if ( ( useOversizedBuffer && ( uploadWidth < potWidth || uploadHeight < potHeight ) ) || ( !useOversizedBuffer && ( uploadWidth != potWidth || uploadHeight != potHeight ) ) )
{
uploadWidth = potWidth;
uploadHeight = potHeight;
if ( potWidth == imageWidth && potHeight == imageHeight ) {
qglCopyTexImage2D( GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, x, y, imageWidth, imageHeight, 0 );
} else {
// we need to create a dummy image with power of two dimensions,
// then do a qglCopyTexSubImage2D of the data we want
qglTexImage2D( GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, potWidth, potHeight, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_BYTE, NULL );
qglCopyTexSubImage2D( GL_TEXTURE_2D, 0, 0, 0, x, y, imageWidth, imageHeight );
}
// This bit runs once only at map start, because it tests whether the image is too small to hold the screen.
// It resizes the texture to a power of two that can hold the screen,
// and then subsequent captures to the texture put the depth component into the RGB channels
qglTexImage2D( GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT24_ARB, potWidth, potHeight, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_BYTE, NULL );
qglCopyTexSubImage2D( GL_TEXTURE_2D, 0, 0, 0, x, y, imageWidth, imageHeight );
} else {
// otherwise, just subimage upload it so that drivers can tell we are going to be changing
// it and don't try and do a texture compression or some other silliness
qglCopyTexSubImage2D( GL_TEXTURE_2D, 0, 0, 0, x, y, imageWidth, imageHeight );
}
// qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST );
// qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST );
qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST );
qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST );
qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE );
qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE );

View file

@ -1434,6 +1434,10 @@ void idMaterial::ParseStage( idLexer &src, const textureRepeat_t trpDefault ) {
ss->drawStateBits |= GLS_DEPTHMASK;
continue;
}
if ( !token.Icmp( "ignoreDepth" ) ) { // Added in #3877.
ss->drawStateBits |= GLS_DEPTHFUNC_ALWAYS;
continue;
}
if ( !token.Icmp( "alphaTest" ) ) {
ss->hasAlphaTest = true;
ss->alphaTestRegister = ParseExpression( src );

View file

@ -302,8 +302,13 @@ public:
virtual float DepthHack() const;
virtual int Memory() const;
float SofteningRadius( const int stage ) const; // #3878 soft particles
private:
void SetSofteningRadii(); // #3878 soft particles
const idDeclParticle * particleSystem;
idList<float> softeningRadii; // #3878 soft particles
};
/*

View file

@ -50,6 +50,7 @@ idRenderModelPrt::InitFromFile
void idRenderModelPrt::InitFromFile( const char *fileName ) {
name = fileName;
particleSystem = static_cast<const idDeclParticle *>( declManager->FindType( DECL_PARTICLE, fileName ) );
SetSofteningRadii(); // # 3878
}
/*
@ -286,3 +287,67 @@ int idRenderModelPrt::Memory() const {
return total;
}
/*
====================
idRenderModelPrt::SetSofteningRadii
Calculate "depth" of each particle stage that represents a 3d volume, so the particle can
be allowed to overdraw solid geometry by the right amount, and the particle "thickness" (visibility)
can be adjusted by how much of it is visible in front of the background surface.
"depth" is by default 0.8 of the particle radius, and particles less than 2 units in size won't be softened.
The particles that represent 3d volumes are the view-aligned ones. Others have depth set to 0.
Cache these values rather than calculate them for each stage every frame.
Added for soft particles -- SteveL #3878.
====================
*/
void idRenderModelPrt::SetSofteningRadii()
{
softeningRadii.AssureSize( particleSystem->stages.Num() );
for ( int i = 0; i < particleSystem->stages.Num(); ++i )
{
const idParticleStage* ps = particleSystem->stages[i];
// DG: for now softeningRadius isn't configurable to avoid breaking the game DLL's ABI
// => always behave like if ps->softeningRadius == -2, which means "auto"
// (doesn't make a difference, so far only TDM particles set the softeningRadius)
/* if ( ps->softeningRadius > -2.0f ) // User has specified a setting
{
softeningRadii[i] = ps->softeningRadius;
}
else */ if ( ps->orientation == POR_VIEW ) // Only view-aligned particle stages qualify for softening
{
float diameter = Max( ps->size.from, ps->size.to );
float scale = Max( ps->aspect.from, ps->aspect.to );
diameter *= Max( scale, 1.0f ); // aspect applies to 1 axis only. If it's < 1, the other axis will still be at scale 1
if ( diameter > 2.0f ) // Particle is big enough to soften
{
softeningRadii[i] = diameter * 0.8f / 2.0f;
}
else // Particle is small. Disable both softening and modelDepthHack
{
softeningRadii[i] = 0.0f;
}
}
else // Particle isn't view-aligned, and no user setting. Don't change anything.
{
softeningRadii[i] = -1;
}
}
}
/*
====================
idRenderModelPrt::SofteningRadius
Return the max radius of the individual quads that make up this stage.
Added for soft particles #3878.
====================
*/
float idRenderModelPrt::SofteningRadius( const int stage ) const {
assert( particleSystem );
assert( stage > -1 && stage < softeningRadii.Num() );
return softeningRadii[stage];
}

View file

@ -78,6 +78,7 @@ typedef struct glconfig_s {
bool twoSidedStencilAvailable;
bool textureNonPowerOfTwoAvailable;
bool depthBoundsTestAvailable;
bool glDebugOutputAvailable;
// GL framebuffer size, see also winWidth and winHeight
int vidWidth, vidHeight; // passed to R_BeginFrame
@ -95,6 +96,8 @@ typedef struct glconfig_s {
bool shouldFillWindowAlpha;
bool isWayland; // DG: for other wayland-specific hacks.. (does *not* detect XWayland!)
bool haveDebugContext;
// For some reason people decided that we need displays with ultra small pixels,
// so everything rendered on them must be scaled up to be legible.
// unfortunately, this bullshit feature was "improved" upon by deciding that the best

View file

@ -239,6 +239,13 @@ idCVar r_screenshotPngCompression("r_screenshotPngCompression", "3", CVAR_RENDER
idCVar r_windowResizable("r_windowResizable", "1", CVAR_RENDERER | CVAR_ARCHIVE | CVAR_BOOL, "Allow resizing (and maximizing) the window (needs SDL2; with 2.0.5 or newer it's applied immediately)" );
idCVar r_vidRestartAlwaysFull( "r_vidRestartAlwaysFull", 0, CVAR_RENDERER | CVAR_ARCHIVE | CVAR_BOOL, "Always do a full vid_restart (ignore 'partial' argument), e.g. when changing window size" );
// DG: for soft particles (ported from TDM)
idCVar r_enableDepthCapture( "r_enableDepthCapture", "-1", CVAR_RENDERER | CVAR_INTEGER,
"enable capturing depth buffer to texture. -1: enable automatically (if soft particles are enabled), 0: disable, 1: enable", -1, 1 ); // #3877
idCVar r_useSoftParticles( "r_useSoftParticles", "1", CVAR_RENDERER | CVAR_ARCHIVE | CVAR_BOOL, "Soften particle transitions when player walks through them or they cross solid geometry. Needs r_enableDepthCapture. Can slow down rendering!" ); // #3878
idCVar r_glDebugContext( "r_glDebugContext", "0", CVAR_RENDERER | CVAR_BOOL, "Enable OpenGL Debug context - requires vid_restart, needs SDL2" );
// define qgl functions
#define QGLPROC(name, rettype, args) rettype (APIENTRYP q##name) args;
#include "renderer/qgl_proc.h"
@ -288,6 +295,9 @@ PFNGLDEPTHBOUNDSEXTPROC qglDepthBoundsEXT;
// DG: couldn't find any extension for this, it's supported in GL2.0 and newer, incl OpenGL ES2.0
PFNGLSTENCILOPSEPARATEPROC qglStencilOpSeparate;
// GL_ARB_debug_output
PFNGLDEBUGMESSAGECALLBACKARBPROC qglDebugMessageCallbackARB;
// eez: This is a slight hack for letting us select the desired screenshot format in other functions
// This is a hack to avoid adding another function parameter to idRenderSystem::TakeScreenshot(),
// which would break the API of the dhewm3 SDK for mods.
@ -296,6 +306,60 @@ PFNGLSTENCILOPSEPARATEPROC qglStencilOpSeparate;
// it must set g_screenshotFormat accordingly before each call to TakeScreenshot().
int g_screenshotFormat = -1;
enum {
// Not all GL.h header know about GL_DEBUG_SEVERITY_NOTIFICATION_*.
QGL_DEBUG_SEVERITY_NOTIFICATION = 0x826B
};
/*
* Callback function for debug output.
*/
static void APIENTRY
DebugCallback( GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length,
const GLchar *message, const void *userParam )
{
const char* sourceStr = "Source: Unknown";
const char* typeStr = "Type: Unknown";
const char* severityStr = "Severity: Unknown";
switch (severity)
{
#define SVRCASE(X, STR) case GL_DEBUG_SEVERITY_ ## X ## _ARB : severityStr = STR; break;
case QGL_DEBUG_SEVERITY_NOTIFICATION: return;
SVRCASE(HIGH, "Severity: High")
SVRCASE(MEDIUM, "Severity: Medium")
SVRCASE(LOW, "Severity: Low")
#undef SVRCASE
}
switch (source)
{
#define SRCCASE(X) case GL_DEBUG_SOURCE_ ## X ## _ARB: sourceStr = "Source: " #X; break;
SRCCASE(API);
SRCCASE(WINDOW_SYSTEM);
SRCCASE(SHADER_COMPILER);
SRCCASE(THIRD_PARTY);
SRCCASE(APPLICATION);
SRCCASE(OTHER);
#undef SRCCASE
}
switch(type)
{
#define TYPECASE(X) case GL_DEBUG_TYPE_ ## X ## _ARB: typeStr = "Type: " #X; break;
TYPECASE(ERROR);
TYPECASE(DEPRECATED_BEHAVIOR);
TYPECASE(UNDEFINED_BEHAVIOR);
TYPECASE(PORTABILITY);
TYPECASE(PERFORMANCE);
TYPECASE(OTHER);
#undef TYPECASE
}
common->Warning( "GLDBG %s %s %s: %s\n", sourceStr, typeStr, severityStr, message );
}
/*
=================
R_CheckExtension
@ -415,15 +479,15 @@ static void R_CheckPortableExtensions( void ) {
qglActiveStencilFaceEXT = (PFNGLACTIVESTENCILFACEEXTPROC)GLimp_ExtensionPointer( "glActiveStencilFaceEXT" );
if( glConfig.glVersion >= 2.0) {
common->Printf( "... got GL2.0+ glStencilOpSeparate()\n" );
common->Printf( "...got GL2.0+ glStencilOpSeparate()\n" );
qglStencilOpSeparate = (PFNGLSTENCILOPSEPARATEPROC)GLimp_ExtensionPointer( "glStencilOpSeparate" );
} else if( R_CheckExtension( "GL_ATI_separate_stencil" ) ) {
common->Printf( "... got glStencilOpSeparateATI() (GL_ATI_separate_stencil)\n" );
common->Printf( "...got glStencilOpSeparateATI() (GL_ATI_separate_stencil)\n" );
// the ATI version of glStencilOpSeparate() has the same signature and should also
// behave identical to the GL2 version (in Mesa3D it's just an alias)
qglStencilOpSeparate = (PFNGLSTENCILOPSEPARATEPROC)GLimp_ExtensionPointer( "glStencilOpSeparateATI" );
} else {
common->Printf( "... don't have glStencilOpSeparateATI() or (GL2.0+) glStencilOpSeparate()\n" );
common->Printf( "X..don't have glStencilOpSeparateATI() or (GL2.0+) glStencilOpSeparate()\n" );
qglStencilOpSeparate = NULL;
}
@ -482,6 +546,37 @@ static void R_CheckPortableExtensions( void ) {
qglDepthBoundsEXT = (PFNGLDEPTHBOUNDSEXTPROC)GLimp_ExtensionPointer( "glDepthBoundsEXT" );
}
// GL_ARB_debug_output
glConfig.glDebugOutputAvailable = false;
if ( glConfig.haveDebugContext ) {
if ( strstr( glConfig.extensions_string, "GL_ARB_debug_output" ) ) {
glConfig.glDebugOutputAvailable = true;
qglDebugMessageCallbackARB = (PFNGLDEBUGMESSAGECALLBACKARBPROC)GLimp_ExtensionPointer( "glDebugMessageCallbackARB" );
if ( r_glDebugContext.GetBool() ) {
common->Printf( "...using GL_ARB_debug_output (r_glDebugContext is set)\n" );
qglDebugMessageCallbackARB(DebugCallback, NULL);
qglEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS_ARB);
} else {
common->Printf( "...found GL_ARB_debug_output, but not using it (r_glDebugContext is not set)\n" );
}
} else {
common->Printf( "X..GL_ARB_debug_output not found\n" );
qglDebugMessageCallbackARB = NULL;
if ( r_glDebugContext.GetBool() ) {
common->Warning( "r_glDebugContext is set, but can't be used because GL_ARB_debug_output is not supported" );
}
}
} else {
if ( strstr( glConfig.extensions_string, "GL_ARB_debug_output" ) ) {
if ( r_glDebugContext.GetBool() ) {
common->Printf( "...found GL_ARB_debug_output, but not using it (no debug context)\n" );
} else {
common->Printf( "...found GL_ARB_debug_output, but not using it (r_glDebugContext is not set)\n" );
}
} else {
common->Printf( "X..GL_ARB_debug_output not found\n" );
}
}
}

View file

@ -31,6 +31,14 @@ If you have questions concerning this license or the applicable additional terms
#include "renderer/tr_local.h"
// DG: if this is defined, the soft particle shaders will be compiled into the executable
// otherwise soft_particle.vfp will be opened as a file just like the other shaders
// (useful when tweaking that shader - when loaded from disk, you can use `reloadARBprograms`
// instead of recompiling the executable)
#ifndef D3_INTEGRATE_SOFTPART_SHADERS
#define D3_INTEGRATE_SOFTPART_SHADERS 1
#endif
/*
=========================================================================================
@ -104,7 +112,7 @@ void RB_ARB2_DrawInteraction( const drawInteraction_t *din ) {
float parm[4];
parm[0] = parm[1] = parm[2] = r_brightness.GetFloat();
parm[3] = 1.0/r_gamma.GetFloat(); // 1.0/gamma so the shader doesn't have to do this calculation
qglProgramEnvParameter4fvARB( GL_FRAGMENT_PROGRAM_ARB, 4, parm );
qglProgramEnvParameter4fvARB( GL_FRAGMENT_PROGRAM_ARB, PP_GAMMA_BRIGHTNESS, parm );
}
// set the textures
@ -345,9 +353,100 @@ static progDef_t progs[MAX_GLPROGS] = {
{ GL_VERTEX_PROGRAM_ARB, VPROG_GLASSWARP, "arbVP_glasswarp.txt" },
{ GL_FRAGMENT_PROGRAM_ARB, FPROG_GLASSWARP, "arbFP_glasswarp.txt" },
// SteveL #3878: Particle softening applied by the engine
{ GL_VERTEX_PROGRAM_ARB, VPROG_SOFT_PARTICLE, "soft_particle.vfp" },
{ GL_FRAGMENT_PROGRAM_ARB, FPROG_SOFT_PARTICLE, "soft_particle.vfp" },
// additional programs can be dynamically specified in materials
};
#if D3_INTEGRATE_SOFTPART_SHADERS
// DG: the following two shaders are taken from TheDarkMod 2.04 (glprogs/soft_particle.vfp)
// (C) 2005-2016 Broken Glass Studios (The Dark Mod Team) and the individual authors
// released under a revised BSD license and GPLv3
const char* softpartVShader = "!!ARBvp1.0 \n"
"OPTION ARB_position_invariant; \n"
"# NOTE: unlike the TDM shader, the following lines use .texcoord and .color \n"
"# instead of .attrib[8] and .attrib[3], to make it work with non-nvidia drivers \n"
"MOV result.texcoord, vertex.texcoord; \n"
"MOV result.color, vertex.color; \n"
"END \n";
const char* softpartFShader = "!!ARBfp1.0 \n"
"# == Fragment Program == \n"
"# taken from The Dark Mod 2.04, adjusted for dhewm3 \n"
"# (C) 2005-2016 Broken Glass Studios (The Dark Mod Team) \n"
"# \n"
"# Input textures \n"
"# texture[0] particle diffusemap \n"
"# texture[1] _currentDepth \n"
"# \n"
"# Constants set by the engine: \n"
"# program.env[22] is reciprocal of _currentDepth size. Lets us convert a screen position to a texcoord in _currentDepth \n"
"# { 1.0f / depthtex.width, 1.0f / depthtex.height, float(depthtex.width)/int(depthtex.width), \n"
"# float(depthtex.height)/int(depthtex.height) } \n"
"# program.env[23] is the particle radius, given as { radius, 1/(fadeRange), 1/radius } \n"
"# fadeRange is the particle diameter for alpha blends (like smoke), but the particle radius for additive \n"
"# blends (light glares), because additive effects work differently. Fog is half as apparent when a wall \n"
"# is in the middle of it. Light glares lose no visibility when they have something to reflect off. \n"
"# program.env[24] is the color channel mask. Particles with additive blend need their RGB channels modified to blend them out. \n"
"# Particles with an alpha blend need their alpha channel modified. \n"
"# \n"
"# Hard-coded constants \n"
"# depth_consts allows us to recover the original depth in Doom units of anything in the depth \n"
"# buffer. Doom3's and thus TDM's projection matrix differs slightly from the classic projection matrix as \n"
"# it implements a \"nearly-infinite\" zFar. The matrix is hard-coded in the engine, so we use hard-coded \n"
"# constants here for efficiency. depth_consts is derived from the numbers in that matrix. \n"
"# \n"
"# next line: prevent dhewm3 from injecting gamma in shader code into this shader, \n"
"# because that looks bad when rendered with additive blending (gets too bright) \n"
"# nodhewm3gammahack \n"
"\n"
"PARAM depth_consts = { 0.33333333, -0.33316667, 0.0, 0.0 }; \n"
"PARAM particle_radius = program.env[23]; \n"
"TEMP tmp, scene_depth, particle_depth, near_fade, fade; \n"
"\n"
"# Map the fragment to a texcoord on our depth image, and sample to find scene_depth \n"
"MUL tmp.xy, fragment.position, program.env[22]; \n"
"TEX scene_depth, tmp, texture[1], 2D; \n"
"MIN scene_depth, scene_depth, 0.9994; # Required by TDM projection matrix. Equates to max recoverable \n"
" # depth of 30k units, which is enough. 0.9995 is infinite depth. \n"
" # This is needed only if there is caulk sky on show (which writes \n"
" # no depth, so leaves 1 in the depth texture). \n"
"\n"
"# Recover original depth in doom units \n"
"MAD tmp, scene_depth, depth_consts.x, depth_consts.y; \n"
"RCP scene_depth, tmp.x; \n"
"\n"
"# Convert particle depth to doom units too \n"
"MAD tmp, fragment.position.z, depth_consts.x, depth_consts.y; \n"
"RCP particle_depth, tmp.x; \n"
"\n"
"# Scale the depth difference by the particle diameter to calc an alpha \n"
"# value based on how much of the 3d volume represented by the particle \n"
"# is in front of the solid scene \n"
"ADD tmp, -scene_depth, particle_depth; # NB depth is negative. 0 at the eye, -100 at 100 units into the screen. \n"
"ADD tmp, tmp, particle_radius.x; # Add the radius so a depth difference of particle radius now equals 0 \n"
"MUL_SAT fade, tmp, particle_radius.y; # divide by the particle radius or diameter and clamp \n"
"\n"
"# Also fade if the particle is too close to our eye position, so it doesn't 'pop' in and out of view \n"
"# Start a linear fade at particle_radius distance from the particle. \n"
"MUL_SAT near_fade, particle_depth, -particle_radius.z; \n"
"\n"
"# Calculate final fade and apply the channel mask \n"
"MUL fade, near_fade, fade; \n"
"ADD_SAT fade, fade, program.env[24]; # saturate the channels that don't want modifying \n"
"\n"
"# Set the color. Multiply by vertex/fragment color as that's how the particle system fades particles in and out \n"
"TEMP oColor; \n"
"TEX oColor, fragment.texcoord, texture[0], 2D; \n"
"MUL oColor, oColor, fade; \n"
"MUL result.color, oColor, fragment.color; \n"
"\n"
"END \n";
#endif // D3_INTEGRATE_SOFTPART_SHADERS
/*
=================
R_LoadARBProgram
@ -391,26 +490,40 @@ static ID_INLINE bool isARBidentifierChar( int c ) {
void R_LoadARBProgram( int progIndex ) {
int ofs;
int err;
idStr fullPath = "glprogs/";
fullPath += progs[progIndex].name;
char *fileBuffer;
char *buffer;
char *start = NULL, *end;
common->Printf( "%s", fullPath.c_str() );
#if D3_INTEGRATE_SOFTPART_SHADERS
if ( progs[progIndex].ident == VPROG_SOFT_PARTICLE || progs[progIndex].ident == FPROG_SOFT_PARTICLE ) {
// these shaders are loaded directly from a string
common->Printf( "<internal> %s", progs[progIndex].name );
const char* srcstr = (progs[progIndex].ident == VPROG_SOFT_PARTICLE) ? softpartVShader : softpartFShader;
// load the program even if we don't support it, so
// fs_copyfiles can generate cross-platform data dumps
fileSystem->ReadFile( fullPath.c_str(), (void **)&fileBuffer, NULL );
if ( !fileBuffer ) {
common->Printf( ": File not found\n" );
return;
// copy to stack memory
buffer = (char *)_alloca( strlen( srcstr ) + 1 );
strcpy( buffer, srcstr );
}
else
#endif // D3_INTEGRATE_SOFTPART_SHADERS
{
idStr fullPath = "glprogs/";
fullPath += progs[progIndex].name;
char *fileBuffer;
common->Printf( "%s", fullPath.c_str() );
// copy to stack memory and free
buffer = (char *)_alloca( strlen( fileBuffer ) + 1 );
strcpy( buffer, fileBuffer );
fileSystem->FreeFile( fileBuffer );
// load the program even if we don't support it, so
// fs_copyfiles can generate cross-platform data dumps
fileSystem->ReadFile( fullPath.c_str(), (void **)&fileBuffer, NULL );
if ( !fileBuffer ) {
common->Printf( ": File not found\n" );
return;
}
// copy to stack memory and free
buffer = (char *)_alloca( strlen( fileBuffer ) + 1 );
strcpy( buffer, fileBuffer );
fileSystem->FreeFile( fileBuffer );
}
if ( !glConfig.isInitialized ) {
return;
@ -454,12 +567,14 @@ void R_LoadARBProgram( int progIndex ) {
end[3] = 0;
// DG: hack gamma correction into shader
if ( r_gammaInShader.GetBool() && progs[progIndex].target == GL_FRAGMENT_PROGRAM_ARB ) {
if ( r_gammaInShader.GetBool() && progs[progIndex].target == GL_FRAGMENT_PROGRAM_ARB
&& strstr( start, "nodhewm3gammahack" ) == NULL )
{
// note that strlen("dhewm3tmpres") == strlen("result.color")
const char* tmpres = "TEMP dhewm3tmpres; # injected by dhewm3 for gamma correction\n";
// Note: program.env[4].xyz = r_brightness; program.env[4].w = 1.0/r_gamma
// Note: program.env[21].xyz = r_brightness; program.env[21].w = 1.0/r_gamma
// outColor.rgb = pow(dhewm3tmpres.rgb*r_brightness, vec3(1.0/r_gamma))
// outColor.a = dhewm3tmpres.a;
const char* extraLines =
@ -468,10 +583,10 @@ void R_LoadARBProgram( int progIndex ) {
// POW might not work with a negative base (it looks wrong with intel's Linux driver)
// and clamping values >1 to 1 is ok because when writing to result.color
// it's clamped anyway and pow(base, exp) is always >= 1 for base >= 1
"MUL_SAT dhewm3tmpres.xyz, program.env[4], dhewm3tmpres;\n" // first multiply with brightness
"POW result.color.x, dhewm3tmpres.x, program.env[4].w;\n" // then do pow(dhewm3tmpres.xyz, vec3(1/gamma))
"POW result.color.y, dhewm3tmpres.y, program.env[4].w;\n" // (apparently POW only supports scalars, not whole vectors)
"POW result.color.z, dhewm3tmpres.z, program.env[4].w;\n"
"MUL_SAT dhewm3tmpres.xyz, program.env[21], dhewm3tmpres;\n" // first multiply with brightness
"POW result.color.x, dhewm3tmpres.x, program.env[21].w;\n" // then do pow(dhewm3tmpres.xyz, vec3(1/gamma))
"POW result.color.y, dhewm3tmpres.y, program.env[21].w;\n" // (apparently POW only supports scalars, not whole vectors)
"POW result.color.z, dhewm3tmpres.z, program.env[21].w;\n"
"MOV result.color.w, dhewm3tmpres.w;\n" // alpha remains unmodified
"\nEND\n\n"; // we add this block right at the end, replacing the original "END" string

View file

@ -33,6 +33,7 @@ If you have questions concerning this license or the applicable additional terms
extern idCVar r_useCarmacksReverse;
extern idCVar r_useStencilOpSeparate;
/*
=====================
RB_BakeTextureMatrixIntoTexgen
@ -313,7 +314,7 @@ void RB_FinishStageTexturing( const shaderStage_t *pStage, const drawSurf_t *sur
qglDisable( GL_FRAGMENT_PROGRAM_ARB );
qglDisable( GL_VERTEX_PROGRAM_ARB );
// Fixme: Hack to get around an apparent bug in ATI drivers. Should remove as soon as it gets fixed.
qglBindProgramARB( GL_VERTEX_PROGRAM_ARB, 0 );
qglBindProgramARB( GL_VERTEX_PROGRAM_ARB, 0 ); // FIXME ...
} else {
qglDisable( GL_TEXTURE_GEN_S );
qglDisable( GL_TEXTURE_GEN_T );
@ -513,6 +514,9 @@ void RB_T_FillDepthBuffer( const drawSurf_t *surf ) {
}
void RB_SetProgramEnvironment( bool isPostProcess ); // so RB_STD_FillDepthBuffer() can use it
/*
=====================
RB_STD_FillDepthBuffer
@ -553,6 +557,21 @@ void RB_STD_FillDepthBuffer( drawSurf_t **drawSurfs, int numDrawSurfs ) {
RB_RenderDrawSurfListWithFunction( drawSurfs, numDrawSurfs, RB_T_FillDepthBuffer );
// Make the early depth pass available to shaders. #3877
bool getDepthCapture = r_enableDepthCapture.GetInteger() == 1
|| (r_enableDepthCapture.GetInteger() == -1 && r_useSoftParticles.GetBool());
if ( getDepthCapture && backEnd.viewDef->renderView.viewID >= 0 ) // Suppress for lightgem rendering passes
{
globalImages->currentDepthImage->CopyDepthbuffer( backEnd.viewDef->viewport.x1,
backEnd.viewDef->viewport.y1,
backEnd.viewDef->viewport.x2 - backEnd.viewDef->viewport.x1 + 1,
backEnd.viewDef->viewport.y2 - backEnd.viewDef->viewport.y1 + 1,
true );
bool isPostProcess = false;
RB_SetProgramEnvironment( isPostProcess );
}
if ( backEnd.viewDef->numClipPlanes ) {
GL_SelectTexture( 1 );
globalImages->BindNull();
@ -575,6 +594,18 @@ SHADER PASSES
RB_SetProgramEnvironment
Sets variables that can be used by all vertex programs
[SteveL #3877] Note on the use of fragment program environmental variables.
Parameters 0 and 1 are set here to allow conversion of screen coordinates to
texture coordinates, for use when sampling _currentRender.
Those same parameters 0 and 1, plus 2 and 3, are given entirely different
meanings in draw_arb2.cpp while light interactions are being drawn.
This function is called again before currentRender size is needed by post processing
effects are done, so there's no clash.
Only parameters 0..3 were in use before #3877 - and in dhewm3 also 4, for gamma in shader.
Now I've used a new parameter 22 for the size of _currentDepth. It's needed throughout,
including by light interactions, and its size might in theory differ from _currentRender.
Parameters 23 and 24 are used by soft particles #3878. Note these can be freely reused by different draw calls.
==================
*/
void RB_SetProgramEnvironment( bool isPostProcess ) {
@ -643,9 +674,23 @@ void RB_SetProgramEnvironment( bool isPostProcess ) {
// (setting them to 1.0 makes them no-ops)
parm[0] = parm[1] = parm[2] = parm[3] = 1.0f;
}
qglProgramEnvParameter4fvARB( GL_FRAGMENT_PROGRAM_ARB, 4, parm );
qglProgramEnvParameter4fvARB( GL_FRAGMENT_PROGRAM_ARB, PP_GAMMA_BRIGHTNESS, parm );
}
// #3877: Allow shaders to access depth buffer.
// Two useful ratios are packed into this parm: [0] and [1] hold the x,y multipliers you need to map a screen
// coordinate (fragment position) to the depth image: those are simply the reciprocal of the depth
// image size, which has been rounded up to a power of two. Slots [3] and [4] hold the ratio of the depth image
// size to the current render image size. These sizes can differ if the game crops the render viewport temporarily
// during post-processing effects. The depth render is smaller during the effect too, but the depth image doesn't
// need to be downsized, whereas the current render image does get downsized when it's captured by the game after
// the skybox render pass. The ratio is needed to map between the two render images.
parm[0] = 1.0f / globalImages->currentDepthImage->uploadWidth;
parm[1] = 1.0f / globalImages->currentDepthImage->uploadHeight;
parm[2] = static_cast<float>(globalImages->currentRenderImage->uploadWidth) / globalImages->currentDepthImage->uploadWidth;
parm[3] = static_cast<float>(globalImages->currentRenderImage->uploadHeight) / globalImages->currentDepthImage->uploadHeight;
qglProgramEnvParameter4fvARB( GL_FRAGMENT_PROGRAM_ARB, PP_CURDEPTH_RECIPR, parm );
//
// set eye position in global space
//
@ -654,8 +699,6 @@ void RB_SetProgramEnvironment( bool isPostProcess ) {
parm[2] = backEnd.viewDef->renderView.vieworg[2];
parm[3] = 1.0;
qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, 1, parm );
}
/*
@ -749,6 +792,9 @@ void RB_STD_T_RenderShaderPasses( const drawSurf_t *surf ) {
return;
}
// check whether we're drawing a soft particle surface #3878
const bool soft_particle = ( surf->dsFlags & DSF_SOFT_PARTICLE ) != 0;
// get the expressions for conditionals / color / texcoords
regs = surf->shaderRegisters;
@ -765,7 +811,8 @@ void RB_STD_T_RenderShaderPasses( const drawSurf_t *surf ) {
RB_EnterWeaponDepthHack();
}
if ( surf->space->modelDepthHack != 0.0f ) {
if ( surf->space->modelDepthHack != 0.0f && !soft_particle ) // #3878 soft particles don't want modelDepthHack, which is
{ // an older way to slightly "soften" particles
RB_EnterModelDepthHack( surf->space->modelDepthHack );
}
@ -791,6 +838,9 @@ void RB_STD_T_RenderShaderPasses( const drawSurf_t *surf ) {
continue;
}
// determine the blend mode (used by soft particles #3878)
const int src_blend = pStage->drawStateBits & GLS_SRCBLEND_BITS;
// see if we are a new-style stage
newShaderStage_t *newStage = pStage->newStage;
if ( newStage ) {
@ -866,7 +916,7 @@ void RB_STD_T_RenderShaderPasses( const drawSurf_t *surf ) {
qglDisable( GL_VERTEX_PROGRAM_ARB );
qglDisable( GL_FRAGMENT_PROGRAM_ARB );
// Fixme: Hack to get around an apparent bug in ATI drivers. Should remove as soon as it gets fixed.
qglBindProgramARB( GL_VERTEX_PROGRAM_ARB, 0 );
qglBindProgramARB( GL_VERTEX_PROGRAM_ARB, 0 ); // FIXME: ...
qglDisableClientState( GL_COLOR_ARRAY );
qglDisableVertexAttribArrayARB( 9 );
@ -874,6 +924,148 @@ void RB_STD_T_RenderShaderPasses( const drawSurf_t *surf ) {
qglDisableClientState( GL_NORMAL_ARRAY );
continue;
}
else if ( soft_particle
&& surf->particle_radius > 0.0f
&& ( src_blend == GLS_SRCBLEND_ONE || src_blend == GLS_SRCBLEND_SRC_ALPHA )
&& tr.backEndRenderer == BE_ARB2
&& !r_skipNewAmbient.GetBool() )
{
// SteveL #3878. Particles are automatically softened by the engine, unless they have shader programs of
// their own (i.e. are "newstages" handled above). This section comes after the newstage part so that if a
// designer has specified their own shader programs, those will be used instead of the soft particle program.
if ( pStage->vertexColor == SVC_IGNORE )
{
// Ignoring vertexColor is not recommended for particles. The particle system uses vertexColor for fading.
// However, there are existing particle effects that don't use it, in which case we default to using the
// rgb color modulation specified in the material like the "old stages" do below.
color[0] = regs[pStage->color.registers[0]];
color[1] = regs[pStage->color.registers[1]];
color[2] = regs[pStage->color.registers[2]];
color[3] = regs[pStage->color.registers[3]];
qglColor4fv( color );
}
else
{
// A properly set-up particle shader
qglColorPointer( 4, GL_UNSIGNED_BYTE, sizeof( idDrawVert ), (void *)&ac->color );
qglEnableClientState( GL_COLOR_ARRAY );
}
#if 0 // debug stuff: render particles opaque so debug colors written in the shader are properly visible
int dsbits = pStage->drawStateBits | GLS_DEPTHFUNC_ALWAYS;
dsbits &= ~(GLS_SRCBLEND_BITS | GLS_DSTBLEND_BITS);
//dsbits |= GLS_SRCBLEND_ONE | GLS_DSTBLEND_ZERO; both values are 0, so this would be a noop
GL_State( dsbits );
#endif
GL_State( pStage->drawStateBits | GLS_DEPTHFUNC_ALWAYS ); // Disable depth clipping. The fragment program will
// handle it to allow overdraw.
qglBindProgramARB( GL_VERTEX_PROGRAM_ARB, VPROG_SOFT_PARTICLE );
qglEnable( GL_VERTEX_PROGRAM_ARB );
// Bind image and _currentDepth
GL_SelectTexture( 0 );
pStage->texture.image->Bind();
GL_SelectTexture( 1 );
globalImages->currentDepthImage->Bind();
qglBindProgramARB( GL_FRAGMENT_PROGRAM_ARB, FPROG_SOFT_PARTICLE );
qglEnable( GL_FRAGMENT_PROGRAM_ARB );
#if 0 // debug stuff
// Set up parameters for fragment program
const char* srcblendstr = "???";
if ( src_blend >= 0 && src_blend <= 9 ) {
const char* blendModes[] = {
"ONE",
"ZERO",
"!! INVALID !!",
"DST_COLOR",
"ONE_MINUS_DST_COLOR",
"SRC_ALPHA",
"ONE_MINUS_SRC_ALPHA",
"DST_ALPHA",
"ONE_MINUS_DST_ALPHA",
"ALPHA_SATURATE"
};
srcblendstr = blendModes[src_blend];
}
int dst_blend = pStage->drawStateBits & GLS_DSTBLEND_BITS;
const char* dstblend = "???";
switch ( dst_blend ) {
#define MY_CASE(X) case GLS_DSTBLEND_ ##X : dstblend = #X; break;
MY_CASE(ZERO)
MY_CASE(ONE)
MY_CASE(SRC_COLOR)
MY_CASE(ONE_MINUS_SRC_COLOR)
MY_CASE(SRC_ALPHA)
MY_CASE(ONE_MINUS_SRC_ALPHA)
MY_CASE(DST_ALPHA)
MY_CASE(ONE_MINUS_DST_ALPHA)
#undef MY_CASE
}
printf("XX mat: %s, src_blend = %s dest_blend = %s radius = %g\n", shader->GetName(), srcblendstr, dstblend, surf->particle_radius);
#endif
// program.env[23] is the particle radius, given as { radius, 1/(faderange), 1/radius }
float fadeRange = 1.0f;
// fadeRange is the particle diameter for alpha blends (like smoke), but the particle radius for additive
// blends (light glares), because additive effects work differently. Fog is half as apparent when a wall
// is in the middle of it. Light glares lose no visibility when they have something to reflect off. See
// The Dark Mod issue #3878 for diagram
if ( src_blend == GLS_SRCBLEND_SRC_ALPHA ) // an alpha blend material
{
fadeRange = surf->particle_radius * 2.0f;
}
else if ( src_blend == GLS_SRCBLEND_ONE ) // an additive (blend add) material
{
fadeRange = surf->particle_radius;
}
float parm[4] = {
surf->particle_radius,
1.0f / ( fadeRange ),
1.0f / surf->particle_radius,
0.0f
};
qglProgramEnvParameter4fvARB( GL_FRAGMENT_PROGRAM_ARB, PP_PARTICLE_RADIUS, parm );
// program.env[24] is the color channel mask. It gets added to the fade multiplier, so adding 1
// to a channel will make sure it doesn't get faded at all. Particles with additive blend
// need their RGB channels modifying to blend them out. Particles with an alpha blend need
// their alpha channel modifying.
if ( src_blend == GLS_SRCBLEND_SRC_ALPHA ) // an alpha blend material
{
parm[0] = parm[1] = parm[2] = 1.0f; // Leave the rgb channels at full strength when fading
parm[3] = 0.0f; // but fade the alpha channel
}
else if ( src_blend == GLS_SRCBLEND_ONE ) // an additive (blend add) material
{
parm[0] = parm[1] = parm[2] = 0.0f; // Fade the rgb channels but
parm[3] = 1.0f; // leave the alpha channel at full strength
}
qglProgramEnvParameter4fvARB( GL_FRAGMENT_PROGRAM_ARB, PP_PARTICLE_COLCHAN_MASK, parm );
// draw it
RB_DrawElementsWithCounters( tri );
// Clean up GL state
GL_SelectTexture( 1 );
globalImages->BindNull();
GL_SelectTexture( 0 );
globalImages->BindNull();
qglDisable( GL_VERTEX_PROGRAM_ARB );
qglDisable( GL_FRAGMENT_PROGRAM_ARB );
if ( pStage->vertexColor != SVC_IGNORE ) {
qglDisableClientState( GL_COLOR_ARRAY );
}
continue;
}
//--------------------------
//
@ -972,7 +1164,8 @@ void RB_STD_T_RenderShaderPasses( const drawSurf_t *surf ) {
if ( shader->TestMaterialFlag(MF_POLYGONOFFSET) ) {
qglDisable( GL_POLYGON_OFFSET_FILL );
}
if ( surf->space->weaponDepthHack || surf->space->modelDepthHack != 0.0f ) {
if ( surf->space->weaponDepthHack || ( !soft_particle && surf->space->modelDepthHack != 0.0f ) ) // #3878 soft particles
{
RB_LeaveDepthHack();
}
}

View file

@ -119,6 +119,9 @@ extern PFNGLPROGRAMLOCALPARAMETER4FVARBPROC qglProgramLocalParameter4fvARB;
// GL_EXT_depth_bounds_test
extern PFNGLDEPTHBOUNDSEXTPROC qglDepthBoundsEXT;
// GL_ARB_debug_output
extern PFNGLDEBUGMESSAGECALLBACKARBPROC qglDebugMessageCallbackARB;
#if defined( _WIN32 ) && defined(ID_ALLOW_TOOLS)
extern BOOL(WINAPI * qwglSwapBuffers)(HDC);

View file

@ -35,6 +35,9 @@ If you have questions concerning this license or the applicable additional terms
#include "renderer/tr_local.h"
#include "Model_local.h"
static const float CHECK_BOUNDS_EPSILON = 1.0f;
/*
@ -673,6 +676,8 @@ void R_LinkLightSurf( const drawSurf_t **link, const srfTriangles_t *tri, const
drawSurf->material = shader;
drawSurf->scissorRect = scissor;
drawSurf->dsFlags = 0;
drawSurf->particle_radius = 0.0f; // #3878
if ( viewInsideShadow ) {
drawSurf->dsFlags |= DSF_VIEW_INSIDE_SHADOW;
}
@ -1186,7 +1191,8 @@ R_AddDrawSurf
=================
*/
void R_AddDrawSurf( const srfTriangles_t *tri, const viewEntity_t *space, const renderEntity_t *renderEntity,
const idMaterial *shader, const idScreenRect &scissor ) {
const idMaterial *shader, const idScreenRect &scissor, const float soft_particle_radius )
{
drawSurf_t *drawSurf;
const float *shaderParms;
static float refRegs[MAX_EXPRESSION_REGISTERS]; // don't put on stack, or VC++ will do a page touch
@ -1198,7 +1204,17 @@ void R_AddDrawSurf( const srfTriangles_t *tri, const viewEntity_t *space, const
drawSurf->material = shader;
drawSurf->scissorRect = scissor;
drawSurf->sort = shader->GetSort() + tr.sortOffset;
drawSurf->dsFlags = 0;
if ( soft_particle_radius != -1.0f ) // #3878
{
drawSurf->dsFlags = DSF_SOFT_PARTICLE;
drawSurf->particle_radius = soft_particle_radius;
}
else
{
drawSurf->dsFlags = 0;
drawSurf->particle_radius = 0.0f;
}
// bumping this offset each time causes surfaces with equal sort orders to still
// deterministically draw in the order they are added
@ -1218,7 +1234,7 @@ void R_AddDrawSurf( const srfTriangles_t *tri, const viewEntity_t *space, const
}
tr.viewDef->drawSurfs = (drawSurf_t **)R_FrameAlloc( tr.viewDef->maxDrawSurfs * sizeof( tr.viewDef->drawSurfs[0] ) );
if(count > 0)
memcpy( tr.viewDef->drawSurfs, old, count ); // XXX null pointer passed as argument 2, which is declared to never be null
memcpy( tr.viewDef->drawSurfs, old, count );
}
tr.viewDef->drawSurfs[tr.viewDef->numDrawSurfs] = drawSurf;
tr.viewDef->numDrawSurfs++;
@ -1423,8 +1439,21 @@ static void R_AddAmbientDrawsurfs( viewEntity_t *vEntity ) {
vertexCache.Touch( tri->indexCache );
}
// Soft Particles -- SteveL #3878
float particle_radius = -1.0f; // Default = disallow softening, but allow modelDepthHack if specified in the decl.
if ( r_useSoftParticles.GetBool() && r_enableDepthCapture.GetInteger() != 0
&& !shader->ReceivesLighting() // don't soften surfaces that are meant to be solid
&& tr.viewDef->renderView.viewID >= 0 ) // Skip during "invisible" rendering passes (e.g. lightgem)
{
const idRenderModelPrt* prt = dynamic_cast<const idRenderModelPrt*>( def->parms.hModel ); // yuck.
if ( prt )
{
particle_radius = prt->SofteningRadius( surf->id );
}
}
// add the surface for drawing
R_AddDrawSurf( tri, vEntity, &vEntity->entityDef->parms, shader, vEntity->scissorRect );
R_AddDrawSurf( tri, vEntity, &vEntity->entityDef->parms, shader, vEntity->scissorRect, particle_radius );
// ambientViewCount is used to allow light interactions to be rejected
// if the ambient surface isn't visible at all
@ -1514,6 +1543,14 @@ void R_AddModelSurfaces( void ) {
continue;
}
// Don't let particle entities re-instantiate their dynamic model during non-visible
// views (in TDM, the light gem render) -- SteveL #3970
if ( tr.viewDef->renderView.viewID < 0
&& dynamic_cast<const idRenderModelPrt*>( vEntity->entityDef->parms.hModel ) != NULL ) // yuck.
{
continue;
}
// add the ambient surface if it has a visible rectangle
if ( !vEntity->scissorRect.IsEmpty() ) {
model = R_EntityDefDynamicModel( vEntity->entityDef );

View file

@ -109,6 +109,7 @@ SURFACES
// drawSurf_t are always allocated and freed every frame, they are never cached
static const int DSF_VIEW_INSIDE_SHADOW = 1;
static const int DSF_SOFT_PARTICLE = 2; // #3878 - soft particles
typedef struct drawSurf_s {
const srfTriangles_t *geo;
@ -121,6 +122,7 @@ typedef struct drawSurf_s {
int dsFlags; // DSF_VIEW_INSIDE_SHADOW, etc
struct vertCache_s *dynamicTexCoords; // float * in vertex cache memory
// specular directions for non vertex program cards, skybox texcoords, etc
float particle_radius; // The radius of individual quads for soft particles #3878
} drawSurf_t;
@ -981,6 +983,10 @@ extern idCVar r_materialOverride; // override all materials
extern idCVar r_debugRenderToTexture;
extern idCVar r_glDebugContext; // DG: use debug context to call logging callbacks on GL errors
extern idCVar r_enableDepthCapture; // DG: disable capturing depth buffer, used for soft particles
extern idCVar r_useSoftParticles;
/*
====================================================================
@ -1179,7 +1185,7 @@ viewEntity_t *R_SetEntityDefViewEntity( idRenderEntityLocal *def );
viewLight_t *R_SetLightDefViewLight( idRenderLightLocal *def );
void R_AddDrawSurf( const srfTriangles_t *tri, const viewEntity_t *space, const renderEntity_t *renderEntity,
const idMaterial *shader, const idScreenRect &scissor );
const idMaterial *shader, const idScreenRect &scissor, const float soft_particle_radius = -1.0f ); // soft particles in #3878
void R_LinkLightSurf( const drawSurf_t **link, const srfTriangles_t *tri, const viewEntity_t *space,
const idRenderLightLocal *light, const idMaterial *shader, const idScreenRect &scissor, bool viewInsideShadow );
@ -1315,6 +1321,10 @@ typedef enum {
FPROG_AMBIENT,
VPROG_GLASSWARP,
FPROG_GLASSWARP,
// SteveL #3878: soft particles
VPROG_SOFT_PARTICLE,
FPROG_SOFT_PARTICLE,
//
PROG_USER
} program_t;
@ -1362,9 +1372,22 @@ typedef enum {
PP_SPECULAR_MATRIX_S,
PP_SPECULAR_MATRIX_T,
PP_COLOR_MODULATE,
PP_COLOR_ADD,
PP_COLOR_ADD, // 17
PP_LIGHT_FALLOFF_TQ = 20 // only for NV programs
PP_LIGHT_FALLOFF_TQ = 20, // only for NV programs - DG: unused
PP_GAMMA_BRIGHTNESS = 21, // DG: for gamma in shader: { r_brightness, r_brightness, r_brightness, 1/r_gamma }
// DG: for soft particles from TDM: reciprocal of _currentDepth size.
// Lets us convert a screen position to a texcoord in _currentDepth
PP_CURDEPTH_RECIPR = 22,
// DG: for soft particles from TDM: particle radius, given as { radius, 1/(fadeRange), 1/radius }
// fadeRange is the particle diameter for alpha blends (like smoke), but the particle radius for additive
// blends (light glares), because additive effects work differently. Fog is half as apparent when a wall
// is in the middle of it. Light glares lose no visibility when they have something to reflect off.
PP_PARTICLE_RADIUS = 23,
// DG: for soft particles from TDM: color channel mask.
// Particles with additive blend need their RGB channels modifying to blend them out
// Particles with an alpha blend need their alpha channel modifying.
PP_PARTICLE_COLCHAN_MASK = 24,
} programParameter_t;

View file

@ -437,7 +437,6 @@ Draw the depth buffer as colors
===================
*/
void RB_ShowDepthBuffer( void ) {
void *depthReadback;
if ( !r_showDepth.GetBool() ) {
return;
@ -450,30 +449,81 @@ void RB_ShowDepthBuffer( void ) {
qglLoadIdentity();
qglOrtho( 0, 1, 0, 1, -1, 1 );
qglRasterPos2f( 0, 0 );
qglPopMatrix();
qglMatrixMode( GL_MODELVIEW );
qglPopMatrix();
GL_State( GLS_DEPTHFUNC_ALWAYS );
qglColor3f( 1, 1, 1 );
globalImages->BindNull();
depthReadback = R_StaticAlloc( glConfig.vidWidth * glConfig.vidHeight*4 );
memset( depthReadback, 0, glConfig.vidWidth * glConfig.vidHeight*4 );
bool haveDepthCapture = r_enableDepthCapture.GetInteger() == 1
|| (r_enableDepthCapture.GetInteger() == -1 && r_useSoftParticles.GetBool());
qglReadPixels( 0, 0, glConfig.vidWidth, glConfig.vidHeight, GL_DEPTH_COMPONENT , GL_FLOAT, depthReadback );
if ( haveDepthCapture ) {
//GL_SelectTexture( 0 );
//qglEnable(GL_TEXTURE_2D);
globalImages->currentDepthImage->Bind();
#if 0
for ( i = 0 ; i < glConfig.vidWidth * glConfig.vidHeight ; i++ ) {
((byte *)depthReadback)[i*4] =
((byte *)depthReadback)[i*4+1] =
((byte *)depthReadback)[i*4+2] = 255 * ((float *)depthReadback)[i];
((byte *)depthReadback)[i*4+3] = 1;
}
const float x=0, y=0, w=1, h=1;
// debug values:
//const float x = 0.1, y = 0.1, w = 0.8, h = 0.8;
//qglColor4f( 0.0f, 0.0f, 0.5f, 1.0f );
const float tx=0, ty=0;
// the actual texturesize of currentDepthImage is the next bigger power of two (POT),
// so the normalized width/height of the part of it we actually wanna show is the following
const float tw = float(glConfig.vidWidth) / float(globalImages->currentDepthImage->uploadWidth);
const float th = float(glConfig.vidHeight) / float(globalImages->currentDepthImage->uploadHeight);
qglBegin( GL_QUADS );
qglTexCoord2f(tx, ty);
qglVertex2f( x, y ); // ( 0,0 );
qglTexCoord2f(tx, ty+th);
qglVertex2f( x, y+h ); // ( 0,1 );
qglTexCoord2f(tx+tw, ty+th);
qglVertex2f( x+w, y+h ); // ( 1,1 );
qglTexCoord2f(tx+tw, ty);
qglVertex2f( x+w, y ); // ( 1,0 );
qglEnd();
// TODO: probably a shader transforming this to something viewable
qglPopMatrix();
qglMatrixMode( GL_MODELVIEW );
qglPopMatrix();
} else {
qglPopMatrix();
qglMatrixMode( GL_MODELVIEW );
qglPopMatrix();
globalImages->BindNull();
void* depthReadback = R_StaticAlloc( glConfig.vidWidth * glConfig.vidHeight*4 );
memset( depthReadback, 0, glConfig.vidWidth * glConfig.vidHeight*4 );
qglReadPixels( 0, 0, glConfig.vidWidth, glConfig.vidHeight, GL_DEPTH_COMPONENT , GL_FLOAT, depthReadback );
#if 0 // the following looks better, but is different from the !r_skipDepthCapture.GetBool() case above
// (which draws the captured depth buffer unaltered, unless we add a shader)
for ( int i = 0, n=glConfig.vidWidth * glConfig.vidHeight; i < n ; i++ ) {
float& px = ((float *)depthReadback)[i];
float d = px;
// the following calculation is based on how the TDM soft particle shader translates the depth value to doom units
// 0.9995 is practically infinite distance, clamping to 0.9994 clamps to max 30k doom units,
// which is more than enough (and prevents a potential division by 0 below)
d = (d < 0.9994f) ? d : 0.9994f;
d = d - 0.9995f; // d is now negative, between -0.0001 and -0.9995
d = 1.0f / d;
// d *= -3.0f; // this would translate d to distance in doom units, doing it together with the next step
d *= (-3.0f / 3000.0f); // now 3000 units is 1.0, i.e. completely white (=> more details for closer distances)
px = d;
}
#endif
qglDrawPixels( glConfig.vidWidth, glConfig.vidHeight, GL_RGBA , GL_UNSIGNED_BYTE, depthReadback );
R_StaticFree( depthReadback );
qglDrawPixels( glConfig.vidWidth, glConfig.vidHeight, GL_LUMINANCE, GL_FLOAT, depthReadback );
R_StaticFree( depthReadback );
}
}
/*

View file

@ -279,6 +279,11 @@ try_again:
#if SDL_VERSION_ATLEAST(2, 0, 0)
if ( r_glDebugContext.GetBool() ) {
common->Printf( "Requesting an OpenGL Debug Context (r_glDebugContext is enabled)\n" );
SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_DEBUG_FLAG);
}
if ( parms.fullScreen && parms.fullScreenDesktop ) {
common->Printf( "Will create a pseudo-fullscreen window at the current desktop resolution\n" );
} else {
@ -435,6 +440,10 @@ try_again:
if (SDL_GL_SetAttribute(SDL_GL_SWAP_CONTROL, r_swapInterval.GetInteger()) < 0)
common->Warning("SDL_GL_SWAP_CONTROL not supported");
if ( r_glDebugContext.GetBool() ) {
common->Warning( "r_glDebugContext is set, but not supported by SDL1.2!\n" );
}
r_swapInterval.ClearModified();
window = SDL_SetVideoMode(parms.width, parms.height, colorbits, flags);
@ -588,6 +597,18 @@ try_again:
}
#endif
glConfig.haveDebugContext = false;
#if SDL_VERSION_ATLEAST(2, 0, 0)
int cflags = 0;
if ( SDL_GL_GetAttribute( SDL_GL_CONTEXT_FLAGS, &cflags ) == 0 ) {
glConfig.haveDebugContext = (cflags & SDL_GL_CONTEXT_DEBUG_FLAG) != 0;
if ( glConfig.haveDebugContext )
common->Printf( "Got a debug context!\n" );
else if( r_glDebugContext.GetBool() ) {
common->Warning( "Requested a debug context, but didn't get one!\n" );
}
}
#endif
break;
}