diff options
author | Jelle Raaijmakers <jelle@gmta.nl> | 2022-08-24 23:47:49 +0200 |
---|---|---|
committer | Andreas Kling <kling@serenityos.org> | 2022-08-27 12:28:05 +0200 |
commit | eb7c3d16fbfd805f9fbb3b819a661db10088fb56 (patch) | |
tree | 51e65bff9fead51d7c8f367d5e522d2f24deec31 /Userland/Libraries/LibSoftGPU | |
parent | d7cfdfe6335de83f25d205cd9863fc18e2854763 (diff) | |
download | serenity-eb7c3d16fbfd805f9fbb3b819a661db10088fb56.zip |
LibGL+LibGPU+LibSoftGPU: Implement flexible pixel format conversion
A GPU (driver) is now responsible for reading and writing pixels from
and to user data. The client (LibGL) is responsible for specifying how
the user data must be interpreted or written to.
This allows us to centralize all pixel format conversion in one class,
`LibSoftGPU::PixelConverter`. For both the input and output image, it
takes a specification containing the image dimensions, the pixel type
and the selection (basically a clipping rect), and converts the pixels
from the input image to the output image.
Effectively this means we now support almost all OpenGL 1.5 formats,
and all custom logic has disappeared from:
- `glDrawPixels`
- `glReadPixels`
- `glTexImage2D`
- `glTexSubImage2D`
The new logic is still unoptimized, but on my machine I experienced no
noticeable slowdown. :^)
Diffstat (limited to 'Userland/Libraries/LibSoftGPU')
-rw-r--r-- | Userland/Libraries/LibSoftGPU/Buffer/Typed2DBuffer.h | 14 | ||||
-rw-r--r-- | Userland/Libraries/LibSoftGPU/CMakeLists.txt | 1 | ||||
-rw-r--r-- | Userland/Libraries/LibSoftGPU/Device.cpp | 153 | ||||
-rw-r--r-- | Userland/Libraries/LibSoftGPU/Device.h | 13 | ||||
-rw-r--r-- | Userland/Libraries/LibSoftGPU/Image.cpp | 201 | ||||
-rw-r--r-- | Userland/Libraries/LibSoftGPU/Image.h | 32 | ||||
-rw-r--r-- | Userland/Libraries/LibSoftGPU/PixelConverter.cpp | 437 | ||||
-rw-r--r-- | Userland/Libraries/LibSoftGPU/PixelConverter.h | 33 |
8 files changed, 660 insertions, 224 deletions
diff --git a/Userland/Libraries/LibSoftGPU/Buffer/Typed2DBuffer.h b/Userland/Libraries/LibSoftGPU/Buffer/Typed2DBuffer.h index a802acfaa3..f8069a19cb 100644 --- a/Userland/Libraries/LibSoftGPU/Buffer/Typed2DBuffer.h +++ b/Userland/Libraries/LibSoftGPU/Buffer/Typed2DBuffer.h @@ -33,20 +33,6 @@ public: ALWAYS_INLINE T* scanline(int y) { return m_buffer->buffer_pointer(0, y, 0); } ALWAYS_INLINE T const* scanline(int y) const { return m_buffer->buffer_pointer(0, y, 0); } - void blit_from_bitmap(Gfx::Bitmap const& bitmap, Gfx::IntRect const& target) requires IsSame<T, u32> - { - VERIFY(bitmap.format() == Gfx::BitmapFormat::BGRA8888 || bitmap.format() == Gfx::BitmapFormat::BGRx8888); - int source_y = 0; - for (int y = target.top(); y <= target.bottom(); ++y) { - auto* buffer_scanline = scanline(y); - auto const* bitmap_scanline = bitmap.scanline(source_y++); - - int source_x = 0; - for (int x = target.left(); x <= target.right(); ++x) - buffer_scanline[x] = bitmap_scanline[source_x++]; - } - } - void blit_flipped_to_bitmap(Gfx::Bitmap& bitmap, Gfx::IntRect const& target) const requires IsSame<T, u32> { VERIFY(bitmap.format() == Gfx::BitmapFormat::BGRA8888 || bitmap.format() == Gfx::BitmapFormat::BGRx8888); diff --git a/Userland/Libraries/LibSoftGPU/CMakeLists.txt b/Userland/Libraries/LibSoftGPU/CMakeLists.txt index bc99282ec6..5a50d7bbe1 100644 --- a/Userland/Libraries/LibSoftGPU/CMakeLists.txt +++ b/Userland/Libraries/LibSoftGPU/CMakeLists.txt @@ -2,6 +2,7 @@ set(SOURCES Clipper.cpp Device.cpp Image.cpp + PixelConverter.cpp Sampler.cpp ) diff --git a/Userland/Libraries/LibSoftGPU/Device.cpp b/Userland/Libraries/LibSoftGPU/Device.cpp index 13d1892e54..5235956022 100644 --- a/Userland/Libraries/LibSoftGPU/Device.cpp +++ b/Userland/Libraries/LibSoftGPU/Device.cpp @@ -18,6 +18,7 @@ #include <LibGfx/Vector3.h> #include <LibSoftGPU/Config.h> #include <LibSoftGPU/Device.h> +#include <LibSoftGPU/PixelConverter.h> #include <LibSoftGPU/PixelQuad.h> #include <LibSoftGPU/SIMD.h> #include <math.h> @@ -66,7 +67,7 @@ constexpr static auto interpolate(T const& v0, T const& v1, T const& v2, Vector3 return v0 * barycentric_coords.x() + v1 * barycentric_coords.y() + v2 * barycentric_coords.z(); } -static GPU::ColorType to_bgra32(FloatVector4 const& color) +static GPU::ColorType to_argb32(FloatVector4 const& color) { auto clamped = color.clamped(0.0f, 1.0f); auto r = static_cast<u8>(clamped.x() * 255); @@ -76,9 +77,9 @@ static GPU::ColorType to_bgra32(FloatVector4 const& color) return a << 24 | r << 16 | g << 8 | b; } -ALWAYS_INLINE static u32x4 to_bgra32(Vector4<f32x4> const& v) +ALWAYS_INLINE static u32x4 to_argb32(Vector4<f32x4> const& color) { - auto clamped = v.clamped(expand4(0.0f), expand4(1.0f)); + auto clamped = color.clamped(expand4(0.0f), expand4(1.0f)); auto r = to_u32x4(clamped.x() * 255); auto g = to_u32x4(clamped.y() * 255); auto b = to_u32x4(clamped.z() * 255); @@ -481,9 +482,9 @@ ALWAYS_INLINE void Device::rasterize(Gfx::IntRect& render_bounds, CB1 set_covera } if (m_options.color_mask == 0xffffffff) - store4_masked(to_bgra32(quad.out_color), color_ptrs[0], color_ptrs[1], color_ptrs[2], color_ptrs[3], quad.mask); + store4_masked(to_argb32(quad.out_color), color_ptrs[0], color_ptrs[1], color_ptrs[2], color_ptrs[3], quad.mask); else - store4_masked((to_bgra32(quad.out_color) & m_options.color_mask) | (dst_u32 & ~m_options.color_mask), color_ptrs[0], color_ptrs[1], color_ptrs[2], color_ptrs[3], quad.mask); + store4_masked((to_argb32(quad.out_color) & m_options.color_mask) | (dst_u32 & ~m_options.color_mask), color_ptrs[0], color_ptrs[1], color_ptrs[2], color_ptrs[3], quad.mask); } } } @@ -1315,7 +1316,7 @@ void Device::resize(Gfx::IntSize const& size) void Device::clear_color(FloatVector4 const& color) { - auto const fill_color = to_bgra32(color); + auto const fill_color = to_argb32(color); auto clear_rect = m_frame_buffer->rect(); if (m_options.scissor_enabled) @@ -1342,35 +1343,116 @@ void Device::clear_stencil(GPU::StencilType value) m_frame_buffer->stencil_buffer()->fill(value, clear_rect); } -void Device::blit_to_color_buffer_at_raster_position(Gfx::Bitmap const& source) +GPU::ImageDataLayout Device::color_buffer_data_layout(Vector2<u32> size, Vector2<i32> offset) +{ + return { + .pixel_type = { + .format = GPU::PixelFormat::BGRA, + .bits = GPU::PixelComponentBits::B8_8_8_8, + .data_type = GPU::PixelDataType::UnsignedInt, + .components_order = GPU::ComponentsOrder::Reversed, + }, + .dimensions = { + .width = static_cast<u32>(m_frame_buffer->rect().width()), + .height = static_cast<u32>(m_frame_buffer->rect().height()), + .depth = 1, + }, + .selection = { + .offset_x = offset.x(), + .offset_y = offset.y(), + .offset_z = 0, + .width = size.x(), + .height = size.y(), + .depth = 1, + }, + }; +} + +GPU::ImageDataLayout Device::depth_buffer_data_layout(Vector2<u32> size, Vector2<i32> offset) +{ + return { + .pixel_type = { + .format = GPU::PixelFormat::DepthComponent, + .bits = GPU::PixelComponentBits::AllBits, + .data_type = GPU::PixelDataType::Float, + }, + .dimensions = { + .width = static_cast<u32>(m_frame_buffer->rect().width()), + .height = static_cast<u32>(m_frame_buffer->rect().height()), + .depth = 1, + }, + .selection = { + .offset_x = offset.x(), + .offset_y = offset.y(), + .offset_z = 0, + .width = size.x(), + .height = size.y(), + .depth = 1, + }, + }; +} + +void Device::blit_from_color_buffer(void* output_data, Vector2<i32> input_offset, GPU::ImageDataLayout const& output_layout) +{ + auto const& output_selection = output_layout.selection; + auto input_layout = color_buffer_data_layout({ output_selection.width, output_selection.height }, input_offset); + + PixelConverter converter { input_layout, output_layout }; + auto const* input_data = m_frame_buffer->color_buffer()->scanline(0); + auto conversion_result = converter.convert(input_data, output_data); + if (conversion_result.is_error()) + dbgln("Pixel conversion failed: {}", conversion_result.error().string_literal()); +} + +void Device::blit_from_depth_buffer(void* output_data, Vector2<i32> input_offset, GPU::ImageDataLayout const& output_layout) +{ + auto const& output_selection = output_layout.selection; + auto input_layout = depth_buffer_data_layout({ output_selection.width, output_selection.height }, input_offset); + + PixelConverter converter { input_layout, output_layout }; + auto const* input_data = m_frame_buffer->depth_buffer()->scanline(0); + auto conversion_result = converter.convert(input_data, output_data); + if (conversion_result.is_error()) + dbgln("Pixel conversion failed: {}", conversion_result.error().string_literal()); +} + +void Device::blit_to_color_buffer_at_raster_position(void const* input_data, GPU::ImageDataLayout const& input_layout) { if (!m_raster_position.valid) return; - INCREASE_STATISTICS_COUNTER(g_num_pixels, source.width() * source.height()); - INCREASE_STATISTICS_COUNTER(g_num_pixels_shaded, source.width() * source.height()); + auto input_selection = input_layout.selection; + INCREASE_STATISTICS_COUNTER(g_num_pixels, input_selection.width * input_selection.height); + INCREASE_STATISTICS_COUNTER(g_num_pixels_shaded, input_selection.width * input_selection.height); + + auto const rasterization_rect = get_rasterization_rect_of_size({ input_selection.width, input_selection.height }); + auto output_layout = color_buffer_data_layout( + { static_cast<u32>(rasterization_rect.width()), static_cast<u32>(rasterization_rect.height()) }, + { rasterization_rect.x(), rasterization_rect.y() }); - auto const blit_rect = get_rasterization_rect_of_size({ source.width(), source.height() }); - m_frame_buffer->color_buffer()->blit_from_bitmap(source, blit_rect); + PixelConverter converter { input_layout, output_layout }; + auto* output_data = m_frame_buffer->color_buffer()->scanline(0); + auto conversion_result = converter.convert(input_data, output_data); + if (conversion_result.is_error()) + dbgln("Pixel conversion failed: {}", conversion_result.error().string_literal()); } -void Device::blit_to_depth_buffer_at_raster_position(Vector<GPU::DepthType> const& depth_values, int width, int height) +void Device::blit_to_depth_buffer_at_raster_position(void const* input_data, GPU::ImageDataLayout const& input_layout) { if (!m_raster_position.valid) return; - auto const raster_rect = get_rasterization_rect_of_size({ width, height }); - auto const y1 = raster_rect.y(); - auto const y2 = y1 + height; - auto const x1 = raster_rect.x(); - auto const x2 = x1 + width; - - auto index = 0; - for (auto y = y1; y < y2; ++y) { - auto depth_line = m_frame_buffer->depth_buffer()->scanline(y); - for (auto x = x1; x < x2; ++x) - depth_line[x] = depth_values[index++]; - } + auto input_selection = input_layout.selection; + auto const rasterization_rect = get_rasterization_rect_of_size({ input_selection.width, input_selection.height }); + auto output_layout = depth_buffer_data_layout( + { static_cast<u32>(rasterization_rect.width()), static_cast<u32>(rasterization_rect.height()) }, + { rasterization_rect.x(), rasterization_rect.y() }); + + PixelConverter converter { input_layout, output_layout }; + auto* output_data = m_frame_buffer->depth_buffer()->scanline(0); + auto conversion_result = converter.convert(input_data, output_data); + if (conversion_result.is_error()) + dbgln("Pixel conversion failed: {}", conversion_result.error().string_literal()); } void Device::blit_color_buffer_to(Gfx::Bitmap& target) @@ -1451,25 +1533,12 @@ void Device::set_light_model_params(GPU::LightModelParameters const& lighting_mo m_lighting_model = lighting_model; } -GPU::ColorType Device::get_color_buffer_pixel(int x, int y) -{ - // FIXME: Reading individual pixels is very slow, rewrite this to transfer whole blocks - if (!m_frame_buffer->rect().contains(x, y)) - return 0; - return m_frame_buffer->color_buffer()->scanline(y)[x]; -} - -GPU::DepthType Device::get_depthbuffer_value(int x, int y) -{ - // FIXME: Reading individual pixels is very slow, rewrite this to transfer whole blocks - if (!m_frame_buffer->rect().contains(x, y)) - return 1.0f; - return m_frame_buffer->depth_buffer()->scanline(y)[x]; -} - -NonnullRefPtr<GPU::Image> Device::create_image(GPU::ImageFormat format, unsigned width, unsigned height, unsigned depth, unsigned levels, unsigned layers) +NonnullRefPtr<GPU::Image> Device::create_image(GPU::PixelType const& pixel_type, u32 width, u32 height, u32 depth, u32 levels, u32 layers) { - VERIFY(format == GPU::ImageFormat::BGRA8888); + VERIFY(pixel_type.format == GPU::PixelFormat::RGBA + && pixel_type.bits == GPU::PixelComponentBits::AllBits + && pixel_type.data_type == GPU::PixelDataType::Float + && pixel_type.components_order == GPU::ComponentsOrder::Normal); VERIFY(width > 0); VERIFY(height > 0); VERIFY(depth > 0); diff --git a/Userland/Libraries/LibSoftGPU/Device.h b/Userland/Libraries/LibSoftGPU/Device.h index fb5ef3c4ea..e10b4c9f32 100644 --- a/Userland/Libraries/LibSoftGPU/Device.h +++ b/Userland/Libraries/LibSoftGPU/Device.h @@ -52,17 +52,17 @@ public: virtual void clear_color(FloatVector4 const&) override; virtual void clear_depth(GPU::DepthType) override; virtual void clear_stencil(GPU::StencilType) override; + virtual void blit_from_color_buffer(void*, Vector2<i32> offset, GPU::ImageDataLayout const&) override; + virtual void blit_from_depth_buffer(void*, Vector2<i32> offset, GPU::ImageDataLayout const&) override; virtual void blit_color_buffer_to(Gfx::Bitmap& target) override; - virtual void blit_to_color_buffer_at_raster_position(Gfx::Bitmap const&) override; - virtual void blit_to_depth_buffer_at_raster_position(Vector<GPU::DepthType> const&, int, int) override; + virtual void blit_to_color_buffer_at_raster_position(void const*, GPU::ImageDataLayout const&) override; + virtual void blit_to_depth_buffer_at_raster_position(void const*, GPU::ImageDataLayout const&) override; virtual void set_options(GPU::RasterizerOptions const&) override; virtual void set_light_model_params(GPU::LightModelParameters const&) override; virtual GPU::RasterizerOptions options() const override { return m_options; } virtual GPU::LightModelParameters light_model() const override { return m_lighting_model; } - virtual GPU::ColorType get_color_buffer_pixel(int x, int y) override; - virtual GPU::DepthType get_depthbuffer_value(int x, int y) override; - virtual NonnullRefPtr<GPU::Image> create_image(GPU::ImageFormat format, unsigned width, unsigned height, unsigned depth, unsigned levels, unsigned layers) override; + virtual NonnullRefPtr<GPU::Image> create_image(GPU::PixelType const&, u32 width, u32 height, u32 depth, u32 levels, u32 layers) override; virtual void set_sampler_config(unsigned, GPU::SamplerConfig const&) override; virtual void set_light_state(unsigned, GPU::Light const&) override; @@ -79,6 +79,9 @@ private: void draw_statistics_overlay(Gfx::Bitmap&); Gfx::IntRect get_rasterization_rect_of_size(Gfx::IntSize size) const; + GPU::ImageDataLayout color_buffer_data_layout(Vector2<u32> size, Vector2<i32> offset); + GPU::ImageDataLayout depth_buffer_data_layout(Vector2<u32> size, Vector2<i32> offset); + template<typename CB1, typename CB2, typename CB3> void rasterize(Gfx::IntRect& render_bounds, CB1 set_coverage_mask, CB2 set_quad_depth, CB3 set_quad_attributes); diff --git a/Userland/Libraries/LibSoftGPU/Image.cpp b/Userland/Libraries/LibSoftGPU/Image.cpp index 1a1189a051..8128e35563 100644 --- a/Userland/Libraries/LibSoftGPU/Image.cpp +++ b/Userland/Libraries/LibSoftGPU/Image.cpp @@ -6,123 +6,11 @@ */ #include <LibSoftGPU/Image.h> +#include <LibSoftGPU/PixelConverter.h> namespace SoftGPU { -static constexpr FloatVector4 unpack_color(void const* ptr, GPU::ImageFormat format) -{ - constexpr auto one_over_255 = 1.0f / 255; - switch (format) { - case GPU::ImageFormat::RGB888: { - auto rgb = reinterpret_cast<u8 const*>(ptr); - return { - rgb[0] * one_over_255, - rgb[1] * one_over_255, - rgb[2] * one_over_255, - 1.0f, - }; - } - case GPU::ImageFormat::BGR888: { - auto bgr = reinterpret_cast<u8 const*>(ptr); - return { - bgr[2] * one_over_255, - bgr[1] * one_over_255, - bgr[0] * one_over_255, - 1.0f, - }; - } - case GPU::ImageFormat::RGBA8888: { - auto rgba = *reinterpret_cast<u32 const*>(ptr); - return { - (rgba & 0xff) * one_over_255, - ((rgba >> 8) & 0xff) * one_over_255, - ((rgba >> 16) & 0xff) * one_over_255, - ((rgba >> 24) & 0xff) * one_over_255, - }; - } - case GPU::ImageFormat::BGRA8888: { - auto bgra = *reinterpret_cast<u32 const*>(ptr); - return { - ((bgra >> 16) & 0xff) * one_over_255, - ((bgra >> 8) & 0xff) * one_over_255, - (bgra & 0xff) * one_over_255, - ((bgra >> 24) & 0xff) * one_over_255, - }; - } - case GPU::ImageFormat::RGB565: { - auto rgb = *reinterpret_cast<u16 const*>(ptr); - return { - ((rgb >> 11) & 0x1f) / 31.f, - ((rgb >> 5) & 0x3f) / 63.f, - (rgb & 0x1f) / 31.f, - 1.0f - }; - } - case GPU::ImageFormat::L8: { - auto luminance = *reinterpret_cast<u8 const*>(ptr); - auto clamped_luminance = luminance * one_over_255; - return { - clamped_luminance, - clamped_luminance, - clamped_luminance, - 1.0f, - }; - } - case GPU::ImageFormat::L8A8: { - auto luminance_and_alpha = reinterpret_cast<u8 const*>(ptr); - auto clamped_luminance = luminance_and_alpha[0] * one_over_255; - return { - clamped_luminance, - clamped_luminance, - clamped_luminance, - luminance_and_alpha[1] * one_over_255, - }; - } - default: - VERIFY_NOT_REACHED(); - } -} - -static constexpr void pack_color(FloatVector4 const& color, void* ptr, GPU::ImageFormat format) -{ - auto r = static_cast<u8>(clamp(color.x(), 0.0f, 1.0f) * 255); - auto g = static_cast<u8>(clamp(color.y(), 0.0f, 1.0f) * 255); - auto b = static_cast<u8>(clamp(color.z(), 0.0f, 1.0f) * 255); - auto a = static_cast<u8>(clamp(color.w(), 0.0f, 1.0f) * 255); - - switch (format) { - case GPU::ImageFormat::RGB888: - reinterpret_cast<u8*>(ptr)[0] = r; - reinterpret_cast<u8*>(ptr)[1] = g; - reinterpret_cast<u8*>(ptr)[2] = b; - return; - case GPU::ImageFormat::BGR888: - reinterpret_cast<u8*>(ptr)[2] = b; - reinterpret_cast<u8*>(ptr)[1] = g; - reinterpret_cast<u8*>(ptr)[0] = r; - return; - case GPU::ImageFormat::RGBA8888: - *reinterpret_cast<u32*>(ptr) = r | (g << 8) | (b << 16) | (a << 24); - return; - case GPU::ImageFormat::BGRA8888: - *reinterpret_cast<u32*>(ptr) = b | (g << 8) | (r << 16) | (a << 24); - return; - case GPU::ImageFormat::RGB565: - *reinterpret_cast<u16*>(ptr) = (r & 0x1f) | ((g & 0x3f) << 5) | ((b & 0x1f) << 11); - return; - case GPU::ImageFormat::L8: - *reinterpret_cast<u8*>(ptr) = r; - return; - case GPU::ImageFormat::L8A8: - reinterpret_cast<u8*>(ptr)[0] = r; - reinterpret_cast<u8*>(ptr)[1] = a; - return; - default: - VERIFY_NOT_REACHED(); - } -} - -Image::Image(void const* ownership_token, unsigned width, unsigned height, unsigned depth, unsigned max_levels, unsigned layers) +Image::Image(void const* ownership_token, u32 width, u32 height, u32 depth, u32 max_levels, u32 layers) : GPU::Image(ownership_token) , m_num_layers(layers) , m_mipmap_buffers(FixedArray<RefPtr<Typed3DBuffer<FloatVector4>>>::must_create_but_fixme_should_propagate_errors(layers * max_levels)) @@ -137,9 +25,9 @@ Image::Image(void const* ownership_token, unsigned width, unsigned height, unsig m_height_is_power_of_two = is_power_of_two(height); m_depth_is_power_of_two = is_power_of_two(depth); - unsigned level; + u32 level; for (level = 0; level < max_levels; ++level) { - for (unsigned layer = 0; layer < layers; ++layer) + for (u32 layer = 0; layer < layers; ++layer) m_mipmap_buffers[layer * layers + level] = MUST(Typed3DBuffer<FloatVector4>::try_create(width, height, depth)); if (width <= 1 && height <= 1 && depth <= 1) @@ -153,45 +41,62 @@ Image::Image(void const* ownership_token, unsigned width, unsigned height, unsig m_num_levels = level + 1; } -void Image::write_texels(unsigned layer, unsigned level, Vector3<unsigned> const& offset, Vector3<unsigned> const& size, void const* data, GPU::ImageDataLayout const& layout) +GPU::ImageDataLayout Image::image_data_layout(u32 level, Vector3<i32> offset) const +{ + auto const width = level_width(level); + auto const height = level_height(level); + auto const depth = level_depth(level); + + // FIXME: we are directly writing to FloatVector4s. We should probably find a better way to do this + return { + .pixel_type = { + .format = GPU::PixelFormat::RGBA, + .bits = GPU::PixelComponentBits::AllBits, + .data_type = GPU::PixelDataType::Float, + }, + .dimensions = { + .width = width, + .height = height, + .depth = depth, + }, + .selection = { + .offset_x = offset.x(), + .offset_y = offset.y(), + .offset_z = offset.z(), + .width = width - offset.x(), + .height = height - offset.y(), + .depth = depth - offset.z(), + }, + }; +} + +void Image::write_texels(u32 layer, u32 level, Vector3<i32> const& output_offset, void const* data, GPU::ImageDataLayout const& input_layout) { VERIFY(layer < num_layers()); VERIFY(level < num_levels()); - VERIFY(offset.x() + size.x() <= level_width(level)); - VERIFY(offset.y() + size.y() <= level_height(level)); - VERIFY(offset.z() + size.z() <= level_depth(level)); - - for (unsigned z = 0; z < size.z(); ++z) { - for (unsigned y = 0; y < size.y(); ++y) { - for (unsigned x = 0; x < size.x(); ++x) { - auto ptr = reinterpret_cast<u8 const*>(data) + layout.depth_stride * z + layout.row_stride * y + layout.column_stride * x; - auto color = unpack_color(ptr, layout.format); - set_texel(layer, level, offset.x() + x, offset.y() + y, offset.z() + z, color); - } - } - } + + auto output_layout = image_data_layout(level, output_offset); + + PixelConverter converter { input_layout, output_layout }; + auto conversion_result = converter.convert(data, texel_pointer(layer, level, 0, 0, 0)); + if (conversion_result.is_error()) + dbgln("Pixel conversion failed: {}", conversion_result.error().string_literal()); } -void Image::read_texels(unsigned layer, unsigned level, Vector3<unsigned> const& offset, Vector3<unsigned> const& size, void* data, GPU::ImageDataLayout const& layout) const +void Image::read_texels(u32 layer, u32 level, Vector3<i32> const& input_offset, void* data, GPU::ImageDataLayout const& output_layout) const { VERIFY(layer < num_layers()); VERIFY(level < num_levels()); - VERIFY(offset.x() + size.x() <= level_width(level)); - VERIFY(offset.y() + size.y() <= level_height(level)); - VERIFY(offset.z() + size.z() <= level_depth(level)); - - for (unsigned z = 0; z < size.z(); ++z) { - for (unsigned y = 0; y < size.y(); ++y) { - for (unsigned x = 0; x < size.x(); ++x) { - auto color = texel(layer, level, offset.x() + x, offset.y() + y, offset.z() + z); - auto ptr = reinterpret_cast<u8*>(data) + layout.depth_stride * z + layout.row_stride * y + layout.column_stride * x; - pack_color(color, ptr, layout.format); - } - } - } + + auto input_layout = image_data_layout(level, input_offset); + + PixelConverter converter { input_layout, output_layout }; + auto conversion_result = converter.convert(texel_pointer(layer, level, 0, 0, 0), data); + if (conversion_result.is_error()) + dbgln("Pixel conversion failed: {}", conversion_result.error().string_literal()); } -void Image::copy_texels(GPU::Image const& source, unsigned source_layer, unsigned source_level, Vector3<unsigned> const& source_offset, Vector3<unsigned> const& size, unsigned destination_layer, unsigned destination_level, Vector3<unsigned> const& destination_offset) +void Image::copy_texels(GPU::Image const& source, u32 source_layer, u32 source_level, Vector3<u32> const& source_offset, Vector3<u32> const& size, u32 destination_layer, u32 destination_level, Vector3<u32> const& destination_offset) { VERIFY(source.has_same_ownership_token(*this)); @@ -208,9 +113,9 @@ void Image::copy_texels(GPU::Image const& source, unsigned source_layer, unsigne VERIFY(destination_offset.y() + size.y() <= level_height(destination_level)); VERIFY(destination_offset.z() + size.z() <= level_depth(destination_level)); - for (unsigned z = 0; z < size.z(); ++z) { - for (unsigned y = 0; y < size.y(); ++y) { - for (unsigned x = 0; x < size.x(); ++x) { + for (u32 z = 0; z < size.z(); ++z) { + for (u32 y = 0; y < size.y(); ++y) { + for (u32 x = 0; x < size.x(); ++x) { auto color = src_image.texel(source_layer, source_level, source_offset.x() + x, source_offset.y() + y, source_offset.z() + z); set_texel(destination_layer, destination_level, destination_offset.x() + x, destination_offset.y() + y, destination_offset.z() + z, color); } diff --git a/Userland/Libraries/LibSoftGPU/Image.h b/Userland/Libraries/LibSoftGPU/Image.h index 841e9dc71c..23accd8542 100644 --- a/Userland/Libraries/LibSoftGPU/Image.h +++ b/Userland/Libraries/LibSoftGPU/Image.h @@ -19,45 +19,47 @@ namespace SoftGPU { class Image final : public GPU::Image { public: - Image(void const* ownership_token, unsigned width, unsigned height, unsigned depth, unsigned max_levels, unsigned layers); + Image(void const* ownership_token, u32 width, u32 height, u32 depth, u32 max_levels, u32 layers); - unsigned level_width(unsigned level) const { return m_mipmap_buffers[level]->width(); } - unsigned level_height(unsigned level) const { return m_mipmap_buffers[level]->height(); } - unsigned level_depth(unsigned level) const { return m_mipmap_buffers[level]->depth(); } - unsigned num_levels() const { return m_num_levels; } - unsigned num_layers() const { return m_num_layers; } + u32 level_width(u32 level) const { return m_mipmap_buffers[level]->width(); } + u32 level_height(u32 level) const { return m_mipmap_buffers[level]->height(); } + u32 level_depth(u32 level) const { return m_mipmap_buffers[level]->depth(); } + u32 num_levels() const { return m_num_levels; } + u32 num_layers() const { return m_num_layers; } bool width_is_power_of_two() const { return m_width_is_power_of_two; } bool height_is_power_of_two() const { return m_height_is_power_of_two; } bool depth_is_power_of_two() const { return m_depth_is_power_of_two; } - FloatVector4 texel(unsigned layer, unsigned level, int x, int y, int z) const + FloatVector4 texel(u32 layer, u32 level, int x, int y, int z) const { return *texel_pointer(layer, level, x, y, z); } - void set_texel(unsigned layer, unsigned level, int x, int y, int z, FloatVector4 const& color) + void set_texel(u32 layer, u32 level, int x, int y, int z, FloatVector4 const& color) { *texel_pointer(layer, level, x, y, z) = color; } - virtual void write_texels(unsigned layer, unsigned level, Vector3<unsigned> const& offset, Vector3<unsigned> const& size, void const* data, GPU::ImageDataLayout const& layout) override; - virtual void read_texels(unsigned layer, unsigned level, Vector3<unsigned> const& offset, Vector3<unsigned> const& size, void* data, GPU::ImageDataLayout const& layout) const override; - virtual void copy_texels(GPU::Image const& source, unsigned source_layer, unsigned source_level, Vector3<unsigned> const& source_offset, Vector3<unsigned> const& size, unsigned destination_layer, unsigned destination_level, Vector3<unsigned> const& destination_offset) override; + virtual void write_texels(u32 layer, u32 level, Vector3<i32> const& output_offset, void const* data, GPU::ImageDataLayout const&) override; + virtual void read_texels(u32 layer, u32 level, Vector3<i32> const& input_offset, void* data, GPU::ImageDataLayout const&) const override; + virtual void copy_texels(GPU::Image const& source, u32 source_layer, u32 source_level, Vector3<u32> const& source_offset, Vector3<u32> const& size, u32 destination_layer, u32 destination_level, Vector3<u32> const& destination_offset) override; private: - FloatVector4 const* texel_pointer(unsigned layer, unsigned level, int x, int y, int z) const + GPU::ImageDataLayout image_data_layout(u32 level, Vector3<i32> offset) const; + + FloatVector4 const* texel_pointer(u32 layer, u32 level, int x, int y, int z) const { return m_mipmap_buffers[layer * m_num_layers + level]->buffer_pointer(x, y, z); } - FloatVector4* texel_pointer(unsigned layer, unsigned level, int x, int y, int z) + FloatVector4* texel_pointer(u32 layer, u32 level, int x, int y, int z) { return m_mipmap_buffers[layer * m_num_layers + level]->buffer_pointer(x, y, z); } private: - unsigned m_num_levels { 0 }; - unsigned m_num_layers { 0 }; + u32 m_num_levels { 0 }; + u32 m_num_layers { 0 }; FixedArray<RefPtr<Typed3DBuffer<FloatVector4>>> m_mipmap_buffers; diff --git a/Userland/Libraries/LibSoftGPU/PixelConverter.cpp b/Userland/Libraries/LibSoftGPU/PixelConverter.cpp new file mode 100644 index 0000000000..5a18f28d61 --- /dev/null +++ b/Userland/Libraries/LibSoftGPU/PixelConverter.cpp @@ -0,0 +1,437 @@ +/* + * Copyright (c) 2022, Jelle Raaijmakers <jelle@gmta.nl> + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include <AK/Array.h> +#include <AK/Error.h> +#include <AK/FloatingPoint.h> +#include <LibSoftGPU/PixelConverter.h> + +namespace SoftGPU { + +template<typename T> +static constexpr T reverse_component_bytes_if_needed(T value, GPU::ImageDataLayout const& image_data_layout) requires(sizeof(T) == 2 || sizeof(T) == 4) +{ + if (image_data_layout.packing.component_bytes_order == GPU::ComponentBytesOrder::Normal) + return value; + VERIFY(image_data_layout.pixel_type.bits == GPU::PixelComponentBits::AllBits); + + auto* u8_ptr = reinterpret_cast<u8*>(&value); + if constexpr (sizeof(T) == 2) { + swap(u8_ptr[0], u8_ptr[1]); + } else if constexpr (sizeof(T) == 4) { + swap(u8_ptr[0], u8_ptr[3]); + swap(u8_ptr[1], u8_ptr[2]); + } + return value; +} + +static constexpr FloatVector4 decode_component_order_for_format(FloatVector4 const& components, GPU::PixelFormat format) +{ + switch (format) { + case GPU::PixelFormat::Alpha: + return { 0.f, 0.f, 0.f, components[0] }; + case GPU::PixelFormat::BGR: + return { components[2], components[1], components[0], 1.f }; + case GPU::PixelFormat::BGRA: + return { components[2], components[1], components[0], components[3] }; + case GPU::PixelFormat::Blue: + return { 0.f, 0.f, components[0], 1.f }; + case GPU::PixelFormat::ColorIndex: + case GPU::PixelFormat::DepthComponent: + case GPU::PixelFormat::StencilIndex: + return { components[0], 0.f, 0.f, 0.f }; + case GPU::PixelFormat::Green: + return { 0.f, components[0], 0.f, 1.f }; + case GPU::PixelFormat::Luminance: + return { components[0], components[0], components[0], 1.f }; + case GPU::PixelFormat::LuminanceAlpha: + return { components[0], components[0], components[0], components[1] }; + case GPU::PixelFormat::Red: + return { components[0], 0.f, 0.f, 1.f }; + case GPU::PixelFormat::RGB: + return { components[0], components[1], components[2], 1.f }; + case GPU::PixelFormat::RGBA: + return components; + } + VERIFY_NOT_REACHED(); +} + +static constexpr FloatVector4 encode_component_order_for_format(FloatVector4 const& components, GPU::PixelFormat format) +{ + switch (format) { + case GPU::PixelFormat::Alpha: + return { components[3], 0.f, 0.f, 0.f }; + case GPU::PixelFormat::BGR: + return { components[2], components[1], components[0], 0.f }; + case GPU::PixelFormat::BGRA: + return { components[2], components[1], components[0], components[3] }; + case GPU::PixelFormat::Blue: + return { components[2], 0.f, 0.f, 0.f }; + case GPU::PixelFormat::ColorIndex: + case GPU::PixelFormat::DepthComponent: + case GPU::PixelFormat::Luminance: + case GPU::PixelFormat::Red: + case GPU::PixelFormat::RGB: + case GPU::PixelFormat::RGBA: + case GPU::PixelFormat::StencilIndex: + return components; + case GPU::PixelFormat::Green: + return { components[1], 0.f, 0.f, 0.f }; + case GPU::PixelFormat::LuminanceAlpha: + return { components[0], components[3], 0.f, 0.f }; + } + VERIFY_NOT_REACHED(); +} + +template<typename S, typename O> +static int read_pixel_values(u8 const* input_data, Array<O, 4>& output_values, GPU::ImageDataLayout const& layout) +{ + auto const& pixel_type = layout.pixel_type; + auto const number_of_data_reads = GPU::number_of_components(pixel_type.format) / GPU::number_of_components(pixel_type.bits); + + for (int i = 0; i < number_of_data_reads; ++i) { + auto storage_value = reinterpret_cast<S const*>(input_data)[i]; + if (layout.pixel_type.bits == GPU::PixelComponentBits::AllBits) { + if constexpr (sizeof(S) == 2 || sizeof(S) == 4) + storage_value = reverse_component_bytes_if_needed(storage_value, layout); + } + O value = storage_value; + + // Special case: convert HalfFloat to regular float + if constexpr (IsSame<O, float>) { + if (pixel_type.data_type == GPU::PixelDataType::HalfFloat) + value = convert_to_native_float(FloatingPointBits<1, 5, 10>(storage_value)); + } + + output_values[i] = value; + } + return number_of_data_reads; +} + +template<typename T> +constexpr FloatVector4 extract_component_values(Span<T> data_values, GPU::PixelType const& pixel_type) +{ + // FIXME: implement fixed point conversion for ::StencilIndex + // FIXME: stencil components should account for GL_MAP_STENCIL + // FIXME: stencil components should get GL_INDEX_SHIFT and GL_INDEX_OFFSET applied + // FIXME: depth components should get GL_DEPTH_SCALE and GL_DEPTH_BIAS applied + // FIXME: color components should get GL_C_SCALE and GL_C_BIAS applied + + auto const number_of_values = data_values.size(); + auto const bits_number_of_components = number_of_components(pixel_type.bits); + VERIFY(bits_number_of_components == 1 || bits_number_of_components == number_of_components(pixel_type.format)); + + // Maps a signed value to -1.0f..1.0f + auto signed_to_float = [](T value) -> float { + auto constexpr number_of_bits = sizeof(T) * 8 - 1; + return max(static_cast<float>(value / static_cast<float>(1 << number_of_bits)), -1.f); + }; + + // Maps an unsigned value to 0.0f..1.0f + auto unsigned_to_float = [](T value, u8 const number_of_bits) -> float { + return static_cast<float>(value / static_cast<double>((1ull << number_of_bits) - 1)); + }; + + // Handle full data values (1 or more) + if (pixel_type.bits == GPU::PixelComponentBits::AllBits) { + FloatVector4 components; + for (size_t i = 0; i < number_of_values; ++i) { + if constexpr (IsSigned<T>) + components[i] = signed_to_float(data_values[i]); + else + components[i] = unsigned_to_float(data_values[i], sizeof(T) * 8); + } + return components; + } + + VERIFY(number_of_values == 1); + T const value = data_values[0]; + auto bitfields = pixel_component_bitfield_lengths(pixel_type.bits); + + // Map arbitrary bitfields to floats + u8 remaining_width = 0; + for (auto bitwidth : bitfields) + remaining_width += bitwidth; + + // "By default the components are laid out from msb (most-significant bit) to lsb (least-significant bit)" + FloatVector4 components; + for (auto i = 0; i < 4; ++i) { + auto bitwidth = bitfields[i]; + if (bitwidth == 0) + break; + remaining_width -= bitwidth; + components[i] = unsigned_to_float((value >> remaining_width) & ((1 << bitwidth) - 1), bitwidth); + } + return components; +} + +template<> +constexpr FloatVector4 extract_component_values(Span<float> data_values, GPU::PixelType const&) +{ + FloatVector4 components; + for (size_t i = 0; i < data_values.size(); ++i) + components[i] = data_values[i]; + return components; +} + +template<typename T> +static FloatVector4 pixel_values_to_components(Span<T> values, GPU::PixelType const& pixel_type) +{ + // Deconstruct read value(s) into separate components + auto components = extract_component_values(values, pixel_type); + if (pixel_type.components_order == GPU::ComponentsOrder::Reversed) + components = { components[3], components[2], components[1], components[0] }; + + // Reconstruct component values in order + auto component_values = decode_component_order_for_format(components, pixel_type.format); + component_values.clamp(0.f, 1.f); + return component_values; +} + +FloatVector4 PixelConverter::read_pixel(u8 const** input_data) +{ + auto read_components = [&]<typename S, typename O>() { + Array<O, 4> values; + auto number_of_values = read_pixel_values<S, O>(*input_data, values, m_input_specification); + *input_data += number_of_values * sizeof(O); + return pixel_values_to_components(values.span().trim(number_of_values), m_input_specification.pixel_type); + }; + switch (m_input_specification.pixel_type.data_type) { + case GPU::PixelDataType::Bitmap: + VERIFY_NOT_REACHED(); + case GPU::PixelDataType::Byte: + return read_components.template operator()<i8, i8>(); + case GPU::PixelDataType::Float: + return read_components.template operator()<float, float>(); + case GPU::PixelDataType::HalfFloat: + return read_components.template operator()<u16, float>(); + case GPU::PixelDataType::Int: + return read_components.template operator()<i32, i32>(); + case GPU::PixelDataType::Short: + return read_components.template operator()<i16, i16>(); + case GPU::PixelDataType::UnsignedByte: + return read_components.template operator()<u8, u8>(); + case GPU::PixelDataType::UnsignedInt: + return read_components.template operator()<u32, u32>(); + case GPU::PixelDataType::UnsignedShort: + return read_components.template operator()<u16, u16>(); + } + VERIFY_NOT_REACHED(); +} + +static constexpr void write_pixel_as_type(u8** output_data, float value, GPU::ImageDataLayout layout) +{ + auto write_value = [&output_data, &layout]<typename T>(T value) -> void { + if constexpr (sizeof(T) == 2 || sizeof(T) == 4) + value = reverse_component_bytes_if_needed(value, layout); + **reinterpret_cast<T**>(output_data) = value; + (*output_data) += sizeof(T); + }; + auto constexpr float_to_signed = []<typename T>(float value) -> T { + auto const signed_max = 1ull << (sizeof(T) * 8 - 1); + auto const unsigned_max = 2 * signed_max - 1; + return round_to<T>((static_cast<double>(value) + 1.) / 2. * unsigned_max - signed_max); + }; + auto constexpr float_to_unsigned = []<typename T>(float value) -> T { + auto const unsigned_max = (1ull << (sizeof(T) * 8)) - 1; + return round_to<T>(static_cast<double>(value) * unsigned_max); + }; + switch (layout.pixel_type.data_type) { + case GPU::PixelDataType::Bitmap: + VERIFY_NOT_REACHED(); + case GPU::PixelDataType::Byte: + write_value(float_to_signed.operator()<i8>(value)); + break; + case GPU::PixelDataType::Float: + write_value(value); + break; + case GPU::PixelDataType::HalfFloat: + write_value(static_cast<u16>(convert_from_native_float<FloatingPointBits<1, 5, 10>>(value).bits())); + break; + case GPU::PixelDataType::Int: + write_value(float_to_signed.operator()<i32>(value)); + break; + case GPU::PixelDataType::Short: + write_value(float_to_signed.operator()<i16>(value)); + break; + case GPU::PixelDataType::UnsignedByte: + write_value(float_to_unsigned.operator()<u8>(value)); + break; + case GPU::PixelDataType::UnsignedInt: + write_value(float_to_unsigned.operator()<u32>(value)); + break; + case GPU::PixelDataType::UnsignedShort: + write_value(float_to_unsigned.operator()<u16>(value)); + break; + } +} + +void constexpr write_pixel_as_bitfield(u8** output_data, FloatVector4 const& components, GPU::PixelType const& pixel_type) +{ + auto constexpr float_to_unsigned = [](float value, u8 bits) { + auto unsigned_max = (1ull << bits) - 1; + return round_to<u64>(value * unsigned_max); + }; + + // Construct value with concatenated bitfields - first component has most significant bits + auto bitfields = pixel_component_bitfield_lengths(pixel_type.bits); + u64 value = 0; + u8 bitsize = 0; + for (auto i = 0; i < 4; ++i) { + value <<= bitsize; + bitsize = bitfields[i]; + if (bitsize == 0) + break; + value |= float_to_unsigned(components[i], bitsize); + } + + // Write out the value in the requested data type + auto write_value = [&output_data]<typename T>(T value) -> void { + **reinterpret_cast<T**>(output_data) = value; + (*output_data) += sizeof(T); + }; + switch (pixel_type.data_type) { + case GPU::PixelDataType::UnsignedByte: + write_value.operator()<u8>(value); + break; + case GPU::PixelDataType::UnsignedInt: + write_value.operator()<u32>(value); + break; + case GPU::PixelDataType::UnsignedShort: + write_value.operator()<u16>(value); + break; + default: + VERIFY_NOT_REACHED(); + } +} + +void PixelConverter::write_pixel(u8** output_data, FloatVector4 const& components) +{ + // NOTE: `components` is already clamped to 0.f..1.f + + // Reorder float components to data order + auto const& pixel_type = m_output_specification.pixel_type; + auto output_components = encode_component_order_for_format(components, pixel_type.format); + if (pixel_type.components_order == GPU::ComponentsOrder::Reversed) + output_components = { output_components[3], output_components[2], output_components[1], output_components[0] }; + + // Write components as full data types + auto const number_of_components_in_pixel = number_of_components(pixel_type.format); + if (pixel_type.bits == GPU::PixelComponentBits::AllBits) { + for (u8 i = 0; i < number_of_components_in_pixel; ++i) + write_pixel_as_type(output_data, output_components[i], m_output_specification); + return; + } + + // Write components as a concatenated bitfield value + VERIFY(number_of_components_in_pixel == number_of_components(pixel_type.bits)); + write_pixel_as_bitfield(output_data, output_components, pixel_type); +} + +static constexpr GPU::ImageSelection restrain_selection_within_dimensions(GPU::ImageSelection selection, GPU::DimensionSpecification const& dimensions) +{ + if (selection.offset_x < 0) { + selection.width += selection.offset_x; + selection.offset_x = 0; + } + if (selection.offset_y < 0) { + selection.height += selection.offset_y; + selection.offset_y = 0; + } + if (selection.offset_z < 0) { + selection.depth += selection.offset_z; + selection.offset_z = 0; + } + + if (selection.offset_x + selection.width > dimensions.width) + selection.width = dimensions.width - selection.offset_x; + if (selection.offset_y + selection.height > dimensions.height) + selection.height = dimensions.height - selection.offset_y; + if (selection.offset_z + selection.depth > dimensions.depth) + selection.depth = dimensions.depth - selection.offset_z; + + return selection; +} + +ErrorOr<void> PixelConverter::convert(void const* input_data, void* output_data) +{ + // Verify pixel data specifications + auto validate_image_data_layout = [](GPU::ImageDataLayout const& specification) -> ErrorOr<void> { + if (specification.packing.row_stride > 0 + && specification.dimensions.width > specification.packing.row_stride) + return Error::from_string_view("Width exceeds the row stride"sv); + + if (specification.packing.depth_stride > 0 + && specification.dimensions.height > specification.packing.depth_stride) + return Error::from_string_view("Height exceeds the depth stride"sv); + + // NOTE: GL_BITMAP is removed from current OpenGL specs. Since it is largely unsupported and it + // requires extra logic (i.e. 8 vs. 1 pixel packing/unpacking), we also do not support it. + if (specification.pixel_type.data_type == GPU::PixelDataType::Bitmap) + return Error::from_string_view("Bitmap is unsupported"sv); + + return {}; + }; + TRY(validate_image_data_layout(m_input_specification)); + TRY(validate_image_data_layout(m_output_specification)); + + // Restrain input and output selection: + // - selection dimensions should be equal + // - selection offsets cannot be negative + // - selection bounds cannot exceed the image dimensions + auto const& input_dimensions = m_input_specification.dimensions; + auto const& output_dimensions = m_output_specification.dimensions; + auto input_selection = restrain_selection_within_dimensions(m_input_specification.selection, input_dimensions); + auto const& output_selection = restrain_selection_within_dimensions(m_output_specification.selection, output_dimensions); + + input_selection.width = min(input_selection.width, output_selection.width); + input_selection.height = min(input_selection.height, output_selection.height); + input_selection.depth = min(input_selection.depth, output_selection.depth); + + // Set up copy parameters + auto const& input_packing = m_input_specification.packing; + auto const input_pixels_per_row = input_packing.row_stride > 0 ? input_packing.row_stride : input_dimensions.width; + auto const input_pixel_size_in_bytes = pixel_size_in_bytes(m_input_specification.pixel_type); + auto const input_row_width_bytes = input_pixels_per_row * input_pixel_size_in_bytes; + auto const input_byte_alignment = input_packing.byte_alignment; + auto const input_row_stride = input_row_width_bytes + (input_byte_alignment - input_row_width_bytes % input_byte_alignment) % input_byte_alignment; + auto const input_rows_per_image = input_packing.depth_stride > 0 ? input_packing.depth_stride : input_dimensions.height; + auto const input_depth_stride = input_rows_per_image * input_row_stride; + + auto const& output_packing = m_output_specification.packing; + auto const output_pixels_per_row = output_packing.row_stride > 0 ? output_packing.row_stride : output_dimensions.width; + auto const output_pixel_size_in_bytes = pixel_size_in_bytes(m_output_specification.pixel_type); + auto const output_row_width_bytes = output_pixels_per_row * output_pixel_size_in_bytes; + auto const output_byte_alignment = output_packing.byte_alignment; + auto const output_row_stride = output_row_width_bytes + (output_byte_alignment - output_row_width_bytes % output_byte_alignment) % output_byte_alignment; + auto const output_rows_per_image = output_packing.depth_stride > 0 ? output_packing.depth_stride : output_dimensions.height; + auto const output_depth_stride = output_rows_per_image * output_row_stride; + + // Copy all pixels from input to output + auto input_bytes = reinterpret_cast<u8 const*>(input_data); + auto output_bytes = reinterpret_cast<u8*>(output_data); + auto output_z = output_selection.offset_z; + for (u32 input_z = input_selection.offset_z; input_z < input_selection.offset_z + input_selection.depth; ++input_z) { + auto output_y = output_selection.offset_y; + for (u32 input_y = input_selection.offset_y; input_y < input_selection.offset_y + input_selection.height; ++input_y) { + auto const* input_scanline = &input_bytes[input_z * input_depth_stride + + input_y * input_row_stride + + input_selection.offset_x * input_pixel_size_in_bytes]; + auto* output_scanline = &output_bytes[output_z * output_depth_stride + + output_y * output_row_stride + + output_selection.offset_x * output_pixel_size_in_bytes]; + for (u32 input_x = input_selection.offset_x; input_x < input_selection.offset_x + input_selection.width; ++input_x) { + auto pixel_components = read_pixel(&input_scanline); + write_pixel(&output_scanline, pixel_components); + } + ++output_y; + } + ++output_z; + } + return {}; +} + +} diff --git a/Userland/Libraries/LibSoftGPU/PixelConverter.h b/Userland/Libraries/LibSoftGPU/PixelConverter.h new file mode 100644 index 0000000000..5c51c47b4b --- /dev/null +++ b/Userland/Libraries/LibSoftGPU/PixelConverter.h @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2022, Jelle Raaijmakers <jelle@gmta.nl> + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include <AK/Error.h> +#include <LibGPU/ImageDataLayout.h> +#include <LibGfx/Vector4.h> + +namespace SoftGPU { + +class PixelConverter { +public: + PixelConverter(GPU::ImageDataLayout input_specification, GPU::ImageDataLayout output_specification) + : m_input_specification { input_specification } + , m_output_specification { output_specification } + { + } + + ErrorOr<void> convert(void const* input_data, void* output_data); + +private: + FloatVector4 read_pixel(u8 const**); + void write_pixel(u8**, FloatVector4 const&); + + GPU::ImageDataLayout m_input_specification; + GPU::ImageDataLayout m_output_specification; +}; + +} |