1 // This file renders vertex buffers, converts raw meshes
2 // to GL meshes, and manages threads that do the raw-mesh
3 // building (found in cave_mesher.c)
6 #include "stb_voxel_render.h"
8 #define STB_GLEXT_DECLARE "glext_list.h"
10 #include "stb_image.h"
11 #include "stb_glprog.h"
14 #include "cave_parse.h"
17 #include "sdl_thread.h"
21 //#define STBVOX_CONFIG_TEX1_EDGE_CLAMP
24 // currently no dynamic way to set mesh cache size or view distance
28 stbvox_mesh_maker g_mesh_maker
;
31 GLint uniform_locations
[64];
33 //#define MAX_QUADS_PER_DRAW (65536 / 4) // assuming 16-bit indices, 4 verts per quad
34 //#define FIXED_INDEX_BUFFER_SIZE (MAX_QUADS_PER_DRAW * 6 * 2) // 16*1024 * 12 == ~192KB
36 // while uploading texture data, this holds our each texture
38 uint32 texture
[TEX_SIZE
][TEX_SIZE
];
52 // mesh is 32x32x255 ... this is hardcoded in that
53 // a mesh covers 2x2 minecraft chunks, no #defines for it
60 int vbuf_size
, fbuf_size
;
62 float transform
[3][3];
65 GLuint vbuf
;// vbuf_tex;
66 GLuint fbuf
, fbuf_tex
;
70 void scale_texture(unsigned char *src
, int x
, int y
, int w
, int h
)
73 assert(w
== 256 && h
== 256);
74 for (j
=0; j
< TEX_SIZE
; ++j
) {
75 for (i
=0; i
< TEX_SIZE
; ++i
) {
77 for (k
=0; k
< 4; ++k
) {
79 val
+= src
[ 4*(x
+(i
>>2)) + 4*w
*(y
+(j
>>2)) + k
]<<24;
86 void build_base_texture(int n
)
89 uint32 color
= stb_rand() | 0x808080;
90 for (y
=0; y
<TEX_SIZE
; ++y
)
91 for (x
=0; x
<TEX_SIZE
; ++x
) {
92 texture
[y
][x
] = (color
+ (stb_rand()&0x1f1f1f))|0xff000000;
96 void build_overlay_texture(int n
)
99 uint32 color
= stb_rand();
104 for (y
=0; y
<TEX_SIZE
; ++y
)
105 for (x
=0; x
<TEX_SIZE
; ++x
) {
109 for (y
=0; y
< TEX_SIZE
/8; ++y
) {
110 for (x
=0; x
< TEX_SIZE
; ++x
) {
111 texture
[y
][x
] = color
;
112 texture
[TEX_SIZE
-1-y
][x
] = color
;
113 texture
[x
][y
] = color
;
114 texture
[x
][TEX_SIZE
-1-y
] = color
;
119 // view radius of about 1024 = 2048 columns / 32 columns-per-mesh = 2^11 / 2^5 = 64x64
120 // so we need bigger than 64x64 so we can precache, which means we have to be
121 // non-power-of-two, or we have to be pretty huge
122 #define CACHED_MESH_NUM_X 128
123 #define CACHED_MESH_NUM_Y 128
126 chunk_mesh cached_chunk_mesh
[CACHED_MESH_NUM_Y
][CACHED_MESH_NUM_X
];
128 void free_chunk(int slot_x
, int slot_y
)
130 chunk_mesh
*cm
= &cached_chunk_mesh
[slot_y
][slot_x
];
131 if (cm
->state
== STATE_valid
) {
132 glDeleteTextures(1, &cm
->fbuf_tex
);
133 glDeleteBuffersARB(1, &cm
->vbuf
);
134 glDeleteBuffersARB(1, &cm
->fbuf
);
135 cached_chunk_mesh
[slot_y
][slot_x
].state
= STATE_invalid
;
139 void upload_mesh(chunk_mesh
*cm
, uint8
*build_buffer
, uint8
*face_buffer
)
141 glGenBuffersARB(1, &cm
->vbuf
);
142 glBindBufferARB(GL_ARRAY_BUFFER_ARB
, cm
->vbuf
);
143 glBufferDataARB(GL_ARRAY_BUFFER_ARB
, cm
->num_quads
*4*sizeof(uint32
), build_buffer
, GL_STATIC_DRAW_ARB
);
144 glBindBufferARB(GL_ARRAY_BUFFER_ARB
, 0);
146 glGenBuffersARB(1, &cm
->fbuf
);
147 glBindBufferARB(GL_TEXTURE_BUFFER_ARB
, cm
->fbuf
);
148 glBufferDataARB(GL_TEXTURE_BUFFER_ARB
, cm
->num_quads
*sizeof(uint32
), face_buffer
, GL_STATIC_DRAW_ARB
);
149 glBindBufferARB(GL_TEXTURE_BUFFER_ARB
, 0);
151 glGenTextures(1, &cm
->fbuf_tex
);
152 glBindTexture(GL_TEXTURE_BUFFER_ARB
, cm
->fbuf_tex
);
153 glTexBufferARB(GL_TEXTURE_BUFFER_ARB
, GL_RGBA8UI
, cm
->fbuf
);
154 glBindTexture(GL_TEXTURE_BUFFER_ARB
, 0);
157 static void upload_mesh_data(raw_mesh
*rm
)
161 int slot_x
= (cx
>> 1) & (CACHED_MESH_NUM_X
-1);
162 int slot_y
= (cy
>> 1) & (CACHED_MESH_NUM_Y
-1);
165 free_chunk(slot_x
, slot_y
);
167 cm
= &cached_chunk_mesh
[slot_y
][slot_x
];
168 cm
->num_quads
= rm
->num_quads
;
170 upload_mesh(cm
, rm
->build_buffer
, rm
->face_buffer
);
171 cm
->vbuf_size
= rm
->num_quads
*4*sizeof(uint32
);
172 cm
->fbuf_size
= rm
->num_quads
*sizeof(uint32
);
173 cm
->priority
= 100000;
177 memcpy(cm
->bounds
, rm
->bounds
, sizeof(cm
->bounds
));
178 memcpy(cm
->transform
, rm
->transform
, sizeof(cm
->transform
));
180 // write barrier here
181 cm
->state
= STATE_valid
;
184 GLint uniform_loc
[16];
185 float table3
[128][3];
192 int view_dist_in_chunks
= 50;
194 int view_dist_in_chunks
= 80;
197 void setup_uniforms(float pos
[3])
201 for (i
=0; i
< STBVOX_UNIFORM_count
; ++i
) {
202 stbvox_uniform_info raw
, *ui
=&raw
;
203 stbvox_get_uniform_info(&raw
, i
);
206 if (i
== STBVOX_UNIFORM_texscale
|| i
== STBVOX_UNIFORM_texgen
|| i
== STBVOX_UNIFORM_color_table
)
210 void *data
= ui
->default_value
;
211 uniform_loc
[i
] = stbgl_find_uniform(main_prog
, ui
->name
);
213 case STBVOX_UNIFORM_face_data
:
218 case STBVOX_UNIFORM_tex_array
:
219 glActiveTextureARB(GL_TEXTURE0_ARB
);
220 glBindTexture(GL_TEXTURE_2D_ARRAY_EXT
, voxel_tex
[0]);
221 glActiveTextureARB(GL_TEXTURE1_ARB
);
222 glBindTexture(GL_TEXTURE_2D_ARRAY_EXT
, voxel_tex
[1]);
223 glActiveTextureARB(GL_TEXTURE0_ARB
);
229 case STBVOX_UNIFORM_color_table
:
230 data
= ui
->default_value
;
231 ((float *)data
)[63*4+3] = 2.0f
; // emissive
234 case STBVOX_UNIFORM_camera_pos
:
236 table3
[0][0] = pos
[0];
237 table3
[0][1] = pos
[1];
238 table3
[0][2] = pos
[2];
239 table3
[0][3] = stb_max(0,(float)sin(step
*2)*0.125f
);
242 case STBVOX_UNIFORM_ambient
: {
244 //float bright = 0.75;
247 // ambient direction is sky-colored upwards
248 // "ambient" lighting is from above
250 table4
[0][1] = -0.5f
;
253 amb
[1][0] = 0.3f
; amb
[1][1] = 0.3f
; amb
[1][2] = 0.3f
; // dark-grey
254 amb
[2][0] = 1.0; amb
[2][1] = 1.0; amb
[2][2] = 1.0; // white
256 // convert so (table[1]*dot+table[2]) gives
257 // above interpolation
258 // lerp((dot+1)/2, amb[1], amb[2])
259 // amb[1] + (amb[2] - amb[1]) * (dot+1)/2
260 // amb[1] + (amb[2] - amb[1]) * dot/2 + (amb[2]-amb[1])/2
262 for (j
=0; j
< 3; ++j
) {
263 table4
[1][j
] = (amb
[2][j
] - amb
[1][j
])/2 * bright
;
264 table4
[2][j
] = (amb
[1][j
] + amb
[2][j
])/2 * bright
;
268 table4
[3][0] = 0.6f
, table4
[3][1] = 0.7f
, table4
[3][2] = 0.9f
;
269 table4
[3][3] = 1.0f
/ (view_dist_in_chunks
* 16);
270 table4
[3][3] *= table4
[3][3];
278 case STBVOX_UNIFORM_TYPE_sampler
: stbglUniform1iv(uniform_loc
[i
], ui
->array_length
, data
); break;
279 case STBVOX_UNIFORM_TYPE_vec2
: stbglUniform2fv(uniform_loc
[i
], ui
->array_length
, data
); break;
280 case STBVOX_UNIFORM_TYPE_vec3
: stbglUniform3fv(uniform_loc
[i
], ui
->array_length
, data
); break;
281 case STBVOX_UNIFORM_TYPE_vec4
: stbglUniform4fv(uniform_loc
[i
], ui
->array_length
, data
); break;
287 GLuint unitex
[64], unibuf
[64];
288 void make_texture_buffer_for_uniform(int uniform
, int slot
)
291 stbvox_uniform_info raw
, *ui
=&raw
;
294 stbvox_get_uniform_info(ui
, uniform
);
295 uloc
= stbgl_find_uniform(main_prog
, ui
->name
);
297 if (uniform
== STBVOX_UNIFORM_color_table
)
298 ((float *)ui
->default_value
)[63*4+3] = 2.0f
; // emissive
300 glGenBuffersARB(1, &unibuf
[uniform
]);
301 glBindBufferARB(GL_ARRAY_BUFFER_ARB
, unibuf
[uniform
]);
302 glBufferDataARB(GL_ARRAY_BUFFER_ARB
, ui
->array_length
* ui
->bytes_per_element
, ui
->default_value
, GL_STATIC_DRAW_ARB
);
303 glBindBufferARB(GL_ARRAY_BUFFER_ARB
, 0);
305 glGenTextures(1, &unitex
[uniform
]);
306 glBindTexture(GL_TEXTURE_BUFFER_ARB
, unitex
[uniform
]);
308 case STBVOX_UNIFORM_TYPE_vec2
: type
= GL_RG32F
; break;
309 case STBVOX_UNIFORM_TYPE_vec3
: type
= GL_RGB32F
; break;
310 case STBVOX_UNIFORM_TYPE_vec4
: type
= GL_RGBA32F
; break;
313 glTexBufferARB(GL_TEXTURE_BUFFER_ARB
, type
, unibuf
[uniform
]);
314 glBindTexture(GL_TEXTURE_BUFFER_ARB
, 0);
316 glActiveTextureARB(GL_TEXTURE0
+ slot
);
317 glBindTexture(GL_TEXTURE_BUFFER_ARB
, unitex
[uniform
]);
318 glActiveTextureARB(GL_TEXTURE0
);
320 stbglUseProgram(main_prog
);
321 stbglUniform1i(uloc
, slot
);
324 #define MAX_MESH_WORKERS 8
325 #define MAX_CHUNK_LOAD_WORKERS 2
327 int num_mesh_workers
;
328 int num_chunk_load_workers
;
337 SDL_sem
* request_received
;
339 SDL_sem
* chunk_server_done_processing
;
343 fast_chunk
*chunks
[4][4];
361 mesh_worker mesh_data
[MAX_MESH_WORKERS
];
362 int num_meshes_started
; // stats
364 int request_chunk(int chunk_x
, int chunk_y
);
365 void update_meshes_from_render_thread(void);
367 unsigned char tex2_data
[64][4];
369 void init_tex2_gradient(void)
372 for (i
=0; i
< 16; ++i
) {
373 tex2_data
[i
+ 0][0] = 64 + 12*i
;
374 tex2_data
[i
+ 0][1] = 32;
375 tex2_data
[i
+ 0][2] = 64;
377 tex2_data
[i
+16][0] = 255;
378 tex2_data
[i
+16][1] = 32 + 8*i
;
379 tex2_data
[i
+16][2] = 64;
381 tex2_data
[i
+32][0] = 255;
382 tex2_data
[i
+32][1] = 160;
383 tex2_data
[i
+32][2] = 64 + 12*i
;
385 tex2_data
[i
+48][0] = 255;
386 tex2_data
[i
+48][1] = 160 + 6*i
;
387 tex2_data
[i
+48][2] = 255;
391 void set_tex2_alpha(float fa
)
394 int a
= (int) stb_lerp(fa
, 0, 255);
395 if (a
< 0) a
= 0; else if (a
> 255) a
= 255;
396 glBindTexture(GL_TEXTURE_2D_ARRAY_EXT
, voxel_tex
[1]);
397 for (i
=0; i
< 64; ++i
) {
399 glTexSubImage3DEXT(GL_TEXTURE_2D_ARRAY_EXT
, 0, 0,0,i
, 1,1,1, GL_RGBA
, GL_UNSIGNED_BYTE
, tex2_data
[i
]);
403 void render_init(void)
406 char *binds
[] = { "attr_vertex", "attr_face", NULL
};
411 unsigned char *texdata
= stbi_load("terrain.png", &w
, &h
, NULL
, 4);
413 stbvox_init_mesh_maker(&g_mesh_maker
);
414 for (i
=0; i
< num_mesh_workers
; ++i
) {
415 stbvox_init_mesh_maker(&mesh_data
[i
].rm
.mm
);
418 vertex
= stbvox_get_vertex_shader();
419 fragment
= stbvox_get_fragment_shader();
422 char error_buffer
[1024];
423 char *main_vertex
[] = { vertex
, NULL
};
424 char *main_fragment
[] = { fragment
, NULL
};
425 main_prog
= stbgl_create_program(main_vertex
, main_fragment
, binds
, error_buffer
, sizeof(error_buffer
));
426 if (main_prog
== 0) {
427 ods("Compile error for main shader: %s\n", error_buffer
);
432 //init_index_buffer();
434 make_texture_buffer_for_uniform(STBVOX_UNIFORM_texscale
, 3);
435 make_texture_buffer_for_uniform(STBVOX_UNIFORM_texgen
, 4);
436 make_texture_buffer_for_uniform(STBVOX_UNIFORM_color_table
, 5);
438 glGenTextures(2, voxel_tex
);
440 glBindTexture(GL_TEXTURE_2D_ARRAY_EXT
, voxel_tex
[0]);
441 glTexImage3DEXT(GL_TEXTURE_2D_ARRAY_EXT
, 0, GL_RGBA
,
442 TEX_SIZE
,TEX_SIZE
,256,
443 0,GL_RGBA
,GL_UNSIGNED_BYTE
,NULL
);
444 for (i
=0; i
< 256; ++i
) {
446 scale_texture(texdata
, (i
&15)*w
/16, (h
/16)*(i
>>4), w
,h
);
448 build_base_texture(i
);
449 glTexSubImage3DEXT(GL_TEXTURE_2D_ARRAY_EXT
, 0, 0,0,i
, TEX_SIZE
,TEX_SIZE
,1, GL_RGBA
, GL_UNSIGNED_BYTE
, texture
[0]);
451 glTexParameteri(GL_TEXTURE_2D_ARRAY_EXT
, GL_TEXTURE_MIN_FILTER
, GL_LINEAR_MIPMAP_LINEAR
);
452 glTexParameteri(GL_TEXTURE_2D_ARRAY_EXT
, GL_TEXTURE_MAG_FILTER
, GL_LINEAR
);
453 glTexParameteri(GL_TEXTURE_2D_ARRAY_EXT
, GL_TEXTURE_MAX_ANISOTROPY_EXT
, 16);
454 #ifdef STBVOX_CONFIG_TEX1_EDGE_CLAMP
455 glTexParameteri(GL_TEXTURE_2D_ARRAY_EXT
, GL_TEXTURE_WRAP_S
, GL_CLAMP_TO_EDGE
);
456 glTexParameteri(GL_TEXTURE_2D_ARRAY_EXT
, GL_TEXTURE_WRAP_T
, GL_CLAMP_TO_EDGE
);
459 glGenerateMipmapEXT(GL_TEXTURE_2D_ARRAY_EXT
);
461 glBindTexture(GL_TEXTURE_2D_ARRAY_EXT
, voxel_tex
[1]);
462 glTexImage3DEXT(GL_TEXTURE_2D_ARRAY_EXT
, 0, GL_RGBA
,
464 0,GL_RGBA
,GL_UNSIGNED_BYTE
,NULL
);
465 init_tex2_gradient();
468 for (i
=0; i
< 128; ++i
) {
469 //build_overlay_texture(i);
470 glTexSubImage3DEXT(GL_TEXTURE_2D_ARRAY_EXT
, 0, 0,0,i
, TEX_SIZE
,TEX_SIZE
,1, GL_RGBA
, GL_UNSIGNED_BYTE
, texture
[0]);
473 glTexParameteri(GL_TEXTURE_2D_ARRAY_EXT
, GL_TEXTURE_MIN_FILTER
, GL_LINEAR_MIPMAP_LINEAR
);
474 glTexParameteri(GL_TEXTURE_2D_ARRAY_EXT
, GL_TEXTURE_MAG_FILTER
, GL_LINEAR
);
475 glGenerateMipmapEXT(GL_TEXTURE_2D_ARRAY_EXT
);
478 void world_init(void)
482 Uint64 start_time
, end_time
;
489 start_time
= SDL_GetPerformanceCounter();
491 // iterate in 8x8 clusters of qchunks at a time to get better converted-chunk-cache reuse
492 // than a purely row-by-row ordering is (single-threaded this is a bigger win than
493 // any of the above optimizations were, since it halves zlib/mc-conversion costs)
494 for (x
=-range
; x
<= range
; x
+= 16)
495 for (y
=-range
; y
<= range
; y
+= 16)
496 for (b
=y
; b
< y
+16 && b
<= range
; b
+= 2)
497 for (a
=x
; a
< x
+16 && a
<= range
; a
+= 2)
498 while (!request_chunk(a
, b
)) { // if request fails, all threads are busy
499 update_meshes_from_render_thread();
503 // wait until all the workers are done,
504 // (this is only needed if we want to time
505 // when the build finishes, or when we want to reset the
506 // cache size; otherwise we could just go ahead and
507 // start rendering whatever we've got)
510 update_meshes_from_render_thread();
511 for (i
=0; i
< num_mesh_workers
; ++i
)
512 if (mesh_data
[i
].state
!= WSTATE_idle
)
514 if (i
== num_mesh_workers
)
519 end_time
= SDL_GetPerformanceCounter();
520 ods("Build time: %7.2fs\n", (end_time
- start_time
) / (float) SDL_GetPerformanceFrequency());
522 // don't waste lots of storage on chunk caches once it's finished starting-up;
523 // this was only needed to be this large because we worked in large blocks
524 // to maximize sharing
525 reset_cache_size(32);
528 extern SDL_mutex
* chunk_cache_mutex
;
530 int mesh_worker_handler(void *data
)
532 mesh_worker
*mw
= data
;
533 mw
->face_buffer
= malloc(FACE_BUFFER_SIZE
);
534 mw
->build_buffer
= malloc(BUILD_BUFFER_SIZE
);
536 // this loop only works because the compiler can't
537 // tell that the SDL_calls don't access mw->state;
538 // really we should barrier that stuff
543 // wait for a chunk request
544 SDL_SemWait(mw
->request_received
);
546 // analyze the chunk request
547 assert(mw
->state
== WSTATE_requested
);
551 // this is inaccurate as it can block while another thread has the cache locked
552 mw
->state
= WSTATE_running
;
554 // get the chunks we need (this takes a lock and caches them)
555 for (j
=0; j
< 4; ++j
)
556 for (i
=0; i
< 4; ++i
)
557 mw
->chunks
[j
][i
] = get_converted_fastchunk(cx
-1 + i
, cy
-1 + j
);
559 // build the mesh based on the chunks
560 mw
->rm
.build_buffer
= mw
->build_buffer
;
561 mw
->rm
.face_buffer
= mw
->face_buffer
;
562 build_chunk(cx
, cy
, mw
->chunks
, &mw
->rm
);
563 mw
->state
= WSTATE_mesh_ready
;
564 // don't need to notify of this, because it gets polled
566 // when done, free the chunks
568 // for efficiency we just take the mutex once around the whole thing,
569 // though this spreads the mutex logic over two files
570 SDL_LockMutex(chunk_cache_mutex
);
571 for (j
=0; j
< 4; ++j
)
572 for (i
=0; i
< 4; ++i
) {
573 deref_fastchunk(mw
->chunks
[j
][i
]);
574 mw
->chunks
[j
][i
] = NULL
;
576 SDL_UnlockMutex(chunk_cache_mutex
);
581 int request_chunk(int chunk_x
, int chunk_y
)
584 for (i
=0; i
< num_mesh_workers
; ++i
) {
585 mesh_worker
*mw
= &mesh_data
[i
];
586 if (mw
->state
== WSTATE_idle
) {
587 mw
->request_cx
= chunk_x
;
588 mw
->request_cy
= chunk_y
;
589 mw
->state
= WSTATE_requested
;
590 SDL_SemPost(mw
->request_received
);
591 ++num_meshes_started
;
598 void prepare_threads(void)
601 int num_proc
= SDL_GetCPUCount();
604 num_mesh_workers
= num_proc
/2;
605 else if (num_proc
> 4)
606 num_mesh_workers
= 4;
608 num_mesh_workers
= num_proc
-1;
611 // Thread usage is probably pretty terrible; need to make a
612 // separate queue of needed chunks, instead of just generating
613 // one request per thread per frame, and a separate queue of
614 // results. (E.g. If it takes 1.5 frames to build mesh, thread
615 // is idle for 0.5 frames.) To fake this for now, I've just
616 // doubled the number of threads to let those serve as a 'queue',
619 num_mesh_workers
*= 2; // try to get better thread usage
621 if (num_mesh_workers
> MAX_MESH_WORKERS
)
622 num_mesh_workers
= MAX_MESH_WORKERS
;
624 for (i
=0; i
< num_mesh_workers
; ++i
) {
625 mesh_worker
*data
= &mesh_data
[i
];
626 data
->request_received
= SDL_CreateSemaphore(0);
627 data
->chunk_server_done_processing
= SDL_CreateSemaphore(0);
628 SDL_CreateThread(mesh_worker_handler
, "mesh worker", data
);
633 // "better" buffer uploading
635 if (glBufferStorage
) {
636 glDeleteBuffersARB(1, &vb
->vbuf
);
637 glGenBuffersARB(1, &vb
->vbuf
);
639 glBindBufferARB(GL_ARRAY_BUFFER_ARB
, vb
->vbuf
);
640 glBufferStorage(GL_ARRAY_BUFFER_ARB
, sizeof(build_buffer
), build_buffer
, 0);
641 glBindBufferARB(GL_ARRAY_BUFFER_ARB
, 0);
643 glBindBufferARB(GL_ARRAY_BUFFER_ARB
, vb
->vbuf
);
644 glBufferDataARB(GL_ARRAY_BUFFER_ARB
, sizeof(build_buffer
), build_buffer
, GL_STATIC_DRAW_ARB
);
645 glBindBufferARB(GL_ARRAY_BUFFER_ARB
, 0);
655 static plane frustum
[6];
657 static void matd_mul(double out
[4][4], double src1
[4][4], double src2
[4][4])
660 for (j
=0; j
< 4; ++j
) {
661 for (i
=0; i
< 4; ++i
) {
663 for (k
=0; k
< 4; ++k
)
664 t
+= src1
[k
][i
] * src2
[j
][k
];
670 // https://fgiesen.wordpress.com/2012/08/31/frustum-planes-from-the-projection-matrix/
671 static void compute_frustum(void)
674 GLdouble mv
[4][4],proj
[4][4], mvproj
[4][4];
675 glGetDoublev(GL_MODELVIEW_MATRIX
, mv
[0]);
676 glGetDoublev(GL_PROJECTION_MATRIX
, proj
[0]);
677 matd_mul(mvproj
, proj
, mv
);
678 for (i
=0; i
< 4; ++i
) {
679 (&frustum
[0].x
)[i
] = (float) (mvproj
[3][i
] + mvproj
[0][i
]);
680 (&frustum
[1].x
)[i
] = (float) (mvproj
[3][i
] - mvproj
[0][i
]);
681 (&frustum
[2].x
)[i
] = (float) (mvproj
[3][i
] + mvproj
[1][i
]);
682 (&frustum
[3].x
)[i
] = (float) (mvproj
[3][i
] - mvproj
[1][i
]);
683 (&frustum
[4].x
)[i
] = (float) (mvproj
[3][i
] + mvproj
[2][i
]);
684 (&frustum
[5].x
)[i
] = (float) (mvproj
[3][i
] - mvproj
[2][i
]);
688 static int test_plane(plane
*p
, float x0
, float y0
, float z0
, float x1
, float y1
, float z1
)
690 // return false if the box is entirely behind the plane
692 assert(x0
<= x1
&& y0
<= y1
&& z0
<= z1
);
693 if (p
->x
> 0) d
+= x1
*p
->x
; else d
+= x0
*p
->x
;
694 if (p
->y
> 0) d
+= y1
*p
->y
; else d
+= y0
*p
->y
;
695 if (p
->z
> 0) d
+= z1
*p
->z
; else d
+= z0
*p
->z
;
696 return d
+ p
->w
>= 0;
699 static int is_box_in_frustum(float *bmin
, float *bmax
)
702 for (i
=0; i
< 6; ++i
)
703 if (!test_plane(&frustum
[i
], bmin
[0], bmin
[1], bmin
[2], bmax
[0], bmax
[1], bmax
[2]))
708 float compute_priority(int cx
, int cy
, float x
, float y
)
710 float distx
, disty
, dist2
;
711 distx
= (cx
*16+8) - x
;
712 disty
= (cy
*16+8) - y
;
713 dist2
= distx
*distx
+ disty
*disty
;
714 return view_dist_in_chunks
*view_dist_in_chunks
* 16 * 16 - dist2
;
717 int chunk_locations
, chunks_considered
, chunks_in_frustum
;
718 int quads_considered
, quads_rendered
;
719 int chunk_storage_rendered
, chunk_storage_considered
, chunk_storage_total
;
720 int update_frustum
= 1;
723 int max_chunk_storage
= 450 << 20;
724 int min_chunk_storage
= 350 << 20;
726 int max_chunk_storage
= 900 << 20;
727 int min_chunk_storage
= 800 << 20;
730 float min_priority
= -500; // this really wants to be in unit space, not squared space
732 int num_meshes_uploaded
;
734 void update_meshes_from_render_thread(void)
737 for (i
=0; i
< num_mesh_workers
; ++i
) {
738 mesh_worker
*mw
= &mesh_data
[i
];
739 if (mw
->state
== WSTATE_mesh_ready
) {
740 upload_mesh_data(&mw
->rm
);
741 ++num_meshes_uploaded
;
742 mw
->state
= WSTATE_idle
;
747 extern float tex2_alpha
;
748 extern int global_hack
;
749 int num_threads_active
;
750 float chunk_server_activity
;
752 void render_caves(float campos
[3])
754 float x
= campos
[0], y
= campos
[1];
755 int qchunk_x
, qchunk_y
;
761 chunk_locations
= chunks_considered
= chunks_in_frustum
= 0;
762 quads_considered
= quads_rendered
= 0;
763 chunk_storage_total
= chunk_storage_considered
= chunk_storage_rendered
= 0;
765 cam_x
= (int) floor(x
+0.5);
766 cam_y
= (int) floor(y
+0.5);
768 qchunk_x
= (((int) floor(x
)+16) >> 5) << 1;
769 qchunk_y
= (((int) floor(y
)+16) >> 5) << 1;
771 glEnable(GL_ALPHA_TEST
);
772 glAlphaFunc(GL_GREATER
, 0.5);
774 stbglUseProgram(main_prog
);
775 setup_uniforms(campos
); // set uniforms to default values inefficiently
776 glActiveTextureARB(GL_TEXTURE2_ARB
);
777 stbglEnableVertexAttribArray(0);
780 float lighting
[2][3] = { { campos
[0],campos
[1],campos
[2] }, { 0.75,0.75,0.65f
} };
782 lighting
[1][0] *= bright
;
783 lighting
[1][1] *= bright
;
784 lighting
[1][2] *= bright
;
785 stbglUniform3fv(stbgl_find_uniform(main_prog
, "light_source"), 2, lighting
[0]);
789 set_tex2_alpha(tex2_alpha
);
791 num_meshes_uploaded
= 0;
792 update_meshes_from_render_thread();
794 // traverse all in-range chunks and analyze them
795 for (j
=-view_dist_in_chunks
; j
<= view_dist_in_chunks
; j
+= 2) {
796 for (i
=-view_dist_in_chunks
; i
<= view_dist_in_chunks
; i
+= 2) {
798 int cx
= qchunk_x
+ i
;
799 int cy
= qchunk_y
+ j
;
801 priority
= compute_priority(cx
, cy
, x
, y
);
802 if (priority
>= min_priority
) {
803 int slot_x
= (cx
>>1) & (CACHED_MESH_NUM_X
-1);
804 int slot_y
= (cy
>>1) & (CACHED_MESH_NUM_Y
-1);
805 chunk_mesh
*cm
= &cached_chunk_mesh
[slot_y
][slot_x
];
807 if (cm
->state
== STATE_valid
&& priority
>= 0) {
808 // check if chunk pos actually matches
809 if (cm
->chunk_x
!= cx
|| cm
->chunk_y
!= cy
) {
810 // we have a stale chunk we need to recreate
811 free_chunk(slot_x
, slot_y
); // it probably will have already gotten freed, but just in case
814 if (cm
->state
== STATE_invalid
) {
817 cm
->state
= STATE_needed
;
819 cm
->priority
= priority
;
824 // draw front-to-back
825 for (rad
= 0; rad
<= view_dist_in_chunks
; rad
+= 2) {
826 for (j
=-rad
; j
<= rad
; j
+= 2) {
827 // if j is +- rad, then iterate i through all values
828 // if j isn't +-rad, then i should be only -rad & rad
832 for (i
=-rad
; i
<= rad
; i
+= step
) {
833 int cx
= qchunk_x
+ i
;
834 int cy
= qchunk_y
+ j
;
835 int slot_x
= (cx
>>1) & (CACHED_MESH_NUM_X
-1);
836 int slot_y
= (cy
>>1) & (CACHED_MESH_NUM_Y
-1);
837 chunk_mesh
*cm
= &cached_chunk_mesh
[slot_y
][slot_x
];
838 if (cm
->state
== STATE_valid
&& cm
->priority
>= 0) {
840 quads_considered
+= cm
->num_quads
;
841 if (is_box_in_frustum(cm
->bounds
[0], cm
->bounds
[1])) {
845 stbglUniform3fv(uniform_loc
[STBVOX_UNIFORM_transform
], 3, cm
->transform
[0]);
846 glBindBufferARB(GL_ARRAY_BUFFER_ARB
, cm
->vbuf
);
847 glVertexAttribIPointer(0, 1, GL_UNSIGNED_INT
, 4, (void*) 0);
848 glBindTexture(GL_TEXTURE_BUFFER_ARB
, cm
->fbuf_tex
);
849 glDrawArrays(GL_QUADS
, 0, cm
->num_quads
*4);
850 quads_rendered
+= cm
->num_quads
;
852 chunk_storage_rendered
+= cm
->vbuf_size
+ cm
->fbuf_size
;
854 chunk_storage_considered
+= cm
->vbuf_size
+ cm
->fbuf_size
;
860 stbglDisableVertexAttribArray(0);
861 glBindBufferARB(GL_ARRAY_BUFFER_ARB
, 0);
862 glActiveTextureARB(GL_TEXTURE0_ARB
);
865 num_meshes_started
= 0;
869 float highest_priority
[MAX_QUEUE
];
870 int highest_i
[MAX_QUEUE
], highest_j
[MAX_QUEUE
];
871 float lowest_priority
= view_dist_in_chunks
* view_dist_in_chunks
* 16 * 16.0f
;
872 int lowest_i
= -1, lowest_j
= -1;
874 for (i
=0; i
< MAX_QUEUE
; ++i
) {
875 highest_priority
[i
] = min_priority
;
880 for (j
=0; j
< CACHED_MESH_NUM_Y
; ++j
) {
881 for (i
=0; i
< CACHED_MESH_NUM_X
; ++i
) {
882 chunk_mesh
*cm
= &cached_chunk_mesh
[j
][i
];
883 if (cm
->state
== STATE_valid
) {
884 cm
->priority
= compute_priority(cm
->chunk_x
, cm
->chunk_y
, x
, y
);
885 chunk_storage_total
+= cm
->vbuf_size
+ cm
->fbuf_size
;
886 if (cm
->priority
< lowest_priority
) {
887 lowest_priority
= cm
->priority
;
892 if (cm
->state
== STATE_needed
) {
893 cm
->priority
= compute_priority(cm
->chunk_x
, cm
->chunk_y
, x
, y
);
894 if (cm
->priority
< min_priority
)
895 cm
->state
= STATE_invalid
;
896 else if (cm
->priority
> highest_priority
[0]) {
898 highest_priority
[0] = cm
->priority
;
901 // bubble this up to right place
902 for (k
=0; k
< MAX_QUEUE
-1; ++k
) {
903 if (highest_priority
[k
] > highest_priority
[k
+1]) {
904 highest_priority
[k
] = highest_priority
[k
+1];
905 highest_priority
[k
+1] = cm
->priority
;
906 highest_i
[k
] = highest_i
[k
+1];
908 highest_j
[k
] = highest_j
[k
+1];
920 // I couldn't find any straightforward logic that avoids
921 // the hysteresis problem of continually creating & freeing
922 // a block on the margin, so I just don't free a block until
923 // it's out of range, but this doesn't actually correctly
924 // handle when the cache is too small for the given range
925 if (chunk_storage_total
>= min_chunk_storage
&& lowest_i
>= 0) {
926 if (cached_chunk_mesh
[lowest_j
][lowest_i
].priority
< -1200) // -1000? 0?
927 free_chunk(lowest_i
, lowest_j
);
930 if (chunk_storage_total
< max_chunk_storage
&& highest_i
[0] >= 0) {
931 for (j
=MAX_QUEUE
-1; j
>= 0; --j
) {
932 if (highest_j
[0] >= 0) {
933 chunk_mesh
*cm
= &cached_chunk_mesh
[highest_j
[j
]][highest_i
[j
]];
934 if (request_chunk(cm
->chunk_x
, cm
->chunk_y
)) {
935 cm
->state
= STATE_requested
;
937 // if we couldn't queue this one, skip the remainder
945 update_meshes_from_render_thread();
947 num_threads_active
= 0;
948 for (i
=0; i
< num_mesh_workers
; ++i
) {
949 num_threads_active
+= (mesh_data
[i
].state
== WSTATE_running
);