| 
 | 1 | +/*  | 
 | 2 | +
  | 
 | 3 | +MIT License  | 
 | 4 | +
  | 
 | 5 | +Copyright (c) 2024 PCSX-Redux authors  | 
 | 6 | +
  | 
 | 7 | +Permission is hereby granted, free of charge, to any person obtaining a copy  | 
 | 8 | +of this software and associated documentation files (the "Software"), to deal  | 
 | 9 | +in the Software without restriction, including without limitation the rights  | 
 | 10 | +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell  | 
 | 11 | +copies of the Software, and to permit persons to whom the Software is  | 
 | 12 | +furnished to do so, subject to the following conditions:  | 
 | 13 | +
  | 
 | 14 | +The above copyright notice and this permission notice shall be included in all  | 
 | 15 | +copies or substantial portions of the Software.  | 
 | 16 | +
  | 
 | 17 | +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR  | 
 | 18 | +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,  | 
 | 19 | +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE  | 
 | 20 | +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER  | 
 | 21 | +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,  | 
 | 22 | +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE  | 
 | 23 | +SOFTWARE.  | 
 | 24 | +
  | 
 | 25 | +*/  | 
 | 26 | + | 
 | 27 | +#include "psyqo/application.hh"  | 
 | 28 | +#include "psyqo/gpu.hh"  | 
 | 29 | +#include "psyqo/trigonometry.hh"  | 
 | 30 | +#include "psyqo/gte-registers.hh"  | 
 | 31 | +#include "psyqo/gte-kernels.hh"  | 
 | 32 | +#include "psyqo/fixed-point.hh"  | 
 | 33 | +#include "psyqo/fragments.hh"  | 
 | 34 | +#include "psyqo/primitives/common.hh"  | 
 | 35 | +#include "psyqo/primitives/quads.hh"  | 
 | 36 | +#include "psyqo/vector.hh"  | 
 | 37 | +#include "psyqo/soft-math.hh"  | 
 | 38 | +#include "psyqo/scene.hh"  | 
 | 39 | + | 
 | 40 | +using namespace psyqo::fixed_point_literals;  | 
 | 41 | +using namespace psyqo::trig_literals;  | 
 | 42 | + | 
 | 43 | +static constexpr unsigned NUM_CUBE_VERTICES = 8;  | 
 | 44 | +static constexpr unsigned NUM_CUBE_FACES = 6;  | 
 | 45 | +static constexpr unsigned ORDERING_TABLE_SIZE = 240;  | 
 | 46 | + | 
 | 47 | +typedef struct {  | 
 | 48 | +	uint8_t  vertices[4];  | 
 | 49 | +	psyqo::Color color;  | 
 | 50 | +} Face;  | 
 | 51 | + | 
 | 52 | + | 
 | 53 | +static constexpr psyqo::Matrix33 identity = {{  | 
 | 54 | +        {1.0_fp, 0.0_fp, 0.0_fp},  | 
 | 55 | +        {0.0_fp, 1.0_fp, 0.0_fp},  | 
 | 56 | +        {0.0_fp, 0.0_fp, 1.0_fp},  | 
 | 57 | +}};  | 
 | 58 | + | 
 | 59 | +class Cube final : public psyqo::Application {  | 
 | 60 | +    void prepare() override;  | 
 | 61 | +    void createScene() override;  | 
 | 62 | + | 
 | 63 | +    public:  | 
 | 64 | +        psyqo::Trig<> m_trig;  | 
 | 65 | +};  | 
 | 66 | + | 
 | 67 | +class CubeScene final : public psyqo::Scene {  | 
 | 68 | + | 
 | 69 | +    void start(StartReason reason) override;  | 
 | 70 | +    void frame() override;  | 
 | 71 | + | 
 | 72 | +    psyqo::Angle m_rot = 0;  | 
 | 73 | + | 
 | 74 | +    // We need to create 2 OrderingTable objects since we can't reuse a single one for both  | 
 | 75 | +    // framebuffers, as the previous one may not finish transfering in time.  | 
 | 76 | +    psyqo::OrderingTable<ORDERING_TABLE_SIZE> m_ots[2];  | 
 | 77 | +      | 
 | 78 | +    // Since we're using an ordering table, we need to sort fill commands as well,   | 
 | 79 | +    // otherwise they'll draw over our beautiful cube.  | 
 | 80 | +    psyqo::Fragments::SimpleFragment<psyqo::Prim::FastFill> m_clear[2];  | 
 | 81 | + | 
 | 82 | +    eastl::array<psyqo::Fragments::SimpleFragment<psyqo::Prim::Quad>, 6> m_quads;  | 
 | 83 | + | 
 | 84 | +    static constexpr psyqo::Color c_bg = {.r = 63, .g = 63, .b = 63};  | 
 | 85 | + | 
 | 86 | + | 
 | 87 | +    static constexpr psyqo::Vec3 c_cubeVertices[NUM_CUBE_VERTICES] = {  | 
 | 88 | +        { .x = -0.05, .y = -0.05, .z = -0.05 },  | 
 | 89 | +        { .x =  0.05, .y = -0.05, .z = -0.05 },  | 
 | 90 | +        { .x = -0.05, .y =  0.05, .z = -0.05 },  | 
 | 91 | +        { .x =  0.05, .y =  0.05, .z = -0.05 },  | 
 | 92 | +        { .x = -0.05, .y = -0.05, .z =  0.05 },  | 
 | 93 | +        { .x =  0.05, .y = -0.05, .z =  0.05 },  | 
 | 94 | +        { .x = -0.05, .y =  0.05, .z =  0.05 },  | 
 | 95 | +        { .x =  0.05, .y =  0.05, .z =  0.05 }  | 
 | 96 | +    };  | 
 | 97 | + | 
 | 98 | + | 
 | 99 | +    static constexpr Face c_cubeFaces[NUM_CUBE_FACES] = {  | 
 | 100 | +        { .vertices = { 0, 1, 2, 3 }, .color = {0,0,255} },  | 
 | 101 | +        { .vertices = { 6, 7, 4, 5 }, .color = {0,255,0} },  | 
 | 102 | +        { .vertices = { 4, 5, 0, 1 }, .color = {0,255,255} },  | 
 | 103 | +        { .vertices = { 7, 6, 3, 2 }, .color = {255,0,0} },  | 
 | 104 | +        { .vertices = { 6, 4, 2, 0 }, .color = {255,0,255}},  | 
 | 105 | +        { .vertices = { 5, 7, 1, 3 }, .color = {255,255,0} }  | 
 | 106 | +    };  | 
 | 107 | +};  | 
 | 108 | + | 
 | 109 | +static Cube cube;  | 
 | 110 | +static CubeScene cubeScene;  | 
 | 111 | + | 
 | 112 | +void Cube::prepare() {  | 
 | 113 | +  psyqo::GPU::Configuration config;  | 
 | 114 | +  config.set(psyqo::GPU::Resolution::W320)  | 
 | 115 | +      .set(psyqo::GPU::VideoMode::AUTO)  | 
 | 116 | +      .set(psyqo::GPU::ColorMode::C15BITS)  | 
 | 117 | +      .set(psyqo::GPU::Interlace::PROGRESSIVE);  | 
 | 118 | + | 
 | 119 | +  gpu().initialize(config);  | 
 | 120 | +}  | 
 | 121 | + | 
 | 122 | +void Cube::createScene() {  | 
 | 123 | +    pushScene(&cubeScene);  | 
 | 124 | +}  | 
 | 125 | + | 
 | 126 | +void CubeScene::start(StartReason reason) {  | 
 | 127 | + | 
 | 128 | +    // Clear the translation registers  | 
 | 129 | +    psyqo::GTE::clear<psyqo::GTE::Register::TRX, psyqo::GTE::Unsafe>();  | 
 | 130 | +    psyqo::GTE::clear<psyqo::GTE::Register::TRY, psyqo::GTE::Unsafe>();  | 
 | 131 | +    psyqo::GTE::clear<psyqo::GTE::Register::TRZ, psyqo::GTE::Unsafe>();  | 
 | 132 | + | 
 | 133 | + | 
 | 134 | +    // Set the screen offset in the GTE. (this is half the X and Y resolutions as standard)  | 
 | 135 | +    psyqo::GTE::write<psyqo::GTE::Register::OFX, psyqo::GTE::Unsafe>(psyqo::FixedPoint<16>(160.0).raw());  | 
 | 136 | +    psyqo::GTE::write<psyqo::GTE::Register::OFY, psyqo::GTE::Unsafe>(psyqo::FixedPoint<16>(120.0).raw());  | 
 | 137 | + | 
 | 138 | + | 
 | 139 | +    // Write the projection plane distance.  | 
 | 140 | +    psyqo::GTE::write<psyqo::GTE::Register::H, psyqo::GTE::Unsafe>(120);  | 
 | 141 | + | 
 | 142 | + | 
 | 143 | +    // Set the scaling for Z averaging.  | 
 | 144 | +    psyqo::GTE::write<psyqo::GTE::Register::ZSF3, psyqo::GTE::Unsafe>(ORDERING_TABLE_SIZE / 3);  | 
 | 145 | +    psyqo::GTE::write<psyqo::GTE::Register::ZSF4, psyqo::GTE::Unsafe>(ORDERING_TABLE_SIZE / 4);  | 
 | 146 | + | 
 | 147 | +}  | 
 | 148 | + | 
 | 149 | +void CubeScene::frame() {  | 
 | 150 | + | 
 | 151 | +    eastl::array<psyqo::Vertex, 4> projected;  | 
 | 152 | + | 
 | 153 | +    // Get which frame we're currently drawing  | 
 | 154 | +    int parity = gpu().getParity();  | 
 | 155 | + | 
 | 156 | +    // Get our current ordering table and fill command  | 
 | 157 | +    auto& ot = m_ots[parity];  | 
 | 158 | +    auto& clear = m_clear[parity];  | 
 | 159 | + | 
 | 160 | +    // Chain the fill command accordingly to clear the buffer  | 
 | 161 | +    gpu().getNextClear(clear.primitive, c_bg);  | 
 | 162 | +    gpu().chain(clear);  | 
 | 163 | +      | 
 | 164 | + | 
 | 165 | +    // We want the cube to appear slightly further away, so we translate it by 512 on the Z-axis.  | 
 | 166 | +    psyqo::GTE::write<psyqo::GTE::Register::TRZ, psyqo::GTE::Unsafe>(512);  | 
 | 167 | +      | 
 | 168 | +    // Here we're setting up the rotation for the spinning cube  | 
 | 169 | +    // First, generate a rotation matrix for the X-axis and Y-axis  | 
 | 170 | +    auto transform = psyqo::SoftMath::generateRotationMatrix33(m_rot, psyqo::SoftMath::Axis::X, cube.m_trig);  | 
 | 171 | +    auto rot = psyqo::SoftMath::generateRotationMatrix33(m_rot, psyqo::SoftMath::Axis::Y, cube.m_trig);  | 
 | 172 | + | 
 | 173 | +    // Multiply the X and Y rotation matrices together  | 
 | 174 | +    psyqo::SoftMath::multiplyMatrix33(transform, rot, &transform);  | 
 | 175 | + | 
 | 176 | +    // Generate a Z-axis rotation matrix (Empty, but it's here for your use)  | 
 | 177 | +    psyqo::SoftMath::generateRotationMatrix33(0, 0, psyqo::SoftMath::Axis::Z, cube.m_trig);  | 
 | 178 | + | 
 | 179 | +    // Apply the combined rotation and write it to the pseudo register for the cube's rotation  | 
 | 180 | +    psyqo::SoftMath::multiplyMatrix33(transform, rot, &transform);  | 
 | 181 | +    psyqo::GTE::writeUnsafe<psyqo::GTE::PseudoRegister::Rotation>(transform);  | 
 | 182 | +      | 
 | 183 | + | 
 | 184 | + | 
 | 185 | +    int faceNum = 0;  | 
 | 186 | + | 
 | 187 | +    for(auto face : c_cubeFaces) {  | 
 | 188 | + | 
 | 189 | +        // We load the first 3 vertices into the GTE. We can't do all 4 at once because the GTE   | 
 | 190 | +        // handles only 3 at a time...  | 
 | 191 | +        psyqo::GTE::writeUnsafe<psyqo::GTE::PseudoRegister::V0>(c_cubeVertices[face.vertices[0]]);  | 
 | 192 | +        psyqo::GTE::writeUnsafe<psyqo::GTE::PseudoRegister::V1>(c_cubeVertices[face.vertices[1]]);  | 
 | 193 | +        psyqo::GTE::writeUnsafe<psyqo::GTE::PseudoRegister::V2>(c_cubeVertices[face.vertices[2]]);  | 
 | 194 | +          | 
 | 195 | +        // We perform rtpt (Perspective transformation) to the three verticies.  | 
 | 196 | +        psyqo::GTE::Kernels::rtpt();  | 
 | 197 | + | 
 | 198 | +        // Nclip determines the winding of the vertices, used to check which direction the face is pointing.  | 
 | 199 | +        // Clockwise winding means the face is oriented towards us.  | 
 | 200 | +        psyqo::GTE::Kernels::nclip();  | 
 | 201 | + | 
 | 202 | +        // Read the result of nclip and skip rendering this face if it's not facing us  | 
 | 203 | +        uint32_t mac0 = 0;  | 
 | 204 | +        psyqo::GTE::read<psyqo::GTE::Register::MAC0>(&mac0);  | 
 | 205 | +        if(mac0 <= 0)  | 
 | 206 | +            continue;  | 
 | 207 | + | 
 | 208 | +        // Since the GTE can only handle 3 vertices at a time, we need to store our first vertex   | 
 | 209 | +        // so we can write our last one.  | 
 | 210 | +        psyqo::GTE::read<psyqo::GTE::Register::SXY0>(&projected[0].packed);  | 
 | 211 | + | 
 | 212 | +        // Write the last vertex  | 
 | 213 | +        psyqo::GTE::writeSafe<psyqo::GTE::PseudoRegister::V0>(c_cubeVertices[face.vertices[3]]);  | 
 | 214 | + | 
 | 215 | +        // Perform rtps (Perspective transformation) to the last vertice (rtpS - single, rtpT - triple).  | 
 | 216 | +        psyqo::GTE::Kernels::rtps();  | 
 | 217 | +          | 
 | 218 | +        // Calculate the average Z for the z-Index to be put in the ordering table  | 
 | 219 | +        psyqo::GTE::Kernels::avsz4();  | 
 | 220 | +        uint32_t zIndex = 0;  | 
 | 221 | +        psyqo::GTE::read<psyqo::GTE::Register::OTZ>(&zIndex);  | 
 | 222 | + | 
 | 223 | + | 
 | 224 | +        // If the Z-index is out of bounds for our ordering table, we skip rendering this face.  | 
 | 225 | +        if(zIndex < 0 || zIndex >= ORDERING_TABLE_SIZE)   | 
 | 226 | +            continue;  | 
 | 227 | +          | 
 | 228 | +        // Read the 3 remaining vertices from the GTE  | 
 | 229 | +        psyqo::GTE::read<psyqo::GTE::Register::SXY0>(&projected[1].packed);  | 
 | 230 | +        psyqo::GTE::read<psyqo::GTE::Register::SXY1>(&projected[2].packed);  | 
 | 231 | +        psyqo::GTE::read<psyqo::GTE::Register::SXY2>(&projected[3].packed);  | 
 | 232 | + | 
 | 233 | +        // Take a Quad fragment from our array, set its vertices, color and make it opaque  | 
 | 234 | +        auto& quad = m_quads[faceNum];  | 
 | 235 | +        quad.primitive.setPointA(projected[0]);  | 
 | 236 | +        quad.primitive.setPointB(projected[1]);  | 
 | 237 | +        quad.primitive.setPointC(projected[2]);  | 
 | 238 | +        quad.primitive.setPointD(projected[3]);  | 
 | 239 | +        quad.primitive.setColor(face.color);  | 
 | 240 | +        quad.primitive.setOpaque();  | 
 | 241 | + | 
 | 242 | +        // Insert the Quad fragment into the ordering table at the calculated Z-index.  | 
 | 243 | +        ot.insert(quad, zIndex);  | 
 | 244 | +        faceNum++;  | 
 | 245 | +    }  | 
 | 246 | +      | 
 | 247 | +    // Send the entire ordering table as a DMA chain to the GPU.      | 
 | 248 | +    gpu().chain(ot);  | 
 | 249 | +    m_rot += 0.005_pi;  | 
 | 250 | +}  | 
 | 251 | + | 
 | 252 | + | 
 | 253 | +int main() {return cube.run();}  | 
 | 254 | + | 
0 commit comments