From 11c807ebd1e0677bc93e6dc98123c9ca210b2cff Mon Sep 17 00:00:00 2001 From: Jelle Raaijmakers Date: Sun, 16 Jan 2022 22:48:46 +0100 Subject: LibGL+LibSoftGPU: Implement the stencil buffer This implements an 8-bit front stencil buffer. Stencil operations are SIMD optimized. LibGL changes include: * New `glStencilMask` and `glStencilMaskSeparate` functions * New context parameter `GL_STENCIL_CLEAR_VALUE` --- Userland/Libraries/LibGL/GL/gl.h | 4 +- Userland/Libraries/LibGL/GLContext.h | 1 + Userland/Libraries/LibGL/GLStencil.cpp | 7 +- Userland/Libraries/LibGL/SoftwareGLContext.cpp | 150 ++++++++++++----- Userland/Libraries/LibGL/SoftwareGLContext.h | 7 +- Userland/Libraries/LibSoftGPU/CMakeLists.txt | 1 + Userland/Libraries/LibSoftGPU/Device.cpp | 209 ++++++++++++++++++++---- Userland/Libraries/LibSoftGPU/Device.h | 22 ++- Userland/Libraries/LibSoftGPU/DeviceInfo.h | 1 + Userland/Libraries/LibSoftGPU/Enums.h | 22 +++ Userland/Libraries/LibSoftGPU/StencilBuffer.cpp | 41 +++++ Userland/Libraries/LibSoftGPU/StencilBuffer.h | 33 ++++ 12 files changed, 420 insertions(+), 78 deletions(-) create mode 100644 Userland/Libraries/LibSoftGPU/StencilBuffer.cpp create mode 100644 Userland/Libraries/LibSoftGPU/StencilBuffer.h (limited to 'Userland') diff --git a/Userland/Libraries/LibGL/GL/gl.h b/Userland/Libraries/LibGL/GL/gl.h index 60bd67261d..af297e3740 100644 --- a/Userland/Libraries/LibGL/GL/gl.h +++ b/Userland/Libraries/LibGL/GL/gl.h @@ -98,6 +98,7 @@ extern "C" { #define GL_COLOR_MATERIAL 0x0B57 #define GL_FOG_START 0x0B63 #define GL_FOG_END 0x0B64 +#define GL_STENCIL_CLEAR_VALUE 0x0B91 #define GL_MATRIX_MODE 0x0BA0 #define GL_NORMALIZE 0x0BA1 #define GL_VIEWPORT 0x0BA2 @@ -605,9 +606,10 @@ GLAPI void glLightModelfv(GLenum pname, GLfloat const* params); GLAPI void glLightModeli(GLenum pname, GLint param); GLAPI void glStencilFunc(GLenum func, GLint ref, GLuint mask); GLAPI void glStencilFuncSeparate(GLenum face, GLenum func, GLint ref, GLuint mask); +GLAPI void glStencilMask(GLuint mask); +GLAPI void glStencilMaskSeparate(GLenum face, GLuint mask); GLAPI void glStencilOp(GLenum sfail, GLenum dpfail, GLenum dppass); GLAPI void glStencilOpSeparate(GLenum face, GLenum sfail, GLenum dpfail, GLenum dppass); -GLAPI void glStencilMask(GLuint mask); GLAPI void glNormal3f(GLfloat nx, GLfloat ny, GLfloat nz); GLAPI void glNormal3fv(GLfloat const* v); GLAPI void glNormalPointer(GLenum type, GLsizei stride, void const* pointer); diff --git a/Userland/Libraries/LibGL/GLContext.h b/Userland/Libraries/LibGL/GLContext.h index bf189197d9..1eecd49830 100644 --- a/Userland/Libraries/LibGL/GLContext.h +++ b/Userland/Libraries/LibGL/GLContext.h @@ -97,6 +97,7 @@ public: virtual void gl_pixel_storei(GLenum pname, GLint param) = 0; virtual void gl_scissor(GLint x, GLint y, GLsizei width, GLsizei height) = 0; virtual void gl_stencil_func_separate(GLenum face, GLenum func, GLint ref, GLuint mask) = 0; + virtual void gl_stencil_mask_separate(GLenum face, GLuint mask) = 0; virtual void gl_stencil_op_separate(GLenum face, GLenum sfail, GLenum dpfail, GLenum dppass) = 0; virtual void gl_normal(GLfloat nx, GLfloat ny, GLfloat nz) = 0; virtual void gl_normal_pointer(GLenum type, GLsizei stride, void const* pointer) = 0; diff --git a/Userland/Libraries/LibGL/GLStencil.cpp b/Userland/Libraries/LibGL/GLStencil.cpp index 80ded3ef5d..7e2916ef08 100644 --- a/Userland/Libraries/LibGL/GLStencil.cpp +++ b/Userland/Libraries/LibGL/GLStencil.cpp @@ -36,5 +36,10 @@ void glStencilOpSeparate(GLenum face, GLenum sfail, GLenum dpfail, GLenum dppass void glStencilMask(GLuint mask) { - dbgln("(STUBBED) glStencilMask(0x{:08x})", mask); + g_gl_context->gl_stencil_mask_separate(GL_FRONT_AND_BACK, mask); +} + +void glStencilMaskSeparate(GLenum face, GLuint mask) +{ + g_gl_context->gl_stencil_mask_separate(face, mask); } diff --git a/Userland/Libraries/LibGL/SoftwareGLContext.cpp b/Userland/Libraries/LibGL/SoftwareGLContext.cpp index 225ddc016c..0ca7b2f897 100644 --- a/Userland/Libraries/LibGL/SoftwareGLContext.cpp +++ b/Userland/Libraries/LibGL/SoftwareGLContext.cpp @@ -165,7 +165,9 @@ Optional SoftwareGLContext::get_context_parameter(GLenum name) return ContextParameter { .type = GL_BOOL, .is_capability = true, .value = { .boolean_value = scissor_enabled } }; } case GL_STENCIL_BITS: - return ContextParameter { .type = GL_INT, .value = { .integer_value = sizeof(float) * 8 } }; + return ContextParameter { .type = GL_INT, .value = { .integer_value = m_device_info.stencil_bits } }; + case GL_STENCIL_CLEAR_VALUE: + return ContextParameter { .type = GL_INT, .value = { .integer_value = m_clear_stencil } }; case GL_STENCIL_TEST: return ContextParameter { .type = GL_BOOL, .is_capability = true, .value = { .boolean_value = m_stencil_test_enabled } }; case GL_TEXTURE_1D: @@ -239,9 +241,8 @@ void SoftwareGLContext::gl_clear(GLbitfield mask) if (mask & GL_DEPTH_BUFFER_BIT) m_rasterizer.clear_depth(static_cast(m_clear_depth)); - // FIXME: implement GL_STENCIL_BUFFER_BIT if (mask & GL_STENCIL_BUFFER_BIT) - dbgln_if(GL_DEBUG, "gl_clear(): GL_STENCIL_BUFFER_BIT is unimplemented"); + m_rasterizer.clear_stencil(m_clear_stencil); } void SoftwareGLContext::gl_clear_color(GLclampf red, GLclampf green, GLclampf blue, GLclampf alpha) @@ -268,9 +269,7 @@ void SoftwareGLContext::gl_clear_stencil(GLint s) RETURN_WITH_ERROR_IF(m_in_draw_state, GL_INVALID_OPERATION); - // FIXME: "s is masked with 2^m - 1 , where m is the number of bits in the stencil buffer" - - m_clear_stencil = s; + m_clear_stencil = static_cast(s & ((1 << m_device_info.stencil_bits) - 1)); } void SoftwareGLContext::gl_color(GLdouble r, GLdouble g, GLdouble b, GLdouble a) @@ -697,6 +696,8 @@ void SoftwareGLContext::gl_enable(GLenum capability) break; case GL_STENCIL_TEST: m_stencil_test_enabled = true; + rasterizer_options.enable_stencil_test = true; + update_rasterizer_options = true; break; case GL_TEXTURE_1D: m_active_texture_unit->set_texture_1d_enabled(true); @@ -808,6 +809,8 @@ void SoftwareGLContext::gl_disable(GLenum capability) break; case GL_STENCIL_TEST: m_stencil_test_enabled = false; + rasterizer_options.enable_stencil_test = false; + update_rasterizer_options = true; break; case GL_TEXTURE_1D: m_active_texture_unit->set_texture_1d_enabled(false); @@ -2631,13 +2634,28 @@ void SoftwareGLContext::gl_stencil_func_separate(GLenum face, GLenum func, GLint || func == GL_ALWAYS), GL_INVALID_ENUM); - // FIXME: "ref is clamped to the range 02^n - 1 , where n is the number of bitplanes in the stencil buffer" + ref = clamp(ref, 0, (1 << m_device_info.stencil_bits) - 1); StencilFunctionOptions new_options = { func, ref, mask }; if (face == GL_FRONT || face == GL_FRONT_AND_BACK) m_stencil_function[Face::Front] = new_options; if (face == GL_BACK || face == GL_FRONT_AND_BACK) m_stencil_function[Face::Back] = new_options; + + m_stencil_configuration_dirty = true; +} + +void SoftwareGLContext::gl_stencil_mask_separate(GLenum face, GLuint mask) +{ + APPEND_TO_CALL_LIST_AND_RETURN_IF_NEEDED(gl_stencil_mask_separate, face, mask); + RETURN_WITH_ERROR_IF(m_in_draw_state, GL_INVALID_OPERATION); + + if (face == GL_FRONT || face == GL_FRONT_AND_BACK) + m_stencil_operation[Face::Front].write_mask = mask; + if (face == GL_BACK || face == GL_FRONT_AND_BACK) + m_stencil_operation[Face::Back].write_mask = mask; + + m_stencil_configuration_dirty = true; } void SoftwareGLContext::gl_stencil_op_separate(GLenum face, GLenum sfail, GLenum dpfail, GLenum dppass) @@ -2647,39 +2665,26 @@ void SoftwareGLContext::gl_stencil_op_separate(GLenum face, GLenum sfail, GLenum RETURN_WITH_ERROR_IF(!(face == GL_FRONT || face == GL_BACK || face == GL_FRONT_AND_BACK), GL_INVALID_ENUM); - RETURN_WITH_ERROR_IF(!(sfail == GL_KEEP - || sfail == GL_ZERO - || sfail == GL_REPLACE - || sfail == GL_INCR - || sfail == GL_INCR_WRAP - || sfail == GL_DECR - || sfail == GL_DECR_WRAP - || sfail == GL_INVERT), - GL_INVALID_ENUM); - RETURN_WITH_ERROR_IF(!(dpfail == GL_KEEP - || dpfail == GL_ZERO - || dpfail == GL_REPLACE - || dpfail == GL_INCR - || dpfail == GL_INCR_WRAP - || dpfail == GL_DECR - || dpfail == GL_DECR_WRAP - || dpfail == GL_INVERT), - GL_INVALID_ENUM); - RETURN_WITH_ERROR_IF(!(dppass == GL_KEEP - || dppass == GL_ZERO - || dppass == GL_REPLACE - || dppass == GL_INCR - || dppass == GL_INCR_WRAP - || dppass == GL_DECR - || dppass == GL_DECR_WRAP - || dppass == GL_INVERT), - GL_INVALID_ENUM); - - StencilOperationOptions new_options = { sfail, dpfail, dppass }; + auto is_valid_op = [](GLenum op) -> bool { + return op == GL_KEEP || op == GL_ZERO || op == GL_REPLACE || op == GL_INCR || op == GL_INCR_WRAP + || op == GL_DECR || op == GL_DECR_WRAP || op == GL_INVERT; + }; + RETURN_WITH_ERROR_IF(!is_valid_op(sfail), GL_INVALID_ENUM); + RETURN_WITH_ERROR_IF(!is_valid_op(dpfail), GL_INVALID_ENUM); + RETURN_WITH_ERROR_IF(!is_valid_op(dppass), GL_INVALID_ENUM); + + auto update_stencil_operation = [&](Face face, GLenum sfail, GLenum dpfail, GLenum dppass) { + auto& stencil_operation = m_stencil_operation[face]; + stencil_operation.op_fail = sfail; + stencil_operation.op_depth_fail = dpfail; + stencil_operation.op_pass = dppass; + }; if (face == GL_FRONT || face == GL_FRONT_AND_BACK) - m_stencil_operation[Face::Front] = new_options; + update_stencil_operation(Face::Front, sfail, dpfail, dppass); if (face == GL_BACK || face == GL_FRONT_AND_BACK) - m_stencil_operation[Face::Back] = new_options; + update_stencil_operation(Face::Back, sfail, dpfail, dppass); + + m_stencil_configuration_dirty = true; } void SoftwareGLContext::gl_normal(GLfloat nx, GLfloat ny, GLfloat nz) @@ -2908,6 +2913,7 @@ void SoftwareGLContext::sync_device_config() sync_device_sampler_config(); sync_device_texcoord_config(); sync_light_state(); + sync_stencil_configuration(); } void SoftwareGLContext::sync_device_sampler_config() @@ -3170,6 +3176,74 @@ void SoftwareGLContext::sync_device_texcoord_config() m_rasterizer.set_options(options); } +void SoftwareGLContext::sync_stencil_configuration() +{ + if (!m_stencil_configuration_dirty) + return; + m_stencil_configuration_dirty = false; + + auto set_device_stencil = [&](SoftGPU::Face face, StencilFunctionOptions func, StencilOperationOptions op) { + SoftGPU::StencilConfiguration device_configuration; + + // Stencil test function + auto map_func = [](GLenum func) -> SoftGPU::StencilTestFunction { + switch (func) { + case GL_ALWAYS: + return SoftGPU::StencilTestFunction::Always; + case GL_EQUAL: + return SoftGPU::StencilTestFunction::Equal; + case GL_GEQUAL: + return SoftGPU::StencilTestFunction::GreaterOrEqual; + case GL_GREATER: + return SoftGPU::StencilTestFunction::Greater; + case GL_LESS: + return SoftGPU::StencilTestFunction::Less; + case GL_LEQUAL: + return SoftGPU::StencilTestFunction::LessOrEqual; + case GL_NEVER: + return SoftGPU::StencilTestFunction::Never; + case GL_NOTEQUAL: + return SoftGPU::StencilTestFunction::NotEqual; + } + VERIFY_NOT_REACHED(); + }; + device_configuration.test_function = map_func(func.func); + device_configuration.reference_value = func.reference_value; + device_configuration.test_mask = func.mask; + + // Stencil operation + auto map_operation = [](GLenum operation) -> SoftGPU::StencilOperation { + switch (operation) { + case GL_DECR: + return SoftGPU::StencilOperation::Decrement; + case GL_DECR_WRAP: + return SoftGPU::StencilOperation::DecrementWrap; + case GL_INCR: + return SoftGPU::StencilOperation::Increment; + case GL_INCR_WRAP: + return SoftGPU::StencilOperation::IncrementWrap; + case GL_INVERT: + return SoftGPU::StencilOperation::Invert; + case GL_KEEP: + return SoftGPU::StencilOperation::Keep; + case GL_REPLACE: + return SoftGPU::StencilOperation::Replace; + case GL_ZERO: + return SoftGPU::StencilOperation::Zero; + } + VERIFY_NOT_REACHED(); + }; + device_configuration.on_stencil_test_fail = map_operation(op.op_fail); + device_configuration.on_depth_test_fail = map_operation(op.op_depth_fail); + device_configuration.on_pass = map_operation(op.op_pass); + device_configuration.write_mask = op.write_mask; + + m_rasterizer.set_stencil_configuration(face, device_configuration); + }; + set_device_stencil(SoftGPU::Face::Front, m_stencil_function[Face::Front], m_stencil_operation[Face::Front]); + set_device_stencil(SoftGPU::Face::Back, m_stencil_function[Face::Back], m_stencil_operation[Face::Back]); +} + void SoftwareGLContext::gl_lightf(GLenum light, GLenum pname, GLfloat param) { APPEND_TO_CALL_LIST_AND_RETURN_IF_NEEDED(gl_lightf, light, pname, param); diff --git a/Userland/Libraries/LibGL/SoftwareGLContext.h b/Userland/Libraries/LibGL/SoftwareGLContext.h index 5494642944..ff5ea056be 100644 --- a/Userland/Libraries/LibGL/SoftwareGLContext.h +++ b/Userland/Libraries/LibGL/SoftwareGLContext.h @@ -128,6 +128,7 @@ public: virtual void gl_pixel_storei(GLenum pname, GLint param) override; virtual void gl_scissor(GLint x, GLint y, GLsizei width, GLsizei height) override; virtual void gl_stencil_func_separate(GLenum face, GLenum func, GLint ref, GLuint mask) override; + virtual void gl_stencil_mask_separate(GLenum face, GLuint mask) override; virtual void gl_stencil_op_separate(GLenum face, GLenum sfail, GLenum dpfail, GLenum dppass) override; virtual void gl_normal(GLfloat nx, GLfloat ny, GLfloat nz) override; virtual void gl_normal_pointer(GLenum type, GLsizei stride, void const* pointer) override; @@ -154,6 +155,7 @@ private: void sync_device_sampler_config(); void sync_device_texcoord_config(); void sync_light_state(); + void sync_stencil_configuration(); template T* store_in_listing(T value) @@ -195,7 +197,7 @@ private: FloatVector4 m_clear_color { 0.0f, 0.0f, 0.0f, 0.0f }; double m_clear_depth { 1.0 }; - GLint m_clear_stencil { 0 }; + u8 m_clear_stencil { 0 }; FloatVector4 m_current_vertex_color = { 1.0f, 1.0f, 1.0f, 1.0f }; FloatVector4 m_current_vertex_tex_coord = { 0.0f, 0.0f, 0.0f, 1.0f }; @@ -225,6 +227,7 @@ private: // Stencil configuration bool m_stencil_test_enabled { false }; + bool m_stencil_configuration_dirty { true }; struct StencilFunctionOptions { GLenum func { GL_ALWAYS }; @@ -237,6 +240,7 @@ private: GLenum op_fail { GL_KEEP }; GLenum op_depth_fail { GL_KEEP }; GLenum op_pass { GL_KEEP }; + GLuint write_mask { NumericLimits::max() }; }; Array m_stencil_operation; @@ -360,6 +364,7 @@ private: decltype(&SoftwareGLContext::gl_polygon_offset), decltype(&SoftwareGLContext::gl_scissor), decltype(&SoftwareGLContext::gl_stencil_func_separate), + decltype(&SoftwareGLContext::gl_stencil_mask_separate), decltype(&SoftwareGLContext::gl_stencil_op_separate), decltype(&SoftwareGLContext::gl_normal), decltype(&SoftwareGLContext::gl_raster_pos), diff --git a/Userland/Libraries/LibSoftGPU/CMakeLists.txt b/Userland/Libraries/LibSoftGPU/CMakeLists.txt index 96c04d4d42..03ee632187 100644 --- a/Userland/Libraries/LibSoftGPU/CMakeLists.txt +++ b/Userland/Libraries/LibSoftGPU/CMakeLists.txt @@ -4,6 +4,7 @@ set(SOURCES Device.cpp Image.cpp Sampler.cpp + StencilBuffer.cpp ) add_compile_options(-Wno-psabi) diff --git a/Userland/Libraries/LibSoftGPU/Device.cpp b/Userland/Libraries/LibSoftGPU/Device.cpp index b2f3c1bd66..fa4509b977 100644 --- a/Userland/Libraries/LibSoftGPU/Device.cpp +++ b/Userland/Libraries/LibSoftGPU/Device.cpp @@ -1,12 +1,14 @@ /* * Copyright (c) 2021, Stephan Unverwerth * Copyright (c) 2021, Jesse Buhagiar + * Copyright (c) 2022, Jelle Raaijmakers * * SPDX-License-Identifier: BSD-2-Clause */ #include #include +#include #include #include #include @@ -82,11 +84,11 @@ static Vector4 to_vec4(u32x4 rgba) }; } -static Gfx::IntRect window_coordinates_to_target_coordinates(Gfx::IntRect const window_rect, Gfx::IntRect const target_rect) +Gfx::IntRect Device::window_coordinates_to_target_coordinates(Gfx::IntRect const& window_rect) { return { window_rect.x(), - target_rect.height() - window_rect.height() - window_rect.y(), + m_render_target->rect().height() - window_rect.height() - window_rect.y(), window_rect.width(), window_rect.height(), }; @@ -213,7 +215,7 @@ void Device::rasterize_triangle(const Triangle& triangle) auto render_bounds = m_render_target->rect(); if (m_options.scissor_enabled) - render_bounds.intersect(window_coordinates_to_target_coordinates(m_options.scissor_box, m_render_target->rect())); + render_bounds.intersect(window_coordinates_to_target_coordinates(m_options.scissor_box)); // Obey top-left rule: // This sets up "zero" for later pixel coverage tests. @@ -229,7 +231,7 @@ void Device::rasterize_triangle(const Triangle& triangle) zero.set_y(0); // This function calculates the 3 edge values for the pixel relative to the triangle. - auto calculate_edge_values4 = [v0, v1, v2](const Vector2& p) -> Vector3 { + auto calculate_edge_values4 = [v0, v1, v2](Vector2 const& p) -> Vector3 { return { edge_function4(v1, v2, p), edge_function4(v2, v0, p), @@ -238,7 +240,7 @@ void Device::rasterize_triangle(const Triangle& triangle) }; // This function tests whether a point as identified by its 3 edge values lies within the triangle - auto test_point4 = [zero](const Vector3& edges) -> i32x4 { + auto test_point4 = [zero](Vector3 const& edges) -> i32x4 { return edges.x() >= zero.x() && edges.y() >= zero.y() && edges.z() >= zero.z(); @@ -257,8 +259,6 @@ void Device::rasterize_triangle(const Triangle& triangle) float const vertex1_eye_absz = fabs(vertex1.eye_coordinates.z()); float const vertex2_eye_absz = fabs(vertex2.eye_coordinates.z()); - // FIXME: implement stencil testing - int const render_bounds_left = render_bounds.x(); int const render_bounds_right = render_bounds.x() + render_bounds.width(); int const render_bounds_top = render_bounds.y(); @@ -269,10 +269,46 @@ void Device::rasterize_triangle(const Triangle& triangle) expand4(subpixel_factor / 2), }; + // Stencil configuration and writing + auto const stencil_configuration = m_stencil_configuration[Face::Front]; + auto const stencil_reference_value = stencil_configuration.reference_value & stencil_configuration.test_mask; + + auto write_to_stencil = [](u8* stencil_ptrs[4], i32x4 stencil_value, StencilOperation op, u8 reference_value, u8 write_mask, i32x4 pixel_mask) { + if (write_mask == 0 || op == StencilOperation::Keep) + return; + + switch (op) { + case StencilOperation::Decrement: + stencil_value = (stencil_value & ~write_mask) | (max(stencil_value - 1, expand4(0)) & write_mask); + break; + case StencilOperation::DecrementWrap: + stencil_value = (stencil_value & ~write_mask) | (((stencil_value - 1) & 0xFF) & write_mask); + break; + case StencilOperation::Increment: + stencil_value = (stencil_value & ~write_mask) | (min(stencil_value + 1, expand4(0xFF)) & write_mask); + break; + case StencilOperation::IncrementWrap: + stencil_value = (stencil_value & ~write_mask) | (((stencil_value + 1) & 0xFF) & write_mask); + break; + case StencilOperation::Invert: + stencil_value ^= write_mask; + break; + case StencilOperation::Replace: + stencil_value = (stencil_value & ~write_mask) | (reference_value & write_mask); + break; + case StencilOperation::Zero: + stencil_value &= ~write_mask; + break; + default: + VERIFY_NOT_REACHED(); + } + + store4_masked(stencil_value, stencil_ptrs[0], stencil_ptrs[1], stencil_ptrs[2], stencil_ptrs[3], pixel_mask); + }; + // Iterate over all blocks within the bounds of the triangle for (int by = by0; by < by1; by += 2) { for (int bx = bx0; bx < bx1; bx += 2) { - PixelQuad quad; quad.screen_coordinates = { @@ -306,14 +342,70 @@ void Device::rasterize_triangle(const Triangle& triangle) int coverage_bits = maskbits(quad.mask); + // Stencil testing + u8* stencil_ptrs[4]; + i32x4 stencil_value; + if (m_options.enable_stencil_test) { + stencil_ptrs[0] = coverage_bits & 1 ? &m_stencil_buffer->scanline(by)[bx] : nullptr; + stencil_ptrs[1] = coverage_bits & 2 ? &m_stencil_buffer->scanline(by)[bx + 1] : nullptr; + stencil_ptrs[2] = coverage_bits & 4 ? &m_stencil_buffer->scanline(by + 1)[bx] : nullptr; + stencil_ptrs[3] = coverage_bits & 8 ? &m_stencil_buffer->scanline(by + 1)[bx + 1] : nullptr; + + stencil_value = load4_masked(stencil_ptrs[0], stencil_ptrs[1], stencil_ptrs[2], stencil_ptrs[3], quad.mask); + stencil_value &= stencil_configuration.test_mask; + + i32x4 stencil_test_passed; + switch (stencil_configuration.test_function) { + case StencilTestFunction::Always: + stencil_test_passed = expand4(~0); + break; + case StencilTestFunction::Equal: + stencil_test_passed = stencil_value == stencil_reference_value; + break; + case StencilTestFunction::Greater: + stencil_test_passed = stencil_value > stencil_reference_value; + break; + case StencilTestFunction::GreaterOrEqual: + stencil_test_passed = stencil_value >= stencil_reference_value; + break; + case StencilTestFunction::Less: + stencil_test_passed = stencil_value < stencil_reference_value; + break; + case StencilTestFunction::LessOrEqual: + stencil_test_passed = stencil_value <= stencil_reference_value; + break; + case StencilTestFunction::Never: + stencil_test_passed = expand4(0); + break; + case StencilTestFunction::NotEqual: + stencil_test_passed = stencil_value != stencil_reference_value; + break; + default: + VERIFY_NOT_REACHED(); + } + + // Update stencil buffer for pixels that failed the stencil test + write_to_stencil( + stencil_ptrs, + stencil_value, + stencil_configuration.on_stencil_test_fail, + stencil_reference_value, + stencil_configuration.write_mask, + quad.mask & ~stencil_test_passed); + + // Update coverage mask + early quad rejection + quad.mask &= stencil_test_passed; + if (none(quad.mask)) + continue; + } + + // Depth testing float* depth_ptrs[4] = { coverage_bits & 1 ? &m_depth_buffer->scanline(by)[bx] : nullptr, coverage_bits & 2 ? &m_depth_buffer->scanline(by)[bx + 1] : nullptr, coverage_bits & 4 ? &m_depth_buffer->scanline(by + 1)[bx] : nullptr, coverage_bits & 8 ? &m_depth_buffer->scanline(by + 1)[bx + 1] : nullptr, }; - - // AND the depth mask onto the coverage mask if (m_options.enable_depth_test) { auto depth = load4_masked(depth_ptrs[0], depth_ptrs[1], depth_ptrs[2], depth_ptrs[3], quad.mask); @@ -321,31 +413,35 @@ void Device::rasterize_triangle(const Triangle& triangle) // FIXME: Also apply depth_offset_factor which depends on the depth gradient quad.depth += m_options.depth_offset_constant * NumericLimits::epsilon(); + i32x4 depth_test_passed; switch (m_options.depth_func) { case DepthTestFunction::Always: + depth_test_passed = expand4(~0); break; case DepthTestFunction::Never: - quad.mask ^= quad.mask; + depth_test_passed = expand4(0); break; case DepthTestFunction::Greater: - quad.mask &= quad.depth > depth; + depth_test_passed = quad.depth > depth; break; case DepthTestFunction::GreaterOrEqual: - quad.mask &= quad.depth >= depth; + depth_test_passed = quad.depth >= depth; break; case DepthTestFunction::NotEqual: #ifdef __SSE__ - quad.mask &= quad.depth != depth; + depth_test_passed = quad.depth != depth; #else - quad.mask[0] = bit_cast(quad.depth[0]) != bit_cast(depth[0]) ? -1 : 0; - quad.mask[1] = bit_cast(quad.depth[1]) != bit_cast(depth[1]) ? -1 : 0; - quad.mask[2] = bit_cast(quad.depth[2]) != bit_cast(depth[2]) ? -1 : 0; - quad.mask[3] = bit_cast(quad.depth[3]) != bit_cast(depth[3]) ? -1 : 0; + depth_test_passed = i32x4 { + bit_cast(quad.depth[0]) != bit_cast(depth[0]) ? -1 : 0, + bit_cast(quad.depth[1]) != bit_cast(depth[1]) ? -1 : 0, + bit_cast(quad.depth[2]) != bit_cast(depth[2]) ? -1 : 0, + bit_cast(quad.depth[3]) != bit_cast(depth[3]) ? -1 : 0, + }; #endif break; case DepthTestFunction::Equal: #ifdef __SSE__ - quad.mask &= quad.depth == depth; + depth_test_passed = quad.depth == depth; #else // // This is an interesting quirk that occurs due to us using the x87 FPU when Serenity is @@ -358,25 +454,52 @@ void Device::rasterize_triangle(const Triangle& triangle) // the first 32-bits of this depth value is "good enough" that if we get a hit on it being // equal, we can pretty much guarantee that it's actually equal. // - quad.mask[0] = bit_cast(quad.depth[0]) == bit_cast(depth[0]) ? -1 : 0; - quad.mask[1] = bit_cast(quad.depth[1]) == bit_cast(depth[1]) ? -1 : 0; - quad.mask[2] = bit_cast(quad.depth[2]) == bit_cast(depth[2]) ? -1 : 0; - quad.mask[3] = bit_cast(quad.depth[3]) == bit_cast(depth[3]) ? -1 : 0; + depth_test_passed = i32x4 { + bit_cast(quad.depth[0]) == bit_cast(depth[0]) ? -1 : 0, + bit_cast(quad.depth[1]) == bit_cast(depth[1]) ? -1 : 0, + bit_cast(quad.depth[2]) == bit_cast(depth[2]) ? -1 : 0, + bit_cast(quad.depth[3]) == bit_cast(depth[3]) ? -1 : 0, + }; #endif break; case DepthTestFunction::LessOrEqual: - quad.mask &= quad.depth <= depth; + depth_test_passed = quad.depth <= depth; break; case DepthTestFunction::Less: - quad.mask &= quad.depth < depth; + depth_test_passed = quad.depth < depth; break; + default: + VERIFY_NOT_REACHED(); } - // Nice, no pixels passed the depth test -> block rejected by early z + // Update stencil buffer for pixels that failed the depth test + if (m_options.enable_stencil_test) { + write_to_stencil( + stencil_ptrs, + stencil_value, + stencil_configuration.on_depth_test_fail, + stencil_reference_value, + stencil_configuration.write_mask, + quad.mask & ~depth_test_passed); + } + + // Update coverage mask + early quad rejection + quad.mask &= depth_test_passed; if (none(quad.mask)) continue; } + // Update stencil buffer for passed pixels + if (m_options.enable_stencil_test) { + write_to_stencil( + stencil_ptrs, + stencil_value, + stencil_configuration.on_pass, + stencil_reference_value, + stencil_configuration.write_mask, + quad.mask); + } + INCREASE_STATISTICS_COUNTER(g_num_pixels_shaded, maskcount(quad.mask)); // Draw the pixels according to the previously generated mask @@ -415,9 +538,8 @@ void Device::rasterize_triangle(const Triangle& triangle) } // Write to depth buffer - if (m_options.enable_depth_test && m_options.enable_depth_write) { + if (m_options.enable_depth_test && m_options.enable_depth_write) store4_masked(quad.depth, depth_ptrs[0], depth_ptrs[1], depth_ptrs[2], depth_ptrs[3], quad.mask); - } // We will not update the color buffer at all if (!m_options.color_mask || !m_options.enable_color_write) @@ -465,8 +587,9 @@ void Device::rasterize_triangle(const Triangle& triangle) } Device::Device(const Gfx::IntSize& size) - : m_render_target { Gfx::Bitmap::try_create(Gfx::BitmapFormat::BGRA8888, size).release_value_but_fixme_should_propagate_errors() } - , m_depth_buffer { adopt_own(*new DepthBuffer(size)) } + : m_render_target(Gfx::Bitmap::try_create(Gfx::BitmapFormat::BGRA8888, size).release_value_but_fixme_should_propagate_errors()) + , m_depth_buffer(make(size)) + , m_stencil_buffer(MUST(StencilBuffer::try_create(size))) { m_options.scissor_box = m_render_target->rect(); m_options.viewport = m_render_target->rect(); @@ -478,7 +601,8 @@ DeviceInfo Device::info() const .vendor_name = "SerenityOS", .device_name = "SoftGPU", .num_texture_units = NUM_SAMPLERS, - .num_lights = NUM_LIGHTS + .num_lights = NUM_LIGHTS, + .stencil_bits = sizeof(u8) * 8, }; } @@ -626,7 +750,7 @@ void Device::draw_primitives(PrimitiveType primitive_type, FloatMatrix4x4 const& } // Now let's transform each triangle and send that to the GPU - auto const viewport = window_coordinates_to_target_coordinates(m_options.viewport, m_render_target->rect()); + auto const viewport = window_coordinates_to_target_coordinates(m_options.viewport); auto const viewport_half_width = viewport.width() / 2.0f; auto const viewport_half_height = viewport.height() / 2.0f; auto const viewport_center_x = viewport.x() + viewport_half_width; @@ -956,7 +1080,7 @@ void Device::clear_color(const FloatVector4& color) if (m_options.scissor_enabled) { auto fill_rect = m_render_target->rect(); - fill_rect.intersect(window_coordinates_to_target_coordinates(m_options.scissor_box, fill_rect)); + fill_rect.intersect(window_coordinates_to_target_coordinates(m_options.scissor_box)); Gfx::Painter painter { *m_render_target }; painter.fill_rect(fill_rect, fill_color); return; @@ -970,13 +1094,23 @@ void Device::clear_depth(float depth) wait_for_all_threads(); if (m_options.scissor_enabled) { - m_depth_buffer->clear(window_coordinates_to_target_coordinates(m_options.scissor_box, m_render_target->rect()), depth); + m_depth_buffer->clear(window_coordinates_to_target_coordinates(m_options.scissor_box), depth); return; } m_depth_buffer->clear(depth); } +void Device::clear_stencil(u8 value) +{ + Gfx::IntRect clear_rect = m_stencil_buffer->rect(); + + if (m_options.scissor_enabled) + clear_rect.intersect(window_coordinates_to_target_coordinates(m_options.scissor_box)); + + m_stencil_buffer->clear(clear_rect, value); +} + void Device::blit_to_color_buffer_at_raster_position(Gfx::Bitmap const& source) { if (!m_raster_position.valid) @@ -1148,6 +1282,11 @@ void Device::set_material_state(Face face, Material const& material) m_materials[face] = material; } +void Device::set_stencil_configuration(Face face, StencilConfiguration const& stencil_configuration) +{ + m_stencil_configuration[face] = stencil_configuration; +} + void Device::set_raster_position(RasterPosition const& raster_position) { m_raster_position = raster_position; @@ -1192,7 +1331,7 @@ Gfx::IntRect Device::raster_rect_in_target_coordinates(Gfx::IntSize size) size.width(), size.height(), }; - return window_coordinates_to_target_coordinates(raster_rect, m_render_target->rect()); + return window_coordinates_to_target_coordinates(raster_rect); } } diff --git a/Userland/Libraries/LibSoftGPU/Device.h b/Userland/Libraries/LibSoftGPU/Device.h index fc38e2cebb..8d761a49f5 100644 --- a/Userland/Libraries/LibSoftGPU/Device.h +++ b/Userland/Libraries/LibSoftGPU/Device.h @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -26,6 +27,7 @@ #include #include #include +#include #include #include @@ -38,6 +40,7 @@ struct TexCoordGenerationConfig { struct RasterizerOptions { bool shade_smooth { true }; + bool enable_stencil_test { false }; bool enable_depth_test { false }; bool enable_depth_write { true }; bool enable_alpha_test { false }; @@ -94,6 +97,17 @@ struct RasterPosition { FloatVector4 texture_coordinates { 0.0f, 0.0f, 0.0f, 1.0f }; }; +struct StencilConfiguration { + StencilTestFunction test_function; + u8 reference_value; + u8 test_mask; + + StencilOperation on_stencil_test_fail; + StencilOperation on_depth_test_fail; + StencilOperation on_pass; + u8 write_mask; +}; + class Device final { public: Device(const Gfx::IntSize& min_size); @@ -104,6 +118,7 @@ public: void resize(const Gfx::IntSize& min_size); void clear_color(const FloatVector4&); void clear_depth(float); + void clear_stencil(u8); void blit_to(Gfx::Bitmap&); void blit_to_color_buffer_at_raster_position(Gfx::Bitmap const&); void blit_to_depth_buffer_at_raster_position(Vector const&, size_t, size_t); @@ -120,6 +135,7 @@ public: void set_sampler_config(unsigned, SamplerConfig const&); void set_light_state(unsigned, Light const&); void set_material_state(Face, Material const&); + void set_stencil_configuration(Face, StencilConfiguration const&); RasterPosition raster_position() const { return m_raster_position; } void set_raster_position(RasterPosition const& raster_position); @@ -128,15 +144,16 @@ public: private: void draw_statistics_overlay(Gfx::Bitmap&); Gfx::IntRect raster_rect_in_target_coordinates(Gfx::IntSize size); + Gfx::IntRect window_coordinates_to_target_coordinates(Gfx::IntRect const&); void rasterize_triangle(const Triangle& triangle); void setup_blend_factors(); void shade_fragments(PixelQuad&); bool test_alpha(PixelQuad&); -private: RefPtr m_render_target; - OwnPtr m_depth_buffer; + NonnullOwnPtr m_depth_buffer; + NonnullOwnPtr m_stencil_buffer; RasterizerOptions m_options; LightModelParameters m_lighting_model; Clipper m_clipper; @@ -149,6 +166,7 @@ private: Array m_lights; Array m_materials; RasterPosition m_raster_position; + Array m_stencil_configuration; }; } diff --git a/Userland/Libraries/LibSoftGPU/DeviceInfo.h b/Userland/Libraries/LibSoftGPU/DeviceInfo.h index 41038aa2a7..840d87536b 100644 --- a/Userland/Libraries/LibSoftGPU/DeviceInfo.h +++ b/Userland/Libraries/LibSoftGPU/DeviceInfo.h @@ -15,6 +15,7 @@ struct DeviceInfo final { String device_name; unsigned num_texture_units; unsigned num_lights; + u8 stencil_bits; }; } diff --git a/Userland/Libraries/LibSoftGPU/Enums.h b/Userland/Libraries/LibSoftGPU/Enums.h index 38f5fa08fe..79c31d2ac7 100644 --- a/Userland/Libraries/LibSoftGPU/Enums.h +++ b/Userland/Libraries/LibSoftGPU/Enums.h @@ -87,6 +87,28 @@ enum class PrimitiveType { Quads, }; +enum StencilOperation { + Decrement, + DecrementWrap, + Increment, + IncrementWrap, + Invert, + Keep, + Replace, + Zero, +}; + +enum StencilTestFunction { + Always, + Equal, + Greater, + GreaterOrEqual, + Less, + LessOrEqual, + Never, + NotEqual, +}; + enum TexCoordGenerationCoordinate { None = 0x0, S = 0x1, diff --git a/Userland/Libraries/LibSoftGPU/StencilBuffer.cpp b/Userland/Libraries/LibSoftGPU/StencilBuffer.cpp new file mode 100644 index 0000000000..b9aad2e626 --- /dev/null +++ b/Userland/Libraries/LibSoftGPU/StencilBuffer.cpp @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2022, Jelle Raaijmakers + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include + +namespace SoftGPU { + +ErrorOr> StencilBuffer::try_create(Gfx::IntSize const& size) +{ + auto rect = Gfx::IntRect { 0, 0, size.width(), size.height() }; + auto data = TRY(FixedArray::try_create(size.area())); + return adopt_own(*new StencilBuffer(rect, move(data))); +} + +StencilBuffer::StencilBuffer(Gfx::IntRect const& rect, FixedArray data) + : m_data(move(data)) + , m_rect(rect) +{ +} + +void StencilBuffer::clear(Gfx::IntRect rect, u8 value) +{ + rect.intersect(m_rect); + + for (int y = rect.top(); y <= rect.bottom(); ++y) { + auto* line = scanline(y); + for (int x = rect.left(); x <= rect.right(); ++x) + line[x] = value; + } +} + +u8* StencilBuffer::scanline(int y) +{ + VERIFY(m_rect.contains_vertically(y)); + return &m_data[y * m_rect.width()]; +} + +} diff --git a/Userland/Libraries/LibSoftGPU/StencilBuffer.h b/Userland/Libraries/LibSoftGPU/StencilBuffer.h new file mode 100644 index 0000000000..b79340557a --- /dev/null +++ b/Userland/Libraries/LibSoftGPU/StencilBuffer.h @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2021, Jelle Raaijmakers + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace SoftGPU { + +class StencilBuffer final { +public: + static ErrorOr> try_create(Gfx::IntSize const& size); + + void clear(Gfx::IntRect rect, u8 value); + Gfx::IntRect const& rect() const { return m_rect; } + u8* scanline(int y); + +private: + StencilBuffer(Gfx::IntRect const& rect, FixedArray data); + + FixedArray m_data; + Gfx::IntRect m_rect; +}; + +} -- cgit v1.2.3