Compare commits

...

6 commits

Author SHA1 Message Date
DH 10391da0d3 amdgpu/cache: do not allow 0 compute thread count
Some checks are pending
Formatting check / formatting-check (push) Waiting to run
Build RPCSX / build-linux (push) Waiting to run
Build RPCSX / build-android (arm64-v8a, armv8-a) (push) Waiting to run
Build RPCSX / build-android (arm64-v8a, armv8.1-a) (push) Waiting to run
Build RPCSX / build-android (arm64-v8a, armv8.2-a) (push) Waiting to run
Build RPCSX / build-android (arm64-v8a, armv8.4-a) (push) Waiting to run
Build RPCSX / build-android (arm64-v8a, armv8.5-a) (push) Waiting to run
Build RPCSX / build-android (arm64-v8a, armv9-a) (push) Waiting to run
Build RPCSX / build-android (arm64-v8a, armv9.1-a) (push) Waiting to run
Build RPCSX / build-android (x86_64, x86-64) (push) Waiting to run
2025-12-02 21:46:26 +03:00
DH ab7f9b3f16 blockpool: add missed used count increment 2025-12-02 21:33:44 +03:00
DH 7c44c8fe4b add missed file 2025-12-02 19:37:19 +03:00
DH 23fd83e3d5 dce: stub getVBlankStatus 2025-12-02 19:37:03 +03:00
DH fed5cfac53 shader: fix segfault in logPhiPredecessorsMismatch, removed useless messages 2025-12-02 19:35:15 +03:00
DH de7c40d330 amdgpu: simplify evaluator 2025-12-02 19:26:31 +03:00
8 changed files with 492 additions and 623 deletions

View file

@ -226,6 +226,7 @@ struct PooledMemoryResource {
{.pmemAddress = block.beginAddress(), .type = type},
false);
freeBlocks.pop_back();
used += mapVirtualRange.size();
virtualRange = rx::AddressRange::fromBeginEnd(
mapVirtualRange.endAddress(), virtualRange.endAddress());

View file

