From 6c8d5c9dab650ce7b5ae4280de124dba60128a4a Mon Sep 17 00:00:00 2001 From: 18woldemar <18woldemar@users.noreply.github.com> Date: Tue, 4 Aug 2026 16:49:45 +0500 Subject: [PATCH 1/3] Ask the card whether it can do DXT instead of assuming it cannot CanVideoCardDoDXT is a TODO that answers "no" on OpenGL whatever the hardware supports, and answering no has a cost: CStreaming::Init then builds MODELS\TXD.IMG, repacking every texture dictionary out of its D3D8 container, and since TXD.DIR is scanned before GTA3.DIR that copy is what gets read from then on - the originals are shadowed. On retail PC data that is 1360 dictionaries, about 180 MB of duplicate on disk and a conversion pass on first run, for a card that could have read the originals directly. The textures really are DXT1 there (platform 8, compression 1, read out of the archive headers) and the rewrite is size-for-size, so nothing is lost to it - it is simply unnecessary work on any desktop GL driver. GetGPUcaps already asks librw the same question, so ask it here too. Desktop GL has S3TC; a GLES device without it still gets the conversion path it needs. Checked by hiding models/txd.img and txd.dir from a run: the game starts, plays and quits normally and does not rebuild them. --- src/rw/TexRead.cpp | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/rw/TexRead.cpp b/src/rw/TexRead.cpp index 9b190a4a1..3587ea220 100644 --- a/src/rw/TexRead.cpp +++ b/src/rw/TexRead.cpp @@ -296,12 +296,16 @@ bool CanVideoCardDoDXT(void) { #ifdef LIBRW - // TODO -#ifdef RW_OPENGL - return false; -#else - return true; -#endif + // Answering "no" unconditionally is not free: CStreaming::Init then builds + // MODELS\TXD.IMG, repacking every texture dictionary out of its D3D8 container, and + // TXD.DIR is scanned before GTA3.DIR - so that copy is what gets read from then on and + // the originals are shadowed. On the retail PC data that is 1360 dictionaries, ~180 MB + // of duplicate and a conversion pass on first run, for a card that can read the + // originals directly. GetGPUcaps already asks librw the same question, so ask it here: + // desktop GL has S3TC, and a GLES device without it still takes the conversion path. + GPUcaps caps; + GetGPUcaps(&caps); + return !!caps.dxtSupport; #else return _rwD3D8CheckValidTextureFormat(D3DFMT_DXT1) && _rwD3D8CheckValidTextureFormat(D3DFMT_DXT3); #endif From 1aae3bf0229329034b19909c99f7e81a01b4ef15 Mon Sep 17 00:00:00 2001 From: 18woldemar <18woldemar@users.noreply.github.com> Date: Tue, 4 Aug 2026 16:49:45 +0500 Subject: [PATCH 2/3] Bound the corona light's pool decal colour (FIX_BUGS) CShadows::StoreStaticShadow takes red, green and blue as uint8 and nothing bounds them on the way in, so a value past 255 wraps rather than clipping - the decal comes out dark instead of merely too bright. Retail data does not reach it: col.r and shadowIntensity are both uint8, and CTimeCycle::GetSpriteBrightness is timecyc.dat's sprBght column, which tops out at exactly 1.00 across all 168 rows, so the product lands on 255.0 and stops. It is reachable from data, though - the parser stores sprBght*10 in an int8, so anything above 1.0 puts a bright lamp over the edge. Guarded, with the original arithmetic kept in the other arm. --- src/renderer/Coronas.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/renderer/Coronas.cpp b/src/renderer/Coronas.cpp index c44e07b42..0836a4f3d 100644 --- a/src/renderer/Coronas.cpp +++ b/src/renderer/Coronas.cpp @@ -913,9 +913,21 @@ CEntity::ProcessLightsForEntity(void) effect->light.shadowSize, 0.0f, 0.0f, -effect->light.shadowSize, 128, +#ifdef FIX_BUGS + // StoreStaticShadow takes these as uint8 and nothing bounds them on + // the way in, so a value past 255 wraps instead of clipping and the + // decal comes out dark rather than merely too bright. Retail data + // stops at exactly 255 (sprBght tops out at 1.00 in timecyc.dat), + // but the parser stores sprBght*10 in an int8, so anything asking + // for more than 1.0 is already over. + Min(effect->col.r*CTimeCycle::GetSpriteBrightness()*effect->light.shadowIntensity/255.0f, 255.0f), + Min(effect->col.g*CTimeCycle::GetSpriteBrightness()*effect->light.shadowIntensity/255.0f, 255.0f), + Min(effect->col.b*CTimeCycle::GetSpriteBrightness()*effect->light.shadowIntensity/255.0f, 255.0f), +#else effect->col.r*CTimeCycle::GetSpriteBrightness()*effect->light.shadowIntensity/255.0f, effect->col.g*CTimeCycle::GetSpriteBrightness()*effect->light.shadowIntensity/255.0f, effect->col.b*CTimeCycle::GetSpriteBrightness()*effect->light.shadowIntensity/255.0f, +#endif 15.0f, 1.0f, 40.0f, false, 0.0f); }else if(lightFlickering){ CShadows::StoreStaticShadow((uintptr)this + i, SHADOWTYPE_ADDITIVE, From 737091fb6a64fa13121f6096fb4971181c2d8d42 Mon Sep 17 00:00:00 2001 From: 18woldemar <18woldemar@users.noreply.github.com> Date: Tue, 4 Aug 2026 16:57:57 +0500 Subject: [PATCH 3/3] Let the Vanilla configuration link again channel.cpp references gPlayerTalkDataSize unconditionally, while sampman_oal.cpp only defines it behind FIX_BUGS - so any build with FIX_BUGS off, which is what the Vanilla configuration is, fails at link: error LNK2001: unresolved external symbol "unsigned int gPlayerTalkDataSize" It shows up in this repository's own CI, where the Windows x86 workflow builds Vanilla. Guarded on both sides; with the fix off the temp stereo buffer is sized from PED_BLOCKSIZE alone, which is what the code did before the player-comment buffer existed. --- src/audio/oal/channel.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/audio/oal/channel.cpp b/src/audio/oal/channel.cpp index 519ecf040..7d06d3de4 100644 --- a/src/audio/oal/channel.cpp +++ b/src/audio/oal/channel.cpp @@ -9,7 +9,11 @@ #endif extern bool IsFXSupported(); +#ifdef FIX_BUGS +// Defined in sampman_oal.cpp behind the same guard, so without one here the Vanilla +// configuration references a symbol that was never emitted and fails at link (LNK2001). extern size_t gPlayerTalkDataSize; +#endif ALuint alSources[NUM_CHANNELS]; ALuint alFilters[NUM_CHANNELS]; @@ -26,7 +30,11 @@ CChannel::InitChannels() if (IsFXSupported()) alGenFilters(NUM_CHANNELS, alFilters); +#ifdef FIX_BUGS tempStereoBuffer = new uint8[Max(PED_BLOCKSIZE, gPlayerTalkDataSize) * 2]; +#else + tempStereoBuffer = new uint8[PED_BLOCKSIZE * 2]; +#endif bChannelsCreated = true; }