--- a/render/fx_renderer/fx_pass.c +++ b/render/fx_renderer/fx_pass.c @@ -1,3 +1,4 @@ +#include /* private G2 state observability */ #include #include #include @@ -21,6 +22,8 @@ #include "scenefx/types/fx/blur_data.h" #include "util/matrix.h" +#include "g2_native.h" + #define MAX_QUADS 86 // 4kb struct fx_render_texture_options fx_render_texture_options_default( @@ -66,6 +69,8 @@ output->name); return false; } + + g2_track_output(pass, output); // Update the buffers if needed struct fx_renderer *renderer = pass->buffer->renderer; @@ -115,6 +120,9 @@ TRACY_BOTH_ZONES_START(pass->buffer->renderer); push_fx_debug(renderer); + + g2_sample_native(pass, pass->buffer, 9, NULL); + g2_forget_output(pass); if (timer) { // clear disjoint flag @@ -898,6 +906,7 @@ set_proj_matrix(shader->proj, pass->projection_matrix, &dst_box); set_tex_matrix(shader->tex_proj, options->transform, &src_fbox); + g2_uniform(pass, shader, texture, options->clip); render(&dst_box, options->clip, shader->pos_attrib); glBindTexture(texture->target, 0); @@ -1057,6 +1066,7 @@ for (int i = 0; i < blur_data.num_passes; ++i) { wlr_region_scale(&scaled_damage, &damage, 1.0f / (1 << (i + 1))); render_blur_segments(pass, fx_options, &renderer->shaders.blur1); + g2_sample_native(pass, fx_options->current_buffer, (unsigned)i + 1, &scaled_damage); } // Upscale @@ -1064,6 +1074,7 @@ // when upsampling we make the region twice as big wlr_region_scale(&scaled_damage, &damage, 1.0f / (1 << i)); render_blur_segments(pass, fx_options, &renderer->shaders.blur2); + g2_sample_native(pass, fx_options->current_buffer, 6u - (unsigned)i, &scaled_damage); } pixman_region32_fini(&scaled_damage); @@ -1223,15 +1234,18 @@ struct fx_render_blur_pass_options blur_options = *fx_options; blur_options.current_buffer = pass->buffer; blur_options.tex_options.base.clip = &clip; + g2_begin(pass, pass->buffer, &blur_options); struct fx_framebuffer *fx_buffer = get_main_buffer_blur(pass, &blur_options); if (fx_buffer != NULL) { // Render the newly blurred content into the blur_buffer fx_render_pass_read_to_buffer(pass, &clip, pass->fx_offscreen_buffers->optimized_blur_buffer, fx_buffer); + g2_sample_native(pass, pass->fx_offscreen_buffers->optimized_blur_buffer, 7, &clip); // Save the current scene pass state fx_render_pass_read_to_buffer(pass, &clip, pass->fx_offscreen_buffers->optimized_no_blur_buffer, pass->buffer); + g2_sample_native(pass, pass->fx_offscreen_buffers->optimized_no_blur_buffer, 8, &clip); } pixman_region32_fini(&clip); --- /dev/null +++ b/render/fx_renderer/g2_gl.h @@ -0,0 +1,134 @@ +/* G2 new source-only intrusive diagnostic. No production API. */ +#ifndef G2_GL_H +#define G2_GL_H +#include +#include +#include +#include +#include +#include +#include +#define G2_CAP (256u * 1024u * 1024u) +#define G2_ERRORS 16 +struct g2_errors { GLenum values[G2_ERRORS]; unsigned count; bool exhausted; }; +struct g2_state { + GLint read_fbo, draw_fbo, rbo, pack, pbo, row, skip_rows, skip_pixels; + GLint viewport[4], scissor[4], program, active, texture; + GLboolean scissor_on; +}; +struct g2_sample { + EGLContext context; EGLint client_version; EGLenum api; + char version[160]; struct g2_state before, after; + struct g2_errors pre, query, read, restore; + GLint object_type, object, level, width, height, format, channels[4]; + GLint read_buffer, samples; GLenum status; size_t bytes; unsigned char *pixels; + bool success; const char *reason; +}; +static struct g2_errors g2_drain(void) { + struct g2_errors e = {0}; + for (unsigned i=0;iG2_CAP || (size_t)w>SIZE_MAX/4/(size_t)h) return false; + *n=(size_t)w*(size_t)h*4; return *n<=G2_CAP-used; +} +static void g2_state_get(struct g2_state *s) { + memset(s,0,sizeof(*s)); + glGetIntegerv(GL_READ_FRAMEBUFFER_BINDING,&s->read_fbo); + glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING,&s->draw_fbo); + glGetIntegerv(GL_RENDERBUFFER_BINDING,&s->rbo); + glGetIntegerv(GL_PACK_ALIGNMENT,&s->pack); + glGetIntegerv(GL_PIXEL_PACK_BUFFER_BINDING,&s->pbo); + glGetIntegerv(GL_PACK_ROW_LENGTH,&s->row); + glGetIntegerv(GL_PACK_SKIP_ROWS,&s->skip_rows); + glGetIntegerv(GL_PACK_SKIP_PIXELS,&s->skip_pixels); + glGetIntegerv(GL_VIEWPORT,s->viewport); glGetIntegerv(GL_SCISSOR_BOX,s->scissor); + s->scissor_on=glIsEnabled(GL_SCISSOR_TEST); + glGetIntegerv(GL_CURRENT_PROGRAM,&s->program); + glGetIntegerv(GL_ACTIVE_TEXTURE,&s->active); + glGetIntegerv(GL_TEXTURE_BINDING_2D,&s->texture); +} +static void g2_state_restore(const struct g2_state *s) { + glBindFramebuffer(GL_READ_FRAMEBUFFER,(GLuint)s->read_fbo); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER,(GLuint)s->draw_fbo); + glBindRenderbuffer(GL_RENDERBUFFER,(GLuint)s->rbo); + glBindBuffer(GL_PIXEL_PACK_BUFFER,(GLuint)s->pbo); + glPixelStorei(GL_PACK_ALIGNMENT,s->pack); + glPixelStorei(GL_PACK_ROW_LENGTH,s->row); + glPixelStorei(GL_PACK_SKIP_ROWS,s->skip_rows); + glPixelStorei(GL_PACK_SKIP_PIXELS,s->skip_pixels); +} +/* Called with the actual current native target. ES2 is explicitly unsupported: + * no ES3-only enum is ever queried before both EGL and GL attest ES3. + * Only native RBO color attachments are accepted. Texture attachments retain + * type/name/level, but safe dimensions on this pinned API are NOT_PROVEN. + * Error queue consumption and synchronous readback cannot be restored. */ +static struct g2_sample g2_read(GLuint fbo,int width,int height,size_t used) { + struct g2_sample s={.reason="NOT_PROVEN"}; bool saved=false, changed=false; + s.context=eglGetCurrentContext(); s.api=eglQueryAPI(); + if(s.context==EGL_NO_CONTEXT || s.api!=EGL_OPENGL_ES_API) {s.reason="UNKNOWN_CONTEXT"; return s;} + if(!eglQueryContext(eglGetCurrentDisplay(),s.context,EGL_CONTEXT_CLIENT_VERSION,&s.client_version)) { + s.reason="EGL_QUERY_FAILED"; return s; + } + s.pre=g2_drain(); if(!g2_clean(s.pre)) {s.reason="PREEXISTING_GL_ERRORS"; return s;} + const char *v=(const char *)glGetString(GL_VERSION); + if(v) snprintf(s.version,sizeof(s.version),"%s",v); + s.query=g2_drain(); if(!g2_clean(s.query)) {s.reason="VERSION_QUERY_ERROR"; return s;} + if(s.client_version<3 || !v || strncmp(v,"OpenGL ES 3.",12)!=0) { + s.reason="GLES_STATE_NOT_SUPPORTED"; return s; + } + if(!g2_size(width,height,used,&s.bytes)) {s.reason="RAW_BUDGET_OR_OVERFLOW"; return s;} + g2_state_get(&s.before); s.query=g2_drain(); + if(!g2_clean(s.query)) {s.reason="STATE_QUERY_ERROR"; return s;} + saved=true; changed=true; + glBindFramebuffer(GL_READ_FRAMEBUFFER,fbo); + glGetFramebufferAttachmentParameteriv(GL_READ_FRAMEBUFFER,GL_COLOR_ATTACHMENT0, + GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE,&s.object_type); + glGetFramebufferAttachmentParameteriv(GL_READ_FRAMEBUFFER,GL_COLOR_ATTACHMENT0, + GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME,&s.object); + if(s.object_type==GL_RENDERBUFFER && s.object>0) { + glBindRenderbuffer(GL_RENDERBUFFER,(GLuint)s.object); + glGetRenderbufferParameteriv(GL_RENDERBUFFER,GL_RENDERBUFFER_WIDTH,&s.width); + glGetRenderbufferParameteriv(GL_RENDERBUFFER,GL_RENDERBUFFER_HEIGHT,&s.height); + glGetRenderbufferParameteriv(GL_RENDERBUFFER,GL_RENDERBUFFER_INTERNAL_FORMAT,&s.format); + glGetRenderbufferParameteriv(GL_RENDERBUFFER,GL_RENDERBUFFER_SAMPLES,&s.samples); + const GLenum c[4]={GL_RENDERBUFFER_RED_SIZE,GL_RENDERBUFFER_GREEN_SIZE,GL_RENDERBUFFER_BLUE_SIZE,GL_RENDERBUFFER_ALPHA_SIZE}; + for(int i=0;i<4;i++) glGetRenderbufferParameteriv(GL_RENDERBUFFER,c[i],&s.channels[i]); + } else if(s.object_type==GL_TEXTURE) { + glGetFramebufferAttachmentParameteriv(GL_READ_FRAMEBUFFER,GL_COLOR_ATTACHMENT0, + GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LEVEL,&s.level); + } + glGetIntegerv(GL_READ_BUFFER,&s.read_buffer); + s.status=glCheckFramebufferStatus(GL_READ_FRAMEBUFFER); + s.query=g2_drain(); + if(!g2_clean(s.query)) {s.reason="ATTACHMENT_QUERY_ERROR"; goto done;} + if(s.object_type!=GL_RENDERBUFFER) {s.reason="TEXTURE_ATTACHMENT_DIMENSIONS_NOT_PROVEN"; goto done;} + if(s.read_buffer!=GL_COLOR_ATTACHMENT0 || s.samples!=0 || s.status!=GL_FRAMEBUFFER_COMPLETE || s.width!=width || s.height!=height || + s.channels[0]!=8 || s.channels[1]!=8 || s.channels[2]!=8 || (s.channels[3]!=0 && s.channels[3]!=8)) { + s.reason="ATTACHMENT_UNSUPPORTED"; goto done; + } + glBindBuffer(GL_PIXEL_PACK_BUFFER,0); glPixelStorei(GL_PACK_ALIGNMENT,1); + glPixelStorei(GL_PACK_ROW_LENGTH,0); glPixelStorei(GL_PACK_SKIP_ROWS,0); glPixelStorei(GL_PACK_SKIP_PIXELS,0); + s.query=g2_drain(); if(!g2_clean(s.query)) {s.reason="PACK_QUERY_ERROR"; goto done;} + s.pixels=malloc(s.bytes); if(!s.pixels) {s.reason="ALLOCATION_FAILED"; goto done;} + /* glReadPixels returns void. NO alpha sentinel or file hash success gate. */ + glReadPixels(0,0,width,height,GL_RGBA,GL_UNSIGNED_BYTE,s.pixels); + s.read=g2_drain(); + if(!g2_clean(s.read)) {s.reason="READ_GL_ERROR"; goto done;} + s.success=true; s.reason="READ_SUCCESS"; + done: + if(saved && changed) { + g2_state_restore(&s.before); g2_state_get(&s.after); s.restore=g2_drain(); + if(!g2_clean(s.restore) || memcmp(&s.before,&s.after,sizeof(s.before)) || eglGetCurrentContext()!=s.context) { + s.success=false; s.reason="RESTORE_FAILED"; + } + } + return s; +} +#endif --- /dev/null +++ b/render/fx_renderer/g2_native.h @@ -0,0 +1,220 @@ +/* PRIVATE G2. Included only by the new diagnostic fx_pass.c. */ +#ifndef G2_NATIVE_H +#define G2_NATIVE_H +#include "g2_gl.h" +#include +#include +#include +#include +#include +#include +#include +#include +#define G2_ROOT "/var/tmp/swayfx-mosaic-corec-v2-i516p6po/gpu-work/acquisition" +static struct { + bool stopped, active; unsigned epoch, point; size_t used; + int dir; FILE *meta; const void *pass, *output; + uintptr_t pass_sequence; char nonce[65]; +} g2={.dir=-1}; +static const char *g2_names[]={"source","down0","down1","down2","up2","up1","up0","optimized-cache","no-blur-cache","composition-presubmit"}; +static void g2_fail(const char *reason) { + g2.stopped=true; + if(g2.meta) {fprintf(g2.meta,"{\"kind\":\"rejected\",\"reason\":\"%s\",\"point\":%u}\n",reason,g2.point); fflush(g2.meta); fsync(fileno(g2.meta)); fclose(g2.meta); g2.meta=NULL;} + g2.active=false; +} +static uint32_t g2_crc(const unsigned char *p,size_t n) { + uint32_t crc=~0u; + for(size_t i=0;i>1)^((0u-(crc&1u))&0xedb88320u);} + return ~crc; +} +static bool g2_write(int fd,const void *ptr,size_t n) { + const char *p=ptr; while(n) {ssize_t k=write(fd,p,n); if(k<0 && errno==EINTR) continue; if(k<=0)return false; p+=k;n-=(size_t)k;} return true; +} +static void g2_error_json(const char *name,struct g2_errors e) { + fprintf(g2.meta,",\"%s\":{\"exhausted\":%s,\"codes\":[",name,e.exhausted?"true":"false"); + for(unsigned i=0;iread_fbo,s->draw_fbo,s->rbo,s->pack,s->pbo,s->row,s->skip_rows,s->skip_pixels,s->viewport[0],s->viewport[1],s->viewport[2],s->viewport[3],s->scissor[0],s->scissor[1],s->scissor[2],s->scissor[3],s->scissor_on,s->program,s->active,s->texture); +} +static void g2_region(const pixman_region32_t *region) { + int n=0; pixman_box32_t *r=region?pixman_region32_rectangles(region,&n):NULL; + fputs(",\"fresh_rectangles\":[",g2.meta); + if(n>256) {fputs("],\"fresh_region_overflow\":true",g2.meta);return;} + for(int i=0;ibuffer,&a))return false; + bool format=a.format==DRM_FORMAT_ABGR8888 || a.format==DRM_FORMAT_XBGR8888 || a.format==DRM_FORMAT_ARGB8888 || a.format==DRM_FORMAT_XRGB8888; + bool amd=a.modifier==UINT64_C(0x020000001046bb04); + if(!format || (a.modifier!=0 && !amd) || a.n_planes!=(amd?2:1) || a.width!=b->buffer->width || a.height!=b->buffer->height || a.width<=0 || a.height<=0)return false; + if((uint64_t)a.stride[0]<(uint64_t)a.width*4)return false; + for(int i=0;ibuffer,b->fbo,b->rbo,b->tex,b->buffer->width,b->buffer->height,(uintptr_t)b->image); + struct wlr_dmabuf_attributes a={0}; + if(!wlr_buffer_get_dmabuf(b->buffer,&a)) {fputs(",\"dmabuf\":null",g2.meta);return;} + /* Borrowed descriptors: NEVER finish(), close(), duplicate or seek these FDs. + * fstat exposes descriptor identity, not a trustworthy DCC allocation length. */ + fprintf(g2.meta,",\"dmabuf\":{\"format\":%u,\"modifier\":\"%016"PRIx64"\",\"dimensions\":[%d,%d],\"borrowed\":true,\"allocation_length\":\"NOT_PROVEN\",\"planes\":[",a.format,a.modifier,a.width,a.height); + if(a.n_planes<1 || a.n_planes>4) {fputs("]}",g2.meta);return;} + for(int i=0;i=0)return true; + const char *d=getenv("MOSAIC_G2_DIR"),*nonce=getenv("MOSAIC_G2_NONCE"); char real[PATH_MAX]; + if(!d || !nonce)return false; + if(strlen(nonce)!=64 || !realpath(d,real) || strcmp(real,G2_ROOT) || strcmp(d,G2_ROOT)) {g2.stopped=true;return false;} + for(unsigned i=0;i<64;i++)if(!((nonce[i]>='0'&&nonce[i]<='9')||(nonce[i]>='a'&&nonce[i]<='f'))) {g2.stopped=true;return false;} + g2.dir=open(d,O_RDONLY|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC); + if(g2.dir<0 || !g2_owned(g2.dir,0700)) {if(g2.dir>=0)close(g2.dir);g2.dir=-1;g2.stopped=true;return false;} + memcpy(g2.nonce,nonce,65);return true; +} +static void g2_begin(struct fx_gles_render_pass *pass,struct fx_framebuffer *source,struct fx_render_blur_pass_options *options); +static bool g2_expected_source(const struct g2_sample *s,unsigned epoch) { + if(s->width!=800 || s->height!=600 || !s->pixels)return false; + const unsigned char a[2][3]={{245,245,245},{15,25,45}}; + const unsigned char b[2][3]={{32,192,224},{176,32,64}}; + for(int y=0;y<600;y++)for(int x=0;x<800;x++) { + const unsigned char *expected=(epoch==2?b:a)[(x/8)%2]; + if(memcmp(s->pixels+((size_t)y*800+(size_t)x)*4,expected,3))return false; + } + return true; /* RGB source admission only; alpha is recorded, never sentinel. */ +} +static void g2_sample_native(struct fx_gles_render_pass *pass,struct fx_framebuffer *b,unsigned point,const pixman_region32_t *region) { + if(!g2.active || g2.pass!=pass)return; + if(point!=g2.point || point>=10) {g2_fail("STAGE_ORDER_OR_DUPLICATE");return;} + if(region && pixman_region32_n_rects(region)>256) {g2_fail("REGION_LIMIT");return;} + if(!g2_dmabuf_supported(b)) {g2_fail("DMABUF_METADATA_UNSUPPORTED");return;} + struct g2_sample s=g2_read(b->fbo,b->buffer->width,b->buffer->height,g2.used); + fprintf(g2.meta,"{\"kind\":\"sample\",\"epoch\":%u,\"pass_sequence\":%"PRIuPTR",\"point\":%u,\"stage\":\"%s\",\"context\":\"%"PRIxPTR"\",\"api\":%u,\"client_version\":%d,\"result\":\"%s\",\"bytes\":%zu,\"attachment\":[%d,%d,%d,%d,%d,%d],\"channels\":[%d,%d,%d,%d],\"status\":%u",g2.epoch,g2.pass_sequence,point,g2_names[point],(uintptr_t)s.context,s.api,s.client_version,s.reason,s.bytes,s.object_type,s.object,s.level,s.width,s.height,s.format,s.channels[0],s.channels[1],s.channels[2],s.channels[3],s.status); + fprintf(g2.meta,",\"read_buffer\":%d,\"samples\":%d",s.read_buffer,s.samples); + fputs(",\"gl_version_hex\":\"",g2.meta);for(size_t i=0;i=0 && g2_write(fd,s.pixels,s.bytes) && fsync(fd)==0; + if(fd>=0 && close(fd))ok=false; + fprintf(g2.meta,",\"crc32\":\"%08x\",\"file\":\"%s\",\"published\":%s}\n",g2_crc(s.pixels,s.bytes),name,ok?"true":"false"); + g2.used+=s.bytes; + if(!ok || fflush(g2.meta) || fsync(fileno(g2.meta))) {free(s.pixels);g2_fail("PUBLICATION_FAILED");return;} + g2.point++; + if(point==0 && !g2_expected_source(&s,g2.epoch)) {free(s.pixels);g2_fail("SOURCE_PATTERN_NOT_CURRENT_EPOCH");return;} + free(s.pixels); + if(point==9) { + fprintf(g2.meta,"{\"kind\":\"end\",\"epoch\":%u,\"points\":10,\"raw_total\":%zu,\"accepted\":true}\n",g2.epoch,g2.used); + bool final_ok=fflush(g2.meta)==0 && fsync(fileno(g2.meta))==0; + if(fclose(g2.meta)) {final_ok=false;} + g2.meta=NULL;g2.active=false; + if(!final_ok)g2.stopped=true; + if(g2.epoch==3)g2.stopped=true; + } +} +/* Explicit intended-output mapping; bounded eight outputs, no public ABI. */ +static struct {const void *pass;bool intended;} g2_outputs[8]; +static void g2_forget_output(struct fx_gles_render_pass *pass) { + for(unsigned i=0;i<8;i++)if(g2_outputs[i].pass==pass) { + g2_outputs[i].pass=NULL;g2_outputs[i].intended=false; + } +} +static bool g2_intended_output(struct fx_gles_render_pass *pass) { + for(unsigned i=0;i<8;i++)if(g2_outputs[i].pass==pass)return g2_outputs[i].intended; + return false; +} +static void g2_track_output(struct fx_gles_render_pass *pass,struct wlr_output *output) { + /* A native pass owns its mapping only until submit. Address reuse overwrites + * prior identity. Overflow never silently associates another output. */ + g2_forget_output(pass); + for(unsigned i=0;i<8;i++)if(!g2_outputs[i].pass) { + g2_outputs[i].pass=pass; + g2_outputs[i].intended=strcmp(output->name,"HEADLESS-1")==0 && output->scale==1.0f && output->transform==WL_OUTPUT_TRANSFORM_NORMAL; + return; + } + g2_fail("TOO_MANY_SIMULTANEOUS_NATIVE_PASSES"); +} +static void g2_begin(struct fx_gles_render_pass *pass,struct fx_framebuffer *source,struct fx_render_blur_pass_options *options) { + if(g2.stopped)return; + if(g2.active) {if(g2.pass==pass)g2_fail("MULTIPLE_OPTIMIZED_CANDIDATES");return;} + if(!g2_intended_output(pass) || !g2_open())return; + unsigned e=g2.epoch+1;if(e>3)return; + char arm[40],consumed[40],meta[40],expected[80],data[81]={0}; + snprintf(arm,sizeof(arm),"epoch-%u.arm",e);snprintf(consumed,sizeof(consumed),"epoch-%u.consumed",e); + snprintf(expected,sizeof(expected),"%s %u\n",g2.nonce,e); + /* WHY: O_NONBLOCK makes non-regular substitutions (especially a FIFO with + * no writer) fail closed without stalling the compositor callback. + * CONTRACT: validate identity, permissions, link count and exact bounded + * framing size before reading or consuming the arm token. */ + int fd=openat(g2.dir,arm,O_RDONLY|O_NONBLOCK|O_NOFOLLOW|O_CLOEXEC);if(fd<0) {if(errno!=ENOENT)g2.stopped=true;return;} + struct stat st; + bool ok=fstat(fd,&st)==0 && S_ISREG(st.st_mode) && st.st_uid==geteuid() && + (st.st_mode&0777)==0600 && st.st_nlink==1 && st.st_size==(off_t)strlen(expected); + if(!ok) {close(fd);g2.stopped=true;return;} + ssize_t n=read(fd,data,sizeof(data)-1); + if(n!=(ssize_t)strlen(expected) || memcmp(data,expected,(size_t)n)) {close(fd);g2.stopped=true;return;} + /* Consume BEFORE any query, readback or publication. Existing consumed token + * is fatal, even if a prior process died or first A was rejected. */ + struct stat linked; + if(linkat(g2.dir,arm,g2.dir,consumed,0) || fstatat(g2.dir,consumed,&linked,AT_SYMLINK_NOFOLLOW) || + !S_ISREG(linked.st_mode) || linked.st_dev!=st.st_dev || linked.st_ino!=st.st_ino || linked.st_nlink!=2 || + unlinkat(g2.dir,arm,0) || fsync(g2.dir)) {close(fd);g2.stopped=true;return;} + close(fd); + g2.epoch=e;g2.point=0;g2.pass=pass;g2.pass_sequence++;g2.active=true; + snprintf(meta,sizeof(meta),"epoch-%u.jsonl",e);fd=openat(g2.dir,meta,O_WRONLY|O_CREAT|O_EXCL|O_NOFOLLOW|O_CLOEXEC,0600); + if(fd<0 || !(g2.meta=fdopen(fd,"w"))) {if(fd>=0)close(fd);g2.stopped=true;g2.active=false;return;} + fprintf(g2.meta,"{\"kind\":\"begin\",\"schema\":\"G2-new-v1\",\"epoch\":%u,\"label\":\"%s\",\"nonce\":\"%s\",\"output\":\"HEADLESS-1\",\"pass\":\"%"PRIxPTR"\",\"offscreen_output_key\":\"%"PRIxPTR"\",\"intrusive_error_queue\":true,\"error_queue_restorable\":false,\"readback_may_synchronize\":true,\"rows\":\"GL_LOW_TO_HIGH\",\"present_buffer\":\"NOT_PROVEN\",\"gpu_completion\":\"NOT_PROVEN\",\"lifetime\":\"NOT_PROVEN\"}\n",e,e==1?"A":e==2?"B":"A-return",g2.nonce,(uintptr_t)pass,(uintptr_t)pass->fx_offscreen_buffers); + if(options->blur_data->num_passes!=3 || options->blur_strength!=1.0f || blur_data_should_parameters_blur_effects(options->blur_data)) {g2_fail("NON_PROSPECTIVE_BLUR_CONFIGURATION");return;} + g2_sample_native(pass,source,0,options->tex_options.base.clip); +} +/* Executed while the actual blur program and source sampler remain bound. */ +static void g2_uniform(struct fx_gles_render_pass *pass,struct blur_shader *shader,struct fx_texture *texture,const pixman_region32_t *region) { + if(!g2.active || g2.pass!=pass || g2.point<1 || g2.point>6)return; + if(region && pixman_region32_n_rects(region)>256) {g2_fail("REGION_LIMIT");return;} + struct g2_errors pre=g2_drain();if(!g2_clean(pre)) {fputs("{\"kind\":\"uniform-errors\"",g2.meta);g2_error_json("pre_errors",pre);fputs("}\n",g2.meta);g2_fail("PRE_UNIFORM_GL_ERROR");return;} + struct g2_state draw_state;GLboolean color_mask[4]={0}; + g2_state_get(&draw_state);glGetBooleanv(GL_COLOR_WRITEMASK,color_mask); + GLboolean blend=glIsEnabled(GL_BLEND),stencil=glIsEnabled(GL_STENCIL_TEST); + GLint program=0,min=0,mag=0,wraps=0,wrapt=0;GLfloat value[2]={0};GLint range[2]={0},precision=0; + glGetIntegerv(GL_CURRENT_PROGRAM,&program); + struct g2_errors initial=g2_drain(); + if(!g2_clean(initial)) {fputs("{\"kind\":\"uniform-errors\"",g2.meta);g2_error_json("query_errors",initial);fputs("}\n",g2.meta);g2_fail("UNIFORM_PROGRAM_QUERY_ERROR");return;} + if(program!=(GLint)shader->program || shader->halfpixel<0 || texture->target!=GL_TEXTURE_2D) {g2_fail("UNIFORM_PROGRAM_OR_TARGET");return;} + GLint actual=glGetUniformLocation((GLuint)program,"halfpixel"); + initial=g2_drain(); + if(!g2_clean(initial) || actual!=shader->halfpixel) {fputs("{\"kind\":\"uniform-errors\"",g2.meta);g2_error_json("query_errors",initial);fputs("}\n",g2.meta);g2_fail("UNIFORM_LOCATION");return;} + GLint count=0;glGetProgramiv((GLuint)program,GL_ACTIVE_UNIFORMS,&count);bool typed=false; + initial=g2_drain(); + if(!g2_clean(initial) || count<0 || count>128) {fputs("{\"kind\":\"uniform-errors\"",g2.meta);g2_error_json("query_errors",initial);fputs("}\n",g2.meta);g2_fail("UNIFORM_COUNT");return;} + for(GLuint i=0;i<(GLuint)count;i++) {char name[128];GLsizei len=0;GLint size=0;GLenum type=0;glGetActiveUniform((GLuint)program,i,sizeof(name),&len,&size,&type,name); + if(len==9 && !memcmp(name,"halfpixel",9) && size==1 && type==GL_FLOAT_VEC2)typed=true; + } + GLfloat radius=0,proj[9]={0},tex_proj[9]={0}; + glGetUniformfv((GLuint)program,shader->radius,&radius); + glGetUniformfv((GLuint)program,shader->proj,proj);glGetUniformfv((GLuint)program,shader->tex_proj,tex_proj); + glGetUniformfv((GLuint)program,actual,value); + glGetShaderPrecisionFormat(GL_FRAGMENT_SHADER,GL_MEDIUM_FLOAT,range,&precision); + glGetTexParameteriv(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,&min);glGetTexParameteriv(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,&mag); + glGetTexParameteriv(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,&wraps);glGetTexParameteriv(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,&wrapt); + struct g2_errors errors=g2_drain();if(!g2_clean(errors)||!typed) {fputs("{\"kind\":\"uniform-errors\"",g2.meta);g2_error_json("query_errors",errors);fputs("}\n",g2.meta);g2_fail("UNIFORM_QUERY_OR_TYPE");return;} + uint32_t bits[2];memcpy(bits,value,sizeof(bits)); + fprintf(g2.meta,"{\"kind\":\"uniform\",\"point\":%u,\"program\":%d,\"location\":%d,\"type\":%u,\"actual_bits\":[%u,%u],\"shader_default_float\":\"mediump\",\"varying\":\"mediump\",\"precision_capability\":[%d,%d,%d],\"operation_precision\":\"NOT_PROVEN\",\"sampler\":[%d,%d,%d,%d],\"source_texture\":%u,\"native_texture_dimensions\":[%u,%u],\"gl_level_dimensions\":\"NOT_PROVEN\"",g2.point,program,actual,GL_FLOAT_VEC2,bits[0],bits[1],range[0],range[1],precision,min,mag,wraps,wrapt,texture->tex,texture->wlr_texture.width,texture->wlr_texture.height); + fprintf(g2.meta,",\"qualifier_basis\":\"FROZEN_LINK_INPUT_SOURCE\",\"shader_source_sha256\":\"%s\"",g2.point<=3?"01664694e4201efb35ec698e89783e220ed930972b5396b86fa842814cd82d4d":"1db325237c9a6c70c2d97518e2f690d448cd006139e9f410cd16e097a1677ad1"); + uint32_t radius_bits,proj_bits[9],tex_proj_bits[9];memcpy(&radius_bits,&radius,4);memcpy(proj_bits,proj,sizeof(proj));memcpy(tex_proj_bits,tex_proj,sizeof(tex_proj)); + fprintf(g2.meta,",\"other_uniforms\":{\"radius_location\":%d,\"radius_bits\":%u,\"proj_location\":%d,\"proj_bits\":[",shader->radius,radius_bits,shader->proj); + for(unsigned i=0;i<9;i++)fprintf(g2.meta,"%s%u",i?",":"",proj_bits[i]); + fprintf(g2.meta,"],\"tex_proj_location\":%d,\"tex_proj_bits\":[",shader->tex_proj); + for(unsigned i=0;i<9;i++)fprintf(g2.meta,"%s%u",i?",":"",tex_proj_bits[i]); + fputs("]}",g2.meta);g2_state_json("draw_state",&draw_state); + fprintf(g2.meta,",\"blend_stencil\":[%u,%u],\"color_mask\":[%u,%u,%u,%u]",blend,stencil,color_mask[0],color_mask[1],color_mask[2],color_mask[3]); + g2_region(region);fputs("}\n",g2.meta); +} +#endif