mirror of
https://github.com/RetroPortingToolKit/psxrecomp
synced 2026-10-26 02:11:36 -04:00
Match encoded desktop colors in OpenXR gameplay and native copies
This commit is contained in:
@@ -119,3 +119,23 @@ See [Third-Party Attribution](../THIRD_PARTY_ATTRIBUTION.md#openxr-sdk-loader---
|
||||
for the Apache-2.0 SDK, MIT JsonCpp notices, cold-cache requirements and package
|
||||
distribution. Consuming this feature requires a framework pin update and runtime
|
||||
rebuild; it does not change guest code generation.
|
||||
|
||||
## Headset color candidate (2026-10-05)
|
||||
|
||||
`fix/vr-headset-color` corrects submission encoding. Native PSX/stereo textures
|
||||
contain display-encoded RGB; the previous unconverted GL_RGBA8 swapchain made
|
||||
the compositor interpret those values as linear. Prefer GL_SRGB8_ALPHA8, with
|
||||
GL_FRAMEBUFFER_SRGB disabled during the copy and restored afterward. If only
|
||||
GL_RGBA8 is available, the copy shader explicitly decodes sRGB instead.
|
||||
|
||||
Gameplay applies desktop post-gamma once before encoding/decoding. Native
|
||||
GL_BACK already contains that adjustment and is copied without applying it
|
||||
again. Neither path changes guest GPU lighting, textures or simulation.
|
||||
`openxr_stats.swapchain_format` reports the selected GL enum (35907 sRGB,
|
||||
32856 linear). The linear-only native fallback uses an intermediate copy.
|
||||
|
||||
`runtime/tests/run_openxr_color_gl.py` runs the actual private copy functions
|
||||
and shader against source-owned RGB/grayscale ramps in hidden real GL. Eight
|
||||
format/gamma/incoming-sRGB-state combinations pass for gameplay and native
|
||||
copies, including vertical orientation, alpha and GL state restoration.
|
||||
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
/* Host presentation only: PSX/display RGB values are already encoded. */
|
||||
#pragma once
|
||||
#include <stdint.h>
|
||||
#define PSX_XR_RGBA8 0x8058
|
||||
#define PSX_XR_SRGB8_ALPHA8 0x8C43
|
||||
static inline int64_t psx_xr_color_format(const int64_t *formats, uint32_t count) {
|
||||
int rgba = 0;
|
||||
for (uint32_t i = 0; i < count; i++) {
|
||||
if (formats[i] == PSX_XR_SRGB8_ALPHA8) return PSX_XR_SRGB8_ALPHA8;
|
||||
if (formats[i] == PSX_XR_RGBA8) rgba = 1;
|
||||
}
|
||||
return rgba ? PSX_XR_RGBA8 : 0;
|
||||
}
|
||||
/* Used only when gamma must be applied or the runtime offers a linear target.
|
||||
* Gamma precedes decoding, matching desktop presentation. Alpha is linear. */
|
||||
#define PSX_XR_COLOR_FS \
|
||||
"#version 330\n" \
|
||||
"in vec2 v_uv; uniform sampler2D u_tex; uniform float u_gamma;\n" \
|
||||
"uniform int u_linear; out vec4 frag;\n" \
|
||||
"void main(){ vec4 c=texture(u_tex,v_uv);\n" \
|
||||
" if(u_gamma != 1.0) c.rgb=pow(max(c.rgb,vec3(0.0)),vec3(1.0/u_gamma));\n" \
|
||||
" if(u_linear != 0) c.rgb=mix(c.rgb/12.92,\n" \
|
||||
" pow((c.rgb+0.055)/1.055,vec3(2.4)),greaterThan(c.rgb,vec3(0.04045)));\n" \
|
||||
" frag=c; }\n"
|
||||
@@ -13,6 +13,7 @@ typedef struct PSXOpenXRStats {
|
||||
float ipd_m;
|
||||
double units_per_meter;
|
||||
uint64_t gl_version, min_gl_version, max_gl_version;
|
||||
int64_t swapchain_format; /* GL_SRGB8_ALPHA8 preferred; GL_RGBA8 decoded copy */
|
||||
char runtime[128];
|
||||
float pose[2][7], fov[2][4];
|
||||
PSXModRenderView view[2];
|
||||
|
||||
@@ -8151,7 +8151,7 @@ static void handle_openxr_stats(int id, const char *json) {
|
||||
"\"last_failure\":\"%s\",\"last_failure_result\":%d,\"view_flags\":%llu,"
|
||||
"\"waits\":%llu,\"submitted\":%llu,\"empty\":%llu,\"failures\":%llu,"
|
||||
"\"predicted_time\":%llu,\"ipd_m\":%.6f,\"units_per_meter\":%.6f,"
|
||||
"\"gl_version\":%llu,\"min_gl_version\":%llu,\"max_gl_version\":%llu,"
|
||||
"\"gl_version\":%llu,\"min_gl_version\":%llu,\"max_gl_version\":%llu,\"swapchain_format\":%lld,"
|
||||
"\"submitted_layer\":%u,\"quad_submitted\":%llu,"
|
||||
"\"submitted_source\":%u,\"native_submitted\":%llu,\"submitted_native_frame\":%llu,"
|
||||
"\"quad_distance_m\":%.6f,\"quad_width_m\":%.6f,\"quad_height_m\":%.6f,\"losses\":%llu}", id,s.compiled,s.enabled,
|
||||
@@ -8161,7 +8161,7 @@ static void handle_openxr_stats(int id, const char *json) {
|
||||
(unsigned long long)s.submitted,(unsigned long long)s.empty,
|
||||
(unsigned long long)s.failures,(unsigned long long)s.predicted_time,s.ipd_m,s.units_per_meter,
|
||||
(unsigned long long)s.gl_version,(unsigned long long)s.min_gl_version,
|
||||
(unsigned long long)s.max_gl_version,s.submitted_layer,
|
||||
(unsigned long long)s.max_gl_version,(long long)s.swapchain_format,s.submitted_layer,
|
||||
(unsigned long long)s.quad_submitted,s.submitted_source,
|
||||
(unsigned long long)s.native_submitted,(unsigned long long)s.submitted_native_frame,
|
||||
s.quad_distance_m,s.quad_width_m,s.quad_height_m,(unsigned long long)s.losses);
|
||||
|
||||
@@ -91,6 +91,7 @@
|
||||
#include "frame_pacing.h"
|
||||
#include "psx_rewind.h"
|
||||
#include "psx_openxr.h"
|
||||
#include "openxr_color.h"
|
||||
|
||||
#include "psx_sdl.h"
|
||||
#if defined(PSX_SDL3)
|
||||
@@ -163,6 +164,7 @@ typedef void (APIENTRY *PFN_glCompileShader)(GLuint);
|
||||
typedef void (APIENTRY *PFN_glGetShaderiv)(GLuint, GLenum, GLint *);
|
||||
typedef void (APIENTRY *PFN_glGetShaderInfoLog)(GLuint, GLsizei, GLsizei *, char *);
|
||||
typedef void (APIENTRY *PFN_glDeleteShader)(GLuint);
|
||||
typedef void (APIENTRY *PFN_glDeleteProgram)(GLuint);
|
||||
typedef GLuint (APIENTRY *PFN_glCreateProgram)(void);
|
||||
typedef void (APIENTRY *PFN_glAttachShader)(GLuint, GLuint);
|
||||
typedef void (APIENTRY *PFN_glLinkProgram)(GLuint);
|
||||
@@ -226,6 +228,7 @@ static PFN_glCompileShader p_glCompileShader;
|
||||
static PFN_glGetShaderiv p_glGetShaderiv;
|
||||
static PFN_glGetShaderInfoLog p_glGetShaderInfoLog;
|
||||
static PFN_glDeleteShader p_glDeleteShader;
|
||||
static PFN_glDeleteProgram p_glDeleteProgram;
|
||||
static PFN_glCreateProgram p_glCreateProgram;
|
||||
static PFN_glAttachShader p_glAttachShader;
|
||||
static PFN_glLinkProgram p_glLinkProgram;
|
||||
@@ -286,6 +289,7 @@ static int load_modern_gl(void) {
|
||||
LOAD(p_glCreateShader, "glCreateShader"); LOAD(p_glShaderSource, "glShaderSource");
|
||||
LOAD(p_glCompileShader, "glCompileShader"); LOAD(p_glGetShaderiv, "glGetShaderiv");
|
||||
LOAD(p_glGetShaderInfoLog, "glGetShaderInfoLog"); LOAD(p_glDeleteShader, "glDeleteShader");
|
||||
LOAD(p_glDeleteProgram, "glDeleteProgram");
|
||||
LOAD(p_glCreateProgram, "glCreateProgram"); LOAD(p_glAttachShader, "glAttachShader");
|
||||
LOAD(p_glLinkProgram, "glLinkProgram"); LOAD(p_glGetProgramiv, "glGetProgramiv");
|
||||
LOAD(p_glGetProgramInfoLog, "glGetProgramInfoLog"); LOAD(p_glUseProgram, "glUseProgram");
|
||||
@@ -416,6 +420,7 @@ static int s_present_w = 0, s_present_h = 0;
|
||||
static GLuint s_osd_tex = 0;
|
||||
static int s_osd_tw = 0, s_osd_th = 0;
|
||||
static GLuint s_present_prog = 0, s_present_vao = 0;
|
||||
static GLuint s_xr_color_prog = 0, s_xr_native_tex = 0;
|
||||
static void gl_swap_with_osd(void);
|
||||
static int s_native_surface_enabled, s_native_surface_pending;
|
||||
static int s_native_surface_rect[4]; /* Fresh native backbuffer content, GL coordinates. */
|
||||
@@ -6330,6 +6335,11 @@ static void pass_free_color_fbo(GLuint *tex, GLuint *rb, GLuint *fbo,
|
||||
* and journal, and forget their names so a new context makes fresh ones. */
|
||||
static void pass_resources_release(void) {
|
||||
psx_openxr_shutdown();
|
||||
if(s_ctx) {
|
||||
if(s_xr_color_prog) p_glDeleteProgram(s_xr_color_prog);
|
||||
if(s_xr_native_tex) glDeleteTextures(1,&s_xr_native_tex);
|
||||
}
|
||||
s_xr_color_prog=s_xr_native_tex=0;
|
||||
stereo_resources_release();
|
||||
pass_gen_release(0);
|
||||
pass_gen_release(1);
|
||||
@@ -6747,10 +6757,48 @@ int psx_mod_openxr_input(PSXModOpenXRInput *input) {
|
||||
int psx_mod_openxr_hands(PSXModOpenXRHands *hands) {
|
||||
return psx_openxr_hands(hands); /* Snapshot only, also safe during replay. */
|
||||
}
|
||||
/* The target stores display-encoded RGB for an sRGB swapchain, or explicitly
|
||||
* decoded RGB for a linear-only runtime. Never apply hardware sRGB encoding
|
||||
* to these outputs. All shader state is restored before returning to native. */
|
||||
static int openxr_color_draw(GLuint source, int w, int h, int flip, float gamma, int linear) {
|
||||
if (!s_xr_color_prog) s_xr_color_prog = build_program(PRESENT_VS, PSX_XR_COLOR_FS);
|
||||
if (!s_xr_color_prog || !s_present_vao) return 0;
|
||||
GLint program, vao, viewport[4], active, binding, min_filter, mag_filter;
|
||||
GLboolean mask[4];
|
||||
const GLenum caps[] = {GL_BLEND, GL_DEPTH_TEST, GL_STENCIL_TEST, GL_CULL_FACE};
|
||||
GLboolean enabled[4];
|
||||
glGetIntegerv(0x8B8D, &program);glGetIntegerv(0x85B5, &vao);
|
||||
glGetIntegerv(GL_VIEWPORT, viewport);glGetIntegerv(0x84E0, &active);
|
||||
glGetBooleanv(GL_COLOR_WRITEMASK, mask);
|
||||
p_glActiveTexture(PSXGL_TEXTURE0);glGetIntegerv(GL_TEXTURE_BINDING_2D, &binding);
|
||||
glBindTexture(GL_TEXTURE_2D, source);
|
||||
glGetTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, &min_filter);
|
||||
glGetTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, &mag_filter);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
for (int i=0;i<4;i++) {enabled[i]=glIsEnabled(caps[i]);glDisable(caps[i]);}
|
||||
glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);glViewport(0,0,w,h);
|
||||
p_glUseProgram(s_xr_color_prog);
|
||||
p_glUniform1i(p_glGetUniformLocation(s_xr_color_prog,"u_tex"),0);
|
||||
p_glUniform1i(p_glGetUniformLocation(s_xr_color_prog,"u_linear"),linear);
|
||||
p_glUniform1f(p_glGetUniformLocation(s_xr_color_prog,"u_gamma"),gamma);
|
||||
p_glUniform4f(p_glGetUniformLocation(s_xr_color_prog,"u_uv_rect"),0,flip?0:1,1,flip?1:0);
|
||||
p_glBindVertexArray(s_present_vao);glDrawArrays(GL_TRIANGLES,0,3);
|
||||
p_glBindVertexArray((GLuint)vao);p_glUseProgram((GLuint)program);
|
||||
glViewport(viewport[0],viewport[1],viewport[2],viewport[3]);
|
||||
glColorMask(mask[0],mask[1],mask[2],mask[3]);
|
||||
for (int i=0;i<4;i++) if(enabled[i]) glEnable(caps[i]);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, min_filter);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, mag_filter);
|
||||
glBindTexture(GL_TEXTURE_2D,(GLuint)binding);p_glActiveTexture((GLenum)active);
|
||||
return 1;
|
||||
}
|
||||
static int openxr_copy_eye(uint32_t eye, uint32_t texture, int w, int h) {
|
||||
StereoPair *p = &s_stereo_pair[s_stereo_current];
|
||||
GLint read_fbo, draw_fbo; GLuint target = 0;
|
||||
GLboolean scissor = glIsEnabled(GL_SCISSOR_TEST);
|
||||
GLboolean srgb = glIsEnabled(0x8DB9); /* GL_FRAMEBUFFER_SRGB */
|
||||
PSXOpenXRStats xr;psx_openxr_stats(&xr);
|
||||
glGetIntegerv(0x8CAA, &read_fbo);
|
||||
glGetIntegerv(0x8CA6, &draw_fbo);
|
||||
p_glGenFramebuffers(1, &target);
|
||||
@@ -6759,17 +6807,23 @@ static int openxr_copy_eye(uint32_t eye, uint32_t texture, int w, int h) {
|
||||
int ok = p_glCheckFramebufferStatus(PSXGL_DRAW_FRAMEBUFFER) == PSXGL_FRAMEBUFFER_COMPLETE;
|
||||
if (ok) {
|
||||
glDisable(GL_SCISSOR_TEST);
|
||||
glDisable(0x8DB9);
|
||||
p_glBindFramebuffer(PSXGL_READ_FRAMEBUFFER, p->fbo[eye]);
|
||||
/* Stereo capture texture row convention is opposite the XR layer.
|
||||
* User confirmed inverted headset output with the original direct blit.
|
||||
* Flip only submission; eye dumps and desktop presentation stay intact. */
|
||||
p_glBlitFramebuffer(0, p->th[eye], p->tw[eye], 0, 0, 0, w, h,
|
||||
GL_COLOR_BUFFER_BIT, GL_LINEAR);
|
||||
if (xr.swapchain_format == PSX_XR_SRGB8_ALPHA8 && s_present_gamma == 1.0f)
|
||||
p_glBlitFramebuffer(0, p->th[eye], p->tw[eye], 0, 0, 0, w, h,
|
||||
GL_COLOR_BUFFER_BIT, GL_LINEAR);
|
||||
else
|
||||
ok = openxr_color_draw(p->tex[eye], w, h, 1, s_present_gamma,
|
||||
xr.swapchain_format != PSX_XR_SRGB8_ALPHA8);
|
||||
glFlush();
|
||||
}
|
||||
p_glBindFramebuffer(PSXGL_READ_FRAMEBUFFER, (GLuint)read_fbo);
|
||||
p_glBindFramebuffer(PSXGL_DRAW_FRAMEBUFFER, (GLuint)draw_fbo);
|
||||
if (scissor) glEnable(GL_SCISSOR_TEST);
|
||||
if (srgb) glEnable(0x8DB9);
|
||||
p_glDeleteFramebuffers(1, &target);return ok;
|
||||
}
|
||||
int psx_mod_openxr_end(int rendered) {
|
||||
@@ -6783,6 +6837,8 @@ int psx_mod_openxr_end(int rendered) {
|
||||
static int openxr_copy_native(uint32_t eye,uint32_t texture,int w,int h) {
|
||||
GLint read_fbo,draw_fbo,read_buffer;GLuint target=0;
|
||||
GLboolean scissor=glIsEnabled(GL_SCISSOR_TEST);
|
||||
GLboolean srgb=glIsEnabled(0x8DB9);
|
||||
PSXOpenXRStats xr;psx_openxr_stats(&xr);
|
||||
(void)eye;
|
||||
glGetIntegerv(0x8CAA,&read_fbo);glGetIntegerv(0x8CA6,&draw_fbo);
|
||||
glGetIntegerv(GL_READ_BUFFER,&read_buffer);
|
||||
@@ -6792,15 +6848,30 @@ static int openxr_copy_native(uint32_t eye,uint32_t texture,int w,int h) {
|
||||
if(ok) {
|
||||
int *r=s_native_surface_rect;
|
||||
glDisable(GL_SCISSOR_TEST);p_glBindFramebuffer(PSXGL_READ_FRAMEBUFFER,0);glReadBuffer(GL_BACK);
|
||||
glDisable(0x8DB9);
|
||||
/* Default framebuffer is already upright: unlike native VRAM/eye
|
||||
* textures, its bottom GL row is the displayed bottom row. */
|
||||
(void)pass_gl_errors();
|
||||
p_glBlitFramebuffer(r[0],r[1],r[0]+r[2],r[1]+r[3],0,0,w,h,GL_COLOR_BUFFER_BIT,GL_LINEAR);
|
||||
glFlush();ok=pass_gl_errors()==0;
|
||||
if (xr.swapchain_format == PSX_XR_SRGB8_ALPHA8)
|
||||
p_glBlitFramebuffer(r[0],r[1],r[0]+r[2],r[1]+r[3],0,0,w,h,GL_COLOR_BUFFER_BIT,GL_LINEAR);
|
||||
else {
|
||||
GLint active,binding;glGetIntegerv(0x84E0,&active);
|
||||
p_glActiveTexture(PSXGL_TEXTURE0);glGetIntegerv(GL_TEXTURE_BINDING_2D,&binding);
|
||||
if(!s_xr_native_tex) glGenTextures(1,&s_xr_native_tex);
|
||||
glBindTexture(GL_TEXTURE_2D,s_xr_native_tex);
|
||||
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_CLAMP_TO_EDGE);
|
||||
glCopyTexImage2D(GL_TEXTURE_2D,0,GL_RGBA8,r[0],r[1],r[2],r[3],0);
|
||||
glBindTexture(GL_TEXTURE_2D,(GLuint)binding);p_glActiveTexture((GLenum)active);
|
||||
/* Desktop gamma is already baked into GL_BACK; decode it once. */
|
||||
ok=openxr_color_draw(s_xr_native_tex,w,h,0,1.0f,1);
|
||||
}
|
||||
glFlush();ok=ok && pass_gl_errors()==0;
|
||||
}
|
||||
p_glBindFramebuffer(PSXGL_READ_FRAMEBUFFER,(GLuint)read_fbo);glReadBuffer((GLenum)read_buffer);
|
||||
p_glBindFramebuffer(PSXGL_DRAW_FRAMEBUFFER,(GLuint)draw_fbo);
|
||||
if(scissor)glEnable(GL_SCISSOR_TEST);
|
||||
if(srgb)glEnable(0x8DB9);
|
||||
p_glDeleteFramebuffers(1,&target);return ok;
|
||||
}
|
||||
static void openxr_present_native(void) {
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
* no other thread creates a GL context or touches XR. Hence no locking. */
|
||||
#include "psx_openxr.h"
|
||||
#include "vr_pose_math.h"
|
||||
#include "openxr_color.h"
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <math.h>
|
||||
@@ -239,15 +240,16 @@ static int initialize(void) {
|
||||
uint32_t formats_n=0;
|
||||
if(!check(xrEnumerateSwapchainFormats(s_session,0,&formats_n,NULL),"formats"))return 0;
|
||||
int64_t *formats=calloc(formats_n,sizeof *formats);if(!formats){s_stats.stage="formats_memory";return 0;}
|
||||
supported=0;
|
||||
if(check(xrEnumerateSwapchainFormats(s_session,formats_n,&formats_n,formats),"formats"))
|
||||
for(uint32_t i=0;i<formats_n;i++)if(formats[i]==GL_RGBA8)supported=1;
|
||||
if(!check(xrEnumerateSwapchainFormats(s_session,formats_n,&formats_n,formats),"formats")) {
|
||||
free(formats);return 0;
|
||||
}
|
||||
s_stats.swapchain_format=psx_xr_color_format(formats,formats_n);
|
||||
free(formats);
|
||||
if(!supported){s_stats.stage="rgba8_format_missing";return 0;}
|
||||
if(!s_stats.swapchain_format){s_stats.stage="color_format_missing";return 0;}
|
||||
for(int eye=0;eye<2;eye++) {
|
||||
s_w[eye]=config[eye].recommendedImageRectWidth;s_h[eye]=config[eye].recommendedImageRectHeight;
|
||||
XrSwapchainCreateInfo sc={XR_TYPE_SWAPCHAIN_CREATE_INFO};
|
||||
sc.usageFlags=XR_SWAPCHAIN_USAGE_COLOR_ATTACHMENT_BIT;sc.format=GL_RGBA8;
|
||||
sc.usageFlags=XR_SWAPCHAIN_USAGE_COLOR_ATTACHMENT_BIT;sc.format=s_stats.swapchain_format;
|
||||
sc.sampleCount=1;sc.width=s_w[eye];sc.height=s_h[eye];sc.faceCount=1;sc.arraySize=1;sc.mipCount=1;
|
||||
if(!check(xrCreateSwapchain(s_session,&sc,&s_chain[eye]),"swapchain"))return 0;
|
||||
if(!check(xrEnumerateSwapchainImages(s_chain[eye],0,&s_count[eye],NULL),"images"))return 0;
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
"""Run actual renderer XR-copy functions against a source-owned ramp on real GL.
|
||||
|
||||
Extract the private submission functions verbatim into an isolated fixture:
|
||||
Windows linkers otherwise retain unrelated public renderer entry points.
|
||||
No mocks of OpenGL or shader/color conversion are used.
|
||||
"""
|
||||
import argparse
|
||||
import json
|
||||
import pathlib
|
||||
import platform
|
||||
import subprocess
|
||||
|
||||
|
||||
def function(source, signature):
|
||||
start = source.index(signature)
|
||||
brace = source.index("{", start)
|
||||
while source.index(";", start) < brace:
|
||||
start = source.index(signature, start + 1)
|
||||
brace = source.index("{", start)
|
||||
depth = 0
|
||||
for pos in range(brace, len(source)):
|
||||
if source[pos] == "{":
|
||||
depth += 1
|
||||
elif source[pos] == "}":
|
||||
depth -= 1
|
||||
if not depth:
|
||||
return source[start:pos + 1]
|
||||
raise ValueError(signature)
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--cc", default="cc")
|
||||
parser.add_argument("--sdl-include", required=True)
|
||||
parser.add_argument("--sdl-library", required=True)
|
||||
parser.add_argument("--output", required=True)
|
||||
args = parser.parse_args()
|
||||
root = pathlib.Path(__file__).resolve().parents[2]
|
||||
dest = pathlib.Path(args.output).resolve()
|
||||
dest.mkdir(parents=True, exist_ok=True)
|
||||
source = (root / "runtime/src/gpu_gl_renderer.c").read_text(encoding="utf-8")
|
||||
loader = function(source, "static int load_modern_gl(void)")
|
||||
prefix = source[:source.index("static int load_modern_gl(void)")] + loader
|
||||
vs_start = source.index("static const char *PRESENT_VS")
|
||||
vs = source[vs_start:source.index("/* Present sampling", vs_start)]
|
||||
pair_start = source.index("typedef struct StereoPair")
|
||||
pair = source[pair_start:source.index("} StereoPair;", pair_start) + len("} StereoPair;")]
|
||||
declarations = """
|
||||
static SDL_GLContext s_ctx;
|
||||
static GLuint s_present_vao, s_xr_color_prog, s_xr_native_tex;
|
||||
static float s_present_gamma=1.0f;
|
||||
static GLenum s_last_fbo_status;
|
||||
static StereoPair s_stereo_pair[2];
|
||||
static int s_stereo_current, s_native_surface_rect[4];
|
||||
"""
|
||||
signatures = ["static GLuint compile_shader(", "static GLuint build_program_ex(",
|
||||
"static GLuint build_program(", "static GLuint make_tex(",
|
||||
"static int make_fbo(", "static uint32_t pass_gl_errors(",
|
||||
"static int openxr_color_draw(", "static int openxr_copy_eye(",
|
||||
"static int openxr_copy_native("]
|
||||
header = prefix + vs + pair + declarations + "\n".join(function(source, s) for s in signatures)
|
||||
(dest / "openxr_color_fixture_renderer.h").write_text(header, encoding="utf-8")
|
||||
exe = dest / ("openxr_color.exe" if platform.system() == "Windows" else "openxr_color")
|
||||
cmd = [args.cc, "-std=gnu11", "-O1", "-DPSX_SDL3=1", "-DPSX_NO_DEBUG_TOOLS=1",
|
||||
"-DSDL_MAIN_HANDLED", "-I" + str(root / "runtime/include"),
|
||||
"-I" + str(root / "runtime/src"),
|
||||
"-I" + args.sdl_include, "-I" + str(dest),
|
||||
str(root / "runtime/tests/test_openxr_color_gl.c"), args.sdl_library,
|
||||
"-o", str(exe)]
|
||||
if platform.system() == "Windows":
|
||||
cmd += ["-lopengl32", "-lmingw32", "-luser32", "-lgdi32", "-lwinmm", "-limm32",
|
||||
"-lole32", "-loleaut32", "-lversion", "-luuid", "-lsetupapi", "-lcfgmgr32", "-lm"]
|
||||
else:
|
||||
cmd += ["-lGL", "-lm", "-ldl", "-lpthread"]
|
||||
build = subprocess.run(cmd, capture_output=True, text=True)
|
||||
receipt = {"command": cmd, "build_exit": build.returncode, "build_stderr": build.stderr}
|
||||
if not build.returncode:
|
||||
result = subprocess.run([str(exe)], capture_output=True, text=True)
|
||||
receipt.update(run_exit=result.returncode, stdout=result.stdout, stderr=result.stderr)
|
||||
(dest / "receipt.json").write_text(json.dumps(receipt, indent=2), encoding="utf-8")
|
||||
print("build:", build.returncode, "GL fixture:", receipt.get("run_exit", "not run"))
|
||||
if build.returncode:
|
||||
print(build.stderr[-4000:])
|
||||
elif receipt["run_exit"]:
|
||||
print(receipt["stderr"][-2000:])
|
||||
return build.returncode or receipt.get("run_exit", 1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -0,0 +1,75 @@
|
||||
/* Source-owned grayscale/color ramp, actual submission callbacks and hidden GL.
|
||||
* No headset or retail payload required. Compile with function-section GC. */
|
||||
#include "openxr_color_fixture_renderer.h"
|
||||
#include <assert.h>
|
||||
static PSXOpenXRStats fixture_xr;
|
||||
void psx_openxr_stats(PSXOpenXRStats *out) { *out=fixture_xr; }
|
||||
static double decode(double x) {return x<=.04045?x/12.92:pow((x+.055)/1.055,2.4);}
|
||||
static void check_pixels(const unsigned char *got,const unsigned char *src,int flip,
|
||||
double gamma,int linear,int alpha) {
|
||||
for(int y=0;y<2;y++) for(int x=0;x<256;x++) for(int c=0;c<(alpha?4:3);c++) {
|
||||
double v=src[((flip?1-y:y)*256+x)*4+c]/255.;
|
||||
if(c<3) {v=pow(v,1/gamma);if(linear)v=decode(v);}
|
||||
int expected=(int)floor(v*255+.5);
|
||||
assert(abs((int)got[(y*256+x)*4+c]-expected)<=1);
|
||||
}
|
||||
}
|
||||
int main(void) {
|
||||
int64_t formats[]={0x1234,PSX_XR_RGBA8,PSX_XR_SRGB8_ALPHA8};
|
||||
assert(psx_xr_color_format(formats,3)==PSX_XR_SRGB8_ALPHA8);
|
||||
assert(psx_xr_color_format(formats,2)==PSX_XR_RGBA8);
|
||||
assert(!psx_xr_color_format(formats,1));assert(!psx_xr_color_format(NULL,0));
|
||||
assert(SDL_Init(SDL_INIT_VIDEO)==0);
|
||||
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);
|
||||
SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE,8);
|
||||
SDL_Window *win=SDL_CreateWindow("XR color fixture",0,0,256,64,SDL_WINDOW_OPENGL|SDL_WINDOW_HIDDEN);
|
||||
assert(win);s_ctx=SDL_GL_CreateContext(win);assert(s_ctx);assert(load_modern_gl());
|
||||
p_glGenVertexArrays(1,&s_present_vao);
|
||||
unsigned char src[256*2*4],got[sizeof src],native[sizeof src];
|
||||
for(int y=0;y<2;y++) for(int x=0;x<256;x++) {
|
||||
int i=(y*256+x)*4;
|
||||
src[i]=y?255-x:x;src[i+1]=y?x:255-x;src[i+2]=x/2;src[i+3]=77;
|
||||
}
|
||||
StereoPair *pair=&s_stereo_pair[0];s_stereo_current=0;
|
||||
pair->tex[0]=make_tex(GL_RGBA8,256,2,GL_RGBA,GL_UNSIGNED_BYTE);
|
||||
glBindTexture(GL_TEXTURE_2D,pair->tex[0]);
|
||||
glTexSubImage2D(GL_TEXTURE_2D,0,0,0,256,2,GL_RGBA,GL_UNSIGNED_BYTE,src);
|
||||
assert(make_fbo(&pair->fbo[0],pair->tex[0],0));pair->tw[0]=256;pair->th[0]=2;
|
||||
for(int linear=0;linear<2;linear++) for(int gamma=0;gamma<2;gamma++) for(int enabled=0;enabled<2;enabled++) {
|
||||
fixture_xr.swapchain_format=linear?PSX_XR_RGBA8:PSX_XR_SRGB8_ALPHA8;
|
||||
GLuint target=make_tex((GLint)fixture_xr.swapchain_format,256,2,GL_RGBA,GL_UNSIGNED_BYTE),fbo=0;
|
||||
assert(make_fbo(&fbo,target,0));
|
||||
p_glBindFramebuffer(PSXGL_FRAMEBUFFER,0);glViewport(3,4,17,19);
|
||||
glEnable(GL_SCISSOR_TEST);glScissor(0,0,1,1);
|
||||
if(enabled) glEnable(0x8DB9);else glDisable(0x8DB9);
|
||||
s_present_gamma=gamma?1.7f:1.f;
|
||||
glEnable(GL_BLEND);glEnable(GL_DEPTH_TEST);glEnable(GL_STENCIL_TEST);glEnable(GL_CULL_FACE);
|
||||
glColorMask(GL_FALSE,GL_TRUE,GL_FALSE,GL_TRUE);
|
||||
assert(openxr_copy_eye(0,target,256,2));assert(glGetError()==GL_NO_ERROR);
|
||||
GLint viewport[4],read_fbo,draw_fbo;GLboolean mask[4];
|
||||
glGetIntegerv(GL_VIEWPORT,viewport);glGetIntegerv(0x8CAA,&read_fbo);glGetIntegerv(0x8CA6,&draw_fbo);
|
||||
glGetBooleanv(GL_COLOR_WRITEMASK,mask);
|
||||
assert(viewport[0]==3&&viewport[1]==4&&viewport[2]==17&&viewport[3]==19);
|
||||
assert(!read_fbo&&!draw_fbo&&!mask[0]&&mask[1]&&!mask[2]&&mask[3]);
|
||||
assert(glIsEnabled(GL_SCISSOR_TEST)&&glIsEnabled(0x8DB9)==enabled);
|
||||
assert(glIsEnabled(GL_BLEND)&&glIsEnabled(GL_DEPTH_TEST)&&glIsEnabled(GL_STENCIL_TEST)&&glIsEnabled(GL_CULL_FACE));
|
||||
p_glBindFramebuffer(PSXGL_READ_FRAMEBUFFER,fbo);glReadBuffer(PSXGL_COLOR_ATTACHMENT0);
|
||||
glReadPixels(0,0,256,2,GL_RGBA,GL_UNSIGNED_BYTE,got);
|
||||
check_pixels(got,src,1,s_present_gamma,linear,1);
|
||||
/* Native already contains desktop gamma: do not apply it a second time. */
|
||||
p_glBindFramebuffer(PSXGL_FRAMEBUFFER,0);glDisable(GL_SCISSOR_TEST);glDisable(0x8DB9);
|
||||
assert(openxr_color_draw(pair->tex[0],256,2,0,s_present_gamma,0));
|
||||
glReadBuffer(GL_BACK);glReadPixels(0,0,256,2,GL_RGBA,GL_UNSIGNED_BYTE,native);
|
||||
s_native_surface_rect[0]=s_native_surface_rect[1]=0;
|
||||
s_native_surface_rect[2]=256;s_native_surface_rect[3]=2;
|
||||
if(enabled)glEnable(0x8DB9);
|
||||
assert(openxr_copy_native(0,target,256,2));assert(glIsEnabled(0x8DB9)==enabled);
|
||||
p_glBindFramebuffer(PSXGL_READ_FRAMEBUFFER,fbo);glReadBuffer(PSXGL_COLOR_ATTACHMENT0);
|
||||
glReadPixels(0,0,256,2,GL_RGBA,GL_UNSIGNED_BYTE,got);
|
||||
check_pixels(got,native,0,1,linear,0);assert(glGetError()==GL_NO_ERROR);
|
||||
p_glBindFramebuffer(PSXGL_FRAMEBUFFER,0);p_glDeleteFramebuffers(1,&fbo);glDeleteTextures(1,&target);
|
||||
}
|
||||
SDL_GL_DeleteContext(s_ctx);SDL_DestroyWindow(win);SDL_Quit();return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user