From b8bd36dbfd6926199f7d5e5b52e67eb5bc626908 Mon Sep 17 00:00:00 2001 From: stijnbilliet Date: Thu, 8 Oct 2026 17:05:27 +0100 Subject: [PATCH 1/3] Implementation of speed streaks post processing effect --- data/shaders/speed_streaks.frag | 117 +++++++++++++++++++++++++++++++ src/config/user_config.hpp | 3 + src/graphics/irr_driver.cpp | 1 + src/graphics/post_processing.cpp | 81 +++++++++++++++++++++ src/graphics/post_processing.hpp | 5 +- src/graphics/speed_streaks.cpp | 77 ++++++++++++++++++++ src/graphics/speed_streaks.hpp | 76 ++++++++++++++++++++ src/karts/abstract_kart.cpp | 22 ++++++ src/karts/abstract_kart.hpp | 11 ++- src/karts/kart.cpp | 37 ++++++---- src/karts/kart.hpp | 6 ++ src/utils/profiler.cpp | 3 +- src/utils/profiler.hpp | 1 + 13 files changed, 425 insertions(+), 15 deletions(-) create mode 100644 data/shaders/speed_streaks.frag create mode 100644 src/graphics/speed_streaks.cpp create mode 100644 src/graphics/speed_streaks.hpp diff --git a/data/shaders/speed_streaks.frag b/data/shaders/speed_streaks.frag new file mode 100644 index 00000000000..4d372894427 --- /dev/null +++ b/data/shaders/speed_streaks.frag @@ -0,0 +1,117 @@ +// SuperTuxKart - a fun racing game with go-kart +// Copyright (C) 2026 the SuperTuxKart team +// Copyright (C) 2026 sbilliet +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 3 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + + +// speed_streaks.frag + +uniform float u_intensity; // 0.0 to 1.0, how strong the effect is (derived from getBoostLevel) +uniform float u_radial_scroll_offset; // Integrated uv offset: encodes speed/time/direction. +uniform float u_time; // Raw world->getTime() specifically needed for color change over time. + +uniform sampler2D noise_texture; + +out vec4 FragColor; + +const float WISP_DENSITY = 0.60; // Density <=> how many wisps should survive the density smoothstep. +const float WISP_SOFTNESS = 0.25; // Cutoff for noise range, determines how soft the wisps appear. +const float WISP_LENGTH = 0.10; // How much should we stretch the UV space radially. +const float WISP_AMOUNT = 6.0; // Amount of discrete sectors to divide the polar coordinates in. +const float WISP_RIPPLE = 0.003; // Strength of the distortion applied to the noise_texture.y coordinates. +const float WISP_ACTIVATION = 0.05; // Threshold boost level at which the effect starts being fully visible. + +const float RADIAL_MASK_SOFTNESS = 1.0; +const float RADIAL_MASK_SIZE_MIN = 0.75; +const float RADIAL_MASK_SIZE_MAX = 0.25; + +const float COLOR_CHANGE_SPEED = 8.0; // At what speed should the color change clockwise over time. +const float COLOR_SATURATION_MIN = 0.1; +const float COLOR_SATURATION_MAX = 1.0; + +const float PI = 3.14159; + +void main() { + // -- COORDS SETUP -- // + // Normalize UVs [0, 1] + vec2 uv = gl_FragCoord.xy / u_screen; + + // Centre UVs [-0.5f, 0.5f] + uv = uv - 0.5f; + + // Correct for aspect ratio (e.g., 16/9 -> 1.77) + uv.x *= u_screen.x / u_screen.y; + + // Convert Cartesian coords to Polar coords (angle & distance) + // Calculate the angle (-PI to PI) and distance around/from the center point. + const float polar_angle_radians = atan(uv.y, uv.x); + const float polar_distance = length(uv); + + // Normalize the angle to the [0, 1] range + const float normalized_angle = polar_angle_radians / (2 * PI); + + const float wisp_sector_index = normalized_angle * WISP_AMOUNT; + + // Radial coordinate (y portion of noise_texture UVs) used for scrolling the texture outwards. + const float base_radial_scroll = (polar_distance - u_radial_scroll_offset) * WISP_LENGTH; + + // -- NOISE SAMPLE(s) -- // + // Wisp noise distortion + const float ripple_angle_frequency = polar_angle_radians * WISP_AMOUNT; + const float radial_ripple = sin(ripple_angle_frequency) * WISP_RIPPLE; + const float distorted_radial_scroll = base_radial_scroll + radial_ripple; + + // First layer of simplex noise (base noise layer) + const vec2 noise_uv_primary = vec2(wisp_sector_index, distorted_radial_scroll); + const float noise_sample_primary = texture(noise_texture, noise_uv_primary).r; + + // Second layer of simplex noise + // Scale and shift coords at different frequencies to create parallax between noise layers. + const vec2 secondary_noise_modifiers = vec2(1.571, 1.350); + const vec2 noise_uv_secondary = noise_uv_primary * secondary_noise_modifiers; + + const float noise_sample_secondary = texture(noise_texture, noise_uv_secondary).r; + + // Intersection of both noise layers, final noise data. + float wisp_noise_mask = min(noise_sample_primary, noise_sample_secondary); + + // Threshold wisp mask using density parameters (e.g. discard range of noise frequencies) + wisp_noise_mask = smoothstep(WISP_DENSITY, WISP_DENSITY + WISP_SOFTNESS, wisp_noise_mask); + + // -- RADIAL MASK -- // + const float radial_mask_size = mix(RADIAL_MASK_SIZE_MIN, RADIAL_MASK_SIZE_MAX, u_intensity); + const float radial_mask = smoothstep(radial_mask_size, radial_mask_size + RADIAL_MASK_SOFTNESS, polar_distance); + wisp_noise_mask = wisp_noise_mask * radial_mask; + + // -- ACTIVATION FADE -- // + // Threshold the visibility of the effect at low boost levels and smoothly fade in at target edge. + float wisp_activation_fade = smoothstep(0.0, WISP_ACTIVATION, u_intensity); + wisp_noise_mask = wisp_noise_mask * wisp_activation_fade; + + //-------------// + // -- COLOR -- // + //-------------// + // Inigo Quilez cosine palette technique to generate smooth procedural gradient based on input factor. + const float color_change_offset = u_time * COLOR_CHANGE_SPEED; // Rotate/Offset through color clockwise over time. + const float base_hue_shift = normalized_angle + color_change_offset; + const float color_saturation = mix(COLOR_SATURATION_MIN, COLOR_SATURATION_MAX, u_intensity); + + const vec3 spectral_hue = 0.5 + 0.5 * cos(6.28318 * (base_hue_shift + vec3(0.0, 0.333, 0.667))); + const vec3 palette_output_color = mix(vec3(1.0), spectral_hue, color_saturation); + + // -- FINAL OUTPUT -- / + FragColor = vec4(palette_output_color * wisp_noise_mask, wisp_noise_mask); +} diff --git a/src/config/user_config.hpp b/src/config/user_config.hpp index 7cf2b9de8f6..72ef2cc9408 100644 --- a/src/config/user_config.hpp +++ b/src/config/user_config.hpp @@ -693,6 +693,9 @@ namespace UserConfigParams PARAM_PREFIX BoolUserConfigParam m_dof PARAM_DEFAULT(BoolUserConfigParam(false, "enable_dof", &m_video_group, "Enable Depth of Field")); + PARAM_PREFIX BoolUserConfigParam m_speed_streaks + PARAM_DEFAULT(BoolUserConfigParam(false, "enable_speed_streaks", + &m_video_group, "Enable Speed Streaks")); PARAM_PREFIX BoolUserConfigParam m_old_driver_popup PARAM_DEFAULT(BoolUserConfigParam(true, "old_driver_popup", &m_video_group, "Determines if popup message about too old drivers should be displayed.")); diff --git a/src/graphics/irr_driver.cpp b/src/graphics/irr_driver.cpp index 8ead0d83be0..4dff27d49b0 100644 --- a/src/graphics/irr_driver.cpp +++ b/src/graphics/irr_driver.cpp @@ -160,6 +160,7 @@ static const char* m_perf_query_phase[Q_LAST] = "Bloom", "Tonemap", "Motion Blur", + "Speed Streaks", "Lightning", "MLAA", "GUI", diff --git a/src/graphics/post_processing.cpp b/src/graphics/post_processing.cpp index 2ae02136a1e..83920dc5a2d 100644 --- a/src/graphics/post_processing.cpp +++ b/src/graphics/post_processing.cpp @@ -35,8 +35,10 @@ #include "graphics/sp/sp_dynamic_draw_call.hpp" #include "graphics/sp/sp_shader.hpp" #include "graphics/sp/sp_uniform_assigner.hpp" +#include "graphics/speed_streaks.hpp" #include "io/file_manager.hpp" #include "karts/abstract_kart.hpp" +#include "karts/kart.hpp" #include "karts/kart_model.hpp" #include "modes/world.hpp" #include "physics/physics.hpp" @@ -576,6 +578,23 @@ class MotionBlurShader : public TextureShader +{ +public: + SpeedStreaksShader() + { + loadProgram(OBJECT, GL_VERTEX_SHADER, "screenquad.vert", + GL_FRAGMENT_SHADER, "speed_streaks.frag"); + assignUniforms("u_intensity", "u_radial_scroll_offset", "u_time"); + assignSamplerNames(0, "noise_texture", ST_BILINEAR_FILTERED); + } //SpeedStreaksShader + void render(GLuint noise_texture, float intensity, float radial_scroll_offset, float time) + { + setTextureUnits(noise_texture); + drawFullScreenEffect(intensity, radial_scroll_offset, time); + } // render +}; // SpeedStreaksShader + // ============================================================================ class GodFadeShader : public TextureShader { @@ -724,9 +743,25 @@ PostProcessing::PostProcessing() STKTexManager::getInstance()->addTexture(m_areamap); areamap->drop(); + // Load the simplex noise texture + const std::string noiseTexturePath = file_manager->getAsset( + FileManager::AssetType::TEXTURE, + "stk_noise_simplex.png" + ); + + //TODO: implement single_channel path, currently loads rgba even though noise_texture is single channel only. + m_noise_texture = GE::createTexture(noiseTexturePath); + if (!m_noise_texture || m_noise_texture->getTextureHandler() == 0) + { + Log::fatal("postprocessing", "Failed to load %s", noiseTexturePath.c_str()); + return; + } + STKTexManager::getInstance()->addTexture(m_noise_texture); + // For preloading shaders MotionBlurShader::getInstance(); LightningShader::getInstance(); + SpeedStreaksShader::getInstance(); } // PostProcessing // ---------------------------------------------------------------------------- @@ -1019,6 +1054,40 @@ void PostProcessing::renderMotionBlur(const FrameBuffer &in_fbo, MotionBlurShader::getInstance()->render(in_fbo, boost_time, depth_stencil_texture); } // renderMotionBlur +// ---------------------------------------------------------------------------- +void PostProcessing::renderSpeedStreaks() +{ + World* world = World::getWorld(); + Camera* cam = Camera::getActiveCamera(); + const Kart* kart = cam ? dynamic_cast(cam->getKart()) : nullptr; + SpeedStreaks* speed_streaks = kart ? kart->getSpeedStreaks() : nullptr; + + float radial_scroll_offset = 0.0f; + float intensity = 0.0f; + + if (speed_streaks) + { + radial_scroll_offset = speed_streaks->getRadialScrollOffset(); + intensity = speed_streaks->getBoostIntensity(); + + auto direction = SpeedStreaks::Direction::FORWARD; + if(cam->getMode() == Camera::CM_REVERSE) + { + direction = SpeedStreaks::Direction::REVERSE; + } + speed_streaks->setDirection(direction); + } + + + glEnable(GL_BLEND); + glBlendEquation(GL_FUNC_ADD); + glBlendFunc(GL_ONE, GL_ONE); + + SpeedStreaksShader::getInstance()->render(m_noise_texture->getTextureHandler(), intensity, radial_scroll_offset, world->getTime()); + + glDisable(GL_BLEND); +} + // ---------------------------------------------------------------------------- void PostProcessing::renderDoF(const FrameBuffer &framebuffer, GLuint color_texture, GLuint depth_stencil_texture) { @@ -1304,6 +1373,18 @@ FrameBuffer *PostProcessing::render(scene::ICameraSceneNode * const camnode, in_fbo = &rtts->getFBO(FBO_RGBA_1); out_fbo = &rtts->getFBO(FBO_RGBA_2); + { + PROFILER_PUSH_CPU_MARKER("- Speed streaks", 0xFF, 0x00, 0x00); + ScopedGPUTimer Timer(irr_driver->getGPUTimer(Q_SPEEDSTREAKS)); + if (isRace && World::getWorld() && UserConfigParams::m_speed_streaks) + { + in_fbo->bind(); + renderSpeedStreaks(); + // additive blend, specifically no ping/pong std::swap + } + PROFILER_POP_CPU_MARKER(); + } + { PROFILER_PUSH_CPU_MARKER("- Motion blur", 0xFF, 0x00, 0x00); ScopedGPUTimer Timer(irr_driver->getGPUTimer(Q_MOTIONBLUR)); diff --git a/src/graphics/post_processing.hpp b/src/graphics/post_processing.hpp index 82d796705bf..b4c3c8e949d 100644 --- a/src/graphics/post_processing.hpp +++ b/src/graphics/post_processing.hpp @@ -47,7 +47,7 @@ class PostProcessing std::vector m_boost_time; video::ITexture* m_areamap; - + video::ITexture* m_noise_texture; public: PostProcessing(); @@ -87,6 +87,9 @@ class PostProcessing void renderMotionBlur(const FrameBuffer &in_fbo, FrameBuffer &out_fbo, GLuint depth_stencil_texture); + + void renderSpeedStreaks(); + void renderGlow(const FrameBuffer& quarter_framebuffer) const; void renderLightning(core::vector3df intensity); diff --git a/src/graphics/speed_streaks.cpp b/src/graphics/speed_streaks.cpp new file mode 100644 index 00000000000..8d69a55b0e8 --- /dev/null +++ b/src/graphics/speed_streaks.cpp @@ -0,0 +1,77 @@ +// SuperTuxKart - a fun racing game with go-kart +// Copyright (C) 2026 the SuperTuxKart-Team +// Copyright (C) 2026 sbilliet +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 3 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +#include "graphics/speed_streaks.hpp" +#include "karts/abstract_kart.hpp" +#include + +using namespace irr; + +SpeedStreaks::SpeedStreaks(const AbstractKart& kart) + : m_kart(kart), + m_filtered_boost_intensity(0.0f), + m_radial_scroll_offset(0.0f), + m_direction(Direction::FORWARD) +{ +} + +// Calculate the boost intensity value. +// See comments on s_boost_intensity_lower_ratio and s_boost_intensity_upper_ratio for parameters. +float SpeedStreaks::calcBoostIntensity(float lower_bound_ratio, float upper_bound_ratio) const +{ + assert(lower_bound_ratio >= 0.0f && upper_bound_ratio > lower_bound_ratio); + float speed_ratio = m_kart.getCurrentAdditionalSpeedRatio(); + + const float result = (speed_ratio - lower_bound_ratio) / (upper_bound_ratio - lower_bound_ratio); + return core::clamp(result, 0.0f, 1.0f); +} + +// Set the direction in which the streaks should move on screen (inwards/outwards). +void SpeedStreaks::setDirection(Direction dir) +{ + m_direction = dir; +} + +void SpeedStreaks::reset() +{ + m_filtered_boost_intensity = 0.0f; + m_radial_scroll_offset = 0.0f; + m_direction = Direction::FORWARD; +} + +void SpeedStreaks::update(float dt) +{ + // Filter the kart's boost intensity to avoid abrupt visual changes. + // - Uses the last level and smoothly approaches the new value over time. + // - The speed at which it does this is dictated by s_boost_smoothing_rate. + const float interpolant = 1.0f - std::exp(-s_boost_intensity_smoothing_rate * dt); //exponential decay + m_filtered_boost_intensity = core::lerp( + m_filtered_boost_intensity, + calcBoostIntensity(s_boost_intensity_lower_ratio, s_boost_intensity_upper_ratio), + interpolant + ); + + // Accumulate UV scroll offset over time. + // Accumulated on CPU to ensure a continuous offset over time; + // Passing per-frame speed(dictated by intensity) and time values directly + // to the shader would produce discontinuous UV scrolling. + // + // Encodes speed/time/ and direction together in a single uniform to be passed to the shader. + const float streak_speed = s_streaks_speed_min + s_streaks_speed_max * m_filtered_boost_intensity; + m_radial_scroll_offset += dt * streak_speed * m_direction; +} diff --git a/src/graphics/speed_streaks.hpp b/src/graphics/speed_streaks.hpp new file mode 100644 index 00000000000..b6195026b22 --- /dev/null +++ b/src/graphics/speed_streaks.hpp @@ -0,0 +1,76 @@ +// SuperTuxKart - a fun racing game with go-kart +// Copyright (C) 2026 the SuperTuxKart-Team +// Copyright (C) 2026 sbilliet +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 3 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +#ifndef HEADER_SPEED_STREAKS_HPP +#define HEADER_SPEED_STREAKS_HPP + +class AbstractKart; +class SpeedStreaks +{ +public: + // Direction in which the streaks should move on screen + // Tied to which camera is active, forward or reversing camera. + enum Direction{ + REVERSE = -1, + FORWARD = 1, + }; + + SpeedStreaks(const AbstractKart& kart); + + void reset(); + void update(float dt); + + // Set the direction in which the streaks should move on screen (inwards/outwards). + void setDirection(Direction direction); + + float getBoostIntensity() const { return m_filtered_boost_intensity; } + float getRadialScrollOffset() const { return m_radial_scroll_offset; } + +private: + // Calculate the boost intensity value. + // See comments on s_boost_intensity_lower_ratio and s_boost_intensity_upper_ratio for parameters. + float calcBoostIntensity(float lower_bound_ratio = 0.0f, float upper_bound_ratio = 1.0f) const; + + // Reference to the kart to which these speed streak parameters belong. + const AbstractKart& m_kart; + + // Smoothened kart boost intensity (lerped from current to the target each frame). + float m_filtered_boost_intensity; + + // Integrated uv offset: encodes speed/time/direction. + float m_radial_scroll_offset; + + // Speed streak direction (move inwards/outwards), based on camera facing. + Direction m_direction; + + // How fast/slow should the boost intensity interpolation settle on the target. + constexpr static float s_boost_intensity_smoothing_rate = 8.0f; + + // Speed excess (as a fraction of the generic engine max speed) at which the + // streak intensity starts rising: 0.0 = the kart's own unboosted top speed. + constexpr static float s_boost_intensity_lower_ratio = 0.0f; + // Speed excess (as a fraction of the generic engine max speed) at which the + // streak intensity reaches 1.0f. + constexpr static float s_boost_intensity_upper_ratio = 0.75f; + + // How fast/slow should the streaks move across the screen at low and full intensity. + constexpr static float s_streaks_speed_min = 3.0f; + constexpr static float s_streaks_speed_max = 6.0f; +}; + +#endif diff --git a/src/karts/abstract_kart.cpp b/src/karts/abstract_kart.cpp index b83f2473fb3..09dd64d2e5a 100644 --- a/src/karts/abstract_kart.cpp +++ b/src/karts/abstract_kart.cpp @@ -276,3 +276,25 @@ void AbstractKart::makeKartRest() body->proceedToTransform(t); setTrans(t); } // makeKartRest + +// ------------------------------------------------------------------------ +/** Returns the speed amount above the kart's own unboosted max speed. + */ +float AbstractKart::getCurrentAdditionalSpeed() const +{ + const float speed = getSpeed(); + const float current_engine_max_speed = m_kart_properties->getEngineMaxSpeed(); + return core::max_(speed - current_engine_max_speed, 0.0f); +} // getCurrentAdditionalSpeed + +// ------------------------------------------------------------------------ +/** Returns the ratio of the speed amount above the kart's own unboosted max speed + * relative to the generic engine max speed. + */ +float AbstractKart::getCurrentAdditionalSpeedRatio() const +{ + const float additional_speed_amount = getCurrentAdditionalSpeed(); + const float absolute_engine_max_speed = m_kart_properties->getEngineGenericMaxSpeed(); + + return additional_speed_amount / absolute_engine_max_speed; +} // getCurrentAdditionalSpeedRatio diff --git a/src/karts/abstract_kart.hpp b/src/karts/abstract_kart.hpp index dafd3c70a6d..5291ed8fdde 100644 --- a/src/karts/abstract_kart.hpp +++ b/src/karts/abstract_kart.hpp @@ -310,7 +310,16 @@ class AbstractKart : public Moveable * category. * \param category Which category to report on. */ virtual int getSpeedIncreaseTicksLeft(unsigned int category) const = 0; - + // ------------------------------------------------------------------------ + /** Returns the speed amount above the kart's own unboosted max speed. + */ + virtual float getCurrentAdditionalSpeed() const; + // ------------------------------------------------------------------------ + /** Returns the ratio of the speed amount above the kart's own unboosted max speed + * relative to the generic engine max speed. + */ + virtual float getCurrentAdditionalSpeedRatio() const; + // ------------------------------------------------------------------------ /** Sets the kart AI boost state. * Not pure abstract, since there is no need to implement this e.g. in Ghost. diff --git a/src/karts/kart.cpp b/src/karts/kart.cpp index 4408cd0e856..194ed7073ed 100644 --- a/src/karts/kart.cpp +++ b/src/karts/kart.cpp @@ -38,6 +38,7 @@ #include "graphics/shadow.hpp" #include "graphics/skid_marks.hpp" #include "graphics/slip_stream.hpp" +#include "graphics/speed_streaks.hpp" #include "graphics/stk_text_billboard.hpp" #include "graphics/stars.hpp" #include "guiengine/scalable_font.hpp" @@ -444,6 +445,9 @@ void Kart::reset() { m_skidmarks->reset(); } + if (m_speedstreaks){ + m_speedstreaks->reset(); + }; #endif Vec3 front(0, 0, getKartLength()*0.5f); @@ -3031,12 +3035,15 @@ void Kart::loadData(RaceManager::KartType type, bool is_animated_model) #ifndef SERVER_ONLY m_skidmarks = nullptr; m_shadow = nullptr; + m_speedstreaks = nullptr; if (!GUIEngine::isNoGraphics() && m_kart_properties->getSkidEnabled()) { m_skidmarks.reset(new SkidMarks(*this)); } + m_speedstreaks.reset(new SpeedStreaks(*this)); + if (!GUIEngine::isNoGraphics() && (!CVS->isGLSL() || !CVS->isShadowEnabled()) && m_kart_properties ->getShadowMaterial()->getSamplerPath(0) != "unicolor_white") @@ -3375,18 +3382,24 @@ void Kart::updateGraphics(float dt) static video::SColor green(255, 61, 87, 23); #ifndef SERVER_ONLY - // draw skidmarks if relevant (we force pink skidmarks on when hitting - // a bubblegum) - if (World::getWorld()->getPhase() != - WorldStatus::IN_GAME_MENU_PHASE && - m_kart_properties->getSkidEnabled() && m_skidmarks) - { - m_skidmarks->update(dt, - m_bubblegum_ticks > 0, - (m_bubblegum_ticks > 0 - ? (m_has_caught_nolok_bubblegum ? &green - : &pink) - : NULL)); + if (World::getWorld()->getPhase() != WorldStatus::IN_GAME_MENU_PHASE) + { + // draw skidmarks if relevant (we force pink skidmarks on when hitting + // a bubblegum) + if (m_kart_properties->getSkidEnabled() && m_skidmarks) + { + m_skidmarks->update(dt, + m_bubblegum_ticks > 0, + (m_bubblegum_ticks > 0 + ? (m_has_caught_nolok_bubblegum ? &green + : &pink) + : NULL)); + } + + // update speedstreak parameters + if(m_speedstreaks){ + m_speedstreaks->update(dt); + } } #endif diff --git a/src/karts/kart.hpp b/src/karts/kart.hpp index 762a51d7f95..2645d40e585 100644 --- a/src/karts/kart.hpp +++ b/src/karts/kart.hpp @@ -53,6 +53,7 @@ class Skidding; class SkidMarks; class SlipStream; class Stars; +class SpeedStreaks; class TerrainInfo; #ifndef SERVER_ONLY @@ -137,6 +138,9 @@ class Kart : public AbstractKart /** The skidmarks object for this kart. */ std::unique_ptr m_skidmarks; + + /** Parameters for SpeedStreaks effect. */ + std::unique_ptr m_speedstreaks; #endif /** All particle effects. */ @@ -594,6 +598,8 @@ class Kart : public AbstractKart // ---------------------------------------------------------------------------------------- virtual Stars* getStarsEffect() const OVERRIDE { return m_stars_effect.get(); } + // ---------------------------------------------------------------------------------------- + SpeedStreaks* getSpeedStreaks() const { return m_speedstreaks.get(); } // ------------------------------------------------------------------------ /** Return the confirmed finish ticks (sent by the server) * indicating that this kart has really finished the race. */ diff --git a/src/utils/profiler.cpp b/src/utils/profiler.cpp index 9821f60a813..09ab23d655d 100644 --- a/src/utils/profiler.cpp +++ b/src/utils/profiler.cpp @@ -724,7 +724,7 @@ void Profiler::writeToFile() << "Dynamic lighting, Particle effects, Animated characters, Geometry Detail, " << "Bloom, Glow, Light Shaft, Anti-Aliasing (MLAA), SSAO," << "Anisotropic Filtering, Shadow Resolution, Light scattering, Degraded IBL," - << "Motion Blur, Depth of Field, Texture compression, HD Textures, HQ Mipmap,"; + << "Motion Blur, Depth of Field, Speed Streaks, Texture compression, HD Textures, HQ Mipmap,"; f << std::endl; f << "Values, " << UserConfigParams::m_real_width << ", " @@ -745,6 +745,7 @@ void Profiler::writeToFile() << UserConfigParams::m_degraded_IBL << ", " << UserConfigParams::m_motionblur << ", " << UserConfigParams::m_dof << ", " + << UserConfigParams::m_speed_streaks << ", " << UserConfigParams::m_texture_compression << ", " << UserConfigParams::m_high_definition_textures << ", " << UserConfigParams::m_hq_mipmap << ","; diff --git a/src/utils/profiler.hpp b/src/utils/profiler.hpp index 8bdcc2db8ec..2b96337b090 100644 --- a/src/utils/profiler.hpp +++ b/src/utils/profiler.hpp @@ -57,6 +57,7 @@ enum QueryPerf Q_BLOOM, Q_TONEMAP, Q_MOTIONBLUR, + Q_SPEEDSTREAKS, Q_LIGHTNING, Q_MLAA, Q_GUI, From c3a63e66c1b206f226993d4eeee45c58d069fc74 Mon Sep 17 00:00:00 2001 From: stijnbilliet Date: Thu, 8 Oct 2026 17:10:14 +0100 Subject: [PATCH 2/3] Adding video settings toggle for speed_streaks effect --- data/gui/dialogs/custom_video_settings.stkgui | 10 ++++++++++ src/main.cpp | 7 +++++++ src/states_screens/dialogs/custom_video_settings.cpp | 5 +++++ src/states_screens/options/options_screen_video.cpp | 4 ++++ src/states_screens/race_result_gui.cpp | 8 +++++--- 5 files changed, 31 insertions(+), 3 deletions(-) diff --git a/data/gui/dialogs/custom_video_settings.stkgui b/data/gui/dialogs/custom_video_settings.stkgui index 2026a22eb17..76e87ec9558 100644 --- a/data/gui/dialogs/custom_video_settings.stkgui +++ b/data/gui/dialogs/custom_video_settings.stkgui @@ -135,6 +135,16 @@ +
+ + +
+ + +
+
+
diff --git a/src/main.cpp b/src/main.cpp index 8e98e738a4e..bdfd63bf5a7 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -699,6 +699,8 @@ void cmdLineHelp() " --disable-light-shaft Disable light shafts (God rays).\n" " --enable-dof Enable depth of field.\n" " --disable-dof Disable depth of field.\n" + " --enable-speed-streaks Enable speed streaks.\n" + " --disable-speed-streaks Disable speed streaks.\n" " --enable-particles Enable particles.\n" " --disable-particles Disable particles.\n" " --enable-animated-characters Enable animated characters.\n" @@ -996,6 +998,11 @@ int handleCmdLinePreliminary() UserConfigParams::m_dof = true; else if (CommandLine::has("--disable-dof")) UserConfigParams::m_dof = false; + // speed streaks + if (CommandLine::has("--enable-speed-streaks")) + UserConfigParams::m_speed_streaks = true; + else if (CommandLine::has("--disable-speed-streaks")) + UserConfigParams::m_speed_streaks = false; // particles effects if (CommandLine::has("--enable-particles")) UserConfigParams::m_particles_effects = 2; diff --git a/src/states_screens/dialogs/custom_video_settings.cpp b/src/states_screens/dialogs/custom_video_settings.cpp index 9940b5c9cc2..34627221103 100644 --- a/src/states_screens/dialogs/custom_video_settings.cpp +++ b/src/states_screens/dialogs/custom_video_settings.cpp @@ -107,6 +107,7 @@ void CustomVideoSettingsDialog::beforeAddingWidgets() getWidget("ssr")->setState(UserConfigParams::m_ssr); getWidget("bloom")->setState(UserConfigParams::m_bloom); getWidget("lightscattering")->setState(UserConfigParams::m_light_scatter); + getWidget("speedstreaks")->setState(UserConfigParams::m_speed_streaks); if (CVS->isEXTTextureCompressionS3TCUsable()) { getWidget("texture_compression")->setState(UserConfigParams::m_texture_compression); @@ -231,6 +232,9 @@ GUIEngine::EventPropagation CustomVideoSettingsDialog::processEvent(const std::s UserConfigParams::m_light_scatter = advanced_pipeline && getWidget("lightscattering")->getState(); + UserConfigParams::m_speed_streaks = + advanced_pipeline && getWidget("speedstreaks")->getState(); + bool force_reload_texture = getWidget("texture_compression")->getState() != UserConfigParams::m_texture_compression; UserConfigParams::m_texture_compression = @@ -342,6 +346,7 @@ void CustomVideoSettingsDialog::updateActivation(const std::string& renderer) getWidget("ssao")->setActive(light); getWidget("ssr")->setActive(light || (vk && real_light)); getWidget("lightshaft")->setActive(light); + getWidget("speedstreaks")->setActive(light); getWidget("ibl")->setActive(light || (vk && real_light)); getWidget("glow")->setActive(light); getWidget("bloom")->setActive(light); diff --git a/src/states_screens/options/options_screen_video.cpp b/src/states_screens/options/options_screen_video.cpp index 890c2f8a8ca..ae3d7a09c1c 100644 --- a/src/states_screens/options/options_screen_video.cpp +++ b/src/states_screens/options/options_screen_video.cpp @@ -409,6 +409,10 @@ void OptionsScreenVideo::updateTooltip() ((UserConfigParams::m_bloom && !vk) ? _("Bloom: Enabled") : _("Bloom: Disabled")); //I18N: in the graphical options + tooltip = tooltip + L"\n" + + ((UserConfigParams::m_speed_streaks && !vk) ? _("Speed streaks: Enabled") : + _("Speed streaks: Disabled")); + //I18N: in the graphical options tooltip = tooltip + L"\n" + ((UserConfigParams::m_ssao && !vk) ? _("Ambient occlusion: Enabled") : _("Ambient occlusion: Disabled")); diff --git a/src/states_screens/race_result_gui.cpp b/src/states_screens/race_result_gui.cpp index 876f4d16bea..64b53471f17 100644 --- a/src/states_screens/race_result_gui.cpp +++ b/src/states_screens/race_result_gui.cpp @@ -2423,7 +2423,7 @@ void RaceResultGUI::displayBenchmarkSummary() font = GUIEngine::getFont(); rect = font->getDimension(title_text.c_str()); - core::stringw info_text[11]; + core::stringw info_text[12]; core::stringw value = StringUtils::toWString( StringUtils::timeToString(float(profiler.getTotalFrametime())/1000000.0f, 2, true)); info_text[0] = _("Test duration: %s", value); @@ -2489,13 +2489,15 @@ void RaceResultGUI::displayBenchmarkSummary() _("Soft shadows: Disabled"); info_text[9] = UserConfigParams::m_dof ? _("Depth of field: Enabled") : _("Depth of field: Disabled"); + info_text[10] = UserConfigParams::m_speed_streaks ? _("Speed streaks: Enabled") : + _("Speed streaks: Disabled"); value = vk ? StringUtils::toWString("Vulkan") : modern_gl ? _("OpenGL (modern)") : gl ? _("OpenGL (legacy)") : directx ? StringUtils::toWString("DirectX 9") : _("Unknown"); - info_text[10] = value; + info_text[11] = value; - for (int i = 0; i < 11; i++) + for (int i = 0; i < 12; i++) { pos = core::rect(current_x, current_y, current_x, current_y); font->draw(info_text[i].c_str(), pos, white_color, true, false); From d2fae5cef5c64871690e4df3bcb967a407d0ecde Mon Sep 17 00:00:00 2001 From: stijnbilliet Date: Sat, 17 Oct 2026 12:16:35 +0100 Subject: [PATCH 3/3] Revert "Adding video settings toggle for speed_streaks effect" This reverts commit c3a63e6. Will be recommited in a separate PR for clarify. --- data/gui/dialogs/custom_video_settings.stkgui | 10 ---------- src/main.cpp | 7 ------- src/states_screens/dialogs/custom_video_settings.cpp | 5 ----- src/states_screens/options/options_screen_video.cpp | 4 ---- src/states_screens/race_result_gui.cpp | 8 +++----- 5 files changed, 3 insertions(+), 31 deletions(-) diff --git a/data/gui/dialogs/custom_video_settings.stkgui b/data/gui/dialogs/custom_video_settings.stkgui index 76e87ec9558..2026a22eb17 100644 --- a/data/gui/dialogs/custom_video_settings.stkgui +++ b/data/gui/dialogs/custom_video_settings.stkgui @@ -135,16 +135,6 @@ -
- - -
- - -
-
-
diff --git a/src/main.cpp b/src/main.cpp index bdfd63bf5a7..8e98e738a4e 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -699,8 +699,6 @@ void cmdLineHelp() " --disable-light-shaft Disable light shafts (God rays).\n" " --enable-dof Enable depth of field.\n" " --disable-dof Disable depth of field.\n" - " --enable-speed-streaks Enable speed streaks.\n" - " --disable-speed-streaks Disable speed streaks.\n" " --enable-particles Enable particles.\n" " --disable-particles Disable particles.\n" " --enable-animated-characters Enable animated characters.\n" @@ -998,11 +996,6 @@ int handleCmdLinePreliminary() UserConfigParams::m_dof = true; else if (CommandLine::has("--disable-dof")) UserConfigParams::m_dof = false; - // speed streaks - if (CommandLine::has("--enable-speed-streaks")) - UserConfigParams::m_speed_streaks = true; - else if (CommandLine::has("--disable-speed-streaks")) - UserConfigParams::m_speed_streaks = false; // particles effects if (CommandLine::has("--enable-particles")) UserConfigParams::m_particles_effects = 2; diff --git a/src/states_screens/dialogs/custom_video_settings.cpp b/src/states_screens/dialogs/custom_video_settings.cpp index 34627221103..9940b5c9cc2 100644 --- a/src/states_screens/dialogs/custom_video_settings.cpp +++ b/src/states_screens/dialogs/custom_video_settings.cpp @@ -107,7 +107,6 @@ void CustomVideoSettingsDialog::beforeAddingWidgets() getWidget("ssr")->setState(UserConfigParams::m_ssr); getWidget("bloom")->setState(UserConfigParams::m_bloom); getWidget("lightscattering")->setState(UserConfigParams::m_light_scatter); - getWidget("speedstreaks")->setState(UserConfigParams::m_speed_streaks); if (CVS->isEXTTextureCompressionS3TCUsable()) { getWidget("texture_compression")->setState(UserConfigParams::m_texture_compression); @@ -232,9 +231,6 @@ GUIEngine::EventPropagation CustomVideoSettingsDialog::processEvent(const std::s UserConfigParams::m_light_scatter = advanced_pipeline && getWidget("lightscattering")->getState(); - UserConfigParams::m_speed_streaks = - advanced_pipeline && getWidget("speedstreaks")->getState(); - bool force_reload_texture = getWidget("texture_compression")->getState() != UserConfigParams::m_texture_compression; UserConfigParams::m_texture_compression = @@ -346,7 +342,6 @@ void CustomVideoSettingsDialog::updateActivation(const std::string& renderer) getWidget("ssao")->setActive(light); getWidget("ssr")->setActive(light || (vk && real_light)); getWidget("lightshaft")->setActive(light); - getWidget("speedstreaks")->setActive(light); getWidget("ibl")->setActive(light || (vk && real_light)); getWidget("glow")->setActive(light); getWidget("bloom")->setActive(light); diff --git a/src/states_screens/options/options_screen_video.cpp b/src/states_screens/options/options_screen_video.cpp index ae3d7a09c1c..890c2f8a8ca 100644 --- a/src/states_screens/options/options_screen_video.cpp +++ b/src/states_screens/options/options_screen_video.cpp @@ -409,10 +409,6 @@ void OptionsScreenVideo::updateTooltip() ((UserConfigParams::m_bloom && !vk) ? _("Bloom: Enabled") : _("Bloom: Disabled")); //I18N: in the graphical options - tooltip = tooltip + L"\n" + - ((UserConfigParams::m_speed_streaks && !vk) ? _("Speed streaks: Enabled") : - _("Speed streaks: Disabled")); - //I18N: in the graphical options tooltip = tooltip + L"\n" + ((UserConfigParams::m_ssao && !vk) ? _("Ambient occlusion: Enabled") : _("Ambient occlusion: Disabled")); diff --git a/src/states_screens/race_result_gui.cpp b/src/states_screens/race_result_gui.cpp index 64b53471f17..876f4d16bea 100644 --- a/src/states_screens/race_result_gui.cpp +++ b/src/states_screens/race_result_gui.cpp @@ -2423,7 +2423,7 @@ void RaceResultGUI::displayBenchmarkSummary() font = GUIEngine::getFont(); rect = font->getDimension(title_text.c_str()); - core::stringw info_text[12]; + core::stringw info_text[11]; core::stringw value = StringUtils::toWString( StringUtils::timeToString(float(profiler.getTotalFrametime())/1000000.0f, 2, true)); info_text[0] = _("Test duration: %s", value); @@ -2489,15 +2489,13 @@ void RaceResultGUI::displayBenchmarkSummary() _("Soft shadows: Disabled"); info_text[9] = UserConfigParams::m_dof ? _("Depth of field: Enabled") : _("Depth of field: Disabled"); - info_text[10] = UserConfigParams::m_speed_streaks ? _("Speed streaks: Enabled") : - _("Speed streaks: Disabled"); value = vk ? StringUtils::toWString("Vulkan") : modern_gl ? _("OpenGL (modern)") : gl ? _("OpenGL (legacy)") : directx ? StringUtils::toWString("DirectX 9") : _("Unknown"); - info_text[11] = value; + info_text[10] = value; - for (int i = 0; i < 12; i++) + for (int i = 0; i < 11; i++) { pos = core::rect(current_x, current_y, current_x, current_y); font->draw(info_text[i].c_str(), pos, white_color, true, false);