Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
d8e9eaf
Add bounded GPU memory, packet, context and blit services
cavenderbi Sep 11, 2026
822aa78
Integrate bounded GPU command processing
cavenderbi Sep 11, 2026
7314dba
Add A2xx shader execution and texture sampling
cavenderbi Sep 11, 2026
673c492
Add triangle rasterization and logical GMEM
cavenderbi Sep 11, 2026
ed841d3
Connect indexed GPU draws to shaders and GMEM
cavenderbi Sep 11, 2026
15524a7
Support tiled GPU resources, depth and blend state
cavenderbi Sep 11, 2026
6632e0d
Clip GPU triangles and execute predicate equality
cavenderbi Sep 11, 2026
c4ed6aa
Complete Navigation shader, depth and texture draws
cavenderbi Sep 11, 2026
3d8ce9c
Support non-face BIN helpers and triangle-strip visibility
cavenderbi Sep 11, 2026
59244bd
Validate A2xx instruction extents, opcodes and jumps
cavenderbi Sep 11, 2026
adebc46
Support integer and half-float vertex fetch formats
cavenderbi Sep 11, 2026
fb4631c
Support mip filtering, cube coordinates and register LOD
cavenderbi Sep 11, 2026
f346b0f
Execute conditional clauses and calls with correct termination
cavenderbi Sep 11, 2026
edc4d5f
Decode A2xx scalar constant arithmetic operands
cavenderbi Sep 11, 2026
a656944
Execute bounded nested A2xx shader loops
cavenderbi Sep 11, 2026
4a3ba86
Sample Mesa linear cube base faces
cavenderbi Sep 11, 2026
6208b82
Execute pixel shader quads for post-ALU derivatives
cavenderbi Sep 11, 2026
06c902f
Set and query explicit texture gradients
cavenderbi Sep 11, 2026
0185135
Implement MOVA and relative shader operands
cavenderbi Sep 11, 2026
085d1a4
Query unbiased texture LOD, filter weights and border contribution
cavenderbi Sep 11, 2026
750b228
Apply scalar predicate priority and legacy multiplication semantics
cavenderbi Sep 11, 2026
1c60875
Merge current upstream main for GPU3D PR integration
cavenderbi Sep 13, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 24 additions & 8 deletions cerf/cpu/emulated_memory.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,11 @@ void EmulatedMemory::AddRegion(uint32_t base, uint32_t size,
std::lock_guard<std::mutex> lk(add_mutex_);

const uint32_t span = decode_span ? decode_span : size;
if (size == 0 || span < size || span % size != 0 ||
if (size == 0 || span < size || uint64_t(base) + span > (uint64_t{1} << 32) ||
span % size != 0 ||
(span != size && (size & (size - 1u)) != 0)) {
LOG(Caution, "EmulatedMemory::AddRegion bad decode span: base=0x%08X "
"size=0x%X span=0x%X (span must be a multiple of a "
"power-of-two size)\n", base, size, span);
LOG(Caution, "EmulatedMemory::AddRegion invalid region: base=0x%08X "
"size=0x%X span=0x%X\n", base, size, span);
CerfFatalExit(CERF_FATAL_RUNTIME_ERROR);
}
const uint32_t wrap_mask = (span == size) ? 0xFFFFFFFFu : (size - 1u);
Expand All @@ -38,10 +38,12 @@ void EmulatedMemory::AddRegion(uint32_t base, uint32_t size,

for (size_t i = 0; i < n; ++i) {
const Region& r = regions_[i];
if (base < r.base + r.span && r.base < base + span) {
if (uint64_t(base) < uint64_t(r.base) + r.span &&
uint64_t(r.base) < uint64_t(base) + span) {
LOG(Caution, "EmulatedMemory::AddRegion overlap: new "
"[0x%08X..0x%08X) vs existing [0x%08X..0x%08X)\n",
base, base + span, r.base, r.base + r.span);
"[0x%08X..0x%llX) vs existing [0x%08X..0x%llX)\n",
base, static_cast<unsigned long long>(base) + span,
r.base, static_cast<unsigned long long>(r.base) + r.span);
CerfFatalExit(CERF_FATAL_RUNTIME_ERROR);
}
}
Expand Down Expand Up @@ -70,7 +72,7 @@ EmulatedMemory::Region* EmulatedMemory::FindRegion(uint32_t vaddr) {
const size_t n = count_.load(std::memory_order_acquire);
for (size_t i = 0; i < n; ++i) {
Region& r = regions_[i];
if (vaddr >= r.base && vaddr < r.base + r.span) {
if (vaddr >= r.base && vaddr - r.base < r.span) {
return &r;
}
}
Expand Down Expand Up @@ -139,6 +141,20 @@ uint8_t* EmulatedMemory::TryTranslateWrite(uint32_t paddr) {
return EnsureBacked(r) + ((paddr - r->base) & r->wrap_mask);
}

uint8_t* EmulatedMemory::TryTranslateRange(uint64_t paddr, uint64_t size, bool write) {
if (size == 0 || paddr > UINT32_MAX || size > (uint64_t{1} << 32) - paddr)
return nullptr;
Region* r = FindRegion(static_cast<uint32_t>(paddr));
if (!r || (write && (r->page_protect == PAGE_READONLY ||
r->page_protect == PAGE_EXECUTE_READ)))
return nullptr;
const uint64_t offset = paddr - r->base;
const uint64_t backed_offset = offset & r->wrap_mask;
if (size > r->span - offset || size > r->size - backed_offset)
return nullptr;
return EnsureBacked(r) + static_cast<size_t>(backed_offset);
}

bool EmulatedMemory::IsSlotRangeUniform(uint32_t span_bytes, uint32_t pa) {
if (span_bytes <= 0x1000u) return true;
const uint32_t base = pa & ~(span_bytes - 1u);
Expand Down
2 changes: 2 additions & 0 deletions cerf/cpu/emulated_memory.h
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ class EmulatedMemory : public Service {
PAGE_EXECUTE_READ regions so writes dispatch as MMIO. */
uint8_t* TryTranslateWrite(uint32_t paddr);

uint8_t* TryTranslateRange(uint64_t paddr, uint64_t size, bool write = false);

bool IsSlotRangeUniform(uint32_t span_bytes, uint32_t pa);

uint8_t ReadByte(uint32_t vaddr);
Expand Down
471 changes: 250 additions & 221 deletions cerf/socs/imx51/imx51_gpu3d.cpp

Large diffs are not rendered by default.

123 changes: 123 additions & 0 deletions cerf/socs/imx51/imx51_gpu3d_blit.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
#include "imx51_gpu3d_blit.h"
#include "imx51_gpu3d_memory.h"
#include "imx51_pixel_pack.h"
#include "../../core/cerf_emulator.h"
#include "../../core/fatal.h"
#include "../../boards/board_context.h"
#include <cstring>

REGISTER_SERVICE(Imx51Gpu3dBlit);

bool Imx51Gpu3dBlit::ShouldRegister() {
auto* board = emu_.TryGet<BoardContext>();
return board && board->GetSoc() == SocFamily::iMX51;
}

void Imx51Gpu3dBlit::HaltUnsupportedAccess(const char* op, uint32_t address, uint64_t value) const {
emu_.Get<Fatal>().Die("GPU blit rejected %s at 0x%08X (value 0x%016llX)",
op, address, static_cast<unsigned long long>(value));
}

uint32_t Imx51Gpu3dBlit::BlitReg(const std::unordered_map<uint32_t, uint32_t>& registers, uint32_t idx, uint64_t pa) {
auto it = registers.find(idx);
if (it == registers.end())
HaltUnsupportedAccess("blit config register not programmed", static_cast<uint32_t>(pa), idx);
return it->second;
}
float Imx51Gpu3dBlit::AsFloat(uint32_t u) { float f; std::memcpy(&f, &u, sizeof(f)); return f; }

/* sync_2 EA5T-14D544-BA.sec, lib2d-z430.dll: 0x41A63F00;
Mesa e97ad748 a2xx.xml: A2XX_SQ_TEX, RB_COLOR_INFO, RB_SURFACE_INFO,
RB_COLORCONTROL, RB_COLOR_MASK, PA_CL_VTE_CNTL and PA_SC_WINDOW_SCISSOR. */
void Imx51Gpu3dBlit::Draw(uint32_t ctrl, uint64_t pa,
const std::unordered_map<uint32_t, uint32_t>& registers,
uint32_t mmu_config) {
/* NXP a1638da9 PA_SU_SC_MODE_CNTL: the C2D shortcut must not bypass the
draw frontend's exclusion of face-stream side effects. */
const uint32_t face = BlitReg(registers, 0x2205u, pa);
if (face & 0xF0000000u)
HaltUnsupportedAccess("faceness controls", static_cast<uint32_t>(pa), face);
if ((ctrl & 0x3Fu) != 6u ||
((ctrl >> 6) & 0x3u) != 2u ||
(ctrl >> 16) != 4u)
HaltUnsupportedAccess("DRAW_INDX not the C2D2 4-vert blit", static_cast<uint32_t>(pa), ctrl);

const uint32_t ci = BlitReg(registers, 0x2001u, pa);
const uint32_t dstFmt = ci & 0xFu;
if ((dstFmt != 5u && dstFmt != 2u) || ((ci >> 9) & 0x3u) != 1u)
HaltUnsupportedAccess("blit dest not COLORX_8_8_8_8/5_6_5 SWAP=1", static_cast<uint32_t>(pa), ci);
const uint32_t dstBase = ci & 0xFFFFF000u;
const uint32_t dstBpp = (dstFmt == 2u) ? 2u : 4u;
const uint32_t dstPitch = BlitReg(registers, 0x2000u, pa) & 0x3FFFu;

const uint32_t cohBase = BlitReg(registers, 0x0A2Au, pa);
uint32_t fb = 0u;
for (uint32_t s = 0u; s < 16u && fb == 0u; ++s) {
auto it = registers.find(0x4801u + s * 6u);
if (it != registers.end() && (it->second & 0xFFFFF000u) == cohBase)
fb = 0x4800u + s * 6u;
}
if (fb == 0u)
HaltUnsupportedAccess("blit source fetch const not found", static_cast<uint32_t>(pa), cohBase);
const uint32_t sw0 = BlitReg(registers, fb + 0u, pa), sw1 = BlitReg(registers, fb + 1u, pa);
const uint32_t sw2 = BlitReg(registers, fb + 2u, pa), sw3 = BlitReg(registers, fb + 3u, pa);
const uint32_t srcFmt = sw1 & 0x3Fu;
const uint32_t swizW = (sw3 >> 10) & 0x7u;
if ((srcFmt != 6u && srcFmt != 4u) || (sw0 >> 31) != 0u ||
((sw3 >> 19) & 0x3u) != 0u || ((sw3 >> 21) & 0x3u) != 0u ||
((sw3 >> 1) & 0x7u) != 2u || ((sw3 >> 4) & 0x7u) != 1u ||
((sw3 >> 7) & 0x7u) != 0u ||
(swizW != 3u && swizW != 5u)) {
HaltUnsupportedAccess("blit source not FMT_8888/565 POINT BGRA", static_cast<uint32_t>(pa), sw3);
}
const uint32_t srcBpp = (srcFmt == 4u) ? 2u : 4u;

if (swizW == 5u && dstBpp == 4u)
HaltUnsupportedAccess("blit SWIZ_W=ONE into 8888 dest (alpha-force not modeled)", static_cast<uint32_t>(pa), sw3);
const uint32_t srcBase = sw1 & 0xFFFFF000u;
const uint32_t srcPitch = ((sw0 >> 22) & 0x1FFu) << 5;
const uint32_t srcW = (sw2 & 0x1FFFu) + 1u;
const uint32_t srcH = ((sw2 >> 13) & 0x1FFFu) + 1u;

if (((BlitReg(registers, 0x2202u, pa) >> 5) & 0x1u) != 1u ||
(BlitReg(registers, 0x2104u, pa) & 0xFu) != 0xFu ||
(BlitReg(registers, 0x2206u, pa) & 0x3Fu) != 0u)
HaltUnsupportedAccess("blit not opaque/full-mask/direct-coord", static_cast<uint32_t>(pa), ci);

const uint32_t vhw = BlitReg(registers, 0x4048u, pa), vhh = BlitReg(registers, 0x4049u, pa);
if (AsFloat(vhw) * 2.0f != static_cast<float>(srcW) ||
AsFloat(vhh) * 2.0f != static_cast<float>(srcH) ||
BlitReg(registers, 0x404Au, pa) != vhw || BlitReg(registers, 0x404Bu, pa) != vhh)
HaltUnsupportedAccess("blit geometry not 1:1 full-screen", static_cast<uint32_t>(pa), srcW);
for (uint32_t k = 0u; k < 4u; ++k)
if (BlitReg(registers, 0x4098u + k, pa) != 0x3F000000u)
HaltUnsupportedAccess("blit tex not full [0,1]", static_cast<uint32_t>(pa), 0x4098u + k);
const uint32_t tl = BlitReg(registers, 0x2081u, pa), br = BlitReg(registers, 0x2082u, pa);
if ((tl & 0x7FFFu) != 0u || ((tl >> 16) & 0x7FFFu) != 0u ||
(br & 0x7FFFu) < srcW || ((br >> 16) & 0x7FFFu) < srcH)
HaltUnsupportedAccess("blit scissor origin/clip", static_cast<uint32_t>(pa), br);

const uint64_t sSpan = uint64_t(srcH - 1u) * srcPitch * srcBpp + uint64_t(srcW) * srcBpp;
const uint64_t dSpan = uint64_t(srcH - 1u) * dstPitch * dstBpp + uint64_t(srcW) * dstBpp;
const uint8_t* s0 = emu_.Get<Imx51Gpu3dMemory>().ReadSpan(srcBase, sSpan, mmu_config);
uint8_t* d0 = emu_.Get<Imx51Gpu3dMemory>().WriteSpan(dstBase, dSpan, mmu_config);
if (srcBpp == dstBpp) {
for (uint32_t y = 0u; y < srcH; ++y)
std::memmove(d0 + uint64_t(y) * dstPitch * dstBpp, s0 + uint64_t(y) * srcPitch * srcBpp, srcW * srcBpp);
} else if (srcBpp == 4u) {
for (uint32_t y = 0u; y < srcH; ++y) {
const uint8_t* srow = s0 + uint64_t(y) * srcPitch * 4u;
uint8_t* drow = d0 + uint64_t(y) * dstPitch * 2u;
for (uint32_t x = 0u; x < srcW; ++x) {
const uint8_t* p = srow + x * 4u;
const uint32_t pixel = uint32_t(p[0]) | (uint32_t(p[1]) << 8) |
(uint32_t(p[2]) << 16) | (uint32_t(p[3]) << 24);
const uint16_t packed = imx51_pixel::PackArgb565(pixel);
drow[x * 2u] = static_cast<uint8_t>(packed);
drow[x * 2u + 1u] = static_cast<uint8_t>(packed >> 8);
}
}
} else {
HaltUnsupportedAccess("blit 565 source into 8888 dest (expand not modeled)", srcBase, dstBase);
}
}
17 changes: 17 additions & 0 deletions cerf/socs/imx51/imx51_gpu3d_blit.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#pragma once
#include "../../core/service.h"
#include <cstdint>
#include <unordered_map>

class Imx51Gpu3dBlit : public Service {
public:
using Service::Service;
bool ShouldRegister() override;
void Draw(uint32_t ctrl, uint64_t packet_address,
const std::unordered_map<uint32_t, uint32_t>& registers, uint32_t mmu_config);

private:
uint32_t BlitReg(const std::unordered_map<uint32_t, uint32_t>& registers, uint32_t index, uint64_t pa);
static float AsFloat(uint32_t value);
[[noreturn]] void HaltUnsupportedAccess(const char* op, uint32_t address, uint64_t value) const;
};
71 changes: 71 additions & 0 deletions cerf/socs/imx51/imx51_gpu3d_context.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
#include "imx51_gpu3d_context.h"
#include "imx51_gpu3d_memory.h"
#include "../../core/cerf_emulator.h"
#include "../../core/fatal.h"
#include "../../boards/board_context.h"
#include "../../state/state_stream.h"
#include <vector>

REGISTER_SERVICE(Imx51Gpu3dContext);

bool Imx51Gpu3dContext::ShouldRegister() {
auto* board = emu_.TryGet<BoardContext>();
return board && board->GetSoc() == SocFamily::iMX51;
}

/* NXP linux-imx a1638da9, gsl_drawctxt.c:74-107,1005-1076; gsl_ringbuffer.h:64. */
void Imx51Gpu3dContext::Load(const Imx51Gpu3dPacket& packet,
std::unordered_map<uint32_t, uint32_t>& registers, uint32_t config) {
auto& memory = emu_.Get<Imx51Gpu3dMemory>();
auto& fatal = emu_.Get<Fatal>();
if (packet.payload_count < 3u || (packet.payload_count & 1u) == 0u)
fatal.Die("GPU context invalid packet length %u", packet.payload_count);
memory.ReadSpan(packet.address, uint64_t(packet.payload_count + 1u) * 4u, config);
const uint32_t address = memory.ReadPa32(packet.address + 4u, config);
const uint32_t first = memory.ReadPa32(packet.address + 8u, config);
const uint32_t type = (first >> 16) & 7u;
if ((address & 0x1FFFu) > 1u || (first & ~0x0107FFFFu) != 0u || (type != 0u && type != 1u && type != 4u))
fatal.Die("GPU context unsupported address/type %08X %08X", address, first);
const uint32_t slot = type == 4u ? 2u : type;
const uint32_t base = type == 4u ? 0x2000u : type == 1u ? 0x4800u : 0x4000u;
const uint32_t limit = type == 4u ? 1024u : type == 1u ? 192u : 2048u;
const uint32_t physical = address & ~0x1FFFu;
const bool enabled = (first & 0x01000000u) != 0u;
/* NXP linux-imx a1638da9, gsl_drawctxt.c:1023-1044: force mismatch and shadow enable. */
const bool load = (address & 1u) || !banks_[slot].enabled || banks_[slot].address != physical;
std::vector<std::pair<uint32_t, uint32_t>> values;
for (uint32_t operand = 1u; operand < packet.payload_count; operand += 2u) {
const uint32_t descriptor = memory.ReadPa32(packet.address + uint64_t(operand + 1u) * 4u, config);
const uint32_t count = memory.ReadPa32(packet.address + uint64_t(operand + 2u) * 4u, config);
const uint32_t offset = descriptor & 0xFFFFu;
const uint32_t allowed = operand == 1u ? 0x0107FFFFu : 0x0007FFFFu;
if ((descriptor & ~allowed) || ((descriptor >> 16) & 7u) != type || offset > limit || count > limit - offset)
fatal.Die("GPU context invalid range %08X %u", descriptor, count);
if (count && load) {
memory.ReadSpan(uint64_t(physical) + uint64_t(offset) * 4u, uint64_t(count) * 4u, config);
for (uint32_t word = 0; word < count; ++word)
values.emplace_back(base + offset + word,
memory.ReadPa32(uint64_t(physical) + uint64_t(offset + word) * 4u, config));
}
}
for (const auto& [index, value] : values) registers[index] = value;
banks_[slot] = {physical, enabled};
}

/* NXP linux-imx a1638da9, gsl_drawctxt.c:620-636,1043,1063,1074: whole-bank shadowing. */
void Imx51Gpu3dContext::ShadowWrite(uint32_t index, uint32_t value, uint32_t config) {
uint32_t slot, base;
if (index >= 0x4000u && index < 0x4800u) { slot = 0u; base = 0x4000u; }
else if (index >= 0x4800u && index < 0x48C0u) { slot = 1u; base = 0x4800u; }
else if (index >= 0x2000u && index < 0x2400u) { slot = 2u; base = 0x2000u; }
else return;
if (banks_[slot].enabled)
emu_.Get<Imx51Gpu3dMemory>().WritePa32(uint64_t(banks_[slot].address) + uint64_t(index - base) * 4u, value, config);
}

void Imx51Gpu3dContext::SaveState(StateWriter& writer) {
for (const auto& bank : banks_) { writer.Write(bank.address); writer.Write(bank.enabled); }
}
void Imx51Gpu3dContext::RestoreState(StateReader& reader) {
for (auto& bank : banks_) { reader.Read(bank.address); reader.Read(bank.enabled); }
}
21 changes: 21 additions & 0 deletions cerf/socs/imx51/imx51_gpu3d_context.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
#pragma once
#include "../../core/service.h"
#include "imx51_gpu3d_packet.h"
#include <array>
#include <unordered_map>

class StateWriter;
class StateReader;

class Imx51Gpu3dContext : public Service {
public:
using Service::Service;
bool ShouldRegister() override;
void Load(const Imx51Gpu3dPacket& packet, std::unordered_map<uint32_t, uint32_t>& registers, uint32_t mmu_config);
void ShadowWrite(uint32_t index, uint32_t value, uint32_t mmu_config);
void SaveState(StateWriter& writer);
void RestoreState(StateReader& reader);
private:
struct Bank { uint32_t address = 0; bool enabled = false; };
std::array<Bank, 3> banks_{};
};
Loading