The display is run by a simple character-based driver. The 800x480 screen is broken into 8x8 character tiles, and the pixel values are scanned out on the fly at 60 Hz.
I plan to use this display driver to debug a CPU (that I haven't written yet) by replacing parts of the text ROM with live links to CPU state. This means I can debug before the CPU has any I/O capabilities, showing all of the registers on-screen and maybe even looking into pipeline state.
Before today, mvminer was a single-threaded program. Today, mvminer has a job queue and can load chunks in the background! Gone are the days of waiting several minutes for the (horribly slow) texture generator to run 1,048,576 times before the display comes up.
Mapping tiles into the virtual texture and loading vertices cause a slight pause while they block the GL thread, so I will build rate limiters into their classes. Right now the virtual tile map and shared vertex buffer can only be operated from the GL thread; they should buffer changes and submit them in reasonably-sized batches per frame.
Here are a few screenshots taken as chunks load around the origin:
mvminer has started loading
oh my this texture generator is slow
mvminer continues to load
As always in the first undesigned iteration, this implementation is a complete hack (the asynchronous jobs are all queued up on startup and aren't owned -- quitting before they finish causes a segfault as their working sets are reclaimed by the system).
Once the code is cleaner again I will need to start on a "loading manager" (not sure of the clearest name for it). This system will maintain a prioritized record of nearby chunks and, if they are loaded, their exposed tile counts. It will issue all of the load/save/map-tiles/unmap-tiles requests.
This image was rendered in a single draw call from a shared VBO/IBO polygon pool. Right now it can hold 1M tiles in a 32MB VBO with a 24MB IBO. The index buffer is extra large since I have to use 32-bit indices, but it ends up being a huge performance win when the polygon pool is only partially full; unused polygons can be made explicitly degenerate by zeroing out the indices.
I may end up switching back to unindexed triangle soup in a 48MB VBO depending on performance. (or 32MB of unindexed quads, but quads are deprecated in newer GL releases) The goal will be to keep the polygon pool and virtual tile map full at all times, prioritizing exposed tiles closest to the player. Of course, none of that logic has been implemented yet, and the indexed-triangle solution is better for a scene like this where the buffers are only 22% full.
I've put in a few evenings working on mvminer's renderer. I have implemented a unique texturing system and I just finished throwing in a complete-hack physics test.
One of my design goals for mvminer is for each block to have a unique, procedurally-generated texture. This will make single material structures more interesting and opens up possibilities for player paintings or other dynamic texture changes.
The first implementation of unique texturing
When I first got it working I wasn't doing actual seeded procedural textures. The screenshot above uses rand() to fill in the index half of a bunch of DXT1 blocks, the color half being selected from a table based on block type. That, of course, is completely nondeterministic and is unrelated to spatial location. With the basics working, I implemented a straightforward Perlin noise function that takes an absolute world location.
The inside of a large box textured with a few octaves of Perlin noise
The result looks pretty good! At some point in here I decided to fix block sizes at 0.5 m to a side (half of Minecraft's block size) and texture resolution at 8x8 per block face. To get a better idea of the noise's spatial qualities I hacked a sphere generator into the new-chunk generation code:
Behold the dirtsphere
For comparison, I set up a similar screenshot, but with each block having a randomized origin location. This represents what a user-created structure would look like, with spatially distant blocks brought together.
Dirtsphere, chaos edition
At this point I was getting tired of waiting for texture generation. Generating ~3000 tiles was taking about 20 seconds, so I switched back to rand() to work on some other things. First up, a simple terrain generator.
mvminer is a game about dirt
I switched the Perlin tile generator back on for that screenshot, and it took a few minutes to come up. My plan is to generate batches of noise, computing corner gradients once per batch and then lerping (well, quintic-erping) a bunch of samples in a tight loop, but that can be done later.
Last night I decided to try integrating a physics engine. I had used the Open Dynamics Engine in a previous project and I was getting to know it quite well, but the limited collision detection system was getting annoying. I thought it was time to try Bullet, so I downloaded the latest archive, built it, and had a horrible hack implementation up and running in a few hours.
There's something odd about this box ... could this be what they call rotation?
Once again, it's a complete hack. I need to code up a solid local physics entity system. Minecraft has weird behavior out in the Far Lands, where 32-bit floats start to have much less sub-integer precision. I intend to avoid that by segmenting the simulation along chunk boundaries and keeping coordinates nice and small. Eventually I'd like to remove Bullet in favor of a custom fixed-point physics system, for the dual purposes of absolute determinism and educational wheel reinvention.
Up next, an in-depth description of the virtual tile map system that makes unique texturing possible (and easy!).
So I've been playing Minecraft. That game eats hours. It's lots of fun, but I find myself wanting more. The Nether/Nexus doesn't work in multiplayer. Textures aren't mipmapped when minified. The set of movable objects is very limited. Redstone is cool, but could be used for so much more. And what does a developer do when existing software isn't quite perfect? Roll your own!
The core mechanics of Minecraft are very simple. The world is an expansive cubic grid of blocks, 128 blocks high but almost unbounded in width and depth. Most blocks are static until modified by outside influences. The potential data set of a world is huge, so it is divided into 16x16 chunks (spanning the full 128 blocks vertically). These chunks are generated procedurally on demand and are persisted to disk.
This I could do. My physics sandbox project has gone into full-on analysis paralysis, and (let's admit it) reinventing wheels in a fresh codebase is just fun.
4D coordinate system: Entering the Nether/Nexus would just be a coordinate transformation. Shift x/y/z to align with the new world and update w.
Free blocks: Detached blocks become physically simulated. Every collision is between two unit cubes!
Stress and fracture: Collisions trigger a local static stress analysis. Nothing ever bends, but world blocks can be freed and free blocks can be separated.