summaryrefslogtreecommitdiffstats
path: root/src/video_core/dma_pusher.cpp
blob: 654e4d9aa5039292a9ef8e296d80bb5f5afd72e6 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
// Copyright 2018 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.

#include "common/microprofile.h"
#include "core/core.h"
#include "core/memory.h"
#include "video_core/dma_pusher.h"
#include "video_core/engines/maxwell_3d.h"
#include "video_core/gpu.h"

namespace Tegra {

DmaPusher::DmaPusher(GPU& gpu) : gpu(gpu) {}

DmaPusher::~DmaPusher() = default;

MICROPROFILE_DEFINE(DispatchCalls, "GPU", "Execute command buffer", MP_RGB(128, 128, 192));

void DmaPusher::DispatchCalls() {
    MICROPROFILE_SCOPE(DispatchCalls);

    // On entering GPU code, assume all memory may be touched by the ARM core.
    gpu.Maxwell3D().dirty_flags.OnMemoryWrite();

    dma_pushbuffer_subindex = 0;

    while (Core::System::GetInstance().IsPoweredOn()) {
        if (!Step()) {
            break;
        }
    }
}

bool DmaPusher::Step() {
    if (dma_get != dma_put) {
        // Push buffer non-empty, read a word
        const auto address = gpu.MemoryManager().GpuToCpuAddress(dma_get);
        ASSERT_MSG(address, "Invalid GPU address");

        GPUVAddr size = dma_put - dma_get;
        ASSERT_MSG(size % sizeof(CommandHeader) == 0, "Invalid aligned GPU addresses");
        command_headers.resize(size / sizeof(CommandHeader));

        Memory::ReadBlock(*address, command_headers.data(), size);

        for (const CommandHeader& command_header : command_headers) {

            // now, see if we're in the middle of a command
            if (dma_state.length_pending) {
                // Second word of long non-inc methods command - method count
                dma_state.length_pending = 0;
                dma_state.method_count = command_header.method_count_;
            } else if (dma_state.method_count) {
                // Data word of methods command
                CallMethod(command_header.argument);

                if (!dma_state.non_incrementing) {
                    dma_state.method++;
                }

                if (dma_increment_once) {
                    dma_state.non_incrementing = true;
                }

                dma_state.method_count--;
            } else {
                // No command active - this is the first word of a new one
                switch (command_header.mode) {
                case SubmissionMode::Increasing:
                    SetState(command_header);
                    dma_state.non_incrementing = false;
                    dma_increment_once = false;
                    break;
                case SubmissionMode::NonIncreasing:
                    SetState(command_header);
                    dma_state.non_incrementing = true;
                    dma_increment_once = false;
                    break;
                case SubmissionMode::Inline:
                    dma_state.method = command_header.method;
                    dma_state.subchannel = command_header.subchannel;
                    CallMethod(command_header.arg_count);
                    dma_state.non_incrementing = true;
                    dma_increment_once = false;
                    break;
                case SubmissionMode::IncreaseOnce:
                    SetState(command_header);
                    dma_state.non_incrementing = false;
                    dma_increment_once = true;
                    break;
                }
            }
        }

        dma_get = dma_put;

        if (!non_main) {
            // TODO (degasus): This is dead code, as dma_mget is never read.
            dma_mget = dma_get;
        }
    } else if (ib_enable && !dma_pushbuffer.empty()) {
        // Current pushbuffer empty, but we have more IB entries to read
        const CommandList& command_list{dma_pushbuffer.front()};
        const CommandListHeader& command_list_header{command_list[dma_pushbuffer_subindex++]};
        dma_get = command_list_header.addr;
        dma_put = dma_get + command_list_header.size * sizeof(u32);
        non_main = command_list_header.is_non_main;

        if (dma_pushbuffer_subindex >= command_list.size()) {
            // We've gone through the current list, remove it from the queue
            dma_pushbuffer.pop();
            dma_pushbuffer_subindex = 0;
        }
    } else {
        // Otherwise, pushbuffer empty and IB empty or nonexistent - nothing to do
        return {};
    }

    return true;
}

void DmaPusher::SetState(const CommandHeader& command_header) {
    dma_state.method = command_header.method;
    dma_state.subchannel = command_header.subchannel;
    dma_state.method_count = command_header.method_count;
}

void DmaPusher::CallMethod(u32 argument) const {
    gpu.CallMethod({dma_state.method, argument, dma_state.subchannel, dma_state.method_count});
}

} // namespace Tegra