From f9c61daaff17acdab5a7adfa25fe297eaf6eaac5 Mon Sep 17 00:00:00 2001 From: Steve Rice Date: Thu, 8 Oct 2026 09:03:41 -0700 Subject: [PATCH 1/5] Pass the shader translators the dialect of GLSL to write nv2a_vertex_shader_to_glsl and nv2a_pixel_shader_to_glsl take a struct nv2a_dialect, which gl_initialize fills from the context, in place of their #ifdef HALO_ANDROID: the #version line, ES's precision qualifiers, the LOD bias each lookup passes where samplers have none (ES), and the clip space conversion glClipControl does on the desktop. The GLSL is the same as before, byte for byte, for every vertex program in the game and 715 pixel shader keys, in each of the three dialects. The first of five steps that move the renderer's OpenGL calls behind a backend interface. Co-Authored-By: Claude Opus 5.5 --- port/linux/src/d3d8_gl.c | 12 +++++- port/linux/src/nv2a_psh.c | 83 ++++++++++++++++----------------------- port/linux/src/nv2a_vsh.c | 35 +++++++---------- port/linux/src/xgpu.h | 48 ++++++++++++++-------- 4 files changed, 90 insertions(+), 88 deletions(-) diff --git a/port/linux/src/d3d8_gl.c b/port/linux/src/d3d8_gl.c index 912e3f1a84..c50e9c1bc9 100644 --- a/port/linux/src/d3d8_gl.c +++ b/port/linux/src/d3d8_gl.c @@ -598,6 +598,9 @@ static void color_to_vec4(D3DCOLOR color, float *out) out[3] = ((color >> 24) & 0xff) / 255.0f; } +/* the GLSL the translators write for this context (gl_initialize) */ +static struct nv2a_dialect shader_dialect; + /* ---------- debugging settings, read once (gl_initialize) */ static struct @@ -1481,6 +1484,10 @@ static void gl_initialize(void) xgpu_capabilities.anisotropy = host_gl_has_extension("GL_EXT_texture_filter_anisotropic"); xgpu_capabilities.base_vertex = es32; xgpu_capabilities.shading_language = major > 3 || (major == 3 && minor >= 1) ? "310 es" : "300 es"; + shader_dialect.version = xgpu_capabilities.shading_language; + shader_dialect.es = TRUE; + shader_dialect.lookup_lod_bias = TRUE; + shader_dialect.clip_control = TRUE; if (major > 3 || (major == 3 && minor >= 1)) { GLint counters = 0; @@ -1505,6 +1512,7 @@ static void gl_initialize(void) } glClipControl(GL_UPPER_LEFT, GL_ZERO_TO_ONE); glEnable(GL_PROGRAM_POINT_SIZE); + shader_dialect.version = "450 core"; #endif glGenVertexArrays(1, &device.vertex_array); glBindVertexArray(device.vertex_array); @@ -2520,7 +2528,7 @@ static GLuint vertex_shader_get(struct vertex_shader_object *program, BOOL immed if (!*shader) { - char *source = nv2a_vertex_shader_to_glsl(program->instructions, program->instruction_count, + char *source = nv2a_vertex_shader_to_glsl(&shader_dialect, program->instructions, program->instruction_count, immediate ? 0 : device.vertex_shader->packed_mask, lit ? &program->lighting : NULL); *shader = xgpu_compile_shader(GL_VERTEX_SHADER, source, "vertex"); @@ -2574,7 +2582,7 @@ static GLuint fragment_shader_get(const struct nv2a_pixel_shader_key *key) entry = calloc(1, sizeof(*entry)); entry->hash = hash; entry->key = *key; - source = nv2a_pixel_shader_to_glsl(key); + source = nv2a_pixel_shader_to_glsl(&shader_dialect, key); entry->shader = xgpu_compile_shader(GL_FRAGMENT_SHADER, source, "pixel"); if (debug_settings.dump_shaders) { diff --git a/port/linux/src/nv2a_psh.c b/port/linux/src/nv2a_psh.c index 211146d159..d5e958fa71 100644 --- a/port/linux/src/nv2a_psh.c +++ b/port/linux/src/nv2a_psh.c @@ -314,49 +314,28 @@ static void dot_input(struct xgpu_text *text, const DWORD *state, int stage) } } -#ifdef HALO_ANDROID -/* ES samplers have no LOD bias: pass D3DTSS_MIPMAPLODBIAS to the lookup */ -#define SAMPLE_BIAS ", texture_lod_bias[%d]" -#define SHADER_VERSION \ - "precision highp float;\n" \ - "precision highp int;\n" \ - "precision highp sampler2D;\n" \ - "precision highp sampler3D;\n" \ - "precision highp samplerCube;\n" -#else -#define SAMPLE_BIAS "" -#define SHADER_VERSION "#version 450 core\n" -#endif - -static void sample(struct xgpu_text *text, const struct nv2a_pixel_shader_key *key, int stage, const char *coordinates) +static void sample(struct xgpu_text *text, const struct nv2a_dialect *dialect, const struct nv2a_pixel_shader_key *key, + int stage, const char *coordinates) { switch (key->sampler_type[stage]) { case _xgpu_sampler_3d: - xgpu_text_append(text, "texture(tex%d, (%s).xyz" SAMPLE_BIAS ")", stage, coordinates -#ifdef HALO_ANDROID - , stage -#endif - ); - break; case _xgpu_sampler_cube: - xgpu_text_append(text, "texture(tex%d, (%s).xyz" SAMPLE_BIAS ")", stage, coordinates -#ifdef HALO_ANDROID - , stage -#endif - ); + xgpu_text_append(text, "texture(tex%d, (%s).xyz", stage, coordinates); break; default: - xgpu_text_append(text, "texture(tex%d, (%s).xy * texture_scale[%d].xy" SAMPLE_BIAS ")", stage, coordinates, stage -#ifdef HALO_ANDROID - , stage -#endif - ); + xgpu_text_append(text, "texture(tex%d, (%s).xy * texture_scale[%d].xy", stage, coordinates, stage); break; } + /* (ES samplers have no LOD bias: D3DTSS_MIPMAPLODBIAS goes to the + lookup) */ + if (dialect->lookup_lod_bias) + xgpu_text_append(text, ", texture_lod_bias[%d]", stage); + xgpu_text_append(text, ")"); } -static void texture_stage(struct xgpu_text *text, const struct nv2a_pixel_shader_key *key, int stage) +static void texture_stage(struct xgpu_text *text, const struct nv2a_dialect *dialect, + const struct nv2a_pixel_shader_key *key, int stage) { const DWORD *state = key->combiner_state; unsigned long mode = stage_mode(key, stage); @@ -374,7 +353,7 @@ static void texture_stage(struct xgpu_text *text, const struct nv2a_pixel_shader case _mode_project3d: snprintf(coordinates, sizeof(coordinates), "vec4(xT%d.xyz / (xT%d.w != 0.0 ? xT%d.w : 1.0), 1.0)", stage, stage, stage); xgpu_text_append(text, "\tt%d = ", stage); - sample(text, key, stage, coordinates); + sample(text, dialect, key, stage, coordinates); xgpu_text_append(text, ";\n"); if (stage == 0 && key->point_threshold && key->sampler_type[stage] == _xgpu_sampler_2d) { @@ -391,7 +370,7 @@ static void texture_stage(struct xgpu_text *text, const struct nv2a_pixel_shader case _mode_cubemap: snprintf(coordinates, sizeof(coordinates), "xT%d", stage); xgpu_text_append(text, "\tt%d = ", stage); - sample(text, key, stage, coordinates); + sample(text, dialect, key, stage, coordinates); xgpu_text_append(text, ";\n"); break; case _mode_passthru: @@ -420,7 +399,7 @@ static void texture_stage(struct xgpu_text *text, const struct nv2a_pixel_shader xgpu_text_append(text, "\t\tvec2 coordinates = xT%d.xy + vec2(bump_matrix[%d].x * d.x + bump_matrix[%d].z * d.y," " bump_matrix[%d].y * d.x + bump_matrix[%d].w * d.y);\n", stage, stage, stage, stage, stage); xgpu_text_append(text, "\t\tt%d = ", stage); - sample(text, key, stage, "vec4(coordinates, 0.0, 1.0)"); + sample(text, dialect, key, stage, "vec4(coordinates, 0.0, 1.0)"); xgpu_text_append(text, ";\n"); if (mode == _mode_bumpenvmap_luminance) { @@ -440,7 +419,7 @@ static void texture_stage(struct xgpu_text *text, const struct nv2a_pixel_shader dot_input(text, state, stage); xgpu_text_append(text, ");\n\tt%d = ", stage); snprintf(coordinates, sizeof(coordinates), "vec4(dot%d, dot%d, 0.0, 1.0)", stage - 1, stage); - sample(text, key, stage, coordinates); + sample(text, dialect, key, stage, coordinates); xgpu_text_append(text, ";\n"); break; case _mode_dot_zw: @@ -455,7 +434,7 @@ static void texture_stage(struct xgpu_text *text, const struct nv2a_pixel_shader xgpu_text_append(text, ");\n\tdot3 = dot(xT3.xyz, "); dot_input(text, state, 3); xgpu_text_append(text, ");\n\tt%d = ", stage); - sample(text, key, stage, "vec4(dot1, dot2, dot3, 1.0)"); + sample(text, dialect, key, stage, "vec4(dot1, dot2, dot3, 1.0)"); xgpu_text_append(text, ";\n"); break; case _mode_dot_reflect_specular: @@ -468,7 +447,7 @@ static void texture_stage(struct xgpu_text *text, const struct nv2a_pixel_shader else xgpu_text_append(text, "\t\tvec3 e = ps_c0[0].xyz;\n"); xgpu_text_append(text, "\t\tvec3 r = 2.0 * n * dot(n, e) / max(dot(n, n), 1.0e-20) - e;\n\t\tt%d = ", stage); - sample(text, key, stage, "vec4(r, 1.0)"); + sample(text, dialect, key, stage, "vec4(r, 1.0)"); xgpu_text_append(text, ";\n\t}\n"); break; case _mode_dot_str_3d: @@ -476,19 +455,19 @@ static void texture_stage(struct xgpu_text *text, const struct nv2a_pixel_shader xgpu_text_append(text, "\tdot%d = dot(xT%d.xyz, ", stage, stage); dot_input(text, state, stage); xgpu_text_append(text, ");\n\tt%d = ", stage); - sample(text, key, stage, "vec4(dot1, dot2, dot3, 1.0)"); + sample(text, dialect, key, stage, "vec4(dot1, dot2, dot3, 1.0)"); xgpu_text_append(text, ";\n"); break; case _mode_dependent_ar: snprintf(coordinates, sizeof(coordinates), "vec4(t%d.a, t%d.r, 0.0, 1.0)", stage_input(state, stage), stage_input(state, stage)); xgpu_text_append(text, "\tt%d = ", stage); - sample(text, key, stage, coordinates); + sample(text, dialect, key, stage, coordinates); xgpu_text_append(text, ";\n"); break; case _mode_dependent_gb: snprintf(coordinates, sizeof(coordinates), "vec4(t%d.g, t%d.b, 0.0, 1.0)", stage_input(state, stage), stage_input(state, stage)); xgpu_text_append(text, "\tt%d = ", stage); - sample(text, key, stage, coordinates); + sample(text, dialect, key, stage, coordinates); xgpu_text_append(text, ";\n"); break; default: @@ -582,7 +561,7 @@ static void model_lighting(struct xgpu_text *text, BOOL point_lights) xgpu_text_append(text, "\treturn clamp(light, 0.0, 1.0);\n}\n"); } -char *nv2a_pixel_shader_to_glsl(const struct nv2a_pixel_shader_key *key) +char *nv2a_pixel_shader_to_glsl(const struct nv2a_dialect *dialect, const struct nv2a_pixel_shader_key *key) { const DWORD *state = key->combiner_state; struct xgpu_text text = { 0 }; @@ -594,8 +573,7 @@ char *nv2a_pixel_shader_to_glsl(const struct nv2a_pixel_shader_key *key) if (combiner_count > 8) combiner_count = 8; -#ifdef HALO_ANDROID - xgpu_text_append(&text, "#version %s\n", xgpu_capabilities.shading_language); + xgpu_text_append(&text, "#version %s\n", dialect->version); if (key->count_samples) { /* samples that pass the depth and stencil tests, as the NV2A's @@ -604,9 +582,16 @@ char *nv2a_pixel_shader_to_glsl(const struct nv2a_pixel_shader_key *key) "layout(early_fragment_tests) in;\n" "layout(binding = 0, offset = 0) uniform atomic_uint visible_samples;\n"); } -#endif + if (dialect->es) + { + xgpu_text_append(&text, + "precision highp float;\n" + "precision highp int;\n" + "precision highp sampler2D;\n" + "precision highp sampler3D;\n" + "precision highp samplerCube;\n"); + } xgpu_text_append(&text, - SHADER_VERSION "in vec4 xD0;\n" "in vec4 xD1;\n" "in vec4 xB0;\n" @@ -618,6 +603,8 @@ char *nv2a_pixel_shader_to_glsl(const struct nv2a_pixel_shader_key *key) "in float xFog;\n" "layout(location = 0) out vec4 fragment_color;\n" XGPU_PIXEL_UNIFORMS); + if (dialect->lookup_lod_bias) + xgpu_text_append(&text, XGPU_PIXEL_UNIFORM_LOD_BIAS); for (stage = 0; stage < 4; stage++) xgpu_text_append(&text, "uniform %s tex%d;\n", sampler_declaration(key->sampler_type[stage]), stage); if (key->per_pixel_lighting) @@ -642,7 +629,7 @@ char *nv2a_pixel_shader_to_glsl(const struct nv2a_pixel_shader_key *key) xgpu_text_append(&text, "\tv0.rgb = model_lighting();\n"); for (stage = 0; stage < 4; stage++) - texture_stage(&text, key, stage); + texture_stage(&text, dialect, key, stage); /* the fog register: rgb is the fog color, alpha the fog factor */ if (key->fog_enable) @@ -741,10 +728,8 @@ char *nv2a_pixel_shader_to_glsl(const struct nv2a_pixel_shader_key *key) xgpu_text_append(&text, "\tresult = vec4(t0.rgb, 1.0);\n"); if (config_boolean("debug.gpu_debug_flat")) xgpu_text_append(&text, "\tresult = xD0.a > 0.0 ? vec4(xD0.rgb, 1.0) : vec4(1.0, 0.0, 1.0, 1.0);\n"); -#ifdef HALO_ANDROID if (key->count_samples) xgpu_text_append(&text, "\tatomicCounterIncrement(visible_samples);\n"); -#endif xgpu_text_append(&text, "\tfragment_color = clamp(result, 0.0, 1.0);\n}\n"); return text.buffer; } diff --git a/port/linux/src/nv2a_vsh.c b/port/linux/src/nv2a_vsh.c index 076696568a..2431b11e6c 100644 --- a/port/linux/src/nv2a_vsh.c +++ b/port/linux/src/nv2a_vsh.c @@ -329,13 +329,6 @@ BOOL nv2a_vertex_shader_lighting(const DWORD *instructions, unsigned long instru /* ---------- translation */ static const char shader_prologue[] = -#ifdef HALO_ANDROID - /* the #version line comes first, from the context's capabilities */ - "precision highp float;\n" - "precision highp int;\n" -#else - "#version 450 core\n" -#endif "uniform vec4 c[192];\n" "uniform vec4 viewport_scale;\n" "uniform vec4 viewport_offset;\n" @@ -383,15 +376,15 @@ static const char shader_prologue[] = " return vec4(1.0, max(s.x, 0.0), specular, 1.0);\n" "}\n"; -char *nv2a_vertex_shader_to_glsl(const DWORD *instructions, unsigned long instruction_count, - unsigned long packed_attribute_mask, const struct nv2a_vertex_lighting *lighting) +char *nv2a_vertex_shader_to_glsl(const struct nv2a_dialect *dialect, const DWORD *instructions, + unsigned long instruction_count, unsigned long packed_attribute_mask, const struct nv2a_vertex_lighting *lighting) { struct xgpu_text text = { 0 }; unsigned long index; -#ifdef HALO_ANDROID - xgpu_text_append(&text, "#version %s\n", xgpu_capabilities.shading_language); -#endif + xgpu_text_append(&text, "#version %s\n", dialect->version); + if (dialect->es) + xgpu_text_append(&text, "precision highp float;\nprecision highp int;\n"); xgpu_text_append(&text, "%s", shader_prologue); /* (the pixel shader's model_lighting; the normal's length in w) */ if (lighting) @@ -564,13 +557,17 @@ char *nv2a_vertex_shader_to_glsl(const DWORD *instructions, unsigned long instru which the clipper also takes. */ "\tif (!(abs(position.w) > 0.0))\n" "\t\tposition = vec4(0.0, 0.0, 0.0, -1.0);\n" - "\tgl_Position = position;\n" -#ifdef HALO_ANDROID + "\tgl_Position = position;\n", + XGPU_VERTEX_CONSTANT_BIAS - 38, XGPU_VERTEX_CONSTANT_BIAS - 37); + if (dialect->clip_control) + { /* what glClipControl(GL_UPPER_LEFT, GL_ZERO_TO_ONE) does on desktop GL: rows from the top, depth 0..1 */ - "\tgl_Position.y = -gl_Position.y;\n" - "\tgl_Position.z = 2.0 * gl_Position.z - gl_Position.w;\n" -#endif + xgpu_text_append(&text, + "\tgl_Position.y = -gl_Position.y;\n" + "\tgl_Position.z = 2.0 * gl_Position.z - gl_Position.w;\n"); + } + xgpu_text_append(&text, "\tgl_PointSize = oPts.x;\n" "\txD0 = clamp(oD0, 0.0, 1.0);\n" "\txD1 = clamp(oD1, 0.0, 1.0);\n" @@ -581,8 +578,6 @@ char *nv2a_vertex_shader_to_glsl(const DWORD *instructions, unsigned long instru "\txT2 = oT2;\n" "\txT3 = oT3;\n" "\txFog = oFog.x;\n" - "}\n", - XGPU_VERTEX_CONSTANT_BIAS - 38, XGPU_VERTEX_CONSTANT_BIAS - 37 - ); + "}\n"); return text.buffer; } diff --git a/port/linux/src/xgpu.h b/port/linux/src/xgpu.h index 8d371c455a..a37e3b71a8 100644 --- a/port/linux/src/xgpu.h +++ b/port/linux/src/xgpu.h @@ -52,6 +52,26 @@ written */ GLuint xgpu_compile_shader(GLenum type, const char *code, const char *what); GLuint xgpu_link_program(GLuint vertex_shader, GLuint fragment_shader, const char *what); +/* ---------- the shading language + +The translators write the GLSL of the context the device has: desktop GL +4.5's, or OpenGL ES's on Android ("300 es" or "310 es"), as gl_initialize +found it (d3d8_gl.c). */ + +struct nv2a_dialect +{ + /* what follows #version: "450 core", "300 es" or "310 es" */ + const char *version; + /* OpenGL ES: the precision qualifiers */ + BOOL es; + /* the samplers have no LOD bias of their own: each lookup passes + D3DTSS_MIPMAPLODBIAS (texture_lod_bias) */ + BOOL lookup_lod_bias; + /* the vertex shader does what glClipControl(GL_UPPER_LEFT, + GL_ZERO_TO_ONE) does on desktop GL: rows from the top, depth 0..1 */ + BOOL clip_control; +}; + /* ---------- generated source text */ struct xgpu_text @@ -89,13 +109,13 @@ BOOL nv2a_vertex_shader_lighting(const DWORD *instructions, unsigned long instru struct nv2a_vertex_lighting *lighting); /* GLSL for an NV2A vertex program (the instruction words after the program -header). Attributes whose bit is set in packed_attribute_mask are fed as -NORMPACKED3 32-bit integers and unpacked in the shader. With lighting (else -NULL), the normal and world position go to the pixel shader too, which -lights the diffuse color for each pixel (nv2a_pixel_shader_key -per_pixel_lighting). Returns a malloc'd string. */ -char *nv2a_vertex_shader_to_glsl(const DWORD *instructions, unsigned long instruction_count, - unsigned long packed_attribute_mask, const struct nv2a_vertex_lighting *lighting); +header), in the dialect given. Attributes whose bit is set in +packed_attribute_mask are fed as NORMPACKED3 32-bit integers and unpacked in +the shader. With lighting (else NULL), the normal and world position go to +the pixel shader too, which lights the diffuse color for each pixel +(nv2a_pixel_shader_key per_pixel_lighting). Returns a malloc'd string. */ +char *nv2a_vertex_shader_to_glsl(const struct nv2a_dialect *dialect, const DWORD *instructions, + unsigned long instruction_count, unsigned long packed_attribute_mask, const struct nv2a_vertex_lighting *lighting); /* ---------- pixel shaders */ @@ -145,14 +165,7 @@ struct nv2a_pixel_shader_key unsigned char alpha_test_samples; }; -char *nv2a_pixel_shader_to_glsl(const struct nv2a_pixel_shader_key *key); - -#ifdef HALO_ANDROID -/* ES samplers have no LOD bias of their own */ -#define XGPU_PIXEL_UNIFORMS_ES "uniform vec4 texture_lod_bias;\n" -#else -#define XGPU_PIXEL_UNIFORMS_ES "" -#endif +char *nv2a_pixel_shader_to_glsl(const struct nv2a_dialect *dialect, const struct nv2a_pixel_shader_key *key); /* the combiner registers that live in uniforms rather than in the program: C0/C1 of each stage and the final combiner, and texture constants */ @@ -166,8 +179,9 @@ C0/C1 of each stage and the final combiner, and texture constants */ "uniform float alpha_reference;\n" \ "uniform vec4 bump_matrix[4];\n" \ "uniform vec4 bump_luminance[4];\n" \ - "uniform vec4 texture_scale[4];\n" \ - XGPU_PIXEL_UNIFORMS_ES + "uniform vec4 texture_scale[4];\n" +/* ... and with dialect lookup_lod_bias, each stage's LOD bias */ +#define XGPU_PIXEL_UNIFORM_LOD_BIAS "uniform vec4 texture_lod_bias;\n" /* the vertex constants the per-pixel model lighting reads, in a uniform of their own (the vertex shader's 192 would pass OpenGL ES's least fragment From 3ef060710971b14c7c143ad6e9d0202a01a67f39 Mon Sep 17 00:00:00 2001 From: Steve Rice Date: Thu, 8 Oct 2026 09:26:00 -0700 Subject: [PATCH 2/5] Put textures and samplers behind a GPU backend interface gpu.h is the interface between the Direct3D device and the code that drives the GPU for it, in fixed-width types only, and gpu_gl.c is its OpenGL backend. This step gives it textures, render targets included, and samplers: xbox_textures.c makes, uploads, reads back and drops its textures through gpu_texture_*, the device makes render targets and the water's mip composites the same way, and it describes each texture stage as a struct gpu_stage, which gpu_gl.c binds with its sampler objects. gpu_initialize probes the context and reports what the device needs to know in a struct gpu_capabilities. The GL state cache, the framebuffers and the multisampled renderbuffers move to gpu_gl.c with them. Until the next steps move the device's draws, clears and presentation behind gpu.h, gpu_gl.h declares what the device still uses directly. The GL calls are the same but for a few redundant ones that go: a texture is bound again for each level it uploads, and sampler objects are cached by the sampler state alone, so the high-res HUD's no longer has an identical twin. The GLSL is unchanged. Co-Authored-By: Claude Opus 5.5 --- port/linux/src/d3d8_gl.c | 967 ++++-------------------------- port/linux/src/gpu.h | 180 ++++++ port/linux/src/gpu_gl.c | 1015 ++++++++++++++++++++++++++++++++ port/linux/src/gpu_gl.h | 203 +++++++ port/linux/src/xbox_textures.c | 170 ++---- port/linux/src/xgpu.h | 49 +- port/linux/src/xgpu_post.c | 1 + 7 files changed, 1594 insertions(+), 991 deletions(-) create mode 100644 port/linux/src/gpu.h create mode 100644 port/linux/src/gpu_gl.c create mode 100644 port/linux/src/gpu_gl.h diff --git a/port/linux/src/d3d8_gl.c b/port/linux/src/d3d8_gl.c index c50e9c1bc9..0469e8d414 100644 --- a/port/linux/src/d3d8_gl.c +++ b/port/linux/src/d3d8_gl.c @@ -22,6 +22,7 @@ Conventions carried over from the Xbox: */ #include "xgpu.h" +#include "gpu_gl.h" #include "sdl_platform.h" #include "halo_ui_pointer.h" #include "port_config.h" @@ -35,25 +36,8 @@ Conventions carried over from the Xbox: void d3d8_surface_initialize(D3DSurface *surface, D3DFORMAT format, unsigned long width, unsigned long height); void d3d8_surface_resize(D3DSurface *surface, D3DFORMAT format, unsigned long width, unsigned long height); -#ifdef HALO_ANDROID -/* OpenGL ES 3 (port/android/README.md): the desktop formats, enumerants -and entry points used below that ES lacks */ -#define GL_BGRA GL_RGBA -#define glDepthRange glDepthRangef -#define glClearDepth glClearDepthf -#ifndef GL_TEXTURE_MAX_ANISOTROPY_EXT -#define GL_TEXTURE_MAX_ANISOTROPY_EXT 0x84fe -#endif -#ifndef GL_TEXTURE_BORDER_COLOR -#define GL_TEXTURE_BORDER_COLOR 0x1004 -#endif -#ifndef GL_CLAMP_TO_BORDER -#define GL_CLAMP_TO_BORDER 0x812d -#endif - -/* what the context supports (gl_initialize) */ -struct xgpu_capabilities xgpu_capabilities; -#endif +/* what the GPU backend can do (gl_initialize) */ +struct gpu_capabilities xgpu_gpu_capabilities; /* ---------- the screen's width @@ -92,7 +76,7 @@ menus are drawn over it (halo_screen_anti_alias, xgpu_post.c); "ssaa2x" draws the screen's targets at twice the resolution each way (screen_mode_choose), which the display blit scales down; "msaa2x" to "msaa8x" draw the back buffer and its depth buffer with that many samples a -pixel (render_target_multisample). The setting is read again between frames +pixel (bind_targets). The setting is read again between frames (halo_screen_commit), so that a change applies from the next one. */ enum @@ -129,13 +113,12 @@ static const struct #define NUMBER_OF_ANTI_ALIASING_VALUES ((int)(sizeof(anti_aliasing_values) / sizeof(anti_aliasing_values[0]))) -/* the value in effect, -1 until the setting is first read; multisampling's -samples a pixel, at most the GPU's (anti_aliasing_prepare); and the GPU's -most samples and largest target (texture and renderbuffer), 0 until the GL -context exists */ +/* the value in effect, -1 until the setting is first read; and +multisampling's samples a pixel, at most the GPU's (anti_aliasing_prepare; +the GPU's most samples and largest target are 0 until the GL context +exists, xgpu_gpu_capabilities) */ static int anti_aliasing_value = -1; static int anti_aliasing_samples; -static GLint anti_aliasing_maximum_samples, maximum_target_size; static void anti_aliasing_prepare(void); @@ -203,14 +186,15 @@ static void screen_mode_choose(long *width, float scale[2]) largest target has (once it is known), which the display blit scales down; not with "original" resolution scaling, which draws the Xbox's 640x480 */ - if (anti_aliasing() == _anti_aliasing_ssaa && maximum_target_size > 0) + if (anti_aliasing() == _anti_aliasing_ssaa && xgpu_gpu_capabilities.max_target_size > 0) { + float maximum_target_size = (float)xgpu_gpu_capabilities.max_target_size; float factor = 2.0f; - if (*width * scale[0] * factor > (float)maximum_target_size) - factor = (float)maximum_target_size / (*width * scale[0]); - if (SCREEN_HEIGHT * scale[1] * factor > (float)maximum_target_size) - factor = (float)maximum_target_size / (SCREEN_HEIGHT * scale[1]); + if (*width * scale[0] * factor > maximum_target_size) + factor = maximum_target_size / (*width * scale[0]); + if (SCREEN_HEIGHT * scale[1] * factor > maximum_target_size) + factor = maximum_target_size / (SCREEN_HEIGHT * scale[1]); if (factor > 1.0f) { scale[0] *= factor; @@ -412,7 +396,7 @@ struct render_target_entry struct xgpu_render_target target; unsigned long last_rendered; /* the back buffer or its depth buffer, which the 3D view is drawn into: - multisampled with multisampling (render_target_multisample) */ + multisampled with multisampling (bind_targets) */ BOOL screen_buffer; }; @@ -427,18 +411,7 @@ static struct render_target_entry **render_target_bucket(unsigned long data) return &render_target_buckets[((data >> 12) ^ (data >> 20)) % RENDER_TARGET_BUCKET_COUNT]; } -struct framebuffer_entry -{ - struct framebuffer_entry *next; - GLuint color; - GLuint depth; - /* color and depth are multisampled renderbuffers, not textures */ - BOOL renderbuffers; - GLuint framebuffer; -}; - static struct render_target_entry *render_targets; -static struct framebuffer_entry *framebuffers; /* ---------- the device */ @@ -512,7 +485,6 @@ struct gl_device unsigned long stream_offset; GLuint index_buffer; unsigned long index_offset; - GLuint samplers[D3DTSS_MAXSTAGES]; GLuint queries[VISIBILITY_TEST_SLOTS]; BOOL query_pending[VISIBILITY_TEST_SLOTS]; @@ -612,352 +584,6 @@ static struct BOOL statistics; } debug_settings; -/* ---------- GL state cache - -Consecutive draws share most of their state, but each sets all of it: the -setters here skip the call when GL already holds the value. Code that -changes GL state behind the cache's back (clears, presentation, texture -uploads, render target and framebuffer creation) calls -xgpu_gl_state_invalidate, after which every value is set again. Unknown -values are all ones, which no real value matches (floats become NaN, which -compares unequal to everything). */ - -#ifdef HALO_ANDROID -struct attribute_pointer -{ - GLuint buffer; - GLint size; - GLenum type; - GLboolean normalized; - GLboolean integer; - GLsizei stride; - unsigned long offset; -}; -#else -/* desktop GL (4.3) separates an attribute's format from the buffer it -reads: the attributes of a stream share one binding, so a draw that moves -the stream rebinds it once instead of pointing each attribute again */ -struct attribute_format -{ - GLint size; - GLenum type; - GLboolean normalized; - GLboolean integer; - GLuint relative_offset; - GLuint binding; -}; - -struct vertex_binding -{ - GLuint buffer; - unsigned long offset; - GLsizei stride; -}; - -/* a binding per stream (setup_streams); GL has at least 16 */ -#define VERTEX_BINDING_COUNT 16 - -/* a vertex array object for each vertex layout (the attributes a draw -enables, and each one's format and binding), made once: a draw binds its -layout's and points the bindings at its streams, rather than enabling, -formatting and binding each attribute again (setup_streams) */ -struct vertex_layout -{ - unsigned long enabled; - struct attribute_format formats[XGPU_VERTEX_ATTRIBUTE_COUNT]; -}; - -struct vertex_array_entry -{ - struct vertex_array_entry *next; - unsigned long hash; - struct vertex_layout layout; - GLuint vertex_array; - /* its bindings, as last pointed (nothing else changes them) */ - struct vertex_binding bindings[VERTEX_BINDING_COUNT]; -}; - -#define VERTEX_ARRAY_BUCKET_COUNT 64 -static struct vertex_array_entry *vertex_array_buckets[VERTEX_ARRAY_BUCKET_COUNT]; -static struct vertex_array_entry *current_vertex_array; -#endif - -static struct -{ - GLuint program; - GLuint framebuffer; - GLint viewport[4]; - GLint scissor[4]; - float depth_range[2]; - unsigned char depth_test, stencil_test, blend, cull_face, offset_fill, offset_line; - unsigned char scissor_test; - GLenum depth_function; - unsigned char depth_mask; - GLenum stencil_function; - GLint stencil_reference; - GLuint stencil_value_mask; - GLenum stencil_operations[3]; - GLuint stencil_write_mask; - GLenum blend_source, blend_destination, blend_equation; - float blend_color[4]; - unsigned char color_mask; - GLenum front_face, cull_mode, polygon_mode; - float polygon_offset[2]; - GLenum active_texture; - /* per unit: the GL_TEXTURE_2D, GL_TEXTURE_CUBE_MAP and GL_TEXTURE_3D - bindings */ - GLuint textures[D3DTSS_MAXSTAGES][3]; - GLuint samplers[D3DTSS_MAXSTAGES]; - GLuint array_buffer; - GLuint element_array_buffer; - unsigned char attribute_enabled[XGPU_VERTEX_ATTRIBUTE_COUNT]; -#ifdef HALO_ANDROID - struct attribute_pointer attribute_pointers[XGPU_VERTEX_ATTRIBUTE_COUNT]; -#else - GLuint vertex_array; -#endif - /* a disabled attribute's value; kind 1 is the integer zero */ - unsigned char attribute_value_kind[XGPU_VERTEX_ATTRIBUTE_COUNT]; - float attribute_values[XGPU_VERTEX_ATTRIBUTE_COUNT][4]; -} gl_state; - -void xgpu_gl_state_invalidate(void) -{ - memset(&gl_state, 0xff, sizeof(gl_state)); -} - -static void state_enable(unsigned char *shadow, GLenum capability, BOOL enabled) -{ - unsigned char value = enabled ? 1 : 0; - - if (*shadow == value) - return; - *shadow = value; - if (value) - glEnable(capability); - else - glDisable(capability); -} - -static void state_program(GLuint program) -{ - if (gl_state.program != program) - { - gl_state.program = program; - glUseProgram(program); - } -} - -static void state_framebuffer(GLuint framebuffer) -{ - if (gl_state.framebuffer != framebuffer) - { - gl_state.framebuffer = framebuffer; - glBindFramebuffer(GL_FRAMEBUFFER, framebuffer); - } -} - -/* gl_state.textures' slot of a target */ -static int texture_slot(GLenum target) -{ - return target == GL_TEXTURE_CUBE_MAP ? 1 : target == GL_TEXTURE_3D ? 2 : 0; -} - -#ifdef HALO_ANDROID -static void state_texture(int unit, GLenum target, GLuint texture) -{ - int slot = texture_slot(target); - - if (gl_state.textures[unit][slot] == texture) - return; - if (gl_state.active_texture != GL_TEXTURE0 + (GLenum)unit) - { - gl_state.active_texture = GL_TEXTURE0 + (GLenum)unit; - glActiveTexture(gl_state.active_texture); - } - gl_state.textures[unit][slot] = texture; - glBindTexture(target, texture); -} -#endif - -static void state_sampler(int unit, GLuint sampler) -{ - if (gl_state.samplers[unit] != sampler) - { - gl_state.samplers[unit] = sampler; - glBindSampler((GLuint)unit, sampler); - } -} - -static void state_array_buffer(GLuint buffer) -{ - if (gl_state.array_buffer != buffer) - { - gl_state.array_buffer = buffer; - glBindBuffer(GL_ARRAY_BUFFER, buffer); - } -} - -static void state_element_array_buffer(GLuint buffer) -{ - if (gl_state.element_array_buffer != buffer) - { - gl_state.element_array_buffer = buffer; - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffer); - } -} - -#ifdef HALO_ANDROID -/* enables the attribute, reading size elements of type from buffer: each -vertex is stride bytes on from the one before it, starting at -buffer_offset, with the attribute relative_offset bytes into it */ -static void state_attribute_stream(GLuint index, GLuint binding, GLuint buffer, GLint size, GLenum type, - GLboolean normalized, BOOL integer, GLsizei stride, unsigned long buffer_offset, unsigned long relative_offset) -{ - struct attribute_pointer *pointer = &gl_state.attribute_pointers[index]; - unsigned long offset = buffer_offset + relative_offset; - - (void)binding; - if (gl_state.attribute_enabled[index] != 1) - { - gl_state.attribute_enabled[index] = 1; - glEnableVertexAttribArray(index); - } - if (pointer->buffer == buffer && pointer->size == size && pointer->type == type && - pointer->normalized == normalized && pointer->integer == (integer ? GL_TRUE : GL_FALSE) && - pointer->stride == stride && pointer->offset == offset) - { - return; - } - state_array_buffer(buffer); - if (integer) - glVertexAttribIPointer(index, size, type, stride, (const void *)offset); - else - glVertexAttribPointer(index, size, type, normalized, stride, (const void *)offset); - pointer->buffer = buffer; - pointer->size = size; - pointer->type = type; - pointer->normalized = normalized; - pointer->integer = integer ? GL_TRUE : GL_FALSE; - pointer->stride = stride; - pointer->offset = offset; -} -#else -/* the vertex array of a layout, made the first time (its attributes enabled, -formatted and bound to their bindings) */ -static struct vertex_array_entry *vertex_array_get(const struct vertex_layout *layout) -{ - const unsigned char *bytes = (const unsigned char *)layout; - unsigned long hash = 2166136261UL, index; - struct vertex_array_entry **bucket, *entry; - - for (index = 0; index < sizeof(*layout); index++) - hash = (hash ^ bytes[index]) * 16777619UL; - bucket = &vertex_array_buckets[hash % VERTEX_ARRAY_BUCKET_COUNT]; - for (entry = *bucket; entry; entry = entry->next) - { - if (entry->hash == hash && !memcmp(&entry->layout, layout, sizeof(*layout))) - return entry; - } - entry = calloc(1, sizeof(*entry)); - entry->hash = hash; - entry->layout = *layout; - memset(entry->bindings, 0xff, sizeof(entry->bindings)); - glGenVertexArrays(1, &entry->vertex_array); - glBindVertexArray(entry->vertex_array); - gl_state.vertex_array = entry->vertex_array; - /* (the index buffer's binding is the vertex array's) */ - gl_state.element_array_buffer = (GLuint)-1; - for (index = 0; index < XGPU_VERTEX_ATTRIBUTE_COUNT; index++) - { - const struct attribute_format *format = &layout->formats[index]; - - if (!(layout->enabled & (1UL << index))) - continue; - glEnableVertexAttribArray((GLuint)index); - if (format->integer) - glVertexAttribIFormat((GLuint)index, format->size, format->type, format->relative_offset); - else - glVertexAttribFormat((GLuint)index, format->size, format->type, format->normalized, format->relative_offset); - glVertexAttribBinding((GLuint)index, format->binding); - } - entry->next = *bucket; - *bucket = entry; - return entry; -} - -static void state_vertex_array(struct vertex_array_entry *entry) -{ - if (gl_state.vertex_array != entry->vertex_array) - { - gl_state.vertex_array = entry->vertex_array; - glBindVertexArray(entry->vertex_array); - /* (the index buffer's binding is the vertex array's) */ - gl_state.element_array_buffer = (GLuint)-1; - } - current_vertex_array = entry; -} - -/* points the bound vertex array's binding at buffer: each vertex is stride -bytes on from the one before it, starting at offset */ -static void state_vertex_buffer(GLuint binding, GLuint buffer, unsigned long offset, GLsizei stride) -{ - struct vertex_binding *vertex_binding = ¤t_vertex_array->bindings[binding]; - - if (vertex_binding->buffer != buffer || vertex_binding->offset != offset || vertex_binding->stride != stride) - { - glBindVertexBuffer(binding, buffer, (GLintptr)offset, stride); - vertex_binding->buffer = buffer; - vertex_binding->offset = offset; - vertex_binding->stride = stride; - } -} - -/* an attribute of a layout */ -static void layout_attribute(struct vertex_layout *layout, unsigned long index, GLuint binding, GLint size, - GLenum type, GLboolean normalized, BOOL integer, unsigned long relative_offset) -{ - struct attribute_format *format = &layout->formats[index]; - - format->size = size; - format->type = type; - format->normalized = normalized; - format->integer = integer ? GL_TRUE : GL_FALSE; - format->relative_offset = (GLuint)relative_offset; - format->binding = binding; - layout->enabled |= 1UL << index; -} -#endif - -/* disables the attribute, which then reads value, or the integer zero */ -static void state_attribute_value(GLuint index, const float *value) -{ - unsigned char kind = value ? 0 : 1; - -#ifdef HALO_ANDROID - if (gl_state.attribute_enabled[index] != 0) - { - gl_state.attribute_enabled[index] = 0; - glDisableVertexAttribArray(index); - } -#endif - if (gl_state.attribute_value_kind[index] == kind && - (!value || !memcmp(gl_state.attribute_values[index], value, sizeof(gl_state.attribute_values[index])))) - { - return; - } - gl_state.attribute_value_kind[index] = kind; - if (value) - { - memcpy(gl_state.attribute_values[index], value, sizeof(gl_state.attribute_values[index])); - glVertexAttrib4fv(index, value); - } - else - { - glVertexAttribI4ui(index, 0, 0, 0, 0); - } -} - /* ---------- vertical blank emulation */ #define VERTICAL_BLANK_NANOSECONDS (1000000000L / 60) @@ -1095,16 +721,6 @@ GLuint xgpu_link_program(GLuint vertex_shader, GLuint fragment_shader, const cha return program; } -#ifndef HALO_ANDROID -static void GLAPIENTRY gl_debug_callback(GLenum source, GLenum type, GLuint id, GLenum severity, - GLsizei length, const GLchar *message, const void *user) -{ - (void)source; (void)id; (void)length; (void)user; - if (severity != GL_DEBUG_SEVERITY_NOTIFICATION) - platform_log("GL %s: %s", type == GL_DEBUG_TYPE_ERROR ? "error" : "debug", message); -} -#endif - /* ---------- render targets */ static void surface_dimensions(const D3DSurface *surface, unsigned long *width, unsigned long *height, BOOL *depth) @@ -1213,17 +829,20 @@ static struct render_target_entry *render_target_get(const D3DSurface *surface) entry->target.scale[1] = scale[1]; entry->target.gl_width = (unsigned long)(width * scale[0] + 0.5f); entry->target.gl_height = (unsigned long)(height * scale[1] + 0.5f); - glGenTextures(1, &entry->target.texture); - glBindTexture(GL_TEXTURE_2D, entry->target.texture); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0); - if (depth) - glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH24_STENCIL8, (GLsizei)entry->target.gl_width, - (GLsizei)entry->target.gl_height, 0, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8, NULL); - else - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, (GLsizei)entry->target.gl_width, (GLsizei)entry->target.gl_height, - 0, GL_BGRA, GL_UNSIGNED_BYTE, NULL); + { + struct gpu_texture_description description; + + memset(&description, 0, sizeof(description)); + description.type = GPU_TEXTURE_2D; + description.format = depth ? GPU_FORMAT_DEPTH_STENCIL : GPU_FORMAT_BGRA8; + description.usage = GPU_USAGE_RENDER_TARGET; + description.width = (uint32_t)entry->target.gl_width; + description.height = (uint32_t)entry->target.gl_height; + description.depth = 1; + description.levels = 1; + entry->target.texture = gpu_texture_create(&description); + } entry->screen_buffer = surface->Data == (depth ? device.depth_buffer.Data : device.back_buffer.Data); - xgpu_gl_state_invalidate(); entry->next = render_targets; render_targets = entry; entry->next_in_bucket = *render_target_bucket(entry->target.data); @@ -1243,148 +862,21 @@ struct xgpu_render_target *xgpu_render_target_find(unsigned long data) return best ? &best->target : NULL; } -/* the framebuffer of these textures, or with renderbuffers, of these -multisampled renderbuffers (render_target_multisample) */ -static GLuint framebuffer_find(GLuint color, GLuint depth, BOOL renderbuffers) -{ - struct framebuffer_entry *entry, **link; - GLenum draw_buffer = color ? GL_COLOR_ATTACHMENT0 : GL_NONE; - - /* (found, it moves to the front: each draw asks again for the one - the draw before it did) */ - for (link = &framebuffers; (entry = *link) != NULL; link = &entry->next) - { - if (entry->color == color && entry->depth == depth && entry->renderbuffers == renderbuffers) - { - *link = entry->next; - entry->next = framebuffers; - framebuffers = entry; - return entry->framebuffer; - } - } - entry = calloc(1, sizeof(*entry)); - entry->color = color; - entry->depth = depth; - entry->renderbuffers = renderbuffers; - glGenFramebuffers(1, &entry->framebuffer); - glBindFramebuffer(GL_FRAMEBUFFER, entry->framebuffer); - if (renderbuffers) - { - if (color) - glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, color); - if (depth) - glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, depth); - } - else - { - if (color) - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, color, 0); - if (depth) - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, depth, 0); - } - glDrawBuffers(1, &draw_buffer); - if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) - platform_log("framebuffer %u/%u%s is incomplete", color, depth, renderbuffers ? " (multisampled)" : ""); - xgpu_gl_state_invalidate(); - entry->next = framebuffers; - framebuffers = entry; - return entry->framebuffer; -} - -static GLuint framebuffer_get(GLuint color, GLuint depth) -{ - return framebuffer_find(color, depth, FALSE); -} - /* ---------- multisampling With display.anti_aliasing's multisampling, the back buffer and its depth -buffer are drawn into multisampled renderbuffers, and so is any target drawn -together with one of them (the mirror's view goes to the secondary target -with the back buffer's depth buffer): a framebuffer's attachments are all -multisampled or none is, so that of a target's texture and its renderbuffer -only one has pixels drawn since the other had them. The renderbuffer's -pixels are resolved into the texture before anything reads the texture (a -draw's textures, the display blit), and the texture's are put into the -renderbuffer when it is made, or made again for other samples. Nothing -samples a depth buffer as a texture (the game's copy of the depth buffer's -memory is a target of its own), so a depth buffer is resolved only when it -stops being multisampled. */ - -/* the samples a pixel of the bound targets: their renderbuffers' (or 1) */ +buffer are drawn into multisampled storage (gpu_gl_bind_targets), and so is +any target drawn together with one of them (the mirror's view goes to the +secondary target with the back buffer's depth buffer): a framebuffer's +attachments are all multisampled or none is. Nothing samples a depth buffer +as a texture (the game's copy of the depth buffer's memory is a target of +its own), so a depth buffer is resolved only when it stops being +multisampled. */ + +/* the samples a pixel of the bound targets: their multisampled storage's +(or 1) */ static int target_samples = 1; -/* the multisampled pixels of a target drawn into since into its texture */ -static void render_target_resolve(struct xgpu_render_target *target) -{ - GLuint read, draw; - - if (!target->unresolved) - return; - target->unresolved = FALSE; - /* (both found first: making a framebuffer binds it) */ - read = target->depth ? framebuffer_find(0, target->multisample, TRUE) : - framebuffer_find(target->multisample, 0, TRUE); - draw = target->depth ? framebuffer_get(0, target->texture) : framebuffer_get(target->texture, 0); - glBindFramebuffer(GL_READ_FRAMEBUFFER, read); - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, draw); - glDisable(GL_SCISSOR_TEST); - glBlitFramebuffer(0, 0, (GLint)target->gl_width, (GLint)target->gl_height, - 0, 0, (GLint)target->gl_width, (GLint)target->gl_height, - target->depth ? GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT : GL_COLOR_BUFFER_BIT, GL_NEAREST); - xgpu_gl_state_invalidate(); -} - -/* a target's renderbuffer with these samples a pixel (0: none, its storage -a pixel, the renderbuffer kept for the framebuffers made of it): its pixels -resolved into its texture first, and the texture's put into the new -storage */ -static void render_target_multisample(struct xgpu_render_target *target, int samples) -{ - GLuint draw; - - if (target->samples == samples) - return; - render_target_resolve(target); - if (!target->multisample) - glGenRenderbuffers(1, &target->multisample); - glBindRenderbuffer(GL_RENDERBUFFER, target->multisample); - glRenderbufferStorageMultisample(GL_RENDERBUFFER, samples, target->depth ? GL_DEPTH24_STENCIL8 : GL_RGBA8, - samples ? (GLsizei)target->gl_width : 1, samples ? (GLsizei)target->gl_height : 1); - glBindRenderbuffer(GL_RENDERBUFFER, 0); - target->samples = samples; - if (!samples) - return; - draw = target->depth ? framebuffer_find(0, target->multisample, TRUE) : - framebuffer_find(target->multisample, 0, TRUE); -#ifdef HALO_ANDROID - /* (ES blits into no multisampled framebuffer: it is cleared instead. A - change of the setting is taken up between frames, and the frame clears - the screen's targets before it draws) */ - glBindFramebuffer(GL_FRAMEBUFFER, draw); - glDisable(GL_SCISSOR_TEST); - glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); - glDepthMask(GL_TRUE); - glStencilMask(0xff); - glClearColor(0.0f, 0.0f, 0.0f, 0.0f); - glClearDepth(1.0f); - glClearStencil(0); - glClear(target->depth ? GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT : GL_COLOR_BUFFER_BIT); -#else - { - GLuint read = target->depth ? framebuffer_get(0, target->texture) : framebuffer_get(target->texture, 0); - - glBindFramebuffer(GL_READ_FRAMEBUFFER, read); - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, draw); - glDisable(GL_SCISSOR_TEST); - glBlitFramebuffer(0, 0, (GLint)target->gl_width, (GLint)target->gl_height, - 0, 0, (GLint)target->gl_width, (GLint)target->gl_height, - target->depth ? GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT : GL_COLOR_BUFFER_BIT, GL_NEAREST); - } -#endif - xgpu_gl_state_invalidate(); -} - /* counts draws and clears into render targets (xgpu_render_target.written) */ static unsigned long render_target_write_serial; @@ -1440,25 +932,12 @@ static BOOL bind_targets(BOOL *has_depth) { samples = 0; } + gpu_gl_bind_targets(color ? color->target.texture : 0, depth ? depth->target.texture : 0, (uint32_t)samples); if (color) - render_target_multisample(&color->target, samples); + color->target.samples = samples; if (depth) - render_target_multisample(&depth->target, samples); - if (samples) - { - state_framebuffer(framebuffer_find(color ? color->target.multisample : 0, - depth ? depth->target.multisample : 0, TRUE)); - if (color) - color->target.unresolved = TRUE; - if (depth) - depth->target.unresolved = TRUE; - target_samples = samples; - } - else - { - state_framebuffer(framebuffer_get(color ? color->target.texture : 0, depth ? depth->target.texture : 0)); - target_samples = 1; - } + depth->target.samples = samples; + target_samples = samples ? samples : 1; *has_depth = depth != NULL; return TRUE; } @@ -1467,53 +946,15 @@ static BOOL bind_targets(BOOL *has_depth) static void gl_initialize(void) { - GLint major = 0, minor = 0; + struct gpu_capabilities *capabilities = &xgpu_gpu_capabilities; int index; - glGetIntegerv(GL_MAJOR_VERSION, &major); - glGetIntegerv(GL_MINOR_VERSION, &minor); -#ifdef HALO_ANDROID - { - BOOL es32 = major > 3 || (major == 3 && minor >= 2); - - /* clip control is emulated in the vertex shader (nv2a_vsh.c) */ - xgpu_capabilities.copy_image = es32 || host_gl_has_extension("GL_EXT_copy_image") || - host_gl_has_extension("GL_OES_copy_image"); - xgpu_capabilities.border_clamp = es32 || host_gl_has_extension("GL_EXT_texture_border_clamp") || - host_gl_has_extension("GL_OES_texture_border_clamp"); - xgpu_capabilities.anisotropy = host_gl_has_extension("GL_EXT_texture_filter_anisotropic"); - xgpu_capabilities.base_vertex = es32; - xgpu_capabilities.shading_language = major > 3 || (major == 3 && minor >= 1) ? "310 es" : "300 es"; - shader_dialect.version = xgpu_capabilities.shading_language; - shader_dialect.es = TRUE; - shader_dialect.lookup_lod_bias = TRUE; - shader_dialect.clip_control = TRUE; - if (major > 3 || (major == 3 && minor >= 1)) - { - GLint counters = 0; - - glGetIntegerv(GL_MAX_FRAGMENT_ATOMIC_COUNTERS, &counters); - xgpu_capabilities.atomic_counters = counters > 0; - } - xgpu_capabilities.s3tc = host_gl_has_extension("GL_EXT_texture_compression_s3tc") || - (host_gl_has_extension("GL_EXT_texture_compression_dxt1") && - host_gl_has_extension("GL_ANGLE_texture_compression_dxt3") && - host_gl_has_extension("GL_ANGLE_texture_compression_dxt5")); - platform_log("OpenGL ES %d.%d: copy image %d, border clamp %d, anisotropy %d, S3TC %d, sample counting %d", - (int)major, (int)minor, xgpu_capabilities.copy_image, xgpu_capabilities.border_clamp, - xgpu_capabilities.anisotropy, xgpu_capabilities.s3tc, xgpu_capabilities.atomic_counters); - } -#else - if (config_boolean("debug.gl_debug")) - { - glEnable(GL_DEBUG_OUTPUT); - glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS); - glDebugMessageCallback(gl_debug_callback, NULL); - } - glClipControl(GL_UPPER_LEFT, GL_ZERO_TO_ONE); - glEnable(GL_PROGRAM_POINT_SIZE); - shader_dialect.version = "450 core"; -#endif + gpu_initialize(capabilities); + shader_dialect.version = capabilities->shading_language_es ? + (capabilities->shading_language >= 310 ? "310 es" : "300 es") : "450 core"; + shader_dialect.es = capabilities->shading_language_es != 0; + shader_dialect.lookup_lod_bias = !capabilities->sampler_lod_bias; + shader_dialect.clip_control = capabilities->shader_clip_control != 0; glGenVertexArrays(1, &device.vertex_array); glBindVertexArray(device.vertex_array); #ifdef HALO_ANDROID @@ -1541,7 +982,6 @@ static void gl_initialize(void) glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, device.index_buffer); glBufferData(GL_ELEMENT_ARRAY_BUFFER, INDEX_BUFFER_SIZE, NULL, GL_STREAM_DRAW); #endif - glGenSamplers(D3DTSS_MAXSTAGES, device.samplers); glGenQueries(VISIBILITY_TEST_SLOTS, device.queries); #ifndef HALO_ANDROID glGenBuffers(1, &device.visibility_results_buffer); @@ -1593,15 +1033,6 @@ static void gl_initialize(void) debug_settings.dump_shaders = *config_string("debug.gpu_dump_shaders") ? config_string("debug.gpu_dump_shaders") : NULL; debug_settings.statistics = config_boolean("debug.gpu_stats"); - { - GLint renderbuffer_size = 0; - - glGetIntegerv(GL_MAX_SAMPLES, &anti_aliasing_maximum_samples); - glGetIntegerv(GL_MAX_TEXTURE_SIZE, &maximum_target_size); - glGetIntegerv(GL_MAX_RENDERBUFFER_SIZE, &renderbuffer_size); - if (renderbuffer_size < maximum_target_size) - maximum_target_size = renderbuffer_size; - } xgpu_gl_state_invalidate(); device.gl_ready = TRUE; if (anti_aliasing_value < 0) @@ -1622,10 +1053,12 @@ static void anti_aliasing_prepare(void) return; mode = anti_aliasing_values[anti_aliasing_value].mode; anti_aliasing_samples = anti_aliasing_values[anti_aliasing_value].samples; - if (anti_aliasing_samples > anti_aliasing_maximum_samples) + if (anti_aliasing_samples > (int)xgpu_gpu_capabilities.max_samples) { - platform_log("anti-aliasing: the GPU has at most %d samples a pixel", (int)anti_aliasing_maximum_samples); - anti_aliasing_samples = anti_aliasing_maximum_samples < 2 ? 0 : anti_aliasing_maximum_samples; + int maximum_samples = (int)xgpu_gpu_capabilities.max_samples; + + platform_log("anti-aliasing: the GPU has at most %d samples a pixel", maximum_samples); + anti_aliasing_samples = maximum_samples < 2 ? 0 : maximum_samples; } if ((mode == _anti_aliasing_fxaa || mode == _anti_aliasing_smaa) && !xgpu_post_prepare(mode == _anti_aliasing_smaa)) platform_log("anti-aliasing: its programs do not build, so the 3D view is not antialiased"); @@ -2699,142 +2132,47 @@ static unsigned long stage_texture_mode(int stage) return (D3D__RenderState[D3DRS_PSTEXTUREMODES] >> (5 * stage)) & 0x1f; } -static GLenum address_mode(DWORD mode) -{ - switch (mode) - { - case D3DTADDRESS_MIRROR: return GL_MIRRORED_REPEAT; - case D3DTADDRESS_CLAMP: return GL_CLAMP_TO_EDGE; -#ifdef HALO_ANDROID - case D3DTADDRESS_BORDER: return xgpu_capabilities.border_clamp ? GL_CLAMP_TO_BORDER : GL_CLAMP_TO_EDGE; -#else - case D3DTADDRESS_BORDER: return GL_CLAMP_TO_BORDER; -#endif - case D3DTADDRESS_CLAMPTOEDGE: return GL_CLAMP_TO_EDGE; - default: return GL_REPEAT; - } -} - -/* ---------- sampler objects - -A sampler object for each sampler state the game uses, made once: a draw -binds the one its state needs, rather than changing a sampler's parameters, -which costs a GL call each, and on Zink a new Vulkan sampler. The game uses -a few dozen states. */ - -#define SAMPLER_STATE_WORDS 11 -#define SAMPLER_CACHE_SIZE 512 - -static struct -{ - DWORD inputs[SAMPLER_STATE_WORDS]; - GLuint sampler; -} sampler_cache[SAMPLER_CACHE_SIZE]; -static unsigned long sampler_cache_count; - -/* a sampler's parameters from its state (configure_sampler's inputs) */ -static void sampler_parameters(GLuint sampler, const DWORD *inputs) +/* a D3DTSS_*FILTER value as gpu.h's (any other is linear, as the Xbox's +filters but POINT are in GL) */ +static unsigned char sampler_filter(DWORD filter) { - DWORD min_filter = inputs[0], mip_filter = inputs[1], mag_filter = inputs[2]; - DWORD lod_bias = inputs[6], maximum_mip_level = inputs[7], anisotropy = inputs[8]; - GLenum minification; - float border[4]; - - if (min_filter == D3DTEXF_POINT) - minification = mip_filter == D3DTEXF_NONE ? GL_NEAREST : - mip_filter == D3DTEXF_POINT ? GL_NEAREST_MIPMAP_NEAREST : GL_NEAREST_MIPMAP_LINEAR; - else - minification = mip_filter == D3DTEXF_NONE ? GL_LINEAR : - mip_filter == D3DTEXF_POINT ? GL_LINEAR_MIPMAP_NEAREST : GL_LINEAR_MIPMAP_LINEAR; - glSamplerParameteri(sampler, GL_TEXTURE_MIN_FILTER, (GLint)minification); - glSamplerParameteri(sampler, GL_TEXTURE_MAG_FILTER, mag_filter == D3DTEXF_POINT ? GL_NEAREST : GL_LINEAR); - glSamplerParameteri(sampler, GL_TEXTURE_WRAP_S, (GLint)address_mode(inputs[3])); - glSamplerParameteri(sampler, GL_TEXTURE_WRAP_T, (GLint)address_mode(inputs[4])); - glSamplerParameteri(sampler, GL_TEXTURE_WRAP_R, (GLint)address_mode(inputs[5])); -#ifdef HALO_ANDROID - /* ES has no sampler LOD bias; the pixel shader applies it - (texture_lod_bias) */ - (void)lod_bias; - glSamplerParameterf(sampler, GL_TEXTURE_MIN_LOD, (float)maximum_mip_level); - if (xgpu_capabilities.anisotropy) - glSamplerParameterf(sampler, GL_TEXTURE_MAX_ANISOTROPY_EXT, - (min_filter == D3DTEXF_ANISOTROPIC && anisotropy > 1) ? (float)anisotropy : 1.0f); - if (xgpu_capabilities.border_clamp) - { - color_to_vec4(inputs[9], border); - glSamplerParameterfv(sampler, GL_TEXTURE_BORDER_COLOR, border); - } -#else - glSamplerParameterf(sampler, GL_TEXTURE_LOD_BIAS, dword_to_float(lod_bias)); - glSamplerParameterf(sampler, GL_TEXTURE_MIN_LOD, (float)maximum_mip_level); - glSamplerParameterf(sampler, GL_TEXTURE_MAX_ANISOTROPY, - (min_filter == D3DTEXF_ANISOTROPIC && anisotropy > 1) ? (float)anisotropy : 1.0f); - color_to_vec4(inputs[9], border); - glSamplerParameterfv(sampler, GL_TEXTURE_BORDER_COLOR, border); -#endif + return filter <= D3DTEXF_GAUSSIANCUBIC ? (unsigned char)filter : GPU_FILTER_LINEAR; } -/* the sampler object of a sampler state, made the first time; with the -cache full (never seen), the stage's own sampler set to it */ -static GLuint sampler_get(int stage, const DWORD *inputs) +static unsigned char sampler_address(DWORD mode) { - unsigned long hash = 2166136261UL, index, probe; - GLuint sampler; - - for (index = 0; index < SAMPLER_STATE_WORDS; index++) - hash = (hash ^ inputs[index]) * 16777619UL; - for (probe = 0; probe < SAMPLER_CACHE_SIZE; probe++) - { - index = (hash + probe) % SAMPLER_CACHE_SIZE; - if (!sampler_cache[index].sampler) - break; - if (!memcmp(sampler_cache[index].inputs, inputs, sizeof(sampler_cache[index].inputs))) - return sampler_cache[index].sampler; - } - if (probe == SAMPLER_CACHE_SIZE || sampler_cache_count >= SAMPLER_CACHE_SIZE * 3 / 4) + switch (mode) { - sampler_parameters(device.samplers[stage], inputs); - return device.samplers[stage]; + case D3DTADDRESS_MIRROR: return GPU_ADDRESS_MIRROR; + case D3DTADDRESS_CLAMP: return GPU_ADDRESS_CLAMP; + case D3DTADDRESS_BORDER: return GPU_ADDRESS_BORDER; + case D3DTADDRESS_CLAMPTOEDGE: return GPU_ADDRESS_CLAMP_TO_EDGE; + default: return GPU_ADDRESS_WRAP; } - glGenSamplers(1, &sampler); - sampler_parameters(sampler, inputs); - memcpy(sampler_cache[index].inputs, inputs, sizeof(sampler_cache[index].inputs)); - sampler_cache[index].sampler = sampler; - sampler_cache_count++; - return sampler; } -/* hires: a high-res HUD texture (hud_hires.h), drawn smaller than it is, so -filtered and from its mip levels whatever the game asks: the HUD's meters are -point sampled for one player, to keep the Xbox bitmaps' texels sharp */ -static void configure_sampler(int stage, BOOL mipmapped, BOOL hires) +/* a stage's sampler state. hires: a high-res HUD texture (hud_hires.h), +drawn smaller than it is, so filtered and from its mip levels whatever the +game asks: the HUD's meters are point sampled for one player, to keep the +Xbox bitmaps' texels sharp */ +static void sampler_state(int stage, BOOL mipmapped, BOOL hires, struct gpu_sampler_state *sampler) { - /* the texture stage state each stage's sampler was last chosen by */ - static DWORD configured[D3DTSS_MAXSTAGES][SAMPLER_STATE_WORDS]; - static GLuint configured_sampler[D3DTSS_MAXSTAGES]; DWORD *state = D3D__TextureState[stage]; - DWORD inputs[SAMPLER_STATE_WORDS]; - inputs[0] = hires ? D3DTEXF_LINEAR : state[D3DTSS_MINFILTER]; - inputs[1] = hires ? D3DTEXF_LINEAR : mipmapped ? state[D3DTSS_MIPFILTER] : D3DTEXF_NONE; - inputs[2] = hires ? D3DTEXF_LINEAR : state[D3DTSS_MAGFILTER]; - inputs[3] = state[D3DTSS_ADDRESSU]; - inputs[4] = state[D3DTSS_ADDRESSV]; - inputs[5] = state[D3DTSS_ADDRESSW]; - inputs[6] = hires ? 0 : state[D3DTSS_MIPMAPLODBIAS]; - inputs[7] = hires ? 0 : state[D3DTSS_MAXMIPLEVEL]; - inputs[8] = state[D3DTSS_MAXANISOTROPY]; - inputs[9] = state[D3DTSS_BORDERCOLOR]; - inputs[10] = hires; - if (!configured_sampler[stage] || memcmp(configured[stage], inputs, sizeof(inputs))) - { - memcpy(configured[stage], inputs, sizeof(inputs)); - configured_sampler[stage] = sampler_get(stage, inputs); - } - state_sampler(stage, configured_sampler[stage]); + memset(sampler, 0, sizeof(*sampler)); + sampler->min_filter = hires ? GPU_FILTER_LINEAR : sampler_filter(state[D3DTSS_MINFILTER]); + sampler->mip_filter = hires ? GPU_FILTER_LINEAR : mipmapped ? sampler_filter(state[D3DTSS_MIPFILTER]) : + GPU_FILTER_NONE; + sampler->mag_filter = hires ? GPU_FILTER_LINEAR : sampler_filter(state[D3DTSS_MAGFILTER]); + sampler->address_u = sampler_address(state[D3DTSS_ADDRESSU]); + sampler->address_v = sampler_address(state[D3DTSS_ADDRESSV]); + sampler->address_w = sampler_address(state[D3DTSS_ADDRESSW]); + sampler->lod_bias = hires ? 0.0f : dword_to_float(state[D3DTSS_MIPMAPLODBIAS]); + sampler->max_mip_level = hires ? 0 : (uint32_t)state[D3DTSS_MAXMIPLEVEL]; + sampler->max_anisotropy = (uint32_t)state[D3DTSS_MAXANISOTROPY]; + sampler->border_color = (uint32_t)state[D3DTSS_BORDERCOLOR]; } - /* ---------- render targets sampled with their mip chain The game renders some textures one mip level at a time, each level being a @@ -2851,38 +2189,17 @@ struct mip_composite { struct mip_composite *next; unsigned long data, width, height, levels; - GLuint texture; + gpu_texture texture; /* the levels last copied, and each one's target's texture and written serial then */ unsigned long rendered_levels; - GLuint level_sources[MIP_COMPOSITE_LEVELS]; + gpu_texture level_sources[MIP_COMPOSITE_LEVELS]; unsigned long level_written[MIP_COMPOSITE_LEVELS]; }; static struct mip_composite *mip_composites; -#ifdef HALO_ANDROID -static GLuint framebuffer_get(GLuint color, GLuint depth); - -/* glCopyImageSubData for ES 3.0/3.1 contexts without the extension */ -static void copy_level_by_blit(GLuint source, GLuint destination, GLint level, GLsizei width, GLsizei height) -{ - static GLuint draw_framebuffer; - - if (!draw_framebuffer) - glGenFramebuffers(1, &draw_framebuffer); - glBindFramebuffer(GL_READ_FRAMEBUFFER, framebuffer_get(source, 0)); - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, draw_framebuffer); - glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, destination, level); - glDisable(GL_SCISSOR_TEST); - glBlitFramebuffer(0, 0, width, height, 0, 0, width, height, GL_COLOR_BUFFER_BIT, GL_NEAREST); - glBindFramebuffer(GL_FRAMEBUFFER, 0); - /* the blit bypasses the cached state, so the next draw must re-apply it */ - xgpu_gl_state_invalidate(); -} -#endif - -static GLuint mip_composite_get(const struct xgpu_texture_description *description, unsigned long data) +static gpu_texture mip_composite_get(const struct xgpu_texture_description *description, unsigned long data) { struct mip_composite *composite; struct xgpu_render_target *targets[MIP_COMPOSITE_LEVELS]; @@ -2904,16 +2221,18 @@ static GLuint mip_composite_get(const struct xgpu_texture_description *descripti composite->width = description->width; composite->height = description->height; composite->levels = description->levels; - glGenTextures(1, &composite->texture); - glBindTexture(GL_TEXTURE_2D, composite->texture); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, (GLint)description->levels - 1); - for (level = 0; level < description->levels; level++) { - GLsizei width = (GLsizei)(description->width >> level ? description->width >> level : 1); - GLsizei height = (GLsizei)(description->height >> level ? description->height >> level : 1); + struct gpu_texture_description levels; - glTexImage2D(GL_TEXTURE_2D, (GLint)level, GL_RGBA8, width, height, 0, GL_BGRA, GL_UNSIGNED_BYTE, NULL); + memset(&levels, 0, sizeof(levels)); + levels.type = GPU_TEXTURE_2D; + levels.format = GPU_FORMAT_BGRA8; + levels.usage = GPU_USAGE_RENDER_TARGET; + levels.width = (uint32_t)description->width; + levels.height = (uint32_t)description->height; + levels.depth = 1; + levels.levels = (uint32_t)description->levels; + composite->texture = gpu_texture_create(&levels); } composite->rendered_levels = ~0UL; composite->next = mip_composites; @@ -2944,30 +2263,14 @@ static GLuint mip_composite_get(const struct xgpu_texture_description *descripti for (level = 0; level < rendered_levels; level++) { struct xgpu_render_target *target = targets[level]; - GLsizei width = (GLsizei)target->width, height = (GLsizei)target->height; composite->level_sources[level] = target->texture; composite->level_written[level] = target->written; - render_target_resolve(target); -#ifdef HALO_ANDROID - if (!xgpu_capabilities.copy_image) - { - copy_level_by_blit(target->texture, composite->texture, (GLint)level, width, height); - } - else -#endif - glCopyImageSubData(target->texture, GL_TEXTURE_2D, 0, 0, 0, 0, - composite->texture, GL_TEXTURE_2D, (GLint)level, 0, 0, 0, width, height, 1); + gpu_texture_copy_level(target->texture, composite->texture, (uint32_t)level); } - glBindTexture(GL_TEXTURE_2D, composite->texture); /* levels the game did not render come from the ones it did */ if (rendered_levels < description->levels) - { - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, rendered_levels ? (GLint)rendered_levels - 1 : 0); - glGenerateMipmap(GL_TEXTURE_2D); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0); - } - xgpu_gl_state_invalidate(); + gpu_texture_generate_mipmaps(composite->texture, rendered_levels ? (uint32_t)rendered_levels - 1 : 0); return composite->texture; } @@ -2975,10 +2278,10 @@ static void bind_textures(struct nv2a_pixel_shader_key *key, float texture_scale { /* Bind only after resolving every stage, since texture uploads can overwrite the active unit's binding. */ - GLenum gl_targets[D3DTSS_MAXSTAGES]; - GLuint gl_textures[D3DTSS_MAXSTAGES]; + struct gpu_stage stages[D3DTSS_MAXSTAGES]; int stage; + memset(stages, 0, sizeof(stages)); for (stage = 0; stage < D3DTSS_MAXSTAGES; stage++) { D3DBaseTexture *texture = device.textures[stage]; @@ -2988,24 +2291,22 @@ static void bind_textures(struct nv2a_pixel_shader_key *key, float texture_scale texture_scale[stage][2] = texture_scale[stage][3] = 1.0f; if (!texture || !texture->Data || mode == 0 || mode == 0x04 || mode == 0x05 || mode == 0x11) { - gl_targets[stage] = GL_TEXTURE_2D; - gl_textures[stage] = 0; key->sampler_type[stage] = mode == 0x11 ? _xgpu_sampler_2d : _xgpu_sampler_none; continue; } { struct xgpu_render_target *target = xgpu_render_target_find(texture->Data); struct xgpu_texture_description description; - GLenum gl_target; - GLuint gl_texture; + unsigned char type; + gpu_texture handle; if (target) { /* (multisampled: its pixels drawn since, resolved) */ - render_target_resolve(target); + gpu_gl_resolve(target->texture); xgpu_texture_describe(texture->Format, texture->Size, &description); - gl_texture = target->texture; - gl_target = GL_TEXTURE_2D; + handle = target->texture; + type = GPU_TEXTURE_2D; if (description.linear) { texture_scale[stage][0] = 1.0f / (float)target->width; @@ -3013,7 +2314,7 @@ static void bind_textures(struct nv2a_pixel_shader_key *key, float texture_scale } if (!description.linear && !description.cube_map && description.levels > 1 && target->width == description.width && target->height == description.height) - gl_texture = mip_composite_get(&description, texture->Data); + handle = mip_composite_get(&description, texture->Data); else description.levels = 1; } @@ -3022,57 +2323,26 @@ static void bind_textures(struct nv2a_pixel_shader_key *key, float texture_scale const D3DCOLOR *palette = device.palettes[stage] && device.palettes[stage]->Data ? (const D3DCOLOR *)PLATFORM_PHYSICAL_TO_VIRTUAL(device.palettes[stage]->Data) : NULL; - gl_texture = xgpu_texture_get((const DWORD *)texture, palette, &gl_target, &description); + handle = xgpu_texture_get((const DWORD *)texture, palette, &type, &description); if (description.linear) { texture_scale[stage][0] = 1.0f / (float)description.width; texture_scale[stage][1] = 1.0f / (float)description.height; } } - gl_targets[stage] = gl_target; - gl_textures[stage] = gl_texture; - configure_sampler(stage, description.levels > 1, description.hires); + stages[stage].texture = handle; + stages[stage].type = type; + sampler_state(stage, description.levels > 1, description.hires, &stages[stage].sampler); if (stage == 0) { key->coverage_alpha = description.hires_coverage != FALSE; key->point_threshold = description.hires_point_threshold != FALSE; } - key->sampler_type[stage] = gl_target == GL_TEXTURE_CUBE_MAP ? _xgpu_sampler_cube : - gl_target == GL_TEXTURE_3D ? _xgpu_sampler_3d : _xgpu_sampler_2d; + key->sampler_type[stage] = type == GPU_TEXTURE_CUBE ? _xgpu_sampler_cube : + type == GPU_TEXTURE_3D ? _xgpu_sampler_3d : _xgpu_sampler_2d; } } -#ifdef HALO_ANDROID - for (stage = 0; stage < D3DTSS_MAXSTAGES; stage++) - state_texture(stage, gl_targets[stage], gl_textures[stage]); -#else - { - /* the units whose texture changes, bound in one call (GL 4.4's - multi-bind) rather than selecting and binding each unit; binding no - texture unbinds all of the unit's targets */ - int first = -1, last = -1; - - for (stage = 0; stage < D3DTSS_MAXSTAGES; stage++) - { - if (gl_state.textures[stage][texture_slot(gl_targets[stage])] != gl_textures[stage]) - { - if (first < 0) - first = stage; - last = stage; - } - } - if (first >= 0) - { - glBindTextures((GLuint)first, (GLsizei)(last - first + 1), &gl_textures[first]); - for (stage = first; stage <= last; stage++) - { - if (gl_textures[stage]) - gl_state.textures[stage][texture_slot(gl_targets[stage])] = gl_textures[stage]; - else - memset(gl_state.textures[stage], 0, sizeof(gl_state.textures[stage])); - } - } - } -#endif + gpu_gl_bind_stages(stages); } static GLenum stencil_operation(DWORD operation) @@ -3397,7 +2667,7 @@ static struct program_entry *prepare_draw(BOOL immediate) key.combiner_state[D3DRS_PSFINALCOMBINERCONSTANT1] = 0; key.texture_modes = D3D__RenderState[D3DRS_PSTEXTUREMODES]; /* the textures before the targets: a render target the draw samples - has its multisampled pixels resolved by a blit (render_target_resolve), + has its multisampled pixels resolved by a blit (gpu_gl_resolve), which binds framebuffers of its own, and the back buffer can be both sampled and drawn into */ bind_textures(&key, uniforms.texture_scale); @@ -4618,12 +3888,13 @@ void halo_screen_anti_alias(short x0, short y0, short x1, short y1) if (!target) return; /* (no longer multisampled, if it was before the setting changed) */ - render_target_multisample(&target->target, 0); + gpu_gl_multisample(target->target.texture, 0); + target->target.samples = 0; corners[0] = scaled_pixel(x0, target->target.scale[0]); corners[1] = scaled_pixel(y0, target->target.scale[1]); corners[2] = scaled_pixel(x1, target->target.scale[0]); corners[3] = scaled_pixel(y1, target->target.scale[1]); - xgpu_post_anti_alias(mode == _anti_aliasing_smaa, framebuffer_get(target->target.texture, 0), + xgpu_post_anti_alias(mode == _anti_aliasing_smaa, gpu_gl_framebuffer(target->target.texture, 0), target->target.gl_width, target->target.gl_height, corners); glBindVertexArray(device.vertex_array); xgpu_gl_state_invalidate(); @@ -4646,7 +3917,7 @@ static void write_screenshot(struct render_target_entry *target) if (!directory) return; pixels = malloc(image_size); - glBindFramebuffer(GL_READ_FRAMEBUFFER, framebuffer_get(target->target.texture, 0)); + glBindFramebuffer(GL_READ_FRAMEBUFFER, gpu_gl_framebuffer(target->target.texture, 0)); glReadPixels(0, 0, (GLsizei)width, (GLsizei)height, GL_BGRA, GL_UNSIGNED_BYTE, pixels); /* the display ignores destination alpha, which the game uses as scratch; image viewers would show it as transparency */ @@ -4700,7 +3971,7 @@ void WINAPI D3DDevice_Present(CONST RECT *source_rectangle, CONST RECT *destinat if (trace_frame()) platform_log("present back buffer %08lx texture %u", (unsigned long)device.back_buffer.Data, back_buffer->target.texture); - render_target_resolve(&back_buffer->target); + gpu_gl_resolve(back_buffer->target.texture); if (screenshot_every > 0 && device.frame % (unsigned long)screenshot_every == 0) write_screenshot(back_buffer); @@ -4720,7 +3991,7 @@ void WINAPI D3DDevice_Present(CONST RECT *source_rectangle, CONST RECT *destinat glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); glClearColor(0.0f, 0.0f, 0.0f, 1.0f); glClear(GL_COLOR_BUFFER_BIT); - glBindFramebuffer(GL_READ_FRAMEBUFFER, framebuffer_get(back_buffer->target.texture, 0)); + glBindFramebuffer(GL_READ_FRAMEBUFFER, gpu_gl_framebuffer(back_buffer->target.texture, 0)); /* row 0 of the render target is the top of the picture */ glBlitFramebuffer(0, 0, (GLint)back_buffer->target.gl_width, (GLint)back_buffer->target.gl_height, x, y + height, x + width, y, GL_COLOR_BUFFER_BIT, GL_LINEAR); diff --git a/port/linux/src/gpu.h b/port/linux/src/gpu.h new file mode 100644 index 0000000000..05dec4ead7 --- /dev/null +++ b/port/linux/src/gpu.h @@ -0,0 +1,180 @@ +/* +GPU.H + +The interface between the Xbox Direct3D device (d3d8_gl.c, and its texture +cache, xbox_textures.c) and the backend that drives the GPU for it (gpu_gl.c: +OpenGL 4.5, or OpenGL ES 3 on Android). The device keeps the Xbox's state and +says what it needs in the neutral terms below; the backend owns the GPU's +objects and makes every call to its API. + +It includes only , and its structs have fixed-width fields only, so +that a backend built for another ABI than the game's (a 64-bit host drawing +for the 32-bit game) can take them as they are. +*/ + +#ifndef __HALO_GPU_H +#define __HALO_GPU_H + +#include + +/* a texture, render targets included; 0 is none */ +typedef uint32_t gpu_texture; + +/* the Xbox's texture stages and vertex attributes (D3DTSS_MAXSTAGES, +XGPU_VERTEX_ATTRIBUTE_COUNT) */ +enum { GPU_STAGE_COUNT = 4, GPU_ATTRIBUTE_COUNT = 16 }; + +/* ---------- the backend */ + +/* how visibility (occlusion) tests count */ +enum +{ + /* the samples that passed */ + GPU_OCCLUSION_EXACT, + /* only whether any passed (OpenGL ES's occlusion queries) */ + GPU_OCCLUSION_ANY_SAMPLE, + /* the pixel shader counts the samples (nv2a_pixel_shader_key count_samples) */ + GPU_OCCLUSION_SHADER_COUNTER, +}; + +struct gpu_capabilities +{ + /* the GLSL version the shaders are written in: 450, or 300 or 310 with + shading_language_es */ + uint16_t shading_language; + uint8_t shading_language_es; + /* samplers apply a LOD bias; otherwise each lookup in the pixel shader + does (nv2a_dialect lookup_lod_bias) */ + uint8_t sampler_lod_bias; + /* the vertex shader turns clip space into GL's, as glClipControl does + on the desktop (nv2a_dialect clip_control) */ + uint8_t shader_clip_control; + /* BC1, BC2 and BC3 textures; otherwise DXT textures are decoded */ + uint8_t s3tc; + /* vertex attributes read D3DCOLOR's byte order (BGRA); otherwise the + device swaps the bytes as it streams them */ + uint8_t vertex_bgra; + /* indexed draws add a base vertex; otherwise the device rebases the + indices */ + uint8_t base_vertex; + /* GPU_OCCLUSION_* */ + uint8_t occlusion; + /* a pixel shader can choose the samples it covers (gl_SampleMask: + nv2a_pixel_shader_key alpha_test_samples) */ + uint8_t sample_mask; + uint8_t pad[2]; + /* the most samples a pixel of a multisampled target can have, and the + largest render target each way */ + uint32_t max_samples; + uint32_t max_target_size; +}; + +/* sets up the backend for the context, which must be current, and reports +what it can do */ +void gpu_initialize(struct gpu_capabilities *capabilities); + +/* ---------- textures */ + +/* gpu_texture_description.type */ +enum { GPU_TEXTURE_2D = 1, GPU_TEXTURE_3D, GPU_TEXTURE_CUBE }; + +/* gpu_texture_description.format. A BGRA8 texel is a 32-bit ARGB word (the +bytes blue, green, red, alpha); an RGBA8 texel the bytes red, green, blue, +alpha. BC1 to BC3 are DXT1, DXT3 and DXT5 blocks. */ +enum +{ + GPU_FORMAT_BGRA8 = 1, + GPU_FORMAT_RGBA8, + GPU_FORMAT_BC1, + GPU_FORMAT_BC2, + GPU_FORMAT_BC3, + GPU_FORMAT_DEPTH_STENCIL, +}; + +/* gpu_texture_description.usage */ +enum +{ + /* its levels come from gpu_texture_upload */ + GPU_USAGE_UPLOAD = 1, + /* drawn into, or given its levels by gpu_texture_copy_level: every + level is made with the texture, its texels undefined */ + GPU_USAGE_RENDER_TARGET, +}; + +struct gpu_texture_description +{ + uint8_t type; + uint8_t format; + uint8_t usage; + uint8_t pad; + uint32_t width, height, depth, levels; +}; + +gpu_texture gpu_texture_create(const struct gpu_texture_description *description); +/* the channel of the texels (0 red, 1 green, 2 blue, 3 alpha) each channel +is sampled from, from the next upload of face 0's level 0 on; red, green, +blue and alpha from their own until then. An RGBA8 texture's channels are +always its own. */ +void gpu_texture_channels(gpu_texture texture, const uint8_t channels[4]); +/* one level of one face (0 but for a cube), size bytes in the texture's +format, or with no data the level with its texels undefined; face 0's +level 0 first */ +void gpu_texture_upload(gpu_texture texture, uint32_t face, uint32_t level, const void *data, uint32_t size); +/* rows first_row to first_row + rows - 1 of a 2D texture's level 0, once +it has been uploaded */ +void gpu_texture_upload_rows(gpu_texture texture, uint32_t first_row, uint32_t rows, const void *data); +/* level 0 of a color render target into level of another, the size that +level 0 is */ +void gpu_texture_copy_level(gpu_texture source, gpu_texture destination, uint32_t level); +/* the levels after base_level, made from base_level */ +void gpu_texture_generate_mipmaps(gpu_texture texture, uint32_t base_level); +void gpu_texture_destroy(gpu_texture texture); +/* level 0 of a 2D texture as BGRA8 rows from the top: its width times its +height times 4 bytes. Returns 0 and writes nothing if size is short of that +or the backend cannot read the texture (depth, and with OpenGL ES, anything +but a color render target). */ +uint32_t gpu_texture_read(gpu_texture texture, void *pixels, uint32_t size); + +/* ---------- samplers */ + +/* texture filters (D3DTSS_MINFILTER, MAGFILTER and MIPFILTER, which they +number as D3DTEXF_* does): GL samples all but POINT linearly, and an +ANISOTROPIC minification with anisotropy */ +enum +{ + GPU_FILTER_NONE, GPU_FILTER_POINT, GPU_FILTER_LINEAR, GPU_FILTER_ANISOTROPIC, + GPU_FILTER_QUINCUNX, GPU_FILTER_GAUSSIAN_CUBIC, +}; + +/* texture addressing (D3DTSS_ADDRESSU, V and W): CLAMP and CLAMP_TO_EDGE +sample alike; BORDER becomes CLAMP_TO_EDGE where the GPU has no border +color */ +enum { GPU_ADDRESS_WRAP, GPU_ADDRESS_MIRROR, GPU_ADDRESS_CLAMP, GPU_ADDRESS_BORDER, GPU_ADDRESS_CLAMP_TO_EDGE }; + +struct gpu_sampler_state +{ + uint8_t min_filter, mag_filter, mip_filter; + uint8_t address_u, address_v, address_w; + uint8_t pad[2]; + /* the first level sampled (D3DTSS_MAXMIPLEVEL) */ + uint32_t max_mip_level; + /* the anisotropy of an ANISOTROPIC minification */ + uint32_t max_anisotropy; + /* added to the level of detail: by the sampler, or without + sampler_lod_bias by the pixel shader (texture_lod_bias) */ + float lod_bias; + /* ARGB, as D3DCOLOR */ + uint32_t border_color; +}; + +/* a texture stage: the texture a draw samples there and how */ +struct gpu_stage +{ + gpu_texture texture; + /* 0: nothing is sampled; else the texture's GPU_TEXTURE_* */ + uint8_t type; + uint8_t pad[3]; + struct gpu_sampler_state sampler; +}; + +#endif diff --git a/port/linux/src/gpu_gl.c b/port/linux/src/gpu_gl.c new file mode 100644 index 0000000000..d9c136b1b8 --- /dev/null +++ b/port/linux/src/gpu_gl.c @@ -0,0 +1,1015 @@ +/* +GPU_GL.C + +The OpenGL backend (gpu.h): OpenGL 4.5 on the desktop, OpenGL ES 3 on +Android. A gpu_texture is the GL texture's name. + +Render targets are textures. With multisampling, draws go to a multisampled +renderbuffer of the target's instead, whose pixels are resolved into the +texture before anything reads it (gpu_gl_resolve). +*/ + +#include "gpu_gl.h" +#include "port_config.h" + +#include +#include +#include + +#ifndef GL_COMPRESSED_RGBA_S3TC_DXT1_EXT +#define GL_COMPRESSED_RGBA_S3TC_DXT1_EXT 0x83f1 +#define GL_COMPRESSED_RGBA_S3TC_DXT3_EXT 0x83f2 +#define GL_COMPRESSED_RGBA_S3TC_DXT5_EXT 0x83f3 +#endif + +#ifdef HALO_ANDROID +/* what the context supports (gpu_initialize) */ +struct xgpu_capabilities xgpu_capabilities; +#endif + +static void color_to_vec4(uint32_t color, float *out) +{ + out[0] = ((color >> 16) & 0xff) / 255.0f; + out[1] = ((color >> 8) & 0xff) / 255.0f; + out[2] = (color & 0xff) / 255.0f; + out[3] = ((color >> 24) & 0xff) / 255.0f; +} + +/* ---------- GL state cache (gpu_gl.h) */ + +#ifndef HALO_ANDROID +#define VERTEX_ARRAY_BUCKET_COUNT 64 +static struct vertex_array_entry *vertex_array_buckets[VERTEX_ARRAY_BUCKET_COUNT]; +static struct vertex_array_entry *current_vertex_array; +#endif + +struct gl_state gl_state; + +void xgpu_gl_state_invalidate(void) +{ + memset(&gl_state, 0xff, sizeof(gl_state)); +} + +void state_enable(unsigned char *shadow, GLenum capability, BOOL enabled) +{ + unsigned char value = enabled ? 1 : 0; + + if (*shadow == value) + return; + *shadow = value; + if (value) + glEnable(capability); + else + glDisable(capability); +} + +void state_program(GLuint program) +{ + if (gl_state.program != program) + { + gl_state.program = program; + glUseProgram(program); + } +} + +static void state_framebuffer(GLuint framebuffer) +{ + if (gl_state.framebuffer != framebuffer) + { + gl_state.framebuffer = framebuffer; + glBindFramebuffer(GL_FRAMEBUFFER, framebuffer); + } +} + +/* gl_state.textures' slot of a target */ +static int texture_slot(GLenum target) +{ + return target == GL_TEXTURE_CUBE_MAP ? 1 : target == GL_TEXTURE_3D ? 2 : 0; +} + +#ifdef HALO_ANDROID +static void state_texture(int unit, GLenum target, GLuint texture) +{ + int slot = texture_slot(target); + + if (gl_state.textures[unit][slot] == texture) + return; + if (gl_state.active_texture != GL_TEXTURE0 + (GLenum)unit) + { + gl_state.active_texture = GL_TEXTURE0 + (GLenum)unit; + glActiveTexture(gl_state.active_texture); + } + gl_state.textures[unit][slot] = texture; + glBindTexture(target, texture); +} +#endif + +static void state_sampler(int unit, GLuint sampler) +{ + if (gl_state.samplers[unit] != sampler) + { + gl_state.samplers[unit] = sampler; + glBindSampler((GLuint)unit, sampler); + } +} + +void state_array_buffer(GLuint buffer) +{ + if (gl_state.array_buffer != buffer) + { + gl_state.array_buffer = buffer; + glBindBuffer(GL_ARRAY_BUFFER, buffer); + } +} + +void state_element_array_buffer(GLuint buffer) +{ + if (gl_state.element_array_buffer != buffer) + { + gl_state.element_array_buffer = buffer; + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffer); + } +} + +#ifdef HALO_ANDROID +/* enables the attribute, reading size elements of type from buffer: each +vertex is stride bytes on from the one before it, starting at +buffer_offset, with the attribute relative_offset bytes into it */ +void state_attribute_stream(GLuint index, GLuint binding, GLuint buffer, GLint size, GLenum type, + GLboolean normalized, BOOL integer, GLsizei stride, unsigned long buffer_offset, unsigned long relative_offset) +{ + struct attribute_pointer *pointer = &gl_state.attribute_pointers[index]; + unsigned long offset = buffer_offset + relative_offset; + + (void)binding; + if (gl_state.attribute_enabled[index] != 1) + { + gl_state.attribute_enabled[index] = 1; + glEnableVertexAttribArray(index); + } + if (pointer->buffer == buffer && pointer->size == size && pointer->type == type && + pointer->normalized == normalized && pointer->integer == (integer ? GL_TRUE : GL_FALSE) && + pointer->stride == stride && pointer->offset == offset) + { + return; + } + state_array_buffer(buffer); + if (integer) + glVertexAttribIPointer(index, size, type, stride, (const void *)offset); + else + glVertexAttribPointer(index, size, type, normalized, stride, (const void *)offset); + pointer->buffer = buffer; + pointer->size = size; + pointer->type = type; + pointer->normalized = normalized; + pointer->integer = integer ? GL_TRUE : GL_FALSE; + pointer->stride = stride; + pointer->offset = offset; +} +#else +/* the vertex array of a layout, made the first time (its attributes enabled, +formatted and bound to their bindings) */ +struct vertex_array_entry *vertex_array_get(const struct vertex_layout *layout) +{ + const unsigned char *bytes = (const unsigned char *)layout; + unsigned long hash = 2166136261UL, index; + struct vertex_array_entry **bucket, *entry; + + for (index = 0; index < sizeof(*layout); index++) + hash = (hash ^ bytes[index]) * 16777619UL; + bucket = &vertex_array_buckets[hash % VERTEX_ARRAY_BUCKET_COUNT]; + for (entry = *bucket; entry; entry = entry->next) + { + if (entry->hash == hash && !memcmp(&entry->layout, layout, sizeof(*layout))) + return entry; + } + entry = calloc(1, sizeof(*entry)); + entry->hash = hash; + entry->layout = *layout; + memset(entry->bindings, 0xff, sizeof(entry->bindings)); + glGenVertexArrays(1, &entry->vertex_array); + glBindVertexArray(entry->vertex_array); + gl_state.vertex_array = entry->vertex_array; + /* (the index buffer's binding is the vertex array's) */ + gl_state.element_array_buffer = (GLuint)-1; + for (index = 0; index < GPU_ATTRIBUTE_COUNT; index++) + { + const struct attribute_format *format = &layout->formats[index]; + + if (!(layout->enabled & (1UL << index))) + continue; + glEnableVertexAttribArray((GLuint)index); + if (format->integer) + glVertexAttribIFormat((GLuint)index, format->size, format->type, format->relative_offset); + else + glVertexAttribFormat((GLuint)index, format->size, format->type, format->normalized, format->relative_offset); + glVertexAttribBinding((GLuint)index, format->binding); + } + entry->next = *bucket; + *bucket = entry; + return entry; +} + +void state_vertex_array(struct vertex_array_entry *entry) +{ + if (gl_state.vertex_array != entry->vertex_array) + { + gl_state.vertex_array = entry->vertex_array; + glBindVertexArray(entry->vertex_array); + /* (the index buffer's binding is the vertex array's) */ + gl_state.element_array_buffer = (GLuint)-1; + } + current_vertex_array = entry; +} + +/* points the bound vertex array's binding at buffer: each vertex is stride +bytes on from the one before it, starting at offset */ +void state_vertex_buffer(GLuint binding, GLuint buffer, unsigned long offset, GLsizei stride) +{ + struct vertex_binding *vertex_binding = ¤t_vertex_array->bindings[binding]; + + if (vertex_binding->buffer != buffer || vertex_binding->offset != offset || vertex_binding->stride != stride) + { + glBindVertexBuffer(binding, buffer, (GLintptr)offset, stride); + vertex_binding->buffer = buffer; + vertex_binding->offset = offset; + vertex_binding->stride = stride; + } +} + +/* an attribute of a layout */ +void layout_attribute(struct vertex_layout *layout, unsigned long index, GLuint binding, GLint size, + GLenum type, GLboolean normalized, BOOL integer, unsigned long relative_offset) +{ + struct attribute_format *format = &layout->formats[index]; + + format->size = size; + format->type = type; + format->normalized = normalized; + format->integer = integer ? GL_TRUE : GL_FALSE; + format->relative_offset = (GLuint)relative_offset; + format->binding = binding; + layout->enabled |= 1UL << index; +} +#endif + +/* disables the attribute, which then reads value, or the integer zero */ +void state_attribute_value(GLuint index, const float *value) +{ + unsigned char kind = value ? 0 : 1; + +#ifdef HALO_ANDROID + if (gl_state.attribute_enabled[index] != 0) + { + gl_state.attribute_enabled[index] = 0; + glDisableVertexAttribArray(index); + } +#endif + if (gl_state.attribute_value_kind[index] == kind && + (!value || !memcmp(gl_state.attribute_values[index], value, sizeof(gl_state.attribute_values[index])))) + { + return; + } + gl_state.attribute_value_kind[index] = kind; + if (value) + { + memcpy(gl_state.attribute_values[index], value, sizeof(gl_state.attribute_values[index])); + glVertexAttrib4fv(index, value); + } + else + { + glVertexAttribI4ui(index, 0, 0, 0, 0); + } +} + +/* ---------- textures */ + +struct texture_record +{ + struct gpu_texture_description description; + /* the channel of the texels each channel is sampled from + (gpu_texture_channels) */ + uint8_t channels[4]; + /* a render target's multisampled renderbuffer, which draws go to with + multisampling, its samples a pixel (0 when it has none), and whether it + has been drawn into since the texture last had its pixels */ + GLuint multisample; + int samples; + BOOL unresolved; +}; + +/* by texture name (names are small and reused) */ +static struct texture_record *texture_records; +static unsigned long texture_record_count; + +/* the record of a texture made by gpu_texture_create; an empty one for any +other */ +static struct texture_record *texture_record(gpu_texture texture) +{ + static struct texture_record none; + + if (texture >= texture_record_count) + { + memset(&none, 0, sizeof(none)); + return &none; + } + return &texture_records[texture]; +} + +static GLenum texture_target(unsigned char type) +{ + return type == GPU_TEXTURE_CUBE ? GL_TEXTURE_CUBE_MAP : type == GPU_TEXTURE_3D ? GL_TEXTURE_3D : GL_TEXTURE_2D; +} + +static GLsizei level_dimension(uint32_t dimension, uint32_t level) +{ + return (GLsizei)(dimension >> level ? dimension >> level : 1); +} + +gpu_texture gpu_texture_create(const struct gpu_texture_description *description) +{ + GLuint texture = 0; + struct texture_record *record; + uint32_t level; + + glGenTextures(1, &texture); + if (texture >= texture_record_count) + { + unsigned long count = texture_record_count ? texture_record_count : 1024; + struct texture_record *grown; + + while (count <= texture) + count *= 2; + grown = realloc(texture_records, count * sizeof(*grown)); + if (!grown) + { + platform_log("GPU: no memory to keep texture %u", texture); + return texture; + } + memset(grown + texture_record_count, 0, (count - texture_record_count) * sizeof(*grown)); + texture_records = grown; + texture_record_count = count; + } + record = &texture_records[texture]; + memset(record, 0, sizeof(*record)); + record->description = *description; + for (level = 0; level < 4; level++) + record->channels[level] = (uint8_t)level; + if (description->usage != GPU_USAGE_RENDER_TARGET) + return texture; + /* a render target, or a texture whose levels are copied from render + targets: every level now, its texels undefined */ + glBindTexture(GL_TEXTURE_2D, texture); + if (description->levels > 1) + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, (GLint)description->levels - 1); + for (level = 0; level < description->levels; level++) + { + GLsizei width = level_dimension(description->width, level); + GLsizei height = level_dimension(description->height, level); + + if (description->format == GPU_FORMAT_DEPTH_STENCIL) + glTexImage2D(GL_TEXTURE_2D, (GLint)level, GL_DEPTH24_STENCIL8, width, height, 0, GL_DEPTH_STENCIL, + GL_UNSIGNED_INT_24_8, NULL); + else + glTexImage2D(GL_TEXTURE_2D, (GLint)level, GL_RGBA8, width, height, 0, GL_BGRA, GL_UNSIGNED_BYTE, NULL); + } + xgpu_gl_state_invalidate(); + return texture; +} + +void gpu_texture_channels(gpu_texture texture, const uint8_t channels[4]) +{ + memcpy(texture_record(texture)->channels, channels, 4); +} + +static GLenum compressed_format(unsigned char format) +{ + switch (format) + { + case GPU_FORMAT_BC1: return GL_COMPRESSED_RGBA_S3TC_DXT1_EXT; + case GPU_FORMAT_BC2: return GL_COMPRESSED_RGBA_S3TC_DXT3_EXT; + default: return GL_COMPRESSED_RGBA_S3TC_DXT5_EXT; + } +} + +void gpu_texture_upload(gpu_texture texture, uint32_t face, uint32_t level, const void *data, uint32_t size) +{ + const struct texture_record *record = texture_record(texture); + const struct gpu_texture_description *description = &record->description; + GLenum target = texture_target(description->type); + GLenum image_target = description->type == GPU_TEXTURE_CUBE ? GL_TEXTURE_CUBE_MAP_POSITIVE_X + face : target; + GLsizei width = level_dimension(description->width, level); + GLsizei height = level_dimension(description->height, level); + GLsizei depth = level_dimension(description->depth, level); + + glBindTexture(target, texture); + xgpu_gl_state_invalidate(); + if (!face && !level) + { + if (description->format != GPU_FORMAT_RGBA8) + { + /* the channel each channel is sampled from, set at every upload: + a texture can be given other texels */ + GLint stored[4] = { GL_RED, GL_GREEN, GL_BLUE, GL_ALPHA }; + +#ifdef HALO_ANDROID + /* BGRA8 texels are given to ES as RGBA, which it reads instead */ + if (description->format == GPU_FORMAT_BGRA8) + { + stored[0] = GL_BLUE; + stored[2] = GL_RED; + } +#endif + glTexParameteri(target, GL_TEXTURE_SWIZZLE_R, stored[record->channels[0] & 3]); + glTexParameteri(target, GL_TEXTURE_SWIZZLE_G, stored[record->channels[1] & 3]); + glTexParameteri(target, GL_TEXTURE_SWIZZLE_B, stored[record->channels[2] & 3]); + glTexParameteri(target, GL_TEXTURE_SWIZZLE_A, stored[record->channels[3] & 3]); + } + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); + glTexParameteri(target, GL_TEXTURE_BASE_LEVEL, 0); + glTexParameteri(target, GL_TEXTURE_MAX_LEVEL, (GLint)description->levels - 1); + } + switch (description->format) + { + case GPU_FORMAT_BC1: + case GPU_FORMAT_BC2: + case GPU_FORMAT_BC3: + if (target == GL_TEXTURE_3D) + glCompressedTexImage3D(image_target, (GLint)level, compressed_format(description->format), width, height, + depth, 0, (GLsizei)size, data); + else + glCompressedTexImage2D(image_target, (GLint)level, compressed_format(description->format), width, height, + 0, (GLsizei)size, data); + break; + default: + { + GLenum format = description->format == GPU_FORMAT_RGBA8 ? GL_RGBA : GL_BGRA; + + if (target == GL_TEXTURE_3D) + glTexImage3D(image_target, (GLint)level, GL_RGBA8, width, height, depth, 0, format, GL_UNSIGNED_BYTE, data); + else + glTexImage2D(image_target, (GLint)level, GL_RGBA8, width, height, 0, format, GL_UNSIGNED_BYTE, data); + break; + } + } +} + +void gpu_texture_upload_rows(gpu_texture texture, uint32_t first_row, uint32_t rows, const void *data) +{ + const struct gpu_texture_description *description = &texture_record(texture)->description; + + glBindTexture(GL_TEXTURE_2D, texture); + xgpu_gl_state_invalidate(); + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, (GLint)first_row, (GLsizei)description->width, (GLsizei)rows, + description->format == GPU_FORMAT_RGBA8 ? GL_RGBA : GL_BGRA, GL_UNSIGNED_BYTE, data); +} + +static void texture_resolve(gpu_texture texture); +static GLuint framebuffer_get(GLuint color, GLuint depth); + +#ifdef HALO_ANDROID +/* glCopyImageSubData for ES 3.0/3.1 contexts without the extension */ +static void copy_level_by_blit(GLuint source, GLuint destination, GLint level, GLsizei width, GLsizei height) +{ + static GLuint draw_framebuffer; + + if (!draw_framebuffer) + glGenFramebuffers(1, &draw_framebuffer); + glBindFramebuffer(GL_READ_FRAMEBUFFER, framebuffer_get(source, 0)); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, draw_framebuffer); + glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, destination, level); + glDisable(GL_SCISSOR_TEST); + glBlitFramebuffer(0, 0, width, height, 0, 0, width, height, GL_COLOR_BUFFER_BIT, GL_NEAREST); + glBindFramebuffer(GL_FRAMEBUFFER, 0); + /* the blit bypasses the cached state, so the next draw must re-apply it */ + xgpu_gl_state_invalidate(); +} +#endif + +void gpu_texture_copy_level(gpu_texture source, gpu_texture destination, uint32_t level) +{ + const struct gpu_texture_description *description = &texture_record(source)->description; + GLsizei width = (GLsizei)description->width, height = (GLsizei)description->height; + + /* (multisampled: its pixels drawn since, resolved) */ + texture_resolve(source); +#ifdef HALO_ANDROID + if (!xgpu_capabilities.copy_image) + { + copy_level_by_blit(source, destination, (GLint)level, width, height); + return; + } +#endif + glCopyImageSubData(source, GL_TEXTURE_2D, 0, 0, 0, 0, destination, GL_TEXTURE_2D, (GLint)level, 0, 0, 0, + width, height, 1); +} + +void gpu_texture_generate_mipmaps(gpu_texture texture, uint32_t base_level) +{ + GLenum target = texture_target(texture_record(texture)->description.type); + + glBindTexture(target, texture); + xgpu_gl_state_invalidate(); + if (base_level) + glTexParameteri(target, GL_TEXTURE_BASE_LEVEL, (GLint)base_level); + glGenerateMipmap(target); + if (base_level) + glTexParameteri(target, GL_TEXTURE_BASE_LEVEL, 0); +} + +void gpu_texture_destroy(gpu_texture texture) +{ + /* (render targets, the only textures with renderbuffers, are never + destroyed) */ + memset(texture_record(texture), 0, sizeof(struct texture_record)); + glDeleteTextures(1, &texture); + xgpu_gl_state_invalidate(); +} + +uint32_t gpu_texture_read(gpu_texture texture, void *pixels, uint32_t size) +{ + const struct gpu_texture_description *description = &texture_record(texture)->description; + uint32_t bytes = description->width * description->height * 4; + + if (description->type != GPU_TEXTURE_2D || description->format == GPU_FORMAT_DEPTH_STENCIL || size < bytes) + return 0; + if (description->usage == GPU_USAGE_RENDER_TARGET) + { + texture_resolve(texture); + glBindFramebuffer(GL_READ_FRAMEBUFFER, framebuffer_get(texture, 0)); + glReadPixels(0, 0, (GLsizei)description->width, (GLsizei)description->height, GL_BGRA, GL_UNSIGNED_BYTE, pixels); +#ifdef HALO_ANDROID + { + /* (read as RGBA) */ + unsigned char *bytes_read = pixels; + uint32_t texel; + + for (texel = 0; texel < description->width * description->height; texel++) + { + unsigned char red = bytes_read[texel * 4]; + + bytes_read[texel * 4] = bytes_read[texel * 4 + 2]; + bytes_read[texel * 4 + 2] = red; + } + } +#endif + return bytes; + } +#ifdef HALO_ANDROID + /* ES cannot read textures back */ + return 0; +#else + glBindTexture(GL_TEXTURE_2D, texture); + xgpu_gl_state_invalidate(); + glGetTexImage(GL_TEXTURE_2D, 0, GL_BGRA, GL_UNSIGNED_BYTE, pixels); + return bytes; +#endif +} + +/* ---------- render targets' framebuffers */ + +struct framebuffer_entry +{ + struct framebuffer_entry *next; + GLuint color; + GLuint depth; + /* color and depth are multisampled renderbuffers, not textures */ + BOOL renderbuffers; + GLuint framebuffer; +}; + +static struct framebuffer_entry *framebuffers; +/* the framebuffer of these textures, or with renderbuffers, of these +multisampled renderbuffers (texture_multisample) */ +static GLuint framebuffer_find(GLuint color, GLuint depth, BOOL renderbuffers) +{ + struct framebuffer_entry *entry, **link; + GLenum draw_buffer = color ? GL_COLOR_ATTACHMENT0 : GL_NONE; + + /* (found, it moves to the front: each draw asks again for the one + the draw before it did) */ + for (link = &framebuffers; (entry = *link) != NULL; link = &entry->next) + { + if (entry->color == color && entry->depth == depth && entry->renderbuffers == renderbuffers) + { + *link = entry->next; + entry->next = framebuffers; + framebuffers = entry; + return entry->framebuffer; + } + } + entry = calloc(1, sizeof(*entry)); + entry->color = color; + entry->depth = depth; + entry->renderbuffers = renderbuffers; + glGenFramebuffers(1, &entry->framebuffer); + glBindFramebuffer(GL_FRAMEBUFFER, entry->framebuffer); + if (renderbuffers) + { + if (color) + glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, color); + if (depth) + glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, depth); + } + else + { + if (color) + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, color, 0); + if (depth) + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, depth, 0); + } + glDrawBuffers(1, &draw_buffer); + if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) + platform_log("framebuffer %u/%u%s is incomplete", color, depth, renderbuffers ? " (multisampled)" : ""); + xgpu_gl_state_invalidate(); + entry->next = framebuffers; + framebuffers = entry; + return entry->framebuffer; +} + +static GLuint framebuffer_get(GLuint color, GLuint depth) +{ + return framebuffer_find(color, depth, FALSE); +} + +GLuint gpu_gl_framebuffer(gpu_texture color, gpu_texture depth) +{ + return framebuffer_get(color, depth); +} + +/* ---------- multisampling + +A framebuffer's attachments are all multisampled or none is (the device +chooses, gpu_gl_bind_targets), so that of a target's texture and its +renderbuffer only one has pixels drawn since the other had them. The +renderbuffer's pixels are resolved into the texture before anything reads +the texture, and the texture's are put into the renderbuffer when it is +made, or made again for other samples. */ + +/* the multisampled pixels of a target drawn into since into its texture */ +static void texture_resolve(gpu_texture texture) +{ + struct texture_record *target = texture_record(texture); + BOOL depth = target->description.format == GPU_FORMAT_DEPTH_STENCIL; + GLint width = (GLint)target->description.width, height = (GLint)target->description.height; + GLuint read, draw; + + if (!target->unresolved) + return; + target->unresolved = FALSE; + /* (both found first: making a framebuffer binds it) */ + read = depth ? framebuffer_find(0, target->multisample, TRUE) : framebuffer_find(target->multisample, 0, TRUE); + draw = depth ? framebuffer_get(0, texture) : framebuffer_get(texture, 0); + glBindFramebuffer(GL_READ_FRAMEBUFFER, read); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, draw); + glDisable(GL_SCISSOR_TEST); + glBlitFramebuffer(0, 0, width, height, 0, 0, width, height, + depth ? GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT : GL_COLOR_BUFFER_BIT, GL_NEAREST); + xgpu_gl_state_invalidate(); +} + +/* a target's renderbuffer with these samples a pixel (0: none, its storage +a pixel, the renderbuffer kept for the framebuffers made of it): its pixels +resolved into its texture first, and the texture's put into the new +storage */ +static void texture_multisample(gpu_texture texture, int samples) +{ + struct texture_record *target = texture_record(texture); + BOOL depth = target->description.format == GPU_FORMAT_DEPTH_STENCIL; + GLint width = (GLint)target->description.width, height = (GLint)target->description.height; + GLuint draw; + + if (target->samples == samples) + return; + texture_resolve(texture); + if (!target->multisample) + glGenRenderbuffers(1, &target->multisample); + glBindRenderbuffer(GL_RENDERBUFFER, target->multisample); + glRenderbufferStorageMultisample(GL_RENDERBUFFER, samples, depth ? GL_DEPTH24_STENCIL8 : GL_RGBA8, + samples ? width : 1, samples ? height : 1); + glBindRenderbuffer(GL_RENDERBUFFER, 0); + target->samples = samples; + if (!samples) + return; + draw = depth ? framebuffer_find(0, target->multisample, TRUE) : framebuffer_find(target->multisample, 0, TRUE); +#ifdef HALO_ANDROID + /* (ES blits into no multisampled framebuffer: it is cleared instead. A + change of the setting is taken up between frames, and the frame clears + the screen's targets before it draws) */ + glBindFramebuffer(GL_FRAMEBUFFER, draw); + glDisable(GL_SCISSOR_TEST); + glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); + glDepthMask(GL_TRUE); + glStencilMask(0xff); + glClearColor(0.0f, 0.0f, 0.0f, 0.0f); + glClearDepth(1.0f); + glClearStencil(0); + glClear(depth ? GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT : GL_COLOR_BUFFER_BIT); +#else + { + GLuint read = depth ? framebuffer_get(0, texture) : framebuffer_get(texture, 0); + + glBindFramebuffer(GL_READ_FRAMEBUFFER, read); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, draw); + glDisable(GL_SCISSOR_TEST); + glBlitFramebuffer(0, 0, width, height, 0, 0, width, height, + depth ? GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT : GL_COLOR_BUFFER_BIT, GL_NEAREST); + } +#endif + xgpu_gl_state_invalidate(); +} + +void gpu_gl_resolve(gpu_texture target) +{ + texture_resolve(target); +} + +void gpu_gl_multisample(gpu_texture target, uint32_t samples) +{ + texture_multisample(target, (int)samples); +} + +void gpu_gl_bind_targets(gpu_texture color, gpu_texture depth, uint32_t samples) +{ + if (color) + texture_multisample(color, (int)samples); + if (depth) + texture_multisample(depth, (int)samples); + if (samples) + { + state_framebuffer(framebuffer_find(color ? texture_record(color)->multisample : 0, + depth ? texture_record(depth)->multisample : 0, TRUE)); + if (color) + texture_record(color)->unresolved = TRUE; + if (depth) + texture_record(depth)->unresolved = TRUE; + } + else + { + state_framebuffer(framebuffer_get(color, depth)); + } +} + +/* ---------- sampler objects + +A sampler object for each sampler state the game uses, made once: a draw +binds the one its state needs, rather than changing a sampler's parameters, +which costs a GL call each, and on Zink a new Vulkan sampler. The game uses +a few dozen states. */ + +#define SAMPLER_CACHE_SIZE 512 + +static struct +{ + struct gpu_sampler_state state; + GLuint sampler; +} sampler_cache[SAMPLER_CACHE_SIZE]; +static unsigned long sampler_cache_count; + +/* each stage's own sampler, which takes the state when the cache is full */ +static GLuint stage_samplers[GPU_STAGE_COUNT]; + +static GLenum address_mode(uint8_t mode) +{ + switch (mode) + { + case GPU_ADDRESS_MIRROR: return GL_MIRRORED_REPEAT; + case GPU_ADDRESS_CLAMP: return GL_CLAMP_TO_EDGE; +#ifdef HALO_ANDROID + case GPU_ADDRESS_BORDER: return xgpu_capabilities.border_clamp ? GL_CLAMP_TO_BORDER : GL_CLAMP_TO_EDGE; +#else + case GPU_ADDRESS_BORDER: return GL_CLAMP_TO_BORDER; +#endif + case GPU_ADDRESS_CLAMP_TO_EDGE: return GL_CLAMP_TO_EDGE; + default: return GL_REPEAT; + } +} + +/* a sampler's parameters from its state */ +static void sampler_parameters(GLuint sampler, const struct gpu_sampler_state *state) +{ + GLenum minification; + float border[4]; + + if (state->min_filter == GPU_FILTER_POINT) + minification = state->mip_filter == GPU_FILTER_NONE ? GL_NEAREST : + state->mip_filter == GPU_FILTER_POINT ? GL_NEAREST_MIPMAP_NEAREST : GL_NEAREST_MIPMAP_LINEAR; + else + minification = state->mip_filter == GPU_FILTER_NONE ? GL_LINEAR : + state->mip_filter == GPU_FILTER_POINT ? GL_LINEAR_MIPMAP_NEAREST : GL_LINEAR_MIPMAP_LINEAR; + glSamplerParameteri(sampler, GL_TEXTURE_MIN_FILTER, (GLint)minification); + glSamplerParameteri(sampler, GL_TEXTURE_MAG_FILTER, state->mag_filter == GPU_FILTER_POINT ? GL_NEAREST : GL_LINEAR); + glSamplerParameteri(sampler, GL_TEXTURE_WRAP_S, (GLint)address_mode(state->address_u)); + glSamplerParameteri(sampler, GL_TEXTURE_WRAP_T, (GLint)address_mode(state->address_v)); + glSamplerParameteri(sampler, GL_TEXTURE_WRAP_R, (GLint)address_mode(state->address_w)); +#ifdef HALO_ANDROID + /* ES has no sampler LOD bias; the pixel shader applies it + (texture_lod_bias) */ + glSamplerParameterf(sampler, GL_TEXTURE_MIN_LOD, (float)state->max_mip_level); + if (xgpu_capabilities.anisotropy) + glSamplerParameterf(sampler, GL_TEXTURE_MAX_ANISOTROPY_EXT, + (state->min_filter == GPU_FILTER_ANISOTROPIC && state->max_anisotropy > 1) ? (float)state->max_anisotropy : 1.0f); + if (xgpu_capabilities.border_clamp) + { + color_to_vec4(state->border_color, border); + glSamplerParameterfv(sampler, GL_TEXTURE_BORDER_COLOR, border); + } +#else + glSamplerParameterf(sampler, GL_TEXTURE_LOD_BIAS, state->lod_bias); + glSamplerParameterf(sampler, GL_TEXTURE_MIN_LOD, (float)state->max_mip_level); + glSamplerParameterf(sampler, GL_TEXTURE_MAX_ANISOTROPY, + (state->min_filter == GPU_FILTER_ANISOTROPIC && state->max_anisotropy > 1) ? (float)state->max_anisotropy : 1.0f); + color_to_vec4(state->border_color, border); + glSamplerParameterfv(sampler, GL_TEXTURE_BORDER_COLOR, border); +#endif +} + +/* the sampler object of a sampler state, made the first time; with the +cache full (never seen), the stage's own sampler set to it */ +static GLuint sampler_get(int stage, const struct gpu_sampler_state *state) +{ + const unsigned char *bytes = (const unsigned char *)state; + unsigned long hash = 2166136261UL, index, probe; + GLuint sampler; + + for (index = 0; index < sizeof(*state); index++) + hash = (hash ^ bytes[index]) * 16777619UL; + for (probe = 0; probe < SAMPLER_CACHE_SIZE; probe++) + { + index = (hash + probe) % SAMPLER_CACHE_SIZE; + if (!sampler_cache[index].sampler) + break; + if (!memcmp(&sampler_cache[index].state, state, sizeof(*state))) + return sampler_cache[index].sampler; + } + if (probe == SAMPLER_CACHE_SIZE || sampler_cache_count >= SAMPLER_CACHE_SIZE * 3 / 4) + { + sampler_parameters(stage_samplers[stage], state); + return stage_samplers[stage]; + } + glGenSamplers(1, &sampler); + sampler_parameters(sampler, state); + sampler_cache[index].state = *state; + sampler_cache[index].sampler = sampler; + sampler_cache_count++; + return sampler; +} + +static void configure_sampler(int stage, const struct gpu_sampler_state *state) +{ + /* the state each stage's sampler was last chosen by */ + static struct gpu_sampler_state configured[GPU_STAGE_COUNT]; + static GLuint configured_sampler[GPU_STAGE_COUNT]; + + if (!configured_sampler[stage] || memcmp(&configured[stage], state, sizeof(*state))) + { + configured[stage] = *state; + configured_sampler[stage] = sampler_get(stage, state); + } + state_sampler(stage, configured_sampler[stage]); +} + +/* ---------- texture stages */ + +void gpu_gl_bind_stages(const struct gpu_stage stages[GPU_STAGE_COUNT]) +{ + GLenum gl_targets[GPU_STAGE_COUNT]; + GLuint gl_textures[GPU_STAGE_COUNT]; + int stage; + + for (stage = 0; stage < GPU_STAGE_COUNT; stage++) + { + if (!stages[stage].type) + { + gl_targets[stage] = GL_TEXTURE_2D; + gl_textures[stage] = 0; + continue; + } + gl_targets[stage] = texture_target(stages[stage].type); + gl_textures[stage] = stages[stage].texture; + configure_sampler(stage, &stages[stage].sampler); + } +#ifdef HALO_ANDROID + for (stage = 0; stage < GPU_STAGE_COUNT; stage++) + state_texture(stage, gl_targets[stage], gl_textures[stage]); +#else + { + /* the units whose texture changes, bound in one call (GL 4.4's + multi-bind) rather than selecting and binding each unit; binding no + texture unbinds all of the unit's targets */ + int first = -1, last = -1; + + for (stage = 0; stage < GPU_STAGE_COUNT; stage++) + { + if (gl_state.textures[stage][texture_slot(gl_targets[stage])] != gl_textures[stage]) + { + if (first < 0) + first = stage; + last = stage; + } + } + if (first >= 0) + { + glBindTextures((GLuint)first, (GLsizei)(last - first + 1), &gl_textures[first]); + for (stage = first; stage <= last; stage++) + { + if (gl_textures[stage]) + gl_state.textures[stage][texture_slot(gl_targets[stage])] = gl_textures[stage]; + else + memset(gl_state.textures[stage], 0, sizeof(gl_state.textures[stage])); + } + } + } +#endif +} + +/* ---------- the backend */ + +#ifndef HALO_ANDROID +static void GLAPIENTRY gl_debug_callback(GLenum source, GLenum type, GLuint id, GLenum severity, + GLsizei length, const GLchar *message, const void *user) +{ + (void)source; (void)id; (void)length; (void)user; + if (severity != GL_DEBUG_SEVERITY_NOTIFICATION) + platform_log("GL %s: %s", type == GL_DEBUG_TYPE_ERROR ? "error" : "debug", message); +} +#endif + +void gpu_initialize(struct gpu_capabilities *capabilities) +{ + GLint major = 0, minor = 0; + + memset(capabilities, 0, sizeof(*capabilities)); + glGetIntegerv(GL_MAJOR_VERSION, &major); + glGetIntegerv(GL_MINOR_VERSION, &minor); +#ifdef HALO_ANDROID + { + BOOL es32 = major > 3 || (major == 3 && minor >= 2); + BOOL es31 = major > 3 || (major == 3 && minor >= 1); + + /* clip control is emulated in the vertex shader (nv2a_vsh.c) */ + xgpu_capabilities.copy_image = es32 || host_gl_has_extension("GL_EXT_copy_image") || + host_gl_has_extension("GL_OES_copy_image"); + xgpu_capabilities.border_clamp = es32 || host_gl_has_extension("GL_EXT_texture_border_clamp") || + host_gl_has_extension("GL_OES_texture_border_clamp"); + xgpu_capabilities.anisotropy = host_gl_has_extension("GL_EXT_texture_filter_anisotropic"); + xgpu_capabilities.base_vertex = es32; + xgpu_capabilities.shading_language = es31 ? "310 es" : "300 es"; + if (es31) + { + GLint counters = 0; + + glGetIntegerv(GL_MAX_FRAGMENT_ATOMIC_COUNTERS, &counters); + xgpu_capabilities.atomic_counters = counters > 0; + } + xgpu_capabilities.s3tc = host_gl_has_extension("GL_EXT_texture_compression_s3tc") || + (host_gl_has_extension("GL_EXT_texture_compression_dxt1") && + host_gl_has_extension("GL_ANGLE_texture_compression_dxt3") && + host_gl_has_extension("GL_ANGLE_texture_compression_dxt5")); + platform_log("OpenGL ES %d.%d: copy image %d, border clamp %d, anisotropy %d, S3TC %d, sample counting %d", + (int)major, (int)minor, xgpu_capabilities.copy_image, xgpu_capabilities.border_clamp, + xgpu_capabilities.anisotropy, xgpu_capabilities.s3tc, xgpu_capabilities.atomic_counters); + capabilities->shading_language = es31 ? 310 : 300; + capabilities->shading_language_es = 1; + capabilities->sampler_lod_bias = 0; + capabilities->shader_clip_control = 1; + capabilities->s3tc = xgpu_capabilities.s3tc != FALSE; + /* (ES has no BGRA attributes) */ + capabilities->vertex_bgra = 0; + capabilities->base_vertex = xgpu_capabilities.base_vertex != FALSE; + capabilities->occlusion = xgpu_capabilities.atomic_counters ? GPU_OCCLUSION_SHADER_COUNTER : + GPU_OCCLUSION_ANY_SAMPLE; + /* (gl_SampleMask: ES has it only from 3.2) */ + capabilities->sample_mask = 0; + } +#else + if (config_boolean("debug.gl_debug")) + { + glEnable(GL_DEBUG_OUTPUT); + glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS); + glDebugMessageCallback(gl_debug_callback, NULL); + } + glClipControl(GL_UPPER_LEFT, GL_ZERO_TO_ONE); + glEnable(GL_PROGRAM_POINT_SIZE); + capabilities->shading_language = 450; + capabilities->sampler_lod_bias = 1; + capabilities->s3tc = 1; + capabilities->vertex_bgra = 1; + capabilities->base_vertex = 1; + capabilities->occlusion = GPU_OCCLUSION_EXACT; + capabilities->sample_mask = 1; +#endif + glGenSamplers(GPU_STAGE_COUNT, stage_samplers); + { + GLint samples = 0, texture_size = 0, renderbuffer_size = 0; + + glGetIntegerv(GL_MAX_SAMPLES, &samples); + glGetIntegerv(GL_MAX_TEXTURE_SIZE, &texture_size); + glGetIntegerv(GL_MAX_RENDERBUFFER_SIZE, &renderbuffer_size); + capabilities->max_samples = (uint32_t)samples; + capabilities->max_target_size = (uint32_t)(renderbuffer_size < texture_size ? renderbuffer_size : texture_size); + } + xgpu_gl_state_invalidate(); +} diff --git a/port/linux/src/gpu_gl.h b/port/linux/src/gpu_gl.h new file mode 100644 index 0000000000..4ac5dfcb37 --- /dev/null +++ b/port/linux/src/gpu_gl.h @@ -0,0 +1,203 @@ +/* +GPU_GL.H + +The OpenGL backend's own declarations (gpu_gl.c), for the files that still +make GL calls besides it: the device (d3d8_gl.c), whose draws, clears and +presentation do not go through gpu.h yet, and the anti-aliasing passes +(xgpu_post.c). +*/ + +#ifndef __HALO_LINUX_GPU_GL_H +#define __HALO_LINUX_GPU_GL_H + +#include "platform.h" +#include "gl.h" +#include "gpu.h" + +#ifdef HALO_ANDROID +/* OpenGL ES 3 (port/android/README.md): the desktop formats, enumerants +and entry points used that ES lacks */ +#define GL_BGRA GL_RGBA +#define glDepthRange glDepthRangef +#define glClearDepth glClearDepthf +#ifndef GL_TEXTURE_MAX_ANISOTROPY_EXT +#define GL_TEXTURE_MAX_ANISOTROPY_EXT 0x84fe +#endif +#ifndef GL_TEXTURE_BORDER_COLOR +#define GL_TEXTURE_BORDER_COLOR 0x1004 +#endif +#ifndef GL_CLAMP_TO_BORDER +#define GL_CLAMP_TO_BORDER 0x812d +#endif + +/* OpenGL ES features that are optional (gpu_initialize) */ +struct xgpu_capabilities +{ + BOOL copy_image; + BOOL border_clamp; + BOOL anisotropy; + BOOL s3tc; + /* ES 3.2: glDrawElementsBaseVertex */ + BOOL base_vertex; + /* ES 3.1 with fragment atomic counters: exact visibility test counts */ + BOOL atomic_counters; + /* "300 es" or "310 es" */ + const char *shading_language; +}; + +extern struct xgpu_capabilities xgpu_capabilities; + +/* port/android/guest/runtime/guest_host.h */ +int host_gl_has_extension(const char *name); +void host_gl_read_buffer(unsigned int buffer, unsigned int offset, unsigned int size, void *data); +void host_gl_buffer_write(unsigned int target, unsigned int offset, unsigned int size, const void *data); +void host_gl_fence_frame(unsigned int slot); +void host_gl_wait_frame(unsigned int slot); +#endif + +/* ---------- GL state cache + +Consecutive draws share most of their state, but each sets all of it: the +setters here skip the call when GL already holds the value. Code that +changes GL state behind the cache's back (clears, presentation, texture +uploads, render target and framebuffer creation) calls +xgpu_gl_state_invalidate, after which every value is set again. +Unknown values are all ones, which no real value matches (floats become +NaN, which compares unequal to everything). */ + +#ifdef HALO_ANDROID +struct attribute_pointer +{ + GLuint buffer; + GLint size; + GLenum type; + GLboolean normalized; + GLboolean integer; + GLsizei stride; + unsigned long offset; +}; +#else +/* desktop GL (4.3) separates an attribute's format from the buffer it +reads: the attributes of a stream share one binding, so a draw that moves +the stream rebinds it once instead of pointing each attribute again */ +struct attribute_format +{ + GLint size; + GLenum type; + GLboolean normalized; + GLboolean integer; + GLuint relative_offset; + GLuint binding; +}; + +struct vertex_binding +{ + GLuint buffer; + unsigned long offset; + GLsizei stride; +}; + +/* a binding per stream (setup_streams); GL has at least 16 */ +#define VERTEX_BINDING_COUNT 16 + +/* a vertex array object for each vertex layout (the attributes a draw +enables, and each one's format and binding), made once: a draw binds its +layout's and points the bindings at its streams, rather than enabling, +formatting and binding each attribute again (setup_streams) */ +struct vertex_layout +{ + unsigned long enabled; + struct attribute_format formats[GPU_ATTRIBUTE_COUNT]; +}; + +struct vertex_array_entry +{ + struct vertex_array_entry *next; + unsigned long hash; + struct vertex_layout layout; + GLuint vertex_array; + /* its bindings, as last pointed (nothing else changes them) */ + struct vertex_binding bindings[VERTEX_BINDING_COUNT]; +}; +#endif + +struct gl_state +{ + GLuint program; + GLuint framebuffer; + GLint viewport[4]; + GLint scissor[4]; + float depth_range[2]; + unsigned char depth_test, stencil_test, blend, cull_face, offset_fill, offset_line; + unsigned char scissor_test; + GLenum depth_function; + unsigned char depth_mask; + GLenum stencil_function; + GLint stencil_reference; + GLuint stencil_value_mask; + GLenum stencil_operations[3]; + GLuint stencil_write_mask; + GLenum blend_source, blend_destination, blend_equation; + float blend_color[4]; + unsigned char color_mask; + GLenum front_face, cull_mode, polygon_mode; + float polygon_offset[2]; + GLenum active_texture; + /* per unit: the GL_TEXTURE_2D, GL_TEXTURE_CUBE_MAP and GL_TEXTURE_3D + bindings */ + GLuint textures[GPU_STAGE_COUNT][3]; + GLuint samplers[GPU_STAGE_COUNT]; + GLuint array_buffer; + GLuint element_array_buffer; + unsigned char attribute_enabled[GPU_ATTRIBUTE_COUNT]; +#ifdef HALO_ANDROID + struct attribute_pointer attribute_pointers[GPU_ATTRIBUTE_COUNT]; +#else + GLuint vertex_array; +#endif + /* a disabled attribute's value; kind 1 is the integer zero */ + unsigned char attribute_value_kind[GPU_ATTRIBUTE_COUNT]; + float attribute_values[GPU_ATTRIBUTE_COUNT][4]; +}; + +extern struct gl_state gl_state; + +void xgpu_gl_state_invalidate(void); + +void state_enable(unsigned char *shadow, GLenum capability, BOOL enabled); +void state_program(GLuint program); +void state_array_buffer(GLuint buffer); +void state_element_array_buffer(GLuint buffer); +#ifdef HALO_ANDROID +void state_attribute_stream(GLuint index, GLuint binding, GLuint buffer, GLint size, GLenum type, + GLboolean normalized, BOOL integer, GLsizei stride, unsigned long buffer_offset, unsigned long relative_offset); +#else +struct vertex_array_entry *vertex_array_get(const struct vertex_layout *layout); +void state_vertex_array(struct vertex_array_entry *entry); +void state_vertex_buffer(GLuint binding, GLuint buffer, unsigned long offset, GLsizei stride); +void layout_attribute(struct vertex_layout *layout, unsigned long index, GLuint binding, GLint size, + GLenum type, GLboolean normalized, BOOL integer, unsigned long relative_offset); +#endif +void state_attribute_value(GLuint index, const float *value); + +/* ---------- render targets */ + +/* the framebuffer of these render targets' textures (either may be 0) */ +GLuint gpu_gl_framebuffer(gpu_texture color, gpu_texture depth); +/* draws go to these render targets (either may be 0), into their +multisampled storage with samples a pixel, or with 0 into their textures */ +void gpu_gl_bind_targets(gpu_texture color, gpu_texture depth, uint32_t samples); +/* a render target's texture gets the pixels drawn into its multisampled +storage since it last had them */ +void gpu_gl_resolve(gpu_texture target); +/* a render target's multisampled storage, with samples a pixel (0: none): +its pixels resolved into its texture first, and the texture's put into the +new storage */ +void gpu_gl_multisample(gpu_texture target, uint32_t samples); + +/* ---------- texture stages */ + +/* binds each stage's texture and sampler */ +void gpu_gl_bind_stages(const struct gpu_stage stages[GPU_STAGE_COUNT]); + +#endif diff --git a/port/linux/src/xbox_textures.c b/port/linux/src/xbox_textures.c index 558fe055a8..5cf42edaa5 100644 --- a/port/linux/src/xbox_textures.c +++ b/port/linux/src/xbox_textures.c @@ -1,7 +1,7 @@ /* XBOX_TEXTURES.C -Xbox texture decoding and the OpenGL texture cache. +Xbox texture decoding and the texture cache. An Xbox texture is a Direct3D header - Common, Data (physical address), Lock, Format and Size - over texels in guest memory. Power-of-two textures @@ -22,18 +22,9 @@ memory_watch.c detects that by write-protecting the pages. #include "../game/cache_file_formats.h" #include -#ifdef HALO_ANDROID -#define GL_BGRA GL_RGBA -#endif #include #include -#ifndef GL_COMPRESSED_RGBA_S3TC_DXT1_EXT -#define GL_COMPRESSED_RGBA_S3TC_DXT1_EXT 0x83f1 -#define GL_COMPRESSED_RGBA_S3TC_DXT3_EXT 0x83f2 -#define GL_COMPRESSED_RGBA_S3TC_DXT5_EXT 0x83f3 -#endif - /* ---------- formats */ enum texel_kind @@ -423,8 +414,7 @@ static BOOL decode_level(const struct xgpu_texture_description *description, uns return TRUE; } -#ifdef HALO_ANDROID -/* ---------- DXT decoding, for ES drivers without S3TC (Mali) */ +/* ---------- DXT decoding, for GPUs without S3TC (Mali) */ static unsigned long color565(unsigned long value) { @@ -546,29 +536,27 @@ static void dxt_decode_level(unsigned char kind, const unsigned char *source, un } } } -#endif -static GLenum compressed_format(unsigned char kind) +/* the texels a texture is given: its own DXT blocks where the GPU takes +them, or else BGRA8 */ +static unsigned char texture_format(const struct xgpu_texture_description *description) { - switch (kind) + if (!description->compressed || !xgpu_gpu_capabilities.s3tc) + return GPU_FORMAT_BGRA8; + switch (format_information(description->format).kind) { - case _texel_dxt1: return GL_COMPRESSED_RGBA_S3TC_DXT1_EXT; - case _texel_dxt3: return GL_COMPRESSED_RGBA_S3TC_DXT3_EXT; - default: return GL_COMPRESSED_RGBA_S3TC_DXT5_EXT; + case _texel_dxt1: return GPU_FORMAT_BC1; + case _texel_dxt3: return GPU_FORMAT_BC2; + default: return GPU_FORMAT_BC3; } } /* ---------- upload */ -/* debug.texture_dump_directory writes level 0 of every upload as a TGA, read back from GL */ -static void texture_dump(GLenum target, const struct xgpu_texture_description *description) +/* debug.texture_dump_directory writes level 0 of every upload as a TGA, read +back from the GPU (where it can: not with OpenGL ES) */ +static void texture_dump(gpu_texture texture, unsigned char type, const struct xgpu_texture_description *description) { -#ifdef HALO_ANDROID - /* ES cannot read textures back */ - (void)target; - (void)description; -} -#else static unsigned long dump_index = 0; const char *directory = *config_string("debug.texture_dump_directory") ? config_string("debug.texture_dump_directory") : NULL; @@ -578,12 +566,16 @@ static void texture_dump(GLenum target, const struct xgpu_texture_description *d char path[512]; FILE *file; - if (!directory || target != GL_TEXTURE_2D) + if (!directory || type != GPU_TEXTURE_2D) return; pixels = malloc(width * height * 4); if (!pixels) return; - glGetTexImage(GL_TEXTURE_2D, 0, GL_BGRA, GL_UNSIGNED_BYTE, pixels); + if (!gpu_texture_read(texture, pixels, (uint32_t)(width * height * 4))) + { + free(pixels); + return; + } snprintf(path, sizeof(path), "%s/tex%05lu_fmt%02x_%lux%lu.tga", directory, dump_index++, (unsigned)description->format, width, height); file = fopen(path, "wb"); @@ -600,7 +592,6 @@ static void texture_dump(GLenum target, const struct xgpu_texture_description *d } free(pixels); } -#endif /* ---------- Custom Edition channel orders @@ -695,20 +686,17 @@ void halo_custom_edition_texels_forget(void) custom_edition_texel_capacity = 0; } -static void upload(GLuint texture, GLenum target, const struct xgpu_texture_description *description, +static void upload(gpu_texture texture, unsigned char type, const struct xgpu_texture_description *description, const unsigned char *base, const D3DCOLOR *palette, unsigned char channel_order) { struct format_information information = format_information(description->format); unsigned long face_count = description->cube_map ? 6 : 1; unsigned long face_size = xgpu_texture_face_size(description); unsigned long largest = description->width * description->height * description->depth; - BOOL decode_compressed = FALSE; + BOOL decode_compressed = description->compressed && !xgpu_gpu_capabilities.s3tc; unsigned long *converted; unsigned long face, level; -#ifdef HALO_ANDROID - decode_compressed = description->compressed && !xgpu_capabilities.s3tc; -#endif converted = description->compressed && !decode_compressed ? NULL : malloc(largest * sizeof(unsigned long)); if (!converted && !(description->compressed && !decode_compressed)) { @@ -716,82 +704,41 @@ static void upload(GLuint texture, GLenum target, const struct xgpu_texture_desc description->width, description->height, description->depth); return; } - glBindTexture(target, texture); - xgpu_gl_state_invalidate(); - /* the channel of the texels each channel is sampled from, set on every - upload: a texture object can be reused for other texels */ - { - GLint channels[4] = { GL_RED, GL_GREEN, GL_BLUE, GL_ALPHA }; - -#ifdef HALO_ANDROID - /* converted texels are BGRA in memory (32-bit ARGB words); ES takes - RGBA */ - if (converted) - { - channels[0] = GL_BLUE; - channels[2] = GL_RED; - } -#endif - if (channel_order != _custom_edition_channels_xbox) - { - GLint stored[4] = { channels[0], channels[1], channels[2], channels[3] }; - unsigned long channel; - - for (channel = 0; channel < 4; channel++) - channels[channel] = stored[custom_edition_channel_sources[channel_order][channel]]; - } - glTexParameteri(target, GL_TEXTURE_SWIZZLE_R, channels[0]); - glTexParameteri(target, GL_TEXTURE_SWIZZLE_G, channels[1]); - glTexParameteri(target, GL_TEXTURE_SWIZZLE_B, channels[2]); - glTexParameteri(target, GL_TEXTURE_SWIZZLE_A, channels[3]); - } - glPixelStorei(GL_UNPACK_ALIGNMENT, 1); - glTexParameteri(target, GL_TEXTURE_BASE_LEVEL, 0); - glTexParameteri(target, GL_TEXTURE_MAX_LEVEL, (GLint)description->levels - 1); + /* the channel of the texels each channel is sampled from, given at + every upload: a texture can be given other texels */ + gpu_texture_channels(texture, custom_edition_channel_sources[channel_order]); for (face = 0; face < face_count; face++) { - GLenum image_target = description->cube_map ? GL_TEXTURE_CUBE_MAP_POSITIVE_X + face : target; - for (level = 0; level < description->levels; level++) { const unsigned char *source = base + face * face_size + xgpu_texture_level_offset(description, level); - GLsizei width = (GLsizei)level_dimension(description->width, level); - GLsizei height = (GLsizei)level_dimension(description->height, level); - GLsizei depth = (GLsizei)level_dimension(description->depth, level); + unsigned long width = level_dimension(description->width, level); + unsigned long height = level_dimension(description->height, level); + unsigned long depth = level_dimension(description->depth, level); if (description->compressed && !decode_compressed) { - if (target == GL_TEXTURE_3D) - glCompressedTexImage3D(image_target, (GLint)level, compressed_format(information.kind), width, height, depth, 0, - (GLsizei)level_bytes(description, level), source); - else - glCompressedTexImage2D(image_target, (GLint)level, compressed_format(information.kind), width, height, 0, - (GLsizei)level_bytes(description, level), source); + gpu_texture_upload(texture, (uint32_t)face, (uint32_t)level, source, + (uint32_t)level_bytes(description, level)); } else { -#ifdef HALO_ANDROID if (decode_compressed) - dxt_decode_level(information.kind, source, (unsigned long)width, (unsigned long)height, - (unsigned long)depth, converted); - else -#endif - if (!decode_level(description, level, source, palette, converted)) + dxt_decode_level(information.kind, source, width, height, depth, converted); + else if (!decode_level(description, level, source, palette, converted)) { platform_log("textures: no memory to convert a %lux%lux%lu texture; it is not drawn", description->width, description->height, description->depth); free(converted); return; } - if (target == GL_TEXTURE_3D) - glTexImage3D(image_target, (GLint)level, GL_RGBA8, width, height, depth, 0, GL_BGRA, GL_UNSIGNED_BYTE, converted); - else - glTexImage2D(image_target, (GLint)level, GL_RGBA8, width, height, 0, GL_BGRA, GL_UNSIGNED_BYTE, converted); + gpu_texture_upload(texture, (uint32_t)face, (uint32_t)level, converted, + (uint32_t)(width * height * depth * 4)); } } } free(converted); - texture_dump(target, description); + texture_dump(texture, type, description); } /* ---------- cache */ @@ -801,8 +748,8 @@ struct texture_entry struct texture_entry *next; DWORD data, format_word, size_word; unsigned long palette_hash; - GLuint texture; - GLenum target; + gpu_texture texture; + unsigned char type; struct xgpu_texture_description description; unsigned long address, size; unsigned long generation; @@ -860,16 +807,16 @@ static unsigned long palette_hash(const D3DCOLOR *palette) return hash ? hash : 1; } -/* an entry's GL texture and description: its high-res HUD texture's, if it has +/* an entry's texture and description: its high-res HUD texture's, if it has one, with the bitmap's own size (which its coordinates are in) */ -static GLuint texture_entry_result(struct texture_entry *entry, GLenum *target, +static gpu_texture texture_entry_result(struct texture_entry *entry, unsigned char *type, struct xgpu_texture_description *description) { - *target = entry->target; + *type = entry->type; *description = entry->description; /* (the high-res text's atlas, for its placeholder bitmap: text_hires.h) */ { - GLuint atlas = text_hires_atlas_texture(entry->data); + gpu_texture atlas = text_hires_atlas_texture(entry->data); if (atlas) { @@ -880,7 +827,7 @@ static GLuint texture_entry_result(struct texture_entry *entry, GLenum *target, /* (a menu's bitmap: menu_files.h) */ { unsigned long levels; - GLuint art = menu_art_texture(entry->data, &levels); + gpu_texture art = menu_art_texture(entry->data, &levels); if (art) { @@ -891,7 +838,7 @@ static GLuint texture_entry_result(struct texture_entry *entry, GLenum *target, } if (entry->override >= 0) { - GLuint texture = hud_hires_override_texture(entry->override, &description->levels); + gpu_texture texture = hud_hires_override_texture(entry->override, &description->levels); if (texture) { @@ -904,7 +851,7 @@ static GLuint texture_entry_result(struct texture_entry *entry, GLenum *target, return entry->texture; } -GLuint xgpu_texture_get(const DWORD *resource, const D3DCOLOR *palette, GLenum *target, +gpu_texture xgpu_texture_get(const DWORD *resource, const D3DCOLOR *palette, unsigned char *type, struct xgpu_texture_description *description) { DWORD data = resource[1], format_word = resource[3], size_word = resource[4]; @@ -931,7 +878,7 @@ GLuint xgpu_texture_get(const DWORD *resource, const D3DCOLOR *palette, GLenum * if (recent_textures[recent].watch_serial == watch_serial) { entry->last_used_frame = texture_frame; - return texture_entry_result(entry, target, description); + return texture_entry_result(entry, type, description); } } @@ -962,7 +909,7 @@ GLuint xgpu_texture_get(const DWORD *resource, const D3DCOLOR *palette, GLenum * if (!entry) { xgpu_texture_describe(format_word, size_word, description); - *target = GL_TEXTURE_2D; + *type = GPU_TEXTURE_2D; return 0; } entry->data = data; @@ -970,8 +917,8 @@ GLuint xgpu_texture_get(const DWORD *resource, const D3DCOLOR *palette, GLenum * entry->size_word = size_word; entry->palette_hash = hash; xgpu_texture_describe(format_word, size_word, &entry->description); - entry->target = entry->description.cube_map ? GL_TEXTURE_CUBE_MAP : - entry->description.depth > 1 ? GL_TEXTURE_3D : GL_TEXTURE_2D; + entry->type = entry->description.cube_map ? GPU_TEXTURE_CUBE : + entry->description.depth > 1 ? GPU_TEXTURE_3D : GPU_TEXTURE_2D; entry->address = (unsigned long)PLATFORM_PHYSICAL_TO_VIRTUAL(data); /* (a size beyond D3DDevice_GetDeviceCaps' is never uploaded: its byte counts would not fit in 32 bits) */ @@ -984,7 +931,19 @@ GLuint xgpu_texture_get(const DWORD *resource, const D3DCOLOR *palette, GLenum * } entry->generation = 0; entry->override = -1; - glGenTextures(1, &entry->texture); + { + struct gpu_texture_description texels; + + memset(&texels, 0, sizeof(texels)); + texels.type = entry->type; + texels.format = texture_format(&entry->description); + texels.usage = GPU_USAGE_UPLOAD; + texels.width = (uint32_t)entry->description.width; + texels.height = (uint32_t)entry->description.height; + texels.depth = (uint32_t)entry->description.depth; + texels.levels = (uint32_t)entry->description.levels; + entry->texture = gpu_texture_create(&texels); + } entry->next = *bucket; *bucket = entry; } @@ -1039,7 +998,7 @@ GLuint xgpu_texture_get(const DWORD *resource, const D3DCOLOR *palette, GLenum * entry->description.height, entry->size, entry->generation, ones * 100 / entry->size, zeros * 100 / entry->size); } - upload(entry->texture, entry->target, &entry->description, (const unsigned char *)entry->address, palette, + upload(entry->texture, entry->type, &entry->description, (const unsigned char *)entry->address, palette, custom_edition_texels_order(entry->address)); } } @@ -1053,7 +1012,7 @@ GLuint xgpu_texture_get(const DWORD *resource, const D3DCOLOR *palette, GLenum * recent_textures[recent].watch_serial = watch_serial; recent_textures[recent].drop_serial = texture_drop_serial; } - return texture_entry_result(entry, target, description); + return texture_entry_result(entry, type, description); } void xgpu_texture_cache_begin_frame(void) @@ -1075,8 +1034,7 @@ void xgpu_texture_cache_begin_frame(void) if (texture_frame - entry->last_used_frame > TEXTURE_IDLE_FRAMES) { *link = entry->next; - glDeleteTextures(1, &entry->texture); - xgpu_gl_state_invalidate(); + gpu_texture_destroy(entry->texture); texture_drop_serial++; free(entry); } diff --git a/port/linux/src/xgpu.h b/port/linux/src/xgpu.h index a37e3b71a8..fdc1ca9357 100644 --- a/port/linux/src/xgpu.h +++ b/port/linux/src/xgpu.h @@ -12,36 +12,15 @@ device itself (d3d8_gl.c). #include "platform.h" #include "gl.h" +#include "gpu.h" -#ifdef HALO_ANDROID -/* OpenGL ES features that are optional (d3d8_gl.c gl_initialize) */ -struct xgpu_capabilities -{ - BOOL copy_image; - BOOL border_clamp; - BOOL anisotropy; - BOOL s3tc; - /* ES 3.2: glDrawElementsBaseVertex */ - BOOL base_vertex; - /* ES 3.1 with fragment atomic counters: exact visibility test counts */ - BOOL atomic_counters; - /* "300 es" or "310 es" */ - const char *shading_language; -}; - -extern struct xgpu_capabilities xgpu_capabilities; - -/* port/android/guest/runtime/guest_host.h */ -int host_gl_has_extension(const char *name); -void host_gl_read_buffer(unsigned int buffer, unsigned int offset, unsigned int size, void *data); -void host_gl_buffer_write(unsigned int target, unsigned int offset, unsigned int size, const void *data); -void host_gl_fence_frame(unsigned int slot); -void host_gl_wait_frame(unsigned int slot); -#endif +/* what the GPU backend can do (gpu_initialize, from d3d8_gl.c +gl_initialize) */ +extern struct gpu_capabilities xgpu_gpu_capabilities; /* ---------- GL state -The device caches the GL state it sets for draws (d3d8_gl.c); code that +The GL backend caches the GL state it sets for draws (gpu_gl.c); code that changes GL state behind it (binding a texture to upload it, deleting one) must call this afterwards. */ @@ -211,9 +190,9 @@ unsigned long xgpu_texture_face_size(const struct xgpu_texture_description *desc unsigned long xgpu_texture_level_offset(const struct xgpu_texture_description *description, unsigned long level); unsigned long xgpu_texture_level_pitch(const struct xgpu_texture_description *description, unsigned long level); -/* the GL texture for an Xbox texture header, uploading or refreshing it -from guest memory as needed; *target receives GL_TEXTURE_2D etc. */ -GLuint xgpu_texture_get(const DWORD *resource, const D3DCOLOR *palette, GLenum *target, +/* the texture for an Xbox texture header, uploading or refreshing it from +guest memory as needed; *type receives GPU_TEXTURE_2D etc. */ +gpu_texture xgpu_texture_get(const DWORD *resource, const D3DCOLOR *palette, unsigned char *type, struct xgpu_texture_description *description); void xgpu_texture_cache_begin_frame(void); @@ -224,24 +203,20 @@ struct xgpu_render_target unsigned long data; /* physical address */ unsigned long width, height; BOOL depth; - GLuint texture; + gpu_texture texture; /* pixels per unit of width and height: more than 1 for the screen's targets when the game draws at the display's resolution (d3d8_gl.c) */ float scale[2]; unsigned long gl_width, gl_height; - /* with multisampling, the multisampled renderbuffer draws go to, its - samples a pixel (0 when it has none), and whether it has been drawn into - since the texture last had its pixels (d3d8_gl.c, - render_target_multisample) */ - GLuint multisample; + /* with multisampling, the samples a pixel of its multisampled storage, + which draws go to (0 when it has none: d3d8_gl.c, bind_targets) */ int samples; - BOOL unresolved; /* changes whenever the target is drawn into or cleared (d3d8_gl.c, bind_targets) */ unsigned long written; }; -/* the GL texture holding a render target with this physical address, or 0 */ +/* the render target with this physical address, or NULL */ struct xgpu_render_target *xgpu_render_target_find(unsigned long data); /* ---------- anti-aliasing diff --git a/port/linux/src/xgpu_post.c b/port/linux/src/xgpu_post.c index 03461ad2a7..d6e8fd2eaa 100644 --- a/port/linux/src/xgpu_post.c +++ b/port/linux/src/xgpu_post.c @@ -21,6 +21,7 @@ anti_aliasing_values). */ #include "xgpu.h" +#include "gpu_gl.h" #include From 3cd86361120447e108fd3cb986c2842960f8770c Mon Sep 17 00:00:00 2001 From: Steve Rice Date: Thu, 8 Oct 2026 13:03:29 -0700 Subject: [PATCH 3/5] Draw through one self-contained packet per draw The device now describes each draw in full as a struct gpu_draw (gpu.h): its targets and samples a pixel, its shaders, viewport and scissor, depth, stencil, blending and rasterization in neutral terms, its texture stages, its vertex streams and attributes, and its index buffer and primitive. gpu_draw applies the whole packet; nothing carries over from the draw before it but what the GL state cache skips. With it go the vertex streams and the vertex constants. The mirror of the Xbox's contiguous memory writes gpu_buffers, and what a draw streams goes through gpu_stream into the backend's frame buffers. The 192 vertex constants and their change serials, log and checkpoint are a struct gpu_constant_store, whose uploads stay incremental, and the other uniforms are a struct gpu_uniforms with a serial of its own. Shaders are compiled through gpu_shader_create; the programs, their uniform locations, the vertex array objects, the sampler binding and the Intel flush are gpu_gl.c's. A draw whose shaders lit for each pixel do not link is drawn again lit for each vertex, as before. The GL calls are the same but for these: a draw's textures are bound after its uniforms rather than before its targets, an indexed draw with no base vertex calls glDrawElements rather than glDrawElementsBaseVertex with 0, and the desktop finds a draw's vertex array by comparing its layout with the previous draw's rather than keeping one on each vertex declaration. The GLSL is unchanged. Clears, visibility tests and presentation still make GL calls of their own, through gpu_gl.h, until the next step. Co-Authored-By: Claude Opus 5.5 --- port/linux/src/d3d8_gl.c | 1394 ++++++++++---------------------------- port/linux/src/gpu.h | 232 ++++++- port/linux/src/gpu_gl.c | 1047 +++++++++++++++++++++++++++- port/linux/src/gpu_gl.h | 156 +---- port/linux/src/xgpu.h | 5 - 5 files changed, 1654 insertions(+), 1180 deletions(-) diff --git a/port/linux/src/d3d8_gl.c b/port/linux/src/d3d8_gl.c index 0469e8d414..6952b289fd 100644 --- a/port/linux/src/d3d8_gl.c +++ b/port/linux/src/d3d8_gl.c @@ -76,7 +76,7 @@ menus are drawn over it (halo_screen_anti_alias, xgpu_post.c); "ssaa2x" draws the screen's targets at twice the resolution each way (screen_mode_choose), which the display blit scales down; "msaa2x" to "msaa8x" draw the back buffer and its depth buffer with that many samples a -pixel (bind_targets). The setting is read again between frames +pixel (bind_targets, gpu_gl_bind_targets). The setting is read again between frames (halo_screen_commit), so that a change applies from the next one. */ enum @@ -300,91 +300,31 @@ struct vertex_shader_object unsigned long element_count; unsigned long packed_mask; /* [0] streams per the declaration, [1] immediate mode (all floats) */ - GLuint shader[2]; + gpu_shader shader[2]; /* one of the game's model lighting programs (halo_vertex_shader_lighting), whose draws can be lit for each pixel (display.per_pixel_lighting): where its lighting's normal and position are (lighting.lights is 0 for the others), and its shaders that hand them on, as shader[] */ struct nv2a_vertex_lighting lighting; - GLuint lit_shader[2]; + gpu_shader lit_shader[2]; /* a shader lit for each pixel failed to compile or link: lit as the vertex shader lights it from then on */ BOOL lighting_failed; -#ifndef HALO_ANDROID - /* the vertex array its draws last used, and the streams they had - (setup_streams: the layout follows from the two) */ - struct vertex_array_entry *vertex_array; - unsigned long vertex_array_streams; -#endif }; -/* ---------- programs */ +/* ---------- pixel shaders */ struct fragment_entry { struct fragment_entry *next; unsigned long hash; struct nv2a_pixel_shader_key key; - GLuint shader; -}; - -/* the uniforms a draw sets besides the vertex constants */ -struct draw_uniforms -{ - float viewport_scale[4]; - float viewport_offset[4]; - float point_size; - float ps_c0[8][4]; - float ps_c1[8][4]; - float ps_final_c0[4]; - float ps_final_c1[4]; - float fog_color[4]; - float fog_parameters[4]; - float alpha_reference; - float bump_matrix[4][4]; - float bump_luminance[4][4]; - float texture_scale[4][4]; - float screen_offset; - float texture_lod_bias[4]; -}; - -struct program_entry -{ - struct program_entry *next; - GLuint vertex_shader; - GLuint fragment_shader; - GLuint program; - GLint constants; - GLint viewport_scale; - GLint viewport_offset; - GLint point_size; - GLint ps_c0, ps_c1, ps_final_c0, ps_final_c1; - GLint fog_color, fog_parameters, alpha_reference; - GLint bump_matrix, bump_luminance, texture_scale; - GLint texture_lod_bias; - GLint screen_offset; - /* the lights of a draw lit for each pixel (XGPU_MODEL_LIGHT_COUNT), and - constants_serial at their last upload */ - GLint model_lights; - unsigned long long model_lights_serial; - - /* the vertex constants c[0..constant_count) the program uses; with - consecutive locations, a changed range is uploaded by itself */ - unsigned long constant_count; - BOOL constants_consecutive; - /* constants_serial at the program's last constant upload (constants_store) */ - unsigned long long constants_serial; - /* draw_uniforms_serial when the uniforms below were brought up to date */ - unsigned long uniforms_serial; - /* what the program's other uniforms hold (all ones: unknown) */ - struct draw_uniforms uniforms; + gpu_shader shader; }; #define FRAGMENT_BUCKETS 1024 -#define PROGRAM_BUCKETS 1024 static struct fragment_entry *fragment_buckets[FRAGMENT_BUCKETS]; -static struct program_entry *program_buckets[PROGRAM_BUCKETS]; /* ---------- render targets */ @@ -415,20 +355,6 @@ static struct render_target_entry *render_targets; /* ---------- the device */ -#ifdef HALO_ANDROID -/* Mobile drivers (Mali) keep every orphaned copy of a buffer until the GPU -is done with it, so a large buffer orphaned each frame costs its size per -frame in flight and more. Instead each frame streams into the next of a few -smaller buffers, reusing one only once the GPU has finished the frame that -last used it (host_gl_wait_frame). A busy frame streams about 5 MB of -vertices. */ -#define STREAM_BUFFER_SIZE (16 * 1024 * 1024) -#define INDEX_BUFFER_SIZE (2 * 1024 * 1024) -#define STREAM_BUFFER_RING 3 -#else -#define STREAM_BUFFER_SIZE (32 * 1024 * 1024) -#define INDEX_BUFFER_SIZE (8 * 1024 * 1024) -#endif #define VISIBILITY_TEST_SLOTS 4096 #ifdef HALO_ANDROID #define VISIBILITY_QUERY GL_ANY_SAMPLES_PASSED @@ -453,7 +379,6 @@ struct gl_device struct vertex_shader_object *vertex_shader; struct vertex_shader_object *program_slots[VERTEX_PROGRAM_SLOTS]; unsigned long program_address; - float constants[XGPU_VERTEX_CONSTANT_COUNT][4]; float viewport_scale[4]; float viewport_offset[4]; @@ -475,17 +400,6 @@ struct gl_device unsigned long immediate_count; unsigned long immediate_capacity; - GLuint vertex_array; - GLuint stream_buffer; -#ifdef HALO_ANDROID - GLuint stream_buffers[STREAM_BUFFER_RING]; - GLuint index_buffers[STREAM_BUFFER_RING]; - unsigned long buffer_ring; -#endif - unsigned long stream_offset; - GLuint index_buffer; - unsigned long index_offset; - GLuint queries[VISIBILITY_TEST_SLOTS]; BOOL query_pending[VISIBILITY_TEST_SLOTS]; /* the pixels each of the game's pixels covered in the test's target @@ -514,9 +428,9 @@ struct gl_device passed (two frames on, D3DDevice_Present) the CPU reads the snapshot into counter_values and gives each listed slot its count: a result is the latest count known, as the desktop's query buffer gives. */ - GLuint counter_snapshots[STREAM_BUFFER_RING]; - unsigned short ring_tests[STREAM_BUFFER_RING][VISIBILITY_TEST_SLOTS][2]; - unsigned long ring_test_count[STREAM_BUFFER_RING]; + GLuint counter_snapshots[GPU_GL_FRAME_RING]; + unsigned short ring_tests[GPU_GL_FRAME_RING][VISIBILITY_TEST_SLOTS][2]; + unsigned long ring_test_count[GPU_GL_FRAME_RING]; GLuint counter_values[VISIBILITY_TEST_SLOTS]; GLuint visibility_latest[VISIBILITY_TEST_SLOTS]; #else @@ -526,9 +440,6 @@ struct gl_device had caught up */ GLuint visibility_results_buffer; volatile GLuint *visibility_results; - /* a pipeline flush every flush_every draws (draw_flush), 0 never */ - unsigned long flush_every; - unsigned long flush_draws; #endif unsigned long frame; @@ -679,48 +590,6 @@ void WINAPI D3DDevice_BlockUntilVerticalBlank(void) pthread_mutex_unlock(&vertical_blank_lock); } -/* ---------- GL helpers */ - -GLuint xgpu_compile_shader(GLenum type, const char *source, const char *what) -{ - GLuint shader = glCreateShader(type); - GLint status = 0; - - glShaderSource(shader, 1, &source, NULL); - glCompileShader(shader); - glGetShaderiv(shader, GL_COMPILE_STATUS, &status); - if (!status) - { - char log[4096]; - - glGetShaderInfoLog(shader, sizeof(log), NULL, log); - platform_log("cannot compile the %s shader:\n%s\n%s", what, log, source); - glDeleteShader(shader); - return 0; - } - return shader; -} - -GLuint xgpu_link_program(GLuint vertex_shader, GLuint fragment_shader, const char *what) -{ - GLuint program = glCreateProgram(); - GLint status = 0; - - glAttachShader(program, vertex_shader); - glAttachShader(program, fragment_shader); - glLinkProgram(program); - glGetProgramiv(program, GL_LINK_STATUS, &status); - if (!status) - { - char log[4096]; - - glGetProgramInfoLog(program, sizeof(log), NULL, log); - platform_log("cannot link the %s program: %s", what, log); - return 0; - } - return program; -} - /* ---------- render targets */ static void surface_dimensions(const D3DSurface *surface, unsigned long *width, unsigned long *height, BOOL *depth) @@ -904,9 +773,11 @@ static GLint target_pixel(float coordinate, int axis) return scaled_pixel(coordinate, target_scale[axis]); } -/* binds the framebuffer for the current targets; returns FALSE if there is -nothing to draw into */ -static BOOL bind_targets(BOOL *has_depth) +/* the targets draws and clears go to, and the samples a pixel of their +multisampled storage (0: their textures); FALSE if there is nothing to draw +into */ +static BOOL bind_targets(gpu_texture *color_target, gpu_texture *depth_target, uint32_t *target_storage, + BOOL *has_depth) { struct render_target_entry *color = render_target_get(device.render_target); struct render_target_entry *depth = render_target_get(device.depth_stencil); @@ -932,7 +803,9 @@ static BOOL bind_targets(BOOL *has_depth) { samples = 0; } - gpu_gl_bind_targets(color ? color->target.texture : 0, depth ? depth->target.texture : 0, (uint32_t)samples); + *color_target = color ? color->target.texture : 0; + *depth_target = depth ? depth->target.texture : 0; + *target_storage = (uint32_t)samples; if (color) color->target.samples = samples; if (depth) @@ -955,33 +828,6 @@ static void gl_initialize(void) shader_dialect.es = capabilities->shading_language_es != 0; shader_dialect.lookup_lod_bias = !capabilities->sampler_lod_bias; shader_dialect.clip_control = capabilities->shader_clip_control != 0; - glGenVertexArrays(1, &device.vertex_array); - glBindVertexArray(device.vertex_array); -#ifdef HALO_ANDROID - { - int ring; - - glGenBuffers(STREAM_BUFFER_RING, device.stream_buffers); - glGenBuffers(STREAM_BUFFER_RING, device.index_buffers); - for (ring = 0; ring < STREAM_BUFFER_RING; ring++) - { - glBindBuffer(GL_ARRAY_BUFFER, device.stream_buffers[ring]); - glBufferData(GL_ARRAY_BUFFER, STREAM_BUFFER_SIZE, NULL, GL_STREAM_DRAW); - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, device.index_buffers[ring]); - glBufferData(GL_ELEMENT_ARRAY_BUFFER, INDEX_BUFFER_SIZE, NULL, GL_STREAM_DRAW); - } - device.stream_buffer = device.stream_buffers[0]; - device.index_buffer = device.index_buffers[0]; - } -#endif -#ifndef HALO_ANDROID - glGenBuffers(1, &device.stream_buffer); - glBindBuffer(GL_ARRAY_BUFFER, device.stream_buffer); - glBufferData(GL_ARRAY_BUFFER, STREAM_BUFFER_SIZE, NULL, GL_STREAM_DRAW); - glGenBuffers(1, &device.index_buffer); - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, device.index_buffer); - glBufferData(GL_ELEMENT_ARRAY_BUFFER, INDEX_BUFFER_SIZE, NULL, GL_STREAM_DRAW); -#endif glGenQueries(VISIBILITY_TEST_SLOTS, device.queries); #ifndef HALO_ANDROID glGenBuffers(1, &device.visibility_results_buffer); @@ -992,18 +838,6 @@ static void gl_initialize(void) GL_MAP_READ_BIT | GL_MAP_PERSISTENT_BIT | GL_MAP_COHERENT_BIT); if (!device.visibility_results) platform_log("cannot map the visibility test results; tests wait for the GPU"); - { - long every = config_integer("debug.gpu_flush_draws"); - const char *renderer = (const char *)glGetString(GL_RENDERER); - - if (every < 0) - every = renderer && strstr(renderer, "Mesa Intel") ? 3 : 0; - if (every > 0) - { - device.flush_every = (unsigned long)every; - platform_log("GPU: a pipeline flush every %ld draws", every); - } - } #endif #ifdef HALO_ANDROID if (xgpu_capabilities.atomic_counters) @@ -1014,8 +848,8 @@ static void gl_initialize(void) glBindBuffer(GL_ATOMIC_COUNTER_BUFFER, device.visibility_counters); glBufferData(GL_ATOMIC_COUNTER_BUFFER, VISIBILITY_TEST_SLOTS * sizeof(GLuint), NULL, GL_DYNAMIC_DRAW); glBindBuffer(GL_ATOMIC_COUNTER_BUFFER, 0); - glGenBuffers(STREAM_BUFFER_RING, device.counter_snapshots); - for (ring = 0; ring < STREAM_BUFFER_RING; ring++) + glGenBuffers(GPU_GL_FRAME_RING, device.counter_snapshots); + for (ring = 0; ring < GPU_GL_FRAME_RING; ring++) { glBindBuffer(GL_COPY_WRITE_BUFFER, device.counter_snapshots[ring]); glBufferData(GL_COPY_WRITE_BUFFER, VISIBILITY_TEST_SLOTS * sizeof(GLuint), NULL, GL_STREAM_READ); @@ -1024,10 +858,7 @@ static void gl_initialize(void) } #endif for (index = 0; index < XGPU_VERTEX_ATTRIBUTE_COUNT; index++) - { device.attributes[index][3] = 1.0f; - glVertexAttrib4fv(index, device.attributes[index]); - } memory_watch_initialize(); debug_settings.skip_vertex_shaders = config_string("debug.gpu_skip_vertex_shaders"); debug_settings.dump_shaders = *config_string("debug.gpu_dump_shaders") ? @@ -1077,44 +908,27 @@ void WINAPI Direct3D_SetPushBufferSize(DWORD push_buffer_size, DWORD segment_cou (void)segment_count; } -/* each vertex constant register's serial is the value constants_serial took -when the register last changed; a program's registers are current up to -the serial it recorded when it last uploaded them. The serials are 64-bit: -the count rises with every register a draw changes (a skinned model changes -up to 132), and 32 bits wrapped within minutes at a high frame rate, after -which every program's next draw found none of its registers changed and -drew with what it last uploaded (another object's node matrices: vertices -flung across the screen for a frame). */ -static unsigned long long constant_serials[XGPU_VERTEX_CONSTANT_COUNT]; -static unsigned long long constants_serial; -/* the register each of the latest serials changed, so a program that is -only a little behind finds its changed registers without a full scan */ -#define CONSTANT_LOG_SIZE 1024 -static unsigned char constant_log[CONSTANT_LOG_SIZE]; -/* the registers changed since the last upload (to any program): the -smallest and largest, and the serial that upload was current to. A program -current to that serial needs them, and nothing in the log. The serial is -64-bit as the others are (cut to 32, it matched a program left at an old -serial, which then took only the checkpoint's registers). */ -static unsigned long long constants_checkpoint_serial; -static unsigned long constants_checkpoint_first = XGPU_VERTEX_CONSTANT_COUNT, constants_checkpoint_last; +/* the vertex constant registers, and when each last changed (gpu.h's +struct gpu_constant_store) */ +static struct gpu_constant_store vertex_constants = { .checkpoint_first = GPU_CONSTANT_COUNT }; static void constants_store(unsigned long first, const void *data, unsigned long count) { + struct gpu_constant_store *store = &vertex_constants; const float (*values)[4] = data; unsigned long index; for (index = 0; index < count; index++) { - if (memcmp(device.constants[first + index], values[index], sizeof(device.constants[0]))) + if (memcmp(store->c[first + index], values[index], sizeof(store->c[0]))) { - memcpy(device.constants[first + index], values[index], sizeof(device.constants[0])); - constant_serials[first + index] = ++constants_serial; - constant_log[constants_serial % CONSTANT_LOG_SIZE] = (unsigned char)(first + index); - if (constants_checkpoint_first > first + index) - constants_checkpoint_first = first + index; - if (constants_checkpoint_last < first + index) - constants_checkpoint_last = first + index; + memcpy(store->c[first + index], values[index], sizeof(store->c[0])); + store->serials[first + index] = ++store->serial; + store->log[store->serial % GPU_CONSTANT_LOG_SIZE] = (uint8_t)(first + index); + if (store->checkpoint_first > first + index) + store->checkpoint_first = (uint32_t)(first + index); + if (store->checkpoint_last < first + index) + store->checkpoint_last = (uint32_t)(first + index); } } } @@ -1502,14 +1316,15 @@ HRESULT WINAPI D3DDevice_EndVisibilityTest(DWORD index) #ifdef HALO_ANDROID if (xgpu_capabilities.atomic_counters) { - unsigned long *count = &device.ring_test_count[device.buffer_ring]; + unsigned long ring = gpu_gl_frame_ring(); + unsigned long *count = &device.ring_test_count[ring]; /* (a slot tested twice in a frame is listed twice: the later counter, resolved after, wins) */ if (*count < VISIBILITY_TEST_SLOTS) { - device.ring_tests[device.buffer_ring][*count][0] = (unsigned short)index; - device.ring_tests[device.buffer_ring][*count][1] = (unsigned short)device.counter_active; + device.ring_tests[ring][*count][0] = (unsigned short)index; + device.ring_tests[ring][*count][1] = (unsigned short)device.counter_active; (*count)++; } device.query_pending[index] = TRUE; @@ -1954,17 +1769,17 @@ static unsigned long hash_words(const void *data, unsigned long size) /* lit: the shader that hands the lighting's normal and position on (vertex_shader_object lit_shader) */ -static GLuint vertex_shader_get(struct vertex_shader_object *program, BOOL immediate, BOOL lit) +static gpu_shader vertex_shader_get(struct vertex_shader_object *program, BOOL immediate, BOOL lit) { int variant = immediate ? 1 : 0; - GLuint *shader = lit ? &program->lit_shader[variant] : &program->shader[variant]; + gpu_shader *shader = lit ? &program->lit_shader[variant] : &program->shader[variant]; if (!*shader) { char *source = nv2a_vertex_shader_to_glsl(&shader_dialect, program->instructions, program->instruction_count, immediate ? 0 : device.vertex_shader->packed_mask, lit ? &program->lighting : NULL); - *shader = xgpu_compile_shader(GL_VERTEX_SHADER, source, "vertex"); + *shader = gpu_shader_create(GPU_SHADER_VERTEX, source); if (debug_settings.dump_shaders) { char path[512]; @@ -1985,7 +1800,7 @@ static GLuint vertex_shader_get(struct vertex_shader_object *program, BOOL immed typedef char pixel_shader_key_size_assert[sizeof(struct nv2a_pixel_shader_key) % 4 == 0 ? 1 : -1]; -static GLuint fragment_shader_get(const struct nv2a_pixel_shader_key *key) +static gpu_shader fragment_shader_get(const struct nv2a_pixel_shader_key *key) { /* consecutive draws mostly use one of a few pixel shaders (an object's parts take turns) */ @@ -2016,7 +1831,7 @@ static GLuint fragment_shader_get(const struct nv2a_pixel_shader_key *key) entry->hash = hash; entry->key = *key; source = nv2a_pixel_shader_to_glsl(&shader_dialect, key); - entry->shader = xgpu_compile_shader(GL_FRAGMENT_SHADER, source, "pixel"); + entry->shader = gpu_shader_create(GPU_SHADER_PIXEL, source); if (debug_settings.dump_shaders) { char path[512]; @@ -2036,95 +1851,6 @@ static GLuint fragment_shader_get(const struct nv2a_pixel_shader_key *key) return entry->shader; } -static struct program_entry *program_get(GLuint vertex_shader, GLuint fragment_shader) -{ - static struct program_entry *last; - unsigned long hash = (vertex_shader * 2654435761UL) ^ fragment_shader; - struct program_entry **bucket = &program_buckets[hash % PROGRAM_BUCKETS]; - struct program_entry *entry; - int stage; - - if (last && last->vertex_shader == vertex_shader && last->fragment_shader == fragment_shader) - return last; - for (entry = *bucket; entry; entry = entry->next) - { - if (entry->vertex_shader == vertex_shader && entry->fragment_shader == fragment_shader) - { - if (!entry->program) - return NULL; - last = entry; - return entry; - } - } - entry = calloc(1, sizeof(*entry)); - entry->vertex_shader = vertex_shader; - entry->fragment_shader = fragment_shader; - memset(&entry->uniforms, 0xff, sizeof(entry->uniforms)); - entry->next = *bucket; - *bucket = entry; - if (!vertex_shader || !fragment_shader) - return NULL; - - entry->program = xgpu_link_program(vertex_shader, fragment_shader, "shader"); - if (!entry->program) - return NULL; - state_program(entry->program); - entry->constants = glGetUniformLocation(entry->program, "c"); - entry->constant_count = XGPU_VERTEX_CONSTANT_COUNT; - if (entry->constants >= 0) - { - unsigned long index; - - /* c[i] is usually at c's location plus i, and the compiler may - drop registers past the last one the program reads */ - entry->constants_consecutive = TRUE; - for (index = 1; index < XGPU_VERTEX_CONSTANT_COUNT; index++) - { - char name[16]; - GLint location; - - snprintf(name, sizeof(name), "c[%lu]", index); - location = glGetUniformLocation(entry->program, name); - if (location < 0) - { - entry->constant_count = index; - break; - } - if (location != entry->constants + (GLint)index) - { - entry->constants_consecutive = FALSE; - entry->constant_count = XGPU_VERTEX_CONSTANT_COUNT; - break; - } - } - } - entry->viewport_scale = glGetUniformLocation(entry->program, "viewport_scale"); - entry->viewport_offset = glGetUniformLocation(entry->program, "viewport_offset"); - entry->point_size = glGetUniformLocation(entry->program, "point_size"); - entry->ps_c0 = glGetUniformLocation(entry->program, "ps_c0"); - entry->ps_c1 = glGetUniformLocation(entry->program, "ps_c1"); - entry->ps_final_c0 = glGetUniformLocation(entry->program, "ps_final_c0"); - entry->ps_final_c1 = glGetUniformLocation(entry->program, "ps_final_c1"); - entry->fog_color = glGetUniformLocation(entry->program, "fog_color"); - entry->fog_parameters = glGetUniformLocation(entry->program, "fog_parameters"); - entry->alpha_reference = glGetUniformLocation(entry->program, "alpha_reference"); - entry->bump_matrix = glGetUniformLocation(entry->program, "bump_matrix"); - entry->bump_luminance = glGetUniformLocation(entry->program, "bump_luminance"); - entry->texture_scale = glGetUniformLocation(entry->program, "texture_scale"); - entry->texture_lod_bias = glGetUniformLocation(entry->program, "texture_lod_bias"); - entry->screen_offset = glGetUniformLocation(entry->program, "screen_offset"); - entry->model_lights = glGetUniformLocation(entry->program, "model_lights"); - for (stage = 0; stage < D3DTSS_MAXSTAGES; stage++) - { - char name[8]; - - snprintf(name, sizeof(name), "tex%d", stage); - glUniform1i(glGetUniformLocation(entry->program, name), stage); - } - last = entry; - return entry; -} - /* ---------- per-draw state */ static unsigned long stage_texture_mode(int stage) @@ -2274,14 +2000,11 @@ static gpu_texture mip_composite_get(const struct xgpu_texture_description *desc return composite->texture; } -static void bind_textures(struct nv2a_pixel_shader_key *key, float texture_scale[4][4]) +/* the draw's texture stages */ +static void bind_textures(struct nv2a_pixel_shader_key *key, float texture_scale[4][4], struct gpu_stage *stages) { - /* Bind only after resolving every stage, since texture uploads can - overwrite the active unit's binding. */ - struct gpu_stage stages[D3DTSS_MAXSTAGES]; int stage; - memset(stages, 0, sizeof(stages)); for (stage = 0; stage < D3DTSS_MAXSTAGES; stage++) { D3DBaseTexture *texture = device.textures[stage]; @@ -2302,8 +2025,6 @@ static void bind_textures(struct nv2a_pixel_shader_key *key, float texture_scale if (target) { - /* (multisampled: its pixels drawn since, resolved) */ - gpu_gl_resolve(target->texture); xgpu_texture_describe(texture->Format, texture->Size, &description); handle = target->texture; type = GPU_TEXTURE_2D; @@ -2342,273 +2063,126 @@ static void bind_textures(struct nv2a_pixel_shader_key *key, float texture_scale type == GPU_TEXTURE_3D ? _xgpu_sampler_3d : _xgpu_sampler_2d; } } - gpu_gl_bind_stages(stages); } -static GLenum stencil_operation(DWORD operation) +static unsigned char compare_function(DWORD function) { - /* Xbox stencil operations are the GL enumerants, plus 0 for ZERO */ - return operation ? (GLenum)operation : GL_ZERO; + /* (0, unset: never) */ + return function >= D3DCMP_NEVER && function <= D3DCMP_ALWAYS ? (unsigned char)(function - D3DCMP_NEVER) : + GPU_COMPARE_NEVER; } -static GLenum blend_equation(DWORD operation) +static unsigned char stencil_operation(DWORD operation) { switch (operation) { - case D3DBLENDOP_SUBTRACT: return GL_FUNC_SUBTRACT; - case D3DBLENDOP_REVSUBTRACT: - case D3DBLENDOP_REVSUBTRACTSIGNED: return GL_FUNC_REVERSE_SUBTRACT; - case D3DBLENDOP_MIN: return GL_MIN; - case D3DBLENDOP_MAX: return GL_MAX; - default: return GL_FUNC_ADD; + case D3DSTENCILOP_ZERO: return GPU_STENCIL_ZERO; + case D3DSTENCILOP_REPLACE: return GPU_STENCIL_REPLACE; + case D3DSTENCILOP_INCRSAT: return GPU_STENCIL_INCREMENT_CLAMP; + case D3DSTENCILOP_DECRSAT: return GPU_STENCIL_DECREMENT_CLAMP; + case D3DSTENCILOP_INVERT: return GPU_STENCIL_INVERT; + case D3DSTENCILOP_INCR: return GPU_STENCIL_INCREMENT_WRAP; + case D3DSTENCILOP_DECR: return GPU_STENCIL_DECREMENT_WRAP; + default: return GPU_STENCIL_KEEP; } } -static void apply_raster_state(BOOL has_depth) +static unsigned char blend_factor(DWORD factor) { - DWORD *rs = D3D__RenderState; - DWORD write = rs[D3DRS_COLORWRITEENABLE]; - GLint viewport[4]; - GLint scissor[4]; - float depth_range[2]; - unsigned char color_mask; - BOOL depth_test = has_depth && rs[D3DRS_ZENABLE]; - - viewport[0] = target_pixel((float)device.viewport.X, 0); - viewport[1] = target_pixel((float)device.viewport.Y, 1); - viewport[2] = target_pixel((float)(device.viewport.X + device.viewport.Width), 0) - viewport[0]; - viewport[3] = target_pixel((float)(device.viewport.Y + device.viewport.Height), 1) - viewport[1]; - if (memcmp(gl_state.viewport, viewport, sizeof(viewport))) - { - memcpy(gl_state.viewport, viewport, sizeof(viewport)); - glViewport(viewport[0], viewport[1], viewport[2], viewport[3]); - } - /* the game never issues a scissor rectangle, and the NV2A scissor register - defaults to the viewport, so fragment clipping follows the viewport: this is - what keeps a split-screen window's geometry from bleeding across the divider */ - /* (glScissor takes the corner and the size, as glViewport does) */ - memcpy(scissor, viewport, sizeof(scissor)); - if (memcmp(gl_state.scissor, scissor, sizeof(scissor))) + switch (factor) { - memcpy(gl_state.scissor, scissor, sizeof(scissor)); - glScissor(scissor[0], scissor[1], scissor[2], scissor[3]); - } - state_enable(&gl_state.scissor_test, GL_SCISSOR_TEST, scissor[2] > 0 && scissor[3] > 0); - depth_range[0] = device.viewport.MinZ; - depth_range[1] = device.viewport.MaxZ; - if (memcmp(gl_state.depth_range, depth_range, sizeof(depth_range))) - { - memcpy(gl_state.depth_range, depth_range, sizeof(depth_range)); - glDepthRange(depth_range[0], depth_range[1]); - } - - state_enable(&gl_state.depth_test, GL_DEPTH_TEST, depth_test); - if (depth_test) - { - GLenum function = rs[D3DRS_ZFUNC] ? (GLenum)rs[D3DRS_ZFUNC] : GL_NEVER; - - if (gl_state.depth_function != function) - { - gl_state.depth_function = function; - glDepthFunc(function); - } - } - { - unsigned char mask = depth_test && rs[D3DRS_ZWRITEENABLE] ? 1 : 0; - - if (gl_state.depth_mask != mask) - { - gl_state.depth_mask = mask; - glDepthMask(mask ? GL_TRUE : GL_FALSE); - } + case D3DBLEND_ZERO: return GPU_BLEND_ZERO; + case D3DBLEND_SRCCOLOR: return GPU_BLEND_SOURCE_COLOR; + case D3DBLEND_INVSRCCOLOR: return GPU_BLEND_ONE_MINUS_SOURCE_COLOR; + case D3DBLEND_SRCALPHA: return GPU_BLEND_SOURCE_ALPHA; + case D3DBLEND_INVSRCALPHA: return GPU_BLEND_ONE_MINUS_SOURCE_ALPHA; + case D3DBLEND_DESTALPHA: return GPU_BLEND_DESTINATION_ALPHA; + case D3DBLEND_INVDESTALPHA: return GPU_BLEND_ONE_MINUS_DESTINATION_ALPHA; + case D3DBLEND_DESTCOLOR: return GPU_BLEND_DESTINATION_COLOR; + case D3DBLEND_INVDESTCOLOR: return GPU_BLEND_ONE_MINUS_DESTINATION_COLOR; + case D3DBLEND_SRCALPHASAT: return GPU_BLEND_SOURCE_ALPHA_SATURATE; + case D3DBLEND_CONSTANTCOLOR: return GPU_BLEND_CONSTANT_COLOR; + case D3DBLEND_INVCONSTANTCOLOR: return GPU_BLEND_ONE_MINUS_CONSTANT_COLOR; + case D3DBLEND_CONSTANTALPHA: return GPU_BLEND_CONSTANT_ALPHA; + case D3DBLEND_INVCONSTANTALPHA: return GPU_BLEND_ONE_MINUS_CONSTANT_ALPHA; + default: return GPU_BLEND_ONE; } +} - state_enable(&gl_state.stencil_test, GL_STENCIL_TEST, has_depth && rs[D3DRS_STENCILENABLE]); - if (has_depth && rs[D3DRS_STENCILENABLE]) +static unsigned char blend_operation(DWORD operation) +{ + switch (operation) { - GLenum function = rs[D3DRS_STENCILFUNC] ? (GLenum)rs[D3DRS_STENCILFUNC] : GL_NEVER; - GLenum operations[3]; - - if (gl_state.stencil_function != function || gl_state.stencil_reference != (GLint)rs[D3DRS_STENCILREF] || - gl_state.stencil_value_mask != rs[D3DRS_STENCILMASK]) - { - gl_state.stencil_function = function; - gl_state.stencil_reference = (GLint)rs[D3DRS_STENCILREF]; - gl_state.stencil_value_mask = rs[D3DRS_STENCILMASK]; - glStencilFunc(function, (GLint)rs[D3DRS_STENCILREF], rs[D3DRS_STENCILMASK]); - } - operations[0] = stencil_operation(rs[D3DRS_STENCILFAIL]); - operations[1] = stencil_operation(rs[D3DRS_STENCILZFAIL]); - operations[2] = stencil_operation(rs[D3DRS_STENCILPASS]); - if (memcmp(gl_state.stencil_operations, operations, sizeof(operations))) - { - memcpy(gl_state.stencil_operations, operations, sizeof(operations)); - glStencilOp(operations[0], operations[1], operations[2]); - } - if (gl_state.stencil_write_mask != rs[D3DRS_STENCILWRITEMASK]) - { - gl_state.stencil_write_mask = rs[D3DRS_STENCILWRITEMASK]; - glStencilMask(rs[D3DRS_STENCILWRITEMASK]); - } + case D3DBLENDOP_SUBTRACT: return GPU_BLEND_OP_SUBTRACT; + case D3DBLENDOP_REVSUBTRACT: + case D3DBLENDOP_REVSUBTRACTSIGNED: return GPU_BLEND_OP_REVERSE_SUBTRACT; + case D3DBLENDOP_MIN: return GPU_BLEND_OP_MIN; + case D3DBLENDOP_MAX: return GPU_BLEND_OP_MAX; + default: return GPU_BLEND_OP_ADD; } +} - state_enable(&gl_state.blend, GL_BLEND, rs[D3DRS_ALPHABLENDENABLE] != 0); - if (rs[D3DRS_ALPHABLENDENABLE]) - { - GLenum equation = blend_equation(rs[D3DRS_BLENDOP]); - float blend_color[4]; - - if (gl_state.blend_source != (GLenum)rs[D3DRS_SRCBLEND] || - gl_state.blend_destination != (GLenum)rs[D3DRS_DESTBLEND]) - { - gl_state.blend_source = (GLenum)rs[D3DRS_SRCBLEND]; - gl_state.blend_destination = (GLenum)rs[D3DRS_DESTBLEND]; - glBlendFunc(gl_state.blend_source, gl_state.blend_destination); - } - if (gl_state.blend_equation != equation) - { - gl_state.blend_equation = equation; - glBlendEquation(equation); - } - color_to_vec4(rs[D3DRS_BLENDCOLOR], blend_color); - if (memcmp(gl_state.blend_color, blend_color, sizeof(blend_color))) - { - memcpy(gl_state.blend_color, blend_color, sizeof(blend_color)); - glBlendColor(blend_color[0], blend_color[1], blend_color[2], blend_color[3]); - } - } - color_mask = (unsigned char)(((write & D3DCOLORWRITEENABLE_RED) ? 1 : 0) | ((write & D3DCOLORWRITEENABLE_GREEN) ? 2 : 0) | - ((write & D3DCOLORWRITEENABLE_BLUE) ? 4 : 0) | ((write & D3DCOLORWRITEENABLE_ALPHA) ? 8 : 0)); - if (gl_state.color_mask != color_mask) - { - gl_state.color_mask = color_mask; - glColorMask((color_mask & 1) != 0, (color_mask & 2) != 0, (color_mask & 4) != 0, (color_mask & 8) != 0); - } +/* the draw's viewport, depth, stencil, blending and rasterization */ +static void raster_state(struct gpu_draw *draw, BOOL has_depth) +{ + DWORD *rs = D3D__RenderState; + DWORD write = rs[D3DRS_COLORWRITEENABLE]; + struct gpu_depth_stencil_state *depth_stencil = &draw->depth_stencil; + struct gpu_blend_state *blend = &draw->blend; + struct gpu_raster_state *raster = &draw->raster; + struct gpu_rect *viewport = &draw->viewport.rect; + + viewport->x = target_pixel((float)device.viewport.X, 0); + viewport->y = target_pixel((float)device.viewport.Y, 1); + viewport->width = target_pixel((float)(device.viewport.X + device.viewport.Width), 0) - viewport->x; + viewport->height = target_pixel((float)(device.viewport.Y + device.viewport.Height), 1) - viewport->y; + /* the game never issues a scissor rectangle, and the NV2A scissor register + defaults to the viewport, so fragment clipping follows the viewport: this is + what keeps a split-screen window's geometry from bleeding across the divider */ + draw->scissor = *viewport; + draw->viewport.min_z = device.viewport.MinZ; + draw->viewport.max_z = device.viewport.MaxZ; + + depth_stencil->depth_test = has_depth && rs[D3DRS_ZENABLE]; + depth_stencil->depth_function = compare_function(rs[D3DRS_ZFUNC]); + depth_stencil->depth_write = rs[D3DRS_ZWRITEENABLE] != 0; + depth_stencil->stencil_test = has_depth && rs[D3DRS_STENCILENABLE]; + depth_stencil->stencil_function = compare_function(rs[D3DRS_STENCILFUNC]); + depth_stencil->stencil_reference = (uint32_t)rs[D3DRS_STENCILREF]; + depth_stencil->stencil_read_mask = (uint32_t)rs[D3DRS_STENCILMASK]; + depth_stencil->stencil_write_mask = (uint32_t)rs[D3DRS_STENCILWRITEMASK]; + depth_stencil->stencil_fail = stencil_operation(rs[D3DRS_STENCILFAIL]); + depth_stencil->stencil_depth_fail = stencil_operation(rs[D3DRS_STENCILZFAIL]); + depth_stencil->stencil_pass = stencil_operation(rs[D3DRS_STENCILPASS]); + + blend->enable = rs[D3DRS_ALPHABLENDENABLE] != 0; + blend->source = blend_factor(rs[D3DRS_SRCBLEND]); + blend->destination = blend_factor(rs[D3DRS_DESTBLEND]); + blend->operation = blend_operation(rs[D3DRS_BLENDOP]); + blend->color = (uint32_t)rs[D3DRS_BLENDCOLOR]; + blend->color_write_mask = (uint8_t)(((write & D3DCOLORWRITEENABLE_RED) ? 1 : 0) | + ((write & D3DCOLORWRITEENABLE_GREEN) ? 2 : 0) | ((write & D3DCOLORWRITEENABLE_BLUE) ? 4 : 0) | + ((write & D3DCOLORWRITEENABLE_ALPHA) ? 8 : 0)); /* the cull mode names the winding to discard; FRONTFACE names the front winding */ - state_enable(&gl_state.cull_face, GL_CULL_FACE, rs[D3DRS_CULLMODE] != D3DCULL_NONE); - if (rs[D3DRS_CULLMODE] != D3DCULL_NONE) - { -#ifdef HALO_ANDROID - /* the vertex shader flips y in clip space, which (unlike desktop - GL's upper-left clip origin) also flips the winding */ - GLenum front_face = rs[D3DRS_FRONTFACE] == D3DFRONT_CCW ? GL_CW : GL_CCW; -#else - GLenum front_face = rs[D3DRS_FRONTFACE] == D3DFRONT_CCW ? GL_CCW : GL_CW; -#endif - GLenum cull_mode = rs[D3DRS_CULLMODE] == rs[D3DRS_FRONTFACE] ? GL_FRONT : GL_BACK; - - if (gl_state.front_face != front_face) - { - gl_state.front_face = front_face; - glFrontFace(front_face); - } - if (gl_state.cull_mode != cull_mode) - { - gl_state.cull_mode = cull_mode; - glCullFace(cull_mode); - } - } -#ifndef HALO_ANDROID - /* ES draws filled polygons only (wireframe is a debug mode) */ - { - GLenum polygon_mode = rs[D3DRS_FILLMODE] == D3DFILL_WIREFRAME ? GL_LINE : - rs[D3DRS_FILLMODE] == D3DFILL_POINT ? GL_POINT : GL_FILL; - - if (gl_state.polygon_mode != polygon_mode) - { - gl_state.polygon_mode = polygon_mode; - glPolygonMode(GL_FRONT_AND_BACK, polygon_mode); - } - } -#endif - + raster->cull_mode = rs[D3DRS_CULLMODE] == D3DCULL_NONE ? GPU_CULL_NONE : + rs[D3DRS_CULLMODE] == rs[D3DRS_FRONTFACE] ? GPU_CULL_FRONT : GPU_CULL_BACK; + raster->front_face = rs[D3DRS_FRONTFACE] == D3DFRONT_CCW ? GPU_FRONT_COUNTER_CLOCKWISE : GPU_FRONT_CLOCKWISE; + raster->fill_mode = rs[D3DRS_FILLMODE] == D3DFILL_WIREFRAME ? GPU_FILL_LINE : + rs[D3DRS_FILLMODE] == D3DFILL_POINT ? GPU_FILL_POINT : GPU_FILL_SOLID; /* D3DRS_ZBIAS is expressed in these states (D3DDevice_SetRenderState_ZBias) */ - state_enable(&gl_state.offset_fill, GL_POLYGON_OFFSET_FILL, rs[D3DRS_SOLIDOFFSETENABLE] != 0); -#ifndef HALO_ANDROID - state_enable(&gl_state.offset_line, GL_POLYGON_OFFSET_LINE, rs[D3DRS_SOLIDOFFSETENABLE] != 0); -#endif - if (rs[D3DRS_SOLIDOFFSETENABLE]) - { - float offset[2]; - - offset[0] = dword_to_float(rs[D3DRS_POLYGONOFFSETZSLOPESCALE]); - offset[1] = dword_to_float(rs[D3DRS_POLYGONOFFSETZOFFSET]); - if (memcmp(gl_state.polygon_offset, offset, sizeof(offset))) - { - memcpy(gl_state.polygon_offset, offset, sizeof(offset)); - glPolygonOffset(offset[0], offset[1]); - } - } + raster->depth_bias = rs[D3DRS_SOLIDOFFSETENABLE] != 0; + raster->depth_bias_slope = dword_to_float(rs[D3DRS_POLYGONOFFSETZSLOPESCALE]); + raster->depth_bias_constant = dword_to_float(rs[D3DRS_POLYGONOFFSETZOFFSET]); } -#ifdef HALO_ANDROID -/* ES has no debug callback in 3.0; debug.gl_debug polls glGetError around -each draw instead, reporting each distinct error a few times */ -static void gl_check_errors(const char *where) -{ - static int enabled = -1; - static unsigned long reports; - GLenum error; - - if (enabled < 0) - enabled = config_boolean("debug.gl_debug"); - if (!enabled) - return; - while ((error = glGetError()) != GL_NO_ERROR) - { - if (reports++ < 200) - platform_log("GL error %04x at %s (frame %lu)", (unsigned)error, where, device.frame); - } -} -#else -#define gl_check_errors(where) ((void)0) -#endif - /* the uniforms of the latest draws, converted from these inputs; the serial counts the conversions */ #define DRAW_UNIFORM_INPUT_COUNT (4 + 4 + 16 + 1 + 16 + 2 + 4 + 1 + 1 + 7 * D3DTSS_MAXSTAGES) static DWORD draw_uniform_inputs[DRAW_UNIFORM_INPUT_COUNT]; -static struct draw_uniforms draw_uniforms; -static unsigned long draw_uniforms_serial; - -/* sets a program's uniform unless it already holds value */ -static void uniform_vec4(GLint location, float *shadow, const float *value, int count) -{ - if (location < 0 || !memcmp(shadow, value, (size_t)count * 4 * sizeof(float))) - return; - memcpy(shadow, value, (size_t)count * 4 * sizeof(float)); - glUniform4fv(location, count, value); -} - -static void uniform_float(GLint location, float *shadow, float value) -{ - if (location < 0 || !memcmp(shadow, &value, sizeof(value))) - return; - *shadow = value; - glUniform1f(location, value); -} - -/* Intel's graphics with Mesa's driver can hang the GPU in a long run of -draws with no pipeline flush between them, which the game's effects make -(hundreds of small draws in a row): the command streamer stops at a draw, -and the reset that follows takes the desktop's other programs with it. -Intel's workaround for a hang of this kind on their DG2 graphics -(Wa_16014538804) is a flush at least every 3 draws, which Mesa does not -apply to the others. A memory barrier is one (and only that: nothing -here writes images). */ -static void draw_flush(void) -{ -#ifndef HALO_ANDROID - if (device.flush_every && ++device.flush_draws >= device.flush_every) - { - device.flush_draws = 0; - glMemoryBarrier(GL_SHADER_IMAGE_ACCESS_BARRIER_BIT); - } -#endif -} +static struct gpu_uniforms draw_uniforms; /* display.per_pixel_lighting: the model lighting programs' draws are lit for each pixel (nv2a_psh.c model_lighting), from shaders of their own; read @@ -2626,26 +2200,30 @@ static BOOL per_pixel_lighting(void) return enabled; } -/* the vertex constant register of each of the per-pixel lighting's -(XGPU_MODEL_LIGHT_COUNT) */ -static unsigned long model_light_register(int light) +/* the draw prepare_draw made, for submit_draw: its program, whether it is +lit for each pixel, and its pixel shader's key */ +static struct { - return (unsigned long)(XGPU_VERTEX_CONSTANT_BIAS + (light ? -80 + light : -82)); -} + struct vertex_shader_object *program; + BOOL immediate; + BOOL lit; + struct nv2a_pixel_shader_key key; +} draw_shaders; -static struct program_entry *prepare_draw(BOOL immediate) +/* the draw's targets, state, shaders, texture stages and uniforms: all of it +but its vertices; FALSE if it is not drawn */ +static BOOL prepare_draw(struct gpu_draw *draw, BOOL immediate) { struct vertex_shader_object *program = current_program(); - struct nv2a_pixel_shader_key key; - struct program_entry *entry; - struct draw_uniforms uniforms; + struct nv2a_pixel_shader_key *key = &draw_shaders.key; + float texture_scale[4][4]; BOOL has_depth = FALSE; int stage; if (!device.gl_ready || !program || !device.vertex_shader || !program->instructions) { stats.skipped_no_program++; - return NULL; + return FALSE; } { const char *skip = debug_settings.skip_vertex_shaders; @@ -2653,161 +2231,56 @@ static struct program_entry *prepare_draw(BOOL immediate) while (skip && *skip) { if ((unsigned long)atol(skip) == program->id) - return NULL; + return FALSE; skip = strchr(skip, ','); if (skip) skip++; } } - memset(&key, 0, sizeof(key)); - memcpy(key.combiner_state, D3D__RenderState, sizeof(key.combiner_state)); + memset(draw, 0, sizeof(*draw)); + memset(key, 0, sizeof(*key)); + memcpy(key->combiner_state, D3D__RenderState, sizeof(key->combiner_state)); /* constants are uniforms, not part of the program */ - memset(&key.combiner_state[D3DRS_PSCONSTANT0_0], 0, 16 * sizeof(DWORD)); - key.combiner_state[D3DRS_PSFINALCOMBINERCONSTANT0] = 0; - key.combiner_state[D3DRS_PSFINALCOMBINERCONSTANT1] = 0; - key.texture_modes = D3D__RenderState[D3DRS_PSTEXTUREMODES]; - /* the textures before the targets: a render target the draw samples - has its multisampled pixels resolved by a blit (gpu_gl_resolve), - which binds framebuffers of its own, and the back buffer can be both - sampled and drawn into */ - bind_textures(&key, uniforms.texture_scale); - if (!bind_targets(&has_depth)) + memset(&key->combiner_state[D3DRS_PSCONSTANT0_0], 0, 16 * sizeof(DWORD)); + key->combiner_state[D3DRS_PSFINALCOMBINERCONSTANT0] = 0; + key->combiner_state[D3DRS_PSFINALCOMBINERCONSTANT1] = 0; + key->texture_modes = D3D__RenderState[D3DRS_PSTEXTUREMODES]; + bind_textures(key, texture_scale, draw->stages); + if (!bind_targets(&draw->color_target, &draw->depth_target, &draw->samples, &has_depth)) { stats.skipped_no_target++; - return NULL; + return FALSE; } - apply_raster_state(has_depth); + raster_state(draw, has_depth); for (stage = 0; stage < D3DTSS_MAXSTAGES; stage++) { - key.alpha_kill[stage] = D3D__TextureState[stage][D3DTSS_ALPHAKILL] == D3DTALPHAKILL_ENABLE; - key.color_sign[stage] = (unsigned char)((D3D__TextureState[stage][D3DTSS_COLORSIGN] >> 28) & 0xf); + key->alpha_kill[stage] = D3D__TextureState[stage][D3DTSS_ALPHAKILL] == D3DTALPHAKILL_ENABLE; + key->color_sign[stage] = (unsigned char)((D3D__TextureState[stage][D3DTSS_COLORSIGN] >> 28) & 0xf); } /* (only with the meter's blend: hud_hires.h, nv2a_pixel_shader_key) */ - key.coverage_alpha = key.coverage_alpha && D3D__RenderState[D3DRS_ALPHABLENDENABLE] && + key->coverage_alpha = key->coverage_alpha && D3D__RenderState[D3DRS_ALPHABLENDENABLE] && D3D__RenderState[D3DRS_SRCBLEND] == D3DBLEND_CONSTANTCOLOR && D3D__RenderState[D3DRS_DESTBLEND] == D3DBLEND_SRCALPHA; - key.point_threshold = key.point_threshold && key.coverage_alpha; - key.alpha_test_function = D3D__RenderState[D3DRS_ALPHATESTENABLE] ? D3D__RenderState[D3DRS_ALPHAFUNC] : 0; -#ifndef HALO_ANDROID + key->point_threshold = key->point_threshold && key->coverage_alpha; + key->alpha_test_function = D3D__RenderState[D3DRS_ALPHATESTENABLE] ? D3D__RenderState[D3DRS_ALPHAFUNC] : 0; /* (gl_SampleMask: ES has it only from 3.2) */ - if (target_samples > 1 && key.alpha_test_function && !D3D__RenderState[D3DRS_ALPHABLENDENABLE]) - key.alpha_test_samples = (unsigned char)target_samples; -#endif - key.fog_enable = D3D__RenderState[D3DRS_FOGENABLE] != 0; - key.fog_table_mode = (unsigned char)D3D__RenderState[D3DRS_FOGTABLEMODE]; -#ifdef HALO_ANDROID - key.count_samples = device.visibility_test_active && xgpu_capabilities.atomic_counters; -#endif - - entry = NULL; - if (program->lighting.lights && !program->lighting_failed && per_pixel_lighting()) + if (xgpu_gpu_capabilities.sample_mask && target_samples > 1 && key->alpha_test_function && + !D3D__RenderState[D3DRS_ALPHABLENDENABLE]) { - key.per_pixel_lighting = (unsigned char)program->lighting.lights; - entry = program_get(vertex_shader_get(program, immediate, TRUE), fragment_shader_get(&key)); - if (!entry) - { - /* drawn as the vertex shader lights it instead (not at all, - were it to fail too) */ - platform_log("GPU: vertex shader %lu cannot be lit for each pixel here (refer to the shader log above): " - "lit for each vertex", program->id); - program->lighting_failed = TRUE; - key.per_pixel_lighting = 0; - } - } - if (!entry) - entry = program_get(vertex_shader_get(program, immediate, FALSE), fragment_shader_get(&key)); - if (!entry) - { - stats.skipped_link++; - gl_check_errors("program"); - return NULL; + key->alpha_test_samples = (unsigned char)target_samples; } - gl_check_errors("state"); - if (immediate) - stats.immediate_draws++; - else - stats.draws++; - draw_flush(); - state_program(entry->program); -#ifdef HALO_ANDROID - if (key.count_samples) - glBindBufferRange(GL_ATOMIC_COUNTER_BUFFER, 0, device.visibility_counters, - (GLintptr)(device.counter_active * sizeof(GLuint)), sizeof(GLuint)); -#endif + key->fog_enable = D3D__RenderState[D3DRS_FOGENABLE] != 0; + key->fog_table_mode = (unsigned char)D3D__RenderState[D3DRS_FOGTABLEMODE]; + key->count_samples = device.visibility_test_active && + xgpu_gpu_capabilities.occlusion == GPU_OCCLUSION_SHADER_COUNTER; - if (entry->constants >= 0 && entry->constants_serial != constants_serial) - { - unsigned long first = entry->constant_count, last = 0, index; - - if (entry->constants_serial == constants_checkpoint_serial && - constants_checkpoint_last < entry->constant_count) - { - if (constants_checkpoint_first <= constants_checkpoint_last) - { - first = constants_checkpoint_first; - last = constants_checkpoint_last; - } - } - else if (constants_serial - entry->constants_serial <= XGPU_VERTEX_CONSTANT_COUNT) - { - unsigned long long serial; - - for (serial = entry->constants_serial + 1; serial <= constants_serial; serial++) - { - index = constant_log[serial % CONSTANT_LOG_SIZE]; - if (index >= entry->constant_count) - continue; - if (first > index) - first = index; - if (last < index) - last = index; - } - } - else - { - for (index = 0; index < entry->constant_count; index++) - { - if (constant_serials[index] > entry->constants_serial) - { - if (first > index) - first = index; - last = index; - } - } - } - if (first < entry->constant_count) - { - if (entry->constants_consecutive) - glUniform4fv(entry->constants + (GLint)first, (GLsizei)(last - first + 1), device.constants[first]); - else - glUniform4fv(entry->constants, XGPU_VERTEX_CONSTANT_COUNT, &device.constants[0][0]); - } - entry->constants_serial = constants_serial; - constants_checkpoint_serial = constants_serial; - constants_checkpoint_first = XGPU_VERTEX_CONSTANT_COUNT; - constants_checkpoint_last = 0; - } - /* the lights of a draw lit for each pixel, from the same registers, when - any of them changed since the program last had them */ - if (entry->model_lights >= 0 && entry->model_lights_serial != constants_serial) - { - int light; - - for (light = 0; light < XGPU_MODEL_LIGHT_COUNT; light++) - { - if (constant_serials[model_light_register(light)] > entry->model_lights_serial) - break; - } - if (light < XGPU_MODEL_LIGHT_COUNT) - { - float lights[XGPU_MODEL_LIGHT_COUNT][4]; - - for (light = 0; light < XGPU_MODEL_LIGHT_COUNT; light++) - memcpy(lights[light], device.constants[model_light_register(light)], sizeof(lights[0])); - glUniform4fv(entry->model_lights, XGPU_MODEL_LIGHT_COUNT, lights[0]); - } - entry->model_lights_serial = constants_serial; - } + draw_shaders.program = program; + draw_shaders.immediate = immediate; + draw_shaders.lit = program->lighting.lights && !program->lighting_failed && per_pixel_lighting(); + if (draw_shaders.lit) + key->per_pixel_lighting = (unsigned char)program->lighting.lights; + draw->vertex_shader = vertex_shader_get(program, immediate, draw_shaders.lit); + draw->pixel_shader = fragment_shader_get(key); /* the state the other uniforms come from: most draws share it with the draw before them, and so share its uniforms */ @@ -2819,7 +2292,7 @@ static struct program_entry *prepare_draw(BOOL immediate) count += 4; memcpy(&inputs[count], device.viewport_offset, sizeof(device.viewport_offset)); count += 4; - memcpy(&inputs[count], uniforms.texture_scale, sizeof(uniforms.texture_scale)); + memcpy(&inputs[count], texture_scale, sizeof(texture_scale)); count += 16; inputs[count++] = D3D__RenderState[D3DRS_POINTSIZE]; for (stage = 0; stage < 8; stage++) @@ -2847,15 +2320,15 @@ static struct program_entry *prepare_draw(BOOL immediate) inputs[count++] = state[D3DTSS_BUMPENVLOFFSET]; inputs[count++] = state[D3DTSS_MIPMAPLODBIAS]; } - if (!draw_uniforms_serial || memcmp(inputs, draw_uniform_inputs, sizeof(inputs))) + if (!draw_uniforms.serial || memcmp(inputs, draw_uniform_inputs, sizeof(inputs))) { - struct draw_uniforms *converted = &draw_uniforms; + struct gpu_uniforms *converted = &draw_uniforms; memcpy(draw_uniform_inputs, inputs, sizeof(inputs)); - draw_uniforms_serial++; + converted->serial++; memcpy(converted->viewport_scale, device.viewport_scale, sizeof(converted->viewport_scale)); memcpy(converted->viewport_offset, device.viewport_offset, sizeof(converted->viewport_offset)); - memcpy(converted->texture_scale, uniforms.texture_scale, sizeof(converted->texture_scale)); + memcpy(converted->texture_scale, texture_scale, sizeof(converted->texture_scale)); converted->point_size = D3D__RenderState[D3DRS_POINTSIZE] ? dword_to_float(D3D__RenderState[D3DRS_POINTSIZE]) : 1.0f; for (stage = 0; stage < 8; stage++) @@ -2887,26 +2360,38 @@ static struct program_entry *prepare_draw(BOOL immediate) converted->screen_offset = (float)UI_OFFSET; } } - /* and a program that has had them since needs none of them */ - if (entry->uniforms_serial == draw_uniforms_serial) - return entry; - entry->uniforms_serial = draw_uniforms_serial; - uniform_vec4(entry->viewport_scale, entry->uniforms.viewport_scale, draw_uniforms.viewport_scale, 1); - uniform_vec4(entry->viewport_offset, entry->uniforms.viewport_offset, draw_uniforms.viewport_offset, 1); - uniform_float(entry->point_size, &entry->uniforms.point_size, draw_uniforms.point_size); - uniform_vec4(entry->ps_c0, entry->uniforms.ps_c0[0], draw_uniforms.ps_c0[0], 8); - uniform_vec4(entry->ps_c1, entry->uniforms.ps_c1[0], draw_uniforms.ps_c1[0], 8); - uniform_vec4(entry->ps_final_c0, entry->uniforms.ps_final_c0, draw_uniforms.ps_final_c0, 1); - uniform_vec4(entry->ps_final_c1, entry->uniforms.ps_final_c1, draw_uniforms.ps_final_c1, 1); - uniform_vec4(entry->fog_color, entry->uniforms.fog_color, draw_uniforms.fog_color, 1); - uniform_vec4(entry->fog_parameters, entry->uniforms.fog_parameters, draw_uniforms.fog_parameters, 1); - uniform_float(entry->alpha_reference, &entry->uniforms.alpha_reference, draw_uniforms.alpha_reference); - uniform_vec4(entry->bump_matrix, entry->uniforms.bump_matrix[0], draw_uniforms.bump_matrix[0], 4); - uniform_vec4(entry->bump_luminance, entry->uniforms.bump_luminance[0], draw_uniforms.bump_luminance[0], 4); - uniform_vec4(entry->texture_scale, entry->uniforms.texture_scale[0], draw_uniforms.texture_scale[0], 4); - uniform_float(entry->screen_offset, &entry->uniforms.screen_offset, draw_uniforms.screen_offset); - uniform_vec4(entry->texture_lod_bias, entry->uniforms.texture_lod_bias, draw_uniforms.texture_lod_bias, 1); - return entry; + return TRUE; +} + +/* draws what prepare_draw and the vertex setup made; a draw whose shaders +lit for each pixel do not link is drawn lit as the vertex shader lights it +(and not at all, were that to fail too) */ +static void submit_draw(struct gpu_draw *draw) +{ + BOOL drawn; + +#ifdef HALO_ANDROID + if (draw_shaders.key.count_samples) + glBindBufferRange(GL_ATOMIC_COUNTER_BUFFER, 0, device.visibility_counters, + (GLintptr)(device.counter_active * sizeof(GLuint)), sizeof(GLuint)); +#endif + drawn = gpu_draw(draw, &vertex_constants, &draw_uniforms) != 0; + if (!drawn && draw_shaders.lit) + { + platform_log("GPU: vertex shader %lu cannot be lit for each pixel here (refer to the shader log above): " + "lit for each vertex", draw_shaders.program->id); + draw_shaders.program->lighting_failed = TRUE; + draw_shaders.key.per_pixel_lighting = 0; + draw->vertex_shader = vertex_shader_get(draw_shaders.program, draw_shaders.immediate, FALSE); + draw->pixel_shader = fragment_shader_get(&draw_shaders.key); + drawn = gpu_draw(draw, &vertex_constants, &draw_uniforms) != 0; + } + if (!drawn) + stats.skipped_link++; + else if (draw_shaders.immediate) + stats.immediate_draws++; + else + stats.draws++; } /* ---------- tracing (debug.gpu_trace_frame) */ @@ -2965,7 +2450,7 @@ static void trace_draw(const char *kind, D3DPRIMITIVETYPE type, unsigned long co for (constant = 0; constant < XGPU_VERTEX_CONSTANT_COUNT; constant++) { - const float *value = device.constants[constant]; + const float *value = vertex_constants.c[constant]; if (value[0] || value[1] || value[2] || value[3]) platform_log(" c[%d] = %g %g %g %g", constant, value[0], value[1], value[2], value[3]); @@ -3005,36 +2490,7 @@ static void trace_draw(const char *kind, D3DPRIMITIVETYPE type, unsigned long co } -/* Mesa's GL thread queues a glBufferSubData of up to 8 KB; a larger one -first waits for everything queued before it to have run. The same bytes in -pieces are queued (all but the largest, as a map loads, which would be -thousands; Android's GL has no such thread). */ -#define BUFFER_UPLOAD_PIECE 4096 -#define BUFFER_UPLOAD_PIECES_MAXIMUM 16 - -static void buffer_upload(GLenum target, unsigned long offset, unsigned long size, const void *data) -{ -#ifndef HALO_ANDROID - if (size <= BUFFER_UPLOAD_PIECE * BUFFER_UPLOAD_PIECES_MAXIMUM) - { - const unsigned char *bytes = data; - - while (size) - { - unsigned long piece = size < BUFFER_UPLOAD_PIECE ? size : BUFFER_UPLOAD_PIECE; - - glBufferSubData(target, (GLintptr)offset, (GLsizeiptr)piece, bytes); - offset += piece; - bytes += piece; - size -= piece; - } - return; - } -#endif - glBufferSubData(target, (GLintptr)offset, (GLsizeiptr)size, data); -} - -/* ---------- the contiguous window in GL buffers +/* ---------- the contiguous window in GPU buffers Vertex and index buffers live in the Xbox's contiguous memory, where most never change once loaded. The mirror keeps a copy of that memory in GL @@ -3065,7 +2521,7 @@ enum static struct { - GLuint buffers[MIRROR_SEGMENT_COUNT]; + gpu_buffer buffers[MIRROR_SEGMENT_COUNT]; unsigned char state[MIRROR_PAGE_COUNT]; unsigned char rewrites[MIRROR_PAGE_COUNT]; /* the page's memory_watch generation when it was uploaded */ @@ -3150,28 +2606,10 @@ static BOOL mirror_refresh(unsigned long first, unsigned long last) mirror.state[page] = _mirror_page_present; } if (!mirror.buffers[segment]) - { - glGenBuffers(1, &mirror.buffers[segment]); - glBindBuffer(GL_COPY_WRITE_BUFFER, mirror.buffers[segment]); - glBufferData(GL_COPY_WRITE_BUFFER, MIRROR_SEGMENT_SIZE, NULL, GL_DYNAMIC_DRAW); - } - glBindBuffer(GL_COPY_WRITE_BUFFER, mirror.buffers[segment]); -#ifdef HALO_ANDROID - /* Mali copies the whole buffer for a glBufferSubData that queued - draws might read (see STREAM_BUFFER_RING); unused pages can be - written without waiting for them */ - if (unused) - { - host_gl_buffer_write(GL_COPY_WRITE_BUFFER, - (unsigned int)(address - PLATFORM_CONTIGUOUS_BASE - segment * MIRROR_SEGMENT_SIZE), - (unsigned int)size, (const void *)address); - continue; - } -#else - (void)unused; -#endif - buffer_upload(GL_COPY_WRITE_BUFFER, address - PLATFORM_CONTIGUOUS_BASE - segment * MIRROR_SEGMENT_SIZE, size, - (const void *)address); + mirror.buffers[segment] = gpu_buffer_create(MIRROR_SEGMENT_SIZE); + gpu_buffer_write(mirror.buffers[segment], + (uint32_t)(address - PLATFORM_CONTIGUOUS_BASE - segment * MIRROR_SEGMENT_SIZE), (uint32_t)size, + (const void *)address, unused ? GPU_WRITE_UNUSED : 0); } return TRUE; } @@ -3180,7 +2618,7 @@ static BOOL mirror_refresh(unsigned long first, unsigned long last) that holds it, the range's offset in that buffer and the newest upload generation of its pages (which changes whenever its contents do); FALSE if the range is outside the window, spans two segments or is volatile */ -static BOOL mirror_range(unsigned long address, unsigned long size, GLuint *buffer, unsigned long *offset, +static BOOL mirror_range(unsigned long address, unsigned long size, gpu_buffer *buffer, unsigned long *offset, unsigned long *generation) { unsigned long start = address - PLATFORM_CONTIGUOUS_BASE; @@ -3279,58 +2717,28 @@ static void index_extent(const WORD *indices, unsigned long count, unsigned long /* ---------- vertex data */ -/* makes room for size bytes of uploads, orphaning the stream buffer if it -is full. A draw reserves room for all of its streams at once: orphaning -between two of them would leave the attributes already pointed at the -buffer reading its new, empty storage. */ -static void stream_reserve(unsigned long size) -{ - if (device.stream_offset + size > STREAM_BUFFER_SIZE) - { - /* orphan the buffer and start again */ - state_array_buffer(device.stream_buffer); - glBufferData(GL_ARRAY_BUFFER, STREAM_BUFFER_SIZE, NULL, GL_STREAM_DRAW); - device.stream_offset = 0; - } -} - -static unsigned long stream_upload(const void *data, unsigned long size) -{ - unsigned long offset; - - size = (size + 15) & ~15UL; - stream_reserve(size); - offset = device.stream_offset; - state_array_buffer(device.stream_buffer); -#ifdef HALO_ANDROID - host_gl_buffer_write(GL_ARRAY_BUFFER, (unsigned int)offset, (unsigned int)size, data); -#else - buffer_upload(GL_ARRAY_BUFFER, offset, size, data); -#endif - device.stream_offset += size; - return offset; -} - -#ifdef HALO_ANDROID -/* stream_upload, with the D3DCOLOR elements of the stream turned from BGRA -into the RGBA byte order ES reads */ -static unsigned long stream_upload_swizzled(const struct vertex_shader_object *declaration, unsigned long stream, - const unsigned char *data, unsigned long size, unsigned long stride) +/* streams the vertices, with the D3DCOLOR elements of the stream turned from +BGRA into the RGBA byte order a GPU without vertex_bgra reads (OpenGL ES) */ +static uint32_t stream_upload(const struct vertex_shader_object *declaration, unsigned long stream, + const unsigned char *data, unsigned long size, unsigned long stride, gpu_buffer *buffer) { static unsigned char *scratch; static unsigned long scratch_size; unsigned long offsets[XGPU_VERTEX_ATTRIBUTE_COUNT]; unsigned long count = 0, index, vertex; - for (index = 0; index < declaration->element_count; index++) + if (!xgpu_gpu_capabilities.vertex_bgra) { - const struct vertex_element *element = &declaration->elements[index]; + for (index = 0; index < declaration->element_count; index++) + { + const struct vertex_element *element = &declaration->elements[index]; - if (element->stream == stream && element->type == D3DVSDT_D3DCOLOR) - offsets[count++] = element->offset; + if (element->stream == stream && element->type == D3DVSDT_D3DCOLOR) + offsets[count++] = element->offset; + } } if (!count || !stride) - return stream_upload(data, size); + return gpu_stream(GPU_STREAM_VERTEX, data, (uint32_t)size, buffer); if (scratch_size < size) { free(scratch); @@ -3349,72 +2757,44 @@ static unsigned long stream_upload_swizzled(const struct vertex_shader_object *d color[2] = blue; } } - return stream_upload(scratch, size); + return gpu_stream(GPU_STREAM_VERTEX, scratch, (uint32_t)size, buffer); } -#endif -static unsigned long index_upload(const void *data, unsigned long size) +static unsigned char attribute_format(const struct vertex_element *element) { - unsigned long offset; - - size = (size + 15) & ~15UL; - state_element_array_buffer(device.index_buffer); - if (device.index_offset + size > INDEX_BUFFER_SIZE) - { - glBufferData(GL_ELEMENT_ARRAY_BUFFER, INDEX_BUFFER_SIZE, NULL, GL_STREAM_DRAW); - device.index_offset = 0; - } - offset = device.index_offset; -#ifdef HALO_ANDROID - host_gl_buffer_write(GL_ELEMENT_ARRAY_BUFFER, (unsigned int)offset, (unsigned int)size, data); -#else - buffer_upload(GL_ELEMENT_ARRAY_BUFFER, offset, size, data); -#endif - device.index_offset += size; - return offset; -} - -static void attribute_format(const struct vertex_element *element, GLint *size, GLenum *type, GLboolean *normalized) -{ - *normalized = GL_FALSE; switch (element->type) { - case D3DVSDT_FLOAT1: *size = 1; *type = GL_FLOAT; break; - case D3DVSDT_FLOAT2: *size = 2; *type = GL_FLOAT; break; - case D3DVSDT_FLOAT3: case D3DVSDT_FLOAT2H: *size = 3; *type = GL_FLOAT; break; - case D3DVSDT_FLOAT4: *size = 4; *type = GL_FLOAT; break; -#ifdef HALO_ANDROID - /* ES has no BGRA attributes: stream_upload_swizzled swaps the bytes */ - case D3DVSDT_D3DCOLOR: *size = 4; *type = GL_UNSIGNED_BYTE; *normalized = GL_TRUE; break; -#else - case D3DVSDT_D3DCOLOR: *size = GL_BGRA; *type = GL_UNSIGNED_BYTE; *normalized = GL_TRUE; break; -#endif - case D3DVSDT_SHORT1: *size = 1; *type = GL_SHORT; break; - case D3DVSDT_SHORT2: *size = 2; *type = GL_SHORT; break; - case D3DVSDT_SHORT3: *size = 3; *type = GL_SHORT; break; - case D3DVSDT_SHORT4: *size = 4; *type = GL_SHORT; break; - case D3DVSDT_NORMSHORT1: *size = 1; *type = GL_SHORT; *normalized = GL_TRUE; break; - case D3DVSDT_NORMSHORT2: *size = 2; *type = GL_SHORT; *normalized = GL_TRUE; break; - case D3DVSDT_NORMSHORT3: *size = 3; *type = GL_SHORT; *normalized = GL_TRUE; break; - case D3DVSDT_NORMSHORT4: *size = 4; *type = GL_SHORT; *normalized = GL_TRUE; break; - case D3DVSDT_PBYTE1: *size = 1; *type = GL_UNSIGNED_BYTE; *normalized = GL_TRUE; break; - case D3DVSDT_PBYTE2: *size = 2; *type = GL_UNSIGNED_BYTE; *normalized = GL_TRUE; break; - case D3DVSDT_PBYTE3: *size = 3; *type = GL_UNSIGNED_BYTE; *normalized = GL_TRUE; break; - case D3DVSDT_PBYTE4: *size = 4; *type = GL_UNSIGNED_BYTE; *normalized = GL_TRUE; break; - default: *size = 4; *type = GL_FLOAT; break; - } -} - -/* upload vertices [first, first + count) of every stream the declaration -uses and point the attributes at them; attribute data then starts at -vertex 0 of the uploaded range */ -#ifdef HALO_ANDROID -/* ES has no BGRA attributes, so a stream with colours is swizzled as it is -uploaded (stream_upload_swizzled) and cannot come from the mirror */ + case D3DVSDT_FLOAT1: return GPU_ATTRIBUTE_FLOAT1; + case D3DVSDT_FLOAT2: return GPU_ATTRIBUTE_FLOAT2; + case D3DVSDT_FLOAT3: case D3DVSDT_FLOAT2H: return GPU_ATTRIBUTE_FLOAT3; + case D3DVSDT_FLOAT4: return GPU_ATTRIBUTE_FLOAT4; + /* (without BGRA attributes, stream_upload swaps the bytes) */ + case D3DVSDT_D3DCOLOR: return xgpu_gpu_capabilities.vertex_bgra ? GPU_ATTRIBUTE_BGRA8 : GPU_ATTRIBUTE_RGBA8; + case D3DVSDT_SHORT1: return GPU_ATTRIBUTE_SHORT1; + case D3DVSDT_SHORT2: return GPU_ATTRIBUTE_SHORT2; + case D3DVSDT_SHORT3: return GPU_ATTRIBUTE_SHORT3; + case D3DVSDT_SHORT4: return GPU_ATTRIBUTE_SHORT4; + case D3DVSDT_NORMSHORT1: return GPU_ATTRIBUTE_NORMSHORT1; + case D3DVSDT_NORMSHORT2: return GPU_ATTRIBUTE_NORMSHORT2; + case D3DVSDT_NORMSHORT3: return GPU_ATTRIBUTE_NORMSHORT3; + case D3DVSDT_NORMSHORT4: return GPU_ATTRIBUTE_NORMSHORT4; + case D3DVSDT_NORMPACKED3: return GPU_ATTRIBUTE_NORMPACKED3; + case D3DVSDT_PBYTE1: return GPU_ATTRIBUTE_UBYTE1; + case D3DVSDT_PBYTE2: return GPU_ATTRIBUTE_UBYTE2; + case D3DVSDT_PBYTE3: return GPU_ATTRIBUTE_UBYTE3; + case D3DVSDT_PBYTE4: return GPU_ATTRIBUTE_UBYTE4; + default: return GPU_ATTRIBUTE_FLOAT4; + } +} + +/* a stream with colors is uploaded with its bytes swapped where the GPU +reads no BGRA attributes (stream_upload), and cannot come from the mirror */ static BOOL stream_has_colors(const struct vertex_shader_object *declaration, unsigned long stream) { unsigned long index; + if (xgpu_gpu_capabilities.vertex_bgra) + return FALSE; for (index = 0; index < declaration->element_count; index++) { if (declaration->elements[index].stream == stream && declaration->elements[index].type == D3DVSDT_D3DCOLOR) @@ -3422,24 +2802,19 @@ static BOOL stream_has_colors(const struct vertex_shader_object *declaration, un } return FALSE; } -#endif -static void setup_streams(unsigned long first, unsigned long count) +/* the draw's vertices [first, first + count) of every stream the +declaration uses, the attributes pointed at them (attribute data then +starts at vertex 0 of the range), and the value of each attribute that +reads none */ +static void setup_streams(struct gpu_draw *draw, unsigned long first, unsigned long count) { struct vertex_shader_object *declaration = device.vertex_shader; - GLuint stream_buffers[16]; - unsigned long stream_offsets[16]; BOOL placed[16] = { FALSE }; - BOOL enabled[XGPU_VERTEX_ATTRIBUTE_COUNT] = { FALSE }; unsigned long index, total = 0; -#ifndef HALO_ANDROID - struct vertex_layout layout; - unsigned long streams_used = 0; - - /* (zeroed: layouts are compared and hashed whole) */ - memset(&layout, 0, sizeof(layout)); -#endif + for (index = 0; index < XGPU_VERTEX_ATTRIBUTE_COUNT; index++) + draw->attributes[index].stream = GPU_STREAM_ZERO; /* the mirror first; then one reservation for everything streamed */ for (index = 0; index < declaration->element_count; index++) { @@ -3447,111 +2822,78 @@ static void setup_streams(unsigned long first, unsigned long count) unsigned long stream = element->stream; unsigned long stride = device.streams[stream].stride; unsigned long bytes = stride ? stride * count : 64; - unsigned long base; + unsigned long base, offset; if (!device.streams[stream].data || element->type == D3DVSDT_NONE || placed[stream]) continue; placed[stream] = TRUE; - stream_buffers[stream] = 0; + draw->streams[stream].buffer = 0; + draw->streams[stream].stride = (uint32_t)stride; base = (unsigned long)PLATFORM_PHYSICAL_TO_VIRTUAL(device.streams[stream].data) + first * stride; -#ifdef HALO_ANDROID - if (!stream_has_colors(declaration, stream)) -#endif - if (mirror_range(base, bytes, &stream_buffers[stream], &stream_offsets[stream], NULL)) + if (!stream_has_colors(declaration, stream) && + mirror_range(base, bytes, &draw->streams[stream].buffer, &offset, NULL)) + { + draw->streams[stream].offset = (uint32_t)offset; continue; - stream_buffers[stream] = 0; + } + draw->streams[stream].buffer = 0; total += (bytes + 15) & ~15UL; } - stream_reserve(total); + gpu_stream_reserve((uint32_t)total); for (index = 0; index < declaration->element_count; index++) { const struct vertex_element *element = &declaration->elements[index]; unsigned long stream = element->stream; unsigned long stride = device.streams[stream].stride; - GLint size; - GLenum type; - GLboolean normalized; + struct gpu_vertex_attribute *attribute = &draw->attributes[element->reg]; if (!device.streams[stream].data || element->type == D3DVSDT_NONE) continue; - if (!stream_buffers[stream]) + if (!draw->streams[stream].buffer) { const unsigned char *base = PLATFORM_PHYSICAL_TO_VIRTUAL(device.streams[stream].data); unsigned long bytes = stride ? stride * count : 64; -#ifdef HALO_ANDROID - stream_offsets[stream] = stream_upload_swizzled(declaration, stream, base + first * stride, bytes, stride); -#else - stream_offsets[stream] = stream_upload(base + first * stride, bytes); -#endif - stream_buffers[stream] = device.stream_buffer; + draw->streams[stream].offset = stream_upload(declaration, stream, base + first * stride, bytes, stride, + &draw->streams[stream].buffer); stats.streamed_bytes += bytes; } -#ifdef HALO_ANDROID - if (element->type == D3DVSDT_NORMPACKED3) - { - state_attribute_stream(element->reg, (GLuint)stream, stream_buffers[stream], 1, GL_UNSIGNED_INT, GL_FALSE, - TRUE, (GLsizei)stride, stream_offsets[stream], element->offset); - } - else - { - attribute_format(element, &size, &type, &normalized); - state_attribute_stream(element->reg, (GLuint)stream, stream_buffers[stream], size, type, normalized, - FALSE, (GLsizei)stride, stream_offsets[stream], element->offset); - } -#else - if (element->type == D3DVSDT_NORMPACKED3) - { - layout_attribute(&layout, element->reg, (GLuint)stream, 1, GL_UNSIGNED_INT, GL_FALSE, TRUE, element->offset); - } - else - { - attribute_format(element, &size, &type, &normalized); - layout_attribute(&layout, element->reg, (GLuint)stream, size, type, normalized, FALSE, element->offset); - } - streams_used |= 1UL << stream; -#endif - enabled[element->reg] = TRUE; + attribute->format = attribute_format(element); + attribute->stream = (uint8_t)stream; + attribute->offset = element->offset; } -#ifndef HALO_ANDROID - /* (the layout follows from the declaration and the streams it has, so - the same two have the same vertex array: no need to look it up) */ - if (!declaration->vertex_array || declaration->vertex_array_streams != streams_used) - { - declaration->vertex_array = vertex_array_get(&layout); - declaration->vertex_array_streams = streams_used; - } - state_vertex_array(declaration->vertex_array); - for (index = 0; index < 16; index++) - { - if (streams_used & (1UL << index)) - state_vertex_buffer((GLuint)index, stream_buffers[index], stream_offsets[index], - (GLsizei)device.streams[index].stride); - } -#endif for (index = 0; index < XGPU_VERTEX_ATTRIBUTE_COUNT; index++) { - if (!enabled[index]) - state_attribute_value(index, declaration->packed_mask & (1UL << index) ? NULL : device.attributes[index]); + if (draw->attributes[index].stream != GPU_STREAM_ZERO || (declaration->packed_mask & (1UL << index))) + continue; + draw->attributes[index].stream = GPU_STREAM_CONSTANT; + memcpy(draw->constant_values[index], device.attributes[index], sizeof(draw->constant_values[index])); } } -static GLenum primitive_mode(D3DPRIMITIVETYPE type) +static uint32_t primitive_mode(D3DPRIMITIVETYPE type) { switch (type) { - case D3DPT_POINTLIST: return GL_POINTS; - case D3DPT_LINELIST: return GL_LINES; - case D3DPT_LINELOOP: return GL_LINE_LOOP; - case D3DPT_LINESTRIP: return GL_LINE_STRIP; + case D3DPT_POINTLIST: return GPU_PRIMITIVE_POINTS; + case D3DPT_LINELIST: return GPU_PRIMITIVE_LINES; + case D3DPT_LINELOOP: return GPU_PRIMITIVE_LINE_LOOP; + case D3DPT_LINESTRIP: return GPU_PRIMITIVE_LINE_STRIP; case D3DPT_TRIANGLESTRIP: - case D3DPT_QUADSTRIP: return GL_TRIANGLE_STRIP; + case D3DPT_QUADSTRIP: return GPU_PRIMITIVE_TRIANGLE_STRIP; case D3DPT_TRIANGLEFAN: - case D3DPT_POLYGON: return GL_TRIANGLE_FAN; - default: return GL_TRIANGLES; + case D3DPT_POLYGON: return GPU_PRIMITIVE_TRIANGLE_FAN; + default: return GPU_PRIMITIVE_TRIANGLES; } } +/* the draw's vertices by count indices, streamed */ +static void draw_indices(struct gpu_draw *draw, const WORD *indices, unsigned long count) +{ + draw->index_offset = gpu_stream(GPU_STREAM_INDEX, indices, (uint32_t)(count * sizeof(WORD)), &draw->index_buffer); + draw->count = (uint32_t)count; +} + /* quads become two triangles each */ static WORD *quad_indices(const WORD *indices, unsigned long count, unsigned long *out_count) { @@ -3593,24 +2935,27 @@ void WINAPI D3DDevice_SetIndices(D3DIndexBuffer *index_data, UINT base_vertex_in void WINAPI D3DDevice_DrawVertices(D3DPRIMITIVETYPE primitive_type, UINT start_vertex, UINT vertex_count) { - if (!vertex_count || !prepare_draw(FALSE)) + struct gpu_draw draw; + + if (!vertex_count || !prepare_draw(&draw, FALSE)) return; trace_draw("draw", primitive_type, vertex_count, NULL); - setup_streams(start_vertex, vertex_count); + setup_streams(&draw, start_vertex, vertex_count); if (primitive_type == D3DPT_QUADLIST) { unsigned long count; WORD *indices = quad_indices(NULL, vertex_count, &count); - glDrawElements(GL_TRIANGLES, (GLsizei)count, GL_UNSIGNED_SHORT, - (const void *)index_upload(indices, count * sizeof(WORD))); + draw.primitive = GPU_PRIMITIVE_TRIANGLES; + draw_indices(&draw, indices, count); free(indices); } else { - glDrawArrays(primitive_mode(primitive_type), 0, (GLsizei)vertex_count); + draw.primitive = primitive_mode(primitive_type); + draw.count = vertex_count; } - gl_check_errors("draw"); + submit_draw(&draw); } void WINAPI D3DDevice_DrawIndexedVertices(D3DPRIMITIVETYPE primitive_type, UINT vertex_count, CONST WORD *index_data) @@ -3618,27 +2963,28 @@ void WINAPI D3DDevice_DrawIndexedVertices(D3DPRIMITIVETYPE primitive_type, UINT unsigned long minimum, maximum, index, count, generation = 0, index_offset = 0; WORD *indices = NULL; const WORD *source = index_data; - GLuint index_buffer = 0; + gpu_buffer index_buffer = 0; + struct gpu_draw draw; BOOL mirrored; - if (!vertex_count || !index_data || !prepare_draw(FALSE)) + if (!vertex_count || !index_data || !prepare_draw(&draw, FALSE)) return; /* quads are drawn as triangles, from indices made for the draw */ - mirrored = primitive_type != D3DPT_QUADLIST && -#ifdef HALO_ANDROID - xgpu_capabilities.base_vertex && -#endif + mirrored = primitive_type != D3DPT_QUADLIST && xgpu_gpu_capabilities.base_vertex && mirror_range((unsigned long)index_data, vertex_count * sizeof(WORD), &index_buffer, &index_offset, &generation); index_extent(index_data, vertex_count, generation, mirrored, &minimum, &maximum); trace_draw("indexed", primitive_type, vertex_count, NULL); /* (the streams from the base vertex on: index i is vertex base + i) */ - setup_streams(device.base_vertex_index + minimum, maximum - minimum + 1); + setup_streams(&draw, device.base_vertex_index + minimum, maximum - minimum + 1); + draw.primitive = primitive_mode(primitive_type); if (mirrored) { /* the attributes start at vertex minimum */ - state_element_array_buffer(index_buffer); - glDrawElementsBaseVertex(primitive_mode(primitive_type), (GLsizei)vertex_count, GL_UNSIGNED_SHORT, - (const void *)index_offset, -(GLint)minimum); + draw.index_buffer = index_buffer; + draw.index_offset = (uint32_t)index_offset; + draw.count = vertex_count; + draw.base_vertex = -(int32_t)minimum; + submit_draw(&draw); return; } stats.streamed_bytes += vertex_count * sizeof(WORD); @@ -3647,26 +2993,25 @@ void WINAPI D3DDevice_DrawIndexedVertices(D3DPRIMITIVETYPE primitive_type, UINT { indices = quad_indices(index_data, vertex_count, &count); source = indices; + draw.primitive = GPU_PRIMITIVE_TRIANGLES; } -#ifdef HALO_ANDROID - if (!xgpu_capabilities.base_vertex) + if (!xgpu_gpu_capabilities.base_vertex) { /* the indices are copied anyway: rebase them */ WORD *rebased = malloc(count * sizeof(WORD) + 2); for (index = 0; index < count; index++) rebased[index] = (WORD)(source[index] - minimum); - glDrawElements(primitive_mode(primitive_type), (GLsizei)count, GL_UNSIGNED_SHORT, - (const void *)index_upload(rebased, count * sizeof(WORD))); + draw_indices(&draw, rebased, count); free(rebased); - free(indices); - return; } -#endif - (void)index; - glDrawElementsBaseVertex(primitive_mode(primitive_type), (GLsizei)count, GL_UNSIGNED_SHORT, - (const void *)index_upload(source, count * sizeof(WORD)), -(GLint)minimum); + else + { + draw_indices(&draw, source, count); + draw.base_vertex = -(int32_t)minimum; + } free(indices); + submit_draw(&draw); } /* ---------- immediate mode */ @@ -3695,52 +3040,39 @@ static void immediate_emit(void) void WINAPI D3DDevice_End(void) { unsigned long stride = XGPU_VERTEX_ATTRIBUTE_COUNT * 4 * sizeof(float); - unsigned long offset, index, count = device.immediate_count; + unsigned long index, count = device.immediate_count; D3DPRIMITIVETYPE type = device.immediate_type; + struct gpu_draw draw; device.immediate_active = FALSE; - if (!count || !prepare_draw(TRUE)) + if (!count || !prepare_draw(&draw, TRUE)) return; trace_draw("immediate", type, count, device.immediate_vertices); - offset = stream_upload(device.immediate_vertices, count * stride); -#ifdef HALO_ANDROID + /* every attribute four floats, one after another */ + draw.streams[0].offset = gpu_stream(GPU_STREAM_VERTEX, device.immediate_vertices, (uint32_t)(count * stride), + &draw.streams[0].buffer); + draw.streams[0].stride = (uint32_t)stride; for (index = 0; index < XGPU_VERTEX_ATTRIBUTE_COUNT; index++) { - state_attribute_stream(index, 0, device.stream_buffer, 4, GL_FLOAT, GL_FALSE, FALSE, (GLsizei)stride, - offset, index * 4 * sizeof(float)); + draw.attributes[index].format = GPU_ATTRIBUTE_FLOAT4; + draw.attributes[index].stream = 0; + draw.attributes[index].offset = (uint16_t)(index * 4 * sizeof(float)); } -#else - { - /* every attribute four floats, one after another */ - static struct vertex_array_entry *immediate_array; - - if (!immediate_array) - { - struct vertex_layout layout; - - memset(&layout, 0, sizeof(layout)); - for (index = 0; index < XGPU_VERTEX_ATTRIBUTE_COUNT; index++) - layout_attribute(&layout, index, 0, 4, GL_FLOAT, GL_FALSE, FALSE, index * 4 * sizeof(float)); - immediate_array = vertex_array_get(&layout); - } - state_vertex_array(immediate_array); - state_vertex_buffer(0, device.stream_buffer, offset, (GLsizei)stride); - } -#endif if (type == D3DPT_QUADLIST) { unsigned long index_count; WORD *indices = quad_indices(NULL, count, &index_count); - glDrawElements(GL_TRIANGLES, (GLsizei)index_count, GL_UNSIGNED_SHORT, - (const void *)index_upload(indices, index_count * sizeof(WORD))); + draw.primitive = GPU_PRIMITIVE_TRIANGLES; + draw_indices(&draw, indices, index_count); free(indices); } else { - glDrawArrays(primitive_mode(type), 0, (GLsizei)count); + draw.primitive = primitive_mode(type); + draw.count = (uint32_t)count; } - gl_check_errors("immediate draw"); + submit_draw(&draw); } static void set_attribute(INT reg, float a, float b, float c, float d) @@ -3797,11 +3129,14 @@ void WINAPI D3DDevice_Clear(DWORD count, CONST D3DRECT *rectangles, DWORD flags, { float rgba[4]; GLbitfield mask = 0; + gpu_texture color_target, depth_target; + uint32_t target_storage; BOOL has_depth = FALSE; DWORD index; - if (!device.gl_ready || !bind_targets(&has_depth)) + if (!device.gl_ready || !bind_targets(&color_target, &depth_target, &target_storage, &has_depth)) return; + gpu_gl_bind_targets(color_target, depth_target, target_storage); if (trace_frame()) platform_log("clear flags %lx color %08lx z %g count %lu target %08lx depth %08lx", (unsigned long)flags, (unsigned long)color, z, (unsigned long)count, @@ -3896,7 +3231,7 @@ void halo_screen_anti_alias(short x0, short y0, short x1, short y1) corners[3] = scaled_pixel(y1, target->target.scale[1]); xgpu_post_anti_alias(mode == _anti_aliasing_smaa, gpu_gl_framebuffer(target->target.texture, 0), target->target.gl_width, target->target.gl_height, corners); - glBindVertexArray(device.vertex_array); + glBindVertexArray(gpu_gl_default_vertex_array); xgpu_gl_state_invalidate(); } @@ -4006,18 +3341,18 @@ void WINAPI D3DDevice_Present(CONST RECT *source_rectangle, CONST RECT *destinat which ES 3.1 does not promise without one) */ glMemoryBarrier(GL_BUFFER_UPDATE_BARRIER_BIT); glBindBuffer(GL_COPY_READ_BUFFER, device.visibility_counters); - glBindBuffer(GL_COPY_WRITE_BUFFER, device.counter_snapshots[device.buffer_ring]); + glBindBuffer(GL_COPY_WRITE_BUFFER, device.counter_snapshots[gpu_gl_frame_ring()]); glCopyBufferSubData(GL_COPY_READ_BUFFER, GL_COPY_WRITE_BUFFER, 0, 0, VISIBILITY_TEST_SLOTS * sizeof(GLuint)); glBindBuffer(GL_COPY_READ_BUFFER, 0); glBindBuffer(GL_COPY_WRITE_BUFFER, 0); } - host_gl_fence_frame((unsigned int)device.buffer_ring); - device.buffer_ring = (device.buffer_ring + 1) % STREAM_BUFFER_RING; - host_gl_wait_frame((unsigned int)device.buffer_ring); - if (xgpu_capabilities.atomic_counters && device.ring_test_count[device.buffer_ring]) + /* the next frame's stream buffers, once the GPU has finished the + frame that last used them */ + gpu_gl_frame_advance(); + if (xgpu_capabilities.atomic_counters && device.ring_test_count[gpu_gl_frame_ring()]) { - unsigned long ring = device.buffer_ring; + unsigned long ring = gpu_gl_frame_ring(); unsigned long test; /* the GPU has passed that frame's fence: its copy is complete, @@ -4031,13 +3366,8 @@ void WINAPI D3DDevice_Present(CONST RECT *source_rectangle, CONST RECT *destinat } device.ring_test_count[ring] = 0; } - device.stream_buffer = device.stream_buffers[device.buffer_ring]; - device.index_buffer = device.index_buffers[device.buffer_ring]; - device.stream_offset = 0; - device.index_offset = 0; #else - device.stream_offset = STREAM_BUFFER_SIZE; /* orphan next frame */ - device.index_offset = INDEX_BUFFER_SIZE; + gpu_gl_frame_advance(); #endif } device.frame++; diff --git a/port/linux/src/gpu.h b/port/linux/src/gpu.h index 05dec4ead7..c56e2c5534 100644 --- a/port/linux/src/gpu.h +++ b/port/linux/src/gpu.h @@ -17,8 +17,9 @@ for the 32-bit game) can take them as they are. #include -/* a texture, render targets included; 0 is none */ -typedef uint32_t gpu_texture; +/* a texture (render targets included), a buffer and a compiled shader; 0 +is none */ +typedef uint32_t gpu_texture, gpu_buffer, gpu_shader; /* the Xbox's texture stages and vertex attributes (D3DTSS_MAXSTAGES, XGPU_VERTEX_ATTRIBUTE_COUNT) */ @@ -177,4 +178,231 @@ struct gpu_stage struct gpu_sampler_state sampler; }; +/* ---------- buffers + +The device's copy of the Xbox's contiguous memory (vertex and index data +that stays put) lives in buffers it writes as the game changes it; what a +draw streams goes into the backend's own buffers for the frame. */ + +/* gpu_buffer_write flags: no draw queued so far reads the range (pages +written for the first time), so the write need not wait for one */ +enum { GPU_WRITE_UNUSED = 1 }; + +gpu_buffer gpu_buffer_create(uint32_t size); +void gpu_buffer_write(gpu_buffer buffer, uint32_t offset, uint32_t size, const void *data, uint32_t flags); + +/* gpu_stream kinds */ +enum { GPU_STREAM_VERTEX = 1, GPU_STREAM_INDEX }; + +/* room for bytes of vertices before a draw's first gpu_stream of them: +starting a new buffer between two of a draw's streams would leave the +earlier ones in the old one */ +void gpu_stream_reserve(uint32_t bytes); +/* copies size bytes (rounded up to 16) into the frame's vertex or index +buffer; returns their offset there, and the buffer in *buffer */ +uint32_t gpu_stream(uint32_t kind, const void *data, uint32_t size, gpu_buffer *buffer); + +/* ---------- shaders */ + +enum { GPU_SHADER_VERTEX = 1, GPU_SHADER_PIXEL }; + +/* source is GLSL in the dialect the capabilities name; 0 if it does not +compile (the log says why) */ +gpu_shader gpu_shader_create(uint32_t stage, const char *source); + +/* ---------- what a draw's shaders read + +The vertex constants: the device's copy of the 192 registers. Each register's +serial is the value serial had when the register last changed, and log +holds the register each of the latest serials changed (modulo its size), so +a backend that uploaded a program's registers at serial s finds what has +changed since without looking at them all. The checkpoint is what changed +since the backend last uploaded registers to any program, which the backend +resets as it does. The serials are 64-bit: a skinned model's draw changes up +to 132 registers, and 32 bits wrapped within minutes at a high frame rate. */ + +enum { GPU_CONSTANT_COUNT = 192, GPU_CONSTANT_LOG_SIZE = 1024 }; + +struct gpu_constant_store +{ + float c[GPU_CONSTANT_COUNT][4]; + uint64_t serials[GPU_CONSTANT_COUNT]; + uint64_t serial; + uint8_t log[GPU_CONSTANT_LOG_SIZE]; + uint64_t checkpoint_serial; + uint32_t checkpoint_first, checkpoint_last; +}; + +/* the per-pixel model lighting's registers (nv2a_psh.c model_lighting): +c[-82] and c[-79] to c[-69], at 96 + those */ +enum { GPU_MODEL_LIGHT_COUNT = 12 }; + +/* the other uniforms (nv2a_vsh.c, nv2a_psh.c), and a serial that changes +whenever any of them does */ +struct gpu_uniforms +{ + float viewport_scale[4]; + float viewport_offset[4]; + float point_size; + float ps_c0[8][4]; + float ps_c1[8][4]; + float ps_final_c0[4]; + float ps_final_c1[4]; + float fog_color[4]; + float fog_parameters[4]; + float alpha_reference; + float bump_matrix[4][4]; + float bump_luminance[4][4]; + float texture_scale[4][4]; + float screen_offset; + float texture_lod_bias[4]; + uint32_t serial; +}; + +/* ---------- draw state */ + +/* depth and stencil tests' comparisons, as D3DCMP_* orders them */ +enum +{ + GPU_COMPARE_NEVER, GPU_COMPARE_LESS, GPU_COMPARE_EQUAL, GPU_COMPARE_LESS_EQUAL, + GPU_COMPARE_GREATER, GPU_COMPARE_NOT_EQUAL, GPU_COMPARE_GREATER_EQUAL, GPU_COMPARE_ALWAYS, +}; + +enum +{ + GPU_STENCIL_KEEP, GPU_STENCIL_ZERO, GPU_STENCIL_REPLACE, GPU_STENCIL_INCREMENT_CLAMP, + GPU_STENCIL_DECREMENT_CLAMP, GPU_STENCIL_INVERT, GPU_STENCIL_INCREMENT_WRAP, GPU_STENCIL_DECREMENT_WRAP, +}; + +enum +{ + GPU_BLEND_ZERO, GPU_BLEND_ONE, + GPU_BLEND_SOURCE_COLOR, GPU_BLEND_ONE_MINUS_SOURCE_COLOR, + GPU_BLEND_SOURCE_ALPHA, GPU_BLEND_ONE_MINUS_SOURCE_ALPHA, + GPU_BLEND_DESTINATION_ALPHA, GPU_BLEND_ONE_MINUS_DESTINATION_ALPHA, + GPU_BLEND_DESTINATION_COLOR, GPU_BLEND_ONE_MINUS_DESTINATION_COLOR, + GPU_BLEND_SOURCE_ALPHA_SATURATE, + GPU_BLEND_CONSTANT_COLOR, GPU_BLEND_ONE_MINUS_CONSTANT_COLOR, + GPU_BLEND_CONSTANT_ALPHA, GPU_BLEND_ONE_MINUS_CONSTANT_ALPHA, +}; + +enum { GPU_BLEND_OP_ADD, GPU_BLEND_OP_SUBTRACT, GPU_BLEND_OP_REVERSE_SUBTRACT, GPU_BLEND_OP_MIN, GPU_BLEND_OP_MAX }; + +/* the faces discarded; the winding of the front ones; how polygons fill */ +enum { GPU_CULL_NONE, GPU_CULL_FRONT, GPU_CULL_BACK }; +enum { GPU_FRONT_CLOCKWISE, GPU_FRONT_COUNTER_CLOCKWISE }; +enum { GPU_FILL_SOLID, GPU_FILL_LINE, GPU_FILL_POINT }; + +/* a rectangle of the targets' pixels, rows from the top; the scissor test +is off when width or height is not above 0 */ +struct gpu_rect +{ + int32_t x, y, width, height; +}; + +struct gpu_viewport +{ + struct gpu_rect rect; + float min_z, max_z; +}; + +/* each comparison and operation is used only while its test is on */ +struct gpu_depth_stencil_state +{ + uint8_t depth_test, depth_write, depth_function; + uint8_t stencil_test, stencil_function; + uint8_t stencil_fail, stencil_depth_fail, stencil_pass; + uint32_t stencil_reference, stencil_read_mask, stencil_write_mask; +}; + +/* the factors, operation and color are used only while enable is on */ +struct gpu_blend_state +{ + uint8_t enable; + uint8_t source, destination, operation; + /* ARGB, as D3DCOLOR */ + uint32_t color; + /* bit 0 red, 1 green, 2 blue, 3 alpha */ + uint8_t color_write_mask; + uint8_t pad[3]; +}; + +struct gpu_raster_state +{ + uint8_t cull_mode, front_face, fill_mode; + /* the depth bias, with a slope term, while enabled */ + uint8_t depth_bias; + float depth_bias_slope, depth_bias_constant; +}; + +/* vertex attribute formats: BGRA8 is D3DCOLOR (with vertex_bgra; RGBA8 the +same swapped), SHORT a plain integer read as a float, NORMSHORT and UBYTE +normalized, and NORMPACKED3 one unsigned integer the vertex shader unpacks */ +enum +{ + GPU_ATTRIBUTE_FLOAT1 = 1, GPU_ATTRIBUTE_FLOAT2, GPU_ATTRIBUTE_FLOAT3, GPU_ATTRIBUTE_FLOAT4, + GPU_ATTRIBUTE_BGRA8, GPU_ATTRIBUTE_RGBA8, + GPU_ATTRIBUTE_SHORT1, GPU_ATTRIBUTE_SHORT2, GPU_ATTRIBUTE_SHORT3, GPU_ATTRIBUTE_SHORT4, + GPU_ATTRIBUTE_NORMSHORT1, GPU_ATTRIBUTE_NORMSHORT2, GPU_ATTRIBUTE_NORMSHORT3, GPU_ATTRIBUTE_NORMSHORT4, + GPU_ATTRIBUTE_UBYTE1, GPU_ATTRIBUTE_UBYTE2, GPU_ATTRIBUTE_UBYTE3, GPU_ATTRIBUTE_UBYTE4, + GPU_ATTRIBUTE_NORMPACKED3, +}; + +/* an attribute's source other than streams 0 to 15: the value in +constant_values, or the integer zero */ +enum { GPU_STREAM_COUNT = 16, GPU_STREAM_CONSTANT = 16, GPU_STREAM_ZERO = 255 }; + +struct gpu_vertex_stream +{ + gpu_buffer buffer; + uint32_t offset; + uint32_t stride; +}; + +struct gpu_vertex_attribute +{ + uint8_t format; + uint8_t stream; + /* bytes from the start of the stream's vertex */ + uint16_t offset; +}; + +enum +{ + GPU_PRIMITIVE_POINTS, GPU_PRIMITIVE_LINES, GPU_PRIMITIVE_LINE_LOOP, GPU_PRIMITIVE_LINE_STRIP, + GPU_PRIMITIVE_TRIANGLES, GPU_PRIMITIVE_TRIANGLE_STRIP, GPU_PRIMITIVE_TRIANGLE_FAN, +}; + +/* one draw, complete: the backend applies it without reference to the draws +before it */ +struct gpu_draw +{ + /* at least one; a depth target is a depth-stencil texture */ + gpu_texture color_target, depth_target; + /* 0: the targets' textures are drawn into; else their multisampled + storage, with this many samples a pixel */ + uint32_t samples; + gpu_shader vertex_shader, pixel_shader; + struct gpu_viewport viewport; + struct gpu_rect scissor; + struct gpu_depth_stencil_state depth_stencil; + struct gpu_blend_state blend; + struct gpu_raster_state raster; + struct gpu_stage stages[GPU_STAGE_COUNT]; + struct gpu_vertex_stream streams[GPU_STREAM_COUNT]; + struct gpu_vertex_attribute attributes[GPU_ATTRIBUTE_COUNT]; + float constant_values[GPU_ATTRIBUTE_COUNT][4]; + /* 0: the vertices are drawn in order; else their 16-bit indices */ + gpu_buffer index_buffer; + uint32_t index_offset; + uint32_t primitive; + uint32_t count; + /* added to each index */ + int32_t base_vertex; +}; + +/* draws; returns 0 if nothing was drawn because the shaders do not link */ +uint32_t gpu_draw(const struct gpu_draw *draw, struct gpu_constant_store *constants, + const struct gpu_uniforms *uniforms); + #endif diff --git a/port/linux/src/gpu_gl.c b/port/linux/src/gpu_gl.c index d9c136b1b8..0c88a03d9b 100644 --- a/port/linux/src/gpu_gl.c +++ b/port/linux/src/gpu_gl.c @@ -35,7 +35,110 @@ static void color_to_vec4(uint32_t color, float *out) out[3] = ((color >> 24) & 0xff) / 255.0f; } -/* ---------- GL state cache (gpu_gl.h) */ +/* ---------- GL state cache + +Consecutive draws share most of their state, but each sets all of it: the +setters here skip the call when GL already holds the value. Code that +changes GL state behind the cache's back (clears, presentation, texture +uploads, render target and framebuffer creation) calls +xgpu_gl_state_invalidate (gpu_gl.h), after which every value is set again. +Unknown values are all ones, which no real value matches (floats become +NaN, which compares unequal to everything). */ + +#ifdef HALO_ANDROID +struct attribute_pointer +{ + GLuint buffer; + GLint size; + GLenum type; + GLboolean normalized; + GLboolean integer; + GLsizei stride; + unsigned long offset; +}; +#else +/* desktop GL (4.3) separates an attribute's format from the buffer it +reads: the attributes of a stream share one binding, so a draw that moves +the stream rebinds it once instead of pointing each attribute again */ +struct attribute_format +{ + GLint size; + GLenum type; + GLboolean normalized; + GLboolean integer; + GLuint relative_offset; + GLuint binding; +}; + +struct vertex_binding +{ + GLuint buffer; + unsigned long offset; + GLsizei stride; +}; + +/* a binding per stream (setup_streams); GL has at least 16 */ +#define VERTEX_BINDING_COUNT 16 + +/* a vertex array object for each vertex layout (the attributes a draw +enables, and each one's format and binding), made once: a draw binds its +layout's and points the bindings at its streams, rather than enabling, +formatting and binding each attribute again (setup_streams) */ +struct vertex_layout +{ + unsigned long enabled; + struct attribute_format formats[GPU_ATTRIBUTE_COUNT]; +}; + +struct vertex_array_entry +{ + struct vertex_array_entry *next; + unsigned long hash; + struct vertex_layout layout; + GLuint vertex_array; + /* its bindings, as last pointed (nothing else changes them) */ + struct vertex_binding bindings[VERTEX_BINDING_COUNT]; +}; +#endif + +static struct +{ + GLuint program; + GLuint framebuffer; + GLint viewport[4]; + GLint scissor[4]; + float depth_range[2]; + unsigned char depth_test, stencil_test, blend, cull_face, offset_fill, offset_line; + unsigned char scissor_test; + GLenum depth_function; + unsigned char depth_mask; + GLenum stencil_function; + GLint stencil_reference; + GLuint stencil_value_mask; + GLenum stencil_operations[3]; + GLuint stencil_write_mask; + GLenum blend_source, blend_destination, blend_equation; + float blend_color[4]; + unsigned char color_mask; + GLenum front_face, cull_mode, polygon_mode; + float polygon_offset[2]; + GLenum active_texture; + /* per unit: the GL_TEXTURE_2D, GL_TEXTURE_CUBE_MAP and GL_TEXTURE_3D + bindings */ + GLuint textures[GPU_STAGE_COUNT][3]; + GLuint samplers[GPU_STAGE_COUNT]; + GLuint array_buffer; + GLuint element_array_buffer; + unsigned char attribute_enabled[GPU_ATTRIBUTE_COUNT]; +#ifdef HALO_ANDROID + struct attribute_pointer attribute_pointers[GPU_ATTRIBUTE_COUNT]; +#else + GLuint vertex_array; +#endif + /* a disabled attribute's value; kind 1 is the integer zero */ + unsigned char attribute_value_kind[GPU_ATTRIBUTE_COUNT]; + float attribute_values[GPU_ATTRIBUTE_COUNT][4]; +} gl_state; #ifndef HALO_ANDROID #define VERTEX_ARRAY_BUCKET_COUNT 64 @@ -43,14 +146,12 @@ static struct vertex_array_entry *vertex_array_buckets[VERTEX_ARRAY_BUCKET_COUNT static struct vertex_array_entry *current_vertex_array; #endif -struct gl_state gl_state; - void xgpu_gl_state_invalidate(void) { memset(&gl_state, 0xff, sizeof(gl_state)); } -void state_enable(unsigned char *shadow, GLenum capability, BOOL enabled) +static void state_enable(unsigned char *shadow, GLenum capability, BOOL enabled) { unsigned char value = enabled ? 1 : 0; @@ -63,7 +164,7 @@ void state_enable(unsigned char *shadow, GLenum capability, BOOL enabled) glDisable(capability); } -void state_program(GLuint program) +static void state_program(GLuint program) { if (gl_state.program != program) { @@ -113,7 +214,7 @@ static void state_sampler(int unit, GLuint sampler) } } -void state_array_buffer(GLuint buffer) +static void state_array_buffer(GLuint buffer) { if (gl_state.array_buffer != buffer) { @@ -122,7 +223,7 @@ void state_array_buffer(GLuint buffer) } } -void state_element_array_buffer(GLuint buffer) +static void state_element_array_buffer(GLuint buffer) { if (gl_state.element_array_buffer != buffer) { @@ -135,7 +236,7 @@ void state_element_array_buffer(GLuint buffer) /* enables the attribute, reading size elements of type from buffer: each vertex is stride bytes on from the one before it, starting at buffer_offset, with the attribute relative_offset bytes into it */ -void state_attribute_stream(GLuint index, GLuint binding, GLuint buffer, GLint size, GLenum type, +static void state_attribute_stream(GLuint index, GLuint binding, GLuint buffer, GLint size, GLenum type, GLboolean normalized, BOOL integer, GLsizei stride, unsigned long buffer_offset, unsigned long relative_offset) { struct attribute_pointer *pointer = &gl_state.attribute_pointers[index]; @@ -169,7 +270,7 @@ void state_attribute_stream(GLuint index, GLuint binding, GLuint buffer, GLint s #else /* the vertex array of a layout, made the first time (its attributes enabled, formatted and bound to their bindings) */ -struct vertex_array_entry *vertex_array_get(const struct vertex_layout *layout) +static struct vertex_array_entry *vertex_array_get(const struct vertex_layout *layout) { const unsigned char *bytes = (const unsigned char *)layout; unsigned long hash = 2166136261UL, index; @@ -210,7 +311,7 @@ struct vertex_array_entry *vertex_array_get(const struct vertex_layout *layout) return entry; } -void state_vertex_array(struct vertex_array_entry *entry) +static void state_vertex_array(struct vertex_array_entry *entry) { if (gl_state.vertex_array != entry->vertex_array) { @@ -224,7 +325,7 @@ void state_vertex_array(struct vertex_array_entry *entry) /* points the bound vertex array's binding at buffer: each vertex is stride bytes on from the one before it, starting at offset */ -void state_vertex_buffer(GLuint binding, GLuint buffer, unsigned long offset, GLsizei stride) +static void state_vertex_buffer(GLuint binding, GLuint buffer, unsigned long offset, GLsizei stride) { struct vertex_binding *vertex_binding = ¤t_vertex_array->bindings[binding]; @@ -238,7 +339,7 @@ void state_vertex_buffer(GLuint binding, GLuint buffer, unsigned long offset, GL } /* an attribute of a layout */ -void layout_attribute(struct vertex_layout *layout, unsigned long index, GLuint binding, GLint size, +static void layout_attribute(struct vertex_layout *layout, unsigned long index, GLuint binding, GLint size, GLenum type, GLboolean normalized, BOOL integer, unsigned long relative_offset) { struct attribute_format *format = &layout->formats[index]; @@ -254,7 +355,7 @@ void layout_attribute(struct vertex_layout *layout, unsigned long index, GLuint #endif /* disables the attribute, which then reads value, or the integer zero */ -void state_attribute_value(GLuint index, const float *value) +static void state_attribute_value(GLuint index, const float *value) { unsigned char kind = value ? 0 : 1; @@ -873,7 +974,8 @@ static void configure_sampler(int stage, const struct gpu_sampler_state *state) /* ---------- texture stages */ -void gpu_gl_bind_stages(const struct gpu_stage stages[GPU_STAGE_COUNT]) +/* binds each stage's texture and sampler */ +static void bind_stages(const struct gpu_stage stages[GPU_STAGE_COUNT]) { GLenum gl_targets[GPU_STAGE_COUNT]; GLuint gl_textures[GPU_STAGE_COUNT]; @@ -925,6 +1027,876 @@ void gpu_gl_bind_stages(const struct gpu_stage stages[GPU_STAGE_COUNT]) #endif } +/* ---------- GL helpers */ + +GLuint xgpu_compile_shader(GLenum type, const char *source, const char *what) +{ + GLuint shader = glCreateShader(type); + GLint status = 0; + + glShaderSource(shader, 1, &source, NULL); + glCompileShader(shader); + glGetShaderiv(shader, GL_COMPILE_STATUS, &status); + if (!status) + { + char log[4096]; + + glGetShaderInfoLog(shader, sizeof(log), NULL, log); + platform_log("cannot compile the %s shader:\n%s\n%s", what, log, source); + glDeleteShader(shader); + return 0; + } + return shader; +} + +GLuint xgpu_link_program(GLuint vertex_shader, GLuint fragment_shader, const char *what) +{ + GLuint program = glCreateProgram(); + GLint status = 0; + + glAttachShader(program, vertex_shader); + glAttachShader(program, fragment_shader); + glLinkProgram(program); + glGetProgramiv(program, GL_LINK_STATUS, &status); + if (!status) + { + char log[4096]; + + glGetProgramInfoLog(program, sizeof(log), NULL, log); + platform_log("cannot link the %s program: %s", what, log); + return 0; + } + return program; +} + +gpu_shader gpu_shader_create(uint32_t stage, const char *source) +{ + return stage == GPU_SHADER_VERTEX ? xgpu_compile_shader(GL_VERTEX_SHADER, source, "vertex") : + xgpu_compile_shader(GL_FRAGMENT_SHADER, source, "pixel"); +} + +/* ---------- programs */ + +struct program_entry +{ + struct program_entry *next; + GLuint vertex_shader; + GLuint fragment_shader; + GLuint program; + GLint constants; + GLint viewport_scale; + GLint viewport_offset; + GLint point_size; + GLint ps_c0, ps_c1, ps_final_c0, ps_final_c1; + GLint fog_color, fog_parameters, alpha_reference; + GLint bump_matrix, bump_luminance, texture_scale; + GLint texture_lod_bias; + GLint screen_offset; + /* the lights of a draw lit for each pixel (GPU_MODEL_LIGHT_COUNT), and + the constants' serial at their last upload */ + GLint model_lights; + unsigned long long model_lights_serial; + + /* the vertex constants c[0..constant_count) the program uses; with + consecutive locations, a changed range is uploaded by itself */ + unsigned long constant_count; + BOOL constants_consecutive; + /* the constants' serial at the program's last constant upload */ + unsigned long long constants_serial; + /* the uniforms' serial when the uniforms below were brought up to date */ + unsigned long uniforms_serial; + /* what the program's other uniforms hold (all ones: unknown) */ + struct gpu_uniforms uniforms; +}; + +#define PROGRAM_BUCKETS 1024 + +static struct program_entry *program_buckets[PROGRAM_BUCKETS]; + +static struct program_entry *program_get(GLuint vertex_shader, GLuint fragment_shader) +{ + static struct program_entry *last; + unsigned long hash = (vertex_shader * 2654435761UL) ^ fragment_shader; + struct program_entry **bucket = &program_buckets[hash % PROGRAM_BUCKETS]; + struct program_entry *entry; + int stage; + + if (last && last->vertex_shader == vertex_shader && last->fragment_shader == fragment_shader) + return last; + for (entry = *bucket; entry; entry = entry->next) + { + if (entry->vertex_shader == vertex_shader && entry->fragment_shader == fragment_shader) + { + if (!entry->program) + return NULL; + last = entry; + return entry; + } + } + entry = calloc(1, sizeof(*entry)); + entry->vertex_shader = vertex_shader; + entry->fragment_shader = fragment_shader; + memset(&entry->uniforms, 0xff, sizeof(entry->uniforms)); + entry->next = *bucket; + *bucket = entry; + if (!vertex_shader || !fragment_shader) + return NULL; + + entry->program = xgpu_link_program(vertex_shader, fragment_shader, "shader"); + if (!entry->program) + return NULL; + state_program(entry->program); + entry->constants = glGetUniformLocation(entry->program, "c"); + entry->constant_count = GPU_CONSTANT_COUNT; + if (entry->constants >= 0) + { + unsigned long index; + + /* c[i] is usually at c's location plus i, and the compiler may + drop registers past the last one the program reads */ + entry->constants_consecutive = TRUE; + for (index = 1; index < GPU_CONSTANT_COUNT; index++) + { + char name[16]; + GLint location; + + snprintf(name, sizeof(name), "c[%lu]", index); + location = glGetUniformLocation(entry->program, name); + if (location < 0) + { + entry->constant_count = index; + break; + } + if (location != entry->constants + (GLint)index) + { + entry->constants_consecutive = FALSE; + entry->constant_count = GPU_CONSTANT_COUNT; + break; + } + } + } + entry->viewport_scale = glGetUniformLocation(entry->program, "viewport_scale"); + entry->viewport_offset = glGetUniformLocation(entry->program, "viewport_offset"); + entry->point_size = glGetUniformLocation(entry->program, "point_size"); + entry->ps_c0 = glGetUniformLocation(entry->program, "ps_c0"); + entry->ps_c1 = glGetUniformLocation(entry->program, "ps_c1"); + entry->ps_final_c0 = glGetUniformLocation(entry->program, "ps_final_c0"); + entry->ps_final_c1 = glGetUniformLocation(entry->program, "ps_final_c1"); + entry->fog_color = glGetUniformLocation(entry->program, "fog_color"); + entry->fog_parameters = glGetUniformLocation(entry->program, "fog_parameters"); + entry->alpha_reference = glGetUniformLocation(entry->program, "alpha_reference"); + entry->bump_matrix = glGetUniformLocation(entry->program, "bump_matrix"); + entry->bump_luminance = glGetUniformLocation(entry->program, "bump_luminance"); + entry->texture_scale = glGetUniformLocation(entry->program, "texture_scale"); + entry->texture_lod_bias = glGetUniformLocation(entry->program, "texture_lod_bias"); + entry->screen_offset = glGetUniformLocation(entry->program, "screen_offset"); + entry->model_lights = glGetUniformLocation(entry->program, "model_lights"); + for (stage = 0; stage < GPU_STAGE_COUNT; stage++) + { + char name[8]; + + snprintf(name, sizeof(name), "tex%d", stage); + glUniform1i(glGetUniformLocation(entry->program, name), stage); + } + last = entry; + return entry; +} + +/* sets a program's uniform unless it already holds value */ +static void uniform_vec4(GLint location, float *shadow, const float *value, int count) +{ + if (location < 0 || !memcmp(shadow, value, (size_t)count * 4 * sizeof(float))) + return; + memcpy(shadow, value, (size_t)count * 4 * sizeof(float)); + glUniform4fv(location, count, value); +} + +static void uniform_float(GLint location, float *shadow, float value) +{ + if (location < 0 || !memcmp(shadow, &value, sizeof(value))) + return; + *shadow = value; + glUniform1f(location, value); +} + +/* ---------- vertex and index data */ + +#ifdef HALO_ANDROID +/* Mobile drivers (Mali) keep every orphaned copy of a buffer until the GPU +is done with it, so a large buffer orphaned each frame costs its size per +frame in flight and more. Instead each frame streams into the next of a few +smaller buffers, reusing one only once the GPU has finished the frame that +last used it (host_gl_wait_frame). A busy frame streams about 5 MB of +vertices. */ +#define STREAM_BUFFER_SIZE (16 * 1024 * 1024) +#define INDEX_BUFFER_SIZE (2 * 1024 * 1024) +#else +#define STREAM_BUFFER_SIZE (32 * 1024 * 1024) +#define INDEX_BUFFER_SIZE (8 * 1024 * 1024) +#endif + +static struct +{ + GLuint stream_buffer; +#ifdef HALO_ANDROID + GLuint stream_buffers[GPU_GL_FRAME_RING]; + GLuint index_buffers[GPU_GL_FRAME_RING]; + unsigned long buffer_ring; +#endif + unsigned long stream_offset; + GLuint index_buffer; + unsigned long index_offset; +#ifndef HALO_ANDROID + /* a pipeline flush every flush_every draws (draw_flush), 0 never */ + unsigned long flush_every; + unsigned long flush_draws; +#endif + /* frames presented (gl_check_errors) */ + unsigned long frame; +} streams; + +/* the vertex array the ES attributes are on, and the desktop's before its +first draw */ +GLuint gpu_gl_default_vertex_array; + +/* Mesa's GL thread queues a glBufferSubData of up to 8 KB; a larger one +first waits for everything queued before it to have run. The same bytes in +pieces are queued (all but the largest, as a map loads, which would be +thousands; Android's GL has no such thread). */ +#define BUFFER_UPLOAD_PIECE 4096 +#define BUFFER_UPLOAD_PIECES_MAXIMUM 16 + +static void buffer_upload(GLenum target, unsigned long offset, unsigned long size, const void *data) +{ +#ifndef HALO_ANDROID + if (size <= BUFFER_UPLOAD_PIECE * BUFFER_UPLOAD_PIECES_MAXIMUM) + { + const unsigned char *bytes = data; + + while (size) + { + unsigned long piece = size < BUFFER_UPLOAD_PIECE ? size : BUFFER_UPLOAD_PIECE; + + glBufferSubData(target, (GLintptr)offset, (GLsizeiptr)piece, bytes); + offset += piece; + bytes += piece; + size -= piece; + } + return; + } +#endif + glBufferSubData(target, (GLintptr)offset, (GLsizeiptr)size, data); +} + +gpu_buffer gpu_buffer_create(uint32_t size) +{ + GLuint buffer = 0; + + glGenBuffers(1, &buffer); + glBindBuffer(GL_COPY_WRITE_BUFFER, buffer); + glBufferData(GL_COPY_WRITE_BUFFER, (GLsizeiptr)size, NULL, GL_DYNAMIC_DRAW); + return buffer; +} + +void gpu_buffer_write(gpu_buffer buffer, uint32_t offset, uint32_t size, const void *data, uint32_t flags) +{ + glBindBuffer(GL_COPY_WRITE_BUFFER, buffer); +#ifdef HALO_ANDROID + /* Mali copies the whole buffer for a glBufferSubData that queued draws + might read (see GPU_GL_FRAME_RING); unused ranges can be written without + waiting for them */ + if (flags & GPU_WRITE_UNUSED) + { + host_gl_buffer_write(GL_COPY_WRITE_BUFFER, offset, size, data); + return; + } +#else + (void)flags; +#endif + buffer_upload(GL_COPY_WRITE_BUFFER, offset, size, data); +} + +/* makes room for size bytes of uploads, orphaning the stream buffer if it +is full. A draw reserves room for all of its streams at once: orphaning +between two of them would leave the attributes already pointed at the +buffer reading its new, empty storage. */ +void gpu_stream_reserve(uint32_t size) +{ + if (streams.stream_offset + size > STREAM_BUFFER_SIZE) + { + /* orphan the buffer and start again */ + state_array_buffer(streams.stream_buffer); + glBufferData(GL_ARRAY_BUFFER, STREAM_BUFFER_SIZE, NULL, GL_STREAM_DRAW); + streams.stream_offset = 0; + } +} + +uint32_t gpu_stream(uint32_t kind, const void *data, uint32_t size, gpu_buffer *buffer) +{ + unsigned long offset; + + size = (size + 15) & ~15U; + if (kind == GPU_STREAM_INDEX) + { + state_element_array_buffer(streams.index_buffer); + if (streams.index_offset + size > INDEX_BUFFER_SIZE) + { + glBufferData(GL_ELEMENT_ARRAY_BUFFER, INDEX_BUFFER_SIZE, NULL, GL_STREAM_DRAW); + streams.index_offset = 0; + } + offset = streams.index_offset; +#ifdef HALO_ANDROID + host_gl_buffer_write(GL_ELEMENT_ARRAY_BUFFER, (unsigned int)offset, (unsigned int)size, data); +#else + buffer_upload(GL_ELEMENT_ARRAY_BUFFER, offset, size, data); +#endif + streams.index_offset += size; + *buffer = streams.index_buffer; + return (uint32_t)offset; + } + gpu_stream_reserve(size); + offset = streams.stream_offset; + state_array_buffer(streams.stream_buffer); +#ifdef HALO_ANDROID + host_gl_buffer_write(GL_ARRAY_BUFFER, (unsigned int)offset, (unsigned int)size, data); +#else + buffer_upload(GL_ARRAY_BUFFER, offset, size, data); +#endif + streams.stream_offset += size; + *buffer = streams.stream_buffer; + return (uint32_t)offset; +} + +unsigned long gpu_gl_frame_ring(void) +{ +#ifdef HALO_ANDROID + return streams.buffer_ring; +#else + return 0; +#endif +} + +void gpu_gl_frame_advance(void) +{ + streams.frame++; +#ifdef HALO_ANDROID + host_gl_fence_frame((unsigned int)streams.buffer_ring); + streams.buffer_ring = (streams.buffer_ring + 1) % GPU_GL_FRAME_RING; + host_gl_wait_frame((unsigned int)streams.buffer_ring); + streams.stream_buffer = streams.stream_buffers[streams.buffer_ring]; + streams.index_buffer = streams.index_buffers[streams.buffer_ring]; + streams.stream_offset = 0; + streams.index_offset = 0; +#else + streams.stream_offset = STREAM_BUFFER_SIZE; /* orphan next frame */ + streams.index_offset = INDEX_BUFFER_SIZE; +#endif +} + +/* ---------- draws */ + +#ifdef HALO_ANDROID +/* ES has no debug callback in 3.0; debug.gl_debug polls glGetError around +each draw instead, reporting each distinct error a few times */ +static void gl_check_errors(const char *where) +{ + static int enabled = -1; + static unsigned long reports; + GLenum error; + + if (enabled < 0) + enabled = config_boolean("debug.gl_debug"); + if (!enabled) + return; + while ((error = glGetError()) != GL_NO_ERROR) + { + if (reports++ < 200) + platform_log("GL error %04x at %s (frame %lu)", (unsigned)error, where, streams.frame); + } +} +#else +#define gl_check_errors(where) ((void)0) +#endif + +/* Intel's graphics with Mesa's driver can hang the GPU in a long run of +draws with no pipeline flush between them, which the game's effects make +(hundreds of small draws in a row): the command streamer stops at a draw, +and the reset that follows takes the desktop's other programs with it. +Intel's workaround for a hang of this kind on their DG2 graphics +(Wa_16014538804) is a flush at least every 3 draws, which Mesa does not +apply to the others. A memory barrier is one (and only that: nothing +here writes images). */ +static void draw_flush(void) +{ +#ifndef HALO_ANDROID + if (streams.flush_every && ++streams.flush_draws >= streams.flush_every) + { + streams.flush_draws = 0; + glMemoryBarrier(GL_SHADER_IMAGE_ACCESS_BARRIER_BIT); + } +#endif +} + +static const GLenum gl_compare_functions[] = +{ + GL_NEVER, GL_LESS, GL_EQUAL, GL_LEQUAL, GL_GREATER, GL_NOTEQUAL, GL_GEQUAL, GL_ALWAYS, +}; + +static const GLenum gl_stencil_operations[] = +{ + GL_KEEP, GL_ZERO, GL_REPLACE, GL_INCR, GL_DECR, GL_INVERT, GL_INCR_WRAP, GL_DECR_WRAP, +}; + +static const GLenum gl_blend_factors[] = +{ + GL_ZERO, GL_ONE, GL_SRC_COLOR, GL_ONE_MINUS_SRC_COLOR, GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, + GL_DST_ALPHA, GL_ONE_MINUS_DST_ALPHA, GL_DST_COLOR, GL_ONE_MINUS_DST_COLOR, GL_SRC_ALPHA_SATURATE, + GL_CONSTANT_COLOR, GL_ONE_MINUS_CONSTANT_COLOR, GL_CONSTANT_ALPHA, GL_ONE_MINUS_CONSTANT_ALPHA, +}; + +static const GLenum gl_blend_equations[] = { GL_FUNC_ADD, GL_FUNC_SUBTRACT, GL_FUNC_REVERSE_SUBTRACT, GL_MIN, GL_MAX }; + +#define TABLE_ENTRY(table, index) ((table)[(index) < sizeof(table) / sizeof((table)[0]) ? (index) : 0]) + +static void apply_raster_state(const struct gpu_draw *draw) +{ + const struct gpu_depth_stencil_state *depth_stencil = &draw->depth_stencil; + const struct gpu_blend_state *blend = &draw->blend; + const struct gpu_raster_state *raster = &draw->raster; + GLint viewport[4]; + GLint scissor[4]; + float depth_range[2]; + + viewport[0] = draw->viewport.rect.x; + viewport[1] = draw->viewport.rect.y; + viewport[2] = draw->viewport.rect.width; + viewport[3] = draw->viewport.rect.height; + if (memcmp(gl_state.viewport, viewport, sizeof(viewport))) + { + memcpy(gl_state.viewport, viewport, sizeof(viewport)); + glViewport(viewport[0], viewport[1], viewport[2], viewport[3]); + } + scissor[0] = draw->scissor.x; + scissor[1] = draw->scissor.y; + scissor[2] = draw->scissor.width; + scissor[3] = draw->scissor.height; + if (memcmp(gl_state.scissor, scissor, sizeof(scissor))) + { + memcpy(gl_state.scissor, scissor, sizeof(scissor)); + glScissor(scissor[0], scissor[1], scissor[2], scissor[3]); + } + state_enable(&gl_state.scissor_test, GL_SCISSOR_TEST, scissor[2] > 0 && scissor[3] > 0); + depth_range[0] = draw->viewport.min_z; + depth_range[1] = draw->viewport.max_z; + if (memcmp(gl_state.depth_range, depth_range, sizeof(depth_range))) + { + memcpy(gl_state.depth_range, depth_range, sizeof(depth_range)); + glDepthRange(depth_range[0], depth_range[1]); + } + + state_enable(&gl_state.depth_test, GL_DEPTH_TEST, depth_stencil->depth_test); + if (depth_stencil->depth_test) + { + GLenum function = TABLE_ENTRY(gl_compare_functions, depth_stencil->depth_function); + + if (gl_state.depth_function != function) + { + gl_state.depth_function = function; + glDepthFunc(function); + } + } + { + unsigned char mask = depth_stencil->depth_test && depth_stencil->depth_write ? 1 : 0; + + if (gl_state.depth_mask != mask) + { + gl_state.depth_mask = mask; + glDepthMask(mask ? GL_TRUE : GL_FALSE); + } + } + + state_enable(&gl_state.stencil_test, GL_STENCIL_TEST, depth_stencil->stencil_test); + if (depth_stencil->stencil_test) + { + GLenum function = TABLE_ENTRY(gl_compare_functions, depth_stencil->stencil_function); + GLenum operations[3]; + + if (gl_state.stencil_function != function || gl_state.stencil_reference != (GLint)depth_stencil->stencil_reference || + gl_state.stencil_value_mask != depth_stencil->stencil_read_mask) + { + gl_state.stencil_function = function; + gl_state.stencil_reference = (GLint)depth_stencil->stencil_reference; + gl_state.stencil_value_mask = depth_stencil->stencil_read_mask; + glStencilFunc(function, (GLint)depth_stencil->stencil_reference, depth_stencil->stencil_read_mask); + } + operations[0] = TABLE_ENTRY(gl_stencil_operations, depth_stencil->stencil_fail); + operations[1] = TABLE_ENTRY(gl_stencil_operations, depth_stencil->stencil_depth_fail); + operations[2] = TABLE_ENTRY(gl_stencil_operations, depth_stencil->stencil_pass); + if (memcmp(gl_state.stencil_operations, operations, sizeof(operations))) + { + memcpy(gl_state.stencil_operations, operations, sizeof(operations)); + glStencilOp(operations[0], operations[1], operations[2]); + } + if (gl_state.stencil_write_mask != depth_stencil->stencil_write_mask) + { + gl_state.stencil_write_mask = depth_stencil->stencil_write_mask; + glStencilMask(depth_stencil->stencil_write_mask); + } + } + + state_enable(&gl_state.blend, GL_BLEND, blend->enable); + if (blend->enable) + { + GLenum source = TABLE_ENTRY(gl_blend_factors, blend->source); + GLenum destination = TABLE_ENTRY(gl_blend_factors, blend->destination); + GLenum equation = TABLE_ENTRY(gl_blend_equations, blend->operation); + float blend_color[4]; + + if (gl_state.blend_source != source || gl_state.blend_destination != destination) + { + gl_state.blend_source = source; + gl_state.blend_destination = destination; + glBlendFunc(source, destination); + } + if (gl_state.blend_equation != equation) + { + gl_state.blend_equation = equation; + glBlendEquation(equation); + } + color_to_vec4(blend->color, blend_color); + if (memcmp(gl_state.blend_color, blend_color, sizeof(blend_color))) + { + memcpy(gl_state.blend_color, blend_color, sizeof(blend_color)); + glBlendColor(blend_color[0], blend_color[1], blend_color[2], blend_color[3]); + } + } + if (gl_state.color_mask != blend->color_write_mask) + { + unsigned char color_mask = blend->color_write_mask; + + gl_state.color_mask = color_mask; + glColorMask((color_mask & 1) != 0, (color_mask & 2) != 0, (color_mask & 4) != 0, (color_mask & 8) != 0); + } + + state_enable(&gl_state.cull_face, GL_CULL_FACE, raster->cull_mode != GPU_CULL_NONE); + if (raster->cull_mode != GPU_CULL_NONE) + { +#ifdef HALO_ANDROID + /* the vertex shader flips y in clip space, which (unlike desktop + GL's upper-left clip origin) also flips the winding */ + GLenum front_face = raster->front_face == GPU_FRONT_COUNTER_CLOCKWISE ? GL_CW : GL_CCW; +#else + GLenum front_face = raster->front_face == GPU_FRONT_COUNTER_CLOCKWISE ? GL_CCW : GL_CW; +#endif + GLenum cull_mode = raster->cull_mode == GPU_CULL_FRONT ? GL_FRONT : GL_BACK; + + if (gl_state.front_face != front_face) + { + gl_state.front_face = front_face; + glFrontFace(front_face); + } + if (gl_state.cull_mode != cull_mode) + { + gl_state.cull_mode = cull_mode; + glCullFace(cull_mode); + } + } +#ifndef HALO_ANDROID + /* ES draws filled polygons only (wireframe is a debug mode) */ + { + GLenum polygon_mode = raster->fill_mode == GPU_FILL_LINE ? GL_LINE : + raster->fill_mode == GPU_FILL_POINT ? GL_POINT : GL_FILL; + + if (gl_state.polygon_mode != polygon_mode) + { + gl_state.polygon_mode = polygon_mode; + glPolygonMode(GL_FRONT_AND_BACK, polygon_mode); + } + } +#endif + + state_enable(&gl_state.offset_fill, GL_POLYGON_OFFSET_FILL, raster->depth_bias); +#ifndef HALO_ANDROID + state_enable(&gl_state.offset_line, GL_POLYGON_OFFSET_LINE, raster->depth_bias); +#endif + if (raster->depth_bias) + { + float offset[2]; + + offset[0] = raster->depth_bias_slope; + offset[1] = raster->depth_bias_constant; + if (memcmp(gl_state.polygon_offset, offset, sizeof(offset))) + { + memcpy(gl_state.polygon_offset, offset, sizeof(offset)); + glPolygonOffset(offset[0], offset[1]); + } + } +} + +/* the vertex constant register of each of the per-pixel lighting's lights */ +static unsigned long model_light_register(int light) +{ + return (unsigned long)(96 + (light ? -80 + light : -82)); +} + +/* the constants and the other uniforms a program has not had yet */ +static void upload_uniforms(struct program_entry *entry, struct gpu_constant_store *store, + const struct gpu_uniforms *uniforms) +{ + if (entry->constants >= 0 && entry->constants_serial != store->serial) + { + unsigned long first = entry->constant_count, last = 0, index; + + if (entry->constants_serial == store->checkpoint_serial && + store->checkpoint_last < entry->constant_count) + { + if (store->checkpoint_first <= store->checkpoint_last) + { + first = store->checkpoint_first; + last = store->checkpoint_last; + } + } + else if (store->serial - entry->constants_serial <= GPU_CONSTANT_COUNT) + { + unsigned long long serial; + + for (serial = entry->constants_serial + 1; serial <= store->serial; serial++) + { + index = store->log[serial % GPU_CONSTANT_LOG_SIZE]; + if (index >= entry->constant_count) + continue; + if (first > index) + first = index; + if (last < index) + last = index; + } + } + else + { + for (index = 0; index < entry->constant_count; index++) + { + if (store->serials[index] > entry->constants_serial) + { + if (first > index) + first = index; + last = index; + } + } + } + if (first < entry->constant_count) + { + if (entry->constants_consecutive) + glUniform4fv(entry->constants + (GLint)first, (GLsizei)(last - first + 1), store->c[first]); + else + glUniform4fv(entry->constants, GPU_CONSTANT_COUNT, &store->c[0][0]); + } + entry->constants_serial = store->serial; + store->checkpoint_serial = store->serial; + store->checkpoint_first = GPU_CONSTANT_COUNT; + store->checkpoint_last = 0; + } + /* the lights of a draw lit for each pixel, from the same registers, when + any of them changed since the program last had them */ + if (entry->model_lights >= 0 && entry->model_lights_serial != store->serial) + { + int light; + + for (light = 0; light < GPU_MODEL_LIGHT_COUNT; light++) + { + if (store->serials[model_light_register(light)] > entry->model_lights_serial) + break; + } + if (light < GPU_MODEL_LIGHT_COUNT) + { + float lights[GPU_MODEL_LIGHT_COUNT][4]; + + for (light = 0; light < GPU_MODEL_LIGHT_COUNT; light++) + memcpy(lights[light], store->c[model_light_register(light)], sizeof(lights[0])); + glUniform4fv(entry->model_lights, GPU_MODEL_LIGHT_COUNT, lights[0]); + } + entry->model_lights_serial = store->serial; + } + /* a program that has had the other uniforms since they last changed + needs none of them */ + if (entry->uniforms_serial == uniforms->serial) + return; + entry->uniforms_serial = uniforms->serial; + uniform_vec4(entry->viewport_scale, entry->uniforms.viewport_scale, uniforms->viewport_scale, 1); + uniform_vec4(entry->viewport_offset, entry->uniforms.viewport_offset, uniforms->viewport_offset, 1); + uniform_float(entry->point_size, &entry->uniforms.point_size, uniforms->point_size); + uniform_vec4(entry->ps_c0, entry->uniforms.ps_c0[0], uniforms->ps_c0[0], 8); + uniform_vec4(entry->ps_c1, entry->uniforms.ps_c1[0], uniforms->ps_c1[0], 8); + uniform_vec4(entry->ps_final_c0, entry->uniforms.ps_final_c0, uniforms->ps_final_c0, 1); + uniform_vec4(entry->ps_final_c1, entry->uniforms.ps_final_c1, uniforms->ps_final_c1, 1); + uniform_vec4(entry->fog_color, entry->uniforms.fog_color, uniforms->fog_color, 1); + uniform_vec4(entry->fog_parameters, entry->uniforms.fog_parameters, uniforms->fog_parameters, 1); + uniform_float(entry->alpha_reference, &entry->uniforms.alpha_reference, uniforms->alpha_reference); + uniform_vec4(entry->bump_matrix, entry->uniforms.bump_matrix[0], uniforms->bump_matrix[0], 4); + uniform_vec4(entry->bump_luminance, entry->uniforms.bump_luminance[0], uniforms->bump_luminance[0], 4); + uniform_vec4(entry->texture_scale, entry->uniforms.texture_scale[0], uniforms->texture_scale[0], 4); + uniform_float(entry->screen_offset, &entry->uniforms.screen_offset, uniforms->screen_offset); + uniform_vec4(entry->texture_lod_bias, entry->uniforms.texture_lod_bias, uniforms->texture_lod_bias, 1); +} + +static void attribute_format(unsigned char format, GLint *size, GLenum *type, GLboolean *normalized) +{ + *normalized = GL_FALSE; + switch (format) + { + case GPU_ATTRIBUTE_FLOAT1: *size = 1; *type = GL_FLOAT; break; + case GPU_ATTRIBUTE_FLOAT2: *size = 2; *type = GL_FLOAT; break; + case GPU_ATTRIBUTE_FLOAT3: *size = 3; *type = GL_FLOAT; break; + case GPU_ATTRIBUTE_BGRA8: *size = GL_BGRA; *type = GL_UNSIGNED_BYTE; *normalized = GL_TRUE; break; + case GPU_ATTRIBUTE_RGBA8: *size = 4; *type = GL_UNSIGNED_BYTE; *normalized = GL_TRUE; break; + case GPU_ATTRIBUTE_SHORT1: *size = 1; *type = GL_SHORT; break; + case GPU_ATTRIBUTE_SHORT2: *size = 2; *type = GL_SHORT; break; + case GPU_ATTRIBUTE_SHORT3: *size = 3; *type = GL_SHORT; break; + case GPU_ATTRIBUTE_SHORT4: *size = 4; *type = GL_SHORT; break; + case GPU_ATTRIBUTE_NORMSHORT1: *size = 1; *type = GL_SHORT; *normalized = GL_TRUE; break; + case GPU_ATTRIBUTE_NORMSHORT2: *size = 2; *type = GL_SHORT; *normalized = GL_TRUE; break; + case GPU_ATTRIBUTE_NORMSHORT3: *size = 3; *type = GL_SHORT; *normalized = GL_TRUE; break; + case GPU_ATTRIBUTE_NORMSHORT4: *size = 4; *type = GL_SHORT; *normalized = GL_TRUE; break; + case GPU_ATTRIBUTE_UBYTE1: *size = 1; *type = GL_UNSIGNED_BYTE; *normalized = GL_TRUE; break; + case GPU_ATTRIBUTE_UBYTE2: *size = 2; *type = GL_UNSIGNED_BYTE; *normalized = GL_TRUE; break; + case GPU_ATTRIBUTE_UBYTE3: *size = 3; *type = GL_UNSIGNED_BYTE; *normalized = GL_TRUE; break; + case GPU_ATTRIBUTE_UBYTE4: *size = 4; *type = GL_UNSIGNED_BYTE; *normalized = GL_TRUE; break; + default: *size = 4; *type = GL_FLOAT; break; + } +} + +/* points the attributes at the draw's streams, and gives the others their +values */ +static void setup_streams(const struct gpu_draw *draw) +{ + unsigned long index; +#ifndef HALO_ANDROID + /* the vertex array of the draw before, and the layout it was for: the + same declaration draws again and again */ + static struct vertex_array_entry *last_array; + static struct vertex_layout last_layout; + struct vertex_layout layout; + unsigned long streams_used = 0; + + /* (zeroed: layouts are compared and hashed whole) */ + memset(&layout, 0, sizeof(layout)); +#endif + for (index = 0; index < GPU_ATTRIBUTE_COUNT; index++) + { + const struct gpu_vertex_attribute *attribute = &draw->attributes[index]; + GLint size; + GLenum type; + GLboolean normalized; + + if (attribute->stream >= GPU_STREAM_COUNT) + continue; +#ifdef HALO_ANDROID + { + const struct gpu_vertex_stream *stream = &draw->streams[attribute->stream]; + + if (attribute->format == GPU_ATTRIBUTE_NORMPACKED3) + { + state_attribute_stream((GLuint)index, attribute->stream, stream->buffer, 1, GL_UNSIGNED_INT, GL_FALSE, + TRUE, (GLsizei)stream->stride, stream->offset, attribute->offset); + } + else + { + attribute_format(attribute->format, &size, &type, &normalized); + state_attribute_stream((GLuint)index, attribute->stream, stream->buffer, size, type, normalized, + FALSE, (GLsizei)stream->stride, stream->offset, attribute->offset); + } + } +#else + if (attribute->format == GPU_ATTRIBUTE_NORMPACKED3) + { + layout_attribute(&layout, index, attribute->stream, 1, GL_UNSIGNED_INT, GL_FALSE, TRUE, attribute->offset); + } + else + { + attribute_format(attribute->format, &size, &type, &normalized); + layout_attribute(&layout, index, attribute->stream, size, type, normalized, FALSE, attribute->offset); + } + streams_used |= 1UL << attribute->stream; +#endif + } +#ifndef HALO_ANDROID + /* (the layout follows from the declaration and the streams it has, so + a draw mostly has the layout of the draw before it: no need to look it + up) */ + if (!last_array || memcmp(&last_layout, &layout, sizeof(layout))) + { + last_array = vertex_array_get(&layout); + last_layout = layout; + } + state_vertex_array(last_array); + for (index = 0; index < GPU_STREAM_COUNT; index++) + { + if (streams_used & (1UL << index)) + state_vertex_buffer((GLuint)index, draw->streams[index].buffer, draw->streams[index].offset, + (GLsizei)draw->streams[index].stride); + } +#endif + for (index = 0; index < GPU_ATTRIBUTE_COUNT; index++) + { + const struct gpu_vertex_attribute *attribute = &draw->attributes[index]; + + if (attribute->stream == GPU_STREAM_CONSTANT) + state_attribute_value((GLuint)index, draw->constant_values[index]); + else if (attribute->stream >= GPU_STREAM_COUNT) + state_attribute_value((GLuint)index, NULL); + } +} + +static const GLenum gl_primitives[] = +{ + GL_POINTS, GL_LINES, GL_LINE_LOOP, GL_LINE_STRIP, GL_TRIANGLES, GL_TRIANGLE_STRIP, GL_TRIANGLE_FAN, +}; + +uint32_t gpu_draw(const struct gpu_draw *draw, struct gpu_constant_store *constants, + const struct gpu_uniforms *uniforms) +{ + struct program_entry *entry; + GLenum mode = TABLE_ENTRY(gl_primitives, draw->primitive); + int stage; + + /* the textures before the targets: a render target the draw samples + has its multisampled pixels resolved by a blit, which binds framebuffers + of its own, and the back buffer can be both sampled and drawn into */ + for (stage = 0; stage < GPU_STAGE_COUNT; stage++) + { + if (draw->stages[stage].type) + texture_resolve(draw->stages[stage].texture); + } + gpu_gl_bind_targets(draw->color_target, draw->depth_target, draw->samples); + apply_raster_state(draw); + entry = program_get(draw->vertex_shader, draw->pixel_shader); + if (!entry) + { + gl_check_errors("program"); + return 0; + } + gl_check_errors("state"); + draw_flush(); + state_program(entry->program); + upload_uniforms(entry, constants, uniforms); + bind_stages(draw->stages); + setup_streams(draw); + if (!draw->index_buffer) + { + glDrawArrays(mode, 0, (GLsizei)draw->count); + } + else + { + state_element_array_buffer(draw->index_buffer); + if (draw->base_vertex) + glDrawElementsBaseVertex(mode, (GLsizei)draw->count, GL_UNSIGNED_SHORT, + (const void *)(uintptr_t)draw->index_offset, draw->base_vertex); + else + glDrawElements(mode, (GLsizei)draw->count, GL_UNSIGNED_SHORT, (const void *)(uintptr_t)draw->index_offset); + } + gl_check_errors("draw"); + return 1; +} + /* ---------- the backend */ #ifndef HALO_ANDROID @@ -1000,8 +1972,55 @@ void gpu_initialize(struct gpu_capabilities *capabilities) capabilities->base_vertex = 1; capabilities->occlusion = GPU_OCCLUSION_EXACT; capabilities->sample_mask = 1; +#endif + glGenVertexArrays(1, &gpu_gl_default_vertex_array); + glBindVertexArray(gpu_gl_default_vertex_array); +#ifdef HALO_ANDROID + { + int ring; + + glGenBuffers(GPU_GL_FRAME_RING, streams.stream_buffers); + glGenBuffers(GPU_GL_FRAME_RING, streams.index_buffers); + for (ring = 0; ring < GPU_GL_FRAME_RING; ring++) + { + glBindBuffer(GL_ARRAY_BUFFER, streams.stream_buffers[ring]); + glBufferData(GL_ARRAY_BUFFER, STREAM_BUFFER_SIZE, NULL, GL_STREAM_DRAW); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, streams.index_buffers[ring]); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, INDEX_BUFFER_SIZE, NULL, GL_STREAM_DRAW); + } + streams.stream_buffer = streams.stream_buffers[0]; + streams.index_buffer = streams.index_buffers[0]; + } +#else + glGenBuffers(1, &streams.stream_buffer); + glBindBuffer(GL_ARRAY_BUFFER, streams.stream_buffer); + glBufferData(GL_ARRAY_BUFFER, STREAM_BUFFER_SIZE, NULL, GL_STREAM_DRAW); + glGenBuffers(1, &streams.index_buffer); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, streams.index_buffer); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, INDEX_BUFFER_SIZE, NULL, GL_STREAM_DRAW); + { + long every = config_integer("debug.gpu_flush_draws"); + const char *renderer = (const char *)glGetString(GL_RENDERER); + + if (every < 0) + every = renderer && strstr(renderer, "Mesa Intel") ? 3 : 0; + if (every > 0) + { + streams.flush_every = (unsigned long)every; + platform_log("GPU: a pipeline flush every %ld draws", every); + } + } #endif glGenSamplers(GPU_STAGE_COUNT, stage_samplers); + { + /* an attribute a draw does not stream reads (0, 0, 0, 1) until + given a value, as the Xbox's registers start */ + static const float origin[4] = { 0.0f, 0.0f, 0.0f, 1.0f }; + GLuint index; + + for (index = 0; index < GPU_ATTRIBUTE_COUNT; index++) + glVertexAttrib4fv(index, origin); + } { GLint samples = 0, texture_size = 0, renderbuffer_size = 0; diff --git a/port/linux/src/gpu_gl.h b/port/linux/src/gpu_gl.h index 4ac5dfcb37..3cba6ac13c 100644 --- a/port/linux/src/gpu_gl.h +++ b/port/linux/src/gpu_gl.h @@ -2,9 +2,9 @@ GPU_GL.H The OpenGL backend's own declarations (gpu_gl.c), for the files that still -make GL calls besides it: the device (d3d8_gl.c), whose draws, clears and -presentation do not go through gpu.h yet, and the anti-aliasing passes -(xgpu_post.c). +make GL calls besides it: the device (d3d8_gl.c), whose clears, visibility +tests and presentation do not go through gpu.h yet, and the anti-aliasing +passes (xgpu_post.c). */ #ifndef __HALO_LINUX_GPU_GL_H @@ -47,6 +47,10 @@ struct xgpu_capabilities extern struct xgpu_capabilities xgpu_capabilities; +/* the frames whose stream buffers (and visibility counts) take turns: one +is reused only once the GPU has finished the frame that last used it */ +#define GPU_GL_FRAME_RING 3 + /* port/android/guest/runtime/guest_host.h */ int host_gl_has_extension(const char *name); void host_gl_read_buffer(unsigned int buffer, unsigned int offset, unsigned int size, void *data); @@ -55,131 +59,14 @@ void host_gl_fence_frame(unsigned int slot); void host_gl_wait_frame(unsigned int slot); #endif -/* ---------- GL state cache - -Consecutive draws share most of their state, but each sets all of it: the -setters here skip the call when GL already holds the value. Code that -changes GL state behind the cache's back (clears, presentation, texture -uploads, render target and framebuffer creation) calls -xgpu_gl_state_invalidate, after which every value is set again. -Unknown values are all ones, which no real value matches (floats become -NaN, which compares unequal to everything). */ - -#ifdef HALO_ANDROID -struct attribute_pointer -{ - GLuint buffer; - GLint size; - GLenum type; - GLboolean normalized; - GLboolean integer; - GLsizei stride; - unsigned long offset; -}; -#else -/* desktop GL (4.3) separates an attribute's format from the buffer it -reads: the attributes of a stream share one binding, so a draw that moves -the stream rebinds it once instead of pointing each attribute again */ -struct attribute_format -{ - GLint size; - GLenum type; - GLboolean normalized; - GLboolean integer; - GLuint relative_offset; - GLuint binding; -}; - -struct vertex_binding -{ - GLuint buffer; - unsigned long offset; - GLsizei stride; -}; - -/* a binding per stream (setup_streams); GL has at least 16 */ -#define VERTEX_BINDING_COUNT 16 - -/* a vertex array object for each vertex layout (the attributes a draw -enables, and each one's format and binding), made once: a draw binds its -layout's and points the bindings at its streams, rather than enabling, -formatting and binding each attribute again (setup_streams) */ -struct vertex_layout -{ - unsigned long enabled; - struct attribute_format formats[GPU_ATTRIBUTE_COUNT]; -}; - -struct vertex_array_entry -{ - struct vertex_array_entry *next; - unsigned long hash; - struct vertex_layout layout; - GLuint vertex_array; - /* its bindings, as last pointed (nothing else changes them) */ - struct vertex_binding bindings[VERTEX_BINDING_COUNT]; -}; -#endif - -struct gl_state -{ - GLuint program; - GLuint framebuffer; - GLint viewport[4]; - GLint scissor[4]; - float depth_range[2]; - unsigned char depth_test, stencil_test, blend, cull_face, offset_fill, offset_line; - unsigned char scissor_test; - GLenum depth_function; - unsigned char depth_mask; - GLenum stencil_function; - GLint stencil_reference; - GLuint stencil_value_mask; - GLenum stencil_operations[3]; - GLuint stencil_write_mask; - GLenum blend_source, blend_destination, blend_equation; - float blend_color[4]; - unsigned char color_mask; - GLenum front_face, cull_mode, polygon_mode; - float polygon_offset[2]; - GLenum active_texture; - /* per unit: the GL_TEXTURE_2D, GL_TEXTURE_CUBE_MAP and GL_TEXTURE_3D - bindings */ - GLuint textures[GPU_STAGE_COUNT][3]; - GLuint samplers[GPU_STAGE_COUNT]; - GLuint array_buffer; - GLuint element_array_buffer; - unsigned char attribute_enabled[GPU_ATTRIBUTE_COUNT]; -#ifdef HALO_ANDROID - struct attribute_pointer attribute_pointers[GPU_ATTRIBUTE_COUNT]; -#else - GLuint vertex_array; -#endif - /* a disabled attribute's value; kind 1 is the integer zero */ - unsigned char attribute_value_kind[GPU_ATTRIBUTE_COUNT]; - float attribute_values[GPU_ATTRIBUTE_COUNT][4]; -}; +/* ---------- GL state -extern struct gl_state gl_state; +The backend caches the GL state it sets for draws; code that changes GL +state behind it (binding a texture to upload it, a blit) must call this +afterwards. */ void xgpu_gl_state_invalidate(void); -void state_enable(unsigned char *shadow, GLenum capability, BOOL enabled); -void state_program(GLuint program); -void state_array_buffer(GLuint buffer); -void state_element_array_buffer(GLuint buffer); -#ifdef HALO_ANDROID -void state_attribute_stream(GLuint index, GLuint binding, GLuint buffer, GLint size, GLenum type, - GLboolean normalized, BOOL integer, GLsizei stride, unsigned long buffer_offset, unsigned long relative_offset); -#else -struct vertex_array_entry *vertex_array_get(const struct vertex_layout *layout); -void state_vertex_array(struct vertex_array_entry *entry); -void state_vertex_buffer(GLuint binding, GLuint buffer, unsigned long offset, GLsizei stride); -void layout_attribute(struct vertex_layout *layout, unsigned long index, GLuint binding, GLint size, - GLenum type, GLboolean normalized, BOOL integer, unsigned long relative_offset); -#endif -void state_attribute_value(GLuint index, const float *value); - /* ---------- render targets */ /* the framebuffer of these render targets' textures (either may be 0) */ @@ -195,9 +82,24 @@ its pixels resolved into its texture first, and the texture's put into the new storage */ void gpu_gl_multisample(gpu_texture target, uint32_t samples); -/* ---------- texture stages */ +/* ---------- shaders */ + +/* a compiled shader, or a linked program of two, or 0 with the log +written */ +GLuint xgpu_compile_shader(GLenum type, const char *code, const char *what); +GLuint xgpu_link_program(GLuint vertex_shader, GLuint fragment_shader, const char *what); + +/* ---------- frames */ + +/* the vertex array the ES attributes are on, and the desktop's before its +first draw */ +extern GLuint gpu_gl_default_vertex_array; -/* binds each stage's texture and sampler */ -void gpu_gl_bind_stages(const struct gpu_stage stages[GPU_STAGE_COUNT]); +/* the frame's slot in the ring of stream buffers (GPU_GL_FRAME_RING; 0 on +the desktop) */ +unsigned long gpu_gl_frame_ring(void); +/* after a frame is presented: the stream buffers of the next (with OpenGL ES, +the next slot's, once the GPU has finished with them) */ +void gpu_gl_frame_advance(void); #endif diff --git a/port/linux/src/xgpu.h b/port/linux/src/xgpu.h index fdc1ca9357..a27679ac72 100644 --- a/port/linux/src/xgpu.h +++ b/port/linux/src/xgpu.h @@ -26,11 +26,6 @@ must call this afterwards. */ void xgpu_gl_state_invalidate(void); -/* a compiled shader, or a linked program of two, or 0 with the log -written */ -GLuint xgpu_compile_shader(GLenum type, const char *code, const char *what); -GLuint xgpu_link_program(GLuint vertex_shader, GLuint fragment_shader, const char *what); - /* ---------- the shading language The translators write the GLSL of the context the device has: desktop GL From 8f5c2eb03f6a7ff0a7b64f0706c9926ed4dcb030 Mon Sep 17 00:00:00 2001 From: Steve Rice Date: Thu, 8 Oct 2026 13:07:36 -0700 Subject: [PATCH 4/5] Clear, present and run visibility tests through the GPU backend gpu_clear takes the targets, what to clear and the rectangles in the targets' pixels; the device still clips the Xbox's rectangles to the viewport and scales them. gpu_present resolves the back buffer, letterboxes it into the window and starts the next frame, which on Android includes copying the visibility counters into the frame's snapshot and taking the next stream buffers once their fence has passed. Screenshots read the back buffer with gpu_texture_read, and the anti-aliasing passes run through gpu_anti_alias_prepare and gpu_anti_alias. The queries, the desktop's mapped result buffer and Android's atomic counters move to gpu_gl.c behind gpu_visibility_begin, gpu_visibility_end and gpu_visibility_result. The device keeps what the game sees: whether a slot has a test, the target's area each count is divided by, and the latest count known, so that an exact count is still turned into the game's pixels and Android's "any sample passed" into a large count, as before. The device makes no GL calls now except to reset the backend's cache once at startup. The GL calls are the same but for the order of a few that do not depend on each other. The GLSL is unchanged. Co-Authored-By: Claude Opus 5.5 --- port/linux/src/d3d8_gl.c | 371 +++++++++------------------------------ port/linux/src/gpu.h | 58 ++++++ port/linux/src/gpu_gl.c | 354 +++++++++++++++++++++++++++++++++---- port/linux/src/gpu_gl.h | 44 ++--- port/linux/src/xgpu.h | 17 -- 5 files changed, 481 insertions(+), 363 deletions(-) diff --git a/port/linux/src/d3d8_gl.c b/port/linux/src/d3d8_gl.c index 6952b289fd..9662ef276c 100644 --- a/port/linux/src/d3d8_gl.c +++ b/port/linux/src/d3d8_gl.c @@ -76,7 +76,7 @@ menus are drawn over it (halo_screen_anti_alias, xgpu_post.c); "ssaa2x" draws the screen's targets at twice the resolution each way (screen_mode_choose), which the display blit scales down; "msaa2x" to "msaa8x" draw the back buffer and its depth buffer with that many samples a -pixel (bind_targets, gpu_gl_bind_targets). The setting is read again between frames +pixel (bind_targets). The setting is read again between frames (halo_screen_commit), so that a change applies from the next one. */ enum @@ -355,13 +355,9 @@ static struct render_target_entry *render_targets; /* ---------- the device */ -#define VISIBILITY_TEST_SLOTS 4096 -#ifdef HALO_ANDROID -#define VISIBILITY_QUERY GL_ANY_SAMPLES_PASSED +#define VISIBILITY_TEST_SLOTS GPU_VISIBILITY_SLOTS +/* a count from a test that only says whether any sample passed */ #define VISIBILITY_ALL_SAMPLES 1000000 -#else -#define VISIBILITY_QUERY GL_SAMPLES_PASSED -#endif struct gl_device { @@ -400,47 +396,13 @@ struct gl_device unsigned long immediate_count; unsigned long immediate_capacity; - GLuint queries[VISIBILITY_TEST_SLOTS]; BOOL query_pending[VISIBILITY_TEST_SLOTS]; /* the pixels each of the game's pixels covered in the test's target - (render_target_get), which its count is divided by */ + (render_target_get), which an exact count is divided by */ float query_area[VISIBILITY_TEST_SLOTS]; - GLuint active_query; BOOL visibility_test_active; - /* (queries read on the CPU) each slot's latest count known, and whether - its query has yet to be read: the game spins on a result it is told is - incomplete, so a query is read only once it says it is available, and - until then the slot's earlier count stands. A frame of The Library's - lights makes hundreds of tests. */ - GLuint visibility_known[VISIBILITY_TEST_SLOTS]; - BOOL visibility_unread[VISIBILITY_TEST_SLOTS]; -#ifdef HALO_ANDROID - /* with atomic counters: one counter per test, used as a ring */ - GLuint visibility_counters; - unsigned long counter_next; - unsigned long counter_active; - /* Reading the counters waits for the draws that counted, which stops - the CPU until the GPU has caught up (the game asks at the start of the - next frame), halving the frame rate on drivers that queue frames (Zink, - Turnip). Instead, at the end of each frame the GPU copies them into the - frame's snapshot buffer of the stream ring, and the frame's tests (each - a result slot and its counter) are listed with it. Once its fence has - passed (two frames on, D3DDevice_Present) the CPU reads the snapshot - into counter_values and gives each listed slot its count: a result is - the latest count known, as the desktop's query buffer gives. */ - GLuint counter_snapshots[GPU_GL_FRAME_RING]; - unsigned short ring_tests[GPU_GL_FRAME_RING][VISIBILITY_TEST_SLOTS][2]; - unsigned long ring_test_count[GPU_GL_FRAME_RING]; - GLuint counter_values[VISIBILITY_TEST_SLOTS]; - GLuint visibility_latest[VISIBILITY_TEST_SLOTS]; -#else - /* each test's latest result, which the GPU writes (as a query buffer) - when the test's draws are done: the game waits for results at the start - of the next frame, and a query would stop the CPU there until the GPU - had caught up */ - GLuint visibility_results_buffer; - volatile GLuint *visibility_results; -#endif + /* each slot's latest count known (gpu_visibility_result) */ + uint32_t visibility_known[VISIBILITY_TEST_SLOTS]; unsigned long frame; unsigned long next_vertex_shader_id; @@ -734,7 +696,7 @@ struct xgpu_render_target *xgpu_render_target_find(unsigned long data) /* ---------- multisampling With display.anti_aliasing's multisampling, the back buffer and its depth -buffer are drawn into multisampled storage (gpu_gl_bind_targets), and so is +buffer are drawn into multisampled storage (bind_targets), and so is any target drawn together with one of them (the mirror's view goes to the secondary target with the back buffer's depth buffer): a framebuffer's attachments are all multisampled or none is. Nothing samples a depth buffer @@ -828,35 +790,6 @@ static void gl_initialize(void) shader_dialect.es = capabilities->shading_language_es != 0; shader_dialect.lookup_lod_bias = !capabilities->sampler_lod_bias; shader_dialect.clip_control = capabilities->shader_clip_control != 0; - glGenQueries(VISIBILITY_TEST_SLOTS, device.queries); -#ifndef HALO_ANDROID - glGenBuffers(1, &device.visibility_results_buffer); - glBindBuffer(GL_QUERY_BUFFER, device.visibility_results_buffer); - glBufferStorage(GL_QUERY_BUFFER, VISIBILITY_TEST_SLOTS * sizeof(GLuint), NULL, - GL_MAP_READ_BIT | GL_MAP_PERSISTENT_BIT | GL_MAP_COHERENT_BIT); - device.visibility_results = glMapBufferRange(GL_QUERY_BUFFER, 0, VISIBILITY_TEST_SLOTS * sizeof(GLuint), - GL_MAP_READ_BIT | GL_MAP_PERSISTENT_BIT | GL_MAP_COHERENT_BIT); - if (!device.visibility_results) - platform_log("cannot map the visibility test results; tests wait for the GPU"); -#endif -#ifdef HALO_ANDROID - if (xgpu_capabilities.atomic_counters) - { - int ring; - - glGenBuffers(1, &device.visibility_counters); - glBindBuffer(GL_ATOMIC_COUNTER_BUFFER, device.visibility_counters); - glBufferData(GL_ATOMIC_COUNTER_BUFFER, VISIBILITY_TEST_SLOTS * sizeof(GLuint), NULL, GL_DYNAMIC_DRAW); - glBindBuffer(GL_ATOMIC_COUNTER_BUFFER, 0); - glGenBuffers(GPU_GL_FRAME_RING, device.counter_snapshots); - for (ring = 0; ring < GPU_GL_FRAME_RING; ring++) - { - glBindBuffer(GL_COPY_WRITE_BUFFER, device.counter_snapshots[ring]); - glBufferData(GL_COPY_WRITE_BUFFER, VISIBILITY_TEST_SLOTS * sizeof(GLuint), NULL, GL_STREAM_READ); - } - glBindBuffer(GL_COPY_WRITE_BUFFER, 0); - } -#endif for (index = 0; index < XGPU_VERTEX_ATTRIBUTE_COUNT; index++) device.attributes[index][3] = 1.0f; memory_watch_initialize(); @@ -891,9 +824,11 @@ static void anti_aliasing_prepare(void) platform_log("anti-aliasing: the GPU has at most %d samples a pixel", maximum_samples); anti_aliasing_samples = maximum_samples < 2 ? 0 : maximum_samples; } - if ((mode == _anti_aliasing_fxaa || mode == _anti_aliasing_smaa) && !xgpu_post_prepare(mode == _anti_aliasing_smaa)) + if ((mode == _anti_aliasing_fxaa || mode == _anti_aliasing_smaa) && + !gpu_anti_alias_prepare(mode == _anti_aliasing_smaa ? GPU_ANTI_ALIAS_SMAA : GPU_ANTI_ALIAS_FXAA)) + { platform_log("anti-aliasing: its programs do not build, so the 3D view is not antialiased"); - xgpu_gl_state_invalidate(); + } } Direct3D *WINAPI Direct3DCreate8(UINT sdk_version) @@ -1267,7 +1202,7 @@ BOOL WINAPI D3DDevice_IsBusy(void) void WINAPI D3DDevice_KickPushBuffer(void) { if (device.gl_ready) - glFlush(); + gpu_flush(); } void WINAPI D3DDevice_InsertCallback(D3DCALLBACKTYPE type, D3DCALLBACK callback, DWORD context) @@ -1284,92 +1219,40 @@ void WINAPI D3DDevice_BeginVisibilityTest(void) { if (!device.gl_ready || device.visibility_test_active) return; - /* the query object is chosen when the test ends; use a scratch one */ device.visibility_test_active = TRUE; -#ifdef HALO_ANDROID - if (xgpu_capabilities.atomic_counters) - { - const GLuint zero = 0; - - device.counter_next = (device.counter_next + 1) % VISIBILITY_TEST_SLOTS; - device.counter_active = device.counter_next; - glBindBuffer(GL_ATOMIC_COUNTER_BUFFER, device.visibility_counters); - host_gl_buffer_write(GL_ATOMIC_COUNTER_BUFFER, (unsigned int)(device.counter_active * sizeof(GLuint)), - sizeof(zero), &zero); - glBindBuffer(GL_ATOMIC_COUNTER_BUFFER, 0); - return; - } -#endif - glBeginQuery(VISIBILITY_QUERY, device.queries[0]); + gpu_visibility_begin(); } HRESULT WINAPI D3DDevice_EndVisibilityTest(DWORD index) { - GLuint scratch; - if (!device.gl_ready || !device.visibility_test_active) return S_OK; device.visibility_test_active = FALSE; index %= VISIBILITY_TEST_SLOTS; if (!index) index = 1; -#ifdef HALO_ANDROID - if (xgpu_capabilities.atomic_counters) - { - unsigned long ring = gpu_gl_frame_ring(); - unsigned long *count = &device.ring_test_count[ring]; - - /* (a slot tested twice in a frame is listed twice: the later - counter, resolved after, wins) */ - if (*count < VISIBILITY_TEST_SLOTS) - { - device.ring_tests[ring][*count][0] = (unsigned short)index; - device.ring_tests[ring][*count][1] = (unsigned short)device.counter_active; - (*count)++; - } - device.query_pending[index] = TRUE; - return S_OK; - } -#endif - glEndQuery(VISIBILITY_QUERY); + gpu_visibility_end((uint32_t)index); /* the target's samples to a game pixel (its pixels, by its samples a - pixel with multisampling): the result is a count of the game's pixels - (visibility_unscaled), which the game divides by its own test's area - (lens flares, rasterizer_lights.c), a split-screen window's or the - screen's alike */ + pixel with multisampling): an exact count is turned into one of the + game's pixels (visibility_unscaled), which the game divides by its own + test's area (lens flares, rasterizer_lights.c), a split-screen window's + or the screen's alike */ device.query_area[index] = target_scale[0] * target_scale[1] * (float)target_samples; - /* swap the scratch query into the requested slot */ - scratch = device.queries[0]; - device.queries[0] = device.queries[index]; - device.queries[index] = scratch; device.query_pending[index] = TRUE; - device.visibility_unread[index] = TRUE; -#ifndef HALO_ANDROID - if (device.visibility_results) - { - /* the GPU writes the count into the slot once it is known (given - by name: Mesa's GL thread waits for everything before a - glGetQueryObjectuiv, even one into a bound buffer) */ - glGetQueryBufferObjectuiv(device.queries[index], device.visibility_results_buffer, GL_QUERY_RESULT, - (GLintptr)(index * sizeof(GLuint))); - } -#endif return S_OK; } -#ifndef HALO_ANDROID /* a count of pixels in the game's pixels */ -static GLuint visibility_unscaled(GLuint samples, DWORD index) +static uint32_t visibility_unscaled(uint32_t samples, DWORD index) { float area = device.query_area[index]; - return area > 1.0f ? (GLuint)(samples / area + 0.5f) : samples; + return area > 1.0f ? (uint32_t)(samples / area + 0.5f) : samples; } -#endif HRESULT WINAPI D3DDevice_GetVisibilityTestResult(DWORD index, UINT *result, ULONGLONG *time_stamp) { - GLuint available = 0, samples = 0; + uint32_t samples = 0; if (time_stamp) *time_stamp = 0; @@ -1382,46 +1265,27 @@ HRESULT WINAPI D3DDevice_GetVisibilityTestResult(DWORD index, UINT *result, ULON *result = 0; return S_OK; } -#ifdef HALO_ANDROID - if (xgpu_capabilities.atomic_counters) - { - /* the latest count the GPU has finished (counter_snapshots) */ - if (result) - *result = device.visibility_latest[index]; - return S_OK; - } -#endif -#ifndef HALO_ANDROID - if (device.visibility_results) - { - /* the latest count the GPU has written: from this test, or while - the GPU is still behind, from the slot's earlier ones */ - if (result) - *result = visibility_unscaled(device.visibility_results[index], index); - return S_OK; - } -#endif /* the latest count known: from this test, or while the GPU is still behind, from the slot's earlier ones */ - if (device.visibility_unread[index]) + if (gpu_visibility_result((uint32_t)index, &samples)) { - glGetQueryObjectuiv(device.queries[index], GL_QUERY_RESULT_AVAILABLE, &available); - if (available) + switch (xgpu_gpu_capabilities.occlusion) { - glGetQueryObjectuiv(device.queries[index], GL_QUERY_RESULT, &samples); -#ifdef HALO_ANDROID - /* ES only says whether any sample passed. The game divides the - count by the test's area (lens flare brightness, + case GPU_OCCLUSION_EXACT: + samples = visibility_unscaled(samples, index); + break; + case GPU_OCCLUSION_ANY_SAMPLE: + /* ES's queries only say whether any sample passed. The game + divides the count by the test's area (lens flare brightness, rasterizer_lights.c): report more than any test covers, well below what would overflow there. */ if (samples) samples = VISIBILITY_ALL_SAMPLES; -#else - samples = visibility_unscaled(samples, index); -#endif - device.visibility_known[index] = samples; - device.visibility_unread[index] = FALSE; + break; + default: + break; } + device.visibility_known[index] = samples; } if (result) *result = device.visibility_known[index]; @@ -2370,11 +2234,6 @@ static void submit_draw(struct gpu_draw *draw) { BOOL drawn; -#ifdef HALO_ANDROID - if (draw_shaders.key.count_samples) - glBindBufferRange(GL_ATOMIC_COUNTER_BUFFER, 0, device.visibility_counters, - (GLintptr)(device.counter_active * sizeof(GLuint)), sizeof(GLuint)); -#endif drawn = gpu_draw(draw, &vertex_constants, &draw_uniforms) != 0; if (!drawn && draw_shaders.lit) { @@ -3127,62 +2986,69 @@ void WINAPI D3DDevice_SetVertexDataColor(INT reg, D3DCOLOR color) void WINAPI D3DDevice_Clear(DWORD count, CONST D3DRECT *rectangles, DWORD flags, D3DCOLOR color, float z, DWORD stencil) { - float rgba[4]; - GLbitfield mask = 0; - gpu_texture color_target, depth_target; - uint32_t target_storage; + static struct gpu_rect *pixels; + static unsigned long pixels_capacity; + struct gpu_clear clear; + unsigned long pixel_count = 0; BOOL has_depth = FALSE; DWORD index; - if (!device.gl_ready || !bind_targets(&color_target, &depth_target, &target_storage, &has_depth)) + memset(&clear, 0, sizeof(clear)); + if (!device.gl_ready || !bind_targets(&clear.color_target, &clear.depth_target, &clear.samples, &has_depth)) return; - gpu_gl_bind_targets(color_target, depth_target, target_storage); if (trace_frame()) platform_log("clear flags %lx color %08lx z %g count %lu target %08lx depth %08lx", (unsigned long)flags, (unsigned long)color, z, (unsigned long)count, device.render_target ? (unsigned long)device.render_target->Data : 0, device.depth_stencil ? (unsigned long)device.depth_stencil->Data : 0); stats.clears++; - color_to_vec4(color, rgba); if (flags & D3DCLEAR_TARGET) { /* the Xbox clears the channels named (D3DCLEAR_TARGET_R, _G, _B, _A): the fog screen clears only alpha, leaving the picture under the fog */ - glColorMask((flags & D3DCLEAR_TARGET_R) != 0, (flags & D3DCLEAR_TARGET_G) != 0, - (flags & D3DCLEAR_TARGET_B) != 0, (flags & D3DCLEAR_TARGET_A) != 0); - glClearColor(rgba[0], rgba[1], rgba[2], rgba[3]); - mask |= GL_COLOR_BUFFER_BIT; + clear.flags |= GPU_CLEAR_COLOR; + clear.channel_mask = (uint8_t)(((flags & D3DCLEAR_TARGET_R) ? 1 : 0) | ((flags & D3DCLEAR_TARGET_G) ? 2 : 0) | + ((flags & D3DCLEAR_TARGET_B) ? 4 : 0) | ((flags & D3DCLEAR_TARGET_A) ? 8 : 0)); + clear.color = (uint32_t)color; } if (has_depth && (flags & D3DCLEAR_ZBUFFER)) { - glDepthMask(GL_TRUE); - glClearDepth(z); - mask |= GL_DEPTH_BUFFER_BIT; + clear.flags |= GPU_CLEAR_DEPTH; + clear.depth = z; } if (has_depth && (flags & D3DCLEAR_STENCIL)) { - glStencilMask(0xff); - glClearStencil((GLint)stencil); - mask |= GL_STENCIL_BUFFER_BIT; + clear.flags |= GPU_CLEAR_STENCIL; + clear.stencil = (uint32_t)stencil; } - if (!mask) + if (!clear.flags) + { + /* (the targets are bound all the same) */ + gpu_clear(&clear, NULL, 0); return; + } + if (pixels_capacity < (count ? count : 1)) + { + free(pixels); + pixels_capacity = count > 16 ? count : 16; + pixels = malloc(pixels_capacity * sizeof(*pixels)); + if (!pixels) + { + pixels_capacity = 0; + return; + } + } if (!count || !rectangles) { /* the NV2A clips a viewport-less clear to the viewport, which is what keeps a split-screen window's clear from wiping the other window */ - GLint x0 = target_pixel((float)device.viewport.X, 0); - GLint y0 = target_pixel((float)device.viewport.Y, 1); - - glEnable(GL_SCISSOR_TEST); - glScissor(x0, y0, target_pixel((float)(device.viewport.X + device.viewport.Width), 0) - x0, - target_pixel((float)(device.viewport.Y + device.viewport.Height), 1) - y0); - glClear(mask); - glDisable(GL_SCISSOR_TEST); - xgpu_gl_state_invalidate(); + pixels[0].x = target_pixel((float)device.viewport.X, 0); + pixels[0].y = target_pixel((float)device.viewport.Y, 1); + pixels[0].width = target_pixel((float)(device.viewport.X + device.viewport.Width), 0) - pixels[0].x; + pixels[0].height = target_pixel((float)(device.viewport.Y + device.viewport.Height), 1) - pixels[0].y; + gpu_clear(&clear, pixels, 1); return; } - glEnable(GL_SCISSOR_TEST); for (index = 0; index < count; index++) { INT left = rectangles[index].x1 > device.viewport.X ? rectangles[index].x1 : device.viewport.X; @@ -3191,18 +3057,17 @@ void WINAPI D3DDevice_Clear(DWORD count, CONST D3DRECT *rectangles, DWORD flags, rectangles[index].x2 : device.viewport.X + device.viewport.Width; INT bottom = rectangles[index].y2 < device.viewport.Y + device.viewport.Height ? rectangles[index].y2 : device.viewport.Y + device.viewport.Height; - GLint x0, y0; + struct gpu_rect *pixel = &pixels[pixel_count]; if (left >= right || top >= bottom) continue; - x0 = target_pixel((float)(left + UI_OFFSET), 0); - y0 = target_pixel((float)top, 1); - glScissor(x0, y0, target_pixel((float)(right + UI_OFFSET), 0) - x0, - target_pixel((float)bottom, 1) - y0); - glClear(mask); + pixel->x = target_pixel((float)(left + UI_OFFSET), 0); + pixel->y = target_pixel((float)top, 1); + pixel->width = target_pixel((float)(right + UI_OFFSET), 0) - pixel->x; + pixel->height = target_pixel((float)bottom, 1) - pixel->y; + pixel_count++; } - glDisable(GL_SCISSOR_TEST); - xgpu_gl_state_invalidate(); + gpu_clear(&clear, pixels, (uint32_t)pixel_count); } /* ---------- the anti-aliasing passes */ @@ -3213,7 +3078,7 @@ game's units of the screen */ void halo_screen_anti_alias(short x0, short y0, short x1, short y1) { struct render_target_entry *target; - GLint corners[4]; + int32_t corners[4]; int mode = anti_aliasing(); if (!device.gl_ready || (mode != _anti_aliasing_fxaa && mode != _anti_aliasing_smaa)) @@ -3223,16 +3088,13 @@ void halo_screen_anti_alias(short x0, short y0, short x1, short y1) if (!target) return; /* (no longer multisampled, if it was before the setting changed) */ - gpu_gl_multisample(target->target.texture, 0); target->target.samples = 0; corners[0] = scaled_pixel(x0, target->target.scale[0]); corners[1] = scaled_pixel(y0, target->target.scale[1]); corners[2] = scaled_pixel(x1, target->target.scale[0]); corners[3] = scaled_pixel(y1, target->target.scale[1]); - xgpu_post_anti_alias(mode == _anti_aliasing_smaa, gpu_gl_framebuffer(target->target.texture, 0), - target->target.gl_width, target->target.gl_height, corners); - glBindVertexArray(gpu_gl_default_vertex_array); - xgpu_gl_state_invalidate(); + gpu_anti_alias(mode == _anti_aliasing_smaa ? GPU_ANTI_ALIAS_SMAA : GPU_ANTI_ALIAS_FXAA, target->target.texture, + corners); } /* ---------- presentation */ @@ -3252,20 +3114,15 @@ static void write_screenshot(struct render_target_entry *target) if (!directory) return; pixels = malloc(image_size); - glBindFramebuffer(GL_READ_FRAMEBUFFER, gpu_gl_framebuffer(target->target.texture, 0)); - glReadPixels(0, 0, (GLsizei)width, (GLsizei)height, GL_BGRA, GL_UNSIGNED_BYTE, pixels); + if (!pixels || !gpu_texture_read(target->target.texture, pixels, (uint32_t)image_size)) + { + free(pixels); + return; + } /* the display ignores destination alpha, which the game uses as scratch; image viewers would show it as transparency */ for (row = 0; row < width * height; row++) - { -#ifdef HALO_ANDROID - unsigned char red = pixels[row * 4]; - - pixels[row * 4] = pixels[row * 4 + 2]; - pixels[row * 4 + 2] = red; -#endif pixels[row * 4 + 3] = 0xff; - } snprintf(path, sizeof(path), "%s/frame%05lu.bmp", directory, device.frame); file = fopen(path, "wb"); if (file) @@ -3301,74 +3158,14 @@ void WINAPI D3DDevice_Present(CONST RECT *source_rectangle, CONST RECT *destinat if (device.gl_ready) { struct render_target_entry *back_buffer = render_target_get(&device.back_buffer); - int window_width, window_height, width, height, x, y; if (trace_frame()) platform_log("present back buffer %08lx texture %u", (unsigned long)device.back_buffer.Data, back_buffer->target.texture); - gpu_gl_resolve(back_buffer->target.texture); if (screenshot_every > 0 && device.frame % (unsigned long)screenshot_every == 0) write_screenshot(back_buffer); - - platform_video_drawable_size(&window_width, &window_height); - /* letterbox to the back buffer's aspect ratio */ - width = window_width; - height = (int)((long)window_width * back_buffer->target.gl_height / back_buffer->target.gl_width); - if (height > window_height) - { - height = window_height; - width = (int)((long)window_height * back_buffer->target.gl_width / back_buffer->target.gl_height); - } - x = (window_width - width) / 2; - y = (window_height - height) / 2; - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); - glDisable(GL_SCISSOR_TEST); - glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); - glClearColor(0.0f, 0.0f, 0.0f, 1.0f); - glClear(GL_COLOR_BUFFER_BIT); - glBindFramebuffer(GL_READ_FRAMEBUFFER, gpu_gl_framebuffer(back_buffer->target.texture, 0)); - /* row 0 of the render target is the top of the picture */ - glBlitFramebuffer(0, 0, (GLint)back_buffer->target.gl_width, (GLint)back_buffer->target.gl_height, - x, y + height, x + width, y, GL_COLOR_BUFFER_BIT, GL_LINEAR); - platform_video_swap(); - xgpu_gl_state_invalidate(); + gpu_present(back_buffer->target.texture); xgpu_texture_cache_begin_frame(); -#ifdef HALO_ANDROID - if (xgpu_capabilities.atomic_counters) - { - /* this frame's counts, for when the GPU is done with it (the - barrier makes the shaders' counter writes visible to the copy, - which ES 3.1 does not promise without one) */ - glMemoryBarrier(GL_BUFFER_UPDATE_BARRIER_BIT); - glBindBuffer(GL_COPY_READ_BUFFER, device.visibility_counters); - glBindBuffer(GL_COPY_WRITE_BUFFER, device.counter_snapshots[gpu_gl_frame_ring()]); - glCopyBufferSubData(GL_COPY_READ_BUFFER, GL_COPY_WRITE_BUFFER, 0, 0, - VISIBILITY_TEST_SLOTS * sizeof(GLuint)); - glBindBuffer(GL_COPY_READ_BUFFER, 0); - glBindBuffer(GL_COPY_WRITE_BUFFER, 0); - } - /* the next frame's stream buffers, once the GPU has finished the - frame that last used them */ - gpu_gl_frame_advance(); - if (xgpu_capabilities.atomic_counters && device.ring_test_count[gpu_gl_frame_ring()]) - { - unsigned long ring = gpu_gl_frame_ring(); - unsigned long test; - - /* the GPU has passed that frame's fence: its copy is complete, - and the slot is free for this frame's tests */ - host_gl_read_buffer(device.counter_snapshots[ring], 0, VISIBILITY_TEST_SLOTS * sizeof(GLuint), - device.counter_values); - for (test = 0; test < device.ring_test_count[ring]; test++) - { - device.visibility_latest[device.ring_tests[ring][test][0]] = - device.counter_values[device.ring_tests[ring][test][1]]; - } - device.ring_test_count[ring] = 0; - } -#else - gpu_gl_frame_advance(); -#endif } device.frame++; stats.presents++; diff --git a/port/linux/src/gpu.h b/port/linux/src/gpu.h index c56e2c5534..88cdc88ac6 100644 --- a/port/linux/src/gpu.h +++ b/port/linux/src/gpu.h @@ -405,4 +405,62 @@ struct gpu_draw uint32_t gpu_draw(const struct gpu_draw *draw, struct gpu_constant_store *constants, const struct gpu_uniforms *uniforms); +/* ---------- clears */ + +/* gpu_clear.flags: what is cleared */ +enum { GPU_CLEAR_COLOR = 1, GPU_CLEAR_DEPTH = 2, GPU_CLEAR_STENCIL = 4 }; + +struct gpu_clear +{ + /* as a draw's (struct gpu_draw); depth and stencil are cleared only with + a depth target */ + gpu_texture color_target, depth_target; + uint32_t samples; + uint32_t flags; + /* the color channels cleared: bit 0 red, 1 green, 2 blue, 3 alpha */ + uint8_t channel_mask; + uint8_t pad[3]; + /* ARGB, as D3DCOLOR */ + uint32_t color; + float depth; + uint32_t stencil; +}; + +/* clears each of the rectangles of the targets' pixels (rows from the top), +whatever the draws' masks are; with no flags, it only makes the targets the +current ones */ +void gpu_clear(const struct gpu_clear *clear, const struct gpu_rect *rectangles, uint32_t count); + +/* ---------- visibility (occlusion) tests */ + +/* the slots a test's count is kept in; slot 0 is the backend's own */ +enum { GPU_VISIBILITY_SLOTS = 4096 }; + +/* the draws until gpu_visibility_end count their samples */ +void gpu_visibility_begin(void); +/* ... into slot (1 to GPU_VISIBILITY_SLOTS - 1) */ +void gpu_visibility_end(uint32_t slot); +/* 1, and in *samples the slot's latest count the GPU has finished (as the +capabilities' occlusion counts), if there is one not given before or the +backend keeps them all; else 0 */ +uint32_t gpu_visibility_result(uint32_t slot, uint32_t *samples); + +/* ---------- frames */ + +/* display.anti_aliasing's passes over a window's 3D view (xgpu_post.c) */ +enum { GPU_ANTI_ALIAS_FXAA = 1, GPU_ANTI_ALIAS_SMAA }; + +/* makes the pass's programs and textures now, rather than in the middle of a +frame; 0 if they cannot be made (once 0, always 0) */ +uint32_t gpu_anti_alias_prepare(uint32_t pass); +/* the pass, in place, on the corners x0, y0 to x1, y1 (rows from the top) of +a color render target, which stops being multisampled */ +void gpu_anti_alias(uint32_t pass, gpu_texture target, const int32_t corners[4]); + +/* sends the commands so far to the GPU */ +void gpu_flush(void); +/* shows the back buffer, letterboxed in the window, and starts the next +frame */ +void gpu_present(gpu_texture back_buffer); + #endif diff --git a/port/linux/src/gpu_gl.c b/port/linux/src/gpu_gl.c index 0c88a03d9b..13c69c71d8 100644 --- a/port/linux/src/gpu_gl.c +++ b/port/linux/src/gpu_gl.c @@ -11,6 +11,7 @@ texture before anything reads it (gpu_gl_resolve). #include "gpu_gl.h" #include "port_config.h" +#include "sdl_platform.h" #include #include @@ -735,11 +736,6 @@ static GLuint framebuffer_get(GLuint color, GLuint depth) return framebuffer_find(color, depth, FALSE); } -GLuint gpu_gl_framebuffer(gpu_texture color, gpu_texture depth) -{ - return framebuffer_get(color, depth); -} - /* ---------- multisampling A framebuffer's attachments are all multisampled or none is (the device @@ -822,17 +818,9 @@ static void texture_multisample(gpu_texture texture, int samples) xgpu_gl_state_invalidate(); } -void gpu_gl_resolve(gpu_texture target) -{ - texture_resolve(target); -} - -void gpu_gl_multisample(gpu_texture target, uint32_t samples) -{ - texture_multisample(target, (int)samples); -} - -void gpu_gl_bind_targets(gpu_texture color, gpu_texture depth, uint32_t samples) +/* draws go to these render targets (either may be 0), into their +multisampled storage with samples a pixel, or with 0 into their textures */ +static void bind_targets(gpu_texture color, gpu_texture depth, uint32_t samples) { if (color) texture_multisample(color, (int)samples); @@ -1257,7 +1245,7 @@ static struct /* the vertex array the ES attributes are on, and the desktop's before its first draw */ -GLuint gpu_gl_default_vertex_array; +static GLuint default_vertex_array; /* Mesa's GL thread queues a glBufferSubData of up to 8 KB; a larger one first waits for everything queued before it to have run. The same bytes in @@ -1367,16 +1355,9 @@ uint32_t gpu_stream(uint32_t kind, const void *data, uint32_t size, gpu_buffer * return (uint32_t)offset; } -unsigned long gpu_gl_frame_ring(void) -{ -#ifdef HALO_ANDROID - return streams.buffer_ring; -#else - return 0; -#endif -} - -void gpu_gl_frame_advance(void) +/* after a frame is presented: the stream buffers of the next (with OpenGL ES, +the next slot's, once the GPU has finished with them) */ +static void frame_advance(void) { streams.frame++; #ifdef HALO_ANDROID @@ -1393,6 +1374,144 @@ void gpu_gl_frame_advance(void) #endif } +/* ---------- visibility (occlusion) tests */ + +#ifdef HALO_ANDROID +#define VISIBILITY_QUERY GL_ANY_SAMPLES_PASSED +#else +#define VISIBILITY_QUERY GL_SAMPLES_PASSED +#endif + +static struct +{ + GLuint queries[GPU_VISIBILITY_SLOTS]; + BOOL active; + /* (queries read on the CPU) whether a slot's query has yet to be read: the + game spins on a result it is told is incomplete, so a query is read only + once it says it is available, and until then the slot's earlier count + stands. A frame of The Library's lights makes hundreds of tests. */ + BOOL unread[GPU_VISIBILITY_SLOTS]; +#ifdef HALO_ANDROID + /* with atomic counters: one counter per test, used as a ring */ + GLuint counters; + unsigned long counter_next; + unsigned long counter_active; + /* Reading the counters waits for the draws that counted, which stops + the CPU until the GPU has caught up (the game asks at the start of the + next frame), halving the frame rate on drivers that queue frames (Zink, + Turnip). Instead, at the end of each frame the GPU copies them into the + frame's snapshot buffer of the stream ring, and the frame's tests (each + a result slot and its counter) are listed with it. Once its fence has + passed (two frames on, gpu_present) the CPU reads the snapshot into + counter_values and gives each listed slot its count: a result is the + latest count known, as the desktop's query buffer gives. */ + GLuint counter_snapshots[GPU_GL_FRAME_RING]; + unsigned short ring_tests[GPU_GL_FRAME_RING][GPU_VISIBILITY_SLOTS][2]; + unsigned long ring_test_count[GPU_GL_FRAME_RING]; + GLuint counter_values[GPU_VISIBILITY_SLOTS]; + GLuint latest[GPU_VISIBILITY_SLOTS]; +#else + /* each test's latest result, which the GPU writes (as a query buffer) + when the test's draws are done: the game waits for results at the start + of the next frame, and a query would stop the CPU there until the GPU + had caught up */ + GLuint results_buffer; + volatile GLuint *results; +#endif +} visibility; + +void gpu_visibility_begin(void) +{ + /* the query object is chosen when the test ends; use a scratch one */ + visibility.active = TRUE; +#ifdef HALO_ANDROID + if (xgpu_capabilities.atomic_counters) + { + const GLuint zero = 0; + + visibility.counter_next = (visibility.counter_next + 1) % GPU_VISIBILITY_SLOTS; + visibility.counter_active = visibility.counter_next; + glBindBuffer(GL_ATOMIC_COUNTER_BUFFER, visibility.counters); + host_gl_buffer_write(GL_ATOMIC_COUNTER_BUFFER, (unsigned int)(visibility.counter_active * sizeof(GLuint)), + sizeof(zero), &zero); + glBindBuffer(GL_ATOMIC_COUNTER_BUFFER, 0); + return; + } +#endif + glBeginQuery(VISIBILITY_QUERY, visibility.queries[0]); +} + +void gpu_visibility_end(uint32_t slot) +{ + GLuint scratch; + + visibility.active = FALSE; +#ifdef HALO_ANDROID + if (xgpu_capabilities.atomic_counters) + { + unsigned long ring = streams.buffer_ring; + unsigned long *count = &visibility.ring_test_count[ring]; + + /* (a slot tested twice in a frame is listed twice: the later + counter, resolved after, wins) */ + if (*count < GPU_VISIBILITY_SLOTS) + { + visibility.ring_tests[ring][*count][0] = (unsigned short)slot; + visibility.ring_tests[ring][*count][1] = (unsigned short)visibility.counter_active; + (*count)++; + } + return; + } +#endif + glEndQuery(VISIBILITY_QUERY); + /* swap the scratch query into the requested slot */ + scratch = visibility.queries[0]; + visibility.queries[0] = visibility.queries[slot]; + visibility.queries[slot] = scratch; + visibility.unread[slot] = TRUE; +#ifndef HALO_ANDROID + if (visibility.results) + { + /* the GPU writes the count into the slot once it is known (given + by name: Mesa's GL thread waits for everything before a + glGetQueryObjectuiv, even one into a bound buffer) */ + glGetQueryBufferObjectuiv(visibility.queries[slot], visibility.results_buffer, GL_QUERY_RESULT, + (GLintptr)(slot * sizeof(GLuint))); + } +#endif +} + +uint32_t gpu_visibility_result(uint32_t slot, uint32_t *samples) +{ + GLuint available = 0, count = 0; + +#ifdef HALO_ANDROID + if (xgpu_capabilities.atomic_counters) + { + /* the latest count the GPU has finished (counter_snapshots) */ + *samples = visibility.latest[slot]; + return 1; + } +#else + if (visibility.results) + { + /* the latest count the GPU has written: from this test, or while + the GPU is still behind, from the slot's earlier ones */ + *samples = visibility.results[slot]; + return 1; + } +#endif + if (!visibility.unread[slot]) + return 0; + glGetQueryObjectuiv(visibility.queries[slot], GL_QUERY_RESULT_AVAILABLE, &available); + if (!available) + return 0; + glGetQueryObjectuiv(visibility.queries[slot], GL_QUERY_RESULT, &count); + visibility.unread[slot] = FALSE; + *samples = count; + return 1; +} + /* ---------- draws */ #ifdef HALO_ANDROID @@ -1866,7 +1985,7 @@ uint32_t gpu_draw(const struct gpu_draw *draw, struct gpu_constant_store *consta if (draw->stages[stage].type) texture_resolve(draw->stages[stage].texture); } - gpu_gl_bind_targets(draw->color_target, draw->depth_target, draw->samples); + bind_targets(draw->color_target, draw->depth_target, draw->samples); apply_raster_state(draw); entry = program_get(draw->vertex_shader, draw->pixel_shader); if (!entry) @@ -1877,6 +1996,11 @@ uint32_t gpu_draw(const struct gpu_draw *draw, struct gpu_constant_store *consta gl_check_errors("state"); draw_flush(); state_program(entry->program); +#ifdef HALO_ANDROID + if (visibility.active && xgpu_capabilities.atomic_counters) + glBindBufferRange(GL_ATOMIC_COUNTER_BUFFER, 0, visibility.counters, + (GLintptr)(visibility.counter_active * sizeof(GLuint)), sizeof(GLuint)); +#endif upload_uniforms(entry, constants, uniforms); bind_stages(draw->stages); setup_streams(draw); @@ -1897,6 +2021,148 @@ uint32_t gpu_draw(const struct gpu_draw *draw, struct gpu_constant_store *consta return 1; } +/* ---------- clears */ + +void gpu_clear(const struct gpu_clear *clear, const struct gpu_rect *rectangles, uint32_t count) +{ + GLbitfield mask = 0; + uint32_t index; + + bind_targets(clear->color_target, clear->depth_target, clear->samples); + if (clear->flags & GPU_CLEAR_COLOR) + { + float rgba[4]; + + glColorMask((clear->channel_mask & 1) != 0, (clear->channel_mask & 2) != 0, (clear->channel_mask & 4) != 0, + (clear->channel_mask & 8) != 0); + color_to_vec4(clear->color, rgba); + glClearColor(rgba[0], rgba[1], rgba[2], rgba[3]); + mask |= GL_COLOR_BUFFER_BIT; + } + if (clear->depth_target && (clear->flags & GPU_CLEAR_DEPTH)) + { + glDepthMask(GL_TRUE); + glClearDepth(clear->depth); + mask |= GL_DEPTH_BUFFER_BIT; + } + if (clear->depth_target && (clear->flags & GPU_CLEAR_STENCIL)) + { + glStencilMask(0xff); + glClearStencil((GLint)clear->stencil); + mask |= GL_STENCIL_BUFFER_BIT; + } + if (!mask) + return; + glEnable(GL_SCISSOR_TEST); + for (index = 0; index < count; index++) + { + glScissor(rectangles[index].x, rectangles[index].y, rectangles[index].width, rectangles[index].height); + glClear(mask); + } + glDisable(GL_SCISSOR_TEST); + xgpu_gl_state_invalidate(); +} + +/* ---------- the anti-aliasing passes (xgpu_post.c) */ + +uint32_t gpu_anti_alias_prepare(uint32_t pass) +{ + BOOL prepared = xgpu_post_prepare(pass == GPU_ANTI_ALIAS_SMAA); + + xgpu_gl_state_invalidate(); + return prepared != FALSE; +} + +void gpu_anti_alias(uint32_t pass, gpu_texture target, const int32_t corners[4]) +{ + const struct gpu_texture_description *description = &texture_record(target)->description; + GLint gl_corners[4]; + + /* (no longer multisampled, if it was before the setting changed) */ + texture_multisample(target, 0); + gl_corners[0] = corners[0]; + gl_corners[1] = corners[1]; + gl_corners[2] = corners[2]; + gl_corners[3] = corners[3]; + xgpu_post_anti_alias(pass == GPU_ANTI_ALIAS_SMAA, framebuffer_get(target, 0), description->width, + description->height, gl_corners); + glBindVertexArray(default_vertex_array); + xgpu_gl_state_invalidate(); +} + +/* ---------- frames */ + +void gpu_flush(void) +{ + glFlush(); +} + +void gpu_present(gpu_texture back_buffer) +{ + const struct gpu_texture_description *description = &texture_record(back_buffer)->description; + int window_width, window_height, width, height, x, y; + + texture_resolve(back_buffer); + platform_video_drawable_size(&window_width, &window_height); + /* letterbox to the back buffer's aspect ratio */ + width = window_width; + height = (int)((long)window_width * description->height / description->width); + if (height > window_height) + { + height = window_height; + width = (int)((long)window_height * description->width / description->height); + } + x = (window_width - width) / 2; + y = (window_height - height) / 2; + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); + glDisable(GL_SCISSOR_TEST); + glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); + glClearColor(0.0f, 0.0f, 0.0f, 1.0f); + glClear(GL_COLOR_BUFFER_BIT); + glBindFramebuffer(GL_READ_FRAMEBUFFER, framebuffer_get(back_buffer, 0)); + /* row 0 of the render target is the top of the picture */ + glBlitFramebuffer(0, 0, (GLint)description->width, (GLint)description->height, + x, y + height, x + width, y, GL_COLOR_BUFFER_BIT, GL_LINEAR); + platform_video_swap(); + xgpu_gl_state_invalidate(); +#ifdef HALO_ANDROID + if (xgpu_capabilities.atomic_counters) + { + /* this frame's counts, for when the GPU is done with it (the + barrier makes the shaders' counter writes visible to the copy, + which ES 3.1 does not promise without one) */ + glMemoryBarrier(GL_BUFFER_UPDATE_BARRIER_BIT); + glBindBuffer(GL_COPY_READ_BUFFER, visibility.counters); + glBindBuffer(GL_COPY_WRITE_BUFFER, visibility.counter_snapshots[streams.buffer_ring]); + glCopyBufferSubData(GL_COPY_READ_BUFFER, GL_COPY_WRITE_BUFFER, 0, 0, + GPU_VISIBILITY_SLOTS * sizeof(GLuint)); + glBindBuffer(GL_COPY_READ_BUFFER, 0); + glBindBuffer(GL_COPY_WRITE_BUFFER, 0); + } + /* the next frame's stream buffers, once the GPU has finished the frame + that last used them */ + frame_advance(); + if (xgpu_capabilities.atomic_counters && visibility.ring_test_count[streams.buffer_ring]) + { + unsigned long ring = streams.buffer_ring; + unsigned long test; + + /* the GPU has passed that frame's fence: its copy is complete, and + the slot is free for this frame's tests */ + host_gl_read_buffer(visibility.counter_snapshots[ring], 0, GPU_VISIBILITY_SLOTS * sizeof(GLuint), + visibility.counter_values); + for (test = 0; test < visibility.ring_test_count[ring]; test++) + { + visibility.latest[visibility.ring_tests[ring][test][0]] = + visibility.counter_values[visibility.ring_tests[ring][test][1]]; + } + visibility.ring_test_count[ring] = 0; + } +#else + frame_advance(); +#endif +} + /* ---------- the backend */ #ifndef HALO_ANDROID @@ -1973,8 +2239,8 @@ void gpu_initialize(struct gpu_capabilities *capabilities) capabilities->occlusion = GPU_OCCLUSION_EXACT; capabilities->sample_mask = 1; #endif - glGenVertexArrays(1, &gpu_gl_default_vertex_array); - glBindVertexArray(gpu_gl_default_vertex_array); + glGenVertexArrays(1, &default_vertex_array); + glBindVertexArray(default_vertex_array); #ifdef HALO_ANDROID { int ring; @@ -2012,6 +2278,34 @@ void gpu_initialize(struct gpu_capabilities *capabilities) } #endif glGenSamplers(GPU_STAGE_COUNT, stage_samplers); + glGenQueries(GPU_VISIBILITY_SLOTS, visibility.queries); +#ifdef HALO_ANDROID + if (xgpu_capabilities.atomic_counters) + { + int ring; + + glGenBuffers(1, &visibility.counters); + glBindBuffer(GL_ATOMIC_COUNTER_BUFFER, visibility.counters); + glBufferData(GL_ATOMIC_COUNTER_BUFFER, GPU_VISIBILITY_SLOTS * sizeof(GLuint), NULL, GL_DYNAMIC_DRAW); + glBindBuffer(GL_ATOMIC_COUNTER_BUFFER, 0); + glGenBuffers(GPU_GL_FRAME_RING, visibility.counter_snapshots); + for (ring = 0; ring < GPU_GL_FRAME_RING; ring++) + { + glBindBuffer(GL_COPY_WRITE_BUFFER, visibility.counter_snapshots[ring]); + glBufferData(GL_COPY_WRITE_BUFFER, GPU_VISIBILITY_SLOTS * sizeof(GLuint), NULL, GL_STREAM_READ); + } + glBindBuffer(GL_COPY_WRITE_BUFFER, 0); + } +#else + glGenBuffers(1, &visibility.results_buffer); + glBindBuffer(GL_QUERY_BUFFER, visibility.results_buffer); + glBufferStorage(GL_QUERY_BUFFER, GPU_VISIBILITY_SLOTS * sizeof(GLuint), NULL, + GL_MAP_READ_BIT | GL_MAP_PERSISTENT_BIT | GL_MAP_COHERENT_BIT); + visibility.results = glMapBufferRange(GL_QUERY_BUFFER, 0, GPU_VISIBILITY_SLOTS * sizeof(GLuint), + GL_MAP_READ_BIT | GL_MAP_PERSISTENT_BIT | GL_MAP_COHERENT_BIT); + if (!visibility.results) + platform_log("cannot map the visibility test results; tests wait for the GPU"); +#endif { /* an attribute a draw does not stream reads (0, 0, 0, 1) until given a value, as the Xbox's registers start */ diff --git a/port/linux/src/gpu_gl.h b/port/linux/src/gpu_gl.h index 3cba6ac13c..d388fe8c4e 100644 --- a/port/linux/src/gpu_gl.h +++ b/port/linux/src/gpu_gl.h @@ -1,10 +1,8 @@ /* GPU_GL.H -The OpenGL backend's own declarations (gpu_gl.c), for the files that still -make GL calls besides it: the device (d3d8_gl.c), whose clears, visibility -tests and presentation do not go through gpu.h yet, and the anti-aliasing -passes (xgpu_post.c). +The OpenGL backend's own declarations (gpu_gl.c), shared with its +anti-aliasing passes (xgpu_post.c). */ #ifndef __HALO_LINUX_GPU_GL_H @@ -67,21 +65,6 @@ afterwards. */ void xgpu_gl_state_invalidate(void); -/* ---------- render targets */ - -/* the framebuffer of these render targets' textures (either may be 0) */ -GLuint gpu_gl_framebuffer(gpu_texture color, gpu_texture depth); -/* draws go to these render targets (either may be 0), into their -multisampled storage with samples a pixel, or with 0 into their textures */ -void gpu_gl_bind_targets(gpu_texture color, gpu_texture depth, uint32_t samples); -/* a render target's texture gets the pixels drawn into its multisampled -storage since it last had them */ -void gpu_gl_resolve(gpu_texture target); -/* a render target's multisampled storage, with samples a pixel (0: none): -its pixels resolved into its texture first, and the texture's put into the -new storage */ -void gpu_gl_multisample(gpu_texture target, uint32_t samples); - /* ---------- shaders */ /* a compiled shader, or a linked program of two, or 0 with the log @@ -89,17 +72,20 @@ written */ GLuint xgpu_compile_shader(GLenum type, const char *code, const char *what); GLuint xgpu_link_program(GLuint vertex_shader, GLuint fragment_shader, const char *what); -/* ---------- frames */ +/* ---------- anti-aliasing + +display.anti_aliasing's passes (xgpu_post.c): FXAA or SMAA antialias each +window's 3D view in place before the HUD and menus are drawn over it, so +that their text stays sharp (gpu_anti_alias). */ -/* the vertex array the ES attributes are on, and the desktop's before its -first draw */ -extern GLuint gpu_gl_default_vertex_array; +/* the programs and textures of FXAA, or of SMAA, made now; FALSE if they +cannot be (once FALSE, it stays so) */ +BOOL xgpu_post_prepare(BOOL smaa); -/* the frame's slot in the ring of stream buffers (GPU_GL_FRAME_RING; 0 on -the desktop) */ -unsigned long gpu_gl_frame_ring(void); -/* after a frame is presented: the stream buffers of the next (with OpenGL ES, -the next slot's, once the GPU has finished with them) */ -void gpu_gl_frame_advance(void); +/* FXAA, or SMAA, on the corners x0, y0 to x1, y1 (from row 0) of a render +target's framebuffer, width by height, GL_RGBA8; FALSE if its programs do +not build */ +BOOL xgpu_post_anti_alias(BOOL smaa, GLuint framebuffer, unsigned long width, unsigned long height, + const GLint corners[4]); #endif diff --git a/port/linux/src/xgpu.h b/port/linux/src/xgpu.h index a27679ac72..d2dea55030 100644 --- a/port/linux/src/xgpu.h +++ b/port/linux/src/xgpu.h @@ -214,21 +214,4 @@ struct xgpu_render_target /* the render target with this physical address, or NULL */ struct xgpu_render_target *xgpu_render_target_find(unsigned long data); -/* ---------- anti-aliasing - -display.anti_aliasing's passes (xgpu_post.c): FXAA or SMAA antialias each -window's 3D view in place before the HUD and menus are drawn over it, so -that their text stays sharp. Supersampling and multisampling are the -device's (d3d8_gl.c). */ - -/* the programs and textures of FXAA, or of SMAA, made now; FALSE if they -cannot be (once FALSE, it stays so) */ -BOOL xgpu_post_prepare(BOOL smaa); - -/* FXAA, or SMAA, on the corners x0, y0 to x1, y1 (from row 0) of a render -target's framebuffer, width by height, GL_RGBA8; FALSE if its programs do -not build */ -BOOL xgpu_post_anti_alias(BOOL smaa, GLuint framebuffer, unsigned long width, unsigned long height, - const GLint corners[4]); - #endif From be1396c5b90982c2b50e80c9f787fb30f73ab456 Mon Sep 17 00:00:00 2001 From: Steve Rice Date: Thu, 8 Oct 2026 13:10:13 -0700 Subject: [PATCH 5/5] Rename d3d8_gl.c to d3d8_device.c, which makes no GL calls The Direct3D device reaches the GPU through gpu.h alone: it no longer includes gl.h or gpu_gl.h, its types and names lose GL (gl_ready is gpu_ready, a render target's gl_width and gl_height are its pixel_width and pixel_height), and it leaves resetting the GL state cache to the backend. The last GL calls outside the backend move behind gpu.h too: the high-res HUD's and the menus' PNG textures and the high-res text's atlas are made, filled and dropped through gpu_texture_*, the atlas taking its new glyphs' rows with gpu_texture_upload_rows. The anti-aliasing passes become gpu_gl_post.c, part of the OpenGL backend. gpu_gl.c and gpu_gl_post.c are now the only files that make GL calls, but for sdl_platform.c's log line naming the context it created. The comments and READMEs that named d3d8_gl.c or xgpu_post.c name the new files. The GL calls are unchanged but for a pixel store setting the high-res text's atlas makes once when it is created. The GLSL is unchanged. Co-Authored-By: Claude Opus 5.5 --- port/android/README.md | 2 +- port/android/host/host_gl.c | 4 +- port/android/host/host_main.c | 2 +- port/include/xdk/xdk_d3d8.h | 2 +- port/linux/README.md | 6 +- port/linux/game/custom_edition_geometry.c | 2 +- port/linux/game/tag_schema_render.c | 2 +- port/linux/include/halo_linux_source_fixups.h | 4 +- port/linux/include/halo_ui_pointer.h | 2 +- port/linux/src/{d3d8_gl.c => d3d8_device.c} | 99 ++++++++++--------- port/linux/src/gpu.h | 13 +-- port/linux/src/gpu_gl.c | 8 +- port/linux/src/gpu_gl.h | 4 +- port/linux/src/{xgpu_post.c => gpu_gl_post.c} | 2 +- port/linux/src/hud_hires.c | 25 ++--- port/linux/src/hud_hires.h | 4 +- port/linux/src/menu_files.c | 4 +- port/linux/src/nv2a_vsh.c | 2 +- port/linux/src/sdl_platform.c | 8 +- port/linux/src/sdl_platform.h | 2 +- port/linux/src/text_hires.c | 25 ++--- port/linux/src/text_hires.h | 2 +- port/linux/src/xbox_textures.c | 2 +- port/linux/src/xgpu.h | 33 +++---- port/third_party/smaa/README.md | 2 +- source/rasterizer/xbox/rasterizer_xbox.c | 2 +- .../rasterizer/xbox/rasterizer_xbox_shadows.c | 2 +- ...asterizer_xbox_vertex_shaders_initialize.c | 2 +- tools/embed_assets.py | 4 +- 29 files changed, 136 insertions(+), 135 deletions(-) rename port/linux/src/{d3d8_gl.c => d3d8_device.c} (97%) rename port/linux/src/{xgpu_post.c => gpu_gl_post.c} (99%) diff --git a/port/android/README.md b/port/android/README.md index ed15f79014..0136616957 100644 --- a/port/android/README.md +++ b/port/android/README.md @@ -239,7 +239,7 @@ two ABIs use the same registers for 32-bit integers, floats and pointers. ### OpenGL ES -The renderer (`port/linux/src/d3d8_gl.c`) uses OpenGL ES 3.0, and some +The renderer's OpenGL backend (`port/linux/src/gpu_gl.c`) uses OpenGL ES 3.0, and some functions of OpenGL ES 3.2 if they are available: - The vertex shaders flip y and change the depth range from 0..1. The front diff --git a/port/android/host/host_gl.c b/port/android/host/host_gl.c index d570a27ffb..19a8369c0b 100644 --- a/port/android/host/host_gl.c +++ b/port/android/host/host_gl.c @@ -57,7 +57,7 @@ int host_gl_has_extension(const char *name) } /* copies size bytes of a buffer object (the snapshots of the visibility -tests' counters, d3d8_gl.c); ES has no glGetBufferSubData, and the mapping +tests' counters, gpu_gl.c); ES has no glGetBufferSubData, and the mapping it offers instead is a host pointer. Binds GL_COPY_READ_BUFFER, which the renderer uses only for its copies, and leaves it unbound. */ void host_gl_read_buffer(uint32_t buffer, uint32_t offset, uint32_t size, void *data) @@ -79,7 +79,7 @@ void host_gl_read_buffer(uint32_t buffer, uint32_t offset, uint32_t size, void * } /* The renderer streams each frame's vertices and indices into the next of -a ring of buffers (d3d8_gl.c). A fence marks the end of each frame's work, +a ring of buffers (gpu_gl.c). A fence marks the end of each frame's work, and a buffer is written again only once the GPU has passed the fence of the frame that last used it: drivers queue several frames, and a draw still waiting to run would otherwise read a later frame's vertices. */ diff --git a/port/android/host/host_main.c b/port/android/host/host_main.c index 401c85bf21..5c401c6a19 100644 --- a/port/android/host/host_main.c +++ b/port/android/host/host_main.c @@ -263,7 +263,7 @@ static void *game_main(void *unused) environment_set(&environment, "HALO_SAVE_ROOT", save_root); { /* the game renders 480 lines at the display's aspect ratio - (landscape) unless display.screen_width says otherwise (d3d8_gl.c) */ + (landscape) unless display.screen_width says otherwise (d3d8_device.c) */ const SDL_DisplayMode *mode; char width[16]; diff --git a/port/include/xdk/xdk_d3d8.h b/port/include/xdk/xdk_d3d8.h index 0da5dbd623..d63d7530ed 100644 --- a/port/include/xdk/xdk_d3d8.h +++ b/port/include/xdk/xdk_d3d8.h @@ -296,7 +296,7 @@ by default, they must be declared extern to be shared) */ #endif /* state the inline functions below read and write, which the platform -layer defines (port/linux/src/d3d8_gl.c); the library's own copies are +layer defines (port/linux/src/d3d8_device.c); the library's own copies are public symbols of the January build (D3D__RenderState, D3D__TextureState, D3D__IndexData) */ extern DWORD D3D__RenderState[D3DRS_MAX]; diff --git a/port/linux/README.md b/port/linux/README.md index adc6f996f3..dd9c647034 100644 --- a/port/linux/README.md +++ b/port/linux/README.md @@ -716,10 +716,10 @@ crafted file) is refused, and the level starts over. | Area | Status | | --- | --- | | Game code | All 466 C files of the game. The changes are in "Game source changes". | -| Graphics | Direct3D 8 on OpenGL 4.5 core through SDL3 (`src/d3d8_gl.c`). The port translates the NV2A vertex shaders and register combiners to GLSL. It decodes all the Xbox texture formats. The vertex and index buffers come from a GL copy of the Xbox memory. | +| Graphics | Direct3D 8 on OpenGL 4.5 core through SDL3: the Direct3D device (`src/d3d8_device.c`) describes each draw to a GPU backend (`src/gpu.h`), which is OpenGL's (`src/gpu_gl.c`). The port translates the NV2A vertex shaders and register combiners to GLSL. It decodes all the Xbox texture formats. The vertex and index buffers come from a GL copy of the Xbox memory. | | High-res HUD | The HUD is drawn from high-res assets: redraws at 8x the size of the maps' bitmaps (4x for the largest), in `port/assets/hud`. They cover the meters, counters, panels and their outlines, the motion sensor, reticles, waypoints and scopes, but no bitmap with English text. `tools/hud_assets.py` makes them from the SVG redraws, and the build embeds them in the executable. When the game uploads one of those bitmaps, `src/hud_hires.c` gives the high-res texture in its place, if the bitmap's pixels are those of the English maps: another language's maps keep their own. The game sizes and places the HUD from its tags as before. `display.high_res_hud = false` turns this off. | | High-res text | The menus' and HUD's text is drawn with Overpass (`port/assets/fonts`, SIL Open Font License) in place of the maps' bitmap fonts, which are Interstate. `src/text_hires.c` rasterizes each glyph with stb_truetype (`port/third_party/stb`) at the resolution the game draws at, into an atlas that a placeholder bitmap of the game stands for. The game lays the text out from its font tags as before. The menus' titles (the screens' headers and the main menu's items) are pictures of text in the maps, so they are drawn as the high-res HUD is: `tools/title_assets.py` sets each one again in OpenCE, Roger White's public-domain Newtown respaced to match the maps' commercial title typeface (`tools/title_font.py`), at 4x the bitmap's size over its own plate or glow, each letter placed where the map's letter is, in `port/assets/titles`. The postgame carnage report's title is set over a hand-made SVG redraw of its panel (`port/assets/titles/svg`) instead. `display.high_res_text = false` turns it off. | -| Anti-aliasing | Off unless `display.anti_aliasing` is set (`src/d3d8_gl.c`, `src/xgpu_post.c`). FXAA (written in the port) and SMAA (`port/third_party/smaa`, MIT licensed, at its HIGH preset, compiled as GLSL) are passes over the 3D view of each window, after the lens flares and before the HUD and the menus (`render/render.c`). Their programs are built when the setting is chosen. Supersampling draws the render targets the size of the screen at two times the resolution in each direction, and the display blit scales them down. Multisampling draws the back buffer and its depth buffer into multisampled renderbuffers, and with them any target that is drawn together with one of them (a mirror's view, in the secondary target with the back buffer's depth buffer), so that the attachments of a framebuffer are all multisampled or none is. A target's pixels are resolved into its texture before something reads the texture (as a texture, or at the display blit). Visibility tests count samples, divided by the samples of a pixel. An alpha-tested surface (foliage, grates) covers the samples of a pixel in proportion to its alpha past the reference (`gl_SampleMask`, not on Android). | +| Anti-aliasing | Off unless `display.anti_aliasing` is set (`src/d3d8_device.c`, `src/gpu_gl.c`, `src/gpu_gl_post.c`). FXAA (written in the port) and SMAA (`port/third_party/smaa`, MIT licensed, at its HIGH preset, compiled as GLSL) are passes over the 3D view of each window, after the lens flares and before the HUD and the menus (`render/render.c`). Their programs are built when the setting is chosen. Supersampling draws the render targets the size of the screen at two times the resolution in each direction, and the display blit scales them down. Multisampling draws the back buffer and its depth buffer into multisampled renderbuffers, and with them any target that is drawn together with one of them (a mirror's view, in the secondary target with the back buffer's depth buffer), so that the attachments of a framebuffer are all multisampled or none is. A target's pixels are resolved into its texture before something reads the texture (as a texture, or at the display blit). Visibility tests count samples, divided by the samples of a pixel. An alpha-tested surface (foliage, grates) covers the samples of a pixel in proportion to its alpha past the reference (`gl_SampleMask`, not on Android). | | Sound | Xbox DirectSound on SDL3 audio (`src/dsound_sdl.c`): PCM and Xbox ADPCM, mixed at 48 kHz, with volume, pitch, mix bins, distance, stereo pan, occlusion and obstruction. There is no Doppler effect, no cones and no reverb. | | Input | XInput on SDL3 (`src/xinput_sdl.c`): keyboard, mouse, gamepads with rumble, and the debug keyboard for the console. | | Files | The Win32 file functions and the MSVC file functions on POSIX, with the translation of Xbox paths. | @@ -770,7 +770,7 @@ definition. Without this check, the linker gives the reference the address - `src/halo_linker_common.c` gives weak storage for some globals of the January link, and for `fast_ftol_C` and `main_crash`. - `main/d3d_intimacy.cpp` reads a private structure of the Xbox Direct3D. - The Linux build does not use this file. `src/d3d8_gl.c` gives + The Linux build does not use this file. `src/d3d8_device.c` gives `d3d_find_flipcount`. - The build returns small structures and unions in registers (`-freg-struct-return`), as on Win32. diff --git a/port/linux/game/custom_edition_geometry.c b/port/linux/game/custom_edition_geometry.c index c579f3fb19..6f47f4b49b 100644 --- a/port/linux/game/custom_edition_geometry.c +++ b/port/linux/game/custom_edition_geometry.c @@ -676,7 +676,7 @@ them. Its uncompressed vertices (cache_file_formats.c checked their size and place) stay where they are. */ /* geometry the renderer draws from: in the Xbox's contiguous memory, as the game's own vertex and index buffers are (physical_memory_map.c), which the -renderer keeps on the GPU (d3d8_gl.c's mirror: anything outside it is sent +renderer keeps on the GPU (d3d8_device.c's mirror: anything outside it is sent again at every draw); in the game's heap when that memory is spent */ static void *geometry_allocate( unsigned long size, diff --git a/port/linux/game/tag_schema_render.c b/port/linux/game/tag_schema_render.c index 1f230883ad..e12abbc3c3 100644 --- a/port/linux/game/tag_schema_render.c +++ b/port/linux/game/tag_schema_render.c @@ -59,7 +59,7 @@ enum _bitmap_group_type_sprites = 3, NUMBER_OF_BITMAP_GROUP_TYPES = 5, - /* the device's (d3d8_gl.c, D3DDevice_GetDeviceCaps; xbox_textures.c + /* the device's (d3d8_device.c, D3DDevice_GetDeviceCaps; xbox_textures.c uploads nothing larger) */ MAXIMUM_BITMAP_SIZE = 4096, MAXIMUM_VOLUME_BITMAP_SIZE = 512, diff --git a/port/linux/include/halo_linux_source_fixups.h b/port/linux/include/halo_linux_source_fixups.h index d4586323d5..f70722bb91 100644 --- a/port/linux/include/halo_linux_source_fixups.h +++ b/port/linux/include/halo_linux_source_fixups.h @@ -55,7 +55,7 @@ unsigned char custom_edition_vehicles_by_placement(void); unsigned char custom_edition_vehicle_placement_allowed(struct scenario_object_datum const *placement); /* the width of the screen the game draws, 480 lines tall: the device's or -the display's shape, or 640 (port/linux/src/d3d8_gl.c) */ +the display's shape, or 640 (port/linux/src/d3d8_device.c) */ long halo_screen_width(void); /* takes up a new width between frames (F11); returns the width */ long halo_screen_commit(void); @@ -65,7 +65,7 @@ long halo_shadow_map_scale(void); /* while TRUE, drawing shifts right to center 640-column layouts */ void halo_screen_ui_offset(unsigned char centered); /* names a model lighting vertex program, whose draws can be lit for each -pixel (port/linux/src/d3d8_gl.c, display.per_pixel_lighting) */ +pixel (port/linux/src/d3d8_device.c, display.per_pixel_lighting) */ void halo_vertex_shader_lighting(unsigned long handle); /* display.anti_aliasing's pass over a window's 3D view, before the HUD and menus (source/render/render.c): the window's bounds on the screen */ diff --git a/port/linux/include/halo_ui_pointer.h b/port/linux/include/halo_ui_pointer.h index bc7035a1a7..d1d18e550e 100644 --- a/port/linux/include/halo_ui_pointer.h +++ b/port/linux/include/halo_ui_pointer.h @@ -4,7 +4,7 @@ HALO_UI_POINTER.H The mouse in the menus of the desktop builds. While a menu is up, the mouse is released and its pointer shows (port/linux/src/sdl_platform.c); each frame the menus (source/interface/ui_widget.c) ask where it is, in their own 640x480 -coordinates (port/linux/src/d3d8_gl.c undoes the letterbox, the scale and the +coordinates (port/linux/src/d3d8_device.c undoes the letterbox, the scale and the widescreen centering), and what it did since. */ diff --git a/port/linux/src/d3d8_gl.c b/port/linux/src/d3d8_device.c similarity index 97% rename from port/linux/src/d3d8_gl.c rename to port/linux/src/d3d8_device.c index 9662ef276c..f61978e8f7 100644 --- a/port/linux/src/d3d8_gl.c +++ b/port/linux/src/d3d8_device.c @@ -1,28 +1,30 @@ /* -D3D8_GL.C +D3D8_DEVICE.C -The Xbox Direct3D 8 device, implemented with OpenGL 4.5. +The Xbox Direct3D 8 device, drawing through a GPU backend (gpu.h; OpenGL's +is gpu_gl.c). It makes no graphics API calls of its own. The game drives the device through the XDK's inline functions, which keep the "simple" render states in D3D__RenderState and call into this file for everything else. At each draw the full state is read back from there and translated: the vertex program into GLSL once per shader (nv2a_vsh.c), the pixel shader - texture stages and register combiners, 57 render states - -into GLSL once per combination (nv2a_psh.c), and the rest into GL state. +into GLSL once per combination (nv2a_psh.c), and the rest into one +struct gpu_draw, which gpu_draw applies whole. Conventions carried over from the Xbox: -- Clip space is D3D's (depth 0..1, y down in window space). glClipControl - (GL_UPPER_LEFT, GL_ZERO_TO_ONE) makes GL agree, so viewports, scissors and - texture rows line up with D3D's top-left origin; the window blit at - Present flips the image back for display. +- Clip space is D3D's (depth 0..1, y down in window space). The backend + makes the GPU agree (glClipControl(GL_UPPER_LEFT, GL_ZERO_TO_ONE) on the + desktop), so viewports, scissors and texture rows line up with D3D's + top-left origin; the window blit at Present flips the image back for + display. - Render targets and textures are identified by the physical address in - their Data field. A texture whose data is a render target samples the GL + their Data field. A texture whose data is a render target samples the render target directly (render-to-texture). - Vertex data is read from guest memory at draw time. */ #include "xgpu.h" -#include "gpu_gl.h" #include "sdl_platform.h" #include "halo_ui_pointer.h" #include "port_config.h" @@ -36,7 +38,7 @@ Conventions carried over from the Xbox: void d3d8_surface_initialize(D3DSurface *surface, D3DFORMAT format, unsigned long width, unsigned long height); void d3d8_surface_resize(D3DSurface *surface, D3DFORMAT format, unsigned long width, unsigned long height); -/* what the GPU backend can do (gl_initialize) */ +/* what the GPU backend can do (gpu_start) */ struct gpu_capabilities xgpu_gpu_capabilities; /* ---------- the screen's width @@ -66,13 +68,13 @@ render target the size of the screen has per unit of it */ static long screen_width; static float screen_scale[2] = { 1.0f, 1.0f }; static long ui_offset; -#define UI_OFFSET ((GLint)ui_offset) +#define UI_OFFSET ((int32_t)ui_offset) /* ---------- anti-aliasing display.anti_aliasing, off unless it is set: the Xbox drew without any. "fxaa" and "smaa" are passes over each window's 3D view before the HUD and -menus are drawn over it (halo_screen_anti_alias, xgpu_post.c); "ssaa2x" +menus are drawn over it (halo_screen_anti_alias, gpu_gl_post.c); "ssaa2x" draws the screen's targets at twice the resolution each way (screen_mode_choose), which the display blit scales down; "msaa2x" to "msaa8x" draw the back buffer and its depth buffer with that many samples a @@ -115,8 +117,8 @@ static const struct /* the value in effect, -1 until the setting is first read; and multisampling's samples a pixel, at most the GPU's (anti_aliasing_prepare; -the GPU's most samples and largest target are 0 until the GL context -exists, xgpu_gpu_capabilities) */ +the GPU's most samples and largest target are 0 until the GPU backend +starts, xgpu_gpu_capabilities) */ static int anti_aliasing_value = -1; static int anti_aliasing_samples; @@ -359,7 +361,7 @@ static struct render_target_entry *render_targets; /* a count from a test that only says whether any sample passed */ #define VISIBILITY_ALL_SAMPLES 1000000 -struct gl_device +struct xbox_device { D3DPRESENT_PARAMETERS presentation; D3DSurface back_buffer; @@ -406,11 +408,11 @@ struct gl_device unsigned long frame; unsigned long next_vertex_shader_id; - BOOL gl_ready; + BOOL gpu_ready; BOOL created; }; -static struct gl_device device; +static struct xbox_device device; /* debug.gpu_stats prints these once a second */ static struct @@ -443,10 +445,10 @@ static void color_to_vec4(D3DCOLOR color, float *out) out[3] = ((color >> 24) & 0xff) / 255.0f; } -/* the GLSL the translators write for this context (gl_initialize) */ +/* the GLSL the translators write for this context (gpu_start) */ static struct nv2a_dialect shader_dialect; -/* ---------- debugging settings, read once (gl_initialize) */ +/* ---------- debugging settings, read once (gpu_start) */ static struct { @@ -658,8 +660,8 @@ static struct render_target_entry *render_target_get(const D3DSurface *surface) entry->target.depth = depth; entry->target.scale[0] = scale[0]; entry->target.scale[1] = scale[1]; - entry->target.gl_width = (unsigned long)(width * scale[0] + 0.5f); - entry->target.gl_height = (unsigned long)(height * scale[1] + 0.5f); + entry->target.pixel_width = (unsigned long)(width * scale[0] + 0.5f); + entry->target.pixel_height = (unsigned long)(height * scale[1] + 0.5f); { struct gpu_texture_description description; @@ -667,8 +669,8 @@ static struct render_target_entry *render_target_get(const D3DSurface *surface) description.type = GPU_TEXTURE_2D; description.format = depth ? GPU_FORMAT_DEPTH_STENCIL : GPU_FORMAT_BGRA8; description.usage = GPU_USAGE_RENDER_TARGET; - description.width = (uint32_t)entry->target.gl_width; - description.height = (uint32_t)entry->target.gl_height; + description.width = (uint32_t)entry->target.pixel_width; + description.height = (uint32_t)entry->target.pixel_height; description.depth = 1; description.levels = 1; entry->target.texture = gpu_texture_create(&description); @@ -717,10 +719,10 @@ static float target_scale[2] = { 1.0f, 1.0f }; /* the pixel edge of a coordinate in a target's units, at its scale: floorf's, without its call (on 32-bit x86 it saves and restores the FPU's rounding, several times a draw) */ -static GLint scaled_pixel(float coordinate, float scale) +static int32_t scaled_pixel(float coordinate, float scale) { float value = coordinate * scale + 0.5f; - GLint pixel = (GLint)value; + int32_t pixel = (int32_t)value; /* (the conversion is toward zero: a negative value with a fraction rounds down one more) */ @@ -730,7 +732,7 @@ static GLint scaled_pixel(float coordinate, float scale) } /* ... in the bound targets' units */ -static GLint target_pixel(float coordinate, int axis) +static int32_t target_pixel(float coordinate, int axis) { return scaled_pixel(coordinate, target_scale[axis]); } @@ -779,7 +781,7 @@ static BOOL bind_targets(gpu_texture *color_target, gpu_texture *depth_target, u /* ---------- device creation */ -static void gl_initialize(void) +static void gpu_start(void) { struct gpu_capabilities *capabilities = &xgpu_gpu_capabilities; int index; @@ -797,15 +799,14 @@ static void gl_initialize(void) debug_settings.dump_shaders = *config_string("debug.gpu_dump_shaders") ? config_string("debug.gpu_dump_shaders") : NULL; debug_settings.statistics = config_boolean("debug.gpu_stats"); - xgpu_gl_state_invalidate(); - device.gl_ready = TRUE; + device.gpu_ready = TRUE; if (anti_aliasing_value < 0) anti_aliasing_read(); else anti_aliasing_prepare(); } -/* what display.anti_aliasing's value needs of the GL context, once there is +/* what display.anti_aliasing's value needs of the GPU backend, once there is one: multisampling's samples, at most the GPU's, and the passes' programs, built as the value is chosen rather than in the middle of a frame (SMAA's are large) */ @@ -813,7 +814,7 @@ static void anti_aliasing_prepare(void) { int mode; - if (!device.gl_ready || anti_aliasing_value < 0) + if (!device.gpu_ready || anti_aliasing_value < 0) return; mode = anti_aliasing_values[anti_aliasing_value].mode; anti_aliasing_samples = anti_aliasing_values[anti_aliasing_value].samples; @@ -973,7 +974,7 @@ HRESULT WINAPI Direct3D_CreateDevice(UINT adapter, D3DDEVTYPE device_type, void viewport_update_constants(); if (!config_boolean("debug.null_renderer") && platform_video_initialize(width, height)) - gl_initialize(); + gpu_start(); else platform_log("Direct3D: running without a window (nothing is displayed)"); device.created = TRUE; @@ -1009,11 +1010,11 @@ static void ui_point_from_window(float window_x, float window_y, short *x, short if (window_width <= 0 || window_height <= 0) return; width = pixel_width; - height = (int)((long)pixel_width * back_buffer->target.gl_height / back_buffer->target.gl_width); + height = (int)((long)pixel_width * back_buffer->target.pixel_height / back_buffer->target.pixel_width); if (height > pixel_height) { height = pixel_height; - width = (int)((long)pixel_height * back_buffer->target.gl_width / back_buffer->target.gl_height); + width = (int)((long)pixel_height * back_buffer->target.pixel_width / back_buffer->target.pixel_height); } left = (pixel_width - width) / 2; top = (pixel_height - height) / 2; @@ -1029,7 +1030,7 @@ int halo_ui_pointer_update(int menus_active, struct halo_ui_pointer *pointer) platform_menus_set_active(menus_active != 0); platform_ui_pointer_set_active(menus_active != 0); - if (!menus_active || !device.gl_ready || !platform_ui_pointer_read(&state)) + if (!menus_active || !device.gpu_ready || !platform_ui_pointer_read(&state)) return 0; memset(pointer, 0, sizeof(*pointer)); ui_point_from_window(state.x, state.y, &pointer->x, &pointer->y); @@ -1192,7 +1193,7 @@ void WINAPI D3DDevice_SetShaderConstantMode(D3DSHADERCONSTANTMODE mode) viewport_update_constants(); } -/* ---------- GPU synchronisation: GL keeps its own ordering */ +/* ---------- GPU synchronisation: the backend keeps its own ordering */ BOOL WINAPI D3DDevice_IsBusy(void) { @@ -1201,7 +1202,7 @@ BOOL WINAPI D3DDevice_IsBusy(void) void WINAPI D3DDevice_KickPushBuffer(void) { - if (device.gl_ready) + if (device.gpu_ready) gpu_flush(); } @@ -1217,7 +1218,7 @@ void WINAPI D3DDevice_InsertCallback(D3DCALLBACKTYPE type, D3DCALLBACK callback, void WINAPI D3DDevice_BeginVisibilityTest(void) { - if (!device.gl_ready || device.visibility_test_active) + if (!device.gpu_ready || device.visibility_test_active) return; device.visibility_test_active = TRUE; gpu_visibility_begin(); @@ -1225,7 +1226,7 @@ void WINAPI D3DDevice_BeginVisibilityTest(void) HRESULT WINAPI D3DDevice_EndVisibilityTest(DWORD index) { - if (!device.gl_ready || !device.visibility_test_active) + if (!device.gpu_ready || !device.visibility_test_active) return S_OK; device.visibility_test_active = FALSE; index %= VISIBILITY_TEST_SLOTS; @@ -1259,7 +1260,7 @@ HRESULT WINAPI D3DDevice_GetVisibilityTestResult(DWORD index, UINT *result, ULON index %= VISIBILITY_TEST_SLOTS; if (!index) index = 1; - if (!device.gl_ready || !device.query_pending[index]) + if (!device.gpu_ready || !device.query_pending[index]) { if (result) *result = 0; @@ -1723,7 +1724,7 @@ static unsigned long stage_texture_mode(int stage) } /* a D3DTSS_*FILTER value as gpu.h's (any other is linear, as the Xbox's -filters but POINT are in GL) */ +filters but POINT are in gpu.h) */ static unsigned char sampler_filter(DWORD filter) { return filter <= D3DTEXF_GAUSSIANCUBIC ? (unsigned char)filter : GPU_FILTER_LINEAR; @@ -1767,7 +1768,7 @@ static void sampler_state(int stage, BOOL mipmapped, BOOL hires, struct gpu_samp The game renders some textures one mip level at a time, each level being a surface of its own (the water's ripple map). Sampling such a texture needs -every level in one GL texture, so the levels' render targets are copied into +every level in one texture, so the levels' render targets are copied into a mipmapped composite. Each draw of the water binds it, some maps (a30) more than once a frame, so the copy (and the mipmaps of the levels the game did not render) is redone only once a level's target has been drawn into since the @@ -1836,7 +1837,7 @@ static gpu_texture mip_composite_get(const struct xgpu_texture_description *desc xgpu_render_target_find(data + xgpu_texture_level_offset(description, level)); if (!target || target->width != width || target->height != height || - target->gl_width != width || target->gl_height != height) + target->pixel_width != width || target->pixel_height != height) break; targets[level] = target; rendered_levels++; @@ -2084,7 +2085,7 @@ static BOOL prepare_draw(struct gpu_draw *draw, BOOL immediate) BOOL has_depth = FALSE; int stage; - if (!device.gl_ready || !program || !device.vertex_shader || !program->instructions) + if (!device.gpu_ready || !program || !device.vertex_shader || !program->instructions) { stats.skipped_no_program++; return FALSE; @@ -2352,7 +2353,7 @@ static void trace_draw(const char *kind, D3DPRIMITIVETYPE type, unsigned long co /* ---------- the contiguous window in GPU buffers Vertex and index buffers live in the Xbox's contiguous memory, where most -never change once loaded. The mirror keeps a copy of that memory in GL +never change once loaded. The mirror keeps a copy of that memory in GPU buffers (one per segment, created when first needed) and uploads a page only when it is first drawn from or after the game has written it: pages are write-protected once uploaded, as cached textures are (memory_watch.c). @@ -2994,7 +2995,7 @@ void WINAPI D3DDevice_Clear(DWORD count, CONST D3DRECT *rectangles, DWORD flags, DWORD index; memset(&clear, 0, sizeof(clear)); - if (!device.gl_ready || !bind_targets(&clear.color_target, &clear.depth_target, &clear.samples, &has_depth)) + if (!device.gpu_ready || !bind_targets(&clear.color_target, &clear.depth_target, &clear.samples, &has_depth)) return; if (trace_frame()) platform_log("clear flags %lx color %08lx z %g count %lu target %08lx depth %08lx", (unsigned long)flags, @@ -3081,7 +3082,7 @@ void halo_screen_anti_alias(short x0, short y0, short x1, short y1) int32_t corners[4]; int mode = anti_aliasing(); - if (!device.gl_ready || (mode != _anti_aliasing_fxaa && mode != _anti_aliasing_smaa)) + if (!device.gpu_ready || (mode != _anti_aliasing_fxaa && mode != _anti_aliasing_smaa)) return; /* (the primary target's view: the back buffer's) */ target = render_target_get(&device.back_buffer); @@ -3103,7 +3104,7 @@ static void write_screenshot(struct render_target_entry *target) { const char *directory = *config_string("debug.screenshot_directory") ? config_string("debug.screenshot_directory") : NULL; - unsigned long width = target->target.gl_width, height = target->target.gl_height; + unsigned long width = target->target.pixel_width, height = target->target.pixel_height; unsigned char *pixels; char path[512]; FILE *file; @@ -3155,7 +3156,7 @@ void WINAPI D3DDevice_Present(CONST RECT *source_rectangle, CONST RECT *destinat if (screenshot_every < 0) screenshot_every = config_integer("debug.screenshot_every"); - if (device.gl_ready) + if (device.gpu_ready) { struct render_target_entry *back_buffer = render_target_get(&device.back_buffer); diff --git a/port/linux/src/gpu.h b/port/linux/src/gpu.h index 88cdc88ac6..9f2a12dd23 100644 --- a/port/linux/src/gpu.h +++ b/port/linux/src/gpu.h @@ -1,11 +1,12 @@ /* GPU.H -The interface between the Xbox Direct3D device (d3d8_gl.c, and its texture -cache, xbox_textures.c) and the backend that drives the GPU for it (gpu_gl.c: -OpenGL 4.5, or OpenGL ES 3 on Android). The device keeps the Xbox's state and -says what it needs in the neutral terms below; the backend owns the GPU's -objects and makes every call to its API. +The interface between the Xbox Direct3D device (d3d8_device.c, with its +texture cache, xbox_textures.c, and the high-res HUD, text and menu art) and +the backend that drives the GPU for it (gpu_gl.c: OpenGL 4.5, or OpenGL ES 3 +on Android). The device keeps the Xbox's state and says what it needs in the +neutral terms below, one self-contained packet per draw; the backend owns +the GPU's objects and makes every call to its API. It includes only , and its structs have fixed-width fields only, so that a backend built for another ABI than the game's (a 64-bit host drawing @@ -447,7 +448,7 @@ uint32_t gpu_visibility_result(uint32_t slot, uint32_t *samples); /* ---------- frames */ -/* display.anti_aliasing's passes over a window's 3D view (xgpu_post.c) */ +/* display.anti_aliasing's passes over a window's 3D view (gpu_gl_post.c) */ enum { GPU_ANTI_ALIAS_FXAA = 1, GPU_ANTI_ALIAS_SMAA }; /* makes the pass's programs and textures now, rather than in the middle of a diff --git a/port/linux/src/gpu_gl.c b/port/linux/src/gpu_gl.c index 13c69c71d8..024e0d4f35 100644 --- a/port/linux/src/gpu_gl.c +++ b/port/linux/src/gpu_gl.c @@ -2,11 +2,13 @@ GPU_GL.C The OpenGL backend (gpu.h): OpenGL 4.5 on the desktop, OpenGL ES 3 on -Android. A gpu_texture is the GL texture's name. +Android, and the only file but its anti-aliasing passes (gpu_gl_post.c) that +makes GL calls, besides the context's creation (sdl_platform.c). A +gpu_texture, gpu_buffer or gpu_shader is the GL object's name. Render targets are textures. With multisampling, draws go to a multisampled renderbuffer of the target's instead, whose pixels are resolved into the -texture before anything reads it (gpu_gl_resolve). +texture before anything reads it (texture_resolve). */ #include "gpu_gl.h" @@ -2063,7 +2065,7 @@ void gpu_clear(const struct gpu_clear *clear, const struct gpu_rect *rectangles, xgpu_gl_state_invalidate(); } -/* ---------- the anti-aliasing passes (xgpu_post.c) */ +/* ---------- the anti-aliasing passes (gpu_gl_post.c) */ uint32_t gpu_anti_alias_prepare(uint32_t pass) { diff --git a/port/linux/src/gpu_gl.h b/port/linux/src/gpu_gl.h index d388fe8c4e..0f559f8be7 100644 --- a/port/linux/src/gpu_gl.h +++ b/port/linux/src/gpu_gl.h @@ -2,7 +2,7 @@ GPU_GL.H The OpenGL backend's own declarations (gpu_gl.c), shared with its -anti-aliasing passes (xgpu_post.c). +anti-aliasing passes (gpu_gl_post.c). */ #ifndef __HALO_LINUX_GPU_GL_H @@ -74,7 +74,7 @@ GLuint xgpu_link_program(GLuint vertex_shader, GLuint fragment_shader, const cha /* ---------- anti-aliasing -display.anti_aliasing's passes (xgpu_post.c): FXAA or SMAA antialias each +display.anti_aliasing's passes (gpu_gl_post.c): FXAA or SMAA antialias each window's 3D view in place before the HUD and menus are drawn over it, so that their text stays sharp (gpu_anti_alias). */ diff --git a/port/linux/src/xgpu_post.c b/port/linux/src/gpu_gl_post.c similarity index 99% rename from port/linux/src/xgpu_post.c rename to port/linux/src/gpu_gl_post.c index d6e8fd2eaa..ccd1319234 100644 --- a/port/linux/src/xgpu_post.c +++ b/port/linux/src/gpu_gl_post.c @@ -16,7 +16,7 @@ edge from the luma around a pixel, the edge's ends found by stepping along it, and the pixel blended across it by where it lies between them, or by how much it stands out from its neighbours. SMAA is port/third_party/smaa's, compiled as GLSL at its HIGH preset: edges from luma, blending weights from -its lookup textures, then the blend. Android has FXAA only (d3d8_gl.c's +its lookup textures, then the blend. Android has FXAA only (d3d8_device.c's anti_aliasing_values). */ diff --git a/port/linux/src/hud_hires.c b/port/linux/src/hud_hires.c index 5352c48389..b0c2571dd6 100644 --- a/port/linux/src/hud_hires.c +++ b/port/linux/src/hud_hires.c @@ -10,7 +10,7 @@ first drawn and kept: up to 69 of the HUD's, about 225 MB with their mip levels, though a game draws only some (the scopes' only when zoomed), and the titles of the menus shown, about 3 MB each (11 MB for the carnage report's, a whole panel). -They are drawn with linear filtering and their mip levels (d3d8_gl.c, +They are drawn with linear filtering and their mip levels (d3d8_device.c, configure_sampler), as they are larger than they appear. The PNGs are the ones tools/hud_assets.py and title_assets.py write, so only @@ -227,22 +227,25 @@ unsigned int hud_hires_png_texture(const void *png, unsigned long size, unsigned { unsigned long width = 0, height = 0, largest; unsigned char *pixels = png_decode(png, size, &width, &height); - GLuint texture; + struct gpu_texture_description description; + gpu_texture texture; if (!pixels) return 0; *levels = 1; for (largest = width > height ? width : height; largest > 1; largest >>= 1) (*levels)++; - glGenTextures(1, &texture); - glBindTexture(GL_TEXTURE_2D, texture); - xgpu_gl_state_invalidate(); - glPixelStorei(GL_UNPACK_ALIGNMENT, 1); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, (GLint)*levels - 1); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, (GLsizei)width, (GLsizei)height, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels); - glGenerateMipmap(GL_TEXTURE_2D); - xgpu_gl_state_invalidate(); + memset(&description, 0, sizeof(description)); + description.type = GPU_TEXTURE_2D; + description.format = GPU_FORMAT_RGBA8; + description.usage = GPU_USAGE_UPLOAD; + description.width = (uint32_t)width; + description.height = (uint32_t)height; + description.depth = 1; + description.levels = (uint32_t)*levels; + texture = gpu_texture_create(&description); + gpu_texture_upload(texture, 0, 0, pixels, (uint32_t)(width * height * 4)); + gpu_texture_generate_mipmaps(texture, 0); free(pixels); return texture; } diff --git a/port/linux/src/hud_hires.h b/port/linux/src/hud_hires.h index 6bd6c1fb08..5fe64c7c5b 100644 --- a/port/linux/src/hud_hires.h +++ b/port/linux/src/hud_hires.h @@ -54,9 +54,9 @@ unsigned int hud_hires_override_texture(long asset, unsigned long *levels); /* a GL texture drawn from an 8-bit RGBA PNG (as the tools write them), with all its mip levels, and their number; 0 if it could not be */ unsigned int hud_hires_png_texture(const void *png, unsigned long size, unsigned long *levels); -/* whether its green is its coverage (d3d8_gl.c, nv2a_psh.c: coverage_alpha) */ +/* whether its green is its coverage (d3d8_device.c, nv2a_psh.c: coverage_alpha) */ int hud_hires_override_coverage(long asset); -/* whether its red holds exact segment thresholds, read unfiltered (d3d8_gl.c, +/* whether its red holds exact segment thresholds, read unfiltered (d3d8_device.c, nv2a_psh.c: point_threshold) */ int hud_hires_override_point_threshold(long asset); diff --git a/port/linux/src/menu_files.c b/port/linux/src/menu_files.c index ce79552927..f04af32835 100644 --- a/port/linux/src/menu_files.c +++ b/port/linux/src/menu_files.c @@ -1037,7 +1037,7 @@ void halo_menus_art_register(void const *texture, char const *png) { free(art[index].png); if (art[index].texture) - glDeleteTextures(1, &art[index].texture); + gpu_texture_destroy(art[index].texture); } memset(&art[index], 0, sizeof(art[index])); art[index].data = data; @@ -1052,7 +1052,7 @@ void halo_menus_art_forget(void) { free(art[index].png); if (art[index].texture) - glDeleteTextures(1, &art[index].texture); + gpu_texture_destroy(art[index].texture); } art_count = 0; } diff --git a/port/linux/src/nv2a_vsh.c b/port/linux/src/nv2a_vsh.c index 2431b11e6c..e76753a0fd 100644 --- a/port/linux/src/nv2a_vsh.c +++ b/port/linux/src/nv2a_vsh.c @@ -333,7 +333,7 @@ static const char shader_prologue[] = "uniform vec4 viewport_scale;\n" "uniform vec4 viewport_offset;\n" "uniform float point_size;\n" - /* columns the menus shift by to center on a wide screen (d3d8_gl.c) */ + /* columns the menus shift by to center on a wide screen (d3d8_device.c) */ "uniform float screen_offset;\n" "out vec4 xD0;\n" "out vec4 xD1;\n" diff --git a/port/linux/src/sdl_platform.c b/port/linux/src/sdl_platform.c index 58130ee41c..c17b9e3896 100644 --- a/port/linux/src/sdl_platform.c +++ b/port/linux/src/sdl_platform.c @@ -3,7 +3,7 @@ SDL_PLATFORM.C The SDL3 window, OpenGL context and event loop behind the Linux build. -The window is created with the Direct3D device (d3d8_gl.c) on the game's +The window is created with the Direct3D device (d3d8_device.c) on the game's main thread, which is also the only thread that pumps events. Keyboard and mouse state gathered here feeds the controller emulation in xinput_sdl.c and the debug keyboard that the game's console reads. @@ -488,7 +488,7 @@ static BOOL platform_window_fullscreen(void) height >= (int)(mode->h * mode->pixel_density + 0.5f); } -/* the size in pixels the game draws its picture at (d3d8_gl.c): the +/* the size in pixels the game draws its picture at (d3d8_device.c): the window's, the display's while fullscreen, or display.resolution's while fullscreen (either kind) where the display has room for it; before the window opens, what it will be. FALSE where display.resolution_scaling is @@ -715,7 +715,7 @@ BOOL platform_video_initialize(unsigned long width, unsigned long height) /* fullscreen (either kind) unless display.mode is the window, which F11 switches to and from: display.window_size, whatever shape the fullscreen picture has. The game draws at the size platform_screen_mode gives - (d3d8_gl.c). */ + (d3d8_device.c). */ (void)width; (void)height; platform_window_size_setting(&platform_window_width, &platform_window_height); @@ -1414,7 +1414,7 @@ int platform_binding_capture_poll(int *input) /* While a menu is up the mouse is released, its pointer shows (centered when the menu opens) and its motion, clicks and wheel go to the menus -(halo_ui_pointer_update, d3d8_gl.c) instead of the controller and the aim. */ +(halo_ui_pointer_update, d3d8_device.c) instead of the controller and the aim. */ void platform_ui_pointer_set_active(BOOL active) { if (!platform_window || (active != FALSE) == (input_state.ui_pointer != FALSE)) diff --git a/port/linux/src/sdl_platform.h b/port/linux/src/sdl_platform.h index 1c4978be73..a77003be4d 100644 --- a/port/linux/src/sdl_platform.h +++ b/port/linux/src/sdl_platform.h @@ -86,7 +86,7 @@ void platform_pump_events(void); /* a snapshot of the input state; consume_motion resets the mouse deltas */ void platform_input_read(struct platform_input_state *state, BOOL consume_motion); #ifndef HALO_ANDROID -/* the pointer in the menus (d3d8_gl.c, halo_ui_pointer_update) */ +/* the pointer in the menus (d3d8_device.c, halo_ui_pointer_update) */ struct platform_ui_pointer { /* in window coordinates, as SDL reports them */ diff --git a/port/linux/src/text_hires.c b/port/linux/src/text_hires.c index 4796f3b757..71fdb73168 100644 --- a/port/linux/src/text_hires.c +++ b/port/linux/src/text_hires.c @@ -59,7 +59,7 @@ static float atlas_scale; /* the pixels per unit its glyphs have */ static int pack_x, pack_y, pack_row_height; static int dirty_top = ATLAS_SIZE, dirty_bottom; static unsigned long placeholder_data, placeholder_width, placeholder_height; -static GLuint atlas_texture; +static gpu_texture atlas_texture; /* tag names compared as the game does, ignoring case (and without the POSIX strcasecmp, which the Windows build has not) */ @@ -259,12 +259,18 @@ unsigned int text_hires_atlas_texture(unsigned long data) return 0; if (!atlas_texture) { - glGenTextures(1, &atlas_texture); - glBindTexture(GL_TEXTURE_2D, atlas_texture); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, ATLAS_SIZE, ATLAS_SIZE, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL); - xgpu_gl_state_invalidate(); + struct gpu_texture_description description; + + memset(&description, 0, sizeof(description)); + description.type = GPU_TEXTURE_2D; + description.format = GPU_FORMAT_RGBA8; + description.usage = GPU_USAGE_UPLOAD; + description.width = ATLAS_SIZE; + description.height = ATLAS_SIZE; + description.depth = 1; + description.levels = 1; + atlas_texture = gpu_texture_create(&description); + gpu_texture_upload(atlas_texture, 0, 0, NULL, ATLAS_SIZE * ATLAS_SIZE * 4); dirty_top = 0; dirty_bottom = ATLAS_SIZE; } @@ -284,10 +290,7 @@ unsigned int text_hires_atlas_texture(unsigned long data) texels[index * 4 + 0] = texels[index * 4 + 1] = texels[index * 4 + 2] = 255; texels[index * 4 + 3] = coverage[index]; } - glBindTexture(GL_TEXTURE_2D, atlas_texture); - glPixelStorei(GL_UNPACK_ALIGNMENT, 1); - glTexSubImage2D(GL_TEXTURE_2D, 0, 0, dirty_top, ATLAS_SIZE, (GLsizei)rows, GL_RGBA, GL_UNSIGNED_BYTE, texels); - xgpu_gl_state_invalidate(); + gpu_texture_upload_rows(atlas_texture, (uint32_t)dirty_top, (uint32_t)rows, texels); free(texels); dirty_top = ATLAS_SIZE; dirty_bottom = 0; diff --git a/port/linux/src/text_hires.h b/port/linux/src/text_hires.h index ea05f2893f..8ea46649f3 100644 --- a/port/linux/src/text_hires.h +++ b/port/linux/src/text_hires.h @@ -54,7 +54,7 @@ void text_hires_register_atlas(const unsigned long *texture, unsigned long width placeholder's, its rasterized glyphs uploaded; 0 otherwise */ unsigned int text_hires_atlas_texture(unsigned long data); -/* d3d8_gl.c: the display's pixels for each of the 480 lines */ +/* d3d8_device.c: the display's pixels for each of the 480 lines */ float halo_screen_pixel_scale(void); #endif diff --git a/port/linux/src/xbox_textures.c b/port/linux/src/xbox_textures.c index 5cf42edaa5..a71c2f4821 100644 --- a/port/linux/src/xbox_textures.c +++ b/port/linux/src/xbox_textures.c @@ -202,7 +202,7 @@ unsigned long xgpu_texture_face_size(const struct xgpu_texture_description *desc return size; } -/* whether the size is one D3DDevice_GetDeviceCaps allows (d3d8_gl.c): up +/* whether the size is one D3DDevice_GetDeviceCaps allows (d3d8_device.c): up to 4096 by 4096, and 512 each way for a volume */ static BOOL texture_size_supported(const struct xgpu_texture_description *description) { diff --git a/port/linux/src/xgpu.h b/port/linux/src/xgpu.h index d2dea55030..b89125cfef 100644 --- a/port/linux/src/xgpu.h +++ b/port/linux/src/xgpu.h @@ -1,36 +1,27 @@ /* XGPU.H -Internals shared by the OpenGL implementation of the Xbox Direct3D API: -the NV2A shader translators (nv2a_vsh.c, nv2a_psh.c), texture decoding +Internals shared by the implementation of the Xbox Direct3D API: the NV2A +shader translators (nv2a_vsh.c, nv2a_psh.c), texture decoding (xbox_textures.c), guest memory write tracking (memory_watch.c) and the -device itself (d3d8_gl.c). +device itself (d3d8_device.c). The GPU is reached through gpu.h only. */ #ifndef __HALO_LINUX_XGPU_H #define __HALO_LINUX_XGPU_H #include "platform.h" -#include "gl.h" #include "gpu.h" -/* what the GPU backend can do (gpu_initialize, from d3d8_gl.c -gl_initialize) */ +/* what the GPU backend can do (gpu_initialize, from d3d8_device.c +gpu_start) */ extern struct gpu_capabilities xgpu_gpu_capabilities; -/* ---------- GL state - -The GL backend caches the GL state it sets for draws (gpu_gl.c); code that -changes GL state behind it (binding a texture to upload it, deleting one) -must call this afterwards. */ - -void xgpu_gl_state_invalidate(void); - /* ---------- the shading language The translators write the GLSL of the context the device has: desktop GL -4.5's, or OpenGL ES's on Android ("300 es" or "310 es"), as gl_initialize -found it (d3d8_gl.c). */ +4.5's, or OpenGL ES's on Android ("300 es" or "310 es"), as gpu_start +found it (d3d8_device.c). */ struct nv2a_dialect { @@ -64,7 +55,7 @@ void xgpu_text_append(struct xgpu_text *text, const char *format, ...) __attribu /* D3D constant register -96 is hardware register 0 */ #define XGPU_VERTEX_CONSTANT_BIAS 96 -/* where one of the game's model lighting programs (d3d8_gl.c +/* where one of the game's model lighting programs (d3d8_device.c halo_vertex_shader_lighting) has the normal, and the world position, that it lights the diffuse color by: the temporary register that holds each before the instruction given */ @@ -200,13 +191,13 @@ struct xgpu_render_target BOOL depth; gpu_texture texture; /* pixels per unit of width and height: more than 1 for the screen's - targets when the game draws at the display's resolution (d3d8_gl.c) */ + targets when the game draws at the display's resolution (d3d8_device.c) */ float scale[2]; - unsigned long gl_width, gl_height; + unsigned long pixel_width, pixel_height; /* with multisampling, the samples a pixel of its multisampled storage, - which draws go to (0 when it has none: d3d8_gl.c, bind_targets) */ + which draws go to (0 when it has none: d3d8_device.c, bind_targets) */ int samples; - /* changes whenever the target is drawn into or cleared (d3d8_gl.c, + /* changes whenever the target is drawn into or cleared (d3d8_device.c, bind_targets) */ unsigned long written; }; diff --git a/port/third_party/smaa/README.md b/port/third_party/smaa/README.md index 0b98575af8..ebe52f46ef 100644 --- a/port/third_party/smaa/README.md +++ b/port/third_party/smaa/README.md @@ -15,6 +15,6 @@ unchanged. `area_tex.zlib` and `search_tex.zlib` are the bytes of re.findall(r'0x([0-9a-fA-F]{2})', t[t.index('{'):])), 9))" AreaTex.h area_tex.zlib `display.anti_aliasing = "smaa"` antialiases the 3D view with it, compiled -as GLSL (`port/linux/src/xgpu_post.c`); the files are embedded in the Linux +as GLSL (`port/linux/src/gpu_gl_post.c`); the files are embedded in the Linux and Windows platform layers by `tools/embed_assets.py`. Android has FXAA in its place. diff --git a/source/rasterizer/xbox/rasterizer_xbox.c b/source/rasterizer/xbox/rasterizer_xbox.c index 3a6ce72c90..6f8ed75726 100644 --- a/source/rasterizer/xbox/rasterizer_xbox.c +++ b/source/rasterizer/xbox/rasterizer_xbox.c @@ -2408,7 +2408,7 @@ void _rasterizer_frame_end( /* The native ports can change the width of the screen between frames (F11 switches the desktop builds between fullscreen at the display's shape and a -640x480 window: halo_screen_commit, port/linux/src/d3d8_gl.c, which resizes +640x480 window: halo_screen_commit, port/linux/src/d3d8_device.c, which resizes the back buffer). The screen and the title-safe frame follow, as set up in rasterizer_initialize, and so do the textures the game made of the back buffer and its copy. */ diff --git a/source/rasterizer/xbox/rasterizer_xbox_shadows.c b/source/rasterizer/xbox/rasterizer_xbox_shadows.c index 678b8f4b5e..794576a0a0 100644 --- a/source/rasterizer/xbox/rasterizer_xbox_shadows.c +++ b/source/rasterizer/xbox/rasterizer_xbox_shadows.c @@ -1026,7 +1026,7 @@ static void rasterizer_shadow_convolve( } /* port: the shadow maps can be drawn larger than the Xbox's 128x128 -(display.shadow_resolution, port/linux/src/d3d8_gl.c): scale times as many +(display.shadow_resolution, port/linux/src/d3d8_device.c): scale times as many texels each way, a power of two. The Xbox's blur takes four taps diagonally half a texel off each texel, on the corners it shares with its neighbours. Each tap averages the four texels there, and together they weigh the texel diff --git a/source/rasterizer/xbox/rasterizer_xbox_vertex_shaders_initialize.c b/source/rasterizer/xbox/rasterizer_xbox_vertex_shaders_initialize.c index b2483967ee..c98ef5fd96 100644 --- a/source/rasterizer/xbox/rasterizer_xbox_vertex_shaders_initialize.c +++ b/source/rasterizer/xbox/rasterizer_xbox_vertex_shaders_initialize.c @@ -182,7 +182,7 @@ boolean rasterizer_vertex_shaders_initialize( error(2, "### ERROR rasterizer_vertex_shaders_initialize failed"); /* port: the model shader's lighting programs, whose draws can be lit for - each pixel (d3d8_gl.c): rasterizer_set_vertex_shader_permutation's for + each pixel (d3d8_device.c): rasterizer_set_vertex_shader_permutation's for models, 17 (planar fog), 10 (point lights), 9 (reflection) and 27 (reflection, one node) */ if (success) diff --git a/tools/embed_assets.py b/tools/embed_assets.py index fc22b4ffd1..5c481ca7e4 100644 --- a/tools/embed_assets.py +++ b/tools/embed_assets.py @@ -36,7 +36,7 @@ FONT_LIST = FONT_ASSETS / "fonts.json" MENU_ASSETS = Path("port/assets/menus") MENU_LIST = MENU_ASSETS / "menus.json" -# SMAA's files and the names port/linux/src/xgpu_post.c declares them by +# SMAA's files and the names port/linux/src/gpu_gl_post.c declares them by SMAA_ASSETS = Path("port/third_party/smaa") SMAA_FILES = (("SMAA.hlsl", "xgpu_smaa_shader"), ("area_tex.zlib", "xgpu_smaa_area_texture"), ("search_tex.zlib", "xgpu_smaa_search_texture")) @@ -207,7 +207,7 @@ def main() -> None: lines.append(f"const unsigned int menu_files_embedded_count = {len(menus)};") lines.append("") # SMAA's shader, as text a GLSL compiler takes (ASCII, ending in a NUL), - # and its lookup textures (xgpu_post.c); each of size 0 that the + # and its lookup textures (gpu_gl_post.c); each of size 0 that the # checkout does not have. Android has no SMAA. lines.append("#ifndef HALO_ANDROID") present = dict(smaa_files())