@ -453,20 +453,16 @@ Cache::ShaderResources::eval(ir::InstructionId instId,
case 8:
result = readPointer<std::uint64_t>(address);
break;
case 12:
result = readPointer<u32vec3>(address);
break;
case 16:
result = readPointer<u32vec4>(address);
break;
case 32:
result = readPointer<std::array<std::uint32_t, 8>>(address);
break;
case 64:
result = readPointer<std::array<std::uint32_t, 16>>(address);
break;
default:
rx::die("unexpected pointer load size {}", loadSize);
rx::dieIf(loadSize % sizeof(std::uint32_t), "unaligned load size {}",
loadSize);
for (std::int32_t offset = 0; offset < loadSize;
offset += sizeof(std::uint32_t)) {
result.add(readPointer<std::uint32_t>(address + offset));
}
break;
}
return result;
@ -2368,7 +2364,7 @@ Cache::Shader Cache::GraphicsTag::getShader(
std::bit_cast<std::uint32_t>(context.paClVports[slot.data].zScale);
break;
case gcn::ConfigType::PsInputVGpr:
if (slot.data > psVgprInput.size()) {
if (slot.data >= psVgprInput.size()) {
configPtr[index] = ~0;
} else {
configPtr[index] = std::bit_cast<std::uint32_t>(psVgprInput[slot.data]);
@ -2456,9 +2452,9 @@ Cache::ComputeTag::getShader(const Registers::ComputeConfig &pgm) {
gcn::Environment env{
.vgprCount = pgm.rsrc1.getVGprCount(),
.sgprCount = pgm.rsrc1.getSGprCount(),
.numThreadX = static_cast<std::uint8_t>(pgm.numThreadX),
.numThreadY = static_cast<std::uint8_t>(pgm.numThreadY),
.numThreadZ = static_cast<std::uint8_t>(pgm.numThreadZ),
.numThreadX = std::max<std::uint8_t>(pgm.numThreadX, 1),
.numThreadY = std::max<std::uint8_t>(pgm.numThreadY, 1),
.numThreadZ = std::max<std::uint8_t>(pgm.numThreadZ, 1),
.userSgprs = std::span(pgm.userData.data(), pgm.rsrc2.userSgpr),
};

View file

@ -311,7 +311,7 @@ void Device::start() {
});
}
std::jthread vblankThread([](const std::stop_token &stopToken) {
std::jthread vblankThread([this](const std::stop_token &stopToken) {
orbis::g_context->deviceEventEmitter->emit(
orbis::kEvFiltDisplay,
[=](orbis::KNote *note) -> std::optional<orbis::intptr_t> {
@ -330,6 +330,8 @@ void Device::start() {
std::chrono::duration_cast<std::chrono::nanoseconds>(period);
std::this_thread::sleep_until(prevVBlank);
vblankCount++;
orbis::g_context->deviceEventEmitter->emit(
orbis::kEvFiltDisplay,
[=](orbis::KNote *note) -> std::optional<orbis::intptr_t> {

View file

@ -68,6 +68,7 @@ struct DeviceContext {
static constexpr auto kMaxProcessCount = 6;
PadState kbPadState{};
std::atomic<std::uint64_t> vblankCount{};
std::atomic<std::uint64_t> cpuCacheCommands[kMaxProcessCount][4]{};
rx::shared_atomic32 cpuCacheCommandsIdle[kMaxProcessCount]{};
rx::shared_atomic32 gpuCacheCommand[kMaxProcessCount]{};

View file

@ -2,33 +2,156 @@
#include "Vector.hpp"
#include "ir/Value.hpp"
#include "rx/align.hpp"
#include "rx/die.hpp"
#include <algorithm>
#include <array>
#include <cstdint>
#include <variant>
#include <cstring>
#include <tuple>
#include <type_traits>
#include <utility>
namespace shader::eval {
struct Value {
using Storage = std::variant<
std::nullptr_t, std::int8_t, std::int16_t, std::int32_t, std::int64_t,
std::uint8_t, std::uint16_t, std::uint32_t, std::uint64_t, float16_t,
float32_t, float64_t, u8vec2, u8vec3, u8vec4, i8vec2, i8vec3, i8vec4,
u16vec2, u16vec3, u16vec4, i16vec2, i16vec3, i16vec4, u32vec2, u32vec3,
u32vec4, i32vec2, i32vec3, i32vec4, u64vec2, u64vec3, u64vec4, i64vec2,
i64vec3, i64vec4, f32vec2, f32vec3, f32vec4, f64vec2, f64vec3, f64vec4,
f16vec2, f16vec3, f16vec4, bool, bvec2, bvec3, bvec4,
std::array<uint32_t, 8>, std::array<std::uint32_t, 16>>;
static constexpr auto StorageSize = std::variant_size_v<Storage>;
Storage storage;
using Types = std::tuple<std::nullptr_t, bool, int8_t, int16_t, int32_t,
int64_t, uint8_t, uint16_t, uint32_t, uint64_t,
float16_t, float32_t, float64_t>;
static constexpr auto kMaxElementCount = 32;
static constexpr auto kMaxTypeAlignment = [] {
auto impl = []<std::size_t... I>(std::index_sequence<I...>) {
std::size_t result = 1;
((result = std::max(alignof(std::tuple_element_t<I, Types>), result)),
...);
return result;
};
return impl(std::make_index_sequence<std::tuple_size_v<Types>>{});
}();
static constexpr auto kMaxTypeSize = [] {
auto impl = []<std::size_t... I>(std::index_sequence<I...>) {
std::size_t result = 1;
((result = std::max(sizeof(std::tuple_element_t<I, Types>), result)),
...);
return result;
};
return rx::alignUp(
impl(std::make_index_sequence<std::tuple_size_v<Types>>{}),
kMaxTypeAlignment);
}();
template <typename T> static consteval std::size_t getTypeIndex() {
auto impl = []<std::size_t... I>(std::index_sequence<I...>) {
std::size_t result = -1;
((result =
std::is_same_v<T, std::tuple_element_t<I, Types>> ? I : result),
...);
return result;
};
return impl(std::make_index_sequence<std::tuple_size_v<Types>>{});
}
static constexpr auto StorageSize = std::tuple_size_v<Types>;
template <typename T, typename RT = std::invoke_result_t<
T, std::span<std::tuple_element_t<0, Types>>>>
RT visit(T &&cb) const {
static constexpr auto table = [] {
std::array<RT (*)(void *cb, const char *data, std::uint32_t count),
std::tuple_size_v<Types>>
result;
auto impl = [&]<std::size_t... I>(std::index_sequence<I...>) {
((result[I] = [](void *cb, const char *data,
std::uint32_t count) -> RT {
return (*reinterpret_cast<T *>(cb))(std::span(
reinterpret_cast<const std::tuple_element_t<I, Types> *>(data),
count));
}),
...);
};
impl(std::make_index_sequence<std::tuple_size_v<Types>>{});
return result;
}();
return table[mTypeIndex](&cb, mData, mCount);
}
[[nodiscard]] std::size_t getConstituentSize() const {
return visit([](auto values) -> std::size_t {
if constexpr (std::is_same_v<decltype(values[0]), std::nullptr_t>) {
return 0;
} else {
return sizeof(values[0]);
}
});
}
explicit operator bool() const { return !empty(); }
bool empty() const { return storage.index() == 0; }
[[nodiscard]] bool empty() const {
return mCount == 0 || mTypeIndex == getTypeIndex<std::nullptr_t>();
}
[[nodiscard]] std::size_t size() const { return mCount; }
Value() : storage(nullptr) {}
Value() = default;
template <typename FT, typename... T>
requires(getTypeIndex<FT>() < std::tuple_size_v<Types> &&
(std::is_same_v<FT, T> && ...))
Value(FT firstValue, T... value) {
add(firstValue);
(add(value), ...);
}
void add(const Value &value) {
if (value.mCount != 1) {
mTypeIndex = getTypeIndex<std::nullptr_t>();
mCount = 1;
return;
}
if (mCount == 0) {
mTypeIndex = value.mTypeIndex;
mCount = 1;
std::memcpy(mData, value.mData, kMaxTypeSize);
return;
}
rx::dieIf(mCount >= kMaxElementCount, "storage too small");
if (mTypeIndex != value.mTypeIndex) {
mTypeIndex = getTypeIndex<std::nullptr_t>();
mCount = 1;
return;
}
auto index = mCount++;
auto elemSize = getConstituentSize();
std::memcpy(mData + elemSize * index, value.mData, kMaxTypeSize);
}
template <typename T>
Value(T &&value)
requires requires { Storage(std::forward<T>(value)); }
: storage(std::forward<T>(value)) {}
requires(getTypeIndex<T>() < std::tuple_size_v<Types>)
void add(T value) {
if (mCount == 0) {
mTypeIndex = getTypeIndex<T>();
mCount = 1;
*getData<T>() = value;
return;
}
rx::dieIf(mCount >= kMaxElementCount, "storage too small");
if (mTypeIndex != getTypeIndex<T>()) {
mTypeIndex = getTypeIndex<std::nullptr_t>();
mCount = 1;
return;
}
getData<T>()[mCount++] = value;
}
static Value compositeConstruct(ir::Value type,
std::span<const Value> constituents);
@ -52,21 +175,43 @@ struct Value {
std::optional<std::int64_t> sExtScalar() const;
template <typename T>
requires requires { std::get<T>(storage); }
T get() const {
return std::get<T>(storage);
requires(getTypeIndex<T>() < std::tuple_size_v<Types>)
[[nodiscard]] const T &get(std::size_t index = 0) const {
rx::dieIf(mTypeIndex != getTypeIndex<T>(),
"eval::Value::get(): invalid type");
rx::dieIf(index >= std::tuple_size_v<Types>,
"eval::Value::get(): invalid index");
return getData<T>()[index];
}
template <auto I>
requires(I < std::tuple_size_v<Types>)
[[nodiscard]] const std::tuple_element_t<I, Types> &
get(std::size_t index = 0) const {
rx::dieIf(mTypeIndex != I, "eval::Value::get(): invalid type");
rx::dieIf(index >= std::tuple_size_v<Types>,
"eval::Value::get(): invalid index");
return getData<std::tuple_element_t<I, Types>>()[index];
}
template <typename T>
requires requires { std::get<T>(storage); }
std::optional<T> as() const {
if (auto result = std::get_if<T>(&storage)) {
return *result;
requires(getTypeIndex<T>() < std::tuple_size_v<Types>)
[[nodiscard]] std::optional<T> as(std::size_t index = 0) const {
rx::dieIf(index >= std::tuple_size_v<Types>,
"eval::Value::as(): invalid index");
if (mTypeIndex == getTypeIndex<T>()) {
return getData<T>()[index];
}
return std::nullopt;
}
template <typename T>
requires(getTypeIndex<T>() < std::tuple_size_v<Types>)
[[nodiscard]] bool is() const {
return mTypeIndex == getTypeIndex<T>();
}
Value operator+(const Value &rhs) const;
Value operator-(const Value &rhs) const;
Value operator*(const Value &rhs) const;
@ -89,5 +234,17 @@ struct Value {
Value operator-() const;
Value operator~() const;
Value operator!() const;
std::size_t index() const { return mTypeIndex; }
private:
template <typename T> T *getData() { return reinterpret_cast<T *>(mData); }
template <typename T> const T *getData() const {
return reinterpret_cast<const T *>(mData);
}
std::uint32_t mTypeIndex = 0;
std::uint32_t mCount = 0;
alignas(kMaxTypeAlignment) char mData[kMaxTypeSize * kMaxElementCount];
};
} // namespace shader::eval

View file

@ -2,132 +2,15 @@
#include "dialect.hpp"
#include "ir.hpp"
#include <cmath>
#include <concepts>
#include <type_traits>
using namespace shader;
template <typename Cond, typename... Args> consteval bool testVisitCond() {
if constexpr (std::is_same_v<Cond, void>) {
return true;
} else {
return Cond{}(std::remove_cvref_t<Args>{}...);
}
};
template <typename Cond, std::size_t U> consteval bool testVisitCond() {
if constexpr (U >= eval::Value::StorageSize) {
return false;
} else if constexpr (std::is_same_v<Cond, void>) {
return true;
} else {
return Cond{}(std::variant_alternative_t<U, eval::Value::Storage>{});
}
};
template <typename Cond = void, size_t I = 0>
constexpr eval::Value visitImpl(const eval::Value &variant, auto &&fn) {
#define DEFINE_CASE(N) \
case I + N: \
if constexpr (testVisitCond<Cond, I + N>()) { \
return std::forward<decltype(fn)>(fn)(std::get<I + N>(variant.storage)); \
} else { \
return {}; \
}
switch (variant.storage.index()) {
DEFINE_CASE(0);
DEFINE_CASE(1);
DEFINE_CASE(2);
DEFINE_CASE(3);
DEFINE_CASE(4);
DEFINE_CASE(5);
DEFINE_CASE(6);
DEFINE_CASE(7);
DEFINE_CASE(8);
DEFINE_CASE(9);
DEFINE_CASE(10);
DEFINE_CASE(11);
DEFINE_CASE(12);
DEFINE_CASE(13);
DEFINE_CASE(14);
DEFINE_CASE(15);
DEFINE_CASE(16);
DEFINE_CASE(17);
DEFINE_CASE(18);
DEFINE_CASE(19);
DEFINE_CASE(20);
DEFINE_CASE(21);
DEFINE_CASE(22);
DEFINE_CASE(23);
DEFINE_CASE(24);
DEFINE_CASE(25);
DEFINE_CASE(26);
DEFINE_CASE(27);
DEFINE_CASE(28);
DEFINE_CASE(29);
DEFINE_CASE(30);
DEFINE_CASE(31);
DEFINE_CASE(32);
DEFINE_CASE(33);
DEFINE_CASE(34);
DEFINE_CASE(35);
DEFINE_CASE(36);
DEFINE_CASE(37);
DEFINE_CASE(38);
DEFINE_CASE(39);
DEFINE_CASE(40);
DEFINE_CASE(41);
DEFINE_CASE(42);
DEFINE_CASE(43);
DEFINE_CASE(44);
DEFINE_CASE(45);
DEFINE_CASE(46);
DEFINE_CASE(47);
DEFINE_CASE(48);
DEFINE_CASE(49);
DEFINE_CASE(50);
DEFINE_CASE(51);
DEFINE_CASE(52);
DEFINE_CASE(53);
DEFINE_CASE(54);
DEFINE_CASE(55);
DEFINE_CASE(56);
DEFINE_CASE(57);
DEFINE_CASE(58);
DEFINE_CASE(59);
DEFINE_CASE(60);
DEFINE_CASE(61);
DEFINE_CASE(62);
DEFINE_CASE(63);
}
#undef DEFINE_CASE
constexpr auto NextIndex = I + 64;
if constexpr (NextIndex < eval::Value::StorageSize) {
return visitImpl<Cond, NextIndex>(std::forward<decltype(fn)>(fn),
std::forward<decltype(variant)>(variant));
}
return {};
}
template <typename Cond = void, typename Cb>
constexpr eval::Value visitScalarType(ir::Value type, Cb &&cb)
requires requires {
{ std::forward<Cb>(cb)(int{}) } -> std::same_as<eval::Value>;
}
{
auto invoke = [&](auto type) -> eval::Value {
if constexpr (testVisitCond<Cond, std::remove_cvref_t<decltype(type)>>()) {
return std::forward<Cb>(cb)(type);
}
return {};
};
template <typename T>
constexpr bool invokeWithType(ir::Value type, T &&invoke) {
if (type == ir::spv::OpTypeBool) {
return invoke(bool{});
invoke(bool{});
return true;
}
if (type == ir::spv::OpTypeInt) {
@ -136,213 +19,106 @@ constexpr eval::Value visitScalarType(ir::Value type, Cb &&cb)
switch (*type.getOperand(0).getAsInt32()) {
case 8:
if (isSigned) {
return invoke(std::int8_t{});
invoke(std::int8_t{});
return true;
}
return invoke(std::uint8_t{});
invoke(std::uint8_t{});
return true;
case 16:
if (isSigned) {
return invoke(std::int16_t{});
invoke(std::int16_t{});
return true;
}
return invoke(std::uint16_t{});
invoke(std::uint16_t{});
return true;
case 32:
if (isSigned) {
return invoke(std::int32_t{});
invoke(std::int32_t{});
return true;
}
return invoke(std::uint32_t{});
invoke(std::uint32_t{});
return true;
case 64:
if (isSigned) {
return invoke(std::int64_t{});
invoke(std::int64_t{});
return true;
}
return invoke(std::uint64_t{});
invoke(std::uint64_t{});
return true;
}
return {};
return false;
}
if (type == ir::spv::OpTypeFloat) {
switch (*type.getOperand(0).getAsInt32()) {
case 16:
return invoke(shader::float16_t{});
invoke(shader::float16_t{});
return true;
case 32:
return invoke(shader::float32_t{});
invoke(shader::float32_t{});
return true;
case 64:
return invoke(shader::float64_t{});
invoke(shader::float64_t{});
return true;
}
return {};
return false;
}
return {};
}
template <typename Cond = void, typename Cb>
constexpr eval::Value visitType(ir::Value type, Cb &&cb)
requires requires {
{ std::forward<Cb>(cb)(int{}) } -> std::same_as<eval::Value>;
}
{
if (type == ir::spv::OpTypeInt || type == ir::spv::OpTypeFloat ||
type == ir::spv::OpTypeBool) {
return visitScalarType<Cond>(type, cb);
}
auto invoke = [&](auto type) -> eval::Value {
if constexpr (testVisitCond<Cond, std::remove_cvref_t<decltype(type)>>()) {
return std::forward<Cb>(cb)(type);
} else {
return {};
}
};
if (type == ir::spv::OpTypeVector) {
switch (*type.getOperand(1).getAsInt32()) {
case 2:
return visitScalarType(
type.getOperand(0).getAsValue(),
[&]<typename T>(T) { return invoke(shader::Vector<T, 2>{}); });
case 3:
return visitScalarType(
type.getOperand(0).getAsValue(),
[&]<typename T>(T) { return invoke(shader::Vector<T, 3>{}); });
case 4:
return visitScalarType(
type.getOperand(0).getAsValue(),
[&]<typename T>(T) { return invoke(shader::Vector<T, 4>{}); });
}
return {};
return invokeWithType(type.getOperand(0).getAsValue(),
std::forward<T>(invoke));
}
return {};
return false;
}
template <typename Cond = void, typename Cb>
eval::Value visit(const eval::Value &value, Cb &&cb) {
using VisitCond = decltype([](auto &&storage) {
using T = std::remove_cvref_t<decltype(storage)>;
if constexpr (std::is_same_v<T, std::nullptr_t>) {
return false;
} else {
return testVisitCond<Cond, T>();
}
static constexpr std::size_t getIrTypeIndex(ir::Value type) {
std::size_t result = 0;
invokeWithType(type, [&](auto type) {
result = eval::Value::getTypeIndex<decltype(type)>();
});
return visitImpl<VisitCond>(value, std::forward<Cb>(cb));
return result;
}
template <typename Cb>
eval::Value visit2(auto &&cond, const eval::Value &value, Cb &&cb) {
if constexpr (cond()) {
return visitImpl(value, std::forward<Cb>(cb));
} else {
return {};
static constexpr std::size_t getIrTypeConstituents(ir::Value type) {
if (type == ir::spv::OpTypeVector) {
return *type.getOperand(1).getAsInt32();
}
return 1;
}
template <typename ValueCond = void, typename TypeVisitCond = void,
typename TypeValueVisitCond = void, typename Cb>
eval::Value visitWithType(const eval::Value &value, ir::Value type, Cb &&cb) {
using ValueVisitCond = decltype([](auto storage) {
if constexpr (std::is_same_v<decltype(storage), std::nullptr_t>) {
return false;
} else {
return testVisitCond<ValueCond, decltype(storage)>();
}
});
return visitImpl<ValueVisitCond>(value, [&](auto &&value) -> eval::Value {
return visitType<TypeVisitCond>(type, [&](auto type) -> eval::Value {
if constexpr (testVisitCond<TypeValueVisitCond, decltype(type),
decltype(value)>()) {
return std::forward<Cb>(cb)(type, value);
} else {
return {};
}
});
});
}
namespace {
template <typename T> struct ComponentTypeImpl {
using type = T;
};
template <typename T, std::size_t N> struct ComponentTypeImpl<Vector<T, N>> {
using type = T;
};
template <typename T, std::size_t N>
struct ComponentTypeImpl<std::array<T, N>> {
using type = T;
};
template <typename T> struct MakeSignedImpl {
using type = std::make_signed_t<T>;
};
template <typename T, std::size_t N> struct MakeSignedImpl<Vector<T, N>> {
using type = Vector<std::make_signed_t<T>, N>;
};
template <typename T> struct MakeUnsignedImpl {
using type = std::make_unsigned_t<T>;
};
template <typename T, std::size_t N> struct MakeUnsignedImpl<Vector<T, N>> {
using type = Vector<std::make_unsigned_t<T>, N>;
};
} // namespace
template <typename T> using ComponentType = typename ComponentTypeImpl<T>::type;
template <typename T> using MakeSigned = typename MakeSignedImpl<T>::type;
template <typename T> using MakeUnsigned = typename MakeUnsignedImpl<T>::type;
template <typename> constexpr std::size_t Components = 1;
template <typename T, std::size_t N>
constexpr std::size_t Components<Vector<T, N>> = N;
template <typename T, std::size_t N>
constexpr std::size_t Components<std::array<T, N>> = N;
template <typename> constexpr bool IsArray = false;
template <typename T, std::size_t N>
constexpr bool IsArray<std::array<T, N>> = true;
eval::Value
eval::Value::compositeConstruct(ir::Value type,
std::span<const eval::Value> constituents) {
using Cond =
decltype([](auto type) { return Components<decltype(type)> > 1; });
if (getIrTypeConstituents(type) != constituents.size()) {
return {};
}
return visitType<Cond>(type, [&](auto type) -> Value {
constexpr std::size_t N = Components<decltype(type)>;
if (N != constituents.size()) {
return {};
}
auto typeIndex = getIrTypeIndex(type);
decltype(type) result;
eval::Value result;
for (auto &elem : constituents) {
result.add(elem);
}
for (std::size_t i = 0; i < N; ++i) {
if (auto value = constituents[i].as<ComponentType<decltype(type)>>()) {
result[i] = *value;
} else {
return {};
}
}
if (result.index() != typeIndex) {
return {};
}
return result;
});
return result;
}
eval::Value eval::Value::compositeExtract(const Value &index) const {
using Cond =
decltype([](auto type) { return Components<decltype(type)> > 1; });
auto optIndexInt = index.zExtScalar();
if (!optIndexInt) {
return {};
@ -350,321 +126,249 @@ eval::Value eval::Value::compositeExtract(const Value &index) const {
auto indexInt = *optIndexInt;
return visit<Cond>(*this, [&](auto &&value) -> Value {
using ValueType = std::remove_cvref_t<decltype(value)>;
constexpr std::size_t N = Components<ValueType>;
if (indexInt >= size()) {
return {};
}
if (indexInt >= N) {
return {};
}
return value[indexInt];
});
eval::Value result;
result.mTypeIndex = mTypeIndex;
result.mCount = 1;
std::memcpy(result.mData, mData + indexInt * getConstituentSize(),
kMaxTypeSize);
return result;
}
eval::Value eval::Value::isNan() const {
using Cond = decltype([](auto type) {
return std::is_floating_point_v<ComponentType<decltype(type)>> &&
!IsArray<decltype(type)>;
});
return visit([](auto value) {
eval::Value result;
return visit<Cond>(*this, [](auto &&value) -> Value {
constexpr std::size_t N = Components<std::remove_cvref_t<decltype(value)>>;
if constexpr (N == 1) {
return std::isnan(value);
} else {
Vector<bool, N> result;
for (std::size_t i = 0; i < N; ++i) {
result[i] = std::isnan(value[i]);
if constexpr (std::is_floating_point_v<
typename decltype(value)::value_type>) {
for (std::size_t i = 0; i < value.size(); ++i) {
result.add(std::isnan(value[i]));
}
return result;
}
return result;
});
}
eval::Value eval::Value::isInf() const {
using Cond = decltype([](auto type) {
return std::is_floating_point_v<ComponentType<decltype(type)>> &&
!IsArray<decltype(type)>;
});
return visit([](auto value) {
eval::Value result;
return visit<Cond>(*this, [](auto &&value) -> Value {
constexpr std::size_t N = Components<std::remove_cvref_t<decltype(value)>>;
if constexpr (N == 1) {
return std::isinf(value);
} else {
Vector<bool, N> result;
for (std::size_t i = 0; i < N; ++i) {
result[i] = std::isinf(value[i]);
if constexpr (std::is_floating_point_v<
typename decltype(value)::value_type>) {
for (std::size_t i = 0; i < value.size(); ++i) {
result.add(std::isinf(value[i]));
}
return result;
}
return result;
});
}
eval::Value eval::Value::isFinite() const {
using Cond = decltype([](auto type) {
return std::is_floating_point_v<ComponentType<decltype(type)>>;
});
return visit([](auto value) {
eval::Value result;
return visit<Cond>(*this, [](auto &&value) -> Value {
constexpr std::size_t N = Components<std::remove_cvref_t<decltype(value)>>;
if constexpr (N == 1) {
return std::isfinite(value);
} else {
Vector<bool, N> result;
for (std::size_t i = 0; i < N; ++i) {
result[i] = std::isfinite(value[i]);
if constexpr (std::is_floating_point_v<
typename decltype(value)::value_type>) {
for (std::size_t i = 0; i < value.size(); ++i) {
result.add(std::isfinite(value[i]));
}
return result;
}
return result;
});
}
eval::Value eval::Value::makeUnsigned() const {
using Cond = decltype([](auto type) {
return std::is_integral_v<ComponentType<decltype(type)>> &&
!std::is_same_v<ComponentType<decltype(type)>, bool> &&
!IsArray<decltype(type)>;
});
return visit([](auto value) {
eval::Value result;
using value_type = typename decltype(value)::value_type;
return visit<Cond>(*this, [](auto &&value) -> Value {
constexpr std::size_t N = Components<std::remove_cvref_t<decltype(value)>>;
using T = std::make_unsigned_t<
ComponentType<std::remove_cvref_t<decltype(value)>>>;
if constexpr (N == 1) {
return static_cast<T>(value);
} else {
Vector<T, N> result;
for (std::size_t i = 0; i < N; ++i) {
result[i] = static_cast<T>(value[i]);
if constexpr (std::is_integral_v<value_type> &&
!std::is_same_v<value_type, bool>) {
for (std::size_t i = 0; i < value.size(); ++i) {
result.add(static_cast<std::make_unsigned_t<value_type>>(value[i]));
}
return result;
}
return result;
});
}
eval::Value eval::Value::makeSigned() const {
using Cond = decltype([](auto type) {
return std::is_integral_v<ComponentType<decltype(type)>> &&
!std::is_same_v<ComponentType<decltype(type)>, bool> &&
!IsArray<decltype(type)>;
});
return visit([](auto value) {
eval::Value result;
using value_type = typename decltype(value)::value_type;
return visit<Cond>(*this, [](auto &&value) -> Value {
constexpr std::size_t N = Components<std::remove_cvref_t<decltype(value)>>;
using T =
std::make_signed_t<ComponentType<std::remove_cvref_t<decltype(value)>>>;
if constexpr (N == 1) {
return static_cast<T>(value);
} else {
Vector<T, N> result;
for (std::size_t i = 0; i < N; ++i) {
result[i] = static_cast<T>(value[i]);
if constexpr (std::is_integral_v<value_type> &&
!std::is_same_v<value_type, bool>) {
for (std::size_t i = 0; i < value.size(); ++i) {
result.add(static_cast<std::make_signed_t<value_type>>(value[i]));
}
return result;
}
return result;
});
}
eval::Value eval::Value::all() const {
using Cond = decltype([](auto type) {
return std::is_same_v<ComponentType<decltype(type)>, bool> &&
(Components<decltype(type)> > 1) && !IsArray<decltype(type)>;
});
return visit<Cond>(*this, [](auto &&value) {
constexpr std::size_t N = Components<std::remove_cvref_t<decltype(value)>>;
for (std::size_t i = 0; i < N; ++i) {
if (!value[i]) {
return false;
return visit([](auto value) {
if constexpr (std::is_same_v<typename decltype(value)::value_type, bool>) {
for (std::size_t i = 0; i < value.size(); ++i) {
if (!value[i]) {
return eval::Value(false);
}
}
return eval::Value(true);
}
return true;
return eval::Value();
});
}
eval::Value eval::Value::any() const {
using Cond = decltype([](auto type) {
return std::is_same_v<ComponentType<decltype(type)>, bool> &&
(Components<decltype(type)> > 1) && !IsArray<decltype(type)>;
});
return visit<Cond>(*this, [](auto &&value) {
constexpr std::size_t N = Components<std::remove_cvref_t<decltype(value)>>;
for (std::size_t i = 0; i < N; ++i) {
if (value[i]) {
return true;
return visit([](auto value) {
if constexpr (std::is_same_v<typename decltype(value)::value_type, bool>) {
for (std::size_t i = 0; i < value.size(); ++i) {
if (value[i]) {
return eval::Value(true);
}
}
return eval::Value(false);
}
return false;
return eval::Value();
});
}
eval::Value eval::Value::select(const Value &trueValue,
const Value &falseValue) const {
using Cond = decltype([](auto type) consteval {
return std::is_same_v<ComponentType<decltype(type)>, bool> &&
!IsArray<decltype(type)>;
});
auto optCond = as<bool>();
if (!optCond) {
return {};
}
return visit<Cond>(*this, [&](auto &&cond) -> Value {
using CondType = std::remove_cvref_t<decltype(cond)>;
using TrueCond = decltype([](auto type) consteval {
return Components<decltype(type)> == Components<CondType>;
});
return visit<TrueCond>(trueValue, [&](auto &&trueValue) {
using TrueValue = std::remove_cvref_t<decltype(trueValue)>;
using FalseCond = decltype([](auto type) {
return std::is_same_v<TrueValue, std::remove_cvref_t<decltype(type)>>;
});
return visit(falseValue, [&](auto &&falseValue) -> Value {
if constexpr (std::is_same_v<TrueValue, std::remove_cvref_t<
decltype(falseValue)>>) {
constexpr std::size_t N = Components<CondType>;
if constexpr (N == 1) {
return cond ? trueValue : falseValue;
} else {
Vector<bool, N> result;
for (std::size_t i = 0; i < N; ++i) {
result[i] = cond[i] ? trueValue[i] : falseValue[i];
}
return result;
}
} else {
return {};
}
});
});
});
auto cond = *optCond;
return cond ? trueValue : falseValue;
}
eval::Value eval::Value::iConvert(ir::Value type, bool isSigned) const {
using Cond = decltype([](auto type) {
using Type = std::remove_cvref_t<decltype(type)>;
eval::Value result;
return std::is_integral_v<ComponentType<Type>> &&
!std::is_same_v<bool, ComponentType<Type>> &&
!IsArray<decltype(type)>;
});
using PairCond = decltype([](auto lhs, auto rhs) {
using Lhs = decltype(lhs);
using Rhs = decltype(rhs);
return !std::is_same_v<Lhs, Rhs> && Components<Lhs> == Components<Rhs>;
});
return visitWithType<Cond, Cond, PairCond>(
*this, type, [&](auto type, auto &&value) -> Value {
using Type = std::remove_cvref_t<decltype(type)>;
using ValueType = std::remove_cvref_t<decltype(value)>;
if (isSigned) {
return static_cast<Type>(static_cast<MakeSigned<ValueType>>(value));
} else {
return static_cast<Type>(static_cast<MakeUnsigned<ValueType>>(value));
(isSigned ? makeSigned() : makeUnsigned()).visit([&](auto value) {
invokeWithType(type, [&](auto type) {
if constexpr (std::is_integral_v<decltype(type)> &&
!std::is_same_v<bool, decltype(type)> &&
std::is_integral_v<typename decltype(value)::value_type> &&
!std::is_same_v<bool,
typename decltype(value)::value_type>) {
for (auto item : value) {
result.add(static_cast<decltype(type)>(item));
}
});
}
});
});
return result;
}
eval::Value eval::Value::fConvert(ir::Value type) const {
using Cond = decltype([](auto type) {
return std::is_floating_point_v<ComponentType<decltype(type)>> &&
!IsArray<decltype(type)>;
eval::Value result;
visit([&](auto value) {
invokeWithType(type, [&](auto type) {
if constexpr (std::is_floating_point_v<decltype(type)> &&
std::is_floating_point_v<
typename decltype(value)::value_type>) {
for (auto item : value) {
result.add(static_cast<decltype(type)>(item));
}
}
});
});
using PairCond = decltype([](auto lhs, auto rhs) {
using Lhs = decltype(lhs);
using Rhs = decltype(rhs);
return !std::is_same_v<Lhs, Rhs> && Components<Lhs> == Components<Rhs>;
});
return visitWithType<void, void, PairCond>(
*this, type, [&](auto type, auto &&value) -> Value {
using Type = std::remove_cvref_t<decltype(type)>;
return static_cast<Type>(value);
});
return result;
}
eval::Value eval::Value::bitcast(ir::Value type) const {
using Cond = decltype([](auto type, auto value) {
using Type = std::remove_cvref_t<decltype(type)>;
eval::Value result;
return sizeof(type) == sizeof(value);
auto resultTypeElemCount = getIrTypeConstituents(type);
visit([&](auto value) {
invokeWithType(type, [&](auto resultType) {
if (value.size_bytes() == sizeof(resultType) * resultTypeElemCount) {
result.mTypeIndex = getIrTypeIndex(type);
result.mCount = resultTypeElemCount;
std::memcpy(result.mData, value.data(), value.size_bytes());
}
});
});
return visitWithType<void, void, Cond>(
*this, type, [](auto type, auto &&value) -> Value {
return std::bit_cast<decltype(type)>(value);
});
return result;
}
std::optional<std::uint64_t> eval::Value::zExtScalar() const {
using Cond = decltype([](auto type) {
return std::is_integral_v<ComponentType<decltype(type)>> &&
!std::is_same_v<ComponentType<decltype(type)>, bool> &&
Components<decltype(type)> == 1 && !IsArray<decltype(type)>;
});
auto result = visit<Cond>(*this, [&](auto value) -> Value {
return static_cast<std::uint64_t>(
static_cast<MakeUnsigned<decltype(value)>>(value));
});
if (result) {
return result.as<std::uint64_t>();
if (empty() || size() != 1) {
return {};
}
return {};
return makeUnsigned().visit([](auto value) -> std::optional<std::uint64_t> {
if constexpr (std::is_integral_v<typename decltype(value)::value_type>) {
return static_cast<std::uint64_t>(value[0]);
} else {
return {};
}
});
}
std::optional<std::int64_t> eval::Value::sExtScalar() const {
using Cond = decltype([](auto type) {
return std::is_integral_v<ComponentType<decltype(type)>> &&
!std::is_same_v<ComponentType<decltype(type)>, bool> &&
Components<decltype(type)> == 1 && !IsArray<decltype(type)>;
});
auto result = visit<Cond>(*this, [&](auto value) -> Value {
return static_cast<std::int64_t>(
static_cast<MakeSigned<decltype(value)>>(value));
});
if (result) {
return result.as<std::int64_t>();
if (empty() || size() != 1) {
return {};
}
return {};
return makeSigned().visit([](auto value) -> std::optional<std::int64_t> {
if constexpr (std::is_integral_v<typename decltype(value)::value_type>) {
return static_cast<std::int64_t>(value[0]);
} else {
return {};
}
});
}
#define DEFINE_BINARY_OP(OP) \
eval::Value eval::Value::operator OP(const Value &rhs) const { \
using LhsCond = decltype([](auto &&lhs) { return true; }); \
return visit<LhsCond>(*this, [&]<typename Lhs>(Lhs &&lhs) -> Value { \
using RhsCond = decltype([](auto &&rhs) { \
return requires(Lhs lhs) { static_cast<Value>(lhs OP rhs); }; \
}); \
return visit<RhsCond>(rhs, [&](auto &&rhs) -> Value { \
return static_cast<Value>(lhs OP rhs); \
if (index() != rhs.index() || size() != rhs.size()) { \
return {}; \
} \
eval::Value result; \
visit([&](auto lhsValues) { \
rhs.visit([&](auto rhsValues) { \
if constexpr (requires { lhsValues[0] OP rhsValues[0]; }) { \
if constexpr (std::is_same_v<decltype(lhsValues[0]), \
decltype(rhsValues[0])>) { \
for (std::size_t i = 0; i < lhsValues.size(); ++i) { \
result.add(lhsValues[i] OP rhsValues[i]); \
} \
} \
} \
}); \
}); \
return result; \
}
#define DEFINE_UNARY_OP(OP) \
eval::Value eval::Value::operator OP() const { \
using Cond = decltype([](auto rhs) { \
return requires { static_cast<Value>(OP rhs); }; \
}); \
return visit<Cond>(*this, [&](auto &&rhs) -> Value { \
return static_cast<Value>(OP rhs); \
eval::Value result; \
visit([&](auto values) { \
if constexpr (requires { OP values[0]; }) { \
for (std::size_t i = 0; i < values.size(); ++i) { \
result.add(OP values[i]); \
} \
} \
}); \
return result; \
}
DEFINE_BINARY_OP(+);

View file

@ -1,14 +1,14 @@
#include "transform/route.hpp"
#include "ir/Block.hpp"
#include "transform/merge.hpp"
#include "SpvConverter.hpp"
#include "analyze.hpp"
#include "dialect.hpp"
#include "ir/Block.hpp"
#include "transform/merge.hpp"
#include <functional>
#include <iostream>
#include <sstream>
#include <rx/die.hpp>
#include <sstream>
#include <unordered_map>
#include <unordered_set>
#include <vector>
@ -22,74 +22,64 @@ using Builder = ir::Builder<ir::builtin::Builder, ir::spv::Builder>;
struct RouteBlockData {
std::unordered_map<ir::Block, std::unordered_set<unsigned>> fromSuccessors;
std::unordered_map<ir::Block, std::unordered_set<ir::Block>> toPredecessors;
std::unordered_map<ir::Block, std::unordered_set<ir::Block>> toAllPredecessors;
std::unordered_map<ir::Block, std::unordered_set<ir::Block>>
toAllPredecessors;
std::unordered_set<ir::Block> patchPredecessors;
};
// Helper function to get block name or generate one
static std::string getBlockName(spv::Context& context, ir::Block block) {
auto label = block.getFirst();
auto name = context.ns.tryGetNameOf(label);
return name.empty()
? "unnamed_" + std::to_string((std::uint32_t)label.getInstId())
: std::string(name);
}
// Log detailed information about phi nodes and their predecessors
static void logPhiPredecessorsMismatch(spv::Context& context, ir::Block to, ir::Instruction firstInst) {
static void logPhiPredecessorsMismatch(spv::Context &context, ir::Block to) {
// Get block label and name
auto blockName = getBlockName(context, to);
auto predsCount = getPredecessors(to).size();
for (auto phi = firstInst; phi && (phi == ir::spv::OpPhi); phi = phi.getNext()) {
std::cerr << "[DEBUG] Block '" << blockName << "' (ID: " << (std::uint32_t)to.getInstId() << ") has " << predsCount << " predecessors";
for (auto phi : ir::range(to.getFirst())) {
if (phi != ir::spv::OpPhi) {
break;
}
// Get number of incoming blocks from phi node
auto incomingCount = phi.getOperandCount() / 2;
if (incomingCount == predsCount) {
continue;
}
// Log mismatch if counts differ
if (incomingCount != predsCount) {
std::cerr << "\n Phi ID: " << (std::uint32_t)phi.getInstId() << ", incoming blocks: " << incomingCount;
std::cerr << " *** MISMATCH! Expected: " << predsCount << " ***\n\n";
// Detailed phi node information
std::cerr << " Phi: ";
phi.print(std::cerr, context.ns);
std::cerr << "\n";
std::cerr << "[DEBUG] Block '" << context.ns.getNameOf(to) << "' has "
<< predsCount << " predecessors\n"
<< "incoming blocks: " << incomingCount;
std::cerr << " *** MISMATCH! Expected: " << predsCount << " ***\n\n";
// Print detailed incoming blocks
std::stringstream phiOperands;
auto opts = PrintOptions().nextLevel();
phiOperands << " Value-Blocks: [\n";
// Detailed phi node information
std::cerr << " Phi: ";
phi.print(std::cerr, context.ns);
std::cerr << "\n";
for (std::size_t i = 1; i < phi.getOperandCount(); i += 2) {
auto value = phi.getOperand(i + 0).getAsValue();
auto block = phi.getOperand(i + 1).getAsValue().staticCast<ir::Block>();
// Print detailed incoming blocks
std::stringstream phiOperands;
auto opts = PrintOptions{.identLevel = 3};
phiOperands << " Value-Blocks: [\n";
phiOperands << " ";
value.print(phiOperands, context.ns, opts.nextLevel());
phiOperands << "\n ";
block.print(phiOperands, context.ns, opts.nextLevel());
for (std::size_t i = 1; i < phi.getOperandCount(); i += 2) {
auto value = phi.getOperand(i + 0).getAsValue();
auto block = phi.getOperand(i + 1).getAsValue();
value.print(phiOperands, context.ns, opts);
phiOperands << "\n";
block.print(phiOperands, context.ns, opts);
if (i != phi.getOperandCount() - 1) {
phiOperands << ",\n\n";
}
auto str = phiOperands.str();
if (str.size() >= 3) {
str.pop_back();
str.pop_back();
str.pop_back();
}
std::cerr << str << "]\n";
}
else {
std::cerr << " and matching incoming blocks\n";
}
std::cerr << phiOperands.str() << "]\n";
}
}
// Analyze edges and build routing data structures
static RouteBlockData analyzeEdges(spv::Context &context, const std::vector<Edge> &edges) {
static RouteBlockData analyzeEdges(spv::Context &context,
const std::vector<Edge> &edges) {
RouteBlockData data;
std::unordered_set<ir::Block> routePredecessors;
@ -107,25 +97,25 @@ static RouteBlockData analyzeEdges(spv::Context &context, const std::vector<Edge
}
// Debug logging for mismatches
for (auto& [to, _] : data.toPredecessors) {
logPhiPredecessorsMismatch(context, to, ir::Block(to).getFirst());
for (auto &[to, _] : data.toPredecessors) {
logPhiPredecessorsMismatch(context, to);
}
return data;
}
// Create route block with appropriate phi node
static std::pair<ir::Block, ir::Value> createRouteBlockWithPhi(
spv::Context &context, ir::InsertionPoint insertPoint,
ir::Location loc, size_t predsCount) {
static std::pair<ir::Block, ir::Value>
createRouteBlockWithPhi(spv::Context &context, ir::InsertionPoint insertPoint,
ir::Location loc, size_t predsCount) {
auto route = Builder::create(context, insertPoint).createBlock(loc);
ir::Value routePhi;
if (predsCount > 1) {
routePhi = Builder::createPrepend(context, route)
.createSpvPhi(loc, predsCount == 2
? context.getTypeBool()
: context.getTypeUInt32());
routePhi =
Builder::createPrepend(context, route)
.createSpvPhi(loc, predsCount == 2 ? context.getTypeBool()
: context.getTypeUInt32());
}
return {route, routePhi};
@ -154,8 +144,9 @@ static std::unordered_map<ir::Value, std::uint32_t> createRouteTerminator(
secondSuccessor);
} else {
// Multiple successors: switch statement
auto routeSwitch = Builder::createAppend(context, route)
.createSpvSwitch(loc, routePhi, toPreds.begin()->first);
auto routeSwitch =
Builder::createAppend(context, route)
.createSpvSwitch(loc, routePhi, toPreds.begin()->first);
successorToId.reserve(toPreds.size());
@ -174,8 +165,7 @@ static std::unordered_map<ir::Value, std::uint32_t> createRouteTerminator(
// Get successor ID based on routing strategy
static ir::Value getSuccessorIdValue(
spv::Context &context, ir::Block successor,
const std::unordered_map<ir::Block, std::unordered_set<ir::Block>>
&toPreds,
const std::unordered_map<ir::Block, std::unordered_set<ir::Block>> &toPreds,
const std::unordered_map<ir::Value, std::uint32_t> &successorToId) {
if (toPreds.size() == 2) {
return context.getBool(successor == toPreds.begin()->first);
@ -189,7 +179,7 @@ static void patchPredecessorBlock(
ir::Value routePhi, const RouteBlockData &data,
const std::unordered_map<ir::Block, std::unordered_set<ir::Block>> &toPreds,
const std::function<ir::Value(ir::Block)> &getSuccessorId) {
auto predSuccessors = getAllSuccessors(patchBlock);
auto terminator = getTerminator(patchBlock);
auto &routeSuccessors = data.fromSuccessors.at(patchBlock);
@ -317,7 +307,8 @@ static void patchPredecessorBlock(
}
// Move all phi nodes from target to route block
static void moveAllPhiNodes(spv::Context &context, ir::Block to, ir::Block route,
static void moveAllPhiNodes(spv::Context &context, ir::Block to,
ir::Block route,
const std::unordered_set<ir::Block> &preds,
const std::vector<Edge> &edges) {
for (auto phi : ir::range(ir::Block(to).getFirst())) {
@ -413,7 +404,7 @@ static void processTargetBlocks(
const std::unordered_map<ir::Block, std::unordered_set<ir::Block>> &toPreds,
const std::vector<Edge> &edges,
const std::function<ir::Value(ir::Block)> &getSuccessorId) {
for (auto &[to, preds] : toPreds) {
if (toPreds.size() > 1) {
auto successorId = getSuccessorId(to);
@ -456,8 +447,8 @@ static void processTargetBlocks(
// Main function
ir::Block shader::transform::createRouteBlock(spv::Context &context,
ir::InsertionPoint insertPoint,
const std::vector<Edge> &edges) {
ir::InsertionPoint insertPoint,
const std::vector<Edge> &edges) {
auto loc = context.getUnknownLocation();
rx::dieIf(edges.empty(), "createRouteBlock: unexpected edges count");
@ -472,27 +463,28 @@ ir::Block shader::transform::createRouteBlock(spv::Context &context,
}
// Step 3: Create route block and phi node
auto [route, routePhi] = createRouteBlockWithPhi(context, insertPoint,
loc, data.toPredecessors.size());
auto [route, routePhi] = createRouteBlockWithPhi(context, insertPoint, loc,
data.toPredecessors.size());
// Step 4: Create appropriate terminator (branch/conditional/switch)
auto successorToId = createRouteTerminator(context, route, routePhi,
loc, data.toPredecessors);
auto successorToId =
createRouteTerminator(context, route, routePhi, loc, data.toPredecessors);
// Step 5: Create lambda for getting successor IDs
auto getSuccessorId = [&](ir::Block successor) {
return getSuccessorIdValue(context, successor, data.toPredecessors, successorToId);
return getSuccessorIdValue(context, successor, data.toPredecessors,
successorToId);
};
// Step 6: Patch predecessor blocks that have multiple routes
for (auto patchBlock : data.patchPredecessors) {
patchPredecessorBlock(context, patchBlock, route, routePhi, data,
data.toPredecessors, getSuccessorId);
data.toPredecessors, getSuccessorId);
}
// Step 7: Process target blocks and update phi nodes
processTargetBlocks(context, route, routePhi, data, data.toPredecessors, edges,
getSuccessorId);
processTargetBlocks(context, route, routePhi, data, data.toPredecessors,
edges, getSuccessorId);
return route;
}

View file

@ -469,6 +469,22 @@ static orbis::ErrorCode dce_ioctl(orbis::File *file, std::uint64_t request,
return {};
});
} else if (args->id == 0xb) {
struct VblankStatus {
orbis::uint64_t count;
orbis::uint64_t processTime;
orbis::uint64_t tsc;
char flags;
};
VblankStatus vblankStatus{};
// TODO: lock bridge header
vblankStatus.count = gpuCtx.vblankCount;
vblankStatus.processTime = 0; // TODO
vblankStatus.tsc = 0; // TODO
vblankStatus.flags = 0; // TODO
std::memcpy(args->ptr, &vblankStatus, sizeof(VblankStatus));
} else { // used during open/close
ORBIS_LOG_NOTICE("dce: UNIMPLEMENTED FlipControl", args->id, args->arg2,
args->ptr, args->size);