feat(shader): implement image_bvh64_intersect_ray

MIMG 0xe7 is the node test of image_bvh_intersect_ray with a 64-bit node
pointer: the pointer takes the first two address dwords (low, high), so
the instruction addresses 12 dwords, or 9 with A16.

The node index and address are computed from the whole pointer. A zero
base in the T# is a null BVH only for the 32-bit form: with 64-bit
pointers the T# may cover all of memory from base 0 and the pointer then
carries the node address, so only the T# size bounds the node.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
DotDebian 2026-10-04 16:45:17 +02:00 committed by Claude
parent e440e2c22b
commit bf635fae68
No known key found for this signature in database
9 changed files with 351 additions and 20 deletions

View File

@ -0,0 +1,304 @@
#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp"
#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp"
#include "prx/libc/include/GuestAllocations.hpp"
#include "Recompiler.hpp"
#include "VulkanTestDevice.hpp"
#ifdef _WIN32
#ifndef NOMINMAX
#define NOMINMAX
#endif
#include <windows.h>
#else
#include <sys/mman.h>
#endif
#include <algorithm>
#include <array>
#include <bit>
#include <cstdint>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <limits>
#include <string>
#include <vector>
namespace {
using AgcDriver::Graphics::Require;
using ShaderRecompiler::ShaderStage;
constexpr std::uint32_t Threads = 32;
constexpr std::uint32_t Record = 16;
constexpr std::uint32_t Forms = 4;
constexpr std::size_t BlockBytes = 65536;
constexpr std::uint64_t NodeBytes = 64;
constexpr std::uint32_t LastNode = 6;
constexpr std::uint32_t Invalid = 0xffffffffu;
constexpr std::uint32_t BoxSort = 0x80000000u;
constexpr std::uint32_t TriangleId = 0x00abc123u;
constexpr std::array<std::uint32_t, 4> Box32Children{0x1000u, 0x1001u, 0x1002u, 0x1003u};
constexpr std::array<std::uint32_t, 4> Box16Children{0x2000u, 0x2001u, 0x2002u, 0x2003u};
constexpr std::array<std::uint32_t, 4> PastEndChildren{0x3000u, 0x3001u, 0x3002u, 0x3003u};
constexpr std::array<const char*, Forms> FormNames{"image_bvh64_intersect_ray", "image_bvh_intersect_ray", "image_bvh64_intersect_ray a16", "image_bvh_intersect_ray a16"};
alignas(256) std::array<std::uint32_t, Threads * Record> Input{};
alignas(256) std::array<std::uint32_t, Threads * Record> Output{};
constexpr std::array<std::uint32_t, 82> MakeCode() {
std::array<std::uint32_t, 82> code{};
std::size_t at = 0;
code[at++] = 0x34020086u;
for (std::uint32_t dword = 0; dword < Record; ++dword) {
code[at++] = 0xe0301000u | (dword * 4u);
code[at++] = 0x80000001u | ((4u + dword) << 8u);
}
code[at++] = 0xbf8c3f70u;
code[at++] = 0xf19c9f01u;
code[at++] = 0x00031405u;
code[at++] = 0xf1989f07u;
code[at++] = 0x00021804u;
code[at++] = 0x0a090807u;
code[at++] = 0x0e0d0c0bu;
code[at++] = 0x0000100fu;
code[at++] = 0xf19c9f05u;
code[at++] = 0x40031c05u;
code[at++] = 0x09080706u;
code[at++] = 0x1312110au;
code[at++] = 0xf1989f05u;
code[at++] = 0x40022004u;
code[at++] = 0x0a090807u;
code[at++] = 0x00131211u;
for (std::uint32_t dword = 0; dword < Record; ++dword) {
code[at++] = 0xe0701000u | (dword * 4u);
code[at++] = 0x80010001u | ((20u + dword) << 8u);
}
code[at++] = 0xbf810000u;
return code;
}
alignas(256) constexpr std::array<std::uint32_t, 82> Code = MakeCode();
struct Ray {
float extent;
std::array<float, 3> origin;
std::array<float, 3> direction;
std::array<float, 3> inverse;
};
struct Case {
std::uint32_t node;
Ray ray;
std::array<std::uint32_t, 4> expected;
};
constexpr float Infinity = std::numeric_limits<float>::infinity();
constexpr std::array<float, 3> Diagonal{0.25f, 0.5f, 1.0f};
constexpr std::array<float, 3> DiagonalInverse{4.0f, 2.0f, 1.0f};
constexpr Ray Long{100.0f, {0.0f, 0.0f, 0.0f}, Diagonal, DiagonalInverse};
constexpr Ray Short{2.0f, {0.0f, 0.0f, 0.0f}, Diagonal, DiagonalInverse};
constexpr Ray Forward{100.0f, {0.25f, 0.25f, 0.0f}, {0.0f, 0.0f, 1.0f}, {Infinity, Infinity, 1.0f}};
constexpr std::uint32_t Pointer(std::uint32_t node, std::uint32_t type) {
return (node << 3u) | type;
}
constexpr std::array<Case, 8> Cases{{
{Pointer(2, 5), Long, {Box32Children[2], Box32Children[0], Invalid, Invalid}},
{Pointer(4, 4), Long, {Box16Children[2], Box16Children[0], Invalid, Invalid}},
{Pointer(1, 0), Forward, {0xc0a00000u, 0xbf800000u, TriangleId, 1u}},
{Pointer(1, 1), Forward, {0x7f800000u, 0x3f800000u, TriangleId, 0u}},
{Pointer(1, 7), Long, {Invalid, Invalid, Invalid, Invalid}},
{Pointer(6, 5), Long, {Invalid, Invalid, Invalid, Invalid}},
{Pointer(7, 0), Forward, {Invalid, Invalid, Invalid, Invalid}},
{Pointer(2, 5), Short, {Box32Children[2], Invalid, Invalid, Invalid}},
}};
constexpr std::array<std::array<float, 6>, 4> Boxes{{
{1.0f, 2.0f, 4.0f, 2.0f, 4.0f, 8.0f},
{10.0f, 0.0f, 0.0f, 11.0f, 1.0f, 1.0f},
{0.25f, 0.5f, 1.0f, 0.5f, 1.0f, 2.0f},
{-2.0f, -4.0f, -8.0f, -1.0f, -2.0f, -4.0f},
}};
constexpr std::array<float, 12> TriangleVertices{0.0f, 0.0f, 5.0f, 1.0f, 0.0f, 5.0f, 0.0f, 1.0f, 5.0f, 1.0f, 1.0f, 5.0f};
std::uint32_t Bits(float value) {
return std::bit_cast<std::uint32_t>(value);
}
std::uint32_t Half(float value) {
const auto bits = Bits(value);
const auto sign = (bits >> 16u) & 0x8000u;
const auto exponent = (bits >> 23u) & 0xffu;
const auto mantissa = bits & 0x7fffffu;
if (exponent == 0u && mantissa == 0u) return sign;
if (exponent == 0xffu && mantissa == 0u) return sign | 0x7c00u;
Require(exponent >= 113u && exponent <= 142u && (mantissa & 0x1fffu) == 0u, "bvh64 intersect ray: a test value is not a float16");
return sign | ((exponent - 112u) << 10u) | (mantissa >> 13u);
}
class GuestBlock {
public:
GuestBlock() {
constexpr std::uintptr_t hint = std::uintptr_t{1} << 40u;
#ifdef _WIN32
for (std::uintptr_t attempt = 0; block == nullptr && attempt < 16u; ++attempt) {
block = static_cast<std::uint8_t*>(VirtualAlloc(reinterpret_cast<void*>(hint + (attempt << 32u)), BlockBytes, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE));
}
if (block == nullptr) block = static_cast<std::uint8_t*>(VirtualAlloc(nullptr, BlockBytes, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE));
#else
void* mapped = mmap(reinterpret_cast<void*>(hint), BlockBytes, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
block = mapped == MAP_FAILED ? nullptr : static_cast<std::uint8_t*>(mapped);
#endif
Require(block != nullptr, "bvh64 intersect ray: cannot allocate the guest block");
std::memset(block, 0, BlockBytes);
GuestAllocations::Mutation().Add(block, BlockBytes, true, false);
}
~GuestBlock() {
GuestAllocations::Mutation().Remove(block);
#ifdef _WIN32
VirtualFree(block, 0, MEM_RELEASE);
#else
munmap(block, BlockBytes);
#endif
}
GuestBlock(const GuestBlock&) = delete;
GuestBlock& operator=(const GuestBlock&) = delete;
std::uint64_t Address() const { return static_cast<std::uint64_t>(reinterpret_cast<std::uintptr_t>(block)); }
void Write(std::uint32_t node, std::uint32_t dword, std::uint32_t value) {
std::memcpy(block + node * NodeBytes + dword * 4u, &value, sizeof(value));
}
private:
std::uint8_t* block = nullptr;
};
void WriteBox32(GuestBlock& guest, std::uint32_t node, const std::array<std::uint32_t, 4>& children) {
for (std::uint32_t child = 0; child < 4u; ++child) {
guest.Write(node, child, children[child]);
for (std::uint32_t index = 0; index < 6u; ++index) guest.Write(node, 4u + child * 6u + index, Bits(Boxes[child][index]));
}
}
void WriteBox16(GuestBlock& guest, std::uint32_t node, const std::array<std::uint32_t, 4>& children) {
for (std::uint32_t child = 0; child < 4u; ++child) {
guest.Write(node, child, children[child]);
for (std::uint32_t index = 0; index < 6u; index += 2u) {
const auto position = child * 6u + index;
guest.Write(node, 4u + position / 2u, Half(Boxes[child][index]) | (Half(Boxes[child][index + 1u]) << 16u));
}
}
}
void WriteTriangles(GuestBlock& guest, std::uint32_t node) {
for (std::uint32_t index = 0; index < TriangleVertices.size(); ++index) guest.Write(node, index, Bits(TriangleVertices[index]));
guest.Write(node, 15u, TriangleId);
}
void FillNodes(GuestBlock& guest) {
WriteTriangles(guest, 1u);
WriteBox32(guest, 2u, Box32Children);
WriteBox16(guest, 4u, Box16Children);
WriteBox32(guest, 6u, PastEndChildren);
WriteTriangles(guest, 7u);
}
void FillInput(std::uint64_t nodeBias) {
for (std::uint32_t tid = 0; tid < Threads; ++tid) {
const auto& test = Cases[tid % Cases.size()];
const auto& ray = test.ray;
const std::uint64_t node64 = nodeBias + test.node;
auto* words = &Input[tid * Record];
words[0] = test.node;
words[1] = static_cast<std::uint32_t>(node64);
words[2] = static_cast<std::uint32_t>(node64 >> 32u);
words[3] = Bits(ray.extent);
for (std::uint32_t axis = 0; axis < 3u; ++axis) {
words[4u + axis] = Bits(ray.origin[axis]);
words[7u + axis] = Bits(ray.direction[axis]);
words[10u + axis] = Bits(ray.inverse[axis]);
}
words[13] = Half(ray.direction[0]) | (Half(ray.direction[1]) << 16u);
words[14] = Half(ray.direction[2]) | (Half(ray.inverse[0]) << 16u);
words[15] = Half(ray.inverse[1]) | (Half(ray.inverse[2]) << 16u);
}
}
std::array<std::uint32_t, 4> BufferDescriptor(const void* data, std::uint32_t bytes) {
const auto address = reinterpret_cast<std::uintptr_t>(data);
return {static_cast<std::uint32_t>(address), static_cast<std::uint32_t>((address >> 32u) & 0xffffu), bytes, 0x01016facu};
}
std::array<std::uint32_t, 4> BvhDescriptor(std::uint64_t base, std::uint64_t lastNode) {
return {static_cast<std::uint32_t>(base >> 8u), static_cast<std::uint32_t>((base >> 40u) & 0xffu) | BoxSort, static_cast<std::uint32_t>(lastNode), static_cast<std::uint32_t>((lastNode >> 32u) & 0x3ffu)};
}
void Run(AgcDriver::VulkanDevice& device, const GuestBlock& guest, bool addressInPointer) {
const auto address = guest.Address();
FillInput(addressInPointer ? address >> 3u : 0u);
Output.fill(0xdeadbeefu);
std::vector<std::uint32_t> userData(16, 0u);
const auto input = BufferDescriptor(Input.data(), static_cast<std::uint32_t>(Input.size() * 4u));
const auto output = BufferDescriptor(Output.data(), static_cast<std::uint32_t>(Output.size() * 4u));
const auto narrow = BvhDescriptor(address, LastNode);
const auto wide = addressInPointer ? BvhDescriptor(0u, address / NodeBytes + LastNode) : narrow;
std::copy(input.begin(), input.end(), userData.begin());
std::copy(output.begin(), output.end(), userData.begin() + 4);
std::copy(narrow.begin(), narrow.end(), userData.begin() + 8);
std::copy(wide.begin(), wide.end(), userData.begin() + 12);
const std::span<const std::uint32_t> code(Code);
const std::array<ShaderRecompiler::MemoryRegion, 1> memory{{{reinterpret_cast<std::uintptr_t>(code.data()), std::as_bytes(code)}}};
const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 0u, {false, false, false}, false, 1};
ShaderRecompiler::RecompileRequest request{
{ShaderStage::Compute, reinterpret_cast<std::uintptr_t>(code.data()), code, 0, {}},
{32, 0, userData, compute, std::nullopt, std::nullopt, memory},
device.Target(),
{0, 0, 0, 128}
};
request.useCache = false;
const auto result = ShaderRecompiler::Recompile(request);
device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast<std::uintptr_t>(code.data()));
device.WaitIdle();
}
void Check(bool addressInPointer) {
const std::string mode = addressInPointer ? "address in the node pointer" : "base in the descriptor";
for (std::uint32_t tid = 0; tid < Threads; ++tid) {
const auto& expected = Cases[tid % Cases.size()].expected;
for (std::uint32_t form = 0; form < Forms; ++form) {
for (std::uint32_t dword = 0; dword < 4u; ++dword) {
const auto actual = Output[tid * Record + form * 4u + dword];
Require(actual == expected[dword], std::string(FormNames[form]) + ", " + mode + ": thread " + std::to_string(tid) + " dword " + std::to_string(dword) + " is " + std::to_string(actual) + ", expected " + std::to_string(expected[dword]));
}
}
}
}
}
int main() {
try {
if (ShaderRecompiler::RayTracingStrict() || ShaderRecompiler::RayTracingMiss()) {
std::puts("skipped, APS5_RAYTRACING replaces the node test");
return VulkanTestSkipped;
}
const auto device = OpenVulkanTestDevice();
if (!device) return VulkanTestSkipped;
GuestBlock guest;
Require((guest.Address() >> 35u) != 0u, "bvh64 intersect ray: the guest block is not above 32 GiB, the node pointer would fit 32 bits");
FillNodes(guest);
for (const bool addressInPointer : {true, false}) {
Run(*device, guest, addressInPointer);
Check(addressInPointer);
}
std::puts("bvh64 intersect ray tests passed");
return 0;
} catch (const std::exception& error) {
std::cerr << error.what() << '\n';
return 1;
}
}

