commit
a1f1010fad
20 changed files with 20216 additions and 2 deletions
@ -0,0 +1,176 @@ |
|||||||
|
# An interview with STB about stb_voxel_render.h |
||||||
|
|
||||||
|
**Q:** |
||||||
|
I suppose you really like Minecraft? |
||||||
|
|
||||||
|
**A:** |
||||||
|
Not really. I mean, I do own it and play it some, and |
||||||
|
I do watch YouTube videos of other people playing it |
||||||
|
once in a while, but I'm not saying it's that great. |
||||||
|
|
||||||
|
But I do love voxels. I've been playing with voxel rendering |
||||||
|
since the mid-late 90's when we were still doing software |
||||||
|
rendering and thinking maybe polygons weren't the answer. |
||||||
|
Once GPUs came along that kind of died off, at least until |
||||||
|
Minecraft brought it back to attention. |
||||||
|
|
||||||
|
**Q:** |
||||||
|
Do you expect people will make a lot of Minecraft clones |
||||||
|
with this? |
||||||
|
|
||||||
|
**A:** |
||||||
|
I hope not! |
||||||
|
|
||||||
|
For one thing, it's a terrible idea for the |
||||||
|
developer. Remember before Minecraft was on the Xbox 360, |
||||||
|
there were a ton of "indie" clones (some maybe making |
||||||
|
decent money even), but then the real Minecraft came out |
||||||
|
and just crushed them (as far as I know). It's just not |
||||||
|
something you really want to compete with. |
||||||
|
|
||||||
|
The reason I made this library is because I'd like |
||||||
|
to see more games with Minecraft's *art style*, not |
||||||
|
necessary its *gameplay*. |
||||||
|
|
||||||
|
I can understand the urge to clone the gameplay. When |
||||||
|
you have a world made of voxels/blocks, there are a |
||||||
|
few things that become incredibly easy to do that would |
||||||
|
otherwise be very hard (at least for an indie) to do in 3D. |
||||||
|
One thing is that procedural generation becomes much easier. |
||||||
|
Another is that destructible environments are easy. Another |
||||||
|
is that you have a world where your average user can build |
||||||
|
stuff that they find satisfactory. |
||||||
|
|
||||||
|
Minecraft is at a sort of local maximum, a sweet spot, where |
||||||
|
it leverages all of those easy-to-dos. And so I'm sure it's |
||||||
|
hard to look at the space of 'games using voxels' and move |
||||||
|
away from that local maximum, to give up some of that. |
||||||
|
But I think that's what people should do. |
||||||
|
|
||||||
|
**Q:** |
||||||
|
So what else can people do with stb_voxel_render? |
||||||
|
|
||||||
|
**A:** |
||||||
|
All of those benefits I mentioned above are still valid even |
||||||
|
if you stay away from the sweet spot. You can make a 3D roguelike |
||||||
|
without player-creation/destruction that uses procedural generation. |
||||||
|
You could make a shooter with pre-designed maps but destructible |
||||||
|
environments. |
||||||
|
|
||||||
|
And I'm sure there are other possible benefits to using voxels/blocks. |
||||||
|
Hopefully this will make it easier for people to explore the space. |
||||||
|
|
||||||
|
The library has a pretty wide range of features to allow |
||||||
|
people to come up with some distinctive looks. For example, |
||||||
|
the art style of Continue?9876543210 was one of the inspirations |
||||||
|
for trying to make the multitexturing capabilities flexible. |
||||||
|
I'm terrible at art, so this isn't really something I can |
||||||
|
come up with myself, but I tried to put in flexible |
||||||
|
technology that could be used multiple ways. |
||||||
|
|
||||||
|
One thing I did intentionally was try to make it possible to |
||||||
|
make nicer looking ground terrain, using the half-height |
||||||
|
slopes and "weird slopes". There are Minecraft mods with |
||||||
|
drivable cars and they just go up these blocky slopes and, |
||||||
|
like, what? So I wanted you to be able to make smoother |
||||||
|
terrain, either just for the look, or for vehicles etc. |
||||||
|
Also, you can cross-fade between two ground textures for |
||||||
|
that classic bad dirt/grass transition that has shipped |
||||||
|
in plenty of professional games. Of course, you could |
||||||
|
just use a separate non-voxel ground renderer for all of |
||||||
|
this. But this way, you can seamlessly integrate everything |
||||||
|
else with it. E.g. in your authoring tool (or procedural |
||||||
|
generation) you can make smooth ground and then cut a |
||||||
|
sharp-edged hole in it for a building's basement or whatever. |
||||||
|
|
||||||
|
Another thing you can do is work at a very different scale. |
||||||
|
In Minecraft, a person is just under 2 blocks tall. In |
||||||
|
Ace of Spades, a person is just under 3 blocks tall. Why |
||||||
|
not 4 or 6? Well, partly because you just need a lot more |
||||||
|
voxels; if a meter is 2 voxels in Mineraft and 4 voxels in |
||||||
|
your game, and you draw the same number of voxels due to |
||||||
|
hardware limits, then your game has half the view distance |
||||||
|
of Minecraft. Since stb_voxel_render is designed to keep |
||||||
|
the meshes small and render efficiently, you can push the |
||||||
|
view distance out further than Minecraft--or use a similar |
||||||
|
view distance and a higher voxel resolution. You could also |
||||||
|
stop making infinite worlds and work at entirely different |
||||||
|
scales; where Minecraft is 1 voxel per meter, you could |
||||||
|
have 20 voxels per meter and make a small arena that's |
||||||
|
50 meters wide and 5 meters tall. |
||||||
|
|
||||||
|
Back when the voxel game Voxatron was announced, the weekend |
||||||
|
after the trailer came out I wrote my own little GPU-accelerated |
||||||
|
version of the engine and thought that was pretty cool. I've |
||||||
|
been tempted many times to extract that and release it, but |
||||||
|
I don't want to steal Voxatron's thunder so I've avoided |
||||||
|
it. You could use this engine to do the same kind of thing, |
||||||
|
although it won't be as efficient as an engine dedicated to |
||||||
|
that style of thing would be. (For example, if you're building |
||||||
|
the whole mesh from scratch every frame--which you should do |
||||||
|
because you want to enable that worst case--you can skip |
||||||
|
creating voxel faces that face away from the camera, since |
||||||
|
they can never be seen.) |
||||||
|
|
||||||
|
**Q:** |
||||||
|
What one thing would you really like to see somebody do? |
||||||
|
|
||||||
|
**A:** |
||||||
|
Before Unity, 3D has seemed deeply problematic in the indie |
||||||
|
space. Software like GameMaker has tried to support 3D but |
||||||
|
it seems like little of note has been done with it. |
||||||
|
|
||||||
|
Minecraft has shown that people can build worlds with that |
||||||
|
toolset far more easily than we've ever seen from those |
||||||
|
other tools. Obviously people have done great things with |
||||||
|
Unity, but those people are much closer to professional |
||||||
|
developers; typically they still need real 3D modelling |
||||||
|
and all of that stuff. |
||||||
|
|
||||||
|
So what I'd really like to see is someone build some kind |
||||||
|
of voxel-game-construction-set. Start with stb_voxel_render, |
||||||
|
maybe expose all the flexibility of stb_voxel_render (so |
||||||
|
people can do different things). Thrown in lua or something |
||||||
|
else for scripting, make some kind of editor that feels |
||||||
|
at least as good as Minecraft and Infinifactory, and see |
||||||
|
where that gets you. |
||||||
|
|
||||||
|
**Q:** |
||||||
|
Why'd you make this library? |
||||||
|
|
||||||
|
**A:** |
||||||
|
Mainly as a way of releasing this technology I've been working |
||||||
|
on since 2011 and seemed unlikely to ever ship myself. In 2011 |
||||||
|
I was playing the voxel shooter Ace of Spades. One of the maps |
||||||
|
that we played on was a partial port of Broville (which is the |
||||||
|
first Minecraft map in stb_voxel_render release trailer). I'd |
||||||
|
made a bunch of procedural level generators for the game, and |
||||||
|
I started trying to make a city generator inspired by Broville. |
||||||
|
|
||||||
|
But I realized it would be a lot of work, and of very little |
||||||
|
value (most of my maps didn't get much play because people |
||||||
|
preferred to play on maps where they could charge straight |
||||||
|
at the enemies and shoot them as fast as possible). So I |
||||||
|
wrote my own voxel engine and started working on a procedural |
||||||
|
city game. But I got bogged down after I finally got the road |
||||||
|
generator working and never got anywhere with building |
||||||
|
generation or gameplay. |
||||||
|
|
||||||
|
stb_voxel_render is actually a complete rewrite from scratch, |
||||||
|
but it's based a lot on what I learned from that previous work. |
||||||
|
|
||||||
|
**Q:** |
||||||
|
About the release video... how long did that take to edit? |
||||||
|
|
||||||
|
**A:** |
||||||
|
About seven or eight hours. I had the first version done in |
||||||
|
maybe six or seven hours, but then I realized I'd left out |
||||||
|
one clip, and when I went back to add it I also gussied up |
||||||
|
a couple other moments in the video. But there was something |
||||||
|
basically identical to it that was done in around six. |
||||||
|
|
||||||
|
**Q:** |
||||||
|
Ok, that's it. Thanks, me. |
||||||
|
|
||||||
|
**A:** |
||||||
|
Thanks *me!* |
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,85 @@ |
|||||||
|
# FAQ |
||||||
|
|
||||||
|
### How to run it? |
||||||
|
|
||||||
|
There's no GUI. Find a directory with Minecraft Anvil files (.mca). |
||||||
|
Copy a Minecraft "terrain.png" into that directory (do a google |
||||||
|
image search). Run from that directory. |
||||||
|
|
||||||
|
### How accurate is this as a Minecraft viewer? |
||||||
|
|
||||||
|
Not very. Many Minecraft blocks are not handled correctly: |
||||||
|
|
||||||
|
* No redstone, rails, or other "flat" blocks |
||||||
|
* No signs, doors, fences, carpets, or other complicated geometry |
||||||
|
* Stairs are turned into ramps |
||||||
|
* Upper slabs turn into lower slabs |
||||||
|
* Wood types only for blocks, not stairs, slabs, etc |
||||||
|
* Colored glass becomes regular glass |
||||||
|
* Glass panes become glass blocks |
||||||
|
* Water is opaque |
||||||
|
* Water level is incorrect |
||||||
|
* No biome coloration |
||||||
|
* Cactus is not shrunk, shows holes |
||||||
|
* Chests are not shrunk |
||||||
|
* Double-chests draw as two chests |
||||||
|
* Pumpkins etc. are not rotated properly |
||||||
|
* Torches are drawn hackily, do not attach to walls |
||||||
|
* Incorrect textures for blocks that postdate terrain.png |
||||||
|
* Transparent textures have black fringes due to non-premultiplied-alpha |
||||||
|
* Skylight and block light are combined in a single value |
||||||
|
* Only blocks at y=1..255 are shown (not y=0) |
||||||
|
* If a 32x32x256 "quad-chunk" needs more than 800K quads, isn't handled (very unlikely) |
||||||
|
|
||||||
|
Some of these are due to engine limitations, and some of |
||||||
|
these are because I didn't make the effort since my |
||||||
|
goal was to make a demo for stb_voxel_render.h, not |
||||||
|
to make a proper Minecraft viewer. |
||||||
|
|
||||||
|
|
||||||
|
### Could this be turned into a proper Minecraft viewer? |
||||||
|
|
||||||
|
Yes and no. Yes, you could do it, but no, it wouldn't |
||||||
|
really resemble this code that much anymore. |
||||||
|
|
||||||
|
You could certainly use this engine to |
||||||
|
render the parts of Minecraft it works for, but many |
||||||
|
of the things it doesn't handle it can't handle at all |
||||||
|
(stairs, water, fences, carpets, etc) because it uses |
||||||
|
low-precision coordinates to store voxel data. |
||||||
|
|
||||||
|
You would have to render all of the stuff it doesn't |
||||||
|
handle through another rendering path. In a game (not |
||||||
|
a viewer) you would need such a path for movable entities |
||||||
|
like doors and carts anyway, so possibly handling other |
||||||
|
things that way wouldn't be so bad. |
||||||
|
|
||||||
|
Rails, ladders, and redstone lines could be implemented by |
||||||
|
using tex2 to overlay those effects, but you can't rotate |
||||||
|
tex1 and tex2 independently, so there may be cases where |
||||||
|
the underlying texture needs a different rotation from the |
||||||
|
overlaid texture, which would require separate rendering. |
||||||
|
Handling redstone's brightness being different from underlying |
||||||
|
block's brightness would require separate rendering. |
||||||
|
|
||||||
|
You can use the face-color effect to do biome coloration, |
||||||
|
but the change won't be smooth the way it is in Minecraft. |
||||||
|
|
||||||
|
|
||||||
|
### Why isn't building the mesh data faster? |
||||||
|
|
||||||
|
Partly because converting from minecraft data is expensive. |
||||||
|
|
||||||
|
Here is the approximate breakdown of an older version |
||||||
|
of this executable and lib that did the building single-threaded. |
||||||
|
|
||||||
|
* 25% loading & parsing minecraft files (4/5ths of this is my crappy zlib) |
||||||
|
* 18% converting from minecraft blockids & lighting to stb blockids & lighting |
||||||
|
* 10% reordering from data[z][y]\[x] (minecraft-style) to data[y][x]\[z] (stb-style) |
||||||
|
* 40% building mesh data |
||||||
|
* 7% uploading mesh data to OpenGL |
||||||
|
|
||||||
|
I did do significant optimizations after the above, so the |
||||||
|
final breakdown is different, but it should give you some |
||||||
|
sense of the costs. |
||||||
|
|
@ -0,0 +1,598 @@ |
|||||||
|
#define _WIN32_WINNT 0x400 |
||||||
|
|
||||||
|
#include <assert.h> |
||||||
|
#include <windows.h> |
||||||
|
|
||||||
|
// stb.h
|
||||||
|
#define STB_DEFINE |
||||||
|
#include "stb.h" |
||||||
|
|
||||||
|
// stb_gl.h
|
||||||
|
#define STB_GL_IMPLEMENTATION |
||||||
|
#define STB_GLEXT_DEFINE "glext_list.h" |
||||||
|
#include "stb_gl.h" |
||||||
|
|
||||||
|
// SDL
|
||||||
|
#include "sdl.h" |
||||||
|
#include "SDL_opengl.h" |
||||||
|
|
||||||
|
// stb_glprog.h
|
||||||
|
#define STB_GLPROG_IMPLEMENTATION |
||||||
|
#define STB_GLPROG_ARB_DEFINE_EXTENSIONS |
||||||
|
#include "stb_glprog.h" |
||||||
|
|
||||||
|
// stb_image.h
|
||||||
|
#define STB_IMAGE_IMPLEMENTATION |
||||||
|
#include "stb_image.h" |
||||||
|
|
||||||
|
// stb_easy_font.h
|
||||||
|
#include "stb_easy_font.h" // doesn't require an IMPLEMENTATION |
||||||
|
|
||||||
|
#include "caveview.h" |
||||||
|
|
||||||
|
char *game_name = "caveview"; |
||||||
|
|
||||||
|
|
||||||
|
#define REVERSE_DEPTH |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
static void print_string(float x, float y, char *text, float r, float g, float b) |
||||||
|
{ |
||||||
|
static char buffer[99999]; |
||||||
|
int num_quads; |
||||||
|
|
||||||
|
num_quads = stb_easy_font_print(x, y, text, NULL, buffer, sizeof(buffer)); |
||||||
|
|
||||||
|
glColor3f(r,g,b); |
||||||
|
glEnableClientState(GL_VERTEX_ARRAY); |
||||||
|
glVertexPointer(2, GL_FLOAT, 16, buffer); |
||||||
|
glDrawArrays(GL_QUADS, 0, num_quads*4); |
||||||
|
glDisableClientState(GL_VERTEX_ARRAY); |
||||||
|
} |
||||||
|
|
||||||
|
static float text_color[3]; |
||||||
|
static float pos_x = 10; |
||||||
|
static float pos_y = 10; |
||||||
|
|
||||||
|
static void print(char *text, ...) |
||||||
|
{ |
||||||
|
char buffer[999]; |
||||||
|
va_list va; |
||||||
|
va_start(va, text); |
||||||
|
vsprintf(buffer, text, va); |
||||||
|
va_end(va); |
||||||
|
print_string(pos_x, pos_y, buffer, text_color[0], text_color[1], text_color[2]); |
||||||
|
pos_y += 10; |
||||||
|
} |
||||||
|
|
||||||
|
float camang[3], camloc[3] = { 60,22,77 }; |
||||||
|
float player_zoom = 1.0; |
||||||
|
float rotate_view = 0.0; |
||||||
|
|
||||||
|
|
||||||
|
void camera_to_worldspace(float world[3], float cam_x, float cam_y, float cam_z) |
||||||
|
{ |
||||||
|
float vec[3] = { cam_x, cam_y, cam_z }; |
||||||
|
float t[3]; |
||||||
|
float s,c; |
||||||
|
s = (float) sin(camang[0]*3.141592/180); |
||||||
|
c = (float) cos(camang[0]*3.141592/180); |
||||||
|
|
||||||
|
t[0] = vec[0]; |
||||||
|
t[1] = c*vec[1] - s*vec[2]; |
||||||
|
t[2] = s*vec[1] + c*vec[2]; |
||||||
|
|
||||||
|
s = (float) sin(camang[2]*3.141592/180); |
||||||
|
c = (float) cos(camang[2]*3.141592/180); |
||||||
|
world[0] = c*t[0] - s*t[1]; |
||||||
|
world[1] = s*t[0] + c*t[1]; |
||||||
|
world[2] = t[2]; |
||||||
|
} |
||||||
|
|
||||||
|
// camera worldspace velocity
|
||||||
|
float cam_vel[3]; |
||||||
|
|
||||||
|
int controls; |
||||||
|
|
||||||
|
#define MAX_VEL 150.0f // blocks per second
|
||||||
|
#define ACCEL 6.0f |
||||||
|
#define DECEL 3.0f |
||||||
|
|
||||||
|
#define STATIC_FRICTION DECEL |
||||||
|
#define EFFECTIVE_ACCEL (ACCEL+DECEL) |
||||||
|
|
||||||
|
// dynamic friction:
|
||||||
|
//
|
||||||
|
// if going at MAX_VEL, ACCEL and friction must cancel
|
||||||
|
// EFFECTIVE_ACCEL = DECEL + DYNAMIC_FRIC*MAX_VEL
|
||||||
|
#define DYNAMIC_FRICTION (ACCEL/(float)MAX_VEL) |
||||||
|
|
||||||
|
float view_x_vel = 0; |
||||||
|
float view_z_vel = 0; |
||||||
|
float pending_view_x; |
||||||
|
float pending_view_z; |
||||||
|
float pending_view_x; |
||||||
|
float pending_view_z; |
||||||
|
|
||||||
|
void process_tick_raw(float dt) |
||||||
|
{ |
||||||
|
int i; |
||||||
|
float thrust[3] = { 0,0,0 }; |
||||||
|
float world_thrust[3]; |
||||||
|
|
||||||
|
// choose direction to apply thrust
|
||||||
|
|
||||||
|
thrust[0] = (controls & 3)== 1 ? EFFECTIVE_ACCEL : (controls & 3)== 2 ? -EFFECTIVE_ACCEL : 0; |
||||||
|
thrust[1] = (controls & 12)== 4 ? EFFECTIVE_ACCEL : (controls & 12)== 8 ? -EFFECTIVE_ACCEL : 0; |
||||||
|
thrust[2] = (controls & 48)==16 ? EFFECTIVE_ACCEL : (controls & 48)==32 ? -EFFECTIVE_ACCEL : 0; |
||||||
|
|
||||||
|
// @TODO clamp thrust[0] & thrust[1] vector length to EFFECTIVE_ACCEL
|
||||||
|
|
||||||
|
camera_to_worldspace(world_thrust, thrust[0], thrust[1], 0); |
||||||
|
world_thrust[2] += thrust[2]; |
||||||
|
|
||||||
|
for (i=0; i < 3; ++i) { |
||||||
|
float acc = world_thrust[i]; |
||||||
|
cam_vel[i] += acc*dt; |
||||||
|
} |
||||||
|
|
||||||
|
if (cam_vel[0] || cam_vel[1] || cam_vel[2]) |
||||||
|
{ |
||||||
|
float vel = (float) sqrt(cam_vel[0]*cam_vel[0] + cam_vel[1]*cam_vel[1] + cam_vel[2]*cam_vel[2]); |
||||||
|
float newvel = vel; |
||||||
|
float dec = STATIC_FRICTION + DYNAMIC_FRICTION*vel; |
||||||
|
newvel = vel - dec*dt; |
||||||
|
if (newvel < 0) |
||||||
|
newvel = 0; |
||||||
|
cam_vel[0] *= newvel/vel; |
||||||
|
cam_vel[1] *= newvel/vel; |
||||||
|
cam_vel[2] *= newvel/vel; |
||||||
|
} |
||||||
|
|
||||||
|
camloc[0] += cam_vel[0] * dt; |
||||||
|
camloc[1] += cam_vel[1] * dt; |
||||||
|
camloc[2] += cam_vel[2] * dt; |
||||||
|
|
||||||
|
view_x_vel *= (float) pow(0.75, dt); |
||||||
|
view_z_vel *= (float) pow(0.75, dt); |
||||||
|
|
||||||
|
view_x_vel += (pending_view_x - view_x_vel)*dt*60; |
||||||
|
view_z_vel += (pending_view_z - view_z_vel)*dt*60; |
||||||
|
|
||||||
|
pending_view_x -= view_x_vel * dt; |
||||||
|
pending_view_z -= view_z_vel * dt; |
||||||
|
camang[0] += view_x_vel * dt; |
||||||
|
camang[2] += view_z_vel * dt; |
||||||
|
camang[0] = stb_clamp(camang[0], -90, 90); |
||||||
|
camang[2] = (float) fmod(camang[2], 360); |
||||||
|
} |
||||||
|
|
||||||
|
void process_tick(float dt) |
||||||
|
{ |
||||||
|
while (dt > 1.0f/60) { |
||||||
|
process_tick_raw(1.0f/60); |
||||||
|
dt -= 1.0f/60; |
||||||
|
} |
||||||
|
process_tick_raw(dt); |
||||||
|
} |
||||||
|
|
||||||
|
void update_view(float dx, float dy) |
||||||
|
{ |
||||||
|
// hard-coded mouse sensitivity, not resolution independent?
|
||||||
|
pending_view_z -= dx*300; |
||||||
|
pending_view_x -= dy*700; |
||||||
|
} |
||||||
|
|
||||||
|
extern int screen_x, screen_y; |
||||||
|
extern int is_synchronous_debug; |
||||||
|
float render_time; |
||||||
|
|
||||||
|
extern int chunk_locations, chunks_considered, chunks_in_frustum; |
||||||
|
extern int quads_considered, quads_rendered; |
||||||
|
extern int chunk_storage_rendered, chunk_storage_considered, chunk_storage_total; |
||||||
|
extern int view_dist_in_chunks; |
||||||
|
extern int num_threads_active, num_meshes_started, num_meshes_uploaded; |
||||||
|
extern float chunk_server_activity; |
||||||
|
|
||||||
|
static Uint64 start_time, end_time; // render time
|
||||||
|
|
||||||
|
float chunk_server_status[32]; |
||||||
|
int chunk_server_pos; |
||||||
|
|
||||||
|
void draw_stats(void) |
||||||
|
{ |
||||||
|
int i; |
||||||
|
|
||||||
|
static Uint64 last_frame_time; |
||||||
|
Uint64 cur_time = SDL_GetPerformanceCounter(); |
||||||
|
float chunk_server=0; |
||||||
|
float frame_time = (cur_time - last_frame_time) / (float) SDL_GetPerformanceFrequency(); |
||||||
|
last_frame_time = cur_time; |
||||||
|
|
||||||
|
chunk_server_status[chunk_server_pos] = chunk_server_activity; |
||||||
|
chunk_server_pos = (chunk_server_pos+1) %32; |
||||||
|
|
||||||
|
for (i=0; i < 32; ++i) |
||||||
|
chunk_server += chunk_server_status[i] / 32.0f; |
||||||
|
|
||||||
|
stb_easy_font_spacing(-0.75); |
||||||
|
pos_y = 10; |
||||||
|
text_color[0] = text_color[1] = text_color[2] = 1.0f; |
||||||
|
print("Frame time: %6.2fms, CPU frame render time: %5.2fms", frame_time*1000, render_time*1000); |
||||||
|
print("Tris: %4.1fM drawn of %4.1fM in range", 2*quads_rendered/1000000.0f, 2*quads_considered/1000000.0f); |
||||||
|
print("Vbuf storage: %dMB in frustum of %dMB in range of %dMB in cache", chunk_storage_rendered>>20, chunk_storage_considered>>20, chunk_storage_total>>20); |
||||||
|
print("Num mesh builds started this frame: %d; num uploaded this frame: %d\n", num_meshes_started, num_meshes_uploaded); |
||||||
|
print("QChunks: %3d in frustum of %3d valid of %3d in range", chunks_in_frustum, chunks_considered, chunk_locations); |
||||||
|
print("Mesh worker threads active: %d", num_threads_active); |
||||||
|
print("View distance: %d blocks", view_dist_in_chunks*16); |
||||||
|
print("%s", glGetString(GL_RENDERER)); |
||||||
|
|
||||||
|
if (is_synchronous_debug) { |
||||||
|
text_color[0] = 1.0; |
||||||
|
text_color[1] = 0.5; |
||||||
|
text_color[2] = 0.5; |
||||||
|
print("SLOWNESS: Synchronous debug output is enabled!"); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
void draw_main(void) |
||||||
|
{ |
||||||
|
glEnable(GL_CULL_FACE); |
||||||
|
glDisable(GL_TEXTURE_2D); |
||||||
|
glDisable(GL_LIGHTING); |
||||||
|
glEnable(GL_DEPTH_TEST); |
||||||
|
#ifdef REVERSE_DEPTH |
||||||
|
glDepthFunc(GL_GREATER); |
||||||
|
glClearDepth(0); |
||||||
|
#else |
||||||
|
glDepthFunc(GL_LESS); |
||||||
|
glClearDepth(1); |
||||||
|
#endif |
||||||
|
glDepthMask(GL_TRUE); |
||||||
|
glDisable(GL_SCISSOR_TEST); |
||||||
|
glClearColor(0.6f,0.7f,0.9f,0.0f); |
||||||
|
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); |
||||||
|
|
||||||
|
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); |
||||||
|
glColor3f(1,1,1); |
||||||
|
glFrontFace(GL_CW); |
||||||
|
glEnable(GL_TEXTURE_2D); |
||||||
|
glDisable(GL_BLEND); |
||||||
|
|
||||||
|
|
||||||
|
glMatrixMode(GL_PROJECTION); |
||||||
|
glLoadIdentity(); |
||||||
|
|
||||||
|
#ifdef REVERSE_DEPTH |
||||||
|
stbgl_Perspective(player_zoom, 90, 70, 3000, 1.0/16); |
||||||
|
#else |
||||||
|
stbgl_Perspective(player_zoom, 90, 70, 1.0/16, 3000); |
||||||
|
#endif |
||||||
|
|
||||||
|
// now compute where the camera should be
|
||||||
|
glMatrixMode(GL_MODELVIEW); |
||||||
|
glLoadIdentity(); |
||||||
|
stbgl_initCamera_zup_facing_y(); |
||||||
|
|
||||||
|
glRotatef(-camang[0],1,0,0); |
||||||
|
glRotatef(-camang[2],0,0,1); |
||||||
|
glTranslatef(-camloc[0], -camloc[1], -camloc[2]); |
||||||
|
|
||||||
|
start_time = SDL_GetPerformanceCounter(); |
||||||
|
render_caves(camloc); |
||||||
|
end_time = SDL_GetPerformanceCounter(); |
||||||
|
|
||||||
|
render_time = (end_time - start_time) / (float) SDL_GetPerformanceFrequency(); |
||||||
|
|
||||||
|
glMatrixMode(GL_PROJECTION); |
||||||
|
glLoadIdentity(); |
||||||
|
gluOrtho2D(0,screen_x/2,screen_y/2,0); |
||||||
|
glMatrixMode(GL_MODELVIEW); |
||||||
|
glLoadIdentity(); |
||||||
|
glDisable(GL_TEXTURE_2D); |
||||||
|
glDisable(GL_BLEND); |
||||||
|
glDisable(GL_CULL_FACE); |
||||||
|
draw_stats(); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#pragma warning(disable:4244; disable:4305; disable:4018) |
||||||
|
|
||||||
|
#define SCALE 2 |
||||||
|
|
||||||
|
void error(char *s) |
||||||
|
{ |
||||||
|
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Error", s, NULL); |
||||||
|
exit(0); |
||||||
|
} |
||||||
|
|
||||||
|
void ods(char *fmt, ...) |
||||||
|
{ |
||||||
|
char buffer[1000]; |
||||||
|
va_list va; |
||||||
|
va_start(va, fmt); |
||||||
|
vsprintf(buffer, fmt, va); |
||||||
|
va_end(va); |
||||||
|
SDL_Log("%s", buffer); |
||||||
|
} |
||||||
|
|
||||||
|
#define TICKS_PER_SECOND 60 |
||||||
|
|
||||||
|
static SDL_Window *window; |
||||||
|
|
||||||
|
extern void draw_main(void); |
||||||
|
extern void process_tick(float dt); |
||||||
|
extern void editor_init(void); |
||||||
|
|
||||||
|
void draw(void) |
||||||
|
{ |
||||||
|
draw_main(); |
||||||
|
SDL_GL_SwapWindow(window); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
static int initialized=0; |
||||||
|
static float last_dt; |
||||||
|
|
||||||
|
int screen_x,screen_y; |
||||||
|
|
||||||
|
float carried_dt = 0; |
||||||
|
#define TICKRATE 60 |
||||||
|
|
||||||
|
float tex2_alpha = 1.0; |
||||||
|
|
||||||
|
int raw_level_time; |
||||||
|
|
||||||
|
float global_timer; |
||||||
|
int global_hack; |
||||||
|
|
||||||
|
int loopmode(float dt, int real, int in_client) |
||||||
|
{ |
||||||
|
if (!initialized) return 0; |
||||||
|
|
||||||
|
if (!real) |
||||||
|
return 0; |
||||||
|
|
||||||
|
// don't allow more than 6 frames to update at a time
|
||||||
|
if (dt > 0.075) dt = 0.075; |
||||||
|
|
||||||
|
global_timer += dt; |
||||||
|
|
||||||
|
carried_dt += dt; |
||||||
|
while (carried_dt > 1.0/TICKRATE) { |
||||||
|
if (global_hack) { |
||||||
|
tex2_alpha += global_hack / 60.0f; |
||||||
|
if (tex2_alpha < 0) tex2_alpha = 0; |
||||||
|
if (tex2_alpha > 1) tex2_alpha = 1; |
||||||
|
} |
||||||
|
//update_input();
|
||||||
|
// if the player is dead, stop the sim
|
||||||
|
carried_dt -= 1.0/TICKRATE; |
||||||
|
} |
||||||
|
|
||||||
|
process_tick(dt); |
||||||
|
draw(); |
||||||
|
|
||||||
|
return 0; |
||||||
|
} |
||||||
|
|
||||||
|
static int quit; |
||||||
|
|
||||||
|
extern int controls; |
||||||
|
|
||||||
|
void active_control_set(int key) |
||||||
|
{ |
||||||
|
controls |= 1 << key; |
||||||
|
} |
||||||
|
|
||||||
|
void active_control_clear(int key) |
||||||
|
{ |
||||||
|
controls &= ~(1 << key); |
||||||
|
} |
||||||
|
|
||||||
|
extern void update_view(float dx, float dy); |
||||||
|
|
||||||
|
void process_sdl_mouse(SDL_Event *e) |
||||||
|
{ |
||||||
|
update_view((float) e->motion.xrel / screen_x, (float) e->motion.yrel / screen_y); |
||||||
|
} |
||||||
|
|
||||||
|
void process_event(SDL_Event *e) |
||||||
|
{ |
||||||
|
switch (e->type) { |
||||||
|
case SDL_MOUSEMOTION: |
||||||
|
process_sdl_mouse(e); |
||||||
|
break; |
||||||
|
case SDL_MOUSEBUTTONDOWN: |
||||||
|
case SDL_MOUSEBUTTONUP: |
||||||
|
break; |
||||||
|
|
||||||
|
case SDL_QUIT: |
||||||
|
quit = 1; |
||||||
|
break; |
||||||
|
|
||||||
|
case SDL_WINDOWEVENT: |
||||||
|
switch (e->window.event) { |
||||||
|
case SDL_WINDOWEVENT_SIZE_CHANGED: |
||||||
|
screen_x = e->window.data1; |
||||||
|
screen_y = e->window.data2; |
||||||
|
loopmode(0,1,0); |
||||||
|
break; |
||||||
|
} |
||||||
|
break; |
||||||
|
|
||||||
|
case SDL_KEYDOWN: { |
||||||
|
int k = e->key.keysym.sym; |
||||||
|
int s = e->key.keysym.scancode; |
||||||
|
SDL_Keymod mod; |
||||||
|
mod = SDL_GetModState(); |
||||||
|
if (k == SDLK_ESCAPE) |
||||||
|
quit = 1; |
||||||
|
|
||||||
|
if (s == SDL_SCANCODE_D) active_control_set(0); |
||||||
|
if (s == SDL_SCANCODE_A) active_control_set(1); |
||||||
|
if (s == SDL_SCANCODE_W) active_control_set(2); |
||||||
|
if (s == SDL_SCANCODE_S) active_control_set(3); |
||||||
|
if (k == SDLK_SPACE) active_control_set(4);
|
||||||
|
if (s == SDL_SCANCODE_LCTRL) active_control_set(5); |
||||||
|
if (s == SDL_SCANCODE_S) active_control_set(6); |
||||||
|
if (s == SDL_SCANCODE_D) active_control_set(7); |
||||||
|
if (k == '1') global_hack = !global_hack; |
||||||
|
if (k == '2') global_hack = -1; |
||||||
|
|
||||||
|
#if 0 |
||||||
|
if (game_mode == GAME_editor) { |
||||||
|
switch (k) { |
||||||
|
case SDLK_RIGHT: editor_key(STBTE_scroll_right); break; |
||||||
|
case SDLK_LEFT : editor_key(STBTE_scroll_left ); break; |
||||||
|
case SDLK_UP : editor_key(STBTE_scroll_up ); break; |
||||||
|
case SDLK_DOWN : editor_key(STBTE_scroll_down ); break; |
||||||
|
} |
||||||
|
switch (s) { |
||||||
|
case SDL_SCANCODE_S: editor_key(STBTE_tool_select); break; |
||||||
|
case SDL_SCANCODE_B: editor_key(STBTE_tool_brush ); break; |
||||||
|
case SDL_SCANCODE_E: editor_key(STBTE_tool_erase ); break; |
||||||
|
case SDL_SCANCODE_R: editor_key(STBTE_tool_rectangle ); break; |
||||||
|
case SDL_SCANCODE_I: editor_key(STBTE_tool_eyedropper); break; |
||||||
|
case SDL_SCANCODE_L: editor_key(STBTE_tool_link); break; |
||||||
|
case SDL_SCANCODE_G: editor_key(STBTE_act_toggle_grid); break; |
||||||
|
} |
||||||
|
if ((e->key.keysym.mod & KMOD_CTRL) && !(e->key.keysym.mod & ~KMOD_CTRL)) { |
||||||
|
switch (s) { |
||||||
|
case SDL_SCANCODE_X: editor_key(STBTE_act_cut ); break; |
||||||
|
case SDL_SCANCODE_C: editor_key(STBTE_act_copy ); break; |
||||||
|
case SDL_SCANCODE_V: editor_key(STBTE_act_paste); break; |
||||||
|
case SDL_SCANCODE_Z: editor_key(STBTE_act_undo ); break; |
||||||
|
case SDL_SCANCODE_Y: editor_key(STBTE_act_redo ); break; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
#endif |
||||||
|
break; |
||||||
|
} |
||||||
|
case SDL_KEYUP: { |
||||||
|
int k = e->key.keysym.sym; |
||||||
|
int s = e->key.keysym.scancode; |
||||||
|
if (s == SDL_SCANCODE_D) active_control_clear(0); |
||||||
|
if (s == SDL_SCANCODE_A) active_control_clear(1); |
||||||
|
if (s == SDL_SCANCODE_W) active_control_clear(2); |
||||||
|
if (s == SDL_SCANCODE_S) active_control_clear(3); |
||||||
|
if (k == SDLK_SPACE) active_control_clear(4);
|
||||||
|
if (s == SDL_SCANCODE_LCTRL) active_control_clear(5); |
||||||
|
if (s == SDL_SCANCODE_S) active_control_clear(6); |
||||||
|
if (s == SDL_SCANCODE_D) active_control_clear(7); |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
static SDL_GLContext *context; |
||||||
|
|
||||||
|
static float getTimestep(float minimum_time) |
||||||
|
{ |
||||||
|
float elapsedTime; |
||||||
|
double thisTime; |
||||||
|
static double lastTime = -1; |
||||||
|
|
||||||
|
if (lastTime == -1) |
||||||
|
lastTime = SDL_GetTicks() / 1000.0 - minimum_time; |
||||||
|
|
||||||
|
for(;;) { |
||||||
|
thisTime = SDL_GetTicks() / 1000.0; |
||||||
|
elapsedTime = (float) (thisTime - lastTime); |
||||||
|
if (elapsedTime >= minimum_time) { |
||||||
|
lastTime = thisTime;
|
||||||
|
return elapsedTime; |
||||||
|
} |
||||||
|
// @TODO: compute correct delay
|
||||||
|
SDL_Delay(1); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
void APIENTRY gl_debug(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar *message, const void *param) |
||||||
|
{ |
||||||
|
ods("%s\n", message); |
||||||
|
} |
||||||
|
|
||||||
|
int is_synchronous_debug; |
||||||
|
void enable_synchronous(void) |
||||||
|
{ |
||||||
|
glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS_ARB); |
||||||
|
is_synchronous_debug = 1; |
||||||
|
} |
||||||
|
|
||||||
|
void prepare_threads(void); |
||||||
|
|
||||||
|
//void stbwingraph_main(void)
|
||||||
|
int SDL_main(int argc, char **argv) |
||||||
|
{ |
||||||
|
SDL_Init(SDL_INIT_VIDEO); |
||||||
|
|
||||||
|
prepare_threads(); |
||||||
|
|
||||||
|
SDL_GL_SetAttribute(SDL_GL_RED_SIZE , 8); |
||||||
|
SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8); |
||||||
|
SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE , 8); |
||||||
|
SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24); |
||||||
|
|
||||||
|
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_COMPATIBILITY); |
||||||
|
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); |
||||||
|
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 1); |
||||||
|
|
||||||
|
#ifdef GL_DEBUG |
||||||
|
SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_DEBUG_FLAG); |
||||||
|
#endif |
||||||
|
|
||||||
|
SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, 4); |
||||||
|
|
||||||
|
screen_x = 1920; |
||||||
|
screen_y = 1080; |
||||||
|
|
||||||
|
window = SDL_CreateWindow("caveview", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, |
||||||
|
screen_x, screen_y, |
||||||
|
SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE |
||||||
|
); |
||||||
|
if (!window) error("Couldn't create window"); |
||||||
|
|
||||||
|
context = SDL_GL_CreateContext(window); |
||||||
|
if (!context) error("Couldn't create context"); |
||||||
|
|
||||||
|
SDL_GL_MakeCurrent(window, context); // is this true by default?
|
||||||
|
|
||||||
|
SDL_SetRelativeMouseMode(SDL_TRUE); |
||||||
|
#if defined(_MSC_VER) && _MSC_VER < 1300 |
||||||
|
// work around broken behavior in VC6 debugging
|
||||||
|
if (IsDebuggerPresent()) |
||||||
|
SDL_SetHint(SDL_HINT_MOUSE_RELATIVE_MODE_WARP, "1"); |
||||||
|
#endif |
||||||
|
|
||||||
|
stbgl_initExtensions(); |
||||||
|
|
||||||
|
#ifdef GL_DEBUG |
||||||
|
if (glDebugMessageCallbackARB) { |
||||||
|
glDebugMessageCallbackARB(gl_debug, NULL); |
||||||
|
|
||||||
|
enable_synchronous(); |
||||||
|
} |
||||||
|
#endif |
||||||
|
|
||||||
|
SDL_GL_SetSwapInterval(1); |
||||||
|
|
||||||
|
render_init(); |
||||||
|
mesh_init(); |
||||||
|
world_init(); |
||||||
|
|
||||||
|
initialized = 1; |
||||||
|
|
||||||
|
while (!quit) { |
||||||
|
SDL_Event e; |
||||||
|
while (SDL_PollEvent(&e)) |
||||||
|
process_event(&e); |
||||||
|
|
||||||
|
loopmode(getTimestep(0.0166f/8), 1, 1); |
||||||
|
} |
||||||
|
|
||||||
|
return 0; |
||||||
|
} |
@ -0,0 +1,927 @@ |
|||||||
|
// This file takes minecraft chunks (decoded by cave_parse) and
|
||||||
|
// uses stb_voxel_render to turn them into vertex buffers.
|
||||||
|
|
||||||
|
#define STB_GLEXT_DECLARE "glext_list.h" |
||||||
|
#include "stb_gl.h" |
||||||
|
#include "stb_image.h" |
||||||
|
#include "stb_glprog.h" |
||||||
|
|
||||||
|
#include "caveview.h" |
||||||
|
#include "cave_parse.h" |
||||||
|
#include "stb.h" |
||||||
|
#include "sdl.h" |
||||||
|
#include "sdl_thread.h" |
||||||
|
#include <math.h> |
||||||
|
|
||||||
|
//#define VHEIGHT_TEST
|
||||||
|
//#define STBVOX_OPTIMIZED_VHEIGHT
|
||||||
|
|
||||||
|
#define STBVOX_CONFIG_MODE 1 |
||||||
|
#define STBVOX_CONFIG_OPENGL_MODELVIEW |
||||||
|
#define STBVOX_CONFIG_PREFER_TEXBUFFER |
||||||
|
//#define STBVOX_CONFIG_LIGHTING_SIMPLE
|
||||||
|
#define STBVOX_CONFIG_FOG_SMOOTHSTEP |
||||||
|
//#define STBVOX_CONFIG_PREMULTIPLIED_ALPHA // this doesn't work properly alpha test without next #define
|
||||||
|
//#define STBVOX_CONFIG_UNPREMULTIPLY // slower, fixes alpha test makes windows & fancy leaves look better
|
||||||
|
//#define STBVOX_CONFIG_TEX1_EDGE_CLAMP
|
||||||
|
#define STBVOX_CONFIG_DISABLE_TEX2 |
||||||
|
|
||||||
|
#define STBVOX_ROTATION_IN_LIGHTING |
||||||
|
#define STB_VOXEL_RENDER_IMPLEMENTATION |
||||||
|
#include "stb_voxel_render.h" |
||||||
|
|
||||||
|
extern void ods(char *fmt, ...); |
||||||
|
|
||||||
|
//#define FANCY_LEAVES // nearly 2x the triangles when enabled (if underground is filled)
|
||||||
|
#define FAST_CHUNK |
||||||
|
#define IN_PLACE |
||||||
|
|
||||||
|
#define SKIP_TERRAIN 48 // use to avoid building underground stuff
|
||||||
|
// allows you to see what perf would be like if underground was efficiently culled,
|
||||||
|
// or if you were making a game without underground
|
||||||
|
|
||||||
|
enum |
||||||
|
{ |
||||||
|
C_empty, |
||||||
|
C_solid, |
||||||
|
C_trans, |
||||||
|
C_cross, |
||||||
|
C_water, |
||||||
|
C_slab, |
||||||
|
C_stair, |
||||||
|
C_force, |
||||||
|
}; |
||||||
|
|
||||||
|
unsigned char geom_map[] = |
||||||
|
{ |
||||||
|
STBVOX_GEOM_empty, |
||||||
|
STBVOX_GEOM_solid, |
||||||
|
STBVOX_GEOM_transp, |
||||||
|
STBVOX_GEOM_crossed_pair, |
||||||
|
STBVOX_GEOM_solid, |
||||||
|
STBVOX_GEOM_slab_lower, |
||||||
|
STBVOX_GEOM_floor_slope_north_is_top, |
||||||
|
STBVOX_GEOM_force, |
||||||
|
}; |
||||||
|
|
||||||
|
unsigned char minecraft_info[256][7] = |
||||||
|
{ |
||||||
|
{ C_empty, 0,0,0,0,0,0 }, |
||||||
|
{ C_solid, 1,1,1,1,1,1 }, |
||||||
|
{ C_solid, 3,3,3,3,40,2 }, |
||||||
|
{ C_solid, 2,2,2,2,2,2 }, |
||||||
|
{ C_solid, 16,16,16,16,16,16 }, |
||||||
|
{ C_solid, 4,4,4,4,4,4 }, |
||||||
|
{ C_cross, 15,15,15,15 }, |
||||||
|
{ C_solid, 17,17,17,17,17,17 }, |
||||||
|
|
||||||
|
// 8
|
||||||
|
{ C_water, 223,223,223,223,223,223 }, |
||||||
|
{ C_water, 223,223,223,223,223,223 }, |
||||||
|
{ C_solid, 255,255,255,255,255,255 }, |
||||||
|
{ C_solid, 255,255,255,255,255,255 }, |
||||||
|
{ C_solid, 18,18,18,18,18,18 }, |
||||||
|
{ C_solid, 19,19,19,19,19,19 }, |
||||||
|
{ C_solid, 32,32,32,32,32,32 }, |
||||||
|
{ C_solid, 33,33,33,33,33,33 }, |
||||||
|
|
||||||
|
// 16
|
||||||
|
{ C_solid, 34,34,34,34,34,34 }, |
||||||
|
{ C_solid, 20,20,20,20,21,21 }, |
||||||
|
#ifdef FANCY_LEAVES |
||||||
|
{ C_force, 52,52,52,52,52,52 }, // leaves
|
||||||
|
#else |
||||||
|
{ C_solid, 53,53,53,53,53,53 }, // leaves
|
||||||
|
#endif |
||||||
|
{ C_solid, 24,24,24,24,24,24 }, |
||||||
|
{ C_trans, 49,49,49,49,49,49 }, // glass
|
||||||
|
{ C_solid, 160,160,160,160,160,160 }, |
||||||
|
{ C_solid, 144,144,144,144,144,144 }, |
||||||
|
{ C_solid, 46,45,45,45,62,62 }, |
||||||
|
|
||||||
|
// 24
|
||||||
|
{ C_solid, 192,192,192,192, 176,176 }, |
||||||
|
{ C_solid, 74,74,74,74,74,74 }, |
||||||
|
{ C_empty }, // bed
|
||||||
|
{ C_empty }, // powered rail
|
||||||
|
{ C_empty }, // detector rail
|
||||||
|
{ C_solid, 106,108,109,108,108,108 }, |
||||||
|
{ C_empty }, // cobweb=11
|
||||||
|
{ C_cross, 39,39,39,39 }, |
||||||
|
|
||||||
|
// 32
|
||||||
|
{ C_cross, 55,55,55,55,0,0 }, |
||||||
|
{ C_solid, 107,108,109,108,108,108 }, |
||||||
|
{ C_empty }, // piston head
|
||||||
|
{ C_solid, 64,64,64,64,64,64 }, // various colors
|
||||||
|
{ C_empty }, // unused
|
||||||
|
{ C_cross, 13,13,13,13,0,0 }, |
||||||
|
{ C_cross, 12,12,12,12,0,0 }, |
||||||
|
{ C_cross, 29,29,29,29,0,0 }, |
||||||
|
|
||||||
|
// 40
|
||||||
|
{ C_cross, 28,28,28,28,0,0 }, |
||||||
|
{ C_solid, 23,23,23,23,23,23 }, |
||||||
|
{ C_solid, 22,22,22,22,22,22 }, |
||||||
|
{ C_solid, 5,5,5,5,6,6, }, |
||||||
|
{ C_slab , 5,5,5,5,6,6, }, |
||||||
|
{ C_solid, 7,7,7,7,7,7, }, |
||||||
|
{ C_solid, 8,8,8,8,9,10 }, |
||||||
|
{ C_solid, 35,35,35,35,4,4, }, |
||||||
|
|
||||||
|
// 48
|
||||||
|
{ C_solid, 36,36,36,36,36,36 }, |
||||||
|
{ C_solid, 37,37,37,37,37,37 }, |
||||||
|
{ C_cross, 80,80,80,80,80,80 }, // torch
|
||||||
|
{ C_empty }, // fire
|
||||||
|
{ C_trans, 65,65,65,65,65,65 }, |
||||||
|
{ C_stair, 4,4,4,4,4,4 }, |
||||||
|
{ C_solid, 26,26,26,27,25,25 }, |
||||||
|
{ C_empty }, // redstone
|
||||||
|
|
||||||
|
// 56
|
||||||
|
{ C_solid, 50,50,50,50,50,50 }, |
||||||
|
{ C_solid, 26,26,26,26,26,26 }, |
||||||
|
{ C_solid, 60,59,59,59,43,43 }, |
||||||
|
{ C_cross, 95,95,95,95 }, |
||||||
|
{ C_solid, 2,2,2,2,86,2 }, |
||||||
|
{ C_solid, 44,45,45,45,62,62 }, |
||||||
|
{ C_solid, 61,45,45,45,62,62 }, |
||||||
|
{ C_empty }, // sign
|
||||||
|
|
||||||
|
// 64
|
||||||
|
{ C_empty }, // door
|
||||||
|
{ C_empty }, // ladder
|
||||||
|
{ C_empty }, // rail
|
||||||
|
{ C_stair, 16,16,16,16,16,16 }, // cobblestone stairs
|
||||||
|
{ C_empty }, // sign
|
||||||
|
{ C_empty }, // lever
|
||||||
|
{ C_empty }, // stone pressure plate
|
||||||
|
{ C_empty }, // iron door
|
||||||
|
|
||||||
|
// 72
|
||||||
|
{ C_empty }, // wooden pressure
|
||||||
|
{ C_solid, 51,51,51,51,51,51 }, |
||||||
|
{ C_solid, 51,51,51,51,51,51 }, |
||||||
|
{ C_empty }, |
||||||
|
{ C_empty }, |
||||||
|
{ C_empty }, |
||||||
|
{ C_empty }, // snow on block below, do as half slab?
|
||||||
|
{ C_solid, 67,67,67,67,67,67 }, |
||||||
|
|
||||||
|
// 80
|
||||||
|
{ C_solid, 66,66,66,66,66,66 }, |
||||||
|
{ C_solid, 70,70,70,70,69,71 }, |
||||||
|
{ C_solid, 72,72,72,72,72,72 }, |
||||||
|
{ C_cross, 73,73,73,73,73,73 }, |
||||||
|
{ C_solid, 74,74,74,74,75,74 }, |
||||||
|
{ C_empty }, // fence
|
||||||
|
{ C_solid,119,118,118,118,102,102 }, |
||||||
|
{ C_solid,103,103,103,103,103,103 }, |
||||||
|
|
||||||
|
// 88
|
||||||
|
{ C_solid, 104,104,104,104,104,104 }, |
||||||
|
{ C_solid, 105,105,105,105,105,105 }, |
||||||
|
{ C_solid, 167,167,167,167,167,167 }, |
||||||
|
{ C_solid, 120,118,118,118,102,102 }, |
||||||
|
{ C_empty }, // cake
|
||||||
|
{ C_empty }, // repeater
|
||||||
|
{ C_empty }, // repeater
|
||||||
|
{ C_solid, 49,49,49,49,49,49 }, // colored glass
|
||||||
|
|
||||||
|
// 96
|
||||||
|
{ C_empty }, |
||||||
|
{ C_empty }, |
||||||
|
{ C_solid, 54,54,54,54,54,54 }, |
||||||
|
{ C_solid, 125,125,125,125,125,125 }, |
||||||
|
{ C_solid, 126,126,126,126,126,126 }, |
||||||
|
{ C_empty }, // bars
|
||||||
|
{ C_trans, 49,49,49,49,49,49 }, // glass pane
|
||||||
|
{ C_solid, 136,136,136,136,137,137 }, // melon
|
||||||
|
|
||||||
|
// 104
|
||||||
|
{ C_empty }, // pumpkin stem
|
||||||
|
{ C_empty }, // melon stem
|
||||||
|
{ C_empty }, // vines
|
||||||
|
{ C_empty }, // gate
|
||||||
|
{ C_stair, 7,7,7,7,7,7, }, // brick stairs
|
||||||
|
{ C_stair, 54,54,54,54,54,54 }, // stone brick stairs
|
||||||
|
{ C_empty }, // mycelium
|
||||||
|
{ C_empty }, // lily pad
|
||||||
|
|
||||||
|
// 112
|
||||||
|
{ C_solid, 224,224,224,224,224,224 }, |
||||||
|
{ C_empty }, // nether brick fence
|
||||||
|
{ C_stair, 224,224,224,224,224,224 }, // nether brick stairs
|
||||||
|
{ C_empty }, // nether wart
|
||||||
|
{ C_solid, 182,182,182,182,166,183 }, |
||||||
|
{ C_empty }, // brewing stand
|
||||||
|
{ C_empty }, // cauldron
|
||||||
|
{ C_empty }, // end portal
|
||||||
|
|
||||||
|
// 120
|
||||||
|
{ C_solid, 159,159,159,159,158,158 }, |
||||||
|
{ C_solid, 175,175,175,175,175,175 }, |
||||||
|
{ C_empty }, // dragon egg
|
||||||
|
{ C_solid, 211,211,211,211,211,211 }, |
||||||
|
{ C_solid, 212,212,212,212,212,212 }, |
||||||
|
{ C_solid, 4,4,4,4,4,4, }, // wood double-slab
|
||||||
|
{ C_slab , 4,4,4,4,4,4, }, // wood slab
|
||||||
|
{ C_empty }, // cocoa
|
||||||
|
|
||||||
|
// 128
|
||||||
|
{ C_solid, 192,192,192,192,176,176 }, // sandstone stairs
|
||||||
|
{ C_solid, 32,32,32,32,32,32 }, // emerald ore
|
||||||
|
{ C_solid, 26,26,26,27,25,25 }, // ender chest
|
||||||
|
{ C_empty }, |
||||||
|
{ C_empty }, |
||||||
|
{ C_solid, 23,23,23,23,23,23 }, // emerald block
|
||||||
|
{ C_solid, 198,198,198,198,198,198 }, // spruce stairs
|
||||||
|
{ C_solid, 214,214,214,214,214,214 }, // birch stairs
|
||||||
|
|
||||||
|
// 136
|
||||||
|
{ C_stair, 199,199,199,199,199,199 }, // jungle stairs
|
||||||
|
{ C_empty }, // command block
|
||||||
|
{ C_empty }, // beacon
|
||||||
|
{ C_slab, 16,16,16,16,16,16 }, // cobblestone wall
|
||||||
|
{ C_empty }, // flower pot
|
||||||
|
{ C_empty }, // carrot
|
||||||
|
{ C_empty }, // potatoes
|
||||||
|
{ C_empty }, // wooden button
|
||||||
|
|
||||||
|
// 144
|
||||||
|
{ C_empty }, // mob head
|
||||||
|
{ C_empty }, // anvil
|
||||||
|
{ C_solid, 26,26,26,27,25,25 }, // trapped chest
|
||||||
|
{ C_empty }, // weighted pressure plate light
|
||||||
|
{ C_empty }, // weighted pressure plat eheavy
|
||||||
|
{ C_empty }, // comparator inactive
|
||||||
|
{ C_empty }, // comparator active
|
||||||
|
{ C_empty }, // daylight sensor
|
||||||
|
|
||||||
|
// 152
|
||||||
|
{ C_solid, 135,135,135,135,135,135 }, // redstone block
|
||||||
|
{ C_solid, 0,0,0,0,0,0, }, // nether quartz ore
|
||||||
|
{ C_empty }, // hopper
|
||||||
|
{ C_solid, 22,22,22,22,22,22 }, // quartz block
|
||||||
|
{ C_stair, 22,22,22,22,22,22 }, // quartz stairs
|
||||||
|
{ C_empty }, // activator rail
|
||||||
|
{ C_solid, 46,45,45,45,62,62 }, // dropper
|
||||||
|
{ C_solid, 72,72,72,72,72,72 }, // stained clay
|
||||||
|
|
||||||
|
// 160
|
||||||
|
{ C_trans, 49,49,49,49,49,49 }, // stained glass pane
|
||||||
|
#ifdef FANCY_LEAVES |
||||||
|
{ C_force, 52,52,52,52,52,52 }, // leaves
|
||||||
|
#else |
||||||
|
{ C_solid, 53,53,53,53,53,53 }, // acacia leaves
|
||||||
|
#endif |
||||||
|
{ C_solid, 20,20,20,20,21,21 }, // acacia tree
|
||||||
|
{ C_solid, 199,199,199,199,199,199 }, // acacia wood stairs
|
||||||
|
{ C_solid, 198,198,198,198,198,198 }, // dark oak stairs
|
||||||
|
{ C_solid, 146,146,146,146,146,146 }, // slime block
|
||||||
|
|
||||||
|
{ C_solid, 176,176,176,176,176,176 }, // red sandstone
|
||||||
|
{ C_solid, 176,176,176,176,176,176 }, // red sandstone
|
||||||
|
|
||||||
|
// 168
|
||||||
|
{ C_empty }, |
||||||
|
{ C_empty }, |
||||||
|
{ C_empty }, |
||||||
|
{ C_empty }, |
||||||
|
{ C_solid, 72,72,72,72,72,72 }, // hardened clay
|
||||||
|
{ C_empty }, |
||||||
|
{ C_empty }, |
||||||
|
{ C_empty }, |
||||||
|
|
||||||
|
// 176
|
||||||
|
{ C_empty }, |
||||||
|
{ C_empty }, |
||||||
|
{ C_solid, 176,176,176,176,176,176 }, // red sandstone
|
||||||
|
}; |
||||||
|
|
||||||
|
unsigned char minecraft_tex1_for_blocktype[256][6]; |
||||||
|
unsigned char effective_blocktype[256]; |
||||||
|
unsigned char minecraft_color_for_blocktype[256][6]; |
||||||
|
unsigned char minecraft_geom_for_blocktype[256]; |
||||||
|
|
||||||
|
uint8 build_buffer[BUILD_BUFFER_SIZE]; |
||||||
|
uint8 face_buffer[FACE_BUFFER_SIZE]; |
||||||
|
|
||||||
|
//GLuint vbuf, fbuf, fbuf_tex;
|
||||||
|
|
||||||
|
//unsigned char tex1_for_blocktype[256][6];
|
||||||
|
|
||||||
|
//unsigned char blocktype[34][34][257];
|
||||||
|
//unsigned char lighting[34][34][257];
|
||||||
|
|
||||||
|
// a superchunk is 64x64x256, with the border blocks computed as well,
|
||||||
|
// which means we need 4x4 chunks plus 16 border chunks plus 4 corner chunks
|
||||||
|
|
||||||
|
#define SUPERCHUNK_X 4 |
||||||
|
#define SUPERCHUNK_Y 4 |
||||||
|
|
||||||
|
unsigned char remap_data[16][16]; |
||||||
|
unsigned char remap[256]; |
||||||
|
unsigned char rotate_data[4] = { 1,3,2,0 }; |
||||||
|
|
||||||
|
void convert_fastchunk_inplace(fast_chunk *fc) |
||||||
|
{ |
||||||
|
int i; |
||||||
|
int num_blocks=0, step=0; |
||||||
|
unsigned char rot[4096]; |
||||||
|
#ifndef IN_PLACE |
||||||
|
unsigned char *storage; |
||||||
|
#endif |
||||||
|
|
||||||
|
memset(rot, 0, 4096); |
||||||
|
|
||||||
|
for (i=0; i < 16; ++i) |
||||||
|
num_blocks += fc->blockdata[i] != NULL; |
||||||
|
|
||||||
|
#ifndef IN_PLACE |
||||||
|
storage = malloc(16*16*16*2 * num_blocks); |
||||||
|
#endif |
||||||
|
|
||||||
|
for (i=0; i < 16; ++i) { |
||||||
|
if (fc->blockdata[i]) { |
||||||
|
int o=0; |
||||||
|
unsigned char *bd,*dd,*lt,*sky; |
||||||
|
unsigned char *out, *outb; |
||||||
|
|
||||||
|
// this ordering allows us to determine which data we can safely overwrite for in-place processing
|
||||||
|
bd = fc->blockdata[i]; |
||||||
|
dd = fc->data[i]; |
||||||
|
lt = fc->light[i]; |
||||||
|
sky = fc->skylight[i]; |
||||||
|
|
||||||
|
#ifdef IN_PLACE |
||||||
|
out = bd; |
||||||
|
#else |
||||||
|
out = storage + 16*16*16*2*step; |
||||||
|
#endif |
||||||
|
|
||||||
|
// bd is written in place, but also reads from dd
|
||||||
|
for (o=0; o < 16*16*16/2; o += 1) { |
||||||
|
unsigned char v1,v2; |
||||||
|
unsigned char d = dd[o]; |
||||||
|
v1 = bd[o*2+0]; |
||||||
|
v2 = bd[o*2+1]; |
||||||
|
|
||||||
|
if (remap[v1]) |
||||||
|
{ |
||||||
|
//unsigned char d = bd[o] & 15;
|
||||||
|
v1 = remap_data[remap[v1]][d&15]; |
||||||
|
rot[o*2+0] = rotate_data[d&3]; |
||||||
|
} else |
||||||
|
v1 = effective_blocktype[v1]; |
||||||
|
|
||||||
|
if (remap[v2]) |
||||||
|
{ |
||||||
|
//unsigned char d = bd[o] >> 4;
|
||||||
|
v2 = remap_data[remap[v2]][d>>4]; |
||||||
|
rot[o*2+1] = rotate_data[(d>>4)&3]; |
||||||
|
} else |
||||||
|
v2 = effective_blocktype[v2]; |
||||||
|
|
||||||
|
out[o*2+0] = v1; |
||||||
|
out[o*2+1] = v2; |
||||||
|
} |
||||||
|
|
||||||
|
// this reads from lt & sky
|
||||||
|
#ifndef IN_PLACE |
||||||
|
outb = out + 16*16*16; |
||||||
|
++step; |
||||||
|
#endif |
||||||
|
|
||||||
|
// MC used to write in this order and it makes it possible to compute in-place
|
||||||
|
if (dd < sky && sky < lt) { |
||||||
|
// @TODO go this path always if !IN_PLACE
|
||||||
|
#ifdef IN_PLACE |
||||||
|
outb = dd; |
||||||
|
#endif |
||||||
|
|
||||||
|
for (o=0; o < 16*16*16/2; ++o) { |
||||||
|
int bright; |
||||||
|
bright = (lt[o]&15)*12 + 15 + (sky[o]&15)*16; |
||||||
|
if (bright > 255) bright = 255; |
||||||
|
if (bright < 32) bright = 32; |
||||||
|
outb[o*2+0] = STBVOX_MAKE_LIGHTING((unsigned char) bright, (rot[o*2+0]&3)); |
||||||
|
|
||||||
|
bright = (lt[o]>>4)*12 + 15 + (sky[o]>>4)*16; |
||||||
|
if (bright > 255) bright = 255; |
||||||
|
if (bright < 32) bright = 32; |
||||||
|
outb[o*2+1] = STBVOX_MAKE_LIGHTING((unsigned char) bright, (rot[o*2+1]&3)); |
||||||
|
} |
||||||
|
} else { |
||||||
|
// @TODO: if blocktype is in between others, this breaks; need to find which side has two pointers, and use that
|
||||||
|
// overwrite rot[] array, then copy out
|
||||||
|
#ifdef IN_PLACE |
||||||
|
outb = (dd < sky) ? dd : sky; |
||||||
|
if (lt < outb) lt = outb; |
||||||
|
#endif |
||||||
|
|
||||||
|
for (o=0; o < 16*16*16/2; ++o) { |
||||||
|
int bright; |
||||||
|
bright = (lt[o]&15)*12 + 15 + (sky[o]&15)*16; |
||||||
|
if (bright > 255) bright = 255; |
||||||
|
if (bright < 32) bright = 32; |
||||||
|
rot[o*2+0] = STBVOX_MAKE_LIGHTING((unsigned char) bright, (rot[o*2+0]&3)); |
||||||
|
|
||||||
|
bright = (lt[o]>>4)*12 + 15 + (sky[o]>>4)*16; |
||||||
|
if (bright > 255) bright = 255; |
||||||
|
if (bright < 32) bright = 32; |
||||||
|
rot[o*2+1] = STBVOX_MAKE_LIGHTING((unsigned char) bright, (rot[o*2+1]&3)); |
||||||
|
} |
||||||
|
|
||||||
|
memcpy(outb, rot, 4096); |
||||||
|
fc->data[i] = outb; |
||||||
|
} |
||||||
|
|
||||||
|
#ifndef IN_PLACE |
||||||
|
fc->blockdata[i] = out; |
||||||
|
fc->data[i] = outb; |
||||||
|
#endif |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
#ifndef IN_PLACE |
||||||
|
free(fc->pointer_to_free); |
||||||
|
fc->pointer_to_free = storage; |
||||||
|
#endif |
||||||
|
} |
||||||
|
|
||||||
|
void make_converted_fastchunk(fast_chunk *fc, int x, int y, int segment, uint8 *sv_blocktype, uint8 *sv_lighting) |
||||||
|
{ |
||||||
|
int z; |
||||||
|
assert(fc == NULL || (fc->refcount > 0 && fc->refcount < 64)); |
||||||
|
if (fc == NULL || fc->blockdata[segment] == NULL) { |
||||||
|
for (z=0; z < 16; ++z) { |
||||||
|
sv_blocktype[z] = C_empty; |
||||||
|
sv_lighting[z] = 255; |
||||||
|
} |
||||||
|
} else { |
||||||
|
unsigned char *block = fc->blockdata[segment]; |
||||||
|
unsigned char *data = fc->data[segment]; |
||||||
|
y = 15-y; |
||||||
|
for (z=0; z < 16; ++z) { |
||||||
|
sv_blocktype[z] = block[z*256 + y*16 + x]; |
||||||
|
sv_lighting [z] = data [z*256 + y*16 + x]; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
#define CHUNK_CACHE 64 |
||||||
|
typedef struct |
||||||
|
{ |
||||||
|
int valid; |
||||||
|
int chunk_x, chunk_y; |
||||||
|
fast_chunk *fc; |
||||||
|
} cached_converted_chunk; |
||||||
|
|
||||||
|
cached_converted_chunk chunk_cache[CHUNK_CACHE][CHUNK_CACHE]; |
||||||
|
int cache_size = CHUNK_CACHE; |
||||||
|
|
||||||
|
void reset_cache_size(int size) |
||||||
|
{ |
||||||
|
int i,j; |
||||||
|
for (j=size; j < cache_size; ++j) { |
||||||
|
for (i=size; i < cache_size; ++i) { |
||||||
|
cached_converted_chunk *ccc = &chunk_cache[j][i]; |
||||||
|
if (ccc->valid) { |
||||||
|
if (ccc->fc) { |
||||||
|
free(ccc->fc->pointer_to_free); |
||||||
|
free(ccc->fc); |
||||||
|
ccc->fc = NULL; |
||||||
|
} |
||||||
|
ccc->valid = 0; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
cache_size = size; |
||||||
|
} |
||||||
|
|
||||||
|
// this must be called inside mutex
|
||||||
|
void deref_fastchunk(fast_chunk *fc) |
||||||
|
{ |
||||||
|
if (fc) { |
||||||
|
assert(fc->refcount > 0); |
||||||
|
--fc->refcount; |
||||||
|
if (fc->refcount == 0) { |
||||||
|
free(fc->pointer_to_free); |
||||||
|
free(fc); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
SDL_mutex * chunk_cache_mutex; |
||||||
|
SDL_mutex * chunk_get_mutex; |
||||||
|
|
||||||
|
void lock_chunk_get_mutex(void) |
||||||
|
{ |
||||||
|
SDL_LockMutex(chunk_get_mutex); |
||||||
|
} |
||||||
|
void unlock_chunk_get_mutex(void) |
||||||
|
{ |
||||||
|
SDL_UnlockMutex(chunk_get_mutex); |
||||||
|
} |
||||||
|
|
||||||
|
fast_chunk *get_converted_fastchunk(int chunk_x, int chunk_y) |
||||||
|
{ |
||||||
|
int slot_x = (chunk_x & (cache_size-1)); |
||||||
|
int slot_y = (chunk_y & (cache_size-1)); |
||||||
|
fast_chunk *fc; |
||||||
|
cached_converted_chunk *ccc; |
||||||
|
SDL_LockMutex(chunk_cache_mutex); |
||||||
|
ccc = &chunk_cache[slot_y][slot_x]; |
||||||
|
if (ccc->valid) { |
||||||
|
if (ccc->chunk_x == chunk_x && ccc->chunk_y == chunk_y) { |
||||||
|
fast_chunk *fc = ccc->fc; |
||||||
|
if (fc) |
||||||
|
++fc->refcount; |
||||||
|
SDL_UnlockMutex(chunk_cache_mutex); |
||||||
|
return fc; |
||||||
|
} |
||||||
|
if (ccc->fc) { |
||||||
|
deref_fastchunk(ccc->fc); |
||||||
|
ccc->fc = NULL; |
||||||
|
ccc->valid = 0; |
||||||
|
} |
||||||
|
} |
||||||
|
SDL_UnlockMutex(chunk_cache_mutex); |
||||||
|
|
||||||
|
fc = get_decoded_fastchunk_uncached(chunk_x, -chunk_y); |
||||||
|
if (fc) |
||||||
|
convert_fastchunk_inplace(fc); |
||||||
|
|
||||||
|
SDL_LockMutex(chunk_cache_mutex); |
||||||
|
// another thread might have updated it, so before we overwrite it...
|
||||||
|
if (ccc->fc) { |
||||||
|
deref_fastchunk(ccc->fc); |
||||||
|
ccc->fc = NULL; |
||||||
|
} |
||||||
|
|
||||||
|
if (fc) |
||||||
|
fc->refcount = 1; // 1 in the cache
|
||||||
|
|
||||||
|
ccc->chunk_x = chunk_x; |
||||||
|
ccc->chunk_y = chunk_y; |
||||||
|
ccc->valid = 1; |
||||||
|
if (fc) |
||||||
|
++fc->refcount; |
||||||
|
ccc->fc = fc; |
||||||
|
SDL_UnlockMutex(chunk_cache_mutex); |
||||||
|
return fc; |
||||||
|
} |
||||||
|
|
||||||
|
void make_map_segment_for_superchunk_preconvert(int chunk_x, int chunk_y, int segment, fast_chunk *fc_table[4][4], uint8 sv_blocktype[34][34][18], uint8 sv_lighting[34][34][18]) |
||||||
|
{ |
||||||
|
int a,b; |
||||||
|
assert((chunk_x & 1) == 0); |
||||||
|
assert((chunk_y & 1) == 0); |
||||||
|
for (b=-1; b < 3; ++b) { |
||||||
|
for (a=-1; a < 3; ++a) { |
||||||
|
int xo = a*16+1; |
||||||
|
int yo = b*16+1; |
||||||
|
int x,y; |
||||||
|
fast_chunk *fc = fc_table[b+1][a+1]; |
||||||
|
for (y=0; y < 16; ++y) |
||||||
|
for (x=0; x < 16; ++x) |
||||||
|
if (xo+x >= 0 && xo+x < 34 && yo+y >= 0 && yo+y < 34) |
||||||
|
make_converted_fastchunk(fc,x,y, segment, sv_blocktype[xo+x][yo+y], sv_lighting[xo+x][yo+y]); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// build 1 mesh covering 2x2 chunks
|
||||||
|
void build_chunk(int chunk_x, int chunk_y, fast_chunk *fc_table[4][4], raw_mesh *rm) |
||||||
|
{ |
||||||
|
int a,b,z; |
||||||
|
stbvox_input_description *map; |
||||||
|
|
||||||
|
#ifdef VHEIGHT_TEST |
||||||
|
unsigned char vheight[34][34][18]; |
||||||
|
#endif |
||||||
|
|
||||||
|
#ifndef STBVOX_CONFIG_DISABLE_TEX2 |
||||||
|
unsigned char tex2_choice[34][34][18]; |
||||||
|
#endif |
||||||
|
|
||||||
|
assert((chunk_x & 1) == 0); |
||||||
|
assert((chunk_y & 1) == 0); |
||||||
|
|
||||||
|
rm->cx = chunk_x; |
||||||
|
rm->cy = chunk_y; |
||||||
|
|
||||||
|
stbvox_set_input_stride(&rm->mm, 34*18, 18); |
||||||
|
|
||||||
|
assert(rm->mm.input.geometry == NULL); |
||||||
|
|
||||||
|
map = stbvox_get_input_description(&rm->mm); |
||||||
|
map->block_tex1_face = minecraft_tex1_for_blocktype; |
||||||
|
map->block_color_face = minecraft_color_for_blocktype; |
||||||
|
map->block_geometry = minecraft_geom_for_blocktype; |
||||||
|
|
||||||
|
stbvox_reset_buffers(&rm->mm); |
||||||
|
stbvox_set_buffer(&rm->mm, 0, 0, rm->build_buffer, BUILD_BUFFER_SIZE); |
||||||
|
stbvox_set_buffer(&rm->mm, 0, 1, rm->face_buffer , FACE_BUFFER_SIZE); |
||||||
|
|
||||||
|
map->blocktype = &rm->sv_blocktype[1][1][1]; // this is (0,0,0), but we need to be able to query off the edges
|
||||||
|
map->lighting = &rm->sv_lighting[1][1][1]; |
||||||
|
|
||||||
|
// fill in the top two rows of the buffer
|
||||||
|
for (a=0; a < 34; ++a) { |
||||||
|
for (b=0; b < 34; ++b) { |
||||||
|
rm->sv_blocktype[a][b][16] = 0; |
||||||
|
rm->sv_lighting [a][b][16] = 255; |
||||||
|
rm->sv_blocktype[a][b][17] = 0; |
||||||
|
rm->sv_lighting [a][b][17] = 255; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
#ifndef STBVOX_CONFIG_DISABLE_TEX2 |
||||||
|
for (a=0; a < 34; ++a) { |
||||||
|
for (b=0; b < 34; ++b) { |
||||||
|
int px = chunk_x*16 + a - 1; |
||||||
|
int py = chunk_y*16 + b - 1; |
||||||
|
float dist = (float) sqrt(px*px + py*py); |
||||||
|
float s1 = (float) sin(dist / 16), s2, s3; |
||||||
|
dist = (float) sqrt((px-80)*(px-80) + (py-50)*(py-50)); |
||||||
|
s2 = (float) sin(dist / 11); |
||||||
|
for (z=0; z < 18; ++z) { |
||||||
|
s3 = (float) sin(z * 3.141592 / 8); |
||||||
|
|
||||||
|
s3 = s1*s2*s3; |
||||||
|
tex2_choice[a][b][z] = 63 & (int) stb_linear_remap(s3,-1,1, -20,83); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
#endif |
||||||
|
|
||||||
|
for (z=256-16; z >= SKIP_TERRAIN; z -= 16) |
||||||
|
{ |
||||||
|
int z0 = z; |
||||||
|
int z1 = z+16; |
||||||
|
if (z1 == 256) z1 = 255; |
||||||
|
|
||||||
|
make_map_segment_for_superchunk_preconvert(chunk_x, chunk_y, z >> 4, fc_table, rm->sv_blocktype, rm->sv_lighting); |
||||||
|
|
||||||
|
map->blocktype = &rm->sv_blocktype[1][1][1-z]; // specify location of 0,0,0 so that accessing z0..z1 gets right data
|
||||||
|
map->lighting = &rm->sv_lighting[1][1][1-z]; |
||||||
|
#ifndef STBVOX_CONFIG_DISABLE_TEX2 |
||||||
|
map->tex2 = &tex2_choice[1][1][1-z]; |
||||||
|
#endif |
||||||
|
|
||||||
|
#ifdef VHEIGHT_TEST |
||||||
|
// hacky test of vheight
|
||||||
|
for (a=0; a < 34; ++a) { |
||||||
|
for (b=0; b < 34; ++b) { |
||||||
|
int c; |
||||||
|
for (c=0; c < 17; ++c) { |
||||||
|
if (rm->sv_blocktype[a][b][c] != 0 && rm->sv_blocktype[a][b][c+1] == 0) { |
||||||
|
// topmost block
|
||||||
|
vheight[a][b][c] = rand() & 255; |
||||||
|
rm->sv_blocktype[a][b][c] = 168; |
||||||
|
} else if (c > 0 && rm->sv_blocktype[a][b][c] != 0 && rm->sv_blocktype[a][b][c-1] == 0) { |
||||||
|
// bottommost block
|
||||||
|
vheight[a][b][c] = ((rand() % 3) << 6) + ((rand() % 3) << 4) + ((rand() % 3) << 2) + (rand() % 3); |
||||||
|
rm->sv_blocktype[a][b][c] = 169; |
||||||
|
} |
||||||
|
} |
||||||
|
vheight[a][b][c] = STBVOX_MAKE_VHEIGHT(2,2,2,2); // flat top
|
||||||
|
} |
||||||
|
} |
||||||
|
map->vheight = &vheight[1][1][1-z]; |
||||||
|
#endif |
||||||
|
|
||||||
|
{ |
||||||
|
stbvox_set_input_range(&rm->mm, 0,0,z0, 32,32,z1); |
||||||
|
stbvox_set_default_mesh(&rm->mm, 0); |
||||||
|
stbvox_make_mesh(&rm->mm); |
||||||
|
} |
||||||
|
|
||||||
|
// copy the bottom two rows of data up to the top
|
||||||
|
for (a=0; a < 34; ++a) { |
||||||
|
for (b=0; b < 34; ++b) { |
||||||
|
rm->sv_blocktype[a][b][16] = rm->sv_blocktype[a][b][0]; |
||||||
|
rm->sv_blocktype[a][b][17] = rm->sv_blocktype[a][b][1]; |
||||||
|
rm->sv_lighting [a][b][16] = rm->sv_lighting [a][b][0]; |
||||||
|
rm->sv_lighting [a][b][17] = rm->sv_lighting [a][b][1]; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
stbvox_set_mesh_coordinates(&rm->mm, chunk_x*16, chunk_y*16, 0); |
||||||
|
stbvox_get_transform(&rm->mm, rm->transform); |
||||||
|
|
||||||
|
stbvox_set_input_range(&rm->mm, 0,0,0, 32,32,255); |
||||||
|
stbvox_get_bounds(&rm->mm, rm->bounds); |
||||||
|
|
||||||
|
rm->num_quads = stbvox_get_quad_count(&rm->mm, 0); |
||||||
|
} |
||||||
|
|
||||||
|
int next_blocktype = 255; |
||||||
|
|
||||||
|
unsigned char mc_rot[4] = { 1,3,2,0 }; |
||||||
|
|
||||||
|
// create blocktypes with rotation baked into type...
|
||||||
|
// @TODO we no longer need this now that we store rotations
|
||||||
|
// in lighting
|
||||||
|
void build_stair_rotations(int blocktype, unsigned char *map) |
||||||
|
{ |
||||||
|
int i; |
||||||
|
|
||||||
|
// use the existing block type for floor stairs; allocate a new type for ceil stairs
|
||||||
|
for (i=0; i < 6; ++i) { |
||||||
|
minecraft_color_for_blocktype[next_blocktype][i] = minecraft_color_for_blocktype[blocktype][i]; |
||||||
|
minecraft_tex1_for_blocktype [next_blocktype][i] = minecraft_tex1_for_blocktype [blocktype][i]; |
||||||
|
} |
||||||
|
minecraft_geom_for_blocktype[next_blocktype] = (unsigned char) STBVOX_MAKE_GEOMETRY(STBVOX_GEOM_ceil_slope_north_is_bottom, 0, 0); |
||||||
|
minecraft_geom_for_blocktype[ blocktype] = (unsigned char) STBVOX_MAKE_GEOMETRY(STBVOX_GEOM_floor_slope_north_is_top, 0, 0); |
||||||
|
|
||||||
|
for (i=0; i < 4; ++i) { |
||||||
|
map[0+i+8] = map[0+i] = blocktype; |
||||||
|
map[4+i+8] = map[4+i] = next_blocktype; |
||||||
|
} |
||||||
|
--next_blocktype; |
||||||
|
} |
||||||
|
|
||||||
|
void build_wool_variations(int bt, unsigned char *map) |
||||||
|
{ |
||||||
|
int i,k; |
||||||
|
unsigned char tex[16] = { 64, 210, 194, 178, 162, 146, 130, 114, 225, 209, 193, 177, 161, 145, 129, 113 }; |
||||||
|
for (i=0; i < 16; ++i) { |
||||||
|
if (i == 0) |
||||||
|
map[i] = bt; |
||||||
|
else { |
||||||
|
map[i] = next_blocktype; |
||||||
|
for (k=0; k < 6; ++k) { |
||||||
|
minecraft_tex1_for_blocktype[next_blocktype][k] = tex[i]; |
||||||
|
} |
||||||
|
minecraft_geom_for_blocktype[next_blocktype] = minecraft_geom_for_blocktype[bt]; |
||||||
|
--next_blocktype; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
void build_wood_variations(int bt, unsigned char *map) |
||||||
|
{ |
||||||
|
int i,k; |
||||||
|
unsigned char tex[4] = { 5, 198, 214, 199 }; |
||||||
|
for (i=0; i < 4; ++i) { |
||||||
|
if (i == 0) |
||||||
|
map[i] = bt; |
||||||
|
else { |
||||||
|
map[i] = next_blocktype; |
||||||
|
for (k=0; k < 6; ++k) { |
||||||
|
minecraft_tex1_for_blocktype[next_blocktype][k] = tex[i]; |
||||||
|
} |
||||||
|
minecraft_geom_for_blocktype[next_blocktype] = minecraft_geom_for_blocktype[bt]; |
||||||
|
--next_blocktype; |
||||||
|
} |
||||||
|
} |
||||||
|
map[i] = map[i-1]; |
||||||
|
++i; |
||||||
|
for (; i < 16; ++i) |
||||||
|
map[i] = bt; |
||||||
|
} |
||||||
|
|
||||||
|
void remap_in_place(int bt, int rm) |
||||||
|
{ |
||||||
|
int i; |
||||||
|
remap[bt] = rm; |
||||||
|
for (i=0; i < 16; ++i) |
||||||
|
remap_data[rm][i] = bt; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
void mesh_init(void) |
||||||
|
{ |
||||||
|
int i; |
||||||
|
|
||||||
|
chunk_cache_mutex = SDL_CreateMutex(); |
||||||
|
chunk_get_mutex = SDL_CreateMutex(); |
||||||
|
|
||||||
|
for (i=0; i < 256; ++i) { |
||||||
|
memcpy(minecraft_tex1_for_blocktype[i], minecraft_info[i]+1, 6); |
||||||
|
effective_blocktype[i] = (minecraft_info[i][0] == C_empty ? 0 : i); |
||||||
|
minecraft_geom_for_blocktype[i] = geom_map[minecraft_info[i][0]]; |
||||||
|
} |
||||||
|
//effective_blocktype[50] = 0; // delete torches
|
||||||
|
|
||||||
|
for (i=0; i < 6*256; ++i) { |
||||||
|
if (minecraft_tex1_for_blocktype[0][i] == 40) |
||||||
|
minecraft_color_for_blocktype[0][i] = 38 | 64; // apply to tex1
|
||||||
|
if (minecraft_tex1_for_blocktype[0][i] == 39) |
||||||
|
minecraft_color_for_blocktype[0][i] = 39 | 64; // apply to tex1
|
||||||
|
if (minecraft_tex1_for_blocktype[0][i] == 105) |
||||||
|
minecraft_color_for_blocktype[0][i] = 63; // emissive
|
||||||
|
if (minecraft_tex1_for_blocktype[0][i] == 212) |
||||||
|
minecraft_color_for_blocktype[0][i] = 63; // emissive
|
||||||
|
if (minecraft_tex1_for_blocktype[0][i] == 80) |
||||||
|
minecraft_color_for_blocktype[0][i] = 63; // emissive
|
||||||
|
} |
||||||
|
|
||||||
|
for (i=0; i < 6; ++i) { |
||||||
|
minecraft_color_for_blocktype[172][i] = 47 | 64; // apply to tex1
|
||||||
|
minecraft_color_for_blocktype[178][i] = 47 | 64; // apply to tex1
|
||||||
|
minecraft_color_for_blocktype[18][i] = 39 | 64; // green
|
||||||
|
minecraft_color_for_blocktype[161][i] = 37 | 64; // green
|
||||||
|
minecraft_color_for_blocktype[10][i] = 63; // emissive lava
|
||||||
|
minecraft_color_for_blocktype[11][i] = 63; // emissive
|
||||||
|
} |
||||||
|
|
||||||
|
#ifdef VHEIGHT_TEST |
||||||
|
effective_blocktype[168] = 168; |
||||||
|
minecraft_tex1_for_blocktype[168][0] = 1; |
||||||
|
minecraft_tex1_for_blocktype[168][1] = 1; |
||||||
|
minecraft_tex1_for_blocktype[168][2] = 1; |
||||||
|
minecraft_tex1_for_blocktype[168][3] = 1; |
||||||
|
minecraft_tex1_for_blocktype[168][4] = 1; |
||||||
|
minecraft_tex1_for_blocktype[168][5] = 1; |
||||||
|
minecraft_geom_for_blocktype[168] = STBVOX_GEOM_floor_vheight_12; |
||||||
|
effective_blocktype[169] = 169; |
||||||
|
minecraft_tex1_for_blocktype[169][0] = 1; |
||||||
|
minecraft_tex1_for_blocktype[169][1] = 1; |
||||||
|
minecraft_tex1_for_blocktype[169][2] = 1; |
||||||
|
minecraft_tex1_for_blocktype[169][3] = 1; |
||||||
|
minecraft_tex1_for_blocktype[169][4] = 1; |
||||||
|
minecraft_tex1_for_blocktype[169][5] = 1; |
||||||
|
minecraft_geom_for_blocktype[169] = STBVOX_GEOM_ceil_vheight_03; |
||||||
|
#endif |
||||||
|
|
||||||
|
remap[53] = 1; |
||||||
|
remap[67] = 2; |
||||||
|
remap[108] = 3; |
||||||
|
remap[109] = 4; |
||||||
|
remap[114] = 5; |
||||||
|
remap[136] = 6; |
||||||
|
remap[156] = 7; |
||||||
|
for (i=0; i < 256; ++i) |
||||||
|
if (remap[i]) |
||||||
|
build_stair_rotations(i, remap_data[remap[i]]); |
||||||
|
remap[35] = 8; |
||||||
|
build_wool_variations(35, remap_data[remap[35]]); |
||||||
|
remap[5] = 11; |
||||||
|
build_wood_variations(5, remap_data[remap[5]]); |
||||||
|
|
||||||
|
// set the remap flags for these so they write the rotation values
|
||||||
|
remap_in_place(54, 9); |
||||||
|
remap_in_place(146, 10); |
||||||
|
} |
||||||
|
|
||||||
|
// Timing stats while optimizing the single-threaded builder
|
||||||
|
|
||||||
|
// 32..-32, 32..-32, SKIP_TERRAIN=0, !FANCY_LEAVES on 'mcrealm' data set
|
||||||
|
|
||||||
|
// 6.27s - reblocked to do 16 z at a time instead of 256 (still using 66x66x258), 4 meshes in parallel
|
||||||
|
// 5.96s - reblocked to use FAST_CHUNK (no intermediate data structure)
|
||||||
|
// 5.45s - unknown change, or previous measurement was wrong
|
||||||
|
|
||||||
|
// 6.12s - use preconverted data, not in-place
|
||||||
|
// 5.91s - use preconverted, in-place
|
||||||
|
// 5.34s - preconvert, in-place, avoid dependency chain (suggested by ryg)
|
||||||
|
// 5.34s - preconvert, in-place, avoid dependency chain, use bit-table instead of byte-table
|
||||||
|
// 5.50s - preconvert, in-place, branchless
|
||||||
|
|
||||||
|
// 6.42s - non-preconvert, avoid dependency chain (not an error)
|
||||||
|
// 5.40s - non-preconvert, w/dependency chain (same as earlier)
|
||||||
|
|
||||||
|
// 5.50s - non-FAST_CHUNK, reblocked outer loop for better cache reuse
|
||||||
|
// 4.73s - FAST_CHUNK non-preconvert, reblocked outer loop
|
||||||
|
// 4.25s - preconvert, in-place, reblocked outer loop
|
||||||
|
// 4.18s - preconvert, in-place, unrolled again
|
||||||
|
// 4.10s - 34x34 1 mesh instead of 66x66 and 4 meshes (will make it easier to do multiple threads)
|
||||||
|
|
||||||
|
// 4.83s - building bitmasks but not using them (2 bits per block, one if empty, one if solid)
|
||||||
|
|
||||||
|
// 5.16s - using empty bitmasks to early out
|
||||||
|
// 5.01s - using solid & empty bitmasks to early out - "foo"
|
||||||
|
// 4.64s - empty bitmask only, test 8 at a time, then test geom
|
||||||
|
// 4.72s - empty bitmask only, 8 at a time, then test bits
|
||||||
|
// 4.46s - split bitmask building into three loops (each byte is separate)
|
||||||
|
// 4.42s - further optimize computing bitmask
|
||||||
|
|
||||||
|
// 4.58s - using solid & empty bitmasks to early out, same as "foo" but faster bitmask building
|
||||||
|
// 4.12s - using solid & empty bitmasks to efficiently test neighbors
|
||||||
|
// 4.04s - using 16-bit fetches (not endian-independent)
|
||||||
|
// - note this is first place that beats previous best '4.10s - 34x34 1 mesh'
|
||||||
|
|
||||||
|
// 4.30s - current time with bitmasks disabled again (note was 4.10s earlier)
|
||||||
|
// 3.95s - bitmasks enabled again, no other changes
|
||||||
|
// 4.00s - current time with bitmasks disabled again, no other changes -- wide variation that is time dependent?
|
||||||
|
// (note that most of the numbers listed here are median of 3 values already)
|
||||||
|
// 3.98s - bitmasks enabled
|
||||||
|
|
||||||
|
// Bitmasks removed from the code as not worth the complexity increase
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Raw data for Q&A:
|
||||||
|
//
|
||||||
|
// 26% parsing & loading minecraft files (4/5ths of which is zlib decode)
|
||||||
|
// 39% building mesh from stb input format
|
||||||
|
// 18% converting from minecraft blocks to stb blocks
|
||||||
|
// 9% reordering from minecraft axis order to stb axis order
|
||||||
|
// 7% uploading vertex buffer to OpenGL
|
@ -0,0 +1,632 @@ |
|||||||
|
#include <assert.h> |
||||||
|
#include <stdio.h> |
||||||
|
#include <limits.h> |
||||||
|
#include <stdlib.h> |
||||||
|
|
||||||
|
#define FAST_CHUNK // disabling this enables the old, slower path that deblocks into a regular form
|
||||||
|
|
||||||
|
#include "cave_parse.h" |
||||||
|
|
||||||
|
#include "stb_image.h" |
||||||
|
#include "stb.h" |
||||||
|
|
||||||
|
#define NUM_CHUNKS_PER_REGION 32 // only on one axis
|
||||||
|
#define NUM_CHUNKS_PER_REGION_LOG2 5 |
||||||
|
|
||||||
|
#define NUM_COLUMNS_PER_CHUNK 16 |
||||||
|
#define NUM_COLUMNS_PER_CHUNK_LOG2 4 |
||||||
|
|
||||||
|
uint32 read_uint32_be(FILE *f) |
||||||
|
{ |
||||||
|
unsigned char data[4]; |
||||||
|
fread(data, 1, 4, f); |
||||||
|
return (data[0]<<24) + (data[1]<<16) + (data[2]<<8) + data[3]; |
||||||
|
} |
||||||
|
|
||||||
|
typedef struct |
||||||
|
{ |
||||||
|
uint8 *data; |
||||||
|
size_t len; |
||||||
|
int x,z; // chunk index
|
||||||
|
int refcount; // for multi-threading
|
||||||
|
} compressed_chunk; |
||||||
|
|
||||||
|
typedef struct |
||||||
|
{ |
||||||
|
int x,z; |
||||||
|
uint32 sector_data[NUM_CHUNKS_PER_REGION][NUM_CHUNKS_PER_REGION]; |
||||||
|
} region; |
||||||
|
|
||||||
|
size_t cached_compressed=0; |
||||||
|
|
||||||
|
FILE *last_region; |
||||||
|
int last_region_x; |
||||||
|
int last_region_z; |
||||||
|
int opened=0; |
||||||
|
|
||||||
|
static void open_file(int reg_x, int reg_z) |
||||||
|
{ |
||||||
|
if (!opened || last_region_x != reg_x || last_region_z != reg_z) { |
||||||
|
char filename[256]; |
||||||
|
if (last_region != NULL) |
||||||
|
fclose(last_region); |
||||||
|
sprintf(filename, "r.%d.%d.mca", reg_x, reg_z); |
||||||
|
last_region = fopen(filename, "rb"); |
||||||
|
last_region_x = reg_x; |
||||||
|
last_region_z = reg_z; |
||||||
|
opened = 1; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
static region *load_region(int reg_x, int reg_z) |
||||||
|
{ |
||||||
|
region *r; |
||||||
|
int x,z; |
||||||
|
|
||||||
|
open_file(reg_x, reg_z); |
||||||
|
|
||||||
|
r = malloc(sizeof(*r)); |
||||||
|
|
||||||
|
if (last_region == NULL) { |
||||||
|
memset(r, 0, sizeof(*r)); |
||||||
|
} else { |
||||||
|
fseek(last_region, 0, SEEK_SET); |
||||||
|
for (z=0; z < NUM_CHUNKS_PER_REGION; ++z) |
||||||
|
for (x=0; x < NUM_CHUNKS_PER_REGION; ++x) |
||||||
|
r->sector_data[z][x] = read_uint32_be(last_region); |
||||||
|
} |
||||||
|
r->x = reg_x; |
||||||
|
r->z = reg_z; |
||||||
|
|
||||||
|
return r; |
||||||
|
} |
||||||
|
|
||||||
|
void free_region(region *r) |
||||||
|
{ |
||||||
|
free(r); |
||||||
|
} |
||||||
|
|
||||||
|
#define MAX_MAP_REGIONS 64 // in one axis: 64 regions * 32 chunk/region * 16 columns/chunk = 16384 columns
|
||||||
|
region *regions[MAX_MAP_REGIONS][MAX_MAP_REGIONS]; |
||||||
|
|
||||||
|
static region *get_region(int reg_x, int reg_z) |
||||||
|
{ |
||||||
|
int slot_x = reg_x & (MAX_MAP_REGIONS-1); |
||||||
|
int slot_z = reg_z & (MAX_MAP_REGIONS-1); |
||||||
|
region *r; |
||||||
|
|
||||||
|
r = regions[slot_z][slot_x]; |
||||||
|
|
||||||
|
if (r) { |
||||||
|
if (r->x == reg_x && r->z == reg_z) |
||||||
|
return r; |
||||||
|
free_region(r); |
||||||
|
} |
||||||
|
|
||||||
|
r = load_region(reg_x, reg_z); |
||||||
|
regions[slot_z][slot_x] = r; |
||||||
|
|
||||||
|
return r; |
||||||
|
} |
||||||
|
|
||||||
|
// about one region, so size should be ok
|
||||||
|
#define NUM_CACHED_X 64 |
||||||
|
#define NUM_CACHED_Z 64 |
||||||
|
|
||||||
|
// @TODO: is it really worth caching these? we probably can just
|
||||||
|
// pull them from the disk cache nearly as efficiently.
|
||||||
|
// Can test that by setting to 1x1?
|
||||||
|
compressed_chunk *cached_chunk[NUM_CACHED_Z][NUM_CACHED_X]; |
||||||
|
|
||||||
|
static void deref_compressed_chunk(compressed_chunk *cc) |
||||||
|
{ |
||||||
|
assert(cc->refcount > 0); |
||||||
|
--cc->refcount; |
||||||
|
if (cc->refcount == 0) { |
||||||
|
if (cc->data) |
||||||
|
free(cc->data); |
||||||
|
free(cc); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
static compressed_chunk *get_compressed_chunk(int chunk_x, int chunk_z) |
||||||
|
{ |
||||||
|
int slot_x = chunk_x & (NUM_CACHED_X-1); |
||||||
|
int slot_z = chunk_z & (NUM_CACHED_Z-1); |
||||||
|
compressed_chunk *cc = cached_chunk[slot_z][slot_x]; |
||||||
|
|
||||||
|
if (cc && cc->x == chunk_x && cc->z == chunk_z) |
||||||
|
return cc; |
||||||
|
else { |
||||||
|
int reg_x = chunk_x >> NUM_CHUNKS_PER_REGION_LOG2; |
||||||
|
int reg_z = chunk_z >> NUM_CHUNKS_PER_REGION_LOG2; |
||||||
|
region *r = get_region(reg_x, reg_z); |
||||||
|
if (cc) { |
||||||
|
deref_compressed_chunk(cc); |
||||||
|
cached_chunk[slot_z][slot_x] = NULL; |
||||||
|
} |
||||||
|
cc = malloc(sizeof(*cc)); |
||||||
|
cc->x = chunk_x; |
||||||
|
cc->z = chunk_z; |
||||||
|
{ |
||||||
|
int subchunk_x = chunk_x & (NUM_CHUNKS_PER_REGION-1); |
||||||
|
int subchunk_z = chunk_z & (NUM_CHUNKS_PER_REGION-1); |
||||||
|
uint32 code = r->sector_data[subchunk_z][subchunk_x]; |
||||||
|
|
||||||
|
if (code & 255) { |
||||||
|
open_file(reg_x, reg_z); |
||||||
|
fseek(last_region, (code>>8)*4096, SEEK_SET); |
||||||
|
cc->len = (code&255)*4096; |
||||||
|
cc->data = malloc(cc->len); |
||||||
|
fread(cc->data, 1, cc->len, last_region); |
||||||
|
} else { |
||||||
|
cc->len = 0; |
||||||
|
cc->data = 0; |
||||||
|
} |
||||||
|
} |
||||||
|
cc->refcount = 1; |
||||||
|
cached_chunk[slot_z][slot_x] = cc; |
||||||
|
return cc; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
// NBT parser -- can automatically parse stuff we don't
|
||||||
|
// have definitions for, but want to explicitly parse
|
||||||
|
// stuff we do have definitions for.
|
||||||
|
//
|
||||||
|
// option 1: auto-parse everything into data structures,
|
||||||
|
// then read those
|
||||||
|
//
|
||||||
|
// option 2: have a "parse next object" which
|
||||||
|
// doesn't resolve whether it expands its children
|
||||||
|
// yet, and then the user either says "expand" or
|
||||||
|
// "skip" after looking at the name. Anything with
|
||||||
|
// "children" without names can't go through this
|
||||||
|
// interface.
|
||||||
|
//
|
||||||
|
// Let's try option 2.
|
||||||
|
|
||||||
|
|
||||||
|
typedef struct |
||||||
|
{ |
||||||
|
unsigned char *buffer_start; |
||||||
|
unsigned char *buffer_end; |
||||||
|
unsigned char *cur; |
||||||
|
int nesting; |
||||||
|
char temp_buffer[256]; |
||||||
|
} nbt; |
||||||
|
|
||||||
|
enum { TAG_End=0, TAG_Byte=1, TAG_Short=2, TAG_Int=3, TAG_Long=4, |
||||||
|
TAG_Float=5, TAG_Double=6, TAG_Byte_Array=7, TAG_String=8, |
||||||
|
TAG_List=9, TAG_Compound=10, TAG_Int_Array=11 }; |
||||||
|
|
||||||
|
static void nbt_get_string_data(unsigned char *data, char *buffer, size_t bufsize) |
||||||
|
{ |
||||||
|
int len = data[0]*256 + data[1]; |
||||||
|
int i; |
||||||
|
for (i=0; i < len && i+1 < (int) bufsize; ++i) |
||||||
|
buffer[i] = (char) data[i+2]; |
||||||
|
buffer[i] = 0; |
||||||
|
} |
||||||
|
|
||||||
|
static char *nbt_peek(nbt *n) |
||||||
|
{ |
||||||
|
unsigned char type = *n->cur; |
||||||
|
if (type == TAG_End) |
||||||
|
return NULL; |
||||||
|
nbt_get_string_data(n->cur+1, n->temp_buffer, sizeof(n->temp_buffer)); |
||||||
|
return n->temp_buffer; |
||||||
|
} |
||||||
|
|
||||||
|
static uint32 nbt_parse_uint32(unsigned char *buffer) |
||||||
|
{ |
||||||
|
return (buffer[0] << 24) + (buffer[1]<<16) + (buffer[2]<<8) + buffer[3]; |
||||||
|
} |
||||||
|
|
||||||
|
static void nbt_skip(nbt *n); |
||||||
|
|
||||||
|
// skip an item that doesn't have an id or name prefix (usable in lists)
|
||||||
|
static void nbt_skip_raw(nbt *n, unsigned char type) |
||||||
|
{ |
||||||
|
switch (type) { |
||||||
|
case TAG_Byte : n->cur += 1; break; |
||||||
|
case TAG_Short : n->cur += 2; break; |
||||||
|
case TAG_Int : n->cur += 4; break; |
||||||
|
case TAG_Long : n->cur += 8; break; |
||||||
|
case TAG_Float : n->cur += 4; break; |
||||||
|
case TAG_Double: n->cur += 8; break; |
||||||
|
case TAG_Byte_Array: n->cur += 4 + 1*nbt_parse_uint32(n->cur); break; |
||||||
|
case TAG_Int_Array : n->cur += 4 + 4*nbt_parse_uint32(n->cur); break; |
||||||
|
case TAG_String : n->cur += 2 + (n->cur[0]*256 + n->cur[1]); break; |
||||||
|
case TAG_List : { |
||||||
|
unsigned char list_type = *n->cur++; |
||||||
|
unsigned int list_len = nbt_parse_uint32(n->cur); |
||||||
|
unsigned int i; |
||||||
|
n->cur += 4; // list_len
|
||||||
|
for (i=0; i < list_len; ++i) |
||||||
|
nbt_skip_raw(n, list_type); |
||||||
|
break; |
||||||
|
} |
||||||
|
case TAG_Compound : { |
||||||
|
while (*n->cur != TAG_End) |
||||||
|
nbt_skip(n); |
||||||
|
nbt_skip(n); // skip the TAG_end
|
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
assert(n->cur <= n->buffer_end); |
||||||
|
} |
||||||
|
|
||||||
|
static void nbt_skip(nbt *n) |
||||||
|
{ |
||||||
|
unsigned char type = *n->cur++; |
||||||
|
if (type == TAG_End) |
||||||
|
return; |
||||||
|
// skip name
|
||||||
|
n->cur += (n->cur[0]*256 + n->cur[1]) + 2; |
||||||
|
nbt_skip_raw(n, type); |
||||||
|
} |
||||||
|
|
||||||
|
// byteswap
|
||||||
|
static void nbt_swap(unsigned char *ptr, int len) |
||||||
|
{ |
||||||
|
int i; |
||||||
|
for (i=0; i < (len>>1); ++i) { |
||||||
|
unsigned char t = ptr[i]; |
||||||
|
ptr[i] = ptr[len-1-i]; |
||||||
|
ptr[len-1-i] = t; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// pass in the expected type, fail if doesn't match
|
||||||
|
// returns a pointer to the data, byteswapped if appropriate
|
||||||
|
static void *nbt_get_fromlist(nbt *n, unsigned char type, int *len) |
||||||
|
{ |
||||||
|
unsigned char *ptr; |
||||||
|
assert(type != TAG_Compound); |
||||||
|
assert(type != TAG_List); // we could support getting lists of primitives as if they were arrays, but eh
|
||||||
|
if (len) *len = 1; |
||||||
|
ptr = n->cur; |
||||||
|
switch (type) { |
||||||
|
case TAG_Byte : break; |
||||||
|
|
||||||
|
case TAG_Short : nbt_swap(ptr, 2); break; |
||||||
|
case TAG_Int : nbt_swap(ptr, 4); break; |
||||||
|
case TAG_Long : nbt_swap(ptr, 8); break; |
||||||
|
case TAG_Float : nbt_swap(ptr, 4); break; |
||||||
|
case TAG_Double: nbt_swap(ptr, 8); break; |
||||||
|
|
||||||
|
case TAG_Byte_Array: |
||||||
|
*len = nbt_parse_uint32(ptr); |
||||||
|
ptr += 4; |
||||||
|
break; |
||||||
|
case TAG_Int_Array: { |
||||||
|
int i; |
||||||
|
*len = nbt_parse_uint32(ptr); |
||||||
|
ptr += 4; |
||||||
|
for (i=0; i < *len; ++i) |
||||||
|
nbt_swap(ptr + 4*i, 4); |
||||||
|
break; |
||||||
|
} |
||||||
|
|
||||||
|
default: assert(0); // unhandled case
|
||||||
|
} |
||||||
|
nbt_skip_raw(n, type); |
||||||
|
return ptr; |
||||||
|
} |
||||||
|
|
||||||
|
static void *nbt_get(nbt *n, unsigned char type, int *len) |
||||||
|
{ |
||||||
|
assert(n->cur[0] == type); |
||||||
|
n->cur += 3 + (n->cur[1]*256+n->cur[2]); |
||||||
|
return nbt_get_fromlist(n, type, len); |
||||||
|
} |
||||||
|
|
||||||
|
static void nbt_begin_compound(nbt *n) // start a compound
|
||||||
|
{ |
||||||
|
assert(*n->cur == TAG_Compound); |
||||||
|
// skip header
|
||||||
|
n->cur += 3 + (n->cur[1]*256 + n->cur[2]); |
||||||
|
++n->nesting; |
||||||
|
} |
||||||
|
|
||||||
|
static void nbt_begin_compound_in_list(nbt *n) // start a compound
|
||||||
|
{ |
||||||
|
++n->nesting; |
||||||
|
} |
||||||
|
|
||||||
|
static void nbt_end_compound(nbt *n) // end a compound
|
||||||
|
{ |
||||||
|
assert(*n->cur == TAG_End); |
||||||
|
assert(n->nesting != 0); |
||||||
|
++n->cur; |
||||||
|
--n->nesting;
|
||||||
|
} |
||||||
|
|
||||||
|
// @TODO no interface to get lists from lists
|
||||||
|
static int nbt_begin_list(nbt *n, unsigned char type) |
||||||
|
{ |
||||||
|
uint32 len; |
||||||
|
unsigned char *ptr; |
||||||
|
|
||||||
|
ptr = n->cur + 3 + (n->cur[1]*256 + n->cur[2]); |
||||||
|
if (ptr[0] != type) |
||||||
|
return -1; |
||||||
|
n->cur = ptr; |
||||||
|
len = nbt_parse_uint32(n->cur+1); |
||||||
|
assert(n->cur[0] == type); |
||||||
|
// @TODO keep a stack with the count to make sure they do it right
|
||||||
|
++n->nesting; |
||||||
|
n->cur += 5; |
||||||
|
return (int) len; |
||||||
|
} |
||||||
|
|
||||||
|
static void nbt_end_list(nbt *n) |
||||||
|
{ |
||||||
|
--n->nesting; |
||||||
|
} |
||||||
|
|
||||||
|
// raw_block chunk is 16x256x16x4 = 2^(4+8+4+2) = 256KB
|
||||||
|
//
|
||||||
|
// if we want to process 64x64x256 at a time, that will be:
|
||||||
|
// 4*4*256KB => 4MB per area in raw_block
|
||||||
|
//
|
||||||
|
// (plus we maybe need to decode adjacent regions)
|
||||||
|
|
||||||
|
|
||||||
|
#ifdef FAST_CHUNK |
||||||
|
typedef fast_chunk parse_chunk; |
||||||
|
#else |
||||||
|
typedef chunk parse_chunk; |
||||||
|
#endif |
||||||
|
|
||||||
|
static parse_chunk *minecraft_chunk_parse(unsigned char *data, size_t len) |
||||||
|
{ |
||||||
|
char *s; |
||||||
|
parse_chunk *c = NULL; |
||||||
|
|
||||||
|
nbt n_store, *n = &n_store; |
||||||
|
n->buffer_start = data; |
||||||
|
n->buffer_end = data + len; |
||||||
|
n->cur = n->buffer_start; |
||||||
|
n->nesting = 0; |
||||||
|
|
||||||
|
nbt_begin_compound(n); |
||||||
|
while ((s = nbt_peek(n)) != NULL) { |
||||||
|
if (!strcmp(s, "Level")) { |
||||||
|
int *height; |
||||||
|
c = malloc(sizeof(*c)); |
||||||
|
#ifdef FAST_CHUNK |
||||||
|
memset(c, 0, sizeof(*c)); |
||||||
|
c->pointer_to_free = data; |
||||||
|
#else |
||||||
|
c->rb[15][15][255].block = 0; |
||||||
|
#endif |
||||||
|
c->max_y = 0; |
||||||
|
|
||||||
|
nbt_begin_compound(n); |
||||||
|
while ((s = nbt_peek(n)) != NULL) { |
||||||
|
if (!strcmp(s, "xPos")) |
||||||
|
c->xpos = *(int *) nbt_get(n, TAG_Int, 0); |
||||||
|
else if (!strcmp(s, "zPos")) |
||||||
|
c->zpos = *(int *) nbt_get(n, TAG_Int, 0); |
||||||
|
else if (!strcmp(s, "Sections")) { |
||||||
|
int count = nbt_begin_list(n, TAG_Compound), i; |
||||||
|
if (count == -1) { |
||||||
|
// this not-a-list case happens in The End and I'm not sure
|
||||||
|
// what it means... possibly one of those silly encodings
|
||||||
|
// where it's not encoded as a list if there's only one?
|
||||||
|
// not worth figuring out
|
||||||
|
nbt_skip(n); |
||||||
|
count = -1; |
||||||
|
} |
||||||
|
for (i=0; i < count; ++i) { |
||||||
|
int yi, len; |
||||||
|
uint8 *light = NULL, *blocks = NULL, *data = NULL, *skylight = NULL; |
||||||
|
nbt_begin_compound_in_list(n); |
||||||
|
while ((s = nbt_peek(n)) != NULL) { |
||||||
|
if (!strcmp(s, "Y")) |
||||||
|
yi = * (uint8 *) nbt_get(n, TAG_Byte, 0); |
||||||
|
else if (!strcmp(s, "BlockLight")) { |
||||||
|
light = nbt_get(n, TAG_Byte_Array, &len); |
||||||
|
assert(len == 2048); |
||||||
|
} else if (!strcmp(s, "Blocks")) { |
||||||
|
blocks = nbt_get(n, TAG_Byte_Array, &len); |
||||||
|
assert(len == 4096); |
||||||
|
} else if (!strcmp(s, "Data")) { |
||||||
|
data = nbt_get(n, TAG_Byte_Array, &len); |
||||||
|
assert(len == 2048); |
||||||
|
} else if (!strcmp(s, "SkyLight")) { |
||||||
|
skylight = nbt_get(n, TAG_Byte_Array, &len); |
||||||
|
assert(len == 2048); |
||||||
|
} |
||||||
|
} |
||||||
|
nbt_end_compound(n); |
||||||
|
|
||||||
|
assert(yi < 16); |
||||||
|
|
||||||
|
#ifndef FAST_CHUNK |
||||||
|
|
||||||
|
// clear data below current max_y
|
||||||
|
{ |
||||||
|
int x,z; |
||||||
|
while (c->max_y < yi*16) { |
||||||
|
for (x=0; x < 16; ++x) |
||||||
|
for (z=0; z < 16; ++z) |
||||||
|
c->rb[z][x][c->max_y].block = 0; |
||||||
|
++c->max_y; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// now assemble the data
|
||||||
|
{ |
||||||
|
int x,y,z, o2=0,o4=0; |
||||||
|
for (y=0; y < 16; ++y) { |
||||||
|
for (z=0; z < 16; ++z) { |
||||||
|
for (x=0; x < 16; x += 2) { |
||||||
|
raw_block *rb = &c->rb[15-z][x][y + yi*16]; // 15-z because switching to z-up will require flipping an axis
|
||||||
|
rb[0].block = blocks[o4]; |
||||||
|
rb[0].light = light[o2] & 15; |
||||||
|
rb[0].data = data[o2] & 15; |
||||||
|
rb[0].skylight = skylight[o2] & 15; |
||||||
|
|
||||||
|
rb[256].block = blocks[o4+1]; |
||||||
|
rb[256].light = light[o2] >> 4; |
||||||
|
rb[256].data = data[o2] >> 4; |
||||||
|
rb[256].skylight = skylight[o2] >> 4; |
||||||
|
|
||||||
|
o2 += 1; |
||||||
|
o4 += 2; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
c->max_y += 16; |
||||||
|
} |
||||||
|
#else |
||||||
|
c->blockdata[yi] = blocks; |
||||||
|
c->data [yi] = data; |
||||||
|
c->light [yi] = light; |
||||||
|
c->skylight [yi] = skylight; |
||||||
|
#endif |
||||||
|
} |
||||||
|
//nbt_end_list(n);
|
||||||
|
} else if (!strcmp(s, "HeightMap")) { |
||||||
|
height = nbt_get(n, TAG_Int_Array, &len); |
||||||
|
assert(len == 256); |
||||||
|
} else |
||||||
|
nbt_skip(n); |
||||||
|
} |
||||||
|
nbt_end_compound(n); |
||||||
|
|
||||||
|
} else |
||||||
|
nbt_skip(n); |
||||||
|
} |
||||||
|
nbt_end_compound(n); |
||||||
|
assert(n->cur == n->buffer_end); |
||||||
|
return c; |
||||||
|
} |
||||||
|
|
||||||
|
#define MAX_DECODED_CHUNK_X 64 |
||||||
|
#define MAX_DECODED_CHUNK_Z 64 |
||||||
|
|
||||||
|
typedef struct |
||||||
|
{ |
||||||
|
int cx,cz; |
||||||
|
fast_chunk *fc; |
||||||
|
int valid; |
||||||
|
} decoded_buffer; |
||||||
|
|
||||||
|
static decoded_buffer decoded_buffers[MAX_DECODED_CHUNK_Z][MAX_DECODED_CHUNK_X]; |
||||||
|
void lock_chunk_get_mutex(void); |
||||||
|
void unlock_chunk_get_mutex(void); |
||||||
|
|
||||||
|
#ifdef FAST_CHUNK |
||||||
|
fast_chunk *get_decoded_fastchunk_uncached(int chunk_x, int chunk_z) |
||||||
|
{ |
||||||
|
unsigned char *decoded; |
||||||
|
compressed_chunk *cc; |
||||||
|
int inlen; |
||||||
|
int len; |
||||||
|
fast_chunk *fc; |
||||||
|
|
||||||
|
lock_chunk_get_mutex(); |
||||||
|
cc = get_compressed_chunk(chunk_x, chunk_z); |
||||||
|
if (cc->len != 0) |
||||||
|
++cc->refcount; |
||||||
|
unlock_chunk_get_mutex(); |
||||||
|
|
||||||
|
if (cc->len == 0) |
||||||
|
return NULL; |
||||||
|
|
||||||
|
assert(cc != NULL); |
||||||
|
|
||||||
|
assert(cc->data[4] == 2); |
||||||
|
|
||||||
|
inlen = nbt_parse_uint32(cc->data); |
||||||
|
decoded = stbi_zlib_decode_malloc_guesssize(cc->data+5, inlen, inlen*3, &len); |
||||||
|
assert(decoded != NULL); |
||||||
|
assert(len != 0); |
||||||
|
|
||||||
|
lock_chunk_get_mutex(); |
||||||
|
deref_compressed_chunk(cc); |
||||||
|
unlock_chunk_get_mutex(); |
||||||
|
|
||||||
|
#ifdef FAST_CHUNK |
||||||
|
fc = minecraft_chunk_parse(decoded, len); |
||||||
|
#else |
||||||
|
fc = NULL; |
||||||
|
#endif |
||||||
|
if (fc == NULL) |
||||||
|
free(decoded); |
||||||
|
return fc; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
decoded_buffer *get_decoded_buffer(int chunk_x, int chunk_z) |
||||||
|
{ |
||||||
|
decoded_buffer *db = &decoded_buffers[chunk_z&(MAX_DECODED_CHUNK_Z-1)][chunk_x&(MAX_DECODED_CHUNK_X-1)]; |
||||||
|
if (db->valid) { |
||||||
|
if (db->cx == chunk_x && db->cz == chunk_z) |
||||||
|
return db; |
||||||
|
if (db->fc) { |
||||||
|
free(db->fc->pointer_to_free); |
||||||
|
free(db->fc); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
db->cx = chunk_x; |
||||||
|
db->cz = chunk_z; |
||||||
|
db->valid = 1; |
||||||
|
db->fc = 0; |
||||||
|
|
||||||
|
{ |
||||||
|
db->fc = get_decoded_fastchunk_uncached(chunk_x, chunk_z); |
||||||
|
return db; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
fast_chunk *get_decoded_fastchunk(int chunk_x, int chunk_z) |
||||||
|
{ |
||||||
|
decoded_buffer *db = get_decoded_buffer(chunk_x, chunk_z); |
||||||
|
return db->fc; |
||||||
|
} |
||||||
|
#endif |
||||||
|
|
||||||
|
#ifndef FAST_CHUNK |
||||||
|
chunk *get_decoded_chunk_raw(int chunk_x, int chunk_z) |
||||||
|
{ |
||||||
|
unsigned char *decoded; |
||||||
|
compressed_chunk *cc = get_compressed_chunk(chunk_x, chunk_z); |
||||||
|
assert(cc != NULL); |
||||||
|
if (cc->len == 0) |
||||||
|
return NULL; |
||||||
|
else { |
||||||
|
chunk *ch; |
||||||
|
int inlen = nbt_parse_uint32(cc->data); |
||||||
|
int len; |
||||||
|
assert(cc->data[4] == 2); |
||||||
|
decoded = stbi_zlib_decode_malloc_guesssize(cc->data+5, inlen, inlen*3, &len); |
||||||
|
assert(decoded != NULL); |
||||||
|
#ifdef FAST_CHUNK |
||||||
|
ch = NULL; |
||||||
|
#else |
||||||
|
ch = minecraft_chunk_parse(decoded, len); |
||||||
|
#endif |
||||||
|
free(decoded); |
||||||
|
return ch; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
static chunk *decoded_chunks[MAX_DECODED_CHUNK_Z][MAX_DECODED_CHUNK_X]; |
||||||
|
chunk *get_decoded_chunk(int chunk_x, int chunk_z) |
||||||
|
{ |
||||||
|
chunk *c = decoded_chunks[chunk_z&(MAX_DECODED_CHUNK_Z-1)][chunk_x&(MAX_DECODED_CHUNK_X-1)]; |
||||||
|
if (c && c->xpos == chunk_x && c->zpos == chunk_z) |
||||||
|
return c; |
||||||
|
if (c) free(c); |
||||||
|
c = get_decoded_chunk_raw(chunk_x, chunk_z); |
||||||
|
decoded_chunks[chunk_z&(MAX_DECODED_CHUNK_Z-1)][chunk_x&(MAX_DECODED_CHUNK_X-1)] = c; |
||||||
|
return c; |
||||||
|
} |
||||||
|
#endif |
@ -0,0 +1,41 @@ |
|||||||
|
#ifndef INCLUDE_CAVE_PARSE_H |
||||||
|
#define INCLUDE_CAVE_PARSE_H |
||||||
|
|
||||||
|
typedef struct |
||||||
|
{ |
||||||
|
unsigned char block; |
||||||
|
unsigned char data; |
||||||
|
unsigned char light:4; |
||||||
|
unsigned char skylight:4; |
||||||
|
} raw_block; |
||||||
|
|
||||||
|
// this is the old fully-decoded chunk
|
||||||
|
typedef struct |
||||||
|
{ |
||||||
|
int xpos, zpos, max_y; |
||||||
|
int height[16][16]; |
||||||
|
raw_block rb[16][16][256]; // [z][x][y] which becomes [y][x][z] in stb
|
||||||
|
} chunk; |
||||||
|
|
||||||
|
chunk *get_decoded_chunk(int chunk_x, int chunk_z); |
||||||
|
|
||||||
|
#define NUM_SEGMENTS 16 |
||||||
|
typedef struct |
||||||
|
{ |
||||||
|
int max_y, xpos, zpos; |
||||||
|
|
||||||
|
unsigned char *blockdata[NUM_SEGMENTS]; |
||||||
|
unsigned char *data[NUM_SEGMENTS]; |
||||||
|
unsigned char *skylight[NUM_SEGMENTS]; |
||||||
|
unsigned char *light[NUM_SEGMENTS]; |
||||||
|
|
||||||
|
void *pointer_to_free;
|
||||||
|
|
||||||
|
int refcount; // this allows multi-threaded building without wrapping in ANOTHER struct
|
||||||
|
} fast_chunk; |
||||||
|
|
||||||
|
fast_chunk *get_decoded_fastchunk(int chunk_x, int chunk_z); // cache, never call free()
|
||||||
|
|
||||||
|
fast_chunk *get_decoded_fastchunk_uncached(int chunk_x, int chunk_z); |
||||||
|
|
||||||
|
#endif |
@ -0,0 +1,951 @@ |
|||||||
|
// This file renders vertex buffers, converts raw meshes
|
||||||
|
// to GL meshes, and manages threads that do the raw-mesh
|
||||||
|
// building (found in cave_mesher.c)
|
||||||
|
|
||||||
|
|
||||||
|
#include "stb_voxel_render.h" |
||||||
|
|
||||||
|
#define STB_GLEXT_DECLARE "glext_list.h" |
||||||
|
#include "stb_gl.h" |
||||||
|
#include "stb_image.h" |
||||||
|
#include "stb_glprog.h" |
||||||
|
|
||||||
|
#include "caveview.h" |
||||||
|
#include "cave_parse.h" |
||||||
|
#include "stb.h" |
||||||
|
#include "sdl.h" |
||||||
|
#include "sdl_thread.h" |
||||||
|
#include <math.h> |
||||||
|
#include <assert.h> |
||||||
|
|
||||||
|
//#define STBVOX_CONFIG_TEX1_EDGE_CLAMP
|
||||||
|
|
||||||
|
|
||||||
|
// currently no dynamic way to set mesh cache size or view distance
|
||||||
|
//#define SHORTVIEW
|
||||||
|
|
||||||
|
|
||||||
|
stbvox_mesh_maker g_mesh_maker; |
||||||
|
|
||||||
|
GLuint main_prog; |
||||||
|
GLint uniform_locations[64]; |
||||||
|
|
||||||
|
//#define MAX_QUADS_PER_DRAW (65536 / 4) // assuming 16-bit indices, 4 verts per quad
|
||||||
|
//#define FIXED_INDEX_BUFFER_SIZE (MAX_QUADS_PER_DRAW * 6 * 2) // 16*1024 * 12 == ~192KB
|
||||||
|
|
||||||
|
// while uploading texture data, this holds our each texture
|
||||||
|
#define TEX_SIZE 64 |
||||||
|
uint32 texture[TEX_SIZE][TEX_SIZE]; |
||||||
|
|
||||||
|
GLuint voxel_tex[2]; |
||||||
|
|
||||||
|
// chunk state
|
||||||
|
enum |
||||||
|
{ |
||||||
|
STATE_invalid, |
||||||
|
STATE_needed, |
||||||
|
STATE_requested, |
||||||
|
STATE_abandoned, |
||||||
|
STATE_valid, |
||||||
|
}; |
||||||
|
|
||||||
|
// mesh is 32x32x255 ... this is hardcoded in that
|
||||||
|
// a mesh covers 2x2 minecraft chunks, no #defines for it
|
||||||
|
typedef struct |
||||||
|
{ |
||||||
|
int state; |
||||||
|
int chunk_x, chunk_y; |
||||||
|
int num_quads; |
||||||
|
float priority; |
||||||
|
int vbuf_size, fbuf_size; |
||||||
|
|
||||||
|
float transform[3][3]; |
||||||
|
float bounds[2][3]; |
||||||
|
|
||||||
|
GLuint vbuf;// vbuf_tex;
|
||||||
|
GLuint fbuf, fbuf_tex; |
||||||
|
|
||||||
|
} chunk_mesh; |
||||||
|
|
||||||
|
void scale_texture(unsigned char *src, int x, int y, int w, int h) |
||||||
|
{ |
||||||
|
int i,j,k; |
||||||
|
assert(w == 256 && h == 256); |
||||||
|
for (j=0; j < TEX_SIZE; ++j) { |
||||||
|
for (i=0; i < TEX_SIZE; ++i) { |
||||||
|
uint32 val=0; |
||||||
|
for (k=0; k < 4; ++k) { |
||||||
|
val >>= 8; |
||||||
|
val += src[ 4*(x+(i>>2)) + 4*w*(y+(j>>2)) + k]<<24; |
||||||
|
} |
||||||
|
texture[j][i] = val; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
void build_base_texture(int n) |
||||||
|
{ |
||||||
|
int x,y; |
||||||
|
uint32 color = stb_rand() | 0x808080; |
||||||
|
for (y=0; y<TEX_SIZE; ++y) |
||||||
|
for (x=0; x<TEX_SIZE; ++x) { |
||||||
|
texture[y][x] = (color + (stb_rand()&0x1f1f1f))|0xff000000; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
void build_overlay_texture(int n) |
||||||
|
{ |
||||||
|
int x,y; |
||||||
|
uint32 color = stb_rand(); |
||||||
|
if (color & 16) |
||||||
|
color = 0xff000000; |
||||||
|
else |
||||||
|
color = 0xffffffff; |
||||||
|
for (y=0; y<TEX_SIZE; ++y) |
||||||
|
for (x=0; x<TEX_SIZE; ++x) { |
||||||
|
texture[y][x] = 0; |
||||||
|
} |
||||||
|
|
||||||
|
for (y=0; y < TEX_SIZE/8; ++y) { |
||||||
|
for (x=0; x < TEX_SIZE; ++x) { |
||||||
|
texture[y][x] = color; |
||||||
|
texture[TEX_SIZE-1-y][x] = color; |
||||||
|
texture[x][y] = color; |
||||||
|
texture[x][TEX_SIZE-1-y] = color; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// view radius of about 1024 = 2048 columns / 32 columns-per-mesh = 2^11 / 2^5 = 64x64
|
||||||
|
// so we need bigger than 64x64 so we can precache, which means we have to be
|
||||||
|
// non-power-of-two, or we have to be pretty huge
|
||||||
|
#define CACHED_MESH_NUM_X 128 |
||||||
|
#define CACHED_MESH_NUM_Y 128 |
||||||
|
|
||||||
|
|
||||||
|
chunk_mesh cached_chunk_mesh[CACHED_MESH_NUM_Y][CACHED_MESH_NUM_X]; |
||||||
|
|
||||||
|
void free_chunk(int slot_x, int slot_y) |
||||||
|
{ |
||||||
|
chunk_mesh *cm = &cached_chunk_mesh[slot_y][slot_x]; |
||||||
|
if (cm->state == STATE_valid) { |
||||||
|
glDeleteTextures(1, &cm->fbuf_tex); |
||||||
|
glDeleteBuffersARB(1, &cm->vbuf); |
||||||
|
glDeleteBuffersARB(1, &cm->fbuf); |
||||||
|
cached_chunk_mesh[slot_y][slot_x].state = STATE_invalid; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
void upload_mesh(chunk_mesh *cm, uint8 *build_buffer, uint8 *face_buffer) |
||||||
|
{ |
||||||
|
glGenBuffersARB(1, &cm->vbuf); |
||||||
|
glBindBufferARB(GL_ARRAY_BUFFER_ARB, cm->vbuf); |
||||||
|
glBufferDataARB(GL_ARRAY_BUFFER_ARB, cm->num_quads*4*sizeof(uint32), build_buffer, GL_STATIC_DRAW_ARB); |
||||||
|
glBindBufferARB(GL_ARRAY_BUFFER_ARB, 0); |
||||||
|
|
||||||
|
glGenBuffersARB(1, &cm->fbuf); |
||||||
|
glBindBufferARB(GL_TEXTURE_BUFFER_ARB, cm->fbuf); |
||||||
|
glBufferDataARB(GL_TEXTURE_BUFFER_ARB, cm->num_quads*sizeof(uint32), face_buffer , GL_STATIC_DRAW_ARB); |
||||||
|
glBindBufferARB(GL_TEXTURE_BUFFER_ARB, 0); |
||||||
|
|
||||||
|
glGenTextures(1, &cm->fbuf_tex); |
||||||
|
glBindTexture(GL_TEXTURE_BUFFER_ARB, cm->fbuf_tex); |
||||||
|
glTexBufferARB(GL_TEXTURE_BUFFER_ARB, GL_RGBA8UI, cm->fbuf); |
||||||
|
glBindTexture(GL_TEXTURE_BUFFER_ARB, 0); |
||||||
|
} |
||||||
|
|
||||||
|
static void upload_mesh_data(raw_mesh *rm) |
||||||
|
{ |
||||||
|
int cx = rm->cx; |
||||||
|
int cy = rm->cy; |
||||||
|
int slot_x = (cx >> 1) & (CACHED_MESH_NUM_X-1); |
||||||
|
int slot_y = (cy >> 1) & (CACHED_MESH_NUM_Y-1); |
||||||
|
chunk_mesh *cm; |
||||||
|
|
||||||
|
free_chunk(slot_x, slot_y); |
||||||
|
|
||||||
|
cm = &cached_chunk_mesh[slot_y][slot_x]; |
||||||
|
cm->num_quads = rm->num_quads; |
||||||
|
|
||||||
|
upload_mesh(cm, rm->build_buffer, rm->face_buffer); |
||||||
|
cm->vbuf_size = rm->num_quads*4*sizeof(uint32); |
||||||
|
cm->fbuf_size = rm->num_quads*sizeof(uint32); |
||||||
|
cm->priority = 100000; |
||||||
|
cm->chunk_x = cx; |
||||||
|
cm->chunk_y = cy; |
||||||
|
|
||||||
|
memcpy(cm->bounds, rm->bounds, sizeof(cm->bounds)); |
||||||
|
memcpy(cm->transform, rm->transform, sizeof(cm->transform)); |
||||||
|
|
||||||
|
// write barrier here
|
||||||
|
cm->state = STATE_valid; |
||||||
|
} |
||||||
|
|
||||||
|
GLint uniform_loc[16]; |
||||||
|
float table3[128][3]; |
||||||
|
float table4[64][4]; |
||||||
|
GLint tablei[2]; |
||||||
|
|
||||||
|
float step=0; |
||||||
|
|
||||||
|
#ifdef SHORTVIEW |
||||||
|
int view_dist_in_chunks = 50; |
||||||
|
#else |
||||||
|
int view_dist_in_chunks = 80; |
||||||
|
#endif |
||||||
|
|
||||||
|
void setup_uniforms(float pos[3]) |
||||||
|
{ |
||||||
|
int i,j; |
||||||
|
step += 1.0f/60.0f; |
||||||
|
for (i=0; i < STBVOX_UNIFORM_count; ++i) { |
||||||
|
stbvox_uniform_info raw, *ui=&raw; |
||||||
|
stbvox_get_uniform_info(&raw, i); |
||||||
|
uniform_loc[i] = -1; |
||||||
|
|
||||||
|
if (i == STBVOX_UNIFORM_texscale || i == STBVOX_UNIFORM_texgen || i == STBVOX_UNIFORM_color_table) |
||||||
|
continue; |
||||||
|
|
||||||
|
if (ui) { |
||||||
|
void *data = ui->default_value; |
||||||
|
uniform_loc[i] = stbgl_find_uniform(main_prog, ui->name); |
||||||
|
switch (i) { |
||||||
|
case STBVOX_UNIFORM_face_data: |
||||||
|
tablei[0] = 2; |
||||||
|
data = tablei; |
||||||
|
break; |
||||||
|
|
||||||
|
case STBVOX_UNIFORM_tex_array: |
||||||
|
glActiveTextureARB(GL_TEXTURE0_ARB); |
||||||
|
glBindTexture(GL_TEXTURE_2D_ARRAY_EXT, voxel_tex[0]); |
||||||
|
glActiveTextureARB(GL_TEXTURE1_ARB); |
||||||
|
glBindTexture(GL_TEXTURE_2D_ARRAY_EXT, voxel_tex[1]); |
||||||
|
glActiveTextureARB(GL_TEXTURE0_ARB); |
||||||
|
tablei[0] = 0; |
||||||
|
tablei[1] = 1; |
||||||
|
data = tablei; |
||||||
|
break; |
||||||
|
|
||||||
|
case STBVOX_UNIFORM_color_table: |
||||||
|
data = ui->default_value; |
||||||
|
((float *)data)[63*4+3] = 2.0f; // emissive
|
||||||
|
break; |
||||||
|
|
||||||
|
case STBVOX_UNIFORM_camera_pos: |
||||||
|
data = table3[0]; |
||||||
|
table3[0][0] = pos[0]; |
||||||
|
table3[0][1] = pos[1]; |
||||||
|
table3[0][2] = pos[2]; |
||||||
|
table3[0][3] = stb_max(0,(float)sin(step*2)*0.125f); |
||||||
|
break; |
||||||
|
|
||||||
|
case STBVOX_UNIFORM_ambient: { |
||||||
|
float bright = 1.0; |
||||||
|
//float bright = 0.75;
|
||||||
|
float amb[3][3]; |
||||||
|
|
||||||
|
// ambient direction is sky-colored upwards
|
||||||
|
// "ambient" lighting is from above
|
||||||
|
table4[0][0] = 0.3f; |
||||||
|
table4[0][1] = -0.5f; |
||||||
|
table4[0][2] = 0.9f; |
||||||
|
|
||||||
|
amb[1][0] = 0.3f; amb[1][1] = 0.3f; amb[1][2] = 0.3f; // dark-grey
|
||||||
|
amb[2][0] = 1.0; amb[2][1] = 1.0; amb[2][2] = 1.0; // white
|
||||||
|
|
||||||
|
// convert so (table[1]*dot+table[2]) gives
|
||||||
|
// above interpolation
|
||||||
|
// lerp((dot+1)/2, amb[1], amb[2])
|
||||||
|
// amb[1] + (amb[2] - amb[1]) * (dot+1)/2
|
||||||
|
// amb[1] + (amb[2] - amb[1]) * dot/2 + (amb[2]-amb[1])/2
|
||||||
|
|
||||||
|
for (j=0; j < 3; ++j) { |
||||||
|
table4[1][j] = (amb[2][j] - amb[1][j])/2 * bright; |
||||||
|
table4[2][j] = (amb[1][j] + amb[2][j])/2 * bright; |
||||||
|
} |
||||||
|
|
||||||
|
// fog color
|
||||||
|
table4[3][0] = 0.6f, table4[3][1] = 0.7f, table4[3][2] = 0.9f; |
||||||
|
table4[3][3] = 1.0f / (view_dist_in_chunks * 16); |
||||||
|
table4[3][3] *= table4[3][3]; |
||||||
|
|
||||||
|
data = table4; |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
switch (ui->type) { |
||||||
|
case STBVOX_UNIFORM_TYPE_sampler: stbglUniform1iv(uniform_loc[i], ui->array_length, data); break; |
||||||
|
case STBVOX_UNIFORM_TYPE_vec2: stbglUniform2fv(uniform_loc[i], ui->array_length, data); break; |
||||||
|
case STBVOX_UNIFORM_TYPE_vec3: stbglUniform3fv(uniform_loc[i], ui->array_length, data); break; |
||||||
|
case STBVOX_UNIFORM_TYPE_vec4: stbglUniform4fv(uniform_loc[i], ui->array_length, data); break; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
GLuint unitex[64], unibuf[64]; |
||||||
|
void make_texture_buffer_for_uniform(int uniform, int slot) |
||||||
|
{ |
||||||
|
GLenum type; |
||||||
|
stbvox_uniform_info raw, *ui=&raw; |
||||||
|
GLint uloc; |
||||||
|
|
||||||
|
stbvox_get_uniform_info(ui, uniform); |
||||||
|
uloc = stbgl_find_uniform(main_prog, ui->name); |
||||||
|
|
||||||
|
if (uniform == STBVOX_UNIFORM_color_table) |
||||||
|
((float *)ui->default_value)[63*4+3] = 2.0f; // emissive
|
||||||
|
|
||||||
|
glGenBuffersARB(1, &unibuf[uniform]); |
||||||
|
glBindBufferARB(GL_ARRAY_BUFFER_ARB, unibuf[uniform]); |
||||||
|
glBufferDataARB(GL_ARRAY_BUFFER_ARB, ui->array_length * ui->bytes_per_element, ui->default_value, GL_STATIC_DRAW_ARB); |
||||||
|
glBindBufferARB(GL_ARRAY_BUFFER_ARB, 0); |
||||||
|
|
||||||
|
glGenTextures(1, &unitex[uniform]); |
||||||
|
glBindTexture(GL_TEXTURE_BUFFER_ARB, unitex[uniform]); |
||||||
|
switch (ui->type) { |
||||||
|
case STBVOX_UNIFORM_TYPE_vec2: type = GL_RG32F; break; |
||||||
|
case STBVOX_UNIFORM_TYPE_vec3: type = GL_RGB32F; break; |
||||||
|
case STBVOX_UNIFORM_TYPE_vec4: type = GL_RGBA32F; break; |
||||||
|
default: assert(0); |
||||||
|
} |
||||||
|
glTexBufferARB(GL_TEXTURE_BUFFER_ARB, type, unibuf[uniform]); |
||||||
|
glBindTexture(GL_TEXTURE_BUFFER_ARB, 0); |
||||||
|
|
||||||
|
glActiveTextureARB(GL_TEXTURE0 + slot); |
||||||
|
glBindTexture(GL_TEXTURE_BUFFER_ARB, unitex[uniform]); |
||||||
|
glActiveTextureARB(GL_TEXTURE0); |
||||||
|
|
||||||
|
stbglUseProgram(main_prog); |
||||||
|
stbglUniform1i(uloc, slot); |
||||||
|
} |
||||||
|
|
||||||
|
#define MAX_MESH_WORKERS 8 |
||||||
|
#define MAX_CHUNK_LOAD_WORKERS 2 |
||||||
|
|
||||||
|
int num_mesh_workers; |
||||||
|
int num_chunk_load_workers; |
||||||
|
|
||||||
|
typedef struct |
||||||
|
{ |
||||||
|
int state; |
||||||
|
int request_cx; |
||||||
|
int request_cy; |
||||||
|
int padding[13]; |
||||||
|
|
||||||
|
SDL_sem * request_received; |
||||||
|
|
||||||
|
SDL_sem * chunk_server_done_processing; |
||||||
|
int chunk_action; |
||||||
|
int chunk_request_x; |
||||||
|
int chunk_request_y; |
||||||
|
fast_chunk *chunks[4][4]; |
||||||
|
|
||||||
|
int padding2[16]; |
||||||
|
raw_mesh rm; |
||||||
|
int padding3[16]; |
||||||
|
|
||||||
|
uint8 *build_buffer; |
||||||
|
uint8 *face_buffer ; |
||||||
|
} mesh_worker; |
||||||
|
|
||||||
|
enum |
||||||
|
{ |
||||||
|
WSTATE_idle, |
||||||
|
WSTATE_requested, |
||||||
|
WSTATE_running, |
||||||
|
WSTATE_mesh_ready, |
||||||
|
}; |
||||||
|
|
||||||
|
mesh_worker mesh_data[MAX_MESH_WORKERS]; |
||||||
|
int num_meshes_started; // stats
|
||||||
|
|
||||||
|
int request_chunk(int chunk_x, int chunk_y); |
||||||
|
void update_meshes_from_render_thread(void); |
||||||
|
|
||||||
|
unsigned char tex2_data[64][4]; |
||||||
|
|
||||||
|
void init_tex2_gradient(void) |
||||||
|
{ |
||||||
|
int i; |
||||||
|
for (i=0; i < 16; ++i) { |
||||||
|
tex2_data[i+ 0][0] = 64 + 12*i; |
||||||
|
tex2_data[i+ 0][1] = 32; |
||||||
|
tex2_data[i+ 0][2] = 64; |
||||||
|
|
||||||
|
tex2_data[i+16][0] = 255; |
||||||
|
tex2_data[i+16][1] = 32 + 8*i; |
||||||
|
tex2_data[i+16][2] = 64; |
||||||
|
|
||||||
|
tex2_data[i+32][0] = 255; |
||||||
|
tex2_data[i+32][1] = 160; |
||||||
|
tex2_data[i+32][2] = 64 + 12*i; |
||||||
|
|
||||||
|
tex2_data[i+48][0] = 255; |
||||||
|
tex2_data[i+48][1] = 160 + 6*i; |
||||||
|
tex2_data[i+48][2] = 255; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
void set_tex2_alpha(float fa) |
||||||
|
{ |
||||||
|
int i; |
||||||
|
int a = (int) stb_lerp(fa, 0, 255); |
||||||
|
if (a < 0) a = 0; else if (a > 255) a = 255; |
||||||
|
glBindTexture(GL_TEXTURE_2D_ARRAY_EXT, voxel_tex[1]); |
||||||
|
for (i=0; i < 64; ++i) { |
||||||
|
tex2_data[i][3] = a; |
||||||
|
glTexSubImage3DEXT(GL_TEXTURE_2D_ARRAY_EXT, 0, 0,0,i, 1,1,1, GL_RGBA, GL_UNSIGNED_BYTE, tex2_data[i]); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
void render_init(void) |
||||||
|
{ |
||||||
|
int i; |
||||||
|
char *binds[] = { "attr_vertex", "attr_face", NULL }; |
||||||
|
char *vertex; |
||||||
|
char *fragment; |
||||||
|
int w=0,h=0; |
||||||
|
|
||||||
|
unsigned char *texdata = stbi_load("terrain.png", &w, &h, NULL, 4); |
||||||
|
|
||||||
|
stbvox_init_mesh_maker(&g_mesh_maker); |
||||||
|
for (i=0; i < num_mesh_workers; ++i) { |
||||||
|
stbvox_init_mesh_maker(&mesh_data[i].rm.mm); |
||||||
|
} |
||||||
|
|
||||||
|
vertex = stbvox_get_vertex_shader(); |
||||||
|
fragment = stbvox_get_fragment_shader(); |
||||||
|
|
||||||
|
{ |
||||||
|
char error_buffer[1024]; |
||||||
|
char *main_vertex[] = { vertex, NULL }; |
||||||
|
char *main_fragment[] = { fragment, NULL }; |
||||||
|
main_prog = stbgl_create_program(main_vertex, main_fragment, binds, error_buffer, sizeof(error_buffer)); |
||||||
|
if (main_prog == 0) { |
||||||
|
ods("Compile error for main shader: %s\n", error_buffer); |
||||||
|
assert(0); |
||||||
|
exit(1); |
||||||
|
} |
||||||
|
} |
||||||
|
//init_index_buffer();
|
||||||
|
|
||||||
|
make_texture_buffer_for_uniform(STBVOX_UNIFORM_texscale , 3); |
||||||
|
make_texture_buffer_for_uniform(STBVOX_UNIFORM_texgen , 4); |
||||||
|
make_texture_buffer_for_uniform(STBVOX_UNIFORM_color_table , 5); |
||||||
|
|
||||||
|
glGenTextures(2, voxel_tex); |
||||||
|
|
||||||
|
glBindTexture(GL_TEXTURE_2D_ARRAY_EXT, voxel_tex[0]); |
||||||
|
glTexImage3DEXT(GL_TEXTURE_2D_ARRAY_EXT, 0, GL_RGBA, |
||||||
|
TEX_SIZE,TEX_SIZE,256, |
||||||
|
0,GL_RGBA,GL_UNSIGNED_BYTE,NULL); |
||||||
|
for (i=0; i < 256; ++i) { |
||||||
|
if (texdata) |
||||||
|
scale_texture(texdata, (i&15)*w/16, (h/16)*(i>>4), w,h); |
||||||
|
else |
||||||
|
build_base_texture(i); |
||||||
|
glTexSubImage3DEXT(GL_TEXTURE_2D_ARRAY_EXT, 0, 0,0,i, TEX_SIZE,TEX_SIZE,1, GL_RGBA, GL_UNSIGNED_BYTE, texture[0]); |
||||||
|
} |
||||||
|
glTexParameteri(GL_TEXTURE_2D_ARRAY_EXT, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); |
||||||
|
glTexParameteri(GL_TEXTURE_2D_ARRAY_EXT, GL_TEXTURE_MAG_FILTER, GL_LINEAR); |
||||||
|
glTexParameteri(GL_TEXTURE_2D_ARRAY_EXT, GL_TEXTURE_MAX_ANISOTROPY_EXT, 16); |
||||||
|
#ifdef STBVOX_CONFIG_TEX1_EDGE_CLAMP |
||||||
|
glTexParameteri(GL_TEXTURE_2D_ARRAY_EXT, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); |
||||||
|
glTexParameteri(GL_TEXTURE_2D_ARRAY_EXT, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); |
||||||
|
#endif |
||||||
|
|
||||||
|
glGenerateMipmapEXT(GL_TEXTURE_2D_ARRAY_EXT); |
||||||
|
|
||||||
|
glBindTexture(GL_TEXTURE_2D_ARRAY_EXT, voxel_tex[1]); |
||||||
|
glTexImage3DEXT(GL_TEXTURE_2D_ARRAY_EXT, 0, GL_RGBA, |
||||||
|
1,1,64, |
||||||
|
0,GL_RGBA,GL_UNSIGNED_BYTE,NULL); |
||||||
|
init_tex2_gradient(); |
||||||
|
set_tex2_alpha(0.0); |
||||||
|
#if 0 |
||||||
|
for (i=0; i < 128; ++i) { |
||||||
|
//build_overlay_texture(i);
|
||||||
|
glTexSubImage3DEXT(GL_TEXTURE_2D_ARRAY_EXT, 0, 0,0,i, TEX_SIZE,TEX_SIZE,1, GL_RGBA, GL_UNSIGNED_BYTE, texture[0]); |
||||||
|
} |
||||||
|
#endif |
||||||
|
glTexParameteri(GL_TEXTURE_2D_ARRAY_EXT, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); |
||||||
|
glTexParameteri(GL_TEXTURE_2D_ARRAY_EXT, GL_TEXTURE_MAG_FILTER, GL_LINEAR); |
||||||
|
glGenerateMipmapEXT(GL_TEXTURE_2D_ARRAY_EXT); |
||||||
|
} |
||||||
|
|
||||||
|
void world_init(void) |
||||||
|
{ |
||||||
|
int a,b,x,y; |
||||||
|
|
||||||
|
Uint64 start_time, end_time; |
||||||
|
#ifdef NDEBUG |
||||||
|
int range = 32; |
||||||
|
#else |
||||||
|
int range = 12; |
||||||
|
#endif |
||||||
|
|
||||||
|
start_time = SDL_GetPerformanceCounter(); |
||||||
|
|
||||||
|
// iterate in 8x8 clusters of qchunks at a time to get better converted-chunk-cache reuse
|
||||||
|
// than a purely row-by-row ordering is (single-threaded this is a bigger win than
|
||||||
|
// any of the above optimizations were, since it halves zlib/mc-conversion costs)
|
||||||
|
for (x=-range; x <= range; x += 16) |
||||||
|
for (y=-range; y <= range; y += 16) |
||||||
|
for (b=y; b < y+16 && b <= range; b += 2) |
||||||
|
for (a=x; a < x+16 && a <= range; a += 2) |
||||||
|
while (!request_chunk(a, b)) { // if request fails, all threads are busy
|
||||||
|
update_meshes_from_render_thread(); |
||||||
|
SDL_Delay(1); |
||||||
|
} |
||||||
|
|
||||||
|
// wait until all the workers are done,
|
||||||
|
// (this is only needed if we want to time
|
||||||
|
// when the build finishes, or when we want to reset the
|
||||||
|
// cache size; otherwise we could just go ahead and
|
||||||
|
// start rendering whatever we've got)
|
||||||
|
for(;;) { |
||||||
|
int i; |
||||||
|
update_meshes_from_render_thread(); |
||||||
|
for (i=0; i < num_mesh_workers; ++i) |
||||||
|
if (mesh_data[i].state != WSTATE_idle) |
||||||
|
break; |
||||||
|
if (i == num_mesh_workers) |
||||||
|
break; |
||||||
|
SDL_Delay(3); |
||||||
|
} |
||||||
|
|
||||||
|
end_time = SDL_GetPerformanceCounter(); |
||||||
|
ods("Build time: %7.2fs\n", (end_time - start_time) / (float) SDL_GetPerformanceFrequency()); |
||||||
|
|
||||||
|
// don't waste lots of storage on chunk caches once it's finished starting-up;
|
||||||
|
// this was only needed to be this large because we worked in large blocks
|
||||||
|
// to maximize sharing
|
||||||
|
reset_cache_size(32); |
||||||
|
} |
||||||
|
|
||||||
|
extern SDL_mutex * chunk_cache_mutex; |
||||||
|
|
||||||
|
int mesh_worker_handler(void *data) |
||||||
|
{ |
||||||
|
mesh_worker *mw = data; |
||||||
|
mw->face_buffer = malloc(FACE_BUFFER_SIZE); |
||||||
|
mw->build_buffer = malloc(BUILD_BUFFER_SIZE); |
||||||
|
|
||||||
|
// this loop only works because the compiler can't
|
||||||
|
// tell that the SDL_calls don't access mw->state;
|
||||||
|
// really we should barrier that stuff
|
||||||
|
for(;;) { |
||||||
|
int i,j; |
||||||
|
int cx,cy; |
||||||
|
|
||||||
|
// wait for a chunk request
|
||||||
|
SDL_SemWait(mw->request_received); |
||||||
|
|
||||||
|
// analyze the chunk request
|
||||||
|
assert(mw->state == WSTATE_requested); |
||||||
|
cx = mw->request_cx; |
||||||
|
cy = mw->request_cy; |
||||||
|
|
||||||
|
// this is inaccurate as it can block while another thread has the cache locked
|
||||||
|
mw->state = WSTATE_running; |
||||||
|
|
||||||
|
// get the chunks we need (this takes a lock and caches them)
|
||||||
|
for (j=0; j < 4; ++j) |
||||||
|
for (i=0; i < 4; ++i) |
||||||
|
mw->chunks[j][i] = get_converted_fastchunk(cx-1 + i, cy-1 + j); |
||||||
|
|
||||||
|
// build the mesh based on the chunks
|
||||||
|
mw->rm.build_buffer = mw->build_buffer; |
||||||
|
mw->rm.face_buffer = mw->face_buffer; |
||||||
|
build_chunk(cx, cy, mw->chunks, &mw->rm); |
||||||
|
mw->state = WSTATE_mesh_ready; |
||||||
|
// don't need to notify of this, because it gets polled
|
||||||
|
|
||||||
|
// when done, free the chunks
|
||||||
|
|
||||||
|
// for efficiency we just take the mutex once around the whole thing,
|
||||||
|
// though this spreads the mutex logic over two files
|
||||||
|
SDL_LockMutex(chunk_cache_mutex); |
||||||
|
for (j=0; j < 4; ++j) |
||||||
|
for (i=0; i < 4; ++i) { |
||||||
|
deref_fastchunk(mw->chunks[j][i]); |
||||||
|
mw->chunks[j][i] = NULL; |
||||||
|
} |
||||||
|
SDL_UnlockMutex(chunk_cache_mutex); |
||||||
|
} |
||||||
|
return 0; |
||||||
|
} |
||||||
|
|
||||||
|
int request_chunk(int chunk_x, int chunk_y) |
||||||
|
{ |
||||||
|
int i; |
||||||
|
for (i=0; i < num_mesh_workers; ++i) { |
||||||
|
mesh_worker *mw = &mesh_data[i]; |
||||||
|
if (mw->state == WSTATE_idle) { |
||||||
|
mw->request_cx = chunk_x; |
||||||
|
mw->request_cy = chunk_y; |
||||||
|
mw->state = WSTATE_requested; |
||||||
|
SDL_SemPost(mw->request_received); |
||||||
|
++num_meshes_started; |
||||||
|
return 1; |
||||||
|
} |
||||||
|
} |
||||||
|
return 0; |
||||||
|
} |
||||||
|
|
||||||
|
void prepare_threads(void) |
||||||
|
{ |
||||||
|
int i; |
||||||
|
int num_proc = SDL_GetCPUCount(); |
||||||
|
|
||||||
|
if (num_proc > 6) |
||||||
|
num_mesh_workers = num_proc/2; |
||||||
|
else if (num_proc > 4) |
||||||
|
num_mesh_workers = 4; |
||||||
|
else
|
||||||
|
num_mesh_workers = num_proc-1; |
||||||
|
|
||||||
|
// @TODO
|
||||||
|
// Thread usage is probably pretty terrible; need to make a
|
||||||
|
// separate queue of needed chunks, instead of just generating
|
||||||
|
// one request per thread per frame, and a separate queue of
|
||||||
|
// results. (E.g. If it takes 1.5 frames to build mesh, thread
|
||||||
|
// is idle for 0.5 frames.) To fake this for now, I've just
|
||||||
|
// doubled the number of threads to let those serve as a 'queue',
|
||||||
|
// but that's dumb.
|
||||||
|
|
||||||
|
num_mesh_workers *= 2; // try to get better thread usage
|
||||||
|
|
||||||
|
if (num_mesh_workers > MAX_MESH_WORKERS) |
||||||
|
num_mesh_workers = MAX_MESH_WORKERS; |
||||||
|
|
||||||
|
for (i=0; i < num_mesh_workers; ++i) { |
||||||
|
mesh_worker *data = &mesh_data[i]; |
||||||
|
data->request_received = SDL_CreateSemaphore(0); |
||||||
|
data->chunk_server_done_processing = SDL_CreateSemaphore(0); |
||||||
|
SDL_CreateThread(mesh_worker_handler, "mesh worker", data); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
// "better" buffer uploading
|
||||||
|
#if 0 |
||||||
|
if (glBufferStorage) { |
||||||
|
glDeleteBuffersARB(1, &vb->vbuf); |
||||||
|
glGenBuffersARB(1, &vb->vbuf); |
||||||
|
|
||||||
|
glBindBufferARB(GL_ARRAY_BUFFER_ARB, vb->vbuf); |
||||||
|
glBufferStorage(GL_ARRAY_BUFFER_ARB, sizeof(build_buffer), build_buffer, 0); |
||||||
|
glBindBufferARB(GL_ARRAY_BUFFER_ARB, 0); |
||||||
|
} else { |
||||||
|
glBindBufferARB(GL_ARRAY_BUFFER_ARB, vb->vbuf); |
||||||
|
glBufferDataARB(GL_ARRAY_BUFFER_ARB, sizeof(build_buffer), build_buffer, GL_STATIC_DRAW_ARB); |
||||||
|
glBindBufferARB(GL_ARRAY_BUFFER_ARB, 0); |
||||||
|
} |
||||||
|
#endif |
||||||
|
|
||||||
|
|
||||||
|
typedef struct |
||||||
|
{ |
||||||
|
float x,y,z,w; |
||||||
|
} plane; |
||||||
|
|
||||||
|
static plane frustum[6]; |
||||||
|
|
||||||
|
static void matd_mul(double out[4][4], double src1[4][4], double src2[4][4]) |
||||||
|
{ |
||||||
|
int i,j,k; |
||||||
|
for (j=0; j < 4; ++j) { |
||||||
|
for (i=0; i < 4; ++i) { |
||||||
|
double t=0; |
||||||
|
for (k=0; k < 4; ++k) |
||||||
|
t += src1[k][i] * src2[j][k]; |
||||||
|
out[i][j] = t; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// https://fgiesen.wordpress.com/2012/08/31/frustum-planes-from-the-projection-matrix/
|
||||||
|
static void compute_frustum(void) |
||||||
|
{ |
||||||
|
int i; |
||||||
|
GLdouble mv[4][4],proj[4][4], mvproj[4][4]; |
||||||
|
glGetDoublev(GL_MODELVIEW_MATRIX , mv[0]); |
||||||
|
glGetDoublev(GL_PROJECTION_MATRIX, proj[0]); |
||||||
|
matd_mul(mvproj, proj, mv); |
||||||
|
for (i=0; i < 4; ++i) { |
||||||
|
(&frustum[0].x)[i] = (float) (mvproj[3][i] + mvproj[0][i]); |
||||||
|
(&frustum[1].x)[i] = (float) (mvproj[3][i] - mvproj[0][i]); |
||||||
|
(&frustum[2].x)[i] = (float) (mvproj[3][i] + mvproj[1][i]); |
||||||
|
(&frustum[3].x)[i] = (float) (mvproj[3][i] - mvproj[1][i]); |
||||||
|
(&frustum[4].x)[i] = (float) (mvproj[3][i] + mvproj[2][i]); |
||||||
|
(&frustum[5].x)[i] = (float) (mvproj[3][i] - mvproj[2][i]); |
||||||
|
}
|
||||||
|
} |
||||||
|
|
||||||
|
static int test_plane(plane *p, float x0, float y0, float z0, float x1, float y1, float z1) |
||||||
|
{ |
||||||
|
// return false if the box is entirely behind the plane
|
||||||
|
float d=0; |
||||||
|
assert(x0 <= x1 && y0 <= y1 && z0 <= z1); |
||||||
|
if (p->x > 0) d += x1*p->x; else d += x0*p->x; |
||||||
|
if (p->y > 0) d += y1*p->y; else d += y0*p->y; |
||||||
|
if (p->z > 0) d += z1*p->z; else d += z0*p->z; |
||||||
|
return d + p->w >= 0; |
||||||
|
} |
||||||
|
|
||||||
|
static int is_box_in_frustum(float *bmin, float *bmax) |
||||||
|
{ |
||||||
|
int i; |
||||||
|
for (i=0; i < 5; ++i) |
||||||
|
if (!test_plane(&frustum[i], bmin[0], bmin[1], bmin[2], bmax[0], bmax[1], bmax[2])) |
||||||
|
return 0; |
||||||
|
return 1; |
||||||
|
} |
||||||
|
|
||||||
|
float compute_priority(int cx, int cy, float x, float y) |
||||||
|
{ |
||||||
|
float distx, disty, dist2; |
||||||
|
distx = (cx*16+8) - x; |
||||||
|
disty = (cy*16+8) - y; |
||||||
|
dist2 = distx*distx + disty*disty; |
||||||
|
return view_dist_in_chunks*view_dist_in_chunks * 16 * 16 - dist2; |
||||||
|
} |
||||||
|
|
||||||
|
int chunk_locations, chunks_considered, chunks_in_frustum; |
||||||
|
int quads_considered, quads_rendered; |
||||||
|
int chunk_storage_rendered, chunk_storage_considered, chunk_storage_total; |
||||||
|
int update_frustum = 1; |
||||||
|
|
||||||
|
#ifdef SHORTVIEW |
||||||
|
int max_chunk_storage = 450 << 20; |
||||||
|
int min_chunk_storage = 350 << 20; |
||||||
|
#else |
||||||
|
int max_chunk_storage = 900 << 20; |
||||||
|
int min_chunk_storage = 800 << 20; |
||||||
|
#endif |
||||||
|
|
||||||
|
float min_priority = -500; // this really wants to be in unit space, not squared space
|
||||||
|
|
||||||
|
int num_meshes_uploaded; |
||||||
|
|
||||||
|
void update_meshes_from_render_thread(void) |
||||||
|
{ |
||||||
|
int i; |
||||||
|
for (i=0; i < num_mesh_workers; ++i) { |
||||||
|
mesh_worker *mw = &mesh_data[i]; |
||||||
|
if (mw->state == WSTATE_mesh_ready) { |
||||||
|
upload_mesh_data(&mw->rm); |
||||||
|
++num_meshes_uploaded; |
||||||
|
mw->state = WSTATE_idle; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
extern float tex2_alpha; |
||||||
|
extern int global_hack; |
||||||
|
int num_threads_active; |
||||||
|
float chunk_server_activity; |
||||||
|
|
||||||
|
void render_caves(float campos[3]) |
||||||
|
{ |
||||||
|
float x = campos[0], y = campos[1]; |
||||||
|
int qchunk_x, qchunk_y; |
||||||
|
int cam_x, cam_y; |
||||||
|
int i,j, rad; |
||||||
|
|
||||||
|
compute_frustum(); |
||||||
|
|
||||||
|
chunk_locations = chunks_considered = chunks_in_frustum = 0; |
||||||
|
quads_considered = quads_rendered = 0; |
||||||
|
chunk_storage_total = chunk_storage_considered = chunk_storage_rendered = 0; |
||||||
|
|
||||||
|
cam_x = (int) floor(x+0.5); |
||||||
|
cam_y = (int) floor(y+0.5); |
||||||
|
|
||||||
|
qchunk_x = (((int) floor(x)+16) >> 5) << 1; |
||||||
|
qchunk_y = (((int) floor(y)+16) >> 5) << 1; |
||||||
|
|
||||||
|
glEnable(GL_ALPHA_TEST); |
||||||
|
glAlphaFunc(GL_GREATER, 0.5); |
||||||
|
|
||||||
|
stbglUseProgram(main_prog); |
||||||
|
setup_uniforms(campos); // set uniforms to default values inefficiently
|
||||||
|
glActiveTextureARB(GL_TEXTURE2_ARB); |
||||||
|
stbglEnableVertexAttribArray(0); |
||||||
|
|
||||||
|
{ |
||||||
|
float lighting[2][3] = { { campos[0],campos[1],campos[2] }, { 0.75,0.75,0.65f } }; |
||||||
|
float bright = 8; |
||||||
|
lighting[1][0] *= bright; |
||||||
|
lighting[1][1] *= bright; |
||||||
|
lighting[1][2] *= bright; |
||||||
|
stbglUniform3fv(stbgl_find_uniform(main_prog, "light_source"), 2, lighting[0]); |
||||||
|
} |
||||||
|
|
||||||
|
if (global_hack) |
||||||
|
set_tex2_alpha(tex2_alpha); |
||||||
|
|
||||||
|
num_meshes_uploaded = 0; |
||||||
|
update_meshes_from_render_thread(); |
||||||
|
|
||||||
|
// traverse all in-range chunks and analyze them
|
||||||
|
for (j=-view_dist_in_chunks; j <= view_dist_in_chunks; j += 2) { |
||||||
|
for (i=-view_dist_in_chunks; i <= view_dist_in_chunks; i += 2) { |
||||||
|
float priority; |
||||||
|
int cx = qchunk_x + i; |
||||||
|
int cy = qchunk_y + j; |
||||||
|
|
||||||
|
priority = compute_priority(cx, cy, x, y); |
||||||
|
if (priority >= min_priority) { |
||||||
|
int slot_x = (cx>>1) & (CACHED_MESH_NUM_X-1); |
||||||
|
int slot_y = (cy>>1) & (CACHED_MESH_NUM_Y-1); |
||||||
|
chunk_mesh *cm = &cached_chunk_mesh[slot_y][slot_x]; |
||||||
|
++chunk_locations; |
||||||
|
if (cm->state == STATE_valid && priority >= 0) { |
||||||
|
// check if chunk pos actually matches
|
||||||
|
if (cm->chunk_x != cx || cm->chunk_y != cy) { |
||||||
|
// we have a stale chunk we need to recreate
|
||||||
|
free_chunk(slot_x, slot_y); // it probably will have already gotten freed, but just in case
|
||||||
|
} |
||||||
|
} |
||||||
|
if (cm->state == STATE_invalid) { |
||||||
|
cm->chunk_x = cx; |
||||||
|
cm->chunk_y = cy; |
||||||
|
cm->state = STATE_needed; |
||||||
|
} |
||||||
|
cm->priority = priority; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// draw front-to-back
|
||||||
|
for (rad = 0; rad <= view_dist_in_chunks; rad += 2) { |
||||||
|
for (j=-rad; j <= rad; j += 2) { |
||||||
|
// if j is +- rad, then iterate i through all values
|
||||||
|
// if j isn't +-rad, then i should be only -rad & rad
|
||||||
|
int step = 2; |
||||||
|
if (abs(j) != rad) |
||||||
|
step = 2*rad; |
||||||
|
for (i=-rad; i <= rad; i += step) { |
||||||
|
int cx = qchunk_x + i; |
||||||
|
int cy = qchunk_y + j; |
||||||
|
int slot_x = (cx>>1) & (CACHED_MESH_NUM_X-1); |
||||||
|
int slot_y = (cy>>1) & (CACHED_MESH_NUM_Y-1); |
||||||
|
chunk_mesh *cm = &cached_chunk_mesh[slot_y][slot_x]; |
||||||
|
if (cm->state == STATE_valid && cm->priority >= 0) { |
||||||
|
++chunks_considered; |
||||||
|
quads_considered += cm->num_quads; |
||||||
|
if (is_box_in_frustum(cm->bounds[0], cm->bounds[1])) { |
||||||
|
++chunks_in_frustum; |
||||||
|
|
||||||
|
// @TODO if in range
|
||||||
|
stbglUniform3fv(uniform_loc[STBVOX_UNIFORM_transform], 3, cm->transform[0]); |
||||||
|
glBindBufferARB(GL_ARRAY_BUFFER_ARB, cm->vbuf); |
||||||
|
glVertexAttribIPointer(0, 1, GL_UNSIGNED_INT, 4, (void*) 0); |
||||||
|
glBindTexture(GL_TEXTURE_BUFFER_ARB, cm->fbuf_tex); |
||||||
|
glDrawArrays(GL_QUADS, 0, cm->num_quads*4); |
||||||
|
quads_rendered += cm->num_quads; |
||||||
|
|
||||||
|
chunk_storage_rendered += cm->vbuf_size + cm->fbuf_size; |
||||||
|
} |
||||||
|
chunk_storage_considered += cm->vbuf_size + cm->fbuf_size; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
stbglDisableVertexAttribArray(0); |
||||||
|
glBindBufferARB(GL_ARRAY_BUFFER_ARB, 0); |
||||||
|
glActiveTextureARB(GL_TEXTURE0_ARB); |
||||||
|
|
||||||
|
stbglUseProgram(0); |
||||||
|
num_meshes_started = 0; |
||||||
|
|
||||||
|
{ |
||||||
|
#define MAX_QUEUE 8 |
||||||
|
float highest_priority[MAX_QUEUE]; |
||||||
|
int highest_i[MAX_QUEUE], highest_j[MAX_QUEUE]; |
||||||
|
float lowest_priority = view_dist_in_chunks * view_dist_in_chunks * 16 * 16.0f; |
||||||
|
int lowest_i = -1, lowest_j = -1; |
||||||
|
|
||||||
|
for (i=0; i < MAX_QUEUE; ++i) { |
||||||
|
highest_priority[i] = min_priority; |
||||||
|
highest_i[i] = -1; |
||||||
|
highest_j[i] = -1; |
||||||
|
} |
||||||
|
|
||||||
|
for (j=0; j < CACHED_MESH_NUM_Y; ++j) { |
||||||
|
for (i=0; i < CACHED_MESH_NUM_X; ++i) { |
||||||
|
chunk_mesh *cm = &cached_chunk_mesh[j][i]; |
||||||
|
if (cm->state == STATE_valid) { |
||||||
|
cm->priority = compute_priority(cm->chunk_x, cm->chunk_y, x, y); |
||||||
|
chunk_storage_total += cm->vbuf_size + cm->fbuf_size; |
||||||
|
if (cm->priority < lowest_priority) { |
||||||
|
lowest_priority = cm->priority; |
||||||
|
lowest_i = i; |
||||||
|
lowest_j = j; |
||||||
|
} |
||||||
|
} |
||||||
|
if (cm->state == STATE_needed) { |
||||||
|
cm->priority = compute_priority(cm->chunk_x, cm->chunk_y, x, y); |
||||||
|
if (cm->priority < min_priority) |
||||||
|
cm->state = STATE_invalid; |
||||||
|
else if (cm->priority > highest_priority[0]) { |
||||||
|
int k; |
||||||
|
highest_priority[0] = cm->priority; |
||||||
|
highest_i[0] = i; |
||||||
|
highest_j[0] = j; |
||||||
|
// bubble this up to right place
|
||||||
|
for (k=0; k < MAX_QUEUE-1; ++k) { |
||||||
|
if (highest_priority[k] > highest_priority[k+1]) { |
||||||
|
highest_priority[k] = highest_priority[k+1]; |
||||||
|
highest_priority[k+1] = cm->priority; |
||||||
|
highest_i[k] = highest_i[k+1]; |
||||||
|
highest_i[k+1] = i; |
||||||
|
highest_j[k] = highest_j[k+1]; |
||||||
|
highest_j[k+1] = j; |
||||||
|
} else { |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
// I couldn't find any straightforward logic that avoids
|
||||||
|
// the hysteresis problem of continually creating & freeing
|
||||||
|
// a block on the margin, so I just don't free a block until
|
||||||
|
// it's out of range, but this doesn't actually correctly
|
||||||
|
// handle when the cache is too small for the given range
|
||||||
|
if (chunk_storage_total >= min_chunk_storage && lowest_i >= 0) { |
||||||
|
if (cached_chunk_mesh[lowest_j][lowest_i].priority < -1200) // -1000? 0?
|
||||||
|
free_chunk(lowest_i, lowest_j); |
||||||
|
} |
||||||
|
|
||||||
|
if (chunk_storage_total < max_chunk_storage && highest_i[0] >= 0) { |
||||||
|
for (j=MAX_QUEUE-1; j >= 0; --j) { |
||||||
|
if (highest_j[0] >= 0) { |
||||||
|
chunk_mesh *cm = &cached_chunk_mesh[highest_j[j]][highest_i[j]]; |
||||||
|
if (request_chunk(cm->chunk_x, cm->chunk_y)) { |
||||||
|
cm->state = STATE_requested; |
||||||
|
} else { |
||||||
|
// if we couldn't queue this one, skip the remainder
|
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
update_meshes_from_render_thread(); |
||||||
|
|
||||||
|
num_threads_active = 0; |
||||||
|
for (i=0; i < num_mesh_workers; ++i) { |
||||||
|
num_threads_active += (mesh_data[i].state == WSTATE_running); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,157 @@ |
|||||||
|
# Microsoft Developer Studio Project File - Name="caveview" - Package Owner=<4> |
||||||
|
# Microsoft Developer Studio Generated Build File, Format Version 6.00 |
||||||
|
# ** DO NOT EDIT ** |
||||||
|
|
||||||
|
# TARGTYPE "Win32 (x86) Application" 0x0101 |
||||||
|
|
||||||
|
CFG=caveview - Win32 Debug |
||||||
|
!MESSAGE This is not a valid makefile. To build this project using NMAKE, |
||||||
|
!MESSAGE use the Export Makefile command and run |
||||||
|
!MESSAGE |
||||||
|
!MESSAGE NMAKE /f "caveview.mak". |
||||||
|
!MESSAGE |
||||||
|
!MESSAGE You can specify a configuration when running NMAKE |
||||||
|
!MESSAGE by defining the macro CFG on the command line. For example: |
||||||
|
!MESSAGE |
||||||
|
!MESSAGE NMAKE /f "caveview.mak" CFG="caveview - Win32 Debug" |
||||||
|
!MESSAGE |
||||||
|
!MESSAGE Possible choices for configuration are: |
||||||
|
!MESSAGE |
||||||
|
!MESSAGE "caveview - Win32 Release" (based on "Win32 (x86) Application") |
||||||
|
!MESSAGE "caveview - Win32 Debug" (based on "Win32 (x86) Application") |
||||||
|
!MESSAGE |
||||||
|
|
||||||
|
# Begin Project |
||||||
|
# PROP AllowPerConfigDependencies 0 |
||||||
|
# PROP Scc_ProjName "" |
||||||
|
# PROP Scc_LocalPath "" |
||||||
|
CPP=cl.exe |
||||||
|
MTL=midl.exe |
||||||
|
RSC=rc.exe |
||||||
|
|
||||||
|
!IF "$(CFG)" == "caveview - Win32 Release" |
||||||
|
|
||||||
|
# PROP BASE Use_MFC 0 |
||||||
|
# PROP BASE Use_Debug_Libraries 0 |
||||||
|
# PROP BASE Output_Dir "Release" |
||||||
|
# PROP BASE Intermediate_Dir "Release" |
||||||
|
# PROP BASE Target_Dir "" |
||||||
|
# PROP Use_MFC 0 |
||||||
|
# PROP Use_Debug_Libraries 0 |
||||||
|
# PROP Output_Dir "Release" |
||||||
|
# PROP Intermediate_Dir "Release" |
||||||
|
# PROP Ignore_Export_Lib 0 |
||||||
|
# PROP Target_Dir "" |
||||||
|
# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_MBCS" /YX /FD /c |
||||||
|
# ADD CPP /nologo /MD /W3 /WX /GX /Zd /O2 /I "../.." /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_MBCS" /FD /c |
||||||
|
# SUBTRACT CPP /YX |
||||||
|
# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /win32 |
||||||
|
# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32 |
||||||
|
# ADD BASE RSC /l 0x409 /d "NDEBUG" |
||||||
|
# ADD RSC /l 0x409 /d "NDEBUG" |
||||||
|
BSC32=bscmake.exe |
||||||
|
# ADD BASE BSC32 /nologo |
||||||
|
# ADD BSC32 /nologo |
||||||
|
LINK32=link.exe |
||||||
|
# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:windows /machine:I386 |
||||||
|
# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib sdl2.lib opengl32.lib glu32.lib winmm.lib sdl2_mixer.lib advapi32.lib /nologo /subsystem:windows /debug /machine:I386 |
||||||
|
# SUBTRACT LINK32 /map |
||||||
|
|
||||||
|
!ELSEIF "$(CFG)" == "caveview - Win32 Debug" |
||||||
|
|
||||||
|
# PROP BASE Use_MFC 0 |
||||||
|
# PROP BASE Use_Debug_Libraries 1 |
||||||
|
# PROP BASE Output_Dir "Debug" |
||||||
|
# PROP BASE Intermediate_Dir "Debug" |
||||||
|
# PROP BASE Target_Dir "" |
||||||
|
# PROP Use_MFC 0 |
||||||
|
# PROP Use_Debug_Libraries 1 |
||||||
|
# PROP Output_Dir "Debug" |
||||||
|
# PROP Intermediate_Dir "Debug" |
||||||
|
# PROP Ignore_Export_Lib 0 |
||||||
|
# PROP Target_Dir "" |
||||||
|
# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "_MBCS" /YX /FD /GZ /c |
||||||
|
# ADD CPP /nologo /MDd /W3 /WX /Gm /GX /Zi /Od /I "../.." /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "_MBCS" /FD /GZ /c |
||||||
|
# ADD BASE MTL /nologo /D "_DEBUG" /mktyplib203 /win32 |
||||||
|
# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32 |
||||||
|
# ADD BASE RSC /l 0x409 /d "_DEBUG" |
||||||
|
# ADD RSC /l 0x409 /d "_DEBUG" |
||||||
|
BSC32=bscmake.exe |
||||||
|
# ADD BASE BSC32 /nologo |
||||||
|
# ADD BSC32 /nologo |
||||||
|
LINK32=link.exe |
||||||
|
# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:windows /debug /machine:I386 /pdbtype:sept |
||||||
|
# ADD LINK32 kernel32.lib user32.lib gdi32.lib advapi32.lib winspool.lib comdlg32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib sdl2.lib opengl32.lib glu32.lib winmm.lib sdl2_mixer.lib /nologo /subsystem:windows /incremental:no /debug /machine:I386 /pdbtype:sept |
||||||
|
|
||||||
|
!ENDIF |
||||||
|
|
||||||
|
# Begin Target |
||||||
|
|
||||||
|
# Name "caveview - Win32 Release" |
||||||
|
# Name "caveview - Win32 Debug" |
||||||
|
# Begin Source File |
||||||
|
|
||||||
|
SOURCE=.\cave_main.c |
||||||
|
# End Source File |
||||||
|
# Begin Source File |
||||||
|
|
||||||
|
SOURCE=.\cave_mesher.c |
||||||
|
# End Source File |
||||||
|
# Begin Source File |
||||||
|
|
||||||
|
SOURCE=.\cave_parse.c |
||||||
|
# End Source File |
||||||
|
# Begin Source File |
||||||
|
|
||||||
|
SOURCE=.\cave_parse.h |
||||||
|
# End Source File |
||||||
|
# Begin Source File |
||||||
|
|
||||||
|
SOURCE=.\cave_render.c |
||||||
|
# End Source File |
||||||
|
# Begin Source File |
||||||
|
|
||||||
|
SOURCE=.\caveview.h |
||||||
|
# End Source File |
||||||
|
# Begin Source File |
||||||
|
|
||||||
|
SOURCE=.\glext.h |
||||||
|
# End Source File |
||||||
|
# Begin Source File |
||||||
|
|
||||||
|
SOURCE=.\glext_list.h |
||||||
|
# End Source File |
||||||
|
# Begin Source File |
||||||
|
|
||||||
|
SOURCE=.\README.md |
||||||
|
# End Source File |
||||||
|
# Begin Source File |
||||||
|
|
||||||
|
SOURCE=.\win32\SDL_windows_main.c |
||||||
|
# End Source File |
||||||
|
# Begin Source File |
||||||
|
|
||||||
|
SOURCE=..\..\stb.h |
||||||
|
# End Source File |
||||||
|
# Begin Source File |
||||||
|
|
||||||
|
SOURCE=..\..\stb_easy_font.h |
||||||
|
# End Source File |
||||||
|
# Begin Source File |
||||||
|
|
||||||
|
SOURCE=.\stb_gl.h |
||||||
|
# End Source File |
||||||
|
# Begin Source File |
||||||
|
|
||||||
|
SOURCE=.\stb_glprog.h |
||||||
|
# End Source File |
||||||
|
# Begin Source File |
||||||
|
|
||||||
|
SOURCE=..\..\stb_image.h |
||||||
|
# End Source File |
||||||
|
# Begin Source File |
||||||
|
|
||||||
|
SOURCE=..\..\stb_voxel_render.h |
||||||
|
# End Source File |
||||||
|
# End Target |
||||||
|
# End Project |
@ -0,0 +1,29 @@ |
|||||||
|
Microsoft Developer Studio Workspace File, Format Version 6.00 |
||||||
|
# WARNING: DO NOT EDIT OR DELETE THIS WORKSPACE FILE! |
||||||
|
|
||||||
|
############################################################################### |
||||||
|
|
||||||
|
Project: "caveview"=.\caveview.dsp - Package Owner=<4> |
||||||
|
|
||||||
|
Package=<5> |
||||||
|
{{{ |
||||||
|
}}} |
||||||
|
|
||||||
|
Package=<4> |
||||||
|
{{{ |
||||||
|
}}} |
||||||
|
|
||||||
|
############################################################################### |
||||||
|
|
||||||
|
Global: |
||||||
|
|
||||||
|
Package=<5> |
||||||
|
{{{ |
||||||
|
}}} |
||||||
|
|
||||||
|
Package=<3> |
||||||
|
{{{ |
||||||
|
}}} |
||||||
|
|
||||||
|
############################################################################### |
||||||
|
|
@ -0,0 +1,51 @@ |
|||||||
|
#ifndef INCLUDE_CAVEVIEW_H |
||||||
|
#define INCLUDE_CAVEVIEW_H |
||||||
|
|
||||||
|
#include "stb.h" |
||||||
|
|
||||||
|
#define STBVOX_ROTATION_IN_LIGHTING |
||||||
|
#include "stb_voxel_render.h" |
||||||
|
|
||||||
|
typedef struct |
||||||
|
{ |
||||||
|
int cx,cy; |
||||||
|
|
||||||
|
stbvox_mesh_maker mm; |
||||||
|
|
||||||
|
uint8 *build_buffer; |
||||||
|
uint8 *face_buffer; |
||||||
|
|
||||||
|
int num_quads; |
||||||
|
float transform[3][3]; |
||||||
|
float bounds[2][3]; |
||||||
|
|
||||||
|
uint8 sv_blocktype[34][34][18]; |
||||||
|
uint8 sv_lighting [34][34][18]; |
||||||
|
} raw_mesh; |
||||||
|
|
||||||
|
// a 3D checkerboard of empty,solid would be: 32x32x255x6/2 ~= 800000
|
||||||
|
// an all-leaf qchunk would be: 32 x 32 x 255 x 6 ~= 1,600,000
|
||||||
|
|
||||||
|
#define BUILD_QUAD_MAX 400000 |
||||||
|
#define BUILD_BUFFER_SIZE (4*4*BUILD_QUAD_MAX) // 4 bytes per vertex, 4 vertices per quad
|
||||||
|
#define FACE_BUFFER_SIZE ( 4*BUILD_QUAD_MAX) // 4 bytes per quad
|
||||||
|
|
||||||
|
|
||||||
|
extern void mesh_init(void); |
||||||
|
extern void render_init(void); |
||||||
|
extern void world_init(void); |
||||||
|
extern void ods(char *fmt, ...); // output debug string
|
||||||
|
extern void reset_cache_size(int size); |
||||||
|
|
||||||
|
|
||||||
|
extern void render_caves(float pos[3]); |
||||||
|
|
||||||
|
|
||||||
|
#include "cave_parse.h" // fast_chunk |
||||||
|
|
||||||
|
extern fast_chunk *get_converted_fastchunk(int chunk_x, int chunk_y); |
||||||
|
extern void build_chunk(int chunk_x, int chunk_y, fast_chunk *fc_table[4][4], raw_mesh *rm); |
||||||
|
extern void reset_cache_size(int size); |
||||||
|
extern void deref_fastchunk(fast_chunk *fc); |
||||||
|
|
||||||
|
#endif |
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,34 @@ |
|||||||
|
GLARB(ActiveTexture,ACTIVETEXTURE) |
||||||
|
GLARB(ClientActiveTexture,CLIENTACTIVETEXTURE) |
||||||
|
GLARB(MultiTexCoord2f,MULTITEXCOORD2F) |
||||||
|
GLEXT(TexImage3D,TEXIMAGE3D) |
||||||
|
GLEXT(TexSubImage3D,TEXSUBIMAGE3D) |
||||||
|
GLEXT(GenerateMipmap,GENERATEMIPMAP) |
||||||
|
GLARB(DebugMessageCallback,DEBUGMESSAGECALLBACK) |
||||||
|
|
||||||
|
GLCORE(VertexAttribIPointer,VERTEXATTRIBIPOINTER) |
||||||
|
|
||||||
|
GLEXT(BindFramebuffer,BINDFRAMEBUFFER) |
||||||
|
GLEXT(DeleteFramebuffers,DELETEFRAMEBUFFERS) |
||||||
|
GLEXT(GenFramebuffers,GENFRAMEBUFFERS) |
||||||
|
GLEXT(CheckFramebufferStatus,CHECKFRAMEBUFFERSTATUS) |
||||||
|
GLEXT(FramebufferTexture2D,FRAMEBUFFERTEXTURE2D) |
||||||
|
GLEXT(BindRenderBuffer,BINDRENDERBUFFER) |
||||||
|
GLEXT(RenderbufferStorage,RENDERBUFFERSTORAGE) |
||||||
|
GLEXT(GenRenderbuffers,GENRENDERBUFFERS) |
||||||
|
GLEXT(BindRenderbuffer,BINDRENDERBUFFER) |
||||||
|
GLEXT(FramebufferRenderbuffer,FRAMEBUFFERRENDERBUFFER) |
||||||
|
GLEXT(GenerateMipmap,GENERATEMIPMAP) |
||||||
|
|
||||||
|
GLARB(BindBuffer ,BINDBUFFER,) |
||||||
|
GLARB(GenBuffers ,GENBUFFERS ) |
||||||
|
GLARB(DeleteBuffers,DELETEBUFFERS) |
||||||
|
GLARB(BufferData ,BUFFERDATA ) |
||||||
|
GLARB(BufferSubData,BUFFERSUBDATA) |
||||||
|
GLARB(MapBuffer ,MAPBUFFER ) |
||||||
|
GLARB(UnmapBuffer ,UNMAPBUFFER ) |
||||||
|
GLARB(TexBuffer ,TEXBUFFER ) |
||||||
|
|
||||||
|
GLEXT(NamedBufferStorage,NAMEDBUFFERSTORAGE) |
||||||
|
GLE(BufferStorage,BUFFERSTORAGE) |
||||||
|
GLE(GetStringi,GETSTRINGI) |
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,504 @@ |
|||||||
|
// stb_glprog v0.02 public domain functions to reduce GLSL boilerplate
|
||||||
|
// http://nothings.org/stb/stb_glprog.h especially with GL1 + ARB extensions
|
||||||
|
//
|
||||||
|
// Following defines *before* including have following effects:
|
||||||
|
//
|
||||||
|
// STB_GLPROG_IMPLEMENTATION
|
||||||
|
// creates the implementation
|
||||||
|
//
|
||||||
|
// STB_GLPROG_STATIC
|
||||||
|
// forces the implementation to be static (private to file that creates it)
|
||||||
|
//
|
||||||
|
// STB_GLPROG_ARB
|
||||||
|
// uses ARB extension names for GLSL functions and enumerants instead of core names
|
||||||
|
//
|
||||||
|
// STB_GLPROG_ARB_DEFINE_EXTENSIONS
|
||||||
|
// instantiates function pointers needed, static to implementing file
|
||||||
|
// to avoid collisions (but will collide if implementing file also
|
||||||
|
// defines any; best to isolate this to its own file in this case).
|
||||||
|
// This will try to automatically #include glext.h, but if it's not
|
||||||
|
// in the default include directories you'll need to include it
|
||||||
|
// yourself and define the next macro.
|
||||||
|
//
|
||||||
|
// STB_GLPROG_SUPPRESS_GLEXT_INCLUDE
|
||||||
|
// disables the automatic #include of glext.h which is normally
|
||||||
|
// forced by STB_GLPROG_ARB_DEFINE_EXTENSIONS
|
||||||
|
//
|
||||||
|
// So, e.g., sample usage on an old Windows compiler:
|
||||||
|
//
|
||||||
|
// #define STB_GLPROG_IMPLEMENTATION
|
||||||
|
// #define STB_GLPROG_ARB_DEFINE_EXTENSIONS
|
||||||
|
// #include <windows.h>
|
||||||
|
// #include "gl/gl.h"
|
||||||
|
// #include "stb_glprog.h"
|
||||||
|
//
|
||||||
|
// Note though that the header-file version of this (when you don't define
|
||||||
|
// STB_GLPROG_IMPLEMENTATION) still uses GLint and such, so you basically
|
||||||
|
// can only include it in places where you're already including GL, especially
|
||||||
|
// on Windows where including "gl.h" requires (some of) "windows.h".
|
||||||
|
//
|
||||||
|
// See following comment blocks for function documentation.
|
||||||
|
//
|
||||||
|
// Version history:
|
||||||
|
// 2013-12-08 v0.02 slightly simplified API and reduced GL resource usage (@rygorous)
|
||||||
|
// 2013-12-08 v0.01 initial release
|
||||||
|
|
||||||
|
|
||||||
|
// header file section starts here
|
||||||
|
#if !defined(INCLUDE_STB_GLPROG_H) |
||||||
|
#define INCLUDE_STB_GLPROG_H |
||||||
|
|
||||||
|
#ifndef STB_GLPROG_STATIC |
||||||
|
#ifdef __cplusplus |
||||||
|
extern "C" { |
||||||
|
#endif |
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
///////////// SHADER CREATION
|
||||||
|
|
||||||
|
|
||||||
|
/// EASY API
|
||||||
|
|
||||||
|
extern GLuint stbgl_create_program(char const **vertex_source, char const **frag_source, char const **binds, char *error, int error_buflen); |
||||||
|
// This function returns a compiled program or 0 if there's an error.
|
||||||
|
// To free the created program, call stbgl_delete_program.
|
||||||
|
//
|
||||||
|
// stbgl_create_program(
|
||||||
|
// char **vertex_source, // NULL or one or more strings with the vertex shader source, with a final NULL
|
||||||
|
// char **frag_source, // NULL or one or more strings with the fragment shader source, with a final NULL
|
||||||
|
// char **binds, // NULL or zero or more strings with attribute bind names, with a final NULL
|
||||||
|
// char *error, // output location where compile error message is placed
|
||||||
|
// int error_buflen) // length of error output buffer
|
||||||
|
//
|
||||||
|
// Returns a GLuint with the GL program object handle.
|
||||||
|
//
|
||||||
|
// If an individual bind string is "", no name is bound to that slot (this
|
||||||
|
// allows you to create binds that aren't continuous integers starting at 0).
|
||||||
|
//
|
||||||
|
// If the vertex shader is NULL, then fixed-function vertex pipeline
|
||||||
|
// is used, if that's legal in your version of GL.
|
||||||
|
//
|
||||||
|
// If the fragment shader is NULL, then fixed-function fragment pipeline
|
||||||
|
// is used, if that's legal in your version of GL.
|
||||||
|
|
||||||
|
extern void stgbl_delete_program(GLuint program); |
||||||
|
// deletes a program created by stbgl_create_program or stbgl_link_program
|
||||||
|
|
||||||
|
|
||||||
|
/// FLEXIBLE API
|
||||||
|
|
||||||
|
extern GLuint stbgl_compile_shader(GLenum type, char const **sources, int num_sources, char *error, int error_buflen); |
||||||
|
// compiles a shader. returns the shader on success or 0 on failure.
|
||||||
|
//
|
||||||
|
// type either: GL_VERTEX_SHADER or GL_FRAGMENT_SHADER
|
||||||
|
// or GL_VERTEX_SHADER_ARB or GL_FRAGMENT_SHADER_ARB
|
||||||
|
// or STBGL_VERTEX_SHADER or STBGL_FRAGMENT_SHADER
|
||||||
|
// sources array of strings containing the shader source
|
||||||
|
// num_sources number of string in sources, or -1 meaning sources is NULL-terminated
|
||||||
|
// error string to output compiler error to
|
||||||
|
// error_buflen length of error buffer in chars
|
||||||
|
|
||||||
|
extern GLuint stbgl_link_program(GLuint vertex_shader, GLuint fragment_shader, char const **binds, int num_binds, char *error, int error_buflen); |
||||||
|
// links a shader. returns the linked program on success or 0 on failure.
|
||||||
|
//
|
||||||
|
// vertex_shader a compiled vertex shader from stbgl_compile_shader, or 0 for fixed-function (if legal)
|
||||||
|
// fragment_shader a compiled fragment shader from stbgl_compile_shader, or 0 for fixed-function (if legal)
|
||||||
|
//
|
||||||
|
|
||||||
|
extern void stbgl_delete_shader(GLuint shader); |
||||||
|
// deletes a shader created by stbgl_compile_shader
|
||||||
|
|
||||||
|
|
||||||
|
///////////// RENDERING WITH SHADERS
|
||||||
|
|
||||||
|
extern GLint stbgl_find_uniform(GLuint prog, char *uniform); |
||||||
|
|
||||||
|
extern void stbgl_find_uniforms(GLuint prog, GLint *locations, char const **uniforms, int num_uniforms); |
||||||
|
// Given the locations array that is num_uniforms long, fills out
|
||||||
|
// the locations of each of those uniforms for the specified program.
|
||||||
|
// If num_uniforms is -1, then uniforms[] must be NULL-terminated
|
||||||
|
|
||||||
|
// the following functions just wrap the difference in naming between GL2+ and ARB,
|
||||||
|
// so you don't need them unless you're using both ARB and GL2+ in the same codebase,
|
||||||
|
// or you're relying on this lib to provide the extensions
|
||||||
|
extern void stbglUseProgram(GLuint program); |
||||||
|
extern void stbglVertexAttribPointer(GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const GLvoid * pointer); |
||||||
|
extern void stbglEnableVertexAttribArray(GLuint index); |
||||||
|
extern void stbglDisableVertexAttribArray(GLuint index); |
||||||
|
extern void stbglUniform1fv(GLint loc, GLsizei count, const GLfloat *v); |
||||||
|
extern void stbglUniform2fv(GLint loc, GLsizei count, const GLfloat *v); |
||||||
|
extern void stbglUniform3fv(GLint loc, GLsizei count, const GLfloat *v); |
||||||
|
extern void stbglUniform4fv(GLint loc, GLsizei count, const GLfloat *v); |
||||||
|
extern void stbglUniform1iv(GLint loc, GLsizei count, const GLint *v); |
||||||
|
extern void stbglUniform2iv(GLint loc, GLsizei count, const GLint *v); |
||||||
|
extern void stbglUniform3iv(GLint loc, GLsizei count, const GLint *v); |
||||||
|
extern void stbglUniform4iv(GLint loc, GLsizei count, const GLint *v); |
||||||
|
extern void stbglUniform1f(GLint loc, float v0); |
||||||
|
extern void stbglUniform2f(GLint loc, float v0, float v1); |
||||||
|
extern void stbglUniform3f(GLint loc, float v0, float v1, float v2); |
||||||
|
extern void stbglUniform4f(GLint loc, float v0, float v1, float v2, float v3); |
||||||
|
extern void stbglUniform1i(GLint loc, GLint v0); |
||||||
|
extern void stbglUniform2i(GLint loc, GLint v0, GLint v1); |
||||||
|
extern void stbglUniform3i(GLint loc, GLint v0, GLint v1, GLint v2); |
||||||
|
extern void stbglUniform4i(GLint loc, GLint v0, GLint v1, GLint v2, GLint v3); |
||||||
|
|
||||||
|
|
||||||
|
////////////// END OF FUNCTIONS
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
#ifdef __cplusplus |
||||||
|
} |
||||||
|
#endif |
||||||
|
#endif // STB_GLPROG_STATIC
|
||||||
|
|
||||||
|
#ifdef STB_GLPROG_ARB |
||||||
|
#define STBGL_VERTEX_SHADER GL_VERTEX_SHADER_ARB |
||||||
|
#define STBGL_FRAGMENT_SHADER GL_FRAGMENT_SHADER_ARB |
||||||
|
#else |
||||||
|
#define STBGL_VERTEX_SHADER GL_VERTEX_SHADER |
||||||
|
#define STBGL_FRAGMENT_SHADER GL_FRAGMENT_SHADER |
||||||
|
#endif |
||||||
|
|
||||||
|
#endif // INCLUDE_STB_GLPROG_H
|
||||||
|
|
||||||
|
|
||||||
|
///////// header file section ends here
|
||||||
|
|
||||||
|
|
||||||
|
#ifdef STB_GLPROG_IMPLEMENTATION |
||||||
|
#include <string.h> // strncpy |
||||||
|
|
||||||
|
#ifdef STB_GLPROG_STATIC |
||||||
|
#define STB_GLPROG_DECLARE static |
||||||
|
#else |
||||||
|
#define STB_GLPROG_DECLARE extern |
||||||
|
#endif |
||||||
|
|
||||||
|
// check if user wants this file to define the GL extensions itself
|
||||||
|
#ifdef STB_GLPROG_ARB_DEFINE_EXTENSIONS |
||||||
|
#define STB_GLPROG_ARB // make sure later code uses the extensions
|
||||||
|
|
||||||
|
#ifndef STB_GLPROG_SUPPRESS_GLEXT_INCLUDE |
||||||
|
#include "glext.h" |
||||||
|
#endif |
||||||
|
|
||||||
|
#define STB_GLPROG_EXTENSIONS \ |
||||||
|
STB_GLPROG_FUNC(ATTACHOBJECT , AttachObject ) \
|
||||||
|
STB_GLPROG_FUNC(BINDATTRIBLOCATION , BindAttribLocation ) \
|
||||||
|
STB_GLPROG_FUNC(COMPILESHADER , CompileShader ) \
|
||||||
|
STB_GLPROG_FUNC(CREATEPROGRAMOBJECT , CreateProgramObject ) \
|
||||||
|
STB_GLPROG_FUNC(CREATESHADEROBJECT , CreateShaderObject ) \
|
||||||
|
STB_GLPROG_FUNC(DELETEOBJECT , DeleteObject ) \
|
||||||
|
STB_GLPROG_FUNC(DETACHOBJECT , DetachObject ) \
|
||||||
|
STB_GLPROG_FUNC(DISABLEVERTEXATTRIBARRAY, DisableVertexAttribArray) \
|
||||||
|
STB_GLPROG_FUNC(ENABLEVERTEXATTRIBARRAY, EnableVertexAttribArray ) \
|
||||||
|
STB_GLPROG_FUNC(GETATTACHEDOBJECTS , GetAttachedObjects ) \
|
||||||
|
STB_GLPROG_FUNC(GETOBJECTPARAMETERIV, GetObjectParameteriv) \
|
||||||
|
STB_GLPROG_FUNC(GETINFOLOG , GetInfoLog ) \
|
||||||
|
STB_GLPROG_FUNC(GETUNIFORMLOCATION , GetUniformLocation ) \
|
||||||
|
STB_GLPROG_FUNC(LINKPROGRAM , LinkProgram ) \
|
||||||
|
STB_GLPROG_FUNC(SHADERSOURCE , ShaderSource ) \
|
||||||
|
STB_GLPROG_FUNC(UNIFORM1F , Uniform1f ) \
|
||||||
|
STB_GLPROG_FUNC(UNIFORM2F , Uniform2f ) \
|
||||||
|
STB_GLPROG_FUNC(UNIFORM3F , Uniform3f ) \
|
||||||
|
STB_GLPROG_FUNC(UNIFORM4F , Uniform4f ) \
|
||||||
|
STB_GLPROG_FUNC(UNIFORM1I , Uniform1i ) \
|
||||||
|
STB_GLPROG_FUNC(UNIFORM2I , Uniform2i ) \
|
||||||
|
STB_GLPROG_FUNC(UNIFORM3I , Uniform3i ) \
|
||||||
|
STB_GLPROG_FUNC(UNIFORM4I , Uniform4i ) \
|
||||||
|
STB_GLPROG_FUNC(UNIFORM1FV , Uniform1fv ) \
|
||||||
|
STB_GLPROG_FUNC(UNIFORM2FV , Uniform2fv ) \
|
||||||
|
STB_GLPROG_FUNC(UNIFORM3FV , Uniform3fv ) \
|
||||||
|
STB_GLPROG_FUNC(UNIFORM4FV , Uniform4fv ) \
|
||||||
|
STB_GLPROG_FUNC(UNIFORM1IV , Uniform1iv ) \
|
||||||
|
STB_GLPROG_FUNC(UNIFORM2IV , Uniform2iv ) \
|
||||||
|
STB_GLPROG_FUNC(UNIFORM3IV , Uniform3iv ) \
|
||||||
|
STB_GLPROG_FUNC(UNIFORM4IV , Uniform4iv ) \
|
||||||
|
STB_GLPROG_FUNC(USEPROGRAMOBJECT , UseProgramObject ) \
|
||||||
|
STB_GLPROG_FUNC(VERTEXATTRIBPOINTER , VertexAttribPointer ) |
||||||
|
|
||||||
|
// define the static function pointers
|
||||||
|
|
||||||
|
#define STB_GLPROG_FUNC(x,y) static PFNGL##x##ARBPROC gl##y##ARB; |
||||||
|
STB_GLPROG_EXTENSIONS |
||||||
|
#undef STB_GLPROG_FUNC |
||||||
|
|
||||||
|
// define the GetProcAddress
|
||||||
|
|
||||||
|
#ifdef _WIN32 |
||||||
|
#ifndef WINGDIAPI |
||||||
|
#ifndef STB__HAS_WGLPROC |
||||||
|
typedef int (__stdcall *stbgl__voidfunc)(void); |
||||||
|
static __declspec(dllimport) stbgl__voidfunc wglGetProcAddress(char *); |
||||||
|
#endif |
||||||
|
#endif |
||||||
|
#define STBGL__GET_FUNC(x) wglGetProcAddress(x) |
||||||
|
#else |
||||||
|
#error "need to define how this platform gets extensions" |
||||||
|
#endif |
||||||
|
|
||||||
|
// create a function that fills out the function pointers
|
||||||
|
|
||||||
|
static void stb_glprog_init(void) |
||||||
|
{ |
||||||
|
static int initialized = 0; // not thread safe!
|
||||||
|
if (initialized) return; |
||||||
|
#define STB_GLPROG_FUNC(x,y) gl##y##ARB = (PFNGL##x##ARBPROC) STBGL__GET_FUNC("gl" #y "ARB"); |
||||||
|
STB_GLPROG_EXTENSIONS |
||||||
|
#undef STB_GLPROG_FUNC |
||||||
|
} |
||||||
|
#undef STB_GLPROG_EXTENSIONS |
||||||
|
|
||||||
|
#else |
||||||
|
static void stb_glprog_init(void) |
||||||
|
{ |
||||||
|
} |
||||||
|
#endif |
||||||
|
|
||||||
|
|
||||||
|
// define generic names for many of the gl functions or extensions for later use;
|
||||||
|
// note that in some cases there are two functions in core and one function in ARB
|
||||||
|
#ifdef STB_GLPROG_ARB |
||||||
|
#define stbglCreateShader glCreateShaderObjectARB |
||||||
|
#define stbglDeleteShader glDeleteObjectARB |
||||||
|
#define stbglAttachShader glAttachObjectARB |
||||||
|
#define stbglDetachShader glDetachObjectARB |
||||||
|
#define stbglShaderSource glShaderSourceARB |
||||||
|
#define stbglCompileShader glCompileShaderARB |
||||||
|
#define stbglGetShaderStatus(a,b) glGetObjectParameterivARB(a, GL_OBJECT_COMPILE_STATUS_ARB, b) |
||||||
|
#define stbglGetShaderInfoLog glGetInfoLogARB |
||||||
|
#define stbglCreateProgram glCreateProgramObjectARB |
||||||
|
#define stbglDeleteProgram glDeleteObjectARB |
||||||
|
#define stbglLinkProgram glLinkProgramARB |
||||||
|
#define stbglGetProgramStatus(a,b) glGetObjectParameterivARB(a, GL_OBJECT_LINK_STATUS_ARB, b) |
||||||
|
#define stbglGetProgramInfoLog glGetInfoLogARB |
||||||
|
#define stbglGetAttachedShaders glGetAttachedObjectsARB |
||||||
|
#define stbglBindAttribLocation glBindAttribLocationARB |
||||||
|
#define stbglGetUniformLocation glGetUniformLocationARB |
||||||
|
#define stbgl_UseProgram glUseProgramObjectARB |
||||||
|
#else |
||||||
|
#define stbglCreateShader glCreateShader |
||||||
|
#define stbglDeleteShader glDeleteShader |
||||||
|
#define stbglAttachShader glAttachShader |
||||||
|
#define stbglDetachShader glDetachShader |
||||||
|
#define stbglShaderSource glShaderSource |
||||||
|
#define stbglCompileShader glCompileShader |
||||||
|
#define stbglGetShaderStatus(a,b) glGetShaderiv(a, GL_COMPILE_STATUS, b) |
||||||
|
#define stbglGetShaderInfoLog glGetShaderInfoLog |
||||||
|
#define stbglCreateProgram glCreateProgram |
||||||
|
#define stbglDeleteProgram glDeleteProgram |
||||||
|
#define stbglLinkProgram glLinkProgram |
||||||
|
#define stbglGetProgramStatus(a,b) glGetProgramiv(a, GL_LINK_STATUS, b) |
||||||
|
#define stbglGetProgramInfoLog glGetProgramInfoLog |
||||||
|
#define stbglGetAttachedShaders glGetAttachedShaders |
||||||
|
#define stbglBindAttribLocation glBindAttribLocation |
||||||
|
#define stbglGetUniformLocation glGetUniformLocation |
||||||
|
#define stbgl_UseProgram glUseProgram |
||||||
|
#endif |
||||||
|
|
||||||
|
|
||||||
|
// perform a safe strcat of 3 strings, given that we can't rely on portable snprintf
|
||||||
|
// if you need to break on error, this is the best place to place a breakpoint
|
||||||
|
static void stb_glprog_error(char *error, int error_buflen, char *str1, char *str2, char *str3) |
||||||
|
{ |
||||||
|
int n = strlen(str1); |
||||||
|
strncpy(error, str1, error_buflen); |
||||||
|
if (n < error_buflen && str2) { |
||||||
|
strncpy(error+n, str2, error_buflen - n); |
||||||
|
n += strlen(str2); |
||||||
|
if (n < error_buflen && str3) { |
||||||
|
strncpy(error+n, str3, error_buflen - n); |
||||||
|
} |
||||||
|
} |
||||||
|
error[error_buflen-1] = 0; |
||||||
|
} |
||||||
|
|
||||||
|
STB_GLPROG_DECLARE GLuint stbgl_compile_shader(GLenum type, char const **sources, int num_sources, char *error, int error_buflen) |
||||||
|
{ |
||||||
|
char *typename = (type == STBGL_VERTEX_SHADER ? "vertex" : "fragment"); |
||||||
|
int len; |
||||||
|
GLint result; |
||||||
|
GLuint shader; |
||||||
|
|
||||||
|
// initialize the extensions if we haven't already
|
||||||
|
stb_glprog_init(); |
||||||
|
|
||||||
|
// allocate
|
||||||
|
|
||||||
|
shader = stbglCreateShader(type); |
||||||
|
if (!shader) { |
||||||
|
stb_glprog_error(error, error_buflen, "Couldn't allocate shader object in stbgl_compile_shader for ", typename, NULL); |
||||||
|
return 0; |
||||||
|
} |
||||||
|
|
||||||
|
// compile
|
||||||
|
|
||||||
|
// if num_sources is negative, assume source is NULL-terminated and count the non-NULL ones
|
||||||
|
if (num_sources < 0) |
||||||
|
for (num_sources = 0; sources[num_sources] != NULL; ++num_sources) |
||||||
|
; |
||||||
|
stbglShaderSource(shader, num_sources, sources, NULL); |
||||||
|
stbglCompileShader(shader); |
||||||
|
stbglGetShaderStatus(shader, &result); |
||||||
|
if (result) |
||||||
|
return shader; |
||||||
|
|
||||||
|
// errors
|
||||||
|
|
||||||
|
stb_glprog_error(error, error_buflen, "Compile error for ", typename, " shader: "); |
||||||
|
len = strlen(error); |
||||||
|
if (len < error_buflen) |
||||||
|
stbglGetShaderInfoLog(shader, error_buflen-len, NULL, error+len); |
||||||
|
|
||||||
|
stbglDeleteShader(shader); |
||||||
|
return 0; |
||||||
|
} |
||||||
|
|
||||||
|
STB_GLPROG_DECLARE GLuint stbgl_link_program(GLuint vertex_shader, GLuint fragment_shader, char const **binds, int num_binds, char *error, int error_buflen) |
||||||
|
{ |
||||||
|
int len; |
||||||
|
GLint result; |
||||||
|
|
||||||
|
// allocate
|
||||||
|
|
||||||
|
GLuint prog = stbglCreateProgram(); |
||||||
|
if (!prog) { |
||||||
|
stb_glprog_error(error, error_buflen, "Couldn't allocate program object in stbgl_link_program", NULL, NULL); |
||||||
|
return 0; |
||||||
|
} |
||||||
|
|
||||||
|
// attach
|
||||||
|
|
||||||
|
if (vertex_shader) |
||||||
|
stbglAttachShader(prog, vertex_shader); |
||||||
|
if (fragment_shader) |
||||||
|
stbglAttachShader(prog, fragment_shader); |
||||||
|
|
||||||
|
// attribute binds
|
||||||
|
|
||||||
|
if (binds) { |
||||||
|
int i; |
||||||
|
// if num_binds is negative, then it is NULL terminated
|
||||||
|
if (num_binds < 0) |
||||||
|
for (num_binds=0; binds[num_binds]; ++num_binds) |
||||||
|
; |
||||||
|
for (i=0; i < num_binds; ++i) |
||||||
|
if (binds[i] && binds[i][0]) // empty binds can be NULL or ""
|
||||||
|
stbglBindAttribLocation(prog, i, binds[i]); |
||||||
|
} |
||||||
|
|
||||||
|
// link
|
||||||
|
|
||||||
|
stbglLinkProgram(prog); |
||||||
|
|
||||||
|
// detach
|
||||||
|
|
||||||
|
if (vertex_shader) |
||||||
|
stbglDetachShader(prog, vertex_shader); |
||||||
|
if (fragment_shader) |
||||||
|
stbglDetachShader(prog, fragment_shader); |
||||||
|
|
||||||
|
// errors
|
||||||
|
|
||||||
|
stbglGetProgramStatus(prog, &result); |
||||||
|
if (result) |
||||||
|
return prog; |
||||||
|
|
||||||
|
stb_glprog_error(error, error_buflen, "Link error: ", NULL, NULL); |
||||||
|
len = strlen(error); |
||||||
|
if (len < error_buflen) |
||||||
|
stbglGetProgramInfoLog(prog, error_buflen-len, NULL, error+len); |
||||||
|
|
||||||
|
stbglDeleteProgram(prog); |
||||||
|
return 0;
|
||||||
|
} |
||||||
|
|
||||||
|
STB_GLPROG_DECLARE GLuint stbgl_create_program(char const **vertex_source, char const **frag_source, char const **binds, char *error, int error_buflen) |
||||||
|
{ |
||||||
|
GLuint vertex, fragment, prog=0; |
||||||
|
vertex = stbgl_compile_shader(STBGL_VERTEX_SHADER, vertex_source, -1, error, error_buflen); |
||||||
|
if (vertex) { |
||||||
|
fragment = stbgl_compile_shader(STBGL_FRAGMENT_SHADER, frag_source, -1, error, error_buflen); |
||||||
|
if (fragment) |
||||||
|
prog = stbgl_link_program(vertex, fragment, binds, -1, error, error_buflen); |
||||||
|
if (fragment) |
||||||
|
stbglDeleteShader(fragment); |
||||||
|
stbglDeleteShader(vertex); |
||||||
|
} |
||||||
|
return prog; |
||||||
|
} |
||||||
|
|
||||||
|
STB_GLPROG_DECLARE void stbgl_delete_shader(GLuint shader) |
||||||
|
{ |
||||||
|
stbglDeleteShader(shader); |
||||||
|
} |
||||||
|
|
||||||
|
STB_GLPROG_DECLARE void stgbl_delete_program(GLuint program) |
||||||
|
{ |
||||||
|
stbglDeleteProgram(program); |
||||||
|
} |
||||||
|
|
||||||
|
GLint stbgl_find_uniform(GLuint prog, char *uniform) |
||||||
|
{ |
||||||
|
return stbglGetUniformLocation(prog, uniform); |
||||||
|
} |
||||||
|
|
||||||
|
STB_GLPROG_DECLARE void stbgl_find_uniforms(GLuint prog, GLint *locations, char const **uniforms, int num_uniforms) |
||||||
|
{ |
||||||
|
int i; |
||||||
|
if (num_uniforms < 0) |
||||||
|
num_uniforms = 999999; |
||||||
|
for (i=0; i < num_uniforms && uniforms[i]; ++i) |
||||||
|
locations[i] = stbglGetUniformLocation(prog, uniforms[i]); |
||||||
|
} |
||||||
|
|
||||||
|
STB_GLPROG_DECLARE void stbglUseProgram(GLuint program) |
||||||
|
{ |
||||||
|
stbgl_UseProgram(program); |
||||||
|
} |
||||||
|
|
||||||
|
#ifdef STB_GLPROG_ARB |
||||||
|
#define STBGL_ARBIFY(name) name##ARB |
||||||
|
#else |
||||||
|
#define STBGL_ARBIFY(name) name |
||||||
|
#endif |
||||||
|
|
||||||
|
STB_GLPROG_DECLARE void stbglVertexAttribPointer(GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const GLvoid * pointer) |
||||||
|
{ |
||||||
|
STBGL_ARBIFY(glVertexAttribPointer)(index, size, type, normalized, stride, pointer); |
||||||
|
} |
||||||
|
|
||||||
|
STB_GLPROG_DECLARE void stbglEnableVertexAttribArray (GLuint index) { STBGL_ARBIFY(glEnableVertexAttribArray )(index); } |
||||||
|
STB_GLPROG_DECLARE void stbglDisableVertexAttribArray(GLuint index) { STBGL_ARBIFY(glDisableVertexAttribArray)(index); } |
||||||
|
|
||||||
|
STB_GLPROG_DECLARE void stbglUniform1fv(GLint loc, GLsizei count, const GLfloat *v) { STBGL_ARBIFY(glUniform1fv)(loc,count,v); } |
||||||
|
STB_GLPROG_DECLARE void stbglUniform2fv(GLint loc, GLsizei count, const GLfloat *v) { STBGL_ARBIFY(glUniform2fv)(loc,count,v); } |
||||||
|
STB_GLPROG_DECLARE void stbglUniform3fv(GLint loc, GLsizei count, const GLfloat *v) { STBGL_ARBIFY(glUniform3fv)(loc,count,v); } |
||||||
|
STB_GLPROG_DECLARE void stbglUniform4fv(GLint loc, GLsizei count, const GLfloat *v) { STBGL_ARBIFY(glUniform4fv)(loc,count,v); } |
||||||
|
|
||||||
|
STB_GLPROG_DECLARE void stbglUniform1iv(GLint loc, GLsizei count, const GLint *v) { STBGL_ARBIFY(glUniform1iv)(loc,count,v); } |
||||||
|
STB_GLPROG_DECLARE void stbglUniform2iv(GLint loc, GLsizei count, const GLint *v) { STBGL_ARBIFY(glUniform2iv)(loc,count,v); } |
||||||
|
STB_GLPROG_DECLARE void stbglUniform3iv(GLint loc, GLsizei count, const GLint *v) { STBGL_ARBIFY(glUniform3iv)(loc,count,v); } |
||||||
|
STB_GLPROG_DECLARE void stbglUniform4iv(GLint loc, GLsizei count, const GLint *v) { STBGL_ARBIFY(glUniform4iv)(loc,count,v); } |
||||||
|
|
||||||
|
STB_GLPROG_DECLARE void stbglUniform1f(GLint loc, float v0) |
||||||
|
{ STBGL_ARBIFY(glUniform1f)(loc,v0); } |
||||||
|
STB_GLPROG_DECLARE void stbglUniform2f(GLint loc, float v0, float v1) |
||||||
|
{ STBGL_ARBIFY(glUniform2f)(loc,v0,v1); } |
||||||
|
STB_GLPROG_DECLARE void stbglUniform3f(GLint loc, float v0, float v1, float v2) |
||||||
|
{ STBGL_ARBIFY(glUniform3f)(loc,v0,v1,v2); } |
||||||
|
STB_GLPROG_DECLARE void stbglUniform4f(GLint loc, float v0, float v1, float v2, float v3) |
||||||
|
{ STBGL_ARBIFY(glUniform4f)(loc,v0,v1,v2,v3); } |
||||||
|
|
||||||
|
STB_GLPROG_DECLARE void stbglUniform1i(GLint loc, GLint v0) |
||||||
|
{ STBGL_ARBIFY(glUniform1i)(loc,v0); } |
||||||
|
STB_GLPROG_DECLARE void stbglUniform2i(GLint loc, GLint v0, GLint v1) |
||||||
|
{ STBGL_ARBIFY(glUniform2i)(loc,v0,v1); } |
||||||
|
STB_GLPROG_DECLARE void stbglUniform3i(GLint loc, GLint v0, GLint v1, GLint v2) |
||||||
|
{ STBGL_ARBIFY(glUniform3i)(loc,v0,v1,v2); } |
||||||
|
STB_GLPROG_DECLARE void stbglUniform4i(GLint loc, GLint v0, GLint v1, GLint v2, GLint v3) |
||||||
|
{ STBGL_ARBIFY(glUniform4i)(loc,v0,v1,v2,v3); } |
||||||
|
|
||||||
|
#endif |
@ -0,0 +1,224 @@ |
|||||||
|
/*
|
||||||
|
SDL_windows_main.c, placed in the public domain by Sam Lantinga 4/13/98 |
||||||
|
|
||||||
|
The WinMain function -- calls your program's main() function |
||||||
|
*/ |
||||||
|
#include "SDL_config.h" |
||||||
|
|
||||||
|
#ifdef __WIN32__ |
||||||
|
|
||||||
|
//#include "../../core/windows/SDL_windows.h"
|
||||||
|
|
||||||
|
/* Include this so we define UNICODE properly */ |
||||||
|
#if defined(__WIN32__) |
||||||
|
#define WIN32_LEAN_AND_MEAN |
||||||
|
#define STRICT |
||||||
|
#ifndef UNICODE |
||||||
|
#define UNICODE 1 |
||||||
|
#endif |
||||||
|
#undef _WIN32_WINNT |
||||||
|
#define _WIN32_WINNT 0x501 /* Need 0x410 for AlphaBlend() and 0x500 for EnumDisplayDevices(), 0x501 for raw input */ |
||||||
|
#endif |
||||||
|
|
||||||
|
#include <windows.h> |
||||||
|
|
||||||
|
/* Routines to convert from UTF8 to native Windows text */ |
||||||
|
#if UNICODE |
||||||
|
#define WIN_StringToUTF8(S) SDL_iconv_string("UTF-8", "UTF-16LE", (char *)(S), (SDL_wcslen(S)+1)*sizeof(WCHAR)) |
||||||
|
#define WIN_UTF8ToString(S) (WCHAR *)SDL_iconv_string("UTF-16LE", "UTF-8", (char *)(S), SDL_strlen(S)+1) |
||||||
|
#else |
||||||
|
/* !!! FIXME: UTF8ToString() can just be a SDL_strdup() here. */ |
||||||
|
#define WIN_StringToUTF8(S) SDL_iconv_string("UTF-8", "ASCII", (char *)(S), (SDL_strlen(S)+1)) |
||||||
|
#define WIN_UTF8ToString(S) SDL_iconv_string("ASCII", "UTF-8", (char *)(S), SDL_strlen(S)+1) |
||||||
|
#endif |
||||||
|
|
||||||
|
/* Sets an error message based on a given HRESULT */ |
||||||
|
extern int WIN_SetErrorFromHRESULT(const char *prefix, HRESULT hr); |
||||||
|
|
||||||
|
/* Sets an error message based on GetLastError(). Always return -1. */ |
||||||
|
extern int WIN_SetError(const char *prefix); |
||||||
|
|
||||||
|
/* Wrap up the oddities of CoInitialize() into a common function. */ |
||||||
|
extern HRESULT WIN_CoInitialize(void); |
||||||
|
extern void WIN_CoUninitialize(void); |
||||||
|
|
||||||
|
/* Returns SDL_TRUE if we're running on Windows Vista and newer */ |
||||||
|
extern BOOL WIN_IsWindowsVistaOrGreater(); |
||||||
|
|
||||||
|
#include <stdio.h> |
||||||
|
#include <stdlib.h> |
||||||
|
|
||||||
|
/* Include the SDL main definition header */ |
||||||
|
#include "SDL.h" |
||||||
|
#include "SDL_main.h" |
||||||
|
|
||||||
|
#ifdef main |
||||||
|
# undef main |
||||||
|
#endif /* main */ |
||||||
|
|
||||||
|
static void |
||||||
|
UnEscapeQuotes(char *arg) |
||||||
|
{ |
||||||
|
char *last = NULL; |
||||||
|
|
||||||
|
while (*arg) { |
||||||
|
if (*arg == '"' && (last != NULL && *last == '\\')) { |
||||||
|
char *c_curr = arg; |
||||||
|
char *c_last = last; |
||||||
|
|
||||||
|
while (*c_curr) { |
||||||
|
*c_last = *c_curr; |
||||||
|
c_last = c_curr; |
||||||
|
c_curr++; |
||||||
|
} |
||||||
|
*c_last = '\0'; |
||||||
|
} |
||||||
|
last = arg; |
||||||
|
arg++; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/* Parse a command line buffer into arguments */ |
||||||
|
static int |
||||||
|
ParseCommandLine(char *cmdline, char **argv) |
||||||
|
{ |
||||||
|
char *bufp; |
||||||
|
char *lastp = NULL; |
||||||
|
int argc, last_argc; |
||||||
|
|
||||||
|
argc = last_argc = 0; |
||||||
|
for (bufp = cmdline; *bufp;) { |
||||||
|
/* Skip leading whitespace */ |
||||||
|
while (SDL_isspace(*bufp)) { |
||||||
|
++bufp; |
||||||
|
} |
||||||
|
/* Skip over argument */ |
||||||
|
if (*bufp == '"') { |
||||||
|
++bufp; |
||||||
|
if (*bufp) { |
||||||
|
if (argv) { |
||||||
|
argv[argc] = bufp; |
||||||
|
} |
||||||
|
++argc; |
||||||
|
} |
||||||
|
/* Skip over word */ |
||||||
|
lastp = bufp; |
||||||
|
while (*bufp && (*bufp != '"' || *lastp == '\\')) { |
||||||
|
lastp = bufp; |
||||||
|
++bufp; |
||||||
|
} |
||||||
|
} else { |
||||||
|
if (*bufp) { |
||||||
|
if (argv) { |
||||||
|
argv[argc] = bufp; |
||||||
|
} |
||||||
|
++argc; |
||||||
|
} |
||||||
|
/* Skip over word */ |
||||||
|
while (*bufp && !SDL_isspace(*bufp)) { |
||||||
|
++bufp; |
||||||
|
} |
||||||
|
} |
||||||
|
if (*bufp) { |
||||||
|
if (argv) { |
||||||
|
*bufp = '\0'; |
||||||
|
} |
||||||
|
++bufp; |
||||||
|
} |
||||||
|
|
||||||
|
/* Strip out \ from \" sequences */ |
||||||
|
if (argv && last_argc != argc) { |
||||||
|
UnEscapeQuotes(argv[last_argc]); |
||||||
|
} |
||||||
|
last_argc = argc; |
||||||
|
} |
||||||
|
if (argv) { |
||||||
|
argv[argc] = NULL; |
||||||
|
} |
||||||
|
return (argc); |
||||||
|
} |
||||||
|
|
||||||
|
/* Show an error message */ |
||||||
|
static void |
||||||
|
ShowError(const char *title, const char *message) |
||||||
|
{ |
||||||
|
/* If USE_MESSAGEBOX is defined, you need to link with user32.lib */ |
||||||
|
#ifdef USE_MESSAGEBOX |
||||||
|
MessageBox(NULL, message, title, MB_ICONEXCLAMATION | MB_OK); |
||||||
|
#else |
||||||
|
fprintf(stderr, "%s: %s\n", title, message); |
||||||
|
#endif |
||||||
|
} |
||||||
|
|
||||||
|
/* Pop up an out of memory message, returns to Windows */ |
||||||
|
static BOOL |
||||||
|
OutOfMemory(void) |
||||||
|
{ |
||||||
|
ShowError("Fatal Error", "Out of memory - aborting"); |
||||||
|
return FALSE; |
||||||
|
} |
||||||
|
|
||||||
|
#if defined(_MSC_VER) |
||||||
|
/* The VC++ compiler needs main defined */ |
||||||
|
#define console_main main |
||||||
|
#endif |
||||||
|
|
||||||
|
/* This is where execution begins [console apps] */ |
||||||
|
int |
||||||
|
console_main(int argc, char *argv[]) |
||||||
|
{ |
||||||
|
int status; |
||||||
|
|
||||||
|
SDL_SetMainReady(); |
||||||
|
|
||||||
|
/* Run the application main() code */ |
||||||
|
status = SDL_main(argc, argv); |
||||||
|
|
||||||
|
/* Exit cleanly, calling atexit() functions */ |
||||||
|
exit(status); |
||||||
|
|
||||||
|
/* Hush little compiler, don't you cry... */ |
||||||
|
return 0; |
||||||
|
} |
||||||
|
|
||||||
|
/* This is where execution begins [windowed apps] */ |
||||||
|
int WINAPI |
||||||
|
WinMain(HINSTANCE hInst, HINSTANCE hPrev, LPSTR szCmdLine, int sw) |
||||||
|
{ |
||||||
|
char **argv; |
||||||
|
int argc; |
||||||
|
char *cmdline; |
||||||
|
|
||||||
|
/* Grab the command line */ |
||||||
|
TCHAR *text = GetCommandLine(); |
||||||
|
#if UNICODE |
||||||
|
cmdline = SDL_iconv_string("UTF-8", "UCS-2-INTERNAL", (char *)(text), (SDL_wcslen(text)+1)*sizeof(WCHAR)); |
||||||
|
#else |
||||||
|
cmdline = SDL_strdup(text); |
||||||
|
#endif |
||||||
|
if (cmdline == NULL) { |
||||||
|
return OutOfMemory(); |
||||||
|
} |
||||||
|
|
||||||
|
/* Parse it into argv and argc */ |
||||||
|
argc = ParseCommandLine(cmdline, NULL); |
||||||
|
argv = SDL_stack_alloc(char *, argc + 1); |
||||||
|
if (argv == NULL) { |
||||||
|
return OutOfMemory(); |
||||||
|
} |
||||||
|
ParseCommandLine(cmdline, argv); |
||||||
|
|
||||||
|
/* Run the main program */ |
||||||
|
console_main(argc, argv); |
||||||
|
|
||||||
|
SDL_stack_free(argv); |
||||||
|
|
||||||
|
SDL_free(cmdline); |
||||||
|
|
||||||
|
/* Hush little compiler, don't you cry... */ |
||||||
|
return 0; |
||||||
|
} |
||||||
|
|
||||||
|
#endif /* __WIN32__ */ |
||||||
|
|
||||||
|
/* vi: set ts=4 sw=4 expandtab: */ |
Loading…
Reference in New Issue