Saturday, August 4, 2018

Dreamcast Tutorial: Drawing a Triangle with the PVR

When diving into Dreamcast development, the PVR was the most confusing thing to me. Coming from a background of Unity and Unreal, having to directly access the graphics chip was a little strange.

A few summers ago I dove into the world of OpenGL, and quickly found myself way over my head! However, looking back, I ended up learning a lot more than I thought I did! Some of the concepts from learning OpenGL end up crossing over with the PVR, given how the functions are setup.

I'll attach the OpenGL tutorial I followed in the references below, in case anyone is interested!



What Is the PVR?

The PVR is the Dreamcast's built-in graphics chip. It handles all the heavy lifting when it comes to rendering. This is in contrast to software rendering (which people seem to love making tutorials about), which uses the CPU to render pixels on the screen and is much slower.

Slower? What do you mean?
Rendering using the PVR is much faster than using the CPU, because the PVR was literally made for this! The CPU would get bogged down by the 307,200 pixels it would have to draw. However, the PVR, and any graphics chip for that matter, is specifically designed to handle drawing all of those pixels!

It's always a bit confusing when people talk about "slower" and "faster" in regards to programming, but if you were to handle all of the rendering through the CPU, the CPU wouldn't have any time to do any other calculations, and your game's performance would suffer as a result.


There's a ton of documentation all over on the hardware, and how it functions, but very little on how to actually use it (aside from this tutorial, but quite frankly I was left a little confused afterwards).

The end goal of this tutorial will be to get something like the following on your screen using only the PVR! No OpenGL, no SDL, no software rendering!


Relevant Example Projects

  • kos/examples/dreamcast/png/example.c
    More or less what I'll be basing this tutorial off.
  • kos/examples/dreamcast/pvr
    Literally anything in here will be relevant, but probably more advanced.



The Code


A lot more here than the input tutorial! I don't want to break down every single line here, so I made sure to put a sufficient amount of comments to help people along! Also, if you're struggling to figure out where this is coming from, I'd highly encourage checking out the KOS documentation!

Initialization



First, we have to initialize our PVR. We do this by first setting up the video mode, more details on that can be found here, and then we initialize the default values for the PVR. If we don't do this, nothing will show up on the screen!

The Render Loop



Like most things in game development, we're going to want to draw our scene in a loop. Here we have our Draw() function sandwiched between a bunch of other nonsensical PVR-related functions. The comments should help, but the big idea here is that we're setting up a list of polygons to draw with pvr_list_begin, and then calling a function that handles the drawing of them.

I included the transparency list from the aforementioned PVR tutorial just to show where it'd go if you had anything that needed transparency.

The Draw Function


Here we handle setting up our PVR polygon context, header, and vertices we're going to be rendering.

Again, most of this is already commented, so I won't explain a ton. The idea is that we need a PVR header in order to actually render anything. We take our context, with our list of polygons to render, and compile it into a header that the PVR uses to render our vertices.

The Vertices


Here we have a single vertex. The pvr_vertex_t struct consists of X, Y, and Z values for positioning (Z being depth), and U and V values for texturing. Vertices are a big part of 3D, so if you're not familiar with them, I'd recommend brushing up on things. Download Blender, mess around with it, and things will start to make sense.

Note: The aforementioned PVR tutorial makes a point to state that triangle vertices must be rendered in a clockwise fashion. Rendering them in any other order may result in issues.

We also have my favorite part: The color! Our vertices' colors (vert.argb) consist of an alpha value (transparency), followed by our red value, green value and finally blue value! We use PVR_PACK_COLOR to get our color values.

You can change these colors to whatever you want! I just went with the traditional red corner, green corner, and blue corner.

And that's pretty much it! We pack our vertices into primitives to render, the PVR runs through our list of vertices, and boom! Triangle!



A Few Notes


If you're like me, you probably tried to mess with the Z-values of the vertices to see if you could do something fancy. If you did, you'll probably notice you'll get something weird like this (where the bottom-right Z value is < 1)


Why is that? Isn't Z our depth? Well, it is, but this is showing us that our camera is rendering in orthographically, and not with perspective.

An example of perspective vs orthographic projections. Author: Unknown

If you trick your mind into believing that the red corner in the triangle above being really far away, it makes a little more sense.

I'm not quite sure how to get perspective working just yet, but I'm quite eager to find out! I'd really love to provide some solid tutorials on how to work with a camera in a scene, once I figure it out, myself!

References


3 comments:

  1. Hello! Thanks for the tutorials, they were very helpful, especially this one. I just wanted to share here that I found that pvr.h doesn't cover perspective transformations. In other words, programming PVR for now only in 2D, if you want 3D you have to use KGL or GLdc. This is reported in a comment at the beginning of the file. See for yourself, follow file link: http://gamedev.allusion.net/docs/kos-current/pvr_8h_source.html

    ReplyDelete
    Replies
    1. Ahh, interesting! Thanks for the info! I'll definitely keep that in mind.

      I think the documentation might be indicating that the PVR itself doesn't have built-in functionality for doing transformations, but I believe you can still rely on third-party libraries to help! For instance, it also seems to mention that the PVR API doesn't support matrix transformations at all, but I seem to recall using the matrix3d.h library when I was messing around with the PVR... I'm not sure though! It'd be worth looking into!

      In any case, I'd strongly recommend people to use GLdc now-a-days anyway! It's so much nicer to work with! :)

      Delete
  2. Yes friend, it sure is possible. I think I expressed myself badly, what I meant was that the PVR API doesn't support transformations. But no doubt you can get around this limitation by implementing the matrices or using third-party libraries as you said. By the way, this is a very interesting project. Let's see what we got! See you

    ReplyDelete