View File

@ -6,12 +6,20 @@
using namespace ShaderRecompiler;
static void Require(bool value) { if (!value) throw std::runtime_error("ray tracing miss regression"); }
int main() {
const std::array<std::uint32_t, 2> code{0xF1981F01u, 0x00000000u};
struct Form {
std::uint32_t word0;
std::uint32_t word1;
RdnaOpcode opcode;
std::uint32_t addressDwords;
};
static void Check(const Form& form) {
const std::array<std::uint32_t, 2> code{form.word0, form.word1};
const RdnaInstruction instruction = DecodeRdnaMimg(0u, code, 0u);
Require(instruction.op == RdnaOpcode::ImageBvhIntersectRay);
Require(instruction.op == form.opcode);
Require(instruction.family == RdnaInstructionFamily::MIMG);
Require(IsImageOpcode(instruction.op));
Require(instruction.imageAddressComponents == form.addressDwords);
Require(GetRdnaImageAddressDwordCount(instruction.imageSampleFlags, instruction.imageAddressComponents) == form.addressDwords);
IrProgram program;
auto& block = program.CreateBlock();
program.SetEntryBlock(block);
@ -26,8 +34,14 @@ int main() {
Require(thrown);
} else {
context.TranslateInstruction(instruction);
if (RayTracingMiss()) {
for (auto* value : block.Instructions()) Require(value->Opcode() != IrOpcode::ImageBvhIntersectRay);
}
bool emitted = false;
for (auto* value : block.Instructions()) emitted = emitted || value->Opcode() == IrOpcode::ImageBvhIntersectRay;
Require(emitted != RayTracingMiss());
}
}
int main() {
Check({0xF1981F01u, 0x00000000u, RdnaOpcode::ImageBvhIntersectRay, 11u});
Check({0xF1981F01u, 0x40000000u, RdnaOpcode::ImageBvhIntersectRay, 8u});
Check({0xF19C1F01u, 0x00000000u, RdnaOpcode::ImageBvh64IntersectRay, 12u});
Check({0xF19C1F01u, 0x40000000u, RdnaOpcode::ImageBvh64IntersectRay, 9u});
}

View File

@ -56,6 +56,7 @@ std::uint32_t estimatedSpirvWords(const RdnaInstruction& instruction) {
case RdnaOpcode::DsPermuteB32:
return 1200u;
case RdnaOpcode::ImageBvhIntersectRay:
case RdnaOpcode::ImageBvh64IntersectRay:
return 2000u;
default:
break;

View File

@ -892,6 +892,7 @@ enum class RdnaOpcode : std::uint16_t {
ImageGather4LO,
ImageGather4O,
ImageBvhIntersectRay,
ImageBvh64IntersectRay,
VInterpP1F32,
VInterpP2F32,
VInterpMovF32,

View File

@ -111,6 +111,7 @@ constexpr ImageOpcodeInfo imageOpcodes[] = {
{0x0eu, RdnaOpcode::ImageGetResinfo, nullptr, 0, false, false, false},
{0x60u, RdnaOpcode::ImageGetLod, nullptr, 0, false, false, false},
{0xe6u, RdnaOpcode::ImageBvhIntersectRay, "image_bvh_intersect_ray", 0, false, false, false},
{0xe7u, RdnaOpcode::ImageBvh64IntersectRay, "image_bvh64_intersect_ray", 0, false, false, false},
};
const ImageOpcodeInfo& lookupOpcode(std::uint32_t opcode) {
@ -306,8 +307,9 @@ RdnaInstruction DecodeRdnaMimg(std::uint32_t programCounter, std::span<const std
if (d16 && !(info.sample || info.gather || opcode == 0u || opcode == 1u || opcode == 8u || opcode == 9u)) {
throw std::runtime_error("MIMG opcode does not support D16");
}
const bool rayQuery = info.opcode == RdnaOpcode::ImageBvhIntersectRay;
std::uint32_t components = rayQuery ? (a16 ? 8u : 11u) : opcode == 0x0Eu ? 1u : coordinateCount(dimension);
const bool rayQuery64 = info.opcode == RdnaOpcode::ImageBvh64IntersectRay;
const bool rayQuery = rayQuery64 || info.opcode == RdnaOpcode::ImageBvhIntersectRay;
std::uint32_t components = rayQuery ? (a16 ? 8u : 11u) + (rayQuery64 ? 1u : 0u) : opcode == 0x0Eu ? 1u : coordinateCount(dimension);
if (opcode == 1u || opcode == 4u || opcode == 5u || opcode == 9u || opcode == 0x0bu) {
++components;
}

View File

@ -828,6 +828,7 @@ bool IsImageOpcode(RdnaOpcode opcode) {
case RdnaOpcode::ImageGather4LO:
case RdnaOpcode::ImageGather4O:
case RdnaOpcode::ImageBvhIntersectRay:
case RdnaOpcode::ImageBvh64IntersectRay:
return true;
default:
return false;

View File

@ -1201,35 +1201,41 @@ void EmitImageBvhIntersectRay(SpirvValueEmitContext& ctx, const IrValue& inst) {
const IrValue* address = ctx.ImageAddress(inst.Argument(1));
const auto component = [&](std::uint32_t index) { return ctx.Arg(*address, index); };
const bool a16 = (ctx.Memory(inst).imageSampleFlags & RdnaImageSampleFlagA16) != 0u;
const bool node64 = ctx.Memory(inst).addressIsFull;
const std::uint32_t ray = node64 ? 2u : 1u;
const auto node = component(0);
const auto type = op(spv::OpBitwiseAnd, u32, node, uint(7u));
const auto nodeIndex = op(spv::OpShiftRightLogical, u64, widen(node), BdaConstant(state, 3u));
const auto nodeLow = component(0);
const auto node = node64 ? pair(nodeLow, component(1)) : widen(nodeLow);
const auto type = op(spv::OpBitwiseAnd, u32, nodeLow, uint(7u));
const auto nodeIndex = op(spv::OpShiftRightLogical, u64, node, BdaConstant(state, 3u));
const auto baseUnits = pair(word[0], op(spv::OpBitwiseAnd, u32, word[1], uint(0xffu)));
const auto lastNode = pair(word[2], op(spv::OpBitwiseAnd, u32, word[3], uint(0x3ffu)));
const auto boxGrow = op(spv::OpBitwiseAnd, u32, op(spv::OpShiftRightLogical, u32, word[1], uint(23u)), uint(0xffu));
const auto boxSort = op(spv::OpINotEqual, boolean, op(spv::OpBitwiseAnd, u32, word[1], uint(0x80000000u)), uint(0u));
const auto barycentrics = op(spv::OpINotEqual, boolean, op(spv::OpBitwiseAnd, u32, word[3], uint(1u << 24u)), uint(0u));
const auto nodeAddress = op(spv::OpIAdd, u64, op(spv::OpShiftLeftLogical, u64, baseUnits, BdaConstant(state, 8u)), op(spv::OpShiftLeftLogical, u64, widen(op(spv::OpBitwiseAnd, u32, node, uint(~7u))), BdaConstant(state, 3u)));
const auto present = op(spv::OpINotEqual, boolean, baseUnits, BdaConstant(state, 0u));
const auto valid = both(present, op(spv::OpULessThanEqual, boolean, nodeIndex, lastNode));
const auto nodeAddress = op(spv::OpIAdd, u64, op(spv::OpShiftLeftLogical, u64, baseUnits, BdaConstant(state, 8u)), op(spv::OpShiftLeftLogical, u64, op(spv::OpBitwiseAnd, u64, node, BdaConstant(state, ~std::uint64_t{7u})), BdaConstant(state, 3u)));
const auto within = [&](spv::Op compare) {
const auto inRange = op(compare, boolean, nodeIndex, lastNode);
return node64 ? inRange : both(op(spv::OpINotEqual, boolean, baseUnits, BdaConstant(state, 0u)), inRange);
};
const auto valid = within(spv::OpULessThanEqual);
const auto isTriangle = both(valid, op(spv::OpULessThanEqual, boolean, type, uint(1u)));
const auto isBox16 = both(valid, op(spv::OpIEqual, boolean, type, uint(4u)));
const auto isBox32 = both(both(present, op(spv::OpULessThan, boolean, nodeIndex, lastNode)), op(spv::OpIEqual, boolean, type, uint(5u)));
const auto isBox32 = both(within(spv::OpULessThan), op(spv::OpIEqual, boolean, type, uint(5u)));
const auto extent = asFloat(component(1));
const auto extent = asFloat(component(ray));
std::array<std::uint32_t, 3> origin{};
std::array<std::uint32_t, 3> direction{};
std::array<std::uint32_t, 3> inverse{};
for (std::uint32_t axis = 0; axis < 3u; ++axis) {
origin[axis] = asFloat(component(2u + axis));
origin[axis] = asFloat(component(ray + 1u + axis));
if (!a16) {
direction[axis] = asFloat(component(5u + axis));
inverse[axis] = asFloat(component(8u + axis));
direction[axis] = asFloat(component(ray + 4u + axis));
inverse[axis] = asFloat(component(ray + 7u + axis));
continue;
}
const auto half = [&](std::uint32_t index) {
const auto packed = component(5u + index / 2u);
const auto packed = component(ray + 4u + index / 2u);
return EmitF16BitsToF32(state, index % 2u == 0u ? op(spv::OpBitwiseAnd, u32, packed, uint(0xffffu)) : op(spv::OpShiftRightLogical, u32, packed, uint(16u)));
};
direction[axis] = half(axis);

View File

@ -61,6 +61,7 @@ bool TranslationContext::imageBvhIntersectRay(const RdnaInstruction& inst) {
memory.kind = ResourceKind::Global;
memory.dataDwords = 4u;
memory.imageSampleFlags = inst.imageA16 ? RdnaImageSampleFlagA16 : 0u;
memory.addressIsFull = inst.op == RdnaOpcode::ImageBvh64IntersectRay;
IrValue* descriptor = constructU32x4(inst.source1, 4u);
IrValue* address = makeImageAddress(inst, inst.source0);
IrValue& result = ir.Emit(IrOpcode::ImageBvhIntersectRay, IrOpcodeType(IrOpcode::ImageBvhIntersectRay), {descriptor, address, &ir.GetExec()}, addMemoryInfo(memory, inst.programCounter));

View File

@ -432,6 +432,7 @@ bool TranslationContext::emitMemory(const RdnaInstruction& inst) {
case RdnaOpcode::ImageGetLod:
return imageGetLod(inst);
case RdnaOpcode::ImageBvhIntersectRay:
case RdnaOpcode::ImageBvh64IntersectRay:
return imageBvhIntersectRay(inst);
default: