devtools: pm4 - show program state

This commit is contained in:
Vinicius Rangel 2024-10-06 02:44:08 -03:00
parent 286b6e7f01
commit 46018530e8
No known key found for this signature in database
GPG Key ID: A5B154D904B761D9
11 changed files with 184 additions and 35 deletions

View File

@ -356,6 +356,8 @@ set(DEV_TOOLS src/core/devtools/layer.cpp
src/core/devtools/widget/frame_graph.cpp src/core/devtools/widget/frame_graph.cpp
src/core/devtools/widget/frame_graph.h src/core/devtools/widget/frame_graph.h
src/core/devtools/widget/imgui_memory_editor.h src/core/devtools/widget/imgui_memory_editor.h
src/core/devtools/widget/reg_view.cpp
src/core/devtools/widget/reg_view.h
src/core/devtools/widget/types.h src/core/devtools/widget/types.h
) )

View File

@ -5,9 +5,10 @@
#include "common/native_clock.h" #include "common/native_clock.h"
#include "common/singleton.h" #include "common/singleton.h"
#include "debug_state.h" #include "debug_state.h"
#include "libraries/kernel/event_queues.h" #include "devtools/widget/types.h"
#include "libraries/kernel/time_management.h" #include "libraries/kernel/time_management.h"
#include "libraries/system/msgdialog.h" #include "libraries/system/msgdialog.h"
#include "video_core/amdgpu/pm4_cmds.h"
using namespace DebugStateType; using namespace DebugStateType;
@ -95,8 +96,35 @@ void DebugStateImpl::ResumeGuestThreads() {
} }
void DebugStateImpl::RequestFrameDump(s32 count) { void DebugStateImpl::RequestFrameDump(s32 count) {
ASSERT(!DumpingCurrentFrame());
gnm_frame_dump_request_count = count; gnm_frame_dump_request_count = count;
frame_dump_list.clear(); frame_dump_list.clear();
frame_dump_list.resize(count); frame_dump_list.resize(count);
waiting_submit_pause = true; waiting_submit_pause = true;
} }
void DebugStateImpl::PushQueueDump(QueueDump dump) {
ASSERT(DumpingCurrentFrame());
std::unique_lock lock{frame_dump_list_mutex};
GetFrameDump().queues.push_back(std::move(dump));
}
void DebugStateImpl::PushRegsDump(uintptr_t base_addr, const AmdGpu::Liverpool::Regs& regs) {
ASSERT(DumpingCurrentReg());
std::unique_lock lock{frame_dump_list_mutex};
auto& dump =
frame_dump_list[frame_dump_list.size() - liverpool_dump_request_count].regs[base_addr];
dump.regs = regs;
for (int i = 0; i < RegDump::MaxShaderStages; i++) {
if (regs.stage_enable.IsStageEnabled(i)) {
auto stage = regs.ProgramForStage(i);
if (stage->address_lo != 0) {
auto code = stage->Code();
dump.stages[i] = ShaderDump{
.user_data = *stage,
.code = std::vector<u32>{code.begin(), code.end()},
};
}
}
}
}

View File

@ -9,6 +9,8 @@
#include <queue> #include <queue>
#include "common/types.h" #include "common/types.h"
#include "video_core/amdgpu/liverpool.h"
#include "video_core/renderer_vulkan/vk_pipeline_cache.h"
#ifdef _WIN32 #ifdef _WIN32
#ifndef WIN32_LEAN_AND_MEAN #ifndef WIN32_LEAN_AND_MEAN
@ -45,8 +47,20 @@ struct QueueDump {
uintptr_t base_addr; uintptr_t base_addr;
}; };
struct ShaderDump {
Vulkan::Liverpool::ShaderProgram user_data{};
std::vector<u32> code{};
};
struct RegDump {
static constexpr size_t MaxShaderStages = 5;
Vulkan::Liverpool::Regs regs{};
std::array<ShaderDump, MaxShaderStages> stages{};
};
struct FrameDump { struct FrameDump {
std::vector<QueueDump> queues; std::vector<QueueDump> queues;
std::unordered_map<uintptr_t, RegDump> regs; // address -> reg dump
}; };
class DebugStateImpl { class DebugStateImpl {
@ -71,6 +85,13 @@ class DebugStateImpl {
std::queue<std::string> debug_message_popup; std::queue<std::string> debug_message_popup;
public: public:
void ShowDebugMessage(std::string message) {
if (message.empty()) {
return;
}
debug_message_popup.push(std::move(message));
}
void AddCurrentThreadToGuestList(); void AddCurrentThreadToGuestList();
void RemoveCurrentThreadFromGuestList(); void RemoveCurrentThreadFromGuestList();
@ -110,17 +131,9 @@ public:
return frame_dump_list[frame_dump_list.size() - gnm_frame_dump_request_count]; return frame_dump_list[frame_dump_list.size() - gnm_frame_dump_request_count];
} }
void PushQueueDump(QueueDump dump) { void PushQueueDump(QueueDump dump);
std::unique_lock lock{frame_dump_list_mutex};
GetFrameDump().queues.push_back(std::move(dump));
}
void ShowDebugMessage(std::string message) { void PushRegsDump(uintptr_t base_addr, const AmdGpu::Liverpool::Regs& regs);
if (message.empty()) {
return;
}
debug_message_popup.push(std::move(message));
}
}; };
} // namespace DebugStateType } // namespace DebugStateType

