diff options
author | Jelle Raaijmakers <jelle@gmta.nl> | 2022-08-24 23:47:49 +0200 |
---|---|---|
committer | Andreas Kling <kling@serenityos.org> | 2022-08-27 12:28:05 +0200 |
commit | eb7c3d16fbfd805f9fbb3b819a661db10088fb56 (patch) | |
tree | 51e65bff9fead51d7c8f367d5e522d2f24deec31 | |
parent | d7cfdfe6335de83f25d205cd9863fc18e2854763 (diff) | |
download | serenity-eb7c3d16fbfd805f9fbb3b819a661db10088fb56.zip |
LibGL+LibGPU+LibSoftGPU: Implement flexible pixel format conversion
A GPU (driver) is now responsible for reading and writing pixels from
and to user data. The client (LibGL) is responsible for specifying how
the user data must be interpreted or written to.
This allows us to centralize all pixel format conversion in one class,
`LibSoftGPU::PixelConverter`. For both the input and output image, it
takes a specification containing the image dimensions, the pixel type
and the selection (basically a clipping rect), and converts the pixels
from the input image to the output image.
Effectively this means we now support almost all OpenGL 1.5 formats,
and all custom logic has disappeared from:
- `glDrawPixels`
- `glReadPixels`
- `glTexImage2D`
- `glTexSubImage2D`
The new logic is still unoptimized, but on my machine I experienced no
noticeable slowdown. :^)
24 files changed, 1350 insertions, 705 deletions
diff --git a/Tests/LibGL/TestRender.cpp b/Tests/LibGL/TestRender.cpp index 4d5448bb49..73c287cf51 100644 --- a/Tests/LibGL/TestRender.cpp +++ b/Tests/LibGL/TestRender.cpp @@ -169,3 +169,33 @@ TEST_CASE(0005_lines_antialiased) context->present(); expect_bitmap_equals_reference(context->frontbuffer(), "0005_lines"sv); } + +TEST_CASE(0006_test_rgb565_texture) +{ + auto context = create_testing_context(64, 64); + + GLuint texture_id; + glGenTextures(1, &texture_id); + glBindTexture(GL_TEXTURE_2D, texture_id); + u16 texture_data[] = { 0xF800, 0xC000, 0x8000, 0x07E0, 0x0600, 0x0400, 0x001F, 0x0018, 0x0010 }; + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 3, 3, 0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, texture_data); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + + glEnable(GL_TEXTURE_2D); + glBegin(GL_QUADS); + glTexCoord2i(0, 0); + glVertex2i(-1, 1); + glTexCoord2i(0, 1); + glVertex2i(-1, -1); + glTexCoord2i(1, 1); + glVertex2i(1, -1); + glTexCoord2i(1, 0); + glVertex2i(1, 1); + glEnd(); + + EXPECT_EQ(glGetError(), 0u); + + context->present(); + expect_bitmap_equals_reference(context->frontbuffer(), "0006_test_rgb565_texture"sv); +} diff --git a/Tests/LibGL/reference-images/0006_test_rgb565_texture.qoi b/Tests/LibGL/reference-images/0006_test_rgb565_texture.qoi Binary files differnew file mode 100644 index 0000000000..888bded0d8 --- /dev/null +++ b/Tests/LibGL/reference-images/0006_test_rgb565_texture.qoi diff --git a/Userland/Libraries/LibGL/CMakeLists.txt b/Userland/Libraries/LibGL/CMakeLists.txt index 511bbecdba..347ba5bb36 100644 --- a/Userland/Libraries/LibGL/CMakeLists.txt +++ b/Userland/Libraries/LibGL/CMakeLists.txt @@ -3,6 +3,7 @@ set(SOURCES ContextParameter.cpp GLAPI.cpp GLContext.cpp + Image.cpp Lighting.cpp List.cpp Matrix.cpp diff --git a/Userland/Libraries/LibGL/ContextParameter.cpp b/Userland/Libraries/LibGL/ContextParameter.cpp index 83a0326f29..c35048c7bd 100644 --- a/Userland/Libraries/LibGL/ContextParameter.cpp +++ b/Userland/Libraries/LibGL/ContextParameter.cpp @@ -584,4 +584,28 @@ GLboolean GLContext::gl_is_enabled(GLenum capability) return parameter.value.boolean_value; } +GPU::PackingSpecification GLContext::get_packing_specification(PackingType packing_type) +{ + // Make use of the fact that the GL_PACK_* and GL_UNPACK_* enum constants are in the exact same order + auto const offset = (packing_type == PackingType::Unpack) ? 0 : (GL_PACK_SWAP_BYTES - GL_UNPACK_SWAP_BYTES); + auto get_packing_value = [&](GLenum packing_parameter) -> GLint { + GLint value; + gl_get_integerv(packing_parameter + offset, &value); + return value; + }; + + // FIXME: add support for GL_UNPACK_SKIP_PIXELS, GL_UNPACK_SKIP_ROWS and GL_UNPACK_LSB_FIRST + GLint byte_alignment { get_packing_value(GL_UNPACK_ALIGNMENT) }; + GLint swap_bytes { get_packing_value(GL_UNPACK_SWAP_BYTES) }; + GLint depth_stride { get_packing_value(GL_UNPACK_IMAGE_HEIGHT) }; + GLint row_stride { get_packing_value(GL_UNPACK_ROW_LENGTH) }; + + return { + .depth_stride = static_cast<u32>(depth_stride), + .row_stride = static_cast<u32>(row_stride), + .byte_alignment = static_cast<u8>(byte_alignment), + .component_bytes_order = swap_bytes == GL_TRUE ? GPU::ComponentBytesOrder::Reversed : GPU::ComponentBytesOrder::Normal, + }; +} + } diff --git a/Userland/Libraries/LibGL/GL/gl.h b/Userland/Libraries/LibGL/GL/gl.h index 55bef5aafd..70052cfd5d 100644 --- a/Userland/Libraries/LibGL/GL/gl.h +++ b/Userland/Libraries/LibGL/GL/gl.h @@ -217,6 +217,7 @@ extern "C" { #define GL_COMPILE_AND_EXECUTE 0x1301 // Type enums +#define GL_BITMAP 0x1A00 #define GL_BYTE 0x1400 #define GL_UNSIGNED_BYTE 0x1401 #define GL_SHORT 0x1402 @@ -228,11 +229,23 @@ extern "C" { #define GL_3_BYTES 0x1408 #define GL_4_BYTES 0x1409 #define GL_DOUBLE 0x140A +#define GL_HALF_FLOAT 0x140B +#define GL_UNSIGNED_BYTE_3_3_2 0x8032 +#define GL_UNSIGNED_SHORT_4_4_4_4 0x8033 +#define GL_UNSIGNED_SHORT_5_5_5_1 0x8034 +#define GL_UNSIGNED_INT_8_8_8_8 0x8035 +#define GL_UNSIGNED_INT_10_10_10_2 0x8036 +#define GL_UNSIGNED_BYTE_2_3_3_REV 0x8362 +#define GL_UNSIGNED_SHORT_5_6_5 0x8363 +#define GL_UNSIGNED_SHORT_5_6_5_REV 0x8364 +#define GL_UNSIGNED_SHORT_4_4_4_4_REV 0x8365 +#define GL_UNSIGNED_SHORT_1_5_5_5_REV 0x8366 +#define GL_UNSIGNED_INT_8_8_8_8_REV 0x8367 +#define GL_UNSIGNED_INT_2_10_10_10_REV 0x8368 #define GL_BOOL 0x8B56 // Format enums #define GL_COLOR_INDEX 0x1900 -#define GL_COLOR_INDEX8_EXT 0x80E5 #define GL_STENCIL_INDEX 0x1901 #define GL_DEPTH_COMPONENT 0x1902 #define GL_RED 0x1903 @@ -244,21 +257,80 @@ extern "C" { #define GL_LUMINANCE 0x1909 #define GL_LUMINANCE8 0x8040 #define GL_LUMINANCE_ALPHA 0x190A -#define GL_LUMINANCE8_ALPHA8 0x8045 +#define GL_R3_G3_B2 0x2A10 #define GL_BGR 0x80E0 #define GL_BGRA 0x80E1 -#define GL_BITMAP 0x1A00 - +#define GL_ALPHA4 0x803B +#define GL_ALPHA8 0x803C +#define GL_ALPHA12 0x803D +#define GL_ALPHA16 0x803E +#define GL_LUMINANCE4 0x803F #define GL_LUMINANCE8 0x8040 +#define GL_LUMINANCE12 0x8041 +#define GL_LUMINANCE16 0x8042 +#define GL_LUMINANCE4_ALPHA4 0x8043 +#define GL_LUMINANCE6_ALPHA2 0x8044 +#define GL_LUMINANCE8_ALPHA8 0x8045 +#define GL_LUMINANCE12_ALPHA4 0x8046 +#define GL_LUMINANCE12_ALPHA12 0x8047 +#define GL_LUMINANCE16_ALPHA16 0x8048 +#define GL_INTENSITY 0x8049 +#define GL_INTENSITY4 0x804A #define GL_INTENSITY8 0x804B -#define GL_R3_G3_B2 0x2A10 +#define GL_INTENSITY12 0x804C +#define GL_INTENSITY16 0x804D #define GL_RGB4 0x804F #define GL_RGB5 0x8050 #define GL_RGB8 0x8051 +#define GL_RGB10 0x8052 +#define GL_RGB12 0x8053 +#define GL_RGB16 0x8054 #define GL_RGBA2 0x8055 #define GL_RGBA4 0x8056 #define GL_RGB5_A1 0x8057 #define GL_RGBA8 0x8058 +#define GL_RGB10_A2 0x8059 +#define GL_RGBA12 0x805A +#define GL_RGBA16 0x805B +#define GL_COLOR_INDEX8_EXT 0x80E5 +#define GL_DEPTH_COMPONENT16 0x81A5 +#define GL_DEPTH_COMPONENT16_SGIX 0x81A5 +#define GL_DEPTH_COMPONENT24 0x81A6 +#define GL_DEPTH_COMPONENT24_SGIX 0x81A6 +#define GL_DEPTH_COMPONENT32 0x81A7 +#define GL_DEPTH_COMPONENT32_SGIX 0x81A7 +#define GL_RG 0x8227 +#define GL_COMPRESSED_ALPHA 0x84E9 +#define GL_COMPRESSED_ALPHA_ARB 0x84E9 +#define GL_COMPRESSED_LUMINANCE 0x84EA +#define GL_COMPRESSED_LUMINANCE_ARB 0x84EA +#define GL_COMPRESSED_LUMINANCE_ALPHA 0x84EB +#define GL_COMPRESSED_LUMINANCE_ALPHA_ARB 0x84EB +#define GL_COMPRESSED_INTENSITY 0x84EC +#define GL_COMPRESSED_INTENSITY_ARB 0x84EC +#define GL_COMPRESSED_RGB 0x84ED +#define GL_COMPRESSED_RGB_ARB 0x84ED +#define GL_COMPRESSED_RGBA 0x84EE +#define GL_COMPRESSED_RGBA_ARB 0x84EE +#define GL_DEPTH_STENCIL 0x84F9 +#define GL_DEPTH_STENCIL_EXT 0x84F9 +#define GL_DEPTH_STENCIL_NV 0x84F9 +#define GL_SRGB 0x8C40 +#define GL_SRGB_EXT 0x8C40 +#define GL_SRGB8 0x8C41 +#define GL_SRGB8_EXT 0x8C41 +#define GL_SRGB_ALPHA 0x8C42 +#define GL_SRGB_ALPHA_EXT 0x8C42 +#define GL_SRGB8_ALPHA8 0x8C43 +#define GL_SRGB8_ALPHA8_EXT 0x8C43 +#define GL_SLUMINANCE_ALPHA 0x8C44 +#define GL_SLUMINANCE_ALPHA_EXT 0x8C44 +#define GL_SLUMINANCE8_ALPHA8 0x8C45 +#define GL_SLUMINANCE8_ALPHA8_EXT 0x8C45 +#define GL_SLUMINANCE 0x8C46 +#define GL_SLUMINANCE_EXT 0x8C46 +#define GL_SLUMINANCE8 0x8C47 +#define GL_SLUMINANCE8_EXT 0x8C47 // Lighting related defines #define GL_LIGHTING 0x0B50 @@ -313,21 +385,6 @@ extern "C" { #define GL_LINE 0x1B01 #define GL_FILL 0x1B02 -// Source pixel data format -#define GL_UNSIGNED_BYTE 0x1401 -#define GL_UNSIGNED_BYTE_3_3_2 0x8032 -#define GL_UNSIGNED_SHORT_4_4_4_4 0x8033 -#define GL_UNSIGNED_SHORT_5_5_5_1 0x8034 -#define GL_UNSIGNED_INT_8_8_8_8 0x8035 -#define GL_UNSIGNED_INT_10_10_10_2 0x8036 -#define GL_UNSIGNED_BYTE_2_3_3_REV 0x8362 -#define GL_UNSIGNED_SHORT_5_6_5 0x8363 -#define GL_UNSIGNED_SHORT_5_6_5_REV 0x8364 -#define GL_UNSIGNED_SHORT_4_4_4_4_REV 0x8365 -#define GL_UNSIGNED_SHORT_1_5_5_5_REV 0x8366 -#define GL_UNSIGNED_INT_8_8_8_8_REV 0x8367 -#define GL_UNSIGNED_INT_2_10_10_10_REV 0x8368 - // Stencil buffer operations #define GL_KEEP 0x1E00 #define GL_REPLACE 0x1E01 diff --git a/Userland/Libraries/LibGL/GLContext.cpp b/Userland/Libraries/LibGL/GLContext.cpp index 729ff0e09e..d31b28122a 100644 --- a/Userland/Libraries/LibGL/GLContext.cpp +++ b/Userland/Libraries/LibGL/GLContext.cpp @@ -11,8 +11,10 @@ #include <AK/Format.h> #include <AK/Vector.h> #include <LibGL/GLContext.h> +#include <LibGL/Image.h> #include <LibGPU/Device.h> #include <LibGPU/Enums.h> +#include <LibGPU/ImageDataLayout.h> #include <LibGPU/ImageFormat.h> #include <LibGfx/Bitmap.h> #include <LibGfx/Vector3.h> @@ -497,289 +499,40 @@ void GLContext::gl_read_pixels(GLint x, GLint y, GLsizei width, GLsizei height, RETURN_WITH_ERROR_IF(m_in_draw_state, GL_INVALID_OPERATION); RETURN_WITH_ERROR_IF(width < 0 || height < 0, GL_INVALID_VALUE); - RETURN_WITH_ERROR_IF(format != GL_COLOR_INDEX - && format != GL_STENCIL_INDEX - && format != GL_DEPTH_COMPONENT - && format != GL_RED - && format != GL_GREEN - && format != GL_BLUE - && format != GL_ALPHA - && format != GL_RGB - && format != GL_RGBA - && format != GL_LUMINANCE - && format != GL_LUMINANCE_ALPHA, - GL_INVALID_ENUM); - - RETURN_WITH_ERROR_IF(type != GL_UNSIGNED_BYTE - && type != GL_BYTE - && type != GL_BITMAP - && type != GL_UNSIGNED_SHORT - && type != GL_SHORT - && type != GL_BLUE - && type != GL_UNSIGNED_INT - && type != GL_INT - && type != GL_FLOAT, - GL_INVALID_ENUM); - - // FIXME: We only support RGBA buffers for now. - // Once we add support for indexed color modes do the correct check here - RETURN_WITH_ERROR_IF(format == GL_COLOR_INDEX, GL_INVALID_OPERATION); - - // FIXME: We do not have stencil buffers yet - // Once we add support for stencil buffers do the correct check here - RETURN_WITH_ERROR_IF(format == GL_STENCIL_INDEX, GL_INVALID_OPERATION); + auto pixel_type_or_error = get_validated_pixel_type(GL_NONE, GL_NONE, format, type); + RETURN_WITH_ERROR_IF(pixel_type_or_error.is_error(), pixel_type_or_error.release_error().code()); + + auto pixel_type = pixel_type_or_error.release_value(); + GPU::ImageDataLayout output_layout = { + .pixel_type = pixel_type, + .packing = get_packing_specification(PackingType::Pack), + .dimensions = { + .width = static_cast<u32>(width), + .height = static_cast<u32>(height), + .depth = 1, + }, + .selection = { + .width = static_cast<u32>(width), + .height = static_cast<u32>(height), + .depth = 1, + }, + }; - if (format == GL_DEPTH_COMPONENT) { - // FIXME: This check needs to be a bit more sophisticated. Currently the buffers - // are hardcoded. Once we add proper structures for them we need to correct this check + if (pixel_type.format == GPU::PixelFormat::DepthComponent) { + // FIXME: This check needs to be a bit more sophisticated. Currently the buffers are + // hardcoded. Once we add proper structures for them we need to correct this check // Error because only back buffer has a depth buffer RETURN_WITH_ERROR_IF(m_current_read_buffer == GL_FRONT || m_current_read_buffer == GL_FRONT_LEFT || m_current_read_buffer == GL_FRONT_RIGHT, GL_INVALID_OPERATION); - } - - // Some helper functions for converting float values to integer types - auto float_to_i8 = [](float f) -> GLchar { - return static_cast<GLchar>((0x7f * min(max(f, 0.0f), 1.0f) - 1) / 2); - }; - - auto float_to_i16 = [](float f) -> GLshort { - return static_cast<GLshort>((0x7fff * min(max(f, 0.0f), 1.0f) - 1) / 2); - }; - - auto float_to_i32 = [](float f) -> GLint { - return static_cast<GLint>((0x7fffffff * min(max(f, 0.0f), 1.0f) - 1) / 2); - }; - - auto float_to_u8 = [](float f) -> GLubyte { - return static_cast<GLubyte>(0xff * min(max(f, 0.0f), 1.0f)); - }; - - auto float_to_u16 = [](float f) -> GLushort { - return static_cast<GLushort>(0xffff * min(max(f, 0.0f), 1.0f)); - }; - - auto float_to_u32 = [](float f) -> GLuint { - return static_cast<GLuint>(0xffffffff * min(max(f, 0.0f), 1.0f)); - }; - - u8 component_size = 0; - switch (type) { - case GL_BYTE: - case GL_UNSIGNED_BYTE: - component_size = 1; - break; - case GL_SHORT: - case GL_UNSIGNED_SHORT: - component_size = 2; - break; - case GL_INT: - case GL_UNSIGNED_INT: - case GL_FLOAT: - component_size = 4; - break; - } - - if (format == GL_DEPTH_COMPONENT) { - auto const row_stride = (width * component_size + m_pack_alignment - 1) / m_pack_alignment * m_pack_alignment; - - // Read from depth buffer - for (GLsizei i = 0; i < height; ++i) { - for (GLsizei j = 0; j < width; ++j) { - float depth = m_rasterizer->get_depthbuffer_value(x + j, y + i); - auto char_ptr = reinterpret_cast<char*>(pixels) + i * row_stride + j * component_size; - - switch (type) { - case GL_BYTE: - *reinterpret_cast<GLchar*>(char_ptr) = float_to_i8(depth); - break; - case GL_SHORT: - *reinterpret_cast<GLshort*>(char_ptr) = float_to_i16(depth); - break; - case GL_INT: - *reinterpret_cast<GLint*>(char_ptr) = float_to_i32(depth); - break; - case GL_UNSIGNED_BYTE: - *reinterpret_cast<GLubyte*>(char_ptr) = float_to_u8(depth); - break; - case GL_UNSIGNED_SHORT: - *reinterpret_cast<GLushort*>(char_ptr) = float_to_u16(depth); - break; - case GL_UNSIGNED_INT: - *reinterpret_cast<GLuint*>(char_ptr) = float_to_u32(depth); - break; - case GL_FLOAT: - *reinterpret_cast<GLfloat*>(char_ptr) = min(max(depth, 0.0f), 1.0f); - break; - } - } - } - return; - } - bool write_red = false; - bool write_green = false; - bool write_blue = false; - bool write_alpha = false; - size_t component_count = 0; - size_t red_offset = 0; - size_t green_offset = 0; - size_t blue_offset = 0; - size_t alpha_offset = 0; - char* red_ptr = nullptr; - char* green_ptr = nullptr; - char* blue_ptr = nullptr; - char* alpha_ptr = nullptr; - - switch (format) { - case GL_RGB: - write_red = true; - write_green = true; - write_blue = true; - component_count = 3; - red_offset = 2; - green_offset = 1; - blue_offset = 0; - break; - case GL_RGBA: - write_red = true; - write_green = true; - write_blue = true; - write_alpha = true; - component_count = 4; - red_offset = 3; - green_offset = 2; - blue_offset = 1; - alpha_offset = 0; - break; - case GL_RED: - write_red = true; - component_count = 1; - red_offset = 0; - break; - case GL_GREEN: - write_green = true; - component_count = 1; - green_offset = 0; - break; - case GL_BLUE: - write_blue = true; - component_count = 1; - blue_offset = 0; - break; - case GL_ALPHA: - write_alpha = true; - component_count = 1; - alpha_offset = 0; - break; - } - - auto const pixel_bytes = component_size * component_count; - auto const row_alignment_bytes = (m_pack_alignment - ((width * pixel_bytes) % m_pack_alignment)) % m_pack_alignment; - - char* out_ptr = reinterpret_cast<char*>(pixels); - for (int i = 0; i < (int)height; ++i) { - for (int j = 0; j < (int)width; ++j) { - Gfx::ARGB32 color {}; - if (m_current_read_buffer == GL_FRONT || m_current_read_buffer == GL_LEFT || m_current_read_buffer == GL_FRONT_LEFT) { - if (y + i >= m_frontbuffer->width() || x + j >= m_frontbuffer->height()) - color = 0; - else - color = m_frontbuffer->scanline(y + i)[x + j]; - } else { - color = m_rasterizer->get_color_buffer_pixel(x + j, y + i); - } - - float red = ((color >> 24) & 0xff) / 255.0f; - float green = ((color >> 16) & 0xff) / 255.0f; - float blue = ((color >> 8) & 0xff) / 255.0f; - float alpha = (color & 0xff) / 255.0f; - - // FIXME: Set up write pointers based on selected endianness (glPixelStore) - red_ptr = out_ptr + (component_size * red_offset); - green_ptr = out_ptr + (component_size * green_offset); - blue_ptr = out_ptr + (component_size * blue_offset); - alpha_ptr = out_ptr + (component_size * alpha_offset); - - switch (type) { - case GL_BYTE: - if (write_red) - *reinterpret_cast<GLchar*>(red_ptr) = float_to_i8(red); - if (write_green) - *reinterpret_cast<GLchar*>(green_ptr) = float_to_i8(green); - if (write_blue) - *reinterpret_cast<GLchar*>(blue_ptr) = float_to_i8(blue); - if (write_alpha) - *reinterpret_cast<GLchar*>(alpha_ptr) = float_to_i8(alpha); - break; - case GL_UNSIGNED_BYTE: - if (write_red) - *reinterpret_cast<GLubyte*>(red_ptr) = float_to_u8(red); - if (write_green) - *reinterpret_cast<GLubyte*>(green_ptr) = float_to_u8(green); - if (write_blue) - *reinterpret_cast<GLubyte*>(blue_ptr) = float_to_u8(blue); - if (write_alpha) - *reinterpret_cast<GLubyte*>(alpha_ptr) = float_to_u8(alpha); - break; - case GL_SHORT: - if (write_red) - *reinterpret_cast<GLshort*>(red_ptr) = float_to_i16(red); - if (write_green) - *reinterpret_cast<GLshort*>(green_ptr) = float_to_i16(green); - if (write_blue) - *reinterpret_cast<GLshort*>(blue_ptr) = float_to_i16(blue); - if (write_alpha) - *reinterpret_cast<GLshort*>(alpha_ptr) = float_to_i16(alpha); - break; - case GL_UNSIGNED_SHORT: - if (write_red) - *reinterpret_cast<GLushort*>(red_ptr) = float_to_u16(red); - if (write_green) - *reinterpret_cast<GLushort*>(green_ptr) = float_to_u16(green); - if (write_blue) - *reinterpret_cast<GLushort*>(blue_ptr) = float_to_u16(blue); - if (write_alpha) - *reinterpret_cast<GLushort*>(alpha_ptr) = float_to_u16(alpha); - break; - case GL_INT: - if (write_red) - *reinterpret_cast<GLint*>(red_ptr) = float_to_i32(red); - if (write_green) - *reinterpret_cast<GLint*>(green_ptr) = float_to_i32(green); - if (write_blue) - *reinterpret_cast<GLint*>(blue_ptr) = float_to_i32(blue); - if (write_alpha) - *reinterpret_cast<GLint*>(alpha_ptr) = float_to_i32(alpha); - break; - case GL_UNSIGNED_INT: - if (write_red) - *reinterpret_cast<GLuint*>(red_ptr) = float_to_u32(red); - if (write_green) - *reinterpret_cast<GLuint*>(green_ptr) = float_to_u32(green); - if (write_blue) - *reinterpret_cast<GLuint*>(blue_ptr) = float_to_u32(blue); - if (write_alpha) - *reinterpret_cast<GLuint*>(alpha_ptr) = float_to_u32(alpha); - break; - case GL_FLOAT: - if (write_red) - *reinterpret_cast<GLfloat*>(red_ptr) = min(max(red, 0.0f), 1.0f); - if (write_green) - *reinterpret_cast<GLfloat*>(green_ptr) = min(max(green, 0.0f), 1.0f); - if (write_blue) - *reinterpret_cast<GLfloat*>(blue_ptr) = min(max(blue, 0.0f), 1.0f); - if (write_alpha) - *reinterpret_cast<GLfloat*>(alpha_ptr) = min(max(alpha, 0.0f), 1.0f); - break; - } - - out_ptr += pixel_bytes; - } - - out_ptr += row_alignment_bytes; + m_rasterizer->blit_from_depth_buffer(pixels, { x, y }, output_layout); + } else if (pixel_type.format == GPU::PixelFormat::StencilIndex) { + dbgln("gl_read_pixels(): GL_STENCIL_INDEX is not yet supported"); + } else { + m_rasterizer->blit_from_color_buffer(pixels, { x, y }, output_layout); } } @@ -798,39 +551,10 @@ void GLContext::gl_draw_pixels(GLsizei width, GLsizei height, GLenum format, GLe { APPEND_TO_CALL_LIST_AND_RETURN_IF_NEEDED(gl_draw_pixels, width, height, format, type, data); - RETURN_WITH_ERROR_IF(format < GL_COLOR_INDEX || format > GL_BGRA, GL_INVALID_ENUM); - - RETURN_WITH_ERROR_IF((type < GL_BYTE || type > GL_FLOAT) - && (type < GL_UNSIGNED_BYTE_3_3_2 || type > GL_UNSIGNED_INT_10_10_10_2) - && (type < GL_UNSIGNED_BYTE_2_3_3_REV || type > GL_UNSIGNED_INT_2_10_10_10_REV), - GL_INVALID_ENUM); - - RETURN_WITH_ERROR_IF(type == GL_BITMAP && !(format == GL_COLOR_INDEX || format == GL_STENCIL_INDEX), GL_INVALID_ENUM); - + RETURN_WITH_ERROR_IF(m_in_draw_state, GL_INVALID_OPERATION); RETURN_WITH_ERROR_IF(width < 0 || height < 0, GL_INVALID_VALUE); // FIXME: GL_INVALID_OPERATION is generated if format is GL_STENCIL_INDEX and there is no stencil buffer - // FIXME: GL_INVALID_OPERATION is generated if format is GL_RED, GL_GREEN, GL_BLUE, GL_ALPHA, GL_RGB, GL_RGBA, - // GL_BGR, GL_BGRA, GL_LUMINANCE, or GL_LUMINANCE_ALPHA, and the GL is in color index mode - - RETURN_WITH_ERROR_IF(format != GL_RGB - && (type == GL_UNSIGNED_BYTE_3_3_2 - || type == GL_UNSIGNED_BYTE_2_3_3_REV - || type == GL_UNSIGNED_SHORT_5_6_5 - || type == GL_UNSIGNED_SHORT_5_6_5_REV), - GL_INVALID_OPERATION); - - RETURN_WITH_ERROR_IF(!(format == GL_RGBA || format == GL_BGRA) - && (type == GL_UNSIGNED_SHORT_4_4_4_4 - || type == GL_UNSIGNED_SHORT_4_4_4_4_REV - || type == GL_UNSIGNED_SHORT_5_5_5_1 - || type == GL_UNSIGNED_SHORT_1_5_5_5_REV - || type == GL_UNSIGNED_INT_8_8_8_8 - || type == GL_UNSIGNED_INT_8_8_8_8_REV - || type == GL_UNSIGNED_INT_10_10_10_2 - || type == GL_UNSIGNED_INT_2_10_10_10_REV), - GL_INVALID_OPERATION); - // FIXME: GL_INVALID_OPERATION is generated if a non-zero buffer object name is bound to the GL_PIXEL_UNPACK_BUFFER // target and the buffer object's data store is currently mapped. // FIXME: GL_INVALID_OPERATION is generated if a non-zero buffer object name is bound to the GL_PIXEL_UNPACK_BUFFER @@ -840,43 +564,31 @@ void GLContext::gl_draw_pixels(GLsizei width, GLsizei height, GLenum format, GLe // target and data is not evenly divisible into the number of bytes needed to store in memory a datum // indicated by type. - RETURN_WITH_ERROR_IF(m_in_draw_state, GL_INVALID_OPERATION); - - // FIXME: we only support RGBA + UNSIGNED_BYTE and DEPTH_COMPONENT + UNSIGNED_SHORT, implement all combinations! - if (!((format == GL_RGBA && type == GL_UNSIGNED_BYTE) || (format == GL_DEPTH_COMPONENT && type == GL_UNSIGNED_SHORT))) { - dbgln_if(GL_DEBUG, "gl_draw_pixels(): support for format {:#x} and/or type {:#x} not implemented", format, type); - return; - } - - // FIXME: implement support for pixel parameters such as GL_UNPACK_ALIGNMENT - - if (format == GL_RGBA) { - auto bitmap_or_error = Gfx::Bitmap::try_create(Gfx::BitmapFormat::BGRA8888, { width, height }); - RETURN_WITH_ERROR_IF(bitmap_or_error.is_error(), GL_OUT_OF_MEMORY); - auto bitmap = bitmap_or_error.release_value(); - - auto pixel_data = static_cast<u32 const*>(data); - for (int y = 0; y < height; ++y) - for (int x = 0; x < width; ++x) - bitmap->set_pixel(x, y, Color::from_argb(*(pixel_data++))); - - m_rasterizer->blit_to_color_buffer_at_raster_position(bitmap); - } else if (format == GL_DEPTH_COMPONENT) { - Vector<float> depth_values; - depth_values.ensure_capacity(width * height); - - auto depth_data = static_cast<u16 const*>(data); - for (int y = 0; y < height; ++y) { - for (int x = 0; x < width; ++x) { - auto u16_value = *(depth_data++); - auto float_value = static_cast<float>(u16_value) / NumericLimits<u16>::max(); - depth_values.append(float_value); - } - } + auto pixel_type_or_error = get_validated_pixel_type(GL_NONE, GL_NONE, format, type); + RETURN_WITH_ERROR_IF(pixel_type_or_error.is_error(), pixel_type_or_error.release_error().code()); + + auto pixel_type = pixel_type_or_error.release_value(); + GPU::ImageDataLayout input_layout = { + .pixel_type = pixel_type, + .packing = get_packing_specification(PackingType::Unpack), + .dimensions = { + .width = static_cast<u32>(width), + .height = static_cast<u32>(height), + .depth = 1, + }, + .selection = { + .width = static_cast<u32>(width), + .height = static_cast<u32>(height), + .depth = 1, + }, + }; - m_rasterizer->blit_to_depth_buffer_at_raster_position(depth_values, width, height); + if (pixel_type.format == GPU::PixelFormat::DepthComponent) { + m_rasterizer->blit_to_depth_buffer_at_raster_position(data, input_layout); + } else if (pixel_type.format == GPU::PixelFormat::StencilIndex) { + dbgln("gl_draw_pixels(): GL_STENCIL_INDEX is not yet supported"); } else { - VERIFY_NOT_REACHED(); + m_rasterizer->blit_to_color_buffer_at_raster_position(data, input_layout); } } diff --git a/Userland/Libraries/LibGL/GLContext.h b/Userland/Libraries/LibGL/GLContext.h index 69bbc9aba5..e520d13703 100644 --- a/Userland/Libraries/LibGL/GLContext.h +++ b/Userland/Libraries/LibGL/GLContext.h @@ -83,6 +83,11 @@ enum Face { Back = 1, }; +enum class PackingType { + Pack, + Unpack, +}; + class GLContext final { public: GLContext(RefPtr<GPU::Driver> driver, NonnullOwnPtr<GPU::Device>, Gfx::Bitmap&); @@ -235,6 +240,7 @@ private: } Optional<ContextParameter> get_context_parameter(GLenum pname); + GPU::PackingSpecification get_packing_specification(PackingType); template<typename T> void get_floating_point(GLenum pname, T* params); diff --git a/Userland/Libraries/LibGL/Image.cpp b/Userland/Libraries/LibGL/Image.cpp new file mode 100644 index 0000000000..aaff615eac --- /dev/null +++ b/Userland/Libraries/LibGL/Image.cpp @@ -0,0 +1,223 @@ +/* + * Copyright (c) 2022, Jelle Raaijmakers <jelle@gmta.nl> + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include <LibGL/Image.h> + +namespace GL { + +ErrorOr<GPU::PixelType> get_validated_pixel_type(GLenum target, GLenum internal_format, GLenum format, GLenum type) +{ + // We accept GL_NONE as target for non-texture related calls (such as `glDrawPixels`) + if (target != GL_NONE + && target != GL_TEXTURE_1D + && target != GL_TEXTURE_2D + && target != GL_TEXTURE_3D + && target != GL_TEXTURE_1D_ARRAY + && target != GL_TEXTURE_2D_ARRAY + && target != GL_TEXTURE_CUBE_MAP + && target != GL_PROXY_TEXTURE_1D + && target != GL_PROXY_TEXTURE_2D + && target != GL_PROXY_TEXTURE_3D) + return Error::from_errno(GL_INVALID_ENUM); + + // Internal format can be a number between 1 and 4. Symbolic formats were only added with EXT_texture, promoted to core in OpenGL 1.1 + if (internal_format == 1) + internal_format = GL_ALPHA; + else if (internal_format == 2) + internal_format = GL_LUMINANCE_ALPHA; + else if (internal_format == 3) + internal_format = GL_RGB; + else if (internal_format == 4) + internal_format = GL_RGBA; + + if (internal_format != GL_NONE + && internal_format != GL_ALPHA + && internal_format != GL_ALPHA4 + && internal_format != GL_ALPHA8 + && internal_format != GL_ALPHA12 + && internal_format != GL_ALPHA16 + && internal_format != GL_COMPRESSED_ALPHA + && internal_format != GL_COMPRESSED_LUMINANCE + && internal_format != GL_COMPRESSED_LUMINANCE_ALPHA + && internal_format != GL_COMPRESSED_INTENSITY + && internal_format != GL_COMPRESSED_RGB + && internal_format != GL_COMPRESSED_RGBA + && internal_format != GL_DEPTH_COMPONENT + && internal_format != GL_DEPTH_COMPONENT16 + && internal_format != GL_DEPTH_COMPONENT24 + && internal_format != GL_DEPTH_COMPONENT32 + && internal_format != GL_DEPTH_STENCIL + && internal_format != GL_LUMINANCE + && internal_format != GL_LUMINANCE4 + && internal_format != GL_LUMINANCE8 + && internal_format != GL_LUMINANCE12 + && internal_format != GL_LUMINANCE16 + && internal_format != GL_LUMINANCE_ALPHA + && internal_format != GL_LUMINANCE4_ALPHA4 + && internal_format != GL_LUMINANCE6_ALPHA2 + && internal_format != GL_LUMINANCE8_ALPHA8 + && internal_format != GL_LUMINANCE12_ALPHA4 + && internal_format != GL_LUMINANCE12_ALPHA12 + && internal_format != GL_LUMINANCE16_ALPHA16 + && internal_format != GL_INTENSITY + && internal_format != GL_INTENSITY4 + && internal_format != GL_INTENSITY8 + && internal_format != GL_INTENSITY12 + && internal_format != GL_INTENSITY16 + && internal_format != GL_R3_G3_B2 + && internal_format != GL_RED + && internal_format != GL_RG + && internal_format != GL_RGB + && internal_format != GL_RGB4 + && internal_format != GL_RGB5 + && internal_format != GL_RGB8 + && internal_format != GL_RGB10 + && internal_format != GL_RGB12 + && internal_format != GL_RGB16 + && internal_format != GL_RGBA + && internal_format != GL_RGBA2 + && internal_format != GL_RGBA4 + && internal_format != GL_RGB5_A1 + && internal_format != GL_RGBA8 + && internal_format != GL_RGB10_A2 + && internal_format != GL_RGBA12 + && internal_format != GL_RGBA16 + && internal_format != GL_SLUMINANCE + && internal_format != GL_SLUMINANCE8 + && internal_format != GL_SLUMINANCE_ALPHA + && internal_format != GL_SLUMINANCE8_ALPHA8 + && internal_format != GL_SRGB + && internal_format != GL_SRGB8 + && internal_format != GL_SRGB_ALPHA + && internal_format != GL_SRGB8_ALPHA8) + return Error::from_errno(GL_INVALID_ENUM); + + if ((format < GL_COLOR_INDEX || format > GL_LUMINANCE_ALPHA) && format != GL_BGR && format != GL_BGRA) + return Error::from_errno(GL_INVALID_ENUM); + + if (type != GL_BITMAP + && (type < GL_BYTE || type > GL_FLOAT) + && type != GL_HALF_FLOAT + && (type < GL_UNSIGNED_BYTE_3_3_2 || type > GL_UNSIGNED_INT_10_10_10_2) + && (type < GL_UNSIGNED_BYTE_2_3_3_REV || type > GL_UNSIGNED_INT_2_10_10_10_REV)) + return Error::from_errno(GL_INVALID_ENUM); + + if (type == GL_BITMAP && format != GL_COLOR_INDEX && format != GL_STENCIL_INDEX) + return Error::from_errno(GL_INVALID_ENUM); + + if (format != GL_RGB && (type == GL_UNSIGNED_BYTE_3_3_2 || type == GL_UNSIGNED_BYTE_2_3_3_REV || type == GL_UNSIGNED_SHORT_5_6_5 || type == GL_UNSIGNED_SHORT_5_6_5_REV)) + return Error::from_errno(GL_INVALID_OPERATION); + + if ((type == GL_UNSIGNED_SHORT_4_4_4_4 + || type == GL_UNSIGNED_SHORT_4_4_4_4_REV + || type == GL_UNSIGNED_SHORT_5_5_5_1 + || type == GL_UNSIGNED_SHORT_1_5_5_5_REV + || type == GL_UNSIGNED_INT_8_8_8_8 + || type == GL_UNSIGNED_INT_8_8_8_8_REV + || type == GL_UNSIGNED_INT_10_10_10_2 + || type == GL_UNSIGNED_INT_2_10_10_10_REV) + && format != GL_RGBA + && format != GL_BGRA) + return Error::from_errno(GL_INVALID_OPERATION); + + if (internal_format != GL_NONE) { + auto const internal_format_is_depth = internal_format == GL_DEPTH_COMPONENT + || internal_format == GL_DEPTH_COMPONENT16 + || internal_format == GL_DEPTH_COMPONENT24 + || internal_format == GL_DEPTH_COMPONENT32; + + if ((target != GL_TEXTURE_2D && target != GL_PROXY_TEXTURE_2D && internal_format_is_depth) + || (format == GL_DEPTH_COMPONENT && !internal_format_is_depth) + || (format != GL_DEPTH_COMPONENT && internal_format_is_depth)) + return Error::from_errno(GL_INVALID_OPERATION); + } + + return get_format_specification(format, type); +} + +GPU::PixelType get_format_specification(GLenum format, GLenum type) +{ + auto get_format = [](GLenum format) -> GPU::PixelFormat { + switch (format) { + case GL_ALPHA: + return GPU::PixelFormat::Alpha; + case GL_BGR: + return GPU::PixelFormat::BGR; + case GL_BGRA: + return GPU::PixelFormat::BGRA; + case GL_BLUE: + return GPU::PixelFormat::Blue; + case GL_COLOR_INDEX: + return GPU::PixelFormat::ColorIndex; + case GL_DEPTH_COMPONENT: + return GPU::PixelFormat::DepthComponent; + case GL_GREEN: + return GPU::PixelFormat::Green; + case GL_LUMINANCE: + return GPU::PixelFormat::Luminance; + case GL_LUMINANCE_ALPHA: + return GPU::PixelFormat::LuminanceAlpha; + case GL_RED: + return GPU::PixelFormat::Red; + case GL_RGB: + return GPU::PixelFormat::RGB; + case GL_RGBA: + return GPU::PixelFormat::RGBA; + case GL_STENCIL_INDEX: + return GPU::PixelFormat::StencilIndex; + } + VERIFY_NOT_REACHED(); + }; + auto pixel_format = get_format(format); + + switch (type) { + case GL_BITMAP: + return { pixel_format, GPU::PixelComponentBits::AllBits, GPU::PixelDataType::Bitmap, GPU::ComponentsOrder::Normal }; + case GL_BYTE: + return { pixel_format, GPU::PixelComponentBits::AllBits, GPU::PixelDataType::Byte, GPU::ComponentsOrder::Normal }; + case GL_FLOAT: + return { pixel_format, GPU::PixelComponentBits::AllBits, GPU::PixelDataType::Float, GPU::ComponentsOrder::Normal }; + case GL_HALF_FLOAT: + return { pixel_format, GPU::PixelComponentBits::AllBits, GPU::PixelDataType::HalfFloat, GPU::ComponentsOrder::Normal }; + case GL_INT: + return { pixel_format, GPU::PixelComponentBits::AllBits, GPU::PixelDataType::Int, GPU::ComponentsOrder::Normal }; + case GL_SHORT: + return { pixel_format, GPU::PixelComponentBits::AllBits, GPU::PixelDataType::Short, GPU::ComponentsOrder::Normal }; + case GL_UNSIGNED_BYTE: + return { pixel_format, GPU::PixelComponentBits::AllBits, GPU::PixelDataType::UnsignedByte, GPU::ComponentsOrder::Normal }; + case GL_UNSIGNED_BYTE_2_3_3_REV: + return { pixel_format, GPU::PixelComponentBits::B2_3_3, GPU::PixelDataType::UnsignedByte, GPU::ComponentsOrder::Reversed }; + case GL_UNSIGNED_BYTE_3_3_2: + return { pixel_format, GPU::PixelComponentBits::B3_3_2, GPU::PixelDataType::UnsignedByte, GPU::ComponentsOrder::Normal }; + case GL_UNSIGNED_INT: + return { pixel_format, GPU::PixelComponentBits::AllBits, GPU::PixelDataType::UnsignedInt, GPU::ComponentsOrder::Normal }; + case GL_UNSIGNED_INT_2_10_10_10_REV: + return { pixel_format, GPU::PixelComponentBits::B2_10_10_10, GPU::PixelDataType::UnsignedInt, GPU::ComponentsOrder::Reversed }; + case GL_UNSIGNED_INT_8_8_8_8: + return { pixel_format, GPU::PixelComponentBits::B8_8_8_8, GPU::PixelDataType::UnsignedInt, GPU::ComponentsOrder::Normal }; + case GL_UNSIGNED_INT_8_8_8_8_REV: + return { pixel_format, GPU::PixelComponentBits::B8_8_8_8, GPU::PixelDataType::UnsignedInt, GPU::ComponentsOrder::Reversed }; + case GL_UNSIGNED_INT_10_10_10_2: + return { pixel_format, GPU::PixelComponentBits::B10_10_10_2, GPU::PixelDataType::UnsignedInt, GPU::ComponentsOrder::Normal }; + case GL_UNSIGNED_SHORT: + return { pixel_format, GPU::PixelComponentBits::AllBits, GPU::PixelDataType::UnsignedShort, GPU::ComponentsOrder::Normal }; + case GL_UNSIGNED_SHORT_1_5_5_5_REV: + return { pixel_format, GPU::PixelComponentBits::B1_5_5_5, GPU::PixelDataType::UnsignedShort, GPU::ComponentsOrder::Reversed }; + case GL_UNSIGNED_SHORT_4_4_4_4: + return { pixel_format, GPU::PixelComponentBits::B4_4_4_4, GPU::PixelDataType::UnsignedShort, GPU::ComponentsOrder::Normal }; + case GL_UNSIGNED_SHORT_4_4_4_4_REV: + return { pixel_format, GPU::PixelComponentBits::B4_4_4_4, GPU::PixelDataType::UnsignedShort, GPU::ComponentsOrder::Reversed }; + case GL_UNSIGNED_SHORT_5_6_5: + return { pixel_format, GPU::PixelComponentBits::B5_6_5, GPU::PixelDataType::UnsignedShort, GPU::ComponentsOrder::Normal }; + case GL_UNSIGNED_SHORT_5_6_5_REV: + return { pixel_format, GPU::PixelComponentBits::B5_6_5, GPU::PixelDataType::UnsignedShort, GPU::ComponentsOrder::Reversed }; + case GL_UNSIGNED_SHORT_5_5_5_1: + return { pixel_format, GPU::PixelComponentBits::B5_5_5_1, GPU::PixelDataType::UnsignedShort, GPU::ComponentsOrder::Normal }; + } + VERIFY_NOT_REACHED(); +} + +} diff --git a/Userland/Libraries/LibGL/Image.h b/Userland/Libraries/LibGL/Image.h new file mode 100644 index 0000000000..aed64d30c0 --- /dev/null +++ b/Userland/Libraries/LibGL/Image.h @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2022, Jelle Raaijmakers <jelle@gmta.nl> + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include <AK/Error.h> +#include <LibGL/GL/gl.h> +#include <LibGL/GLContext.h> +#include <LibGPU/ImageDataLayout.h> +#include <LibGPU/ImageFormat.h> + +namespace GL { + +GPU::PixelType get_format_specification(GLenum format, GLenum type); +ErrorOr<GPU::PixelType> get_validated_pixel_type(GLenum target, GLenum internal_format, GLenum format, GLenum type); + +} diff --git a/Userland/Libraries/LibGL/Tex/Texture2D.cpp b/Userland/Libraries/LibGL/Tex/Texture2D.cpp index 061cbe9090..49e6b33bc2 100644 --- a/Userland/Libraries/LibGL/Tex/Texture2D.cpp +++ b/Userland/Libraries/LibGL/Tex/Texture2D.cpp @@ -1,6 +1,7 @@ /* * Copyright (c) 2021, Jesse Buhagiar <jooster669@gmail.com> * Copyright (c) 2021, Stephan Unverwerth <s.unverwerth@serenityos.org> + * Copyright (c) 2022, Jelle Raaijmakers <jelle@gmta.nl> * * SPDX-License-Identifier: BSD-2-Clause */ @@ -10,87 +11,33 @@ namespace GL { -void Texture2D::upload_texture_data(GLuint lod, GLint internal_format, GLsizei width, GLsizei height, GLenum format, GLenum type, GLvoid const* pixels, GLsizei pixels_per_row, u8 byte_alignment) +void Texture2D::upload_texture_data(GLuint lod, GLenum internal_format, GPU::ImageDataLayout input_layout, GLvoid const* pixels) { // NOTE: Some target, format, and internal formats are currently unsupported. // Considering we control this library, and `gl.h` itself, we don't need to add any // checks here to see if we support them; the program will simply fail to compile.. auto& mip = m_mipmaps[lod]; - mip.set_width(width); - mip.set_height(height); - m_internal_format = internal_format; + mip.set_width(input_layout.selection.width); + mip.set_height(input_layout.selection.height); // No pixel data was supplied; leave the texture memory uninitialized. if (pixels == nullptr) return; - replace_sub_texture_data(lod, 0, 0, width, height, format, type, pixels, pixels_per_row, byte_alignment); + replace_sub_texture_data(lod, input_layout, { 0, 0, 0 }, pixels); } -void Texture2D::replace_sub_texture_data(GLuint lod, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, GLvoid const* pixels, GLsizei pixels_per_row, u8 byte_alignment) +void Texture2D::replace_sub_texture_data(GLuint lod, GPU::ImageDataLayout input_layout, Vector3<i32> const& output_offset, GLvoid const* pixels) { - auto& mip = m_mipmaps[lod]; - - // FIXME: We currently only support GL_UNSIGNED_BYTE and GL_UNSIGNED_SHORT_5_6_5 pixel data - VERIFY(type == GL_UNSIGNED_BYTE || type == GL_UNSIGNED_SHORT_5_6_5); - VERIFY(xoffset >= 0 && yoffset >= 0 && xoffset + width <= mip.width() && yoffset + height <= mip.height()); - VERIFY(pixels_per_row == 0 || pixels_per_row >= xoffset + width); - // FIXME: We currently depend on the first glTexImage2D call to attach an image to mipmap level 0, which initializes the GPU image // Ideally we would create separate GPU images for each level and merge them into a final image // once used for rendering for the first time. if (device_image().is_null()) return; - u8 pixel_size_bytes; - switch (type) { - case GL_UNSIGNED_BYTE: - pixel_size_bytes = (format == GL_RGBA || format == GL_BGRA) ? 4 : 3; - break; - case GL_UNSIGNED_SHORT_5_6_5: - pixel_size_bytes = sizeof(u16); - break; - default: - VERIFY_NOT_REACHED(); - } - - // Calculate row offset at end to fit alignment - int const physical_width = pixels_per_row > 0 ? pixels_per_row : width; - size_t const physical_width_bytes = physical_width * pixel_size_bytes; - - GPU::ImageDataLayout layout; - layout.column_stride = pixel_size_bytes; - layout.row_stride = physical_width_bytes + (byte_alignment - physical_width_bytes % byte_alignment) % byte_alignment; - layout.depth_stride = 0; - - if (type == GL_UNSIGNED_SHORT_5_6_5) { - layout.format = GPU::ImageFormat::RGB565; - } else if (type == GL_UNSIGNED_BYTE) { - if (format == GL_RGB) - layout.format = GPU::ImageFormat::RGB888; - else if (format == GL_BGR) - layout.format = GPU::ImageFormat::BGR888; - else if (format == GL_RGBA) - layout.format = GPU::ImageFormat::RGBA8888; - else if (format == GL_BGRA) - layout.format = GPU::ImageFormat::BGRA8888; - } - - Vector3<unsigned> offset { - static_cast<unsigned>(xoffset), - static_cast<unsigned>(yoffset), - 0 - }; - - Vector3<unsigned> size { - static_cast<unsigned>(width), - static_cast<unsigned>(height), - 1 - }; - - device_image()->write_texels(0, lod, offset, size, pixels, layout); + device_image()->write_texels(0, lod, output_offset, pixels, input_layout); } } diff --git a/Userland/Libraries/LibGL/Tex/Texture2D.h b/Userland/Libraries/LibGL/Tex/Texture2D.h index e471e18d16..5ef8a3b740 100644 --- a/Userland/Libraries/LibGL/Tex/Texture2D.h +++ b/Userland/Libraries/LibGL/Tex/Texture2D.h @@ -1,6 +1,7 @@ /* * Copyright (c) 2021, Jesse Buhagiar <jooster669@gmail.com> * Copyright (c) 2021, Stephan Unverwerth <s.unverwerth@serenityos.org> + * Copyright (c) 2022, Jelle Raaijmakers <jelle@gmta.nl> * * SPDX-License-Identifier: BSD-2-Clause */ @@ -10,13 +11,11 @@ #include "Texture.h" #include <AK/Array.h> -#include <AK/RefCounted.h> -#include <AK/Vector.h> #include <LibGL/GL/gl.h> #include <LibGL/Tex/MipMap.h> #include <LibGL/Tex/Sampler2D.h> -#include <LibGfx/Vector2.h> -#include <LibGfx/Vector4.h> +#include <LibGPU/ImageDataLayout.h> +#include <LibGfx/Vector3.h> namespace GL { @@ -28,8 +27,8 @@ public: virtual bool is_texture_2d() const override { return true; } - void upload_texture_data(GLuint lod, GLint internal_format, GLsizei width, GLsizei height, GLenum format, GLenum type, GLvoid const* pixels, GLsizei pixels_per_row, u8 byte_alignment); - void replace_sub_texture_data(GLuint lod, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, GLvoid const* pixels, GLsizei pixels_per_row, u8 byte_alignment); + void upload_texture_data(GLuint lod, GLenum internal_format, GPU::ImageDataLayout input_layout, GLvoid const* pixels); + void replace_sub_texture_data(GLuint lod, GPU::ImageDataLayout input_layout, Vector3<i32> const& output_offset, GLvoid const* pixels); MipMap const& mipmap(unsigned lod) const { diff --git a/Userland/Libraries/LibGL/Texture.cpp b/Userland/Libraries/LibGL/Texture.cpp index 019ba87d35..4e80a7bff6 100644 --- a/Userland/Libraries/LibGL/Texture.cpp +++ b/Userland/Libraries/LibGL/Texture.cpp @@ -8,6 +8,8 @@ #include <AK/Debug.h> #include <LibGL/GLContext.h> +#include <LibGL/Image.h> +#include <LibGPU/ImageDataLayout.h> namespace GL { @@ -306,26 +308,20 @@ void GLContext::gl_tex_gen_floatv(GLenum coord, GLenum pname, GLfloat const* par m_texcoord_generation_dirty = true; } +// FIXME: talk to GPU::Device to determine supported GPU::PixelTypes +constexpr GPU::PixelType texture_fixed_pixel_type = { + .format = GPU::PixelFormat::RGBA, + .bits = GPU::PixelComponentBits::AllBits, + .data_type = GPU::PixelDataType::Float, +}; + void GLContext::gl_tex_image_2d(GLenum target, GLint level, GLint internal_format, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, GLvoid const* data) { RETURN_WITH_ERROR_IF(m_in_draw_state, GL_INVALID_OPERATION); - // We only support GL_TEXTURE_2D for now - RETURN_WITH_ERROR_IF(target != GL_TEXTURE_2D, GL_INVALID_ENUM); - - // Internal format can also be a number between 1 and 4. Symbolic formats were only added with EXT_texture, promoted to core in OpenGL 1.1 - if (internal_format == 1) - internal_format = GL_ALPHA; - else if (internal_format == 2) - internal_format = GL_LUMINANCE_ALPHA; - else if (internal_format == 3) - internal_format = GL_RGB; - else if (internal_format == 4) - internal_format = GL_RGBA; + auto pixel_type_or_error = get_validated_pixel_type(target, internal_format, format, type); + RETURN_WITH_ERROR_IF(pixel_type_or_error.is_error(), pixel_type_or_error.release_error().code()); - // We only support symbolic constants for now - RETURN_WITH_ERROR_IF(!(internal_format == GL_RGB || internal_format == GL_RGBA || internal_format == GL_LUMINANCE8 || internal_format == GL_LUMINANCE8_ALPHA8), GL_INVALID_ENUM); - RETURN_WITH_ERROR_IF(!(type == GL_UNSIGNED_BYTE || type == GL_UNSIGNED_SHORT_5_6_5), GL_INVALID_VALUE); RETURN_WITH_ERROR_IF(level < 0 || level > Texture2D::LOG2_MAX_TEXTURE_SIZE, GL_INVALID_VALUE); RETURN_WITH_ERROR_IF(width < 0 || height < 0 || width > (2 + Texture2D::MAX_TEXTURE_SIZE) || height > (2 + Texture2D::MAX_TEXTURE_SIZE), GL_INVALID_VALUE); // Check if width and height are a power of 2 @@ -345,11 +341,26 @@ void GLContext::gl_tex_image_2d(GLenum target, GLint level, GLint internal_forma // that constructing GL textures in any but the default mipmap order, going from level 0 upwards will cause mip levels to stay uninitialized. // To be spec compliant we should create the device image once the texture has become complete and is used for rendering the first time. // All images that were attached before the device image was created need to be stored somewhere to be used to initialize the device image once complete. - texture_2d->set_device_image(m_rasterizer->create_image(GPU::ImageFormat::BGRA8888, width, height, 1, 999, 1)); + texture_2d->set_device_image(m_rasterizer->create_image(texture_fixed_pixel_type, width, height, 1, 999, 1)); m_sampler_config_is_dirty = true; } - texture_2d->upload_texture_data(level, internal_format, width, height, format, type, data, m_unpack_row_length, m_unpack_alignment); + GPU::ImageDataLayout input_layout = { + .pixel_type = pixel_type_or_error.release_value(), + .packing = get_packing_specification(PackingType::Unpack), + .dimensions = { + .width = static_cast<u32>(width), + .height = static_cast<u32>(height), + .depth = 1, + }, + .selection = { + .width = static_cast<u32>(width), + .height = static_cast<u32>(height), + .depth = 1, + }, + }; + + texture_2d->upload_texture_data(level, internal_format, input_layout, data); } void GLContext::gl_tex_parameter(GLenum target, GLenum pname, GLfloat param) @@ -454,12 +465,7 @@ void GLContext::gl_tex_sub_image_2d(GLenum target, GLint level, GLint xoffset, G { RETURN_WITH_ERROR_IF(m_in_draw_state, GL_INVALID_OPERATION); - // We only support GL_TEXTURE_2D for now - RETURN_WITH_ERROR_IF(target != GL_TEXTURE_2D, GL_INVALID_ENUM); - // We only support symbolic constants for now - RETURN_WITH_ERROR_IF(!(format == GL_RGBA || format == GL_RGB), GL_INVALID_VALUE); - RETURN_WITH_ERROR_IF(!(type == GL_UNSIGNED_BYTE || type == GL_UNSIGNED_SHORT_5_6_5), GL_INVALID_VALUE); RETURN_WITH_ERROR_IF(level < 0 || level > Texture2D::LOG2_MAX_TEXTURE_SIZE, GL_INVALID_VALUE); RETURN_WITH_ERROR_IF(width < 0 || height < 0 || width > (2 + Texture2D::MAX_TEXTURE_SIZE) || height > (2 + Texture2D::MAX_TEXTURE_SIZE), GL_INVALID_VALUE); @@ -467,9 +473,27 @@ void GLContext::gl_tex_sub_image_2d(GLenum target, GLint level, GLint xoffset, G auto texture_2d = m_active_texture_unit->texture_2d_target_texture(); RETURN_WITH_ERROR_IF(texture_2d.is_null(), GL_INVALID_OPERATION); + auto pixel_type_or_error = get_validated_pixel_type(target, texture_2d->internal_format(), format, type); + RETURN_WITH_ERROR_IF(pixel_type_or_error.is_error(), pixel_type_or_error.release_error().code()); + RETURN_WITH_ERROR_IF(xoffset < 0 || yoffset < 0 || xoffset + width > texture_2d->width_at_lod(level) || yoffset + height > texture_2d->height_at_lod(level), GL_INVALID_VALUE); - texture_2d->replace_sub_texture_data(level, xoffset, yoffset, width, height, format, type, data, m_unpack_row_length, m_unpack_alignment); + GPU::ImageDataLayout input_layout = { + .pixel_type = pixel_type_or_error.release_value(), + .packing = get_packing_specification(PackingType::Unpack), + .dimensions = { + .width = static_cast<u32>(width), + .height = static_cast<u32>(height), + .depth = 1, + }, + .selection = { + .width = static_cast<u32>(width), + .height = static_cast<u32>(height), + .depth = 1, + }, + }; + + texture_2d->replace_sub_texture_data(level, input_layout, { xoffset, yoffset, 0 }, data); } void GLContext::sync_device_sampler_config() diff --git a/Userland/Libraries/LibGPU/Device.h b/Userland/Libraries/LibGPU/Device.h index c90f1a1c93..5f4ef2a691 100644 --- a/Userland/Libraries/LibGPU/Device.h +++ b/Userland/Libraries/LibGPU/Device.h @@ -1,5 +1,6 @@ /* * Copyright (c) 2022, Stephan Unverwerth <s.unverwerth@serenityos.org> + * Copyright (c) 2022, Jelle Raaijmakers <jelle@gmta.nl> * * SPDX-License-Identifier: BSD-2-Clause */ @@ -15,7 +16,7 @@ #include <LibGPU/DeviceInfo.h> #include <LibGPU/Enums.h> #include <LibGPU/Image.h> -#include <LibGPU/ImageFormat.h> +#include <LibGPU/ImageDataLayout.h> #include <LibGPU/Light.h> #include <LibGPU/LightModelParameters.h> #include <LibGPU/Material.h> @@ -30,13 +31,14 @@ #include <LibGfx/Matrix4x4.h> #include <LibGfx/Rect.h> #include <LibGfx/Size.h> +#include <LibGfx/Vector2.h> #include <LibGfx/Vector4.h> namespace GPU { class Device { public: - virtual ~Device() { } + virtual ~Device() = default; virtual DeviceInfo info() const = 0; @@ -46,16 +48,16 @@ public: virtual void clear_depth(DepthType) = 0; virtual void clear_stencil(StencilType) = 0; virtual void blit_color_buffer_to(Gfx::Bitmap& target) = 0; - virtual void blit_to_color_buffer_at_raster_position(Gfx::Bitmap const&) = 0; - virtual void blit_to_depth_buffer_at_raster_position(Vector<DepthType> const&, int, int) = 0; + virtual void blit_from_color_buffer(void*, Vector2<i32> offset, GPU::ImageDataLayout const&) = 0; + virtual void blit_from_depth_buffer(void*, Vector2<i32> offset, GPU::ImageDataLayout const&) = 0; + virtual void blit_to_color_buffer_at_raster_position(void const*, GPU::ImageDataLayout const&) = 0; + virtual void blit_to_depth_buffer_at_raster_position(void const*, GPU::ImageDataLayout const&) = 0; virtual void set_options(RasterizerOptions const&) = 0; virtual void set_light_model_params(LightModelParameters const&) = 0; virtual RasterizerOptions options() const = 0; virtual LightModelParameters light_model() const = 0; - virtual ColorType get_color_buffer_pixel(int x, int y) = 0; - virtual DepthType get_depthbuffer_value(int x, int y) = 0; - virtual NonnullRefPtr<Image> create_image(ImageFormat format, unsigned width, unsigned height, unsigned depth, unsigned levels, unsigned layers) = 0; + virtual NonnullRefPtr<Image> create_image(PixelType const&, u32 width, u32 height, u32 depth, u32 levels, u32 layers) = 0; virtual void set_sampler_config(unsigned, SamplerConfig const&) = 0; virtual void set_light_state(unsigned, Light const&) = 0; diff --git a/Userland/Libraries/LibGPU/Image.h b/Userland/Libraries/LibGPU/Image.h index 4803d4ec17..1f2e189c39 100644 --- a/Userland/Libraries/LibGPU/Image.h +++ b/Userland/Libraries/LibGPU/Image.h @@ -21,9 +21,9 @@ public: virtual ~Image() { } - virtual void write_texels(unsigned layer, unsigned level, Vector3<unsigned> const& offset, Vector3<unsigned> const& size, void const* data, ImageDataLayout const& layout) = 0; - virtual void read_texels(unsigned layer, unsigned level, Vector3<unsigned> const& offset, Vector3<unsigned> const& size, void* data, ImageDataLayout const& layout) const = 0; - virtual void copy_texels(Image const& source, unsigned source_layer, unsigned source_level, Vector3<unsigned> const& source_offset, Vector3<unsigned> const& size, unsigned destination_layer, unsigned destination_level, Vector3<unsigned> const& destination_offset) = 0; + virtual void write_texels(u32 layer, u32 level, Vector3<i32> const& output_offset, void const* data, ImageDataLayout const&) = 0; + virtual void read_texels(u32 layer, u32 level, Vector3<i32> const& input_offset, void* data, ImageDataLayout const&) const = 0; + virtual void copy_texels(Image const& source, u32 source_layer, u32 source_level, Vector3<u32> const& source_offset, Vector3<u32> const& size, u32 destination_layer, u32 destination_level, Vector3<u32> const& destination_offset) = 0; void const* ownership_token() const { return m_ownership_token; } bool has_same_ownership_token(Image const& other) const { return other.ownership_token() == ownership_token(); } diff --git a/Userland/Libraries/LibGPU/ImageDataLayout.h b/Userland/Libraries/LibGPU/ImageDataLayout.h index 60e135ee48..1a3604bef7 100644 --- a/Userland/Libraries/LibGPU/ImageDataLayout.h +++ b/Userland/Libraries/LibGPU/ImageDataLayout.h @@ -1,5 +1,6 @@ /* * Copyright (c) 2021, Stephan Unverwerth <s.unverwerth@serenityos.org> + * Copyright (c) 2022, Jelle Raaijmakers <jelle@gmta.nl> * * SPDX-License-Identifier: BSD-2-Clause */ @@ -10,11 +11,41 @@ namespace GPU { +// Order of bytes within a single component +enum class ComponentBytesOrder { + Normal, + Reversed, +}; + +struct PackingSpecification final { + u32 depth_stride { 0 }; + u32 row_stride { 0 }; + u8 byte_alignment { 1 }; + ComponentBytesOrder component_bytes_order { ComponentBytesOrder::Normal }; +}; + +// Full dimensions of the image +struct DimensionSpecification final { + u32 width; + u32 height; + u32 depth; +}; + +// Subselection (source or target) within the image +struct ImageSelection final { + i32 offset_x { 0 }; + i32 offset_y { 0 }; + i32 offset_z { 0 }; + u32 width; + u32 height; + u32 depth; +}; + struct ImageDataLayout final { - GPU::ImageFormat format; - size_t column_stride; - size_t row_stride; - size_t depth_stride; + PixelType pixel_type; + PackingSpecification packing {}; + DimensionSpecification dimensions; + ImageSelection selection; }; } diff --git a/Userland/Libraries/LibGPU/ImageFormat.h b/Userland/Libraries/LibGPU/ImageFormat.h index acae63d96a..7d4dc86d11 100644 --- a/Userland/Libraries/LibGPU/ImageFormat.h +++ b/Userland/Libraries/LibGPU/ImageFormat.h @@ -1,5 +1,6 @@ /* * Copyright (c) 2021, Stephan Unverwerth <s.unverwerth@serenityos.org> + * Copyright (c) 2022, Jelle Raaijmakers <jelle@gmta.nl> * Copyright (c) 2022, the SerenityOS developers. * * SPDX-License-Identifier: BSD-2-Clause @@ -7,37 +8,169 @@ #pragma once -#include <AK/Types.h> +#include <AK/Array.h> namespace GPU { -enum class ImageFormat { - RGB565, - RGB888, - BGR888, - RGBA8888, - BGRA8888, - L8, - L8A8, +// The pixel data's representation +enum class PixelFormat { + Alpha, + BGR, + BGRA, + Blue, + ColorIndex, + DepthComponent, + Green, + Luminance, + LuminanceAlpha, + Red, + RGB, + RGBA, + StencilIndex, }; -static constexpr size_t element_size(ImageFormat format) +// Bit width assigned to individual components within a single pixel's value +enum class PixelComponentBits { + AllBits, + B1_5_5_5, + B2_3_3, + B2_10_10_10, + B3_3_2, + B4_4_4_4, + B5_5_5_1, + B5_6_5, + B8_8_8_8, + B10_10_10_2, +}; + +// The base data type used as pixel storage +enum class PixelDataType { + Bitmap, + Byte, + Float, + HalfFloat, + Int, + Short, + UnsignedByte, + UnsignedInt, + UnsignedShort, +}; + +// Order of components within a single pixel +enum class ComponentsOrder { + Normal, + Reversed, +}; + +struct PixelType final { + PixelFormat format; + PixelComponentBits bits; + PixelDataType data_type; + ComponentsOrder components_order { ComponentsOrder::Normal }; +}; + +static constexpr int number_of_components(PixelFormat format) { switch (format) { - case ImageFormat::L8: + case PixelFormat::Alpha: + case PixelFormat::Blue: + case PixelFormat::ColorIndex: + case PixelFormat::DepthComponent: + case PixelFormat::Green: + case PixelFormat::Luminance: + case PixelFormat::Red: + case PixelFormat::StencilIndex: return 1; - case ImageFormat::RGB565: - case ImageFormat::L8A8: + case PixelFormat::LuminanceAlpha: return 2; - case ImageFormat::RGB888: - case ImageFormat::BGR888: + case PixelFormat::BGR: + case PixelFormat::RGB: + return 3; + case PixelFormat::BGRA: + case PixelFormat::RGBA: + return 4; + } + VERIFY_NOT_REACHED(); +} + +static constexpr int number_of_components(PixelComponentBits bits) +{ + switch (bits) { + case PixelComponentBits::AllBits: + return 1; + case PixelComponentBits::B2_3_3: + case PixelComponentBits::B3_3_2: + case PixelComponentBits::B5_6_5: return 3; - case ImageFormat::RGBA8888: - case ImageFormat::BGRA8888: + case PixelComponentBits::B1_5_5_5: + case PixelComponentBits::B2_10_10_10: + case PixelComponentBits::B4_4_4_4: + case PixelComponentBits::B5_5_5_1: + case PixelComponentBits::B8_8_8_8: + case PixelComponentBits::B10_10_10_2: return 4; - default: + } + VERIFY_NOT_REACHED(); +} + +static constexpr Array<u8, 4> pixel_component_bitfield_lengths(PixelComponentBits bits) +{ + switch (bits) { + case PixelComponentBits::AllBits: VERIFY_NOT_REACHED(); + case PixelComponentBits::B1_5_5_5: + return { 1, 5, 5, 5 }; + case PixelComponentBits::B2_3_3: + return { 2, 3, 3 }; + case PixelComponentBits::B2_10_10_10: + return { 2, 10, 10, 10 }; + case PixelComponentBits::B3_3_2: + return { 3, 3, 2 }; + case PixelComponentBits::B4_4_4_4: + return { 4, 4, 4, 4 }; + case PixelComponentBits::B5_5_5_1: + return { 5, 5, 5, 1 }; + case PixelComponentBits::B5_6_5: + return { 5, 6, 5 }; + case PixelComponentBits::B8_8_8_8: + return { 8, 8, 8, 8 }; + case PixelComponentBits::B10_10_10_2: + return { 10, 10, 10, 2 }; } + VERIFY_NOT_REACHED(); +} + +static constexpr size_t pixel_data_type_size_in_bytes(PixelDataType data_type) +{ + switch (data_type) { + case PixelDataType::Bitmap: + return sizeof(u8); + case PixelDataType::Byte: + return sizeof(u8); + case PixelDataType::Float: + return sizeof(float); + case PixelDataType::HalfFloat: + return sizeof(float) / 2; + case PixelDataType::Int: + return sizeof(i32); + case PixelDataType::Short: + return sizeof(i16); + case PixelDataType::UnsignedByte: + return sizeof(u8); + case PixelDataType::UnsignedInt: + return sizeof(u32); + case PixelDataType::UnsignedShort: + return sizeof(u16); + } + VERIFY_NOT_REACHED(); +} + +static constexpr u8 pixel_size_in_bytes(PixelType pixel_type) +{ + auto component_size_in_bytes = pixel_data_type_size_in_bytes(pixel_type.data_type); + if (pixel_type.bits == PixelComponentBits::AllBits) + return component_size_in_bytes * number_of_components(pixel_type.format); + return component_size_in_bytes; } } diff --git a/Userland/Libraries/LibSoftGPU/Buffer/Typed2DBuffer.h b/Userland/Libraries/LibSoftGPU/Buffer/Typed2DBuffer.h index a802acfaa3..f8069a19cb 100644 --- a/Userland/Libraries/LibSoftGPU/Buffer/Typed2DBuffer.h +++ b/Userland/Libraries/LibSoftGPU/Buffer/Typed2DBuffer.h @@ -33,20 +33,6 @@ public: ALWAYS_INLINE T* scanline(int y) { return m_buffer->buffer_pointer(0, y, 0); } ALWAYS_INLINE T const* scanline(int y) const { return m_buffer->buffer_pointer(0, y, 0); } - void blit_from_bitmap(Gfx::Bitmap const& bitmap, Gfx::IntRect const& target) requires IsSame<T, u32> - { - VERIFY(bitmap.format() == Gfx::BitmapFormat::BGRA8888 || bitmap.format() == Gfx::BitmapFormat::BGRx8888); - int source_y = 0; - for (int y = target.top(); y <= target.bottom(); ++y) { - auto* buffer_scanline = scanline(y); - auto const* bitmap_scanline = bitmap.scanline(source_y++); - - int source_x = 0; - for (int x = target.left(); x <= target.right(); ++x) - buffer_scanline[x] = bitmap_scanline[source_x++]; - } - } - void blit_flipped_to_bitmap(Gfx::Bitmap& bitmap, Gfx::IntRect const& target) const requires IsSame<T, u32> { VERIFY(bitmap.format() == Gfx::BitmapFormat::BGRA8888 || bitmap.format() == Gfx::BitmapFormat::BGRx8888); diff --git a/Userland/Libraries/LibSoftGPU/CMakeLists.txt b/Userland/Libraries/LibSoftGPU/CMakeLists.txt index bc99282ec6..5a50d7bbe1 100644 --- a/Userland/Libraries/LibSoftGPU/CMakeLists.txt +++ b/Userland/Libraries/LibSoftGPU/CMakeLists.txt @@ -2,6 +2,7 @@ set(SOURCES Clipper.cpp Device.cpp Image.cpp + PixelConverter.cpp Sampler.cpp ) diff --git a/Userland/Libraries/LibSoftGPU/Device.cpp b/Userland/Libraries/LibSoftGPU/Device.cpp index 13d1892e54..5235956022 100644 --- a/Userland/Libraries/LibSoftGPU/Device.cpp +++ b/Userland/Libraries/LibSoftGPU/Device.cpp @@ -18,6 +18,7 @@ #include <LibGfx/Vector3.h> #include <LibSoftGPU/Config.h> #include <LibSoftGPU/Device.h> +#include <LibSoftGPU/PixelConverter.h> #include <LibSoftGPU/PixelQuad.h> #include <LibSoftGPU/SIMD.h> #include <math.h> @@ -66,7 +67,7 @@ constexpr static auto interpolate(T const& v0, T const& v1, T const& v2, Vector3 return v0 * barycentric_coords.x() + v1 * barycentric_coords.y() + v2 * barycentric_coords.z(); } -static GPU::ColorType to_bgra32(FloatVector4 const& color) +static GPU::ColorType to_argb32(FloatVector4 const& color) { auto clamped = color.clamped(0.0f, 1.0f); auto r = static_cast<u8>(clamped.x() * 255); @@ -76,9 +77,9 @@ static GPU::ColorType to_bgra32(FloatVector4 const& color) return a << 24 | r << 16 | g << 8 | b; } -ALWAYS_INLINE static u32x4 to_bgra32(Vector4<f32x4> const& v) +ALWAYS_INLINE static u32x4 to_argb32(Vector4<f32x4> const& color) { - auto clamped = v.clamped(expand4(0.0f), expand4(1.0f)); + auto clamped = color.clamped(expand4(0.0f), expand4(1.0f)); auto r = to_u32x4(clamped.x() * 255); auto g = to_u32x4(clamped.y() * 255); auto b = to_u32x4(clamped.z() * 255); @@ -481,9 +482,9 @@ ALWAYS_INLINE void Device::rasterize(Gfx::IntRect& render_bounds, CB1 set_covera } if (m_options.color_mask == 0xffffffff) - store4_masked(to_bgra32(quad.out_color), color_ptrs[0], color_ptrs[1], color_ptrs[2], color_ptrs[3], quad.mask); + store4_masked(to_argb32(quad.out_color), color_ptrs[0], color_ptrs[1], color_ptrs[2], color_ptrs[3], quad.mask); else - store4_masked((to_bgra32(quad.out_color) & m_options.color_mask) | (dst_u32 & ~m_options.color_mask), color_ptrs[0], color_ptrs[1], color_ptrs[2], color_ptrs[3], quad.mask); + store4_masked((to_argb32(quad.out_color) & m_options.color_mask) | (dst_u32 & ~m_options.color_mask), color_ptrs[0], color_ptrs[1], color_ptrs[2], color_ptrs[3], quad.mask); } } } @@ -1315,7 +1316,7 @@ void Device::resize(Gfx::IntSize const& size) void Device::clear_color(FloatVector4 const& color) { - auto const fill_color = to_bgra32(color); + auto const fill_color = to_argb32(color); auto clear_rect = m_frame_buffer->rect(); if (m_options.scissor_enabled) @@ -1342,35 +1343,116 @@ void Device::clear_stencil(GPU::StencilType value) m_frame_buffer->stencil_buffer()->fill(value, clear_rect); } -void Device::blit_to_color_buffer_at_raster_position(Gfx::Bitmap const& source) +GPU::ImageDataLayout Device::color_buffer_data_layout(Vector2<u32> size, Vector2<i32> offset) +{ + return { + .pixel_type = { + .format = GPU::PixelFormat::BGRA, + .bits = GPU::PixelComponentBits::B8_8_8_8, + .data_type = GPU::PixelDataType::UnsignedInt, + .components_order = GPU::ComponentsOrder::Reversed, + }, + .dimensions = { + .width = static_cast<u32>(m_frame_buffer->rect().width()), + .height = static_cast<u32>(m_frame_buffer->rect().height()), + .depth = 1, + }, + .selection = { + .offset_x = offset.x(), + .offset_y = offset.y(), + .offset_z = 0, + .width = size.x(), + .height = size.y(), + .depth = 1, + }, + }; +} + +GPU::ImageDataLayout Device::depth_buffer_data_layout(Vector2<u32> size, Vector2<i32> offset) +{ + return { + .pixel_type = { + .format = GPU::PixelFormat::DepthComponent, + .bits = GPU::PixelComponentBits::AllBits, + .data_type = GPU::PixelDataType::Float, + }, + .dimensions = { + .width = static_cast<u32>(m_frame_buffer->rect().width()), + .height = static_cast<u32>(m_frame_buffer->rect().height()), + .depth = 1, + }, + .selection = { + .offset_x = offset.x(), + .offset_y = offset.y(), + .offset_z = 0, + .width = size.x(), + .height = size.y(), + .depth = 1, + }, + }; +} + +void Device::blit_from_color_buffer(void* output_data, Vector2<i32> input_offset, GPU::ImageDataLayout const& output_layout) +{ + auto const& output_selection = output_layout.selection; + auto input_layout = color_buffer_data_layout({ output_selection.width, output_selection.height }, input_offset); + + PixelConverter converter { input_layout, output_layout }; + auto const* input_data = m_frame_buffer->color_buffer()->scanline(0); + auto conversion_result = converter.convert(input_data, output_data); + if (conversion_result.is_error()) + dbgln("Pixel conversion failed: {}", conversion_result.error().string_literal()); +} + +void Device::blit_from_depth_buffer(void* output_data, Vector2<i32> input_offset, GPU::ImageDataLayout const& output_layout) +{ + auto const& output_selection = output_layout.selection; + auto input_layout = depth_buffer_data_layout({ output_selection.width, output_selection.height }, input_offset); + + PixelConverter converter { input_layout, output_layout }; + auto const* input_data = m_frame_buffer->depth_buffer()->scanline(0); + auto conversion_result = converter.convert(input_data, output_data); + if (conversion_result.is_error()) + dbgln("Pixel conversion failed: {}", conversion_result.error().string_literal()); +} + +void Device::blit_to_color_buffer_at_raster_position(void const* input_data, GPU::ImageDataLayout const& input_layout) { if (!m_raster_position.valid) return; - INCREASE_STATISTICS_COUNTER(g_num_pixels, source.width() * source.height()); - INCREASE_STATISTICS_COUNTER(g_num_pixels_shaded, source.width() * source.height()); + auto input_selection = input_layout.selection; + INCREASE_STATISTICS_COUNTER(g_num_pixels, input_selection.width * input_selection.height); + INCREASE_STATISTICS_COUNTER(g_num_pixels_shaded, input_selection.width * input_selection.height); + + auto const rasterization_rect = get_rasterization_rect_of_size({ input_selection.width, input_selection.height }); + auto output_layout = color_buffer_data_layout( + { static_cast<u32>(rasterization_rect.width()), static_cast<u32>(rasterization_rect.height()) }, + { rasterization_rect.x(), rasterization_rect.y() }); - auto const blit_rect = get_rasterization_rect_of_size({ source.width(), source.height() }); - m_frame_buffer->color_buffer()->blit_from_bitmap(source, blit_rect); + PixelConverter converter { input_layout, output_layout }; + auto* output_data = m_frame_buffer->color_buffer()->scanline(0); + auto conversion_result = converter.convert(input_data, output_data); + if (conversion_result.is_error()) + dbgln("Pixel conversion failed: {}", conversion_result.error().string_literal()); } -void Device::blit_to_depth_buffer_at_raster_position(Vector<GPU::DepthType> const& depth_values, int width, int height) +void Device::blit_to_depth_buffer_at_raster_position(void const* input_data, GPU::ImageDataLayout const& input_layout) { if (!m_raster_position.valid) return; - auto const raster_rect = get_rasterization_rect_of_size({ width, height }); - auto const y1 = raster_rect.y(); - auto const y2 = y1 + height; - auto const x1 = raster_rect.x(); - auto const x2 = x1 + width; - - auto index = 0; - for (auto y = y1; y < y2; ++y) { - auto depth_line = m_frame_buffer->depth_buffer()->scanline(y); - for (auto x = x1; x < x2; ++x) - depth_line[x] = depth_values[index++]; - } + auto input_selection = input_layout.selection; + auto const rasterization_rect = get_rasterization_rect_of_size({ input_selection.width, input_selection.height }); + auto output_layout = depth_buffer_data_layout( + { static_cast<u32>(rasterization_rect.width()), static_cast<u32>(rasterization_rect.height()) }, + { rasterization_rect.x(), rasterization_rect.y() }); + + PixelConverter converter { input_layout, output_layout }; + auto* output_data = m_frame_buffer->depth_buffer()->scanline(0); + auto conversion_result = converter.convert(input_data, output_data); + if (conversion_result.is_error()) + dbgln("Pixel conversion failed: {}", conversion_result.error().string_literal()); } void Device::blit_color_buffer_to(Gfx::Bitmap& target) @@ -1451,25 +1533,12 @@ void Device::set_light_model_params(GPU::LightModelParameters const& lighting_mo m_lighting_model = lighting_model; } -GPU::ColorType Device::get_color_buffer_pixel(int x, int y) -{ - // FIXME: Reading individual pixels is very slow, rewrite this to transfer whole blocks - if (!m_frame_buffer->rect().contains(x, y)) - return 0; - return m_frame_buffer->color_buffer()->scanline(y)[x]; -} - -GPU::DepthType Device::get_depthbuffer_value(int x, int y) -{ - // FIXME: Reading individual pixels is very slow, rewrite this to transfer whole blocks - if (!m_frame_buffer->rect().contains(x, y)) - return 1.0f; - return m_frame_buffer->depth_buffer()->scanline(y)[x]; -} - -NonnullRefPtr<GPU::Image> Device::create_image(GPU::ImageFormat format, unsigned width, unsigned height, unsigned depth, unsigned levels, unsigned layers) +NonnullRefPtr<GPU::Image> Device::create_image(GPU::PixelType const& pixel_type, u32 width, u32 height, u32 depth, u32 levels, u32 layers) { - VERIFY(format == GPU::ImageFormat::BGRA8888); + VERIFY(pixel_type.format == GPU::PixelFormat::RGBA + && pixel_type.bits == GPU::PixelComponentBits::AllBits + && pixel_type.data_type == GPU::PixelDataType::Float + && pixel_type.components_order == GPU::ComponentsOrder::Normal); VERIFY(width > 0); VERIFY(height > 0); VERIFY(depth > 0); diff --git a/Userland/Libraries/LibSoftGPU/Device.h b/Userland/Libraries/LibSoftGPU/Device.h index fb5ef3c4ea..e10b4c9f32 100644 --- a/Userland/Libraries/LibSoftGPU/Device.h +++ b/Userland/Libraries/LibSoftGPU/Device.h @@ -52,17 +52,17 @@ public: virtual void clear_color(FloatVector4 const&) override; virtual void clear_depth(GPU::DepthType) override; virtual void clear_stencil(GPU::StencilType) override; + virtual void blit_from_color_buffer(void*, Vector2<i32> offset, GPU::ImageDataLayout const&) override; + virtual void blit_from_depth_buffer(void*, Vector2<i32> offset, GPU::ImageDataLayout const&) override; virtual void blit_color_buffer_to(Gfx::Bitmap& target) override; - virtual void blit_to_color_buffer_at_raster_position(Gfx::Bitmap const&) override; - virtual void blit_to_depth_buffer_at_raster_position(Vector<GPU::DepthType> const&, int, int) override; + virtual void blit_to_color_buffer_at_raster_position(void const*, GPU::ImageDataLayout const&) override; + virtual void blit_to_depth_buffer_at_raster_position(void const*, GPU::ImageDataLayout const&) override; virtual void set_options(GPU::RasterizerOptions const&) override; virtual void set_light_model_params(GPU::LightModelParameters const&) override; virtual GPU::RasterizerOptions options() const override { return m_options; } virtual GPU::LightModelParameters light_model() const override { return m_lighting_model; } - virtual GPU::ColorType get_color_buffer_pixel(int x, int y) override; - virtual GPU::DepthType get_depthbuffer_value(int x, int y) override; - virtual NonnullRefPtr<GPU::Image> create_image(GPU::ImageFormat format, unsigned width, unsigned height, unsigned depth, unsigned levels, unsigned layers) override; + virtual NonnullRefPtr<GPU::Image> create_image(GPU::PixelType const&, u32 width, u32 height, u32 depth, u32 levels, u32 layers) override; virtual void set_sampler_config(unsigned, GPU::SamplerConfig const&) override; virtual void set_light_state(unsigned, GPU::Light const&) override; @@ -79,6 +79,9 @@ private: void draw_statistics_overlay(Gfx::Bitmap&); Gfx::IntRect get_rasterization_rect_of_size(Gfx::IntSize size) const; + GPU::ImageDataLayout color_buffer_data_layout(Vector2<u32> size, Vector2<i32> offset); + GPU::ImageDataLayout depth_buffer_data_layout(Vector2<u32> size, Vector2<i32> offset); + template<typename CB1, typename CB2, typename CB3> void rasterize(Gfx::IntRect& render_bounds, CB1 set_coverage_mask, CB2 set_quad_depth, CB3 set_quad_attributes); diff --git a/Userland/Libraries/LibSoftGPU/Image.cpp b/Userland/Libraries/LibSoftGPU/Image.cpp index 1a1189a051..8128e35563 100644 --- a/Userland/Libraries/LibSoftGPU/Image.cpp +++ b/Userland/Libraries/LibSoftGPU/Image.cpp @@ -6,123 +6,11 @@ */ #include <LibSoftGPU/Image.h> +#include <LibSoftGPU/PixelConverter.h> namespace SoftGPU { -static constexpr FloatVector4 unpack_color(void const* ptr, GPU::ImageFormat format) -{ - constexpr auto one_over_255 = 1.0f / 255; - switch (format) { - case GPU::ImageFormat::RGB888: { - auto rgb = reinterpret_cast<u8 const*>(ptr); - return { - rgb[0] * one_over_255, - rgb[1] * one_over_255, - rgb[2] * one_over_255, - 1.0f, - }; - } - case GPU::ImageFormat::BGR888: { - auto bgr = reinterpret_cast<u8 const*>(ptr); - return { - bgr[2] * one_over_255, - bgr[1] * one_over_255, - bgr[0] * one_over_255, - 1.0f, - }; - } - case GPU::ImageFormat::RGBA8888: { - auto rgba = *reinterpret_cast<u32 const*>(ptr); - return { - (rgba & 0xff) * one_over_255, - ((rgba >> 8) & 0xff) * one_over_255, - ((rgba >> 16) & 0xff) * one_over_255, - ((rgba >> 24) & 0xff) * one_over_255, - }; - } - case GPU::ImageFormat::BGRA8888: { - auto bgra = *reinterpret_cast<u32 const*>(ptr); - return { - ((bgra >> 16) & 0xff) * one_over_255, - ((bgra >> 8) & 0xff) * one_over_255, - (bgra & 0xff) * one_over_255, - ((bgra >> 24) & 0xff) * one_over_255, - }; - } - case GPU::ImageFormat::RGB565: { - auto rgb = *reinterpret_cast<u16 const*>(ptr); - return { - ((rgb >> 11) & 0x1f) / 31.f, - ((rgb >> 5) & 0x3f) / 63.f, - (rgb & 0x1f) / 31.f, - 1.0f - }; - } - case GPU::ImageFormat::L8: { - auto luminance = *reinterpret_cast<u8 const*>(ptr); - auto clamped_luminance = luminance * one_over_255; - return { - clamped_luminance, - clamped_luminance, - clamped_luminance, - 1.0f, - }; - } - case GPU::ImageFormat::L8A8: { - auto luminance_and_alpha = reinterpret_cast<u8 const*>(ptr); - auto clamped_luminance = luminance_and_alpha[0] * one_over_255; - return { - clamped_luminance, - clamped_luminance, - clamped_luminance, - luminance_and_alpha[1] * one_over_255, - }; - } - default: - VERIFY_NOT_REACHED(); - } -} - -static constexpr void pack_color(FloatVector4 const& color, void* ptr, GPU::ImageFormat format) -{ - auto r = static_cast<u8>(clamp(color.x(), 0.0f, 1.0f) * 255); - auto g = static_cast<u8>(clamp(color.y(), 0.0f, 1.0f) * 255); - auto b = static_cast<u8>(clamp(color.z(), 0.0f, 1.0f) * 255); - auto a = static_cast<u8>(clamp(color.w(), 0.0f, 1.0f) * 255); - - switch (format) { - case GPU::ImageFormat::RGB888: - reinterpret_cast<u8*>(ptr)[0] = r; - reinterpret_cast<u8*>(ptr)[1] = g; - reinterpret_cast<u8*>(ptr)[2] = b; - return; - case GPU::ImageFormat::BGR888: - reinterpret_cast<u8*>(ptr)[2] = b; - reinterpret_cast<u8*>(ptr)[1] = g; - reinterpret_cast<u8*>(ptr)[0] = r; - return; - case GPU::ImageFormat::RGBA8888: - *reinterpret_cast<u32*>(ptr) = r | (g << 8) | (b << 16) | (a << 24); - return; - case GPU::ImageFormat::BGRA8888: - *reinterpret_cast<u32*>(ptr) = b | (g << 8) | (r << 16) | (a << 24); - return; - case GPU::ImageFormat::RGB565: - *reinterpret_cast<u16*>(ptr) = (r & 0x1f) | ((g & 0x3f) << 5) | ((b & 0x1f) << 11); - return; - case GPU::ImageFormat::L8: - *reinterpret_cast<u8*>(ptr) = r; - return; - case GPU::ImageFormat::L8A8: - reinterpret_cast<u8*>(ptr)[0] = r; - reinterpret_cast<u8*>(ptr)[1] = a; - return; - default: - VERIFY_NOT_REACHED(); - } -} - -Image::Image(void const* ownership_token, unsigned width, unsigned height, unsigned depth, unsigned max_levels, unsigned layers) +Image::Image(void const* ownership_token, u32 width, u32 height, u32 depth, u32 max_levels, u32 layers) : GPU::Image(ownership_token) , m_num_layers(layers) , m_mipmap_buffers(FixedArray<RefPtr<Typed3DBuffer<FloatVector4>>>::must_create_but_fixme_should_propagate_errors(layers * max_levels)) @@ -137,9 +25,9 @@ Image::Image(void const* ownership_token, unsigned width, unsigned height, unsig m_height_is_power_of_two = is_power_of_two(height); m_depth_is_power_of_two = is_power_of_two(depth); - unsigned level; + u32 level; for (level = 0; level < max_levels; ++level) { - for (unsigned layer = 0; layer < layers; ++layer) + for (u32 layer = 0; layer < layers; ++layer) m_mipmap_buffers[layer * layers + level] = MUST(Typed3DBuffer<FloatVector4>::try_create(width, height, depth)); if (width <= 1 && height <= 1 && depth <= 1) @@ -153,45 +41,62 @@ Image::Image(void const* ownership_token, unsigned width, unsigned height, unsig m_num_levels = level + 1; } -void Image::write_texels(unsigned layer, unsigned level, Vector3<unsigned> const& offset, Vector3<unsigned> const& size, void const* data, GPU::ImageDataLayout const& layout) +GPU::ImageDataLayout Image::image_data_layout(u32 level, Vector3<i32> offset) const +{ + auto const width = level_width(level); + auto const height = level_height(level); + auto const depth = level_depth(level); + + // FIXME: we are directly writing to FloatVector4s. We should probably find a better way to do this + return { + .pixel_type = { + .format = GPU::PixelFormat::RGBA, + .bits = GPU::PixelComponentBits::AllBits, + .data_type = GPU::PixelDataType::Float, + }, + .dimensions = { + .width = width, + .height = height, + .depth = depth, + }, + .selection = { + .offset_x = offset.x(), + .offset_y = offset.y(), + .offset_z = offset.z(), + .width = width - offset.x(), + .height = height - offset.y(), + .depth = depth - offset.z(), + }, + }; +} + +void Image::write_texels(u32 layer, u32 level, Vector3<i32> const& output_offset, void const* data, GPU::ImageDataLayout const& input_layout) { VERIFY(layer < num_layers()); VERIFY(level < num_levels()); - VERIFY(offset.x() + size.x() <= level_width(level)); - VERIFY(offset.y() + size.y() <= level_height(level)); - VERIFY(offset.z() + size.z() <= level_depth(level)); - - for (unsigned z = 0; z < size.z(); ++z) { - for (unsigned y = 0; y < size.y(); ++y) { - for (unsigned x = 0; x < size.x(); ++x) { - auto ptr = reinterpret_cast<u8 const*>(data) + layout.depth_stride * z + layout.row_stride * y + layout.column_stride * x; - auto color = unpack_color(ptr, layout.format); - set_texel(layer, level, offset.x() + x, offset.y() + y, offset.z() + z, color); - } - } - } + + auto output_layout = image_data_layout(level, output_offset); + + PixelConverter converter { input_layout, output_layout }; + auto conversion_result = converter.convert(data, texel_pointer(layer, level, 0, 0, 0)); + if (conversion_result.is_error()) + dbgln("Pixel conversion failed: {}", conversion_result.error().string_literal()); } -void Image::read_texels(unsigned layer, unsigned level, Vector3<unsigned> const& offset, Vector3<unsigned> const& size, void* data, GPU::ImageDataLayout const& layout) const +void Image::read_texels(u32 layer, u32 level, Vector3<i32> const& input_offset, void* data, GPU::ImageDataLayout const& output_layout) const { VERIFY(layer < num_layers()); VERIFY(level < num_levels()); - VERIFY(offset.x() + size.x() <= level_width(level)); - VERIFY(offset.y() + size.y() <= level_height(level)); - VERIFY(offset.z() + size.z() <= level_depth(level)); - - for (unsigned z = 0; z < size.z(); ++z) { - for (unsigned y = 0; y < size.y(); ++y) { - for (unsigned x = 0; x < size.x(); ++x) { - auto color = texel(layer, level, offset.x() + x, offset.y() + y, offset.z() + z); - auto ptr = reinterpret_cast<u8*>(data) + layout.depth_stride * z + layout.row_stride * y + layout.column_stride * x; - pack_color(color, ptr, layout.format); - } - } - } + + auto input_layout = image_data_layout(level, input_offset); + + PixelConverter converter { input_layout, output_layout }; + auto conversion_result = converter.convert(texel_pointer(layer, level, 0, 0, 0), data); + if (conversion_result.is_error()) + dbgln("Pixel conversion failed: {}", conversion_result.error().string_literal()); } -void Image::copy_texels(GPU::Image const& source, unsigned source_layer, unsigned source_level, Vector3<unsigned> const& source_offset, Vector3<unsigned> const& size, unsigned destination_layer, unsigned destination_level, Vector3<unsigned> const& destination_offset) +void Image::copy_texels(GPU::Image const& source, u32 source_layer, u32 source_level, Vector3<u32> const& source_offset, Vector3<u32> const& size, u32 destination_layer, u32 destination_level, Vector3<u32> const& destination_offset) { VERIFY(source.has_same_ownership_token(*this)); @@ -208,9 +113,9 @@ void Image::copy_texels(GPU::Image const& source, unsigned source_layer, unsigne VERIFY(destination_offset.y() + size.y() <= level_height(destination_level)); VERIFY(destination_offset.z() + size.z() <= level_depth(destination_level)); - for (unsigned z = 0; z < size.z(); ++z) { - for (unsigned y = 0; y < size.y(); ++y) { - for (unsigned x = 0; x < size.x(); ++x) { + for (u32 z = 0; z < size.z(); ++z) { + for (u32 y = 0; y < size.y(); ++y) { + for (u32 x = 0; x < size.x(); ++x) { auto color = src_image.texel(source_layer, source_level, source_offset.x() + x, source_offset.y() + y, source_offset.z() + z); set_texel(destination_layer, destination_level, destination_offset.x() + x, destination_offset.y() + y, destination_offset.z() + z, color); } diff --git a/Userland/Libraries/LibSoftGPU/Image.h b/Userland/Libraries/LibSoftGPU/Image.h index 841e9dc71c..23accd8542 100644 --- a/Userland/Libraries/LibSoftGPU/Image.h +++ b/Userland/Libraries/LibSoftGPU/Image.h @@ -19,45 +19,47 @@ namespace SoftGPU { class Image final : public GPU::Image { public: - Image(void const* ownership_token, unsigned width, unsigned height, unsigned depth, unsigned max_levels, unsigned layers); + Image(void const* ownership_token, u32 width, u32 height, u32 depth, u32 max_levels, u32 layers); - unsigned level_width(unsigned level) const { return m_mipmap_buffers[level]->width(); } - unsigned level_height(unsigned level) const { return m_mipmap_buffers[level]->height(); } - unsigned level_depth(unsigned level) const { return m_mipmap_buffers[level]->depth(); } - unsigned num_levels() const { return m_num_levels; } - unsigned num_layers() const { return m_num_layers; } + u32 level_width(u32 level) const { return m_mipmap_buffers[level]->width(); } + u32 level_height(u32 level) const { return m_mipmap_buffers[level]->height(); } + u32 level_depth(u32 level) const { return m_mipmap_buffers[level]->depth(); } + u32 num_levels() const { return m_num_levels; } + u32 num_layers() const { return m_num_layers; } bool width_is_power_of_two() const { return m_width_is_power_of_two; } bool height_is_power_of_two() const { return m_height_is_power_of_two; } bool depth_is_power_of_two() const { return m_depth_is_power_of_two; } - FloatVector4 texel(unsigned layer, unsigned level, int x, int y, int z) const + FloatVector4 texel(u32 layer, u32 level, int x, int y, int z) const { return *texel_pointer(layer, level, x, y, z); } - void set_texel(unsigned layer, unsigned level, int x, int y, int z, FloatVector4 const& color) + void set_texel(u32 layer, u32 level, int x, int y, int z, FloatVector4 const& color) { *texel_pointer(layer, level, x, y, z) = color; } - virtual void write_texels(unsigned layer, unsigned level, Vector3<unsigned> const& offset, Vector3<unsigned> const& size, void const* data, GPU::ImageDataLayout const& layout) override; - virtual void read_texels(unsigned layer, unsigned level, Vector3<unsigned> const& offset, Vector3<unsigned> const& size, void* data, GPU::ImageDataLayout const& layout) const override; - virtual void copy_texels(GPU::Image const& source, unsigned source_layer, unsigned source_level, Vector3<unsigned> const& source_offset, Vector3<unsigned> const& size, unsigned destination_layer, unsigned destination_level, Vector3<unsigned> const& destination_offset) override; + virtual void write_texels(u32 layer, u32 level, Vector3<i32> const& output_offset, void const* data, GPU::ImageDataLayout const&) override; + virtual void read_texels(u32 layer, u32 level, Vector3<i32> const& input_offset, void* data, GPU::ImageDataLayout const&) const override; + virtual void copy_texels(GPU::Image const& source, u32 source_layer, u32 source_level, Vector3<u32> const& source_offset, Vector3<u32> const& size, u32 destination_layer, u32 destination_level, Vector3<u32> const& destination_offset) override; private: - FloatVector4 const* texel_pointer(unsigned layer, unsigned level, int x, int y, int z) const + GPU::ImageDataLayout image_data_layout(u32 level, Vector3<i32> offset) const; + + FloatVector4 const* texel_pointer(u32 layer, u32 level, int x, int y, int z) const { return m_mipmap_buffers[layer * m_num_layers + level]->buffer_pointer(x, y, z); } - FloatVector4* texel_pointer(unsigned layer, unsigned level, int x, int y, int z) + FloatVector4* texel_pointer(u32 layer, u32 level, int x, int y, int z) { return m_mipmap_buffers[layer * m_num_layers + level]->buffer_pointer(x, y, z); } private: - unsigned m_num_levels { 0 }; - unsigned m_num_layers { 0 }; + u32 m_num_levels { 0 }; + u32 m_num_layers { 0 }; FixedArray<RefPtr<Typed3DBuffer<FloatVector4>>> m_mipmap_buffers; diff --git a/Userland/Libraries/LibSoftGPU/PixelConverter.cpp b/Userland/Libraries/LibSoftGPU/PixelConverter.cpp new file mode 100644 index 0000000000..5a18f28d61 --- /dev/null +++ b/Userland/Libraries/LibSoftGPU/PixelConverter.cpp @@ -0,0 +1,437 @@ +/* + * Copyright (c) 2022, Jelle Raaijmakers <jelle@gmta.nl> + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include <AK/Array.h> +#include <AK/Error.h> +#include <AK/FloatingPoint.h> +#include <LibSoftGPU/PixelConverter.h> + +namespace SoftGPU { + +template<typename T> +static constexpr T reverse_component_bytes_if_needed(T value, GPU::ImageDataLayout const& image_data_layout) requires(sizeof(T) == 2 || sizeof(T) == 4) +{ + if (image_data_layout.packing.component_bytes_order == GPU::ComponentBytesOrder::Normal) + return value; + VERIFY(image_data_layout.pixel_type.bits == GPU::PixelComponentBits::AllBits); + + auto* u8_ptr = reinterpret_cast<u8*>(&value); + if constexpr (sizeof(T) == 2) { + swap(u8_ptr[0], u8_ptr[1]); + } else if constexpr (sizeof(T) == 4) { + swap(u8_ptr[0], u8_ptr[3]); + swap(u8_ptr[1], u8_ptr[2]); + } + return value; +} + +static constexpr FloatVector4 decode_component_order_for_format(FloatVector4 const& components, GPU::PixelFormat format) +{ + switch (format) { + case GPU::PixelFormat::Alpha: + return { 0.f, 0.f, 0.f, components[0] }; + case GPU::PixelFormat::BGR: + return { components[2], components[1], components[0], 1.f }; + case GPU::PixelFormat::BGRA: + return { components[2], components[1], components[0], components[3] }; + case GPU::PixelFormat::Blue: + return { 0.f, 0.f, components[0], 1.f }; + case GPU::PixelFormat::ColorIndex: + case GPU::PixelFormat::DepthComponent: + case GPU::PixelFormat::StencilIndex: + return { components[0], 0.f, 0.f, 0.f }; + case GPU::PixelFormat::Green: + return { 0.f, components[0], 0.f, 1.f }; + case GPU::PixelFormat::Luminance: + return { components[0], components[0], components[0], 1.f }; + case GPU::PixelFormat::LuminanceAlpha: + return { components[0], components[0], components[0], components[1] }; + case GPU::PixelFormat::Red: + return { components[0], 0.f, 0.f, 1.f }; + case GPU::PixelFormat::RGB: + return { components[0], components[1], components[2], 1.f }; + case GPU::PixelFormat::RGBA: + return components; + } + VERIFY_NOT_REACHED(); +} + +static constexpr FloatVector4 encode_component_order_for_format(FloatVector4 const& components, GPU::PixelFormat format) +{ + switch (format) { + case GPU::PixelFormat::Alpha: + return { components[3], 0.f, 0.f, 0.f }; + case GPU::PixelFormat::BGR: + return { components[2], components[1], components[0], 0.f }; + case GPU::PixelFormat::BGRA: + return { components[2], components[1], components[0], components[3] }; + case GPU::PixelFormat::Blue: + return { components[2], 0.f, 0.f, 0.f }; + case GPU::PixelFormat::ColorIndex: + case GPU::PixelFormat::DepthComponent: + case GPU::PixelFormat::Luminance: + case GPU::PixelFormat::Red: + case GPU::PixelFormat::RGB: + case GPU::PixelFormat::RGBA: + case GPU::PixelFormat::StencilIndex: + return components; + case GPU::PixelFormat::Green: + return { components[1], 0.f, 0.f, 0.f }; + case GPU::PixelFormat::LuminanceAlpha: + return { components[0], components[3], 0.f, 0.f }; + } + VERIFY_NOT_REACHED(); +} + +template<typename S, typename O> +static int read_pixel_values(u8 const* input_data, Array<O, 4>& output_values, GPU::ImageDataLayout const& layout) +{ + auto const& pixel_type = layout.pixel_type; + auto const number_of_data_reads = GPU::number_of_components(pixel_type.format) / GPU::number_of_components(pixel_type.bits); + + for (int i = 0; i < number_of_data_reads; ++i) { + auto storage_value = reinterpret_cast<S const*>(input_data)[i]; + if (layout.pixel_type.bits == GPU::PixelComponentBits::AllBits) { + if constexpr (sizeof(S) == 2 || sizeof(S) == 4) + storage_value = reverse_component_bytes_if_needed(storage_value, layout); + } + O value = storage_value; + + // Special case: convert HalfFloat to regular float + if constexpr (IsSame<O, float>) { + if (pixel_type.data_type == GPU::PixelDataType::HalfFloat) + value = convert_to_native_float(FloatingPointBits<1, 5, 10>(storage_value)); + } + + output_values[i] = value; + } + return number_of_data_reads; +} + +template<typename T> +constexpr FloatVector4 extract_component_values(Span<T> data_values, GPU::PixelType const& pixel_type) +{ + // FIXME: implement fixed point conversion for ::StencilIndex + // FIXME: stencil components should account for GL_MAP_STENCIL + // FIXME: stencil components should get GL_INDEX_SHIFT and GL_INDEX_OFFSET applied + // FIXME: depth components should get GL_DEPTH_SCALE and GL_DEPTH_BIAS applied + // FIXME: color components should get GL_C_SCALE and GL_C_BIAS applied + + auto const number_of_values = data_values.size(); + auto const bits_number_of_components = number_of_components(pixel_type.bits); + VERIFY(bits_number_of_components == 1 || bits_number_of_components == number_of_components(pixel_type.format)); + + // Maps a signed value to -1.0f..1.0f + auto signed_to_float = [](T value) -> float { + auto constexpr number_of_bits = sizeof(T) * 8 - 1; + return max(static_cast<float>(value / static_cast<float>(1 << number_of_bits)), -1.f); + }; + + // Maps an unsigned value to 0.0f..1.0f + auto unsigned_to_float = [](T value, u8 const number_of_bits) -> float { + return static_cast<float>(value / static_cast<double>((1ull << number_of_bits) - 1)); + }; + + // Handle full data values (1 or more) + if (pixel_type.bits == GPU::PixelComponentBits::AllBits) { + FloatVector4 components; + for (size_t i = 0; i < number_of_values; ++i) { + if constexpr (IsSigned<T>) + components[i] = signed_to_float(data_values[i]); + else + components[i] = unsigned_to_float(data_values[i], sizeof(T) * 8); + } + return components; + } + + VERIFY(number_of_values == 1); + T const value = data_values[0]; + auto bitfields = pixel_component_bitfield_lengths(pixel_type.bits); + + // Map arbitrary bitfields to floats + u8 remaining_width = 0; + for (auto bitwidth : bitfields) + remaining_width += bitwidth; + + // "By default the components are laid out from msb (most-significant bit) to lsb (least-significant bit)" + FloatVector4 components; + for (auto i = 0; i < 4; ++i) { + auto bitwidth = bitfields[i]; + if (bitwidth == 0) + break; + remaining_width -= bitwidth; + components[i] = unsigned_to_float((value >> remaining_width) & ((1 << bitwidth) - 1), bitwidth); + } + return components; +} + +template<> +constexpr FloatVector4 extract_component_values(Span<float> data_values, GPU::PixelType const&) +{ + FloatVector4 components; + for (size_t i = 0; i < data_values.size(); ++i) + components[i] = data_values[i]; + return components; +} + +template<typename T> +static FloatVector4 pixel_values_to_components(Span<T> values, GPU::PixelType const& pixel_type) +{ + // Deconstruct read value(s) into separate components + auto components = extract_component_values(values, pixel_type); + if (pixel_type.components_order == GPU::ComponentsOrder::Reversed) + components = { components[3], components[2], components[1], components[0] }; + + // Reconstruct component values in order + auto component_values = decode_component_order_for_format(components, pixel_type.format); + component_values.clamp(0.f, 1.f); + return component_values; +} + +FloatVector4 PixelConverter::read_pixel(u8 const** input_data) +{ + auto read_components = [&]<typename S, typename O>() { + Array<O, 4> values; + auto number_of_values = read_pixel_values<S, O>(*input_data, values, m_input_specification); + *input_data += number_of_values * sizeof(O); + return pixel_values_to_components(values.span().trim(number_of_values), m_input_specification.pixel_type); + }; + switch (m_input_specification.pixel_type.data_type) { + case GPU::PixelDataType::Bitmap: + VERIFY_NOT_REACHED(); + case GPU::PixelDataType::Byte: + return read_components.template operator()<i8, i8>(); + case GPU::PixelDataType::Float: + return read_components.template operator()<float, float>(); + case GPU::PixelDataType::HalfFloat: + return read_components.template operator()<u16, float>(); + case GPU::PixelDataType::Int: + return read_components.template operator()<i32, i32>(); + case GPU::PixelDataType::Short: + return read_components.template operator()<i16, i16>(); + case GPU::PixelDataType::UnsignedByte: + return read_components.template operator()<u8, u8>(); + case GPU::PixelDataType::UnsignedInt: + return read_components.template operator()<u32, u32>(); + case GPU::PixelDataType::UnsignedShort: + return read_components.template operator()<u16, u16>(); + } + VERIFY_NOT_REACHED(); +} + +static constexpr void write_pixel_as_type(u8** output_data, float value, GPU::ImageDataLayout layout) +{ + auto write_value = [&output_data, &layout]<typename T>(T value) -> void { + if constexpr (sizeof(T) == 2 || sizeof(T) == 4) + value = reverse_component_bytes_if_needed(value, layout); + **reinterpret_cast<T**>(output_data) = value; + (*output_data) += sizeof(T); + }; + auto constexpr float_to_signed = []<typename T>(float value) -> T { + auto const signed_max = 1ull << (sizeof(T) * 8 - 1); + auto const unsigned_max = 2 * signed_max - 1; + return round_to<T>((static_cast<double>(value) + 1.) / 2. * unsigned_max - signed_max); + }; + auto constexpr float_to_unsigned = []<typename T>(float value) -> T { + auto const unsigned_max = (1ull << (sizeof(T) * 8)) - 1; + return round_to<T>(static_cast<double>(value) * unsigned_max); + }; + switch (layout.pixel_type.data_type) { + case GPU::PixelDataType::Bitmap: + VERIFY_NOT_REACHED(); + case GPU::PixelDataType::Byte: + write_value(float_to_signed.operator()<i8>(value)); + break; + case GPU::PixelDataType::Float: + write_value(value); + break; + case GPU::PixelDataType::HalfFloat: + write_value(static_cast<u16>(convert_from_native_float<FloatingPointBits<1, 5, 10>>(value).bits())); + break; + case GPU::PixelDataType::Int: + write_value(float_to_signed.operator()<i32>(value)); + break; + case GPU::PixelDataType::Short: + write_value(float_to_signed.operator()<i16>(value)); + break; + case GPU::PixelDataType::UnsignedByte: + write_value(float_to_unsigned.operator()<u8>(value)); + break; + case GPU::PixelDataType::UnsignedInt: + write_value(float_to_unsigned.operator()<u32>(value)); + break; + case GPU::PixelDataType::UnsignedShort: + write_value(float_to_unsigned.operator()<u16>(value)); + break; + } +} + +void constexpr write_pixel_as_bitfield(u8** output_data, FloatVector4 const& components, GPU::PixelType const& pixel_type) +{ + auto constexpr float_to_unsigned = [](float value, u8 bits) { + auto unsigned_max = (1ull << bits) - 1; + return round_to<u64>(value * unsigned_max); + }; + + // Construct value with concatenated bitfields - first component has most significant bits + auto bitfields = pixel_component_bitfield_lengths(pixel_type.bits); + u64 value = 0; + u8 bitsize = 0; + for (auto i = 0; i < 4; ++i) { + value <<= bitsize; + bitsize = bitfields[i]; + if (bitsize == 0) + break; + value |= float_to_unsigned(components[i], bitsize); + } + + // Write out the value in the requested data type + auto write_value = [&output_data]<typename T>(T value) -> void { + **reinterpret_cast<T**>(output_data) = value; + (*output_data) += sizeof(T); + }; + switch (pixel_type.data_type) { + case GPU::PixelDataType::UnsignedByte: + write_value.operator()<u8>(value); + break; + case GPU::PixelDataType::UnsignedInt: + write_value.operator()<u32>(value); + break; + case GPU::PixelDataType::UnsignedShort: + write_value.operator()<u16>(value); + break; + default: + VERIFY_NOT_REACHED(); + } +} + +void PixelConverter::write_pixel(u8** output_data, FloatVector4 const& components) +{ + // NOTE: `components` is already clamped to 0.f..1.f + + // Reorder float components to data order + auto const& pixel_type = m_output_specification.pixel_type; + auto output_components = encode_component_order_for_format(components, pixel_type.format); + if (pixel_type.components_order == GPU::ComponentsOrder::Reversed) + output_components = { output_components[3], output_components[2], output_components[1], output_components[0] }; + + // Write components as full data types + auto const number_of_components_in_pixel = number_of_components(pixel_type.format); + if (pixel_type.bits == GPU::PixelComponentBits::AllBits) { + for (u8 i = 0; i < number_of_components_in_pixel; ++i) + write_pixel_as_type(output_data, output_components[i], m_output_specification); + return; + } + + // Write components as a concatenated bitfield value + VERIFY(number_of_components_in_pixel == number_of_components(pixel_type.bits)); + write_pixel_as_bitfield(output_data, output_components, pixel_type); +} + +static constexpr GPU::ImageSelection restrain_selection_within_dimensions(GPU::ImageSelection selection, GPU::DimensionSpecification const& dimensions) +{ + if (selection.offset_x < 0) { + selection.width += selection.offset_x; + selection.offset_x = 0; + } + if (selection.offset_y < 0) { + selection.height += selection.offset_y; + selection.offset_y = 0; + } + if (selection.offset_z < 0) { + selection.depth += selection.offset_z; + selection.offset_z = 0; + } + + if (selection.offset_x + selection.width > dimensions.width) + selection.width = dimensions.width - selection.offset_x; + if (selection.offset_y + selection.height > dimensions.height) + selection.height = dimensions.height - selection.offset_y; + if (selection.offset_z + selection.depth > dimensions.depth) + selection.depth = dimensions.depth - selection.offset_z; + + return selection; +} + +ErrorOr<void> PixelConverter::convert(void const* input_data, void* output_data) +{ + // Verify pixel data specifications + auto validate_image_data_layout = [](GPU::ImageDataLayout const& specification) -> ErrorOr<void> { + if (specification.packing.row_stride > 0 + && specification.dimensions.width > specification.packing.row_stride) + return Error::from_string_view("Width exceeds the row stride"sv); + + if (specification.packing.depth_stride > 0 + && specification.dimensions.height > specification.packing.depth_stride) + return Error::from_string_view("Height exceeds the depth stride"sv); + + // NOTE: GL_BITMAP is removed from current OpenGL specs. Since it is largely unsupported and it + // requires extra logic (i.e. 8 vs. 1 pixel packing/unpacking), we also do not support it. + if (specification.pixel_type.data_type == GPU::PixelDataType::Bitmap) + return Error::from_string_view("Bitmap is unsupported"sv); + + return {}; + }; + TRY(validate_image_data_layout(m_input_specification)); + TRY(validate_image_data_layout(m_output_specification)); + + // Restrain input and output selection: + // - selection dimensions should be equal + // - selection offsets cannot be negative + // - selection bounds cannot exceed the image dimensions + auto const& input_dimensions = m_input_specification.dimensions; + auto const& output_dimensions = m_output_specification.dimensions; + auto input_selection = restrain_selection_within_dimensions(m_input_specification.selection, input_dimensions); + auto const& output_selection = restrain_selection_within_dimensions(m_output_specification.selection, output_dimensions); + + input_selection.width = min(input_selection.width, output_selection.width); + input_selection.height = min(input_selection.height, output_selection.height); + input_selection.depth = min(input_selection.depth, output_selection.depth); + + // Set up copy parameters + auto const& input_packing = m_input_specification.packing; + auto const input_pixels_per_row = input_packing.row_stride > 0 ? input_packing.row_stride : input_dimensions.width; + auto const input_pixel_size_in_bytes = pixel_size_in_bytes(m_input_specification.pixel_type); + auto const input_row_width_bytes = input_pixels_per_row * input_pixel_size_in_bytes; + auto const input_byte_alignment = input_packing.byte_alignment; + auto const input_row_stride = input_row_width_bytes + (input_byte_alignment - input_row_width_bytes % input_byte_alignment) % input_byte_alignment; + auto const input_rows_per_image = input_packing.depth_stride > 0 ? input_packing.depth_stride : input_dimensions.height; + auto const input_depth_stride = input_rows_per_image * input_row_stride; + + auto const& output_packing = m_output_specification.packing; + auto const output_pixels_per_row = output_packing.row_stride > 0 ? output_packing.row_stride : output_dimensions.width; + auto const output_pixel_size_in_bytes = pixel_size_in_bytes(m_output_specification.pixel_type); + auto const output_row_width_bytes = output_pixels_per_row * output_pixel_size_in_bytes; + auto const output_byte_alignment = output_packing.byte_alignment; + auto const output_row_stride = output_row_width_bytes + (output_byte_alignment - output_row_width_bytes % output_byte_alignment) % output_byte_alignment; + auto const output_rows_per_image = output_packing.depth_stride > 0 ? output_packing.depth_stride : output_dimensions.height; + auto const output_depth_stride = output_rows_per_image * output_row_stride; + + // Copy all pixels from input to output + auto input_bytes = reinterpret_cast<u8 const*>(input_data); + auto output_bytes = reinterpret_cast<u8*>(output_data); + auto output_z = output_selection.offset_z; + for (u32 input_z = input_selection.offset_z; input_z < input_selection.offset_z + input_selection.depth; ++input_z) { + auto output_y = output_selection.offset_y; + for (u32 input_y = input_selection.offset_y; input_y < input_selection.offset_y + input_selection.height; ++input_y) { + auto const* input_scanline = &input_bytes[input_z * input_depth_stride + + input_y * input_row_stride + + input_selection.offset_x * input_pixel_size_in_bytes]; + auto* output_scanline = &output_bytes[output_z * output_depth_stride + + output_y * output_row_stride + + output_selection.offset_x * output_pixel_size_in_bytes]; + for (u32 input_x = input_selection.offset_x; input_x < input_selection.offset_x + input_selection.width; ++input_x) { + auto pixel_components = read_pixel(&input_scanline); + write_pixel(&output_scanline, pixel_components); + } + ++output_y; + } + ++output_z; + } + return {}; +} + +} diff --git a/Userland/Libraries/LibSoftGPU/PixelConverter.h b/Userland/Libraries/LibSoftGPU/PixelConverter.h new file mode 100644 index 0000000000..5c51c47b4b --- /dev/null +++ b/Userland/Libraries/LibSoftGPU/PixelConverter.h @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2022, Jelle Raaijmakers <jelle@gmta.nl> + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include <AK/Error.h> +#include <LibGPU/ImageDataLayout.h> +#include <LibGfx/Vector4.h> + +namespace SoftGPU { + +class PixelConverter { +public: + PixelConverter(GPU::ImageDataLayout input_specification, GPU::ImageDataLayout output_specification) + : m_input_specification { input_specification } + , m_output_specification { output_specification } + { + } + + ErrorOr<void> convert(void const* input_data, void* output_data); + +private: + FloatVector4 read_pixel(u8 const**); + void write_pixel(u8**, FloatVector4 const&); + + GPU::ImageDataLayout m_input_specification; + GPU::ImageDataLayout m_output_specification; +}; + +} |