mirror of
https://github.com/RetroPortingToolKit/psxrecomp
synced 2026-10-25 18:05:12 -04:00
GL: opt-in bloom ([video] bloom), sourced from 3D and additive primitives only
[video] bloom = 0.0 (off, default) .. 2.0 adds a glow around lights on the
OpenGL backend. PSX_BLOOM overrides it for one run; {"cmd":"bloom"} reads or
changes strength, threshold and a debug view live. It is a plain [video] key,
so [quality.*] presets can switch it off on low-end machines.
The HUD never glows: with bloom on every textured and untextured draw is
followed by two stencil-only passes of the same vertices that keep a
"may glow" mark in stencil bit 1 (bit 0 stays the mask-bit mirror). Opaque
primitives clear it under their coverage, then PGXP-proven 3D primitives and
additive semi-transparent texels/primitives (modes 1 and 3) set it, painter
ordered like the colour; 3D and non-3D primitives never share a batch while
bloom is on. Semi-transparent primitives leave the mark alone and the PGXP
depth-clear pass (colour writes off) marks nothing. Without PGXP geometry only
additive primitives glow.
The pass runs on the composed game image before AA and the OSD: the presented
surface's marks are blitted into a quarter-resolution stencil, a soft-knee
bright pass runs under it, three separable Gaussian iterations spread it and a
screen blend adds it back. The marks follow the picture: native-wide centre
copies and Smooth motion surfaces (redraw and reprojection) copy stencil with
colour, and generated frames get the same pass. Off: nothing runs, no state
changes, and the composed frame is byte-identical to master.
test_gl_bloom (gl_bloom_test): off is byte-identical before/after bloom and
with --dump against master; on, a bright 2D panel and a panel over a lit 3D
polygon are byte-identical to off, an additive and a 3D polygon glow; guest
VRAM is unchanged; on this thread and on the render thread.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -62,6 +62,7 @@ Columns: **N** = native, **D** = DuckStation oracle.
|
||||
| `present_shot_seq` | ✓ | | — | Completion counter for `present_shot`, plus `wrote` (1 = that completion produced a PNG). Sample before staging, poll until `seq` moves. Advances on success *and* failure, so the poll always terminates |
|
||||
| `fmv_chroma` | ✓ | | `on` (optional: 0/1) | Read or set `[video] fmv_chroma_smoothing` live. Replies `on` and `frames` (24-bit presents smoothed so far) |
|
||||
| `texture_lod` | ✓ | | `mode` (optional: 0 off, 1 mipmap), `aniso` (optional: 1..16) | Read or set `[video] texture_lod` / `anisotropic_filtering` live. Replies `mode`, `aniso` |
|
||||
| `bloom` | ✓ | | `strength` (0..2), `threshold`, `view` (0 normal, 1 bloom only, 2 split, 3 sources), `time` (0/1), all optional | Read or set bloom live (`[video] bloom`). Replies the settings, `passes` and, while `time` is on, `us_mean`: the mean `glFinish`-fenced pass time over `timed` passes since the last query |
|
||||
| `post_aa` | ✓ | | `mode` (optional: 0 off, 1 fxaa, 2 fxaa_hq) | Read or set post-process anti-aliasing live (`[video] antialiasing_mode`). Replies `mode`, `passes` (filtered presents so far) and `gpu_us`: the mean pass time since the last query when the run has `PSX_POST_AA_TIME=N` (N passes per present between `glFinish` fences, a measurement mode), else 0 |
|
||||
| `hd_textures` | ✓ | | optional `replacements`, `dump` (0 or 1), `reload` (1) | HD pack root, switches, backend support, replacement count, matched/ready/applied draw counts, queued/pending dumps, composition hits/builds/pixels, GL uploads/evictions, decoded images/evictions/bytes. Optional controls update the configured host pack on the emulation thread; no configured root is an error. Reload retains unchanged upload identities. OpenGL displays replacements; software/Vulkan retain original artwork. Pair with `present_shot` for visual evidence and native VRAM probes for architectural data |
|
||||
| `gl_interp` | ✓ | | — | OpenGL frame-rate presenter ([FRAME_RATE.md](FRAME_RATE.md)): enabled/suspended, host and target Hz, swaps, `source` (`vblank`/`flip`), `flip_period`, `captures` (new source frames) and `duplicates` (VBlanks that re-presented the same frame) |
|
||||
@@ -491,9 +492,9 @@ between Play and TCP availability.
|
||||
|
||||
## Complete command index (generated)
|
||||
|
||||
**369 commands registered** — 356 on the native server (`runtime/src/debug_server.c`), 61 on the Beetle server (`runtime/src/beetle_debug_server.c`).
|
||||
**371 commands registered** — 358 on the native server (`runtime/src/debug_server.c`), 61 on the Beetle server (`runtime/src/beetle_debug_server.c`).
|
||||
|
||||
82 of 369 have prose above; **287 are index-only**. An index-only command still works — it just has no description here yet. Send it `{"cmd":"<name>"}` and read the reply, or find its `handle_*` function in the server source.
|
||||
83 of 371 have prose above; **288 are index-only**. An index-only command still works — it just has no description here yet. Send it `{"cmd":"<name>"}` and read the reply, or find its `handle_*` function in the server source.
|
||||
|
||||
Regenerate with `python tools/gen_tcp_commands.py`; `--check` fails if this block has drifted from the code.
|
||||
|
||||
@@ -509,6 +510,7 @@ Regenerate with `python tools/gen_tcp_commands.py`; `--check` fails if this bloc
|
||||
| `batch_sites` | ✓ | | ✓ |
|
||||
| `bios_info` | ✓ | | ✓ |
|
||||
| `bioscall_dump` | ✓ | | |
|
||||
| `bloom` | ✓ | | ✓ |
|
||||
| `c0_history` | ✓ | | |
|
||||
| `call_focus_dump` | ✓ | | |
|
||||
| `call_focus_reset` | ✓ | | |
|
||||
@@ -654,6 +656,7 @@ Regenerate with `python tools/gen_tcp_commands.py`; `--check` fails if this bloc
|
||||
| `history` | ✓ | ✓ | ✓ |
|
||||
| `hle_dump` | ✓ | | ✓ |
|
||||
| `host_launch_timings` | ✓ | | ✓ |
|
||||
| `host_osd_shot` | ✓ | | |
|
||||
| `host_profile` | ✓ | | |
|
||||
| `idle_skip` | ✓ | | |
|
||||
| `imask_trace` | ✓ | | |
|
||||
|
||||
@@ -604,6 +604,32 @@ the pixels to show it. Native keeps the window exactly as before.
|
||||
downsamples to the window. It accepts 1 to 32 in both `game.toml` and the
|
||||
player's `settings.toml`; 1 (the default) is native and unchanged.
|
||||
|
||||
### Bloom (`bloom`)
|
||||
|
||||
Opt-in glow around lights, OpenGL only. Off (0) changes nothing.
|
||||
|
||||
```toml
|
||||
[video]
|
||||
bloom = 1.0 # 0.0 (off, default) .. 2.0 strength
|
||||
```
|
||||
|
||||
Only what 3D primitives (proven from GTE data, so it needs PGXP geometry) and
|
||||
additive semi-transparent primitives (blend modes 1 and 3) drew can glow: each
|
||||
draw marks its pixels in a spare stencil bit, and a sprite, fill or HUD element
|
||||
drawn over them clears the mark. The marks gate both ends of the pass: only
|
||||
marked pixels feed the bright pass, and only marked pixels take the glow back,
|
||||
so a HUD, sprite or fill pixel is never changed, even right next to a lamp
|
||||
(the halo spreads over 3D surroundings only). The pass runs on the composed
|
||||
game image like `antialiasing_mode` (before it, sharing its copy texture and
|
||||
full-screen pass): a quarter-resolution bright pass, three Gaussian iterations
|
||||
and a screen blend.
|
||||
It follows 4:3, native-wide, supersample, dynamic resolution, the render
|
||||
thread and Smooth motion (generated frames carry the marks of the frame they
|
||||
are drawn from). Without PGXP geometry only additive primitives glow.
|
||||
`PSX_BLOOM=<0..2>` overrides it for one run and the `bloom` TCP command reads
|
||||
or changes it live. It is a plain `[video]` key, so a `[quality.*]` preset can
|
||||
turn it off on low-end machines.
|
||||
|
||||
### Anti-aliasing (`antialiasing_mode`, `supersample`)
|
||||
|
||||
Both are opt-in and change nothing unless set.
|
||||
|
||||
@@ -1 +1 @@
|
||||
d0e31158eda04d03cc0da52b06f7dd9c8d142f24b6e5c4aeb1442683eeb90379
|
||||
c85714906d3fdc85c7dca4d04daa574a029e4d5c7b0f273e19c25e5633889916
|
||||
|
||||
@@ -762,6 +762,15 @@ static RuntimeConfig parse_runtime_block(const toml::value& cfg, const fs::path&
|
||||
else throw std::runtime_error(fmt::format(
|
||||
"[video] antialiasing_mode must be \"off\", \"fxaa\" or \"fxaa_hq\": {}", mode));
|
||||
}
|
||||
// bloom: opt-in glow (OpenGL), 0.0 (off, default) .. 2.0.
|
||||
if (video.contains("bloom")) {
|
||||
const auto& v = toml::find(video, "bloom");
|
||||
const double f = v.is_integer() ? static_cast<double>(v.as_integer())
|
||||
: toml::get<double>(v);
|
||||
if (!(f >= 0.0 && f <= 2.0))
|
||||
throw std::runtime_error(fmt::format("[video] bloom out of range (0.0..2.0): {}", f));
|
||||
rt.video_bloom = static_cast<float>(f);
|
||||
}
|
||||
if (video.contains("supersample")) {
|
||||
const auto& v = toml::find(video, "supersample");
|
||||
const double f = v.is_integer() ? static_cast<double>(v.as_integer())
|
||||
|
||||
@@ -455,6 +455,9 @@ struct RuntimeConfig {
|
||||
// "fxaa_hq". Separate from the legacy antialiasing bool above, which the
|
||||
// launcher and settings.toml already persist as the linear present filter.
|
||||
int video_antialiasing_mode = 0;
|
||||
// bloom: opt-in glow strength, 0.0 (off, the default) .. 2.0. A plain
|
||||
// [video] key, so [quality.*] presets can set it.
|
||||
float video_bloom = 0.0f;
|
||||
// supersample: factor on the internal-resolution target (Match display or
|
||||
// a line preset), 1.0..4.0, kept in thousandths (internal_resolution.h).
|
||||
int video_supersample_milli = 1000;
|
||||
|
||||
@@ -388,6 +388,16 @@ if(BUILD_TESTING)
|
||||
--output "${CMAKE_CURRENT_BINARY_DIR}/gl-post-aa-results")
|
||||
set_tests_properties(gl_post_aa_test PROPERTIES
|
||||
LABELS "gpu;opengl;hardware" TIMEOUT 180 SKIP_RETURN_CODE 77)
|
||||
add_test(NAME gl_bloom_test
|
||||
COMMAND "${Python3_EXECUTABLE}"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/tests/run_gl_texture_filter.py"
|
||||
--fixture "${CMAKE_CURRENT_SOURCE_DIR}/tests/test_gl_bloom.c"
|
||||
--cc "${CMAKE_C_COMPILER}"
|
||||
--sdl-include "$<TARGET_PROPERTY:SDL3::Headers,INTERFACE_INCLUDE_DIRECTORIES>"
|
||||
--sdl-library "$<TARGET_FILE:SDL3::SDL3-static>"
|
||||
--output "${CMAKE_CURRENT_BINARY_DIR}/gl-bloom-results")
|
||||
set_tests_properties(gl_bloom_test PROPERTIES
|
||||
LABELS "gpu;opengl;hardware" TIMEOUT 180 SKIP_RETURN_CODE 77)
|
||||
add_test(NAME gl_hd_texture_pack_test
|
||||
COMMAND "${Python3_EXECUTABLE}"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/tests/run_gl_texture_filter.py"
|
||||
|
||||
@@ -184,6 +184,21 @@ int gl_renderer_has_bezel(void);
|
||||
* filtered and guest VRAM is never touched. Takes effect at the next present;
|
||||
* safe to call from any thread. */
|
||||
enum { GL_POST_AA_OFF = 0, GL_POST_AA_FXAA = 1, GL_POST_AA_FXAA_HQ = 2 };
|
||||
/* Opt-in bloom ([video] bloom, 0 = off .. 2; PSX_BLOOM overrides): glow from
|
||||
* 3D and additive primitives only, never the HUD (gl_bloom.inc). Safe from
|
||||
* any thread; takes effect at the next draw. */
|
||||
void gl_renderer_set_bloom(float strength);
|
||||
float gl_renderer_get_bloom(void);
|
||||
typedef struct {
|
||||
float strength, threshold;
|
||||
int view;
|
||||
uint64_t passes, us_n;
|
||||
double us_mean; /* glFinish-bracketed pass cost while timing, then reset */
|
||||
} GlBloomInfo;
|
||||
/* Debug: negative arguments keep the current value. view 0 normal, 1 bloom
|
||||
* only, 2 split (stock | bloom), 3 the masked bright pass. */
|
||||
void gl_renderer_bloom_debug(float strength, float threshold, int view, int time,
|
||||
GlBloomInfo *out);
|
||||
int gl_renderer_set_post_aa(int mode);
|
||||
int gl_renderer_post_aa(void);
|
||||
uint64_t gl_renderer_post_aa_passes(void);
|
||||
|
||||
@@ -8598,6 +8598,25 @@ static void handle_synth_recurse(int id, const char *json)
|
||||
|
||||
/* post_aa: post-process anti-aliasing, live. {"cmd":"post_aa"} reads it,
|
||||
* {"cmd":"post_aa","mode":0|1|2} sets it (off, fxaa, fxaa_hq). */
|
||||
/* bloom: {"cmd":"bloom"} reads; optional "strength" (0..2), "threshold",
|
||||
* "view" (0 normal, 1 bloom only, 2 split, 3 sources) and "time" (1: time
|
||||
* each pass; the mean resets on read) set. */
|
||||
static void handle_bloom(int id, const char *json)
|
||||
{
|
||||
const char *p = strstr(json, "\"strength\"");
|
||||
float strength = -1.0f, threshold = -1.0f;
|
||||
if (p && (p = strchr(p, ':'))) strength = (float)strtod(p + 1, NULL);
|
||||
p = strstr(json, "\"threshold\"");
|
||||
if (p && (p = strchr(p, ':'))) threshold = (float)strtod(p + 1, NULL);
|
||||
GlBloomInfo bi;
|
||||
gl_renderer_bloom_debug(strength, threshold, json_get_int(json, "view", -1),
|
||||
json_get_int(json, "time", -1), &bi);
|
||||
send_fmt("{\"id\":%d,\"ok\":true,\"strength\":%.3f,\"threshold\":%.3f,\"view\":%d,"
|
||||
"\"passes\":%llu,\"timed\":%llu,\"us_mean\":%.1f}",
|
||||
id, bi.strength, bi.threshold, bi.view, (unsigned long long)bi.passes,
|
||||
(unsigned long long)bi.us_n, bi.us_mean);
|
||||
}
|
||||
|
||||
static void handle_post_aa(int id, const char *json)
|
||||
{
|
||||
int mode = json_get_int(json, "mode", -1);
|
||||
@@ -15922,6 +15941,7 @@ static const CmdEntry s_commands[] = {
|
||||
{ "accurate_blending", handle_accurate_blending },
|
||||
|
||||
{ "texture_filter", handle_texture_filter },
|
||||
{ "bloom", handle_bloom },
|
||||
{ "gl_ws_ablate", handle_gl_ws_ablate },
|
||||
{ "gl_interp", handle_gl_interp },
|
||||
{ "render_pass_stats", handle_render_pass_stats },
|
||||
|
||||
@@ -0,0 +1,309 @@
|
||||
/* ---- Bloom ([video] bloom) -------------------------------------------------
|
||||
* Opt-in, off by default: with strength 0 nothing here runs, no draw is marked
|
||||
* and the present path is the stock one.
|
||||
*
|
||||
* Marks. Only pixels a 3D primitive (PGXP-proven: needs PGXP geometry) or an
|
||||
* additive semi-transparent primitive (modes 1 and 3) drew last are "lit":
|
||||
* every draw keeps that in stencil bit 1 of the surface it draws into
|
||||
* (BCLS_DRAW in gpu_gl_renderer.c), so a 2D sprite, a fill or the HUD drawn
|
||||
* over a lamp clears it again. The mark travels with the picture: native-wide
|
||||
* centre copies and frame-generation surfaces copy it.
|
||||
*
|
||||
* Pass. Like post-process AA (and with its plumbing: post_clip_rect, the
|
||||
* shared copy texture, post_fullscreen) this works on the composed game image
|
||||
* in the window, before AA and the OSD, so it composes with 4:3, native-wide,
|
||||
* supersample and dynamic resolution. The presented surface's marks are
|
||||
* blitted (NEAREST, flipped to window orientation) into a window-sized stencil
|
||||
* and turned into a mask texture. The marks gate both ends: only lit pixels
|
||||
* feed the soft-knee bright pass (quarter resolution, three separable Gaussian
|
||||
* iterations), and only lit pixels take the screen blend back. A HUD, sprite
|
||||
* or fill pixel is therefore never a source and never changed, even right
|
||||
* next to a lamp. A temporal-blending in-between present reuses the last
|
||||
* marks. Guest VRAM and the surfaces are never written. */
|
||||
static float s_bloom_thr = 0.55f; /* bright-pass threshold (max channel) */
|
||||
static int s_bloom_view = 0; /* debug: 1 bloom only, 2 split, 3 sources */
|
||||
static int s_bloom_failed = 0;
|
||||
static GLuint s_bloom_src_fbo = 0;
|
||||
static int s_bloom_src[4]; /* x0,y0,x1,y1 in source FBO px (y0 = top) */
|
||||
static int s_bloom_src_valid = 0, s_bloom_mask_ok = 0;
|
||||
static GLuint s_bloom_mask_tex = 0, s_bloom_rb = 0, s_bloom_mfbo = 0; /* window-sized marks */
|
||||
static GLuint s_bloom_tex[2] = {0, 0}, s_bloom_fbo[2] = {0, 0}; /* quarter-res bloom */
|
||||
static int s_bloom_w = 0, s_bloom_h = 0;
|
||||
static GLuint s_bloom_mask_prog = 0, s_bloom_bright_prog = 0, s_bloom_blur_prog = 0,
|
||||
s_bloom_comp_prog = 0;
|
||||
static uint64_t s_bloom_passes = 0;
|
||||
static int s_bloom_time = 0; /* debug: glFinish-bracketed cost */
|
||||
static double s_bloom_us_sum = 0.0; static uint64_t s_bloom_us_n = 0;
|
||||
|
||||
static const char *BLOOM_COMMON =
|
||||
"#version 330\n"
|
||||
"in vec2 v_uv; out vec4 o;\n";
|
||||
|
||||
static const char *BLOOM_MASK_FS_BODY =
|
||||
"void main(){ o = vec4(1.0); }\n";
|
||||
|
||||
static const char *BLOOM_BRIGHT_FS_BODY =
|
||||
"uniform sampler2D u_tex, u_mask; uniform vec2 u_rcp; uniform float u_thr;\n"
|
||||
"vec3 S(vec2 p){ return textureLod(u_tex, p, 0.0).rgb * textureLod(u_mask, p, 0.0).r; }\n"
|
||||
"void main(){\n"
|
||||
/* 4 taps of the full-resolution image, lit pixels only */
|
||||
" vec3 c = 0.25*(S(v_uv+u_rcp*vec2(-1,-1))+S(v_uv+u_rcp*vec2(1,-1))+S(v_uv+u_rcp*vec2(-1,1))+S(v_uv+u_rcp*vec2(1,1)));\n"
|
||||
" float l = max(c.r, max(c.g, c.b));\n"
|
||||
" float k = 0.15; float s = clamp(l - u_thr + k, 0.0, 2.0*k); s = s*s/(4.0*k+1e-5);\n"
|
||||
" float w = max(s, l - u_thr) / max(l, 1e-4);\n"
|
||||
" o = vec4(c * w, 1.0);\n"
|
||||
"}\n";
|
||||
|
||||
static const char *BLOOM_BLUR_FS_BODY =
|
||||
"uniform sampler2D u_tex; uniform vec2 u_dir;\n"
|
||||
"void main(){\n"
|
||||
" vec3 c = textureLod(u_tex, v_uv, 0.0).rgb * 0.2270270;\n"
|
||||
" c += (textureLod(u_tex, v_uv + u_dir*1.3846154, 0.0).rgb + textureLod(u_tex, v_uv - u_dir*1.3846154, 0.0).rgb) * 0.3162162;\n"
|
||||
" c += (textureLod(u_tex, v_uv + u_dir*3.2307692, 0.0).rgb + textureLod(u_tex, v_uv - u_dir*3.2307692, 0.0).rgb) * 0.0702703;\n"
|
||||
" o = vec4(c, 1.0);\n"
|
||||
"}\n";
|
||||
|
||||
/* Exact per-pixel: an unlit pixel is the copy's texel, unchanged. */
|
||||
static const char *BLOOM_COMP_FS_BODY =
|
||||
"uniform sampler2D u_tex, u_bloom, u_mask; uniform float u_k; uniform int u_view; uniform ivec2 u_org;\n"
|
||||
"void main(){\n"
|
||||
" ivec2 p = ivec2(gl_FragCoord.xy) - u_org;\n"
|
||||
" vec4 c4 = texelFetch(u_tex, p, 0); vec3 c = c4.rgb;\n"
|
||||
" bool lit = texelFetch(u_mask, p, 0).r > 0.5;\n"
|
||||
" vec3 b = clamp(textureLod(u_bloom, v_uv, 0.0).rgb * u_k, 0.0, 1.0);\n"
|
||||
" vec3 r = lit ? 1.0 - (1.0 - c) * (1.0 - b) : c;\n"
|
||||
" if (u_view == 1 || u_view == 3) r = b;\n"
|
||||
/* 2: split view, stock left / bloom right */
|
||||
" if (u_view == 2) { if (v_uv.x < 0.5) r = c; if (abs(v_uv.x - 0.5) < 0.0012) r = vec3(1.0); }\n"
|
||||
" o = vec4(r, c4.a);\n"
|
||||
"}\n";
|
||||
|
||||
static GLuint bloom_build(const char *body) {
|
||||
static char buf[4096];
|
||||
snprintf(buf, sizeof(buf), "%s%s", BLOOM_COMMON, body);
|
||||
return build_program(PRESENT_VS, buf);
|
||||
}
|
||||
|
||||
static void bloom_tex(GLuint *t, int w, int h, GLenum ifmt, GLenum fmt, GLenum type, GLenum filt) {
|
||||
if (!*t) glGenTextures(1, t);
|
||||
glBindTexture(GL_TEXTURE_2D, *t);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, ifmt, w, h, 0, fmt, type, NULL);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filt);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filt);
|
||||
}
|
||||
static void bloom_fbo(GLuint *f, GLuint tex) {
|
||||
if (!*f) p_glGenFramebuffers(1, f);
|
||||
p_glBindFramebuffer(PSXGL_FRAMEBUFFER, *f);
|
||||
p_glFramebufferTexture2D(PSXGL_FRAMEBUFFER, PSXGL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex, 0);
|
||||
}
|
||||
|
||||
/* The present path's source: its FBO and the displayed rect, in native units
|
||||
* of a texture tex_w x tex_h native units across (as present_target_quad). */
|
||||
static void bloom_note_source(GLuint fbo, GLuint tex, float tex_w, float tex_h,
|
||||
int x, int y, int w, int h) {
|
||||
s_bloom_src_valid = 0;
|
||||
if (!(s_bloom > 0.0f) || !fbo || !tex || tex_w <= 0.0f || tex_h <= 0.0f) return;
|
||||
GLint tw = 0, th = 0;
|
||||
glBindTexture(GL_TEXTURE_2D, tex);
|
||||
glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &tw);
|
||||
glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &th);
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
const float sx = (float)tw / tex_w, sy = (float)th / tex_h;
|
||||
s_bloom_src_fbo = fbo;
|
||||
s_bloom_src[0] = (int)lrintf((float)x * sx);
|
||||
s_bloom_src[1] = (int)lrintf((float)y * sy);
|
||||
s_bloom_src[2] = (int)lrintf((float)(x + w) * sx);
|
||||
s_bloom_src[3] = (int)lrintf((float)(y + h) * sy);
|
||||
s_bloom_src_valid = 1;
|
||||
}
|
||||
|
||||
/* The GL state the pass changes, put back afterwards so whatever runs next
|
||||
* (post-AA, the OSD, the next frame's draws) sees what it left. */
|
||||
typedef struct {
|
||||
GLboolean blend, depth, scissor, stencil;
|
||||
GLfloat clear[4];
|
||||
GLint viewport[4], fbo, rfbo;
|
||||
} BloomGlState;
|
||||
static void bloom_state_save(BloomGlState *st) {
|
||||
st->blend = glIsEnabled(GL_BLEND); st->depth = glIsEnabled(GL_DEPTH_TEST);
|
||||
st->scissor = glIsEnabled(GL_SCISSOR_TEST); st->stencil = glIsEnabled(GL_STENCIL_TEST);
|
||||
glGetFloatv(GL_COLOR_CLEAR_VALUE, st->clear);
|
||||
glGetIntegerv(GL_VIEWPORT, st->viewport);
|
||||
glGetIntegerv(0x8CA6 /*DRAW_FRAMEBUFFER_BINDING*/, &st->fbo);
|
||||
glGetIntegerv(0x8CAA /*READ_FRAMEBUFFER_BINDING*/, &st->rfbo);
|
||||
}
|
||||
static void bloom_state_restore(const BloomGlState *st) {
|
||||
if (st->blend) glEnable(GL_BLEND); else glDisable(GL_BLEND);
|
||||
if (st->depth) glEnable(GL_DEPTH_TEST); else glDisable(GL_DEPTH_TEST);
|
||||
if (st->scissor) glEnable(GL_SCISSOR_TEST); else glDisable(GL_SCISSOR_TEST);
|
||||
if (st->stencil) glEnable(GL_STENCIL_TEST); else glDisable(GL_STENCIL_TEST);
|
||||
glClearColor(st->clear[0], st->clear[1], st->clear[2], st->clear[3]);
|
||||
glViewport(st->viewport[0], st->viewport[1], st->viewport[2], st->viewport[3]);
|
||||
p_glBindFramebuffer(PSXGL_DRAW_FRAMEBUFFER, (GLuint)st->fbo);
|
||||
p_glBindFramebuffer(PSXGL_READ_FRAMEBUFFER, (GLuint)st->rfbo);
|
||||
}
|
||||
|
||||
static void bloom_apply_pass(int src_ok, int lx, int ly, int lw, int lh);
|
||||
/* fresh: the noted source is this present's (0: reuse the last marks). */
|
||||
static void bloom_apply(int fresh, int lx, int ly, int lw, int lh) {
|
||||
const int src_ok = fresh && s_bloom_src_valid;
|
||||
s_bloom_src_valid = 0;
|
||||
if (!(s_bloom > 0.0f) || !s_ctx || s_bloom_failed) return;
|
||||
if (!post_clip_rect(&lx, &ly, &lw, &lh, 8)) return;
|
||||
BloomGlState st;
|
||||
bloom_state_save(&st);
|
||||
bloom_apply_pass(src_ok, lx, ly, lw, lh);
|
||||
bloom_state_restore(&st);
|
||||
}
|
||||
|
||||
static void bloom_apply_pass(int src_ok, int lx, int ly, int lw, int lh) {
|
||||
if (!s_bloom_comp_prog) {
|
||||
s_bloom_mask_prog = bloom_build(BLOOM_MASK_FS_BODY);
|
||||
s_bloom_bright_prog = bloom_build(BLOOM_BRIGHT_FS_BODY);
|
||||
s_bloom_blur_prog = bloom_build(BLOOM_BLUR_FS_BODY);
|
||||
s_bloom_comp_prog = bloom_build(BLOOM_COMP_FS_BODY);
|
||||
if (!s_bloom_mask_prog || !s_bloom_bright_prog || !s_bloom_blur_prog || !s_bloom_comp_prog) {
|
||||
fprintf(stdout, "psxrecomp: bloom shader build failed; bloom off\n");
|
||||
s_bloom_failed = 1; return;
|
||||
}
|
||||
}
|
||||
#ifndef PSX_NO_DEBUG_TOOLS
|
||||
uint64_t t0 = 0;
|
||||
if (s_bloom_time) { glFinish(); t0 = SDL_GetPerformanceCounter(); }
|
||||
#endif
|
||||
const int bw = lw / 4 > 0 ? lw / 4 : 1, bh = lh / 4 > 0 ? lh / 4 : 1;
|
||||
p_glActiveTexture(PSXGL_TEXTURE0);
|
||||
if (s_bloom_w != lw || s_bloom_h != lh) {
|
||||
for (int i = 0; i < 2; i++) {
|
||||
bloom_tex(&s_bloom_tex[i], bw, bh, 0x881A /*RGBA16F*/, GL_RGBA, GL_FLOAT, GL_LINEAR);
|
||||
bloom_fbo(&s_bloom_fbo[i], s_bloom_tex[i]);
|
||||
}
|
||||
bloom_tex(&s_bloom_mask_tex, lw, lh, 0x8229 /*R8*/, 0x1903 /*RED*/, GL_UNSIGNED_BYTE, GL_NEAREST);
|
||||
bloom_fbo(&s_bloom_mfbo, s_bloom_mask_tex);
|
||||
if (!s_bloom_rb) p_glGenRenderbuffers(1, &s_bloom_rb);
|
||||
p_glBindRenderbuffer(PSXGL_RENDERBUFFER, s_bloom_rb);
|
||||
p_glRenderbufferStorage(PSXGL_RENDERBUFFER, PSXGL_DEPTH24_STENCIL8, lw, lh);
|
||||
p_glFramebufferRenderbuffer(PSXGL_FRAMEBUFFER, PSXGL_DEPTH_STENCIL_ATTACHMENT,
|
||||
PSXGL_RENDERBUFFER, s_bloom_rb);
|
||||
s_bloom_w = lw; s_bloom_h = lh; s_bloom_mask_ok = 0;
|
||||
}
|
||||
glDisable(GL_BLEND); glDisable(GL_DEPTH_TEST); glDisable(GL_SCISSOR_TEST);
|
||||
glClearColor(0.f, 0.f, 0.f, 0.f);
|
||||
if (src_ok) {
|
||||
/* marks: source rect (y0 = top row) -> window orientation (row 0 = bottom) */
|
||||
p_glBindFramebuffer(PSXGL_READ_FRAMEBUFFER, s_bloom_src_fbo);
|
||||
p_glBindFramebuffer(PSXGL_DRAW_FRAMEBUFFER, s_bloom_mfbo);
|
||||
p_glBlitFramebuffer(s_bloom_src[0], s_bloom_src[1], s_bloom_src[2], s_bloom_src[3],
|
||||
0, lh, lw, 0, GL_STENCIL_BUFFER_BIT, GL_NEAREST);
|
||||
/* marks -> mask texture (1 = lit) */
|
||||
p_glBindFramebuffer(PSXGL_FRAMEBUFFER, s_bloom_mfbo);
|
||||
glViewport(0, 0, lw, lh);
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
glEnable(GL_STENCIL_TEST);
|
||||
glStencilFunc(GL_EQUAL, 0x02, 0x02);
|
||||
glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
|
||||
post_fullscreen(s_bloom_mask_prog, p_glGetUniformLocation(s_bloom_mask_prog, "u_uv_rect"));
|
||||
glDisable(GL_STENCIL_TEST);
|
||||
s_bloom_mask_ok = 1;
|
||||
}
|
||||
if (!s_bloom_mask_ok) return;
|
||||
post_copy_bind(lw, lh);
|
||||
post_copy_window(lx, ly, lw, lh);
|
||||
|
||||
/* bright pass from lit pixels */
|
||||
p_glBindFramebuffer(PSXGL_FRAMEBUFFER, s_bloom_fbo[0]);
|
||||
glViewport(0, 0, bw, bh);
|
||||
p_glActiveTexture(PSXGL_TEXTURE0 + 1); glBindTexture(GL_TEXTURE_2D, s_bloom_mask_tex);
|
||||
p_glActiveTexture(PSXGL_TEXTURE0);
|
||||
p_glUseProgram(s_bloom_bright_prog);
|
||||
p_glUniform1i(p_glGetUniformLocation(s_bloom_bright_prog, "u_tex"), 0);
|
||||
p_glUniform1i(p_glGetUniformLocation(s_bloom_bright_prog, "u_mask"), 1);
|
||||
p_glUniform2f(p_glGetUniformLocation(s_bloom_bright_prog, "u_rcp"), 1.0f / (float)lw, 1.0f / (float)lh);
|
||||
p_glUniform1f(p_glGetUniformLocation(s_bloom_bright_prog, "u_thr"), s_bloom_thr);
|
||||
post_fullscreen(s_bloom_bright_prog, p_glGetUniformLocation(s_bloom_bright_prog, "u_uv_rect"));
|
||||
if (s_bloom_view != 3) {
|
||||
p_glUseProgram(s_bloom_blur_prog);
|
||||
p_glUniform1i(p_glGetUniformLocation(s_bloom_blur_prog, "u_tex"), 0);
|
||||
const GLint ud = p_glGetUniformLocation(s_bloom_blur_prog, "u_dir");
|
||||
const GLint uv = p_glGetUniformLocation(s_bloom_blur_prog, "u_uv_rect");
|
||||
for (int it = 0; it < 3; it++) {
|
||||
const float sp = (float)(1 + it);
|
||||
p_glBindFramebuffer(PSXGL_FRAMEBUFFER, s_bloom_fbo[1]);
|
||||
glBindTexture(GL_TEXTURE_2D, s_bloom_tex[0]);
|
||||
p_glUniform2f(ud, sp / (float)bw, 0.0f);
|
||||
post_fullscreen(s_bloom_blur_prog, uv);
|
||||
p_glBindFramebuffer(PSXGL_FRAMEBUFFER, s_bloom_fbo[0]);
|
||||
glBindTexture(GL_TEXTURE_2D, s_bloom_tex[1]);
|
||||
p_glUniform2f(ud, 0.0f, sp / (float)bh);
|
||||
post_fullscreen(s_bloom_blur_prog, uv);
|
||||
}
|
||||
}
|
||||
/* composite into the window rect, lit pixels only */
|
||||
p_glBindFramebuffer(PSXGL_FRAMEBUFFER, 0);
|
||||
glViewport(lx, ly, lw, lh);
|
||||
p_glUseProgram(s_bloom_comp_prog);
|
||||
p_glActiveTexture(PSXGL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, s_post_copy_tex);
|
||||
p_glActiveTexture(PSXGL_TEXTURE0 + 1); glBindTexture(GL_TEXTURE_2D, s_bloom_tex[0]);
|
||||
p_glActiveTexture(PSXGL_TEXTURE0 + 2); glBindTexture(GL_TEXTURE_2D, s_bloom_mask_tex);
|
||||
p_glUniform1i(p_glGetUniformLocation(s_bloom_comp_prog, "u_tex"), 0);
|
||||
p_glUniform1i(p_glGetUniformLocation(s_bloom_comp_prog, "u_bloom"), 1);
|
||||
p_glUniform1i(p_glGetUniformLocation(s_bloom_comp_prog, "u_mask"), 2);
|
||||
p_glUniform2i(p_glGetUniformLocation(s_bloom_comp_prog, "u_org"), lx, ly);
|
||||
p_glUniform1f(p_glGetUniformLocation(s_bloom_comp_prog, "u_k"), s_bloom_view == 3 ? 1.0f : s_bloom);
|
||||
p_glUniform1i(p_glGetUniformLocation(s_bloom_comp_prog, "u_view"), s_bloom_view);
|
||||
post_fullscreen(s_bloom_comp_prog, p_glGetUniformLocation(s_bloom_comp_prog, "u_uv_rect"));
|
||||
for (int u = 2; u >= 0; u--) { p_glActiveTexture(PSXGL_TEXTURE0 + u); glBindTexture(GL_TEXTURE_2D, 0); }
|
||||
p_glBindVertexArray(0);
|
||||
p_glUseProgram(0);
|
||||
#ifndef PSX_NO_DEBUG_TOOLS
|
||||
if (s_bloom_time) {
|
||||
glFinish();
|
||||
s_bloom_us_sum += (double)(SDL_GetPerformanceCounter() - t0) * 1e6 /
|
||||
(double)SDL_GetPerformanceFrequency();
|
||||
s_bloom_us_n++;
|
||||
}
|
||||
#endif
|
||||
s_bloom_passes++;
|
||||
}
|
||||
|
||||
/* Objects died with the context. */
|
||||
static void bloom_release(void) {
|
||||
s_bloom_mask_tex = s_bloom_rb = s_bloom_mfbo = 0;
|
||||
s_bloom_tex[0] = s_bloom_tex[1] = s_bloom_fbo[0] = s_bloom_fbo[1] = 0;
|
||||
s_bloom_mask_prog = s_bloom_bright_prog = s_bloom_blur_prog = s_bloom_comp_prog = 0;
|
||||
s_bloom_w = s_bloom_h = 0; s_bloom_failed = 0; s_bloom_mask_ok = 0; s_bloom_src_valid = 0;
|
||||
}
|
||||
|
||||
/* PSX_BLOOM=<0..2> overrides [video] bloom. */
|
||||
void gl_renderer_set_bloom(float strength) {
|
||||
GL_RT_SYNC("set_bloom");
|
||||
const char *e = getenv("PSX_BLOOM");
|
||||
if (e && *e) strength = (float)atof(e);
|
||||
if (!(strength >= 0.0f)) strength = 0.0f;
|
||||
if (strength > 2.0f) strength = 2.0f;
|
||||
s_bloom = strength;
|
||||
s_bcls_on = strength > 0.0f;
|
||||
pgxp_render_wanted_update();
|
||||
}
|
||||
float gl_renderer_get_bloom(void) { return s_bloom; }
|
||||
|
||||
void gl_renderer_bloom_debug(float strength, float threshold, int view, int time,
|
||||
GlBloomInfo *out) {
|
||||
GL_RT_SYNC("bloom_debug");
|
||||
if (strength >= 0.0f && strength <= 2.0f) {
|
||||
s_bloom = strength; s_bcls_on = strength > 0.0f;
|
||||
pgxp_render_wanted_update();
|
||||
}
|
||||
if (threshold > 0.0f) s_bloom_thr = threshold;
|
||||
if (view >= 0) s_bloom_view = view;
|
||||
if (time >= 0) s_bloom_time = time ? 1 : 0;
|
||||
if (out) {
|
||||
out->strength = s_bloom; out->threshold = s_bloom_thr; out->view = s_bloom_view;
|
||||
out->passes = s_bloom_passes;
|
||||
out->us_mean = s_bloom_us_n ? s_bloom_us_sum / (double)s_bloom_us_n : 0.0;
|
||||
out->us_n = s_bloom_us_n;
|
||||
s_bloom_us_sum = 0.0; s_bloom_us_n = 0;
|
||||
}
|
||||
}
|
||||
+211
-45
@@ -1373,9 +1373,13 @@ static GlPresEvent s_pres_ring[GL_PRES_RING_CAP];
|
||||
static uint64_t s_pres_seq = 0;
|
||||
|
||||
static void post_aa_apply(int lx, int ly, int lw, int lh);
|
||||
static float s_bloom = 0.0f; /* [video] bloom strength (gl_bloom.inc) */
|
||||
static void bloom_apply(int fresh, int lx, int ly, int lw, int lh);
|
||||
static void pres_record(int path, int dx, int dy, int w, int h,
|
||||
int lx, int ly, int lw, int lh) {
|
||||
/* The composed game image, before hold-last capture and the OSD. */
|
||||
if (s_bloom > 0.0f && (path == GL_PRES_VRAM || path == GL_PRES_WIDE || path == GL_PRES_INTERP))
|
||||
bloom_apply(path != GL_PRES_INTERP, lx, ly, lw, lh);
|
||||
if (s_post_aa && (path == GL_PRES_VRAM || path == GL_PRES_WIDE || path == GL_PRES_INTERP))
|
||||
post_aa_apply(lx, ly, lw, lh);
|
||||
s_native_surface_pending=s_native_surface_enabled &&
|
||||
@@ -1461,8 +1465,8 @@ static const char *PRESENT_VS =
|
||||
* (luma contrast test, edge orientation, bounded end-of-edge search,
|
||||
* sub-pixel blend); it is one copy plus one full-screen pass. */
|
||||
static GLuint build_program(const char *vs, const char *fs);
|
||||
static GLuint s_paa_prog = 0, s_paa_tex = 0;
|
||||
static int s_paa_tw = 0, s_paa_th = 0, s_paa_failed = 0;
|
||||
static GLuint s_paa_prog = 0;
|
||||
static int s_paa_failed = 0;
|
||||
static GLint s_paa_uTex = -1, s_paa_uRcp = -1, s_paa_uUvRect = -1, s_paa_uQuality = -1;
|
||||
static uint64_t s_paa_passes = 0;
|
||||
#ifndef PSX_NO_DEBUG_TOOLS
|
||||
@@ -1546,20 +1550,62 @@ uint64_t gl_renderer_post_aa_passes(void) {
|
||||
return s_paa_passes;
|
||||
}
|
||||
|
||||
/* ---- shared post-pass plumbing (post-process AA, bloom) -------------------
|
||||
* Both passes work in place on the composed game image in the window: clip
|
||||
* the rect to the drawable, snapshot it into one shared copy texture (the
|
||||
* passes run one after the other, each from a fresh copy), and draw a
|
||||
* full-window triangle over it with PRESENT_VS. */
|
||||
static GLuint s_post_copy_tex = 0;
|
||||
static int s_post_copy_w = 0, s_post_copy_h = 0;
|
||||
/* Clip [lx,ly,lw,lh] to the drawable; 0 when less than min px remain. */
|
||||
static int post_clip_rect(int *lx, int *ly, int *lw, int *lh, int min) {
|
||||
int ww = 0, wh = 0;
|
||||
SDL_GL_GetDrawableSize(s_win, &ww, &wh);
|
||||
if (*lx < 0) { *lw += *lx; *lx = 0; }
|
||||
if (*ly < 0) { *lh += *ly; *ly = 0; }
|
||||
if (*lx + *lw > ww) *lw = ww - *lx;
|
||||
if (*ly + *lh > wh) *lh = wh - *ly;
|
||||
return *lw >= min && *lh >= min;
|
||||
}
|
||||
/* Bind (and size) the shared copy texture on unit 0. */
|
||||
static void post_copy_bind(int lw, int lh) {
|
||||
p_glActiveTexture(PSXGL_TEXTURE0);
|
||||
if (!s_post_copy_tex || s_post_copy_w != lw || s_post_copy_h != lh) {
|
||||
if (!s_post_copy_tex) glGenTextures(1, &s_post_copy_tex);
|
||||
glBindTexture(GL_TEXTURE_2D, s_post_copy_tex);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, lw, lh, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
s_post_copy_w = lw; s_post_copy_h = lh;
|
||||
} else {
|
||||
glBindTexture(GL_TEXTURE_2D, s_post_copy_tex);
|
||||
}
|
||||
}
|
||||
/* Window rect -> the shared copy (bottom-up, as the window). */
|
||||
static void post_copy_window(int lx, int ly, int lw, int lh) {
|
||||
p_glBindFramebuffer(PSXGL_READ_FRAMEBUFFER, 0);
|
||||
glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, lx, ly, lw, lh);
|
||||
}
|
||||
/* One full-target triangle with prog (PRESENT_VS). PRESENT_VS flips v;
|
||||
* (0,1,1,0) cancels it for a bottom-up copy. */
|
||||
static void post_fullscreen(GLuint prog, GLint uv_rect) {
|
||||
p_glUseProgram(prog);
|
||||
p_glUniform4f(uv_rect, 0.0f, 1.0f, 1.0f, 0.0f);
|
||||
p_glBindVertexArray(s_present_vao);
|
||||
glDrawArrays(GL_TRIANGLES, 0, 3);
|
||||
}
|
||||
|
||||
static void post_aa_release(void) {
|
||||
s_paa_prog = 0; s_paa_tex = 0; s_paa_tw = s_paa_th = 0; s_paa_failed = 0;
|
||||
s_paa_prog = 0; s_paa_failed = 0;
|
||||
s_post_copy_tex = 0; s_post_copy_w = s_post_copy_h = 0;
|
||||
}
|
||||
|
||||
/* The composed image in [lx,ly,lw,lh] of the bound window target, in place. */
|
||||
static void post_aa_apply(int lx, int ly, int lw, int lh) {
|
||||
if (!s_post_aa || !s_ctx || s_paa_failed) return;
|
||||
int ww = 0, wh = 0;
|
||||
SDL_GL_GetDrawableSize(s_win, &ww, &wh);
|
||||
if (lx < 0) { lw += lx; lx = 0; }
|
||||
if (ly < 0) { lh += ly; ly = 0; }
|
||||
if (lx + lw > ww) lw = ww - lx;
|
||||
if (ly + lh > wh) lh = wh - ly;
|
||||
if (lw < 3 || lh < 3) return;
|
||||
if (!post_clip_rect(&lx, &ly, &lw, &lh, 3)) return;
|
||||
if (!s_paa_prog) {
|
||||
s_paa_prog = build_program(PRESENT_VS, POST_AA_FS);
|
||||
if (!s_paa_prog) { s_paa_failed = 1; return; }
|
||||
@@ -1568,19 +1614,7 @@ static void post_aa_apply(int lx, int ly, int lw, int lh) {
|
||||
s_paa_uUvRect = p_glGetUniformLocation(s_paa_prog, "u_uv_rect");
|
||||
s_paa_uQuality = p_glGetUniformLocation(s_paa_prog, "u_quality");
|
||||
}
|
||||
p_glActiveTexture(PSXGL_TEXTURE0);
|
||||
if (!s_paa_tex || s_paa_tw != lw || s_paa_th != lh) {
|
||||
if (!s_paa_tex) glGenTextures(1, &s_paa_tex);
|
||||
glBindTexture(GL_TEXTURE_2D, s_paa_tex);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, lw, lh, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
s_paa_tw = lw; s_paa_th = lh;
|
||||
} else {
|
||||
glBindTexture(GL_TEXTURE_2D, s_paa_tex);
|
||||
}
|
||||
post_copy_bind(lw, lh);
|
||||
#ifndef PSX_NO_DEBUG_TOOLS
|
||||
if (s_paa_time < 0) {
|
||||
const char *e = getenv("PSX_POST_AA_TIME");
|
||||
@@ -1594,18 +1628,14 @@ static void post_aa_apply(int lx, int ly, int lw, int lh) {
|
||||
const int reps = 1;
|
||||
#endif
|
||||
for (int r = 0; r < reps; r++) {
|
||||
p_glBindFramebuffer(PSXGL_READ_FRAMEBUFFER, 0);
|
||||
glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, lx, ly, lw, lh);
|
||||
post_copy_window(lx, ly, lw, lh);
|
||||
p_glBindFramebuffer(PSXGL_FRAMEBUFFER, 0);
|
||||
glViewport(lx, ly, lw, lh);
|
||||
p_glUseProgram(s_paa_prog);
|
||||
p_glUniform1i(s_paa_uTex, 0);
|
||||
p_glUniform2f(s_paa_uRcp, 1.0f / (float)lw, 1.0f / (float)lh);
|
||||
p_glUniform1i(s_paa_uQuality, s_post_aa == GL_POST_AA_FXAA_HQ ? 1 : 0);
|
||||
/* PRESENT_VS flips v; (0,1,1,0) cancels it for a bottom-up copy. */
|
||||
p_glUniform4f(s_paa_uUvRect, 0.0f, 1.0f, 1.0f, 0.0f);
|
||||
p_glBindVertexArray(s_present_vao);
|
||||
glDrawArrays(GL_TRIANGLES, 0, 3);
|
||||
post_fullscreen(s_paa_prog, s_paa_uUvRect);
|
||||
p_glBindVertexArray(0);
|
||||
p_glUseProgram(0);
|
||||
}
|
||||
@@ -1826,6 +1856,7 @@ static const char *GEO_VS =
|
||||
"noperspective out vec4 v_col;\n"
|
||||
"smooth out vec3 v_col_p; /* perspective-correct colour (PGXP, G1.14) */\n"
|
||||
"flat out int v_cp;\n"
|
||||
"noperspective out float v_b3d; /* bloom source: 1 on a PGXP 3D vertex */\n"
|
||||
"void main(){\n"
|
||||
" /* a_col.a carries the mask bit (0/1) and, for a PGXP 3D vertex, its\n"
|
||||
" * GTE SZ: a = mask + 2*(sz + 65536*cp), cp = perspective colour. A\n"
|
||||
@@ -1836,6 +1867,7 @@ static const char *GEO_VS =
|
||||
" if (a < 0.0) { m = 0.0; zn = 0.9999; sz = 0.0; cp = 0.0; } /* inside the far plane: never clipped */\n"
|
||||
" else if (sz > 0.5) { zn = 1.0 - 512.0 / (max(sz * (1.0 - u_zbias) - (u_zbias > 0.0 ? 48.0 : 0.0), 1.0) + 256.0); if (cp > 0.5) w = sz / 1024.0; }\n"
|
||||
" v_col = vec4(a_col.rgb, m); v_col_p = a_col.rgb; v_cp = int(cp);\n"
|
||||
" v_b3d = (a >= 0.0 && sz > 0.5) ? 1.0 : 0.0;\n"
|
||||
" float xb = a_pos.x;\n"
|
||||
" if (u_xscale < 0.0) {\n"
|
||||
" float s = -u_xscale; float h = u_xhalf / s;\n"
|
||||
@@ -1846,7 +1878,12 @@ static const char *GEO_VS =
|
||||
static const char *GEO_FS =
|
||||
"#version 330\n"
|
||||
"noperspective in vec4 v_col; smooth in vec3 v_col_p; flat in int v_cp; out vec4 frag;\n"
|
||||
"void main(){ frag = v_cp != 0 ? vec4(v_col_p, v_col.a) : v_col; }\n";
|
||||
"noperspective in float v_b3d;\n"
|
||||
"uniform int u_bcls; /* bloom source pass (gl_bloom.inc): 0 off, 2 clear, 1 mark */\n"
|
||||
"uniform int u_badd; /* this batch blends additively */\n"
|
||||
"void main(){\n"
|
||||
" if (u_bcls != 0) { if (u_bcls == 1 && !(v_b3d > 0.999 || u_badd != 0)) discard; frag = vec4(0.0); return; }\n"
|
||||
" frag = v_cp != 0 ? vec4(v_col_p, v_col.a) : v_col; }\n";
|
||||
|
||||
/* Textured prims: sample raw 1555 VRAM (integer), CLUT decode per depth,
|
||||
* texture window, optional bilinear, texel-0 discard, STP-split discard,
|
||||
@@ -1892,6 +1929,7 @@ static const char *TEX_VS =
|
||||
"flat out vec4 v_hd_source; flat out int v_hd_mode;\n"
|
||||
"flat out int v_filter;\n"
|
||||
"smooth out vec3 v_col_p; flat out int v_cp;\n"
|
||||
"noperspective out float v_b3d; /* bloom source: 1 on a PGXP 3D vertex */\n"
|
||||
"void main(){ v_uv = a_uv; v_uv_p = a_uv; v_col = a_col; v_col_p = a_col.rgb;\n"
|
||||
" v_persp = (a_q > 0.0) ? 1 : 0;\n"
|
||||
" v_tpage = ivec2(a_tpage + 0.5); v_clut = ivec2(a_clut + 0.5);\n"
|
||||
@@ -1920,7 +1958,7 @@ static const char *TEX_VS =
|
||||
" float pz = a_col.a < 0.0 ? -a_col.a : 0.0;\n"
|
||||
" float cp = pz >= 65536.0 ? 1.0 : 0.0, sz = pz - 65536.0 * cp, zn = 0.0;\n"
|
||||
" if (sz > 0.5) { zn = 1.0 - 512.0 / (max(sz * (1.0 - u_zbias) - (u_zbias > 0.0 ? 48.0 : 0.0), 1.0) + 256.0); if (a_q <= 0.0 && cp > 0.5) w = sz / 1024.0; }\n"
|
||||
" v_cp = int(cp);\n"
|
||||
" v_cp = int(cp); v_b3d = sz > 0.5 ? 1.0 : 0.0;\n"
|
||||
" vec2 ndc = vec2((xb+u_shift+u_xoff)/u_xhalf - 1.0, (a_pos.y+u_shift)/256.0 - 1.0);\n"
|
||||
" gl_Position = vec4(ndc * w, zn * w, w); }\n";
|
||||
static const char *TEX_FS =
|
||||
@@ -1929,6 +1967,8 @@ static const char *TEX_FS =
|
||||
"smooth in vec2 v_uv_p; flat in int v_persp;\n"
|
||||
"smooth in vec3 v_col_p; flat in int v_cp;\n"
|
||||
"out vec4 frag; out vec4 blend_factor;\n"
|
||||
"noperspective in float v_b3d;\n"
|
||||
"uniform int u_bcls; /* bloom source pass (gl_bloom.inc): 0 off, 2 clear, 1 mark */\n"
|
||||
"flat in ivec2 v_tpage; /* texture page base, VRAM px */\n"
|
||||
"flat in ivec2 v_clut; /* CLUT base, VRAM px */\n"
|
||||
"flat in int v_depth; /* 0=4bit 1=8bit 2=15bit */\n"
|
||||
@@ -2157,6 +2197,11 @@ static const char *TEX_FS =
|
||||
" stp = stpf >= 0.5 ? 1 : 0;\n"
|
||||
" }\n"
|
||||
" if ((v_filter & 16) != 0 && v_hd_mode == 0) rgb = lod_sample(uv0, dx0, dy0, stp, rgb);\n"
|
||||
" if (u_bcls != 0) { /* 3D, or additive (modes 1 and 3) semi texels */\n"
|
||||
" if (u_bcls == 1 && !(v_b3d > 0.999 || ((v_semi == 2 || v_semi == 4) && stp == 1))) discard;\n"
|
||||
" if (u_bcls == 2 && v_semi != 0 && stp == 1) discard; /* see-through: keeps what is under */\n"
|
||||
" frag = vec4(0.0); blend_factor = vec4(0.0); return;\n"
|
||||
" }\n"
|
||||
" if (u_semipass == 1 && stp == 1) discard;\n"
|
||||
" if (u_semipass == 2 && stp == 0) discard;\n"
|
||||
" if (v_raw == 0) rgb = clamp(rgb * (v_cp != 0 ? v_col_p : v_col.rgb) * 2.0, 0.0, 1.0);\n"
|
||||
@@ -3308,7 +3353,11 @@ int gl_renderer_get_wide_fast(void) { return s_wide_fast; }
|
||||
* the middle of a replayed frame; they are safe at session_reboot. */
|
||||
static int s_pgxp_render_wanted = 0; /* any PGXP renderer feature on */
|
||||
static void pgxp_render_wanted_update(void) {
|
||||
__atomic_store_n(&s_pgxp_render_wanted, (s_pgxp_depth || s_pgxp_cpersp || s_pgxp_seam) ? 1 : 0, __ATOMIC_RELEASE);
|
||||
/* Bloom marks PGXP-proven 3D triangles as its sources, so it wants the
|
||||
* per-triangle depth too (it never enables the depth test). */
|
||||
__atomic_store_n(&s_pgxp_render_wanted,
|
||||
(s_pgxp_depth || s_pgxp_cpersp || s_pgxp_seam || s_bloom > 0.0f) ? 1 : 0,
|
||||
__ATOMIC_RELEASE);
|
||||
}
|
||||
int gl_renderer_pgxp_render_wanted(void) {
|
||||
return __atomic_load_n(&s_pgxp_render_wanted, __ATOMIC_ACQUIRE);
|
||||
@@ -3432,6 +3481,7 @@ static float s_tb[TEXBATCH_MAXV * TEXV];
|
||||
static int s_tb_n = 0; /* verts queued */
|
||||
static int s_tb_semi = -2;
|
||||
static int s_tb_depth = 0; /* PGXP depth mode (batch key) */
|
||||
static int s_tb_b3d = 0; /* bloom on: 3D (bloom source) batch (key) */
|
||||
static int s_tb_mask = 0, s_tb_filter = 0;
|
||||
static GLuint s_tb_bank_tex;
|
||||
static GLuint s_tb_hd_tex;
|
||||
@@ -3580,6 +3630,75 @@ void gl_renderer_batch_diag(uint64_t out[9]) {
|
||||
* blended per the PSX mode. Cross-prim order is kept by isolating semi prims to
|
||||
* one per batch (see gpu_textured_triangle), so this batch holds a single prim
|
||||
* whose two passes do not self-overlap. */
|
||||
/* ---- bloom source marking ([video] bloom; gl_bloom.inc) -------------------
|
||||
* With bloom on, every textured and untextured draw is followed by two
|
||||
* stencil-only passes of the same vertices that set stencil bit 1 (bit 0 is
|
||||
* the mask-bit mirror) to "this pixel may feed the bloom": first every covered
|
||||
* pixel clears it, then the fragments of 3D primitives (PGXP-proven, so this
|
||||
* needs PGXP geometry) and of additive semi-transparent primitives set it.
|
||||
* Opaque 2D, sprites and the HUD therefore clear it wherever they draw,
|
||||
* painter ordered like the colour; semi-transparent texels and primitives
|
||||
* leave it as it was. Bloom off: none of this runs and no state changes. */
|
||||
static int s_bcls_on = 0;
|
||||
static GLint s_tex_uBcls = -1, s_geo_uBcls = -1, s_geo_uBadd = -1;
|
||||
typedef struct {
|
||||
GLboolean st, bl, dm, cm[4];
|
||||
GLint func, ref, vmask, wmask, sf, zf, zp;
|
||||
} BclsSave;
|
||||
static void bcls_begin(BclsSave *v, int tex, int additive) {
|
||||
v->st = glIsEnabled(GL_STENCIL_TEST); v->bl = glIsEnabled(GL_BLEND);
|
||||
glGetBooleanv(GL_DEPTH_WRITEMASK, &v->dm);
|
||||
glGetBooleanv(GL_COLOR_WRITEMASK, v->cm);
|
||||
glGetIntegerv(GL_STENCIL_FUNC, &v->func); glGetIntegerv(GL_STENCIL_REF, &v->ref);
|
||||
glGetIntegerv(GL_STENCIL_VALUE_MASK, &v->vmask); glGetIntegerv(GL_STENCIL_WRITEMASK, &v->wmask);
|
||||
glGetIntegerv(GL_STENCIL_FAIL, &v->sf); glGetIntegerv(GL_STENCIL_PASS_DEPTH_FAIL, &v->zf);
|
||||
glGetIntegerv(GL_STENCIL_PASS_DEPTH_PASS, &v->zp);
|
||||
glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
|
||||
glDisable(GL_BLEND);
|
||||
glDepthMask(GL_FALSE);
|
||||
glEnable(GL_STENCIL_TEST);
|
||||
glStencilMask(0x02);
|
||||
glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);
|
||||
if (!tex) p_glUniform1i(s_geo_uBadd, additive);
|
||||
}
|
||||
/* pass 2: clear bit 1 under every covered pixel; pass 1: set it on sources. */
|
||||
static void bcls_pass(int tex, int pass) {
|
||||
glStencilFunc(GL_ALWAYS, pass == 1 ? 0x02 : 0x00, 0xFF);
|
||||
p_glUniform1i(tex ? s_tex_uBcls : s_geo_uBcls, pass);
|
||||
}
|
||||
static void bcls_end(const BclsSave *v, int tex) {
|
||||
p_glUniform1i(tex ? s_tex_uBcls : s_geo_uBcls, 0);
|
||||
if (!tex) p_glUniform1i(s_geo_uBadd, 0);
|
||||
glColorMask(v->cm[0], v->cm[1], v->cm[2], v->cm[3]);
|
||||
if (v->bl) glEnable(GL_BLEND);
|
||||
glDepthMask(v->dm);
|
||||
glStencilFunc((GLenum)v->func, v->ref, (GLuint)v->vmask);
|
||||
glStencilMask((GLuint)v->wmask);
|
||||
glStencilOp((GLenum)v->sf, (GLenum)v->zf, (GLenum)v->zp);
|
||||
if (!v->st) glDisable(GL_STENCIL_TEST);
|
||||
}
|
||||
/* A draw with colour writes off (the PGXP depth-clear pass) draws nothing
|
||||
* one sees, so it marks nothing either. */
|
||||
static int bcls_colour_draw(void) {
|
||||
GLboolean cm[4]; glGetBooleanv(GL_COLOR_WRITEMASK, cm);
|
||||
return cm[0] || cm[1] || cm[2];
|
||||
}
|
||||
/* DRAW, then (bloom on) its two marking passes. GEO semi modes 1 and 3 add.
|
||||
* A semi-transparent primitive does not clear: what shows through it (a lamp
|
||||
* under a fog or shade quad) keeps glowing. */
|
||||
#define BCLS_DRAW(tex, semi, DRAW) do { \
|
||||
DRAW; \
|
||||
if (s_bcls_on && bcls_colour_draw()) { \
|
||||
BclsSave bcls_sv_; \
|
||||
bcls_begin(&bcls_sv_, (tex), (semi) == 1 || (semi) == 3); \
|
||||
if ((semi) < 0) { bcls_pass((tex), 2); DRAW; } \
|
||||
bcls_pass((tex), 1); DRAW; \
|
||||
bcls_end(&bcls_sv_, (tex)); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#include "gl_bloom.inc"
|
||||
|
||||
/* tb_mask / check are the batch's set-mask and the mask-check state it was
|
||||
* drawn under; track_stencil lets the canonical pass record deferred stencil
|
||||
* (a replay into the high-resolution window must not touch that flag). */
|
||||
@@ -3624,6 +3743,13 @@ static void tex_draw_passes_ex(int nverts, int semi, int tb_mask, int check,
|
||||
p_glUniform1i(s_uSemipass, 2);
|
||||
glDrawArrays(GL_TRIANGLES, 0, nverts);
|
||||
}
|
||||
if (s_bcls_on) {
|
||||
BclsSave sv;
|
||||
bcls_begin(&sv, 1, 0);
|
||||
bcls_pass(1, 2); glDrawArrays(GL_TRIANGLES, 0, nverts);
|
||||
bcls_pass(1, 1); glDrawArrays(GL_TRIANGLES, 0, nverts);
|
||||
bcls_end(&sv, 1);
|
||||
}
|
||||
}
|
||||
static void tex_batch_draw_passes(int nverts, int semi) {
|
||||
tex_draw_passes_ex(nverts, semi, s_tb_mask, s_mask_check, 1);
|
||||
@@ -3869,7 +3995,7 @@ static void hiw_replay_wide(void) {
|
||||
p_glBufferData(PSXGL_ARRAY_BUFFER, (ptrdiff_t)((size_t)c->vcount * 6 * sizeof(float)),
|
||||
s_hq_v + c->vfirst, PSXGL_STREAM_DRAW);
|
||||
depth_apply_ex(c->depth, 0);
|
||||
glDrawArrays(GL_TRIANGLES, 0, c->vcount);
|
||||
BCLS_DRAW(0, c->semi, glDrawArrays(GL_TRIANGLES, 0, c->vcount));
|
||||
depth_restore_ex(c->depth, 0, c->vcount);
|
||||
}
|
||||
}
|
||||
@@ -3950,7 +4076,7 @@ static void hiw_flush_queue(void) {
|
||||
p_glBufferData(PSXGL_ARRAY_BUFFER, (ptrdiff_t)((size_t)c->vcount * 6 * sizeof(float)),
|
||||
s_hq_v + c->vfirst, PSXGL_STREAM_DRAW);
|
||||
depth_apply_ex(c->depth, 0);
|
||||
glDrawArrays(GL_TRIANGLES, 0, c->vcount);
|
||||
BCLS_DRAW(0, c->semi, glDrawArrays(GL_TRIANGLES, 0, c->vcount));
|
||||
depth_restore_ex(c->depth, 0, c->vcount);
|
||||
}
|
||||
}
|
||||
@@ -4047,6 +4173,7 @@ static float s_fb[(FLATBATCH_MAXV + 2 * FLATBATCH_MAXL) * 6];
|
||||
static int s_fb_n = 0;
|
||||
static int s_fb_semi = -2;
|
||||
static int s_fb_depth = 0; /* PGXP depth mode (batch key) */
|
||||
static int s_fb_b3d = 0; /* bloom on: 3D (bloom source) batch (key) */
|
||||
static int s_fb_mask = -1;
|
||||
/* GL_TRIANGLES, or GL_LINES for a batch of native-wide lines (gpu_geometry). */
|
||||
static GLenum s_fb_mode = GL_TRIANGLES;
|
||||
@@ -4135,8 +4262,8 @@ static void flush_flat_batch_impl(void) {
|
||||
p_glBufferData(PSXGL_ARRAY_BUFFER, (ptrdiff_t)((nverts + 2 * nl) * 6 * sizeof(float)),
|
||||
s_fb, PSXGL_STREAM_DRAW);
|
||||
depth_apply_ex(dmode, 0);
|
||||
if (nl) flat_batch_draw_hr_lines(nverts, nl);
|
||||
else glDrawArrays(fmode, 0, nverts);
|
||||
BCLS_DRAW(0, semi, if (nl) flat_batch_draw_hr_lines(nverts, nl);
|
||||
else glDrawArrays(fmode, 0, nverts));
|
||||
depth_restore_ex(dmode, 0, nverts);
|
||||
int mirror = g_wide_cur && !s_wide_suppress && s_ws_ablate != 1 &&
|
||||
!(!g_ws_bd_stretch_on && mirror_flat_batch_center_only(nverts));
|
||||
@@ -4157,7 +4284,7 @@ static void flush_flat_batch_impl(void) {
|
||||
wide_target_begin(dx, s_geo_uXoff, s_geo_uXhalf);
|
||||
wide_set_bd_scale(s_geo_uXscale, s_geo_uXcenter);
|
||||
depth_apply_ex(dmode, 0);
|
||||
if (s_ws_ablate != 2) glDrawArrays(fmode, 0, nverts);
|
||||
if (s_ws_ablate != 2) BCLS_DRAW(0, semi, glDrawArrays(fmode, 0, nverts));
|
||||
depth_restore_ex(dmode, 0, nverts);
|
||||
wide_clear_bd_scale(s_geo_uXscale, s_geo_uXcenter);
|
||||
wide_target_end(s_geo_uXoff, s_geo_uXhalf);
|
||||
@@ -4514,7 +4641,7 @@ static void gpu_geometry(GLenum mode, const int *xs, const int *ys,
|
||||
p_glBindBuffer(PSXGL_ARRAY_BUFFER, s_geo_vbo);
|
||||
p_glBufferData(PSXGL_ARRAY_BUFFER, (ptrdiff_t)(draw_n * 6 * sizeof(float)),
|
||||
draw_verts, PSXGL_STREAM_DRAW);
|
||||
glDrawArrays(draw_mode, 0, draw_n);
|
||||
BCLS_DRAW(0, semi, glDrawArrays(draw_mode, 0, draw_n));
|
||||
if (is_line && s_out_scale > 1 && draw_mode == GL_LINES) {
|
||||
/* windowed: hr drew a 1x line; the S surfaces take the quad */
|
||||
p_glBufferData(PSXGL_ARRAY_BUFFER, (ptrdiff_t)(6 * 6 * sizeof(float)),
|
||||
@@ -4538,7 +4665,7 @@ static void gpu_geometry(GLenum mode, const int *xs, const int *ys,
|
||||
gl_perf_mirror_begin();
|
||||
wide_target_begin(dx, s_geo_uXoff, s_geo_uXhalf);
|
||||
wide_set_bd_scale(s_geo_uXscale, s_geo_uXcenter);
|
||||
if (s_ws_ablate != 2) glDrawArrays(draw_mode, 0, draw_n);
|
||||
if (s_ws_ablate != 2) BCLS_DRAW(0, semi, glDrawArrays(draw_mode, 0, draw_n));
|
||||
wide_clear_bd_scale(s_geo_uXscale, s_geo_uXcenter);
|
||||
wide_target_end(s_geo_uXoff, s_geo_uXhalf);
|
||||
gl_perf_mirror_end();
|
||||
@@ -4550,9 +4677,11 @@ static void gpu_geometry(GLenum mode, const int *xs, const int *ys,
|
||||
int gate = bd_prim_gate(xs, n, 0);
|
||||
const int dmode = n == 3 ? pgxp_tri_depth_mode(semi) : 0;
|
||||
if (dmode) depth_before_tri();
|
||||
/* Bloom on: 3D sources and the rest never share a batch (BCLS_DRAW). */
|
||||
const int b3d = s_bcls_on && n == 3 && pgxp_tri_is_3d();
|
||||
if (s_fb_n > 0 && (s_fb_mode != GL_TRIANGLES || s_fb_semi != semi ||
|
||||
s_fb_mask != (int)s_mask_set || s_fb_gate != gate ||
|
||||
s_fb_depth != dmode))
|
||||
s_fb_depth != dmode || s_fb_b3d != b3d))
|
||||
flush_flat_batch();
|
||||
if (s_fb_n + n > FLATBATCH_MAXV)
|
||||
flush_flat_batch();
|
||||
@@ -4561,6 +4690,7 @@ static void gpu_geometry(GLenum mode, const int *xs, const int *ys,
|
||||
s_fb_mask = (int)s_mask_set;
|
||||
s_fb_gate = gate;
|
||||
s_fb_depth = dmode;
|
||||
s_fb_b3d = b3d;
|
||||
|
||||
float mask_a = s_mask_set ? 1.0f : 0.0f;
|
||||
float *v0 = &s_fb[s_fb_n * 6];
|
||||
@@ -4708,6 +4838,9 @@ static void gpu_textured_triangle(const int *xs, const int *ys,
|
||||
(twx != s_tb_twin[0] || twy != s_tb_twin[1] ||
|
||||
tox != s_tb_twin[2] || toy != s_tb_twin[3])) reason = 5;
|
||||
else if (tdmode != s_tb_depth) reason = 7;
|
||||
/* Bloom marks a batch in two passes (clear, then sources), so
|
||||
* one batch holds either 3D sources or not: painter order. */
|
||||
else if (s_bcls_on && pgxp_tri_is_3d() != s_tb_b3d) reason = 7;
|
||||
}
|
||||
if (reason >= 0) {
|
||||
s_batch_reason[reason]++;
|
||||
@@ -4717,6 +4850,7 @@ static void gpu_textured_triangle(const int *xs, const int *ys,
|
||||
if (s_tb_n == 0) { /* opening a batch: capture its keyed state */
|
||||
s_tb_semi = batch_semi; s_tb_mask = s_mask_set; s_tb_filter = filter; s_tb_gate = gate;
|
||||
s_tb_depth = tdmode;
|
||||
s_tb_b3d = s_bcls_on && pgxp_tri_is_3d();
|
||||
s_tb_bank_tex = s_selected_bank_tex;
|
||||
s_tb_bank_live_clut = s_selected_bank_live_clut;
|
||||
s_tb_hd_tex = hd_tex;
|
||||
@@ -4808,7 +4942,7 @@ static void wide_flat_rect_direct(int wx, int y, int ww, int h, uint16_t c, int
|
||||
p_glBindVertexArray(s_geo_vao);
|
||||
p_glBindBuffer(PSXGL_ARRAY_BUFFER, s_geo_vbo);
|
||||
p_glBufferData(PSXGL_ARRAY_BUFFER, sizeof verts, verts, PSXGL_STREAM_DRAW);
|
||||
glDrawArrays(GL_TRIANGLES, 0, 6);
|
||||
BCLS_DRAW(0, semi, glDrawArrays(GL_TRIANGLES, 0, 6));
|
||||
if (s_hiw) p_glUniform1f(s_geo_uShift, s_shift_hr);
|
||||
p_glUniform1f(s_geo_uXoff, 0.0f);
|
||||
p_glUniform1f(s_geo_uXhalf, 512.0f);
|
||||
@@ -5920,6 +6054,9 @@ static int init_gpu_raster(void) {
|
||||
accurate_apply_uniforms();
|
||||
s_uLimits = p_glGetUniformLocation(s_tex_prog, "u_limits");
|
||||
s_uHdTexture = p_glGetUniformLocation(s_tex_prog, "u_hd_texture");
|
||||
s_tex_uBcls = p_glGetUniformLocation(s_tex_prog, "u_bcls");
|
||||
s_geo_uBcls = p_glGetUniformLocation(s_geo_prog, "u_bcls");
|
||||
s_geo_uBadd = p_glGetUniformLocation(s_geo_prog, "u_badd");
|
||||
s_uBlitSrc = p_glGetUniformLocation(s_blit_prog, "u_src");
|
||||
s_uBlitPass = p_glGetUniformLocation(s_blit_prog, "u_stp_pass");
|
||||
s_uBlitMaskset = p_glGetUniformLocation(s_blit_prog, "u_maskset");
|
||||
@@ -6395,6 +6532,7 @@ void gl_renderer_shutdown(void) {
|
||||
s_hold_th = 0;
|
||||
s_interp_fbo = 0; /* died with the context */
|
||||
post_aa_release();
|
||||
bloom_release();
|
||||
}
|
||||
|
||||
/* CPU-readout present (24-bit FMV frames and the PSX_GL_FORCE_CPU_PRESENT
|
||||
@@ -9895,6 +10033,7 @@ static void present_vram_impl(int disp_x, int disp_y, int w, int h, int linear,
|
||||
}
|
||||
present_target_quad(src_tex, tw, th,
|
||||
src_x, disp_y, w, h, linear, lx, ly, lw, lh, 1, 1, src_scale);
|
||||
bloom_note_source(src_fbo, src_tex, tw, th, src_x, disp_y, w, h);
|
||||
}
|
||||
pres_record(GL_PRES_VRAM, disp_x, disp_y, w, h, lx, ly, lw, lh);
|
||||
gpu_timeline_note(GTL_PRESENT, GTL_PATH_VRAM_FBO,
|
||||
@@ -9925,6 +10064,9 @@ static void wide_blit_center(GLuint wide_fbo, int base_x, int disp_y, int disp_h
|
||||
if (native_w <= 0) return;
|
||||
int S = s_out_scale;
|
||||
(void)disp_y; (void)disp_h;
|
||||
/* Bloom on: the centre's bloom-source bits (stencil bit 1) come with it;
|
||||
* bit 0 is rebuilt from alpha anyway (the wst_add below). */
|
||||
const GLbitfield bits = GL_COLOR_BUFFER_BIT | (s_bcls_on ? GL_STENCIL_BUFFER_BIT : 0);
|
||||
/* Colour only: the centre's stencil is left behind (see s_wst_*). */
|
||||
wst_add(wide_index(wide_fbo), g_wide_off, 0, g_wide_off + native_w, VRAM_H);
|
||||
/* Copy the canonical framebuffer column into the wide surface CENTRE over the
|
||||
@@ -9948,7 +10090,7 @@ static void wide_blit_center(GLuint wide_fbo, int base_x, int disp_y, int disp_h
|
||||
p_glBindFramebuffer(PSXGL_READ_FRAMEBUFFER, s_hr_fbo);
|
||||
p_glBlitFramebuffer(cx0 * R, 0, cx1 * R, VRAM_H * R,
|
||||
g_wide_off * S, 0, (g_wide_off + native_w) * S, VRAM_H * S,
|
||||
GL_COLOR_BUFFER_BIT, GL_NEAREST);
|
||||
bits, GL_NEAREST);
|
||||
}
|
||||
for (int t = 0; t < s_hiw_n; t++) {
|
||||
const HiwTile *T = in ? in : &s_hiw_t[t];
|
||||
@@ -9958,7 +10100,7 @@ static void wide_blit_center(GLuint wide_fbo, int base_x, int disp_y, int disp_h
|
||||
p_glBlitFramebuffer((h0 - T->x0) * S, 0, (h1 - T->x0) * S, VRAM_H * S,
|
||||
(g_wide_off + h0 - cx0) * S, 0,
|
||||
(g_wide_off + h1 - cx0) * S, VRAM_H * S,
|
||||
GL_COLOR_BUFFER_BIT, GL_NEAREST);
|
||||
bits, GL_NEAREST);
|
||||
}
|
||||
if (in) break;
|
||||
}
|
||||
@@ -9973,7 +10115,7 @@ static void wide_blit_center(GLuint wide_fbo, int base_x, int disp_y, int disp_h
|
||||
(base_x + native_w) * S, VRAM_H * S,
|
||||
g_wide_off * S, 0,
|
||||
(g_wide_off + native_w) * S, VRAM_H * S,
|
||||
GL_COLOR_BUFFER_BIT, GL_NEAREST);
|
||||
bits, GL_NEAREST);
|
||||
p_glBindFramebuffer(PSXGL_READ_FRAMEBUFFER, 0);
|
||||
p_glBindFramebuffer(PSXGL_DRAW_FRAMEBUFFER, 0);
|
||||
}
|
||||
@@ -10062,6 +10204,8 @@ static int present_wide_fbo_impl(int disp_x, int disp_y, int disp_h, int linear)
|
||||
? (float)wide_as / (float)s_out_scale : 1.0f;
|
||||
present_target_quad(tex, (float)g_wide_w * wext, (float)VRAM_H * wext,
|
||||
0, disp_y, g_wide_w, disp_h, linear, lx, ly, lw, lh, 1, 1, s_out_scale);
|
||||
bloom_note_source(fbo, tex, (float)g_wide_w * wext, (float)VRAM_H * wext,
|
||||
0, disp_y, g_wide_w, disp_h);
|
||||
pres_record(GL_PRES_WIDE, disp_x, disp_y, g_wide_w, disp_h, lx, ly, lw, lh);
|
||||
gpu_timeline_note(GTL_PRESENT, GTL_PATH_WIDE_FBO,
|
||||
(uint32_t)disp_x | ((uint32_t)disp_y << 16));
|
||||
@@ -11624,7 +11768,18 @@ static void fg_blit2(GLuint src, GLuint dst, int sx, int sy, int dx, int dy, int
|
||||
p_glBindFramebuffer(PSXGL_READ_FRAMEBUFFER, src);
|
||||
p_glBindFramebuffer(PSXGL_DRAW_FRAMEBUFFER, dst);
|
||||
glDisable(GL_SCISSOR_TEST);
|
||||
p_glBlitFramebuffer(sx, sy, sx + w, sy + h, dx, dy, dx + w, dy + h, GL_COLOR_BUFFER_BIT, GL_NEAREST);
|
||||
/* Bloom on: the bloom-source bits come along (stencil bit 1; bit 0 is
|
||||
* marked stale by the caller and rebuilt from alpha). */
|
||||
p_glBlitFramebuffer(sx, sy, sx + w, sy + h, dx, dy, dx + w, dy + h,
|
||||
GL_COLOR_BUFFER_BIT | (s_bcls_on ? GL_STENCIL_BUFFER_BIT : 0), GL_NEAREST);
|
||||
p_glBindFramebuffer(PSXGL_READ_FRAMEBUFFER, 0);
|
||||
p_glBindFramebuffer(PSXGL_DRAW_FRAMEBUFFER, 0);
|
||||
}
|
||||
static void fg_blit_stencil(GLuint src, GLuint dst, int sx, int sy, int dx, int dy, int w, int h) {
|
||||
p_glBindFramebuffer(PSXGL_READ_FRAMEBUFFER, src);
|
||||
p_glBindFramebuffer(PSXGL_DRAW_FRAMEBUFFER, dst);
|
||||
glDisable(GL_SCISSOR_TEST);
|
||||
p_glBlitFramebuffer(sx, sy, sx + w, sy + h, dx, dy, dx + w, dy + h, GL_STENCIL_BUFFER_BIT, GL_NEAREST);
|
||||
p_glBindFramebuffer(PSXGL_READ_FRAMEBUFFER, 0);
|
||||
p_glBindFramebuffer(PSXGL_DRAW_FRAMEBUFFER, 0);
|
||||
}
|
||||
@@ -11928,11 +12083,17 @@ static void fg_compose(const FgList *b, GLuint wide_fbo, GLuint wide_tex, int li
|
||||
if (b->wide) {
|
||||
present_target_quad(wide_tex, (float)g_wide_w, (float)VRAM_H, 0, b->disp[1], g_wide_w,
|
||||
b->disp[3], linear, lx, ly, lw, lh, 1, 1, s_out_scale);
|
||||
bloom_note_source(wide_fbo, wide_tex, (float)g_wide_w, (float)VRAM_H, 0, b->disp[1],
|
||||
g_wide_w, b->disp[3]);
|
||||
} else {
|
||||
present_bezel(ww, wh, lx, ly, lw, lh);
|
||||
present_target_quad(s_hr_tex, (float)VRAM_W, (float)VRAM_H, b->disp[0], b->disp[1],
|
||||
b->disp[2], b->disp[3], linear, lx, ly, lw, lh, 1, 1, s_out_scale);
|
||||
bloom_note_source(s_hr_fbo, s_hr_tex, (float)VRAM_W, (float)VRAM_H, b->disp[0], b->disp[1],
|
||||
b->disp[2], b->disp[3]);
|
||||
}
|
||||
/* A generated frame is the same picture: the same bloom. */
|
||||
if (s_bloom > 0.0f) bloom_apply(1, lx, ly, lw, lh);
|
||||
}
|
||||
|
||||
/* Draw and present the frame at phase t between the two lists. Returns 1
|
||||
@@ -12506,6 +12667,11 @@ static int fg_generate(double t, int swap) {
|
||||
if (L->disp[0] != b->disp[0] || lh != dh || L->disp[2] != b->disp[2]) return 0;
|
||||
/* Reprojected frames start from the list's snapshot instead (below). */
|
||||
const int will_rp = s_fg_reproject && L == b && s_fg_fit.ok && rp_snap_slot((int)(L - s_fg_l)) >= 0;
|
||||
if (will_rp && s_bcls_on) {
|
||||
/* The warp writes colour only: the newer real frame's bloom sources. */
|
||||
fg_blit_stencil(s_hr_fbo, s_fg_hr_fbo, b->disp[0] * S, dy * S, L->disp[0] * S, ly * S, b->disp[2] * S, dh * S);
|
||||
if (iw >= 0) fg_blit_stencil(s_wide_fbo[iw], s_fg_w_fbo, 0, dy * S, 0, ly * S, g_wide_w * S, dh * S);
|
||||
}
|
||||
if (!will_rp) {
|
||||
fg_blit2(s_hr_fbo, s_fg_hr_fbo, b->disp[0] * S, dy * S, L->disp[0] * S, ly * S, b->disp[2] * S, dh * S);
|
||||
if (iw >= 0) fg_blit2(s_wide_fbo[iw], s_fg_w_fbo, 0, dy * S, 0, ly * S, g_wide_w * S, dh * S);
|
||||
|
||||
@@ -1229,6 +1229,7 @@ extern "C" EMSCRIPTEN_KEEPALIVE void psx_web_set_smooth_60fps(int enabled) {
|
||||
static int g_video_scale = 1; /* internal-resolution SSAA factor */
|
||||
static bool g_video_aa = true; /* linear present filtering */
|
||||
static int g_video_post_aa = 0; /* [video] antialiasing_mode / PSX_AA_MODE (GL_POST_AA_*) */
|
||||
static float g_video_bloom = 0.0f; /* [video] bloom (PSX_BLOOM overrides) */
|
||||
static int g_video_ss_milli = 1000; /* [video] supersample / PSX_SUPERSAMPLE */
|
||||
/* FMV present reconstruction (VIDEO_FMV_FILTER_*), pushed to the GL renderer
|
||||
* once the config is resolved. Only consulted while g_video_aa is on. */
|
||||
@@ -16332,6 +16333,7 @@ int main(int argc, char** argv) {
|
||||
}
|
||||
g_video_aa = gc.runtime.video_antialiasing;
|
||||
g_video_post_aa = gc.runtime.video_antialiasing_mode;
|
||||
g_video_bloom = gc.runtime.video_bloom;
|
||||
g_video_ss_milli = gc.runtime.video_supersample_milli;
|
||||
g_video_texfilter = gc.runtime.video_texture_filter;
|
||||
g_video_fmv_filter = gc.runtime.video_fmv_filter;
|
||||
@@ -19114,6 +19116,7 @@ session_reboot:
|
||||
}
|
||||
gl_renderer_set_swap_interval(0);
|
||||
(void)gl_renderer_set_post_aa(g_video_post_aa);
|
||||
gl_renderer_set_bloom(g_video_bloom);
|
||||
g_gl_active = gl_renderer_init_context(s_headless_gl_window) != 0;
|
||||
if (!g_gl_active || gr_backend() != GR_BACKEND_OPENGL ||
|
||||
!(SDL_GetWindowFlags(s_headless_gl_window) & SDL_WINDOW_HIDDEN)) {
|
||||
@@ -19326,6 +19329,7 @@ session_reboot:
|
||||
gl_renderer_set_dynamic_resolution(
|
||||
((dynres_requested() || dbg_live_scale) && g_video_scale_applies) ? 1 : 0);
|
||||
(void)gl_renderer_set_post_aa(g_video_post_aa);
|
||||
gl_renderer_set_bloom(g_video_bloom);
|
||||
g_gl_active = (gl_renderer_init_context(sdl_window) != 0);
|
||||
dynres_setup();
|
||||
if (dbg_live_scale && g_gl_active) {
|
||||
|
||||
@@ -0,0 +1,229 @@
|
||||
/* Bloom ([video] bloom) on a real, hidden OpenGL context.
|
||||
*
|
||||
* - off: the composed frame is the stock one (byte-identical before and
|
||||
* after a round trip through bloom; the pass never runs);
|
||||
* - on: a bright 2D primitive (sprite, HUD) and a 2D one drawn over a lit 3D
|
||||
* one gain no glow at all (byte-identical to off), while an additive
|
||||
* primitive and a PGXP-proven 3D one glow around their edges;
|
||||
* - guest VRAM / readbacks are the same either way, with and without the
|
||||
* render thread.
|
||||
* With --dump PATH the off frame of every scene is written raw (RGB,
|
||||
* bottom-up) so a run against another tree can be compared byte for byte
|
||||
* (BLOOM_FIXTURE_NO_BLOOM builds it without the bloom API). */
|
||||
#define GL_PRESENT_TEST_HOOK(gen) bloom_capture_hook(gen)
|
||||
static void bloom_capture_hook(int gen);
|
||||
#define main scale_fixture_main
|
||||
#include "test_gl_scale_invariance.c"
|
||||
#undef main
|
||||
void gpu_hd_textures_begin_upload(int x,int y,int w,int h){(void)x;(void)y;(void)w;(void)h;}
|
||||
void gpu_hd_textures_track_upload(int x,int y,int w,int h,const uint16_t* p){(void)x;(void)y;(void)w;(void)h;(void)p;}
|
||||
void gpu_hd_textures_invalidate(int x,int y,int w,int h){(void)x;(void)y;(void)w;(void)h;}
|
||||
void gpu_hd_textures_begin_copy(int sx,int sy,int x,int y,int w,int h){(void)sx;(void)sy;(void)x;(void)y;(void)w;(void)h;}
|
||||
void gpu_hd_textures_end_copy(void){}
|
||||
#ifndef PSX_NO_DEBUG_TOOLS
|
||||
int present_image_ring_accepting(void){return 0;}
|
||||
int present_image_ring_thumb_w(int w,int h){(void)w;(void)h;return 0;}
|
||||
void present_image_ring_push(uint32_t f,const uint8_t* p,int w,int pitch,int flip){(void)f;(void)p;(void)w;(void)pitch;(void)flip;}
|
||||
void present_image_ring_push_argb(uint32_t f,const uint32_t* p,int w,int h,int pitch){(void)f;(void)p;(void)w;(void)h;(void)pitch;}
|
||||
#endif
|
||||
void present_shot_done(int ok) { (void)ok; }
|
||||
int host_osd_needs_present(void) { return 0; }
|
||||
int psx_present_vsync_owns_cadence(void) { return 0; }
|
||||
void latency_ring_mark(LatencyStage stage) { (void)stage; }
|
||||
void psx_host_sleep_ms(unsigned ms) { SDL_Delay(ms); }
|
||||
void psx_host_sleep_micros(unsigned us) { SDL_Delay((us + 999u) / 1000u); }
|
||||
int present_shot_take(char* out,int n) { (void)out; (void)n; return 0; }
|
||||
int host_osd_image(const uint32_t** p,int* w,int* h) { (void)p; (void)w; (void)h; return 0; }
|
||||
int host_osd_volume_image(const uint32_t** p,int* w,int* h) { return host_osd_image(p,w,h); }
|
||||
void host_osd_present_done(void) {}
|
||||
int psx_rewind_overlay_image(const uint32_t** p,int* w,int* h) { return host_osd_image(p,w,h); }
|
||||
float psx_rewind_slide(void) { return 0; }
|
||||
int psx_savestate_menu_overlay_image(const uint32_t** p,int* w,int* h) { return host_osd_image(p,w,h); }
|
||||
|
||||
static uint8_t *s_cap; static int s_cap_w, s_cap_h, s_cap_n;
|
||||
static void bloom_capture_hook(int gen) {
|
||||
(void)gen;
|
||||
SDL_GL_GetDrawableSize(s_win, &s_cap_w, &s_cap_h);
|
||||
free(s_cap);
|
||||
s_cap = (uint8_t *)malloc((size_t)s_cap_w * s_cap_h * 3);
|
||||
glReadBuffer(GL_BACK);
|
||||
glPixelStorei(GL_PACK_ALIGNMENT, 1);
|
||||
glReadPixels(0, 0, s_cap_w, s_cap_h, GL_RGB, GL_UNSIGNED_BYTE, s_cap);
|
||||
glPixelStorei(GL_PACK_ALIGNMENT, 4);
|
||||
s_cap_n++;
|
||||
}
|
||||
|
||||
enum { SC_2D, SC_ADD, SC_3D, SC_HUD_OVER_3D, SC_PARTIAL, SC_N };
|
||||
static const char *k_scene[SC_N] = { "2d", "additive", "3d", "hud-over-3d", "partial" };
|
||||
/* SC_PARTIAL's grey HUD panels (native x, y, w, h): one beside the lamp, one
|
||||
* covering part of it. */
|
||||
static const int k_hud[2][4] = { { 204, 100, 40, 40 }, { 150, 110, 30, 20 } };
|
||||
|
||||
/* A PGXP-proven triangle: precise positions plus GTE SZ, as gpu.c sends. */
|
||||
static void tri3d(int x0, int y0, int x1, int y1, int x2, int y2, uint16_t c) {
|
||||
glb_set_precise_triangle(1, x0 * 65536, y0 * 65536, x1 * 65536, y1 * 65536,
|
||||
x2 * 65536, y2 * 65536);
|
||||
glb_set_depth_triangle(1, 2000.0f, 2000.0f, 2000.0f);
|
||||
glb_draw_flat_triangle(x0, y0, x1, y1, x2, y2, c);
|
||||
}
|
||||
|
||||
static uint8_t *frame(int scene, float bloom, uint64_t *vram_hash) {
|
||||
#ifndef BLOOM_FIXTURE_NO_BLOOM
|
||||
gl_renderer_set_bloom(bloom);
|
||||
#else
|
||||
(void)bloom;
|
||||
#endif
|
||||
glb_set_draw_area(0, 0, FRAME_W - 1, FRAME_H - 1); glb_set_draw_offset(0, 0);
|
||||
glb_set_mask_bits(0, 0); glb_set_semi_transparency(0, 0);
|
||||
glb_fill_rect(0, 0, FRAME_W, FRAME_H, 0x0000);
|
||||
/* A dark 3D ground everywhere: lit (may take a halo), below the threshold. */
|
||||
tri3d(0, 0, FRAME_W, 0, 0, FRAME_H, 0x0842);
|
||||
tri3d(FRAME_W, 0, FRAME_W, FRAME_H, 0, FRAME_H, 0x0842);
|
||||
switch (scene) {
|
||||
case SC_2D: /* a white HUD panel */
|
||||
glb_draw_flat_rect(120, 90, 80, 60, 0x7fff);
|
||||
break;
|
||||
case SC_ADD: /* an additive light halo over black */
|
||||
glb_set_semi_transparency(1, 1);
|
||||
glb_draw_flat_triangle(120, 150, 200, 150, 160, 80, 0x7fff);
|
||||
glb_set_semi_transparency(0, 0);
|
||||
break;
|
||||
case SC_3D: /* a lit 3D polygon */
|
||||
tri3d(120, 150, 200, 150, 160, 80, 0x7fff);
|
||||
break;
|
||||
case SC_PARTIAL: /* a visible lamp with grey HUD panels beside and over it */
|
||||
tri3d(120, 150, 200, 150, 160, 80, 0x7fff);
|
||||
for (int i = 0; i < 2; i++) glb_draw_flat_rect(k_hud[i][0], k_hud[i][1], k_hud[i][2], k_hud[i][3], 0x4210);
|
||||
break;
|
||||
case SC_HUD_OVER_3D: /* the same 3D polygon under an opaque panel */
|
||||
tri3d(120, 150, 200, 150, 160, 80, 0x7fff);
|
||||
glb_draw_flat_rect(100, 70, 120, 90, 0x7fff);
|
||||
break;
|
||||
}
|
||||
gl_renderer_sync_cpu();
|
||||
check(gl_renderer_fbo_peek(0, 0, FRAME_W, FRAME_H, peek), "vram readback");
|
||||
*vram_hash = fnv(peek, FRAME_W * FRAME_H * 2, 0xcbf29ce484222325ull);
|
||||
const int n0 = s_cap_n;
|
||||
if (s_rth_on) gl_renderer_render_thread_frame_boundary();
|
||||
gl_renderer_present_vram(0, 0, FRAME_W, FRAME_H, 0, 0);
|
||||
if (s_rth_on) {
|
||||
gl_renderer_render_thread_frame_boundary();
|
||||
gl_renderer_render_thread_sync("bloom-capture");
|
||||
}
|
||||
check(s_cap_n == n0 + 1 && s_cap, "present captured");
|
||||
uint8_t *c = (uint8_t *)malloc((size_t)s_cap_w * s_cap_h * 3);
|
||||
memcpy(c, s_cap, (size_t)s_cap_w * s_cap_h * 3);
|
||||
return c;
|
||||
}
|
||||
|
||||
/* Window pixels of SC_PARTIAL's HUD panels that differ between a and b. */
|
||||
static long hud_changed(const uint8_t *a, const uint8_t *b) {
|
||||
const int sx = s_cap_w / FRAME_W, sy = s_cap_h / FRAME_H;
|
||||
long n = 0;
|
||||
for (int i = 0; i < 2; i++)
|
||||
for (int y = k_hud[i][1] * sy; y < (k_hud[i][1] + k_hud[i][3]) * sy; y++)
|
||||
for (int x = k_hud[i][0] * sx; x < (k_hud[i][0] + k_hud[i][2]) * sx; x++) {
|
||||
const size_t o = ((size_t)(s_cap_h - 1 - y) * s_cap_w + x) * 3; /* bottom-up */
|
||||
n += memcmp(a + o, b + o, 3) != 0;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
/* Brightness the pass added on the dark ground around the shapes (the glow). */
|
||||
static long glow(const uint8_t *off, const uint8_t *on) {
|
||||
long sum = 0;
|
||||
for (int i = 0; i < s_cap_w * s_cap_h; i++) {
|
||||
if (off[i * 3] > 0x50 || off[i * 3 + 1] > 0x50 || off[i * 3 + 2] > 0x50) continue;
|
||||
for (int c = 0; c < 3; c++) sum += abs((int)on[i * 3 + c] - (int)off[i * 3 + c]);
|
||||
}
|
||||
return sum;
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
const char *dump = argc > 2 && !strcmp(argv[1], "--dump") ? argv[2] : NULL;
|
||||
if (SDL_Init(SDL_INIT_VIDEO) != 0) { fprintf(stderr, "SKIP no video\n"); return 77; }
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3);
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
|
||||
#if defined(__APPLE__)
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_FORWARD_COMPATIBLE_FLAG);
|
||||
#endif
|
||||
SDL_Window *win = SDL_CreateWindow("Bloom hidden test", 0, 0, 640, 480,
|
||||
SDL_WINDOW_OPENGL | SDL_WINDOW_HIDDEN);
|
||||
if (!win) { fprintf(stderr, "SKIP no window\n"); return 77; }
|
||||
glb_init(vram); glb_set_scale(2); gl_renderer_set_swap_interval(0);
|
||||
if (!gl_renderer_init_context(win)) { fprintf(stderr, "SKIP no GL context\n"); return 77; }
|
||||
uint8_t *off[SC_N]; uint64_t h_off[SC_N];
|
||||
for (int s = 0; s < SC_N; s++) off[s] = frame(s, 0.0f, &h_off[s]);
|
||||
const size_t bytes = (size_t)s_cap_w * s_cap_h * 3;
|
||||
if (dump) {
|
||||
FILE *f = fopen(dump, "wb");
|
||||
for (int s = 0; s < SC_N; s++)
|
||||
check(f && fwrite(off[s], 1, bytes, f) == bytes, "dump");
|
||||
if (f) fclose(f);
|
||||
}
|
||||
#ifndef BLOOM_FIXTURE_NO_BLOOM
|
||||
GlBloomInfo bi;
|
||||
gl_renderer_bloom_debug(-1.0f, -1.0f, -1, -1, &bi);
|
||||
check(bi.passes == 0, "off never runs the bloom pass");
|
||||
for (int pass = 0; pass < 2; pass++) { /* 0: this thread, 1: render thread */
|
||||
if (pass == 1) {
|
||||
check(gl_renderer_render_thread_start(2) == 1, "render thread started");
|
||||
gl_renderer_render_thread_frame_boundary();
|
||||
}
|
||||
long g[SC_N];
|
||||
for (int s = 0; s < SC_N; s++) {
|
||||
uint64_t h = 0;
|
||||
uint8_t *on = frame(s, 1.5f, &h);
|
||||
g[s] = glow(off[s], on);
|
||||
check(h == h_off[s], "guest VRAM identical with bloom on");
|
||||
if (s == SC_2D || s == SC_HUD_OVER_3D)
|
||||
check(memcmp(off[s], on, bytes) == 0, "2D / HUD pixels never glow");
|
||||
if (s == SC_PARTIAL) {
|
||||
const long hc = hud_changed(off[s], on);
|
||||
printf("partial: hud pixels changed=%ld\n", hc);
|
||||
check(hc == 0, "HUD panels beside and over a lamp are byte-identical");
|
||||
}
|
||||
free(on);
|
||||
uint8_t *back = frame(s, 0.0f, &h);
|
||||
check(memcmp(off[s], back, bytes) == 0 && h == h_off[s], "off after bloom is byte-identical");
|
||||
free(back);
|
||||
}
|
||||
printf("%s drawable=%dx%d glow 2d=%ld additive=%ld 3d=%ld hud-over-3d=%ld\n",
|
||||
pass ? "rt" : "st", s_cap_w, s_cap_h, g[SC_2D], g[SC_ADD], g[SC_3D], g[SC_HUD_OVER_3D]);
|
||||
check(g[SC_2D] == 0 && g[SC_HUD_OVER_3D] == 0, "no glow from 2D");
|
||||
check(g[SC_ADD] > 20000, "additive primitive glows");
|
||||
check(g[SC_3D] > 20000, "3D primitive glows");
|
||||
check(g[SC_PARTIAL] > 20000, "a partly covered lamp still glows");
|
||||
}
|
||||
gl_renderer_render_thread_stop();
|
||||
/* The pass leaves the GL state as it found it, both ways round. */
|
||||
{
|
||||
uint64_t h = 0;
|
||||
free(frame(SC_3D, 1.5f, &h)); /* marks for a reused-marks pass */
|
||||
int ww = 0, wh = 0; SDL_GL_GetDrawableSize(s_win, &ww, &wh);
|
||||
for (int on = 0; on < 2; on++) {
|
||||
BloomGlState want, got;
|
||||
if (on) { glEnable(GL_BLEND); glEnable(GL_DEPTH_TEST); glEnable(GL_SCISSOR_TEST); glEnable(GL_STENCIL_TEST); }
|
||||
else { glDisable(GL_BLEND); glDisable(GL_DEPTH_TEST); glDisable(GL_SCISSOR_TEST); glDisable(GL_STENCIL_TEST); }
|
||||
glClearColor(0.25f, 0.5f, 0.75f, on ? 1.0f : 0.0f);
|
||||
glViewport(3, 5, 101 + on, 77);
|
||||
p_glBindFramebuffer(PSXGL_FRAMEBUFFER, on ? s_hr_fbo : 0);
|
||||
bloom_state_save(&want);
|
||||
const uint64_t n0 = s_bloom_passes;
|
||||
bloom_apply(0, 0, 0, ww, wh);
|
||||
bloom_state_save(&got);
|
||||
check(s_bloom_passes == n0 + 1, "state test ran the pass");
|
||||
check(memcmp(&want, &got, sizeof want) == 0, "bloom pass leaves GL state unchanged");
|
||||
}
|
||||
p_glBindFramebuffer(PSXGL_FRAMEBUFFER, 0);
|
||||
glDisable(GL_BLEND); glDisable(GL_DEPTH_TEST); glDisable(GL_SCISSOR_TEST); glDisable(GL_STENCIL_TEST);
|
||||
}
|
||||
(void)k_scene;
|
||||
#endif
|
||||
for (int s = 0; s < SC_N; s++) free(off[s]);
|
||||
check(glGetError() == GL_NO_ERROR, "GL errors");
|
||||
printf("checks=%d failures=%d\n", checks, failures);
|
||||
gl_renderer_shutdown(); SDL_DestroyWindow(win); SDL_Quit();
|
||||
return failures ? 1 : 0;
|
||||
}
|
||||
Reference in New Issue
Block a user