/* * Copyright (c) 2026 The NetBSD Foundation, Inc. * All rights reserved. * * This code is derived from software contributed to The NetBSD Foundation * by Radoslaw Kujawa. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ /* * veritemesa hardware texture stage */ #include "vrdrv.h" #include "context.h" #include "imports.h" #include "texstore.h" #include "texobj.h" #include "swrast/swrast.h" #define VR_MAX_LEVELS 9 /* 256x256 .. 1x1 */ struct vr_texture { /* hw[0].addr == 0 -> not resident; mip chain in hw[1..] */ struct rlgl_tex hw[VR_MAX_LEVELS]; GLint nlevels; /* resident levels (1 = no mips) */ GLboolean has_alpha; /* uploaded as 4444, else 565 */ GLboolean dirty; /* core image newer than VRAM */ GLboolean capped; /* nlevels is all that fit VRAM; * do not retry for more (anti-thrash) */ GLuint lru; /* last validate tick */ GLuint used_frame; /* frame last bound (evict pinning) */ struct gl_texture_object *obj; struct vr_texture *next; }; /* * Frame counter for evict-pinning */ static GLuint g_vr_frame = 1; void vrTexNewFrame(void) { g_vr_frame++; } /* vrtris.c reads the level array for per-triangle mip binds */ struct rlgl_tex * vrTexLevel(struct vr_texture *t, GLint level) { return &t->hw[level]; } GLint vrTexNumLevels(struct vr_texture *t) { return t->nlevels; } void vrInitTexConsts(GLcontext *ctx) { ctx->Const.MaxTextureUnits = 1; ctx->Const.MaxTextureImageUnits = 1; ctx->Const.MaxTextureCoordUnits = 1; /* 2^(9-1) = 256: mesaconf tex-256 scene proves the PE takes it */ ctx->Const.MaxTextureLevels = 9; } static struct vr_texture * vr_tex_data(GLcontext *ctx, struct gl_texture_object *tObj) { vrmesa_context *vmesa = VRMESA_CONTEXT(ctx); struct vr_texture *t = tObj->DriverData; if (t == NULL) { t = CALLOC_STRUCT(vr_texture); if (t == NULL) return NULL; t->obj = tObj; t->dirty = GL_TRUE; t->next = vmesa->textures; vmesa->textures = t; tObj->DriverData = t; VRP_COUNT(tv_creates); } return t; } static void vr_tex_unload(vrmesa_context *vmesa, struct vr_texture *t) { GLint i; /* * The mip pyramid is ONE contiguous allocation */ if (t->hw[0].addr != 0) rlgl_tex_free(&vmesa->rl, &t->hw[0]); for (i = 1; i < VR_MAX_LEVELS; i++) t->hw[i].addr = 0; t->nlevels = 0; /* addresses may be recycled: kill the bind cache */ vmesa->rl.cur_tex = 0; } static void vr_teximage2d(GLcontext *ctx, GLenum target, GLint level, GLint internalFormat, GLint width, GLint height, GLint border, GLenum format, GLenum type, const GLvoid *pixels, const struct gl_pixelstore_attrib *packing, struct gl_texture_object *texObj, struct gl_texture_image *texImage) { struct vr_texture *t; _mesa_store_teximage2d(ctx, target, level, internalFormat, width, height, border, format, type, pixels, packing, texObj, texImage); t = vr_tex_data(ctx, texObj); if (t != NULL) t->dirty = GL_TRUE; } static void vr_texsubimage2d(GLcontext *ctx, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *pixels, const struct gl_pixelstore_attrib *packing, struct gl_texture_object *texObj, struct gl_texture_image *texImage) { struct vr_texture *t; _mesa_store_texsubimage2d(ctx, target, level, xoffset, yoffset, width, height, format, type, pixels, packing, texObj, texImage); t = vr_tex_data(ctx, texObj); if (t != NULL) t->dirty = GL_TRUE; /* v1: full re-upload */ } static void vr_delete_texture(GLcontext *ctx, struct gl_texture_object *tObj) { vrmesa_context *vmesa = VRMESA_CONTEXT(ctx); struct vr_texture *t = tObj->DriverData; if (t != NULL) { struct vr_texture **pp; vr_tex_unload(vmesa, t); for (pp = &vmesa->textures; *pp != NULL; pp = &(*pp)->next) if (*pp == t) { *pp = t->next; break; } _mesa_free(t); tObj->DriverData = NULL; } _mesa_delete_texture_object(ctx, tObj); } /* * Hardware glCopyTexSubImage2D. */ static void vr_copytexsubimage2d(GLcontext *ctx, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height) { vrmesa_context *vmesa = VRMESA_CONTEXT(ctx); struct rlgl *rl = &vmesa->rl; struct gl_texture_object *tObj = ctx->Texture.Unit[0].Current2D; GLint fbW = (GLint)rl->v.width, fbH = (GLint)rl->v.height; struct vr_texture *t = (tObj != NULL) ? tObj->DriverData : NULL; GLboolean was_dirty = (t != NULL) ? t->dirty : GL_TRUE; /* * Keep the canonical core image correct in every case. */ _swrast_copy_texsubimage2d(ctx, target, level, xoffset, yoffset, x, y, width, height); if (t == NULL) return; if (target != GL_TEXTURE_2D || level != tObj->BaseLevel || t->hw[0].addr == 0 || t->has_alpha || t->nlevels != 1 || was_dirty || ctx->_ImageTransferState != 0 || width <= 0 || height <= 0 || x < 0 || y < 0 || x + width > fbW || y + height > fbH) { t->dirty = GL_TRUE; /* re-upload from the updated core */ return; } if (rlgl_copy_to_tex(rl, vr_fb_base(rl, ctx->Pixel.ReadBuffer), fbH, t->hw[0].addr, t->hw[0].w, x, y, xoffset, yoffset, width, height) != 0) { t->dirty = GL_TRUE; return; } t->dirty = GL_FALSE; /* VRAM matches the core copy */ vmesa->engine_dirty = GL_TRUE; } void vrInitTexFuncs(struct dd_function_table *functions) { functions->TexImage2D = vr_teximage2d; functions->TexSubImage2D = vr_texsubimage2d; functions->CopyTexSubImage2D = vr_copytexsubimage2d; functions->DeleteTexture = vr_delete_texture; } /* * Can the PE render the CURRENT texture binding? */ GLboolean vrTexHwOk(GLcontext *ctx) { const struct gl_texture_unit *unit = &ctx->Texture.Unit[0]; const struct gl_texture_object *tObj; const struct gl_texture_image *img; if (ctx->Texture._EnabledUnits != 0x1) return GL_FALSE; if (unit->_ReallyEnabled != TEXTURE_2D_BIT) return GL_FALSE; tObj = unit->Current2D; if (tObj == NULL) return GL_FALSE; img = tObj->Image[0][tObj->BaseLevel]; if (img == NULL || img->Border != 0 || img->Width > 256 || img->Height > 256) return GL_FALSE; if (tObj->WrapS != GL_REPEAT && tObj->WrapS != GL_CLAMP_TO_EDGE && tObj->WrapS != GL_CLAMP) return GL_FALSE; if (tObj->WrapT != GL_REPEAT && tObj->WrapT != GL_CLAMP_TO_EDGE && tObj->WrapT != GL_CLAMP) return GL_FALSE; /* * GL_CLAMP equals CLAMP_TO_EDGE under NEAREST sampling, but * a LINEAR footprint mixes the border color at the edge */ if (tObj->WrapS == GL_CLAMP || tObj->WrapT == GL_CLAMP) { if (tObj->MagFilter == GL_LINEAR || tObj->MinFilter == GL_LINEAR || tObj->MinFilter == GL_LINEAR_MIPMAP_NEAREST || tObj->MinFilter == GL_LINEAR_MIPMAP_LINEAR) return GL_FALSE; } /* all six MIN filters: mipmap ones ride per-poly selection */ if (tObj->MagFilter != GL_NEAREST && tObj->MagFilter != GL_LINEAR) return GL_FALSE; if (unit->EnvMode != GL_MODULATE && unit->EnvMode != GL_REPLACE && unit->EnvMode != GL_DECAL) return GL_FALSE; return GL_TRUE; } static GLuint vr_alloc_width(GLuint w) { if (w < 2) return 2; if (w == 4) return 8; return w; } static int vr_tex_upload(vrmesa_context *vmesa, struct vr_texture *t, const struct gl_texture_image *img, GLint level) { GLuint w = img->Width, h = img->Height; GLuint aw = vr_alloc_width(w); GLushort *buf; GLuint x, y; int error; if (img->FetchTexelc == NULL) return -1; buf = _mesa_malloc(aw * h * sizeof(GLushort)); if (buf == NULL) return -1; for (y = 0; y < h; y++) for (x = 0; x < aw; x++) { GLchan texel[4]; img->FetchTexelc((struct gl_texture_image *)img, x < w ? x : w - 1, y, 0, texel); buf[y * aw + x] = t->has_alpha ? (GLushort) (((texel[3] & 0xf0) << 8) | ((texel[0] & 0xf0) << 4) | (texel[1] & 0xf0) | (texel[2] >> 4)) : (GLushort) (((texel[0] & 0xf8) << 8) | ((texel[1] & 0xfc) << 3) | (texel[2] >> 3)); } error = rlgl_tex_upload(&vmesa->rl, &t->hw[level], buf); _mesa_free(buf); return error; } static GLboolean vr_min_is_mip(GLenum f) { return f != GL_NEAREST && f != GL_LINEAR; } static GLboolean vr_base_has_alpha(const struct gl_texture_image *img) { switch (img->TexFormat->BaseFormat) { case GL_RGBA: case GL_ALPHA: case GL_LUMINANCE_ALPHA: case GL_INTENSITY: return GL_TRUE; default: return GL_FALSE; } } /* * Make the current binding resident and bound */ GLboolean vrTexValidate(GLcontext *ctx) { vrmesa_context *vmesa = VRMESA_CONTEXT(ctx); struct gl_texture_object *tObj = ctx->Texture.Unit[0].Current2D; const struct gl_texture_image *img = tObj->Image[0][tObj->BaseLevel]; struct vr_texture *t = vr_tex_data(ctx, tObj); GLint want, i; GLboolean was_resident; VRP_ZE(VRP_Z_TEXVALIDATE); if (t == NULL) { VRP_COUNT(tv_failv); VRP_ZX(VRP_Z_TEXVALIDATE); return GL_FALSE; } t->lru = ++vmesa->lru_tick; t->used_frame = g_vr_frame; /* pin: not evictable this frame */ was_resident = (t->hw[0].addr != 0); /* levels wanted: full chain for mipmap MIN filters */ if (vr_min_is_mip(tObj->MinFilter)) { want = 0; while (want < VR_MAX_LEVELS - 1 && tObj->Image[0][tObj->BaseLevel + want] != NULL && (tObj->Image[0][tObj->BaseLevel + want]->Width > 1 || tObj->Image[0][tObj->BaseLevel + want]->Height > 1)) want++; want++; /* include the 1x1 (or last) level */ } else want = 1; if (t->hw[0].addr != 0 && (t->dirty || (t->nlevels < want && !t->capped))) { if (t->dirty) VRP_COUNT(tv_dirty); else VRP_COUNT(tv_nlevels); vr_tex_unload(vmesa, t); } if (t->hw[0].addr == 0) { int nl; if (!was_resident) VRP_COUNT(tv_first); t->has_alpha = vr_base_has_alpha(img); /* * Allocate the whole pyramid as ONE contiguous block */ for (nl = want; nl >= 1; nl--) { for (i = 0; i < nl; i++) { const struct gl_texture_image *li = tObj->Image[0][tObj->BaseLevel + i]; if (li == NULL || rlgl_tex_init(&t->hw[i], vr_alloc_width(li->Width), li->Height, t->has_alpha ? RLGL_FMT_4444 : RLGL_FMT_565) != 0) break; } if (i < nl) /* a level was missing/bad */ continue; while (rlgl_tex_pyramid(&vmesa->rl, t->hw, nl) != 0) { struct vr_texture *victim = NULL, *it; for (it = vmesa->textures; it != NULL; it = it->next) if (it != t && it->hw[0].addr != 0 && it->used_frame != g_vr_frame && (victim == NULL || it->lru < victim->lru)) victim = it; if (victim == NULL) break; /* try fewer levels */ vr_tex_unload(vmesa, victim); VRP_COUNT(tv_evict); } if (t->hw[0].addr != 0) break; /* nl levels allocated */ } if (t->hw[0].addr == 0) { VRP_COUNT(tv_failv); VRP_ZX(VRP_Z_TEXVALIDATE); return GL_FALSE; /* not even the base fit */ } for (i = 0; i < nl; i++) { const struct gl_texture_image *li = tObj->Image[0][tObj->BaseLevel + i]; if (vr_tex_upload(vmesa, t, li, i) != 0) { vr_tex_unload(vmesa, t); VRP_COUNT(tv_failu); VRP_ZX(VRP_Z_TEXVALIDATE); return GL_FALSE; } } t->nlevels = nl; t->capped = (nl < want); t->dirty = GL_FALSE; vmesa->engine_dirty = GL_TRUE; } else { VRP_COUNT(tv_bindonly); /* resident, no upload */ } rlgl_tex_env_code(&vmesa->rl, ctx->Texture.Unit[0].EnvMode == GL_MODULATE ? RLGL_ENV_MODULATE : (ctx->Texture.Unit[0].EnvMode == GL_DECAL ? RLGL_ENV_DECAL : RLGL_ENV_REPLACE)); rlgl_tex_filter(&vmesa->rl, tObj->MagFilter == GL_LINEAR); rlgl_tex_bind(&vmesa->rl, &t->hw[0]); rlgl_tex_wrap(&vmesa->rl, tObj->WrapS != GL_REPEAT, tObj->WrapT != GL_REPEAT); if (_mesa_getenv("VRMESA_DEBUG")) _mesa_printf("vrTexValidate: obj=%u nlvl=%d addr0=%x " "min=%x\n", tObj->Name, t->nlevels, t->hw[0].addr, tObj->MinFilter); vmesa->tex_cur = t; vmesa->tex_mip = vr_min_is_mip(tObj->MinFilter) && t->nlevels > 1; vmesa->tex_min_bilin = (tObj->MinFilter == GL_LINEAR_MIPMAP_NEAREST || tObj->MinFilter == GL_LINEAR_MIPMAP_LINEAR || tObj->MinFilter == GL_LINEAR); vmesa->tex_mag_bilin = (tObj->MagFilter == GL_LINEAR); vmesa->cur_level = 0; vmesa->cur_bilin = vmesa->tex_mag_bilin; vmesa->tex_w = vmesa->tex_base_w = (GLfloat)img->Width; vmesa->tex_h = vmesa->tex_base_h = (GLfloat)img->Height; vmesa->tex_off = vmesa->tex_mag_bilin ? 0.5F : 0.0F; VRP_ZX(VRP_Z_TEXVALIDATE); return GL_TRUE; }