Skip to content

Commit

Permalink
texture: implement GL_SPHERE_MAP in software
Browse files Browse the repository at this point in the history
This implementation complies with the OpenGL specification. In a
follow-up commit we will give the client the possibility to choose
between this software implementation and the faster (but wrong) GPU
implementation.
  • Loading branch information
mardy committed Nov 24, 2024
1 parent 01fbae2 commit 956ea73
Show file tree
Hide file tree
Showing 7 changed files with 272 additions and 16 deletions.
2 changes: 2 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ add_library(${TARGET} STATIC
src/stencil.h
src/texel.h
src/texture.c
src/texture_gen_sw.c
src/texture_gen_sw.h
src/texture_unit.c
src/texture_unit.h
src/types.h
Expand Down
146 changes: 133 additions & 13 deletions src/arrays.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,12 @@ POSSIBILITY OF SUCH DAMAGE.

#include "debug.h"
#include "state.h"
#include "texture_gen_sw.h"
#include "utils.h"
#include "vbo.h"

#include <cstdlib>
#include <cstring>
#include <limits>
#include <ogc/gx.h>
#include <variant>
Expand Down Expand Up @@ -133,15 +137,86 @@ static TemplateSelectionInfo select_template(GLenum type,
return info;
}

struct VertexReaderBase {
struct AbstractVertexReader {
virtual void setup_draw() = 0;
virtual void draw_done() = 0;

virtual void set_tex_coord_source(uint8_t source) = 0;
virtual uint8_t get_tex_coord_source() const = 0;

virtual void process_element(int index) = 0;

virtual void read_color(int index, GXColor *color) = 0;
virtual void read_pos3f(int index, Pos3f pos) = 0;
virtual void read_norm3f(int index, Norm3f norm) = 0;
virtual void read_tex2f(int index, Tex2f tex) = 0;
};

struct GeneratedTexVertexReader: public AbstractVertexReader {
GeneratedTexVertexReader(OgxArrayReader *orig):
prev(orig) {}

template <class T>
static T *create_at(OgxArrayReader *location) {
OgxArrayReader *orig =
static_cast<OgxArrayReader*>(malloc(sizeof(OgxArrayReader)));
memcpy(orig, location, sizeof(OgxArrayReader));
return new (location) T(orig);
}

void setup_draw() override {
u8 attribute = GX_VA_TEX0 + s_num_tex_arrays++;
GX_SetVtxDesc(attribute, GX_DIRECT);
GX_SetVtxAttrFmt(GX_VTXFMT0, attribute, GX_TEX_ST, GX_F32, 0);
}

void draw_done() override {
if (prev) {
void *old_copy = prev;
memcpy(this, old_copy, sizeof(OgxArrayReader));
free(old_copy);
}
}

void set_tex_coord_source(uint8_t source) override {
tex_coord_source = source;
}
uint8_t get_tex_coord_source() const override { return tex_coord_source; }

void process_element(int index) override {
Tex2f value;
read_tex2f(index, value);
GX_TexCoord2f32(value[0], value[1]);
}

void read_color(int index, GXColor *color) override {}
void read_pos3f(int index, Pos3f pos) override {}
void read_norm3f(int index, Norm3f norm) override {}

protected:
/* This is the instance of the VertexReader that the
* GeneratedTexVertexReader is replacing. It will have to be restored in
* place after the drawing has been done. */
OgxArrayReader *prev;
uint8_t tex_coord_source;
};

struct SphereMapTexReader: public GeneratedTexVertexReader {
using GeneratedTexVertexReader::GeneratedTexVertexReader;
void read_tex2f(int index, Tex2f tex) override {
_ogx_texture_gen_sw_sphere_map(index, tex);
}
};

struct VertexReaderBase: public AbstractVertexReader {
VertexReaderBase(GxVertexFormat format, VboType vbo, const void *data,
int stride):
format(format),
data(static_cast<const char *>(vbo ?
_ogx_vbo_get_data(vbo, data) : data)),
stride(stride), dup_color(false), vbo(vbo) {}

virtual void setup_draw() {
void setup_draw() override {
if (format.attribute >= GX_VA_TEX0 &&
format.attribute <= GX_VA_TEX7) {
/* Texture coordinates must be enable sequentially */
Expand All @@ -163,18 +238,17 @@ struct VertexReaderBase {
this->dup_color = dup_color;
}

void set_tex_coord_source(uint8_t source) { tex_coord_source = source; }
void draw_done() override {}

void set_tex_coord_source(uint8_t source) override {
tex_coord_source = source;
}
uint8_t get_tex_coord_source() const override { return tex_coord_source; }

bool has_same_data(VertexReaderBase *other) const {
return data == other->data && stride == other->stride;
}

virtual void process_element(int index) = 0;

virtual void read_color(int index, GXColor *color) = 0;
virtual void read_pos3f(int index, Pos3f pos) = 0;
virtual void read_norm3f(int index, Norm3f norm) = 0;
virtual void read_tex2f(int index, Tex2f tex) = 0;

GxVertexFormat format;
const char *data;
uint16_t stride;
Expand Down Expand Up @@ -476,8 +550,28 @@ void _ogx_arrays_setup_draw(bool has_normals, uint8_t num_colors,
s_tex_unit_mask = 0;
if (tex_unit_mask) {
for (int i = 0; i < MAX_TEXTURE_UNITS; i++) {
if (tex_unit_mask & (1 << i)) {
VertexReaderBase *r = get_reader(&glparamstate.texcoord_array[i]);
VertexReaderBase *r = get_reader(&glparamstate.texcoord_array[i]);
u8 unit_bit = 1 << i;
if (glparamstate.texture_unit[i].gen_enabled) {
/* Some kinds of texture generation cannot be performed by the
* GPU, and we have to generate the texture coordinates in
* software. */
if (_ogx_texture_gen_sw_enabled(i)) {
GeneratedTexVertexReader *gr;
switch (glparamstate.texture_unit[i].gen_mode) {
case GL_SPHERE_MAP:
gr = GeneratedTexVertexReader::create_at
<SphereMapTexReader>(&glparamstate.texcoord_array[i]);
break;
}
gr->setup_draw();
gr->set_tex_coord_source(GX_TG_TEX0 + sent_tex_arrays++);
s_tex_unit_mask |= unit_bit;
continue;
}
}

if (tex_unit_mask & unit_bit) {
/* See if the data array is the same as the positional or
* normal array. This is not just an optimization, it's
* actually needed because GX only supports up to two input
Expand All @@ -496,7 +590,7 @@ void _ogx_arrays_setup_draw(bool has_normals, uint8_t num_colors,
* but let's leave this optimisation for later. */
r->setup_draw();
r->set_tex_coord_source(GX_TG_TEX0 + sent_tex_arrays++);
s_tex_unit_mask |= (1 << i);
s_tex_unit_mask |= unit_bit;
}
}
}
Expand All @@ -507,6 +601,11 @@ void _ogx_arrays_setup_draw(bool has_normals, uint8_t num_colors,
/* s_tex_unit_mask has been set in the loop above */
}

uint8_t _ogx_arrays_get_units_with_tex_coord()
{
return s_tex_unit_mask;
}

void _ogx_arrays_process_element(int index)
{
get_reader(&glparamstate.vertex_array)->process_element(index);
Expand All @@ -529,6 +628,27 @@ void _ogx_arrays_process_element(int index)
}
}

void _ogx_arrays_draw_done()
{
get_reader(&glparamstate.vertex_array)->draw_done();

if (s_has_normals) {
get_reader(&glparamstate.normal_array)->draw_done();
}

if (s_num_colors) {
get_reader(&glparamstate.color_array)->draw_done();
}

if (s_tex_unit_mask) {
for (int i = 0; i < MAX_TEXTURE_UNITS; i++) {
if (s_tex_unit_mask & (1 << i)) {
get_reader(&glparamstate.texcoord_array[i])->draw_done();
}
}
}
}

void _ogx_array_reader_enable_dup_color(OgxArrayReader *reader,
bool dup_color)
{
Expand Down
7 changes: 7 additions & 0 deletions src/arrays.h
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,14 @@ void _ogx_array_reader_init(OgxArrayReader *reader,
int num_components, GLenum type, int stride);
void _ogx_arrays_setup_draw(bool has_normals, uint8_t num_colors,
uint8_t tex_unit_mask);
/* Get the mask of units having texture coordinates. This is not necessarily
* the same as glparamstate.cs.texcoord_enabled, because we might have
* generated more arrays via software if the mode is not supported by the
* hardware (GL_SPHERE_MAP) */
uint8_t _ogx_arrays_get_units_with_tex_coord();
void _ogx_arrays_process_element(int index);
/* Any memory allocated by the OgxArrayReader objects can be released. */
void _ogx_arrays_draw_done();
void _ogx_array_reader_enable_dup_color(OgxArrayReader *reader,
bool dup_color);
void _ogx_array_reader_process_element(OgxArrayReader *reader, int index);
Expand Down
2 changes: 2 additions & 0 deletions src/gc_gl.c
Original file line number Diff line number Diff line change
Expand Up @@ -2407,6 +2407,7 @@ void glDrawArrays(GLenum mode, GLint first, GLsizei count)
draw_arrays_general(gxmode, first, count);
glparamstate.draw_count++;
}
_ogx_arrays_draw_done();

_ogx_gpu_resources_pop();
}
Expand Down Expand Up @@ -2436,6 +2437,7 @@ void glDrawElements(GLenum mode, GLsizei count, GLenum type, const GLvoid *indic
draw_elements_general(gxmode, count, type, indices);
glparamstate.draw_count++;
}
_ogx_arrays_draw_done();

_ogx_gpu_resources_pop();
}
Expand Down
74 changes: 74 additions & 0 deletions src/texture_gen_sw.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/*****************************************************************************
Copyright (c) 2024 Alberto Mardegan ([email protected])
All rights reserved.
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 "texture_gen_sw.h"

#include "debug.h"
#include "utils.h"

#include <ogc/gu.h>

bool _ogx_texture_gen_sw_enabled(uint8_t unit)
{
const OgxTextureUnit *tu = &glparamstate.texture_unit[unit];

if (tu->gen_mode != GL_SPHERE_MAP) return false;

/* We need normal coordinates to be there */
if (!glparamstate.cs.normal_enabled) return false;

return true;
}

void _ogx_texture_gen_sw_sphere_map(int index, Tex2f out)
{
guVector pos, normal;
_ogx_array_reader_read_pos3f(&glparamstate.vertex_array,
index, (float *)&pos);
_ogx_array_reader_read_norm3f(&glparamstate.normal_array,
index, (float *)&normal);
/* Transform coordinates to eye-space */
guVecMultiply(glparamstate.modelview_matrix, &pos, &pos);
/* For the normal, only use the scale + rotation part of the matrix */
guVecMultiplySR(glparamstate.modelview_matrix, &normal, &normal);
guVecNormalize(&pos);

/* The pos vector now can be used to represent the vector from the eye
* to the vertex. Reflect it across the normal to find the reflection
* vector. */
float pos_dot_normal = guVecDotProduct(&pos, &normal);

guVector reflection;
guVecScale(&normal, &normal, -pos_dot_normal * 2);
guVecAdd(&pos, &normal, &reflection);
float rx = reflection.x, ry = reflection.y, rz = reflection.z;
float m = sqrtf(rx * rx + ry * ry + (rz + 1) * (rz + 1)) * 2;
out[0] = rx / m + 0.5f;
out[1] = ry / m + 0.5f;
}
46 changes: 46 additions & 0 deletions src/texture_gen_sw.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*****************************************************************************
Copyright (c) 2024 Alberto Mardegan ([email protected])
All rights reserved.
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_TEXTURE_GEN_SW_H
#define OPENGX_TEXTURE_GEN_SW_H

#include "types.h"

#ifdef __cplusplus
extern "C" {
#endif

bool _ogx_texture_gen_sw_enabled(uint8_t unit);
void _ogx_texture_gen_sw_sphere_map(int index, Tex2f tex);

#ifdef __cplusplus
} // extern C
#endif

#endif /* OPENGX_TEXTURE_GEN_SW_H */
Loading

0 comments on commit 956ea73

Please sign in to comment.