From 967cfcfd6b339fc5dbf49e4134ff46a3ed37c056 Mon Sep 17 00:00:00 2001 From: Alberto Mardegan Date: Fri, 18 Oct 2024 17:45:34 +0300 Subject: [PATCH 01/17] utils: fix mtx44project() for perspective matrices The computation was incorrect. --- src/utils.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/utils.h b/src/utils.h index ffc89ed..11bc677 100644 --- a/src/utils.h +++ b/src/utils.h @@ -119,9 +119,9 @@ static inline void mtx44project(const Mtx44 p, const guVector *v, out->y = p[1][1] * v->y + p[1][2] * v->z + p[1][3]; out->z = p[2][2] * v->z + p[2][3]; if (p[3][2] != 0) { - out->x = -out->x; - out->y = -out->y; - out->z = -out->z; + out->x /= -v->z; + out->y /= -v->z; + out->z /= -v->z; } } From 51eba3a36146cde12b1480e18b27072bed0d7d29 Mon Sep 17 00:00:00 2001 From: Alberto Mardegan Date: Tue, 15 Oct 2024 16:30:46 +0300 Subject: [PATCH 02/17] pixels: refactor texel classes Remove constructors with parameters and instead add separate methods to set the desired texel data. --- src/pixels.cpp | 86 ++++++++++++++++++++++++-------------------------- 1 file changed, 41 insertions(+), 45 deletions(-) diff --git a/src/pixels.cpp b/src/pixels.cpp index f3c383a..d797392 100644 --- a/src/pixels.cpp +++ b/src/pixels.cpp @@ -70,7 +70,7 @@ static inline uint8_t luminance_from_rgb(uint8_t r, uint8_t g, uint8_t b) } struct Texel { - virtual void setColor(GXColor color) = 0; + virtual void set_color(GXColor color) = 0; virtual void store (void *texture, int x, int y, int pitch) = 0; virtual int pitch_for_width(int width) = 0; }; @@ -81,11 +81,12 @@ struct TexelRGBA8: public Texel { static constexpr bool has_luminance = false; TexelRGBA8() = default; - TexelRGBA8(uint8_t r, uint8_t g, uint8_t b, uint8_t a): - r(r), g(g), b(b), a(a) {} + void set_color(uint8_t red, uint8_t green, uint8_t blue, uint8_t alpha) { + r = red; g = green; b =blue; a = alpha; + } - void setColor(GXColor c) override { - r = c.r; g = c.g; b = c.b; a = c.a; + void set_color(GXColor c) override { + set_color(c.r, c.g, c.b, c.a); } void store(void *texture, int x, int y, int pitch) override { @@ -114,7 +115,8 @@ struct TexelRGBA8: public Texel { struct Texel16: public Texel { Texel16() = default; - Texel16(uint16_t value): word(value) {} + void setWord(uint16_t value) { word = value; } + void store(void *texture, int x, int y, int pitch) override { int block_x = x / 4; int block_y = y / 4; @@ -143,12 +145,13 @@ struct TexelIA8: public Texel16 { static constexpr bool has_luminance = true; TexelIA8() = default; - TexelIA8(uint8_t intensity, uint8_t alpha): - Texel16((alpha << 8) | intensity) {} + void set_luminance_alpha(uint8_t luminance, uint8_t alpha) { + setWord((alpha << 8) | luminance); + } - void setColor(GXColor c) override { + void set_color(GXColor c) override { int luminance = luminance_from_rgb(c.r, c.g, c.b); - word = (c.a << 8) | luminance; + set_luminance_alpha(luminance, c.a); } }; @@ -157,22 +160,18 @@ struct TexelRGB565: public Texel16 { static constexpr bool has_rgb = true; TexelRGB565() = default; - TexelRGB565(uint8_t r, uint8_t g, uint8_t b): - Texel16(((r & 0xf8) << 8) | + void set_color(uint8_t r, uint8_t g, uint8_t b) { + setWord(((r & 0xf8) << 8) | ((g & 0xfc) << 3) | - ((b & 0xf8) >> 3)) {} - - void setColor(GXColor c) override { - word = ((c.r & 0xf8) << 8) | - ((c.g & 0xfc) << 3) | - ((c.b & 0xf8) >> 3); + ((b & 0xf8) >> 3)); } + + void set_color(GXColor c) override { set_color(c.r, c.g, c.b); } }; struct Texel8: public Texel { Texel8() = default; - Texel8(uint8_t value): - value(value) {} + void setByte(uint8_t b) { value = b; } void store(void *texture, int x, int y, int pitch) override { int block_x = x / 8; @@ -198,9 +197,10 @@ struct TexelI8: public Texel8 { static constexpr bool has_luminance = true; using Texel8::Texel8; - void setColor(GXColor c) override { - value = luminance_from_rgb(c.r, c.g, c.b); + void set_color(GXColor c) override { + setByte(luminance_from_rgb(c.r, c.g, c.b)); } + void set_luminance(uint8_t l) { setByte(l); } }; struct TexelA8: public Texel8 { @@ -209,15 +209,13 @@ struct TexelA8: public Texel8 { static constexpr bool has_luminance = false; using Texel8::Texel8; - void setColor(GXColor c) override { - value = c.a; - } + void set_color(GXColor c) override { setByte(c.a); } + void set_alpha(uint8_t a) { setByte(a); } }; struct TexelI4: public Texel { TexelI4() = default; - TexelI4(uint8_t value): - value(value >> 4) {} + void set_luminance(uint8_t luminance) { value = luminance >> 4; } void store(void *texture, int x, int y, int pitch) override { int block_x = x / 8; @@ -245,9 +243,7 @@ struct TexelI4: public Texel { int pitch_for_width(int width) override { return compute_pitch(width); } - void setColor(GXColor c) override { - value = c.r; - } + void set_color(GXColor c) override { set_luminance(c.r); } uint8_t value; }; @@ -283,19 +279,19 @@ struct DataReader { template static inline const T *read(const T *data, P &pixel) { if constexpr (NUM_ELEMS == 4 && P::has_rgb && P::has_alpha) { - pixel = P(component(data[0]), - component(data[1]), - component(data[2]), - component(data[3])); + pixel.set_color(component(data[0]), + component(data[1]), + component(data[2]), + component(data[3])); } else if constexpr (NUM_ELEMS >= 3 && P::has_rgb && !P::has_alpha) { - pixel = P(component(data[0]), - component(data[1]), - component(data[2])); + pixel.set_color(component(data[0]), + component(data[1]), + component(data[2])); } else if constexpr (NUM_ELEMS == 3 && P::has_rgb && P::has_alpha) { - pixel = P(component(data[0]), - component(data[1]), - component(data[2]), - 1.0f); + pixel.set_color(component(data[0]), + component(data[1]), + component(data[2]), + 1.0f); } else { /* TODO (maybe) support converting from intensity to RGB */ uint8_t luminance, alpha; @@ -328,11 +324,11 @@ struct DataReader { } if constexpr (P::has_luminance && P::has_alpha) { - pixel = P(luminance, alpha); + pixel.set_luminance_alpha(luminance, alpha); } else if constexpr (P::has_luminance) { - pixel = P(luminance); + pixel.set_luminance(luminance); } else { /* Only alpha */ - pixel = P(alpha); + pixel.set_alpha(alpha); } } @@ -809,7 +805,7 @@ void _ogx_bytes_to_texture(const void *data, GLenum format, GLenum type, } for (int rx = 0; rx < width; rx++) { GXColor c = reader->read(); - texel->setColor(c); + texel->set_color(c); texel->store(dst, x + rx, y + ry, dstpitch); } } From 70bc23384e647b3365899c305d9f174d57b17366 Mon Sep 17 00:00:00 2001 From: Alberto Mardegan Date: Sun, 20 Oct 2024 21:52:02 +0300 Subject: [PATCH 03/17] pixels: Reuse the Texel object throughout the conversion Do not instantiate the Texture object for a single pixel, but just once at the beginning of the texture creation. This does not save us any memory allocations (as a matter of fact, the Texel object is allocated on the stack at no cost), but allows us to implement some optimizations in the conversion process. --- src/pixels.cpp | 91 ++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 66 insertions(+), 25 deletions(-) diff --git a/src/pixels.cpp b/src/pixels.cpp index d797392..370ded3 100644 --- a/src/pixels.cpp +++ b/src/pixels.cpp @@ -71,8 +71,27 @@ static inline uint8_t luminance_from_rgb(uint8_t r, uint8_t g, uint8_t b) struct Texel { virtual void set_color(GXColor color) = 0; - virtual void store (void *texture, int x, int y, int pitch) = 0; + virtual void store() = 0; virtual int pitch_for_width(int width) = 0; + + virtual void set_area(void *data, int x, int y, int width, int height, + int pitch) { + m_data = data; + m_start_x = m_x = x; + m_start_y = m_y = y; + m_width = width; + m_height = height; + m_pitch = pitch; + } + + void *m_data; + int m_x; + int m_y; + int m_start_x; + int m_start_y; + int m_width; + int m_height; + int m_pitch; }; struct TexelRGBA8: public Texel { @@ -89,15 +108,20 @@ struct TexelRGBA8: public Texel { set_color(c.r, c.g, c.b, c.a); } - void store(void *texture, int x, int y, int pitch) override { - int block_x = x / 4; - int block_y = y / 4; - uint8_t *d = static_cast(texture) + - block_y * pitch * 4 + block_x * 64 + (y % 4) * 8 + (x % 4) * 2; + void store() override { + int block_x = m_x / 4; + int block_y = m_y / 4; + uint8_t *d = static_cast(m_data) + + block_y * m_pitch * 4 + block_x * 64 + (m_y % 4) * 8 + (m_x % 4) * 2; d[0] = a; d[1] = r; d[32] = g; d[33] = b; + m_x++; + if (m_x == m_start_x + m_width) { + m_y++; + m_x = m_start_x; + } } static inline int compute_pitch(int width) { @@ -117,13 +141,18 @@ struct Texel16: public Texel { Texel16() = default; void setWord(uint16_t value) { word = value; } - void store(void *texture, int x, int y, int pitch) override { - int block_x = x / 4; - int block_y = y / 4; - uint8_t *d = static_cast(texture) + - block_y * pitch * 4 + block_x * 32 + (y % 4) * 8 + (x % 4) * 2; + void store() override { + int block_x = m_x / 4; + int block_y = m_y / 4; + uint8_t *d = static_cast(m_data) + + block_y * m_pitch * 4 + block_x * 32 + (m_y % 4) * 8 + (m_x % 4) * 2; d[0] = byte0(); d[1] = byte1(); + m_x++; + if (m_x == m_start_x + m_width) { + m_y++; + m_x = m_start_x; + } } static inline int compute_pitch(int width) { @@ -173,12 +202,17 @@ struct Texel8: public Texel { Texel8() = default; void setByte(uint8_t b) { value = b; } - void store(void *texture, int x, int y, int pitch) override { - int block_x = x / 8; - int block_y = y / 4; - uint8_t *d = static_cast(texture) + - block_y * pitch * 4 + block_x * 32 + (y % 4) * 8 + (x % 8); + void store() override { + int block_x = m_x / 8; + int block_y = m_y / 4; + uint8_t *d = static_cast(m_data) + + block_y * m_pitch * 4 + block_x * 32 + (m_y % 4) * 8 + (m_x % 8); d[0] = value; + m_x++; + if (m_x == m_start_x + m_width) { + m_y++; + m_x = m_start_x; + } } static inline int compute_pitch(int width) { @@ -217,16 +251,16 @@ struct TexelI4: public Texel { TexelI4() = default; void set_luminance(uint8_t luminance) { value = luminance >> 4; } - void store(void *texture, int x, int y, int pitch) override { - int block_x = x / 8; - int block_y = y / 8; - uint8_t *d = static_cast(texture) + - block_y * pitch * 8 + block_x * 32 + (y % 8) * 4 + (x % 8) / 2; + void store() override { + int block_x = m_x / 8; + int block_y = m_y / 8; + uint8_t *d = static_cast(m_data) + + block_y * m_pitch * 8 + block_x * 32 + (m_y % 8) * 4 + (m_x % 8) / 2; uint8_t texel_pair = d[0]; /* This is extremely inefficient! TODO: rework the Texel classes so * that they operate as a stream writer, similarly to the reader * classes. */ - if (x % 2 == 1) { + if (m_x % 2 == 1) { texel_pair &= 0xf0; texel_pair |= (value & 0xf); } else { @@ -234,6 +268,11 @@ struct TexelI4: public Texel { texel_pair |= (value << 4); } d[0] = texel_pair; + m_x++; + if (m_x == m_start_x + m_width) { + m_y++; + m_x = m_start_x; + } } static inline int compute_pitch(int width) { @@ -576,12 +615,13 @@ void load_texture_typed(const void *src, int width, int height, glparamstate.unpack_row_length : width; int srcpitch = READER::pitch_for_width(row_length); + TEXEL texel; + texel.set_area(dest, x, y, width, height, dstpitch); for (int ry = 0; ry < height; ry++) { const DataType *srcline = READER::row_ptr(src, ry, srcpitch); for (int rx = 0; rx < width; rx++) { - TEXEL texel; srcline = READER::read(srcline, texel); - texel.store(dest, rx + x, ry + y, dstpitch); + texel.store(); } } } @@ -797,6 +837,7 @@ void _ogx_bytes_to_texture(const void *data, GLenum format, GLenum type, skip_pixels_after = row_length - width; } + texel->set_area(dst, x, y, width, height, dstpitch); for (int ry = 0; ry < height; ry++) { if (ry > 0) { for (int i = 0; i < skip_pixels_after; i++) { @@ -806,7 +847,7 @@ void _ogx_bytes_to_texture(const void *data, GLenum format, GLenum type, for (int rx = 0; rx < width; rx++) { GXColor c = reader->read(); texel->set_color(c); - texel->store(dst, x + rx, y + ry, dstpitch); + texel->store(); } } } From e852488e9befafa603dce73c314c4826d4ccb1ec Mon Sep 17 00:00:00 2001 From: Alberto Mardegan Date: Tue, 15 Oct 2024 19:41:25 +0300 Subject: [PATCH 04/17] pixels: optimize writing of I4 textures --- src/pixels.cpp | 51 +++++++++++++++++++++++++++++++++++++------------- 1 file changed, 38 insertions(+), 13 deletions(-) diff --git a/src/pixels.cpp b/src/pixels.cpp index 370ded3..cd62b75 100644 --- a/src/pixels.cpp +++ b/src/pixels.cpp @@ -251,27 +251,48 @@ struct TexelI4: public Texel { TexelI4() = default; void set_luminance(uint8_t luminance) { value = luminance >> 4; } - void store() override { + uint8_t *current_address() const { int block_x = m_x / 8; int block_y = m_y / 8; - uint8_t *d = static_cast(m_data) + + return static_cast(m_data) + block_y * m_pitch * 8 + block_x * 32 + (m_y % 8) * 4 + (m_x % 8) / 2; - uint8_t texel_pair = d[0]; - /* This is extremely inefficient! TODO: rework the Texel classes so - * that they operate as a stream writer, similarly to the reader - * classes. */ - if (m_x % 2 == 1) { - texel_pair &= 0xf0; - texel_pair |= (value & 0xf); + } + + void read_first_odd_pixel_in_line() { + if (m_start_x % 2 != 0) { + /* We start drawing at the second half of a byte, so read the first + * half which we need to preserve */ + uint8_t *d = current_address(); + last_texel = d[0] & 0xf0; + } + } + + virtual void set_area(void *data, int x, int y, int width, int height, + int pitch) { + Texel::set_area(data, x, y, width, height, pitch); + read_first_odd_pixel_in_line(); + } + + void store() override { + uint8_t *d = nullptr; + if (m_x % 2 == 0) { + last_texel = value << 4; } else { - texel_pair &= 0x0f; - texel_pair |= (value << 4); + d = current_address(); + d[0] = last_texel | (value & 0xf); } - d[0] = texel_pair; m_x++; - if (m_x == m_start_x + m_width) { + if (m_x == m_start_x + m_width) { /* new line */ + if (!d) { /* write the lonely last pixel of this line*/ + d = current_address(); + uint8_t b = d[0] & 0xf; + d[0] = b | last_texel; + } m_y++; m_x = m_start_x; + if (m_y < m_start_y + m_height) { + read_first_odd_pixel_in_line(); + } } } @@ -285,6 +306,7 @@ struct TexelI4: public Texel { void set_color(GXColor c) override { set_luminance(c.r); } uint8_t value; + uint8_t last_texel; }; template static inline uint8_t component(T value); @@ -697,6 +719,9 @@ void _ogx_bytes_to_texture(const void *data, GLenum format, GLenum type, void *dst, uint32_t gx_format, int x, int y, int dstpitch) { + /* Skip degenerate cases */ + if (width <= 0 || height <= 0) return; + /* The GL_UNPACK_SKIP_ROWS and GL_UNPACK_SKIP_PIXELS can be handled here by * modifiying the source data pointer. */ bool need_skip_pixels = false; From f65cf1afc83c647092183338d189d1aa3d2c70e9 Mon Sep 17 00:00:00 2001 From: Alberto Mardegan Date: Tue, 15 Oct 2024 23:01:44 +0300 Subject: [PATCH 05/17] pixels: simplify handling of 16-bit texels It's easier if we consider the texel as a single 16-bit element, rather than two different bytes. --- src/pixels.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/pixels.cpp b/src/pixels.cpp index cd62b75..d9b7daa 100644 --- a/src/pixels.cpp +++ b/src/pixels.cpp @@ -141,13 +141,16 @@ struct Texel16: public Texel { Texel16() = default; void setWord(uint16_t value) { word = value; } - void store() override { + uint16_t *current_address() { int block_x = m_x / 4; int block_y = m_y / 4; - uint8_t *d = static_cast(m_data) + - block_y * m_pitch * 4 + block_x * 32 + (m_y % 4) * 8 + (m_x % 4) * 2; - d[0] = byte0(); - d[1] = byte1(); + return reinterpret_cast(static_cast(m_data) + + block_y * m_pitch * 4 + block_x * 32 + (m_y % 4) * 8 + (m_x % 4) * 2); + } + + void store() override { + uint16_t *d = current_address(); + d[0] = word; m_x++; if (m_x == m_start_x + m_width) { m_y++; @@ -162,9 +165,6 @@ struct Texel16: public Texel { int pitch_for_width(int width) override { return compute_pitch(width); } - uint8_t byte0() const { return word >> 8; } - uint8_t byte1() const { return word & 0xff; } - uint16_t word; }; From 16166003b8bd15f65bfbe367e64d06bdfcaa82fa Mon Sep 17 00:00:00 2001 From: Alberto Mardegan Date: Wed, 16 Oct 2024 21:54:51 +0300 Subject: [PATCH 06/17] Move texel code to separate header This code is all about C++ templates, which we will be using from another file, too. --- CMakeLists.txt | 1 + src/pixels.cpp | 251 +----------------------------------------- src/texel.h | 291 +++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 293 insertions(+), 250 deletions(-) create mode 100644 src/texel.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 827dfca..f052ea9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -44,6 +44,7 @@ add_library(${TARGET} STATIC src/state.h src/stencil.c src/stencil.h + src/texel.h src/texture.c src/utils.h src/vertex.cpp diff --git a/src/pixels.cpp b/src/pixels.cpp index d9b7daa..6d6c5c1 100644 --- a/src/pixels.cpp +++ b/src/pixels.cpp @@ -35,6 +35,7 @@ POSSIBILITY OF SUCH DAMAGE. #include "debug.h" #include "opengx.h" #include "state.h" +#include "texel.h" #include #include @@ -59,256 +60,6 @@ static struct FastConversion { 0, }; -static inline uint8_t luminance_from_rgb(uint8_t r, uint8_t g, uint8_t b) -{ - /* TODO: fix this, the three components do not have the same weight to the - * human eye. Use a formula from - * https://songho.ca/dsp/luminance/luminance.html - */ - int luminance = int(r) + int(g) + int(b); - return luminance / 3; -} - -struct Texel { - virtual void set_color(GXColor color) = 0; - virtual void store() = 0; - virtual int pitch_for_width(int width) = 0; - - virtual void set_area(void *data, int x, int y, int width, int height, - int pitch) { - m_data = data; - m_start_x = m_x = x; - m_start_y = m_y = y; - m_width = width; - m_height = height; - m_pitch = pitch; - } - - void *m_data; - int m_x; - int m_y; - int m_start_x; - int m_start_y; - int m_width; - int m_height; - int m_pitch; -}; - -struct TexelRGBA8: public Texel { - static constexpr bool has_rgb = true; - static constexpr bool has_alpha = true; - static constexpr bool has_luminance = false; - - TexelRGBA8() = default; - void set_color(uint8_t red, uint8_t green, uint8_t blue, uint8_t alpha) { - r = red; g = green; b =blue; a = alpha; - } - - void set_color(GXColor c) override { - set_color(c.r, c.g, c.b, c.a); - } - - void store() override { - int block_x = m_x / 4; - int block_y = m_y / 4; - uint8_t *d = static_cast(m_data) + - block_y * m_pitch * 4 + block_x * 64 + (m_y % 4) * 8 + (m_x % 4) * 2; - d[0] = a; - d[1] = r; - d[32] = g; - d[33] = b; - m_x++; - if (m_x == m_start_x + m_width) { - m_y++; - m_x = m_start_x; - } - } - - static inline int compute_pitch(int width) { - /* texel are in pairs of 4x4 blocks, each element 2 bytes wide */ - return ((width + 3) / 4) * 16; - } - - int pitch_for_width(int width) override { return compute_pitch(width); } - - uint8_t r; - uint8_t g; - uint8_t b; - uint8_t a; -}; - -struct Texel16: public Texel { - Texel16() = default; - void setWord(uint16_t value) { word = value; } - - uint16_t *current_address() { - int block_x = m_x / 4; - int block_y = m_y / 4; - return reinterpret_cast(static_cast(m_data) + - block_y * m_pitch * 4 + block_x * 32 + (m_y % 4) * 8 + (m_x % 4) * 2); - } - - void store() override { - uint16_t *d = current_address(); - d[0] = word; - m_x++; - if (m_x == m_start_x + m_width) { - m_y++; - m_x = m_start_x; - } - } - - static inline int compute_pitch(int width) { - /* texel are in 4x4 blocks, each element 2 bytes wide */ - return ((width + 3) / 4) * 8; - } - - int pitch_for_width(int width) override { return compute_pitch(width); } - - uint16_t word; -}; - -struct TexelIA8: public Texel16 { - static constexpr bool has_rgb = false; - static constexpr bool has_alpha = true; - static constexpr bool has_luminance = true; - - TexelIA8() = default; - void set_luminance_alpha(uint8_t luminance, uint8_t alpha) { - setWord((alpha << 8) | luminance); - } - - void set_color(GXColor c) override { - int luminance = luminance_from_rgb(c.r, c.g, c.b); - set_luminance_alpha(luminance, c.a); - } -}; - -struct TexelRGB565: public Texel16 { - static constexpr bool has_alpha = false; - static constexpr bool has_rgb = true; - - TexelRGB565() = default; - void set_color(uint8_t r, uint8_t g, uint8_t b) { - setWord(((r & 0xf8) << 8) | - ((g & 0xfc) << 3) | - ((b & 0xf8) >> 3)); - } - - void set_color(GXColor c) override { set_color(c.r, c.g, c.b); } -}; - -struct Texel8: public Texel { - Texel8() = default; - void setByte(uint8_t b) { value = b; } - - void store() override { - int block_x = m_x / 8; - int block_y = m_y / 4; - uint8_t *d = static_cast(m_data) + - block_y * m_pitch * 4 + block_x * 32 + (m_y % 4) * 8 + (m_x % 8); - d[0] = value; - m_x++; - if (m_x == m_start_x + m_width) { - m_y++; - m_x = m_start_x; - } - } - - static inline int compute_pitch(int width) { - /* texel are in 8x4 blocks, each element 1 bytes wide */ - return ((width + 7) / 8) * 8; - } - - int pitch_for_width(int width) override { return compute_pitch(width); } - - uint8_t value; -}; - -struct TexelI8: public Texel8 { - static constexpr bool has_rgb = false; - static constexpr bool has_alpha = false; - static constexpr bool has_luminance = true; - - using Texel8::Texel8; - void set_color(GXColor c) override { - setByte(luminance_from_rgb(c.r, c.g, c.b)); - } - void set_luminance(uint8_t l) { setByte(l); } -}; - -struct TexelA8: public Texel8 { - static constexpr bool has_rgb = false; - static constexpr bool has_alpha = true; - static constexpr bool has_luminance = false; - - using Texel8::Texel8; - void set_color(GXColor c) override { setByte(c.a); } - void set_alpha(uint8_t a) { setByte(a); } -}; - -struct TexelI4: public Texel { - TexelI4() = default; - void set_luminance(uint8_t luminance) { value = luminance >> 4; } - - uint8_t *current_address() const { - int block_x = m_x / 8; - int block_y = m_y / 8; - return static_cast(m_data) + - block_y * m_pitch * 8 + block_x * 32 + (m_y % 8) * 4 + (m_x % 8) / 2; - } - - void read_first_odd_pixel_in_line() { - if (m_start_x % 2 != 0) { - /* We start drawing at the second half of a byte, so read the first - * half which we need to preserve */ - uint8_t *d = current_address(); - last_texel = d[0] & 0xf0; - } - } - - virtual void set_area(void *data, int x, int y, int width, int height, - int pitch) { - Texel::set_area(data, x, y, width, height, pitch); - read_first_odd_pixel_in_line(); - } - - void store() override { - uint8_t *d = nullptr; - if (m_x % 2 == 0) { - last_texel = value << 4; - } else { - d = current_address(); - d[0] = last_texel | (value & 0xf); - } - m_x++; - if (m_x == m_start_x + m_width) { /* new line */ - if (!d) { /* write the lonely last pixel of this line*/ - d = current_address(); - uint8_t b = d[0] & 0xf; - d[0] = b | last_texel; - } - m_y++; - m_x = m_start_x; - if (m_y < m_start_y + m_height) { - read_first_odd_pixel_in_line(); - } - } - } - - static inline int compute_pitch(int width) { - /* texel are in 8x8 blocks, each element 4 bits wide */ - return ((width + 7) / 8) * 4; - } - - int pitch_for_width(int width) override { return compute_pitch(width); } - - void set_color(GXColor c) override { set_luminance(c.r); } - - uint8_t value; - uint8_t last_texel; -}; - template static inline uint8_t component(T value); template <> inline uint8_t component(uint8_t value) { return value; } template <> inline uint8_t component(uint16_t value) { return value >> 8; } diff --git a/src/texel.h b/src/texel.h new file mode 100644 index 0000000..538cc94 --- /dev/null +++ b/src/texel.h @@ -0,0 +1,291 @@ +/***************************************************************************** +Copyright (c) 2024 Alberto Mardegan (mardy@users.sourceforge.net) +All rights reserved. + +Attention! Contains pieces of code from others such as Mesa and GRRLib + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. +3. Neither the name of copyright holders nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED +TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL COPYRIGHT HOLDERS OR CONTRIBUTORS +BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +*****************************************************************************/ + +#ifndef OPENGX_TEXEL_H +#define OPENGX_TEXEL_H + +#include "opengx.h" + +#include +#include +#include + +static inline uint8_t luminance_from_rgb(uint8_t r, uint8_t g, uint8_t b) +{ + /* TODO: fix this, the three components do not have the same weight to the + * human eye. Use a formula from + * https://songho.ca/dsp/luminance/luminance.html + */ + int luminance = int(r) + int(g) + int(b); + return luminance / 3; +} + +struct Texel { + virtual void set_color(GXColor color) = 0; + virtual void store() = 0; + virtual int pitch_for_width(int width) = 0; + + virtual void set_area(void *data, int x, int y, int width, int height, + int pitch) { + m_data = data; + m_start_x = m_x = x; + m_start_y = m_y = y; + m_width = width; + m_height = height; + m_pitch = pitch; + } + + void *m_data; + int m_x; + int m_y; + int m_start_x; + int m_start_y; + int m_width; + int m_height; + int m_pitch; +}; + +struct TexelRGBA8: public Texel { + static constexpr bool has_rgb = true; + static constexpr bool has_alpha = true; + static constexpr bool has_luminance = false; + + TexelRGBA8() = default; + void set_color(uint8_t red, uint8_t green, uint8_t blue, uint8_t alpha) { + r = red; g = green; b =blue; a = alpha; + } + + void set_color(GXColor c) override { + set_color(c.r, c.g, c.b, c.a); + } + + void store() override { + int block_x = m_x / 4; + int block_y = m_y / 4; + uint8_t *d = static_cast(m_data) + + block_y * m_pitch * 4 + block_x * 64 + (m_y % 4) * 8 + (m_x % 4) * 2; + d[0] = a; + d[1] = r; + d[32] = g; + d[33] = b; + m_x++; + if (m_x == m_start_x + m_width) { + m_y++; + m_x = m_start_x; + } + } + + static inline int compute_pitch(int width) { + /* texel are in pairs of 4x4 blocks, each element 2 bytes wide */ + return ((width + 3) / 4) * 16; + } + + int pitch_for_width(int width) override { return compute_pitch(width); } + + uint8_t r; + uint8_t g; + uint8_t b; + uint8_t a; +}; + +struct Texel16: public Texel { + Texel16() = default; + void setWord(uint16_t value) { word = value; } + + uint16_t *current_address() { + int block_x = m_x / 4; + int block_y = m_y / 4; + return reinterpret_cast(static_cast(m_data) + + block_y * m_pitch * 4 + block_x * 32 + (m_y % 4) * 8 + (m_x % 4) * 2); + } + + void store() override { + uint16_t *d = current_address(); + d[0] = word; + m_x++; + if (m_x == m_start_x + m_width) { + m_y++; + m_x = m_start_x; + } + } + + static inline int compute_pitch(int width) { + /* texel are in 4x4 blocks, each element 2 bytes wide */ + return ((width + 3) / 4) * 8; + } + + int pitch_for_width(int width) override { return compute_pitch(width); } + + uint16_t word; +}; + +struct TexelIA8: public Texel16 { + static constexpr bool has_rgb = false; + static constexpr bool has_alpha = true; + static constexpr bool has_luminance = true; + + TexelIA8() = default; + void set_luminance_alpha(uint8_t luminance, uint8_t alpha) { + setWord((alpha << 8) | luminance); + } + + void set_color(GXColor c) override { + int luminance = luminance_from_rgb(c.r, c.g, c.b); + set_luminance_alpha(luminance, c.a); + } +}; + +struct TexelRGB565: public Texel16 { + static constexpr bool has_alpha = false; + static constexpr bool has_rgb = true; + + TexelRGB565() = default; + void set_color(uint8_t r, uint8_t g, uint8_t b) { + setWord(((r & 0xf8) << 8) | + ((g & 0xfc) << 3) | + ((b & 0xf8) >> 3)); + } + + void set_color(GXColor c) override { set_color(c.r, c.g, c.b); } +}; + +struct Texel8: public Texel { + Texel8() = default; + void setByte(uint8_t b) { value = b; } + + void store() override { + int block_x = m_x / 8; + int block_y = m_y / 4; + uint8_t *d = static_cast(m_data) + + block_y * m_pitch * 4 + block_x * 32 + (m_y % 4) * 8 + (m_x % 8); + d[0] = value; + m_x++; + if (m_x == m_start_x + m_width) { + m_y++; + m_x = m_start_x; + } + } + + static inline int compute_pitch(int width) { + /* texel are in 8x4 blocks, each element 1 bytes wide */ + return ((width + 7) / 8) * 8; + } + + int pitch_for_width(int width) override { return compute_pitch(width); } + + uint8_t value; +}; + +struct TexelI8: public Texel8 { + static constexpr bool has_rgb = false; + static constexpr bool has_alpha = false; + static constexpr bool has_luminance = true; + + using Texel8::Texel8; + void set_color(GXColor c) override { + setByte(luminance_from_rgb(c.r, c.g, c.b)); + } + void set_luminance(uint8_t l) { setByte(l); } +}; + +struct TexelA8: public Texel8 { + static constexpr bool has_rgb = false; + static constexpr bool has_alpha = true; + static constexpr bool has_luminance = false; + + using Texel8::Texel8; + void set_color(GXColor c) override { setByte(c.a); } + void set_alpha(uint8_t a) { setByte(a); } +}; + +struct TexelI4: public Texel { + TexelI4() = default; + void set_luminance(uint8_t luminance) { value = luminance >> 4; } + + uint8_t *current_address() const { + int block_x = m_x / 8; + int block_y = m_y / 8; + return static_cast(m_data) + + block_y * m_pitch * 8 + block_x * 32 + (m_y % 8) * 4 + (m_x % 8) / 2; + } + + void read_first_odd_pixel_in_line() { + if (m_start_x % 2 != 0) { + /* We start drawing at the second half of a byte, so read the first + * half which we need to preserve */ + uint8_t *d = current_address(); + last_texel = d[0] & 0xf0; + } + } + + virtual void set_area(void *data, int x, int y, int width, int height, + int pitch) { + Texel::set_area(data, x, y, width, height, pitch); + read_first_odd_pixel_in_line(); + } + + void store() override { + uint8_t *d = nullptr; + if (m_x % 2 == 0) { + last_texel = value << 4; + } else { + d = current_address(); + d[0] = last_texel | (value & 0xf); + } + m_x++; + if (m_x == m_start_x + m_width) { /* new line */ + if (!d) { /* write the lonely last pixel of this line*/ + d = current_address(); + uint8_t b = d[0] & 0xf; + d[0] = b | last_texel; + } + m_y++; + m_x = m_start_x; + if (m_y < m_start_y + m_height) { + read_first_odd_pixel_in_line(); + } + } + } + + static inline int compute_pitch(int width) { + /* texel are in 8x8 blocks, each element 4 bits wide */ + return ((width + 7) / 8) * 4; + } + + int pitch_for_width(int width) override { return compute_pitch(width); } + + void set_color(GXColor c) override { set_luminance(c.r); } + + uint8_t value; + uint8_t last_texel; +}; + +#endif /* OPENGX_TEXEL_H */ From d141c8523262c151c0ffd6e4d4e2babd798dfb86 Mon Sep 17 00:00:00 2001 From: Alberto Mardegan Date: Wed, 16 Oct 2024 22:00:51 +0300 Subject: [PATCH 07/17] texel: add read functionality This will be used in glReadPixels, where we have to convert an EFB image into OpenGL pixel data. --- src/texel.h | 97 ++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 78 insertions(+), 19 deletions(-) diff --git a/src/texel.h b/src/texel.h index 538cc94..91b11c2 100644 --- a/src/texel.h +++ b/src/texel.h @@ -51,6 +51,7 @@ static inline uint8_t luminance_from_rgb(uint8_t r, uint8_t g, uint8_t b) struct Texel { virtual void set_color(GXColor color) = 0; virtual void store() = 0; + virtual GXColor read() = 0; virtual int pitch_for_width(int width) = 0; virtual void set_area(void *data, int x, int y, int width, int height, @@ -63,6 +64,14 @@ struct Texel { m_pitch = pitch; } + void next() { + m_x++; + if (m_x == m_start_x + m_width) { + m_y++; + m_x = m_start_x; + } + } + void *m_data; int m_x; int m_y; @@ -87,20 +96,26 @@ struct TexelRGBA8: public Texel { set_color(c.r, c.g, c.b, c.a); } - void store() override { + uint8_t *current_address() { int block_x = m_x / 4; int block_y = m_y / 4; - uint8_t *d = static_cast(m_data) + + return static_cast(m_data) + block_y * m_pitch * 4 + block_x * 64 + (m_y % 4) * 8 + (m_x % 4) * 2; + } + + void store() override { + uint8_t *d = current_address(); d[0] = a; d[1] = r; d[32] = g; d[33] = b; - m_x++; - if (m_x == m_start_x + m_width) { - m_y++; - m_x = m_start_x; - } + next(); + } + + GXColor read() override { + uint8_t *d = current_address(); + next(); + return { d[1], d[32], d[33], d[0] }; } static inline int compute_pitch(int width) { @@ -130,11 +145,7 @@ struct Texel16: public Texel { void store() override { uint16_t *d = current_address(); d[0] = word; - m_x++; - if (m_x == m_start_x + m_width) { - m_y++; - m_x = m_start_x; - } + next(); } static inline int compute_pitch(int width) { @@ -161,6 +172,14 @@ struct TexelIA8: public Texel16 { int luminance = luminance_from_rgb(c.r, c.g, c.b); set_luminance_alpha(luminance, c.a); } + + GXColor read() override { + uint16_t *d = current_address(); + next(); + uint8_t alpha = *d >> 8; + uint8_t luminance = *d & 0xff; + return { luminance, luminance, luminance, alpha }; + } }; struct TexelRGB565: public Texel16 { @@ -175,23 +194,38 @@ struct TexelRGB565: public Texel16 { } void set_color(GXColor c) override { set_color(c.r, c.g, c.b); } + + GXColor read() override { + uint16_t *d = current_address(); + next(); + uint8_t red = (*d >> 8) & 0xf8; + uint8_t green = (*d >> 3) & 0xfc; + uint8_t blue = (*d << 3) & 0xf8; + /* fill the lowest bits by repeating the highest ones */ + return { + uint8_t(red | (red >> 5)), + uint8_t(green | (green >> 6)), + uint8_t(blue | (blue >> 5)), + 255 + }; + } }; struct Texel8: public Texel { Texel8() = default; void setByte(uint8_t b) { value = b; } - void store() override { + uint8_t *current_address() { int block_x = m_x / 8; int block_y = m_y / 4; - uint8_t *d = static_cast(m_data) + + return static_cast(m_data) + block_y * m_pitch * 4 + block_x * 32 + (m_y % 4) * 8 + (m_x % 8); + } + + void store() override { + uint8_t *d = current_address(); + next(); d[0] = value; - m_x++; - if (m_x == m_start_x + m_width) { - m_y++; - m_x = m_start_x; - } } static inline int compute_pitch(int width) { @@ -214,6 +248,12 @@ struct TexelI8: public Texel8 { setByte(luminance_from_rgb(c.r, c.g, c.b)); } void set_luminance(uint8_t l) { setByte(l); } + + GXColor read() override { + uint8_t *d = current_address(); + next(); + return { d[0], d[0], d[0], 255 }; + } }; struct TexelA8: public Texel8 { @@ -224,6 +264,12 @@ struct TexelA8: public Texel8 { using Texel8::Texel8; void set_color(GXColor c) override { setByte(c.a); } void set_alpha(uint8_t a) { setByte(a); } + + GXColor read() override { + uint8_t *d = current_address(); + next(); + return { 255, 255, 255, d[0] }; + } }; struct TexelI4: public Texel { @@ -275,6 +321,19 @@ struct TexelI4: public Texel { } } + GXColor read() override { + uint8_t *d = current_address(); + uint8_t c = m_x % 2 == 0 ? (d[0] & 0xf0) : (d[0] << 4); + m_x++; + if (m_x == m_start_x + m_width) { /* new line */ + m_y++; + m_x = m_start_x; + } + /* fill the lowest bits by repeating the highest ones */ + c |= (c >> 4); + return { c, c, c, 255 }; + } + static inline int compute_pitch(int width) { /* texel are in 8x8 blocks, each element 4 bits wide */ return ((width + 7) / 8) * 4; From 4847927f65e520e8e3789104e5fd12775f9f562f Mon Sep 17 00:00:00 2001 From: Alberto Mardegan Date: Thu, 17 Oct 2024 17:45:49 +0300 Subject: [PATCH 08/17] pixels: move OpenGL types and formats to header file These can be used by raster.c as well. --- CMakeLists.txt | 2 + src/pixel_stream.cpp | 63 ++++++++++ src/pixel_stream.h | 246 +++++++++++++++++++++++++++++++++++++++ src/pixels.cpp | 266 ++++--------------------------------------- src/pixels.h | 1 - 5 files changed, 333 insertions(+), 245 deletions(-) create mode 100644 src/pixel_stream.cpp create mode 100644 src/pixel_stream.h diff --git a/CMakeLists.txt b/CMakeLists.txt index f052ea9..bd712bd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -37,6 +37,8 @@ add_library(${TARGET} STATIC src/image_DXT.c src/image_DXT.h src/opengx.h + src/pixel_stream.cpp + src/pixel_stream.h src/pixels.cpp src/pixels.h src/raster.cpp diff --git a/src/pixel_stream.cpp b/src/pixel_stream.cpp new file mode 100644 index 0000000..1874e7c --- /dev/null +++ b/src/pixel_stream.cpp @@ -0,0 +1,63 @@ +/***************************************************************************** +Copyright (c) 2024 Alberto Mardegan (mardy@users.sourceforge.net) +All rights reserved. + +Attention! Contains pieces of code from others such as Mesa and GRRLib + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. +3. Neither the name of copyright holders nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED +TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL COPYRIGHT HOLDERS OR CONTRIBUTORS +BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +*****************************************************************************/ + +#include "pixel_stream.h" + +const struct MasksPerType _ogx_pixels_masks_per_type[] = { + {GL_UNSIGNED_BYTE_3_3_2, 1, 3, 3, 2, 0, 0, 3, 6, 0 }, + {GL_UNSIGNED_BYTE_2_3_3_REV, 1, 3, 3, 2, 0, 5, 2, 0, 0 }, + {GL_UNSIGNED_SHORT_5_6_5, 2, 5, 6, 5, 0, 0, 5, 11, 0 }, + {GL_UNSIGNED_SHORT_5_6_5_REV, 2, 5, 6, 5, 0, 11, 5, 0, 0 }, + {GL_UNSIGNED_SHORT_4_4_4_4, 2, 4, 4, 4, 4, 0, 4, 8, 12 }, + {GL_UNSIGNED_SHORT_4_4_4_4_REV, 2, 4, 4, 4, 4, 12, 8, 4, 0 }, + {GL_UNSIGNED_SHORT_5_5_5_1, 2, 5, 5, 5, 1, 0, 5, 10, 15 }, + {GL_UNSIGNED_SHORT_1_5_5_5_REV, 2, 5, 5, 5, 1, 11, 6, 1, 0 }, + {GL_UNSIGNED_INT_8_8_8_8, 4, 8, 8, 8, 8, 0, 8, 16, 24 }, + {GL_UNSIGNED_INT_8_8_8_8_REV, 4, 8, 8, 8, 8, 24, 16, 8, 0 }, + {GL_UNSIGNED_INT_10_10_10_2, 4, 10, 10, 10, 2, 0, 10, 20, 30 }, + {GL_UNSIGNED_INT_2_10_10_10_REV, 4, 10, 10, 10, 2, 22, 12, 2, 0 }, + {0, } +}; + +const struct ComponentsPerFormat _ogx_pixels_components_per_format[] = { + { GL_RGBA, 4, { 0, 1, 2, 3 }}, + { GL_BGRA, 4, { 2, 1, 0, 3 }}, + { GL_RGB, 3, { 0, 1, 2 }}, + { GL_BGR, 3, { 2, 1, 0 }}, + { GL_LUMINANCE_ALPHA, 2, { 0, 3 }}, + { GL_INTENSITY, 1, { 0 }}, + { GL_LUMINANCE, 1, { 0 }}, + { GL_RED, 1, { 0 }}, + { GL_GREEN, 1, { 1 }}, + { GL_BLUE, 1, { 2 }}, + { GL_ALPHA, 1, { 3 }}, + { 0, } +}; diff --git a/src/pixel_stream.h b/src/pixel_stream.h new file mode 100644 index 0000000..e9bdc84 --- /dev/null +++ b/src/pixel_stream.h @@ -0,0 +1,246 @@ +/***************************************************************************** +Copyright (c) 2024 Alberto Mardegan (mardy@users.sourceforge.net) +All rights reserved. + +Attention! Contains pieces of code from others such as Mesa and GRRLib + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. +3. Neither the name of copyright holders nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED +TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL COPYRIGHT HOLDERS OR CONTRIBUTORS +BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +*****************************************************************************/ + +#ifndef OPENGX_PIXEL_STREAM_H +#define OPENGX_PIXEL_STREAM_H + +#include +#include +#include + +#include "state.h" + +extern const struct ComponentsPerFormat { + GLenum format; + char components_per_pixel; + char component_index[4]; /* component role (0=red, ..., 3=alpha) */ +} _ogx_pixels_components_per_format[]; + +extern const struct MasksPerType { + GLenum type; + char bytes; /* number of bytes per pixel */ + char rbits; /* bits of data for each component */ + char gbits; + char bbits; + char abits; + char roff; /* offsets (relative to memory layout, not registers */ + char goff; + char boff; + char aoff; +} _ogx_pixels_masks_per_type[]; + +/* Base class for the generic reader: this is used as base class by the + * CompoundPixelStream and the GenericPixelStream classes below. + * + * Note that for the time being we assume the pitch to be the minimum required + * to store a row of pixels. + */ +struct PixelStreamBase { + virtual GXColor read() = 0; +}; + +template static inline uint8_t component(T value); +template <> inline uint8_t component(uint8_t value) { return value; } +template <> inline uint8_t component(uint16_t value) { return value >> 8; } +template <> inline uint8_t component(uint32_t value) { return value >> 24; } +template <> inline uint8_t component(float value) { + return (uint8_t)(int(std::clamp(value, 0.0f, 1.0f) * 255.0f) & 0xff); +} + +template static inline T glcomponent(uint8_t component); +template <> inline uint8_t glcomponent(uint8_t value) { return value; } +template <> inline uint16_t glcomponent(uint8_t value) { + return (value << 8) | value; +} +template <> inline uint32_t glcomponent(uint8_t value) { + return (value << 24) | (value << 16) | value; +} +template <> inline float glcomponent(uint8_t value) { return value / 255.0f; } + +/* This class handles reading of pixels stored in one of the formats listed + * above, where each pixel is packed in at most 32 bits. */ +struct CompoundPixelStream: public PixelStreamBase { + CompoundPixelStream() = default; + CompoundPixelStream(const void *data, GLenum format, GLenum type): + data(static_cast(data)), + format(format), + mask_data(*find_mask_per_type(type)) { + if (format == GL_BGR || format == GL_BGRA) { /* swap red and blue */ + char tmp = mask_data.roff; + mask_data.roff = mask_data.boff; + mask_data.boff = tmp; + } + rmask = compute_mask(mask_data.rbits, mask_data.roff); + gmask = compute_mask(mask_data.gbits, mask_data.goff); + bmask = compute_mask(mask_data.bbits, mask_data.boff); + amask = compute_mask(mask_data.abits, mask_data.aoff); + } + + static const MasksPerType *find_mask_per_type(GLenum type) { + for (int i = 0; _ogx_pixels_masks_per_type[i].type != 0; i++) { + if (_ogx_pixels_masks_per_type[i].type == type) { + return &_ogx_pixels_masks_per_type[i]; + } + } + return nullptr; + } + + inline uint32_t compute_mask(int nbits, int offset) { + uint32_t mask = (1 << nbits) - 1; + return mask << (mask_data.bytes * 8 - (nbits + offset)); + } + + inline uint8_t read_component(uint32_t pixel, uint32_t mask, + int nbits, int offset) { + uint32_t value = pixel & mask; + int shift = mask_data.bytes * 8 - offset - 8; + uint8_t c = shift > 0 ? (value >> shift) : (value << -shift); + if (nbits < 8) { + c |= (c >> nbits); + } + return c; + } + + inline uint32_t read_pixel() const { + uint32_t pixel = 0; + for (int i = 0; i < 4; i++) { + if (i < mask_data.bytes) { + pixel <<= 8; + pixel |= data[n_read + i]; + } + } + return pixel; + } + + GXColor read() override { + uint32_t pixel = read_pixel(); + GXColor c; + c.r = read_component(pixel, rmask, mask_data.rbits, mask_data.roff); + c.g = read_component(pixel, gmask, mask_data.gbits, mask_data.goff); + c.b = read_component(pixel, bmask, mask_data.bbits, mask_data.boff); + if (mask_data.abits > 0) { + c.a = read_component(pixel, amask, mask_data.abits, mask_data.aoff); + } else { + c.a = 255; + } + n_read += mask_data.bytes; + return c; + } + + const char *data; + int n_read = 0; + uint32_t rmask; + uint32_t gmask; + uint32_t bmask; + uint32_t amask; + MasksPerType mask_data; + GLenum format; +}; + +/* This class handles reading of pixels from bitmap (1-bit depth) */ +struct BitmapPixelStream: public PixelStreamBase { + BitmapPixelStream() = default; + /* The OpenGL spec fixes the format of bitmaps to GL_COLOR_INDEX, so no + * need to have it as a parameter here */ + BitmapPixelStream(const void *data): + data(static_cast(data)) { + // TODO: add handling of row width and row alignment (to all readers!) + } + + inline uint8_t read_pixel() const { + uint8_t byte = data[n_read / 8]; + int shift = glparamstate.unpack_lsb_first ? + (n_read % 8) : (7 - n_read % 8); + bool bit = (byte >> shift) & 0x1; + return bit ? 255 : 0; + } + + GXColor read() override { + uint8_t pixel = read_pixel(); + n_read++; + return { pixel, pixel, pixel, 255 }; + } + + const uint8_t *data; + int n_read = 0; +}; + +/* This is a generic class to read pixels whose components are expressed by 8, + * 16, 32 bit wide integers or by 32 bit floats. + */ +template +struct GenericPixelStream: public PixelStreamBase { + GenericPixelStream(const void *data, GLenum format, GLenum type): + data(static_cast(data)), format(format), + component_data(*find_component_data(format)) {} + + static const ComponentsPerFormat *find_component_data(GLenum format) { + for (int i = 0; _ogx_pixels_components_per_format[i].format != 0; i++) { + if (_ogx_pixels_components_per_format[i].format == format) { + return &_ogx_pixels_components_per_format[i]; + } + } + return nullptr; + } + + int pitch_for_width(int width) { + return width * component_data.components_per_pixel * sizeof(T); + } + + GXColor read() override { + union { + uint8_t components[4]; + GXColor c; + } pixel = { 0, 0, 0, 255 }; + + const ComponentsPerFormat &cd = component_data; + for (int i = 0; i < cd.components_per_pixel; i++) { + pixel.components[cd.component_index[i]] = component(data[n_read++]); + } + + /* Some formats require a special handling */ + if (cd.format == GL_INTENSITY || + cd.format == GL_LUMINANCE || + cd.format == GL_LUMINANCE_ALPHA) { + pixel.c.g = pixel.c.b = pixel.c.r; + if (cd.format == GL_INTENSITY) pixel.c.a = pixel.c.r; + } + + return pixel.c; + } + + const T *data; + GLenum format; + int n_read = 0; + ComponentsPerFormat component_data; +}; + +#endif /* OPENGX_PIXEL_STREAM_H */ diff --git a/src/pixels.cpp b/src/pixels.cpp index 6d6c5c1..49c60c6 100644 --- a/src/pixels.cpp +++ b/src/pixels.cpp @@ -34,10 +34,10 @@ POSSIBILITY OF SUCH DAMAGE. #include "debug.h" #include "opengx.h" +#include "pixel_stream.h" #include "state.h" #include "texel.h" -#include #include #include #include @@ -60,14 +60,6 @@ static struct FastConversion { 0, }; -template static inline uint8_t component(T value); -template <> inline uint8_t component(uint8_t value) { return value; } -template <> inline uint8_t component(uint16_t value) { return value >> 8; } -template <> inline uint8_t component(uint32_t value) { return value >> 24; } -template <> inline uint8_t component(float value) { - return (uint8_t)(int(std::clamp(value, 0.0f, 1.0f) * 255.0f) & 0xff); -} - /* This template class is used to perform reading of a pixel and storing it in * the desired texture format in a single go. It does that in about 1/5th of * the time the generic algorithm (on Dolphin the difference is even bigger, up @@ -163,220 +155,6 @@ using DataReaderIntensity = DataReader; template using DataReaderAlpha = DataReader; -/* Base class for the generic reader: this is used as base class by the - * CompoundDataReader and the GenericDataReader classes below. - * - * Note that for the time being we assume the pitch to be the minimum required - * to store a row of pixels. - */ -struct DataReaderBase { - virtual GXColor read() = 0; -}; - -static const struct MasksPerType { - GLenum type; - char bytes; /* number of bytes per pixel */ - char rbits; /* bits of data for each component */ - char gbits; - char bbits; - char abits; - char roff; /* offsets (relative to memory layout, not registers */ - char goff; - char boff; - char aoff; -} s_masks_per_type[] = { - {GL_UNSIGNED_BYTE_3_3_2, 1, 3, 3, 2, 0, 0, 3, 6, 0 }, - {GL_UNSIGNED_BYTE_2_3_3_REV, 1, 3, 3, 2, 0, 5, 2, 0, 0 }, - {GL_UNSIGNED_SHORT_5_6_5, 2, 5, 6, 5, 0, 0, 5, 11, 0 }, - {GL_UNSIGNED_SHORT_5_6_5_REV, 2, 5, 6, 5, 0, 11, 5, 0, 0 }, - {GL_UNSIGNED_SHORT_4_4_4_4, 2, 4, 4, 4, 4, 0, 4, 8, 12 }, - {GL_UNSIGNED_SHORT_4_4_4_4_REV, 2, 4, 4, 4, 4, 12, 8, 4, 0 }, - {GL_UNSIGNED_SHORT_5_5_5_1, 2, 5, 5, 5, 1, 0, 5, 10, 15 }, - {GL_UNSIGNED_SHORT_1_5_5_5_REV, 2, 5, 5, 5, 1, 11, 6, 1, 0 }, - {GL_UNSIGNED_INT_8_8_8_8, 4, 8, 8, 8, 8, 0, 8, 16, 24 }, - {GL_UNSIGNED_INT_8_8_8_8_REV, 4, 8, 8, 8, 8, 24, 16, 8, 0 }, - {GL_UNSIGNED_INT_10_10_10_2, 4, 10, 10, 10, 2, 0, 10, 20, 30 }, - {GL_UNSIGNED_INT_2_10_10_10_REV, 4, 10, 10, 10, 2, 22, 12, 2, 0 }, - {0, } -}; - -/* This class handles reading of pixels stored in one of the formats listed - * above, where each pixel is packed in at most 32 bits. */ -struct CompoundDataReader: public DataReaderBase { - CompoundDataReader() = default; - CompoundDataReader(const void *data, GLenum format, GLenum type): - data(static_cast(data)), - format(format), - mask_data(*find_mask_per_type(type)) { - if (format == GL_BGR || format == GL_BGRA) { /* swap red and blue */ - char tmp = mask_data.roff; - mask_data.roff = mask_data.boff; - mask_data.boff = tmp; - } - rmask = compute_mask(mask_data.rbits, mask_data.roff); - gmask = compute_mask(mask_data.gbits, mask_data.goff); - bmask = compute_mask(mask_data.bbits, mask_data.boff); - amask = compute_mask(mask_data.abits, mask_data.aoff); - } - - static const MasksPerType *find_mask_per_type(GLenum type) { - for (int i = 0; s_masks_per_type[i].type != 0; i++) { - if (s_masks_per_type[i].type == type) { - return &s_masks_per_type[i]; - } - } - return nullptr; - } - - inline uint32_t compute_mask(int nbits, int offset) { - uint32_t mask = (1 << nbits) - 1; - return mask << (mask_data.bytes * 8 - (nbits + offset)); - } - - inline uint8_t read_component(uint32_t pixel, uint32_t mask, - int nbits, int offset) { - uint32_t value = pixel & mask; - int shift = mask_data.bytes * 8 - offset - 8; - uint8_t c = shift > 0 ? (value >> shift) : (value << -shift); - if (nbits < 8) { - c |= (c >> nbits); - } - return c; - } - - inline uint32_t read_pixel() const { - uint32_t pixel = 0; - for (int i = 0; i < 4; i++) { - if (i < mask_data.bytes) { - pixel <<= 8; - pixel |= data[n_read + i]; - } - } - return pixel; - } - - GXColor read() override { - uint32_t pixel = read_pixel(); - GXColor c; - c.r = read_component(pixel, rmask, mask_data.rbits, mask_data.roff); - c.g = read_component(pixel, gmask, mask_data.gbits, mask_data.goff); - c.b = read_component(pixel, bmask, mask_data.bbits, mask_data.boff); - if (mask_data.abits > 0) { - c.a = read_component(pixel, amask, mask_data.abits, mask_data.aoff); - } else { - c.a = 255; - } - n_read += mask_data.bytes; - return c; - } - - const char *data; - int n_read = 0; - uint32_t rmask; - uint32_t gmask; - uint32_t bmask; - uint32_t amask; - MasksPerType mask_data; - GLenum format; -}; - -/* This class handles reading of pixels from bitmap (1-bit depth) */ -struct BitmapDataReader: public DataReaderBase { - BitmapDataReader() = default; - /* The OpenGL spec fixes the format of bitmaps to GL_COLOR_INDEX, so no - * need to have it as a parameter here */ - BitmapDataReader(const void *data): - data(static_cast(data)) { - // TODO: add handling of row width and row alignment (to all readers!) - } - - inline uint8_t read_pixel() const { - uint8_t byte = data[n_read / 8]; - int shift = glparamstate.unpack_lsb_first ? - (n_read % 8) : (7 - n_read % 8); - bool bit = (byte >> shift) & 0x1; - return bit ? 255 : 0; - } - - GXColor read() override { - uint8_t pixel = read_pixel(); - n_read++; - return { pixel, pixel, pixel, 255 }; - } - - const uint8_t *data; - int n_read = 0; -}; - -static const struct ComponentsPerFormat { - GLenum format; - char components_per_pixel; - char component_index[4]; /* component role (0=red, ..., 3=alpha) */ -} s_components_per_format[] = { - { GL_RGBA, 4, { 0, 1, 2, 3 }}, - { GL_BGRA, 4, { 2, 1, 0, 3 }}, - { GL_RGB, 3, { 0, 1, 2 }}, - { GL_BGR, 3, { 2, 1, 0 }}, - { GL_LUMINANCE_ALPHA, 2, { 0, 3 }}, - { GL_INTENSITY, 1, { 0 }}, - { GL_LUMINANCE, 1, { 0 }}, - { GL_RED, 1, { 0 }}, - { GL_GREEN, 1, { 1 }}, - { GL_BLUE, 1, { 2 }}, - { GL_ALPHA, 1, { 3 }}, - { 0, } -}; - -/* This is a generic class to read pixels whose components are expressed by 8, - * 16, 32 bit wide integers or by 32 bit floats. - */ -template -struct GenericDataReader: public DataReaderBase { - GenericDataReader(const void *data, GLenum format, GLenum type): - data(static_cast(data)), format(format), - component_data(*find_component_data(format)) {} - - static const ComponentsPerFormat *find_component_data(GLenum format) { - for (int i = 0; s_components_per_format[i].format != 0; i++) { - if (s_components_per_format[i].format == format) { - return &s_components_per_format[i]; - } - } - return nullptr; - } - - int pitch_for_width(int width) { - return width * component_data.components_per_pixel * sizeof(T); - } - - GXColor read() override { - union { - uint8_t components[4]; - GXColor c; - } pixel = { 0, 0, 0, 255 }; - - const ComponentsPerFormat &cd = component_data; - for (int i = 0; i < cd.components_per_pixel; i++) { - pixel.components[cd.component_index[i]] = component(data[n_read++]); - } - - /* Some formats require a special handling */ - if (cd.format == GL_INTENSITY || - cd.format == GL_LUMINANCE || - cd.format == GL_LUMINANCE_ALPHA) { - pixel.c.g = pixel.c.b = pixel.c.r; - if (cd.format == GL_INTENSITY) pixel.c.a = pixel.c.r; - } - - return pixel.c; - } - - const T *data; - GLenum format; - int n_read = 0; - ComponentsPerFormat component_data; -}; - template static inline void load_texture_typed(const void *src, int width, int height, void *dest, int x, int y, int dstpitch) @@ -446,7 +224,7 @@ static int get_pixel_size_in_bits(GLenum format, GLenum type) case GL_UNSIGNED_INT_2_10_10_10_REV: { const MasksPerType *mask = - CompoundDataReader::find_mask_per_type(type); + CompoundPixelStream::find_mask_per_type(type); return mask->bytes * 8; } case GL_BITMAP: @@ -456,7 +234,7 @@ static int get_pixel_size_in_bits(GLenum format, GLenum type) } const ComponentsPerFormat *c = - GenericDataReader::find_component_data(format); + GenericPixelStream::find_component_data(format); if (!c) { warning("Unknown texture format %x\n", format); return 0; @@ -529,14 +307,14 @@ void _ogx_bytes_to_texture(const void *data, GLenum format, GLenum type, Texel *texel; std::variant< - BitmapDataReader, - CompoundDataReader, - GenericDataReader, - GenericDataReader, - GenericDataReader, - GenericDataReader + BitmapPixelStream, + CompoundPixelStream, + GenericPixelStream, + GenericPixelStream, + GenericPixelStream, + GenericPixelStream > reader_v; - DataReaderBase *reader; + PixelStreamBase *reader; switch (gx_format) { case GX_TF_RGBA8: @@ -567,20 +345,20 @@ void _ogx_bytes_to_texture(const void *data, GLenum format, GLenum type, switch (type) { case GL_UNSIGNED_BYTE: - reader_v = GenericDataReader(data, format, type); - reader = &std::get>(reader_v); + reader_v = GenericPixelStream(data, format, type); + reader = &std::get>(reader_v); break; case GL_UNSIGNED_SHORT: - reader_v = GenericDataReader(data, format, type); - reader = &std::get>(reader_v); + reader_v = GenericPixelStream(data, format, type); + reader = &std::get>(reader_v); break; case GL_UNSIGNED_INT: - reader_v = GenericDataReader(data, format, type); - reader = &std::get>(reader_v); + reader_v = GenericPixelStream(data, format, type); + reader = &std::get>(reader_v); break; case GL_FLOAT: - reader_v = GenericDataReader(data, format, type); - reader = &std::get>(reader_v); + reader_v = GenericPixelStream(data, format, type); + reader = &std::get>(reader_v); break; case GL_UNSIGNED_BYTE_3_3_2: case GL_UNSIGNED_BYTE_2_3_3_REV: @@ -594,12 +372,12 @@ void _ogx_bytes_to_texture(const void *data, GLenum format, GLenum type, case GL_UNSIGNED_INT_8_8_8_8_REV: case GL_UNSIGNED_INT_10_10_10_2: case GL_UNSIGNED_INT_2_10_10_10_REV: - reader_v = CompoundDataReader(data, format, type); - reader = &std::get(reader_v); + reader_v = CompoundPixelStream(data, format, type); + reader = &std::get(reader_v); break; case GL_BITMAP: - reader_v = BitmapDataReader(data); - reader = &std::get(reader_v); + reader_v = BitmapPixelStream(data); + reader = &std::get(reader_v); break; default: warning("Unknown texture data type %x\n", type); diff --git a/src/pixels.h b/src/pixels.h index c9af2bb..cc95ca1 100644 --- a/src/pixels.h +++ b/src/pixels.h @@ -49,7 +49,6 @@ uint8_t _ogx_gl_format_to_gx(GLenum format); uint8_t _ogx_find_best_gx_format(GLenum format, GLenum internal_format, int width, int height); - #ifdef __cplusplus } // extern C #endif From efcc60bf7273aef2425adc186b91c778fdfe1a27 Mon Sep 17 00:00:00 2001 From: Alberto Mardegan Date: Sat, 19 Oct 2024 10:45:49 +0300 Subject: [PATCH 09/17] efb: add method to save a region of the EFB This will be used by glReadPixels(). --- src/efb.c | 19 +++++++++++++++---- src/efb.h | 12 ++++++++++++ 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/src/efb.c b/src/efb.c index f88eef4..632249c 100644 --- a/src/efb.c +++ b/src/efb.c @@ -41,12 +41,14 @@ POSSIBILITY OF SUCH DAMAGE. OgxEfbContentType _ogx_efb_content_type = OGX_EFB_SCENE; -void _ogx_efb_save_to_buffer(uint8_t format, uint16_t width, uint16_t height, - void *texels, OgxEfbFlags flags) +void _ogx_efb_save_area_to_buffer(uint8_t format, + uint16_t x, uint16_t y, + uint16_t width, uint16_t height, + void *texels, OgxEfbFlags flags) { GX_SetCopyFilter(GX_FALSE, NULL, GX_FALSE, NULL); - GX_SetTexCopySrc(glparamstate.viewport[0], - glparamstate.viewport[1], + GX_SetTexCopySrc(x, + y, width, height); GX_SetTexCopyDst(width, height, format, GX_FALSE); @@ -59,6 +61,15 @@ void _ogx_efb_save_to_buffer(uint8_t format, uint16_t width, uint16_t height, GX_WaitDrawDone(); } +void _ogx_efb_save_to_buffer(uint8_t format, uint16_t width, uint16_t height, + void *texels, OgxEfbFlags flags) +{ + _ogx_efb_save_area_to_buffer(format, + glparamstate.viewport[0], + glparamstate.viewport[1], + width, height, texels, flags); +} + void _ogx_efb_restore_texobj(GXTexObj *texobj) { _ogx_setup_2D_projection(); diff --git a/src/efb.h b/src/efb.h index 4bf8189..6e094ac 100644 --- a/src/efb.h +++ b/src/efb.h @@ -38,6 +38,10 @@ POSSIBILITY OF SUCH DAMAGE. #include #include +#ifdef __cplusplus +extern "C" { +#endif + typedef enum { OGX_EFB_NONE = 0, OGX_EFB_CLEAR = 1 << 0, @@ -55,6 +59,10 @@ extern OgxEfbContentType _ogx_efb_content_type; void _ogx_efb_save_to_buffer(uint8_t format, uint16_t width, uint16_t height, void *texels, OgxEfbFlags flags); +void _ogx_efb_save_area_to_buffer(uint8_t format, + uint16_t x, uint16_t y, + uint16_t width, uint16_t height, + void *texels, OgxEfbFlags flags); void _ogx_efb_restore_texobj(GXTexObj *texobj); typedef struct { @@ -84,4 +92,8 @@ static inline void _ogx_efb_set_content_type(OgxEfbContentType content_type) { _ogx_efb_set_content_type_real(content_type); } +#ifdef __cplusplus +} // extern C +#endif + #endif /* OPENGX_EFB_H */ From 7ec412fa918a26f1c6f6fbb57072cc4ed0b47236 Mon Sep 17 00:00:00 2001 From: Alberto Mardegan Date: Sat, 19 Oct 2024 11:13:33 +0300 Subject: [PATCH 10/17] PixelStream: add a setup function to pass image data and size --- src/pixel_stream.h | 44 ++++++++++++++++++++++++++++---------------- src/pixels.cpp | 13 +++++++------ 2 files changed, 35 insertions(+), 22 deletions(-) diff --git a/src/pixel_stream.h b/src/pixel_stream.h index e9bdc84..261b2a4 100644 --- a/src/pixel_stream.h +++ b/src/pixel_stream.h @@ -64,7 +64,23 @@ extern const struct MasksPerType { * to store a row of pixels. */ struct PixelStreamBase { + void setup_stream(void *data, int width, int height) { + m_width = width; + m_height = height; + m_data = data; + // TODO: add handling of row width and row alignment (to all readers!) + } + void setup_stream(const void *data, int width, int height) { + setup_stream(const_cast(data), width, height); + } + virtual GXColor read() = 0; + virtual void write(GXColor color) = 0; + +protected: + void *m_data; + int m_width; + int m_height; }; template static inline uint8_t component(T value); @@ -89,8 +105,7 @@ template <> inline float glcomponent(uint8_t value) { return value / 255.0f; } * above, where each pixel is packed in at most 32 bits. */ struct CompoundPixelStream: public PixelStreamBase { CompoundPixelStream() = default; - CompoundPixelStream(const void *data, GLenum format, GLenum type): - data(static_cast(data)), + CompoundPixelStream(GLenum format, GLenum type): format(format), mask_data(*find_mask_per_type(type)) { if (format == GL_BGR || format == GL_BGRA) { /* swap red and blue */ @@ -134,7 +149,7 @@ struct CompoundPixelStream: public PixelStreamBase { for (int i = 0; i < 4; i++) { if (i < mask_data.bytes) { pixel <<= 8; - pixel |= data[n_read + i]; + pixel |= d()[n_read + i]; } } return pixel; @@ -155,7 +170,8 @@ struct CompoundPixelStream: public PixelStreamBase { return c; } - const char *data; + const char *d() const { return static_cast(m_data); } + char *d() { return static_cast(m_data); } int n_read = 0; uint32_t rmask; uint32_t gmask; @@ -168,15 +184,9 @@ struct CompoundPixelStream: public PixelStreamBase { /* This class handles reading of pixels from bitmap (1-bit depth) */ struct BitmapPixelStream: public PixelStreamBase { BitmapPixelStream() = default; - /* The OpenGL spec fixes the format of bitmaps to GL_COLOR_INDEX, so no - * need to have it as a parameter here */ - BitmapPixelStream(const void *data): - data(static_cast(data)) { - // TODO: add handling of row width and row alignment (to all readers!) - } inline uint8_t read_pixel() const { - uint8_t byte = data[n_read / 8]; + uint8_t byte = d()[n_read / 8]; int shift = glparamstate.unpack_lsb_first ? (n_read % 8) : (7 - n_read % 8); bool bit = (byte >> shift) & 0x1; @@ -189,7 +199,8 @@ struct BitmapPixelStream: public PixelStreamBase { return { pixel, pixel, pixel, 255 }; } - const uint8_t *data; + const uint8_t *d() const { return static_cast(m_data); } + uint8_t *d() { return static_cast(m_data); } int n_read = 0; }; @@ -198,8 +209,8 @@ struct BitmapPixelStream: public PixelStreamBase { */ template struct GenericPixelStream: public PixelStreamBase { - GenericPixelStream(const void *data, GLenum format, GLenum type): - data(static_cast(data)), format(format), + GenericPixelStream(GLenum format, GLenum type): + format(format), component_data(*find_component_data(format)) {} static const ComponentsPerFormat *find_component_data(GLenum format) { @@ -223,7 +234,7 @@ struct GenericPixelStream: public PixelStreamBase { const ComponentsPerFormat &cd = component_data; for (int i = 0; i < cd.components_per_pixel; i++) { - pixel.components[cd.component_index[i]] = component(data[n_read++]); + pixel.components[cd.component_index[i]] = component(d()[n_read++]); } /* Some formats require a special handling */ @@ -237,7 +248,8 @@ struct GenericPixelStream: public PixelStreamBase { return pixel.c; } - const T *data; + T *d() { return static_cast(m_data); } + const T *d() const { return static_cast(m_data); } GLenum format; int n_read = 0; ComponentsPerFormat component_data; diff --git a/src/pixels.cpp b/src/pixels.cpp index 49c60c6..5e3ad9f 100644 --- a/src/pixels.cpp +++ b/src/pixels.cpp @@ -345,19 +345,19 @@ void _ogx_bytes_to_texture(const void *data, GLenum format, GLenum type, switch (type) { case GL_UNSIGNED_BYTE: - reader_v = GenericPixelStream(data, format, type); + reader_v = GenericPixelStream(format, type); reader = &std::get>(reader_v); break; case GL_UNSIGNED_SHORT: - reader_v = GenericPixelStream(data, format, type); + reader_v = GenericPixelStream(format, type); reader = &std::get>(reader_v); break; case GL_UNSIGNED_INT: - reader_v = GenericPixelStream(data, format, type); + reader_v = GenericPixelStream(format, type); reader = &std::get>(reader_v); break; case GL_FLOAT: - reader_v = GenericPixelStream(data, format, type); + reader_v = GenericPixelStream(format, type); reader = &std::get>(reader_v); break; case GL_UNSIGNED_BYTE_3_3_2: @@ -372,11 +372,11 @@ void _ogx_bytes_to_texture(const void *data, GLenum format, GLenum type, case GL_UNSIGNED_INT_8_8_8_8_REV: case GL_UNSIGNED_INT_10_10_10_2: case GL_UNSIGNED_INT_2_10_10_10_REV: - reader_v = CompoundPixelStream(data, format, type); + reader_v = CompoundPixelStream(format, type); reader = &std::get(reader_v); break; case GL_BITMAP: - reader_v = BitmapPixelStream(data); + reader_v = BitmapPixelStream(); reader = &std::get(reader_v); break; default: @@ -391,6 +391,7 @@ void _ogx_bytes_to_texture(const void *data, GLenum format, GLenum type, skip_pixels_after = row_length - width; } + reader->setup_stream(data, width, height); texel->set_area(dst, x, y, width, height, dstpitch); for (int ry = 0; ry < height; ry++) { if (ry > 0) { From 2b84ea3a7305220d29c5ebc06ab6d2c6f4a46a06 Mon Sep 17 00:00:00 2001 From: Alberto Mardegan Date: Sun, 20 Oct 2024 19:20:07 +0300 Subject: [PATCH 11/17] pixels: fast conversions are only available to a few data types --- src/pixels.cpp | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/pixels.cpp b/src/pixels.cpp index 5e3ad9f..0d020fe 100644 --- a/src/pixels.cpp +++ b/src/pixels.cpp @@ -275,13 +275,15 @@ void _ogx_bytes_to_texture(const void *data, GLenum format, GLenum type, * instantiation of the template takes some space, and the number of * possible combinations is polynomial. */ - for (int i = 0; i < MAX_FAST_CONVERSIONS; i++) { - const FastConversion &c = s_registered_conversions[i]; - if (c.gl_format == 0) break; - - if (c.gl_format == format && c.gx_format == gx_format) { - c.conv.func(data, type, width, height, dst, x, y, dstpitch); - return; + if (type == GL_BYTE || type == GL_UNSIGNED_BYTE || type == GL_FLOAT) { + for (int i = 0; i < MAX_FAST_CONVERSIONS; i++) { + const FastConversion &c = s_registered_conversions[i]; + if (c.gl_format == 0) break; + + if (c.gl_format == format && c.gx_format == gx_format) { + c.conv.func(data, type, width, height, dst, x, y, dstpitch); + return; + } } } From 7ab289f8b0064e5e9f1491dd78dce7cd1d2febf8 Mon Sep 17 00:00:00 2001 From: Alberto Mardegan Date: Sun, 20 Oct 2024 21:48:40 +0300 Subject: [PATCH 12/17] Implement glReadPixels() - color only Partial implementation, where we read the color buffer only. --- src/gc_gl.c | 1 - src/pixel_stream.h | 85 ++++++++++++++++++++++++- src/raster.cpp | 150 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 232 insertions(+), 4 deletions(-) diff --git a/src/gc_gl.c b/src/gc_gl.c index d670b72..44a23af 100644 --- a/src/gc_gl.c +++ b/src/gc_gl.c @@ -2611,7 +2611,6 @@ void glPushAttrib(GLbitfield mask) {} void glPopAttrib(void) {} void glPushClientAttrib(GLbitfield mask) {} void glPopClientAttrib(void) {} -void glReadPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, GLvoid *data) {} /* ****** NOTES ****** diff --git a/src/pixel_stream.h b/src/pixel_stream.h index 261b2a4..ad8ec48 100644 --- a/src/pixel_stream.h +++ b/src/pixel_stream.h @@ -68,6 +68,7 @@ struct PixelStreamBase { m_width = width; m_height = height; m_data = data; + setup(); // TODO: add handling of row width and row alignment (to all readers!) } void setup_stream(const void *data, int width, int height) { @@ -76,6 +77,7 @@ struct PixelStreamBase { virtual GXColor read() = 0; virtual void write(GXColor color) = 0; + virtual void setup() {} protected: void *m_data; @@ -170,9 +172,50 @@ struct CompoundPixelStream: public PixelStreamBase { return c; } - const char *d() const { return static_cast(m_data); } - char *d() { return static_cast(m_data); } + inline void write_component(uint32_t *pixel, uint8_t value, uint32_t mask, + int nbits, int offset) { + /* This function assumes that the bits which we'll write into "pixel" + * are initialized to 0 */ + int shift = mask_data.bytes * 8 - offset - 8; + uint32_t c = shift > 0 ? (value << shift) : (value >> -shift); + *pixel |= c & mask; + } + + inline void write_pixel(uint32_t pixel) { + for (int i = mask_data.bytes - 1; i >= 0; i--) { + d()[m_write_pos + i] = uint8_t(pixel); + pixel >>= 8; + } + m_write_pos += mask_data.bytes; + if (m_write_pos % m_bytes_per_row == 0) { + /* A new row has started; since OpenGL starts from the bottom left + * corner, we need to move to the line above, backwords */ + m_write_pos -= 2 * m_bytes_per_row; + } + } + + void write(GXColor color) override { + uint32_t pixel = 0; + write_component(&pixel, color.r, rmask, mask_data.rbits, mask_data.roff); + write_component(&pixel, color.g, gmask, mask_data.gbits, mask_data.goff); + write_component(&pixel, color.b, bmask, mask_data.bbits, mask_data.boff); + if (mask_data.abits > 0) { + write_component(&pixel, color.a, amask, mask_data.abits, mask_data.aoff); + } + write_pixel(pixel); + } + + void setup() override { + m_bytes_per_row = m_width * mask_data.bytes; + /* We start writing from the bottom row */ + m_write_pos = (m_height - 1) * m_bytes_per_row; + } + + const uint8_t *d() const { return static_cast(m_data); } + uint8_t *d() { return static_cast(m_data); } int n_read = 0; + int m_write_pos; + int m_bytes_per_row; uint32_t rmask; uint32_t gmask; uint32_t bmask; @@ -199,8 +242,13 @@ struct BitmapPixelStream: public PixelStreamBase { return { pixel, pixel, pixel, 255 }; } + void write(GXColor color) override { + /* TODO: writing a bitmap might only be useful to dump the stencil + * buffer, which is not a common case. Let's implement this when we + * meet the need. */ + } + const uint8_t *d() const { return static_cast(m_data); } - uint8_t *d() { return static_cast(m_data); } int n_read = 0; }; @@ -226,6 +274,14 @@ struct GenericPixelStream: public PixelStreamBase { return width * component_data.components_per_pixel * sizeof(T); } + void check_next_row() { + if (m_write_pos % m_components_per_row == 0) { + /* A new row has started; since OpenGL starts from the bottom left + * corner, we need to move to the line above, backwords */ + m_write_pos -= 2 * m_components_per_row; + } + } + GXColor read() override { union { uint8_t components[4]; @@ -248,10 +304,33 @@ struct GenericPixelStream: public PixelStreamBase { return pixel.c; } + void write(GXColor color) override { + union { + uint8_t components[4]; + GXColor c; + } pixel; + + pixel.c = color; + const ComponentsPerFormat &cd = component_data; + for (int i = 0; i < cd.components_per_pixel; i++) { + d()[m_write_pos++] = glcomponent(pixel.components[cd.component_index[i]]); + } + check_next_row(); + } + + void setup() override { + const ComponentsPerFormat &cd = component_data; + m_components_per_row = m_width * cd.components_per_pixel; + /* We start writing from the bottom row */ + m_write_pos = (m_height - 1) * m_components_per_row; + } + T *d() { return static_cast(m_data); } const T *d() const { return static_cast(m_data); } GLenum format; int n_read = 0; + int m_write_pos; + int m_components_per_row; ComponentsPerFormat component_data; }; diff --git a/src/raster.cpp b/src/raster.cpp index 5022be9..193c10e 100644 --- a/src/raster.cpp +++ b/src/raster.cpp @@ -31,12 +31,16 @@ POSSIBILITY OF SUCH DAMAGE. #include "clip.h" #include "debug.h" +#include "efb.h" +#include "pixel_stream.h" #include "pixels.h" #include "state.h" +#include "texel.h" #include "utils.h" #include #include +#include #include void glPixelZoom(GLfloat xfactor, GLfloat yfactor) @@ -316,6 +320,152 @@ void glBitmap(GLsizei width, GLsizei height, free(texels); } +static const struct ReadPixelFormat { + GLenum format; + uint8_t gx_copy_format; + uint8_t gx_dest_format; + int n_components; +} s_read_pixel_formats[] = { + { GL_RED, GX_CTF_R8, GX_TF_I8, 1 }, + { GL_GREEN, GX_CTF_G8, GX_TF_I8, 1 }, + { GL_BLUE, GX_CTF_B8, GX_TF_I8, 1 }, + { GL_ALPHA, GX_CTF_A8, GX_TF_I8, 1 }, + { GL_LUMINANCE, GX_TF_I8, GX_TF_I8, 1 }, + { GL_LUMINANCE_ALPHA, GX_TF_IA8, GX_TF_IA8, 2 }, + { GL_RGB, GX_TF_RGBA8, GX_TF_RGBA8, 3 }, + { GL_RGBA, GX_TF_RGBA8, GX_TF_RGBA8, 4 }, + { 0, }, +}; + +union PixelData { + GXColor color; + uint8_t component[4]; + float depth; +}; + +struct TextureReader { + TextureReader(const ReadPixelFormat *read_format, + void *texels, int width, int height): + m_texel(new_texel_for_format(read_format->gx_dest_format)) + { + int pitch = m_texel->pitch_for_width(width); + m_texel->set_area(texels, 0, 0, width, height, pitch); + } + + Texel *new_texel_for_format(uint8_t gx_format) { + switch (gx_format) { + case GX_TF_I8: return new TexelI8; + case GX_TF_IA8: return new TexelIA8; + case GX_TF_RGBA8: return new TexelRGBA8; + default: return nullptr; + } + } + + void read(PixelData *pixel) { + GXColor c = m_texel->read(); + pixel->color = c; + } + + std::unique_ptr m_texel; +}; + +struct PixelWriter { + PixelWriter(void *data, int width, int height, GLenum format, GLenum type): + m_pixel(new_pixel_for_format(format, type)), + m_width(width), m_height(height), + m_format(format), m_type(type) { + m_pixel->setup_stream(data, width, height); + } + + PixelStreamBase *new_pixel_for_format(GLenum format, GLenum type) { + switch (type) { + case GL_UNSIGNED_BYTE: + return new GenericPixelStream(format, type); + case GL_UNSIGNED_SHORT: + return new GenericPixelStream(format, type); + case GL_UNSIGNED_INT: + return new GenericPixelStream(format, type); + case GL_FLOAT: + return new GenericPixelStream(format, type); + case GL_UNSIGNED_BYTE_3_3_2: + case GL_UNSIGNED_BYTE_2_3_3_REV: + case GL_UNSIGNED_SHORT_5_6_5: + case GL_UNSIGNED_SHORT_5_6_5_REV: + case GL_UNSIGNED_SHORT_4_4_4_4: + case GL_UNSIGNED_SHORT_4_4_4_4_REV: + case GL_UNSIGNED_SHORT_5_5_5_1: + case GL_UNSIGNED_SHORT_1_5_5_5_REV: + case GL_UNSIGNED_INT_8_8_8_8: + case GL_UNSIGNED_INT_8_8_8_8_REV: + case GL_UNSIGNED_INT_10_10_10_2: + case GL_UNSIGNED_INT_2_10_10_10_REV: + return new CompoundPixelStream(format, type); + default: + warning("Unknown texture data type %x\n", type); + return nullptr; + } + } + + void write(const PixelData *pixel) { + m_pixel->write(pixel->color); + } + + std::unique_ptr m_pixel; + int m_width; + int m_height; + GLenum m_format; + GLenum m_type; +}; + +void glReadPixels(GLint x, GLint y, GLsizei width, GLsizei height, + GLenum format, GLenum type, GLvoid *data) +{ + uint8_t gxformat = 0xff; + const ReadPixelFormat *read_format = NULL; + int n_components; + + switch (format) { + case GL_COLOR_INDEX: + warning("glReadPixels: GL_COLOR_INDEX not supported"); + return; + case GL_STENCIL_INDEX: + warning("glReadPixels: GL_STENCIL_INDEX not implemented"); + // TODO + return; + case GL_DEPTH_COMPONENT: + warning("glReadPixels: GL_DEPTH_COMPONENT not implemented"); + // TODO + return; + } + + for (int i = 0; s_read_pixel_formats[i].format != 0; i++) { + if (s_read_pixel_formats[i].format == format) { + read_format = &s_read_pixel_formats[i]; + break; + } + } + if (!read_format) { + warning("glReadPixels: unsupported format %04x", format); + return; + } + + u32 size = GX_GetTexBufferSize(width, height, + read_format->gx_dest_format, 0, GX_FALSE); + void *texels = memalign(32, size); + _ogx_efb_save_area_to_buffer(read_format->gx_copy_format, x, y, + width, height, texels, OGX_EFB_NONE); + TextureReader reader(read_format, texels, width, height); + PixelWriter writer(data, width, height, format, type); + for (int row = 0; row < height; row++) { + for (int col = 0; col < width; col++) { + PixelData pixel; + reader.read(&pixel); + writer.write(&pixel); + } + } + free(texels); +} + void glDrawPixels(GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *pixels) { From a797a5b32fbefc5d0c7e866496933521e1ad507c Mon Sep 17 00:00:00 2001 From: Alberto Mardegan Date: Sun, 20 Oct 2024 21:49:13 +0300 Subject: [PATCH 13/17] pixels: print image type in hexadecimal format This makes it easier to lookup the type in the GL.h header file. --- src/pixels.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pixels.cpp b/src/pixels.cpp index 0d020fe..29d00c6 100644 --- a/src/pixels.cpp +++ b/src/pixels.cpp @@ -194,7 +194,7 @@ void load_texture(const void *data, GLenum type, int width, int height, dst, x, y, dstpitch); break; default: - warning("Unsupported texture format %d", type); + warning("Unsupported texture format %04x", type); } } From 870101be6817dc3287422c4268fa273423200ea1 Mon Sep 17 00:00:00 2001 From: Alberto Mardegan Date: Mon, 21 Oct 2024 20:40:59 +0300 Subject: [PATCH 14/17] textures: support GL_RED, GL_BLUE and GL_GREEN formats The simplest way to support them is by using the RGBA8 format, but the optimal way would be using I8 in order to save memory; however, that would require playing a bit with the TEV setup and these would cease to be just simple textures. --- src/pixels.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/pixels.cpp b/src/pixels.cpp index 29d00c6..7263eff 100644 --- a/src/pixels.cpp +++ b/src/pixels.cpp @@ -441,6 +441,9 @@ uint8_t _ogx_gl_format_to_gx(GLenum format) case GL_RGBA: case GL_BGRA: case GL_COMPRESSED_RGBA_ARB: /* No support for compressed alpha textures */ + case GL_RED: + case GL_GREEN: + case GL_BLUE: return GX_TF_RGBA8; case GL_LUMINANCE_ALPHA: return GX_TF_IA8; case GL_LUMINANCE: return GX_TF_I8; From f3218bc889181b85ad374eb45f7714d1e63c226e Mon Sep 17 00:00:00 2001 From: Alberto Mardegan Date: Mon, 21 Oct 2024 20:46:00 +0300 Subject: [PATCH 15/17] glDrawPixels: properly draw surfaces in GL_LUMINANCE format The I8 format used by GX sets the alpha value to the intensity of the pixel, but GL_LUMINANCE should be completely opaque. Therefore, setup the TEV so that the alpha is not read from the texture, but from a register where we've set it to 255. --- src/raster.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/raster.cpp b/src/raster.cpp index 193c10e..2694706 100644 --- a/src/raster.cpp +++ b/src/raster.cpp @@ -500,6 +500,14 @@ void glDrawPixels(GLsizei width, GLsizei height, GLenum format, GLenum type, GX_SetNumChans(0); GX_SetTevOp(GX_TEVSTAGE0, GX_REPLACE); + bool no_alpha = false; + if (format == GL_LUMINANCE) { + /* Set alpha to 1.0 */ + GXColor ccol = { 0, 0, 0, 255 }; + GX_SetTevColor(GX_TEVREG0, ccol); + GX_SetTevAlphaIn(GX_TEVSTAGE0, + GX_CA_A0, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO); + } draw_raster_texture(&texture, width, height, pos_x, pos_y, pos_z); /* We need to wait for the drawing to be complete before freeing the From 70f3c136dc9d033145ccd931d7b9f9d75a7a40b6 Mon Sep 17 00:00:00 2001 From: Alberto Mardegan Date: Mon, 21 Oct 2024 20:48:26 +0300 Subject: [PATCH 16/17] glReadPixels: support reading the Z-buffer We also support the GL_DEPTH_BIAS and GL_DEPTH_OFFSET, because they help in visualizing the Z buffer if it's drawn back to the screen (otherwise most Z-changes are too subtle to be perceived when printed as a colour). --- src/getters.c | 12 ++++++++++++ src/pixel_stream.cpp | 1 + src/pixel_stream.h | 24 ++++++++++++++++++++++++ src/pixels.cpp | 37 +++++++++++++++++++++++++++++++++++++ src/raster.cpp | 17 +++++++++++++---- src/state.h | 4 ++++ 6 files changed, 91 insertions(+), 4 deletions(-) diff --git a/src/getters.c b/src/getters.c index b2ae37f..51ad02a 100644 --- a/src/getters.c +++ b/src/getters.c @@ -129,10 +129,16 @@ void glGetFloatv(GLenum pname, GLfloat *params) case GL_CURRENT_RASTER_POSITION: floatcpy(params, glparamstate.raster_pos, 4); break; + case GL_DEPTH_BIAS: + *params = glparamstate.transfer_depth_bias; + break; case GL_DEPTH_RANGE: params[0] = glparamstate.depth_near; params[1] = glparamstate.depth_far; break; + case GL_DEPTH_SCALE: + *params = glparamstate.transfer_depth_scale; + break; case GL_MODELVIEW_MATRIX: for (int i = 0; i < 3; i++) for (int j = 0; j < 4; j++) @@ -173,6 +179,12 @@ void glGetIntegerv(GLenum pname, GLint *params) case GL_READ_BUFFER: *params = glparamstate.active_buffer; break; + case GL_INDEX_OFFSET: + *params = glparamstate.transfer_index_offset; + break; + case GL_INDEX_SHIFT: + *params = glparamstate.transfer_index_shift; + break; case GL_MAX_CLIP_PLANES: *params = MAX_CLIP_PLANES; return; diff --git a/src/pixel_stream.cpp b/src/pixel_stream.cpp index 1874e7c..8e93d50 100644 --- a/src/pixel_stream.cpp +++ b/src/pixel_stream.cpp @@ -59,5 +59,6 @@ const struct ComponentsPerFormat _ogx_pixels_components_per_format[] = { { GL_GREEN, 1, { 1 }}, { GL_BLUE, 1, { 2 }}, { GL_ALPHA, 1, { 3 }}, + { GL_DEPTH_COMPONENT, 1, {}}, { 0, } }; diff --git a/src/pixel_stream.h b/src/pixel_stream.h index ad8ec48..c883502 100644 --- a/src/pixel_stream.h +++ b/src/pixel_stream.h @@ -103,6 +103,18 @@ template <> inline uint32_t glcomponent(uint8_t value) { } template <> inline float glcomponent(uint8_t value) { return value / 255.0f; } +template static inline T depth_component(GXColor c); +template <> inline uint8_t depth_component(GXColor c) { return c.r; } +template <> inline uint16_t depth_component(GXColor c) { + return (c.r << 8) | c.g; +} +template <> inline uint32_t depth_component(GXColor c) { + return (c.r << 16) | (c.g << 8) | c.b; +} +template <> inline float depth_component(GXColor c) { + return depth_component(c) / float(0xffffff); +} + /* This class handles reading of pixels stored in one of the formats listed * above, where each pixel is packed in at most 32 bits. */ struct CompoundPixelStream: public PixelStreamBase { @@ -334,4 +346,16 @@ struct GenericPixelStream: public PixelStreamBase { ComponentsPerFormat component_data; }; +template +struct DepthPixelStream: public GenericPixelStream { + using GenericPixelStream::GenericPixelStream; + using GenericPixelStream::m_write_pos; + + void write(GXColor color) override { + this->d()[m_write_pos++] = depth_component(color) + * glparamstate.transfer_depth_scale + glparamstate.transfer_depth_bias; + this->check_next_row(); + } +}; + #endif /* OPENGX_PIXEL_STREAM_H */ diff --git a/src/pixels.cpp b/src/pixels.cpp index 7263eff..04631ca 100644 --- a/src/pixels.cpp +++ b/src/pixels.cpp @@ -520,3 +520,40 @@ void ogx_register_tex_conversion(GLenum format, GLenum internal_format, c.gx_format = gx_format; c.conv.id = converter; } + +void glPixelTransferf(GLenum pname, GLfloat param) +{ + switch (pname) { + case GL_DEPTH_BIAS: + glparamstate.transfer_depth_bias = param; + break; + case GL_DEPTH_SCALE: + glparamstate.transfer_depth_scale = param; + break; + case GL_RED_BIAS: + case GL_RED_SCALE: + case GL_GREEN_BIAS: + case GL_GREEN_SCALE: + case GL_BLUE_BIAS: + case GL_BLUE_SCALE: + case GL_ALPHA_BIAS: + case GL_ALPHA_SCALE: + warning("Scaling color components is not implemented"); + break; + } +} + +void glPixelTransferi(GLenum pname, GLint param) +{ + switch (pname) { + case GL_INDEX_OFFSET: + glparamstate.transfer_index_offset = param; + break; + case GL_INDEX_SHIFT: + glparamstate.transfer_index_shift = param; + break; + default: + glPixelTransferf(pname, param); + break; + } +} diff --git a/src/raster.cpp b/src/raster.cpp index 2694706..a5b24d1 100644 --- a/src/raster.cpp +++ b/src/raster.cpp @@ -334,6 +334,7 @@ static const struct ReadPixelFormat { { GL_LUMINANCE_ALPHA, GX_TF_IA8, GX_TF_IA8, 2 }, { GL_RGB, GX_TF_RGBA8, GX_TF_RGBA8, 3 }, { GL_RGBA, GX_TF_RGBA8, GX_TF_RGBA8, 4 }, + { GL_DEPTH_COMPONENT, GX_TF_Z24X8, GX_TF_RGBA8, 1 }, { 0, }, }; @@ -378,6 +379,18 @@ struct PixelWriter { } PixelStreamBase *new_pixel_for_format(GLenum format, GLenum type) { + if (format == GL_DEPTH_COMPONENT) { + switch (type) { + case GL_UNSIGNED_BYTE: + return new DepthPixelStream(format, type); + case GL_UNSIGNED_SHORT: + return new DepthPixelStream(format, type); + case GL_UNSIGNED_INT: + return new DepthPixelStream(format, type); + case GL_FLOAT: + return new DepthPixelStream(format, type); + } + } switch (type) { case GL_UNSIGNED_BYTE: return new GenericPixelStream(format, type); @@ -432,10 +445,6 @@ void glReadPixels(GLint x, GLint y, GLsizei width, GLsizei height, warning("glReadPixels: GL_STENCIL_INDEX not implemented"); // TODO return; - case GL_DEPTH_COMPONENT: - warning("glReadPixels: GL_DEPTH_COMPONENT not implemented"); - // TODO - return; } for (int i = 0; s_read_pixel_formats[i].format != 0; i++) { diff --git a/src/state.h b/src/state.h index cee7f90..e758405 100644 --- a/src/state.h +++ b/src/state.h @@ -133,6 +133,10 @@ typedef struct glparams_ float clearz; float polygon_offset_factor; float polygon_offset_units; + float transfer_depth_scale; + float transfer_depth_bias; + int16_t transfer_index_shift; + int16_t transfer_index_offset; OgxPixelMapTables *pixel_maps; /* Only allocated if glPixelMap is called */ From 785b33ad9284b0c1769a975b8adef732e51b4238 Mon Sep 17 00:00:00 2001 From: Alberto Mardegan Date: Tue, 22 Oct 2024 21:17:09 +0300 Subject: [PATCH 17/17] glReadPixels(): support reading the stencil buffer Tested with readpixels.cpp: https://github.com/mardy/freeglut/blob/with-tutorials/tutorials/readpixels.cpp --- src/pixel_stream.cpp | 1 + src/pixel_stream.h | 13 ++++++++++ src/raster.cpp | 57 ++++++++++++++++++++++++++++++-------------- src/stencil.c | 5 ++++ src/stencil.h | 11 +++++++++ 5 files changed, 69 insertions(+), 18 deletions(-) diff --git a/src/pixel_stream.cpp b/src/pixel_stream.cpp index 8e93d50..bb43e90 100644 --- a/src/pixel_stream.cpp +++ b/src/pixel_stream.cpp @@ -60,5 +60,6 @@ const struct ComponentsPerFormat _ogx_pixels_components_per_format[] = { { GL_BLUE, 1, { 2 }}, { GL_ALPHA, 1, { 3 }}, { GL_DEPTH_COMPONENT, 1, {}}, + { GL_STENCIL_INDEX, 1, { 0 }}, { 0, } }; diff --git a/src/pixel_stream.h b/src/pixel_stream.h index c883502..e393c72 100644 --- a/src/pixel_stream.h +++ b/src/pixel_stream.h @@ -358,4 +358,17 @@ struct DepthPixelStream: public GenericPixelStream { } }; +template +struct StencilPixelStream: public GenericPixelStream { + using GenericPixelStream::GenericPixelStream; + using GenericPixelStream::m_write_pos; + + void write(GXColor color) override { + this->d()[m_write_pos++] = glcomponent(color.r) + * (1 << glparamstate.transfer_index_shift) + + glparamstate.transfer_index_offset; + this->check_next_row(); + } +}; + #endif /* OPENGX_PIXEL_STREAM_H */ diff --git a/src/raster.cpp b/src/raster.cpp index a5b24d1..a0a140e 100644 --- a/src/raster.cpp +++ b/src/raster.cpp @@ -35,6 +35,7 @@ POSSIBILITY OF SUCH DAMAGE. #include "pixel_stream.h" #include "pixels.h" #include "state.h" +#include "stencil.h" #include "texel.h" #include "utils.h" @@ -355,6 +356,7 @@ struct TextureReader { Texel *new_texel_for_format(uint8_t gx_format) { switch (gx_format) { + case GX_CTF_R4: return new TexelI4; case GX_TF_I8: return new TexelI8; case GX_TF_IA8: return new TexelIA8; case GX_TF_RGBA8: return new TexelRGBA8; @@ -390,6 +392,17 @@ struct PixelWriter { case GL_FLOAT: return new DepthPixelStream(format, type); } + } else if (format == GL_STENCIL_INDEX) { + switch (type) { + case GL_UNSIGNED_BYTE: + return new StencilPixelStream(format, type); + case GL_UNSIGNED_SHORT: + return new StencilPixelStream(format, type); + case GL_UNSIGNED_INT: + return new StencilPixelStream(format, type); + case GL_FLOAT: + return new StencilPixelStream(format, type); + } } switch (type) { case GL_UNSIGNED_BYTE: @@ -435,17 +448,10 @@ void glReadPixels(GLint x, GLint y, GLsizei width, GLsizei height, { uint8_t gxformat = 0xff; const ReadPixelFormat *read_format = NULL; + ReadPixelFormat stencil_format; int n_components; - - switch (format) { - case GL_COLOR_INDEX: - warning("glReadPixels: GL_COLOR_INDEX not supported"); - return; - case GL_STENCIL_INDEX: - warning("glReadPixels: GL_STENCIL_INDEX not implemented"); - // TODO - return; - } + void *texels = nullptr; + bool must_free_texels = false; for (int i = 0; s_read_pixel_formats[i].format != 0; i++) { if (s_read_pixel_formats[i].format == format) { @@ -454,15 +460,27 @@ void glReadPixels(GLint x, GLint y, GLsizei width, GLsizei height, } } if (!read_format) { - warning("glReadPixels: unsupported format %04x", format); - return; + if (format == GL_STENCIL_INDEX) { + OgxEfbBuffer *stencil = _ogx_stencil_get_buffer(); + stencil_format = { + format, 0, + uint8_t(GX_GetTexObjFmt(&stencil->texobj)), 1 }; + read_format = &stencil_format; + texels = _ogx_efb_buffer_get_texels(stencil); + } else { + warning("glReadPixels: unsupported format %04x", format); + return; + } } - u32 size = GX_GetTexBufferSize(width, height, - read_format->gx_dest_format, 0, GX_FALSE); - void *texels = memalign(32, size); - _ogx_efb_save_area_to_buffer(read_format->gx_copy_format, x, y, - width, height, texels, OGX_EFB_NONE); + if (!texels) { + u32 size = GX_GetTexBufferSize(width, height, + read_format->gx_dest_format, 0, GX_FALSE); + texels = memalign(32, size); + _ogx_efb_save_area_to_buffer(read_format->gx_copy_format, x, y, + width, height, texels, OGX_EFB_NONE); + must_free_texels = true; + } TextureReader reader(read_format, texels, width, height); PixelWriter writer(data, width, height, format, type); for (int row = 0; row < height; row++) { @@ -472,7 +490,10 @@ void glReadPixels(GLint x, GLint y, GLsizei width, GLsizei height, writer.write(&pixel); } } - free(texels); + + if (must_free_texels) { + free(texels); + } } void glDrawPixels(GLsizei width, GLsizei height, GLenum format, GLenum type, diff --git a/src/stencil.c b/src/stencil.c index 298957d..4ade837 100644 --- a/src/stencil.c +++ b/src/stencil.c @@ -630,6 +630,11 @@ void _ogx_stencil_clear() s_stencil_texture_needs_update = false; } +OgxEfbBuffer *_ogx_stencil_get_buffer() +{ + return s_stencil_buffer; +} + void ogx_stencil_create(OgxStencilFlags flags) { s_wants_stencil = true; diff --git a/src/stencil.h b/src/stencil.h index 9771cc2..2fa2f4e 100644 --- a/src/stencil.h +++ b/src/stencil.h @@ -33,11 +33,16 @@ POSSIBILITY OF SUCH DAMAGE. #ifndef OPENGX_STENCIL_H #define OPENGX_STENCIL_H +#include "efb.h" #include "opengx.h" #include #include +#ifdef __cplusplus +extern "C" { +#endif + extern OgxStencilFlags _ogx_stencil_flags; void _ogx_stencil_enabled(void); @@ -56,4 +61,10 @@ void _ogx_stencil_draw(OgxStencilDrawCallback callback, void *cb_data); void _ogx_stencil_load_into_efb(); void _ogx_stencil_save_to_efb(); +OgxEfbBuffer *_ogx_stencil_get_buffer(void); + +#ifdef __cplusplus +} // extern C +#endif + #endif /* OPENGX_STENCIL_H */