diff options
| -rw-r--r-- | CHANGELOG.md | 25 | ||||
| -rw-r--r-- | sokol_app.h | 7 | ||||
| -rw-r--r-- | sokol_gfx.h | 65 | ||||
| -rw-r--r-- | util/sokol_gfx_imgui.h | 5 |
4 files changed, 101 insertions, 1 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index 9f5c9f1d..df88a5ce 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,30 @@ ## Updates +### 26-Jan-2026 + +- sokol_gfx.h: added support for dual-source-blending behind a new + feature flag `sg_query_features().dual_source_blending`: + - GL: available since 3.3 + - GLES3/WebGL2: generally not available + - D3D11/Metal: generally available + - WebGPU: dynamic availability via `WGPUFeatureName_DualSourceBlending` + - experimental Vulkan backend: currently required (initialization will + fail when the `dualSrcBlend` feature is not available) + + Trying to use dual-source blend-factors when `sg_features.dual_source_blending` + is false will result in a validation layer error in debug mode and 'undefined + behaviour' in release mode. + + Note that currently there is no sokol-sample which tests/demonstrates + dual-source-blending. The plan here is to replace the `blend-sapp` and + `blend-op-sapp` samples with a new sample which has a Dear ImGui UI to control + all blending features. + + Many thanks to @Tremus for the original PR to kick things off! + + Original PR: https://github.com/floooh/sokol/pull/1423 + Final PR (making the feature dynamic): https://github.com/floooh/sokol/pull/1426 + ### 24-Jan-2026 - sokol_app.h/sokol_gfx.h vk: object debug labels are now working in the experimental diff --git a/sokol_app.h b/sokol_app.h index ccfd7a84..db2e8dcb 100644 --- a/sokol_app.h +++ b/sokol_app.h @@ -4122,7 +4122,7 @@ _SOKOL_PRIVATE void _sapp_wgpu_request_device_cb(WGPURequestDeviceStatus status, _SOKOL_PRIVATE void _sapp_wgpu_create_device_and_swapchain(void) { SOKOL_ASSERT(_sapp.wgpu.adapter); size_t cur_feature_index = 1; - #define _SAPP_WGPU_MAX_REQUESTED_FEATURES (8) + #define _SAPP_WGPU_MAX_REQUESTED_FEATURES (16) WGPUFeatureName requiredFeatures[_SAPP_WGPU_MAX_REQUESTED_FEATURES] = { WGPUFeatureName_Depth32FloatStencil8, }; @@ -4143,6 +4143,10 @@ _SOKOL_PRIVATE void _sapp_wgpu_create_device_and_swapchain(void) { SOKOL_ASSERT(cur_feature_index < _SAPP_WGPU_MAX_REQUESTED_FEATURES); requiredFeatures[cur_feature_index++] = WGPUFeatureName_Float32Filterable; } + if (wgpuAdapterHasFeature(_sapp.wgpu.adapter, WGPUFeatureName_DualSourceBlending)) { + SOKOL_ASSERT(cur_feature_index < _SAPP_WGPU_MAX_REQUESTED_FEATURES); + requiredFeatures[cur_feature_index++] = WGPUFeatureName_DualSourceBlending; + } #undef _SAPP_WGPU_MAX_REQUESTED_FEATURES WGPULimits adapterLimits = WGPU_LIMITS_INIT; @@ -4515,6 +4519,7 @@ _SOKOL_PRIVATE void _sapp_vk_create_device(void) { required.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2; required.pNext = &vk13_features; required.features.samplerAnisotropy = VK_TRUE; + required.features.dualSrcBlend = VK_TRUE; if (supports.features.textureCompressionBC) { required.features.textureCompressionBC = VK_TRUE; } diff --git a/sokol_gfx.h b/sokol_gfx.h index 93659030..e4561bc2 100644 --- a/sokol_gfx.h +++ b/sokol_gfx.h @@ -2226,6 +2226,7 @@ typedef struct sg_features { bool separate_buffer_types; // cannot use the same buffer for vertex and indices (only WebGL2) bool draw_base_vertex; // draw with (base vertex > 0) && (base_instance == 0) supported bool draw_base_instance; // draw with (base instance > 0) supported + bool dual_source_blending; // dual-source-blending supported bool gl_texture_views; // supports 'proper' texture views (GL 4.3+) } sg_features; @@ -2726,6 +2727,10 @@ typedef enum sg_blend_factor { SG_BLENDFACTOR_ONE_MINUS_BLEND_COLOR, SG_BLENDFACTOR_BLEND_ALPHA, SG_BLENDFACTOR_ONE_MINUS_BLEND_ALPHA, + SG_BLENDFACTOR_SRC1_COLOR, + SG_BLENDFACTOR_ONE_MINUS_SRC1_COLOR, + SG_BLENDFACTOR_SRC1_ALPHA, + SG_BLENDFACTOR_ONE_MINUS_SRC1_ALPHA, _SG_BLENDFACTOR_NUM, _SG_BLENDFACTOR_FORCE_U32 = 0x7FFFFFFF } sg_blend_factor; @@ -4629,6 +4634,7 @@ typedef struct sg_stats { _SG_LOGITEM_XMACRO(VALIDATE_PIPELINEDESC_ATTR_SEMANTICS, "D3D11 missing vertex attribute semantics in shader") \ _SG_LOGITEM_XMACRO(VALIDATE_PIPELINEDESC_SHADER_READONLY_STORAGEBUFFERS, "sg_pipeline_desc.shader: only readonly storage buffer bindings allowed in render pipelines") \ _SG_LOGITEM_XMACRO(VALIDATE_PIPELINEDESC_BLENDOP_MINMAX_REQUIRES_BLENDFACTOR_ONE, "SG_BLENDOP_MIN/MAX requires all blend factors to be SG_BLENDFACTOR_ONE") \ + _SG_LOGITEM_XMACRO(VALIDATE_PIPELINEDESC_DUAL_SOURCE_BLENDING_NOT_SUPPORTED, "dual source blending not supported (sg_features.dual_source_blending)") \ _SG_LOGITEM_XMACRO(VALIDATE_VIEWDESC_CANARY, "sg_view_desc not initialized") \ _SG_LOGITEM_XMACRO(VALIDATE_VIEWDESC_UNIQUE_VIEWTYPE, "sg_view_desc: only one view type can be active") \ _SG_LOGITEM_XMACRO(VALIDATE_VIEWDESC_ANY_VIEWTYPE, "sg_view_desc: exactly one view type must be active") \ @@ -5624,6 +5630,9 @@ inline int sg_append_buffer(sg_buffer buf_id, const sg_range& data) { return sg_ #define _SOKOL_GL_HAS_TEXSTORAGE (1) #define _SOKOL_GL_HAS_BASEINSTANCE (1) #endif + #if defined(GL_VERSION_3_3) || defined(_SOKOL_USE_WIN32_GL_LOADER) + #define _SOKOL_GL_HAS_DUALSOURCEBLENDING (1) + #endif #if defined(GL_VERSION_3_2) || defined(_SOKOL_USE_WIN32_GL_LOADER) #define _SOKOL_GL_HAS_BASEVERTEX (1) #endif @@ -5631,6 +5640,7 @@ inline int sg_append_buffer(sg_buffer buf_id, const sg_range& data) { return sg_ #if defined(TARGET_OS_IPHONE) && !TARGET_OS_IPHONE #define _SOKOL_GL_HAS_COLORMASKI (1) #define _SOKOL_GL_HAS_BASEVERTEX (1) + #define _SOKOL_GL_HAS_DUALSOURCEBLENDING (1) #else #define _SOKOL_GL_HAS_TEXSTORAGE (1) #endif @@ -5653,6 +5663,9 @@ inline int sg_append_buffer(sg_buffer buf_id, const sg_range& data) { return sg_ #define _SOKOL_GL_HAS_TEXSTORAGE (1) #define _SOKOL_GL_HAS_BASEINSTANCE (1) #endif + #if defined(GL_VERSION_3_3) + #define _SOKOL_GL_HAS_DUALSOURCEBLENDING (1) + #endif #if defined(GL_VERSION_3_2) #define _SOKOL_GL_HAS_BASEVERTEX (1) #endif @@ -5771,6 +5784,10 @@ inline int sg_append_buffer(sg_buffer buf_id, const sg_range& data) { return sg_ #define GL_ONE_MINUS_CONSTANT_ALPHA 0x8004 #define GL_NONE 0 #define GL_SRC_COLOR 0x0300 + #define GL_SRC1_ALPHA 0x8589 + #define GL_SRC1_COLOR 0x88F9 + #define GL_ONE_MINUS_SRC1_ALPHA 0x88FB + #define GL_ONE_MINUS_SRC1_COLOR 0x88FA #define GL_BYTE 0x1400 #define GL_TEXTURE_CUBE_MAP_NEGATIVE_Z 0x851A #define GL_LINE_STRIP 0x0003 @@ -8060,6 +8077,18 @@ _SOKOL_PRIVATE bool _sg_attachments_alive(const _sg_attachments_ptrs_t* atts_ptr return true; } +_SOKOL_PRIVATE bool _sg_is_dualsource_blendfactor(sg_blend_factor f) { + switch (f) { + case SG_BLENDFACTOR_SRC1_COLOR: + case SG_BLENDFACTOR_ONE_MINUS_SRC1_COLOR: + case SG_BLENDFACTOR_SRC1_ALPHA: + case SG_BLENDFACTOR_ONE_MINUS_SRC1_ALPHA: + return true; + default: + return false; + } +} + _SOKOL_PRIVATE void _sg_buffer_common_init(_sg_buffer_common_t* cmn, const sg_buffer_desc* desc) { cmn->size = (int)desc->size; cmn->append_pos = 0; @@ -9398,6 +9427,12 @@ _SOKOL_PRIVATE GLenum _sg_gl_blend_factor(sg_blend_factor f) { case SG_BLENDFACTOR_ONE_MINUS_BLEND_COLOR: return GL_ONE_MINUS_CONSTANT_COLOR; case SG_BLENDFACTOR_BLEND_ALPHA: return GL_CONSTANT_ALPHA; case SG_BLENDFACTOR_ONE_MINUS_BLEND_ALPHA: return GL_ONE_MINUS_CONSTANT_ALPHA; + #if defined(_SOKOL_GL_HAS_DUALSOURCEBLENDING) + case SG_BLENDFACTOR_SRC1_COLOR: return GL_SRC1_COLOR; + case SG_BLENDFACTOR_ONE_MINUS_SRC1_COLOR: return GL_ONE_MINUS_SRC1_COLOR; + case SG_BLENDFACTOR_SRC1_ALPHA: return GL_SRC1_ALPHA; + case SG_BLENDFACTOR_ONE_MINUS_SRC1_ALPHA: return GL_ONE_MINUS_SRC1_ALPHA; + #endif default: SOKOL_UNREACHABLE; return 0; } } @@ -9947,6 +9982,7 @@ _SOKOL_PRIVATE void _sg_gl_init_caps_glcore(void) { #endif _sg.features.draw_base_vertex = version >= 320; _sg.features.draw_base_instance = version >= 420; + _sg.features.dual_source_blending = version >= 330; // scan extensions bool has_s3tc = false; // BC1..BC3 @@ -10035,6 +10071,7 @@ _SOKOL_PRIVATE void _sg_gl_init_caps_gles3(void) { #endif _sg.features.draw_base_vertex = version >= 320; _sg.features.draw_base_instance = false; + _sg.features.dual_source_blending = false; bool has_s3tc = false; // BC1..BC3 bool has_rgtc = false; // BC4 and BC5 @@ -12978,6 +13015,10 @@ _SOKOL_PRIVATE D3D11_BLEND _sg_d3d11_blend_factor(sg_blend_factor f) { case SG_BLENDFACTOR_ONE_MINUS_BLEND_COLOR: return D3D11_BLEND_INV_BLEND_FACTOR; case SG_BLENDFACTOR_BLEND_ALPHA: return D3D11_BLEND_BLEND_FACTOR; case SG_BLENDFACTOR_ONE_MINUS_BLEND_ALPHA: return D3D11_BLEND_INV_BLEND_FACTOR; + case SG_BLENDFACTOR_SRC1_COLOR: return D3D11_BLEND_SRC1_COLOR; + case SG_BLENDFACTOR_ONE_MINUS_SRC1_COLOR: return D3D11_BLEND_INV_SRC1_COLOR; + case SG_BLENDFACTOR_SRC1_ALPHA: return D3D11_BLEND_SRC1_ALPHA; + case SG_BLENDFACTOR_ONE_MINUS_SRC1_ALPHA: return D3D11_BLEND_INV_SRC1_ALPHA; default: SOKOL_UNREACHABLE; return (D3D11_BLEND) 0; } } @@ -13034,6 +13075,7 @@ _SOKOL_PRIVATE void _sg_d3d11_init_caps(void) { _sg.features.msaa_texture_bindings = true; _sg.features.draw_base_vertex = true; _sg.features.draw_base_instance = true; + _sg.features.dual_source_blending = true; _sg.limits.max_image_size_2d = 16 * 1024; _sg.limits.max_image_size_cube = 16 * 1024; @@ -14709,6 +14751,10 @@ _SOKOL_PRIVATE MTLBlendFactor _sg_mtl_blend_factor(sg_blend_factor f) { case SG_BLENDFACTOR_ONE_MINUS_BLEND_COLOR: return MTLBlendFactorOneMinusBlendColor; case SG_BLENDFACTOR_BLEND_ALPHA: return MTLBlendFactorBlendAlpha; case SG_BLENDFACTOR_ONE_MINUS_BLEND_ALPHA: return MTLBlendFactorOneMinusBlendAlpha; + case SG_BLENDFACTOR_SRC1_COLOR: return MTLBlendFactorSource1Color; + case SG_BLENDFACTOR_ONE_MINUS_SRC1_COLOR: return MTLBlendFactorOneMinusSource1Color; + case SG_BLENDFACTOR_SRC1_ALPHA: return MTLBlendFactorSource1Alpha; + case SG_BLENDFACTOR_ONE_MINUS_SRC1_ALPHA: return MTLBlendFactorOneMinusSource1Alpha; default: SOKOL_UNREACHABLE; return (MTLBlendFactor)0; } } @@ -14993,6 +15039,7 @@ _SOKOL_PRIVATE void _sg_mtl_init_caps(void) { _sg.features.msaa_texture_bindings = true; _sg.features.draw_base_vertex = true; _sg.features.draw_base_instance = true; + _sg.features.dual_source_blending = true; _sg.features.image_clamp_to_border = false; #if (MAC_OS_X_VERSION_MAX_ALLOWED >= 120000) || (__IPHONE_OS_VERSION_MAX_ALLOWED >= 140000) @@ -16964,6 +17011,10 @@ _SOKOL_PRIVATE WGPUBlendFactor _sg_wgpu_blendfactor(sg_blend_factor f) { // FIXME: separate blend alpha value not supported? case SG_BLENDFACTOR_BLEND_ALPHA: return WGPUBlendFactor_Constant; case SG_BLENDFACTOR_ONE_MINUS_BLEND_ALPHA: return WGPUBlendFactor_OneMinusConstant; + case SG_BLENDFACTOR_SRC1_COLOR: return WGPUBlendFactor_Src1 ; + case SG_BLENDFACTOR_ONE_MINUS_SRC1_COLOR: return WGPUBlendFactor_OneMinusSrc1; + case SG_BLENDFACTOR_SRC1_ALPHA: return WGPUBlendFactor_Src1Alpha; + case SG_BLENDFACTOR_ONE_MINUS_SRC1_ALPHA: return WGPUBlendFactor_OneMinusSrc1Alpha; default: SOKOL_UNREACHABLE; return WGPUBlendFactor_Force32; @@ -17006,6 +17057,7 @@ _SOKOL_PRIVATE void _sg_wgpu_init_caps(void) { _sg.features.msaa_texture_bindings = true; _sg.features.draw_base_vertex = true; _sg.features.draw_base_instance = true; + _sg.features.dual_source_blending = wgpuDeviceHasFeature(_sg.wgpu.dev, WGPUFeatureName_DualSourceBlending); wgpuDeviceGetLimits(_sg.wgpu.dev, &_sg.wgpu.limits); @@ -20235,6 +20287,10 @@ _SOKOL_PRIVATE VkBlendFactor _sg_vk_blend_factor(sg_blend_factor f) { case SG_BLENDFACTOR_ONE_MINUS_BLEND_COLOR: return VK_BLEND_FACTOR_ONE_MINUS_CONSTANT_COLOR; case SG_BLENDFACTOR_BLEND_ALPHA: return VK_BLEND_FACTOR_CONSTANT_ALPHA; case SG_BLENDFACTOR_ONE_MINUS_BLEND_ALPHA: return VK_BLEND_FACTOR_ONE_MINUS_CONSTANT_ALPHA; + case SG_BLENDFACTOR_SRC1_COLOR: return VK_BLEND_FACTOR_SRC1_COLOR ; + case SG_BLENDFACTOR_ONE_MINUS_SRC1_COLOR: return VK_BLEND_FACTOR_ONE_MINUS_SRC1_COLOR; + case SG_BLENDFACTOR_SRC1_ALPHA: return VK_BLEND_FACTOR_SRC1_ALPHA; + case SG_BLENDFACTOR_ONE_MINUS_SRC1_ALPHA: return VK_BLEND_FACTOR_ONE_MINUS_SRC1_ALPHA; default: SOKOL_UNREACHABLE; return VK_BLEND_FACTOR_ONE; @@ -20386,6 +20442,7 @@ _SOKOL_PRIVATE void _sg_vk_init_caps(void) { _sg.features.msaa_texture_bindings = true; _sg.features.draw_base_vertex = true; _sg.features.draw_base_instance = true; + _sg.features.dual_source_blending = true; SOKOL_ASSERT(_sg.vk.phys_dev); _sg.vk.descriptor_buffer_props.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_BUFFER_PROPERTIES_EXT; @@ -22920,6 +22977,14 @@ _SOKOL_PRIVATE bool _sg_validate_pipeline_desc(const sg_pipeline_desc* desc) { if ((bs->op_alpha == SG_BLENDOP_MIN) || (bs->op_alpha == SG_BLENDOP_MAX)) { _SG_VALIDATE((bs->src_factor_alpha == SG_BLENDFACTOR_ONE) && (bs->dst_factor_alpha == SG_BLENDFACTOR_ONE), VALIDATE_PIPELINEDESC_BLENDOP_MINMAX_REQUIRES_BLENDFACTOR_ONE); } + const bool needs_dualsource_blending = + _sg_is_dualsource_blendfactor(bs->src_factor_rgb) || + _sg_is_dualsource_blendfactor(bs->dst_factor_rgb) || + _sg_is_dualsource_blendfactor(bs->src_factor_alpha) || + _sg_is_dualsource_blendfactor(bs->dst_factor_alpha); + if (needs_dualsource_blending) { + _SG_VALIDATE(_sg.features.dual_source_blending, VALIDATE_PIPELINEDESC_DUAL_SOURCE_BLENDING_NOT_SUPPORTED); + } } return _sg_validate_end(); #endif diff --git a/util/sokol_gfx_imgui.h b/util/sokol_gfx_imgui.h index ed363143..72a6c6c6 100644 --- a/util/sokol_gfx_imgui.h +++ b/util/sokol_gfx_imgui.h @@ -1470,6 +1470,10 @@ _SOKOL_PRIVATE const char* _sgimgui_blendfactor_string(sg_blend_factor f) { case SG_BLENDFACTOR_ONE_MINUS_BLEND_COLOR: return "SG_BLENDFACTOR_ONE_MINUS_BLEND_COLOR"; case SG_BLENDFACTOR_BLEND_ALPHA: return "SG_BLENDFACTOR_BLEND_ALPHA"; case SG_BLENDFACTOR_ONE_MINUS_BLEND_ALPHA: return "SG_BLENDFACTOR_ONE_MINUS_BLEND_ALPHA"; + case SG_BLENDFACTOR_SRC1_COLOR: return "SG_BLENDFACTOR_SRC1_COLOR"; + case SG_BLENDFACTOR_ONE_MINUS_SRC1_COLOR: return "SG_BLENDFACTOR_ONE_MINUS_SRC1_COLOR"; + case SG_BLENDFACTOR_SRC1_ALPHA: return "SG_BLENDFACTOR_SRC1_ALPHA"; + case SG_BLENDFACTOR_ONE_MINUS_SRC1_ALPHA: return "SG_BLENDFACTOR_ONE_MINUS_SRC1_ALPHA"; default: return "???"; } } @@ -4467,6 +4471,7 @@ _SOKOL_PRIVATE void _sgimgui_draw_caps_panel(void) { _sgimgui_igtext(" separate_buffer_types: %s", _sgimgui_bool_string(f.separate_buffer_types)); _sgimgui_igtext(" draw_base_vertex: %s", _sgimgui_bool_string(f.draw_base_vertex)); _sgimgui_igtext(" draw_base_instance: %s", _sgimgui_bool_string(f.draw_base_instance)); + _sgimgui_igtext(" dual_source_blending: %s", _sgimgui_bool_string(f.dual_source_blending)); _sgimgui_igtext(" gl_texture_views: %s", _sgimgui_bool_string(f.gl_texture_views)); sg_limits l = sg_query_limits(); _sgimgui_igtext("\nLimits:\n"); |