/* * 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. */ /* libv3d - thin userland layer over /dev/verite3d. */ #include #include #include #include #include #include #include #include #include #include "libv3d.h" uint64_t v3d_now_ns(void) { struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); return (uint64_t)ts.tv_sec * 1000000000ULL + (uint64_t)ts.tv_nsec; } /* 0 when timing is off: the accumulators just stay zero */ static uint64_t v3d_t0(const struct v3d *v) { return v->opt_stats ? v3d_now_ns() : 0; } int v3d_open(struct v3d *v, const char *devpath, const char *ucpath) { struct v3d_init vi; struct v3d_mode vm; static uint8_t uc[1024 * 1024]; FILE *f; size_t sz; uint32_t w; int s; memset(v, 0, sizeof(*v)); if (getenv("RLGL_STATS") != NULL) v->opt_stats = atoi(getenv("RLGL_STATS")); v->opt_nulldraw = getenv("RLGL_NULLDRAW") != NULL; v->opt_nosync = getenv("RLGL_NOSYNC") != NULL; v->opt_noflip = getenv("RLGL_NOFLIP") != NULL; v->opt_tearflip = getenv("RLGL_TEARFLIP") != NULL; if (v->opt_nulldraw || v->opt_nosync || v->opt_noflip || v->opt_tearflip) fprintf(stderr, "libv3d: EXPERIMENT MODE nulldraw=%d " "nosync=%d noflip=%d tearflip=%d\n", v->opt_nulldraw, v->opt_nosync, v->opt_noflip, v->opt_tearflip); v->fd = open(devpath, O_RDWR); if (v->fd == -1) { perror(devpath); return -1; } f = fopen(ucpath, "rb"); if (f == NULL) { perror(ucpath); goto fail; } sz = fread(uc, 1, sizeof(uc), f); fclose(f); vi.vi_ucode = (uint64_t)(uintptr_t)uc; vi.vi_size = (uint32_t)sz; if (ioctl(v->fd, V3D_INIT, &vi) == -1) { perror("V3D_INIT"); goto fail; } v->ctx_base = vi.vi_ctx_base; v->memsize = vi.vi_memsize; v->pool_base = vi.vi_pool_base; for (s = 0; s < V3D_RING_SLOTS; s++) { v->slot[s] = mmap(NULL, V3D_SLOT_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, v->fd, (off_t)s * V3D_SLOT_SIZE); if (v->slot[s] == MAP_FAILED) { perror("mmap slot"); goto fail; } } /* GL context up; fence it */ usleep(10000); v3d_emit1(v, V3D_GL_CTX_INIT); if (v3d_sync(v) != 0) goto fail; /* mode query fills geometry */ memset(&vm, 0, sizeof(vm)); if (ioctl(v->fd, V3D_MODE, &vm) == -1) goto fail; v->width = vm.vm_width; v->height = vm.vm_height; v->stride = vm.vm_stride; v->pe_stride = vm.vm_pe_stride; (void)w; return 0; fail: close(v->fd); v->fd = -1; return -1; } void v3d_close(struct v3d *v) { if (v->fd == -1) return; /* park politely */ v3d_emit1(v, V3D_GL_EXIT); (void)v3d_flush(v); if (v->vram != NULL) { munmap(v->vram, v->memsize); v->vram = NULL; } close(v->fd); v->fd = -1; } const uint8_t * v3d_vram(struct v3d *v) { if (v->vram == NULL) { /* RW for span writes (Mesa swrast), RO fallback */ v->vram = mmap(NULL, v->memsize, PROT_READ | PROT_WRITE, MAP_SHARED, v->fd, V3D_VRAM_MMAP_OFF); if (v->vram == MAP_FAILED) v->vram = mmap(NULL, v->memsize, PROT_READ, MAP_SHARED, v->fd, V3D_VRAM_MMAP_OFF); if (v->vram == MAP_FAILED) { perror("mmap vram"); v->vram = NULL; } } return v->vram; } void v3d_emit(struct v3d *v, const uint32_t *w, uint32_t n) { if (v->n + n > V3D_SLOT_SIZE / 4) { if (v3d_flush(v) != 0) return; } memcpy(v->slot[v->cur] + v->n, w, n * 4); v->n += n; v->st.words += n; } void v3d_emit1(struct v3d *v, uint32_t w) { v3d_emit(v, &w, 1); } uint32_t * v3d_reserve(struct v3d *v, uint32_t nwords) { if (nwords > V3D_SLOT_SIZE / 4) return NULL; if (v->n + nwords > V3D_SLOT_SIZE / 4) { if (v3d_flush(v) != 0) return NULL; } return v->slot[v->cur] + v->n; } void v3d_commit(struct v3d *v, uint32_t nwords) { v->n += nwords; v->st.words += nwords; } void v3d_emitf(struct v3d *v, float f) { union { float f; uint32_t u; } u; u.f = f; v3d_emit1(v, u.u); } int v3d_flush(struct v3d *v) { struct v3d_submit vs; uint64_t t0; if (v->n == 0) return 0; vs.vs_slot = (uint32_t)v->cur; vs.vs_len = v->n * 4; vs.vs_swap = 1; /* XXX: host-native words */ v->n = 0; v->cur = (v->cur + 1) % V3D_RING_SLOTS; v->st.submits++; v->st.submit_bytes += vs.vs_len; if (v->opt_nulldraw) return 0; t0 = v3d_t0(v); if (ioctl(v->fd, V3D_SUBMIT, &vs) == -1) { perror("V3D_SUBMIT"); return -1; } if (t0 != 0) v->st.submit_ns += v3d_now_ns() - t0; return 0; } int v3d_sync(struct v3d *v) { uint32_t word = V3D_GL_SYNC; uint64_t t0; if (v3d_flush(v) != 0) return -1; v->st.syncs++; if (v->opt_nulldraw) return 0; t0 = v3d_t0(v); if (ioctl(v->fd, V3D_SYNC, &word) == -1) { perror("V3D_SYNC"); return -1; } if (t0 != 0) v->st.sync_ns += v3d_now_ns() - t0; return 0; } int v3d_alloc(struct v3d *v, uint32_t size, uint32_t align, uint32_t *addr) { struct v3d_alloc va; va.va_size = size; va.va_align = align; if (ioctl(v->fd, V3D_ALLOC, &va) == -1) return -1; *addr = va.va_addr; return 0; } int v3d_mode(struct v3d *v, uint32_t depth, uint32_t frame_base) { struct v3d_mode vm; memset(&vm, 0, sizeof(vm)); vm.vm_depth = depth; vm.vm_frame_base = frame_base; if (ioctl(v->fd, V3D_MODE, &vm) == -1) return -1; v->width = vm.vm_width; v->height = vm.vm_height; v->stride = vm.vm_stride; v->pe_stride = vm.vm_pe_stride; return 0; } int v3d_flip(struct v3d *v, uint32_t frame_base) { uint64_t t0; int r; if (v3d_flush(v) != 0) return -1; v->st.flips++; if (v->opt_nulldraw || v->opt_noflip) return 0; if (v->opt_tearflip) frame_base |= V3D_FLIP_NOWAIT; t0 = v3d_t0(v); r = ioctl(v->fd, V3D_FLIP, &frame_base); if (t0 != 0) v->st.flip_ns += v3d_now_ns() - t0; return r; } void v3d_st(struct v3d *v, uint32_t reg, uint32_t val) { uint32_t w[2]; w[0] = (1 << 12) | reg; w[1] = val; v->st.st_calls++; v3d_emit(v, w, 2); } void v3d_texbind(struct v3d *v, uint32_t addr, uint32_t srcstride, uint32_t w, uint32_t h, uint32_t uscale, uint32_t vscale) { uint32_t c[6]; c[0] = (4 << 12) | V3D_PE_SRCBASE; c[1] = addr; c[2] = srcstride; c[3] = ((h - 1) << 16) | (w - 1); c[4] = uscale; c[5] = vscale; v->st.texbinds++; v3d_emit(v, c, 6); } /* below this many payload bytes of slot room, flush for a fresh slot */ #define V3D_UPLOAD_MIN 4096 int v3d_upload(struct v3d *v, uint32_t dst, const void *data, uint32_t bytes) { const uint32_t *p = data; uint32_t chunk, room, hdr[3]; v->st.uploads++; v->st.upload_bytes += bytes; while (bytes > 0) { /* * Batch into the current slot alongside whatever is * already queued */ room = (V3D_SLOT_SIZE / 4 - v->n) * 4; if (room < V3D_UPLOAD_MIN + 3 * 4) { if (v3d_flush(v) != 0) return -1; room = V3D_SLOT_SIZE; } chunk = bytes; if (chunk > room - 3 * 4) chunk = room - 3 * 4; hdr[0] = V3D_GL_MEM_WRITE; hdr[1] = dst; hdr[2] = chunk; v3d_emit(v, hdr, 3); v3d_emit(v, p, chunk / 4); p += chunk / 4; dst += chunk; bytes -= chunk; } return 0; } void v3d_clear(struct v3d *v, uint32_t addr, uint32_t stride, uint32_t wbytes, uint32_t h, uint32_t val) { uint32_t c[6]; c[0] = V3D_GL_CLEAR_RECT; c[1] = addr; c[2] = stride; c[3] = wbytes; c[4] = h; c[5] = val; v3d_emit(v, c, 6); }