View File

@ -1044,9 +1044,9 @@ void CmdListViewer::OnDispatch(AmdGpu::PM4Type3Header const* header, u32 const*
EndGroup(); EndGroup();
} }
CmdListViewer::CmdListViewer(const std::vector<u32>& cmd_list, uintptr_t base_addr, CmdListViewer::CmdListViewer(const FrameDumpViewer* parent, const std::vector<u32>& cmd_list,
std::string name) uintptr_t base_addr, std::string name)
: base_addr(base_addr), name(std::move(name)) { : parent(parent), base_addr(base_addr), name(std::move(name)) {
using namespace AmdGpu; using namespace AmdGpu;
cmdb_addr = (uintptr_t)cmd_list.data(); cmdb_addr = (uintptr_t)cmd_list.data();
@ -1244,6 +1244,12 @@ void CmdListViewer::Draw() {
if (!group_batches || CollapsingHeader(batch_hdr)) { if (!group_batches || CollapsingHeader(batch_hdr)) {
auto bb = ctx.LastItemData.Rect; auto bb = ctx.LastItemData.Rect;
if (group_batches) { if (group_batches) {
if (IsItemToggledOpen()) {
if (parent->frame_dump.regs.contains(batch.command_addr)) {
batch_view.data = parent->frame_dump.regs.at(batch.command_addr);
batch_view.open = true;
}
}
Indent(); Indent();
} }
auto const batch_sz = batch.end_addr - batch.start_addr; auto const batch_sz = batch.end_addr - batch.start_addr;
@ -1262,19 +1268,19 @@ void CmdListViewer::Draw() {
Gcn::GetOpCodeName((u32)op)); Gcn::GetOpCodeName((u32)op));
if (TreeNode(header_name)) { if (TreeNode(header_name)) {
bool just_opened = IsItemToggledOpen(); const bool just_opened = IsItemToggledOpen();
if (just_opened) {
// Editor
cmdb_view.GotoAddrAndHighlight(
reinterpret_cast<size_t>(pm4_hdr) - cmdb_addr,
reinterpret_cast<size_t>(pm4_hdr) - cmdb_addr +
(pm4_hdr->count + 2) * 4);
}
if (BeginTable("split", 1)) { if (BeginTable("split", 1)) {
TableNextColumn(); TableNextColumn();
Text("size: %d", pm4_hdr->count + 1); Text("size: %d", pm4_hdr->count + 1);
if (just_opened) {
// Editor
cmdb_view.GotoAddrAndHighlight(
reinterpret_cast<size_t>(pm4_hdr) - cmdb_addr,
reinterpret_cast<size_t>(pm4_hdr) - cmdb_addr +
(pm4_hdr->count + 2) * 4);
}
auto const* it_body = auto const* it_body =
reinterpret_cast<uint32_t const*>(pm4_hdr + 1); reinterpret_cast<uint32_t const*>(pm4_hdr + 1);
@ -1367,6 +1373,18 @@ void CmdListViewer::Draw() {
} }
End(); End();
} }
if (batch_view.open) {
batch_view.Draw();
}
for (auto it = extra_batch_view.begin(); it != extra_batch_view.end(); ++it) {
if (!it->open) {
it = extra_batch_view.erase(it);
continue;
}
it->Draw();
++it;
}
} }
} // namespace Core::Devtools::Widget } // namespace Core::Devtools::Widget

View File

@ -10,8 +10,8 @@
#include "common/types.h" #include "common/types.h"
#include "imgui_memory_editor.h" #include "imgui_memory_editor.h"
#include "reg_view.h"
#include "types.h" #include "types.h"
#include "video_core/buffer_cache/buffer_cache.h"
namespace AmdGpu { namespace AmdGpu {
union PM4Type3Header; union PM4Type3Header;
@ -24,6 +24,8 @@ class FrameDumpViewer;
class CmdListViewer { class CmdListViewer {
const FrameDumpViewer* parent;
uintptr_t base_addr; uintptr_t base_addr;
std::string name; std::string name;
std::vector<GPUEvent> events{}; std::vector<GPUEvent> events{};
@ -37,6 +39,9 @@ class CmdListViewer {
int vqid{255}; int vqid{255};
s32 highlight_batch{-1}; s32 highlight_batch{-1};
RegView batch_view;
std::vector<RegView> extra_batch_view;
void OnNop(AmdGpu::PM4Type3Header const* header, u32 const* body); void OnNop(AmdGpu::PM4Type3Header const* header, u32 const* body);
void OnSetBase(AmdGpu::PM4Type3Header const* header, u32 const* body); void OnSetBase(AmdGpu::PM4Type3Header const* header, u32 const* body);
void OnSetContextReg(AmdGpu::PM4Type3Header const* header, u32 const* body); void OnSetContextReg(AmdGpu::PM4Type3Header const* header, u32 const* body);
@ -47,8 +52,8 @@ public:
static void LoadConfig(const char* line); static void LoadConfig(const char* line);
static void SerializeConfig(ImGuiTextBuffer* buf); static void SerializeConfig(ImGuiTextBuffer* buf);
explicit CmdListViewer(const std::vector<u32>& cmd_list, uintptr_t base_addr = 0, explicit CmdListViewer(const FrameDumpViewer* parent, const std::vector<u32>& cmd_list,
std::string name = ""); uintptr_t base_addr = 0, std::string name = "");
void Draw(); void Draw();
}; };

View File

@ -49,7 +49,7 @@ FrameDumpViewer::FrameDumpViewer(FrameDump _frame_dump) : frame_dump(std::move(_
const auto fname = const auto fname =
fmt::format("{}_{}_{:02}_{:02}", id, magic_enum::enum_name(selected_queue_type), fmt::format("{}_{}_{:02}_{:02}", id, magic_enum::enum_name(selected_queue_type),
selected_submit_num, selected_queue_num2); selected_submit_num, selected_queue_num2);
cmd_list_viewer.emplace_back(cmd.data, cmd.base_addr, fname); cmd_list_viewer.emplace_back(this, cmd.data, cmd.base_addr, fname);
if (cmd.type == QueueType::dcb && cmd.submit_num == selected_submit_num && if (cmd.type == QueueType::dcb && cmd.submit_num == selected_submit_num &&
cmd.num2 == selected_queue_num2) { cmd.num2 == selected_queue_num2) {
selected_cmd = cmd_list_viewer.size() - 1; selected_cmd = cmd_list_viewer.size() - 1;

View File

@ -0,0 +1,37 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <imgui.h>
#include "reg_view.h"
using namespace ImGui;
namespace Core::Devtools::Widget {
RegView::RegView() {
static int unique_id = 0;
id = unique_id++;
}
void RegView::Draw() {
char name[32];
snprintf(name, sizeof(name), "Reg view###reg_dump_%d", id);
if (Begin(name, &open, ImGuiWindowFlags_NoSavedSettings)) {
if (BeginTable("Enable shaders", 2)) {
for (int i = 0; i < DebugStateType::RegDump::MaxShaderStages; i++) {
TableNextRow();
TableSetColumnIndex(0);
const char* names[] = {"vs", "ps", "gs", "es", "hs", "ls"};
Text("%s", names[i]);
TableSetColumnIndex(1);
Text("%X", data.regs.stage_enable.IsStageEnabled(i));
TableSetColumnIndex(0);
}
EndTable();
}
}
End();
}
} // namespace Core::Devtools::Widget

View File

@ -0,0 +1,22 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "core/debug_state.h"
namespace Core::Devtools::Widget {
class RegView {
int id;
public:
bool open = false;
DebugStateType::RegDump data;
RegView();
void Draw();
};
} // namespace Core::Devtools::Widget

View File

@ -505,6 +505,11 @@ void PS4_SYSV_ABI sceGnmDingDong(u32 gnm_vqid, u32 next_offs_dw) {
: (asc_queue.ring_size_dw << 2u) - *asc_queue.read_addr; : (asc_queue.ring_size_dw << 2u) - *asc_queue.read_addr;
const std::span acb_span{acb_ptr, acb_size >> 2u}; const std::span acb_span{acb_ptr, acb_size >> 2u};
liverpool->SubmitAsc(vqid, acb_span);
*asc_queue.read_addr += acb_size;
*asc_queue.read_addr %= asc_queue.ring_size_dw * 4;
if (DebugState.DumpingCurrentFrame()) { if (DebugState.DumpingCurrentFrame()) {
static auto last_frame_num = -1LL; static auto last_frame_num = -1LL;
static u32 seq_num{}; static u32 seq_num{};
@ -537,11 +542,6 @@ void PS4_SYSV_ABI sceGnmDingDong(u32 gnm_vqid, u32 next_offs_dw) {
.base_addr = base_addr, .base_addr = base_addr,
}); });
} }
liverpool->SubmitAsc(vqid, acb_span);
*asc_queue.read_addr += acb_size;
*asc_queue.read_addr %= asc_queue.ring_size_dw * 4;
} }
int PS4_SYSV_ABI sceGnmDingDongForWorkload() { int PS4_SYSV_ABI sceGnmDingDongForWorkload() {
@ -2165,6 +2165,7 @@ s32 PS4_SYSV_ABI sceGnmSubmitCommandBuffers(u32 count, const u32* dcb_gpu_addrs[
const auto& dcb_span = std::span{dcb_gpu_addrs[cbpair], dcb_size_dw}; const auto& dcb_span = std::span{dcb_gpu_addrs[cbpair], dcb_size_dw};
const auto& ccb_span = std::span{ccb, ccb_size_dw}; const auto& ccb_span = std::span{ccb, ccb_size_dw};
liverpool->SubmitGfx(dcb_span, ccb_span);
if (DebugState.DumpingCurrentFrame()) { if (DebugState.DumpingCurrentFrame()) {
static auto last_frame_num = -1LL; static auto last_frame_num = -1LL;
static u32 seq_num{}; static u32 seq_num{};
@ -2192,8 +2193,6 @@ s32 PS4_SYSV_ABI sceGnmSubmitCommandBuffers(u32 count, const u32* dcb_gpu_addrs[
.base_addr = reinterpret_cast<uintptr_t>(ccb), .base_addr = reinterpret_cast<uintptr_t>(ccb),
}); });
} }
liverpool->SubmitGfx(dcb_span, ccb_span);
} }
return ORBIS_OK; return ORBIS_OK;

View File

@ -6,6 +6,7 @@
#include "common/debug.h" #include "common/debug.h"
#include "common/polyfill_thread.h" #include "common/polyfill_thread.h"
#include "common/thread.h" #include "common/thread.h"
#include "core/debug_state.h"
#include "core/libraries/videoout/driver.h" #include "core/libraries/videoout/driver.h"
#include "video_core/amdgpu/liverpool.h" #include "video_core/amdgpu/liverpool.h"
#include "video_core/amdgpu/pm4_cmds.h" #include "video_core/amdgpu/pm4_cmds.h"
@ -359,6 +360,9 @@ Liverpool::Task Liverpool::ProcessGraphics(std::span<const u32> dcb, std::span<c
regs.index_base_address.base_addr_hi.Assign(draw_index->index_base_hi); regs.index_base_address.base_addr_hi.Assign(draw_index->index_base_hi);
regs.num_indices = draw_index->index_count; regs.num_indices = draw_index->index_count;
regs.draw_initiator = draw_index->draw_initiator; regs.draw_initiator = draw_index->draw_initiator;
if (DebugState.DumpingCurrentReg()) {
DebugState.PushRegsDump(reinterpret_cast<uintptr_t>(header), regs);
}
if (rasterizer) { if (rasterizer) {
const auto cmd_address = reinterpret_cast<const void*>(header); const auto cmd_address = reinterpret_cast<const void*>(header);
rasterizer->ScopeMarkerBegin(fmt::format("dcb:{}:DrawIndex2", cmd_address)); rasterizer->ScopeMarkerBegin(fmt::format("dcb:{}:DrawIndex2", cmd_address));
@ -373,6 +377,9 @@ Liverpool::Task Liverpool::ProcessGraphics(std::span<const u32> dcb, std::span<c
regs.max_index_size = draw_index_off->max_size; regs.max_index_size = draw_index_off->max_size;
regs.num_indices = draw_index_off->index_count; regs.num_indices = draw_index_off->index_count;
regs.draw_initiator = draw_index_off->draw_initiator; regs.draw_initiator = draw_index_off->draw_initiator;
if (DebugState.DumpingCurrentReg()) {
DebugState.PushRegsDump(reinterpret_cast<uintptr_t>(header), regs);
}
if (rasterizer) { if (rasterizer) {
const auto cmd_address = reinterpret_cast<const void*>(header); const auto cmd_address = reinterpret_cast<const void*>(header);
rasterizer->ScopeMarkerBegin( rasterizer->ScopeMarkerBegin(
@ -386,6 +393,9 @@ Liverpool::Task Liverpool::ProcessGraphics(std::span<const u32> dcb, std::span<c
const auto* draw_index = reinterpret_cast<const PM4CmdDrawIndexAuto*>(header); const auto* draw_index = reinterpret_cast<const PM4CmdDrawIndexAuto*>(header);
regs.num_indices = draw_index->index_count; regs.num_indices = draw_index->index_count;
regs.draw_initiator = draw_index->draw_initiator; regs.draw_initiator = draw_index->draw_initiator;
if (DebugState.DumpingCurrentReg()) {
DebugState.PushRegsDump(reinterpret_cast<uintptr_t>(header), regs);
}
if (rasterizer) { if (rasterizer) {
const auto cmd_address = reinterpret_cast<const void*>(header); const auto cmd_address = reinterpret_cast<const void*>(header);
rasterizer->ScopeMarkerBegin(fmt::format("dcb:{}:DrawIndexAuto", cmd_address)); rasterizer->ScopeMarkerBegin(fmt::format("dcb:{}:DrawIndexAuto", cmd_address));
@ -399,6 +409,9 @@ Liverpool::Task Liverpool::ProcessGraphics(std::span<const u32> dcb, std::span<c
const auto offset = draw_indirect->data_offset; const auto offset = draw_indirect->data_offset;
const auto ib_address = mapped_queues[GfxQueueId].indirect_args_addr; const auto ib_address = mapped_queues[GfxQueueId].indirect_args_addr;
const auto size = sizeof(PM4CmdDrawIndirect::DrawInstancedArgs); const auto size = sizeof(PM4CmdDrawIndirect::DrawInstancedArgs);
if (DebugState.DumpingCurrentReg()) {
DebugState.PushRegsDump(reinterpret_cast<uintptr_t>(header), regs);
}
if (rasterizer) { if (rasterizer) {
const auto cmd_address = reinterpret_cast<const void*>(header); const auto cmd_address = reinterpret_cast<const void*>(header);
rasterizer->ScopeMarkerBegin(fmt::format("dcb:{}:DrawIndirect", cmd_address)); rasterizer->ScopeMarkerBegin(fmt::format("dcb:{}:DrawIndirect", cmd_address));
@ -413,6 +426,9 @@ Liverpool::Task Liverpool::ProcessGraphics(std::span<const u32> dcb, std::span<c
const auto offset = draw_index_indirect->data_offset; const auto offset = draw_index_indirect->data_offset;
const auto ib_address = mapped_queues[GfxQueueId].indirect_args_addr; const auto ib_address = mapped_queues[GfxQueueId].indirect_args_addr;
const auto size = sizeof(PM4CmdDrawIndexIndirect::DrawIndexInstancedArgs); const auto size = sizeof(PM4CmdDrawIndexIndirect::DrawIndexInstancedArgs);
if (DebugState.DumpingCurrentReg()) {
DebugState.PushRegsDump(reinterpret_cast<uintptr_t>(header), regs);
}
if (rasterizer) { if (rasterizer) {
const auto cmd_address = reinterpret_cast<const void*>(header); const auto cmd_address = reinterpret_cast<const void*>(header);
rasterizer->ScopeMarkerBegin( rasterizer->ScopeMarkerBegin(
@ -428,6 +444,9 @@ Liverpool::Task Liverpool::ProcessGraphics(std::span<const u32> dcb, std::span<c
regs.cs_program.dim_y = dispatch_direct->dim_y; regs.cs_program.dim_y = dispatch_direct->dim_y;
regs.cs_program.dim_z = dispatch_direct->dim_z; regs.cs_program.dim_z = dispatch_direct->dim_z;
regs.cs_program.dispatch_initiator = dispatch_direct->dispatch_initiator; regs.cs_program.dispatch_initiator = dispatch_direct->dispatch_initiator;
if (DebugState.DumpingCurrentReg()) {
DebugState.PushRegsDump(reinterpret_cast<uintptr_t>(header), regs);
}
if (rasterizer && (regs.cs_program.dispatch_initiator & 1)) { if (rasterizer && (regs.cs_program.dispatch_initiator & 1)) {
const auto cmd_address = reinterpret_cast<const void*>(header); const auto cmd_address = reinterpret_cast<const void*>(header);
rasterizer->ScopeMarkerBegin(fmt::format("dcb:{}:Dispatch", cmd_address)); rasterizer->ScopeMarkerBegin(fmt::format("dcb:{}:Dispatch", cmd_address));
@ -442,6 +461,9 @@ Liverpool::Task Liverpool::ProcessGraphics(std::span<const u32> dcb, std::span<c
const auto offset = dispatch_indirect->data_offset; const auto offset = dispatch_indirect->data_offset;
const auto ib_address = mapped_queues[GfxQueueId].indirect_args_addr; const auto ib_address = mapped_queues[GfxQueueId].indirect_args_addr;
const auto size = sizeof(PM4CmdDispatchIndirect::GroupDimensions); const auto size = sizeof(PM4CmdDispatchIndirect::GroupDimensions);
if (DebugState.DumpingCurrentReg()) {
DebugState.PushRegsDump(reinterpret_cast<uintptr_t>(header), regs);
}
if (rasterizer && (regs.cs_program.dispatch_initiator & 1)) { if (rasterizer && (regs.cs_program.dispatch_initiator & 1)) {
const auto cmd_address = reinterpret_cast<const void*>(header); const auto cmd_address = reinterpret_cast<const void*>(header);
rasterizer->ScopeMarkerBegin( rasterizer->ScopeMarkerBegin(
@ -620,6 +642,9 @@ Liverpool::Task Liverpool::ProcessCompute(std::span<const u32> acb, int vqid) {
regs.cs_program.dim_y = dispatch_direct->dim_y; regs.cs_program.dim_y = dispatch_direct->dim_y;
regs.cs_program.dim_z = dispatch_direct->dim_z; regs.cs_program.dim_z = dispatch_direct->dim_z;
regs.cs_program.dispatch_initiator = dispatch_direct->dispatch_initiator; regs.cs_program.dispatch_initiator = dispatch_direct->dispatch_initiator;
if (DebugState.DumpingCurrentReg()) {
DebugState.PushRegsDump(reinterpret_cast<uintptr_t>(header), regs);
}
if (rasterizer && (regs.cs_program.dispatch_initiator & 1)) { if (rasterizer && (regs.cs_program.dispatch_initiator & 1)) {
const auto cmd_address = reinterpret_cast<const void*>(header); const auto cmd_address = reinterpret_cast<const void*>(header);
rasterizer->ScopeMarkerBegin(fmt::format("acb[{}]:{}:Dispatch", vqid, cmd_address)); rasterizer->ScopeMarkerBegin(fmt::format("acb[{}]:{}:Dispatch", vqid, cmd_address));

View File

@ -935,7 +935,7 @@ struct Liverpool {
BitField<5, 1, u32> gs_en; BitField<5, 1, u32> gs_en;
BitField<6, 1, u32> vs_en; BitField<6, 1, u32> vs_en;
bool IsStageEnabled(u32 stage) { bool IsStageEnabled(u32 stage) const {
switch (stage) { switch (stage) {
case 0: case 0:
case 1: case 1: