From 67f82f577f15918b2619e5bea266a6ca718204c7 Mon Sep 17 00:00:00 2001 From: Alberto Mardegan Date: Fri, 27 Sep 2024 20:33:38 +0300 Subject: [PATCH 1/3] Implement glReadBuffer() and glDrawBuffer() We don't really know if double buffering is used, since it's a decision that the integration library takes. We limit the implementation to a check whether the correct value is being set; we don't really allow changing the read or the write buffer, since reading or writing from the XFB would be troublesome (it's in YUV format). --- src/functions.c | 2 +- src/gc_gl.c | 25 ++++++++++++++++++++++++- src/getters.c | 7 +++++++ src/opengx.h | 8 ++++++++ src/state.h | 1 + 5 files changed, 41 insertions(+), 2 deletions(-) diff --git a/src/functions.c b/src/functions.c index 112776e..ae889c4 100644 --- a/src/functions.c +++ b/src/functions.c @@ -109,7 +109,7 @@ static const ProcMap s_proc_map[] = { PROC(glDisable), PROC(glDisableClientState), PROC(glDrawArrays), - //PROC(glDrawBuffer), + PROC(glDrawBuffer), PROC(glDrawElements), //PROC(glDrawPixels), //PROC(glEdgeFlag), diff --git a/src/gc_gl.c b/src/gc_gl.c index bb882d5..4e8591c 100644 --- a/src/gc_gl.c +++ b/src/gc_gl.c @@ -184,6 +184,13 @@ static void setup_cull_mode() } } +int ogx_enable_double_buffering(int double_buffering) +{ + int had_double_buffering = glparamstate.active_buffer == GL_BACK; + glparamstate.active_buffer = double_buffering ? GL_BACK : GL_FRONT; + return had_double_buffering; +} + int ogx_prepare_swap_buffers() { return glparamstate.render_mode == GL_RENDER ? 0 : -1; @@ -368,6 +375,8 @@ void ogx_initialize() glparamstate.stencil.op_zfail = GL_KEEP; glparamstate.stencil.op_zpass = GL_KEEP; + glparamstate.active_buffer = GL_BACK; + glparamstate.error = GL_NO_ERROR; glparamstate.draw_count = 0; @@ -462,6 +471,21 @@ void _ogx_efb_set_content_type_real(OgxEfbContentType content_type) _ogx_efb_content_type = content_type; } +void glDrawBuffer(GLenum mode) +{ + if (mode != glparamstate.active_buffer) { + warning("Change the read or write buffer is not implemented"); + set_error(GL_INVALID_OPERATION); + } +} + +void glReadBuffer(GLenum mode) +{ + /* We currently don't support changing read/write buffers, so the + * implementation can be the same. */ + glDrawBuffer(mode); +} + void glEnable(GLenum cap) { // TODO HANDLE_CALL_LIST(ENABLE, cap); @@ -2533,7 +2557,6 @@ void glPopAttrib(void) {} void glPushClientAttrib(GLbitfield mask) {} void glPopClientAttrib(void) {} void glPolygonMode(GLenum face, GLenum mode) {} -void glReadBuffer(GLenum mode) {} void glReadPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, GLvoid *data) {} /* diff --git a/src/getters.c b/src/getters.c index 05a4bb4..99f8fb8 100644 --- a/src/getters.c +++ b/src/getters.c @@ -143,6 +143,9 @@ void glGetFloatv(GLenum pname, GLfloat *params) void glGetIntegerv(GLenum pname, GLint *params) { switch (pname) { + case GL_AUX_BUFFERS: + *params = 0; + break; case GL_CLIP_PLANE0: case GL_CLIP_PLANE1: case GL_CLIP_PLANE2: @@ -152,6 +155,10 @@ void glGetIntegerv(GLenum pname, GLint *params) *params = glparamstate.clip_plane_mask & (1 << (pname - GL_CLIP_PLANE0)); return; + case GL_DRAW_BUFFER: + case GL_READ_BUFFER: + *params = glparamstate.active_buffer; + break; case GL_MAX_CLIP_PLANES: *params = MAX_CLIP_PLANES; return; diff --git a/src/opengx.h b/src/opengx.h index 9412470..f9fa9ff 100644 --- a/src/opengx.h +++ b/src/opengx.h @@ -42,6 +42,14 @@ extern "C" { void ogx_initialize(void); void *ogx_get_proc_address(const char *proc); +/* Enable or disable double buffering. This is actually a choice entirely made + * by the integration library, but opengx must be informed about it in order to + * select the right value for the glReadBuffer() and glDrawBuffer() functions. + * + * If this is not called, opengx assumes double buffering is on. + */ +int ogx_enable_double_buffering(int double_buffering); + /* The display integration library (SDL, GLUT, etc.) should call this function * before copying the EFB to the XFB (and optionally, drawing a cursor). The * opengx library might need to restore the EFB (in case it was configured into diff --git a/src/state.h b/src/state.h index b2bf9bb..ad2b6a4 100644 --- a/src/state.h +++ b/src/state.h @@ -104,6 +104,7 @@ typedef struct glparams_ OgxTexgenMask texture_gen_enabled; GLenum glcullmode; GLenum render_mode; + GLenum active_buffer; /* no separate buffers for reading and writing */ int glcurtex; int draw_count; GXColor clear_color; From 5c95fd4508862346b6cb7076b5c08cab069934ee Mon Sep 17 00:00:00 2001 From: Alberto Mardegan Date: Mon, 30 Sep 2024 17:31:09 +0300 Subject: [PATCH 2/3] Delay switching the EFB content type until really needed This can help avoid unnecessary switches (which are quite expensive). Do not assume that after drawing to the stencil we what to draw to the scene again, let's wait until a drawing command is effectively issued. --- src/call_lists.c | 3 +++ src/gc_gl.c | 4 ++++ src/stencil.c | 2 -- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/call_lists.c b/src/call_lists.c index ecd69ed..a20e2d9 100644 --- a/src/call_lists.c +++ b/src/call_lists.c @@ -32,6 +32,7 @@ POSSIBILITY OF SUCH DAMAGE. #include "call_lists.h" #include "debug.h" +#include "efb.h" #include "stencil.h" #include "utils.h" @@ -193,6 +194,8 @@ static void run_gx_list(struct GXDisplayList *gxlist) { struct client_state cs; + _ogx_efb_set_content_type(OGX_EFB_SCENE); + cs = glparamstate.cs; glparamstate.cs = gxlist->cs; _ogx_apply_state(); diff --git a/src/gc_gl.c b/src/gc_gl.c index 4e8591c..340b599 100644 --- a/src/gc_gl.c +++ b/src/gc_gl.c @@ -1161,6 +1161,8 @@ void glClear(GLbitfield mask) return; } + _ogx_efb_set_content_type(OGX_EFB_SCENE); + if (mask & GL_STENCIL_BUFFER_BIT) { _ogx_stencil_clear(); } @@ -2359,6 +2361,7 @@ void glDrawArrays(GLenum mode, GLint first, GLsizei count) _ogx_stencil_draw(flat_draw_geometry, &draw_data); } + _ogx_efb_set_content_type(OGX_EFB_SCENE); should_draw = _ogx_setup_render_stages(); _ogx_apply_state(); @@ -2401,6 +2404,7 @@ void glDrawElements(GLenum mode, GLsizei count, GLenum type, const GLvoid *indic _ogx_stencil_draw(flat_draw_elements, &draw_data); } + _ogx_efb_set_content_type(OGX_EFB_SCENE); should_draw = _ogx_setup_render_stages(); _ogx_apply_state(); /* When not building a display list, we can optimize the drawing by diff --git a/src/stencil.c b/src/stencil.c index 26857ea..93dfe66 100644 --- a/src/stencil.c +++ b/src/stencil.c @@ -549,8 +549,6 @@ void _ogx_stencil_draw(OgxStencilDrawCallback callback, void *cb_data) callback, cb_data); } - _ogx_efb_set_content_type(OGX_EFB_SCENE); - glparamstate.dirty.bits.dirty_texture_gen = 1; } From fe737d5390b5f88c4cdb2c2f10c05cfadb591b06 Mon Sep 17 00:00:00 2001 From: Alberto Mardegan Date: Tue, 1 Oct 2024 17:42:18 +0300 Subject: [PATCH 3/3] Implement glAccum() Generate the accumulation buffer by capturing the EFB contents, then use the appropriate blending modes to render the accumulation buffer on the scene. Tested with the accanti.c and accum.c sample programs found online: - https://openglut.sourceforge.net/accanti_8c.html - https://fossies.org/linux/mesa-demos/src/samples/accum.c --- CMakeLists.txt | 2 + src/accum.c | 213 ++++++++++++++++++++++++++++++++++++++++++++++++ src/accum.h | 39 +++++++++ src/efb.h | 1 + src/functions.c | 4 +- src/gc_gl.c | 15 ++++ src/state.h | 1 + 7 files changed, 273 insertions(+), 2 deletions(-) create mode 100644 src/accum.c create mode 100644 src/accum.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 832541b..280c661 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -19,6 +19,8 @@ set(TARGET opengx) include(GNUInstallDirs) add_library(${TARGET} STATIC + src/accum.c + src/accum.h src/arrays.cpp src/arrays.h src/call_lists.c diff --git a/src/accum.c b/src/accum.c new file mode 100644 index 0000000..c065c5a --- /dev/null +++ b/src/accum.c @@ -0,0 +1,213 @@ +/***************************************************************************** +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 "opengx.h" + +#include "accum.h" +#include "debug.h" +#include "efb.h" +#include "state.h" +#include "utils.h" + +#include +#include + +static OgxEfbBuffer *s_accum_buffer = NULL; + +static void draw_screen(GXTexObj *texture, float value) +{ + _ogx_setup_2D_projection(); + + u16 width = glparamstate.viewport[2]; + u16 height = glparamstate.viewport[3]; + + GX_ClearVtxDesc(); + GX_SetVtxDesc(GX_VA_POS, GX_DIRECT); + GX_SetVtxDesc(GX_VA_CLR0, GX_DIRECT); + GX_SetVtxDesc(GX_VA_TEX0, GX_DIRECT); + GX_SetVtxAttrFmt(GX_VTXFMT0, GX_VA_POS, GX_POS_XY, GX_U16, 0); + GX_SetVtxAttrFmt(GX_VTXFMT0, GX_VA_CLR0, GX_CLR_RGBA, GX_RGBA8, 0); + GX_SetVtxAttrFmt(GX_VTXFMT0, GX_VA_TEX0, GX_TEX_ST, GX_U8, 0); + if (texture) { + GX_SetTexCoordGen(GX_TEXCOORD0, GX_TG_MTX2x4, GX_TG_TEX0, GX_IDENTITY); + GX_SetTevOp(GX_TEVSTAGE0, GX_MODULATE); + GX_SetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD0, GX_TEXMAP0, GX_COLOR0A0); + GX_SetNumTexGens(1); + GX_LoadTexObj(texture, GX_TEXMAP0); + } else { + GX_SetTevOrder(GX_TEVSTAGE0, GX_TEXCOORDNULL, + GX_TEXMAP_NULL, GX_COLOR0A0); + GX_SetTevOp(GX_TEVSTAGE0, GX_PASSCLR); + GX_SetNumTexGens(0); + } + GX_SetNumTevStages(1); + GX_SetNumChans(1); + GX_SetChanCtrl(GX_COLOR0A0, GX_DISABLE, GX_SRC_VTX, GX_SRC_VTX, + 0, GX_DF_NONE, GX_AF_NONE); + + GX_SetCullMode(GX_CULL_NONE); + glparamstate.dirty.bits.dirty_cull = 1; + + GX_SetZMode(GX_FALSE, GX_ALWAYS, GX_FALSE); + glparamstate.dirty.bits.dirty_z = 1; + + GX_SetAlphaCompare(GX_ALWAYS, 0, GX_AOP_OR, GX_ALWAYS, 0); + glparamstate.dirty.bits.dirty_alphatest = 1; + + GX_SetColorUpdate(GX_TRUE); + glparamstate.dirty.bits.dirty_color_update = 1; + + u8 intensity = (u8)(value * 255.0f); + GX_Begin(GX_QUADS, GX_VTXFMT0, 4); + GX_Position2u16(0, 0); + GX_Color4u8(intensity, intensity, intensity, intensity); + GX_TexCoord2u8(0, 0); + GX_Position2u16(0, height); + GX_Color4u8(intensity, intensity, intensity, intensity); + GX_TexCoord2u8(0, 1); + GX_Position2u16(width, height); + GX_Color4u8(intensity, intensity, intensity, intensity); + GX_TexCoord2u8(1, 1); + GX_Position2u16(width, 0); + GX_Color4u8(intensity, intensity, intensity, intensity); + GX_TexCoord2u8(1, 0); + GX_End(); +} + +static OgxEfbBuffer *save_scene_into_texture() +{ + OgxEfbBuffer *scene = NULL; + + /* Since the accumulation buffer typically combines several frames, we + * don't need to capture the current scene with maximum precision; a 16-bit + * format should be enough */ + _ogx_efb_buffer_prepare(&scene, GX_TF_RGB565); + _ogx_efb_buffer_save(scene, OGX_EFB_COLOR); + return scene; +} + +void _ogx_accum_clear() +{ + if (!s_accum_buffer) return; + + void *texels; + u8 format, unused; + u16 width, height; + GX_GetTexObjAll(&s_accum_buffer->texobj, &texels, &width, &height, &format, + &unused, &unused, &unused); + texels = MEM_PHYSICAL_TO_K0(texels); + u32 size = GX_GetTexBufferSize(width, height, format, 0, GX_FALSE); + int n_blocks = size / 32; + GXColor c = glparamstate.accum_clear_color; + uint16_t pixcolor_ar = c.a << 8 | c.r; + uint16_t pixcolor_gb = c.g << 8 | c.b; + uint16_t *dst = texels; + for (int i = 0; i < n_blocks; i++) { + /* In RGBA8 format, the EFB alternates AR blocks with GB blocks */ + uint16_t pixcolor = (i % 2 == 0) ? pixcolor_ar : pixcolor_gb; + for (int j = 0; j < 16; j++) { + *dst++ = pixcolor; + } + } + DCStoreRangeNoSync(texels, size); +} + +void _ogx_accum_load_into_efb() +{ + GX_InvalidateTexAll(); + _ogx_efb_restore_texobj(&s_accum_buffer->texobj); +} + +void _ogx_accum_save_to_efb() +{ + GX_DrawDone(); + _ogx_efb_buffer_save(s_accum_buffer, OGX_EFB_COLOR); +} + +void glClearAccum(GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha) +{ + glparamstate.accum_clear_color.r = clampf_01(red) * 255.0f; + glparamstate.accum_clear_color.g = clampf_01(green) * 255.0f; + glparamstate.accum_clear_color.b = clampf_01(blue) * 255.0f; + glparamstate.accum_clear_color.a = clampf_01(alpha) * 255.0f; +} + +void glAccum(GLenum op, GLfloat value) +{ + OgxEfbBuffer *scene_buffer = NULL; + GXTexObj *texture = NULL; + bool must_draw = false; + + _ogx_efb_buffer_prepare(&s_accum_buffer, GX_TF_RGBA8); + if (op == GL_ACCUM || op == GL_LOAD) { + scene_buffer = save_scene_into_texture(); + texture = &scene_buffer->texobj; + } + + _ogx_efb_set_content_type(OGX_EFB_ACCUM); + if (op == GL_ACCUM || op == GL_LOAD) { + if (op == GL_ACCUM) { + GX_SetBlendMode(GX_BM_BLEND, GX_BL_ONE, GX_BL_ONE, GX_LO_COPY); + } else { + GX_SetBlendMode(GX_BM_NONE, GX_BL_ZERO, GX_BL_ZERO, GX_LO_COPY); + } + must_draw = true; + } else if (op == GL_ADD) { + GX_SetBlendMode(GX_BM_BLEND, GX_BL_ONE, GX_BL_ONE, GX_LO_COPY); + must_draw = true; + } else if (op == GL_MULT) { + GX_SetBlendMode(GX_BM_BLEND, GX_BL_ZERO, GX_BL_SRCALPHA, GX_LO_COPY); + must_draw = true; + } + + if (must_draw) { + draw_screen(texture, value); + glparamstate.dirty.bits.dirty_blend = 1; + } + + if (op == GL_RETURN && value == 1.0f) { + /* Just leave the accumulation buffer on the scene, since it doesn't + * leave to be altered */ + _ogx_efb_content_type = OGX_EFB_SCENE; + } else { + /* We must render the contents of the accumulation buffer with an + * intensity given by "value". */ + _ogx_efb_set_content_type(OGX_EFB_SCENE); + + GX_SetBlendMode(GX_BM_NONE, GX_BL_ZERO, GX_BL_ZERO, GX_LO_COPY); + glparamstate.dirty.bits.dirty_blend = 1; + draw_screen(&s_accum_buffer->texobj, value); + } + + if (scene_buffer) { + free(scene_buffer); + } +} diff --git a/src/accum.h b/src/accum.h new file mode 100644 index 0000000..b6a535f --- /dev/null +++ b/src/accum.h @@ -0,0 +1,39 @@ +/***************************************************************************** +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_ACCUM_H +#define OPENGX_ACCUM_H + +void _ogx_accum_clear(void); +void _ogx_accum_save_to_efb(void); +void _ogx_accum_load_into_efb(void); + +#endif /* OPENGX_ACCUM_H */ diff --git a/src/efb.h b/src/efb.h index 31f1859..4bf8189 100644 --- a/src/efb.h +++ b/src/efb.h @@ -48,6 +48,7 @@ typedef enum { typedef enum { OGX_EFB_SCENE = 1, OGX_EFB_STENCIL, + OGX_EFB_ACCUM, } OgxEfbContentType; extern OgxEfbContentType _ogx_efb_content_type; diff --git a/src/functions.c b/src/functions.c index ae889c4..4be4735 100644 --- a/src/functions.c +++ b/src/functions.c @@ -42,7 +42,7 @@ int _ogx_functions_c = 0; /* referenced by gc_gl.c, see the comment in there */ #define PROC(name) { #name, name } static const ProcMap s_proc_map[] = { - //PROC(glAccum), + PROC(glAccum), PROC(glAlphaFunc), //PROC(glAreTexturesResident), PROC(glArrayElement), @@ -54,7 +54,7 @@ static const ProcMap s_proc_map[] = { PROC(glCallList), PROC(glCallLists), PROC(glClear), - //PROC(glClearAccum), + PROC(glClearAccum), PROC(glClearColor), PROC(glClearDepth), //PROC(glClearIndex), diff --git a/src/gc_gl.c b/src/gc_gl.c index 340b599..fd5f945 100644 --- a/src/gc_gl.c +++ b/src/gc_gl.c @@ -45,6 +45,7 @@ POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ +#include "accum.h" #include "call_lists.h" #include "clip.h" #include "debug.h" @@ -212,6 +213,10 @@ void ogx_initialize() glparamstate.clear_color.g = 0; glparamstate.clear_color.b = 0; glparamstate.clear_color.a = 1; + glparamstate.accum_clear_color.r = 0; + glparamstate.accum_clear_color.g = 0; + glparamstate.accum_clear_color.b = 0; + glparamstate.accum_clear_color.a = 0; glparamstate.clearz = 1.0f; glparamstate.ztest = GX_FALSE; // Depth test disabled but z write enabled @@ -457,6 +462,9 @@ void _ogx_efb_set_content_type_real(OgxEfbContentType content_type) case OGX_EFB_STENCIL: _ogx_stencil_save_to_efb(); break; + case OGX_EFB_ACCUM: + _ogx_accum_save_to_efb(); + break; } /* Restore data from previously stored EFB for this content type */ @@ -467,6 +475,9 @@ void _ogx_efb_set_content_type_real(OgxEfbContentType content_type) case OGX_EFB_STENCIL: _ogx_stencil_load_into_efb(); break; + case OGX_EFB_ACCUM: + _ogx_accum_load_into_efb(); + break; } _ogx_efb_content_type = content_type; } @@ -1167,6 +1178,10 @@ void glClear(GLbitfield mask) _ogx_stencil_clear(); } + if (mask & GL_ACCUM_BUFFER_BIT) { + _ogx_accum_clear(); + } + if (mask & GL_DEPTH_BUFFER_BIT) { GX_SetZMode(GX_TRUE, GX_ALWAYS, GX_TRUE); GX_SetZCompLoc(GX_DISABLE); diff --git a/src/state.h b/src/state.h index ad2b6a4..3e58ab1 100644 --- a/src/state.h +++ b/src/state.h @@ -108,6 +108,7 @@ typedef struct glparams_ int glcurtex; int draw_count; GXColor clear_color; + GXColor accum_clear_color; float clearz; float polygon_offset_factor; float polygon_offset_units;