diff options
Diffstat (limited to '')
| -rw-r--r-- | src/citra_qt/bootmanager.cpp | 1 | ||||
| -rw-r--r-- | src/citra_qt/debugger/graphics.cpp | 16 | ||||
| -rw-r--r-- | src/core/core.cpp | 20 | ||||
| -rw-r--r-- | src/core/hle/kernel/address_arbiter.cpp | 3 | ||||
| -rw-r--r-- | src/core/hle/kernel/event.cpp | 2 | ||||
| -rw-r--r-- | src/core/hle/kernel/mutex.cpp | 2 | ||||
| -rw-r--r-- | src/core/hle/kernel/thread.cpp | 6 | ||||
| -rw-r--r-- | src/core/hle/kernel/thread.h | 6 | ||||
| -rw-r--r-- | src/core/hle/service/gsp.cpp | 148 | ||||
| -rw-r--r-- | src/core/hle/service/gsp.h | 73 | ||||
| -rw-r--r-- | src/core/hle/service/srv.cpp | 18 | ||||
| -rw-r--r-- | src/core/hle/svc.cpp | 2 | ||||
| -rw-r--r-- | src/core/hw/gpu.cpp | 27 | ||||
| -rw-r--r-- | src/core/mem_map_funcs.cpp | 2 | ||||
| -rw-r--r-- | src/video_core/gpu_debugger.h | 12 |
15 files changed, 231 insertions, 107 deletions
diff --git a/src/citra_qt/bootmanager.cpp b/src/citra_qt/bootmanager.cpp index 421b2af55..a162e6dfe 100644 --- a/src/citra_qt/bootmanager.cpp +++ b/src/citra_qt/bootmanager.cpp | |||
| @@ -42,7 +42,6 @@ void EmuThread::run() | |||
| 42 | emit CPUStepped(); | 42 | emit CPUStepped(); |
| 43 | } | 43 | } |
| 44 | } | 44 | } |
| 45 | HW::Update(); | ||
| 46 | } | 45 | } |
| 47 | 46 | ||
| 48 | Core::Stop(); | 47 | Core::Stop(); |
diff --git a/src/citra_qt/debugger/graphics.cpp b/src/citra_qt/debugger/graphics.cpp index 0f911a015..a86a55404 100644 --- a/src/citra_qt/debugger/graphics.cpp +++ b/src/citra_qt/debugger/graphics.cpp | |||
| @@ -25,16 +25,16 @@ QVariant GPUCommandStreamItemModel::data(const QModelIndex& index, int role) con | |||
| 25 | return QVariant(); | 25 | return QVariant(); |
| 26 | 26 | ||
| 27 | int command_index = index.row(); | 27 | int command_index = index.row(); |
| 28 | const GSP_GPU::GXCommand& command = GetDebugger()->ReadGXCommandHistory(command_index); | 28 | const GSP_GPU::Command& command = GetDebugger()->ReadGXCommandHistory(command_index); |
| 29 | if (role == Qt::DisplayRole) | 29 | if (role == Qt::DisplayRole) |
| 30 | { | 30 | { |
| 31 | std::map<GSP_GPU::GXCommandId, const char*> command_names = { | 31 | std::map<GSP_GPU::CommandId, const char*> command_names = { |
| 32 | { GSP_GPU::GXCommandId::REQUEST_DMA, "REQUEST_DMA" }, | 32 | { GSP_GPU::CommandId::REQUEST_DMA, "REQUEST_DMA" }, |
| 33 | { GSP_GPU::GXCommandId::SET_COMMAND_LIST_FIRST, "SET_COMMAND_LIST_FIRST" }, | 33 | { GSP_GPU::CommandId::SET_COMMAND_LIST_FIRST, "SET_COMMAND_LIST_FIRST" }, |
| 34 | { GSP_GPU::GXCommandId::SET_MEMORY_FILL, "SET_MEMORY_FILL" }, | 34 | { GSP_GPU::CommandId::SET_MEMORY_FILL, "SET_MEMORY_FILL" }, |
| 35 | { GSP_GPU::GXCommandId::SET_DISPLAY_TRANSFER, "SET_DISPLAY_TRANSFER" }, | 35 | { GSP_GPU::CommandId::SET_DISPLAY_TRANSFER, "SET_DISPLAY_TRANSFER" }, |
| 36 | { GSP_GPU::GXCommandId::SET_TEXTURE_COPY, "SET_TEXTURE_COPY" }, | 36 | { GSP_GPU::CommandId::SET_TEXTURE_COPY, "SET_TEXTURE_COPY" }, |
| 37 | { GSP_GPU::GXCommandId::SET_COMMAND_LIST_LAST, "SET_COMMAND_LIST_LAST" } | 37 | { GSP_GPU::CommandId::SET_COMMAND_LIST_LAST, "SET_COMMAND_LIST_LAST" } |
| 38 | }; | 38 | }; |
| 39 | const u32* command_data = reinterpret_cast<const u32*>(&command); | 39 | const u32* command_data = reinterpret_cast<const u32*>(&command); |
| 40 | QString str = QString("%1 %2 %3 %4 %5 %6 %7 %8 %9").arg(command_names[command.id]) | 40 | QString str = QString("%1 %2 %3 %4 %5 %6 %7 %8 %9").arg(command_names[command.id]) |
diff --git a/src/core/core.cpp b/src/core/core.cpp index 7dc0809d0..fc9909377 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp | |||
| @@ -26,21 +26,25 @@ ARM_Interface* g_sys_core = nullptr; ///< ARM11 system (OS) core | |||
| 26 | /// Run the core CPU loop | 26 | /// Run the core CPU loop |
| 27 | void RunLoop() { | 27 | void RunLoop() { |
| 28 | for (;;){ | 28 | for (;;){ |
| 29 | g_app_core->Run(GPU::kFrameTicks); | 29 | // This function loops for 100 instructions in the CPU before trying to update hardware. |
| 30 | // This is a little bit faster than SingleStep, and should be pretty much equivalent. The | ||
| 31 | // number of instructions chosen is fairly arbitrary, however a large number will more | ||
| 32 | // drastically affect the frequency of GSP interrupts and likely break things. The point of | ||
| 33 | // this is to just loop in the CPU for more than 1 instruction to reduce overhead and make | ||
| 34 | // it a little bit faster... | ||
| 35 | g_app_core->Run(100); | ||
| 30 | HW::Update(); | 36 | HW::Update(); |
| 31 | Kernel::Reschedule(); | 37 | if (HLE::g_reschedule) { |
| 38 | Kernel::Reschedule(); | ||
| 39 | } | ||
| 32 | } | 40 | } |
| 33 | } | 41 | } |
| 34 | 42 | ||
| 35 | /// Step the CPU one instruction | 43 | /// Step the CPU one instruction |
| 36 | void SingleStep() { | 44 | void SingleStep() { |
| 37 | g_app_core->Step(); | 45 | g_app_core->Step(); |
| 38 | 46 | HW::Update(); | |
| 39 | // Update and reschedule after approx. 1 frame | 47 | if (HLE::g_reschedule) { |
| 40 | u64 current_ticks = Core::g_app_core->GetTicks(); | ||
| 41 | if ((current_ticks - g_last_ticks) >= GPU::kFrameTicks || HLE::g_reschedule) { | ||
| 42 | g_last_ticks = current_ticks; | ||
| 43 | HW::Update(); | ||
| 44 | Kernel::Reschedule(); | 48 | Kernel::Reschedule(); |
| 45 | } | 49 | } |
| 46 | } | 50 | } |
diff --git a/src/core/hle/kernel/address_arbiter.cpp b/src/core/hle/kernel/address_arbiter.cpp index 61717bbe4..bdf76e0c2 100644 --- a/src/core/hle/kernel/address_arbiter.cpp +++ b/src/core/hle/kernel/address_arbiter.cpp | |||
| @@ -53,7 +53,7 @@ Result ArbitrateAddress(Handle handle, ArbitrationType type, u32 address, s32 va | |||
| 53 | for(int i = 0; i < value; i++) | 53 | for(int i = 0; i < value; i++) |
| 54 | ArbitrateHighestPriorityThread(handle, address); | 54 | ArbitrateHighestPriorityThread(handle, address); |
| 55 | } | 55 | } |
| 56 | HLE::Reschedule(__func__); | 56 | break; |
| 57 | 57 | ||
| 58 | // Wait current thread (acquire the arbiter)... | 58 | // Wait current thread (acquire the arbiter)... |
| 59 | case ArbitrationType::WaitIfLessThan: | 59 | case ArbitrationType::WaitIfLessThan: |
| @@ -61,6 +61,7 @@ Result ArbitrateAddress(Handle handle, ArbitrationType type, u32 address, s32 va | |||
| 61 | Kernel::WaitCurrentThread(WAITTYPE_ARB, handle); | 61 | Kernel::WaitCurrentThread(WAITTYPE_ARB, handle); |
| 62 | HLE::Reschedule(__func__); | 62 | HLE::Reschedule(__func__); |
| 63 | } | 63 | } |
| 64 | break; | ||
| 64 | 65 | ||
| 65 | default: | 66 | default: |
| 66 | ERROR_LOG(KERNEL, "unknown type=%d", type); | 67 | ERROR_LOG(KERNEL, "unknown type=%d", type); |
diff --git a/src/core/hle/kernel/event.cpp b/src/core/hle/kernel/event.cpp index 127c0cfc6..1e417e09c 100644 --- a/src/core/hle/kernel/event.cpp +++ b/src/core/hle/kernel/event.cpp | |||
| @@ -42,7 +42,7 @@ public: | |||
| 42 | if (std::find(waiting_threads.begin(), waiting_threads.end(), thread) == waiting_threads.end()) { | 42 | if (std::find(waiting_threads.begin(), waiting_threads.end(), thread) == waiting_threads.end()) { |
| 43 | waiting_threads.push_back(thread); | 43 | waiting_threads.push_back(thread); |
| 44 | } | 44 | } |
| 45 | Kernel::WaitCurrentThread(WAITTYPE_EVENT); | 45 | Kernel::WaitCurrentThread(WAITTYPE_EVENT, GetHandle()); |
| 46 | } | 46 | } |
| 47 | if (reset_type != RESETTYPE_STICKY && !permanent_locked) { | 47 | if (reset_type != RESETTYPE_STICKY && !permanent_locked) { |
| 48 | locked = true; | 48 | locked = true; |
diff --git a/src/core/hle/kernel/mutex.cpp b/src/core/hle/kernel/mutex.cpp index 1ccf1eb73..055f503f9 100644 --- a/src/core/hle/kernel/mutex.cpp +++ b/src/core/hle/kernel/mutex.cpp | |||
| @@ -48,7 +48,7 @@ public: | |||
| 48 | *wait = locked; | 48 | *wait = locked; |
| 49 | 49 | ||
| 50 | if (locked) { | 50 | if (locked) { |
| 51 | Kernel::WaitCurrentThread(WAITTYPE_MUTEX); | 51 | Kernel::WaitCurrentThread(WAITTYPE_MUTEX, GetHandle()); |
| 52 | } | 52 | } |
| 53 | 53 | ||
| 54 | return 0; | 54 | return 0; |
diff --git a/src/core/hle/kernel/thread.cpp b/src/core/hle/kernel/thread.cpp index 86bbf29d0..1d7ded6f6 100644 --- a/src/core/hle/kernel/thread.cpp +++ b/src/core/hle/kernel/thread.cpp | |||
| @@ -274,7 +274,11 @@ Thread* NextThread() { | |||
| 274 | return Kernel::g_object_pool.GetFast<Thread>(next); | 274 | return Kernel::g_object_pool.GetFast<Thread>(next); |
| 275 | } | 275 | } |
| 276 | 276 | ||
| 277 | /// Puts the current thread in the wait state for the given type | 277 | /** |
| 278 | * Puts the current thread in the wait state for the given type | ||
| 279 | * @param wait_type Type of wait | ||
| 280 | * @param wait_handle Handle of Kernel object that we are waiting on, defaults to current thread | ||
| 281 | */ | ||
| 278 | void WaitCurrentThread(WaitType wait_type, Handle wait_handle) { | 282 | void WaitCurrentThread(WaitType wait_type, Handle wait_handle) { |
| 279 | Thread* thread = GetCurrentThread(); | 283 | Thread* thread = GetCurrentThread(); |
| 280 | thread->wait_type = wait_type; | 284 | thread->wait_type = wait_type; |
diff --git a/src/core/hle/kernel/thread.h b/src/core/hle/kernel/thread.h index f2bfdfa1a..39fa38b75 100644 --- a/src/core/hle/kernel/thread.h +++ b/src/core/hle/kernel/thread.h | |||
| @@ -69,7 +69,11 @@ void ArbitrateAllThreads(u32 arbiter, u32 address); | |||
| 69 | /// Gets the current thread handle | 69 | /// Gets the current thread handle |
| 70 | Handle GetCurrentThreadHandle(); | 70 | Handle GetCurrentThreadHandle(); |
| 71 | 71 | ||
| 72 | /// Puts the current thread in the wait state for the given type | 72 | /** |
| 73 | * Puts the current thread in the wait state for the given type | ||
| 74 | * @param wait_type Type of wait | ||
| 75 | * @param wait_handle Handle of Kernel object that we are waiting on, defaults to current thread | ||
| 76 | */ | ||
| 73 | void WaitCurrentThread(WaitType wait_type, Handle wait_handle=GetCurrentThreadHandle()); | 77 | void WaitCurrentThread(WaitType wait_type, Handle wait_handle=GetCurrentThreadHandle()); |
| 74 | 78 | ||
| 75 | /// Put current thread in a wait state - on WaitSynchronization | 79 | /// Put current thread in a wait state - on WaitSynchronization |
diff --git a/src/core/hle/service/gsp.cpp b/src/core/hle/service/gsp.cpp index b20203e27..e241b31c8 100644 --- a/src/core/hle/service/gsp.cpp +++ b/src/core/hle/service/gsp.cpp | |||
| @@ -11,55 +11,35 @@ | |||
| 11 | #include "core/hle/kernel/event.h" | 11 | #include "core/hle/kernel/event.h" |
| 12 | #include "core/hle/kernel/shared_memory.h" | 12 | #include "core/hle/kernel/shared_memory.h" |
| 13 | #include "core/hle/service/gsp.h" | 13 | #include "core/hle/service/gsp.h" |
| 14 | |||
| 15 | #include "core/hw/gpu.h" | 14 | #include "core/hw/gpu.h" |
| 16 | 15 | ||
| 17 | #include "video_core/gpu_debugger.h" | 16 | #include "video_core/gpu_debugger.h" |
| 18 | 17 | ||
| 19 | //////////////////////////////////////////////////////////////////////////////////////////////////// | ||
| 20 | |||
| 21 | // Main graphics debugger object - TODO: Here is probably not the best place for this | 18 | // Main graphics debugger object - TODO: Here is probably not the best place for this |
| 22 | GraphicsDebugger g_debugger; | 19 | GraphicsDebugger g_debugger; |
| 23 | 20 | ||
| 24 | /// GSP shared memory GX command buffer header | ||
| 25 | union GX_CmdBufferHeader { | ||
| 26 | u32 hex; | ||
| 27 | |||
| 28 | // Current command index. This index is updated by GSP module after loading the command data, | ||
| 29 | // right before the command is processed. When this index is updated by GSP module, the total | ||
| 30 | // commands field is decreased by one as well. | ||
| 31 | BitField<0,8,u32> index; | ||
| 32 | |||
| 33 | // Total commands to process, must not be value 0 when GSP module handles commands. This must be | ||
| 34 | // <=15 when writing a command to shared memory. This is incremented by the application when | ||
| 35 | // writing a command to shared memory, after increasing this value TriggerCmdReqQueue is only | ||
| 36 | // used if this field is value 1. | ||
| 37 | BitField<8,8,u32> number_commands; | ||
| 38 | }; | ||
| 39 | |||
| 40 | //////////////////////////////////////////////////////////////////////////////////////////////////// | 21 | //////////////////////////////////////////////////////////////////////////////////////////////////// |
| 41 | // Namespace GSP_GPU | 22 | // Namespace GSP_GPU |
| 42 | 23 | ||
| 43 | namespace GSP_GPU { | 24 | namespace GSP_GPU { |
| 44 | 25 | ||
| 45 | Handle g_event = 0; | 26 | Handle g_interrupt_event = 0; ///< Handle to event triggered when GSP interrupt has been signalled |
| 46 | Handle g_shared_memory = 0; | 27 | Handle g_shared_memory = 0; ///< Handle to GSP shared memorys |
| 28 | u32 g_thread_id = 1; ///< Thread index into interrupt relay queue, 1 is arbitrary | ||
| 47 | 29 | ||
| 48 | u32 g_thread_id = 0; | 30 | /// Gets a pointer to a thread command buffer in GSP shared memory |
| 31 | static inline u8* GetCommandBuffer(u32 thread_id) { | ||
| 32 | if (0 == g_shared_memory) | ||
| 33 | return nullptr; | ||
| 49 | 34 | ||
| 50 | /// Gets a pointer to the start (header) of a command buffer in GSP shared memory | 35 | return Kernel::GetSharedMemoryPointer(g_shared_memory, |
| 51 | static inline u8* GX_GetCmdBufferPointer(u32 thread_id, u32 offset=0) { | 36 | 0x800 + (thread_id * sizeof(CommandBuffer))); |
| 52 | return Kernel::GetSharedMemoryPointer(g_shared_memory, 0x800 + (thread_id * 0x200) + offset); | ||
| 53 | } | 37 | } |
| 54 | 38 | ||
| 55 | /// Finishes execution of a GSP command | 39 | /// Gets a pointer to the interrupt relay queue for a given thread index |
| 56 | void GX_FinishCommand(u32 thread_id) { | 40 | static inline InterruptRelayQueue* GetInterruptRelayQueue(u32 thread_id) { |
| 57 | GX_CmdBufferHeader* header = (GX_CmdBufferHeader*)GX_GetCmdBufferPointer(thread_id); | 41 | return (InterruptRelayQueue*)Kernel::GetSharedMemoryPointer(g_shared_memory, |
| 58 | 42 | sizeof(InterruptRelayQueue) * thread_id); | |
| 59 | g_debugger.GXCommandProcessed(GX_GetCmdBufferPointer(thread_id, 0x20 + (header->index * 0x20))); | ||
| 60 | |||
| 61 | header->number_commands = header->number_commands - 1; | ||
| 62 | // TODO: Increment header->index? | ||
| 63 | } | 43 | } |
| 64 | 44 | ||
| 65 | /// Write a GSP GPU hardware register | 45 | /// Write a GSP GPU hardware register |
| @@ -133,39 +113,55 @@ void ReadHWRegs(Service::Interface* self) { | |||
| 133 | void RegisterInterruptRelayQueue(Service::Interface* self) { | 113 | void RegisterInterruptRelayQueue(Service::Interface* self) { |
| 134 | u32* cmd_buff = Service::GetCommandBuffer(); | 114 | u32* cmd_buff = Service::GetCommandBuffer(); |
| 135 | u32 flags = cmd_buff[1]; | 115 | u32 flags = cmd_buff[1]; |
| 136 | g_event = cmd_buff[3]; | 116 | g_interrupt_event = cmd_buff[3]; |
| 137 | 117 | g_shared_memory = Kernel::CreateSharedMemory("GSPSharedMem"); | |
| 138 | _assert_msg_(GSP, (g_event != 0), "handle is not valid!"); | ||
| 139 | 118 | ||
| 140 | Kernel::SetEventLocked(g_event, false); | 119 | _assert_msg_(GSP, (g_interrupt_event != 0), "handle is not valid!"); |
| 141 | 120 | ||
| 142 | // Hack - This function will permanently set the state of the GSP event such that GPU command | 121 | cmd_buff[2] = g_thread_id++; // ThreadID |
| 143 | // synchronization barriers always passthrough. Correct solution would be to set this after the | 122 | cmd_buff[4] = g_shared_memory; // GSP shared memory |
| 144 | // GPU as processed all queued up commands, but due to the emulator being single-threaded they | ||
| 145 | // will always be ready. | ||
| 146 | Kernel::SetPermanentLock(g_event, true); | ||
| 147 | 123 | ||
| 148 | cmd_buff[0] = 0; // Result - no error | 124 | Kernel::SignalEvent(g_interrupt_event); // TODO(bunnei): Is this correct? |
| 149 | cmd_buff[2] = g_thread_id; // ThreadID | ||
| 150 | cmd_buff[4] = g_shared_memory; // GSP shared memory | ||
| 151 | } | 125 | } |
| 152 | 126 | ||
| 127 | /** | ||
| 128 | * Signals that the specified interrupt type has occurred to userland code | ||
| 129 | * @param interrupt_id ID of interrupt that is being signalled | ||
| 130 | */ | ||
| 131 | void SignalInterrupt(InterruptId interrupt_id) { | ||
| 132 | if (0 == g_interrupt_event) { | ||
| 133 | WARN_LOG(GSP, "cannot synchronize until GSP event has been created!"); | ||
| 134 | return; | ||
| 135 | } | ||
| 136 | if (0 == g_shared_memory) { | ||
| 137 | WARN_LOG(GSP, "cannot synchronize until GSP shared memory has been created!"); | ||
| 138 | return; | ||
| 139 | } | ||
| 140 | for (int thread_id = 0; thread_id < 0x4; ++thread_id) { | ||
| 141 | InterruptRelayQueue* interrupt_relay_queue = GetInterruptRelayQueue(thread_id); | ||
| 142 | interrupt_relay_queue->number_interrupts = interrupt_relay_queue->number_interrupts + 1; | ||
| 153 | 143 | ||
| 154 | /// This triggers handling of the GX command written to the command buffer in shared memory. | 144 | u8 next = interrupt_relay_queue->index; |
| 155 | void TriggerCmdReqQueue(Service::Interface* self) { | 145 | next += interrupt_relay_queue->number_interrupts; |
| 146 | next = next % 0x34; // 0x34 is the number of interrupt slots | ||
| 156 | 147 | ||
| 148 | interrupt_relay_queue->slot[next] = interrupt_id; | ||
| 149 | interrupt_relay_queue->error_code = 0x0; // No error | ||
| 150 | } | ||
| 151 | Kernel::SignalEvent(g_interrupt_event); | ||
| 152 | } | ||
| 153 | |||
| 154 | /// Executes the next GSP command | ||
| 155 | void ExecuteCommand(const Command& command) { | ||
| 157 | // Utility function to convert register ID to address | 156 | // Utility function to convert register ID to address |
| 158 | auto WriteGPURegister = [](u32 id, u32 data) { | 157 | auto WriteGPURegister = [](u32 id, u32 data) { |
| 159 | GPU::Write<u32>(0x1EF00000 + 4 * id, data); | 158 | GPU::Write<u32>(0x1EF00000 + 4 * id, data); |
| 160 | }; | 159 | }; |
| 161 | 160 | ||
| 162 | GX_CmdBufferHeader* header = (GX_CmdBufferHeader*)GX_GetCmdBufferPointer(g_thread_id); | ||
| 163 | auto& command = *(const GXCommand*)GX_GetCmdBufferPointer(g_thread_id, 0x20 + (header->index * 0x20)); | ||
| 164 | |||
| 165 | switch (command.id) { | 161 | switch (command.id) { |
| 166 | 162 | ||
| 167 | // GX request DMA - typically used for copying memory from GSP heap to VRAM | 163 | // GX request DMA - typically used for copying memory from GSP heap to VRAM |
| 168 | case GXCommandId::REQUEST_DMA: | 164 | case CommandId::REQUEST_DMA: |
| 169 | memcpy(Memory::GetPointer(command.dma_request.dest_address), | 165 | memcpy(Memory::GetPointer(command.dma_request.dest_address), |
| 170 | Memory::GetPointer(command.dma_request.source_address), | 166 | Memory::GetPointer(command.dma_request.source_address), |
| 171 | command.dma_request.size); | 167 | command.dma_request.size); |
| @@ -174,24 +170,27 @@ void TriggerCmdReqQueue(Service::Interface* self) { | |||
| 174 | // ctrulib homebrew sends all relevant command list data with this command, | 170 | // ctrulib homebrew sends all relevant command list data with this command, |
| 175 | // hence we do all "interesting" stuff here and do nothing in SET_COMMAND_LIST_FIRST. | 171 | // hence we do all "interesting" stuff here and do nothing in SET_COMMAND_LIST_FIRST. |
| 176 | // TODO: This will need some rework in the future. | 172 | // TODO: This will need some rework in the future. |
| 177 | case GXCommandId::SET_COMMAND_LIST_LAST: | 173 | case CommandId::SET_COMMAND_LIST_LAST: |
| 178 | { | 174 | { |
| 179 | auto& params = command.set_command_list_last; | 175 | auto& params = command.set_command_list_last; |
| 180 | WriteGPURegister(GPU::Regs::CommandProcessor + 2, params.address >> 3); | 176 | WriteGPURegister(GPU::Regs::CommandProcessor + 2, params.address >> 3); |
| 181 | WriteGPURegister(GPU::Regs::CommandProcessor, params.size >> 3); | 177 | WriteGPURegister(GPU::Regs::CommandProcessor, params.size >> 3); |
| 182 | WriteGPURegister(GPU::Regs::CommandProcessor + 4, 1); // TODO: Not sure if we are supposed to always write this .. seems to trigger processing though | 178 | |
| 179 | // TODO: Not sure if we are supposed to always write this .. seems to trigger processing though | ||
| 180 | WriteGPURegister(GPU::Regs::CommandProcessor + 4, 1); | ||
| 183 | 181 | ||
| 184 | // TODO: Move this to GPU | 182 | // TODO: Move this to GPU |
| 185 | // TODO: Not sure what units the size is measured in | 183 | // TODO: Not sure what units the size is measured in |
| 186 | g_debugger.CommandListCalled(params.address, | 184 | g_debugger.CommandListCalled(params.address, |
| 187 | (u32*)Memory::GetPointer(params.address), | 185 | (u32*)Memory::GetPointer(params.address), |
| 188 | params.size); | 186 | params.size); |
| 187 | SignalInterrupt(InterruptId::P3D); | ||
| 189 | break; | 188 | break; |
| 190 | } | 189 | } |
| 191 | 190 | ||
| 192 | // It's assumed that the two "blocks" behave equivalently. | 191 | // It's assumed that the two "blocks" behave equivalently. |
| 193 | // Presumably this is done simply to allow two memory fills to run in parallel. | 192 | // Presumably this is done simply to allow two memory fills to run in parallel. |
| 194 | case GXCommandId::SET_MEMORY_FILL: | 193 | case CommandId::SET_MEMORY_FILL: |
| 195 | { | 194 | { |
| 196 | auto& params = command.memory_fill; | 195 | auto& params = command.memory_fill; |
| 197 | WriteGPURegister(GPU::Regs::MemoryFill, params.start1 >> 3); | 196 | WriteGPURegister(GPU::Regs::MemoryFill, params.start1 >> 3); |
| @@ -207,8 +206,18 @@ void TriggerCmdReqQueue(Service::Interface* self) { | |||
| 207 | } | 206 | } |
| 208 | 207 | ||
| 209 | // TODO: Check if texture copies are implemented correctly.. | 208 | // TODO: Check if texture copies are implemented correctly.. |
| 210 | case GXCommandId::SET_DISPLAY_TRANSFER: | 209 | case CommandId::SET_DISPLAY_TRANSFER: |
| 211 | case GXCommandId::SET_TEXTURE_COPY: | 210 | // TODO(bunnei): Signalling all of these interrupts here is totally wrong, but it seems to |
| 211 | // work well enough for running demos. Need to figure out how these all work and trigger | ||
| 212 | // them correctly. | ||
| 213 | SignalInterrupt(InterruptId::PSC0); | ||
| 214 | SignalInterrupt(InterruptId::PSC1); | ||
| 215 | SignalInterrupt(InterruptId::PPF); | ||
| 216 | SignalInterrupt(InterruptId::P3D); | ||
| 217 | SignalInterrupt(InterruptId::DMA); | ||
| 218 | break; | ||
| 219 | |||
| 220 | case CommandId::SET_TEXTURE_COPY: | ||
| 212 | { | 221 | { |
| 213 | auto& params = command.image_copy; | 222 | auto& params = command.image_copy; |
| 214 | WriteGPURegister(GPU::Regs::DisplayTransfer, params.in_buffer_address >> 3); | 223 | WriteGPURegister(GPU::Regs::DisplayTransfer, params.in_buffer_address >> 3); |
| @@ -225,7 +234,7 @@ void TriggerCmdReqQueue(Service::Interface* self) { | |||
| 225 | 234 | ||
| 226 | // TODO: Figure out what exactly SET_COMMAND_LIST_FIRST and SET_COMMAND_LIST_LAST | 235 | // TODO: Figure out what exactly SET_COMMAND_LIST_FIRST and SET_COMMAND_LIST_LAST |
| 227 | // are supposed to do. | 236 | // are supposed to do. |
| 228 | case GXCommandId::SET_COMMAND_LIST_FIRST: | 237 | case CommandId::SET_COMMAND_LIST_FIRST: |
| 229 | { | 238 | { |
| 230 | break; | 239 | break; |
| 231 | } | 240 | } |
| @@ -233,8 +242,26 @@ void TriggerCmdReqQueue(Service::Interface* self) { | |||
| 233 | default: | 242 | default: |
| 234 | ERROR_LOG(GSP, "unknown command 0x%08X", (int)command.id.Value()); | 243 | ERROR_LOG(GSP, "unknown command 0x%08X", (int)command.id.Value()); |
| 235 | } | 244 | } |
| 245 | } | ||
| 236 | 246 | ||
| 237 | GX_FinishCommand(g_thread_id); | 247 | /// This triggers handling of the GX command written to the command buffer in shared memory. |
| 248 | void TriggerCmdReqQueue(Service::Interface* self) { | ||
| 249 | |||
| 250 | // Iterate through each thread's command queue... | ||
| 251 | for (unsigned thread_id = 0; thread_id < 0x4; ++thread_id) { | ||
| 252 | CommandBuffer* command_buffer = (CommandBuffer*)GetCommandBuffer(thread_id); | ||
| 253 | |||
| 254 | // Iterate through each command... | ||
| 255 | for (unsigned i = 0; i < command_buffer->number_commands; ++i) { | ||
| 256 | g_debugger.GXCommandProcessed((u8*)&command_buffer->commands[i]); | ||
| 257 | |||
| 258 | // Decode and execute command | ||
| 259 | ExecuteCommand(command_buffer->commands[i]); | ||
| 260 | |||
| 261 | // Indicates that command has completed | ||
| 262 | command_buffer->number_commands = command_buffer->number_commands - 1; | ||
| 263 | } | ||
| 264 | } | ||
| 238 | } | 265 | } |
| 239 | 266 | ||
| 240 | const Interface::FunctionInfo FunctionTable[] = { | 267 | const Interface::FunctionInfo FunctionTable[] = { |
| @@ -275,7 +302,10 @@ const Interface::FunctionInfo FunctionTable[] = { | |||
| 275 | 302 | ||
| 276 | Interface::Interface() { | 303 | Interface::Interface() { |
| 277 | Register(FunctionTable, ARRAY_SIZE(FunctionTable)); | 304 | Register(FunctionTable, ARRAY_SIZE(FunctionTable)); |
| 278 | g_shared_memory = Kernel::CreateSharedMemory("GSPSharedMem"); | 305 | |
| 306 | g_interrupt_event = 0; | ||
| 307 | g_shared_memory = 0; | ||
| 308 | g_thread_id = 1; | ||
| 279 | } | 309 | } |
| 280 | 310 | ||
| 281 | Interface::~Interface() { | 311 | Interface::~Interface() { |
diff --git a/src/core/hle/service/gsp.h b/src/core/hle/service/gsp.h index a83cb4846..fccebef7e 100644 --- a/src/core/hle/service/gsp.h +++ b/src/core/hle/service/gsp.h | |||
| @@ -12,7 +12,19 @@ | |||
| 12 | 12 | ||
| 13 | namespace GSP_GPU { | 13 | namespace GSP_GPU { |
| 14 | 14 | ||
| 15 | enum class GXCommandId : u32 { | 15 | /// GSP interrupt ID |
| 16 | enum class InterruptId : u8 { | ||
| 17 | PSC0 = 0x00, | ||
| 18 | PSC1 = 0x01, | ||
| 19 | PDC0 = 0x02, // Seems called every vertical screen line | ||
| 20 | PDC1 = 0x03, // Seems called every frame | ||
| 21 | PPF = 0x04, | ||
| 22 | P3D = 0x05, | ||
| 23 | DMA = 0x06, | ||
| 24 | }; | ||
| 25 | |||
| 26 | /// GSP command ID | ||
| 27 | enum class CommandId : u32 { | ||
| 16 | REQUEST_DMA = 0x00, | 28 | REQUEST_DMA = 0x00, |
| 17 | SET_COMMAND_LIST_LAST = 0x01, | 29 | SET_COMMAND_LIST_LAST = 0x01, |
| 18 | 30 | ||
| @@ -29,8 +41,32 @@ enum class GXCommandId : u32 { | |||
| 29 | SET_COMMAND_LIST_FIRST = 0x05, | 41 | SET_COMMAND_LIST_FIRST = 0x05, |
| 30 | }; | 42 | }; |
| 31 | 43 | ||
| 32 | struct GXCommand { | 44 | /// GSP thread interrupt relay queue |
| 33 | BitField<0, 8, GXCommandId> id; | 45 | struct InterruptRelayQueue { |
| 46 | union { | ||
| 47 | u32 hex; | ||
| 48 | |||
| 49 | // Index of last interrupt in the queue | ||
| 50 | BitField<0,8,u32> index; | ||
| 51 | |||
| 52 | // Number of interrupts remaining to be processed by the userland code | ||
| 53 | BitField<8,8,u32> number_interrupts; | ||
| 54 | |||
| 55 | // Error code - zero on success, otherwise an error has occurred | ||
| 56 | BitField<16,8,u32> error_code; | ||
| 57 | }; | ||
| 58 | |||
| 59 | u32 unk0; | ||
| 60 | u32 unk1; | ||
| 61 | |||
| 62 | InterruptId slot[0x34]; ///< Interrupt ID slots | ||
| 63 | }; | ||
| 64 | static_assert(sizeof(InterruptRelayQueue) == 0x40, | ||
| 65 | "InterruptRelayQueue struct has incorrect size"); | ||
| 66 | |||
| 67 | /// GSP command | ||
| 68 | struct Command { | ||
| 69 | BitField<0, 8, CommandId> id; | ||
| 34 | 70 | ||
| 35 | union { | 71 | union { |
| 36 | struct { | 72 | struct { |
| @@ -64,7 +100,30 @@ struct GXCommand { | |||
| 64 | u8 raw_data[0x1C]; | 100 | u8 raw_data[0x1C]; |
| 65 | }; | 101 | }; |
| 66 | }; | 102 | }; |
| 67 | static_assert(sizeof(GXCommand) == 0x20, "GXCommand struct has incorrect size"); | 103 | static_assert(sizeof(Command) == 0x20, "Command struct has incorrect size"); |
| 104 | |||
| 105 | /// GSP shared memory GX command buffer header | ||
| 106 | struct CommandBuffer { | ||
| 107 | union { | ||
| 108 | u32 hex; | ||
| 109 | |||
| 110 | // Current command index. This index is updated by GSP module after loading the command | ||
| 111 | // data, right before the command is processed. When this index is updated by GSP module, | ||
| 112 | // the total commands field is decreased by one as well. | ||
| 113 | BitField<0,8,u32> index; | ||
| 114 | |||
| 115 | // Total commands to process, must not be value 0 when GSP module handles commands. This | ||
| 116 | // must be <=15 when writing a command to shared memory. This is incremented by the | ||
| 117 | // application when writing a command to shared memory, after increasing this value | ||
| 118 | // TriggerCmdReqQueue is only used if this field is value 1. | ||
| 119 | BitField<8,8,u32> number_commands; | ||
| 120 | }; | ||
| 121 | |||
| 122 | u32 unk[7]; | ||
| 123 | |||
| 124 | Command commands[0xF]; | ||
| 125 | }; | ||
| 126 | static_assert(sizeof(CommandBuffer) == 0x200, "CommandBuffer struct has incorrect size"); | ||
| 68 | 127 | ||
| 69 | /// Interface to "srv:" service | 128 | /// Interface to "srv:" service |
| 70 | class Interface : public Service::Interface { | 129 | class Interface : public Service::Interface { |
| @@ -84,4 +143,10 @@ public: | |||
| 84 | 143 | ||
| 85 | }; | 144 | }; |
| 86 | 145 | ||
| 146 | /** | ||
| 147 | * Signals that the specified interrupt type has occurred to userland code | ||
| 148 | * @param interrupt_id ID of interrupt that is being signalled | ||
| 149 | */ | ||
| 150 | void SignalInterrupt(InterruptId interrupt_id); | ||
| 151 | |||
| 87 | } // namespace | 152 | } // namespace |
diff --git a/src/core/hle/service/srv.cpp b/src/core/hle/service/srv.cpp index f45c0efc2..8f8413d02 100644 --- a/src/core/hle/service/srv.cpp +++ b/src/core/hle/service/srv.cpp | |||
| @@ -5,28 +5,30 @@ | |||
| 5 | #include "core/hle/hle.h" | 5 | #include "core/hle/hle.h" |
| 6 | #include "core/hle/service/srv.h" | 6 | #include "core/hle/service/srv.h" |
| 7 | #include "core/hle/service/service.h" | 7 | #include "core/hle/service/service.h" |
| 8 | #include "core/hle/kernel/mutex.h" | 8 | #include "core/hle/kernel/event.h" |
| 9 | 9 | ||
| 10 | //////////////////////////////////////////////////////////////////////////////////////////////////// | 10 | //////////////////////////////////////////////////////////////////////////////////////////////////// |
| 11 | // Namespace SRV | 11 | // Namespace SRV |
| 12 | 12 | ||
| 13 | namespace SRV { | 13 | namespace SRV { |
| 14 | 14 | ||
| 15 | Handle g_mutex = 0; | 15 | Handle g_event_handle = 0; |
| 16 | 16 | ||
| 17 | void Initialize(Service::Interface* self) { | 17 | void Initialize(Service::Interface* self) { |
| 18 | DEBUG_LOG(OSHLE, "called"); | 18 | DEBUG_LOG(OSHLE, "called"); |
| 19 | if (!g_mutex) { | ||
| 20 | g_mutex = Kernel::CreateMutex(true, "SRV:Lock"); | ||
| 21 | } | ||
| 22 | } | 19 | } |
| 23 | 20 | ||
| 24 | void GetProcSemaphore(Service::Interface* self) { | 21 | void GetProcSemaphore(Service::Interface* self) { |
| 25 | DEBUG_LOG(OSHLE, "called"); | 22 | DEBUG_LOG(OSHLE, "called"); |
| 26 | // Get process semaphore? | 23 | |
| 27 | u32* cmd_buff = Service::GetCommandBuffer(); | 24 | u32* cmd_buff = Service::GetCommandBuffer(); |
| 28 | cmd_buff[1] = 0; // No error | 25 | |
| 29 | cmd_buff[3] = g_mutex; // Return something... 0 == nullptr, raises an exception | 26 | // TODO(bunnei): Change to a semaphore once these have been implemented |
| 27 | g_event_handle = Kernel::CreateEvent(RESETTYPE_ONESHOT, "SRV:Event"); | ||
| 28 | Kernel::SetEventLocked(g_event_handle, false); | ||
| 29 | |||
| 30 | cmd_buff[1] = 0; // No error | ||
| 31 | cmd_buff[3] = g_event_handle; | ||
| 30 | } | 32 | } |
| 31 | 33 | ||
| 32 | void GetServiceHandle(Service::Interface* self) { | 34 | void GetServiceHandle(Service::Interface* self) { |
diff --git a/src/core/hle/svc.cpp b/src/core/hle/svc.cpp index 17967f260..328d048bd 100644 --- a/src/core/hle/svc.cpp +++ b/src/core/hle/svc.cpp | |||
| @@ -185,8 +185,6 @@ Result CreateAddressArbiter(u32* arbiter) { | |||
| 185 | 185 | ||
| 186 | /// Arbitrate address | 186 | /// Arbitrate address |
| 187 | Result ArbitrateAddress(Handle arbiter, u32 address, u32 type, u32 value, s64 nanoseconds) { | 187 | Result ArbitrateAddress(Handle arbiter, u32 address, u32 type, u32 value, s64 nanoseconds) { |
| 188 | DEBUG_LOG(SVC, "called arbiter=0x%08X, address=0x%08X, type=0x%08X, value=0x%08X, " | ||
| 189 | "nanoseconds=%d", arbiter, address, type, value, nanoseconds); | ||
| 190 | return Kernel::ArbitrateAddress(arbiter, static_cast<Kernel::ArbitrationType>(type), address, | 188 | return Kernel::ArbitrateAddress(arbiter, static_cast<Kernel::ArbitrationType>(type), address, |
| 191 | value); | 189 | value); |
| 192 | } | 190 | } |
diff --git a/src/core/hw/gpu.cpp b/src/core/hw/gpu.cpp index c00be2a83..d94c2329b 100644 --- a/src/core/hw/gpu.cpp +++ b/src/core/hw/gpu.cpp | |||
| @@ -7,7 +7,11 @@ | |||
| 7 | 7 | ||
| 8 | #include "core/core.h" | 8 | #include "core/core.h" |
| 9 | #include "core/mem_map.h" | 9 | #include "core/mem_map.h" |
| 10 | |||
| 11 | #include "core/hle/hle.h" | ||
| 10 | #include "core/hle/kernel/thread.h" | 12 | #include "core/hle/kernel/thread.h" |
| 13 | #include "core/hle/service/gsp.h" | ||
| 14 | |||
| 11 | #include "core/hw/gpu.h" | 15 | #include "core/hw/gpu.h" |
| 12 | 16 | ||
| 13 | #include "video_core/video_core.h" | 17 | #include "video_core/video_core.h" |
| @@ -17,7 +21,8 @@ namespace GPU { | |||
| 17 | 21 | ||
| 18 | RegisterSet<u32, Regs> g_regs; | 22 | RegisterSet<u32, Regs> g_regs; |
| 19 | 23 | ||
| 20 | u64 g_last_ticks = 0; ///< Last CPU ticks | 24 | u32 g_cur_line = 0; ///< Current vertical screen line |
| 25 | u64 g_last_line_ticks = 0; ///< CPU tick count from last vertical screen line | ||
| 21 | 26 | ||
| 22 | /** | 27 | /** |
| 23 | * Sets whether the framebuffers are in the GSP heap (FCRAM) or VRAM | 28 | * Sets whether the framebuffers are in the GSP heap (FCRAM) or VRAM |
| @@ -247,19 +252,31 @@ template void Write<u8>(u32 addr, const u8 data); | |||
| 247 | 252 | ||
| 248 | /// Update hardware | 253 | /// Update hardware |
| 249 | void Update() { | 254 | void Update() { |
| 255 | auto& framebuffer_top = g_regs.Get<Regs::FramebufferTop>(); | ||
| 250 | u64 current_ticks = Core::g_app_core->GetTicks(); | 256 | u64 current_ticks = Core::g_app_core->GetTicks(); |
| 251 | 257 | ||
| 252 | // Fake a vertical blank | 258 | // Synchronize line... |
| 253 | if ((current_ticks - g_last_ticks) >= kFrameTicks) { | 259 | if ((current_ticks - g_last_line_ticks) >= GPU::kFrameTicks / framebuffer_top.height) { |
| 254 | g_last_ticks = current_ticks; | 260 | GSP_GPU::SignalInterrupt(GSP_GPU::InterruptId::PDC0); |
| 261 | g_cur_line++; | ||
| 262 | g_last_line_ticks = current_ticks; | ||
| 263 | } | ||
| 264 | |||
| 265 | // Synchronize frame... | ||
| 266 | if (g_cur_line >= framebuffer_top.height) { | ||
| 267 | g_cur_line = 0; | ||
| 268 | GSP_GPU::SignalInterrupt(GSP_GPU::InterruptId::PDC1); | ||
| 255 | VideoCore::g_renderer->SwapBuffers(); | 269 | VideoCore::g_renderer->SwapBuffers(); |
| 256 | Kernel::WaitCurrentThread(WAITTYPE_VBLANK); | 270 | Kernel::WaitCurrentThread(WAITTYPE_VBLANK); |
| 271 | HLE::Reschedule(__func__); | ||
| 257 | } | 272 | } |
| 258 | } | 273 | } |
| 259 | 274 | ||
| 260 | /// Initialize hardware | 275 | /// Initialize hardware |
| 261 | void Init() { | 276 | void Init() { |
| 262 | g_last_ticks = Core::g_app_core->GetTicks(); | 277 | g_cur_line = 0; |
| 278 | g_last_line_ticks = Core::g_app_core->GetTicks(); | ||
| 279 | |||
| 263 | // SetFramebufferLocation(FRAMEBUFFER_LOCATION_FCRAM); | 280 | // SetFramebufferLocation(FRAMEBUFFER_LOCATION_FCRAM); |
| 264 | SetFramebufferLocation(FRAMEBUFFER_LOCATION_VRAM); | 281 | SetFramebufferLocation(FRAMEBUFFER_LOCATION_VRAM); |
| 265 | 282 | ||
diff --git a/src/core/mem_map_funcs.cpp b/src/core/mem_map_funcs.cpp index 038d3bffa..305be8468 100644 --- a/src/core/mem_map_funcs.cpp +++ b/src/core/mem_map_funcs.cpp | |||
| @@ -168,7 +168,7 @@ u8 *GetPointer(const u32 addr) { | |||
| 168 | return g_system_mem + (vaddr & SYSTEM_MEMORY_MASK); | 168 | return g_system_mem + (vaddr & SYSTEM_MEMORY_MASK); |
| 169 | 169 | ||
| 170 | // VRAM | 170 | // VRAM |
| 171 | } else if ((vaddr > VRAM_VADDR) && (vaddr < VRAM_VADDR_END)) { | 171 | } else if ((vaddr >= VRAM_VADDR) && (vaddr < VRAM_VADDR_END)) { |
| 172 | return g_vram + (vaddr & VRAM_MASK); | 172 | return g_vram + (vaddr & VRAM_MASK); |
| 173 | 173 | ||
| 174 | } else { | 174 | } else { |
diff --git a/src/video_core/gpu_debugger.h b/src/video_core/gpu_debugger.h index d92ceaa72..5d85f90b9 100644 --- a/src/video_core/gpu_debugger.h +++ b/src/video_core/gpu_debugger.h | |||
| @@ -49,7 +49,7 @@ public: | |||
| 49 | */ | 49 | */ |
| 50 | virtual void GXCommandProcessed(int total_command_count) | 50 | virtual void GXCommandProcessed(int total_command_count) |
| 51 | { | 51 | { |
| 52 | const GSP_GPU::GXCommand& cmd = observed->ReadGXCommandHistory(total_command_count-1); | 52 | const GSP_GPU::Command& cmd = observed->ReadGXCommandHistory(total_command_count-1); |
| 53 | ERROR_LOG(GSP, "Received command: id=%x", (int)cmd.id.Value()); | 53 | ERROR_LOG(GSP, "Received command: id=%x", (int)cmd.id.Value()); |
| 54 | } | 54 | } |
| 55 | 55 | ||
| @@ -81,10 +81,10 @@ public: | |||
| 81 | if (observers.empty()) | 81 | if (observers.empty()) |
| 82 | return; | 82 | return; |
| 83 | 83 | ||
| 84 | gx_command_history.push_back(GSP_GPU::GXCommand()); | 84 | gx_command_history.push_back(GSP_GPU::Command()); |
| 85 | GSP_GPU::GXCommand& cmd = gx_command_history[gx_command_history.size()-1]; | 85 | GSP_GPU::Command& cmd = gx_command_history[gx_command_history.size()-1]; |
| 86 | 86 | ||
| 87 | memcpy(&cmd, command_data, sizeof(GSP_GPU::GXCommand)); | 87 | memcpy(&cmd, command_data, sizeof(GSP_GPU::Command)); |
| 88 | 88 | ||
| 89 | ForEachObserver([this](DebuggerObserver* observer) { | 89 | ForEachObserver([this](DebuggerObserver* observer) { |
| 90 | observer->GXCommandProcessed(this->gx_command_history.size()); | 90 | observer->GXCommandProcessed(this->gx_command_history.size()); |
| @@ -123,7 +123,7 @@ public: | |||
| 123 | } ); | 123 | } ); |
| 124 | } | 124 | } |
| 125 | 125 | ||
| 126 | const GSP_GPU::GXCommand& ReadGXCommandHistory(int index) const | 126 | const GSP_GPU::Command& ReadGXCommandHistory(int index) const |
| 127 | { | 127 | { |
| 128 | // TODO: Is this thread-safe? | 128 | // TODO: Is this thread-safe? |
| 129 | return gx_command_history[index]; | 129 | return gx_command_history[index]; |
| @@ -155,7 +155,7 @@ private: | |||
| 155 | 155 | ||
| 156 | std::vector<DebuggerObserver*> observers; | 156 | std::vector<DebuggerObserver*> observers; |
| 157 | 157 | ||
| 158 | std::vector<GSP_GPU::GXCommand> gx_command_history; | 158 | std::vector<GSP_GPU::Command> gx_command_history; |
| 159 | 159 | ||
| 160 | // vector of pairs of command lists and their storage address | 160 | // vector of pairs of command lists and their storage address |
| 161 | std::vector<std::pair<u32,PicaCommandList>> command_lists; | 161 | std::vector<std::pair<u32,PicaCommandList>> command_lists; |