mirror of
https://github.com/HeyPuter/gex.git
synced 2026-11-13 20:12:43 -10:00
gex-gl: a mapped buffer range keeps what the application did not write
glMapBufferRange gave the application a zero-filled shadow and glUnmapBuffer uploaded all of it: a write-only map without an invalidate bit zeroed the bytes it did not write, and a map with GL_MAP_FLUSH_EXPLICIT_BIT (ring buffers, Unity's among them) overwrote everything but the flushed ranges. The shadow now reads the range back unless it is invalidated or flushed explicitly, and an explicitly flushed map uploads only what was flushed. Test: tests/gl-map-buffer.sh (the probe checks itself on native Mesa, then under gexc --gl; 1 of 3 cases passed before). Co-Authored-By: Claude Opus 5.5 <[email protected]> (cherry picked from commit 6ab47b868d431e3e85f6058c8362a7e09aa2bf72)
This commit is contained in:
3 files changed
+123
-2
No files matched your search
+13
-2
@@ -579,7 +579,14 @@ void *glMapBufferRange(unsigned target, long offset, long length,
|
||||
object->map_offset = (size_t)offset;
|
||||
object->map_access = access;
|
||||
object->map = calloc(1, object->map_length ? object->map_length : 1);
|
||||
if (object->map && (access & 1u) && object->map_length)
|
||||
/* The shadow must hold the buffer's bytes wherever the application may
|
||||
read them (GL_MAP_READ_BIT) or where unmap uploads bytes it did not
|
||||
write: without GL_MAP_INVALIDATE_{RANGE,BUFFER}_BIT the range keeps
|
||||
its old contents. With GL_MAP_FLUSH_EXPLICIT_BIT only flushed ranges
|
||||
are uploaded, so nothing else needs to be read. */
|
||||
unsigned keep = (access & 1u) ||
|
||||
!(access & (0x4u | 0x8u | 0x10u)); /* INVALIDATE_RANGE/BUFFER, FLUSH_EXPLICIT */
|
||||
if (object->map && keep && object->map_length)
|
||||
glGetBufferSubData(target, offset, length, object->map);
|
||||
return object->map;
|
||||
}
|
||||
@@ -594,7 +601,11 @@ void *glMapBuffer(unsigned target, unsigned access) {
|
||||
unsigned char glUnmapBuffer(unsigned target) {
|
||||
struct buffer_object_state *object = buffer_object(bound_buffer(target), 0);
|
||||
if (!object || !object->map) return 0;
|
||||
if (object->map_access & 2u)
|
||||
/* With GL_MAP_FLUSH_EXPLICIT_BIT the application said which ranges it
|
||||
wrote (glFlushMappedBufferRange, already uploaded); uploading the
|
||||
whole shadow would overwrite the rest of the range with zeros --
|
||||
a ring buffer's other, in-flight, contents. */
|
||||
if ((object->map_access & 2u) && !(object->map_access & 0x10u))
|
||||
glBufferSubData(target, (long)object->map_offset, (long)object->map_length, object->map);
|
||||
free(object->map);
|
||||
object->map = NULL;
|
||||
|
||||
Executable
+19
@@ -0,0 +1,19 @@
|
||||
#!/bin/sh
|
||||
# glMapBufferRange's contents rules, natively (x86-64 guest, host GL): the
|
||||
# probe checks itself on native Mesa first, then runs under gexc against
|
||||
# guest/gl built for x86-64.
|
||||
#
|
||||
# tests/gl-map-buffer.sh [gexc] (default target/x86/release/gexc)
|
||||
set -eu
|
||||
ROOT=$(CDPATH='' cd -- "$(dirname "$0")/.." && pwd)
|
||||
GEXC=${1:-$ROOT/target/x86/release/gexc}
|
||||
SOURCE="$ROOT/tests/integration/programs/gl-map-buffer-probe.c"
|
||||
WORK=${WORK:-$(mktemp -d)}
|
||||
mkdir -p "$WORK"
|
||||
cc -O2 -Wall -Wextra -o "$WORK/probe" "$SOURCE" -lEGL -lGL
|
||||
EGL_PLATFORM=surfaceless LIBGL_ALWAYS_SOFTWARE=1 "$WORK/probe" | tee "$WORK/native.log"
|
||||
grep -q 'SUMMARY cases=3 passed=3 failed=0' "$WORK/native.log"
|
||||
GEX_CC=${GEX_CC:-cc} "$ROOT/guest/gl/build.sh" "$WORK/guest-gl" >/dev/null
|
||||
"$GEXC" run --gl --env LD_LIBRARY_PATH="$WORK/guest-gl" "$WORK/probe" | tee "$WORK/gex.log"
|
||||
grep -q 'SUMMARY cases=3 passed=3 failed=0' "$WORK/gex.log"
|
||||
echo "ok gl-map-buffer"
|
||||
@@ -0,0 +1,91 @@
|
||||
/* glMapBufferRange keeps what it should: a write-only map without an
|
||||
* invalidate bit leaves unwritten bytes as they were, and a map with
|
||||
* GL_MAP_FLUSH_EXPLICIT_BIT changes only the ranges flushed (Unity's ring
|
||||
* buffers). gex-gl's guest shadow used to upload zeros over both. Prints
|
||||
* SUMMARY cases=N passed=N failed=0 when right; runs on native Mesa too.
|
||||
*
|
||||
* cc -o probe gl-map-buffer-probe.c -lEGL -lGL (native reference)
|
||||
* gexc run --gl --env LD_LIBRARY_PATH=GUEST_GL probe (guest/gl/build.sh) */
|
||||
#define GL_GLEXT_PROTOTYPES 1
|
||||
#include <EGL/egl.h>
|
||||
#include <GL/glcorearb.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
static int cases, passed;
|
||||
|
||||
static void check(const char *name, GLuint buf, const unsigned char *want) {
|
||||
unsigned char got[64];
|
||||
glBindBuffer(GL_ARRAY_BUFFER, buf);
|
||||
glGetBufferSubData(GL_ARRAY_BUFFER, 0, sizeof got, got);
|
||||
int ok = memcmp(got, want, sizeof got) == 0;
|
||||
cases++;
|
||||
passed += ok;
|
||||
printf("CASE %s %s\n", name, ok ? "PASS" : "FAIL");
|
||||
if (!ok) {
|
||||
for (int i = 0; i < 64; i++) printf("%02x%s", got[i], i % 16 == 15 ? "\n" : " ");
|
||||
}
|
||||
}
|
||||
|
||||
static GLuint fresh(unsigned char *shadow) {
|
||||
GLuint b;
|
||||
glGenBuffers(1, &b);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, b);
|
||||
for (int i = 0; i < 64; i++) shadow[i] = (unsigned char)(0xa0 + i);
|
||||
glBufferData(GL_ARRAY_BUFFER, 64, shadow, GL_DYNAMIC_DRAW);
|
||||
return b;
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
|
||||
if (display == EGL_NO_DISPLAY || !eglInitialize(display, NULL, NULL) || !eglBindAPI(EGL_OPENGL_API)) {
|
||||
fprintf(stderr, "FATAL egl_initialize error=0x%x\n", eglGetError());
|
||||
return 2;
|
||||
}
|
||||
const EGLint config_attributes[] = {
|
||||
EGL_SURFACE_TYPE, EGL_PBUFFER_BIT, EGL_RENDERABLE_TYPE, EGL_OPENGL_BIT,
|
||||
EGL_RED_SIZE, 8, EGL_GREEN_SIZE, 8, EGL_BLUE_SIZE, 8, EGL_ALPHA_SIZE, 8, EGL_NONE,
|
||||
};
|
||||
EGLConfig config = NULL;
|
||||
EGLint config_count = 0;
|
||||
if (!eglChooseConfig(display, config_attributes, &config, 1, &config_count) || config_count < 1) return 2;
|
||||
const EGLint context_attributes[] = {EGL_CONTEXT_MAJOR_VERSION, 4, EGL_CONTEXT_MINOR_VERSION, 4, EGL_NONE};
|
||||
const EGLint surface_attributes[] = {EGL_WIDTH, 16, EGL_HEIGHT, 8, EGL_NONE};
|
||||
EGLContext context = eglCreateContext(display, config, EGL_NO_CONTEXT, context_attributes);
|
||||
EGLSurface surface = eglCreatePbufferSurface(display, config, surface_attributes);
|
||||
if (context == EGL_NO_CONTEXT || surface == EGL_NO_SURFACE || !eglMakeCurrent(display, surface, surface, context))
|
||||
return 2;
|
||||
|
||||
unsigned char want[64];
|
||||
|
||||
/* Write-only, no invalidate: bytes 16..47 mapped, only 20..23 written. */
|
||||
GLuint a = fresh(want);
|
||||
unsigned char *p = glMapBufferRange(GL_ARRAY_BUFFER, 16, 32, GL_MAP_WRITE_BIT);
|
||||
if (p) memset(p + 4, 0x11, 4);
|
||||
glUnmapBuffer(GL_ARRAY_BUFFER);
|
||||
memset(want + 20, 0x11, 4);
|
||||
check("write_keeps_unwritten", a, want);
|
||||
|
||||
/* Flush explicit: map 0..63, write 8..15 and 40..47, flush only 8..15. */
|
||||
GLuint b = fresh(want);
|
||||
p = glMapBufferRange(GL_ARRAY_BUFFER, 0, 64,
|
||||
GL_MAP_WRITE_BIT | GL_MAP_FLUSH_EXPLICIT_BIT | GL_MAP_UNSYNCHRONIZED_BIT);
|
||||
if (p) {
|
||||
memset(p + 8, 0x22, 8);
|
||||
glFlushMappedBufferRange(GL_ARRAY_BUFFER, 8, 8);
|
||||
}
|
||||
glUnmapBuffer(GL_ARRAY_BUFFER);
|
||||
memset(want + 8, 0x22, 8);
|
||||
check("flush_explicit_only_flushed", b, want);
|
||||
|
||||
/* Invalidate range: the whole mapped range written, as the app must. */
|
||||
GLuint c = fresh(want);
|
||||
p = glMapBufferRange(GL_ARRAY_BUFFER, 32, 16, GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_RANGE_BIT);
|
||||
if (p) memset(p, 0x33, 16);
|
||||
glUnmapBuffer(GL_ARRAY_BUFFER);
|
||||
memset(want + 32, 0x33, 16);
|
||||
check("invalidate_range_written", c, want);
|
||||
|
||||
printf("SUMMARY cases=%d passed=%d failed=%d\n", cases, passed, cases - passed);
|
||||
return cases == passed ? 0 : 1;
|
||||
}
|
||||
Reference in new issue
Block a user