From 1540c56e6c82bfbdb1fd9b29c34c7ac1a399a1d3 Mon Sep 17 00:00:00 2001 From: Jelle Raaijmakers Date: Sun, 4 Sep 2022 22:18:16 +0200 Subject: [PATCH] LibGL+LibGPU+LibSoftGPU: Implement `GL_GENERATE_MIPMAP` We can now generate texture mipmaps on the fly if the client requests it. This fixes the missing textures in our PrBoom+ port. --- Userland/Libraries/LibGL/GL/gl.h | 1 + Userland/Libraries/LibGL/Tex/MipMap.h | 25 ----- Userland/Libraries/LibGL/Tex/Texture.h | 1 + Userland/Libraries/LibGL/Tex/Texture2D.cpp | 22 +++-- Userland/Libraries/LibGL/Tex/Texture2D.h | 19 +--- Userland/Libraries/LibGL/Texture.cpp | 15 ++- Userland/Libraries/LibGPU/Image.h | 8 ++ Userland/Libraries/LibSoftGPU/Image.cpp | 106 ++++++++++++++++++--- Userland/Libraries/LibSoftGPU/Image.h | 11 ++- Userland/Libraries/LibSoftGPU/Sampler.cpp | 22 ++--- 10 files changed, 148 insertions(+), 82 deletions(-) delete mode 100644 Userland/Libraries/LibGL/Tex/MipMap.h diff --git a/Userland/Libraries/LibGL/GL/gl.h b/Userland/Libraries/LibGL/GL/gl.h index d59596b16f..42e48746e3 100644 --- a/Userland/Libraries/LibGL/GL/gl.h +++ b/Userland/Libraries/LibGL/GL/gl.h @@ -506,6 +506,7 @@ extern "C" { #define GL_REPEAT 0x2901 #define GL_CLAMP_TO_BORDER 0x812D #define GL_CLAMP_TO_EDGE 0x812F +#define GL_GENERATE_MIPMAP 0x8191 #define GL_MIRRORED_REPEAT 0x8370 #define GL_SUBTRACT 0x84E7 #define GL_TEXTURE_FILTER_CONTROL 0x8500 diff --git a/Userland/Libraries/LibGL/Tex/MipMap.h b/Userland/Libraries/LibGL/Tex/MipMap.h deleted file mode 100644 index fc2d697d75..0000000000 --- a/Userland/Libraries/LibGL/Tex/MipMap.h +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (c) 2021, Jesse Buhagiar - * Copyright (c) 2021, Stephan Unverwerth - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#pragma once - -#include - -namespace GL { - -class MipMap { -public: - void set_width(GLsizei width) { m_width = width; } - void set_height(GLsizei height) { m_height = height; } - GLsizei width() const { return m_width; } - GLsizei height() const { return m_height; } - -private: - GLsizei m_width { 0 }; - GLsizei m_height { 0 }; -}; -} diff --git a/Userland/Libraries/LibGL/Tex/Texture.h b/Userland/Libraries/LibGL/Tex/Texture.h index 452201f7d9..24b1bbb638 100644 --- a/Userland/Libraries/LibGL/Tex/Texture.h +++ b/Userland/Libraries/LibGL/Tex/Texture.h @@ -21,6 +21,7 @@ public: virtual bool is_texture_3d() const { return false; } virtual bool is_cube_map() const { return false; } + RefPtr device_image() const { return m_device_image; } RefPtr device_image() { return m_device_image; } void set_device_image(RefPtr image) { m_device_image = image; } diff --git a/Userland/Libraries/LibGL/Tex/Texture2D.cpp b/Userland/Libraries/LibGL/Tex/Texture2D.cpp index 1f6c2351d0..3ed97dc6e9 100644 --- a/Userland/Libraries/LibGL/Tex/Texture2D.cpp +++ b/Userland/Libraries/LibGL/Tex/Texture2D.cpp @@ -19,20 +19,15 @@ void Texture2D::download_texture_data(GLuint lod, GPU::ImageDataLayout output_la 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]; 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, input_layout, { 0, 0, 0 }, pixels); + if (lod == 0 && m_generate_mipmaps) + device_image()->regenerate_mipmaps(); } void Texture2D::replace_sub_texture_data(GLuint lod, GPU::ImageDataLayout input_layout, Vector3 const& output_offset, GLvoid const* pixels) @@ -42,7 +37,18 @@ void Texture2D::replace_sub_texture_data(GLuint lod, GPU::ImageDataLayout input_ // once used for rendering for the first time. VERIFY(!device_image().is_null()); - device_image()->write_texels(0, lod, output_offset, pixels, input_layout); + device_image()->write_texels(lod, output_offset, pixels, input_layout); + if (lod == 0 && m_generate_mipmaps) + device_image()->regenerate_mipmaps(); +} + +void Texture2D::set_generate_mipmaps(bool generate_mipmaps) +{ + if (m_generate_mipmaps == generate_mipmaps) + return; + m_generate_mipmaps = generate_mipmaps; + if (generate_mipmaps && !device_image().is_null()) + device_image()->regenerate_mipmaps(); } } diff --git a/Userland/Libraries/LibGL/Tex/Texture2D.h b/Userland/Libraries/LibGL/Tex/Texture2D.h index cd8e5f42b0..41f4a90088 100644 --- a/Userland/Libraries/LibGL/Tex/Texture2D.h +++ b/Userland/Libraries/LibGL/Tex/Texture2D.h @@ -10,10 +10,8 @@ #include "Texture.h" -#include #include #include -#include #include #include @@ -31,24 +29,17 @@ public: 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 - { - if (lod >= m_mipmaps.size()) - return m_mipmaps.last(); - - return m_mipmaps.at(lod); - } - + void set_generate_mipmaps(bool generate_mipmaps); GLenum internal_format() const { return m_internal_format; } Sampler2D const& sampler() const { return m_sampler; } Sampler2D& sampler() { return m_sampler; } - int width_at_lod(unsigned level) const { return (level >= m_mipmaps.size()) ? 0 : m_mipmaps.at(level).width(); } - int height_at_lod(unsigned level) const { return (level >= m_mipmaps.size()) ? 0 : m_mipmaps.at(level).height(); } + int width_at_lod(unsigned level) const { return static_cast(device_image()->width_at_level(level)); } + int height_at_lod(unsigned level) const { return static_cast(device_image()->height_at_level(level)); } + int depth_at_lod(unsigned level) const { return static_cast(device_image()->depth_at_level(level)); } private: - // FIXME: Mipmaps are currently unused, but we have the plumbing for it at least - Array m_mipmaps; + bool m_generate_mipmaps { false }; GLenum m_internal_format; Sampler2D m_sampler; }; diff --git a/Userland/Libraries/LibGL/Texture.cpp b/Userland/Libraries/LibGL/Texture.cpp index c8f9ff9d77..6ef6819a9f 100644 --- a/Userland/Libraries/LibGL/Texture.cpp +++ b/Userland/Libraries/LibGL/Texture.cpp @@ -582,10 +582,11 @@ void GLContext::gl_tex_parameter(GLenum target, GLenum pname, GLfloat param) RETURN_WITH_ERROR_IF(target != GL_TEXTURE_2D, GL_INVALID_ENUM); // FIXME: implement the remaining parameters. (https://docs.gl/gl2/glTexParameter) - RETURN_WITH_ERROR_IF(!(pname == GL_TEXTURE_MIN_FILTER - || pname == GL_TEXTURE_MAG_FILTER - || pname == GL_TEXTURE_WRAP_S - || pname == GL_TEXTURE_WRAP_T), + RETURN_WITH_ERROR_IF(pname != GL_GENERATE_MIPMAP + && pname != GL_TEXTURE_MIN_FILTER + && pname != GL_TEXTURE_MAG_FILTER + && pname != GL_TEXTURE_WRAP_S + && pname != GL_TEXTURE_WRAP_T, GL_INVALID_ENUM); // We assume GL_TEXTURE_2D (see above) @@ -593,6 +594,10 @@ void GLContext::gl_tex_parameter(GLenum target, GLenum pname, GLfloat param) VERIFY(!texture_2d.is_null()); switch (pname) { + case GL_GENERATE_MIPMAP: + RETURN_WITH_ERROR_IF(param != GL_TRUE && param != GL_FALSE, GL_INVALID_ENUM); + texture_2d->set_generate_mipmaps(param == GL_TRUE); + break; case GL_TEXTURE_MIN_FILTER: RETURN_WITH_ERROR_IF(!(param == GL_NEAREST || param == GL_LINEAR @@ -652,7 +657,7 @@ void GLContext::gl_tex_parameterfv(GLenum target, GLenum pname, GLfloat const* p RETURN_WITH_ERROR_IF(target != GL_TEXTURE_2D, GL_INVALID_ENUM); // FIXME: implement the remaining parameters. (https://docs.gl/gl2/glTexParameter) - RETURN_WITH_ERROR_IF(!(pname == GL_TEXTURE_BORDER_COLOR), GL_INVALID_ENUM); + RETURN_WITH_ERROR_IF(pname != GL_TEXTURE_BORDER_COLOR, GL_INVALID_ENUM); // We assume GL_TEXTURE_2D (see above) auto texture_2d = m_active_texture_unit->texture_2d_target_texture(); diff --git a/Userland/Libraries/LibGPU/Image.h b/Userland/Libraries/LibGPU/Image.h index 5c9147cf41..aed3f581c4 100644 --- a/Userland/Libraries/LibGPU/Image.h +++ b/Userland/Libraries/LibGPU/Image.h @@ -1,5 +1,6 @@ /* * Copyright (c) 2022, Stephan Unverwerth + * Copyright (c) 2022, Jelle Raaijmakers * * SPDX-License-Identifier: BSD-2-Clause */ @@ -21,6 +22,13 @@ public: virtual ~Image() { } + virtual u32 width_at_level(u32 level) const = 0; + virtual u32 height_at_level(u32 level) const = 0; + virtual u32 depth_at_level(u32 level) const = 0; + virtual u32 number_of_levels() const = 0; + + virtual void regenerate_mipmaps() = 0; + virtual void write_texels(u32 level, Vector3 const& output_offset, void const* input_data, ImageDataLayout const&) = 0; virtual void read_texels(u32 level, Vector3 const& input_offset, void* output_data, ImageDataLayout const&) const = 0; virtual void copy_texels(Image const& source, u32 source_level, Vector3 const& source_offset, Vector3 const& size, u32 destination_level, Vector3 const& destination_offset) = 0; diff --git a/Userland/Libraries/LibSoftGPU/Image.cpp b/Userland/Libraries/LibSoftGPU/Image.cpp index 795fc93206..5535c8c0e7 100644 --- a/Userland/Libraries/LibSoftGPU/Image.cpp +++ b/Userland/Libraries/LibSoftGPU/Image.cpp @@ -5,6 +5,9 @@ * SPDX-License-Identifier: BSD-2-Clause */ +#include +#include +#include #include #include @@ -42,14 +45,14 @@ Image::Image(void const* ownership_token, GPU::PixelFormat const& pixel_format, depth = max(depth / 2, 1); } - m_num_levels = level + 1; + m_number_of_levels = level + 1; } 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); + auto const width = width_at_level(level); + auto const height = height_at_level(level); + auto const depth = depth_at_level(level); // FIXME: we are directly writing to FloatVector4s. We should probably find a better way to do this return { @@ -76,7 +79,7 @@ GPU::ImageDataLayout Image::image_data_layout(u32 level, Vector3 offset) co void Image::write_texels(u32 level, Vector3 const& output_offset, void const* input_data, GPU::ImageDataLayout const& input_layout) { - VERIFY(level < num_levels()); + VERIFY(level < number_of_levels()); auto output_layout = image_data_layout(level, output_offset); auto texel_data = texel_pointer(level, 0, 0, 0); @@ -98,7 +101,7 @@ void Image::write_texels(u32 level, Vector3 const& output_offset, void cons void Image::read_texels(u32 level, Vector3 const& input_offset, void* output_data, GPU::ImageDataLayout const& output_layout) const { - VERIFY(level < num_levels()); + VERIFY(level < number_of_levels()); auto input_layout = image_data_layout(level, input_offset); @@ -114,14 +117,14 @@ void Image::copy_texels(GPU::Image const& source, u32 source_level, Vector3 auto const& src_image = static_cast(source); - VERIFY(source_level < src_image.num_levels()); - VERIFY(source_offset.x() + size.x() <= src_image.level_width(source_level)); - VERIFY(source_offset.y() + size.y() <= src_image.level_height(source_level)); - VERIFY(source_offset.z() + size.z() <= src_image.level_depth(source_level)); - VERIFY(destination_level < num_levels()); - VERIFY(destination_offset.x() + size.x() <= level_width(destination_level)); - VERIFY(destination_offset.y() + size.y() <= level_height(destination_level)); - VERIFY(destination_offset.z() + size.z() <= level_depth(destination_level)); + VERIFY(source_level < src_image.number_of_levels()); + VERIFY(source_offset.x() + size.x() <= src_image.width_at_level(source_level)); + VERIFY(source_offset.y() + size.y() <= src_image.height_at_level(source_level)); + VERIFY(source_offset.z() + size.z() <= src_image.depth_at_level(source_level)); + VERIFY(destination_level < number_of_levels()); + VERIFY(destination_offset.x() + size.x() <= width_at_level(destination_level)); + VERIFY(destination_offset.y() + size.y() <= height_at_level(destination_level)); + VERIFY(destination_offset.z() + size.z() <= depth_at_level(destination_level)); for (u32 z = 0; z < size.z(); ++z) { for (u32 y = 0; y < size.y(); ++y) { @@ -133,4 +136,79 @@ void Image::copy_texels(GPU::Image const& source, u32 source_level, Vector3 } } +static GPU::ImageDataLayout image_data_layout_for_bitmap(Gfx::Bitmap& bitmap) +{ + VERIFY(bitmap.format() == Gfx::BitmapFormat::BGRA8888); + return GPU::ImageDataLayout { + .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(bitmap.width()), + .height = static_cast(bitmap.height()), + .depth = 1, + }, + .selection = { + .width = static_cast(bitmap.width()), + .height = static_cast(bitmap.height()), + .depth = 1, + }, + }; +} + +void Image::regenerate_mipmaps() +{ + // FIXME: currently this only works for 2D Images + VERIFY(depth_at_level(0) == 1); + + auto empty_bitmap_for_level = [&](u32 level) -> NonnullRefPtr { + Gfx::IntSize size = { width_at_level(level), height_at_level(level) }; + return MUST(Gfx::Bitmap::try_create(Gfx::BitmapFormat::BGRA8888, size)); + }; + auto copy_image_into_bitmap = [&](u32 level) -> NonnullRefPtr { + auto bitmap = empty_bitmap_for_level(level); + + auto input_layout = image_data_layout(level, { 0, 0, 0 }); + auto const* input_data = texel_pointer(level, 0, 0, 0); + + auto output_layout = image_data_layout_for_bitmap(bitmap); + auto* output_data = bitmap->scanline(0); + + PixelConverter converter { input_layout, output_layout }; + MUST(converter.convert(input_data, output_data, {})); + return bitmap; + }; + auto copy_bitmap_into_level = [&](NonnullRefPtr bitmap, u32 level) { + VERIFY(level >= 1); + + auto input_layout = image_data_layout_for_bitmap(bitmap); + auto const* input_data = bitmap->scanline(0); + + auto output_layout = image_data_layout(level, { 0, 0, 0 }); + auto* output_data = texel_pointer(level, 0, 0, 0); + + PixelConverter converter { input_layout, output_layout }; + MUST(converter.convert(input_data, output_data, {})); + }; + + // For levels 1..number_of_levels-1, we generate downscaled versions of the level above + for (u32 level = 1; level < m_number_of_levels; ++level) { + auto higher_level_bitmap = copy_image_into_bitmap(level - 1); + auto current_level_bitmap = empty_bitmap_for_level(level); + + Gfx::Painter current_level_painter { current_level_bitmap }; + current_level_painter.draw_scaled_bitmap( + current_level_bitmap->rect(), + higher_level_bitmap, + higher_level_bitmap->rect(), + 1.f, + Gfx::Painter::ScalingMode::BilinearBlend); + + copy_bitmap_into_level(current_level_bitmap, level); + } +} + } diff --git a/Userland/Libraries/LibSoftGPU/Image.h b/Userland/Libraries/LibSoftGPU/Image.h index 23c7941155..8e702d9a54 100644 --- a/Userland/Libraries/LibSoftGPU/Image.h +++ b/Userland/Libraries/LibSoftGPU/Image.h @@ -22,15 +22,16 @@ class Image final : public GPU::Image { public: Image(void const* ownership_token, GPU::PixelFormat const&, u32 width, u32 height, u32 depth, u32 max_levels); - 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; } + virtual u32 width_at_level(u32 level) const override { return m_mipmap_buffers[level]->width(); } + virtual u32 height_at_level(u32 level) const override { return m_mipmap_buffers[level]->height(); } + virtual u32 depth_at_level(u32 level) const override { return m_mipmap_buffers[level]->depth(); } + virtual u32 number_of_levels() const override { return m_number_of_levels; } 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; } GPU::ImageDataLayout image_data_layout(u32 level, Vector3 offset) const; + virtual void regenerate_mipmaps() override; FloatVector4 texel(u32 level, int x, int y, int z) const { @@ -57,7 +58,7 @@ public: } private: - u32 m_num_levels { 0 }; + u32 m_number_of_levels { 0 }; GPU::PixelFormat m_pixel_format; FixedArray>> m_mipmap_buffers; diff --git a/Userland/Libraries/LibSoftGPU/Sampler.cpp b/Userland/Libraries/LibSoftGPU/Sampler.cpp index b027e99af4..d85d7dd845 100644 --- a/Userland/Libraries/LibSoftGPU/Sampler.cpp +++ b/Userland/Libraries/LibSoftGPU/Sampler.cpp @@ -117,8 +117,8 @@ Vector4 Sampler::sample_2d(Vector2 const& uv) // FIXME: Static casting from u32 to float could silently truncate here. // u16 should be plenty enough for texture dimensions and would allow textures of up to 65536x65536x65536 pixels. auto texel_coordinates = uv; - texel_coordinates.set_x(texel_coordinates.x() * static_cast(image.level_width(base_level))); - texel_coordinates.set_y(texel_coordinates.y() * static_cast(image.level_height(base_level))); + texel_coordinates.set_x(texel_coordinates.x() * static_cast(image.width_at_level(base_level))); + texel_coordinates.set_y(texel_coordinates.y() * static_cast(image.height_at_level(base_level))); auto dtdx = ddx(texel_coordinates); auto dtdy = ddy(texel_coordinates); auto scale_factor = max(dtdx.dot(dtdx), dtdy.dot(dtdy)); @@ -138,7 +138,7 @@ Vector4 Sampler::sample_2d(Vector2 const& uv) auto texture_lod_bias = AK::clamp(m_config.level_of_detail_bias, -MAX_TEXTURE_LOD_BIAS, MAX_TEXTURE_LOD_BIAS); // FIXME: Instead of clamping to num_levels - 1, actually make the max mipmap level configurable with glTexParameteri(GL_TEXTURE_MAX_LEVEL, max_level) auto min_level = expand4(static_cast(base_level)); - auto max_level = expand4(static_cast(image.num_levels()) - 1.f); + auto max_level = expand4(static_cast(image.number_of_levels()) - 1.f); auto lambda_xy = log2_approximate(scale_factor) * .5f + texture_lod_bias; auto level = clamp(lambda_xy, min_level, max_level); @@ -157,16 +157,16 @@ Vector4 Sampler::sample_2d_lod(Vector2 const& auto const& image = *static_ptr_cast(m_config.bound_image); u32x4 const width = { - image.level_width(level[0]), - image.level_width(level[1]), - image.level_width(level[2]), - image.level_width(level[3]), + image.width_at_level(level[0]), + image.width_at_level(level[1]), + image.width_at_level(level[2]), + image.width_at_level(level[3]), }; u32x4 const height = { - image.level_height(level[0]), - image.level_height(level[1]), - image.level_height(level[2]), - image.level_height(level[3]), + image.height_at_level(level[0]), + image.height_at_level(level[1]), + image.height_at_level(level[2]), + image.height_at_level(level[3]), }; auto f_width = to_f32x4(width);