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