From 744b7cabca6f911169d12e71b2ec99b7dd9dcced Mon Sep 17 00:00:00 2001 From: Kai Blaschke Date: Mon, 10 Nov 2025 12:24:45 +0100 Subject: [PATCH 01/12] Force use of Boost's config package in projectM's installed CMake config --- src/libprojectM/projectM4Config.cmake.in | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/libprojectM/projectM4Config.cmake.in b/src/libprojectM/projectM4Config.cmake.in index 00244c4dff..883a6738b9 100644 --- a/src/libprojectM/projectM4Config.cmake.in +++ b/src/libprojectM/projectM4Config.cmake.in @@ -13,7 +13,11 @@ if(NOT "@ENABLE_EMSCRIPTEN@") # ENABLE_EMSCRIPTEN endif() endif() if("@ENABLE_BOOST_FILESYSTEM@") # ENABLE_BOOST_FILESYSTEM - find_dependency(Boost COMPONENTS Filesystem) + if(POLICY CMP0167) + cmake_policy(SET CMP0167 NEW) + endif() + + find_dependency(Boost CONFIG NO_MODULE COMPONENTS Filesystem) endif() if(CMAKE_SYSTEM_NAME STREQUAL "Windows") find_dependency(GLEW) From 614ad90f190bf9a50d24c3b2d177957fe2d7ddfb Mon Sep 17 00:00:00 2001 From: yoyofr Date: Wed, 12 Nov 2025 09:31:01 +0100 Subject: [PATCH 02/12] Set vertex_point_size uniform for all uses of the untextured vertex shader Only custom waves set the point size to 2, default waveforms still render the 2x2 pattern when using dots. Other draw calls aren't affected as they only draw lines/triangles, but it's good practice to not leave the uniform in an undefined state. Signed-off-by: Kai Blaschke --- src/libprojectM/MilkdropPreset/Border.cpp | 1 + src/libprojectM/MilkdropPreset/CustomShape.cpp | 3 +-- src/libprojectM/MilkdropPreset/CustomWaveform.cpp | 1 + src/libprojectM/MilkdropPreset/DarkenCenter.cpp | 1 + src/libprojectM/MilkdropPreset/Filters.cpp | 1 + src/libprojectM/MilkdropPreset/Waveform.cpp | 1 + 6 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/libprojectM/MilkdropPreset/Border.cpp b/src/libprojectM/MilkdropPreset/Border.cpp index 8ed978c138..10dd8c9d90 100644 --- a/src/libprojectM/MilkdropPreset/Border.cpp +++ b/src/libprojectM/MilkdropPreset/Border.cpp @@ -37,6 +37,7 @@ void Border::Draw(const PerFrameContext& presetPerFrameContext) auto shader = m_presetState.untexturedShader.lock(); shader->Bind(); shader->SetUniformMat4x4("vertex_transformation", PresetState::orthogonalProjection); + shader->SetUniformFloat("vertex_point_size", 1.0f); m_borderMesh.Bind(); diff --git a/src/libprojectM/MilkdropPreset/CustomShape.cpp b/src/libprojectM/MilkdropPreset/CustomShape.cpp index ae30b7ea17..4f9a0f3acf 100644 --- a/src/libprojectM/MilkdropPreset/CustomShape.cpp +++ b/src/libprojectM/MilkdropPreset/CustomShape.cpp @@ -223,8 +223,7 @@ void CustomShape::Draw() auto shader = m_presetState.untexturedShader.lock(); shader->Bind(); shader->SetUniformMat4x4("vertex_transformation", PresetState::orthogonalProjection); - - m_outlineMesh.Bind(); + shader->SetUniformFloat("vertex_point_size", 1.0f); glVertexAttrib4f(1, static_cast(*m_perFrameContext.border_r), diff --git a/src/libprojectM/MilkdropPreset/CustomWaveform.cpp b/src/libprojectM/MilkdropPreset/CustomWaveform.cpp index 0b62ec28fa..b24574f18f 100644 --- a/src/libprojectM/MilkdropPreset/CustomWaveform.cpp +++ b/src/libprojectM/MilkdropPreset/CustomWaveform.cpp @@ -175,6 +175,7 @@ void CustomWaveform::Draw(const PerFrameContext& presetPerFrameContext) auto shader = m_presetState.untexturedShader.lock(); shader->Bind(); shader->SetUniformMat4x4("vertex_transformation", PresetState::orthogonalProjection); + shader->SetUniformFloat("vertex_point_size", m_drawThick ? 2.0f : 1.0f); auto iterations = (m_drawThick && !m_useDots) ? 4 : 1; diff --git a/src/libprojectM/MilkdropPreset/DarkenCenter.cpp b/src/libprojectM/MilkdropPreset/DarkenCenter.cpp index c550afc3ea..1bd1d8a126 100644 --- a/src/libprojectM/MilkdropPreset/DarkenCenter.cpp +++ b/src/libprojectM/MilkdropPreset/DarkenCenter.cpp @@ -42,6 +42,7 @@ void DarkenCenter::Draw() auto shader = m_presetState.untexturedShader.lock(); shader->Bind(); shader->SetUniformMat4x4("vertex_transformation", PresetState::orthogonalProjection); + shader->SetUniformFloat("vertex_point_size", 1.0f); m_mesh.Draw(); diff --git a/src/libprojectM/MilkdropPreset/Filters.cpp b/src/libprojectM/MilkdropPreset/Filters.cpp index c84d9784be..e5d8702df1 100644 --- a/src/libprojectM/MilkdropPreset/Filters.cpp +++ b/src/libprojectM/MilkdropPreset/Filters.cpp @@ -29,6 +29,7 @@ void Filters::Draw() auto shader = m_presetState.untexturedShader.lock(); shader->Bind(); shader->SetUniformMat4x4("vertex_transformation", PresetState::orthogonalProjection); + shader->SetUniformFloat("vertex_point_size", 1.0f); glVertexAttrib4f(1, 1.0, 1.0, 1.0, 1.0); diff --git a/src/libprojectM/MilkdropPreset/Waveform.cpp b/src/libprojectM/MilkdropPreset/Waveform.cpp index caaca5987a..701ed24dc0 100644 --- a/src/libprojectM/MilkdropPreset/Waveform.cpp +++ b/src/libprojectM/MilkdropPreset/Waveform.cpp @@ -42,6 +42,7 @@ void Waveform::Draw(const PerFrameContext& presetPerFrameContext) auto shader = m_presetState.untexturedShader.lock(); shader->Bind(); shader->SetUniformMat4x4("vertex_transformation", PresetState::orthogonalProjection); + shader->SetUniformFloat("vertex_point_size", 1.0f); // Additive wave drawing (vice overwrite) if (m_presetState.additiveWaves) From d3734dc4c31d79dc759291d7b6382c4fa5b3d921 Mon Sep 17 00:00:00 2001 From: yoyofr Date: Wed, 12 Nov 2025 09:54:47 +0100 Subject: [PATCH 03/12] Fix some default waveform rendering bugs All waves were rendered upside-down, breaking presets requiring the wave to be at the top or bottom due to warp movement. Using the flipped transformation matrix for drawing will take care of this globally. Also fixed two small mistakes regarding operators. Signed-off-by: Kai Blaschke --- src/libprojectM/MilkdropPreset/Waveform.cpp | 2 +- src/libprojectM/MilkdropPreset/Waveforms/Line.cpp | 2 +- src/libprojectM/MilkdropPreset/Waveforms/WaveformMath.cpp | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/libprojectM/MilkdropPreset/Waveform.cpp b/src/libprojectM/MilkdropPreset/Waveform.cpp index 701ed24dc0..0277d10c4d 100644 --- a/src/libprojectM/MilkdropPreset/Waveform.cpp +++ b/src/libprojectM/MilkdropPreset/Waveform.cpp @@ -41,7 +41,7 @@ void Waveform::Draw(const PerFrameContext& presetPerFrameContext) auto shader = m_presetState.untexturedShader.lock(); shader->Bind(); - shader->SetUniformMat4x4("vertex_transformation", PresetState::orthogonalProjection); + shader->SetUniformMat4x4("vertex_transformation", PresetState::orthogonalProjectionFlipped); shader->SetUniformFloat("vertex_point_size", 1.0f); // Additive wave drawing (vice overwrite) diff --git a/src/libprojectM/MilkdropPreset/Waveforms/Line.cpp b/src/libprojectM/MilkdropPreset/Waveforms/Line.cpp index 01271180ab..a180baaebe 100644 --- a/src/libprojectM/MilkdropPreset/Waveforms/Line.cpp +++ b/src/libprojectM/MilkdropPreset/Waveforms/Line.cpp @@ -17,7 +17,7 @@ void Line::GenerateVertices(const PresetState& presetState, const PerFrameContex m_wave1Vertices.resize(m_samples); - ClipWaveformEdges(1.57f + m_mysteryWaveParam); + ClipWaveformEdges(1.57f * m_mysteryWaveParam); for (int i = 0; i < m_samples; i++) { diff --git a/src/libprojectM/MilkdropPreset/Waveforms/WaveformMath.cpp b/src/libprojectM/MilkdropPreset/Waveforms/WaveformMath.cpp index f33e3d1ea5..f3e50f211b 100644 --- a/src/libprojectM/MilkdropPreset/Waveforms/WaveformMath.cpp +++ b/src/libprojectM/MilkdropPreset/Waveforms/WaveformMath.cpp @@ -71,7 +71,7 @@ auto WaveformMath::GetVertices(const PresetState& presetState, m_mysteryWaveParam = static_cast(*presetPerFrameContext.wave_mystery); - if (UsesNormalizedMysteryParam() && (m_mysteryWaveParam < 1.0f || m_mysteryWaveParam > 1.0f)) + if (UsesNormalizedMysteryParam() && (m_mysteryWaveParam < -1.0f || m_mysteryWaveParam > 1.0f)) { m_mysteryWaveParam = m_mysteryWaveParam * 0.5f + 0.5f; m_mysteryWaveParam -= std::floor(m_mysteryWaveParam); From ec59502d5acf2d7b38fcc133231a656c375f098a Mon Sep 17 00:00:00 2001 From: yoyofr Date: Wed, 19 Nov 2025 11:00:54 +0100 Subject: [PATCH 04/12] Set warp texel offset to 0 by default, as 0.5 introduces a drift. Since this might be vendor-specific behavior, a new API function to configure the X/Y texel offsets has been added. Signed-off-by: Kai Blaschke --- src/api/include/projectM-4/parameters.h | 28 +++++++++++++++++++ .../MilkdropPreset/PerPixelMesh.cpp | 4 +-- src/libprojectM/ProjectM.cpp | 17 +++++++++++ src/libprojectM/ProjectM.hpp | 6 ++++ src/libprojectM/ProjectMCWrapper.cpp | 12 ++++++++ src/libprojectM/Renderer/RenderContext.hpp | 3 ++ 6 files changed, 68 insertions(+), 2 deletions(-) diff --git a/src/api/include/projectM-4/parameters.h b/src/api/include/projectM-4/parameters.h index f68e10ad8d..5bd7d901dd 100644 --- a/src/api/include/projectM-4/parameters.h +++ b/src/api/include/projectM-4/parameters.h @@ -227,6 +227,34 @@ PROJECTM_EXPORT void projectm_set_mesh_size(projectm_handle instance, size_t wid */ PROJECTM_EXPORT void projectm_get_mesh_size(projectm_handle instance, size_t* width, size_t* height); +/** + * @brief Applies a sub-texel offset for main texture lookups in the warp shader. + * + * Original Milkdrop uses 0.5 here, but it doesn't seem to be required in OpenGL as this value + * introduces a slight warp drift to the top left. As this may be vendor-specific, this value can + * be configured externally to fix any possible drift. + * + * @param instance The projectM instance handle. + * @param offset_X The offset in texels in the horizontal direction. Milkdrop uses 0.5, default in projectM is 0.0. + * @param offset_y The offset in texels in the vertical direction. Milkdrop uses 0.5, default in projectM is 0.0. + * @since 4.2.0 + */ +PROJECTM_EXPORT void projectm_set_texel_offset(projectm_handle instance, float offset_X, float offset_y); + +/** + * @brief Retrieves the current sub-texel offsets for main texture lookups in the warp shader. + * + * Original Milkdrop uses 0.5 here, but it doesn't seem to be required in OpenGL as this value + * introduces a slight warp drift to the top left. As this may be vendor-specific, this value can + * be configured externally to fix any possible drift. + * + * @param instance The projectM instance handle. + * @param offset_X A valid pointer to a float variable that will receive the currently set horizontal texel offset. + * @param offset_y A valid pointer to a float variable that will receive the currently set vertical texel offset. + * @since 4.2.0 + */ +PROJECTM_EXPORT void projectm_get_texel_offset(projectm_handle instance, float* offset_X, float* offset_y); + /** * @brief Sets the current/average frames per second. * diff --git a/src/libprojectM/MilkdropPreset/PerPixelMesh.cpp b/src/libprojectM/MilkdropPreset/PerPixelMesh.cpp index bb9bd8d85f..8241910a14 100644 --- a/src/libprojectM/MilkdropPreset/PerPixelMesh.cpp +++ b/src/libprojectM/MilkdropPreset/PerPixelMesh.cpp @@ -308,8 +308,8 @@ void PerPixelMesh::WarpedBlit(const PresetState& presetState, }; // Texel alignment - glm::vec2 const texelOffsets{0.5f / static_cast(presetState.renderContext.viewportSizeX), - 0.5f / static_cast(presetState.renderContext.viewportSizeY)}; + glm::vec2 const texelOffsets{presetState.renderContext.texelOffsetX / static_cast(presetState.renderContext.viewportSizeX), + presetState.renderContext.texelOffsetY / static_cast(presetState.renderContext.viewportSizeY)}; // Decay float decay = std::min(static_cast(*perFrameContext.decay), 1.0f); diff --git a/src/libprojectM/ProjectM.cpp b/src/libprojectM/ProjectM.cpp index 495c5d4b7d..dc73ea0f2d 100644 --- a/src/libprojectM/ProjectM.cpp +++ b/src/libprojectM/ProjectM.cpp @@ -455,6 +455,18 @@ void ProjectM::SetMeshSize(uint32_t meshResolutionX, uint32_t meshResolutionY) m_meshY = std::max(8u, std::min(400u, m_meshY)); } +void ProjectM::TexelOffsets(float& texelOffsetX, float& texelOffsetY) const +{ + texelOffsetX = m_texelOffsetX; + texelOffsetY = m_texelOffsetY; +} + +void ProjectM::SetTexelOffsets(float texelOffsetX, float texelOffsetY) +{ + m_texelOffsetX = texelOffsetX; + m_texelOffsetY = texelOffsetY; +} + auto ProjectM::PCM() -> libprojectM::Audio::PCM& { return m_audioStorage; @@ -493,8 +505,13 @@ auto ProjectM::GetRenderContext() -> Renderer::RenderContext ctx.aspectY = (m_windowWidth > m_windowHeight) ? static_cast(m_windowHeight) / static_cast(m_windowWidth) : 1.0f; ctx.invAspectX = 1.0f / ctx.aspectX; ctx.invAspectY = 1.0f / ctx.aspectY; + ctx.perPixelMeshX = static_cast(m_meshX); ctx.perPixelMeshY = static_cast(m_meshY); + + ctx.texelOffsetX = m_texelOffsetX; + ctx.texelOffsetY = m_texelOffsetY; + ctx.textureManager = m_textureManager.get(); ctx.shaderCache = m_shaderCache.get(); diff --git a/src/libprojectM/ProjectM.hpp b/src/libprojectM/ProjectM.hpp index a718d500a4..f83f9c49b3 100644 --- a/src/libprojectM/ProjectM.hpp +++ b/src/libprojectM/ProjectM.hpp @@ -177,6 +177,10 @@ class PROJECTM_EXPORT ProjectM void SetMeshSize(uint32_t meshResolutionX, uint32_t meshResolutionY); + void TexelOffsets(float& texelOffsetX, float& texelOffsetY) const; + + void SetTexelOffsets(float texelOffsetX, float texelOffsetY); + void Touch(float touchX, float touchY, int pressure, int touchType); void TouchDrag(float touchX, float touchY, int pressure); @@ -265,6 +269,8 @@ class PROJECTM_EXPORT ProjectM bool m_aspectCorrection{true}; //!< If true, corrects aspect ratio for non-rectangular windows. float m_easterEgg{1.0}; //!< Random preset duration modifier. See TimeKeeper class. float m_previousFrameVolume{}; //!< Volume in previous frame, used for hard cuts. + float m_texelOffsetX{0.0}; //!< Horizontal warp shader texel offset + float m_texelOffsetY{0.0}; //!< Vertical warp shader texel offset std::vector m_textureSearchPaths; ///!< List of paths to search for texture files diff --git a/src/libprojectM/ProjectMCWrapper.cpp b/src/libprojectM/ProjectMCWrapper.cpp index 5119314cf4..e0d9f1325d 100644 --- a/src/libprojectM/ProjectMCWrapper.cpp +++ b/src/libprojectM/ProjectMCWrapper.cpp @@ -273,6 +273,18 @@ void projectm_get_mesh_size(projectm_handle instance, size_t* width, size_t* hei *height = static_cast(h); } +void projectm_set_texel_offsete(projectm_handle instance, float offset_X, float offset_y) +{ + auto projectMInstance = handle_to_instance(instance); + projectMInstance->SetTexelOffsets(offset_X, offset_y); +} + +void projectm_get_texel_offsete(projectm_handle instance, float* offset_X, float* offset_y) +{ + auto projectMInstance = handle_to_instance(instance); + projectMInstance->TexelOffsets(*offset_X, *offset_y); +} + void projectm_set_mesh_size(projectm_handle instance, size_t width, size_t height) { auto projectMInstance = handle_to_instance(instance); diff --git a/src/libprojectM/Renderer/RenderContext.hpp b/src/libprojectM/Renderer/RenderContext.hpp index 895a36b85c..bd8adb472e 100644 --- a/src/libprojectM/Renderer/RenderContext.hpp +++ b/src/libprojectM/Renderer/RenderContext.hpp @@ -31,6 +31,9 @@ class RenderContext int perPixelMeshX{64}; //!< Per-pixel/per-vertex mesh X resolution. int perPixelMeshY{48}; //!< Per-pixel/per-vertex mesh Y resolution. + float texelOffsetX{0.0f}; //!< Horizontal texel offset in the warp shader. + float texelOffsetY{0.0f}; //!< Vertical texel offset in the warp shader. + TextureManager* textureManager{nullptr}; //!< Holds all loaded textures for shader access. ShaderCache* shaderCache{nullptr}; //!< The shader chace of this projectM instance. }; From d0d9e56bf1076d85e4d8087f31c0ba57238d5b2d Mon Sep 17 00:00:00 2001 From: yoyofr Date: Wed, 19 Nov 2025 10:40:42 +0100 Subject: [PATCH 05/12] Add additional row-access functions for matrices in HLSL transpiler Fixes "martin - mayday (shifters escape retouched).milk" and probably some others. Signed-off-by: Kai Blaschke --- vendor/hlslparser/src/GLSLGenerator.cpp | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/vendor/hlslparser/src/GLSLGenerator.cpp b/vendor/hlslparser/src/GLSLGenerator.cpp index c8afbd797d..a68ab58760 100644 --- a/vendor/hlslparser/src/GLSLGenerator.cpp +++ b/vendor/hlslparser/src/GLSLGenerator.cpp @@ -257,10 +257,25 @@ bool GLSLGenerator::Generate(HLSLTree* tree, Target target, Version version, con // Output the special function used to access rows in a matrix. m_writer.WriteLine(0, "vec2 %s(mat2 m, int i) { return vec2( m[0][i], m[1][i] ); }", m_matrixRowFunction); + m_writer.WriteLine(0, "vec2 %s(mat2 m, float i_float) { int i=int(i_float); return vec2( m[0][i], m[1][i] ); }", m_matrixRowFunction); + m_writer.WriteLine(0, "vec2 %s(mat2x3 m, int i) { return vec2( m[0][i], m[1][i]); }", m_matrixRowFunction); + m_writer.WriteLine(0, "vec2 %s(mat2x3 m, float i_float) { int i=int(i_float); return vec2( m[0][i], m[1][i]); }", m_matrixRowFunction); + m_writer.WriteLine(0, "vec2 %s(mat2x4 m, int i) { return vec2( m[0][i], m[1][i]); }", m_matrixRowFunction); + m_writer.WriteLine(0, "vec2 %s(mat2x4 m, float i_float) { int i=int(i_float); return vec2( m[0][i], m[1][i]); }", m_matrixRowFunction); + m_writer.WriteLine(0, "vec3 %s(mat3 m, int i) { return vec3( m[0][i], m[1][i], m[2][i] ); }", m_matrixRowFunction); + m_writer.WriteLine(0, "vec3 %s(mat3 m, float i_float) { int i=int(i_float); return vec3( m[0][i], m[1][i], m[2][i] ); }", m_matrixRowFunction); + m_writer.WriteLine(0, "vec3 %s(mat3x2 m, int i) { return vec3( m[0][i], m[1][i], m[2][i] ); }", m_matrixRowFunction); + m_writer.WriteLine(0, "vec3 %s(mat3x2 m, float i_float) { int i=int(i_float); return vec3( m[0][i], m[1][i], m[2][i] ); }", m_matrixRowFunction); m_writer.WriteLine(0, "vec3 %s(mat3x4 m, int i) { return vec3( m[0][i], m[1][i], m[2][i] ); }", m_matrixRowFunction); + m_writer.WriteLine(0, "vec3 %s(mat3x4 m, float i_float) { int i=int(i_float); return vec3( m[0][i], m[1][i], m[2][i] ); }", m_matrixRowFunction); + m_writer.WriteLine(0, "vec4 %s(mat4 m, int i) { return vec4( m[0][i], m[1][i], m[2][i], m[3][i] ); }", m_matrixRowFunction); + m_writer.WriteLine(0, "vec4 %s(mat4 m, float i_float) { int i=int(i_float); return vec4( m[0][i], m[1][i], m[2][i], m[3][i] ); }", m_matrixRowFunction); m_writer.WriteLine(0, "vec4 %s(mat4x3 m, int i) { return vec4( m[0][i], m[1][i], m[2][i], m[3][i] ); }", m_matrixRowFunction); + m_writer.WriteLine(0, "vec4 %s(mat4x3 m, float i_float) { int i=int(i_float); return vec4( m[0][i], m[1][i], m[2][i], m[3][i] ); }", m_matrixRowFunction); + m_writer.WriteLine(0, "vec4 %s(mat4x2 m, int i) { return vec4( m[0][i], m[1][i], m[2][i], m[3][i] ); }", m_matrixRowFunction); + m_writer.WriteLine(0, "vec4 %s(mat4x2 m, float i_float) { int i=int(i_float); return vec4( m[0][i], m[1][i], m[2][i], m[3][i] ); }", m_matrixRowFunction); // Output the special function used to do matrix cast for OpenGL 2.0 if (m_versionLegacy) From 7d968bc1dde4c018012c2cc0edf51eeec582accb Mon Sep 17 00:00:00 2001 From: yoyofr Date: Wed, 19 Nov 2025 12:01:48 +0100 Subject: [PATCH 06/12] Add missing "fwidth" intrinsic in HLSL transpiler Signed-off-by: Kai Blaschke --- vendor/hlslparser/src/HLSLParser.cpp | 147 ++++++++++++++------------- 1 file changed, 76 insertions(+), 71 deletions(-) diff --git a/vendor/hlslparser/src/HLSLParser.cpp b/vendor/hlslparser/src/HLSLParser.cpp index 358df27703..aaf73007c8 100644 --- a/vendor/hlslparser/src/HLSLParser.cpp +++ b/vendor/hlslparser/src/HLSLParser.cpp @@ -36,7 +36,7 @@ enum CompareFunctionsResult Function2Better }; - + /** This structure stores a HLSLFunction-like declaration for an intrinsic function */ struct Intrinsic { @@ -103,7 +103,7 @@ struct Intrinsic HLSLFunction function; HLSLArgument argument[4]; }; - + Intrinsic SamplerIntrinsic(const char* name, HLSLBaseType returnType, HLSLBaseType arg1, HLSLBaseType samplerType, HLSLBaseType arg2) { Intrinsic i(name, returnType, arg1, arg2); @@ -113,7 +113,7 @@ Intrinsic SamplerIntrinsic(const char* name, HLSLBaseType returnType, HLSLBaseTy -static const int _numberTypeRank[NumericType_Count][NumericType_Count] = +static const int _numberTypeRank[NumericType_Count][NumericType_Count] = { //F B I U { 0, 4, 4, 4 }, // NumericType_Float @@ -261,7 +261,7 @@ static const EffectState samplerStates[] = { {"MipMapLodBias", 8, floatValues}, {"MaxMipLevel", 9, integerValues}, {"MaxAnisotropy", 10, integerValues}, - {"sRGBTexture", 11, booleanValues}, + {"sRGBTexture", 11, booleanValues}, }; static const EffectState effectStates[] = { @@ -415,7 +415,7 @@ static const EffectState pipelineStates[] = { #define SAMPLER_INTRINSIC_FUNCTION(name, sampler, arg1) \ Intrinsic( name, HLSLBaseType_Float4, sampler, arg1) #endif - + const Intrinsic _intrinsic[] = { INTRINSIC_FLOAT1_FUNCTION( "abs" ), @@ -478,6 +478,11 @@ const Intrinsic _intrinsic[] = Intrinsic( "length", HLSLBaseType_Float, HLSLBaseType_Float3 ), Intrinsic( "length", HLSLBaseType_Float, HLSLBaseType_Float4 ), + Intrinsic( "fwidth", HLSLBaseType_Float, HLSLBaseType_Float ), + Intrinsic( "fwidth", HLSLBaseType_Float, HLSLBaseType_Float2 ), + Intrinsic( "fwidth", HLSLBaseType_Float, HLSLBaseType_Float3 ), + Intrinsic( "fwidth", HLSLBaseType_Float, HLSLBaseType_Float4 ), + Intrinsic( "distance", HLSLBaseType_Float, HLSLBaseType_Float , HLSLBaseType_Float ), Intrinsic( "distance", HLSLBaseType_Float, HLSLBaseType_Float2, HLSLBaseType_Float2 ), Intrinsic( "distance", HLSLBaseType_Float, HLSLBaseType_Float3, HLSLBaseType_Float3 ), @@ -617,10 +622,10 @@ const Intrinsic _intrinsic[] = INTRINSIC_FLOAT1_FUNCTION( "log" ), INTRINSIC_FLOAT1_FUNCTION( "log2" ), INTRINSIC_FLOAT1_FUNCTION( "log10" ), - + INTRINSIC_FLOAT1_FUNCTION( "ddx" ), INTRINSIC_FLOAT1_FUNCTION( "ddy" ), - + INTRINSIC_FLOAT1_FUNCTION( "sign" ), INTRINSIC_FLOAT2_FUNCTION( "step" ), INTRINSIC_FLOAT2_FUNCTION( "reflect" ), @@ -657,15 +662,15 @@ const Intrinsic _intrinsic[] = Intrinsic("asuint", HLSLBaseType_Uint, HLSLBaseType_Float), SAMPLER_INTRINSIC_FUNCTION("tex2D", HLSLBaseType_Sampler2D, HLSLBaseType_Float2), - + Intrinsic("tex2Dproj", HLSLBaseType_Float4, HLSLBaseType_Sampler2D, HLSLBaseType_Float4), SAMPLER_INTRINSIC_FUNCTION("tex2Dlod", HLSLBaseType_Sampler2D, HLSLBaseType_Float4), - + Intrinsic("tex2Dlod", HLSLBaseType_Float4, HLSLBaseType_Sampler2D, HLSLBaseType_Float4, HLSLBaseType_Int2), // With offset. SAMPLER_INTRINSIC_FUNCTION("tex2Dbias", HLSLBaseType_Sampler2D, HLSLBaseType_Float4), - + Intrinsic("tex2Dgrad", HLSLBaseType_Float4, HLSLBaseType_Sampler2D, HLSLBaseType_Float2, HLSLBaseType_Float2, HLSLBaseType_Float2), Intrinsic("tex2Dgather", HLSLBaseType_Float4, HLSLBaseType_Sampler2D, HLSLBaseType_Float2, HLSLBaseType_Int), Intrinsic("tex2Dgather", HLSLBaseType_Float4, HLSLBaseType_Sampler2D, HLSLBaseType_Float2, HLSLBaseType_Int2, HLSLBaseType_Int), // With offset. @@ -718,7 +723,7 @@ const int _binaryOpPriority[] = // IC: I'm not sure this table is right, but any errors should be caught by the backend compiler. // Also, this is operator dependent. The type resulting from (float4 * float4x4) is not the same as (float4 + float4x4). // We should probably distinguish between component-wise operator and only allow same dimensions -HLSLBaseType _binaryOpTypeLookup[HLSLBaseType_NumericCount][HLSLBaseType_NumericCount] = +HLSLBaseType _binaryOpTypeLookup[HLSLBaseType_NumericCount][HLSLBaseType_NumericCount] = { { // float HLSLBaseType_Float, HLSLBaseType_Float2, HLSLBaseType_Float3, HLSLBaseType_Float4, @@ -1001,7 +1006,7 @@ static const char* GetBinaryOpName(HLSLBinaryOp binaryOp) * 4.) Conversion + scalar dimension promotion * 5.) Truncation (vector -> scalar or lower component vector, matrix -> scalar or lower component matrix) * 6.) Conversion + truncation - */ + */ static int GetTypeCastRank(HLSLTree * tree, const HLSLType& srcType, const HLSLType& dstType) { /*if (srcType.array != dstType.array || srcType.arraySize != dstType.arraySize) @@ -1039,7 +1044,7 @@ static int GetTypeCastRank(HLSLTree * tree, const HLSLType& srcType, const HLSLT { return srcType.samplerType == dstType.samplerType ? 0 : -1; } - + return 0; } @@ -1081,7 +1086,7 @@ static int GetTypeCastRank(HLSLTree * tree, const HLSLType& srcType, const HLSLT } return result; - + } static bool GetFunctionCallCastRanks(HLSLTree* tree, const HLSLFunctionCall* call, const HLSLFunction* function, int* rankBuffer) @@ -1095,7 +1100,7 @@ static bool GetFunctionCallCastRanks(HLSLTree* tree, const HLSLFunctionCall* cal const HLSLExpression* expression = call->argument; const HLSLArgument* argument = function->argument; - + for (int i = 0; i < call->numArguments; ++i) { int rank = GetTypeCastRank(tree, expression->expressionType, argument->type); @@ -1105,7 +1110,7 @@ static bool GetFunctionCallCastRanks(HLSLTree* tree, const HLSLFunctionCall* cal } rankBuffer[i] = rank; - + argument = argument->nextArgument; expression = expression->nextExpression; } @@ -1129,7 +1134,7 @@ struct CompareRanks }; static CompareFunctionsResult CompareFunctions(HLSLTree* tree, const HLSLFunctionCall* call, const HLSLFunction* function1, const HLSLFunction* function2) -{ +{ #if defined _WIN32 && !defined alloca int* function1Ranks = static_cast(_alloca(sizeof(int) * call->numArguments)); @@ -1161,7 +1166,7 @@ static CompareFunctionsResult CompareFunctions(HLSLTree* tree, const HLSLFunctio std::sort(function1Ranks, function1Ranks + call->numArguments, CompareRanks()); std::sort(function2Ranks, function2Ranks + call->numArguments, CompareRanks()); - + for (int i = 0; i < call->numArguments; ++i) { if (function1Ranks[i] < function2Ranks[i]) @@ -1242,7 +1247,7 @@ static bool GetBinaryOpResultType(HLSLBinaryOp binaryOp, const HLSLType& type1, result.array = false; result.arraySize = NULL; result.flags = (type1.flags & type2.flags) & HLSLTypeFlag_Const; // Propagate constness. - + return result.baseType != HLSLBaseType_Unknown; } @@ -1358,7 +1363,7 @@ bool HLSLParser::ParseTopLevel(HLSLStatement*& statement) int line = GetLineNumber(); const char* fileName = GetFileName(); - + HLSLType type; //HLSLBaseType type; //const char* typeName = NULL; @@ -1390,7 +1395,7 @@ bool HLSLParser::ParseTopLevel(HLSLStatement*& statement) structure->name = structName; m_userTypes.PushBack(structure); - + HLSLStructField* lastField = NULL; // Add the struct to our list of user defined types. @@ -1544,7 +1549,7 @@ bool HLSLParser::ParseTopLevel(HLSLStatement*& statement) // Note, no semi-colon at the end of a function declaration. statement = function; - + return true; } else @@ -1693,7 +1698,7 @@ bool HLSLParser::ParseStatement(HLSLStatement*& statement, const HLSLType& retur } HLSLAttribute * attributes = NULL; - ParseAttributeBlock(attributes); // @@ Leak if not assigned to node? + ParseAttributeBlock(attributes); // @@ Leak if not assigned to node? #if 0 // @@ Work in progress. // Static statements: @if only for now. @@ -1704,9 +1709,9 @@ bool HLSLParser::ParseStatement(HLSLStatement*& statement, const HLSLType& retur //HLSLIfStatement* ifStatement = m_tree->AddNode(fileName, line); //ifStatement->isStatic = true; //ifStatement->attributes = attributes; - + HLSLExpression * condition = NULL; - + m_allowUndeclaredIdentifiers = true; // Not really correct... better to push to stack? if (!Expect('(') || !ParseExpression(condition) || !Expect(')')) { @@ -1714,25 +1719,25 @@ bool HLSLParser::ParseStatement(HLSLStatement*& statement, const HLSLType& retur return false; } m_allowUndeclaredIdentifiers = false; - + if ((condition->expressionType.flags & HLSLTypeFlag_Const) == 0) { m_tokenizer.Error("Syntax error: @if condition is not constant"); return false; } - + int conditionValue; if (!m_tree->GetExpressionValue(condition, conditionValue)) { m_tokenizer.Error("Syntax error: Cannot evaluate @if condition"); return false; } - + if (!conditionValue) m_disableSemanticValidation = true; - + HLSLStatement * ifStatements = NULL; HLSLStatement * elseStatements = NULL; - + if (!ParseStatementOrBlock(ifStatements, returnType, /*scoped=*/false)) { m_disableSemanticValidation = false; @@ -1741,7 +1746,7 @@ bool HLSLParser::ParseStatement(HLSLStatement*& statement, const HLSLType& retur if (Accept(HLSLToken_Else)) { if (conditionValue) m_disableSemanticValidation = true; - + if (!ParseStatementOrBlock(elseStatements, returnType, /*scoped=*/false)) { m_disableSemanticValidation = false; @@ -1749,12 +1754,12 @@ bool HLSLParser::ParseStatement(HLSLStatement*& statement, const HLSLType& retur } } m_disableSemanticValidation = false; - + if (conditionValue) statement = ifStatements; else statement = elseStatements; - + // @@ Free the pruned statements? - + return true; } else { @@ -1762,7 +1767,7 @@ bool HLSLParser::ParseStatement(HLSLStatement*& statement, const HLSLType& retur } } #endif - + // If statement. if (Accept(HLSLToken_If)) { @@ -1783,7 +1788,7 @@ bool HLSLParser::ParseStatement(HLSLStatement*& statement, const HLSLType& retur } return true; } - + // For statement. if (Accept(HLSLToken_For)) { @@ -2184,15 +2189,15 @@ bool HLSLParser::AcceptAssign(HLSLBinaryOp& binaryOp) else if (Accept(HLSLToken_MinusEqual)) { binaryOp = HLSLBinaryOp_SubAssign; - } + } else if (Accept(HLSLToken_TimesEqual)) { binaryOp = HLSLBinaryOp_MulAssign; - } + } else if (Accept(HLSLToken_DivideEqual)) { binaryOp = HLSLBinaryOp_DivAssign; - } + } else { return false; @@ -2242,10 +2247,10 @@ bool HLSLParser::ParseBinaryExpression(int priority, HLSLExpression*& expression return false; } - + // Propagate constness. binaryExpression->expressionType.flags = (expression->expressionType.flags | expression2->expressionType.flags) & HLSLTypeFlag_Const; - + expression = binaryExpression; } else if (_conditionalOpPriority > priority && Accept('?')) @@ -2253,7 +2258,7 @@ bool HLSLParser::ParseBinaryExpression(int priority, HLSLExpression*& expression HLSLConditionalExpression* conditionalExpression = m_tree->AddNode(fileName, line); conditionalExpression->condition = expression; - + HLSLExpression* expression1 = NULL; HLSLExpression* expression2 = NULL; if (!ParseBinaryExpression(_conditionalOpPriority, expression1) || !Expect(':') || !ParseBinaryExpression(_conditionalOpPriority, expression2)) @@ -2311,7 +2316,7 @@ bool HLSLParser::ParsePartialConstructor(HLSLExpression*& expression, HLSLBaseTy if (!ParseExpressionList(')', false, constructorExpression->argument, numArguments)) { return false; - } + } constructorExpression->expressionType = constructorExpression->type; constructorExpression->expressionType.flags = HLSLTypeFlag_Const; expression = constructorExpression; @@ -2336,7 +2341,7 @@ bool HLSLParser::ParseTerminalExpression(HLSLExpression*& expression, char& need } if (unaryOp == HLSLUnaryOp_BitNot) { - if (unaryExpression->expression->expressionType.baseType < HLSLBaseType_FirstInteger || + if (unaryExpression->expression->expressionType.baseType < HLSLBaseType_FirstInteger || unaryExpression->expression->expressionType.baseType > HLSLBaseType_LastInteger) { const char * typeName = GetTypeName(unaryExpression->expression->expressionType); @@ -2359,7 +2364,7 @@ bool HLSLParser::ParseTerminalExpression(HLSLExpression*& expression, char& need } unaryExpression->expressionType = HLSLType(HLSLBaseType_Bool); - + // Propagate constness. unaryExpression->expressionType.flags = unaryExpression->expression->expressionType.flags & HLSLTypeFlag_Const; } @@ -2395,7 +2400,7 @@ bool HLSLParser::ParseTerminalExpression(HLSLExpression*& expression, char& need expression = unaryExpression; return true; } - + // Expressions inside parenthesis or casts. char expressionEndChar = 0; if (Accept('(')) @@ -2426,7 +2431,7 @@ bool HLSLParser::ParseTerminalExpression(HLSLExpression*& expression, char& need castingExpression->expressionType = type; return Expect(')') && ParseExpression(castingExpression->expression); } - + int numArguments = 0; if (!ParseExpressionList(expressionEndChar, false, expression, numArguments)) { @@ -2447,7 +2452,7 @@ bool HLSLParser::ParseTerminalExpression(HLSLExpression*& expression, char& need // Terminal values. float fValue = 0.0f; int iValue = 0; - + if (AcceptFloat(fValue)) { HLSLLiteralExpression* literalExpression = m_tree->AddNode(fileName, line); @@ -2728,7 +2733,7 @@ bool HLSLParser::ParseArgumentList(HLSLArgument*& firstArgument, int& numArgumen { const char* fileName = GetFileName(); int line = GetLineNumber(); - + HLSLArgument* lastArgument = NULL; numArguments = 0; @@ -3000,7 +3005,7 @@ const EffectState* GetEffectState(const char* name, bool isSamplerState, bool is { const EffectState* validStates = effectStates; int count = sizeof(effectStates)/sizeof(effectStates[0]); - + if (isPipeline) { validStates = pipelineStates; @@ -3016,7 +3021,7 @@ const EffectState* GetEffectState(const char* name, bool isSamplerState, bool is // Case insensitive comparison. for (int i = 0; i < count; i++) { - if (String_EqualNoCase(name, validStates[i].name)) + if (String_EqualNoCase(name, validStates[i].name)) { return &validStates[i]; } @@ -3028,12 +3033,12 @@ const EffectState* GetEffectState(const char* name, bool isSamplerState, bool is static const EffectStateValue* GetStateValue(const char* name, const EffectState* state) { // Case insensitive comparison. - for (int i = 0; ; i++) + for (int i = 0; ; i++) { const EffectStateValue & value = state->values[i]; if (value.name == NULL) break; - if (String_EqualNoCase(name, value.name)) + if (String_EqualNoCase(name, value.name)) { return &value; } @@ -3099,7 +3104,7 @@ bool HLSLParser::ParseStateValue(const EffectState * state, HLSLStateAssignment* const bool expectsFloat = state->values == floatValues; const bool expectsBoolean = state->values == booleanValues; - if (!expectsExpression && !expectsInteger && !expectsFloat && !expectsBoolean) + if (!expectsExpression && !expectsInteger && !expectsFloat && !expectsBoolean) { if (m_tokenizer.GetToken() != HLSLToken_Identifier) { @@ -3176,7 +3181,7 @@ bool HLSLParser::ParseStateValue(const EffectState * state, HLSLStateAssignment* return false; } } - else + else { // Expect one of the allowed values. const EffectStateValue * stateValue = GetStateValue(m_tokenizer.GetIdentifier(), state); @@ -3233,7 +3238,7 @@ bool HLSLParser::ParseAttributeList(HLSLAttribute*& firstAttribute) { const char* fileName = GetFileName(); int line = GetLineNumber(); - + HLSLAttribute * lastAttribute = firstAttribute; do { const char * identifier = NULL; @@ -3242,12 +3247,12 @@ bool HLSLParser::ParseAttributeList(HLSLAttribute*& firstAttribute) } HLSLAttribute * attribute = m_tree->AddNode(fileName, line); - + if (strcmp(identifier, "unroll") == 0) attribute->attributeType = HLSLAttributeType_Unroll; else if (strcmp(identifier, "flatten") == 0) attribute->attributeType = HLSLAttributeType_Flatten; else if (strcmp(identifier, "branch") == 0) attribute->attributeType = HLSLAttributeType_Branch; else if (strcmp(identifier, "nofastmath") == 0) attribute->attributeType = HLSLAttributeType_NoFastMath; - + // @@ parse arguments, () not required if attribute constructor has no arguments. if (firstAttribute == NULL) @@ -3259,7 +3264,7 @@ bool HLSLParser::ParseAttributeList(HLSLAttribute*& firstAttribute) lastAttribute->nextAttribute = attribute; } lastAttribute = attribute; - + } while(Accept(',')); return true; @@ -3339,7 +3344,7 @@ bool HLSLParser::ParseStage(HLSLStatement*& statement) bool HLSLParser::Parse(const char* fileName, const char* buffer, size_t length) -{ +{ HLSLRoot* root = m_tree->GetRoot(); HLSLStatement* lastStatement = NULL; @@ -3352,7 +3357,7 @@ bool HLSLParser::Parse(const char* fileName, const char* buffer, size_t length) return false; } if (statement != NULL) - { + { if (lastStatement == NULL) { root->statement = statement; @@ -3556,7 +3561,7 @@ bool HLSLParser::ApplyPreprocessor(const char* fileName, const char* buffer, siz m_tokenizer.Next(); } - if (valueProcessed == "main") + if (valueProcessed == "main") { valueProcessed = "sampler_fw_main"; } @@ -3854,12 +3859,12 @@ bool HLSLParser::AcceptTypeModifier(int& flags) bool HLSLParser::AcceptInterpolationModifier(int& flags) { if (Accept("linear")) - { - flags |= HLSLTypeFlag_Linear; + { + flags |= HLSLTypeFlag_Linear; return true; } else if (Accept("centroid")) - { + { flags |= HLSLTypeFlag_Centroid; return true; } @@ -3908,7 +3913,7 @@ bool HLSLParser::AcceptType(bool allowVoid, HLSLType& type/*, bool acceptFlags*/ case HLSLToken_Double1x1: type.baseType = HLSLBaseType_Float; break; - case HLSLToken_Float2: + case HLSLToken_Float2: case HLSLToken_Float2x1: case HLSLToken_Half2: case HLSLToken_Half2x1: @@ -4041,7 +4046,7 @@ bool HLSLParser::AcceptType(bool allowVoid, HLSLType& type/*, bool acceptFlags*/ if (type.baseType != HLSLBaseType_Void) { m_tokenizer.Next(); - + if (IsSamplerType(type.baseType)) { // Parse optional sampler type. @@ -4058,7 +4063,7 @@ bool HLSLParser::AcceptType(bool allowVoid, HLSLType& type/*, bool acceptFlags*/ return false; } m_tokenizer.Next(); - + if (!Expect('>')) { m_tokenizer.Error("Syntax error: '>' expected for sampler type"); @@ -4303,7 +4308,7 @@ const HLSLFunction* HLSLParser::MatchFunctionCall(const HLSLFunctionCall* functi if (function->name == name) { nameMatches = true; - + CompareFunctionsResult result = CompareFunctions( m_tree, functionCall, function, matchedFunction ); if (result == Function1Better) { @@ -4454,7 +4459,7 @@ bool HLSLParser::GetMemberType(const HLSLType& objectType, HLSLMemberAccess * me static const HLSLBaseType intType[] = { HLSLBaseType_Int, HLSLBaseType_Int2, HLSLBaseType_Int3, HLSLBaseType_Int4 }; static const HLSLBaseType uintType[] = { HLSLBaseType_Uint, HLSLBaseType_Uint2, HLSLBaseType_Uint3, HLSLBaseType_Uint4 }; static const HLSLBaseType boolType[] = { HLSLBaseType_Bool, HLSLBaseType_Bool2, HLSLBaseType_Bool3, HLSLBaseType_Bool4 }; - + switch (baseTypeDescriptions[objectType.baseType].numericType) { case NumericType_Float: @@ -4474,7 +4479,7 @@ bool HLSLParser::GetMemberType(const HLSLType& objectType, HLSLMemberAccess * me } memberAccess->swizzle = true; - + return true; } From b7328ab2752460d95243aa707ff92dcec4beaba7 Mon Sep 17 00:00:00 2001 From: yoyofr Date: Wed, 19 Nov 2025 12:08:00 +0100 Subject: [PATCH 07/12] Fix a constant's type in float acos(float) overload in HLSL transpiler Signed-off-by: Kai Blaschke --- vendor/hlslparser/src/GLSLGenerator.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vendor/hlslparser/src/GLSLGenerator.cpp b/vendor/hlslparser/src/GLSLGenerator.cpp index a68ab58760..f5cda9bb26 100644 --- a/vendor/hlslparser/src/GLSLGenerator.cpp +++ b/vendor/hlslparser/src/GLSLGenerator.cpp @@ -426,7 +426,7 @@ bool GLSLGenerator::Generate(HLSLTree* tree, Target target, Version version, con * (such as post-increment or -decrement) the result would not be what we expect * if we evaluated this equation as a macro in OutputExpression. */ - m_writer.WriteLine(0, "float %s(float x) { if (abs(x) > 1.0) { return 0.39269908169872415*(4-sign(x)*(abs(x) - 3.0)*(abs(x) - 3.0)); } else { return acos(x); } }", m_acosFunction); + m_writer.WriteLine(0, "float %s(float x) { if (abs(x) > 1.0) { return 0.39269908169872415*(4.0-sign(x)*(abs(x) - 3.0)*(abs(x) - 3.0)); } else { return acos(x); } }", m_acosFunction); m_writer.WriteLine(0, "vec2 %s(vec2 x) { vec2 ret; ret.x = %s(x.x); ret.y = %s(x.y); return ret; }", m_acosFunction, m_acosFunction, m_acosFunction); m_writer.WriteLine(0, "vec3 %s(vec3 x) { vec3 ret; ret.x = %s(x.x); ret.y = %s(x.y); ret.z = %s(x.z); return ret; }", m_acosFunction, m_acosFunction, m_acosFunction, m_acosFunction); m_writer.WriteLine(0, "vec4 %s(vec4 x) { vec4 ret; ret.x = %s(x.x); ret.y = %s(x.y); ret.z = %s(x.z); ret.w = %s(x.w); return ret; }", m_acosFunction, m_acosFunction, m_acosFunction, m_acosFunction, m_acosFunction); From ae7c220d546125d9d1be9ad9a8b9cd44ae4a6eae Mon Sep 17 00:00:00 2001 From: yoyofr Date: Wed, 19 Nov 2025 12:15:16 +0100 Subject: [PATCH 08/12] Place shader "PS" function into its own line and wrap arguments for readability. Signed-off-by: Kai Blaschke --- src/libprojectM/MilkdropPreset/MilkdropShader.cpp | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/libprojectM/MilkdropPreset/MilkdropShader.cpp b/src/libprojectM/MilkdropPreset/MilkdropShader.cpp index 345ef69ff9..f2b203fe30 100644 --- a/src/libprojectM/MilkdropPreset/MilkdropShader.cpp +++ b/src/libprojectM/MilkdropPreset/MilkdropShader.cpp @@ -371,11 +371,22 @@ void MilkdropShader::PreprocessPresetShader(std::string& program) { if (m_type == ShaderType::WarpShader) { - program.replace(int(found), 11, "void PS(float4 _vDiffuse : COLOR, float4 _uv : TEXCOORD0, float2 _rad_ang : TEXCOORD1, out float4 _return_value : COLOR0, out float4 _mv_tex_coords : COLOR1)\n"); + program.replace(int(found), 11, R"( +void PS(float4 _vDiffuse : COLOR, + float4 _uv : TEXCOORD0, + float2 _rad_ang : TEXCOORD1, + out float4 _return_value : COLOR0, + out float4 _mv_tex_coords : COLOR1) +)"); } else { - program.replace(int(found), 11, "void PS(float4 _vDiffuse : COLOR, float2 _uv : TEXCOORD0, float2 _rad_ang : TEXCOORD1, out float4 _return_value : COLOR)\n"); + program.replace(int(found), 11, R"( +void PS(float4 _vDiffuse : COLOR, + float2 _uv : TEXCOORD0, + float2 _rad_ang : TEXCOORD1, + out float4 _return_value : COLOR) +)"); } } else From 2ed1df6370665026730ac86833e26d33ee51a1bb Mon Sep 17 00:00:00 2001 From: yoyofr Date: Wed, 19 Nov 2025 12:26:28 +0100 Subject: [PATCH 09/12] Fix wrong type used in PCM smoothing in custom waves. Signed-off-by: Kai Blaschke --- src/libprojectM/MilkdropPreset/CustomWaveform.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libprojectM/MilkdropPreset/CustomWaveform.cpp b/src/libprojectM/MilkdropPreset/CustomWaveform.cpp index b24574f18f..3ca12d9f44 100644 --- a/src/libprojectM/MilkdropPreset/CustomWaveform.cpp +++ b/src/libprojectM/MilkdropPreset/CustomWaveform.cpp @@ -104,7 +104,7 @@ void CustomWaveform::Draw(const PerFrameContext& presetPerFrameContext) // PCM data smoothing const int offset1 = m_spectrum ? 0 : (maxSampleCount - sampleCount) / 2 - m_sep / 2; const int offset2 = m_spectrum ? 0 : (maxSampleCount - sampleCount) / 2 + m_sep / 2; - const int t = m_spectrum ? static_cast(static_cast(maxSampleCount - m_sep) / static_cast(sampleCount)) : 1; + const float t = m_spectrum ? static_cast(maxSampleCount - m_sep) / static_cast(sampleCount) : 1.0f; const float mix1 = std::pow(m_smoothing * 0.98f, 0.5f); const float mix2 = 1.0f - mix1; From 18437522f5c46d0880b85f6759b10611de4ef0f5 Mon Sep 17 00:00:00 2001 From: yoyofr Date: Wed, 19 Nov 2025 13:03:32 +0100 Subject: [PATCH 10/12] Apply possible HLSL compiler optimization for pow() function with literal 1 as exponent Signed-off-by: Kai Blaschke --- vendor/hlslparser/src/GLSLGenerator.cpp | 68 ++++++++++++++++++++----- 1 file changed, 56 insertions(+), 12 deletions(-) diff --git a/vendor/hlslparser/src/GLSLGenerator.cpp b/vendor/hlslparser/src/GLSLGenerator.cpp index f5cda9bb26..c913e82682 100644 --- a/vendor/hlslparser/src/GLSLGenerator.cpp +++ b/vendor/hlslparser/src/GLSLGenerator.cpp @@ -119,7 +119,7 @@ GLSLGenerator::GLSLGenerator() : #else m_version = Version_330; #endif - + m_versionLegacy = false; m_inAttribPrefix = NULL; m_outAttribPrefix = NULL; @@ -244,7 +244,7 @@ bool GLSLGenerator::Generate(HLSLTree* tree, Target target, Version version, con m_writer.WriteLine(0, "precision highp float;"); } else if (m_version == Version_300_ES) - { + { m_writer.WriteLine(0, "#version 300 es"); m_writer.WriteLine(0, "precision highp float;"); m_writer.WriteLine(0, "precision highp sampler3D;"); @@ -420,7 +420,7 @@ bool GLSLGenerator::Generate(HLSLTree* tree, Target target, Version version, con * domain. To do this, we bail out to a separate function that piecewise-defines * a polynomial that qualitatively matches what we see under DX9. The DX9 * implementation itself is unknown, so this is only a rough match. - * + * * We also implement this as a separate function because we need to evaluate the * argument multiple times. If the argument expression involves side-effects * (such as post-increment or -decrement) the result would not be what we expect @@ -547,7 +547,7 @@ void GLSLGenerator::OutputExpressionList(HLSLExpression* expression, HLSLArgumen { m_writer.Write(", "); } - + HLSLType* expectedType = NULL; if (argument != NULL) { @@ -1028,16 +1028,60 @@ void GLSLGenerator::OutputExpression(HLSLExpression* expression, const HLSLType* Error("%s expects 2 arguments", functionName); return; } + /* See rsqrt above regarding abs(). Note that this behaves * as expected on some drivers but not others, so we add * the abs() call for compatibility across drivers. + * + * There's one special case though: if the exponent is a literal "1" (or "1.0"), + * don't use pow() at all, just return the base (arg 1) unchanged, even if negative. + * This is probably due to an HLSL compiler optimization which does the same thing. + * When not optimized, pow(x, 1) with a negative value of x would return NaN instead. */ - m_writer.Write("pow(abs("); - OutputExpression(argument[0], &functionCall->function->returnType); - m_writer.Write("),"); - OutputExpression(argument[1], &functionCall->function->returnType); - m_writer.Write(")"); - handled = true; + if (argument[1]->nodeType == HLSLNodeType_LiteralExpression) + { + HLSLLiteralExpression* literalExpression = static_cast(argument[1]); + float value = 0.0; + bool found = false; + switch (literalExpression->type) + { + case HLSLBaseType_Float: + value = literalExpression->fValue; + found = true; + break; + case HLSLBaseType_Int: + case HLSLBaseType_Uint: + value = literalExpression->iValue; + found = true; + break; + case HLSLBaseType_Bool: + value = literalExpression->bValue; + found = true; + break; + default: + break; + } + + // Replace the function call with just arg 1. + if (found && value == 1.0) + { + m_writer.Write("("); + OutputExpression(argument[0], &functionCall->function->returnType); + m_writer.Write(")"); + handled = true; + } + } + + // Other cases, including variable exponent arguments, will still call pow(). + if (!handled) + { + m_writer.Write("pow(abs("); + OutputExpression(argument[0], &functionCall->function->returnType); + m_writer.Write("),"); + OutputExpression(argument[1], &functionCall->function->returnType); + m_writer.Write(")"); + handled = true; + } } else if (String_Equal(functionName, "ldexp")) { @@ -1173,7 +1217,7 @@ void GLSLGenerator::OutputIdentifier(const char* name) { name = m_asinFunction; } - else + else { // The identifier could be a GLSL reserved word (if it's not also a HLSL reserved word). name = GetSafeIdentifierName(name); @@ -2155,7 +2199,7 @@ void GLSLGenerator::Error(const char* format, ...) va_start(arg, format); Log_ErrorArgList(format, arg); va_end(arg); -} +} const char* GLSLGenerator::GetSafeIdentifierName(const char* name) const { From b78d6e7be1e9a7bb38774be1040547efcc556248 Mon Sep 17 00:00:00 2001 From: Kai Blaschke Date: Mon, 24 Nov 2025 10:58:52 +0100 Subject: [PATCH 11/12] Keep references to textures and samplers in descriptors Previously, if the TextureManager was recreated, e.g. when changing the texture paths, all loaded textures were deleted, even if currently in use by a preset. Storing textures and samplers as shared pointers instead of weak pointers will make sure the objects are kept alive until the preset is unloaded. Signed-off-by: Kai Blaschke --- .../Renderer/TextureSamplerDescriptor.cpp | 50 +++++++++---------- .../Renderer/TextureSamplerDescriptor.hpp | 16 ++++-- 2 files changed, 37 insertions(+), 29 deletions(-) diff --git a/src/libprojectM/Renderer/TextureSamplerDescriptor.cpp b/src/libprojectM/Renderer/TextureSamplerDescriptor.cpp index b9a7870f93..25efe8e392 100644 --- a/src/libprojectM/Renderer/TextureSamplerDescriptor.cpp +++ b/src/libprojectM/Renderer/TextureSamplerDescriptor.cpp @@ -20,71 +20,71 @@ TextureSamplerDescriptor::TextureSamplerDescriptor(const std::shared_ptr bool { - return m_texture.expired() || m_sampler.expired(); + return m_texture->Empty(); } void TextureSamplerDescriptor::Bind(GLint unit, const Shader& shader) const { - auto texture = m_texture.lock(); - auto sampler = m_sampler.lock(); - if (texture && sampler) + if (m_texture && m_sampler) { - texture->Bind(unit, sampler); + m_texture->Bind(unit, m_sampler); shader.SetUniformInt(std::string("sampler_" + m_samplerName).c_str(), unit); // Might be setting this more than once if the texture is used with different wrap/filter modes, but this rarely happens. - shader.SetUniformFloat4(std::string("texsize_" + m_sizeName).c_str(), {texture->Width(), - texture->Height(), - 1.0f / static_cast(texture->Width()), - 1.0f / static_cast(texture->Height())}); + shader.SetUniformFloat4(std::string("texsize_" + m_sizeName).c_str(), {m_texture->Width(), + m_texture->Height(), + 1.0f / static_cast(m_texture->Width()), + 1.0f / static_cast(m_texture->Height())}); // Bind shorthand random texture size uniform if (m_sizeName.substr(0, 4) == "rand" && m_sizeName.length() > 7 && m_sizeName.at(6) == '_') { - shader.SetUniformFloat4(std::string("texsize_" + m_sizeName.substr(0, 6)).c_str(), {texture->Width(), - texture->Height(), - 1.0f / static_cast(texture->Width()), - 1.0f / static_cast(texture->Height())}); + shader.SetUniformFloat4(std::string("texsize_" + m_sizeName.substr(0, 6)).c_str(), {m_texture->Width(), + m_texture->Height(), + 1.0f / static_cast(m_texture->Width()), + 1.0f / static_cast(m_texture->Height())}); } } } void TextureSamplerDescriptor::Unbind(GLint unit) { - auto texture = m_texture.lock(); - if (texture) + if (m_texture) { - texture->Unbind(unit); + m_texture->Unbind(unit); } Sampler::Unbind(unit); } auto TextureSamplerDescriptor::Texture() const -> std::shared_ptr { - return m_texture.lock(); + return m_texture; } -void TextureSamplerDescriptor::Texture(std::weak_ptr texture) +void TextureSamplerDescriptor::Texture(const std::shared_ptr& texture) { m_texture = texture; } +void TextureSamplerDescriptor::Texture(const std::weak_ptr& texture) +{ + m_texture = texture.lock(); +} + auto TextureSamplerDescriptor::Sampler() const -> std::shared_ptr { - return m_sampler.lock(); + return m_sampler; } auto TextureSamplerDescriptor::SamplerDeclaration() const -> std::string { - auto texture = m_texture.lock(); - auto sampler = m_sampler.lock(); - if (!texture || !sampler) + if (!m_texture || !m_sampler) { return {}; } std::string declaration = "uniform "; - if (texture->Type() == GL_TEXTURE_3D) + if (m_texture->Type() == GL_TEXTURE_3D) { declaration.append("sampler3D sampler_"); } @@ -109,9 +109,7 @@ auto TextureSamplerDescriptor::SamplerDeclaration() const -> std::string auto TextureSamplerDescriptor::TexSizeDeclaration() const -> std::string { - auto texture = m_texture.lock(); - auto sampler = m_sampler.lock(); - if (!texture || !sampler) + if (!m_texture || !m_sampler) { return {}; } diff --git a/src/libprojectM/Renderer/TextureSamplerDescriptor.hpp b/src/libprojectM/Renderer/TextureSamplerDescriptor.hpp index 40deba8eca..0e4d7997c1 100644 --- a/src/libprojectM/Renderer/TextureSamplerDescriptor.hpp +++ b/src/libprojectM/Renderer/TextureSamplerDescriptor.hpp @@ -1,6 +1,10 @@ #pragma once +#include "Renderer/Sampler.hpp" #include "Renderer/Shader.hpp" +#include "Renderer/Texture.hpp" + +#include namespace libprojectM { namespace Renderer { @@ -65,11 +69,17 @@ class TextureSamplerDescriptor */ auto Texture() const -> std::shared_ptr; + /** + * @brief Updates the internal texture with a new one. + * @param texture A shared pointer to the new texture. + */ + void Texture(const std::shared_ptr& texture); + /** * @brief Updates the internal texture with a new one. * @param texture A weak pointer to the new texture. */ - void Texture(std::weak_ptr texture); + void Texture(const std::weak_ptr& texture); /** * @brief Returns a pointer to the stored sampler. @@ -96,8 +106,8 @@ class TextureSamplerDescriptor void TryUpdate(TextureManager& textureManager); private: - std::weak_ptr m_texture; //!< A weak reference to the texture. - std::weak_ptr m_sampler; //!< A weak reference to the sampler. + std::shared_ptr m_texture; //!< A reference to the texture. + std::shared_ptr m_sampler; //!< A reference to the sampler. std::string m_samplerName; //!< The name of the texture sampler as referenced in the shader. std::string m_sizeName; //!< The name of the "texsize_" uniform as referenced in the shader. bool m_updateFailed{false}; //!< Set to true if the update try failed, e.g. texture could not be loaded. From 65153400e241baca03625a3435183897907e8f2c Mon Sep 17 00:00:00 2001 From: yoyofr Date: Wed, 19 Nov 2025 10:36:27 +0100 Subject: [PATCH 12/12] fixed an issue with sep attribute mngt / per pixel Signed-off-by: Kai Blaschke --- src/libprojectM/MilkdropPreset/CustomWaveform.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/libprojectM/MilkdropPreset/CustomWaveform.cpp b/src/libprojectM/MilkdropPreset/CustomWaveform.cpp index 3ca12d9f44..3ca7bbe755 100644 --- a/src/libprojectM/MilkdropPreset/CustomWaveform.cpp +++ b/src/libprojectM/MilkdropPreset/CustomWaveform.cpp @@ -76,6 +76,9 @@ void CustomWaveform::Draw(const PerFrameContext& presetPerFrameContext) int const maxSampleCount{m_spectrum ? Audio::SpectrumSamples : Audio::WaveformSamples}; + int sampleCount = std::min(maxSampleCount, static_cast(*m_perFrameContext.samples)); + sampleCount -= m_sep; + // Initialize and execute per-frame code LoadPerFrameEvaluationVariables(presetPerFrameContext); m_perFrameContext.ExecutePerFrameCode(); @@ -83,8 +86,7 @@ void CustomWaveform::Draw(const PerFrameContext& presetPerFrameContext) // Copy Q and T vars to per-point context InitPerPointEvaluationVariables(); - int sampleCount = std::min(maxSampleCount, static_cast(*m_perFrameContext.samples)); - sampleCount -= m_sep; + sampleCount = std::min(maxSampleCount, static_cast(*m_perFrameContext.samples)); // If there aren't enough samples to draw a single line or dot, skip drawing the waveform. if ((m_useDots && sampleCount < 1) || sampleCount < 2)