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 new file mode 100644 index 0000000000..888bded0d8 Binary files /dev/null and b/Tests/LibGL/reference-images/0006_test_rgb565_texture.qoi differ 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(depth_stride), + .row_stride = static_cast(row_stride), + .byte_alignment = static_cast(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 #include #include +#include #include #include +#include #include #include #include @@ -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); + 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()); - 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); + 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(width), + .height = static_cast(height), + .depth = 1, + }, + .selection = { + .width = static_cast(width), + .height = static_cast(height), + .depth = 1, + }, + }; - // 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); - - 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((0x7f * min(max(f, 0.0f), 1.0f) - 1) / 2); - }; - - auto float_to_i16 = [](float f) -> GLshort { - return static_cast((0x7fff * min(max(f, 0.0f), 1.0f) - 1) / 2); - }; - - auto float_to_i32 = [](float f) -> GLint { - return static_cast((0x7fffffff * min(max(f, 0.0f), 1.0f) - 1) / 2); - }; - - auto float_to_u8 = [](float f) -> GLubyte { - return static_cast(0xff * min(max(f, 0.0f), 1.0f)); - }; - - auto float_to_u16 = [](float f) -> GLushort { - return static_cast(0xffff * min(max(f, 0.0f), 1.0f)); - }; - - auto float_to_u32 = [](float f) -> GLuint { - return static_cast(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(pixels) + i * row_stride + j * component_size; - - switch (type) { - case GL_BYTE: - *reinterpret_cast(char_ptr) = float_to_i8(depth); - break; - case GL_SHORT: - *reinterpret_cast(char_ptr) = float_to_i16(depth); - break; - case GL_INT: - *reinterpret_cast(char_ptr) = float_to_i32(depth); - break; - case GL_UNSIGNED_BYTE: - *reinterpret_cast(char_ptr) = float_to_u8(depth); - break; - case GL_UNSIGNED_SHORT: - *reinterpret_cast(char_ptr) = float_to_u16(depth); - break; - case GL_UNSIGNED_INT: - *reinterpret_cast(char_ptr) = float_to_u32(depth); - break; - case GL_FLOAT: - *reinterpret_cast(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(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(red_ptr) = float_to_i8(red); - if (write_green) - *reinterpret_cast(green_ptr) = float_to_i8(green); - if (write_blue) - *reinterpret_cast(blue_ptr) = float_to_i8(blue); - if (write_alpha) - *reinterpret_cast(alpha_ptr) = float_to_i8(alpha); - break; - case GL_UNSIGNED_BYTE: - if (write_red) - *reinterpret_cast(red_ptr) = float_to_u8(red); - if (write_green) - *reinterpret_cast(green_ptr) = float_to_u8(green); - if (write_blue) - *reinterpret_cast(blue_ptr) = float_to_u8(blue); - if (write_alpha) - *reinterpret_cast(alpha_ptr) = float_to_u8(alpha); - break; - case GL_SHORT: - if (write_red) - *reinterpret_cast(red_ptr) = float_to_i16(red); - if (write_green) - *reinterpret_cast(green_ptr) = float_to_i16(green); - if (write_blue) - *reinterpret_cast(blue_ptr) = float_to_i16(blue); - if (write_alpha) - *reinterpret_cast(alpha_ptr) = float_to_i16(alpha); - break; - case GL_UNSIGNED_SHORT: - if (write_red) - *reinterpret_cast(red_ptr) = float_to_u16(red); - if (write_green) - *reinterpret_cast(green_ptr) = float_to_u16(green); - if (write_blue) - *reinterpret_cast(blue_ptr) = float_to_u16(blue); - if (write_alpha) - *reinterpret_cast(alpha_ptr) = float_to_u16(alpha); - break; - case GL_INT: - if (write_red) - *reinterpret_cast(red_ptr) = float_to_i32(red); - if (write_green) - *reinterpret_cast(green_ptr) = float_to_i32(green); - if (write_blue) - *reinterpret_cast(blue_ptr) = float_to_i32(blue); - if (write_alpha) - *reinterpret_cast(alpha_ptr) = float_to_i32(alpha); - break; - case GL_UNSIGNED_INT: - if (write_red) - *reinterpret_cast(red_ptr) = float_to_u32(red); - if (write_green) - *reinterpret_cast(green_ptr) = float_to_u32(green); - if (write_blue) - *reinterpret_cast(blue_ptr) = float_to_u32(blue); - if (write_alpha) - *reinterpret_cast(alpha_ptr) = float_to_u32(alpha); - break; - case GL_FLOAT: - if (write_red) - *reinterpret_cast(red_ptr) = min(max(red, 0.0f), 1.0f); - if (write_green) - *reinterpret_cast(green_ptr) = min(max(green, 0.0f), 1.0f); - if (write_blue) - *reinterpret_cast(blue_ptr) = min(max(blue, 0.0f), 1.0f); - if (write_alpha) - *reinterpret_cast(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); + 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()); - // 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; - } + 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(width), + .height = static_cast(height), + .depth = 1, + }, + .selection = { + .width = static_cast(width), + .height = static_cast(height), + .depth = 1, + }, + }; - // 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(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 depth_values; - depth_values.ensure_capacity(width * height); - - auto depth_data = static_cast(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(u16_value) / NumericLimits::max(); - depth_values.append(float_value); - } - } - - 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 driver, NonnullOwnPtr, Gfx::Bitmap&); @@ -235,6 +240,7 @@ private: } Optional get_context_parameter(GLenum pname); + GPU::PackingSpecification get_packing_specification(PackingType); template 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 + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include + +namespace GL { + +ErrorOr 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 + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include +#include +#include + +namespace GL { + +GPU::PixelType get_format_specification(GLenum format, GLenum type); +ErrorOr 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 * Copyright (c) 2021, Stephan Unverwerth + * Copyright (c) 2022, Jelle Raaijmakers * * 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 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 offset { - static_cast(xoffset), - static_cast(yoffset), - 0 - }; - - Vector3 size { - static_cast(width), - static_cast(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 * Copyright (c) 2021, Stephan Unverwerth + * Copyright (c) 2022, Jelle Raaijmakers * * SPDX-License-Identifier: BSD-2-Clause */ @@ -10,13 +11,11 @@ #include "Texture.h" #include -#include -#include #include #include #include -#include -#include +#include +#include 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 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 #include +#include +#include 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); + 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()); - // 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; - - // 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(width), + .height = static_cast(height), + .depth = 1, + }, + .selection = { + .width = static_cast(width), + .height = static_cast(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(width), + .height = static_cast(height), + .depth = 1, + }, + .selection = { + .width = static_cast(width), + .height = static_cast(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 + * Copyright (c) 2022, Jelle Raaijmakers * * SPDX-License-Identifier: BSD-2-Clause */ @@ -15,7 +16,7 @@ #include #include #include -#include +#include #include #include #include @@ -30,13 +31,14 @@ #include #include #include +#include #include 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 const&, int, int) = 0; + virtual void blit_from_color_buffer(void*, Vector2 offset, GPU::ImageDataLayout const&) = 0; + virtual void blit_from_depth_buffer(void*, Vector2 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 create_image(ImageFormat format, unsigned width, unsigned height, unsigned depth, unsigned levels, unsigned layers) = 0; + virtual NonnullRefPtr 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 const& offset, Vector3 const& size, void const* data, ImageDataLayout const& layout) = 0; - virtual void read_texels(unsigned layer, unsigned level, Vector3 const& offset, Vector3 const& size, void* data, ImageDataLayout const& layout) const = 0; - virtual void copy_texels(Image const& source, unsigned source_layer, unsigned source_level, Vector3 const& source_offset, Vector3 const& size, unsigned destination_layer, unsigned destination_level, Vector3 const& destination_offset) = 0; + virtual void write_texels(u32 layer, u32 level, Vector3 const& output_offset, void const* data, ImageDataLayout const&) = 0; + virtual void read_texels(u32 layer, u32 level, Vector3 const& input_offset, void* data, ImageDataLayout const&) const = 0; + virtual void copy_texels(Image const& source, u32 source_layer, u32 source_level, Vector3 const& source_offset, Vector3 const& size, u32 destination_layer, u32 destination_level, Vector3 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 + * Copyright (c) 2022, Jelle Raaijmakers * * 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 + * Copyright (c) 2022, Jelle Raaijmakers * Copyright (c) 2022, the SerenityOS developers. * * SPDX-License-Identifier: BSD-2-Clause @@ -7,37 +8,169 @@ #pragma once -#include +#include 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 ImageFormat::RGBA8888: - case ImageFormat::BGRA8888: + case PixelFormat::BGRA: + case PixelFormat::RGBA: return 4; - default: - VERIFY_NOT_REACHED(); } + 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 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; + } + VERIFY_NOT_REACHED(); +} + +static constexpr Array 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 - { - 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 { 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 #include #include +#include #include #include #include @@ -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(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 const& v) +ALWAYS_INLINE static u32x4 to_argb32(Vector4 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 size, Vector2 offset) { - 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 const blit_rect = get_rasterization_rect_of_size({ source.width(), source.height() }); - m_frame_buffer->color_buffer()->blit_from_bitmap(source, blit_rect); + 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(m_frame_buffer->rect().width()), + .height = static_cast(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_to_depth_buffer_at_raster_position(Vector const& depth_values, int width, int height) +GPU::ImageDataLayout Device::depth_buffer_data_layout(Vector2 size, Vector2 offset) +{ + return { + .pixel_type = { + .format = GPU::PixelFormat::DepthComponent, + .bits = GPU::PixelComponentBits::AllBits, + .data_type = GPU::PixelDataType::Float, + }, + .dimensions = { + .width = static_cast(m_frame_buffer->rect().width()), + .height = static_cast(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 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 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; - 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 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 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 const rasterization_rect = get_rasterization_rect_of_size({ input_selection.width, input_selection.height }); + auto output_layout = color_buffer_data_layout( + { static_cast(rasterization_rect.width()), static_cast(rasterization_rect.height()) }, + { rasterization_rect.x(), rasterization_rect.y() }); + + 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(void const* input_data, GPU::ImageDataLayout const& input_layout) +{ + if (!m_raster_position.valid) + return; + + 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(rasterization_rect.width()), static_cast(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) +NonnullRefPtr Device::create_image(GPU::PixelType const& pixel_type, u32 width, u32 height, u32 depth, u32 levels, u32 layers) { - // 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 Device::create_image(GPU::ImageFormat format, unsigned width, unsigned height, unsigned depth, unsigned levels, unsigned 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 offset, GPU::ImageDataLayout const&) override; + virtual void blit_from_depth_buffer(void*, Vector2 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 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 create_image(GPU::ImageFormat format, unsigned width, unsigned height, unsigned depth, unsigned levels, unsigned layers) override; + virtual NonnullRefPtr 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 size, Vector2 offset); + GPU::ImageDataLayout depth_buffer_data_layout(Vector2 size, Vector2 offset); + template 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 +#include 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(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(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(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(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(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(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(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(clamp(color.x(), 0.0f, 1.0f) * 255); - auto g = static_cast(clamp(color.y(), 0.0f, 1.0f) * 255); - auto b = static_cast(clamp(color.z(), 0.0f, 1.0f) * 255); - auto a = static_cast(clamp(color.w(), 0.0f, 1.0f) * 255); - - switch (format) { - case GPU::ImageFormat::RGB888: - reinterpret_cast(ptr)[0] = r; - reinterpret_cast(ptr)[1] = g; - reinterpret_cast(ptr)[2] = b; - return; - case GPU::ImageFormat::BGR888: - reinterpret_cast(ptr)[2] = b; - reinterpret_cast(ptr)[1] = g; - reinterpret_cast(ptr)[0] = r; - return; - case GPU::ImageFormat::RGBA8888: - *reinterpret_cast(ptr) = r | (g << 8) | (b << 16) | (a << 24); - return; - case GPU::ImageFormat::BGRA8888: - *reinterpret_cast(ptr) = b | (g << 8) | (r << 16) | (a << 24); - return; - case GPU::ImageFormat::RGB565: - *reinterpret_cast(ptr) = (r & 0x1f) | ((g & 0x3f) << 5) | ((b & 0x1f) << 11); - return; - case GPU::ImageFormat::L8: - *reinterpret_cast(ptr) = r; - return; - case GPU::ImageFormat::L8A8: - reinterpret_cast(ptr)[0] = r; - reinterpret_cast(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>>::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::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 const& offset, Vector3 const& size, void const* data, GPU::ImageDataLayout const& layout) +GPU::ImageDataLayout Image::image_data_layout(u32 level, Vector3 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 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(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 const& offset, Vector3 const& size, void* data, GPU::ImageDataLayout const& layout) const +void Image::read_texels(u32 layer, u32 level, Vector3 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(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 const& source_offset, Vector3 const& size, unsigned destination_layer, unsigned destination_level, Vector3 const& destination_offset) +void Image::copy_texels(GPU::Image const& source, u32 source_layer, u32 source_level, Vector3 const& source_offset, Vector3 const& size, u32 destination_layer, u32 destination_level, Vector3 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 const& offset, Vector3 const& size, void const* data, GPU::ImageDataLayout const& layout) override; - virtual void read_texels(unsigned layer, unsigned level, Vector3 const& offset, Vector3 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 const& source_offset, Vector3 const& size, unsigned destination_layer, unsigned destination_level, Vector3 const& destination_offset) override; + virtual void write_texels(u32 layer, u32 level, Vector3 const& output_offset, void const* data, GPU::ImageDataLayout const&) override; + virtual void read_texels(u32 layer, u32 level, Vector3 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 const& source_offset, Vector3 const& size, u32 destination_layer, u32 destination_level, Vector3 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 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>> 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 + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include + +namespace SoftGPU { + +template +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(&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 +static int read_pixel_values(u8 const* input_data, Array& 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(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) { + 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 +constexpr FloatVector4 extract_component_values(Span 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(value / static_cast(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(value / static_cast((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) + 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 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 +static FloatVector4 pixel_values_to_components(Span 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 = [&]() { + Array values; + auto number_of_values = read_pixel_values(*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()(); + case GPU::PixelDataType::Float: + return read_components.template operator()(); + case GPU::PixelDataType::HalfFloat: + return read_components.template operator()(); + case GPU::PixelDataType::Int: + return read_components.template operator()(); + case GPU::PixelDataType::Short: + return read_components.template operator()(); + case GPU::PixelDataType::UnsignedByte: + return read_components.template operator()(); + case GPU::PixelDataType::UnsignedInt: + return read_components.template operator()(); + case GPU::PixelDataType::UnsignedShort: + return read_components.template operator()(); + } + VERIFY_NOT_REACHED(); +} + +static constexpr void write_pixel_as_type(u8** output_data, float value, GPU::ImageDataLayout layout) +{ + auto write_value = [&output_data, &layout](T value) -> void { + if constexpr (sizeof(T) == 2 || sizeof(T) == 4) + value = reverse_component_bytes_if_needed(value, layout); + **reinterpret_cast(output_data) = value; + (*output_data) += sizeof(T); + }; + auto constexpr float_to_signed = [](float value) -> T { + auto const signed_max = 1ull << (sizeof(T) * 8 - 1); + auto const unsigned_max = 2 * signed_max - 1; + return round_to((static_cast(value) + 1.) / 2. * unsigned_max - signed_max); + }; + auto constexpr float_to_unsigned = [](float value) -> T { + auto const unsigned_max = (1ull << (sizeof(T) * 8)) - 1; + return round_to(static_cast(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()(value)); + break; + case GPU::PixelDataType::Float: + write_value(value); + break; + case GPU::PixelDataType::HalfFloat: + write_value(static_cast(convert_from_native_float>(value).bits())); + break; + case GPU::PixelDataType::Int: + write_value(float_to_signed.operator()(value)); + break; + case GPU::PixelDataType::Short: + write_value(float_to_signed.operator()(value)); + break; + case GPU::PixelDataType::UnsignedByte: + write_value(float_to_unsigned.operator()(value)); + break; + case GPU::PixelDataType::UnsignedInt: + write_value(float_to_unsigned.operator()(value)); + break; + case GPU::PixelDataType::UnsignedShort: + write_value(float_to_unsigned.operator()(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(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](T value) -> void { + **reinterpret_cast(output_data) = value; + (*output_data) += sizeof(T); + }; + switch (pixel_type.data_type) { + case GPU::PixelDataType::UnsignedByte: + write_value.operator()(value); + break; + case GPU::PixelDataType::UnsignedInt: + write_value.operator()(value); + break; + case GPU::PixelDataType::UnsignedShort: + write_value.operator()(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 PixelConverter::convert(void const* input_data, void* output_data) +{ + // Verify pixel data specifications + auto validate_image_data_layout = [](GPU::ImageDataLayout const& specification) -> ErrorOr { + 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(input_data); + auto output_bytes = reinterpret_cast(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 + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include + +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 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; +}; + +}