From: hgn Date: Fri, 13 Dec 2024 02:37:46 +0000 (+0000) Subject: move explicit bezier to vg X-Git-Url: https://harrygodden.com/git/?p=vg.git;a=commitdiff_plain;h=HEAD;hp=cc5c184b1264d2e33398c1a4c03e75fb42ca04f5 move explicit bezier to vg --- diff --git a/shaders/blit.vs b/shaders/blit.vs new file mode 100644 index 0000000..06ffba6 --- /dev/null +++ b/shaders/blit.vs @@ -0,0 +1,10 @@ +layout (location=0) in vec2 a_co; +out vec2 aUv; + +uniform vec2 uInverseRatio; + +void main() +{ + gl_Position = vec4(a_co*2.0-1.0,0.0,1.0); + aUv = a_co * uInverseRatio; +} diff --git a/shaders/blit_blur.fs b/shaders/blit_blur.fs new file mode 100644 index 0000000..592c83f --- /dev/null +++ b/shaders/blit_blur.fs @@ -0,0 +1,32 @@ +out vec4 FragColor; +uniform sampler2D uTexMain; +uniform sampler2D uTexMotion; +uniform float uBlurStrength; +uniform vec2 uOverrideDir; +uniform vec2 uClampUv; + +in vec2 aUv; + +vec2 rand_hash22( vec2 p ) +{ + vec3 p3 = fract(vec3(p.xyx) * 213.8976123); + p3 += dot(p3, p3.yzx+19.19); + return fract(vec2((p3.x + p3.y)*p3.z, (p3.x+p3.z)*p3.y)); +} + +void main() +{ + vec2 vuv = aUv; + + vec2 vrand = rand_hash22( vuv ) * 2.0 - vec2(1.0); + vec2 vrand1 = rand_hash22( vrand ) * 2.0 - vec2(1.0); + + vec2 vdir = texture( uTexMotion, vuv ).xy * uBlurStrength + uOverrideDir; + + vec4 vcolour0 = texture( uTexMain, min(vuv + vdir*vrand.x,uClampUv) ); + vec4 vcolour1 = texture( uTexMain, min(vuv + vdir*vrand.y,uClampUv) ); + vec4 vcolour2 = texture( uTexMain, min(vuv + vdir*vrand1.x,uClampUv) ); + vec4 vcolour3 = texture( uTexMain, min(vuv + vdir*vrand1.y,uClampUv) ); + + FragColor = ( vcolour0 + vcolour1 + vcolour2 + vcolour3 ) * 0.25; +} diff --git a/shaders/blit_colour.fs b/shaders/blit_colour.fs new file mode 100644 index 0000000..7eb290e --- /dev/null +++ b/shaders/blit_colour.fs @@ -0,0 +1,9 @@ +out vec4 FragColor; +uniform vec4 uColour; + +in vec2 aUv; + +void main() +{ + FragColor = uColour; +} diff --git a/shaders/blit_tex.fs b/shaders/blit_tex.fs new file mode 100644 index 0000000..634e84f --- /dev/null +++ b/shaders/blit_tex.fs @@ -0,0 +1,41 @@ +out vec4 FragColor; +uniform sampler2D uTexMain; + +in vec2 aUv; + +float kPi = 3.14159265358979; + +vec2 fisheye_distort(vec2 xy) +{ + float aperture = 1350.0; + float apertureHalf = 0.5 * aperture * (kPi / 180.0); + float maxFactor = sin(apertureHalf); + + vec2 uv; + float d = length(xy); + if(d < (2.0-maxFactor)) + { + d = length(xy * maxFactor); + float z = sqrt(1.0 - d * d); + float r = atan(d, z) / kPi; + float phi = atan(xy.y, xy.x); + + uv.x = r * cos(phi) + 0.5; + uv.y = r * sin(phi) + 0.5; + } + else + { + uv = 0.5*xy + 0.5; + } + + return uv; +} + + +void main() +{ + vec2 vwarp = 2.0*aUv - 1.0; + vwarp = fisheye_distort( vwarp ); + + FragColor = texture( uTexMain, aUv ); +} diff --git a/shaders/motion_vectors_common.glsl b/shaders/motion_vectors_common.glsl new file mode 100644 index 0000000..7e6c114 --- /dev/null +++ b/shaders/motion_vectors_common.glsl @@ -0,0 +1 @@ +const float k_motion_lerp_amount = 0.01; diff --git a/shaders/motion_vectors_fs.glsl b/shaders/motion_vectors_fs.glsl new file mode 100644 index 0000000..b92ae2d --- /dev/null +++ b/shaders/motion_vectors_fs.glsl @@ -0,0 +1,16 @@ +#include "../vg/shaders/motion_vectors_common.glsl" + +layout (location = 1) out vec4 oMotionVec; + +in vec3 aMotionVec0; +in vec3 aMotionVec1; + +void compute_motion_vectors() +{ + // Write motion vectors + vec2 vmotion0 = aMotionVec0.xy / aMotionVec0.z; + vec2 vmotion1 = aMotionVec1.xy / aMotionVec1.z; + vec2 vmotion = (vmotion1-vmotion0) * (1.0/k_motion_lerp_amount); + + oMotionVec = vec4( vmotion, 0.0, 1.0 ); +} diff --git a/shaders/motion_vectors_vs.glsl b/shaders/motion_vectors_vs.glsl new file mode 100644 index 0000000..4eee0a6 --- /dev/null +++ b/shaders/motion_vectors_vs.glsl @@ -0,0 +1,14 @@ +#include "../vg/shaders/motion_vectors_common.glsl" + +out vec3 aMotionVec0; +out vec3 aMotionVec1; + +void vs_motion_out( vec4 vproj0, vec4 vproj1 ) +{ + // This magically solves some artifacting errors! + // + vproj1 = vproj0*(1.0-k_motion_lerp_amount) + vproj1*k_motion_lerp_amount; + + aMotionVec0 = vec3( vproj0.xy, vproj0.w ); + aMotionVec1 = vec3( vproj1.xy, vproj1.w ); +} diff --git a/src/fonts/vg_font_thin_3.png b/src/fonts/vg_font_thin_3.png index ad1f1f4..d3239dd 100644 Binary files a/src/fonts/vg_font_thin_3.png and b/src/fonts/vg_font_thin_3.png differ diff --git a/src/fonts/vg_font_thin_3.xcf b/src/fonts/vg_font_thin_3.xcf index 20f4098..66e8368 100644 Binary files a/src/fonts/vg_font_thin_3.xcf and b/src/fonts/vg_font_thin_3.xcf differ diff --git a/vg_async.c b/vg_async.c index 0d7809d..9a3da53 100644 --- a/vg_async.c +++ b/vg_async.c @@ -5,9 +5,6 @@ struct vg_async vg_async; enum vg_thread_purpose vg_thread_purpose(void); enum engine_status _vg_engine_status(void); -/* - * Allocate an asynchronous call with a bit of memory - */ vg_async_item *vg_async_alloc( u32 size ) { /* ditch out here if engine crashed. this serves as the 'quit checking' */ @@ -21,16 +18,19 @@ vg_async_item *vg_async_alloc( u32 size ) remaining = vg_linear_remaining( vg_async.buffer ), capacity = vg_linear_get_capacity( vg_async.buffer ); - if( total_allocation > capacity ){ + if( total_allocation > capacity ) + { SDL_AtomicUnlock( &vg_async.sl_index ); - vg_error( "Requested: %umb. Buffer size: %umb\n", + vg_fatal_condition(); + vg_info( "async alloc invalid size\n" ); + vg_info( "Requested: %umb. Buffer size: %umb\n", (total_allocation/1024)/1024, (capacity/1024)/1024 ); - - vg_fatal_error( "async alloc invalid size\n" ); + vg_fatal_exit(); } - if( total_allocation > remaining ){ + if( total_allocation > remaining ) + { SDL_AtomicUnlock( &vg_async.sl_index ); SDL_SemWait( vg_async.sem_wait_for_flush ); SDL_AtomicLock( &vg_async.sl_index ); @@ -68,20 +68,14 @@ vg_async_item *vg_async_alloc( u32 size ) */ void vg_async_stall(void) { - vg_assert_thread(k_thread_purpose_loader); -#if 0 - vg_info( "async_stall: %d\n", SDL_SemValue( vg_async.sem_wait_for_flush ) ); -#endif + VG_ASSERT( vg_thread_purpose() == k_thread_purpose_loader ); SDL_SemWait( vg_async.sem_wait_for_flush ); } -/* - * Mark the call as being filled and ready to go - */ void vg_async_dispatch( vg_async_item *item, void (*runner)( void *payload, u32 size ) ) { - vg_assert_thread(k_thread_purpose_loader); + VG_ASSERT( vg_thread_purpose() == k_thread_purpose_loader ); if( SDL_SemValue(vg_async.sem_wait_for_flush) ) SDL_SemWait(vg_async.sem_wait_for_flush); @@ -90,22 +84,16 @@ void vg_async_dispatch( vg_async_item *item, SDL_AtomicUnlock( &vg_async.sl_index ); } -/* - * Make a simple async call without allocating extra. - */ void vg_async_call( void (*runner)( void *payload, u32 size ), void *payload, u32 size ) { - vg_assert_thread(k_thread_purpose_loader); + VG_ASSERT( vg_thread_purpose() == k_thread_purpose_loader ); vg_async_item *call = vg_async_alloc(0); call->payload = payload; call->size = size; vg_async_dispatch( call, runner ); } -/* - * Run as much of the async buffer as possible - */ void vg_run_async_checked(void) { SDL_AtomicLock( &vg_async.sl_index ); @@ -127,12 +115,11 @@ void vg_run_async_checked(void) } } } - else{ + else + { SDL_AtomicUnlock( &vg_async.sl_index ); return; } - - /* TODO: if exceed max frametime.... */ } if( !SDL_SemValue( vg_async.sem_wait_for_flush ) ){ diff --git a/vg_async.h b/vg_async.h index e0a2528..7f28c8e 100644 --- a/vg_async.h +++ b/vg_async.h @@ -10,7 +10,8 @@ static void vg_assert_thread( enum vg_thread_purpose required ); typedef struct vg_async_item vg_async_item; -struct vg_async_item{ +struct vg_async_item +{ vg_async_item *next; void *payload; @@ -30,14 +31,24 @@ struct vg_async } extern vg_async; -/* TODO: Docu */ +void vg_async_init(void); +/* + * Make a simple async call without allocating extra. + */ void vg_async_call( void (*runner)( void *payload, u32 size ), void *payload, u32 size ); + +/* + * Run as much of the async buffer as possible + */ void vg_run_async_checked(void); -void vg_async_init(void); +/* + * Allocate an asynchronous call with a bit of memory + */ vg_async_item *vg_async_alloc( u32 size ); + /* * Mark the call as being filled and ready to go */ diff --git a/vg_audio.c b/vg_audio.c index de57c9f..b3e79be 100644 --- a/vg_audio.c +++ b/vg_audio.c @@ -26,6 +26,28 @@ static struct vg_profile vg_prof_audio_mix, vg_prof_audio_dsp; + +static f64 _vg_audio_budget() +{ + audio_lock(); + f64 ms = ((double)vg_audio.samples_last / 44100.0) * 1000.0; + audio_unlock(); + + return ms; +} + +struct vg_profile_set static _vg_prof_audio = +{ + .name = "audio", + .count = 3, + .get_budget = _vg_audio_budget, + .list = + { + &vg_prof_audio_decode, &vg_prof_audio_mix, &vg_prof_audio_dsp + } +}; + + /* * These functions are called from the main thread and used to prevent bad * access. TODO: They should be no-ops in release builds. @@ -104,42 +126,6 @@ void vg_audio_device_init(void) } } -void vg_audio_register(void) -{ - vg_console_reg_var( "debug_audio", &vg_audio.debug_ui, - k_var_dtype_i32, VG_VAR_CHEAT ); - vg_console_reg_var( "debug_dsp", &vg_audio.debug_dsp, - k_var_dtype_i32, VG_VAR_CHEAT ); - vg_console_reg_var( "volume", &vg_audio.external_global_volume, - k_var_dtype_f32, VG_VAR_PERSISTENT ); - vg_console_reg_var( "vg_audio_device", &vg_audio.device_choice, - k_var_dtype_str, VG_VAR_PERSISTENT ); - vg_console_reg_var( "vg_dsp", &vg_audio.dsp_enabled, - k_var_dtype_i32, VG_VAR_PERSISTENT ); -} - -void vg_audio_init(void) -{ - /* allocate memory */ - /* 32mb fixed */ - vg_audio.audio_pool = - vg_create_linear_allocator( vg_mem.rtmemory, 1024*1024*32, - VG_MEMORY_SYSTEM ); - - /* fixed */ - u32 decode_size = AUDIO_DECODE_SIZE * AUDIO_CHANNELS; - vg_audio.decode_buffer = vg_linear_alloc( vg_mem.rtmemory, decode_size ); - - vg_dsp_init(); - vg_audio_device_init(); -} - -void vg_audio_free(void) -{ - vg_dsp_free(); - SDL_CloseAudioDevice( vg_audio.sdl_output_device ); -} - /* * thread 1 */ @@ -631,22 +617,22 @@ static void audio_channel_mix( audio_channel *ch, float *buffer ) if( !vg_validf( framevol_l ) || !vg_validf( framevol_r ) || - !vg_validf( frame_samplerate ) ){ - vg_fatal_error( "Invalid sampling conditions.\n" - "This crash is to protect your ears.\n" - " channel: %p (%s)\n" - " sample_rate: %f\n" - " volume: L%f R%f\n" - " listener: %.2f %.2f %.2f [%.2f %.2f %.2f]\n", - ch, ch->name, frame_samplerate, - framevol_l, framevol_r, - vg_audio.internal_listener_pos[0], - vg_audio.internal_listener_pos[1], - vg_audio.internal_listener_pos[2], - vg_audio.internal_listener_ears[0], - vg_audio.internal_listener_ears[1], - vg_audio.internal_listener_ears[2] - ); + !vg_validf( frame_samplerate ) ) + { + vg_fatal_condition(); + vg_info( "Invalid sampling conditions.\n" + "This crash is to protect your ears.\n" ); + vg_info( " channel: %p (%s)\n", ch, ch->name ); + vg_info( " sample_rate: %f\n", frame_samplerate ); + vg_info( " volume: L%f R%f\n", framevol_l, framevol_r ); + vg_info( " listener: %.2f %.2f %.2f [%.2f %.2f %.2f]\n", + vg_audio.internal_listener_pos[0], + vg_audio.internal_listener_pos[1], + vg_audio.internal_listener_pos[2], + vg_audio.internal_listener_ears[0], + vg_audio.internal_listener_ears[1], + vg_audio.internal_listener_ears[2] ); + vg_fatal_exit(); } } @@ -715,7 +701,8 @@ static void audio_channel_mix( audio_channel *ch, float *buffer ) vg_profile_end( &_vg_prof_audio_mix ); } -static void audio_mixer_callback( void *user, u8 *stream, int byte_count ){ +static void audio_mixer_callback( void *user, u8 *stream, int byte_count ) +{ /* * Copy data and move edit flags to commit flags * ------------------------------------------------------------- */ @@ -728,13 +715,15 @@ static void audio_mixer_callback( void *user, u8 *stream, int byte_count ){ vg_audio.internal_listener_velocity ); vg_audio.internal_global_volume = vg_audio.external_global_volume; - for( int i=0; iallocated ) continue; - if( ch->activity == k_channel_activity_alive ){ + if( ch->activity == k_channel_activity_alive ) + { if( (ch->cursor >= ch->source_length) && !(ch->flags & AUDIO_FLAG_LOOP) ) { @@ -756,7 +745,8 @@ static void audio_mixer_callback( void *user, u8 *stream, int byte_count ){ } /* process new channels */ - if( ch->activity == k_channel_activity_reset ){ + if( ch->activity == k_channel_activity_reset ) + { ch->_ = ch->editable_state; ch->cursor = 0; ch->source_length = 0; @@ -769,23 +759,25 @@ static void audio_mixer_callback( void *user, u8 *stream, int byte_count ){ ch->editable_state.relinquished = ch->_.relinquished; - if( ch->editble_state_write_mask & AUDIO_EDIT_VOLUME ){ + if( ch->editble_state_write_mask & AUDIO_EDIT_VOLUME ) + { ch->_.volume = ch->editable_state.volume; ch->_.volume_target = ch->editable_state.volume; } - else{ + else ch->editable_state.volume = ch->_.volume; - } - if( ch->editble_state_write_mask & AUDIO_EDIT_VOLUME_SLOPE ){ + if( ch->editble_state_write_mask & AUDIO_EDIT_VOLUME_SLOPE ) + { ch->volume_movement_start = ch->_.volume; ch->volume_movement = 0; ch->_.volume_target = ch->editable_state.volume_target; ch->_.volume_rate = ch->editable_state.volume_rate; } - else{ + else + { ch->editable_state.volume_target = ch->_.volume_target; ch->editable_state.volume_rate = ch->_.volume_rate; } @@ -797,7 +789,8 @@ static void audio_mixer_callback( void *user, u8 *stream, int byte_count ){ ch->editable_state.sampling_rate = ch->_.sampling_rate; - if( ch->editble_state_write_mask & AUDIO_EDIT_LFO_ATTACHMENT ){ + if( ch->editble_state_write_mask & AUDIO_EDIT_LFO_ATTACHMENT ) + { ch->_.lfo = ch->editable_state.lfo; ch->_.lfo_amount = ch->editable_state.lfo_amount; } @@ -819,13 +812,16 @@ static void audio_mixer_callback( void *user, u8 *stream, int byte_count ){ ch->editble_state_write_mask = 0x00; } - for( int i=0; ieditble_state_write_mask & AUDIO_EDIT_LFO_WAVE ){ + if( lfo->editble_state_write_mask & AUDIO_EDIT_LFO_WAVE ) + { lfo->_.wave_type = lfo->editable_state.wave_type; - if( lfo->_.wave_type == k_lfo_polynomial_bipolar ){ + if( lfo->_.wave_type == k_lfo_polynomial_bipolar ) + { lfo->_.polynomial_coefficient = lfo->editable_state.polynomial_coefficient; lfo->sqrt_polynomial_coefficient = @@ -833,15 +829,18 @@ static void audio_mixer_callback( void *user, u8 *stream, int byte_count ){ } } - if( lfo->editble_state_write_mask & AUDIO_EDIT_LFO_PERIOD ){ - if( lfo->_.period ){ + if( lfo->editble_state_write_mask & AUDIO_EDIT_LFO_PERIOD ) + { + if( lfo->_.period ) + { float t = lfo->time; t/= (float)lfo->_.period; lfo->_.period = lfo->editable_state.period; lfo->time = lfo->_.period * t; } - else{ + else + { lfo->time = 0; lfo->_.period = lfo->editable_state.period; } @@ -856,10 +855,12 @@ static void audio_mixer_callback( void *user, u8 *stream, int byte_count ){ /* * Process spawns * ------------------------------------------------------------- */ - for( int i=0; iactivity == k_channel_activity_wake ){ + if( ch->activity == k_channel_activity_wake ) + { if( audio_channel_load_source( ch ) ) ch->activity = k_channel_activity_alive; else @@ -948,14 +949,14 @@ static void audio_mixer_callback( void *user, u8 *stream, int byte_count ){ vg_profile_increment( &_vg_prof_audio_mix ); vg_profile_increment( &_vg_prof_dsp ); - vg_prof_audio_mix = _vg_prof_audio_mix; - vg_prof_audio_decode = _vg_prof_audio_decode; - vg_prof_audio_dsp = _vg_prof_dsp; - - vg_audio.samples_last = frame_count; + if( vg_audio.inspector_open ) + { + vg_prof_audio_mix = _vg_prof_audio_mix; + vg_prof_audio_decode = _vg_prof_audio_decode; + vg_prof_audio_dsp = _vg_prof_dsp; - if( vg_audio.debug_dsp ) - vg_dsp_update_texture(); + vg_audio.samples_last = frame_count; + } audio_unlock(); } @@ -983,9 +984,11 @@ void audio_clip_load( audio_clip *clip, void *lin_alloc ) * NULL when we get the clip */ - if( format == k_audio_format_vorbis ){ - if( !clip->path ){ - vg_fatal_error( "No path specified, embeded vorbis unsupported" ); + if( format == k_audio_format_vorbis ) + { + if( !clip->path ) + { + vg_error( "No path specified, embeded vorbis unsupported\n" ); } audio_lock(); @@ -993,17 +996,22 @@ void audio_clip_load( audio_clip *clip, void *lin_alloc ) audio_unlock(); if( !clip->data ) - vg_fatal_error( "Audio failed to load" ); + { + vg_error( "Audio failed to load\n" ); + } float mb = (float)(clip->size) / (1024.0f*1024.0f); vg_info( "Loaded audio clip '%s' (%.1fmb)\n", clip->path, mb ); } - else if( format == k_audio_format_stereo ){ - vg_fatal_error( "Unsupported format (Stereo uncompressed)" ); + else if( format == k_audio_format_stereo ) + { + vg_error( "Unsupported format (Stereo uncompressed)\n" ); } - else if( format == k_audio_format_bird ){ - if( !clip->data ){ - vg_fatal_error( "No data, external birdsynth unsupported" ); + else if( format == k_audio_format_bird ) + { + if( !clip->data ) + { + vg_error( "No data, external birdsynth unsupported\n" ); } u32 total_size = clip->size + sizeof(struct synth_bird); @@ -1011,7 +1019,9 @@ void audio_clip_load( audio_clip *clip, void *lin_alloc ) total_size = vg_align8( total_size ); if( total_size > AUDIO_DECODE_SIZE ) - vg_fatal_error( "Bird coding too long\n" ); + { + vg_error( "Bird coding too long, and exceeds maximum decode size\n" ); + } struct synth_bird *bird = vg_linear_alloc( lin_alloc, total_size ); memcpy( &bird->settings, clip->data, clip->size ); @@ -1021,9 +1031,11 @@ void audio_clip_load( audio_clip *clip, void *lin_alloc ) vg_info( "Loaded bird synthesis pattern (%u bytes)\n", total_size ); } - else{ - if( !clip->path ){ - vg_fatal_error( "No path specified, embeded mono unsupported" ); + else + { + if( !clip->path ) + { + vg_error( "No path specified, embeded mono unsupported\n" ); } vg_linear_clear( vg_mem.scratch ); @@ -1040,10 +1052,13 @@ void audio_clip_load( audio_clip *clip, void *lin_alloc ) stb_vorbis *decoder = stb_vorbis_open_memory( filedata, fsize, &err, &alloc ); - if( !decoder ){ - vg_error( "stb_vorbis_open_memory failed on '%s' (%d)\n", + if( !decoder ) + { + vg_fatal_condition(); + vg_info( "Vorbis decode error\n" ); + vg_info( "stb_vorbis_open_memory failed on '%s' (%d)\n", clip->path, err ); - vg_fatal_error( "Vorbis decode error" ); + vg_fatal_exit(); } /* only mono is supported in uncompressed */ @@ -1059,13 +1074,9 @@ void audio_clip_load( audio_clip *clip, void *lin_alloc ) decoder, clip->data, length_samples ); if( read_samples != length_samples ) - vg_fatal_error( "Decode error" ); - -#if 0 - float mb = (float)(data_size) / (1024.0f*1024.0f); - vg_info( "Loaded audio clip '%s' (%.1fmb) %u samples\n", clip->path, mb, - length_samples ); -#endif + { + vg_error( "Decode error, read_samples did not match length_samples\n" ); + } } } @@ -1081,72 +1092,47 @@ static void audio_require_clip_loaded( audio_clip *clip ) return; audio_unlock(); + vg_fatal_error( "Must load audio clip before playing! \n" ); } /* * Debugging */ +struct vg_audio_view_data +{ + i32 view_3d; +}; -void audio_debug_ui( - -#ifdef VG_3D - m4x4f -#else - m3x3f -#endif - mtx_pv ){ +static void cb_vg_audio_view( ui_context *ctx, ui_rect rect, + struct vg_magi_panel *magi ) +{ + struct vg_audio_view_data *vd = magi->data; - if( !vg_audio.debug_ui ) - return; + ui_rect left, panel; + ui_split( rect, k_ui_axis_v, 256, 2, left, panel ); + ui_checkbox( ctx, left, "3D labels", &vd->view_3d ); audio_lock(); - - glBindTexture( GL_TEXTURE_2D, vg_dsp.view_texture ); - glTexSubImage2D( GL_TEXTURE_2D, 0, 0, 0, 256, 256, - GL_RGBA, GL_UNSIGNED_BYTE, - vg_dsp.view_texture_buffer ); - - /* - * Profiler - * ----------------------------------------------------------------------- - */ - - float budget = ((double)vg_audio.samples_last / 44100.0) * 1000.0; - vg_profile_drawn( (struct vg_profile *[]){ &vg_prof_audio_decode, - &vg_prof_audio_mix, - &vg_prof_audio_dsp}, 3, - budget, (ui_rect){ 4, VG_PROFILE_SAMPLE_COUNT*2 + 8, - 512, 0 }, 0, 0 ); - - char perf[128]; - - /* Draw UI */ - ui_rect window = { - 0, - 0, - 800, - AUDIO_CHANNELS * 18 - }; - - if( vg_audio.debug_dsp ){ - ui_rect view_thing = { 4, vg.window_y-512-4, 512, 512 }; - ui_image( view_thing, vg_dsp.view_texture ); - } - ui_rect overlap_buffer[ AUDIO_CHANNELS ]; u32 overlap_length = 0; /* Draw audio stack */ - for( int i=0; iallocated ) + { + if( show_row ) + ui_fill( ctx, row, 0x50333333 ); - if( !ch->allocated ){ - ui_fill( row, 0x50333333 ); continue; } @@ -1192,18 +1178,23 @@ void audio_debug_ui( ch->editable_state.volume, ch->name ); - ui_fill( row, 0xa0000000 | ch->colour ); - ui_text( row, perf, 1, k_ui_align_middle_left, 0 ); + if( show_row ) + { + ui_fill( ctx, row, 0xa0000000 | ch->colour ); + ui_text( ctx, row, perf, 1, k_ui_align_middle_left, 0 ); + } #ifdef VG_3D - if( AUDIO_FLAG_SPACIAL_3D ){ + if( vd->view_3d && (ch->flags & AUDIO_FLAG_SPACIAL_3D) ) + { v4f wpos; v3_copy( ch->editable_state.spacial_falloff, wpos ); wpos[3] = 1.0f; - m4x4_mulv( mtx_pv, wpos, wpos ); + m4x4_mulv( vg.pv, wpos, wpos ); - if( wpos[3] > 0.0f ){ + if( wpos[3] > 0.0f ) + { v2_muls( wpos, (1.0f/wpos[3]) * 0.5f, wpos ); v2_add( wpos, (v2f){ 0.5f, 0.5f }, wpos ); @@ -1213,9 +1204,11 @@ void audio_debug_ui( wr[2] = 1000; wr[3] = 17; - for( int j=0; j<12; j++ ){ + for( int j=0; j<12; j++ ) + { int collide = 0; - for( int k=0; k= wk[0])) && ((wr[1] <= wk[1]+wk[3]) && (wr[1]+wr[3] >= wk[1])) ) @@ -1231,7 +1224,7 @@ void audio_debug_ui( wr[1] += 18; } - ui_text( wr, perf, 1, k_ui_align_middle_left, 0 ); + ui_text( ctx, wr, perf, 1, k_ui_align_middle_left, 0 ); rect_copy( wr, overlap_buffer[ overlap_length ++ ] ); } } @@ -1240,3 +1233,66 @@ void audio_debug_ui( audio_unlock(); } + +static void cb_vg_audio_close( struct vg_magi_panel *me ) +{ + vg_audio.inspector_open = 0; + free( me->data ); +} + +static int cmd_vg_audio( int argc, const char *argv[] ) +{ + if( vg_audio.inspector_open ) + { + vg_error( "Only 1 audio inspector at a time.\n" ); + return 0; + } + + vg_audio.inspector_open = 1; + ui_px w = 800, h=8*18; + struct vg_magi_panel *magi = _vg_magi_open( w,h, VG_MAGI_ALL ); + magi->title = "Audio inspector"; + + struct vg_audio_view_data *vd = malloc(sizeof(struct vg_audio_view_data)); + vd->view_3d = 0; + + magi->data = vd; + magi->ui_cb = cb_vg_audio_view; + magi->close_cb = cb_vg_audio_close; + return 1; +} + +void vg_audio_register(void) +{ + vg_console_reg_cmd( "vg_audio", cmd_vg_audio, NULL ); + vg_console_reg_var( "volume", &vg_audio.external_global_volume, + k_var_dtype_f32, VG_VAR_PERSISTENT ); + vg_console_reg_var( "vg_audio_device", &vg_audio.device_choice, + k_var_dtype_str, VG_VAR_PERSISTENT ); + vg_console_reg_var( "vg_dsp", &vg_audio.dsp_enabled, + k_var_dtype_i32, VG_VAR_PERSISTENT ); +} + +void vg_audio_init(void) +{ + _vg_profile_reg_set( &_vg_prof_audio ); + + /* allocate memory */ + /* 32mb fixed */ + vg_audio.audio_pool = + vg_create_linear_allocator( vg_mem.rtmemory, 1024*1024*32, + VG_MEMORY_SYSTEM ); + + /* fixed */ + u32 decode_size = AUDIO_DECODE_SIZE * AUDIO_CHANNELS; + vg_audio.decode_buffer = vg_linear_alloc( vg_mem.rtmemory, decode_size ); + + vg_dsp_init(); + vg_audio_device_init(); +} + +void vg_audio_free(void) +{ + SDL_CloseAudioDevice( vg_audio.sdl_output_device ); +} + diff --git a/vg_audio.h b/vg_audio.h index ba10348..f660cbb 100644 --- a/vg_audio.h +++ b/vg_audio.h @@ -51,18 +51,23 @@ typedef struct audio_lfo audio_lfo; struct audio_clip { - union { /* TODO oof.. */ - u64 _p64_; + union + { const char *path; void *func; + + u64 _p64_; /* force width to be 64 because we save this structure on + disk in Skaterift yay */ }; u32 flags; u32 size; - union{ - u64 _p64; + union + { void *data; + + u64 _p64; }; }; @@ -169,7 +174,8 @@ struct vg_audio_system } channels[ AUDIO_CHANNELS ]; - int debug_ui, debug_ui_3d, debug_dsp, dsp_enabled; + bool inspector_open; + int dsp_enabled; v3f internal_listener_pos, internal_listener_ears, @@ -218,12 +224,3 @@ int audio_oneshot( audio_clip *clip, f32 volume, f32 pan ); void audio_set_lfo_wave( int id, enum lfo_wave_type type, f32 coefficient ); void audio_set_lfo_frequency( int id, float freq ); int audio_channel_load_source( audio_channel *ch ); - -void audio_debug_ui( - -#ifdef VG_3D - m4x4f -#else - m3x3f -#endif - mtx_pv ); diff --git a/vg_audio_dsp.c b/vg_audio_dsp.c index 3714e32..cd49883 100644 --- a/vg_audio_dsp.c +++ b/vg_audio_dsp.c @@ -9,7 +9,10 @@ float *dsp_allocate( u32 samples ) samples = vg_align4( samples ); if( vg_dsp.allocations + samples > (1024*1024)/4 ) - vg_fatal_error( "too much dsp" ); + { + vg_fatal_error( "Ran out of memory in the DSP buffer\n" + " Request was %u samples\n", samples ); + } float *buf = &vg_dsp.buffer[ vg_dsp.allocations ]; vg_dsp.allocations += samples; @@ -128,22 +131,10 @@ static struct dsp_lpf __echos_lpf[8]; #endif static struct dsp_schroeder __diffusion_chain[8]; -static void async_vg_dsp_alloc_texture( void *payload, u32 size ) +void vg_dsp_init( void ) { - glGenTextures( 1, &vg_dsp.view_texture ); - glBindTexture( GL_TEXTURE_2D, vg_dsp.view_texture ); - glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA, 256, 256, 0, - GL_RGBA, GL_UNSIGNED_BYTE, vg_dsp.view_texture_buffer ); - glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST ); - glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST ); -} - -void vg_dsp_init( void ){ vg_rand_seed( &vg_dsp.rand, 461 ); vg_dsp.buffer = vg_linear_alloc( vg_mem.rtmemory, 1024*1024*1 ); - vg_dsp.view_texture_buffer = vg_linear_alloc( vg_mem.rtmemory, 512*512 ); - - vg_async_call( async_vg_dsp_alloc_texture, NULL, 0 ); /* temporary global design */ dsp_init_lpf( &__lpf_mud_free, 125.0f ); @@ -283,16 +274,3 @@ void dsp_update_tunings(void) vg_dsp.echo_tunings[i] *= volumes[i]; } } - -void vg_dsp_free( void ) -{ - glDeleteTextures( 1, &vg_dsp.view_texture ); -} - -void vg_dsp_update_texture( void ) -{ - for( int i=0; i<512*512; i++ ){ - float v = vg_clampf( vg_dsp.buffer[i] * 0.5f + 0.5f, 0.0f, 1.0f ); - vg_dsp.view_texture_buffer[i] = v * 255.0f; - } -} diff --git a/vg_audio_dsp.h b/vg_audio_dsp.h index d74bc02..4a1e9ae 100644 --- a/vg_audio_dsp.h +++ b/vg_audio_dsp.h @@ -3,7 +3,7 @@ //#define VG_ECHO_LPF_BUTTERWORTH #include "vg_platform.h" -#include "dep/glad/glad.h" +#include "vg_opengl.h" #include "vg_m.h" struct vg_dsp @@ -11,9 +11,6 @@ struct vg_dsp float *buffer; u32 allocations; - u8 *view_texture_buffer; - GLuint view_texture; - float echo_distances[14], echo_tunings[8], reverb_wet_mix, @@ -51,7 +48,6 @@ void vg_dsp_init( void ); void vg_dsp_free( void ); void dsp_update_tunings(void); void vg_dsp_process( float *stereo_in, float *stereo_out ); -void vg_dsp_update_texture( void ); f32 dsp_biquad_process( struct dsp_biquad *bq, f32 xn ); void dsp_init_biquad_butterworth_lpf( struct dsp_biquad *bq, f32 fc ); diff --git a/vg_build.h b/vg_build.h index b40ecd3..9a1d861 100644 --- a/vg_build.h +++ b/vg_build.h @@ -1,5 +1,3 @@ -/* zig cc scripting tools */ - #include #include #include @@ -14,18 +12,21 @@ /* we dont free dynamic vg_strs in this program. so, we dont care.. */ const char *__asan_default_options() { return "detect_leaks=0"; } -struct vg_env +struct vg_project { - u32 optimization; + vg_str uid, bin_folder; +}; - bool fresh, - debug_asan; +struct vg_compiler_env +{ + u32 optimization; + bool debug_asan; enum platform { k_platform_anyplatform, k_platform_windows, - k_platform_linux + k_platform_linux, } platform; @@ -41,7 +42,7 @@ struct vg_env { k_compiler_blob, k_compiler_clang, - k_compiler_zigcc + k_compiler_zigcc, } compiler; @@ -51,49 +52,40 @@ struct vg_env k_libc_version_2_23, } libc; -}; - -struct vg_env vg_test_env = { - .arch = k_architecture_x86_64, - .compiler = k_compiler_clang, - .libc = k_libc_version_native, +} +vg_test_env = +{ + .optimization = 0, .debug_asan = 1, - .fresh = 0, .platform = k_platform_linux, - .optimization = 0 -}; - -struct vg_env vg_release_env = { .arch = k_architecture_x86_64, - .compiler = k_compiler_zigcc, - .libc = k_libc_version_2_23, - .fresh = 1, + .compiler = k_compiler_clang, + .libc = k_libc_version_native +}, +vg_release_env = +{ .optimization = 3, + .debug_asan = 0, .platform = k_platform_anyplatform, - .debug_asan = 0 + .arch = k_architecture_x86_64, + .compiler = k_compiler_zigcc, + .libc = k_libc_version_2_23 }; -struct vg_project +struct vg_compiler_conf { - struct vg_env *env; - - vg_str include, /* -I */ - library, /* -L */ - link, /* -llibrary */ - sources, /* file.c obj.o */ - uid, /* env/project identifier */ - target, /* result object name */ - - /* generated */ - compiled_objects; /* space seperated paths to compiled objects */ - - enum obj_type { - k_obj_type_none, - k_obj_type_exe, - k_obj_type_obj, - k_obj_type_shared, - } - type; + vg_str include, + library, + link, + defines; +}; + +enum obj_type +{ + k_obj_type_none, + k_obj_type_exe, + k_obj_type_obj, + k_obj_type_shared, }; /* @@ -104,28 +96,21 @@ static const char *platform_names[] = { [k_platform_anyplatform] = "anyplatform", [k_platform_windows] = "windows", - [k_platform_linux] = "linux" + [k_platform_linux] = "linux", }; static const char *architecture_names[] = { [k_architecture_anyarch] = "anyarch", [k_architecture_i386] = "i386", - [k_architecture_x86_64] = "x86_64" + [k_architecture_x86_64] = "x86_64", }; static const char *compiler_names[] = { [k_compiler_blob] = "blob", [k_compiler_clang] = "clang", - [k_compiler_zigcc] = "zig-cc" -}; - -static const char *compiler_paths[] = -{ - [k_compiler_blob] = NULL, - [k_compiler_clang] = "clang", - [k_compiler_zigcc] = "zig cc" + [k_compiler_zigcc] = "zig-cc", }; static const char *libc_names[] = @@ -134,45 +119,6 @@ static const char *libc_names[] = [k_libc_version_2_23] = ".2.23" }; -/* - * source specification - * -------------------------------------------------------------------------- */ - -void vg_add_source( struct vg_project *proj, const char *source ) -{ - if( proj->type == k_obj_type_none ) - vg_fatal_error( "Cannot add source code without setting binary type\n" ); - - vg_strcat( &proj->sources, source ); - vg_strcat( &proj->sources, " " ); -} - -void vg_include_dir( struct vg_project *proj, const char *dir ) -{ - if( proj->type == k_obj_type_none ) - vg_fatal_error( "Cannot add include dir without setting binary type\n" ); - - vg_strcat( &proj->include, dir ); - vg_strcat( &proj->include, " " ); -} - -void vg_library_dir( struct vg_project *proj, const char *dir ) -{ - if( proj->type == k_obj_type_none ) - vg_fatal_error( "Cannot add library dir without setting binary type\n" ); - - vg_strcat( &proj->library, dir ); - vg_strcat( &proj->library, " " ); -} - -void vg_link( struct vg_project *proj, const char *lib ) -{ - if( proj->type == k_obj_type_none ) - vg_fatal_error( "Cannot link library without setting binary type\n" ); - - vg_strcat( &proj->link, lib ); -} - /* * OS & file tools * -------------------------------------------------------------------------- */ @@ -193,14 +139,14 @@ void vg_syscall( const char *fmt, ... ) void vg_add_blob( struct vg_project *proj, const char *blob, const char *dest ) { - vg_syscall( "cp %s bin/%s/%s", blob, proj->uid.buffer, dest ); + vg_syscall( "cp %s %s/%s", blob, proj->bin_folder.buffer, dest ); } void vg_symlink( struct vg_project *proj, const char *folder, const char *bin_name ) { char dest[512]; - snprintf( dest, 512, "bin/%s/%s", proj->uid.buffer, bin_name ); + snprintf( dest, 512, "%s/%s", proj->bin_folder.buffer, bin_name ); if( !access( dest, F_OK ) ) vg_syscall( "unlink %s", dest ); vg_syscall( "ln -srf %s %s", folder, dest ); @@ -208,207 +154,226 @@ void vg_symlink( struct vg_project *proj, void vg_tarball_project( struct vg_project *proj ) { - vg_syscall( "tar -chzvf dist/%s-%u.tar.gz bin/%s/", - proj->uid.buffer, time(NULL), proj->uid.buffer ); + vg_syscall( "tar -chzvf dist/%s-%u.tar.gz %s/", + proj->uid.buffer, time(NULL), proj->bin_folder.buffer ); +} + +bool vg_platform_posix( enum platform p ) +{ + if( p == k_platform_linux ) return 1; + else return 0; } /* - * The project configurator and compiler. + * Project * -------------------------------------------------------------------------- */ -void vg_project_new_target( struct vg_project *proj, const char *name, - enum obj_type type ) +/* Initialize the project structure, proj, + * IN folder/name, + * CLEAR IF fresh + */ +void vg_project_init( struct vg_project *proj, + const char *folder, + const char *name, + struct vg_compiler_env *env, + bool fresh ) { - proj->type = type; + vg_strnull( &proj->uid, NULL, 0 ); + vg_strcat( &proj->uid, name ); - vg_strnull( &proj->include, NULL, -1 ); - vg_strnull( &proj->library, NULL, -1 ); - vg_strnull( &proj->link, NULL, -1 ); - vg_strnull( &proj->sources, NULL, -1 ); - vg_strnull( &proj->target, NULL, -1 ); - - /* - * Setup target with appropriate extension - */ - vg_strcat( &proj->target, name ); - - if( proj->env->platform == k_platform_windows ) + if( env ) { - if( type == k_obj_type_exe ) - vg_strcat( &proj->target, ".exe" ); - else if( type == k_obj_type_shared ) - vg_strcat( &proj->target, ".dll" ); - else if( type == k_obj_type_obj ) - vg_strcat( &proj->target, ".obj" ); + vg_strcat( &proj->uid, "-" ); + vg_strcat( &proj->uid, platform_names[ env->platform ] ); + vg_strcat( &proj->uid, "-" ); + vg_strcat( &proj->uid, architecture_names[ env->arch ] ); + vg_strcat( &proj->uid, "-" ); + vg_strcat( &proj->uid, compiler_names[ env->compiler ] ); } + vg_strnull( &proj->bin_folder, NULL, 0 ); + vg_strcat( &proj->bin_folder, folder ); + vg_strcat( &proj->bin_folder, "/" ); + vg_strcat( &proj->bin_folder, proj->uid.buffer ); - if( proj->env->platform == k_platform_linux ) - { - if( type == k_obj_type_shared ) - vg_strcat( &proj->target, ".so" ); - else if( type == k_obj_type_obj ) - vg_strcat( &proj->target, ".o" ); - } + vg_info( "project_init: %s (fresh: %s)\n (%s)\n", + name, + fresh? "yes":"no", + proj->bin_folder.buffer ); - /* - * Add some regular includes / library dirs - */ - if( type != k_obj_type_none ) - { - vg_include_dir( proj, "-I." ); - vg_include_dir( proj, "-I./vg" ); - vg_library_dir( proj, "-L." ); - vg_library_dir( proj, "-L/usr/lib" ); - } - - vg_info( " New target: %s\n", name ); + if( fresh ) + vg_syscall( "rm -rf %s", proj->bin_folder.buffer ); + vg_syscall( "mkdir -p %s", proj->bin_folder.buffer ); } -void vg_project_init( struct vg_project *proj, - struct vg_env *env, - const char *identifier ) +struct compile_result { - proj->env = env; - proj->type = k_obj_type_none; - - vg_strnull( &proj->uid, NULL, -1 ); - vg_strnull( &proj->compiled_objects, NULL, -1 ); + vg_str path, + rel_path; +}; +/* run a compiler.. return compiled object relative to project folder + */ +struct compile_result +vg_compiler_run( struct vg_project *project, + struct vg_compiler_env *env, + struct vg_compiler_conf *conf, + const char *sources, + const char *target_name, + enum obj_type type ) +{ /* check for problems in configuration */ - if( env->libc != k_libc_version_native ){ - if( env->compiler != k_compiler_zigcc ){ - vg_fatal_error( + if( env->libc != k_libc_version_native ) + { + if( env->compiler != k_compiler_zigcc ) + { + vg_fatal_condition(); + vg_info( "Cannot specify libc version using the '%s' compiler.\n", compiler_names[ env->compiler ] ); + vg_fatal_exit(); } } - if( env->compiler == k_compiler_clang ){ - if( env->platform != k_platform_linux ){ - vg_fatal_error( "Cannot compile for '%s' using the '%s' compiler;" ); + if( env->compiler == k_compiler_clang ) + { + if( env->platform != k_platform_linux ) + { + vg_fatal_condition(); + vg_info( "Cannot compile for '%s' using the '%s' compiler;" ); + vg_fatal_exit(); } } - vg_strcat( &proj->uid, identifier ); - vg_strcatch( &proj->uid, '-' ); - vg_strcat( &proj->uid, platform_names[ env->platform ] ); - vg_strcatch( &proj->uid, '-' ); - vg_strcat( &proj->uid, architecture_names[ env->arch ] ); - vg_strcatch( &proj->uid, '-' ); - vg_strcat( &proj->uid, compiler_names[ env->compiler ] ); - - if( proj->uid.i < 3 ) - vg_fatal_error( "failed to create project UID\n" ); - - vg_info( "project_init: %s (%s, %s, compiler: %s, opt:%u, fresh: %s)\n", - identifier, - platform_names[env->platform], - architecture_names[env->arch], - compiler_names[env->compiler], - env->optimization, - env->fresh? "yes":"no"); - - if( env->fresh ) - vg_syscall( "rm -rf bin/%s", proj->uid.buffer ); - vg_syscall( "mkdir -p bin/%s", proj->uid.buffer ); -} - -void vg_compile_project( struct vg_project *proj ) -{ - vg_str cmd; - vg_strnull( &cmd, NULL, -1 ); + vg_str cmd = {0}; + vg_strcat( &cmd, "ccache " ); /* compiler specification */ - vg_strcat( &cmd, "ccache " ); - vg_strcat( &cmd, compiler_paths[ proj->env->compiler ] ); + + if( env->compiler == k_compiler_zigcc ) + vg_strcat( &cmd, "zig cc " ); + else if( env->compiler == k_compiler_clang ) + vg_strcat( &cmd, "clang" ); + vg_strcat( &cmd, " -std=gnu99 -D_REENTRANT \\\n" ); - if( proj->env->optimization ) + if( env->optimization ) { vg_strcat( &cmd, " -O" ); - vg_strcati32( &cmd, proj->env->optimization ); - vg_strcat( &cmd, " -flto \\\n" ); + vg_strcati32( &cmd, env->optimization ); } else { - /* add debugger / asan information */ vg_strcat( &cmd, " -O0 -ggdb3 -fno-omit-frame-pointer " ); - - if( (proj->env->compiler == k_compiler_clang) && proj->env->debug_asan ) - { - vg_strcat( &cmd, " -rdynamic -fsanitize=address -fPIE " - "-fstack-protector-strong " ); - } - vg_strcat( &cmd, "\\\n" ); } + if( (env->compiler == k_compiler_clang) && env->debug_asan ) + { + vg_strcat( &cmd, " -rdynamic -fsanitize=address -fPIE " + "-fstack-protector-strong " ); + } + + /* always want this */ + vg_strcat( &cmd, " -flto \\\n" ); + /* want a lot of warnings but not useless ones */ vg_strcat( &cmd, " -Wall -ferror-limit=8\\\n" " -Wno-unused-function -Wno-unused-variable\\\n" " -Wno-unused-command-line-argument -Wno-unused-but-set-variable\\\n" ); - if( proj->env->compiler != k_compiler_clang ) + if( env->compiler != k_compiler_clang ) vg_strcat( &cmd, " -Wno-format-truncation\\\n" ); + /* defines */ + vg_strcat( &cmd, " " ); + vg_strcat( &cmd, conf->defines.buffer ); + vg_strcat( &cmd, "\\\n" ); + /* include paths */ vg_strcat( &cmd, " " ); - vg_strcat( &cmd, proj->include.buffer ); + vg_strcat( &cmd, conf->include.buffer ); vg_strcat( &cmd, "\\\n" ); /* library paths */ vg_strcat( &cmd, " " ); - vg_strcat( &cmd, proj->library.buffer ); + vg_strcat( &cmd, conf->library.buffer ); vg_strcat( &cmd, "\\\n" ); /* sources */ vg_strcat( &cmd, " " ); - if( proj->type == k_obj_type_obj ) - vg_strcat( &cmd, "-c " ); + if( type == k_obj_type_obj ) + vg_strcat( &cmd, "-c -fPIC " ); - if( proj->type == k_obj_type_shared ) + if( type == k_obj_type_shared ) + { vg_strcat( &cmd, "-shared -fPIC " ); + } - vg_strcat( &cmd, proj->sources.buffer ); + vg_strcat( &cmd, sources ); vg_strcat( &cmd, "\\\n" ); - /* output */ - vg_strcat( &cmd, " -o bin/" ); - vg_strcat( &cmd, proj->uid.buffer ); - vg_strcat( &cmd, "/" ); - vg_strcat( &cmd, proj->target.buffer ); + struct compile_result res = {0}; + + vg_strcat( &res.rel_path, target_name ); + + if( env->platform == k_platform_windows ) + { + if( type == k_obj_type_exe ) + vg_strcat( &res.rel_path, ".exe" ); + else if( type == k_obj_type_shared ) + vg_strcat( &res.rel_path, ".dll" ); + else if( type == k_obj_type_obj ) + vg_strcat( &res.rel_path, ".obj" ); + } + + if( vg_platform_posix( env->platform ) ) + { + if( type == k_obj_type_shared ) + vg_strcat( &res.rel_path, ".so" ); + else if( type == k_obj_type_obj ) + vg_strcat( &res.rel_path, ".o" ); + } + + vg_strcat( &res.path, project->bin_folder.buffer ); + vg_strcat( &res.path, "/" ); + vg_strcat( &res.path, res.rel_path.buffer ); + + vg_strcat( &cmd, " -o " ); + vg_strcat( &cmd, res.path.buffer ); vg_strcat( &cmd, "\\\n" ); /* link */ vg_strcat( &cmd, " " ); - vg_strcat( &cmd, proj->link.buffer ); + vg_strcat( &cmd, conf->link.buffer ); vg_strcat( &cmd, "\\\n" ); - if( proj->type == k_obj_type_exe ) + if( type == k_obj_type_exe ) { vg_strcat( &cmd, " -Wl,-rpath=./\\\n" ); } - /* target platform specification (zig-cc only) */ - if( proj->env->compiler == k_compiler_zigcc ){ + /* platform specification (zig-cc only) */ + if( env->compiler == k_compiler_zigcc ) + { vg_strcat( &cmd, " -target " ); - vg_strcat( &cmd, architecture_names[proj->env->arch] ); + vg_strcat( &cmd, architecture_names[env->arch] ); vg_strcat( &cmd, "-" ); - vg_strcat( &cmd, platform_names[proj->env->platform] ); + vg_strcat( &cmd, platform_names[env->platform] ); - if( proj->env->platform == k_platform_linux ){ + if( env->platform == k_platform_linux ) + { vg_strcat( &cmd, "-gnu" ); - vg_strcat( &cmd, libc_names[proj->env->libc] ); + vg_strcat( &cmd, libc_names[env->libc] ); } - if( proj->env->platform == k_platform_windows ) + if( env->platform == k_platform_windows ) { /* we currently dont want pdb pretty much ever. goodbye! */ - if( proj->type == k_obj_type_exe ) + if( type == k_obj_type_exe ) { vg_strcat( &cmd, " /pdb:/dev/null" ); vg_strcat( &cmd, " /SUBSYSTEM:windows" ); @@ -417,13 +382,7 @@ void vg_compile_project( struct vg_project *proj ) } vg_syscall( cmd.buffer ); - - /* add to results */ - vg_strcat( &proj->compiled_objects, "bin/" ); - vg_strcat( &proj->compiled_objects, proj->uid.buffer ); - vg_strcat( &proj->compiled_objects, "/" ); - vg_strcat( &proj->compiled_objects, proj->target.buffer ); - vg_strcat( &proj->compiled_objects, " \\\n " ); + return res; } /* @@ -432,12 +391,16 @@ void vg_compile_project( struct vg_project *proj ) struct vg_engine_config { - bool use_3d, legacy_support_vg_msg1, log_source_info, steam_api, + bool use_3d, + legacy_support_vg_msg1, + log_source_info, + steam_api, custom_game_settings, custom_shaders; i32 fixed_update_hz; } -vg_engine_default_config = { +vg_engine_default_config = +{ .use_3d = 1, .fixed_update_hz = 60, .legacy_support_vg_msg1 = 0, @@ -447,98 +410,125 @@ vg_engine_default_config = { .custom_shaders = 0 }; -void vg_add_engine( struct vg_project *proj, struct vg_engine_config *config ) +struct compile_result +vg_make_app( struct vg_project *proj, + struct vg_engine_config *vg_conf, + struct vg_compiler_env *env, + struct vg_compiler_conf *conf, + const char *sources, + const char *appname ) { + struct vg_project vg_proj; + vg_project_init( &vg_proj, "bin", ".vg", env, 0 ); + /* building assets */ vg_build_default_font(); - if( !config ) config = &vg_engine_default_config; - vg_str config_string; - vg_strnull( &config_string, NULL, -1 ); - vg_strcat( &config_string, config->use_3d? "-DVG_3D \\\n": "-DVG_2D \\\n" ); - vg_strcat( &config_string, "-DVG_TIMESTEP_FIXED=\"(1.0/" ); - vg_strcati32( &config_string, config->fixed_update_hz ); - vg_strcat( &config_string, ".0)\" \\\n" ); - if( config->legacy_support_vg_msg1 ) - vg_strcat( &config_string, "-DVG_MSG_V1_SUPPORT \\\n" ); - if( config->log_source_info ) - vg_strcat( &config_string, "-DVG_LOG_SOURCE_INFO \\\n" ); - if( config->custom_game_settings ) - vg_strcat( &config_string, "-DVG_GAME_SETTINGS \\\n" ); - if( config->custom_shaders ) - vg_strcat( &config_string, "-DVG_CUSTOM_SHADERS \\\n" ); - - vg_strcat( &config_string, "\\\n" ); - - /* compile heavy dependencies seperately */ - struct vg_project dep_proj; - struct vg_env env = *proj->env; - env.optimization = 3; - env.debug_asan = 0; - - vg_project_init( &dep_proj, proj->env, "vg" ); + /* add config defines to compiler config */ + if( !vg_conf ) vg_conf = &vg_engine_default_config; + vg_strcat( &conf->defines, vg_conf->use_3d? "-DVG_3D \\\n": "-DVG_2D \\\n" ); + vg_strcatf( &conf->defines, "-DVG_TIMESTEP_FIXED=\"(1.0/%d.0)\" \\\n", + vg_conf->fixed_update_hz ); + + if( vg_conf->legacy_support_vg_msg1 ) + vg_strcat( &conf->defines, "-DVG_MSG_V1_SUPPORT \\\n" ); + + if( vg_conf->log_source_info ) + vg_strcat( &conf->defines, "-DVG_LOG_SOURCE_INFO \\\n" ); + + if( vg_conf->custom_game_settings ) + vg_strcat( &conf->defines, "-DVG_GAME_SETTINGS \\\n" ); + + if( vg_conf->custom_shaders ) + vg_strcat( &conf->defines, "-DVG_CUSTOM_SHADERS \\\n" ); + + if( env->arch == k_architecture_i386 ) + vg_strcat( &conf->defines, "-DVG_32 \\\n" ); + else + vg_strcat( &conf->defines, "-DVG_64 \\\n" ); + + vg_strcat( &conf->defines, "\\\n" ); + vg_strcat( &conf->include, "-I. -I./vg -I./vg/dep " ); + + /* compile all the components + * ----------------------------------------------------------------------- */ + vg_str components = {0}; + vg_strcatf( &components, "%s ", sources ); + + struct vg_compiler_env denv = *env; + //denv.optimization = 3; /* external dependencies */ - vg_project_new_target( &dep_proj, "vg_deps", k_obj_type_obj ); - vg_add_source( &dep_proj, "vg/vg_depencies.c" ); - vg_compile_project( &dep_proj ); + struct compile_result depencies = + vg_compiler_run( &vg_proj, &denv, conf, "vg/vg_depencies.c", + "vg_deps", k_obj_type_obj ); + vg_strcatf( &components, "%s ", depencies.path ); /* glad */ - vg_project_new_target( &dep_proj, "vg_glad", k_obj_type_obj ); - vg_add_source( &dep_proj, "vg/dep/glad/glad.c" ); - vg_include_dir( &dep_proj, "-I./vg/dep " ); - vg_compile_project( &dep_proj ); + struct compile_result glad = + vg_compiler_run( &vg_proj, &denv, conf, "vg/dep/glad/glad.c", + "vg_glad", k_obj_type_obj ); + vg_strcatf( &components, "%s ", glad.path ); /* core engine */ - vg_project_new_target( &dep_proj, "vg_engine_core", k_obj_type_obj ); - vg_add_source( &dep_proj, config_string.buffer ); - vg_add_source( &dep_proj, "vg/vg_engine.c" ); - vg_include_dir( &dep_proj, "-I./vg/dep " ); - vg_compile_project( &dep_proj ); + struct compile_result vg = + vg_compiler_run( &vg_proj, &denv, conf, "vg/vg_engine.c", + "vg_engine_core", k_obj_type_obj ); + vg_strcatf( &components, "%s ", vg.path ); /* steamworks */ - if( config->steam_api ) + if( vg_conf->steam_api ) { - vg_project_new_target( &dep_proj, "vg_steam", k_obj_type_obj ); - vg_add_source( &dep_proj, "vg/vg_steam.c" ); - vg_compile_project( &dep_proj ); + struct compile_result steam = + vg_compiler_run( &vg_proj, &denv, conf, "vg/vg_steam.c", + "vg_steam", k_obj_type_obj ); + vg_strcatf( &components, "%s ", steam.path ); - if( proj->env->platform == k_platform_linux ) + if( env->platform == k_platform_linux ) { vg_add_blob( proj, "vg/dep/steam/libsteam_api.so", "" ); - vg_link( proj, "-lsteam_api " ); + vg_strcat( &conf->link, "-lsteam_api " ); } - else if( proj->env->platform == k_platform_windows ) + else if( env->platform == k_platform_windows ) { vg_add_blob( proj, "vg/dep/steam/steam_api64.dll", "" ); - vg_link( proj, "vg/dep/steam/steam_api64.dll " ); + vg_strcat( &conf->link, "vg/dep/steam/steam_api64.dll " ); } - vg_library_dir( proj, "-L./vg/dep/steam " ); - vg_include_dir( proj, "-I./vg/dep " ); + vg_strcat( &conf->library, "-L./vg/dep/steam " ); } - /* precipitate to the client project */ + /* link */ + vg_strcat( &conf->library, "-L. -L/usr/lib " ); + vg_strcat( &conf->link, "-lm " ); - vg_link( proj, "-lm " ); - if( proj->env->platform == k_platform_linux ) + if( env->platform == k_platform_linux ) { - vg_link( proj, "-lSDL2 -lGL -lX11 -lXxf86vm " - "-lXrandr -lXi -ldl -pthread " ); + vg_strcat( &conf->link, "-lSDL2 -lGL -lX11 -lXxf86vm " + "-lXrandr -lXi -ldl -pthread " ); + + return vg_compiler_run( proj, env, conf, components.buffer, + appname, k_obj_type_exe ); } - else if( proj->env->platform == k_platform_windows ) + else if( env->platform == k_platform_windows ) { - vg_link( proj, "-lSDL2main -lSDL2 -lopengl32 \\\n" ); - vg_link( proj, "vg/dep/sdl/SDL2.dll " ); + vg_strcat( &conf->link, "-lSDL2main -lSDL2 -lopengl32 \\\n" ); + vg_strcat( &conf->link, "vg/dep/sdl/SDL2.dll " ); vg_add_blob( proj, "vg/dep/sdl/SDL2.dll ", "" ); - vg_library_dir( proj, "-L./vg/dep/sdl " ); + vg_strcat( &conf->library, "-L./vg/dep/sdl " ); + + return vg_compiler_run( proj, env, conf, components.buffer, + appname, k_obj_type_exe ); + } + else + { + vg_fatal_condition(); + vg_info( "No compile procedure set for platform '%s'\n", + platform_names[env->platform] ); + vg_fatal_exit(); } - vg_add_source( proj, config_string.buffer ); - vg_add_source( proj, dep_proj.compiled_objects.buffer ); - vg_add_source( proj, "\\\n" ); - vg_include_dir( proj, "-I./vg/dep " ); - vg_link( proj, "-lm " ); + return (struct compile_result){}; } void vg_add_controller_database( struct vg_project *proj ) diff --git a/vg_build_font.h b/vg_build_font.h index 5278ca1..e743b46 100644 --- a/vg_build_font.h +++ b/vg_build_font.h @@ -111,6 +111,7 @@ void vg_build_default_font(void) vg_build_font_face_run( &small, 'a', 'z', 0, 28 ); vg_build_font_face_run( &small, '0', '9', 208,14 ); vg_build_font_face_run( &small, 0x7f, 0xa4, 0,42 ); + vg_build_font_face_run( &small, 0xb0, 0xb3, 208,28 ); vg_build_write_font_face( fp, &small ); vg_font_face large = @@ -123,11 +124,12 @@ void vg_build_default_font(void) vg_build_font_face_run( &large, '!', '/', 12, 56 ); vg_build_font_face_run( &large, '[', '`', 192,56 ); vg_build_font_face_run( &large, '{', '~', 264,56 ); - vg_build_font_face_run( &large, ':', '@', 324,56 ); + vg_build_font_face_run( &large, ':', '@', 312,56 ); vg_build_font_face_run( &large, 'A', 'Z', 0, 77 ); vg_build_font_face_run( &large, 'a', 'z', 0, 98 ); vg_build_font_face_run( &large, '0', '9', 312,77 ); vg_build_font_face_run( &large, 0x7f, 0xa4, 0,119 ); + vg_build_font_face_run( &large, 0xb0, 0xb3, 312,98 ); vg_build_write_font_face( fp, &large ); vg_font_face title = @@ -152,6 +154,7 @@ void vg_build_default_font(void) vg_build_font_face_run( &title, 0x7f, 0x88, 120,350 ); vg_build_font_face_run( &title, 0x93, 0x98, 360,350 ); vg_build_font_face_run( &title, 0x99, 0xa4, 0,392 ); + vg_build_font_face_run( &title, 0xb0, 0xb2, 288,392 ); vg_build_write_font_face( fp, &title ); fclose( fp ); diff --git a/vg_build_utils_shader.h b/vg_build_utils_shader.h index 1e1c8b1..235c67c 100644 --- a/vg_build_utils_shader.h +++ b/vg_build_utils_shader.h @@ -136,20 +136,27 @@ static int compile_subshader( vg_str *str, char *name ) void vg_build_shader_impl( char *path ) { FILE *fp = fopen( path, "w" ); - fputs( vg_shaderbuild.code_function_body.buffer, fp ); + + if( vg_shaderbuild.code_function_body.buffer ) + fputs( vg_shaderbuild.code_function_body.buffer, fp ); fputs( "\n\n", fp ); fputs( "void vg_auto_shader_link(void)\n{\n", fp ); - fputs( vg_shaderbuild.code_link.buffer, fp ); + if( vg_shaderbuild.code_link.buffer ) + fputs( vg_shaderbuild.code_link.buffer, fp ); fputs( "}\n\n", fp ); fputs( "void vg_auto_shader_register(void)\n{\n", fp ); - fputs( vg_shaderbuild.code_register.buffer, fp ); + if( vg_shaderbuild.code_register.buffer ) + fputs( vg_shaderbuild.code_register.buffer, fp ); fputs( "}\n\n", fp ); fclose( fp ); } +#define _S( NAME, VS, FS ) \ + vg_build_shader( "shaders/" VS, "shaders/" FS, NULL, "shaders", NAME ) + int vg_build_shader( char *src_vert, /* path/to/vert.vs */ char *src_frag, /* path/to/frag.fs */ char *src_geo, /* unused currently */ @@ -158,9 +165,9 @@ int vg_build_shader( char *src_vert, /* path/to/vert.vs */ { if( !vg_shaderbuild.init ) { - vg_strnull( &vg_shaderbuild.code_link, NULL, -1 ); - vg_strnull( &vg_shaderbuild.code_register, NULL, -1 ); - vg_strnull( &vg_shaderbuild.code_function_body, NULL, -1 ); + vg_strnull( &vg_shaderbuild.code_link, NULL, 0 ); + vg_strnull( &vg_shaderbuild.code_register, NULL, 0 ); + vg_strnull( &vg_shaderbuild.code_function_body, NULL, 0 ); vg_shaderbuild.init = 1; } @@ -180,10 +187,7 @@ int vg_build_shader( char *src_vert, /* path/to/vert.vs */ FILE *header = fopen( path_header, "w" ); if( !header ) - { - fprintf(stderr, "Could not open '%s'\n", path_header ); - return 0; - } + vg_fatal_error( "Could not open '%s'\n", path_header ); fprintf( header, "#pragma once\n" ); fprintf( header, "#include \"vg/vg_engine.h\"\n" ); @@ -197,14 +201,14 @@ int vg_build_shader( char *src_vert, /* path/to/vert.vs */ if( !compile_subshader( c_body, src_vert ) ) { fclose( header ); - return 0; + vg_fatal_error( "Failed to assemble vertex source code" ); } vg_strcatf( c_body, "\n .fs = \n" ); if( !compile_subshader( c_body, src_frag ) ) { fclose( header ); - return 0; + vg_fatal_error( "Failed to assemble fragment source code" ); } vg_strcatf( c_body, "\n};\n\n" ); @@ -244,7 +248,7 @@ int vg_build_shader( char *src_vert, /* path/to/vert.vs */ if( uf->array ) continue; - for( int j=0; jdepth+1 >= vg_list_size(it->stack) ){ + if( it->depth+1 >= VG_ARRAY_LEN(it->stack) ){ vg_error( "Maximum stack reached!\n" ); return 0; } diff --git a/vg_camera.c b/vg_camera.c index 8cd261f..3828645 100644 --- a/vg_camera.c +++ b/vg_camera.c @@ -40,11 +40,13 @@ void vg_m4x3_transform_camera( m4x3f m, vg_camera *cam ) */ void vg_camera_update_transform( vg_camera *cam ) { - v4f qyaw, qpitch, qcam; + v4f qyaw, qpitch, qroll, qcam; q_axis_angle( qyaw, (v3f){ 0.0f, 1.0f, 0.0f }, -cam->angles[0] ); q_axis_angle( qpitch, (v3f){ 1.0f, 0.0f, 0.0f }, -cam->angles[1] ); + q_axis_angle( qroll, (v3f){ 0.0f, 0.0f, 1.0f }, -cam->angles[2] ); q_mul( qyaw, qpitch, qcam ); + q_mul( qcam, qroll, qcam ); q_m3x3( qcam, cam->transform ); v3_copy( cam->pos, cam->transform[3] ); } diff --git a/vg_console.c b/vg_console.c index 561bb63..a57c1a2 100644 --- a/vg_console.c +++ b/vg_console.c @@ -1,6 +1,6 @@ #include "vg_engine.h" #include "vg_console.h" -#include "vg_imgui.h" +#include "vg_ui/imgui.h" #include "vg_log.h" #include "vg_string.h" #include @@ -10,11 +10,8 @@ struct vg_console vg_console; void vg_console_reg_var( const char *alias, void *ptr, enum vg_var_dtype type, u32 flags ) { - if( vg_thread_purpose() == k_thread_purpose_main ) - vg_fatal_error( "FIXME: Cannot register VAR from main thread" ); - - if( vg_console.var_count > vg_list_size(vg_console.vars) ) - vg_fatal_error( "Too many vars registered" ); + VG_ASSERT( vg_thread_purpose() != k_thread_purpose_main ); + VG_ASSERT( vg_console.var_count < VG_ARRAY_LEN(vg_console.vars) ); vg_var *var = &vg_console.vars[ vg_console.var_count ++ ]; var->name = alias; @@ -29,11 +26,8 @@ void vg_console_reg_cmd( const char *alias, int (*function)(int argc, const char *argv[]), void (*poll_suggest)(int argc, const char *argv[]) ) { - if( vg_thread_purpose() == k_thread_purpose_main ) - vg_fatal_error( "FIXME: Cannot register CMD from main thread" ); - - if( vg_console.function_count > vg_list_size(vg_console.functions) ) - vg_fatal_error( "Too many functions registered" ); + VG_ASSERT( vg_thread_purpose() != k_thread_purpose_main ); + VG_ASSERT( vg_console.function_count < VG_ARRAY_LEN(vg_console.functions) ); vg_cmd *cmd = &vg_console.functions[ vg_console.function_count ++ ]; @@ -46,12 +40,14 @@ void vg_console_reg_cmd( const char *alias, static int _vg_console_list( int argc, char const *argv[] ) { - for( int i=0; iname ); } - for( int i=0; idata_type], @@ -65,25 +61,32 @@ static void vg_console_write_persistent(void) { FILE *fp = fopen( "cfg/auto.conf", "w" ); - if( !fp ){ + if( !fp ) + { vg_error( "Cannot open cfg/auto.conf\n" ); return; } - for( int i=0; iflags & VG_VAR_PERSISTENT ){ - if( cv->data_type == k_var_dtype_i32 ){ + if( cv->flags & VG_VAR_PERSISTENT ) + { + if( cv->data_type == k_var_dtype_i32 ) + { fprintf( fp, "%s %d\n", cv->name, *(i32 *)(cv->data) ); } - else if( cv->data_type == k_var_dtype_u32 ){ + else if( cv->data_type == k_var_dtype_u32 ) + { fprintf( fp, "%s %u\n", cv->name, *(u32 *)(cv->data) ); } - else if( cv->data_type == k_var_dtype_f32 ){ + else if( cv->data_type == k_var_dtype_f32 ) + { fprintf( fp, "%s %.5f\n", cv->name, *(float *)(cv->data ) ); } - else if( cv->data_type == k_var_dtype_str ){ + else if( cv->data_type == k_var_dtype_str ) + { vg_str *str = cv->data; if( str->buffer && (str->i > 0) ) fprintf( fp, "%s %s\n", cv->name, str->buffer ); @@ -110,9 +113,12 @@ static int vg_console_tokenize( const char *src, char *dst, int arg_count = 0, in_token = 0; - for( int i=0;; i ++ ){ - if( src[i] ){ - if( src[i] == ' ' || src[i] == '\t' ){ + for( int i=0;; i ++ ) + { + if( src[i] ) + { + if( src[i] == ' ' || src[i] == '\t' ) + { if( in_token ) dst[i] = '\0'; @@ -121,16 +127,19 @@ static int vg_console_tokenize( const char *src, char *dst, if( arg_count == 8 ) break; } - else{ + else + { dst[i] = src[i]; - if( !in_token ){ + if( !in_token ) + { args[ arg_count ++ ] = &dst[i]; in_token = 1; } } } - else{ + else + { dst[i] = '\0'; break; } @@ -141,9 +150,11 @@ static int vg_console_tokenize( const char *src, char *dst, vg_var *vg_console_match_var( const char *kw ) { - for( int i=0; iname, kw ) ){ + if( !strcmp( cv->name, kw ) ) + { return cv; } } @@ -153,9 +164,11 @@ vg_var *vg_console_match_var( const char *kw ) vg_cmd *vg_console_match_cmd( const char *kw ) { - for( int i=0; iname, kw ) ){ + if( !strcmp( cmd->name, kw ) ) + { return cmd; } } @@ -175,26 +188,33 @@ void vg_execute_console_input( const char *cmd, bool silent ) vg_var *cv = vg_console_match_var( args[0] ); vg_cmd *fn = vg_console_match_cmd( args[0] ); - if( cv ){ + if( cv ) + { /* Cvar Matched, try get value */ - if( arg_count >= 2 ){ - if( cv->flags & VG_VAR_CHEAT ){ - if( !vg_console.cheats && !silent ){ + if( arg_count >= 2 ) + { + if( cv->flags & VG_VAR_CHEAT ) + { + if( !vg_console.cheats && !silent ) + { vg_error( "variable is cheat protected\n" ); return; } } if( (cv->data_type == k_var_dtype_u32) || - (cv->data_type == k_var_dtype_i32) ){ + (cv->data_type == k_var_dtype_i32) ) + { int *ptr = cv->data; *ptr = atoi( args[1] ); } - else if( cv->data_type == k_var_dtype_f32 ){ + else if( cv->data_type == k_var_dtype_f32 ) + { float *ptr = cv->data; *ptr = atof( args[1] ); } - else if( cv->data_type == k_var_dtype_str ){ + else if( cv->data_type == k_var_dtype_str ) + { vg_str *str = cv->data; vg_strfree( str ); vg_strnull( str, NULL, -1 ); @@ -205,14 +225,16 @@ void vg_execute_console_input( const char *cmd, bool silent ) } } } - else{ + else + { if( cv->data_type == k_var_dtype_i32 ) vg_info( "= %d\n", *((int *)cv->data) ); else if( cv->data_type == k_var_dtype_u32 ) vg_info( "= %u\n", *((u32 *)cv->data) ); else if( cv->data_type == k_var_dtype_f32 ) vg_info( "= %.4f\n", *((float *)cv->data) ); - else if( cv->data_type == k_var_dtype_str ){ + else if( cv->data_type == k_var_dtype_str ) + { vg_str *str = cv->data; vg_info( "= '%s'\n", str->buffer ); } @@ -220,7 +242,8 @@ void vg_execute_console_input( const char *cmd, bool silent ) return; } - else if( fn ){ + else if( fn ) + { fn->function( arg_count-1, args+1 ); return; } @@ -229,7 +252,8 @@ void vg_execute_console_input( const char *cmd, bool silent ) vg_error( "No command/var named '%s'. Use 'list' to view all\n",args[0]); } -u32 str_lev_distance( const char *s1, const char *s2 ){ +u32 str_lev_distance( const char *s1, const char *s2 ) +{ u32 m = strlen( s1 ), n = strlen( s2 ); @@ -264,18 +288,22 @@ u32 str_lev_distance( const char *s1, const char *s2 ){ return costs[n]; } -u32 str_lcs( const char *s1, const char *s2 ){ +u32 str_lcs( const char *s1, const char *s2 ) +{ u32 m = VG_MIN( 31, strlen( s1 ) ), n = VG_MIN( 31, strlen( s2 ) ); int suff[32][32], result = 0; - for( int i=0; i<=m; i++ ){ - for( int j=0; j<=n; j++ ){ + for( int i=0; i<=m; i++ ) + { + for( int j=0; j<=n; j++ ) + { if( i == 0 || j == 0 ) suff[i][j] = 0; - else if( s1[i-1] == s2[j-1] ){ + else if( s1[i-1] == s2[j-1] ) + { suff[i][j] = suff[i-1][j-1] + 1; result = VG_MAX( result, suff[i][j] ); } @@ -291,6 +319,9 @@ u32 str_lcs( const char *s1, const char *s2 ){ void console_suggest_score_text( const char *str, const char *input, int minscore ) { + if( !str ) + return; + /* filter duplicates */ for( int i=0; ibest_pos; j -- ) vg_console.suggestions[j] = vg_console.suggestions[j-1]; @@ -319,15 +351,15 @@ void console_suggest_score_text( const char *str, const char *input, vg_console.suggestions[ best_pos ].lev_score = score; if( vg_console.suggestion_count < - vg_list_size( vg_console.suggestions ) ) + VG_ARRAY_LEN( vg_console.suggestions ) ) vg_console.suggestion_count ++; } } -static void console_update_suggestions(void) +static void console_update_suggestions( ui_context *ctx ) { - if( vg_ui.focused_control_type != k_ui_control_textbox || - vg_ui.textbuf != vg_console.input ) + if( ctx->focused_control_type != k_ui_control_textbox || + ctx->textbuf != vg_console.input ) return; vg_console.suggestion_count = 0; @@ -341,12 +373,12 @@ static void console_update_suggestions(void) * - cursors should match */ - if( vg_ui.textbox.cursor_pos == 0 ) return; - if( vg_ui.textbox.cursor_pos != vg_ui.textbox.cursor_user ) return; - if( vg_console.input[ vg_ui.textbox.cursor_pos ] != '\0' ) return; + if( ctx->textbox.cursor_pos == 0 ) return; + if( ctx->textbox.cursor_pos != ctx->textbox.cursor_user ) return; + if( vg_console.input[ ctx->textbox.cursor_pos ] != '\0' ) return; - if( (vg_console.input[ vg_ui.textbox.cursor_pos -1 ] == ' ') || - (vg_console.input[ vg_ui.textbox.cursor_pos -1 ] == '\t') ) + if( (vg_console.input[ ctx->textbox.cursor_pos -1 ] == ' ') || + (vg_console.input[ ctx->textbox.cursor_pos -1 ] == '\t') ) return; char temp[128]; @@ -357,18 +389,22 @@ static void console_update_suggestions(void) vg_console.suggestion_pastepos = args[token_count-1]-temp; /* Score all our commands and cvars */ - if( token_count == 1 ){ - for( int i=0; iname, args[0], 1 ); } - for( int i=0; iname, args[0], 1 ); } } - else{ + else + { vg_cmd *cmd = vg_console_match_cmd( args[0] ); vg_var *var = vg_console_match_var( args[0] ); @@ -378,7 +414,8 @@ static void console_update_suggestions(void) } /* some post processing */ - for( int i=0; itextbox.cursor_user, + &ctx->textbox.cursor_pos, 10000, 1 ); } - else{ + else + { strncpy( target, vg_console.suggestions[ vg_console.suggestion_select ].str, - vg_list_size( vg_console.input )-1 ); + VG_ARRAY_LEN( vg_console.input )-1 ); - _ui_textbox_move_cursor( &vg_ui.textbox.cursor_user, - &vg_ui.textbox.cursor_pos, 10000, 1 ); - _ui_textbox_put_char( ' ' ); + _ui_textbox_move_cursor( ctx, + &ctx->textbox.cursor_user, + &ctx->textbox.cursor_pos, 10000, 1 ); + _ui_textbox_put_char( ctx, ' ' ); } } static void _console_suggest_store_normal(void) { - if( vg_console.suggestion_select == -1 ){ + if( vg_console.suggestion_select == -1 ) + { char *target = &vg_console.input[ vg_console.suggestion_pastepos ]; strcpy( vg_console.input_copy, target ); } } -static void console_suggest_next(void) +void console_suggest_next( ui_context *ctx ) { - if( vg_console.suggestion_count ){ + if( vg_console.suggestion_count ) + { _console_suggest_store_normal(); vg_console.suggestion_select ++; @@ -432,13 +475,14 @@ static void console_suggest_next(void) if( vg_console.suggestion_select >= vg_console.suggestion_count ) vg_console.suggestion_select = -1; - _console_fetch_suggestion(); + _console_fetch_suggestion( ctx ); } } -static void console_suggest_prev(void) +void console_suggest_prev( ui_context *ctx ) { - if( vg_console.suggestion_count ){ + if( vg_console.suggestion_count ) + { _console_suggest_store_normal(); vg_console.suggestion_select --; @@ -446,14 +490,15 @@ static void console_suggest_prev(void) if( vg_console.suggestion_select < -1 ) vg_console.suggestion_select = vg_console.suggestion_count-1; - _console_fetch_suggestion(); + _console_fetch_suggestion( ctx ); } } -static void _vg_console_on_update( char *buf, u32 len ) +static void _vg_console_on_update( ui_context *ctx, char *buf, u32 len ) { - if( buf == vg_console.input ){ - console_update_suggestions(); + if( buf == vg_console.input ) + { + console_update_suggestions( ctx ); } } @@ -464,13 +509,14 @@ static void console_history_get( char* buf, int entry_num ) int offset = VG_MIN( entry_num, vg_console.history_count -1 ), pick = (vg_console.history_last - offset) % - vg_list_size( vg_console.history ); + VG_ARRAY_LEN( vg_console.history ); strcpy( buf, vg_console.history[ pick ] ); } -static void _vg_console_on_up( char *buf, u32 len ) +static void _vg_console_on_up( ui_context *ctx, char *buf, u32 len ) { - if( buf == vg_console.input ){ + if( buf == vg_console.input ) + { vg_console.history_pos = VG_MAX ( @@ -480,34 +526,38 @@ static void _vg_console_on_up( char *buf, u32 len ) vg_console.history_pos+1, VG_MIN ( - vg_list_size( vg_console.history ), + VG_ARRAY_LEN( vg_console.history ), vg_console.history_count - 1 ) ) ); console_history_get( vg_console.input, vg_console.history_pos ); - _ui_textbox_move_cursor( &vg_ui.textbox.cursor_user, - &vg_ui.textbox.cursor_pos, - vg_list_size(vg_console.input)-1, 1); + _ui_textbox_move_cursor( ctx, + &ctx->textbox.cursor_user, + &ctx->textbox.cursor_pos, + VG_ARRAY_LEN(vg_console.input)-1, 1 ); } } -static void _vg_console_on_down( char *buf, u32 len ) +static void _vg_console_on_down( ui_context *ctx, char *buf, u32 len ) { - if( buf == vg_console.input ){ + if( buf == vg_console.input ) + { vg_console.history_pos = VG_MAX( 0, vg_console.history_pos-1 ); console_history_get( vg_console.input, vg_console.history_pos ); - _ui_textbox_move_cursor( &vg_ui.textbox.cursor_user, - &vg_ui.textbox.cursor_pos, - vg_list_size(vg_console.input)-1, 1 ); + _ui_textbox_move_cursor( ctx, + &ctx->textbox.cursor_user, + &ctx->textbox.cursor_pos, + VG_ARRAY_LEN(vg_console.input)-1, 1 ); } } -static void _vg_console_on_enter( char *buf, u32 len ) +static void _vg_console_on_enter( ui_context *ctx, char *buf, u32 len ) { - if( buf == vg_console.input ){ + if( buf == vg_console.input ) + { if( !strlen( vg_console.input ) ) return; @@ -517,9 +567,9 @@ static void _vg_console_on_enter( char *buf, u32 len ) vg_console.history[ vg_console.history_last ]) ) { vg_console.history_last = ( vg_console.history_last + 1) % - vg_list_size(vg_console.history ); + VG_ARRAY_LEN(vg_console.history ); vg_console.history_count = - VG_MIN( vg_list_size( vg_console.history ), + VG_MIN( VG_ARRAY_LEN( vg_console.history ), vg_console.history_count + 1 ); strcpy( vg_console.history[ vg_console.history_last ], vg_console.input ); @@ -527,12 +577,15 @@ static void _vg_console_on_enter( char *buf, u32 len ) vg_console.history_pos = -1; vg_execute_console_input( vg_console.input, 0 ); - _ui_textbox_move_cursor( &vg_ui.textbox.cursor_user, - &vg_ui.textbox.cursor_pos, -10000, 1 ); + _ui_textbox_move_cursor( ctx, + &ctx->textbox.cursor_user, + &ctx->textbox.cursor_pos, -10000, 1 ); vg_console.input[0] = '\0'; - console_update_suggestions(); + console_update_suggestions( ctx ); } + + vg_console.auto_focus = 1; } int vg_console_exec( int argc, const char *argv[] ) @@ -547,23 +600,29 @@ int vg_console_exec( int argc, const char *argv[] ) strncat( path, argv[0], 250 ); FILE *fp = fopen( path, "r" ); - if( fp ){ + if( fp ) + { char line[256]; - while( fgets( line, sizeof( line ), fp ) ){ + while( fgets( line, sizeof( line ), fp ) ) + { line[ strcspn( line, "\r\n#" ) ] = 0x00; - if( line[0] != 0x00 ){ + if( line[0] != 0x00 ) + { + strcpy( vg_console.input, line ); vg_execute_console_input( line, silent ); } } fclose( fp ); } - else{ + else + { vg_error( "Could not open '%s'\n", path ); } + vg_console.input[0] = '\0'; return 0; } @@ -586,14 +645,14 @@ void vg_console_load_autos(void) vg_console_exec( 2, (const char *[]){ "auto.conf", "silent" } ); } -void vg_console_draw(void) +void vg_console_draw( ui_context *ctx ) { if( !vg_console.enabled ) return; SDL_AtomicLock( &vg_log.print_sl ); int ptr = vg_log.log_line_current; - int const fh = vg_ui.font->sy, log_lines = 32; + int const fh = ctx->font->sy, log_lines = 32; int console_lines = VG_MIN( log_lines, vg_log.log_line_count ); ui_rect rect_log = { 0, 0, vg.window_x, log_lines*fh }, @@ -603,33 +662,38 @@ void vg_console_draw(void) /* * log */ - u32 bg_colour = (ui_colour( k_ui_bg )&0x00ffffff)|0x9f000000; + u32 bg_colour = (ui_colour( ctx, k_ui_bg )&0x00ffffff)|0x9f000000; - ui_fill( rect_log, bg_colour ); + ui_fill( ctx, rect_log, bg_colour ); rect_line[1] = rect_log[1]+rect_log[3]-fh; - for( int i=0; isx*vg_console.suggestion_pastepos; + rect_suggest[0] += 6 + ctx->font->sx*vg_console.suggestion_pastepos; rect_suggest[1] += rect_input[3]; - rect_suggest[2] = vg_ui.font->sx * vg_console.suggestion_maxlen; + rect_suggest[2] = ctx->font->sx * vg_console.suggestion_maxlen; rect_suggest[3] = vg_console.suggestion_count * fh; - ui_fill( rect_suggest, bg_colour ); + ui_fill( ctx, rect_suggest, bg_colour ); rect_suggest[3] = fh; - for( int i=0; i +#include "vg/vg_ui/imgui.c" +#include "vg/vg_ui/imgui_impl_opengl.c" +#include "vg/vg_default_font.gc" enum engine_status _vg_engine_status(void) { @@ -21,22 +25,21 @@ enum vg_thread_purpose vg_thread_purpose(void) { SDL_AtomicLock( &vg.sl_status ); - if( vg.thread_id_main == SDL_GetThreadID(NULL) ){ + SDL_threadID id = SDL_GetThreadID(NULL); + if( vg.thread_id_main == id ) + { SDL_AtomicUnlock( &vg.sl_status ); return k_thread_purpose_main; } - else{ + else if( vg.thread_id_loader == id ) + { SDL_AtomicUnlock( &vg.sl_status ); return k_thread_purpose_loader; } -} - -static void vg_assert_thread( enum vg_thread_purpose required ) -{ - enum vg_thread_purpose purpose = vg_thread_purpose(); - - if( purpose != required ){ - vg_fatal_error( "thread_purpose must be %u not %u\n", required, purpose ); + else + { + SDL_AtomicUnlock( &vg.sl_status ); + return k_thread_purpose_nothing; } } @@ -47,36 +50,44 @@ static void _vg_opengl_sync_init(void) #include "vg_console.h" #include "vg_profiler.h" +#include "vg_magi.h" +#include "vg_mem_view.h" #ifndef VG_NO_AUDIO #include "vg_audio.h" #endif #include "vg_shader.h" #include "vg_tex.h" #include "vg_input.h" -#include "vg_imgui.h" +#include "vg_framebuffer.h" +#include "vg_render.h" #include "vg_lines.h" #include "vg_rigidbody_view.h" #include "vg_loader.h" #include "vg_opt.h" +#include "vg/vg_ui/imgui.h" /* Diagnostic */ static struct vg_profile vg_prof_update = {.name="update()"}, vg_prof_render = {.name="render()"}, vg_prof_swap = {.name="swap"}; -void vg_checkgl( const char *src_info ) +static f64 _vg_gameloop_budget() { - int fail = 0; + int frame_target = vg.display_refresh_rate; + if( vg.fps_limit > 0 ) frame_target = vg.fps_limit; + return (1.0/(f64)frame_target)*1000.0; +} - GLenum err; - while( (err = glGetError()) != GL_NO_ERROR ){ - vg_error( "(%s) OpenGL Error: #%d\n", src_info, err ); - fail = 1; +struct vg_profile_set static _vg_prof_gameloop = +{ + .name = "gameloop", + .get_budget = _vg_gameloop_budget, + .count = 3, + .list = + { + &vg_prof_update, &vg_prof_render, &vg_prof_swap } - - if( fail ) - vg_fatal_error( "OpenGL Error" ); -} +}; static void async_vg_bake_shaders( void *payload, u32 size ) { @@ -92,17 +103,18 @@ void vg_bake_shaders(void) void async_internal_complete( void *payload, u32 size ) { vg_success( "Internal async setup complete\n" ); - SDL_AtomicLock( &vg.sl_status ); - if( vg.engine_status == k_engine_status_crashed ){ + SDL_AtomicLock( &vg.sl_status ); + if( vg.engine_status == k_engine_status_crashed ) + { SDL_AtomicUnlock( &vg.sl_status ); return; } - else{ + else vg.engine_status = k_engine_status_running; - } - SDL_AtomicUnlock( &vg.sl_status ); + + vg_magi_restore(); } #ifdef VG_CUSTOM_SHADERS @@ -111,19 +123,32 @@ void vg_auto_shader_register(void); /* created from codegen */ static void _vg_load_full( void *data ) { +vg_info(" Copyright . . . -----, ,----- ,---. .---. \n" ); +vg_info(" 2021-2024 |\\ /| | / | | | | /| \n" ); +vg_info(" | \\ / | +-- / +----- +---' | / | \n" ); +vg_info(" | \\ / | | / | | \\ | / | \n" ); +vg_info(" | \\/ | | / | | \\ | / | \n" ); +vg_info(" ' ' '--' [] '----- '----- ' ' '---' " + "SOFTWARE\n" ); + vg_preload(); vg_tex2d_replace_with_error_async( &vg.tex_missing ); vg_async_stall(); vg_ui.tex_bg = vg.tex_missing; /* internal */ + vg_loader_step( vg_framebuffer_init, NULL ); + vg_loader_step( vg_render_init, NULL ); vg_loader_step( vg_input_init, vg_input_free ); vg_loader_step( vg_lines_init, NULL ); vg_loader_step( vg_rb_view_init, NULL ); + _vg_profile_reg_set( &_vg_prof_gameloop ); + #ifndef VG_NO_AUDIO vg_loader_step( vg_audio_init, vg_audio_free ); #endif vg_loader_step( vg_profiler_init, NULL ); + vg_loader_step( vg_mem_view_init, NULL ); /* client */ #ifdef VG_CUSTOM_SHADERS @@ -141,42 +166,51 @@ static void _vg_process_events(void) v2_zero( vg.mouse_wheel ); v2_zero( vg.mouse_delta ); - /* Update input */ - vg_process_inputs(); - /* SDL event loop */ SDL_Event event; - while( SDL_PollEvent( &event ) ){ - if( event.type == SDL_KEYDOWN ){ + while( SDL_PollEvent( &event ) ) + { + if( event.type == SDL_KEYDOWN ) + { if( vg_console.enabled && - (vg_ui.focused_control_type != k_ui_control_modal) ){ + (vg_ui.ctx.focused_control_type != k_ui_control_modal) ) + { if( event.key.keysym.sym == SDLK_ESCAPE || - event.key.keysym.scancode == SDL_SCANCODE_GRAVE ){ + event.key.keysym.scancode == SDL_SCANCODE_GRAVE ) + { vg_console.enabled = 0; - ui_defocus_all(); + ui_defocus_all( &vg_ui.ctx ); } else if( (event.key.keysym.mod & KMOD_CTRL) && - event.key.keysym.sym == SDLK_n ){ - console_suggest_next(); + event.key.keysym.sym == SDLK_n ) + { + console_suggest_next( &vg_ui.ctx ); } else if( (event.key.keysym.mod & KMOD_CTRL ) && - event.key.keysym.sym == SDLK_p ){ - console_suggest_prev(); + event.key.keysym.sym == SDLK_p ) + { + console_suggest_prev( &vg_ui.ctx ); } - else{ - ui_proc_key( event.key.keysym ); + else + { + vg_ui_handle_sdl_key( &vg_ui.ctx, event.key.keysym ); } } - else{ - if( event.key.keysym.scancode == SDL_SCANCODE_GRAVE ){ + else + { + if( event.key.keysym.scancode == SDL_SCANCODE_GRAVE ) + { + vg_console.auto_focus = 1; vg_console.enabled = 1; } - else { - ui_proc_key( event.key.keysym ); + else + { + vg_ui_handle_sdl_key( &vg_ui.ctx, event.key.keysym ); } } } - else if( event.type == SDL_MOUSEWHEEL ){ + else if( event.type == SDL_MOUSEWHEEL ) + { vg.mouse_wheel[0] += event.wheel.preciseX; vg.mouse_wheel[1] += event.wheel.preciseY; } @@ -191,36 +225,47 @@ static void _vg_process_events(void) { vg_input_controller_event( &event ); } - else if( event.type == SDL_MOUSEMOTION ){ + else if( event.type == SDL_MOUSEMOTION ) + { vg.mouse_delta[0] += event.motion.xrel; vg.mouse_delta[1] += event.motion.yrel; } - else if( event.type == SDL_WINDOWEVENT ){ - if( event.window.event == SDL_WINDOWEVENT_SIZE_CHANGED ){ + else if( event.type == SDL_WINDOWEVENT ) + { + if( event.window.event == SDL_WINDOWEVENT_SIZE_CHANGED ) + { int w, h; SDL_GL_GetDrawableSize( vg.window, &w, &h ); - if( !w || !h ){ + if( !w || !h ) + { vg_warn( "Got a invalid framebuffer size: " "%dx%d... ignoring\n", w, h ); } - else{ + else + { + + i32 delta[2] = { w - vg.window_x, h - vg.window_y }; + _vg_magi_area_change( delta ); vg.window_x = w; vg.window_y = h; - vg_framebuffer_resize(w,h); + vg_framebuffer_update_sizes(); } } - else if( event.window.event == SDL_WINDOWEVENT_CLOSE ){ + else if( event.window.event == SDL_WINDOWEVENT_CLOSE ) + { vg.window_should_close = 1; } } - else if( event.type == SDL_TEXTINPUT ){ - ui_proc_utf8( event.text.text ); + else if( event.type == SDL_TEXTINPUT ) + { + ui_proc_utf8( &vg_ui.ctx, event.text.text ); } } - SDL_GetMouseState( &vg.mouse_pos[0], &vg.mouse_pos[1] ); + vg.mouse_state = SDL_GetMouseState( &vg.mouse_pos[0], &vg.mouse_pos[1] ); + vg_process_inputs(); } static void _vg_gameloop_update(void) @@ -255,7 +300,7 @@ static void _vg_gameloop_update(void) vg_profile_end( &vg_prof_update ); } -static void vg_settings_gui(void); +static void vg_settings_gui( ui_context *ctx ); static void _vg_gameloop_render(void) { vg_profile_begin( &vg_prof_render ); @@ -269,87 +314,74 @@ static void _vg_gameloop_render(void) /* ui */ vg.engine_stage = k_engine_stage_ui; { - ui_prerender(); - if( vg_console.enabled ){ - vg_ui.ignore_input_frames = 10; - vg_gui(); - vg_ui.ignore_input_frames = 0; - vg_ui.wants_mouse = 1; - vg_console_draw(); + ui_prerender( &vg_ui.ctx ); + vg_ui_set_screen( vg.window_x, vg.window_y ); + ui_update_mouse( &vg_ui.ctx, + (ui_px[2]){ vg.mouse_pos[0], vg.mouse_pos[1] }, vg.mouse_state ); + + vg_framebuffer_ui( &vg_ui.ctx ); + + if( vg_console.enabled ) + { + ui_ignore_input_frames( &vg_ui.ctx, 10 ); + vg_gui( &vg_ui.ctx ); + ui_ignore_input_frames( &vg_ui.ctx, 0 ); + ui_capture_mouse( &vg_ui.ctx, 1 ); + vg_console_draw( &vg_ui.ctx ); } - else vg_gui(); + else vg_gui( &vg_ui.ctx ); if( vg.settings_open ) - vg_settings_gui(); - - /* vg tools */ -#ifndef VG_NO_AUDIO - audio_debug_ui( vg.pv ); -#endif + vg_settings_gui( &vg_ui.ctx ); + + _vg_magi_render( &vg_ui.ctx ); - /* profiling */ - if( vg_profiler ){ - int frame_target = vg.display_refresh_rate; - if( vg.fps_limit > 0 ) frame_target = vg.fps_limit; - vg_profile_drawn( - (struct vg_profile *[]){ - &vg_prof_update,&vg_prof_render,&vg_prof_swap}, 3, - (1.0f/(float)frame_target)*1000.0f, - (ui_rect){ 4, 4, 250, 0 }, 0, 0 - ); - char perf[256]; - - snprintf( perf, 255, - "x: %d y: %d\n" - "refresh: %d (%.1fms)\n" - "samples: %d\n" - "iterations: %d (acc: %.3fms%%)\n" - "time: real(%.2f) delta(%.2f) rate(%.2f)\n" - " extrap(%.2f) frame(%.2f) spin( "PRINTF_U64" )\n", - vg.window_x, vg.window_y, - frame_target, (1.0f/(float)frame_target)*1000.0f, - vg.samples, - vg.fixed_iterations, - (vg.time_fixed_accumulator/VG_TIMESTEP_FIXED)*100.0f, - vg.time_real, vg.time_delta, vg.time_rate, - vg.time_fixed_extrapolate, vg.time_frame_delta, - vg.time_spinning ); - - ui_text( (ui_rect){258,4,900,900},perf,1,0,k_ui_align_left); - } - ui_postrender(); + ui_postrender( &vg_ui.ctx, vg.time_frame_delta ); + vg_ui_post_update(); } } -static void vg_changevsync(void){ - if( vg.vsync && (vg.vsync_feature != k_vsync_feature_error) ){ +static void vg_changevsync(void) +{ + if( vg.vsync && (vg.vsync_feature != k_vsync_feature_error) ) + { /* turn on vsync if not enabled */ enum vsync_feature requested = k_vsync_feature_enabled; if( vg.vsync < 0 ) requested = k_vsync_feature_enabled_adaptive; - if( vg.vsync_feature != requested ){ + if( vg.vsync_feature != requested ) + { vg_info( "Setting swap interval\n" ); int swap_interval = 1; if( requested == k_vsync_feature_enabled_adaptive ) swap_interval = -1; - if( SDL_GL_SetSwapInterval( swap_interval ) == -1 ){ - if( requested == k_vsync_feature_enabled ){ - vg_error( "Vsync is not supported by your system\n" ); - vg_warn( "You may be overriding it in your" - " graphics control panel.\n" ); + if( SDL_GL_SetSwapInterval( swap_interval ) == -1 ) + { + if( requested == k_vsync_feature_enabled ) + { + ui_start_modal( &vg_ui.ctx, + "Vsync not supported on this system.\n\n" + "You may be overriding it in your" + " graphics \ncontrol panel.\n", + UI_MODAL_BAD ); } - else{ - vg_error( "Adaptive Vsync is not supported by your system\n" ); + else + { + ui_start_modal( &vg_ui.ctx, + "Adaptive Vsync is not supported by your system\n\n" + "You may be overriding it in your" + " graphics \ncontrol panel.\n", + UI_MODAL_BAD ); } vg.vsync_feature = k_vsync_feature_error; vg.vsync = 0; - /* TODO: Make popup to notify user that this happened */ } - else{ + else + { vg_success( "Vsync enabled (%d)\n", requested ); vg.vsync_feature = requested; } @@ -389,38 +421,44 @@ static int vg_framefilter( double dt ){ return 0; } -static int _vg_crashscreen(void) +static void _vg_crashscreen(void) { -#if 0 - if( vg_getkey( SDLK_ESCAPE ) ) - return 1; -#endif - glBindFramebuffer( GL_FRAMEBUFFER, 0 ); glEnable(GL_BLEND); glDisable(GL_DEPTH_TEST); glBlendFunc(GL_ONE_MINUS_DST_ALPHA, GL_DST_ALPHA); glBlendEquation(GL_FUNC_ADD); - glClearColor( 0.15f + sinf(vg.time_real)*0.1f, 0.0f, 0.0f,1.0f ); + glClearColor( 0.15f,0.0f,0.0f,1.0f ); glClear( GL_COLOR_BUFFER_BIT ); glViewport( 0,0, vg.window_x, vg.window_y ); -#if 0 - _vg_render_log(); -#endif + ui_prerender( &vg_ui.ctx ); + vg_ui_set_screen( vg.window_x, vg.window_y ); + ui_update_mouse( &vg_ui.ctx, + (ui_px[2]){ vg.mouse_pos[0], vg.mouse_pos[1] }, vg.mouse_state ); - return 0; -} + vg_framebuffer_ui( &vg_ui.ctx ); -static void _vg_gameloop(void){ - //vg.time_fixed_accumulator = 0.75f * (1.0f/60.0f); + vg_console.enabled = 1; + ui_ignore_input_frames( &vg_ui.ctx, 10 ); + vg_gui( &vg_ui.ctx ); + ui_ignore_input_frames( &vg_ui.ctx, 0 ); + ui_capture_mouse( &vg_ui.ctx, 1 ); + vg_console_draw( &vg_ui.ctx ); + ui_postrender( &vg_ui.ctx, vg.time_frame_delta ); + vg_ui_post_update(); +} + +static void _vg_gameloop(void) +{ vg.time_hp = SDL_GetPerformanceCounter(); vg.time_hp_last = vg.time_hp; int post_start = 0; - while(1){ + while(1) + { vg.time_hp = SDL_GetPerformanceCounter(); u64 udt = vg.time_hp - vg.time_hp_last; vg.time_hp_last = vg.time_hp; @@ -453,21 +491,25 @@ static void _vg_gameloop(void){ if( vg.window_should_close ) break; - if( status == k_engine_status_crashed ){ - if( _vg_crashscreen() ) - break; + if( status == k_engine_status_crashed ) + { + _vg_crashscreen(); } - else{ - if( status == k_engine_status_running ){ + else + { + if( status == k_engine_status_running ) + { _vg_gameloop_update(); _vg_gameloop_render(); } - else{ + else + { vg_loader_render(); } } - if( vg.loader_ring > 0.01f ){ + if( vg.loader_ring > 0.01f ) + { vg_loader_render_ring( vg.loader_ring ); vg.loader_ring -= vg.time_frame_delta * 0.5f; } @@ -480,28 +522,30 @@ static void _vg_gameloop(void){ static void _vg_process_launch_opts_internal( int argc, char *argv[] ) { char *arg; - while( vg_argp( argc, argv ) ){ - if( (arg = vg_opt_arg( 'w' )) ){ + while( vg_argp( argc, argv ) ) + { + if( (arg = vg_opt_arg( 'w', "Render output width" )) ) vg.window_x = atoi( arg ); - } - if( (arg = vg_opt_arg( 'h' )) ){ + if( (arg = vg_opt_arg( 'h', "Render output height" )) ) vg.window_y = atoi( arg ); - } - if( (arg = vg_long_opt_arg( "samples" )) ){ + if( (arg = vg_long_opt_arg( "samples", "Rendering samples per pixel" )) ) vg.samples = VG_MAX( 0, VG_MIN( 8, atoi( arg ) ) ); - } - if( vg_long_opt( "use-libc-malloc" ) ){ + if( vg_long_opt( "use-libc-malloc", "Use standard libc allocator" ) ) vg_mem.use_libc_malloc = 1; - } - if( vg_long_opt( "high-performance" ) ){ + if( vg_long_opt( "high-performance", "Turn graphics to lowest quality" ) ) vg.quality_profile = k_quality_profile_low; - } vg_launch_opt(); + + if( vg_long_opt( "help", "Helps you" ) ) + { + _vg_print_options(); + exit(0); + } } } @@ -509,7 +553,8 @@ static void _vg_init_window( const char *window_name ) { vg_info( "SDL_INIT\n" ); - if( SDL_Init( SDL_INIT_VIDEO ) != 0 ){ + if( SDL_Init( SDL_INIT_VIDEO ) != 0 ) + { vg_error( "SDL_Init failed: %s\n", SDL_GetError() ); exit(0); } @@ -552,7 +597,8 @@ static void _vg_init_window( const char *window_name ) mode_index = 0; SDL_DisplayMode video_mode; - if( SDL_GetDesktopDisplayMode( display_index, &video_mode ) ){ + if( SDL_GetDesktopDisplayMode( display_index, &video_mode ) ) + { vg_error( "SDL_GetDesktopDisplayMode failed: %s\n", SDL_GetError() ); SDL_Quit(); exit(0); @@ -562,7 +608,8 @@ static void _vg_init_window( const char *window_name ) vg.window_x = video_mode.w; vg.window_y = video_mode.h; - if( vg.screen_mode == 2 ){ + if( vg.screen_mode == 2 ) + { vg.window_x = 1280; vg.window_y = 720; } @@ -584,11 +631,13 @@ static void _vg_init_window( const char *window_name ) vg_info( "CreateWindow( %d %d %u )\n", vg.window_x, vg.window_y, flags ); if((vg.window = SDL_CreateWindow( window_name, 0, 0, - vg.window_x, vg.window_y, flags ))){ + vg.window_x, vg.window_y, flags ))) + { if( vg.screen_mode == 2 ) SDL_SetWindowPosition( vg.window, video_mode.w-vg.window_x, 0 ); } - else{ + else + { vg_error( "SDL_CreateWindow failed: %s", SDL_GetError() ); exit(0); } @@ -605,17 +654,20 @@ static void _vg_init_window( const char *window_name ) /* * OpenGL loading */ - if( (vg.gl_context = SDL_GL_CreateContext(vg.window) )){ + if( (vg.gl_context = SDL_GL_CreateContext(vg.window) )) + { SDL_GL_GetDrawableSize( vg.window, &vg.window_x, &vg.window_y ); vg_success( "Window created (%dx%d)\n", vg.window_x, vg.window_y ); } - else{ + else + { vg_error( "SDL_GL_CreateContext failed: %s\n", SDL_GetError() ); SDL_Quit(); exit(0); } - if( !gladLoadGLLoader((GLADloadproc)SDL_GL_GetProcAddress) ) { + if( !gladLoadGLLoader((GLADloadproc)SDL_GL_GetProcAddress) ) + { vg_error( "Glad Failed to initialize\n" ); SDL_GL_DeleteContext( vg.gl_context ); SDL_Quit(); @@ -628,38 +680,49 @@ static void _vg_init_window( const char *window_name ) SDL_GL_SetSwapInterval(0); /* disable vsync while loading */ SDL_DisplayMode dispmode; - if( !SDL_GetWindowDisplayMode( vg.window, &dispmode ) ){ - if( dispmode.refresh_rate ){ + if( !SDL_GetWindowDisplayMode( vg.window, &dispmode ) ) + { + if( dispmode.refresh_rate ) + { vg.display_refresh_rate = dispmode.refresh_rate; } } - if( vg.display_refresh_rate < 25 || vg.display_refresh_rate > 300 ){ + if( vg.display_refresh_rate < 25 || vg.display_refresh_rate > 300 ) + { vg.display_refresh_rate = 60; } vg_info( "Display refresh rate: %d\n", dispmode.refresh_rate ); - if( !vg.fps_limit) vg.fps_limit = vg.display_refresh_rate; + if( !vg.fps_limit ) + vg.fps_limit = vg.display_refresh_rate; } static void _vg_terminate(void) { /* Shutdown */ + vg_magi_save(); vg_console_write_persistent(); SDL_AtomicLock( &vg.sl_status ); vg.engine_status = k_engine_status_none; SDL_AtomicUnlock( &vg.sl_status ); - vg_loader_free(); - - vg_success( "If you see this it means everything went.. \"well\".....\n" ); + vg_loader_atexit(); SDL_GL_DeleteContext( vg.gl_context ); SDL_Quit(); + + vg_success( "The program has terminated 'correctly'\n" ); exit(0); } +static int cmd_log_memory( int argc, const char *argv[] ) +{ + vg_mem_log( vg_mem.rtmemory, 0, "rtmemory" ); + return 0; +} + static int cmd_vg_settings_toggle( int argc, const char *argv[] ); void vg_enter( int argc, char *argv[], const char *window_name ) { @@ -669,6 +732,7 @@ void vg_enter( int argc, char *argv[], const char *window_name ) /* Systems init */ vg_alloc_quota(); vg_console_init(); + vg_magi_init(); vg_console_reg_var( "vg_fps_limit", &vg.fps_limit, k_var_dtype_i32, VG_VAR_PERSISTENT ); @@ -678,10 +742,12 @@ void vg_enter( int argc, char *argv[], const char *window_name ) k_var_dtype_i32, VG_VAR_PERSISTENT ); vg_console_reg_var( "vg_screen_mode", &vg.screen_mode, k_var_dtype_i32, VG_VAR_PERSISTENT ); + vg_audio_register(); vg_console_load_autos(); vg_console_reg_cmd( "vg_settings", cmd_vg_settings_toggle, NULL ); + vg_console_reg_cmd( "vg_rtmemory", cmd_log_memory, NULL ); _vg_init_window( window_name ); vg_async_init(); @@ -691,24 +757,41 @@ void vg_enter( int argc, char *argv[], const char *window_name ) /* Opengl-required systems */ vg_ui_init(); - vg_loader_init(); vg.engine_status = k_engine_status_load_internal; + vg_loader_step( vg_loader_init, vg_loader_free ); _vg_opengl_sync_init(); - vg_loader_start( _vg_load_full, NULL ); + vg_loader_start( _vg_load_full, NULL ); _vg_gameloop(); _vg_terminate(); } -void vg_fatal_error( const char *fmt, ... ) +#ifndef _WIN32 + #include +#endif + +void vg_fatal_exit(void) { - va_list args; - va_start( args, fmt ); - _vg_logx_va( stderr, NULL, "fatal", KRED, fmt, args ); - va_end( args ); + /* append backtrace */ +#if !defined(_WIN32) + void *array[20]; + char **strings; + int size, i; - vg_print_backtrace(); + size = backtrace( array, 20 ); + strings = backtrace_symbols( array, size ); + + if( strings != NULL ) + { + vg_error( "\n---------------- gnu backtrace -------------\n" ); + + for( int i=0; i= '0') && (buf[i] <= '9')) || (buf[i] == '\0') ) buf[j ++] = buf[i]; } } -struct ui_textbox_callbacks static vg_settings_ui_int_callbacks = { +struct ui_textbox_callbacks static vg_settings_ui_int_callbacks = +{ .change = vg_settings_ui_int }; -static bool vg_settings_ranged_i32_valid( struct vg_setting_ranged_i32 *prop ){ +static bool vg_settings_ranged_i32_valid( struct vg_setting_ranged_i32 *prop ) +{ if( prop->new_value < prop->min ) return 0; if( prop->new_value > prop->max ) return 0; return 1; } -static bool vg_settings_ranged_i32_diff( struct vg_setting_ranged_i32 *prop ){ +static bool vg_settings_ranged_i32_diff( struct vg_setting_ranged_i32 *prop ) +{ if( prop->new_value != *prop->actual_value ) return 1; else return 0; } -static bool vg_settings_ui_ranged_i32( struct vg_setting_ranged_i32 *prop, - ui_rect rect ){ +static bool vg_settings_ui_ranged_i32( ui_context *ctx, + struct vg_setting_ranged_i32 *prop, + ui_rect rect ) +{ ui_rect orig; rect_copy( rect, orig ); - ui_textbox( rect, prop->label, prop->buf, sizeof(prop->buf), + ui_textbox( ctx, rect, prop->label, prop->buf, sizeof(prop->buf), 1, 0, &vg_settings_ui_int_callbacks ); prop->new_value = atoi( prop->buf ); if( vg_settings_ranged_i32_diff( prop ) ) - vg_settings_ui_draw_diff( orig ); + vg_settings_ui_draw_diff( ctx, orig ); bool valid = vg_settings_ranged_i32_valid( prop ); - if( !valid ){ + if( !valid ) + { ui_rect _null, line; ui_split( orig, k_ui_axis_h, -1, 0, _null, line ); line[1] += 3; - ui_fill( line, ui_colour( k_ui_red ) ); + ui_fill( ctx, line, ui_colour( ctx, k_ui_red ) ); } return valid; @@ -855,16 +960,17 @@ bool vg_settings_enum_diff( struct vg_setting_enum *prop ) else return 0; } -bool vg_settings_enum( struct vg_setting_enum *prop, ui_rect rect ) +bool vg_settings_enum( ui_context *ctx, + struct vg_setting_enum *prop, ui_rect rect ) { ui_rect orig; rect_copy( rect, orig ); - ui_enum( rect, prop->label, + ui_enum( ctx, rect, prop->label, prop->options, prop->option_count, &prop->new_value ); if( vg_settings_enum_diff( prop ) ) - vg_settings_ui_draw_diff( orig ); + vg_settings_ui_draw_diff( ctx, orig ); return 1; } @@ -876,50 +982,60 @@ void ui_settings_enum_init( struct vg_setting_enum *prop ) /* .. */ -void vg_settings_ui_header( ui_rect inout_panel, const char *name ) +void vg_settings_ui_header( ui_context *ctx, + ui_rect inout_panel, const char *name ) { ui_rect rect; - ui_standard_widget( inout_panel, rect, 2 ); - ui_text( rect, name, 1, k_ui_align_middle_center, ui_colour(k_ui_fg+3) ); + ui_standard_widget( ctx, inout_panel, rect, 2 ); + ui_text( ctx, rect, name, 1, + k_ui_align_middle_center, ui_colour(ctx, k_ui_fg+3) ); } -bool vg_settings_apply_button( ui_rect inout_panel, bool validated ) +bool vg_settings_apply_button( ui_context *ctx, + ui_rect inout_panel, bool validated ) { ui_rect last_row; - ui_px height = (vg_ui.font->sy + 18) * k_ui_scale; - ui_split( inout_panel, k_ui_axis_h, -height, k_ui_padding, + ui_px height = ui_standard_widget_height( ctx, 1 ); + ui_split( inout_panel, k_ui_axis_h, -height, 8, inout_panel, last_row ); const char *string = "Apply"; - if( validated ){ - if( ui_button( last_row, string ) == 1 ) + if( validated ) + { + if( ui_button( ctx, last_row, string ) == 1 ) return 1; } - else{ + else + { ui_rect rect; - ui_standard_widget( last_row, rect, 1 ); - ui_fill( rect, ui_colour( k_ui_bg+1 ) ); - ui_outline( rect, -1, ui_colour( k_ui_red ), 0 ); + ui_standard_widget( ctx, last_row, rect, 1 ); + ui_fill( ctx, rect, ui_colour( ctx, k_ui_bg+1 ) ); + ui_outline( ctx, rect, -1, ui_colour( ctx, k_ui_red ), 0 ); - ui_rect t = { 0,0, ui_text_line_width( string ), 14 }; + ui_rect t = { 0,0, ui_text_line_width( ctx, string ), 14 }; ui_rect_center( rect, t ); - ui_text( t, string, 1, k_ui_align_left, ui_colour(k_ui_fg+3) ); + ui_text( ctx, t, string, 1, k_ui_align_left, ui_colour(ctx,k_ui_fg+3) ); } return 0; } -static void vg_settings_video_apply(void){ - if( vg_settings_enum_diff( &vg_settings.screenmode ) ){ +static void vg_settings_video_apply(void) +{ + if( vg_settings_enum_diff( &vg_settings.screenmode ) ) + { vg.screen_mode = vg_settings.screenmode.new_value; - if( (vg.screen_mode == 0) || (vg.screen_mode == 1) ){ + if( (vg.screen_mode == 0) || (vg.screen_mode == 1) ) + { SDL_DisplayMode video_mode; - if( SDL_GetDesktopDisplayMode( 0, &video_mode ) ){ + if( SDL_GetDesktopDisplayMode( 0, &video_mode ) ) + { vg_error("SDL_GetDesktopDisplayMode failed: %s\n", SDL_GetError()); } - else { + else + { //vg.display_refresh_rate = video_mode.refresh_rate; vg.window_x = video_mode.w; vg.window_y = video_mode.h; @@ -931,7 +1047,8 @@ static void vg_settings_video_apply(void){ SDL_SetWindowFullscreen( vg.window, SDL_WINDOW_FULLSCREEN_DESKTOP ); if( vg.screen_mode == 1 ) SDL_SetWindowFullscreen( vg.window, SDL_WINDOW_FULLSCREEN ); - if( vg.screen_mode == 2 ){ + if( vg.screen_mode == 2 ) + { SDL_SetWindowFullscreen( vg.window, 0 ); SDL_SetWindowSize( vg.window, 1280, 720 ); SDL_SetWindowPosition( vg.window, 16, 16 ); @@ -945,11 +1062,12 @@ static void vg_settings_video_apply(void){ vg.vsync = vg_settings.vsync.new_value; } -static void vg_settings_video_gui( ui_rect panel ){ +static void vg_settings_video_gui( ui_context *ctx, ui_rect panel ) +{ bool validated = 1; ui_rect rq; - ui_standard_widget( panel, rq, 1 ); - vg_settings_enum( &vg_settings.quality, rq ); + ui_standard_widget( ctx, panel, rq, 1 ); + vg_settings_enum( ctx, &vg_settings.quality, rq ); /* FIXME */ #if 0 @@ -959,41 +1077,49 @@ static void vg_settings_video_gui( ui_rect panel ){ #endif /* frame timing */ - vg_settings_ui_header( panel, "Frame Timing" ); + vg_settings_ui_header( ctx, panel, "Frame Timing" ); ui_rect duo, d0,d1; - ui_standard_widget( panel, duo, 1 ); + ui_standard_widget( ctx, panel, duo, 1 ); ui_split_ratio( duo, k_ui_axis_v, 0.5f, 16, d0, d1 ); - vg_settings_enum( &vg_settings.vsync, d0 ); - validated &= vg_settings_ui_ranged_i32( &vg_settings.fps_limit, d1 ); + vg_settings_enum( ctx, &vg_settings.vsync, d0 ); + validated &= vg_settings_ui_ranged_i32( ctx, &vg_settings.fps_limit, d1 ); /* profiler */ - ui_standard_widget( panel, duo, 10 ); + ui_standard_widget( ctx, panel, duo, 10 ); int frame_target = vg.display_refresh_rate; if( !vg.vsync ) frame_target = vg.fps_limit; vg_profile_drawn( - (struct vg_profile *[]){ - &vg_prof_update,&vg_prof_render,&vg_prof_swap}, 3, + ctx, + (struct vg_profile *[]) + { + &vg_prof_update, + &vg_prof_render, + &vg_prof_swap + }, 3, (1.0f/(f32)frame_target)*1500.0f, duo, 1, 0 ); - ui_fill( (ui_rect){ duo[0], duo[1]+(duo[3]*2)/3, duo[2], 1 }, - ui_colour(k_ui_fg) ); + ui_fill( ctx, (ui_rect){ duo[0], duo[1]+(duo[3]*2)/3, duo[2], 1 }, + ui_colour(ctx, k_ui_fg) ); /* window spec */ - vg_settings_ui_header( panel, "Window Specification" ); + vg_settings_ui_header( ctx, panel, "Window Specification" ); - ui_standard_widget( panel, duo, 1 ); - vg_settings_enum( &vg_settings.screenmode, duo ); + ui_standard_widget( ctx, panel, duo, 1 ); + vg_settings_enum( ctx, &vg_settings.screenmode, duo ); - if( vg_settings_apply_button( panel, validated ) ) + if( vg_settings_apply_button( ctx, panel, validated ) ) vg_settings_video_apply(); } -static void vg_settings_audio_apply(void){ - if( vg_settings_enum_diff( &vg_settings.audio_devices ) ){ - if( vg_audio.sdl_output_device ){ +static void vg_settings_audio_apply(void) +{ + if( vg_settings_enum_diff( &vg_settings.audio_devices ) ) + { + if( vg_audio.sdl_output_device ) + { vg_info( "Closing audio device %d\n", vg_audio.sdl_output_device ); SDL_CloseAudioDevice( vg_audio.sdl_output_device ); } @@ -1001,16 +1127,20 @@ static void vg_settings_audio_apply(void){ vg_strfree( &vg_audio.device_choice ); if( vg_settings.audio_devices.new_value == -1 ){ } - else if( vg_settings.audio_devices.new_value == -2 ){ - vg_fatal_error( "Programming error\n" ); + else if( vg_settings.audio_devices.new_value == -2 ) + { + vg_fatal_exit(); } - else { + else + { struct ui_enum_opt *selected = NULL, *oi; - for( int i=0; ivalue == vg_settings.audio_devices.new_value ){ + if( oi->value == vg_settings.audio_devices.new_value ) + { selected = oi; break; } @@ -1026,7 +1156,8 @@ static void vg_settings_audio_apply(void){ } audio_lock(); - if( vg_settings_enum_diff( &vg_settings.dsp ) ){ + if( vg_settings_enum_diff( &vg_settings.dsp ) ) + { *vg_settings.dsp.actual_value = vg_settings.dsp.new_value; } @@ -1034,15 +1165,16 @@ static void vg_settings_audio_apply(void){ audio_unlock(); } -static void vg_settings_audio_gui( ui_rect panel ){ +static void vg_settings_audio_gui( ui_context *ctx, ui_rect panel ) +{ ui_rect rq; - ui_standard_widget( panel, rq, 1 ); - vg_settings_enum( &vg_settings.audio_devices, rq ); + ui_standard_widget( ctx, panel, rq, 1 ); + vg_settings_enum( ctx, &vg_settings.audio_devices, rq ); - ui_standard_widget( panel, rq, 1 ); - vg_settings_enum( &vg_settings.dsp, rq ); + ui_standard_widget( ctx, panel, rq, 1 ); + vg_settings_enum( ctx, &vg_settings.dsp, rq ); - if( vg_settings_apply_button( panel, 1 ) ) + if( vg_settings_apply_button( ctx, panel, 1 ) ) vg_settings_audio_apply(); } @@ -1110,27 +1242,27 @@ void vg_settings_close(void) free( vg_settings.audio_devices.options ); } -static void vg_settings_gui(void) +static void vg_settings_gui( ui_context *ctx ) { ui_rect null; ui_rect screen = { 0, 0, vg.window_x, vg.window_y }; ui_rect window = { 0, 0, 1000, 700 }; ui_rect_center( screen, window ); - vg_ui.wants_mouse = 1; + ui_capture_mouse( ctx, 1 ); - ui_fill( window, ui_colour( k_ui_bg+1 ) ); - ui_outline( window, 1, ui_colour( k_ui_bg+7 ), 0 ); + ui_fill( ctx, window, ui_colour( ctx, k_ui_bg+1 ) ); + ui_outline( ctx, window, 1, ui_colour( ctx, k_ui_bg+7 ), 0 ); ui_rect title, panel; ui_split( window, k_ui_axis_h, 28, 0, title, panel ); - ui_fill( title, ui_colour( k_ui_bg+7 ) ); - ui_text( title, "Settings", 1, k_ui_align_middle_center, - ui_colourcont(k_ui_bg+7) ); + ui_fill( ctx, title, ui_colour( ctx, k_ui_bg+7 ) ); + ui_text( ctx, title, "Settings", 1, k_ui_align_middle_center, + ui_colourcont(ctx, k_ui_bg+7) ); ui_rect quit_button; ui_split( title, k_ui_axis_v, title[2]-title[3], 2, title, quit_button ); - if( ui_button_text( quit_button, "X", 1 ) == k_ui_button_click ) + if( ui_button_text( ctx, quit_button, "X", 1 ) == k_ui_button_click ) { vg_settings_close(); return; @@ -1145,31 +1277,32 @@ static void vg_settings_gui(void) }; static i32 page = 0; - ui_tabs( panel, panel, opts, vg_list_size(opts), &page ); + ui_tabs( ctx, panel, panel, opts, VG_ARRAY_LEN(opts), &page ); - if( page == 0 ){ - vg_settings_video_gui( panel ); + if( page == 0 ) + { + vg_settings_video_gui( ctx, panel ); } else if( page == 1 ) - vg_settings_audio_gui( panel ); + vg_settings_audio_gui( ctx, panel ); #ifdef VG_GAME_SETTINGS else if( page == 2 ) - vg_game_settings_gui( panel ); + vg_game_settings_gui( ctx, panel ); #endif } -static int cmd_vg_settings_toggle( int argc, const char *argv[] ){ +static int cmd_vg_settings_toggle( int argc, const char *argv[] ) +{ vg_settings_open(); return 0; } /* * Graphic cards will check these to force it to use the GPU. - * TODO: explicit declexport. since -flto strips these symbols in release. */ -u32 NvOptimusEnablement = 0x00000001; -int AmdPowerXpressRequestHighPerformance = 1; +__attribute__((used)) u32 NvOptimusEnablement = 0x00000001; +__attribute__((used)) int AmdPowerXpressRequestHighPerformance = 1; #include "vg_async.c" #include "vg_audio.c" @@ -1180,7 +1313,6 @@ int AmdPowerXpressRequestHighPerformance = 1; #include "vg_camera.c" #include "vg_lines.c" #include "vg_console.c" -#include "vg_imgui.c" #include "vg_input.c" #include "vg_io.c" #include "vg_loader.c" @@ -1194,11 +1326,16 @@ int AmdPowerXpressRequestHighPerformance = 1; #include "vg_perlin.c" #include "vg_string.c" #include "vg_profiler.c" +#include "vg_magi.c" #include "vg_rigidbody_collision.c" #include "vg_rigidbody_constraints.c" #include "vg_rigidbody.c" #include "vg_rigidbody_view.c" #include "vg_shader.c" +#include "vg_framebuffer.c" +#include "vg_render.c" +#include "vg_opengl.c" +#include "vg_mem_view.c" #ifdef VG_CUSTOM_SHADERS #include "shaders/impl.c" diff --git a/vg_engine.h b/vg_engine.h index 81bbda5..c17938e 100644 --- a/vg_engine.h +++ b/vg_engine.h @@ -69,15 +69,17 @@ #endif #define VG_ENGINE -#define SDL_MAIN_HANDLED +#include "vg_opengl.h" -#include "dep/glad/glad.h" +#define SDL_MAIN_HANDLED #include "dep/sdl/include/SDL.h" #include "vg_platform.h" #include "vg_mem.h" #include "vg_m.h" -#include "vg_imgui.h" +#include "vg_font.h" +#include "vg_string.h" +#include "vg_ui/imgui.h" #include @@ -98,7 +100,7 @@ extern void vg_fixed_update(void); extern void vg_post_update(void); extern void vg_render(void); -extern void vg_gui(void); +extern void vg_gui( ui_context *ctx ); void vg_settings_open(void); void vg_settings_close(void); @@ -116,19 +118,22 @@ enum vg_thread_purpose k_thread_purpose_loader }; -struct vg_engine { + +struct vg_engine +{ /* Engine sync */ SDL_Window *window; SDL_GLContext gl_context; - - SDL_sem *sem_loader; /* allows only one loader at a time */ - jmp_buf env_loader_exit; + SDL_sem *sem_loader; /* allows only one loader at a time */ SDL_threadID thread_id_main, thread_id_loader; - void *thread_data; SDL_SpinLock sl_status; + + jmp_buf env_loader_exit; + void *thread_data; + enum engine_status{ k_engine_status_none, k_engine_status_load_internal, @@ -160,7 +165,7 @@ struct vg_engine { } vsync_feature; - i32 mouse_pos[2]; + i32 mouse_pos[2], mouse_state; v2f mouse_delta, mouse_wheel; @@ -205,6 +210,26 @@ struct vg_engine { } extern vg; +struct vg_ui +{ + GLuint vao, vbo, ebo; + m3x3f pv; + ui_context ctx; + GLuint tex_glyphs; + v2f inverse_font_sheet; + + SDL_Cursor *cursor_map[ k_ui_cursor_max ]; + + /* at some point this should be implementation specific? */ + v4f colour; + f32 frosting; + v2f bg_inverse_ratio; + GLuint tex_bg; +} +extern vg_ui; +void vg_ui_set_screen( i32 width, i32 height ); +void vg_ui_set_mouse_pos( ui_px x, ui_px y ); + struct vg_setting_enum { i32 new_value, *actual_value; @@ -223,10 +248,13 @@ struct vg_setting_ranged_i32 void ui_settings_ranged_i32_init( struct vg_setting_ranged_i32 *prop ); void ui_settings_enum_init( struct vg_setting_enum *prop ); -bool vg_settings_enum( struct vg_setting_enum *prop, ui_rect rect ); bool vg_settings_enum_diff( struct vg_setting_enum *prop ); -void vg_settings_ui_header( ui_rect inout_panel, const char *name ); -bool vg_settings_apply_button( ui_rect inout_panel, bool validated ); +bool vg_settings_enum( ui_context *ctx, + struct vg_setting_enum *prop, ui_rect rect ); +void vg_settings_ui_header( ui_context *ctx, + ui_rect inout_panel, const char *name ); +bool vg_settings_apply_button( ui_context *ctx, + ui_rect inout_panel, bool validated ); enum engine_status _vg_engine_status(void); enum vg_thread_purpose vg_thread_purpose(void); diff --git a/vg_font.h b/vg_font.h index 5fcb29b..9a17561 100644 --- a/vg_font.h +++ b/vg_font.h @@ -1,3 +1,5 @@ +#pragma once + typedef struct vg_font_char vg_font_char; typedef struct vg_font_face vg_font_face; typedef struct vg_font_sheet vg_font_sheet; diff --git a/vg_framebuffer.c b/vg_framebuffer.c new file mode 100644 index 0000000..253c3a5 --- /dev/null +++ b/vg_framebuffer.c @@ -0,0 +1,549 @@ +#include "vg_framebuffer.h" +#include "vg_platform.h" +#include "vg_async.h" + +struct +{ + vg_framebuffer *list[16]; + u32 count; +} +static _vg_framebuffer; + +void vg_framebuffer_get_res( vg_framebuffer *fb, int *x, int *y ) +{ + if( fb->resolution_div ) + { + *x = vg.window_x / fb->resolution_div; + *y = vg.window_y / fb->resolution_div; + } + else + { + *x = fb->fixed_w; + *y = fb->fixed_h; + } +} + +void vg_framebuffer_inverse_ratio( vg_framebuffer *fb, v2f inverse ) +{ + if( fb ) + { + int x, y; + vg_framebuffer_get_res( fb, &x, &y ); + + v2f render = { fb->render_w, fb->render_h }, + original = { x, y }; + + v2_div( render, original, inverse ); + } + else + { + v2_div( (v2f){1.0f,1.0f}, (v2f){ vg.window_x, vg.window_y }, inverse ); + } +} + +void vg_framebuffer_bind( vg_framebuffer *fb, f32 scaling ) +{ + int x, y; + vg_framebuffer_get_res( fb, &x, &y ); + + if( scaling != 1.0f ) + { + x = scaling*(float)x; + y = scaling*(float)y; + + x = VG_MAX( 16, x ); + y = VG_MAX( 16, y ); + + fb->render_w = x; + fb->render_h = y; + } + else + { + fb->render_w = x; + fb->render_h = y; + } + + glBindFramebuffer( GL_FRAMEBUFFER, fb->id ); + glViewport( 0, 0, x, y ); +} + +void vg_framebuffer_bind_texture( vg_framebuffer *fb, int attachment, int slot ) +{ + vg_framebuffer_attachment *at = &fb->attachments[attachment]; + + if( (at->purpose != k_framebuffer_attachment_type_texture) && + (at->purpose != k_framebuffer_attachment_type_texture_depth) ) + { + vg_fatal_condition(); + vg_info( "illegal operation: bind non-texture framebuffer" + " attachment to texture slot" ); + vg_fatal_exit(); + } + + glActiveTexture( GL_TEXTURE0 + slot ); + glBindTexture( GL_TEXTURE_2D, fb->attachments[attachment].id ); +} + +/* + * Convert OpenGL attachment ID enum to string + */ +#define FB_FORMAT_STR( E ) { E, #E }, +static const char *render_fb_attachment_str( GLenum e ) +{ + struct { GLenum e; const char *str; } + formats[] = + { + FB_FORMAT_STR(GL_COLOR_ATTACHMENT0) + FB_FORMAT_STR(GL_COLOR_ATTACHMENT1) + FB_FORMAT_STR(GL_COLOR_ATTACHMENT2) + FB_FORMAT_STR(GL_COLOR_ATTACHMENT3) + FB_FORMAT_STR(GL_COLOR_ATTACHMENT4) + FB_FORMAT_STR(GL_DEPTH_STENCIL_ATTACHMENT) + }; + + for( int i=0; ipurpose == k_framebuffer_attachment_type_renderbuffer ) + { + glBindRenderbuffer( GL_RENDERBUFFER, a->id ); + glRenderbufferStorage( GL_RENDERBUFFER, a->internalformat, rx, ry ); + } + else if( a->purpose == k_framebuffer_attachment_type_texture || + a->purpose == k_framebuffer_attachment_type_texture_depth ) + { + glBindTexture( GL_TEXTURE_2D, a->id ); + glTexImage2D( GL_TEXTURE_2D, 0, a->internalformat, rx, ry, + 0, a->format, a->type, NULL ); + } +} + +vg_framebuffer *vg_framebuffer_allocate( void *alloc, + u32 attachment_count, bool track ) +{ + vg_framebuffer *fb = vg_linear_alloc( alloc, sizeof(vg_framebuffer) ); + + if( track ) + { + if( _vg_framebuffer.count != VG_ARRAY_LEN(_vg_framebuffer.list) ) + _vg_framebuffer.list[ _vg_framebuffer.count ++ ] = fb; + else + { + vg_fatal_condition(); + vg_info( "Framebuffer list is full, and tried to allocate another.\n"); + vg_fatal_exit(); + } + } + + fb->attachments = vg_linear_alloc( alloc, + sizeof(vg_framebuffer_attachment) * attachment_count ); + fb->attachment_count = attachment_count; + + return fb; +} + +static void async_framebuffer_create( void *payload, u32 size ) +{ + vg_framebuffer *fb = payload; + glGenFramebuffers( 1, &fb->id ); + glBindFramebuffer( GL_FRAMEBUFFER, fb->id ); + + int rx, ry; + vg_framebuffer_get_res( fb, &rx, &ry ); + + vg_info( "allocate_framebuffer( %s, %dx%d )\n", fb->display_name, rx, ry ); + vg_info( "{\n" ); + + GLenum colour_attachments[ fb->attachment_count ]; + u32 colour_count = 0; + + for( u32 j=0; jattachment_count; j++ ) + { + vg_framebuffer_attachment *attachment = &fb->attachments[j]; + + if( attachment->purpose == k_framebuffer_attachment_type_none ) + continue; + + vg_info( " %s: %s\n", + render_fb_attachment_str( attachment->attachment ), + render_fb_format_str( attachment->internalformat ) ); + + if( attachment->purpose == k_framebuffer_attachment_type_renderbuffer ) + { + glGenRenderbuffers( 1, &attachment->id ); + vg_framebuffer_allocate_texture( fb, attachment ); + glFramebufferRenderbuffer( GL_FRAMEBUFFER, + GL_DEPTH_STENCIL_ATTACHMENT, + GL_RENDERBUFFER, attachment->id ); + } + else if( attachment->purpose == k_framebuffer_attachment_type_texture || + attachment->purpose == k_framebuffer_attachment_type_texture_depth ) + { + glGenTextures( 1, &attachment->id ); + vg_framebuffer_allocate_texture( fb, attachment ); + glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR ); + glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR ); + glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE ); + glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE ); + + glFramebufferTexture2D( GL_FRAMEBUFFER, attachment->attachment, + GL_TEXTURE_2D, attachment->id, 0 ); + + if( attachment->purpose == k_framebuffer_attachment_type_texture ) + colour_attachments[ colour_count ++ ] = attachment->attachment; + } + } + + glDrawBuffers( colour_count, colour_attachments ); + + /* + * Check result + */ + GLenum result = glCheckFramebufferStatus( GL_FRAMEBUFFER ); + + if( result == GL_FRAMEBUFFER_COMPLETE ) + { + vg_success( " status: complete\n" ); + vg_info( "}\n" ); + } + else + { + vg_fatal_condition(); + if( result == GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT ) + vg_info( " status: Incomplete attachment" ); + else if( result == GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT ) + vg_info( " status: Missing attachment" ); + else if( result == GL_FRAMEBUFFER_UNSUPPORTED ) + vg_info( " status: Unsupported framebuffer format" ); + else + vg_info( " status: Generic Error" ); + vg_info( "}\n" ); + vg_fatal_exit(); + } +} + +void vg_framebuffer_create( vg_framebuffer *fb ) +{ + vg_async_call( async_framebuffer_create, fb, 0 ); +} + +void vg_framebuffer_ui( ui_context *ctx ) +{ + ui_px w = vg.window_x/3, + h = vg.window_y/3; + + ui_rect frame = {0,0,vg.window_x/3,vg.window_y/3}; + + for( int i=0; i<_vg_framebuffer.count; i++ ) + { + vg_framebuffer *fb = _vg_framebuffer.list[i]; + + for( int j=0; jattachment_count; j++ ) + { + vg_framebuffer_attachment *at = &fb->attachments[j]; + + if( !at->debug_view ) + continue; + + ui_fill( ctx, frame, 0xff000000 ); + if( at->purpose == k_framebuffer_attachment_type_renderbuffer ) + { + ui_text( ctx, frame, + "", 1, k_ui_align_middle_center, 0 ); + } + else + { + f32 img_ratio = (f32)fb->render_w / (f32)fb->render_h, + frame_ratio = (f32)w / (f32)h; + + ui_rect img = { 0,0, 0,0 }; + + if( img_ratio >= frame_ratio ) + { + img[2] = w; + img[3] = w / img_ratio; + } + else + { + img[2] = h * img_ratio; + img[3] = h; + } + + ui_rect_center( frame, img ); + ui_image( ctx, img, &fb->attachments[j].id ); + } + + ui_rect panel; + rect_copy( frame, panel ); + ui_info( ctx, panel, fb->display_name ); + ui_info( ctx, panel, at->display_name ); + ui_info( ctx, panel, render_fb_attachment_str( at->attachment ) ); + ui_info( ctx, panel, render_fb_format_str( at->internalformat ) ); + ui_info( ctx, panel, render_fb_format_str( at->format ) ); + + frame[0] += w; + + if( (frame[0] + w) > vg.window_x ) + { + frame[0] = 0; + frame[1] += h; + } + } + } +} + +static void vg_framebuffer_show( vg_framebuffer *fb, + vg_framebuffer_attachment *at, + int operation ) +{ + at->debug_view = operation; + vg_info( "%s %s:%s\n", (operation? "shown": "hidden" ), + fb->display_name, at->display_name ); +} + +/* + * arg0: command "show"/"hide" + * arg1: framebuffer name /"all" + * arg2: subname /none + */ +static int vg_framebuffer_control( int argc, char const *argv[] ) +{ + if( argc < 2 ) + { + vg_error( "Usage: fb \"show/hide\" /\"all\" /none\n" ); + return 0; + } + + int modify_all = 0, + operation = 0; + + if( !strcmp( argv[0], "show" ) ) + operation = 1; + else if( !strcmp( argv[0], "hide" ) ) + operation = 0; + else + { + vg_error( "Unknown framebuffer operation: '%s'\n", argv[0] ); + return 0; + } + + if( !strcmp( argv[1], "all" ) ) + modify_all = 1; + + for( int i=0; i<_vg_framebuffer.count; i++ ) + { + vg_framebuffer *fb = _vg_framebuffer.list[i]; + + for( int j=0; jattachment_count; j++ ) + { + vg_framebuffer_attachment *at = &fb->attachments[j]; + + if( at->purpose == k_framebuffer_attachment_type_none ) + continue; + + if( modify_all ) + { + vg_framebuffer_show( fb, at, operation ); + } + else + { + if( !strcmp( fb->display_name, argv[1] ) ) + { + if( argc == 2 ) + vg_framebuffer_show( fb, at, operation ); + else if( !strcmp( at->display_name, argv[2] ) ) + vg_framebuffer_show( fb, at, operation ); + } + } + } + } + + return 0; +} + +static void vg_framebuffer_poll( int argc, char const *argv[] ) +{ + const char *term = argv[argc-1]; + + if( argc == 1 ) + { + console_suggest_score_text( "show", term, 0 ); + console_suggest_score_text( "hide", term, 0 ); + } + else if( argc == 2 ) + { + console_suggest_score_text( "all", term, 0 ); + + for( int i=0; i<_vg_framebuffer.count; i++ ) + { + vg_framebuffer *fb = _vg_framebuffer.list[i]; + console_suggest_score_text( fb->display_name, term, 0 ); + } + } + else if( argc == 3 ) + { + int modify_all = 0; + + if( !strcmp( argv[1], "all" ) ) + modify_all = 1; + + for( int i=0; i<_vg_framebuffer.count; i++ ) + { + vg_framebuffer *fb = _vg_framebuffer.list[ i ]; + + for( int j=0; jattachment_count; j++ ) + { + vg_framebuffer_attachment *at = &fb->attachments[j]; + + if( at->purpose == k_framebuffer_attachment_type_none ) + continue; + + if( modify_all ) + { + console_suggest_score_text( at->display_name, term, 0 ); + } + else if( !strcmp( fb->display_name, argv[1] ) ) + { + console_suggest_score_text( at->display_name, term, 0 ); + } + } + } + } +} + +void vg_framebuffer_init(void) +{ + //vg_console_reg_var( "blur_strength", &k_blur_strength, k_var_dtype_f32, 0 ); + //vg_console_reg_var( "render_scale", &k_render_scale, + // k_var_dtype_f32, VG_VAR_PERSISTENT ); + //vg_console_reg_var( "fov", &k_fov, k_var_dtype_f32, VG_VAR_PERSISTENT ); + //vg_console_reg_var( "cam_height", &k_cam_height, + // k_var_dtype_f32, VG_VAR_PERSISTENT ); + //vg_console_reg_var( "blur_effect", &k_blur_effect, + // k_var_dtype_i32, VG_VAR_PERSISTENT ); + + vg_console_reg_cmd( "fb", vg_framebuffer_control, + vg_framebuffer_poll ); +} + +void vg_framebuffer_update_sizes(void) +{ + for( int i=0; i<_vg_framebuffer.count; i++ ) + { + vg_framebuffer *fb = _vg_framebuffer.list[i]; + for( int j=0; jattachment_count; j++ ) + { + vg_framebuffer_attachment *attachment = &fb->attachments[j]; + vg_framebuffer_allocate_texture( fb, attachment ); + } + } +} diff --git a/vg_framebuffer.h b/vg_framebuffer.h new file mode 100644 index 0000000..90500e0 --- /dev/null +++ b/vg_framebuffer.h @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2021-2024 Mt.ZERO Software, Harry Godden - All Rights Reserved + */ +#pragma once +#include "vg_engine.h" + +typedef struct vg_framebuffer vg_framebuffer; +typedef struct vg_framebuffer_attachment vg_framebuffer_attachment; + +struct vg_framebuffer +{ + const char *display_name; + int resolution_div, /* If 0: Use fixed_w, fixed_h. + If non-0: Automatically size itself to + the window resolution divided by + this value */ + fixed_w, + fixed_h, + + render_w, /* The currently rendering resolution */ + render_h; + GLuint id; + + struct vg_framebuffer_attachment + { + const char *display_name; + + enum vg_framebuffer_attachment_type + { + k_framebuffer_attachment_type_none, + k_framebuffer_attachment_type_texture, + k_framebuffer_attachment_type_renderbuffer, + k_framebuffer_attachment_type_texture_depth + } + purpose; + + enum vg_framebuffer_quality_profile + { + k_framebuffer_quality_all, + k_framebuffer_quality_high_only + } + quality; + + GLenum internalformat, + format, + type, + attachment; + + GLuint id; + + /* Runtime */ + int debug_view; + } + *attachments; + u32 attachment_count; +}; + +/* + * Initialize framebuffer system + */ +void vg_framebuffer_init(void); + +/* + * Get the current (automatically scaled or fixed) resolution of framebuffer + */ +void vg_framebuffer_get_res( vg_framebuffer *fb, int *x, int *y ); + +/* + * Get the inverse ratio to project pixel coordinates (0->1920) to UV coordinates + * + * NOTE: won't necesarily use the full 0->1 range, but may index a subsection + * of the framebuffer if using variable scale rendering. + */ +void vg_framebuffer_inverse_ratio( vg_framebuffer *fb, v2f inverse ); + +/* + * Bind framebuffer for drawing to + */ +void vg_framebuffer_bind( vg_framebuffer *fb, f32 scaling ); + +/* + * Bind framebuffer attachment's texture + */ +void vg_framebuffer_bind_texture( vg_framebuffer *fb, int attachment, int slot ); + +/* + * Allocation of a framebuffer memory. Optionally, track this framebuffer in + * debugging systems. + */ +vg_framebuffer *vg_framebuffer_allocate( void *alloc, + u32 attachment_count, bool track ); + +/* + * Allocate graphics memory and initialize + */ +void vg_framebuffer_create( vg_framebuffer *fb ); + +/* + * Draw framebuffer debugging stuff + */ +void vg_framebuffer_ui( ui_context *ctx ); + +void vg_framebuffer_update_sizes(void); diff --git a/vg_imgui.c b/vg_imgui.c deleted file mode 100644 index 51792ff..0000000 --- a/vg_imgui.c +++ /dev/null @@ -1,2276 +0,0 @@ -/* Copyright (C) 2021-2024 Harry Godden (hgn) - All Rights Reserved */ - -/* - * Principles: - * - * 1. layout is defined by subdividing - * 2. a parent node should never be resized by the content after creation - * 3. when the ui is in an interactive state, no controls should ever move - * 4. controls directly reference a memory location and use that as their - * unique id - * 5. a maximum of ONE control per memory location can be drawn at any given - * point. - */ - -#pragma once - -#include "vg_imgui.h" -#include "vg_engine.h" -#include "vg_tex.h" -#include "vg_shader.h" -#include - -ui_px k_ui_widget_height = 28, - k_ui_scale = 1, - k_ui_padding = 8; - -#include "vg/vg_default_font.gc" - -struct vg_imgui vg_ui = { - .scheme = { - [ k_ui_bg+0 ] = UI_RGB( 0x1d2021 ), - [ k_ui_bg+1 ] = UI_RGB( 0x282828 ), - [ k_ui_bg+2 ] = UI_RGB( 0x3c3836 ), - [ k_ui_bg+3 ] = UI_RGB( 0x504945 ), - [ k_ui_bg+4 ] = UI_RGB( 0x665c54 ), - [ k_ui_bg+5 ] = UI_RGB( 0x7c6f64 ), - [ k_ui_bg+6 ] = UI_RGB( 0x928374 ), - [ k_ui_bg+7 ] = UI_RGB( 0xa89984 ), - - [ k_ui_fg+0 ] = UI_RGB( 0xebdbb2 ), - [ k_ui_fg+1 ] = UI_RGB( 0xfbf1c7 ), - [ k_ui_fg+2 ] = UI_RGB( 0xd5c4a1 ), - [ k_ui_fg+3 ] = UI_RGB( 0xbdae93 ), - [ k_ui_fg+4 ] = UI_RGB( 0xa89984 ), - [ k_ui_fg+5 ] = UI_RGB( 0x000000 ), - [ k_ui_fg+6 ] = UI_RGB( 0x000000 ), - [ k_ui_fg+7 ] = UI_RGB( 0x000000 ), - - [ k_ui_red ] = UI_RGB( 0xcc241d ), - [ k_ui_orange ] = UI_RGB( 0xd65d0e ), - [ k_ui_yellow ] = UI_RGB( 0xd79921 ), - [ k_ui_green ] = UI_RGB( 0x98971a ), - [ k_ui_aqua ] = UI_RGB( 0x689d6a ), - [ k_ui_blue ] = UI_RGB( 0x458588 ), - [ k_ui_purple ] = UI_RGB( 0xb16286 ), - [ k_ui_gray ] = UI_RGB( 0x928374 ), - [ k_ui_red + k_ui_brighter ] = UI_RGB( 0xfb4934 ), - [ k_ui_orange + k_ui_brighter ] = UI_RGB( 0xfe8019 ), - [ k_ui_yellow + k_ui_brighter ] = UI_RGB( 0xfabd2f ), - [ k_ui_green + k_ui_brighter ] = UI_RGB( 0xb8bb26 ), - [ k_ui_aqua + k_ui_brighter ] = UI_RGB( 0x8ec07c ), - [ k_ui_blue + k_ui_brighter ] = UI_RGB( 0x83a598 ), - [ k_ui_purple + k_ui_brighter ] = UI_RGB( 0xd3869b ), - [ k_ui_gray + k_ui_brighter ] = UI_RGB( 0xa89984 ), - }, - .font = &vgf_default_small, - .colour = {1.0f,1.0f,1.0f,1.0f}, - .bg_inverse_ratio = {1,1} -}; - -static struct vg_shader _shader_ui ={ - .name = "[vg] ui - transparent", - .vs = { - .orig_file = NULL, - .static_src = - "layout (location=0) in vec2 a_co;" - "layout (location=1) in vec2 a_uv;" - "layout (location=2) in vec4 a_colour;" - "uniform mat3 uPv;" - "uniform vec2 uBGInverseRatio;" - "uniform vec2 uInverseFontSheet;" - "" - "out vec4 aTexCoords;" - "out vec4 aColour;" - "" - "void main(){" - "vec4 proj_pos = vec4( uPv * vec3( a_co, 1.0 ), 1.0 );" - "gl_Position = proj_pos;" - "aTexCoords = vec4( a_uv * uInverseFontSheet, " - " (proj_pos.xy*0.5+0.5) * uBGInverseRatio );" - "aColour = a_colour;" - "}", - }, - .fs = { - .orig_file = NULL, - .static_src = - "uniform sampler2D uTexGlyphs;" - "uniform sampler2D uTexBG;" - "uniform vec4 uColour;" - "uniform float uSpread;" - "out vec4 FragColor;" - "" - "in vec4 aTexCoords;" - "in vec4 aColour;" - "" - "vec2 rand_hash22( vec2 p ){" - "vec3 p3 = fract(vec3(p.xyx) * 213.8976123);" - "p3 += dot(p3, p3.yzx+19.19);" - "return fract(vec2((p3.x + p3.y)*p3.z, (p3.x+p3.z)*p3.y));" - "}" - "" - "void main(){" - "vec4 diffuse = aColour;" - - "vec4 avg = vec4(0.0);" - - "if( aColour.a == 0.0 ){" - "avg = aColour;" - "avg.a = texture( uTexGlyphs, aTexCoords.xy ).r;" - "}" - "else{" - "if( uSpread > 0.0001 ){" - "for( int i=0; i<4; i ++ ){" - "vec2 spread = rand_hash22(aTexCoords.zw+vec2(float(i)));" - "avg += texture( uTexBG, aTexCoords.zw + (spread-0.5)*uSpread );" - "}" - "avg *= 0.25;" - "avg.a = 1.0;" - "avg.rgb = mix( avg.rgb, aColour.rgb, aColour.a );" - "}" - "else{" - "avg = aColour;" - "}" - "}" - - "FragColor = avg * uColour;" - "}" - } -}; - -static struct vg_shader _shader_ui_image = { - .name = "[vg] ui_image", - .vs = - { - .orig_file = NULL, - .static_src = - "layout (location=0) in vec2 a_co;" - "layout (location=1) in vec2 a_uv;" - "layout (location=2) in vec4 a_colour;" - "uniform mat3 uPv;" - - "out vec2 aTexCoords;" - "out vec4 aColour;" - "out vec2 aWsp;" - - "void main()" - "{" - "gl_Position = vec4( uPv * vec3( a_co, 1.0 ), 1.0 );" - "aTexCoords = a_uv * 0.00390625;" - "aColour = a_colour;" - - "aWsp = a_co;" - "}", - }, - .fs = - { - .orig_file = NULL, - .static_src = - "uniform sampler2D uTexImage;" - "uniform vec4 uColour;" - "out vec4 FragColor;" - - "in vec2 aTexCoords;" - "in vec4 aColour;" - "in vec2 aWsp;" - - "void main()" - "{" - "vec4 colour = texture( uTexImage, aTexCoords );" - "FragColor = colour * uColour;" - "}" - } -}; - -static struct vg_shader _shader_ui_hsv = { - .name = "[vg] ui_hsv", - .vs = { - .orig_file = NULL, - .static_src = - "layout (location=0) in vec2 a_co;" - "layout (location=1) in vec2 a_uv;" - "layout (location=2) in vec4 a_colour;" - "uniform mat3 uPv;" - - "out vec2 aTexCoords;" - "out vec4 aColour;" - "out vec2 aWsp;" - - "void main()" - "{" - "gl_Position = vec4( uPv * vec3( a_co, 1.0 ), 1.0 );" - "aTexCoords = a_uv * 0.00390625;" - "aColour = a_colour;" - - "aWsp = a_co;" - "}", - }, - .fs = - { - .orig_file = NULL, - .static_src = - "uniform float uHue;" - "out vec4 FragColor;" - - "in vec2 aTexCoords;" - "in vec4 aColour;" - "in vec2 aWsp;" - - "void main()" - "{" - "vec3 c = vec3( uHue, aTexCoords );" - "vec4 K = vec4(1.0,2.0/3.0,1.0/3.0,3.0);" - "vec3 p = abs(fract(c.xxx+K.xyz)*6.0 - K.www);" - "vec3 colour = c.z*mix(K.xxx,clamp(p-K.xxx,0.0,1.0),c.y);" - "FragColor = vec4( colour, 1.0 );" - "}" - } -}; - -void vg_ui_init(void) -{ - if( !vg_shader_compile( &_shader_ui ) || - !vg_shader_compile( &_shader_ui_image ) || - !vg_shader_compile( &_shader_ui_hsv ) ){ - vg_fatal_error( "Failed to compile ui shader" ); - } - - /* - * Vertex buffer - * ---------------------------------------- - */ - - vg_ui.max_indices = 20000; - vg_ui.max_verts = 30000; - - /* Generate the buffer we are gonna be drawing to */ - glGenVertexArrays( 1, &vg_ui.vao ); - glGenBuffers( 1, &vg_ui.vbo ); - glGenBuffers( 1, &vg_ui.ebo ); - - glBindVertexArray( vg_ui.vao ); - glBindBuffer( GL_ARRAY_BUFFER, vg_ui.vbo ); - - glBufferData( GL_ARRAY_BUFFER, - vg_ui.max_verts * sizeof( struct ui_vert ), - NULL, GL_DYNAMIC_DRAW ); - glBindVertexArray( vg_ui.vao ); - - glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, vg_ui.ebo ); - glBufferData( GL_ELEMENT_ARRAY_BUFFER, - vg_ui.max_indices * sizeof( u16 ), NULL, GL_DYNAMIC_DRAW ); - - VG_CHECK_GL_ERR(); - - /* Set pointers */ - u32 const stride = sizeof( struct ui_vert ); - - /* XY */ - glVertexAttribPointer( 0, 2, GL_SHORT, GL_FALSE, stride, - (void *)offsetof( struct ui_vert, co ) ); - glEnableVertexAttribArray( 0 ); - - /* UV */ - glVertexAttribPointer( 1, 2, GL_UNSIGNED_SHORT, GL_FALSE, stride, - (void *)offsetof( struct ui_vert, uv ) ); - glEnableVertexAttribArray( 1 ); - - /* COLOUR */ - glVertexAttribPointer( 2, 4, GL_UNSIGNED_BYTE, GL_TRUE, stride, - (void *)offsetof( struct ui_vert, colour ) ); - glEnableVertexAttribArray( 2 ); - - VG_CHECK_GL_ERR(); - - /* Alloc RAM default context */ - u32 vert_size = vg_ui.max_verts*sizeof(struct ui_vert), - inds_size = vg_align8( vg_ui.max_indices*sizeof(u16) ); - - vg_ui.vertex_buffer = vg_linear_alloc( vg_mem.rtmemory, vert_size ); - vg_ui.indice_buffer = vg_linear_alloc( vg_mem.rtmemory, inds_size ); - - /* font - * ----------------------------------------------------- - */ - - /* Load default font */ - - vg_font_sheet *sheet = &vg_default_font_sheet; - u32 pixels = 0, - total = sheet->w*sheet->h, - data = 0; - - vg_linear_clear( vg_mem.scratch ); - u8 *image = vg_linear_alloc( vg_mem.scratch, total ); - - while( pixels < total ) - { - for( int b = 31; b >= 0; b-- ) - { - image[ pixels ++ ] = (sheet->bitmap[data] & (0x1u << b))? 0xffu: 0x00u; - - if( pixels >= total ) - { - total = 0; - break; - } - } - data++; - } - - vg_ui.inverse_font_sheet[0] = 1.0/(f64)sheet->w; - vg_ui.inverse_font_sheet[1] = 1.0/(f64)sheet->h; - - glGenTextures( 1, &vg_ui.tex_glyphs ); - glBindTexture( GL_TEXTURE_2D, vg_ui.tex_glyphs ); - glTexImage2D( GL_TEXTURE_2D, 0, GL_R8, sheet->w, sheet->h, 0, - GL_RED, GL_UNSIGNED_BYTE, image ); - - VG_CHECK_GL_ERR(); - glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE ); - glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE ); - glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST ); - glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST ); - - /* - * Cursors - * --------------------------------------------------------------- - */ - - vg_ui.cursor_map[ k_ui_cursor_default ] = - SDL_CreateSystemCursor( SDL_SYSTEM_CURSOR_ARROW ); - vg_ui.cursor_map[ k_ui_cursor_hand ] = - SDL_CreateSystemCursor( SDL_SYSTEM_CURSOR_HAND ); - vg_ui.cursor_map[ k_ui_cursor_ibeam ] = - SDL_CreateSystemCursor( SDL_SYSTEM_CURSOR_IBEAM ); -} - -void rect_copy( ui_rect a, ui_rect b ){ - for( int i=0; i<4; i++ ) - b[i] = a[i]; -} - -void ui_flush( enum ui_shader shader, f32 w, f32 h ){ - u32 vertex_offset = vg_ui.vert_start*sizeof(ui_vert), - vertex_count = vg_ui.cur_vert-vg_ui.vert_start, - vertex_size = vertex_count*sizeof(ui_vert), - - indice_offset = vg_ui.indice_start*sizeof(u16), - indice_count = vg_ui.cur_indice-vg_ui.indice_start, - indice_size = indice_count * sizeof(u16); - - if( !vertex_size || !indice_size ) - return; - - glBindVertexArray( vg_ui.vao ); - glBindBuffer( GL_ARRAY_BUFFER, vg_ui.vbo ); - glBufferSubData( GL_ARRAY_BUFFER, vertex_offset, vertex_size, - vg_ui.vertex_buffer+vg_ui.vert_start ); - - glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, vg_ui.ebo ); - glBufferSubData( GL_ELEMENT_ARRAY_BUFFER, indice_offset, indice_size, - vg_ui.indice_buffer+vg_ui.indice_start ); - - glDisable( GL_DEPTH_TEST ); - glEnable( GL_BLEND ); - glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); - glBlendEquation( GL_FUNC_ADD ); - glDisable( GL_CULL_FACE ); - - m3x3f view = M3X3_IDENTITY; - m3x3_translate( view, (v3f){ -1.0f, 1.0f, 0.0f } ); - m3x3_scale( view, (v3f){ 1.0f/(w*0.5f), - -1.0f/(h*0.5f), 1.0f } ); - - if( shader == k_ui_shader_colour ){ - glUseProgram( _shader_ui.id ); - - glUniformMatrix3fv( glGetUniformLocation( _shader_ui.id, "uPv" ), 1, - GL_FALSE, (float *)view ); - glUniform4fv( glGetUniformLocation( _shader_ui.id, "uColour" ), 1, - vg_ui.colour ); - - glActiveTexture( GL_TEXTURE0 ); - glBindTexture( GL_TEXTURE_2D, vg_ui.tex_glyphs ); - glUniform1i( glGetUniformLocation( _shader_ui.id, "uTexGlyphs" ), 0 ); - - glActiveTexture( GL_TEXTURE1 ); - glBindTexture( GL_TEXTURE_2D, vg_ui.tex_bg ); - glUniform1i( glGetUniformLocation( _shader_ui.id, "uTexBG" ), 1 ); - glUniform1f( glGetUniformLocation( _shader_ui.id, "uSpread" ), - vg_ui.frosting ); - glUniform2fv( glGetUniformLocation( _shader_ui.id, "uBGInverseRatio" ), - 1, vg_ui.bg_inverse_ratio ); - glUniform2fv( glGetUniformLocation( _shader_ui.id, "uInverseFontSheet" ), - 1, vg_ui.inverse_font_sheet ); - } - else if( shader == k_ui_shader_image ){ - glUseProgram( _shader_ui_image.id ); - glUniformMatrix3fv( glGetUniformLocation( _shader_ui_image.id, "uPv" ), 1, - GL_FALSE, (float *)view ); - glUniform1i( glGetUniformLocation(_shader_ui_image.id,"uTexImage"), 0 ); - glUniform4fv( glGetUniformLocation( _shader_ui_image.id, "uColour" ), 1, - vg_ui.colour ); - } - else if( shader == k_ui_shader_hsv ){ - glUseProgram( _shader_ui_hsv.id ); - glUniformMatrix3fv( glGetUniformLocation( _shader_ui_hsv.id, "uPv" ), 1, - GL_FALSE, (float *)view ); - glUniform1f( glGetUniformLocation(_shader_ui_hsv.id,"uHue"), vg_ui.hue ); - } - else - vg_fatal_error( "Invalid UI shader (%d)\n", shader ); - - glDrawElements( GL_TRIANGLES, indice_count, GL_UNSIGNED_SHORT, - (void *)(vg_ui.indice_start*sizeof(u16)) ); - - glDisable( GL_BLEND ); - - vg_ui.indice_start = vg_ui.cur_indice; - vg_ui.vert_start = vg_ui.cur_vert; -} - -struct ui_vert *ui_fill_rect( ui_rect rect, u32 colour, ui_px uv[4] ) -{ - /* this if far from ideal but stops us from crashing */ - if( (vg_ui.cur_vert + 4 > vg_ui.max_verts) || - (vg_ui.cur_indice + 6 > vg_ui.max_indices)) - { - return &vg_ui.vertex_buffer[0]; - } - - struct ui_vert *vertices = &vg_ui.vertex_buffer[ vg_ui.cur_vert ]; - u16 *indices = &vg_ui.indice_buffer[ vg_ui.cur_indice ]; - - for( int i=0; i<4; i++ ) - { - vertices[i].colour = colour; - } - - vertices[0].co[0] = rect[0]; - vertices[0].co[1] = rect[1]; - vertices[0].uv[0] = uv[0]; - vertices[0].uv[1] = uv[1]; - vertices[1].co[0] = rect[0]+rect[2]; - vertices[1].co[1] = rect[1]; - vertices[1].uv[0] = uv[2]; - vertices[1].uv[1] = uv[1]; - vertices[2].co[0] = rect[0]+rect[2]; - vertices[2].co[1] = rect[1]+rect[3]; - vertices[2].uv[0] = uv[2]; - vertices[2].uv[1] = uv[3]; - vertices[3].co[0] = rect[0]; - vertices[3].co[1] = rect[1]+rect[3]; - vertices[3].uv[0] = uv[0]; - vertices[3].uv[1] = uv[3]; - u16 ind_start = vg_ui.cur_vert; - - u16 start = vg_ui.cur_vert; - u32 mesh[] = { 0,2,1, 0,3,2 }; - - for( u32 i=0; i vg_ui.max_verts) || - (vg_ui.cur_indice + 24 > vg_ui.max_indices)) - return; - - struct ui_vert *vertices = &vg_ui.vertex_buffer[ vg_ui.cur_vert ]; - u16 *indices = &vg_ui.indice_buffer[ vg_ui.cur_indice ]; - - for( int i=0; i<8; i++ ){ - vertices[i].uv[0] = 4; - vertices[i].uv[1] = 4; - vertices[i].colour = colour; - } - - vertices[0].co[0] = rect[0]; - vertices[0].co[1] = rect[1]; - vertices[1].co[0] = rect[0]+rect[2]; - vertices[1].co[1] = rect[1]; - vertices[2].co[0] = rect[0]+rect[2]; - vertices[2].co[1] = rect[1]+rect[3]; - vertices[3].co[0] = rect[0]; - vertices[3].co[1] = rect[1]+rect[3]; - vertices[4].co[0] = vertices[0].co[0]-thickness; - vertices[4].co[1] = vertices[0].co[1]-thickness; - vertices[5].co[0] = vertices[1].co[0]+thickness; - vertices[5].co[1] = vertices[1].co[1]-thickness; - vertices[6].co[0] = vertices[2].co[0]+thickness; - vertices[6].co[1] = vertices[2].co[1]+thickness; - vertices[7].co[0] = vertices[3].co[0]-thickness; - vertices[7].co[1] = vertices[3].co[1]+thickness; - - u16 start = vg_ui.cur_vert; - u32 mesh[] = { 0,5,4, 0,1,5, 1,6,5, 1,2,6, 2,7,6, 2,3,7, 3,4,7, 3,0,4 }; - - if( !mask ) - mask = UI_TOP|UI_LEFT|UI_BOTTOM|UI_RIGHT; - - u32 c = 0; - for( u32 i=0; i rp ) dir = k_ui_axis_h; - else dir = k_ui_axis_v; - other = dir ^ 0x1; - - d[2+dir] = rect[2+dir]; - d[2+other] = (rect[2+dir] * size[other]) / size[dir]; - - ui_rect_center( rect, d ); -} - -void ui_split_ratio( ui_rect rect, enum ui_axis dir, float ratio, - ui_px gap, ui_rect l, ui_rect r ) -{ - ui_px width = (float)rect[ 2+(dir^0x1) ] * ratio; - ui_split( rect, dir, width, gap, l, r ); -} - -void ui_rect_pad( ui_rect rect, ui_px pad[2] ) -{ - rect[0] += pad[0]; - rect[1] += pad[1]; - rect[2] -= pad[0]*2; - rect[3] -= pad[1]*2; -} - -ui_px ui_text_line_width( const char *str ) -{ - int length = 0; - const char *_c = str; - u8 c; - - while( (c = *(_c ++)) ){ - if( c >= 32 ) length ++; - else if( c == '\n' ) break; - } - - return length * vg_ui.font->sx; -} - -ui_px ui_text_string_height( const char *str ) -{ - int height = 1; - const char *_c = str; - u8 c; - - while( (c = *(_c ++)) ) - { - if( c == '\n' ) height ++; - } - - return height * vg_ui.font->sy; -} - -ui_px ui_text_aligned_x( const char *str, ui_rect rect, ui_px scale, - enum ui_align align ) -{ - enum ui_align lwr = k_ui_align_lwr & align; - if( lwr == k_ui_align_left ){ - return rect[0]; - } - else{ - ui_px width = ui_text_line_width( str ) * scale; - - if( lwr == k_ui_align_right ) - return rect[0] + rect[2]-width; - else - return rect[0] + (rect[2]-width)/2; - } -} - -static ui_px ui_min( ui_px a, ui_px b ){ return ab?a:b; } -static ui_px ui_clamp( ui_px a, ui_px min, ui_px max ){ - return ui_min( max, ui_max( a, min ) ); -} - -int ui_clip( ui_rect parent, ui_rect child, ui_rect clipped ) -{ - ui_px parent_max[2], child_max[2]; - parent_max[0] = parent[0]+parent[2]; - parent_max[1] = parent[1]+parent[3]; - child_max[0] = child[0]+child[2]; - child_max[1] = child[1]+child[3]; - - clipped[0] = ui_clamp( child[0], parent[0], parent_max[0] ); - clipped[1] = ui_clamp( child[1], parent[1], parent_max[1] ); - clipped[2] = ui_clamp( child_max[0], parent[0], parent_max[0] ); - clipped[3] = ui_clamp( child_max[1], parent[1], parent_max[1] ); - - if( clipped[0] == clipped[2] || - clipped[1] == clipped[3] ) - return 0; - - clipped[2] -= clipped[0]; - clipped[3] -= clipped[1]; - - return 1; -} - -int ui_inside_rect( ui_rect rect, ui_px co[2] ) -{ - if( co[0] >= rect[0] && - co[1] >= rect[1] && - co[0] < rect[0]+rect[2] && - co[1] < rect[1]+rect[3] ){ - return 1; - } - else - return 0; -} - -int ui_click_down( u32 mask ) -{ - if( vg_ui.ignore_input_frames ) return 0; - if( (vg_ui.mouse_state[0] & mask) && - !(vg_ui.mouse_state[1] & mask) ) - return 1; - else - return 0; -} - -int ui_clicking( u32 mask ) -{ - if( vg_ui.ignore_input_frames ) return 0; - return vg_ui.mouse_state[0] & mask; -} - -int ui_click_up( u32 mask ) -{ - if( vg_ui.ignore_input_frames ) return 0; - if( (vg_ui.mouse_state[1] & mask) && - !(vg_ui.mouse_state[0] & mask) ) - return 1; - else - return 0; -} - -void ui_set_mouse_pos( ui_px x, ui_px y ) -{ - SDL_WarpMouseInWindow( vg.window, x, y ); - vg_ui.mouse[0] = x; - vg_ui.mouse[1] = y; - vg_ui.mouse_pos_overriden = 1; -} - -void ui_prerender(void) -{ - int x, y; - vg_ui.mouse_state[1] = vg_ui.mouse_state[0]; - vg_ui.mouse_state[0] = SDL_GetMouseState( &x, &y ); - vg_ui.mouse_delta[0] = x-vg_ui.mouse[0]; - vg_ui.mouse_delta[1] = y-vg_ui.mouse[1]; - - if( !vg_ui.mouse_pos_overriden ) - { - vg_ui.mouse[0] = x; - vg_ui.mouse[1] = y; - } - - vg_ui.cur_vert = 0; - vg_ui.cur_indice = 0; - vg_ui.vert_start = 0; - vg_ui.indice_start = 0; - vg_ui.focused_control_hit = 0; - vg_ui.cursor = k_ui_cursor_default; - vg_ui.wants_mouse = 0; - vg_ui.mouse_pos_overriden = 0; - - if( vg_ui.ignore_input_frames ) - { - vg_ui.ignore_input_frames --; - return; - } - - if( ui_click_down(UI_MOUSE_LEFT)||ui_click_down(UI_MOUSE_MIDDLE) ) - { - vg_ui.mouse_click[0] = vg_ui.mouse[0]; - vg_ui.mouse_click[1] = vg_ui.mouse[1]; - } -} - -u32 ui_colour( enum ui_scheme_colour id ) -{ - return vg_ui.scheme[ id ]; -} - -/* get an appropriately contrasting colour given the base */ -u32 ui_colourcont( enum ui_scheme_colour id ) -{ - if ( id < k_ui_bg+6 ) return ui_colour( k_ui_fg ); - else if( id < k_ui_fg ) return ui_colour( k_ui_bg+1 ); - else if( id < k_ui_hue ) return ui_colour( k_ui_bg+3 ); - else if( id < k_ui_red+k_ui_brighter ) return ui_colour( k_ui_fg ); - else return ui_colour( k_ui_fg+1 ); -} - -void ui_hex_to_norm( u32 hex, v4f norm ) -{ - norm[0] = ((hex ) & 0xff); - norm[1] = ((hex>>8 ) & 0xff); - norm[2] = ((hex>>16) & 0xff); - norm[3] = ((hex>>24) & 0xff); - v4_muls( norm, 1.0f/255.0f, norm ); -} - -u32 v4f_u32_colour( v4f colour ) -{ - u32 r = colour[0] * 255.0f, - g = colour[1] * 255.0f, - b = colour[2] * 255.0f, - a = colour[3] * 255.0f; - - return r | (g<<8) | (b<<16) | (a<<24); -} - -static void ui_text_glyph( const struct vg_font_face *ff, - u8 glyph, ui_rect out_texcoords ) -{ - const vg_font_char *ch = &ff->map[ glyph ]; - - out_texcoords[0] = ch->x; - out_texcoords[1] = ch->y; - out_texcoords[2] = ch->x + ff->cw; - out_texcoords[3] = ch->y + ff->ch; -} - -u32 ui_opacity( u32 colour, f32 opacity ) -{ - u32 alpha = opacity * 255.0f; - return (colour & 0x00ffffff) | (alpha << 24); -} - -u32 ui_ntext( ui_rect rect, const char *str, u32 len, ui_px scale, - enum ui_align align, u32 colour ) -{ - ui_px glow_text = 0; - - ui_rect text_cursor; - if( colour == 0 ) colour = ui_colour( k_ui_fg ); - - colour &= 0x00ffffff; - - const char *_c = str; - u8 c; - - text_cursor[0] = ui_text_aligned_x( str, rect, scale, align ); - text_cursor[1] = rect[1]; - text_cursor[2] = vg_ui.font->cw*scale; - text_cursor[3] = vg_ui.font->ch*scale; - - u32 printed_chars = 0; - - if( align & (k_ui_align_middle|k_ui_align_bottom) ) - { - ui_px height = ui_text_string_height( str ) * scale; - - if( align & k_ui_align_bottom ) - text_cursor[1] += rect[3]-height; - else - text_cursor[1] += (rect[3]-height)/2; - } - - while( (c = *(_c ++)) ) - { - if( printed_chars >= len ) - { - printed_chars = 0; - text_cursor[1] += vg_ui.font->sy*scale; - text_cursor[0] = ui_text_aligned_x( _c, rect, scale, align ); - text_cursor[0] -= vg_ui.font->sx*scale; - - ui_rect glyph; - ui_text_glyph( vg_ui.font, '\xb6' /*FIXME*/, glyph ); - ui_fill_rect( text_cursor, 0x00ffffff, glyph ); - text_cursor[0] += vg_ui.font->sx*scale; - } - - if( c == '\n' ) - { - text_cursor[1] += vg_ui.font->sy*scale; - text_cursor[0] = ui_text_aligned_x( _c, rect, scale, align ); - printed_chars = 0; - continue; - } - else if( c >= 33 ) - { - ui_rect glyph; - ui_text_glyph( vg_ui.font, c, glyph ); - - ui_rect cursor_clipped; - if( ui_clip( rect, text_cursor, cursor_clipped ) ) - { - if( glow_text ) - { - cursor_clipped[1] += glow_text; - ui_fill_rect( cursor_clipped, 0x00ffffff, glyph ); - cursor_clipped[1] -= glow_text; - } - - ui_fill_rect( cursor_clipped, colour, glyph ); - } - } - else if( c == '\x1B' ) - { - /* vt codes */ - _c ++; - u16 colour_id = 0; - for( int i=0; i<3; i ++ ) - { - if( _c[i] ) - { - if( _c[i] == 'm' ) - { - _c = _c + i + 1; - - switch( colour_id ){ - case '0': colour = ui_colour( k_ui_fg ); break; - case '3'|'0'<<8: colour = ui_colour( k_ui_bg ); break; - case '3'|'1'<<8: colour = ui_colour( k_ui_red ); break; - case '3'|'2'<<8: colour = ui_colour( k_ui_green ); break; - case '3'|'3'<<8: colour = ui_colour( k_ui_yellow ); break; - case '3'|'4'<<8: colour = ui_colour( k_ui_blue ); break; - case '3'|'5'<<8: colour = ui_colour( k_ui_purple ); break; - case '3'|'6'<<8: colour = ui_colour( k_ui_aqua ); break; - case '3'|'7'<<8: colour = 0xffffffff; break; - } - - colour &= 0x00ffffff; - break; - } - - colour_id |= _c[i] << (i*8); - } - else - { - _c = _c +i; - break; - } - } - - continue; - } - else if( c == '\x06' ) - { - glow_text = *_c; - _c ++; - continue; - } - else if( c == '\t' ) - { - text_cursor[0] += vg_ui.font->sx*scale*4; - printed_chars += 4; - continue; - } - - text_cursor[0] += vg_ui.font->sx*scale; - printed_chars ++; - } - - return printed_chars; -} - -u32 ui_text( ui_rect rect, const char *str, ui_px scale, - enum ui_align align, u32 colour ) -{ - return ui_ntext( rect, str, 1024, scale, align, colour ); -} - -void ui_font_face( vg_font_face *ff ) -{ - vg_ui.font = ff; -} - -/* - * Standard layout stuff - * ----------------------------------------------------------------------------- - */ - -void ui_panel( ui_rect in_rect, ui_rect out_panel ) -{ - ui_fill( in_rect, ui_colour( k_ui_bg+1 ) ); - ui_outline( in_rect, 1, ui_colour( k_ui_bg+7 ), 0 ); - rect_copy( in_rect, out_panel ); - ui_rect_pad( out_panel, (ui_px[2]){ k_ui_padding, k_ui_padding } ); -} - -void ui_label( ui_rect rect, const char *text, ui_px size, - ui_px gap, ui_rect r ) -{ - ui_rect l; - ui_px width = (ui_text_line_width(text)+vg_ui.font->sx) * size; - ui_split( rect, k_ui_axis_v, width, gap, l, r ); - ui_text( l, text, 1, k_ui_align_middle_left, 0 ); -} - -void ui_standard_widget( ui_rect inout_panel, ui_rect out_rect, ui_px count ) -{ - ui_px height = (count * vg_ui.font->sy + 18) * k_ui_scale; - ui_split( inout_panel, k_ui_axis_h, height, k_ui_padding, - out_rect, inout_panel ); -} - -void ui_info( ui_rect inout_panel, const char *text ) -{ - ui_rect box; - ui_standard_widget( inout_panel, box, 1 ); - ui_text( box, text, 1, k_ui_align_middle_left, 0 ); -} - -void ui_image( ui_rect rect, GLuint image ) -{ - ui_flush( k_ui_shader_colour, vg.window_x, vg.window_y ); - glActiveTexture( GL_TEXTURE0 ); - glBindTexture( GL_TEXTURE_2D, image ); - ui_fill_rect( rect, 0xffffffff, (ui_px[4]){ 0,256,256,0 } ); - ui_flush( k_ui_shader_image, vg.window_x, vg.window_y ); -} - -void ui_defocus_all(void) -{ - if( vg_ui.focused_control_type == k_ui_control_textbox ){ - SDL_StopTextInput(); - if( vg_ui.textbox.callbacks.escape ) - vg_ui.textbox.callbacks.escape(); - } - - vg_ui.focused_control_id = NULL; - vg_ui.focused_control_hit = 0; - vg_ui.focused_control_type = k_ui_control_none; -} - -/* TODO: split this out into a formatless button and one that auto fills */ -enum ui_button_state ui_colourbutton( ui_rect rect, - enum ui_scheme_colour colour, - enum ui_scheme_colour hover_colour, - enum ui_scheme_colour hi_colour, - bool const fill ) -{ - int clickup= ui_click_up(UI_MOUSE_LEFT), - click = ui_clicking(UI_MOUSE_LEFT) | clickup, - target = ui_inside_rect( rect, vg_ui.mouse_click ) && click, - hover = ui_inside_rect( rect, vg_ui.mouse ); - - u32 col_base = vg_ui.scheme[ colour ], - col_highlight = vg_ui.scheme[ hi_colour? hi_colour: k_ui_fg ], - col_hover = vg_ui.scheme[ hover_colour? hover_colour: - colour + k_ui_brighter ]; - - if( vg_ui.focused_control_type != k_ui_control_none ){ - clickup = 0; - click = 0; - target = 0; - hover = 0; - } - - if( hover ){ - vg_ui.cursor = k_ui_cursor_hand; - } - - if( click ){ - if( target ){ - if( hover ){ - if( clickup ){ - vg_ui.ignore_input_frames = 2; - ui_defocus_all(); - - if( fill ) { - ui_fill( rect, col_highlight ); - rect_copy( rect, vg_ui.click_fader ); - rect_copy( rect, vg_ui.click_fader_end ); - vg_ui.click_fader_end[3] = 0; - ui_rect_center( rect, vg_ui.click_fader_end ); - vg_ui.click_fade_opacity = 1.0f; - } - - return k_ui_button_click; - } - else{ - if( fill ) ui_fill( rect, col_highlight ); - return k_ui_button_holding_inside; - } - } - else{ - if( fill ) ui_fill( rect, col_base ); - ui_outline( rect, 1, col_highlight, 0 ); - return k_ui_button_holding_outside; - } - } - else{ - if( fill ) ui_fill( rect, col_base ); - return k_ui_button_none; - } - } - else{ - if( hover ){ - if( fill ) ui_fill( rect, col_hover ); - return k_ui_button_hover; - } - else{ - if( fill ) ui_fill( rect, col_base ); - return k_ui_button_none; - } - } -} - -enum ui_button_state ui_colourbutton_text( - ui_rect rect, const char *string, ui_px scale, - enum ui_scheme_colour colour ){ - enum ui_button_state state = ui_colourbutton( rect, colour, 0, 0, 1 ); - ui_rect t = { 0,0, ui_text_line_width( string )*scale, 14*scale }; - ui_rect_center( rect, t ); - - u32 text_colour = ui_colourcont(colour); - if( state == k_ui_button_holding_inside ) - text_colour = colour; - - ui_text( t, string, scale, k_ui_align_left, text_colour ); - return state; -} - -enum ui_button_state ui_button_text( ui_rect rect, - const char *string, ui_px scale ) -{ - return ui_colourbutton_text( rect, string, scale, k_ui_bg+4 ); -} - -enum ui_button_state ui_button( ui_rect inout_panel, const char *string ) -{ - ui_rect rect; - ui_standard_widget( inout_panel, rect, 1 ); - return ui_colourbutton_text( rect, string, 1, k_ui_bg+4 ); -} - -static void ui_enum_post(void); -void ui_postrender(void) -{ - if( vg_ui.click_fade_opacity > 0.0f ){ - float scale = vg_ui.click_fade_opacity; - scale = vg_maxf( 1.0f/255.0f, scale*scale ); - - vg_ui.click_fade_opacity -= vg.time_frame_delta * 3.8f; - u32 colour = (0x00ffffff & ui_colour(k_ui_fg))|0x7f000000; - - v4f begin, end, dest; - for( int i=0; i<4; i++ ){ - begin[i] = vg_ui.click_fader[i]; - end[i] = vg_ui.click_fader_end[i]+1; - } - - v4_lerp( end, begin, scale, dest ); - - ui_rect rect; - for( int i=0; i<4; i++ ){ - rect[i] = dest[i]; - } - - ui_fill( rect, colour ); - } - - if( vg_ui.focused_control_type == k_ui_control_enum ){ - ui_enum_post(); - } - else if( vg_ui.focused_control_type == k_ui_control_modal ){ - ui_rect screen = {0,0,vg.window_x,vg.window_y}; - ui_fill( screen, 0xa0000000 ); - ui_rect box = {0,0,400,200}; - - u32 colour = ui_colour(k_ui_fg), - type = vg_ui.modal.options & UI_MODAL_TYPE_BITS; - if ( type == 1 ) colour = ui_colour(k_ui_green); - else if( type == 2 ) colour = ui_colour(k_ui_red); - else if( type == 3 ) colour = ui_colour(k_ui_yellow); - - ui_rect_center( screen, box ); - ui_fill( box, ui_colour(k_ui_bg) ); - ui_outline( box, -1, colour, 0 ); - - ui_rect message; - rect_copy( box, message ); - message[3] = 100; - ui_rect_center( box, message ); - - ui_rect row0, row1, btn; - ui_split_ratio( message, k_ui_axis_h, 0.5f, 0, row0, row1 ); - row0[0] += vg_ui.font->sx; - ui_ntext( row0, vg_ui.modal.message, (box[2]/vg_ui.font->sx)-2, 1, - k_ui_align_left, colour ); - - rect_copy( row1, btn ); - btn[2] = 86; - btn[3] = 28; - ui_rect_center( row1, btn ); - - vg_ui.focused_control_type = k_ui_control_none; /* HACK */ - if( ui_button_text( btn, "OK", 1 ) != 1 ) - vg_ui.focused_control_hit = 1; - vg_ui.focused_control_type = k_ui_control_modal; /* HACK */ - vg_ui.wants_mouse = 1; - } - - ui_flush( k_ui_shader_colour, vg.window_x, vg.window_y ); - - if( !vg_ui.focused_control_hit ){ - ui_defocus_all(); - } - - if( vg_ui.wants_mouse ){ - SDL_SetWindowGrab( vg.window, SDL_FALSE ); - SDL_SetRelativeMouseMode( SDL_FALSE ); - } - else{ - SDL_SetWindowGrab( vg.window, SDL_TRUE ); - SDL_SetRelativeMouseMode( SDL_TRUE ); - } - - SDL_SetCursor( vg_ui.cursor_map[ vg_ui.cursor ] ); - SDL_ShowCursor(1); -} - -/* - * checkbox - * ----------------------------------------------------------------------------- - */ - -int ui_checkbox( ui_rect inout_panel, const char *str_label, i32 *data ) -{ - ui_rect rect, label, box; - ui_standard_widget( inout_panel, rect, 1 ); - - ui_split( rect, k_ui_axis_v, -rect[3], 0, label, box ); - ui_text( label, str_label, k_ui_scale, k_ui_align_middle_left, 0 ); - - int changed = ui_colourbutton( box, k_ui_bg, 0, 0, 1 )==1; - if( changed ) - *data = (*data) ^ 0x1; - - if( *data ){ - ui_rect_pad( box, (ui_px[2]){4,4} ); - ui_fill( box, ui_colour( k_ui_orange ) ); - } - - return changed; -} - -/* - * Dropdown / Enum - * ----------------------------------------------------------------------------- - */ - -/* - * unfortunately no return value since we only find out that event in the - * postrender step. - */ -void ui_enum( ui_rect inout_panel, const char *str_label, - struct ui_enum_opt *options, u32 len, i32 *value ) -{ - ui_rect rect, label, box; - ui_standard_widget( inout_panel, rect, 1 ); - ui_label( rect, str_label, k_ui_scale, 0, box ); - - const char *display = "OUT OF RANGE"; - int valid = 0; - for( u32 i=0; iterminator to start */ - int remaining_length = strlen( vg_ui.textbuf )+1-end; - memmove( &vg_ui.textbuf[ start ], - &vg_ui.textbuf[ end ], - remaining_length ); - return start; -} - -static void _ui_textbox_to_clipboard(void){ - int start, end; - _ui_textbox_make_selection( &start, &end ); - char buffer[512]; - - if( end-start ){ - memcpy( buffer, &vg_ui.textbuf[ start ], end-start ); - buffer[ end-start ] = 0x00; - SDL_SetClipboardText( buffer ); - } -} - -static void _ui_textbox_change_callback(void){ - if( vg_ui.textbox.callbacks.change ){ - vg_ui.textbox.callbacks.change( vg_ui.textbuf, vg_ui.textbox.len ); - - /* we gave permission to modify the buffer in this callback so.. */ - int len = strlen( vg_ui.textbuf ); - vg_ui.textbox.cursor_user = VG_MIN( vg_ui.textbox.cursor_user, len ); - vg_ui.textbox.cursor_pos = VG_MIN( vg_ui.textbox.cursor_pos, len ); - } -} - -void ui_start_modal( const char *message, u32 options ); -static void _ui_textbox_clipboard_paste(void){ - if( !SDL_HasClipboardText() ) - return; - - char *text = SDL_GetClipboardText(); - - if( !text ) - return; - - int datastart = _ui_textbox_delete_char( 0 ); - int length = strlen( text ); - - if( (vg_ui.textbox.len - strlen(vg_ui.textbuf)) < length ){ - ui_start_modal( "Clipboard content exceeds buffer size.", UI_MODAL_BAD ); - return; - } - - int cpylength = _ui_textbox_makeroom( datastart, length ); - - memcpy( vg_ui.textbuf + datastart, text, cpylength); - _ui_textbox_move_cursor( &vg_ui.textbox.cursor_user, - &vg_ui.textbox.cursor_pos, cpylength, 1 ); - SDL_free( text ); - _ui_textbox_change_callback(); -} - -void _ui_textbox_put_char( char c ) -{ - vg_ui.textbox.cursor_user = _ui_textbox_delete_char(0); - if( (vg_ui.textbox.len - strlen(vg_ui.textbuf)) <= 1 ) return; - - if( _ui_textbox_makeroom( vg_ui.textbox.cursor_user, 1 ) ) - vg_ui.textbuf[ vg_ui.textbox.cursor_user ] = c; - - _ui_textbox_move_cursor( &vg_ui.textbox.cursor_user, - &vg_ui.textbox.cursor_pos, 1, 1 ); -} - -/* Receed secondary cursor */ -void _ui_textbox_left_select(void) -{ - _ui_textbox_move_cursor( &vg_ui.textbox.cursor_user, NULL, -1, 0 ); -} - -/* Match and receed both cursors */ -void _ui_textbox_left(void) -{ - int cursor_diff = vg_ui.textbox.cursor_pos - vg_ui.textbox.cursor_user? 0: 1; - - _ui_textbox_move_cursor( &vg_ui.textbox.cursor_user, - &vg_ui.textbox.cursor_pos, -cursor_diff, 1 ); -} - -void _ui_textbox_up(void) -{ - if( vg_ui.textbox.flags & UI_TEXTBOX_MULTILINE ){ - int line_begin = vg_ui.textbox.cursor_user; - - while( line_begin ){ - if( vg_ui.textbuf[ line_begin-1 ] == '\n' ){ - break; - } - - line_begin --; - } - - if( line_begin ){ - int line_above_begin = line_begin-1; - - while( line_above_begin ){ - if( vg_ui.textbuf[ line_above_begin-1 ] == '\n' ){ - break; - } - - line_above_begin --; - } - - int offset = vg_ui.textbox.cursor_user - line_begin, - line_length_above = line_begin - line_above_begin -1; - - offset = VG_MIN( line_length_above, offset ); - - vg_ui.textbox.cursor_user = line_above_begin+offset; - vg_ui.textbox.cursor_pos = line_above_begin+offset; - } - else{ - vg_ui.textbox.cursor_user = line_begin; - vg_ui.textbox.cursor_pos = line_begin; - } - } - else{ - if( vg_ui.textbox.callbacks.up ){ - vg_ui.textbox.callbacks.up( vg_ui.textbuf, vg_ui.textbox.len ); - } - } -} - -void _ui_textbox_down(void) -{ - if( vg_ui.textbox.flags & UI_TEXTBOX_MULTILINE ){ - int line_begin = vg_ui.textbox.cursor_user; - - while( line_begin ){ - if( vg_ui.textbuf[ line_begin-1 ] == '\n' ){ - break; - } - - line_begin --; - } - - int line_below_begin = vg_ui.textbox.cursor_user; - - while(1){ - if( vg_ui.textbuf[ line_below_begin ] == '\0' ){ - vg_ui.textbox.cursor_user = line_below_begin; - vg_ui.textbox.cursor_pos = line_below_begin; - return; - } - - if( vg_ui.textbuf[ line_below_begin ] == '\n' ){ - line_below_begin ++; - break; - } - - line_below_begin ++; - } - - int line_below_end = line_below_begin; - while(1){ - if( vg_ui.textbuf[ line_below_end ] == '\0' || - vg_ui.textbuf[ line_below_end ] == '\n' ){ - line_below_end ++; - break; - } - line_below_end ++; - } - - int offset = vg_ui.textbox.cursor_user - line_begin, - line_length_below = line_below_end - line_below_begin -1; - - offset = VG_MIN( line_length_below, offset ); - - vg_ui.textbox.cursor_user = line_below_begin+offset; - vg_ui.textbox.cursor_pos = line_below_begin+offset; - } - else{ - if( vg_ui.textbox.callbacks.down ){ - vg_ui.textbox.callbacks.down( vg_ui.textbuf, vg_ui.textbox.len ); - } - } -} - -void _ui_textbox_right_select(void) -{ - _ui_textbox_move_cursor( &vg_ui.textbox.cursor_user, NULL, 1, 0 ); -} - -void _ui_textbox_right(void) -{ - int cursor_diff = vg_ui.textbox.cursor_pos - vg_ui.textbox.cursor_user? 0: 1; - - _ui_textbox_move_cursor( &vg_ui.textbox.cursor_user, - &vg_ui.textbox.cursor_pos, +cursor_diff, 1 ); -} - -void _ui_textbox_backspace(void) -{ - if( vg_ui.focused_control_type == k_ui_control_textbox ){ - vg_ui.textbox.cursor_user = _ui_textbox_delete_char( -1 ); - vg_ui.textbox.cursor_pos = vg_ui.textbox.cursor_user; - _ui_textbox_change_callback(); - } -} - -void _ui_textbox_delete(void) -{ - if( vg_ui.focused_control_type == k_ui_control_textbox ){ - vg_ui.textbox.cursor_user = _ui_textbox_delete_char( 1 ); - vg_ui.textbox.cursor_pos = vg_ui.textbox.cursor_user; - _ui_textbox_change_callback(); - } -} - -void _ui_textbox_home_select(void) -{ - i32 start = vg_ui.textbox.cursor_user; - - if( vg_ui.textbox.flags & UI_TEXTBOX_MULTILINE ){ - while( start ){ - if( vg_ui.textbuf[start-1] == '\n' ) - break; - else - start --; - } - } - else - start = 0; - - vg_ui.textbox.cursor_user = start; -} - -void _ui_textbox_home(void) -{ - _ui_textbox_home_select(); - vg_ui.textbox.cursor_pos = vg_ui.textbox.cursor_user; -} - -void _ui_textbox_end_select(void) -{ - i32 end = vg_ui.textbox.cursor_user; - - if( vg_ui.textbox.flags & UI_TEXTBOX_MULTILINE ){ - while( vg_ui.textbuf[end] ){ - if( vg_ui.textbuf[end] == '\n' ) - break; - else - end ++; - } - } - else - end = VG_MIN( vg_ui.textbox.len-1, strlen(vg_ui.textbuf) ); - - vg_ui.textbox.cursor_user = end; -} - -void _ui_textbox_end(void) -{ - _ui_textbox_end_select(); - vg_ui.textbox.cursor_pos = vg_ui.textbox.cursor_user; -} - -void _ui_textbox_select_all(void) -{ - _ui_textbox_move_cursor( &vg_ui.textbox.cursor_user, NULL, 10000, 0); - _ui_textbox_move_cursor( &vg_ui.textbox.cursor_pos, NULL, -10000, 0); -} - -void _ui_textbox_cut(void) -{ - _ui_textbox_to_clipboard(); - vg_ui.textbox.cursor_user = _ui_textbox_delete_char(0); - vg_ui.textbox.cursor_pos = vg_ui.textbox.cursor_user; - _ui_textbox_change_callback(); -} - -void _ui_textbox_enter(void) -{ - if( vg_ui.focused_control_type == k_ui_control_textbox ){ - vg_ui.ignore_input_frames = 2; - - if( vg_ui.textbox.callbacks.enter ) - vg_ui.textbox.callbacks.enter( vg_ui.textbuf, vg_ui.textbox.len ); - - if( vg_ui.focused_control_type != k_ui_control_textbox ) return; - - if( vg_ui.textbox.flags & UI_TEXTBOX_MULTILINE ){ - _ui_textbox_put_char( '\n' ); - _ui_textbox_change_callback(); - } - else{ - if( !(vg_ui.textbox.flags & UI_TEXTBOX_AUTOFOCUS ) ) - ui_defocus_all(); - } - } -} - -/* - * based on a visual character coordinate relative to the anchor of the textbox, - * this works out the linear place in the buffer that coordinate maps to - * - * input coordinates go in co[0], co[1], and the result index is in co[2] - */ -static void _ui_textbox_calc_index_from_grid( int co[3], int wrap_length ){ - int i[3] = {0,0,0}; - - char c; - while( (c = vg_ui.textbuf[i[2]]) ){ - if( i[1]==co[1] && i[0]>=co[0] ) break; - - if( i[0] >= wrap_length ){ - i[1] ++; - i[0] = 0; - } - - if( c >= 32 && c <= 126 ){ - i[0] ++; - i[2] ++; - } - else if( c == '\n' ){ - i[1] ++; - - if( i[1] > co[1] ) break; - - i[2] ++; - i[0] = 0; - } - else i[2] ++; - } - - co[0] = i[0]; - co[1] = i[1]; - co[2] = i[2]; -} - -/* - * based on the index specied in co[2], work out the visual character - * coordinates and store them in co[0], co[1] - */ -static void _ui_textbox_index_calc_coords( int co[3], int wrap_length ){ - co[0] = 0; - co[1] = 0; - - char c; - int i=0; - - while( (c = vg_ui.textbuf[i ++]) ){ - if( i > co[2] ) break; - if( co[0] >= wrap_length ){ - co[1] ++; - co[0] = 0; - } - if( c >= 32 && c <= 126 ) co[0] ++; - else if( c == '\n' ){ - co[1] ++; - co[0] = 0; - } - } -} - -/* - * calculate the number of characters remaining until either: - * - the wrap_length limit is hit - * - end of the line/string - * - * index must be fully populated with visual X/Y, and linear index - */ -static int _ui_textbox_run_remaining( int index[3], int wrap_length ){ - int i=0, printed_chars=0; - char c; - while( (c = vg_ui.textbuf[index[2] + (i ++)]) ){ - if( index[0]+i >= wrap_length ) break; - if( c >= 32 && c <= 126 ) printed_chars ++; - else if( c == '\n' ) break; - } - - return printed_chars+1; -} - -int ui_textbox( ui_rect inout_panel, const char *label, - char *buf, u32 len, u32 lines, u32 flags, - struct ui_textbox_callbacks *callbacks ) -{ - if( lines > 1 ) flags |= UI_TEXTBOX_MULTILINE; - - ui_rect rect; - ui_standard_widget( inout_panel, rect, lines ); - - if( label ) - ui_label( rect, label, 1, 0, rect ); - - int clickup= ui_click_up(UI_MOUSE_LEFT), - clickdown = ui_click_down(UI_MOUSE_LEFT), - click = ui_clicking(UI_MOUSE_LEFT) | clickup, - target = ui_inside_rect( rect, vg_ui.mouse_click ) && click, - hover = ui_inside_rect( rect, vg_ui.mouse ); - - /* allow instant transitions from textbox->textbox */ - if( (vg_ui.focused_control_type != k_ui_control_none) && - (vg_ui.focused_control_type != k_ui_control_textbox) ){ - clickup = 0; - clickdown = 0; - click = 0; - target = 0; - hover = 0; - flags &= ~UI_TEXTBOX_AUTOFOCUS; - } - - u32 col_base = ui_colour( k_ui_bg ), - col_highlight = ui_colour( k_ui_fg ), - col_cursor = (0x00ffffff & ui_colour(k_ui_fg))|0x7f000000; - - ui_px border = -1; - - ui_rect text_rect; - rect_copy( rect, text_rect ); - - if( flags & UI_TEXTBOX_MULTILINE ) text_rect[3] = rect[3]-16; - else text_rect[3] = vg_ui.font->sy; - - text_rect[2] -= 16; - ui_rect_center( rect, text_rect ); - - ui_px wrap_length = 1024; - - if( flags & UI_TEXTBOX_WRAP ) - wrap_length = text_rect[2] / vg_ui.font->sx; - - if( hover ) - { - vg_ui.cursor = k_ui_cursor_ibeam; - } - - if( vg_ui.focused_control_id == buf ) - { - ui_fill( rect, col_base ); - ui_ntext( text_rect, buf, wrap_length, 1, k_ui_align_left, 0 ); - - if( !(flags & UI_TEXTBOX_AUTOFOCUS) && ((clickup||clickdown) && !target)) - { - ui_defocus_all(); - } - else - { - vg_ui.focused_control_hit = 1; - if( click && target ){ - int p0[3] ={ - (vg_ui.mouse_click[0] - text_rect[0]) / vg_ui.font->sx, - (vg_ui.mouse_click[1] - text_rect[1]) / vg_ui.font->sy, - -1 - }, - p1[3] = { - (vg_ui.mouse[0] - text_rect[0]) / vg_ui.font->sx, - (vg_ui.mouse[1] - text_rect[1]) / vg_ui.font->sy, - -1 - }; - - if( flags & UI_TEXTBOX_MULTILINE ) - { - _ui_textbox_calc_index_from_grid( p0, wrap_length ); - _ui_textbox_calc_index_from_grid( p1, wrap_length ); - - vg_ui.textbox.cursor_pos = p0[2]; - vg_ui.textbox.cursor_user = p1[2]; - } - else - { - int max = strlen( buf ); - vg_ui.textbox.cursor_pos = VG_MAX( 0, VG_MIN( max, p0[0] )), - vg_ui.textbox.cursor_user = VG_MAX( 0, VG_MIN( max, p1[0] )); - } - } - - ui_outline( rect, -2, vg_ui.scheme[ k_ui_orange ], 0 ); - - ui_rect cursor; - - int c0 = vg_ui.textbox.cursor_pos, - c1 = vg_ui.textbox.cursor_user, - start = VG_MIN( c0, c1 ), - end = VG_MAX( c0, c1 ), - chars = end-start; - - if( flags & (UI_TEXTBOX_WRAP|UI_TEXTBOX_MULTILINE) ) - { - int pos[3], remaining = chars; - - pos[2] = start; - _ui_textbox_index_calc_coords( pos, wrap_length ); - - if( start==end ) - { - cursor[0] = text_rect[0] + pos[0]*vg_ui.font->sx-1; - cursor[1] = text_rect[1] + pos[1]*14; - cursor[2] = 2; - cursor[3] = 13; - ui_fill( cursor, col_cursor ); - rect_copy( cursor, vg_ui.click_fader_end ); - } - else - { - while( remaining ) - { - int run = _ui_textbox_run_remaining( pos, wrap_length ); - run = VG_MIN( run, remaining ); - - cursor[0] = text_rect[0] + pos[0]*vg_ui.font->sx-1; - cursor[1] = text_rect[1] + pos[1]*14; - cursor[2] = (float)(run)*(float)vg_ui.font->sx; - cursor[3] = 13; - - ui_fill( cursor, col_cursor ); - - remaining -= run; - pos[0] = 0; - pos[1] ++; - pos[2] += run; - } - rect_copy( cursor, vg_ui.click_fader_end ); - } - } - else - { - cursor[0] = text_rect[0] + start*vg_ui.font->sx-1; - cursor[1] = text_rect[1]; - cursor[3] = 13; - - if( start==end ) - { - cursor[2] = 2; - } - else - { - cursor[2] = (float)(chars)*(float)vg_ui.font->sx; - } - - if( (vg_ui.click_fade_opacity<=0.0f) && - ui_clip( rect, cursor, cursor ) ) - { - ui_fill( cursor, col_cursor ); - } - - rect_copy( cursor, vg_ui.click_fader_end ); - } - } - - return 0; - } - - if( click || (flags & UI_TEXTBOX_AUTOFOCUS) ) - { - if( (target && hover) || (flags & UI_TEXTBOX_AUTOFOCUS) ) - { - ui_defocus_all(); - - ui_fill( rect, col_highlight ); - vg_ui.ignore_input_frames = 2; - rect_copy( rect, vg_ui.click_fader ); - rect_copy( rect, vg_ui.click_fader_end ); - - vg_ui.click_fade_opacity = 1.0f; - vg_ui.textbuf = buf; - vg_ui.focused_control_hit = 1; - vg_ui.focused_control_type = k_ui_control_textbox; - vg_ui.textbox.len = len; - vg_ui.textbox.flags = flags; - vg_ui.textbox.cursor_pos = 0; - vg_ui.textbox.cursor_user = 0; - - if( callbacks ) - { - vg_ui.textbox.callbacks = *callbacks; - } - else - { - vg_ui.textbox.callbacks.change = NULL; - vg_ui.textbox.callbacks.down = NULL; - vg_ui.textbox.callbacks.up = NULL; - vg_ui.textbox.callbacks.enter = NULL; - } - - SDL_StartTextInput(); - } - } - - ui_fill( rect, col_base ); - - if( hover ) - { - ui_outline( rect, -1, col_highlight, 0 ); - } - - ui_ntext( text_rect, buf, wrap_length, 1, k_ui_align_left, 0 ); - return 0; -} - -/* - * Tabs - * ----------------------------------------------------------------------------- - */ - -void ui_tabs( ui_rect inout_panel, ui_rect out_content_panel, - const char **titles, u32 count, i32 *page ) -{ - ui_rect bar; - ui_standard_widget( inout_panel, bar, 1 ); - - i32 cur_page = *page; - - f32 width = (f32)inout_panel[2] / (f32)count; - - ui_px h = (inout_panel[1] + inout_panel[3]) - (bar[1]+bar[3]); - inout_panel[1] = bar[1]+bar[3]; - inout_panel[3] = h; - - ui_fill( inout_panel, ui_colour( k_ui_bg+2 ) ); - ui_outline( inout_panel, 1, ui_colour( k_ui_bg+5 ), 0 ); - - rect_copy( inout_panel, out_content_panel ); - ui_rect_pad( out_content_panel, (ui_px[2]){ k_ui_padding, k_ui_padding } ); - - /* place buttons */ - for( i32 i=0; ikey == ev.sym ){ - if( mapping->mod == 0 ){ - if( mod == 0 ){ - mapping->handler(); - return; - } - } - else if( (mod & mapping->mod) == mapping->mod ){ - mapping->handler(); - return; - } - } - } -} - -/* - * Callback for text entry mode - */ -void ui_proc_utf8( const char *text ) -{ - if( vg_ui.focused_control_type == k_ui_control_textbox ){ - const char *ptr = text; - - while( *ptr ){ - if( *ptr != '`' ) _ui_textbox_put_char( *ptr ); - ptr ++; - } - - _ui_textbox_change_callback(); - } -} - -/* - * Development utils - * ----------------------------------------------------------------------------- - */ - -void ui_dev_colourview(void) -{ - ui_rect window = {vg.window_x-256,0,256,vg.window_y}, swatch; - - const char *names[vg_list_size(vg_ui.scheme)] = { - [k_ui_bg] = "k_ui_bg", "k_ui_bg+1", "k_ui_bg+2", "k_ui_bg+3", - "k_ui_bg+4", "k_ui_bg+5", "k_ui_bg+6", "k_ui_bg+7", - - [k_ui_fg] = "k_ui_fg", "k_ui_fg+1", "k_ui_fg+2", "k_ui_fg+3", - "k_ui_fg+4", "k_ui_fg+5", "k_ui_fg+6", "k_ui_fg+7", - - [k_ui_red] = "k_ui_red", "k_ui_orange", "k_ui_yellow", "k_ui_green", - "k_ui_aqua", "k_ui_blue", "k_ui_purple", "k_ui_gray", - "k_ui_red+8","k_ui_orange+8","k_ui_yellow+8","k_ui_green+8", - "k_ui_aqua+8","k_ui_blue+8","k_ui_purple+8","k_ui_gray+8" }; - - ui_rect col[2]; - ui_split_ratio( window, k_ui_axis_v, 0.5f, 0, col[0], col[1] ); - - for( int i=0; i>16) - -#define UI_TEXTBOX_MULTILINE 0x1 -#define UI_TEXTBOX_WRAP 0x2 -#define UI_TEXTBOX_AUTOFOCUS 0x4 - -#define UI_MODAL_OK 0x0 -#define UI_MODAL_GOOD 0x1 -#define UI_MODAL_BAD 0x2 -#define UI_MODAL_WARN 0x3 -#define UI_MODAL_TYPE_BITS 0x3 - -#define UI_MOUSE_LEFT (SDL_BUTTON(SDL_BUTTON_LEFT)) -#define UI_MOUSE_RIGHT (SDL_BUTTON(SDL_BUTTON_RIGHT)) -#define UI_MOUSE_MIDDLE (SDL_BUTTON(SDL_BUTTON_MIDDLE)) - -#define UI_TOP 0x1 -#define UI_LEFT 0x2 -#define UI_BOTTOM 0x4 -#define UI_RIGHT 0x8 - -struct vg_imgui -{ - struct ui_vert *vertex_buffer; - u16 *indice_buffer; - u32 max_verts, max_indices, - cur_vert, cur_indice, - vert_start, indice_start; - - union { - void *focused_control_id; /* uses the memory location of various locking - controls as an id */ - char *textbuf; - i32 *ptr_enum; - }; - - u32 focused_control_hit; - enum ui_control_type{ - k_ui_control_none, - k_ui_control_textbox, - k_ui_control_enum, - k_ui_control_modal - } - focused_control_type; - - union{ /* controls that can be focused */ - struct ui_textbuf{ - int cursor_user, cursor_pos; - u32 len; - u32 flags; - - struct ui_textbox_callbacks{ - void (*enter)( char *, u32 ), - (*up)( char *, u32 ), - (*down)( char *, u32 ), - (*change)( char *, u32 ), - (*escape)( void ); - } - callbacks; - } - textbox; - - struct ui_enum{ - struct ui_enum_opt{ - i32 value; - const char *alias; - } - *options; - u32 option_count; - ui_rect rect; - } - _enum; - }; - - struct ui_modal{ - const char *message; - u32 options; - - struct ui_modal_callbacks{ - void (*close)(u32); - } - callbacks; - } - modal; - - GLuint tex_glyphs, vao, vbo, ebo, tex_bg; - v2f bg_inverse_ratio; - - ui_px mouse[2], mouse_delta[2], mouse_click[2]; - u32 mouse_state[2]; - u32 ignore_input_frames; - bool mouse_pos_overriden; - int wants_mouse; - - ui_rect click_fader, click_fader_end; - float click_fade_opacity; - f32 frosting; - - ui_scheme scheme; - const vg_font_face *font; - v2f inverse_font_sheet; - - enum ui_cursor{ - k_ui_cursor_default, - k_ui_cursor_ibeam, - k_ui_cursor_hand, - k_ui_cursor_max - } - cursor; - - SDL_Cursor *cursor_map[ k_ui_cursor_max ]; - v4f colour; - - f32 hue; /* lol this sucks */ -} -extern vg_ui; - -enum ui_button_state { - k_ui_button_none = 0x0, - k_ui_button_click = 0x1, - k_ui_button_holding_inside = 0x2, - k_ui_button_holding_outside = 0x4, - k_ui_button_hover = 0x8 -}; - -/* TODO: docu.. */ - -void vg_ui_init(void); -void rect_copy( ui_rect a, ui_rect b ); -void ui_flush( enum ui_shader shader, f32 w, f32 h ); -struct ui_vert *ui_fill_rect( ui_rect rect, u32 colour, ui_px uv[4] ); -struct ui_vert *ui_fill( ui_rect rect, u32 colour ); -void ui_outline( ui_rect rect, ui_px thickness, u32 colour, u32 mask ); -void ui_split( ui_rect rect, enum ui_axis other, ui_px width, ui_px gap, - ui_rect l, ui_rect r ); -void ui_rect_center( ui_rect parent, ui_rect rect ); -void ui_fit_item( ui_rect rect, ui_px size[2], ui_rect d ); -void ui_split_ratio( ui_rect rect, enum ui_axis dir, float ratio, - ui_px gap, ui_rect l, ui_rect r ); -void ui_rect_pad( ui_rect rect, ui_px pad[2] ); -ui_px ui_text_line_width( const char *str ); -ui_px ui_text_string_height( const char *str ); -ui_px ui_text_aligned_x( const char *str, ui_rect rect, ui_px scale, - enum ui_align align ); -int ui_clip( ui_rect parent, ui_rect child, ui_rect clipped ); -int ui_inside_rect( ui_rect rect, ui_px co[2] ); -int ui_click_down( u32 mask ); -int ui_clicking( u32 mask ); -int ui_click_up( u32 mask ); -void ui_set_mouse_pos( ui_px x, ui_px y ); -void ui_prerender(void); -u32 ui_colour( enum ui_scheme_colour id ); - -/* get an appropriately contrasting colour given the base */ -u32 ui_colourcont( enum ui_scheme_colour id ); - -void ui_hex_to_norm( u32 hex, v4f norm ); -u32 v4f_u32_colour( v4f colour ); - -u32 ui_opacity( u32 colour, f32 opacity ); -void ui_font_face( vg_font_face *ff ); -u32 ui_ntext( ui_rect rect, const char *str, u32 len, ui_px scale, - enum ui_align align, u32 colour ); -u32 ui_text( ui_rect rect, const char *str, ui_px scale, - enum ui_align align, u32 colour ); -void ui_panel( ui_rect in_rect, ui_rect out_panel ); -void ui_label( ui_rect rect, const char *text, ui_px size, - ui_px gap, ui_rect r ); -void ui_standard_widget( ui_rect inout_panel, ui_rect out_rect, ui_px count ); -void ui_info( ui_rect inout_panel, const char *text ); -void ui_image( ui_rect rect, GLuint image ); -void ui_defocus_all(void); -enum ui_button_state ui_colourbutton( ui_rect rect, - enum ui_scheme_colour colour, - enum ui_scheme_colour hover_colour, - enum ui_scheme_colour hi_colour, - bool const fill ); -enum ui_button_state ui_colourbutton_text( - ui_rect rect, const char *string, ui_px scale, - enum ui_scheme_colour colour ); -enum ui_button_state ui_button_text( ui_rect rect, - const char *string, ui_px scale ); -enum ui_button_state ui_button( ui_rect inout_panel, const char *string ); -void ui_postrender(void); - -int ui_checkbox( ui_rect inout_panel, const char *str_label, i32 *data ); -void ui_enum( ui_rect inout_panel, const char *str_label, - struct ui_enum_opt *options, u32 len, i32 *value ); -bool ui_slider( ui_rect inout_panel, const char *str_label, - f32 min, f32 max, f32 *value, const char *format ); -void ui_colourpicker( ui_rect inout_panel, const char *str_label, v4f value ); -int ui_textbox( ui_rect inout_panel, const char *label, - char *buf, u32 len, u32 lines, u32 flags, - struct ui_textbox_callbacks *callbacks ); -void ui_tabs( ui_rect inout_panel, ui_rect out_content_panel, - const char **titles, u32 count, i32 *page ); -void ui_start_modal( const char *message, u32 options ); -void ui_proc_key( SDL_Keysym ev ); -void ui_proc_utf8( const char *text ); -void ui_dev_colourview(void); - -void _ui_textbox_move_cursor( int *cursor0, int *cursor1, - int dir, int snap_together ); -int _ui_textbox_delete_char( int direction ); -void _ui_textbox_put_char( char c ); -void _ui_textbox_up(void); -void _ui_textbox_left(void); -void _ui_textbox_left_select(void); -void _ui_textbox_down(void); -void _ui_textbox_right_select(void); -void _ui_textbox_right(void); -void _ui_textbox_backspace(void); -void _ui_textbox_delete(void); -void _ui_textbox_home_select(void); -void _ui_textbox_home(void); -void _ui_textbox_end_select(void); -void _ui_textbox_end(void); -void _ui_textbox_select_all(void); -void _ui_textbox_cut(void); -void _ui_textbox_enter(void); diff --git a/vg_input.c b/vg_input.c index 74732f4..ee0df5f 100644 --- a/vg_input.c +++ b/vg_input.c @@ -128,13 +128,33 @@ void vg_input_device_event( SDL_Event *ev ) } } +static void vg_input_set_active_controller( int index, const char *why ) +{ + if( vg_input.active_controller_index != index ) + { + vg_input.display_input_type = + SDL_GameControllerGetType( vg_input.controllers[index].handle ); + vg_input.active_controller_index = index; + vg_info( "Switching controller index to #%d. (%s)\n", index, why ); + } + + if( vg_input.display_input_method != k_input_method_controller ) + { + vg_input.display_input_method = k_input_method_controller; + vg_info( "Switching input method to controller. (%s)\n", why ); + } +} + void vg_input_controller_event( SDL_Event *ev ) { - if( ev->type == SDL_CONTROLLERAXISMOTION ){ - for( int i=0; itype == SDL_CONTROLLERAXISMOTION ) + { + for( int i=0; icaxis.which == esta->instance_id ){ + if( ev->caxis.which == esta->instance_id ) + { float value = (float)ev->caxis.value / 32767.0f; if( ev->caxis.axis == SDL_CONTROLLER_AXIS_LEFTX || @@ -146,6 +166,8 @@ void vg_input_controller_event( SDL_Event *ev ) high = vg_maxf( 0.0f, fabsf(value) - deadz ); value = vg_signf(value) * (high / (1.0f-deadz)); + if( fabsf(value) > 0.5f ) + vg_input_set_active_controller( i, "Stick pushed >|0.5|" ); } esta->axises[ ev->caxis.axis ] = value; @@ -153,50 +175,26 @@ void vg_input_controller_event( SDL_Event *ev ) } } } - else if( ev->type == SDL_CONTROLLERBUTTONDOWN ){ - struct vg_controller *active = NULL; - - if( vg_input.active_controller_index >= 0 ) - active = &vg_input.controllers[vg_input.active_controller_index]; - - if( !active || (ev->cbutton.which != active->instance_id) ){ - active = NULL; - vg_input.active_controller_index = -1; - vg_input.display_input_method = k_input_method_kbm; - - for( int i=0; icbutton.which ){ - active = &vg_input.controllers[i]; - vg_input.active_controller_index = i; - vg_input.display_input_type = - SDL_GameControllerGetType(active->handle); - break; - } - } - - if( active ){ - vg_info( "Switching active controller index to #%d\n", - vg_input.active_controller_index ); - } - else{ - vg_error( "Input out of range (SDL_JoystickID#%d)\n", - ev->cbutton.which ); - } - } - - if( active ){ - if( vg_input.display_input_method != k_input_method_controller ){ - vg_input.display_input_method = k_input_method_controller; - vg_info( "display_input: k_input_method_controller\n" ); + else if( ev->type == SDL_CONTROLLERBUTTONDOWN ) + { + for( int i=0; iinstance_id == ev->cbutton.which ) + { + vg_input_set_active_controller( i, "Button press" ); + esta->buttons[ ev->cbutton.button ] = 1; + break; } - active->buttons[ ev->cbutton.button ] = 1; } } - else if( ev->type == SDL_CONTROLLERBUTTONUP ){ - for( int i=0; itype == SDL_CONTROLLERBUTTONUP ) + { + for( int i=0; icbutton.which == esta->instance_id ){ + if( ev->cbutton.which == esta->instance_id ) + { esta->buttons[ ev->cbutton.button ] = 0; break; } @@ -210,10 +208,13 @@ void vg_process_inputs(void) vg_input.sdl_keys = SDL_GetKeyboardState( &count ); vg_input.sdl_mouse = SDL_GetMouseState(NULL,NULL); - if( vg_input.display_input_method != k_input_method_kbm ){ + if( vg_input.display_input_method != k_input_method_kbm ) + { /* check for giving keyboard priority */ - for( int i=0; i 64.0f ) + { + vg_input.display_input_method = k_input_method_kbm; + vg_input.hidden_mouse_travel = 0.0f; + vg_info( "display_input: k_input_method_kbm (mouse move)\n" ); } } + else + vg_input.hidden_mouse_travel = 0.0f; } void async_vg_input_init( void *payload, u32 size ) @@ -383,7 +394,7 @@ next_code:; else if( op == vg_gui_visible ) pc ++; else - vg_fatal_error( "unknown op\n" ); + vg_fatal_error( "unknown op (%u)\n", op ); goto next_code; } @@ -395,10 +406,10 @@ const char *controller_button_str( SDL_GameControllerButton button ) { static const char *controller_glyphs[ SDL_CONTROLLER_BUTTON_MAX ][2] = { /* xbox/generic playstation */ - [ SDL_CONTROLLER_BUTTON_A ] = { KGRN "\x06\x02\x85",KBLU "\x06\x02\x82" }, - [ SDL_CONTROLLER_BUTTON_B ] = { KRED "\x06\x02\x86",KRED "\x06\x02\x81" }, - [ SDL_CONTROLLER_BUTTON_X ] = { KBLU "\x06\x02\x83",KMAG "\x06\x02\x7f" }, - [ SDL_CONTROLLER_BUTTON_Y ] = { KYEL "\x06\x02\x84",KGRN "\x06\x02\x80" }, + [ SDL_CONTROLLER_BUTTON_A ] = { KGRN "\x06\x02\x85",KBLU "\x06\x02\x82" }, + [ SDL_CONTROLLER_BUTTON_B ] = { KRED "\x06\x02\x86",KRED "\x06\x02\x81" }, + [ SDL_CONTROLLER_BUTTON_X ] = { KBLU "\x06\x02\x83",KMAG "\x06\x02\x7f" }, + [ SDL_CONTROLLER_BUTTON_Y ] = { KYEL "\x06\x02\x84",KGRN "\x06\x02\x80" }, [ SDL_CONTROLLER_BUTTON_LEFTSTICK ] = { "\x87","\x87" }, [ SDL_CONTROLLER_BUTTON_RIGHTSTICK ] = { "\x8b","\x8b" }, [ SDL_CONTROLLER_BUTTON_LEFTSHOULDER ] = { "\x91","\x91" }, diff --git a/vg_input.h b/vg_input.h index a203123..1636e07 100644 --- a/vg_input.h +++ b/vg_input.h @@ -47,6 +47,7 @@ struct vg_input { const u8 *sdl_keys; u32 sdl_mouse; + f32 hidden_mouse_travel; struct vg_controller{ SDL_GameController *handle; /* handle for controller. NULL if unused */ diff --git a/vg_io.c b/vg_io.c index 76e8ca3..b100992 100644 --- a/vg_io.c +++ b/vg_io.c @@ -108,17 +108,16 @@ void vg_dir_close( vg_dir *dir ) dir->index = 0; } -void vg_file_print_invalid( FILE *fp ) +void vg_file_error_info( FILE *fp ) { - if( feof( fp )) { + if( feof( fp )) + { vg_error( "mdl_open: header too short\n" ); } - else{ - if( ferror( fp )) - vg_error( "mdl_open: %s\n", strerror(errno) ); - else - vg_error( "mdl_open: unkown failure\n" ); - + else + { + if( ferror( fp )) vg_info( "fopen: %s\n", strerror(errno) ); + else vg_info( "fopen: unkown failure\n" ); } } @@ -128,13 +127,15 @@ void vg_file_print_invalid( FILE *fp ) void *vg_file_read( void *lin_alloc, const char *path, u32 *size ) { FILE *f = fopen( path, "rb" ); - if( f ){ + if( f ) + { void *buffer = lin_alloc? vg_linear_alloc( lin_alloc, 0 ): NULL; u64 current = 0; /* read in chunks */ - for( u32 i=0; 1; i++ ){ + for( u32 i=0; 1; i++ ) + { if( lin_alloc ) buffer = vg_linear_extend( lin_alloc,buffer,VG_FILE_IO_CHUNK_SIZE ); else @@ -143,18 +144,27 @@ void *vg_file_read( void *lin_alloc, const char *path, u32 *size ) u64 l = fread( buffer + current, 1, VG_FILE_IO_CHUNK_SIZE, f ); current += l; - if( l != VG_FILE_IO_CHUNK_SIZE ){ - if( feof( f ) ){ + if( l != VG_FILE_IO_CHUNK_SIZE ) + { + if( feof( f ) ) + { break; } - else{ - if( ferror( f ) ){ + else + { + if( ferror( f ) ) + { fclose(f); - vg_fatal_error( "read error" ); + vg_fatal_condition(); + vg_info( "Read error\n" ); + vg_fatal_exit(); } - else{ + else + { fclose(f); - vg_fatal_error( "unknown error codition" ); + vg_fatal_condition(); + vg_info( "Unknown error condition\n" ); + vg_fatal_exit(); } } } diff --git a/vg_io.h b/vg_io.h index fefb6bf..0991f0e 100644 --- a/vg_io.h +++ b/vg_io.h @@ -35,7 +35,6 @@ int vg_dirskip( vg_dir *dir ); int vg_dir_next_entry( vg_dir *dir ); enum vg_entry_type vg_dir_entry_type( vg_dir *dir ); void vg_dir_close( vg_dir *dir ); -void vg_file_print_invalid( FILE *fp ); /* * File I/O @@ -50,3 +49,4 @@ char *vg_file_read_text( void *lin_alloc, const char *path, u32 *sz ); int vg_asset_write( const char *path, void *data, i64 size ); int vg_file_copy( const char *src, const char *dst, void *lin_alloc ); const char *vg_path_filename( const char *path ); +void vg_file_error_info( FILE *fp ); diff --git a/vg_lines.c b/vg_lines.c index 6315482..94f8b68 100644 --- a/vg_lines.c +++ b/vg_lines.c @@ -57,7 +57,6 @@ static void async_vg_lines_init( void *payload, u32 payload_size ) glBufferData( GL_ARRAY_BUFFER, VG_LINES_BUFFER_SIZE, NULL, GL_DYNAMIC_DRAW ); glBindVertexArray( vg_lines.vao ); - VG_CHECK_GL_ERR(); /* Pointers */ glVertexAttribPointer( @@ -79,8 +78,6 @@ static void async_vg_lines_init( void *payload, u32 payload_size ) (void*)(offsetof( struct vg_lines_vert, colour )) ); glEnableVertexAttribArray( 1 ); - - VG_CHECK_GL_ERR(); } void vg_lines_init(void) @@ -187,7 +184,7 @@ void vg_line_boxf( boxf box, u32 colour ) {4,5},{5,7},{7,6},{6,4}, {4,0},{5,1},{6,2},{7,3}}; - vg_line_mesh( verts, indices, vg_list_size(indices), colour ); + vg_line_mesh( verts, indices, VG_ARRAY_LEN(indices), colour ); } void vg_line_boxf_transformed( m4x3f m, boxf box, u32 colour ) @@ -205,7 +202,7 @@ void vg_line_boxf_transformed( m4x3f m, boxf box, u32 colour ) {4,5},{5,7},{7,6},{6,4}, {4,0},{5,1},{6,2},{7,3}}; - vg_line_mesh( verts, indices, vg_list_size(indices), colour ); + vg_line_mesh( verts, indices, VG_ARRAY_LEN(indices), colour ); } void vg_line_cross(v3f pos,u32 colour, float scale) diff --git a/vg_loader.c b/vg_loader.c index 55ab081..f16d2c4 100644 --- a/vg_loader.c +++ b/vg_loader.c @@ -78,18 +78,18 @@ void vg_loader_init(void) glVertexAttribPointer( 0, 2, GL_FLOAT, GL_FALSE, sizeof(float)*2, (void*)0 ); glEnableVertexAttribArray( 0 ); - VG_CHECK_GL_ERR(); - - if( !vg_shader_compile( &_shader_loader ) ) - vg_fatal_error( "failed to compile shader" ); + vg_compile_shader( &_shader_loader ); } -static void vg_loader_free(void) +void vg_loader_free(void) { - vg_info( "vg_loader_free\n" ); glDeleteVertexArrays( 1, &vg_loader.vao ); glDeleteBuffers( 1, &vg_loader.vbo ); +} +void vg_loader_atexit(void) +{ + vg_info( "Shutdown steps\n" ); for( int i=0; i -#endif - struct vg_log vg_log; static void _vg_log_append_line( const char *str ) { - if( vg_log.log_line_count < vg_list_size( vg_log.log ) ) + if( vg_log.log_line_count < VG_ARRAY_LEN( vg_log.log ) ) vg_log.log_line_count ++; char *dest = vg_log.log[ vg_log.log_line_current ++ ]; - vg_strncpy( str, dest, vg_list_size(vg_log.log[0]), k_strncpy_allow_cutoff ); + vg_strncpy( str, dest, VG_ARRAY_LEN(vg_log.log[0]), k_strncpy_allow_cutoff ); - if( vg_log.log_line_current >= vg_list_size( vg_log.log ) ) + if( vg_log.log_line_current >= VG_ARRAY_LEN( vg_log.log ) ) vg_log.log_line_current = 0; } @@ -28,65 +24,62 @@ void _vg_logx_va( FILE *file, const char *colour, const char *fmt, va_list args ) { - - /* @model.h:2000 info| dwahjdiawdjaiwdwadwa djaidjwa\n - * | djwaidwaj waodawh a\n - * | dwajdkiawjdiw - */ - #ifdef VG_ENGINE SDL_AtomicLock( &vg_log.print_sl ); #endif - char buffer[4096]; - - vsnprintf( buffer, vg_list_size(buffer), fmt, args ); + char buffer[4096], line[96]; + vsnprintf( buffer, VG_ARRAY_LEN(buffer), fmt, args ); - const char *line = buffer; - char logline[96]; + int line_length = snprintf( line, 90, "%s%7s" KNRM "|%s ", + colour, prefix, colour ); - for( u32 i=0; i= 90 ) + { + line[ line_length ++ ] = '\n'; + line[ line_length ] = '\0'; + flush = 1; + marker = '.'; + } - snprintf( logline, 96, "%s%7s" KNRM "|%s %s", - colour, line_prefix, colour, line ); - _vg_log_append_line( logline ); + if( c == '\n' ) + { + line[ line_length ] = '\0'; + flush = 1; + } - if( location ){ + if( flush || c == '\0' ) + { + if( location ) + { #ifdef VG_ENGINE - const char *thread_colours[] = { - KGRN, KMAG, KCYN, KYEL, KBLU - }; - + const char *thread_colours[] = { KGRN, KMAG, KCYN, KYEL, KBLU }; const char *colour = thread_colours[ - (vg_thread_purpose() % vg_list_size( thread_colours ))]; + (vg_thread_purpose() % VG_ARRAY_LEN( thread_colours ))]; - fprintf( file, "%s[%u]"KNRM"%.32s", - colour, vg_thread_purpose(), line_location ); + fprintf( file, "%s%u|"KNRM"%.32s", + colour, vg_thread_purpose(), location ); #else - fprintf( file, KNRM "%.32s", line_location ); + fprintf( file, KNRM "%.32s", location ); #endif } - fputs( logline, file ); - fputc( '\n', file ); - fputs( KNRM, file ); - - if( c == '\0' ) break; - if( buffer[i+1] == '\0' ) break; - line = buffer+i+1; + _vg_log_append_line( line ); + fputs( line, file ); + line_length = snprintf( line, 90, "%s " KNRM "%c%s ", + colour, marker, colour ); } + + if( c == '\0' ) break; + if( buffer[i+1] == '\0' ) break; } #ifdef VG_ENGINE @@ -112,27 +105,7 @@ void vg_logx( FILE *file, va_end( args ); } -void vg_print_backtrace(void) +void vg_fatal_condition(void) { -#ifndef _WIN32 - void *array[20]; - char **strings; - int size, i; - - size = backtrace( array, 20 ); - strings = backtrace_symbols( array, size ); - - if( strings != NULL ) - { - vg_error( "---------------- gnu backtrace -------------\n" ); - - for( int i=0; i= box[0][0]) && (pt[1] >= box[0][1]) && (pt[2] >= box[0][2]) && + (pt[0] <= box[1][0]) && (pt[1] <= box[1][1]) && (pt[2] <= box[1][2]) ) + { + return 1; + } + else return 0; +} + static int box_within( boxf greater, boxf lesser ) { v3f a, b; @@ -1578,8 +1588,7 @@ static int box_within( boxf greater, boxf lesser ) { return 1; } - - return 0; + else return 0; } static inline void box_init_inf( boxf box ){ @@ -2294,6 +2303,56 @@ static void eval_bezier3( v3f p0, v3f p1, v3f p2, f32 t, v3f p ) v3_muladds( p, p2, t*t, p ); } +static f32 explicit_bezier( f32 A[2], f32 B[2], f32 C[2], f32 D[2], f32 x ) +{ + f32 dAxDx = D[0]-A[0], + unitBx = (B[0] - A[0]) / dAxDx, + unitCx = (C[0] - A[0]) / dAxDx, + + /* cubic coefficients */ + a = 3.0f*unitBx - 3.0f*unitCx + 1.0f, + b = -6.0f*unitBx + 3.0f*unitCx, + c = 3.0f*unitBx, + d = -(x - A[0]) / dAxDx, + + t0 = 0.0f, + Ft0 = d, + t1 = 1.0f, + Ft1 = a+b+c+d, + tc, Ftcx; + + /* Illinois method to find root */ + for( u32 j=0; j<8; j ++ ) + { + tc = t1 - Ft1*(t1-t0)/(Ft1-Ft0); + Ftcx = tc*tc*tc*a + tc*tc*b + tc*c + d; + + if( fabsf(Ftcx) < 0.00001f ) + break; + + if( Ft1*Ftcx < 0.0f ) + { + t0 = t1; + Ft0 = Ft1; + } + else + Ft0 *= 0.5f; + + t1 = tc; + Ft1 = Ftcx; + } + + /* Evaluate parametric bezier */ + f32 t2 = tc*tc, + t3 = tc*tc*tc; + + return D[1] * t3 + + C[1] * (-3.0f*t3 + 3.0f*t2) + + B[1] * ( 3.0f*t3 - 6.0f*t2 + 3.0f*tc) + + A[1] * (-1.0f*t3 + 3.0f*t2 - 3.0f*tc + 1.0f); +} + + /* * ----------------------------------------------------------------------------- * Section 5.f Volumes diff --git a/vg_m.hc b/vg_m.hc deleted file mode 100644 index bdf5343..0000000 --- a/vg_m.hc +++ /dev/null @@ -1,2611 +0,0 @@ -/* Copyright (C) 2021-2024 Harry Godden (hgn) - All Rights Reserved - * - * 0. Misc - * 1. Scalar operations - * 2. Vectors - * 2.a 2D Vectors - * 2.b 3D Vectors - * 2.c 4D Vectors - * 3. Quaternions - * 4. Matrices - * 4.a 2x2 matrices - * 4.b 3x3 matrices - * 4.c 4x3 matrices - * 4.d 4x4 matrices - * 5. Geometry - * 5.a Boxes - * 5.b Planes - * 5.c Closest points - * 5.d Raycast & Spherecasts - * 5.e Curves - * 5.f Volumes - * 5.g Inertia tensors - * 6. Statistics - * 6.a Random numbers - */ - -#pragma once - -#include "vg_stdint.h" -#include -#include - -#define VG_PIf 3.14159265358979323846264338327950288f -#define VG_TAUf 6.28318530717958647692528676655900576f - -/* - * ----------------------------------------------------------------------------- - * Section 0. Misc Operations - * ----------------------------------------------------------------------------- - */ - -/* get the f32 as the raw bits in a u32 without converting */ -static u32 vg_ftu32( f32 a ) -{ - u32 *ptr = (u32 *)(&a); - return *ptr; -} - -/* check if f32 is infinite */ -static int vg_isinff( f32 a ) -{ - return ((vg_ftu32(a)) & 0x7FFFFFFFU) == 0x7F800000U; -} - -/* check if f32 is not a number */ -static int vg_isnanf( f32 a ) -{ - return !vg_isinff(a) && ((vg_ftu32(a)) & 0x7F800000U) == 0x7F800000U; -} - -/* check if f32 is a number and is not infinite */ -static int vg_validf( f32 a ) -{ - return ((vg_ftu32(a)) & 0x7F800000U) != 0x7F800000U; -} - -static int v3_valid( v3f a ){ - for( u32 i=0; i<3; i++ ) - if( !vg_validf(a[i]) ) return 0; - return 1; -} - -/* - * ----------------------------------------------------------------------------- - * Section 1. Scalar Operations - * ----------------------------------------------------------------------------- - */ - -static inline f32 vg_minf( f32 a, f32 b ){ return a < b? a: b; } -static inline f32 vg_maxf( f32 a, f32 b ){ return a > b? a: b; } - -static inline int vg_min( int a, int b ){ return a < b? a: b; } -static inline int vg_max( int a, int b ){ return a > b? a: b; } - -static inline f32 vg_clampf( f32 a, f32 min, f32 max ) -{ - return vg_minf( max, vg_maxf( a, min ) ); -} - -static inline f32 vg_signf( f32 a ) -{ - return a < 0.0f? -1.0f: 1.0f; -} - -static inline f32 vg_fractf( f32 a ) -{ - return a - floorf( a ); -} - -static inline f64 vg_fractf64( f64 a ){ - return a - floor( a ); -} - -static f32 vg_cfrictf( f32 velocity, f32 F ) -{ - return -vg_signf(velocity) * vg_minf( F, fabsf(velocity) ); -} - -static inline f32 vg_rad( f32 deg ) -{ - return deg * VG_PIf / 180.0f; -} - -/* angle to reach b from a */ -static f32 vg_angle_diff( f32 a, f32 b ){ - f32 d = fmod(b,VG_TAUf)-fmodf(a,VG_TAUf); - if( fabsf(d) > VG_PIf ) - d = -vg_signf(d) * (VG_TAUf - fabsf(d)); - - return d; -} - -/* - * quantize float to bit count - */ -static u32 vg_quantf( f32 a, u32 bits, f32 min, f32 max ){ - u32 mask = (0x1 << bits) - 1; - return vg_clampf((a - min) * ((f32)mask/(max-min)), 0.0f, mask ); -} - -/* - * un-quantize discreet to float - */ -static f32 vg_dequantf( u32 q, u32 bits, f32 min, f32 max ){ - u32 mask = (0x1 << bits) - 1; - return min + (f32)q * ((max-min) / (f32)mask); -} - -/* https://iquilezles.org/articles/functions/ - * - * Use k to control the stretching of the function. Its maximum, which is 1, - * happens at exactly x = 1/k. - */ -static f32 vg_exp_impulse( f32 x, f32 k ){ - f32 h = k*x; - return h*expf(1.0f-h); -} - -/* - * ----------------------------------------------------------------------------- - * Section 2.a 2D Vectors - * ----------------------------------------------------------------------------- - */ - -static inline void v2_copy( v2f a, v2f d ) -{ - d[0] = a[0]; d[1] = a[1]; -} - -static inline void v2_zero( v2f a ) -{ - a[0] = 0.f; a[1] = 0.f; -} - -static inline void v2_add( v2f a, v2f b, v2f d ) -{ - d[0] = a[0]+b[0]; d[1] = a[1]+b[1]; -} - -static inline void v2_sub( v2f a, v2f b, v2f d ) -{ - d[0] = a[0]-b[0]; d[1] = a[1]-b[1]; -} - -static inline void v2_minv( v2f a, v2f b, v2f dest ) -{ - dest[0] = vg_minf(a[0], b[0]); - dest[1] = vg_minf(a[1], b[1]); -} - -static inline void v2_maxv( v2f a, v2f b, v2f dest ) -{ - dest[0] = vg_maxf(a[0], b[0]); - dest[1] = vg_maxf(a[1], b[1]); -} - -static inline f32 v2_dot( v2f a, v2f b ) -{ - return a[0] * b[0] + a[1] * b[1]; -} - -static inline f32 v2_cross( v2f a, v2f b ) -{ - return a[0]*b[1] - a[1]*b[0]; -} - -static inline void v2_abs( v2f a, v2f d ) -{ - d[0] = fabsf( a[0] ); - d[1] = fabsf( a[1] ); -} - -static inline void v2_muls( v2f a, f32 s, v2f d ) -{ - d[0] = a[0]*s; d[1] = a[1]*s; -} - -static inline void v2_divs( v2f a, f32 s, v2f d ) -{ - d[0] = a[0]/s; d[1] = a[1]/s; -} - -static inline void v2_mul( v2f a, v2f b, v2f d ) -{ - d[0] = a[0]*b[0]; - d[1] = a[1]*b[1]; -} - -static inline void v2_div( v2f a, v2f b, v2f d ) -{ - d[0] = a[0]/b[0]; d[1] = a[1]/b[1]; -} - -static inline void v2_muladd( v2f a, v2f b, v2f s, v2f d ) -{ - d[0] = a[0]+b[0]*s[0]; - d[1] = a[1]+b[1]*s[1]; -} - -static inline void v2_muladds( v2f a, v2f b, f32 s, v2f d ) -{ - d[0] = a[0]+b[0]*s; - d[1] = a[1]+b[1]*s; -} - -static inline f32 v2_length2( v2f a ) -{ - return a[0]*a[0] + a[1]*a[1]; -} - -static inline f32 v2_length( v2f a ) -{ - return sqrtf( v2_length2( a ) ); -} - -static inline f32 v2_dist2( v2f a, v2f b ) -{ - v2f delta; - v2_sub( a, b, delta ); - return v2_length2( delta ); -} - -static inline f32 v2_dist( v2f a, v2f b ) -{ - return sqrtf( v2_dist2( a, b ) ); -} - -static inline void v2_lerp( v2f a, v2f b, f32 t, v2f d ) -{ - d[0] = a[0] + t*(b[0]-a[0]); - d[1] = a[1] + t*(b[1]-a[1]); -} - -static inline void v2_normalize( v2f a ) -{ - v2_muls( a, 1.0f / v2_length( a ), a ); -} - -static void v2_normalize_clamp( v2f a ) -{ - f32 l2 = v2_length2( a ); - if( l2 > 1.0f ) - v2_muls( a, 1.0f/sqrtf(l2), a ); -} - -static inline void v2_floor( v2f a, v2f b ) -{ - b[0] = floorf( a[0] ); - b[1] = floorf( a[1] ); -} - -static inline void v2_fill( v2f a, f32 v ) -{ - a[0] = v; - a[1] = v; -} - -static inline void v2_copysign( v2f a, v2f b ) -{ - a[0] = copysignf( a[0], b[0] ); - a[1] = copysignf( a[1], b[1] ); -} - -/* integer variants - * ---------------- */ - -static inline void v2i_copy( v2i a, v2i b ) -{ - b[0] = a[0]; b[1] = a[1]; -} - -static inline int v2i_eq( v2i a, v2i b ) -{ - return ((a[0] == b[0]) && (a[1] == b[1])); -} - -static inline void v2i_add( v2i a, v2i b, v2i d ) -{ - d[0] = a[0]+b[0]; d[1] = a[1]+b[1]; -} - -static inline void v2i_sub( v2i a, v2i b, v2i d ) -{ - d[0] = a[0]-b[0]; d[1] = a[1]-b[1]; -} - -/* - * ----------------------------------------------------------------------------- - * Section 2.b 3D Vectors - * ----------------------------------------------------------------------------- - */ - -static inline void v3_copy( v3f a, v3f b ) -{ - b[0] = a[0]; b[1] = a[1]; b[2] = a[2]; -} - -static inline void v3_zero( v3f a ) -{ - a[0] = 0.f; a[1] = 0.f; a[2] = 0.f; -} - -static inline void v3_add( v3f a, v3f b, v3f d ) -{ - d[0] = a[0]+b[0]; d[1] = a[1]+b[1]; d[2] = a[2]+b[2]; -} - -static inline void v3i_add( v3i a, v3i b, v3i d ) -{ - d[0] = a[0]+b[0]; d[1] = a[1]+b[1]; d[2] = a[2]+b[2]; -} - -static inline void v3_sub( v3f a, v3f b, v3f d ) -{ - d[0] = a[0]-b[0]; d[1] = a[1]-b[1]; d[2] = a[2]-b[2]; -} - -static inline void v3i_sub( v3i a, v3i b, v3i d ) -{ - d[0] = a[0]-b[0]; d[1] = a[1]-b[1]; d[2] = a[2]-b[2]; -} - -static inline void v3_mul( v3f a, v3f b, v3f d ) -{ - d[0] = a[0]*b[0]; d[1] = a[1]*b[1]; d[2] = a[2]*b[2]; -} - -static inline void v3_div( v3f a, v3f b, v3f d ) -{ - d[0] = b[0]!=0.0f? a[0]/b[0]: INFINITY; - d[1] = b[1]!=0.0f? a[1]/b[1]: INFINITY; - d[2] = b[2]!=0.0f? a[2]/b[2]: INFINITY; -} - -static inline void v3_muls( v3f a, f32 s, v3f d ) -{ - d[0] = a[0]*s; d[1] = a[1]*s; d[2] = a[2]*s; -} - -static inline void v3_fill( v3f a, f32 v ) -{ - a[0] = v; - a[1] = v; - a[2] = v; -} - -static inline void v3_divs( v3f a, f32 s, v3f d ) -{ - if( s == 0.0f ) - v3_fill( d, INFINITY ); - else - { - d[0] = a[0]/s; - d[1] = a[1]/s; - d[2] = a[2]/s; - } -} - -static inline void v3_muladds( v3f a, v3f b, f32 s, v3f d ) -{ - d[0] = a[0]+b[0]*s; d[1] = a[1]+b[1]*s; d[2] = a[2]+b[2]*s; -} - -static inline void v3_muladd( v2f a, v2f b, v2f s, v2f d ) -{ - d[0] = a[0]+b[0]*s[0]; - d[1] = a[1]+b[1]*s[1]; - d[2] = a[2]+b[2]*s[2]; -} - -static inline f32 v3_dot( v3f a, v3f b ) -{ - return a[0] * b[0] + a[1] * b[1] + a[2] * b[2]; -} - -static inline void v3_cross( v3f a, v3f b, v3f dest ) -{ - v3f d; - d[0] = a[1]*b[2] - a[2]*b[1]; - d[1] = a[2]*b[0] - a[0]*b[2]; - d[2] = a[0]*b[1] - a[1]*b[0]; - v3_copy( d, dest ); -} - -static inline f32 v3_length2( v3f a ) -{ - return v3_dot( a, a ); -} - -static inline f32 v3_length( v3f a ) -{ - return sqrtf( v3_length2( a ) ); -} - -static inline f32 v3_dist2( v3f a, v3f b ) -{ - v3f delta; - v3_sub( a, b, delta ); - return v3_length2( delta ); -} - -static inline f32 v3_dist( v3f a, v3f b ) -{ - return sqrtf( v3_dist2( a, b ) ); -} - -static inline void v3_normalize( v3f a ) -{ - v3_muls( a, 1.f / v3_length( a ), a ); -} - -static inline f32 vg_lerpf( f32 a, f32 b, f32 t ){ - return a + t*(b-a); -} - -static inline f64 vg_lerp( f64 a, f64 b, f64 t ) -{ - return a + t*(b-a); -} - -static inline void vg_slewf( f32 *a, f32 b, f32 speed ){ - f32 d = vg_signf( b-*a ), - c = *a + d*speed; - *a = vg_minf( b*d, c*d ) * d; -} - -static inline f32 vg_smoothstepf( f32 x ){ - return x*x*(3.0f - 2.0f*x); -} - - -/* correctly lerp around circular period -pi -> pi */ -static f32 vg_alerpf( f32 a, f32 b, f32 t ) -{ - f32 d = fmodf( b-a, VG_TAUf ), - s = fmodf( 2.0f*d, VG_TAUf ) - d; - return a + s*t; -} - -static inline void v3_lerp( v3f a, v3f b, f32 t, v3f d ) -{ - d[0] = a[0] + t*(b[0]-a[0]); - d[1] = a[1] + t*(b[1]-a[1]); - d[2] = a[2] + t*(b[2]-a[2]); -} - -static inline void v3_minv( v3f a, v3f b, v3f dest ) -{ - dest[0] = vg_minf(a[0], b[0]); - dest[1] = vg_minf(a[1], b[1]); - dest[2] = vg_minf(a[2], b[2]); -} - -static inline void v3_maxv( v3f a, v3f b, v3f dest ) -{ - dest[0] = vg_maxf(a[0], b[0]); - dest[1] = vg_maxf(a[1], b[1]); - dest[2] = vg_maxf(a[2], b[2]); -} - -static inline f32 v3_minf( v3f a ) -{ - return vg_minf( vg_minf( a[0], a[1] ), a[2] ); -} - -static inline f32 v3_maxf( v3f a ) -{ - return vg_maxf( vg_maxf( a[0], a[1] ), a[2] ); -} - -static inline void v3_floor( v3f a, v3f b ) -{ - b[0] = floorf( a[0] ); - b[1] = floorf( a[1] ); - b[2] = floorf( a[2] ); -} - -static inline void v3_ceil( v3f a, v3f b ) -{ - b[0] = ceilf( a[0] ); - b[1] = ceilf( a[1] ); - b[2] = ceilf( a[2] ); -} - -static inline void v3_negate( v3f a, v3f b ) -{ - b[0] = -a[0]; - b[1] = -a[1]; - b[2] = -a[2]; -} - -static inline void v3_rotate( v3f v, f32 angle, v3f axis, v3f d ) -{ - v3f v1, v2, k; - f32 c, s; - - c = cosf( angle ); - s = sinf( angle ); - - v3_copy( axis, k ); - v3_normalize( k ); - v3_muls( v, c, v1 ); - v3_cross( k, v, v2 ); - v3_muls( v2, s, v2 ); - v3_add( v1, v2, v1 ); - v3_muls( k, v3_dot(k, v) * (1.0f - c), v2); - v3_add( v1, v2, d ); -} - -static void v3_tangent_basis( v3f n, v3f tx, v3f ty ){ - /* Compute tangent basis (box2d) */ - if( fabsf( n[0] ) >= 0.57735027f ){ - tx[0] = n[1]; - tx[1] = -n[0]; - tx[2] = 0.0f; - } - else{ - tx[0] = 0.0f; - tx[1] = n[2]; - tx[2] = -n[1]; - } - - v3_normalize( tx ); - v3_cross( n, tx, ty ); -} - -/* - * Compute yaw and pitch based of a normalized vector representing forward - * forward: -z - * result -> (YAW,PITCH,0.0) - */ -static void v3_angles( v3f v, v3f out_angles ){ - float yaw = atan2f( v[0], -v[2] ), - pitch = atan2f( - -v[1], - sqrtf( - v[0]*v[0] + v[2]*v[2] - ) - ); - - out_angles[0] = yaw; - out_angles[1] = pitch; - out_angles[2] = 0.0f; -} - -/* - * Compute the forward vector from (YAW,PITCH,ROLL) - * forward: -z - */ -static void v3_angles_vector( v3f angles, v3f out_v ){ - out_v[0] = sinf( angles[0] ) * cosf( angles[1] ); - out_v[1] = -sinf( angles[1] ); - out_v[2] = -cosf( angles[0] ) * cosf( angles[1] ); -} - -/* - * ----------------------------------------------------------------------------- - * Section 2.c 4D Vectors - * ----------------------------------------------------------------------------- - */ - -static inline void v4_copy( v4f a, v4f b ) -{ - b[0] = a[0]; b[1] = a[1]; b[2] = a[2]; b[3] = a[3]; -} - -static inline void v4_add( v4f a, v4f b, v4f d ) -{ - d[0] = a[0]+b[0]; - d[1] = a[1]+b[1]; - d[2] = a[2]+b[2]; - d[3] = a[3]+b[3]; -} - -static inline void v4_zero( v4f a ) -{ - a[0] = 0.f; a[1] = 0.f; a[2] = 0.f; a[3] = 0.f; -} - -static inline void v4_muls( v4f a, f32 s, v4f d ) -{ - d[0] = a[0]*s; - d[1] = a[1]*s; - d[2] = a[2]*s; - d[3] = a[3]*s; -} - -static inline void v4_muladds( v4f a, v4f b, f32 s, v4f d ) -{ - d[0] = a[0]+b[0]*s; - d[1] = a[1]+b[1]*s; - d[2] = a[2]+b[2]*s; - d[3] = a[3]+b[3]*s; -} - -static inline void v4_lerp( v4f a, v4f b, f32 t, v4f d ) -{ - d[0] = a[0] + t*(b[0]-a[0]); - d[1] = a[1] + t*(b[1]-a[1]); - d[2] = a[2] + t*(b[2]-a[2]); - d[3] = a[3] + t*(b[3]-a[3]); -} - -static inline f32 v4_dot( v4f a, v4f b ) -{ - return a[0]*b[0] + a[1]*b[1] + a[2]*b[2] + a[3]*b[3]; -} - -static inline f32 v4_length( v4f a ) -{ - return sqrtf( v4_dot(a,a) ); -} - -/* - * ----------------------------------------------------------------------------- - * Section 3 Quaternions - * ----------------------------------------------------------------------------- - */ - -static inline void q_identity( v4f q ) -{ - q[0] = 0.0f; q[1] = 0.0f; q[2] = 0.0f; q[3] = 1.0f; -} - -static inline void q_axis_angle( v4f q, v3f axis, f32 angle ) -{ - f32 a = angle*0.5f, - c = cosf(a), - s = sinf(a); - - q[0] = s*axis[0]; - q[1] = s*axis[1]; - q[2] = s*axis[2]; - q[3] = c; -} - -static inline void q_mul( v4f q, v4f q1, v4f d ) -{ - v4f t; - t[0] = q[3]*q1[0] + q[0]*q1[3] + q[1]*q1[2] - q[2]*q1[1]; - t[1] = q[3]*q1[1] - q[0]*q1[2] + q[1]*q1[3] + q[2]*q1[0]; - t[2] = q[3]*q1[2] + q[0]*q1[1] - q[1]*q1[0] + q[2]*q1[3]; - t[3] = q[3]*q1[3] - q[0]*q1[0] - q[1]*q1[1] - q[2]*q1[2]; - v4_copy( t, d ); -} - -static inline void q_normalize( v4f q ) -{ - f32 l2 = v4_dot(q,q); - if( l2 < 0.00001f ) q_identity( q ); - else { - f32 s = 1.0f/sqrtf(l2); - q[0] *= s; - q[1] *= s; - q[2] *= s; - q[3] *= s; - } -} - -static inline void q_inv( v4f q, v4f d ) -{ - f32 s = 1.0f / v4_dot(q,q); - d[0] = -q[0]*s; - d[1] = -q[1]*s; - d[2] = -q[2]*s; - d[3] = q[3]*s; -} - -static inline void q_nlerp( v4f a, v4f b, f32 t, v4f d ){ - if( v4_dot(a,b) < 0.0f ){ - v4f c; - v4_muls( b, -1.0f, c ); - v4_lerp( a, c, t, d ); - } - else - v4_lerp( a, b, t, d ); - - q_normalize( d ); -} - -static inline void q_m3x3( v4f q, m3x3f d ) -{ - f32 - l = v4_length(q), - s = l > 0.0f? 2.0f/l: 0.0f, - - xx = s*q[0]*q[0], xy = s*q[0]*q[1], wx = s*q[3]*q[0], - yy = s*q[1]*q[1], yz = s*q[1]*q[2], wy = s*q[3]*q[1], - zz = s*q[2]*q[2], xz = s*q[0]*q[2], wz = s*q[3]*q[2]; - - d[0][0] = 1.0f - yy - zz; - d[1][1] = 1.0f - xx - zz; - d[2][2] = 1.0f - xx - yy; - d[0][1] = xy + wz; - d[1][2] = yz + wx; - d[2][0] = xz + wy; - d[1][0] = xy - wz; - d[2][1] = yz - wx; - d[0][2] = xz - wy; -} - -static void q_mulv( v4f q, v3f v, v3f d ) -{ - v3f v1, v2; - - v3_muls( q, 2.0f*v3_dot(q,v), v1 ); - v3_muls( v, q[3]*q[3] - v3_dot(q,q), v2 ); - v3_add( v1, v2, v1 ); - v3_cross( q, v, v2 ); - v3_muls( v2, 2.0f*q[3], v2 ); - v3_add( v1, v2, d ); -} - -static f32 q_dist( v4f q0, v4f q1 ){ - return acosf( 2.0f * v4_dot(q0,q1) -1.0f ); -} - -/* - * ----------------------------------------------------------------------------- - * Section 4.a 2x2 matrices - * ----------------------------------------------------------------------------- - */ - -#define M2X2_INDENTIY {{1.0f, 0.0f, }, \ - {0.0f, 1.0f, }} - -#define M2X2_ZERO {{0.0f, 0.0f, }, \ - {0.0f, 0.0f, }} - -static inline void m2x2_copy( m2x2f a, m2x2f b ) -{ - v2_copy( a[0], b[0] ); - v2_copy( a[1], b[1] ); -} - -static inline void m2x2_identity( m2x2f a ) -{ - m2x2f id = M2X2_INDENTIY; - m2x2_copy( id, a ); -} - -static inline void m2x2_create_rotation( m2x2f a, f32 theta ) -{ - f32 s, c; - - s = sinf( theta ); - c = cosf( theta ); - - a[0][0] = c; - a[0][1] = -s; - a[1][0] = s; - a[1][1] = c; -} - -static inline void m2x2_mulv( m2x2f m, v2f v, v2f d ) -{ - v2f res; - - res[0] = m[0][0]*v[0] + m[1][0]*v[1]; - res[1] = m[0][1]*v[0] + m[1][1]*v[1]; - - v2_copy( res, d ); -} - -/* - * ----------------------------------------------------------------------------- - * Section 4.b 3x3 matrices - * ----------------------------------------------------------------------------- - */ - -#define M3X3_IDENTITY {{1.0f, 0.0f, 0.0f, },\ - { 0.0f, 1.0f, 0.0f, },\ - { 0.0f, 0.0f, 1.0f, }} - -#define M3X3_ZERO {{0.0f, 0.0f, 0.0f, },\ - { 0.0f, 0.0f, 0.0f, },\ - { 0.0f, 0.0f, 0.0f, }} - - -static void euler_m3x3( v3f angles, m3x3f d ) -{ - f32 cosY = cosf( angles[0] ), - sinY = sinf( angles[0] ), - cosP = cosf( angles[1] ), - sinP = sinf( angles[1] ), - cosR = cosf( angles[2] ), - sinR = sinf( angles[2] ); - - d[2][0] = -sinY * cosP; - d[2][1] = sinP; - d[2][2] = cosY * cosP; - - d[0][0] = cosY * cosR; - d[0][1] = sinR; - d[0][2] = sinY * cosR; - - v3_cross( d[0], d[2], d[1] ); -} - -static void m3x3_q( m3x3f m, v4f q ) -{ - f32 diag, r, rinv; - - diag = m[0][0] + m[1][1] + m[2][2]; - if( diag >= 0.0f ) - { - r = sqrtf( 1.0f + diag ); - rinv = 0.5f / r; - q[0] = rinv * (m[1][2] - m[2][1]); - q[1] = rinv * (m[2][0] - m[0][2]); - q[2] = rinv * (m[0][1] - m[1][0]); - q[3] = r * 0.5f; - } - else if( m[0][0] >= m[1][1] && m[0][0] >= m[2][2] ) - { - r = sqrtf( 1.0f - m[1][1] - m[2][2] + m[0][0] ); - rinv = 0.5f / r; - q[0] = r * 0.5f; - q[1] = rinv * (m[0][1] + m[1][0]); - q[2] = rinv * (m[0][2] + m[2][0]); - q[3] = rinv * (m[1][2] - m[2][1]); - } - else if( m[1][1] >= m[2][2] ) - { - r = sqrtf( 1.0f - m[0][0] - m[2][2] + m[1][1] ); - rinv = 0.5f / r; - q[0] = rinv * (m[0][1] + m[1][0]); - q[1] = r * 0.5f; - q[2] = rinv * (m[1][2] + m[2][1]); - q[3] = rinv * (m[2][0] - m[0][2]); - } - else - { - r = sqrtf( 1.0f - m[0][0] - m[1][1] + m[2][2] ); - rinv = 0.5f / r; - q[0] = rinv * (m[0][2] + m[2][0]); - q[1] = rinv * (m[1][2] + m[2][1]); - q[2] = r * 0.5f; - q[3] = rinv * (m[0][1] - m[1][0]); - } -} - -/* a X b == [b]T a == ...*/ -static void m3x3_skew_symetric( m3x3f a, v3f v ) -{ - a[0][0] = 0.0f; - a[0][1] = v[2]; - a[0][2] = -v[1]; - a[1][0] = -v[2]; - a[1][1] = 0.0f; - a[1][2] = v[0]; - a[2][0] = v[1]; - a[2][1] = -v[0]; - a[2][2] = 0.0f; -} - -/* aka kronecker product */ -static void m3x3_outer_product( m3x3f out_m, v3f a, v3f b ) -{ - out_m[0][0] = a[0]*b[0]; - out_m[0][1] = a[0]*b[1]; - out_m[0][2] = a[0]*b[2]; - out_m[1][0] = a[1]*b[0]; - out_m[1][1] = a[1]*b[1]; - out_m[1][2] = a[1]*b[2]; - out_m[2][0] = a[2]*b[0]; - out_m[2][1] = a[2]*b[1]; - out_m[2][2] = a[2]*b[2]; -} - -static void m3x3_add( m3x3f a, m3x3f b, m3x3f d ) -{ - v3_add( a[0], b[0], d[0] ); - v3_add( a[1], b[1], d[1] ); - v3_add( a[2], b[2], d[2] ); -} - -static void m3x3_sub( m3x3f a, m3x3f b, m3x3f d ) -{ - v3_sub( a[0], b[0], d[0] ); - v3_sub( a[1], b[1], d[1] ); - v3_sub( a[2], b[2], d[2] ); -} - -static inline void m3x3_copy( m3x3f a, m3x3f b ) -{ - v3_copy( a[0], b[0] ); - v3_copy( a[1], b[1] ); - v3_copy( a[2], b[2] ); -} - -static inline void m3x3_identity( m3x3f a ) -{ - m3x3f id = M3X3_IDENTITY; - m3x3_copy( id, a ); -} - -static void m3x3_diagonal( m3x3f out_a, f32 v ) -{ - m3x3_identity( out_a ); - out_a[0][0] = v; - out_a[1][1] = v; - out_a[2][2] = v; -} - -static void m3x3_setdiagonalv3( m3x3f a, v3f v ) -{ - a[0][0] = v[0]; - a[1][1] = v[1]; - a[2][2] = v[2]; -} - -static inline void m3x3_zero( m3x3f a ) -{ - m3x3f z = M3X3_ZERO; - m3x3_copy( z, a ); -} - -static inline void m3x3_inv( m3x3f src, m3x3f dest ) -{ - f32 a = src[0][0], b = src[0][1], c = src[0][2], - d = src[1][0], e = src[1][1], f = src[1][2], - g = src[2][0], h = src[2][1], i = src[2][2]; - - f32 det = 1.f / - (+a*(e*i-h*f) - -b*(d*i-f*g) - +c*(d*h-e*g)); - - dest[0][0] = (e*i-h*f)*det; - dest[0][1] = -(b*i-c*h)*det; - dest[0][2] = (b*f-c*e)*det; - dest[1][0] = -(d*i-f*g)*det; - dest[1][1] = (a*i-c*g)*det; - dest[1][2] = -(a*f-d*c)*det; - dest[2][0] = (d*h-g*e)*det; - dest[2][1] = -(a*h-g*b)*det; - dest[2][2] = (a*e-d*b)*det; -} - -static f32 m3x3_det( m3x3f m ) -{ - return m[0][0] * (m[1][1] * m[2][2] - m[2][1] * m[1][2]) - - m[0][1] * (m[1][0] * m[2][2] - m[1][2] * m[2][0]) - + m[0][2] * (m[1][0] * m[2][1] - m[1][1] * m[2][0]); -} - -static inline void m3x3_transpose( m3x3f src, m3x3f dest ) -{ - f32 a = src[0][0], b = src[0][1], c = src[0][2], - d = src[1][0], e = src[1][1], f = src[1][2], - g = src[2][0], h = src[2][1], i = src[2][2]; - - dest[0][0] = a; - dest[0][1] = d; - dest[0][2] = g; - dest[1][0] = b; - dest[1][1] = e; - dest[1][2] = h; - dest[2][0] = c; - dest[2][1] = f; - dest[2][2] = i; -} - -static inline void m3x3_mul( m3x3f a, m3x3f b, m3x3f d ) -{ - f32 a00 = a[0][0], a01 = a[0][1], a02 = a[0][2], - a10 = a[1][0], a11 = a[1][1], a12 = a[1][2], - a20 = a[2][0], a21 = a[2][1], a22 = a[2][2], - - b00 = b[0][0], b01 = b[0][1], b02 = b[0][2], - b10 = b[1][0], b11 = b[1][1], b12 = b[1][2], - b20 = b[2][0], b21 = b[2][1], b22 = b[2][2]; - - d[0][0] = a00*b00 + a10*b01 + a20*b02; - d[0][1] = a01*b00 + a11*b01 + a21*b02; - d[0][2] = a02*b00 + a12*b01 + a22*b02; - d[1][0] = a00*b10 + a10*b11 + a20*b12; - d[1][1] = a01*b10 + a11*b11 + a21*b12; - d[1][2] = a02*b10 + a12*b11 + a22*b12; - d[2][0] = a00*b20 + a10*b21 + a20*b22; - d[2][1] = a01*b20 + a11*b21 + a21*b22; - d[2][2] = a02*b20 + a12*b21 + a22*b22; -} - -static inline void m3x3_mulv( m3x3f m, v3f v, v3f d ) -{ - v3f res; - - res[0] = m[0][0]*v[0] + m[1][0]*v[1] + m[2][0]*v[2]; - res[1] = m[0][1]*v[0] + m[1][1]*v[1] + m[2][1]*v[2]; - res[2] = m[0][2]*v[0] + m[1][2]*v[1] + m[2][2]*v[2]; - - v3_copy( res, d ); -} - -static inline void m3x3_projection( m3x3f dst, - f32 const left, f32 const right, f32 const bottom, f32 const top ) -{ - f32 rl, tb; - - m3x3_zero( dst ); - - rl = 1.0f / (right - left); - tb = 1.0f / (top - bottom); - - dst[0][0] = 2.0f * rl; - dst[1][1] = 2.0f * tb; - dst[2][2] = 1.0f; -} - -static inline void m3x3_translate( m3x3f m, v3f v ) -{ - m[2][0] = m[0][0] * v[0] + m[1][0] * v[1] + m[2][0]; - m[2][1] = m[0][1] * v[0] + m[1][1] * v[1] + m[2][1]; - m[2][2] = m[0][2] * v[0] + m[1][2] * v[1] + m[2][2]; -} - -static inline void m3x3_scale( m3x3f m, v3f v ) -{ - v3_muls( m[0], v[0], m[0] ); - v3_muls( m[1], v[1], m[1] ); - v3_muls( m[2], v[2], m[2] ); -} - -static inline void m3x3_scalef( m3x3f m, f32 f ) -{ - v3f v; - v3_fill( v, f ); - m3x3_scale( m, v ); -} - -static inline void m3x3_rotate( m3x3f m, f32 angle ) -{ - f32 m00 = m[0][0], m10 = m[1][0], - m01 = m[0][1], m11 = m[1][1], - m02 = m[0][2], m12 = m[1][2]; - f32 c, s; - - s = sinf( angle ); - c = cosf( angle ); - - m[0][0] = m00 * c + m10 * s; - m[0][1] = m01 * c + m11 * s; - m[0][2] = m02 * c + m12 * s; - - m[1][0] = m00 * -s + m10 * c; - m[1][1] = m01 * -s + m11 * c; - m[1][2] = m02 * -s + m12 * c; -} - -/* - * ----------------------------------------------------------------------------- - * Section 4.c 4x3 matrices - * ----------------------------------------------------------------------------- - */ - -#define M4X3_IDENTITY {{1.0f, 0.0f, 0.0f, },\ - { 0.0f, 1.0f, 0.0f, },\ - { 0.0f, 0.0f, 1.0f, },\ - { 0.0f, 0.0f, 0.0f }} - -static inline void m4x3_to_3x3( m4x3f a, m3x3f b ) -{ - v3_copy( a[0], b[0] ); - v3_copy( a[1], b[1] ); - v3_copy( a[2], b[2] ); -} - -static inline void m4x3_invert_affine( m4x3f a, m4x3f b ) -{ - m3x3_transpose( a, b ); - m3x3_mulv( b, a[3], b[3] ); - v3_negate( b[3], b[3] ); -} - -static void m4x3_invert_full( m4x3f src, m4x3f dst ) -{ - f32 t2, t4, t5, - det, - a = src[0][0], b = src[0][1], c = src[0][2], - e = src[1][0], f = src[1][1], g = src[1][2], - i = src[2][0], j = src[2][1], k = src[2][2], - m = src[3][0], n = src[3][1], o = src[3][2]; - - t2 = j*o - n*k; - t4 = i*o - m*k; - t5 = i*n - m*j; - - dst[0][0] = f*k - g*j; - dst[1][0] =-(e*k - g*i); - dst[2][0] = e*j - f*i; - dst[3][0] =-(e*t2 - f*t4 + g*t5); - - dst[0][1] =-(b*k - c*j); - dst[1][1] = a*k - c*i; - dst[2][1] =-(a*j - b*i); - dst[3][1] = a*t2 - b*t4 + c*t5; - - t2 = f*o - n*g; - t4 = e*o - m*g; - t5 = e*n - m*f; - - dst[0][2] = b*g - c*f ; - dst[1][2] =-(a*g - c*e ); - dst[2][2] = a*f - b*e ; - dst[3][2] =-(a*t2 - b*t4 + c * t5); - - det = 1.0f / (a * dst[0][0] + b * dst[1][0] + c * dst[2][0]); - v3_muls( dst[0], det, dst[0] ); - v3_muls( dst[1], det, dst[1] ); - v3_muls( dst[2], det, dst[2] ); - v3_muls( dst[3], det, dst[3] ); -} - -static inline void m4x3_copy( m4x3f a, m4x3f b ) -{ - v3_copy( a[0], b[0] ); - v3_copy( a[1], b[1] ); - v3_copy( a[2], b[2] ); - v3_copy( a[3], b[3] ); -} - -static inline void m4x3_identity( m4x3f a ) -{ - m4x3f id = M4X3_IDENTITY; - m4x3_copy( id, a ); -} - -static void m4x3_mul( m4x3f a, m4x3f b, m4x3f d ) -{ - f32 - a00 = a[0][0], a01 = a[0][1], a02 = a[0][2], - a10 = a[1][0], a11 = a[1][1], a12 = a[1][2], - a20 = a[2][0], a21 = a[2][1], a22 = a[2][2], - a30 = a[3][0], a31 = a[3][1], a32 = a[3][2], - b00 = b[0][0], b01 = b[0][1], b02 = b[0][2], - b10 = b[1][0], b11 = b[1][1], b12 = b[1][2], - b20 = b[2][0], b21 = b[2][1], b22 = b[2][2], - b30 = b[3][0], b31 = b[3][1], b32 = b[3][2]; - - d[0][0] = a00*b00 + a10*b01 + a20*b02; - d[0][1] = a01*b00 + a11*b01 + a21*b02; - d[0][2] = a02*b00 + a12*b01 + a22*b02; - d[1][0] = a00*b10 + a10*b11 + a20*b12; - d[1][1] = a01*b10 + a11*b11 + a21*b12; - d[1][2] = a02*b10 + a12*b11 + a22*b12; - d[2][0] = a00*b20 + a10*b21 + a20*b22; - d[2][1] = a01*b20 + a11*b21 + a21*b22; - d[2][2] = a02*b20 + a12*b21 + a22*b22; - d[3][0] = a00*b30 + a10*b31 + a20*b32 + a30; - d[3][1] = a01*b30 + a11*b31 + a21*b32 + a31; - d[3][2] = a02*b30 + a12*b31 + a22*b32 + a32; -} - -#if 0 /* shat appf mingw wstringop-overflow */ -inline -#endif -static void m4x3_mulv( m4x3f m, v3f v, v3f d ) -{ - v3f res; - - res[0] = m[0][0]*v[0] + m[1][0]*v[1] + m[2][0]*v[2] + m[3][0]; - res[1] = m[0][1]*v[0] + m[1][1]*v[1] + m[2][1]*v[2] + m[3][1]; - res[2] = m[0][2]*v[0] + m[1][2]*v[1] + m[2][2]*v[2] + m[3][2]; - - v3_copy( res, d ); -} - -/* - * Transform plane ( xyz, distance ) - */ -static void m4x3_mulp( m4x3f m, v4f p, v4f d ) -{ - v3f o; - - v3_muls( p, p[3], o ); - m4x3_mulv( m, o, o ); - m3x3_mulv( m, p, d ); - - d[3] = v3_dot( o, d ); -} - -/* - * Affine transforms - */ - -static void m4x3_translate( m4x3f m, v3f v ) -{ - v3_muladds( m[3], m[0], v[0], m[3] ); - v3_muladds( m[3], m[1], v[1], m[3] ); - v3_muladds( m[3], m[2], v[2], m[3] ); -} - -static void m4x3_rotate_x( m4x3f m, f32 angle ) -{ - m4x3f t = M4X3_IDENTITY; - f32 c, s; - - c = cosf( angle ); - s = sinf( angle ); - - t[1][1] = c; - t[1][2] = s; - t[2][1] = -s; - t[2][2] = c; - - m4x3_mul( m, t, m ); -} - -static void m4x3_rotate_y( m4x3f m, f32 angle ) -{ - m4x3f t = M4X3_IDENTITY; - f32 c, s; - - c = cosf( angle ); - s = sinf( angle ); - - t[0][0] = c; - t[0][2] = -s; - t[2][0] = s; - t[2][2] = c; - - m4x3_mul( m, t, m ); -} - -static void m4x3_rotate_z( m4x3f m, f32 angle ) -{ - m4x3f t = M4X3_IDENTITY; - f32 c, s; - - c = cosf( angle ); - s = sinf( angle ); - - t[0][0] = c; - t[0][1] = s; - t[1][0] = -s; - t[1][1] = c; - - m4x3_mul( m, t, m ); -} - -static void m4x3_expand( m4x3f m, m4x4f d ) -{ - v3_copy( m[0], d[0] ); - v3_copy( m[1], d[1] ); - v3_copy( m[2], d[2] ); - v3_copy( m[3], d[3] ); - d[0][3] = 0.0f; - d[1][3] = 0.0f; - d[2][3] = 0.0f; - d[3][3] = 1.0f; -} - -static void m4x3_decompose( m4x3f m, v3f co, v4f q, v3f s ) -{ - v3_copy( m[3], co ); - s[0] = v3_length(m[0]); - s[1] = v3_length(m[1]); - s[2] = v3_length(m[2]); - - m3x3f rot; - v3_divs( m[0], s[0], rot[0] ); - v3_divs( m[1], s[1], rot[1] ); - v3_divs( m[2], s[2], rot[2] ); - - m3x3_q( rot, q ); -} - -static void m4x3_expand_aabb_point( m4x3f m, boxf box, v3f point ){ - v3f v; - m4x3_mulv( m, point, v ); - - v3_minv( box[0], v, box[0] ); - v3_maxv( box[1], v, box[1] ); -} - -static void m4x3_expand_aabb_aabb( m4x3f m, boxf boxa, boxf boxb ){ - v3f a; v3f b; - v3_copy( boxb[0], a ); - v3_copy( boxb[1], b ); - m4x3_expand_aabb_point( m, boxa, (v3f){ a[0], a[1], a[2] } ); - m4x3_expand_aabb_point( m, boxa, (v3f){ a[0], b[1], a[2] } ); - m4x3_expand_aabb_point( m, boxa, (v3f){ b[0], b[1], a[2] } ); - m4x3_expand_aabb_point( m, boxa, (v3f){ b[0], a[1], a[2] } ); - m4x3_expand_aabb_point( m, boxa, (v3f){ a[0], a[1], b[2] } ); - m4x3_expand_aabb_point( m, boxa, (v3f){ a[0], b[1], b[2] } ); - m4x3_expand_aabb_point( m, boxa, (v3f){ b[0], b[1], b[2] } ); - m4x3_expand_aabb_point( m, boxa, (v3f){ b[0], a[1], b[2] } ); -} -static inline void m4x3_lookat( m4x3f m, v3f pos, v3f target, v3f up ) -{ - v3f dir; - v3_sub( target, pos, dir ); - v3_normalize( dir ); - - v3_copy( dir, m[2] ); - - v3_cross( up, m[2], m[0] ); - v3_normalize( m[0] ); - - v3_cross( m[2], m[0], m[1] ); - v3_copy( pos, m[3] ); -} - -/* - * ----------------------------------------------------------------------------- - * Section 4.d 4x4 matrices - * ----------------------------------------------------------------------------- - */ - -#define M4X4_IDENTITY {{1.0f, 0.0f, 0.0f, 0.0f },\ - { 0.0f, 1.0f, 0.0f, 0.0f },\ - { 0.0f, 0.0f, 1.0f, 0.0f },\ - { 0.0f, 0.0f, 0.0f, 1.0f }} -#define M4X4_ZERO {{0.0f, 0.0f, 0.0f, 0.0f },\ - { 0.0f, 0.0f, 0.0f, 0.0f },\ - { 0.0f, 0.0f, 0.0f, 0.0f },\ - { 0.0f, 0.0f, 0.0f, 0.0f }} - -static void m4x4_projection( m4x4f m, f32 angle, - f32 ratio, f32 fnear, f32 ffar ) -{ - f32 scale = tanf( angle * 0.5f * VG_PIf / 180.0f ) * fnear, - r = ratio * scale, - l = -r, - t = scale, - b = -t; - - m[0][0] = 2.0f * fnear / (r - l); - m[0][1] = 0.0f; - m[0][2] = 0.0f; - m[0][3] = 0.0f; - - m[1][0] = 0.0f; - m[1][1] = 2.0f * fnear / (t - b); - m[1][2] = 0.0f; - m[1][3] = 0.0f; - - m[2][0] = (r + l) / (r - l); - m[2][1] = (t + b) / (t - b); - m[2][2] = -(ffar + fnear) / (ffar - fnear); - m[2][3] = -1.0f; - - m[3][0] = 0.0f; - m[3][1] = 0.0f; - m[3][2] = -2.0f * ffar * fnear / (ffar - fnear); - m[3][3] = 0.0f; -} - -static void m4x4_translate( m4x4f m, v3f v ) -{ - v4_muladds( m[3], m[0], v[0], m[3] ); - v4_muladds( m[3], m[1], v[1], m[3] ); - v4_muladds( m[3], m[2], v[2], m[3] ); -} - -static inline void m4x4_copy( m4x4f a, m4x4f b ) -{ - v4_copy( a[0], b[0] ); - v4_copy( a[1], b[1] ); - v4_copy( a[2], b[2] ); - v4_copy( a[3], b[3] ); -} - -static inline void m4x4_identity( m4x4f a ) -{ - m4x4f id = M4X4_IDENTITY; - m4x4_copy( id, a ); -} - -static inline void m4x4_zero( m4x4f a ) -{ - m4x4f zero = M4X4_ZERO; - m4x4_copy( zero, a ); -} - -static inline void m4x4_mul( m4x4f a, m4x4f b, m4x4f d ) -{ - f32 a00 = a[0][0], a01 = a[0][1], a02 = a[0][2], a03 = a[0][3], - a10 = a[1][0], a11 = a[1][1], a12 = a[1][2], a13 = a[1][3], - a20 = a[2][0], a21 = a[2][1], a22 = a[2][2], a23 = a[2][3], - a30 = a[3][0], a31 = a[3][1], a32 = a[3][2], a33 = a[3][3], - - b00 = b[0][0], b01 = b[0][1], b02 = b[0][2], b03 = b[0][3], - b10 = b[1][0], b11 = b[1][1], b12 = b[1][2], b13 = b[1][3], - b20 = b[2][0], b21 = b[2][1], b22 = b[2][2], b23 = b[2][3], - b30 = b[3][0], b31 = b[3][1], b32 = b[3][2], b33 = b[3][3]; - - d[0][0] = a00*b00 + a10*b01 + a20*b02 + a30*b03; - d[0][1] = a01*b00 + a11*b01 + a21*b02 + a31*b03; - d[0][2] = a02*b00 + a12*b01 + a22*b02 + a32*b03; - d[0][3] = a03*b00 + a13*b01 + a23*b02 + a33*b03; - d[1][0] = a00*b10 + a10*b11 + a20*b12 + a30*b13; - d[1][1] = a01*b10 + a11*b11 + a21*b12 + a31*b13; - d[1][2] = a02*b10 + a12*b11 + a22*b12 + a32*b13; - d[1][3] = a03*b10 + a13*b11 + a23*b12 + a33*b13; - d[2][0] = a00*b20 + a10*b21 + a20*b22 + a30*b23; - d[2][1] = a01*b20 + a11*b21 + a21*b22 + a31*b23; - d[2][2] = a02*b20 + a12*b21 + a22*b22 + a32*b23; - d[2][3] = a03*b20 + a13*b21 + a23*b22 + a33*b23; - d[3][0] = a00*b30 + a10*b31 + a20*b32 + a30*b33; - d[3][1] = a01*b30 + a11*b31 + a21*b32 + a31*b33; - d[3][2] = a02*b30 + a12*b31 + a22*b32 + a32*b33; - d[3][3] = a03*b30 + a13*b31 + a23*b32 + a33*b33; -} - -static inline void m4x4_mulv( m4x4f m, v4f v, v4f d ) -{ - v4f res; - - res[0] = m[0][0]*v[0] + m[1][0]*v[1] + m[2][0]*v[2] + m[3][0]*v[3]; - res[1] = m[0][1]*v[0] + m[1][1]*v[1] + m[2][1]*v[2] + m[3][1]*v[3]; - res[2] = m[0][2]*v[0] + m[1][2]*v[1] + m[2][2]*v[2] + m[3][2]*v[3]; - res[3] = m[0][3]*v[0] + m[1][3]*v[1] + m[2][3]*v[2] + m[3][3]*v[3]; - - v4_copy( res, d ); -} - -static inline void m4x4_inv( m4x4f a, m4x4f d ) -{ - f32 a00 = a[0][0], a01 = a[0][1], a02 = a[0][2], a03 = a[0][3], - a10 = a[1][0], a11 = a[1][1], a12 = a[1][2], a13 = a[1][3], - a20 = a[2][0], a21 = a[2][1], a22 = a[2][2], a23 = a[2][3], - a30 = a[3][0], a31 = a[3][1], a32 = a[3][2], a33 = a[3][3], - det, - t[6]; - - t[0] = a22*a33 - a32*a23; - t[1] = a21*a33 - a31*a23; - t[2] = a21*a32 - a31*a22; - t[3] = a20*a33 - a30*a23; - t[4] = a20*a32 - a30*a22; - t[5] = a20*a31 - a30*a21; - - d[0][0] = a11*t[0] - a12*t[1] + a13*t[2]; - d[1][0] =-(a10*t[0] - a12*t[3] + a13*t[4]); - d[2][0] = a10*t[1] - a11*t[3] + a13*t[5]; - d[3][0] =-(a10*t[2] - a11*t[4] + a12*t[5]); - - d[0][1] =-(a01*t[0] - a02*t[1] + a03*t[2]); - d[1][1] = a00*t[0] - a02*t[3] + a03*t[4]; - d[2][1] =-(a00*t[1] - a01*t[3] + a03*t[5]); - d[3][1] = a00*t[2] - a01*t[4] + a02*t[5]; - - t[0] = a12*a33 - a32*a13; - t[1] = a11*a33 - a31*a13; - t[2] = a11*a32 - a31*a12; - t[3] = a10*a33 - a30*a13; - t[4] = a10*a32 - a30*a12; - t[5] = a10*a31 - a30*a11; - - d[0][2] = a01*t[0] - a02*t[1] + a03*t[2]; - d[1][2] =-(a00*t[0] - a02*t[3] + a03*t[4]); - d[2][2] = a00*t[1] - a01*t[3] + a03*t[5]; - d[3][2] =-(a00*t[2] - a01*t[4] + a02*t[5]); - - t[0] = a12*a23 - a22*a13; - t[1] = a11*a23 - a21*a13; - t[2] = a11*a22 - a21*a12; - t[3] = a10*a23 - a20*a13; - t[4] = a10*a22 - a20*a12; - t[5] = a10*a21 - a20*a11; - - d[0][3] =-(a01*t[0] - a02*t[1] + a03*t[2]); - d[1][3] = a00*t[0] - a02*t[3] + a03*t[4]; - d[2][3] =-(a00*t[1] - a01*t[3] + a03*t[5]); - d[3][3] = a00*t[2] - a01*t[4] + a02*t[5]; - - det = 1.0f / (a00*d[0][0] + a01*d[1][0] + a02*d[2][0] + a03*d[3][0]); - v4_muls( d[0], det, d[0] ); - v4_muls( d[1], det, d[1] ); - v4_muls( d[2], det, d[2] ); - v4_muls( d[3], det, d[3] ); -} - -/* - * http://www.terathon.com/lengyel/Lengyel-Oblique.pdf - */ -static void m4x4_clip_projection( m4x4f mat, v4f plane ){ - v4f c = - { - (vg_signf(plane[0]) + mat[2][0]) / mat[0][0], - (vg_signf(plane[1]) + mat[2][1]) / mat[1][1], - -1.0f, - (1.0f + mat[2][2]) / mat[3][2] - }; - - v4_muls( plane, 2.0f / v4_dot(plane,c), c ); - - mat[0][2] = c[0]; - mat[1][2] = c[1]; - mat[2][2] = c[2] + 1.0f; - mat[3][2] = c[3]; -} - -/* - * Undoes the above operation - */ -static void m4x4_reset_clipping( m4x4f mat, float ffar, float fnear ){ - mat[0][2] = 0.0f; - mat[1][2] = 0.0f; - mat[2][2] = -(ffar + fnear) / (ffar - fnear); - mat[3][2] = -2.0f * ffar * fnear / (ffar - fnear); -} - -/* - * ----------------------------------------------------------------------------- - * Section 5.a Boxes - * ----------------------------------------------------------------------------- - */ - -static inline void box_addpt( boxf a, v3f pt ) -{ - v3_minv( a[0], pt, a[0] ); - v3_maxv( a[1], pt, a[1] ); -} - -static inline void box_concat( boxf a, boxf b ) -{ - v3_minv( a[0], b[0], a[0] ); - v3_maxv( a[1], b[1], a[1] ); -} - -static inline void box_copy( boxf a, boxf b ) -{ - v3_copy( a[0], b[0] ); - v3_copy( a[1], b[1] ); -} - -static inline int box_overlap( boxf a, boxf b ) -{ - return - ( a[0][0] <= b[1][0] && a[1][0] >= b[0][0] ) && - ( a[0][1] <= b[1][1] && a[1][1] >= b[0][1] ) && - ( a[0][2] <= b[1][2] && a[1][2] >= b[0][2] ) - ; -} - -static int box_within( boxf greater, boxf lesser ) -{ - v3f a, b; - v3_sub( lesser[0], greater[0], a ); - v3_sub( lesser[1], greater[1], b ); - - if( (a[0] >= 0.0f) && (a[1] >= 0.0f) && (a[2] >= 0.0f) && - (b[0] <= 0.0f) && (b[1] <= 0.0f) && (b[2] <= 0.0f) ) - { - return 1; - } - - return 0; -} - -static inline void box_init_inf( boxf box ){ - v3_fill( box[0], INFINITY ); - v3_fill( box[1], -INFINITY ); -} - -/* - * ----------------------------------------------------------------------------- - * Section 5.b Planes - * ----------------------------------------------------------------------------- - */ - -static inline void tri_to_plane( f64 a[3], f64 b[3], - f64 c[3], f64 p[4] ) -{ - f64 edge0[3]; - f64 edge1[3]; - f64 l; - - edge0[0] = b[0] - a[0]; - edge0[1] = b[1] - a[1]; - edge0[2] = b[2] - a[2]; - - edge1[0] = c[0] - a[0]; - edge1[1] = c[1] - a[1]; - edge1[2] = c[2] - a[2]; - - p[0] = edge0[1] * edge1[2] - edge0[2] * edge1[1]; - p[1] = edge0[2] * edge1[0] - edge0[0] * edge1[2]; - p[2] = edge0[0] * edge1[1] - edge0[1] * edge1[0]; - - l = sqrt(p[0] * p[0] + p[1] * p[1] + p[2] * p[2]); - p[3] = (p[0] * a[0] + p[1] * a[1] + p[2] * a[2]) / l; - - p[0] = p[0] / l; - p[1] = p[1] / l; - p[2] = p[2] / l; -} - -static int plane_intersect3( v4f a, v4f b, v4f c, v3f p ) -{ - f32 const epsilon = 1e-6f; - - v3f x; - v3_cross( a, b, x ); - f32 d = v3_dot( x, c ); - - if( (d < epsilon) && (d > -epsilon) ) return 0; - - v3f v0, v1, v2; - v3_cross( b, c, v0 ); - v3_cross( c, a, v1 ); - v3_cross( a, b, v2 ); - - v3_muls( v0, a[3], p ); - v3_muladds( p, v1, b[3], p ); - v3_muladds( p, v2, c[3], p ); - v3_divs( p, d, p ); - - return 1; -} - -static int plane_intersect2( v4f a, v4f b, v3f p, v3f n ) -{ - f32 const epsilon = 1e-6f; - - v4f c; - v3_cross( a, b, c ); - f32 d = v3_length2( c ); - - if( (d < epsilon) && (d > -epsilon) ) - return 0; - - v3f v0, v1, vx; - v3_cross( c, b, v0 ); - v3_cross( a, c, v1 ); - - v3_muls( v0, a[3], vx ); - v3_muladds( vx, v1, b[3], vx ); - v3_divs( vx, d, p ); - v3_copy( c, n ); - - return 1; -} - -static int plane_segment( v4f plane, v3f a, v3f b, v3f co ) -{ - f32 d0 = v3_dot( a, plane ) - plane[3], - d1 = v3_dot( b, plane ) - plane[3]; - - if( d0*d1 < 0.0f ) - { - f32 tot = 1.0f/( fabsf(d0)+fabsf(d1) ); - - v3_muls( a, fabsf(d1) * tot, co ); - v3_muladds( co, b, fabsf(d0) * tot, co ); - return 1; - } - - return 0; -} - -static inline f64 plane_polarity( f64 p[4], f64 a[3] ) -{ - return - (a[0] * p[0] + a[1] * p[1] + a[2] * p[2]) - -(p[0]*p[3] * p[0] + p[1]*p[3] * p[1] + p[2]*p[3] * p[2]) - ; -} - -static f32 ray_plane( v4f plane, v3f co, v3f dir ){ - f32 d = v3_dot( plane, dir ); - if( fabsf(d) > 1e-6f ){ - v3f v0; - v3_muls( plane, plane[3], v0 ); - v3_sub( v0, co, v0 ); - return v3_dot( v0, plane ) / d; - } - else return INFINITY; -} - -/* - * ----------------------------------------------------------------------------- - * Section 5.c Closest point functions - * ----------------------------------------------------------------------------- - */ - -/* - * These closest point tests were learned from Real-Time Collision Detection by - * Christer Ericson - */ -static f32 closest_segment_segment( v3f p1, v3f q1, v3f p2, v3f q2, - f32 *s, f32 *t, v3f c1, v3f c2) -{ - v3f d1,d2,r; - v3_sub( q1, p1, d1 ); - v3_sub( q2, p2, d2 ); - v3_sub( p1, p2, r ); - - f32 a = v3_length2( d1 ), - e = v3_length2( d2 ), - f = v3_dot( d2, r ); - - const f32 kEpsilon = 0.0001f; - - if( a <= kEpsilon && e <= kEpsilon ) - { - *s = 0.0f; - *t = 0.0f; - v3_copy( p1, c1 ); - v3_copy( p2, c2 ); - - v3f v0; - v3_sub( c1, c2, v0 ); - - return v3_length2( v0 ); - } - - if( a<= kEpsilon ) - { - *s = 0.0f; - *t = vg_clampf( f / e, 0.0f, 1.0f ); - } - else - { - f32 c = v3_dot( d1, r ); - if( e <= kEpsilon ) - { - *t = 0.0f; - *s = vg_clampf( -c / a, 0.0f, 1.0f ); - } - else - { - f32 b = v3_dot(d1,d2), - d = a*e-b*b; - - if( d != 0.0f ) - { - *s = vg_clampf((b*f - c*e)/d, 0.0f, 1.0f); - } - else - { - *s = 0.0f; - } - - *t = (b*(*s)+f) / e; - - if( *t < 0.0f ) - { - *t = 0.0f; - *s = vg_clampf( -c / a, 0.0f, 1.0f ); - } - else if( *t > 1.0f ) - { - *t = 1.0f; - *s = vg_clampf((b-c)/a,0.0f,1.0f); - } - } - } - - v3_muladds( p1, d1, *s, c1 ); - v3_muladds( p2, d2, *t, c2 ); - - v3f v0; - v3_sub( c1, c2, v0 ); - return v3_length2( v0 ); -} - -static int point_inside_aabb( boxf box, v3f point ) -{ - if((point[0]<=box[1][0]) && (point[1]<=box[1][1]) && (point[2]<=box[1][2]) && - (point[0]>=box[0][0]) && (point[1]>=box[0][1]) && (point[2]>=box[0][2]) ) - return 1; - else - return 0; -} - -static void closest_point_aabb( v3f p, boxf box, v3f dest ) -{ - v3_maxv( p, box[0], dest ); - v3_minv( dest, box[1], dest ); -} - -static void closest_point_obb( v3f p, boxf box, - m4x3f mtx, m4x3f inv_mtx, v3f dest ) -{ - v3f local; - m4x3_mulv( inv_mtx, p, local ); - closest_point_aabb( local, box, local ); - m4x3_mulv( mtx, local, dest ); -} - -static f32 closest_point_segment( v3f a, v3f b, v3f point, v3f dest ) -{ - v3f v0, v1; - v3_sub( b, a, v0 ); - v3_sub( point, a, v1 ); - - f32 t = v3_dot( v1, v0 ) / v3_length2(v0); - t = vg_clampf(t,0.0f,1.0f); - v3_muladds( a, v0, t, dest ); - return t; -} - -static void closest_on_triangle( v3f p, v3f tri[3], v3f dest ) -{ - v3f ab, ac, ap; - f32 d1, d2; - - /* Region outside A */ - v3_sub( tri[1], tri[0], ab ); - v3_sub( tri[2], tri[0], ac ); - v3_sub( p, tri[0], ap ); - - d1 = v3_dot(ab,ap); - d2 = v3_dot(ac,ap); - if( d1 <= 0.0f && d2 <= 0.0f ) - { - v3_copy( tri[0], dest ); - v3_copy( (v3f){INFINITY,INFINITY,INFINITY}, dest ); - return; - } - - /* Region outside B */ - v3f bp; - f32 d3, d4; - - v3_sub( p, tri[1], bp ); - d3 = v3_dot( ab, bp ); - d4 = v3_dot( ac, bp ); - - if( d3 >= 0.0f && d4 <= d3 ) - { - v3_copy( tri[1], dest ); - v3_copy( (v3f){INFINITY,INFINITY,INFINITY}, dest ); - return; - } - - /* Edge region of AB */ - f32 vc = d1*d4 - d3*d2; - if( vc <= 0.0f && d1 >= 0.0f && d3 <= 0.0f ) - { - f32 v = d1 / (d1-d3); - v3_muladds( tri[0], ab, v, dest ); - v3_copy( (v3f){INFINITY,INFINITY,INFINITY}, dest ); - return; - } - - /* Region outside C */ - v3f cp; - f32 d5, d6; - v3_sub( p, tri[2], cp ); - d5 = v3_dot(ab, cp); - d6 = v3_dot(ac, cp); - - if( d6 >= 0.0f && d5 <= d6 ) - { - v3_copy( tri[2], dest ); - v3_copy( (v3f){INFINITY,INFINITY,INFINITY}, dest ); - return; - } - - /* Region of AC */ - f32 vb = d5*d2 - d1*d6; - if( vb <= 0.0f && d2 >= 0.0f && d6 <= 0.0f ) - { - f32 w = d2 / (d2-d6); - v3_muladds( tri[0], ac, w, dest ); - v3_copy( (v3f){INFINITY,INFINITY,INFINITY}, dest ); - return; - } - - /* Region of BC */ - f32 va = d3*d6 - d5*d4; - if( va <= 0.0f && (d4-d3) >= 0.0f && (d5-d6) >= 0.0f ) - { - f32 w = (d4-d3) / ((d4-d3) + (d5-d6)); - v3f bc; - v3_sub( tri[2], tri[1], bc ); - v3_muladds( tri[1], bc, w, dest ); - v3_copy( (v3f){INFINITY,INFINITY,INFINITY}, dest ); - return; - } - - /* P inside region, Q via barycentric coordinates uvw */ - f32 d = 1.0f/(va+vb+vc), - v = vb*d, - w = vc*d; - - v3_muladds( tri[0], ab, v, dest ); - v3_muladds( dest, ac, w, dest ); -} - -enum contact_type -{ - k_contact_type_default, - k_contact_type_disabled, - k_contact_type_edge -}; - -static enum contact_type closest_on_triangle_1( v3f p, v3f tri[3], v3f dest ) -{ - v3f ab, ac, ap; - f32 d1, d2; - - /* Region outside A */ - v3_sub( tri[1], tri[0], ab ); - v3_sub( tri[2], tri[0], ac ); - v3_sub( p, tri[0], ap ); - - d1 = v3_dot(ab,ap); - d2 = v3_dot(ac,ap); - if( d1 <= 0.0f && d2 <= 0.0f ) - { - v3_copy( tri[0], dest ); - return k_contact_type_default; - } - - /* Region outside B */ - v3f bp; - f32 d3, d4; - - v3_sub( p, tri[1], bp ); - d3 = v3_dot( ab, bp ); - d4 = v3_dot( ac, bp ); - - if( d3 >= 0.0f && d4 <= d3 ) - { - v3_copy( tri[1], dest ); - return k_contact_type_edge; - } - - /* Edge region of AB */ - f32 vc = d1*d4 - d3*d2; - if( vc <= 0.0f && d1 >= 0.0f && d3 <= 0.0f ) - { - f32 v = d1 / (d1-d3); - v3_muladds( tri[0], ab, v, dest ); - return k_contact_type_edge; - } - - /* Region outside C */ - v3f cp; - f32 d5, d6; - v3_sub( p, tri[2], cp ); - d5 = v3_dot(ab, cp); - d6 = v3_dot(ac, cp); - - if( d6 >= 0.0f && d5 <= d6 ) - { - v3_copy( tri[2], dest ); - return k_contact_type_edge; - } - - /* Region of AC */ - f32 vb = d5*d2 - d1*d6; - if( vb <= 0.0f && d2 >= 0.0f && d6 <= 0.0f ) - { - f32 w = d2 / (d2-d6); - v3_muladds( tri[0], ac, w, dest ); - return k_contact_type_edge; - } - - /* Region of BC */ - f32 va = d3*d6 - d5*d4; - if( va <= 0.0f && (d4-d3) >= 0.0f && (d5-d6) >= 0.0f ) - { - f32 w = (d4-d3) / ((d4-d3) + (d5-d6)); - v3f bc; - v3_sub( tri[2], tri[1], bc ); - v3_muladds( tri[1], bc, w, dest ); - return k_contact_type_edge; - } - - /* P inside region, Q via barycentric coordinates uvw */ - f32 d = 1.0f/(va+vb+vc), - v = vb*d, - w = vc*d; - - v3_muladds( tri[0], ab, v, dest ); - v3_muladds( dest, ac, w, dest ); - - return k_contact_type_default; -} - -static void closest_point_elipse( v2f p, v2f e, v2f o ) -{ - v2f pabs, ei, e2, ve, t; - - v2_abs( p, pabs ); - v2_div( (v2f){ 1.0f, 1.0f }, e, ei ); - v2_mul( e, e, e2 ); - v2_mul( ei, (v2f){ e2[0]-e2[1], e2[1]-e2[0] }, ve ); - - v2_fill( t, 0.70710678118654752f ); - - for( int i=0; i<3; i++ ){ - v2f v, u, ud, w; - - v2_mul( ve, t, v ); /* ve*t*t*t */ - v2_mul( v, t, v ); - v2_mul( v, t, v ); - - v2_sub( pabs, v, u ); - v2_normalize( u ); - - v2_mul( t, e, ud ); - v2_sub( ud, v, ud ); - - v2_muls( u, v2_length( ud ), u ); - - v2_add( v, u, w ); - v2_mul( w, ei, w ); - - v2_maxv( (v2f){0.0f,0.0f}, w, t ); - v2_normalize( t ); - } - - v2_mul( t, e, o ); - v2_copysign( o, p ); -} - -/* - * ----------------------------------------------------------------------------- - * Section 5.d Raycasts & Spherecasts - * ----------------------------------------------------------------------------- - */ - -static int ray_aabb1( boxf box, v3f co, v3f dir_inv, f32 dist ) -{ - v3f v0, v1; - f32 tmin, tmax; - - v3_sub( box[0], co, v0 ); - v3_sub( box[1], co, v1 ); - - v3_mul( v0, dir_inv, v0 ); - v3_mul( v1, dir_inv, v1 ); - - tmin = vg_minf( v0[0], v1[0] ); - tmax = vg_maxf( v0[0], v1[0] ); - tmin = vg_maxf( tmin, vg_minf( v0[1], v1[1] )); - tmax = vg_minf( tmax, vg_maxf( v0[1], v1[1] )); - tmin = vg_maxf( tmin, vg_minf( v0[2], v1[2] )); - tmax = vg_minf( tmax, vg_maxf( v0[2], v1[2] )); - - return (tmax >= tmin) && (tmin <= dist) && (tmax >= 0.0f); -} - -/* Time of intersection with ray vs triangle */ -static int ray_tri( v3f tri[3], v3f co, - v3f dir, f32 *dist, int backfaces ) -{ - f32 const kEpsilon = 0.00001f; - - v3f v0, v1, h, s, q, n; - f32 a,f,u,v,t; - - f32 *pa = tri[0], - *pb = tri[1], - *pc = tri[2]; - - v3_sub( pb, pa, v0 ); - v3_sub( pc, pa, v1 ); - v3_cross( dir, v1, h ); - v3_cross( v0, v1, n ); - - if( (v3_dot( n, dir ) > 0.0f) && !backfaces ) /* Backface culling */ - return 0; - - /* Parralel */ - a = v3_dot( v0, h ); - - if( a > -kEpsilon && a < kEpsilon ) - return 0; - - f = 1.0f/a; - v3_sub( co, pa, s ); - - u = f * v3_dot(s, h); - if( u < 0.0f || u > 1.0f ) - return 0; - - v3_cross( s, v0, q ); - v = f * v3_dot( dir, q ); - if( v < 0.0f || u+v > 1.0f ) - return 0; - - t = f * v3_dot(v1, q); - if( t > kEpsilon ) - { - *dist = t; - return 1; - } - else return 0; -} - -/* time of intersection with ray vs sphere */ -static int ray_sphere( v3f c, f32 r, - v3f co, v3f dir, f32 *t ) -{ - v3f m; - v3_sub( co, c, m ); - - f32 b = v3_dot( m, dir ), - c1 = v3_dot( m, m ) - r*r; - - /* Exit if r’s origin outside s (c > 0) and r pointing away from s (b > 0) */ - if( c1 > 0.0f && b > 0.0f ) - return 0; - - f32 discr = b*b - c1; - - /* A negative discriminant corresponds to ray missing sphere */ - if( discr < 0.0f ) - return 0; - - /* - * Ray now found to intersect sphere, compute smallest t value of - * intersection - */ - *t = -b - sqrtf( discr ); - - /* If t is negative, ray started inside sphere so clamp t to zero */ - if( *t < 0.0f ) - *t = 0.0f; - - return 1; -} - -/* - * time of intersection of ray vs cylinder - * The cylinder does not have caps but is finite - * - * Heavily adapted from regular segment vs cylinder from: - * Real-Time Collision Detection - */ -static int ray_uncapped_finite_cylinder( v3f q, v3f p, f32 r, - v3f co, v3f dir, f32 *t ) -{ - v3f d, m, n, sb; - v3_muladds( co, dir, 1.0f, sb ); - - v3_sub( q, p, d ); - v3_sub( co, p, m ); - v3_sub( sb, co, n ); - - f32 md = v3_dot( m, d ), - nd = v3_dot( n, d ), - dd = v3_dot( d, d ), - nn = v3_dot( n, n ), - mn = v3_dot( m, n ), - a = dd*nn - nd*nd, - k = v3_dot( m, m ) - r*r, - c = dd*k - md*md; - - if( fabsf(a) < 0.00001f ) - { - /* Segment runs parallel to cylinder axis */ - return 0; - } - - f32 b = dd*mn - nd*md, - discr = b*b - a*c; - - if( discr < 0.0f ) - return 0; /* No real roots; no intersection */ - - *t = (-b - sqrtf(discr)) / a; - if( *t < 0.0f ) - return 0; /* Intersection behind ray */ - - /* Check within cylinder segment */ - if( md + (*t)*nd < 0.0f ) - return 0; - - if( md + (*t)*nd > dd ) - return 0; - - /* Segment intersects cylinder between the endcaps; t is correct */ - return 1; -} - -/* - * Time of intersection of sphere and triangle. Origin must be outside the - * colliding area. This is a fairly long procedure. - */ -static int spherecast_triangle( v3f tri[3], - v3f co, v3f dir, f32 r, f32 *t, v3f n ) -{ - v3f sum[3]; - v3f v0, v1; - - v3_sub( tri[1], tri[0], v0 ); - v3_sub( tri[2], tri[0], v1 ); - v3_cross( v0, v1, n ); - v3_normalize( n ); - v3_muladds( tri[0], n, r, sum[0] ); - v3_muladds( tri[1], n, r, sum[1] ); - v3_muladds( tri[2], n, r, sum[2] ); - - int hit = 0; - f32 t_min = INFINITY, - t1; - - if( ray_tri( sum, co, dir, &t1, 0 ) ){ - t_min = vg_minf( t_min, t1 ); - hit = 1; - } - - /* - * Currently disabled; ray_sphere requires |d| = 1. it is not very important. - */ -#if 0 - for( int i=0; i<3; i++ ){ - if( ray_sphere( tri[i], r, co, dir, &t1 ) ){ - t_min = vg_minf( t_min, t1 ); - hit = 1; - } - } -#endif - - for( int i=0; i<3; i++ ){ - int i0 = i, - i1 = (i+1)%3; - - if( ray_uncapped_finite_cylinder( tri[i0], tri[i1], r, co, dir, &t1 ) ){ - if( t1 < t_min ){ - t_min = t1; - - v3f co1, ct, cx; - v3_add( dir, co, co1 ); - v3_lerp( co, co1, t_min, ct ); - - closest_point_segment( tri[i0], tri[i1], ct, cx ); - v3_sub( ct, cx, n ); - v3_normalize( n ); - } - - hit = 1; - } - } - - *t = t_min; - return hit; -} - -/* - * ----------------------------------------------------------------------------- - * Section 5.e Curves - * ----------------------------------------------------------------------------- - */ - -static void eval_bezier_time( v3f p0, v3f p1, v3f h0, v3f h1, f32 t, v3f p ) -{ - f32 tt = t*t, - ttt = tt*t; - - v3_muls( p1, ttt, p ); - v3_muladds( p, h1, 3.0f*tt -3.0f*ttt, p ); - v3_muladds( p, h0, 3.0f*ttt -6.0f*tt +3.0f*t, p ); - v3_muladds( p, p0, 3.0f*tt -ttt -3.0f*t +1.0f, p ); -} - -static void eval_bezier3( v3f p0, v3f p1, v3f p2, f32 t, v3f p ) -{ - f32 u = 1.0f-t; - - v3_muls( p0, u*u, p ); - v3_muladds( p, p1, 2.0f*u*t, p ); - v3_muladds( p, p2, t*t, p ); -} - -/* - * ----------------------------------------------------------------------------- - * Section 5.f Volumes - * ----------------------------------------------------------------------------- - */ - -static f32 vg_sphere_volume( f32 r ){ - return (4.0f/3.0f) * VG_PIf * r*r*r; -} - -static f32 vg_box_volume( boxf box ){ - v3f e; - v3_sub( box[1], box[0], e ); - return e[0]*e[1]*e[2]; -} - -static f32 vg_cylinder_volume( f32 r, f32 h ){ - return VG_PIf * r*r * h; -} - -static f32 vg_capsule_volume( f32 r, f32 h ){ - return vg_sphere_volume( r ) + vg_cylinder_volume( r, h-r*2.0f ); -} - -static void vg_sphere_bound( f32 r, boxf out_box ){ - v3_fill( out_box[0], -r ); - v3_fill( out_box[1], r ); -} - -static void vg_capsule_bound( f32 r, f32 h, boxf out_box ){ - v3_copy( (v3f){-r,-h*0.5f,r}, out_box[0] ); - v3_copy( (v3f){-r, h*0.5f,r}, out_box[1] ); -} - - -/* - * ----------------------------------------------------------------------------- - * Section 5.g Inertia Tensors - * ----------------------------------------------------------------------------- - */ - -/* - * Translate existing inertia tensor - */ -static void vg_translate_inertia( m3x3f inout_inertia, f32 mass, v3f d ){ - /* - * I = I_0 + m*[(d.d)E_3 - d(X)d] - * - * I: updated tensor - * I_0: original tensor - * m: scalar mass - * d: translation vector - * (X): outer product - * E_3: identity matrix - */ - m3x3f t, outer, scale; - m3x3_diagonal( t, v3_dot(d,d) ); - m3x3_outer_product( outer, d, d ); - m3x3_sub( t, outer, t ); - m3x3_diagonal( scale, mass ); - m3x3_mul( scale, t, t ); - m3x3_add( inout_inertia, t, inout_inertia ); -} - -/* - * Rotate existing inertia tensor - */ -static void vg_rotate_inertia( m3x3f inout_inertia, m3x3f rotation ){ - /* - * I = R I_0 R^T - * - * I: updated tensor - * I_0: original tensor - * R: rotation matrix - * R^T: tranposed rotation matrix - */ - - m3x3f Rt; - m3x3_transpose( rotation, Rt ); - m3x3_mul( rotation, inout_inertia, inout_inertia ); - m3x3_mul( inout_inertia, Rt, inout_inertia ); -} -/* - * Create inertia tensor for box - */ -static void vg_box_inertia( boxf box, f32 mass, m3x3f out_inertia ){ - v3f e, com; - v3_sub( box[1], box[0], e ); - v3_muladds( box[0], e, 0.5f, com ); - - f32 ex2 = e[0]*e[0], - ey2 = e[1]*e[1], - ez2 = e[2]*e[2], - ix = (ey2+ez2) * mass * (1.0f/12.0f), - iy = (ex2+ez2) * mass * (1.0f/12.0f), - iz = (ex2+ey2) * mass * (1.0f/12.0f); - - m3x3_identity( out_inertia ); - m3x3_setdiagonalv3( out_inertia, (v3f){ ix, iy, iz } ); - vg_translate_inertia( out_inertia, mass, com ); -} - -/* - * Create inertia tensor for sphere - */ -static void vg_sphere_inertia( f32 r, f32 mass, m3x3f out_inertia ){ - f32 ixyz = r*r * mass * (2.0f/5.0f); - - m3x3_identity( out_inertia ); - m3x3_setdiagonalv3( out_inertia, (v3f){ ixyz, ixyz, ixyz } ); -} - -/* - * Create inertia tensor for capsule - */ -static void vg_capsule_inertia( f32 r, f32 h, f32 mass, m3x3f out_inertia ){ - f32 density = mass / vg_capsule_volume( r, h ), - ch = h-r*2.0f, /* cylinder height */ - cm = VG_PIf * ch*r*r * density, /* cylinder mass */ - hm = VG_TAUf * (1.0f/3.0f) * r*r*r * density, /* hemisphere mass */ - - iy = r*r*cm * 0.5f, - ixz = iy * 0.5f + cm*ch*ch*(1.0f/12.0f), - - aux0= (hm*2.0f*r*r)/5.0f; - - iy += aux0 * 2.0f; - - f32 aux1= ch*0.5f, - aux2= aux0 + hm*(aux1*aux1 + 3.0f*(1.0f/8.0f)*ch*r); - - ixz += aux2*2.0f; - - m3x3_identity( out_inertia ); - m3x3_setdiagonalv3( out_inertia, (v3f){ ixz, iy, ixz } ); -} - -/* - * ----------------------------------------------------------------------------- - * Section 6.a PSRNG and some distributions - * ----------------------------------------------------------------------------- - */ - -/* An implementation of the MT19937 Algorithm for the Mersenne Twister - * by Evan Sultanik. Based upon the pseudocode in: M. Matsumoto and - * T. Nishimura, "Mersenne Twister: A 623-dimensionally - * equidistributed uniform pseudorandom number generator," ACM - * Transactions on Modeling and Computer Simulation Vol. 8, No. 1, - * January pp.3-30 1998. - * - * http://www.sultanik.com/Mersenne_twister - * https://github.com/ESultanik/mtwister/blob/master/mtwister.c - */ - -#define MT_UPPER_MASK 0x80000000 -#define MT_LOWER_MASK 0x7fffffff -#define MT_TEMPERING_MASK_B 0x9d2c5680 -#define MT_TEMPERING_MASK_C 0xefc60000 - -#define MT_STATE_VECTOR_LENGTH 624 - -/* changes to STATE_VECTOR_LENGTH also require changes to this */ -#define MT_STATE_VECTOR_M 397 - -typedef struct vg_rand vg_rand; -struct vg_rand { - u32 mt[MT_STATE_VECTOR_LENGTH]; - i32 index; -}; - -static void vg_rand_seed( vg_rand *rand, unsigned long seed ) { - /* set initial seeds to mt[STATE_VECTOR_LENGTH] using the generator - * from Line 25 of Table 1 in: Donald Knuth, "The Art of Computer - * Programming," Vol. 2 (2nd Ed.) pp.102. - */ - rand->mt[0] = seed & 0xffffffff; - for( rand->index=1; rand->indexindex++){ - rand->mt[rand->index] = (6069 * rand->mt[rand->index-1]) & 0xffffffff; - } -} - -/* - * Generates a pseudo-randomly generated long. - */ -static u32 vg_randu32( vg_rand *rand ) { - u32 y; - /* mag[x] = x * 0x9908b0df for x = 0,1 */ - static u32 mag[2] = {0x0, 0x9908b0df}; - if( rand->index >= MT_STATE_VECTOR_LENGTH || rand->index < 0 ){ - /* generate STATE_VECTOR_LENGTH words at a time */ - int kk; - if( rand->index >= MT_STATE_VECTOR_LENGTH+1 || rand->index < 0 ){ - vg_rand_seed( rand, 4357 ); - } - for( kk=0; kkmt[kk] & MT_UPPER_MASK) | - (rand->mt[kk+1] & MT_LOWER_MASK); - rand->mt[kk] = rand->mt[kk+MT_STATE_VECTOR_M] ^ (y>>1) ^ mag[y & 0x1]; - } - for( ; kkmt[kk] & MT_UPPER_MASK) | - (rand->mt[kk+1] & MT_LOWER_MASK); - rand->mt[kk] = - rand->mt[ kk+(MT_STATE_VECTOR_M-MT_STATE_VECTOR_LENGTH)] ^ - (y >> 1) ^ mag[y & 0x1]; - } - y = (rand->mt[MT_STATE_VECTOR_LENGTH-1] & MT_UPPER_MASK) | - (rand->mt[0] & MT_LOWER_MASK); - rand->mt[MT_STATE_VECTOR_LENGTH-1] = - rand->mt[MT_STATE_VECTOR_M-1] ^ (y >> 1) ^ mag[y & 0x1]; - rand->index = 0; - } - y = rand->mt[rand->index++]; - y ^= (y >> 11); - y ^= (y << 7) & MT_TEMPERING_MASK_B; - y ^= (y << 15) & MT_TEMPERING_MASK_C; - y ^= (y >> 18); - return y; -} - -/* - * Generates a pseudo-randomly generated f64 in the range [0..1]. - */ -static inline f64 vg_randf64( vg_rand *rand ){ - return (f64)vg_randu32(rand)/(f64)0xffffffff; -} - -static inline f64 vg_randf64_range( vg_rand *rand, f64 min, f64 max ){ - return vg_lerp( min, max, (f64)vg_randf64(rand) ); -} - -static inline void vg_rand_dir( vg_rand *rand, v3f dir ){ - dir[0] = vg_randf64(rand); - dir[1] = vg_randf64(rand); - dir[2] = vg_randf64(rand); - - /* warning: *could* be 0 length. - * very unlikely.. 1 in (2^32)^3. but its mathematically wrong. */ - - v3_muls( dir, 2.0f, dir ); - v3_sub( dir, (v3f){1.0f,1.0f,1.0f}, dir ); - - v3_normalize( dir ); -} - -static inline void vg_rand_sphere( vg_rand *rand, v3f co ){ - vg_rand_dir(rand,co); - v3_muls( co, cbrtf( vg_randf64(rand) ), co ); -} - -static void vg_rand_disc( vg_rand *rand, v2f co ){ - f32 a = vg_randf64(rand) * VG_TAUf; - co[0] = sinf(a); - co[1] = cosf(a); - v2_muls( co, sqrtf( vg_randf64(rand) ), co ); -} - -static void vg_rand_cone( vg_rand *rand, v3f out_dir, f32 angle ){ - f32 r = sqrtf(vg_randf64(rand)) * angle * 0.5f, - a = vg_randf64(rand) * VG_TAUf; - - out_dir[0] = sinf(a) * sinf(r); - out_dir[1] = cosf(a) * sinf(r); - out_dir[2] = cosf(r); -} - -static void vg_hsv_rgb( v3f hsv, v3f rgb ){ - i32 i = floorf( hsv[0]*6.0f ); - f32 v = hsv[2], - f = hsv[0] * 6.0f - (f32)i, - p = v * (1.0f-hsv[1]), - q = v * (1.0f-f*hsv[1]), - t = v * (1.0f-(1.0f-f)*hsv[1]); - - switch( i % 6 ){ - case 0: rgb[0] = v; rgb[1] = t; rgb[2] = p; break; - case 1: rgb[0] = q; rgb[1] = v; rgb[2] = p; break; - case 2: rgb[0] = p; rgb[1] = v; rgb[2] = t; break; - case 3: rgb[0] = p; rgb[1] = q; rgb[2] = v; break; - case 4: rgb[0] = t; rgb[1] = p; rgb[2] = v; break; - case 5: rgb[0] = v; rgb[1] = p; rgb[2] = q; break; - } -} - -static void vg_rgb_hsv( v3f rgb, v3f hsv ){ - f32 min = v3_minf( rgb ), - max = v3_maxf( rgb ), - range = max-min, - k_epsilon = 0.00001f; - - hsv[2] = max; - if( range < k_epsilon ){ - hsv[0] = 0.0f; - hsv[1] = 0.0f; - return; - } - - if( max > k_epsilon ){ - hsv[1] = range/max; - } - else { - hsv[0] = 0.0f; - hsv[1] = 0.0f; - return; - } - - if( rgb[0] >= max ) - hsv[0] = (rgb[1]-rgb[2])/range; - else if( max == rgb[1] ) - hsv[0] = 2.0f+(rgb[2]-rgb[0])/range; - else - hsv[0] = 4.0f+(rgb[0]-rgb[1])/range; - - hsv[0] = vg_fractf( hsv[0] * (60.0f/360.0f) ); -} diff --git a/vg_magi.c b/vg_magi.c new file mode 100644 index 0000000..15ad090 --- /dev/null +++ b/vg_magi.c @@ -0,0 +1,328 @@ +#include "vg_magi.h" +#include "vg_console.h" + +struct vg_magi _vg_magi; + +struct vg_magi_panel *_vg_magi_open( ui_px w, ui_px h, u32 flags ) +{ + VG_ASSERT( _vg_magi.panel_count < VG_MAGI_MAX_PANELS ); + struct vg_magi_panel *panel = &_vg_magi.panels[ _vg_magi.panel_count ++ ]; + panel->rect[0] = 32; + panel->rect[1] = 32; + panel->rect[2] = w; + panel->rect[3] = h; + panel->title = ""; + panel->data = NULL; + panel->minimized = 0; + panel->flags = flags; + panel->ui_cb = NULL; + panel->close_cb = NULL; + panel->min_w = w; + panel->min_h = h; + panel->corner = 0; + + if( flags & VG_MAGI_PERSISTENT ) + strcpy( panel->cmd, vg_console.input ); + + return panel; +} + +void _vg_magi_area_change( i32 d[2] ) +{ + for( u32 i=0; i<_vg_magi.panel_count; i ++ ) + { + struct vg_magi_panel *panel = &_vg_magi.panels[ i ]; + if( panel->corner & 0x1 ) + panel->rect[0] += d[0]; + if( panel->corner & 0x2 ) + panel->rect[1] += d[1]; + } +} + +static void vg_magi_getcorner( ui_rect rect, u32 id, ui_px corner[2] ) +{ + corner[0] = rect[0]; + corner[1] = rect[1]; + if( id&0x1 ) corner[0] += rect[2]; + if( id&0x2 ) corner[1] += rect[3]; +} + +void _vg_magi_render( ui_context *ctx ) +{ + if( _vg_magi.panel_count == 0 ) return; + + u32 highlight = 0xffffffff, + top = _vg_magi.panel_count-1; + + if( _vg_magi.mode == k_magi_mode_none ) + { + for( u32 i=0; i<_vg_magi.panel_count; i ++ ) + { + struct vg_magi_panel *panel = &_vg_magi.panels[ i ]; + if( ui_inside_rect( panel->rect, ctx->mouse ) ) + highlight = i; + } + + /* bring to top */ + if( (highlight < top) && ui_click_down( ctx, UI_MOUSE_ANY ) ) + { + struct vg_magi_panel temp = _vg_magi.panels[ highlight ]; + + for( i32 i=0, j=0; i<_vg_magi.panel_count; i ++ ) + if( i != highlight ) + _vg_magi.panels[ j ++ ] = _vg_magi.panels[ i ]; + + _vg_magi.panels[ top ] = temp; + highlight = top; + } + } + else + { + highlight = top; + struct vg_magi_panel *ptop = &_vg_magi.panels[ top ]; + + if( _vg_magi.mode == k_magi_mode_drag ) + { + ptop->rect[0] = _vg_magi.drag_original[0] + ctx->mouse_delta[0]; + ptop->rect[1] = _vg_magi.drag_original[1] + ctx->mouse_delta[1]; + + ui_rect vp = { 0,0, ctx->area[0],ctx->area[1] }; + + f32 min2 = 9999999.9f; + for( u32 i=0; i<4; i ++ ) + { + ui_px c0[2], c1[2]; + vg_magi_getcorner( vp, i, c0 ); + vg_magi_getcorner( ptop->rect, i, c1 ); + + f32 dx = c0[0]-c1[0], + dy = c0[1]-c1[1], + d2 = dx*dx + dy*dy; + + if( d2 < min2 ) + { + min2 = d2; + ptop->corner = i; + } + } + } + else if( _vg_magi.mode == k_magi_mode_resize ) + { + ui_px dx = ctx->mouse_delta[0]; + if( _vg_magi.drag_left ) dx = -dx; + + ptop->rect[2] = _vg_magi.drag_original[2] + dx; + if( ptop->rect[2] < ptop->min_w ) + { + ptop->rect[2] = ptop->min_w; + dx = ptop->min_w - _vg_magi.drag_original[2]; + } + + if( _vg_magi.drag_left ) + ptop->rect[0] = _vg_magi.drag_original[0] - dx; + + if( !ptop->minimized ) + { + ui_px dy = ctx->mouse_delta[1]; + if( _vg_magi.drag_top ) dy = -dy; + + ptop->rect[3] = _vg_magi.drag_original[3] + dy; + if( ptop->rect[3] < ptop->min_h ) + { + ptop->rect[3] = ptop->min_h; + dy = ptop->min_h - _vg_magi.drag_original[3]; + } + + if( _vg_magi.drag_top ) + ptop->rect[1] = _vg_magi.drag_original[1] - dy; + } + } + + if( ui_click_up( ctx, UI_MOUSE_ANY ) ) + _vg_magi.mode = k_magi_mode_none; + } + + i32 j=0; + for( i32 i=0; i<_vg_magi.panel_count; i ++ ) + { + struct vg_magi_panel *panel = &_vg_magi.panels[ i ]; + + ui_rect title, rect; + ui_split( panel->rect, k_ui_axis_h, 28, 0, title, rect ); + ui_fill( ctx, title, ui_opacity( ui_colour( ctx, k_ui_bg+7 ), 0.9f ) ); + + ui_rect min_button, quit_button; + ui_split( title, k_ui_axis_v, title[2]-title[3], 2, title, quit_button ); + int should_close = ui_button_text( ctx, quit_button, "X", 1 ); + + ui_split( title, k_ui_axis_v, title[2]-title[3], 2, title, min_button ); + int should_min = ui_button_text( ctx, min_button, + panel->minimized? "+": "-", 1 ); + + if( panel->flags & VG_MAGI_PERSISTENT ) + { + ui_text( ctx, title, panel->cmd, 1, k_ui_align_middle_center, + ui_colourcont( ctx, k_ui_bg+7 ) ); + } + else + { + ui_text( ctx, title, panel->title, 1, k_ui_align_middle_center, + ui_colourcont( ctx, k_ui_bg+7 ) ); + } + + ui_fill( ctx, rect, ui_opacity( ui_colour( ctx, k_ui_bg+1 ), 0.7f ) ); + + if( i == highlight ) + { + /* TODO: enable interaction */ + + if( ui_click_down( ctx, UI_MOUSE_MIDDLE ) ) + { + if( panel->flags & VG_MAGI_MOVEABLE ) + { + _vg_magi.mode = k_magi_mode_drag; + rect_copy( panel->rect, _vg_magi.drag_original ); + } + } + else if( ui_click_down( ctx, UI_MOUSE_RIGHT ) ) + { + if( panel->flags & VG_MAGI_RESIZEABLE ) + { + _vg_magi.mode = k_magi_mode_resize; + _vg_magi.drag_top = 0; + _vg_magi.drag_left = 0; + + if( ctx->mouse[0] < panel->rect[0]+panel->rect[2]/2 ) + _vg_magi.drag_left = 1; + if( ctx->mouse[1] < panel->rect[1]+panel->rect[3]/2 ) + _vg_magi.drag_top = 1; + + rect_copy( panel->rect, _vg_magi.drag_original ); + } + } + + ui_outline( ctx, panel->rect, 1, ui_colour( ctx, k_ui_bg+7 ), 0 ); + } + else + { + /* TODO: disable interaction */ + } + + if( !panel->minimized ) + panel->ui_cb( ctx, rect, panel ); + + if( should_close == 1 ) + { + if( panel->close_cb ) + panel->close_cb( panel ); + + continue; + } + + if( should_min == 1 ) + { + panel->minimized ^= 0x1; + if( panel->minimized ) + { + panel->sh = panel->rect[3]; + panel->rect[3] = 32; + } + else + { + panel->rect[3] = panel->sh; + } + } + + if( j != i ) + _vg_magi.panels[ j ] = _vg_magi.panels[ i ]; + + j ++; + } + + _vg_magi.panel_count = j; +} + +void vg_magi_restore(void) +{ + vg_console_exec( 2, (const char *[]){ "magi.conf", "silent" } ); +} + +void vg_magi_save(void) +{ + FILE *fp = fopen( "cfg/magi.conf", "w" ); + + if( !fp ) + { + vg_error( "Cannot open cfg/magi.conf\n" ); + return; + } + + for( u32 i=0; i<_vg_magi.panel_count; i ++ ) + { + struct vg_magi_panel *magi = &_vg_magi.panels[i]; + + if( magi->flags & VG_MAGI_PERSISTENT ) + { + ui_rect vp = {0,0,vg.window_x,vg.window_y}; + ui_px c[2], p[2]; + vg_magi_getcorner( vp, magi->corner, c ); + p[0] = magi->rect[0] - c[0]; + p[1] = magi->rect[1] - c[1]; + + fprintf( fp, "%s\n", magi->cmd ); + fprintf( fp, "magi_pos %d %d %d %d %d %d\n", + p[0], p[1], magi->rect[2], + magi->minimized? magi->sh: magi->rect[3], + (i32)magi->minimized, magi->corner ); + } + } + + fclose( fp ); +} + +static int cmd_vg_magi_dim( int argc, const char *argv[] ) +{ + if( !_vg_magi.panel_count ) + { + vg_error( "No active panels to apply that to.\n" ); + return 0; + } + + if( argc == 0 ) + { + vg_error( "Usage: magi_pos x y w h \n" ); + return 0; + } + + struct vg_magi_panel *magi = &_vg_magi.panels[ _vg_magi.panel_count-1 ]; + for( int i=0; i<4; i ++ ) + if( argc >= i+1 ) + magi->rect[i] = atoi( argv[i] ); + + if( argc >= 5 ) + { + magi->minimized = atoi( argv[4] ); + if( magi->minimized ) + { + magi->sh = magi->rect[3]; + magi->rect[3] = 32; + } + } + + if( argc >= 6 ) + { + ui_rect vp = {0,0,vg.window_x,vg.window_y}; + ui_px c[2]; + magi->corner = atoi( argv[5] ); + vg_magi_getcorner( vp, magi->corner, c ); + magi->rect[0] += c[0]; + magi->rect[1] += c[1]; + } + + return 1; +} + +void vg_magi_init(void) +{ + vg_console_reg_cmd( "magi_pos", cmd_vg_magi_dim, NULL ); +} diff --git a/vg_magi.h b/vg_magi.h new file mode 100644 index 0000000..0290914 --- /dev/null +++ b/vg_magi.h @@ -0,0 +1,52 @@ +#pragma once +#define VG_MAGI_MAX_PANELS 8 + +#define VG_MAGI_RESIZEABLE 0x1 +#define VG_MAGI_MOVEABLE 0x2 +#define VG_MAGI_PERSISTENT 0x4 +#define VG_MAGI_ALL (VG_MAGI_RESIZEABLE|VG_MAGI_MOVEABLE|VG_MAGI_PERSISTENT) + +struct vg_magi +{ + struct vg_magi_panel + { + bool minimized; + const char *title; + ui_rect rect; + ui_px sh; + + u32 corner; /* which corner of screen relative to? TL, TR, BL, BR */ + + u32 flags; + void *data; + + char cmd[96]; /* used for persistence */ + ui_px min_w, min_h; + + void(*ui_cb)( ui_context *ctx, ui_rect rect, struct vg_magi_panel *magi ); + void(*close_cb)( struct vg_magi_panel *me ); + } + panels[ VG_MAGI_MAX_PANELS ]; + u32 panel_count; + + enum magi_mode + { + k_magi_mode_none, + k_magi_mode_drag, + k_magi_mode_resize + } + mode; + + bool drag_top, drag_left; + + ui_rect drag_original; + ui_px drag_start[2]; +} +extern _vg_magi; + +struct vg_magi_panel *_vg_magi_open( ui_px w, ui_px h, u32 flags ); +void _vg_magi_render( ui_context *ctx ); +void vg_magi_init(void); +void vg_magi_save(void); +void vg_magi_restore(void); +void _vg_magi_area_change( i32 d[2] ); diff --git a/vg_mem.c b/vg_mem.c index 8fa1057..0e07918 100644 --- a/vg_mem.c +++ b/vg_mem.c @@ -38,23 +38,33 @@ void *_vg_linear_alloc( void *buffer, u32 size, const char *constr_name ) size = vg_align8( size ); if( ((u64)buffer) % 8 ) - vg_fatal_error( "unaligned buffer (%p)", buffer ); + vg_fatal_error( "Tried allocating to an unaligned buffer (%p)", buffer ); vg_linear_allocator *alloc = vg_linear_header( buffer ); if( (alloc->cur + size) > alloc->size ) - { vg_fatal_error( "linear allocator overflow (%u + %u > %u)\n", - alloc->cur, size, alloc->size ); - } + alloc->cur, size, alloc->size ); if( alloc->flags & VG_MEMORY_SYSTEM ) + { if( (alloc->allocation_count + 1) > VG_MAX_ALLOCATIONS ) - vg_fatal_error( "Max linear allocations reached" ); + { + vg_fatal_condition(); + vg_info( "Linear allocators marked as 'SYSTEMS', have a hard limit of " + "%u allocations.\n\n" + "The limit was exceeded by the following request:\n", + VG_MAX_ALLOCATIONS ); + vg_info( " Name: %s\n", constr_name ); + vg_info( " Size: %u bytes\n", size ); + vg_fatal_exit(); + } + } void *data; - if( vg_mem.use_libc_malloc && (alloc->flags & VG_MEMORY_SYSTEM) ){ + if( vg_mem.use_libc_malloc && (alloc->flags & VG_MEMORY_SYSTEM) ) + { data = malloc( size ); vg_allocation_meta *meta = &alloc->alloc_table[ alloc->allocation_count ]; @@ -63,24 +73,21 @@ void *_vg_linear_alloc( void *buffer, u32 size, const char *constr_name ) meta->size = size; meta->name = constr_name; } - else{ + else data = buffer + alloc->cur; - } u8 *bytes = data; - for( u32 i=0; iallocation_count ++; alloc->last_alloc = data; alloc->last_alloc_size = size; alloc->cur += size; - if( ((u64)data) % 8 ){ - vg_fatal_error( "unaligned" ); - } - + if( ((u64)data) % 8 ) + vg_fatal_error( "Resultant allocation was unaligned, most likely memory " + "corruption or programmer error\n" ); return data; } @@ -94,27 +101,45 @@ void *vg_linear_resize( void *buffer, void *data, u32 newsize ) newsize = vg_align8( newsize ); if( alloc->last_alloc != data ) - vg_fatal_error( "This block has been fixed!" ); + vg_fatal_error( "Tried to resize allocation in linear allocator which " + "has allocated other things after this block.\n" ); if( (alloc->cur - alloc->last_alloc_size + newsize) > alloc->size ) - vg_fatal_error( "Cannot resize, overflow" ); + vg_fatal_error( "Tried to resize allocation to new size which would " + "overflow the allocator\n" ); alloc->cur -= alloc->last_alloc_size; alloc->cur += newsize; alloc->last_alloc_size = newsize; - if( vg_mem.use_libc_malloc && (alloc->flags & VG_MEMORY_SYSTEM) ){ + if( vg_mem.use_libc_malloc && (alloc->flags & VG_MEMORY_SYSTEM) ) + { data = realloc( data, newsize ); if( !data ) - vg_fatal_error( "realloc failed" ); + vg_fatal_error( "We are libc mode, and realloc failed" ); alloc->alloc_table[ alloc->allocation_count-1 ].data = data; + alloc->alloc_table[ alloc->allocation_count-1 ].size = newsize; alloc->last_alloc = data; return data; } - else{ - return data; + else return data; +} + +void vg_libc_del_recursive( void *buffer ) +{ + vg_linear_allocator *alloc = vg_linear_header( buffer ); + for( u32 i=0; iallocation_count; i ++ ) + { + vg_allocation_meta *meta = &alloc->alloc_table[i]; + if( meta->type == k_allocation_type_linear ) + vg_libc_del_recursive( meta->data ); + else + free( meta->data ); } + + free( alloc->alloc_table ); + free( alloc ); } /* its possible to delete just the last item */ @@ -122,17 +147,17 @@ void vg_linear_del( void *buffer, void *data ) { vg_linear_allocator *alloc = vg_linear_header( buffer ); - if( alloc->last_alloc != data ){ - vg_fatal_error( "This block has been fixed! Last alloc: %p, this: %p\n", - alloc->last_alloc, data ); - } + if( alloc->last_alloc != data ) + vg_fatal_error( "This block has been fixed and cannot be deleted.\n" + "Last alloc: %p, this: %p\n", alloc->last_alloc, data ); - if( vg_mem.use_libc_malloc && (alloc->flags & VG_MEMORY_SYSTEM) ){ + if( vg_mem.use_libc_malloc && (alloc->flags & VG_MEMORY_SYSTEM) ) + { vg_allocation_meta *meta = &alloc->alloc_table[alloc->allocation_count-1]; if( meta->type == k_allocation_type_linear ) - vg_fatal_error( "Cannot free a linear allocator in this conext" ); - - free( data ); + vg_libc_del_recursive( meta->data ); + else + free( data ); } alloc->allocation_count --; @@ -222,25 +247,27 @@ void vg_linear_clear( void *buffer ) void *_vg_create_linear_allocator( void *lin_alloc, u32 size, u16 flags, const char *constr_name) { - if( sizeof( vg_linear_allocator ) != 32 ) - vg_fatal_error( "Programming error" ); + VG_ASSERT( sizeof( vg_linear_allocator ) == 32 ); vg_linear_allocator *header; u32 block_size = size + sizeof(vg_linear_allocator); /* Creating it inside an existing one */ - if( lin_alloc ){ + if( lin_alloc ) + { vg_linear_allocator *alloc = vg_linear_header( lin_alloc ); if( alloc->cur + block_size > alloc->size ) - vg_fatal_error( "Out of memory" ); + vg_fatal_error( "Out of memory (%u + %u > %u)\n", + alloc->cur, block_size, alloc->size ); if( alloc->allocation_count + 1 > VG_MAX_ALLOCATIONS ) - vg_fatal_error( "Max allocations in linear allocator" ); + vg_fatal_error( "Exceeded max allocations in linear allocator (%u)\n", + VG_MAX_ALLOCATIONS ); if( (flags && VG_MEMORY_SYSTEM) && (alloc->flags & VG_MEMORY_REALTIME) ) - vg_fatal_error( "Cannot declare realtime allocator inside systems" - " allocator" ); + vg_fatal_error( "Cannot declare realtime allocator inside systems " + "allocator"); if( vg_mem.use_libc_malloc ){ vg_allocation_meta *meta = @@ -256,9 +283,7 @@ void *_vg_create_linear_allocator( void *lin_alloc, u32 size, meta->size = size; meta->name = constr_name; } - else{ - header = lin_alloc + alloc->cur; - } + else header = lin_alloc + alloc->cur; alloc->cur += block_size; alloc->last_alloc = header; @@ -279,12 +304,12 @@ void *_vg_create_linear_allocator( void *lin_alloc, u32 size, header->size = size; header->flags = flags; - if( vg_mem.use_libc_malloc && (flags & VG_MEMORY_SYSTEM) ){ + if( vg_mem.use_libc_malloc && (flags & VG_MEMORY_SYSTEM) ) + { u32 table_size = sizeof(vg_allocation_meta)*VG_MAX_ALLOCATIONS; header->alloc_table = malloc( table_size ); } - else - header->alloc_table = NULL; + else header->alloc_table = NULL; return header+1; } @@ -308,16 +333,29 @@ void vg_alloc_quota(void) "Scratch buffer" ); } +static void vg_mem_print_size( u32 bytes, char buf[32] ) +{ + if( bytes > 1024*1024 ) + snprintf( buf, 32, "%umb", bytes/(1024*1024) ); + else if( bytes > 1024 ) + snprintf( buf, 32, "%ukb", bytes/1024 ); + else + snprintf( buf, 32, "%ub", bytes ); +} + void vg_mem_log( void *lin_alloc, int depth, const char *name ) { if( vg_mem.use_libc_malloc ){ vg_linear_allocator *alloc = vg_linear_header( lin_alloc ); u32 s = alloc->size; - f32 p = ((float)alloc->cur / (float)alloc->size) * 100.0f; + f32 p = ((float)alloc->cur / (float)s) * 100.0f; for( int i=0; iflags & VG_MEMORY_SYSTEM ){ for( u32 i=0; iallocation_count; i++ ){ @@ -325,7 +363,8 @@ void vg_mem_log( void *lin_alloc, int depth, const char *name ) if( meta->type == k_allocation_type_block ){ for( int i=0; iname, meta->size ); + vg_mem_print_size( meta->size, asize ); + printf( "B(%s): %s\n", meta->name, asize ); } else{ vg_mem_log( meta->data, depth +1, meta->name ); diff --git a/vg_mem.h b/vg_mem.h index 6fde883..94042a9 100644 --- a/vg_mem.h +++ b/vg_mem.h @@ -1,6 +1,6 @@ #pragma once -#define VG_MAX_ALLOCATIONS 128 +#define VG_MAX_ALLOCATIONS 256 typedef struct vg_linear_allocator vg_linear_allocator; typedef struct vg_allocation_meta vg_allocation_meta; diff --git a/vg_mem_pool.c b/vg_mem_pool.c index 1fe3302..0a830a2 100644 --- a/vg_mem_pool.c +++ b/vg_mem_pool.c @@ -69,12 +69,11 @@ void vg_pool_watch( vg_pool *pool, u16 id ) { vg_pool_node *node = vg_pool_nodeptr( pool, id ); - if( !node->ref_count ){ + if( !node->ref_count ) vg_pool_unlink( pool, id ); - } if( node->ref_count == 0xffff ) - vg_fatal_error( "pool watch missmatch (limit is 128)\n" ); + vg_fatal_error( "Pool watch missmatch (limit is 128)\n" ); node->ref_count ++; } @@ -88,7 +87,8 @@ void vg_pool_unwatch( vg_pool *pool, u16 id ) vg_fatal_error( "pool unwatch missmatch (no watchers)\n" ); node->ref_count --; - if( !node->ref_count ){ + if( !node->ref_count ) + { vg_pool_node *head = vg_pool_nodeptr( pool, pool->head ), *tail = vg_pool_nodeptr( pool, pool->tail ); diff --git a/vg_mem_view.c b/vg_mem_view.c new file mode 100644 index 0000000..c36913e --- /dev/null +++ b/vg_mem_view.c @@ -0,0 +1,428 @@ +#include "vg_ui/imgui.h" + +int vg_mem_view = 0; + +void squarey_layout( ui_rect rect, f32 *areas, u32 area_count, + void (*cb)( u32, ui_rect, void* ), void *cb_user ) +{ + f32 area_total = 0.0f; + for( u32 i=0; iindices[idx]; + vg_allocation_meta *meta = &inf->alloc->alloc_table[idx]; + + u32 colour; + if( inf->root_colour ) colour = inf->root_colour; + else colour = ~0xff000000&((idx+5)*0x45d9f3b); + + if( meta->type == k_allocation_type_block ) + { + ui_fill( inf->ui_ctx, tmp, 0x80000000 | colour ); + ui_outline( inf->ui_ctx, tmp, -1, 0xff000000 | colour, 0 ); + } + else + vg_mem_view_ui( inf->ui_ctx, tmp, meta->data, meta->name, colour, NULL ); + + if( inf->result ) + { + if( ui_inside_rect( rect, inf->ui_ctx->mouse ) ) + { + inf->result->target = meta; + inf->result->colour = colour; + rect_copy( rect, inf->result->target_rect ); + } + } +} + +vg_linear_allocator *_CB_values; +int _CB_vg_mem_sort( const void *a, const void *b ) +{ + i32 ia = *((const i32 *)a), ib = *((const i32 *)b); + f32 fa = _CB_values->alloc_table[ ia ].size, + fb = _CB_values->alloc_table[ ib ].size; + return (fa < fb) - (fa > fb); +} + +static void vg_mem_view_ui( ui_context *ctx, ui_rect rect, + void *lin_alloc, const char *name, u32 root_colour, + struct vg_mem_view_result *result ) +{ + f32 sizes[ VG_MAX_ALLOCATIONS ]; + i32 indices[ VG_MAX_ALLOCATIONS ]; + + if( vg_mem.use_libc_malloc ) + { + vg_linear_allocator *alloc = vg_linear_header( lin_alloc ); + struct vg_mem_draw_inf inf = + { + .ui_ctx = ctx, + .alloc = alloc, + .indices = indices, + .result = result, + .root_colour = root_colour + }; + + if( alloc->allocation_count ) + { + if( alloc->cur < alloc->size ) + { + u32 short_side = rect[3] < rect[2]? 1: 0; + f32 p = 1.0f-((f32)alloc->cur / (f32)alloc->size); + ui_px h = (f32)rect[2+short_side^0x1] * p*p; + ui_rect out_box = {rect[0], rect[1]}; + out_box[ 2+short_side^0x1 ] = h; + out_box[ 2+short_side ] = rect[ 2+short_side ]; + + ui_fill( ctx, out_box, 0x80000000 ); + + char asize[32]; + vg_mem_print_size( alloc->size-alloc->cur, asize ); + ui_text( ctx, out_box, asize, 1, k_ui_align_middle_center, 0 ); + + rect[ short_side^0x1 ] += h; + rect[ 2+short_side^0x1 ] -= h; + } + + if( alloc->flags & VG_MEMORY_SYSTEM ) + { + for( i32 i=0; iallocation_count; i ++ ) + indices[i] = i; + + u32 count = alloc->allocation_count; + _CB_values = alloc; + qsort( indices, count, sizeof(i32), _CB_vg_mem_sort ); + + for( u32 i=0; iallocation_count; i++ ) + { + i32 indice = indices[i]; + + vg_allocation_meta *meta = &alloc->alloc_table[indice]; + sizes[i] = sqrtf(meta->size); + } + + squarey_layout( rect, sizes, count, vg_mem_draw_cb, &inf ); + } + else + { + //printf( " (UNTRACKED)\n" ); + } + } + else + { + ui_fill( ctx, rect, 0x80101010 ); + //ui_text( ctx, rect, name, 1, k_ui_align_left, 0 ); + } + } + else + { + //vg_error( "allocations are not tracked (turn on libc mode)\n" ); + } +} + +struct mem_view_data +{ + void *main_buffer[8], + *inspecting; + v4f inspect_colour; + + const char *walk[8]; + + u32 depth; + + f32 vis_data[256*256]; + GLuint tex; +}; + +static void cb_vg_mem_view( ui_context *ctx, ui_rect rect, + struct vg_magi_panel *magi ) +{ + struct mem_view_data *mv = magi->data; + struct vg_mem_view_result result = { .target=NULL }; + + ui_rect left; + ui_split( rect, k_ui_axis_v, 256+16, 2, left, rect ); + ui_fill( ctx, left, ui_opacity( ui_colour( ctx,k_ui_bg+1 ), 0.8f ) ); + + /* Tree back-tracker */ + { + ui_rect bib = { left[0],left[1],left[2],24 }; + u32 new_depth = mv->depth; + for( u32 i=0; idepth+1; i ++ ) + { + if( i != mv->depth ) + { + if( ui_button_text( ctx, bib, mv->walk[ i ], 1 ) == 1 ) + { + new_depth = i; + } + } + else + { + ui_fill( ctx, bib, ui_colour( ctx, k_ui_bg ) ); + ui_text( ctx, bib, mv->walk[i], 1, k_ui_align_middle_center, 0 ); + } + + bib[0] += 8; + bib[2] -= 8; + bib[1] += 24; + } + mv->depth = new_depth; + ui_px v = (mv->depth+1)*24; + left[1] += v; + left[3] -= v; + } + + if( vg_mem.use_libc_malloc == 0 ) + { + ui_text( ctx, rect, "Run with --use-libc-malloc to view memory", + 1, k_ui_align_middle_center, 0 ); + return; + } + + vg_mem_view_ui( ctx, rect, mv->main_buffer[ mv->depth ], "", 0, &result ); + + if( result.target ) + { + ui_outline( ctx, result.target_rect, 1, ui_colour( ctx,k_ui_bg+7 ), 0 ); + ui_info( ctx, left, result.target->name ); + + char buf[32]; + vg_mem_print_size( result.target->size, buf ); + ui_info( ctx, left, buf ); + + if( mv->inspecting != result.target->data ) + { + if( result.target->type == k_allocation_type_block ) + { + for( u32 i=0; i<256*256; i++ ) mv->vis_data[i] = 0.0f; + f32 max_freq = 0.0f; + + const u8 *src = result.target->data; + for( u32 i=0; isize-1; i ++ ) + { + u8 x = src[i], y = src[i+1]; + u32 offset = y*256 + x; + + mv->vis_data[ offset ] += 1.0f; + max_freq = vg_maxf( mv->vis_data[ offset ], max_freq ); + } + + f32 median = 0.0f; + for( u32 i=0; i<256*256; i++ ) median += mv->vis_data[i]; + median /= 256.0f*256.0f; + + f32 inv_max = 1.0f/logf(1.0f+median*2.0f); + for( u32 i=0; i<256*256; i++ ) + { + f32 v = logf(mv->vis_data[i]) * inv_max; + mv->vis_data[i] = v; + } + + glBindTexture( GL_TEXTURE_2D, mv->tex ); + glTexSubImage2D( GL_TEXTURE_2D, 0, 0, 0, 256, 256, + GL_RED, GL_FLOAT, mv->vis_data ); + } + + mv->inspecting = result.target->data; + + f32 nrm = 1.6f / 255.0f; + mv->inspect_colour[0] = (f32)((result.colour ) & 0xff) * nrm; + mv->inspect_colour[1] = (f32)((result.colour>>8 ) & 0xff) * nrm; + mv->inspect_colour[2] = (f32)((result.colour>>16) & 0xff) * nrm; + mv->inspect_colour[3] = 1.0f; + } + + if( result.target->type == k_allocation_type_block ) + ui_info( ctx, left, "Leaf" ); + else + ui_info( ctx, left, "Linear Allocator (expand)" ); + + if( result.target->type == k_allocation_type_block ) + { + ui_rect freq_box = { left[0]+8, left[1], 256,256 }; + + ui_flush( ctx, k_ui_shader_colour, NULL ); + ui_fill_rect( ctx, freq_box, 0xffffffff, (ui_px[4]){ 0,256,256,0 } ); + struct ui_batch_shader_data_image inf = { .resource = &mv->tex }; + v4_copy( mv->inspect_colour, vg_ui.colour ); + ui_flush( ctx, k_ui_shader_grad, &inf ); + v4_copy( (v4f){1,1,1,1}, vg_ui.colour ); + } + + if( result.target->type == k_allocation_type_linear ) + { + if( ui_click_down( ctx, UI_MOUSE_LEFT ) ) + { + mv->main_buffer[ mv->depth+1 ] = result.target->data; + mv->walk[ mv->depth+1 ] = result.target->name; + mv->inspecting = NULL; + mv->depth ++; + } + } + } +} + +static void cb_mem_view_close( struct vg_magi_panel *me ) +{ + struct mem_view_data *mv = me->data; + glDeleteTextures( 1, &mv->tex ); + free( me->data ); +} + +static struct +{ + const char *name; + void **buffer; +} +_vg_mem_named_buffers[] = +{ + { "rtmemory", &vg_mem.rtmemory }, + { "scratch", &vg_mem.scratch } +}; + +static int cmd_vg_mem_view( int argc, const char *argv[] ) +{ + if( argc == 1 ) + { + for( u32 i=0; ititle = "Memory outliner"; + + struct mem_view_data *mv = malloc(sizeof(struct mem_view_data)); + mv->main_buffer[0] = *_vg_mem_named_buffers[i].buffer; + mv->walk[0] = _vg_mem_named_buffers[i].name; + mv->depth = 0; + mv->inspecting = NULL; + + glGenTextures( 1, &mv->tex ); + glBindTexture( GL_TEXTURE_2D, mv->tex ); + glTexImage2D( GL_TEXTURE_2D, 0, GL_R32F, 256,256, 0, + GL_RED, GL_FLOAT, NULL ); + glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST ); + glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST ); + + magi->data = mv; + magi->ui_cb = cb_vg_mem_view; + magi->close_cb = cb_mem_view_close; + return 1; + } + } + + vg_error( "No named buffer '%s'\n", argv[0] ); + } + else + vg_error( "Usage: vg_mem_view \n" ); + + return 0; +} + +static void cmd_vg_mem_view_poll( int argc, const char *argv[] ) +{ + const char *term = argv[ argc-1 ]; + + if( argc == 1 ) + for( u32 i=0; i 16 ) vg_fatal_error( "Too large\n" ); + VG_ASSERT( count <= 16 ); return ((count-1)<<2); } @@ -472,7 +472,7 @@ void vg_msg_print( vg_msg *msg, u32 len ) if( base == k_vg_msg_unsigned ){ printf( -#ifdef _WIN32 +#ifdef VG_32 "%llu" #else "%lu" @@ -481,7 +481,7 @@ void vg_msg_print( vg_msg *msg, u32 len ) } else if( base == k_vg_msg_signed ){ printf( -#ifdef _WIN32 +#ifdef VG_32 "%lld" #else "%ld" diff --git a/vg_opengl.c b/vg_opengl.c new file mode 100644 index 0000000..8e4e55a --- /dev/null +++ b/vg_opengl.c @@ -0,0 +1,38 @@ +#include "vg/vg_opengl.h" + +static const char *gl_error_names[] = +{ + "GL_INVALID_ENUM", + "GL_INVALID_VALUE", + "GL_INVALID_OPERATION", + "GL_STACK_OVERFLOW", + "GL_STACK_UNDERFLOW", + "GL_OUT_OF_MEMORY", + "GL_INVALID_FRAMEBUFFER_OPERATION" +}; + +void vg_opengl_log_errors(void) +{ + u32 err_count = 0; + GLenum err; + while( (err = glGetError()) != GL_NO_ERROR ) + { + if( !err_count ) + vg_info( "OpenGL error buffer:\n" ); + + u32 err_i = err - 0x0500; + if( err_i < VG_ARRAY_LEN(gl_error_names) ) + { + vg_info( " %u %s\n", err, gl_error_names[ err_i ] ); + } + else + { + vg_info( "%u (unknown code)\n", err ); + } + + err_count ++; + } + + if( !err_count ) + vg_info( "OpenGL error buffer is empty.\n" ); +} diff --git a/vg_opengl.h b/vg_opengl.h new file mode 100644 index 0000000..a59c1f0 --- /dev/null +++ b/vg_opengl.h @@ -0,0 +1,3 @@ +#include "dep/glad/glad.h" + +void vg_opengl_log_errors(void); diff --git a/vg_opt.c b/vg_opt.c index ba0f9c4..80b3146 100644 --- a/vg_opt.c +++ b/vg_opt.c @@ -13,6 +13,7 @@ * short options | -a value * multi-set options | -ab value * + * long gnu flag | --long-opt * long gnu options | --long-value=test * standard agument | regular_thing */ @@ -23,6 +24,88 @@ static int vg_argc = 0; static int vg_consume_next = 0; static char **vg_argv; +enum vg_opt_type +{ + k_vg_opt_type_standard, + k_vg_opt_type_flag, + k_vg_opt_type_arg, + k_vg_opt_type_long_flag, + k_vg_opt_type_long_arg +}; + +struct _vg_opt_reg +{ + const char *alias, *desc; + char alias_c; + u8 type; +} +static _vg_all_opts[32]; +static u32 _vg_opt_count = 0; + +static void _vg_opt_observe( const char *alias, char alias_c, const char *desc, + enum vg_opt_type type ) +{ + for( u32 i=0; i<_vg_opt_count; i ++ ) + { + struct _vg_opt_reg *reg = &_vg_all_opts[i]; + if( (type == k_vg_opt_type_flag || type == k_vg_opt_type_arg) + && reg->alias_c == alias_c ) + { + return; + } + + if( (type == k_vg_opt_type_long_flag || type == k_vg_opt_type_long_arg) + && reg->alias == alias ) + { + return; + } + + if( (type == k_vg_opt_type_standard) + && (reg->alias == NULL && reg->alias_c == 0) ) + { + return; + } + } + + if( _vg_opt_count < VG_ARRAY_LEN( _vg_all_opts ) ) + { + struct _vg_opt_reg *reg = &_vg_all_opts[ _vg_opt_count ++ ]; + reg->alias = alias; + reg->alias_c = alias_c; + reg->desc = desc; + reg->type = type; + } +} + +void _vg_print_options(void) +{ + for( u32 i=0; i<_vg_opt_count; i ++ ) + { + struct _vg_opt_reg *reg = &_vg_all_opts[i]; + + if( reg->type == k_vg_opt_type_flag ) + printf( "-%c %s\n", reg->alias_c, reg->desc ); + + if( reg->type == k_vg_opt_type_arg ) + printf( "-%c %s\n", reg->alias_c, reg->desc ); + + if( reg->type == k_vg_opt_type_long_flag ) + printf( "--%-21s %s\n", reg->alias, reg->desc ); + + if( reg->type == k_vg_opt_type_long_arg ) + { + char temp[32]; + snprintf( temp, 32, "--%s=", reg->alias ); + printf( "%-23s %s\n", temp, reg->desc ); + } + + if( reg->type == k_vg_opt_type_standard ) + { + printf( " %s\n", reg->desc ); + } + } +} + /* Will return 0 if exhausted */ int vg_argp( int argc, char *argv[] ) { @@ -70,8 +153,10 @@ int vg_argp( int argc, char *argv[] ) } /* Example: see if -c is set */ -int vg_opt( char c ) +int vg_opt( char c, const char *desc ) { + _vg_opt_observe( NULL, c, desc, k_vg_opt_type_flag ); + char *carg = vg_argv[ vg_argi ]; if( carg[0] == '-' ) @@ -91,9 +176,11 @@ int vg_opt( char c ) } /* Example: get -c *value* */ -char *vg_opt_arg( char c ) +char *vg_opt_arg( char c, const char *desc ) { - if( vg_opt( c ) ) + _vg_opt_observe( NULL, c, desc, k_vg_opt_type_arg ); + + if( vg_opt( c, NULL ) ) { if( vg_argi < vg_argc-1 ) { @@ -112,8 +199,10 @@ char *vg_opt_arg( char c ) } /* Example see if --big is set */ -int vg_long_opt( char *name ) +int vg_long_opt( char *name, const char *desc ) { + _vg_opt_observe( name, 0, desc, k_vg_opt_type_long_flag ); + char *carg = vg_argv[ vg_argi ]; if( carg[0] == '-' ) @@ -132,8 +221,10 @@ int vg_long_opt( char *name ) } /* Example: get --big=value */ -char *vg_long_opt_arg( char *name ) +char *vg_long_opt_arg( char *name, const char *desc ) { + _vg_opt_observe( name, 0, desc, k_vg_opt_type_long_arg ); + char *carg = vg_argv[ vg_argi ]; if( carg[0] == '-' ) @@ -173,8 +264,10 @@ char *vg_long_opt_arg( char *name ) } /* Example: get regular_thing */ -char *vg_arg(void) +char *vg_arg( const char *desc ) { + _vg_opt_observe( NULL, 0, desc, k_vg_opt_type_standard ); + char *carg = vg_argv[ vg_argi ]; if( carg[0] != '-' ) diff --git a/vg_opt.h b/vg_opt.h index 6f2d879..0dac45c 100644 --- a/vg_opt.h +++ b/vg_opt.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021-2024 Mt.ZERO Software, Harry Godden - All Rights Reserved + * Copyright (C) 2021-2024 Mt.ZERO Software - All Rights Reserved */ #pragma once @@ -8,16 +8,19 @@ int vg_argp( int argc, char *argv[] ); /* Example: see if -c is set */ -int vg_opt( char c ); +int vg_opt( char c, const char *desc ); /* Example: get -c *value* */ -char *vg_opt_arg( char c ); +char *vg_opt_arg( char c, const char *desc ); /* Example see if --big is set */ -int vg_long_opt( char *name ); +int vg_long_opt( char *name, const char *desc ); /* Example: get --big=value */ -char *vg_long_opt_arg( char *name ); +char *vg_long_opt_arg( char *name, const char *desc ); /* Example: get regular_thing */ -char *vg_arg(void); +char *vg_arg( const char *desc ); + +/* You should probably bind this to --help (and call it last!) */ +void _vg_print_options(void); diff --git a/vg_platform.h b/vg_platform.h index b846b06..e9bf50b 100644 --- a/vg_platform.h +++ b/vg_platform.h @@ -28,15 +28,16 @@ typedef v3f m4x3f[4]; typedef v4f m4x4f[4]; typedef v3f boxf[2]; -/* anything compiled against VG shall implement this function somewhere. */ +/* anything compiled against VG shall implement vg_fatal_exit() somewhere. */ +void vg_fatal_condition(void); +void vg_fatal_exit(void); void vg_fatal_error( const char *fmt, ... ); #define VG_ASSERT( ITEM, ... ) \ - if( !( ITEM ) ) { \ - vg_fatal_error( "Assertion failed: " VG_LOG_MCSTR(ITEM) "\n" \ - VG_LOG_WHERE ); \ - } + if( !( ITEM ) ) { \ + vg_fatal_error( "Assertion failed: " VG_LOG_MCSTR(ITEM) "\n" VG_LOG_WHERE );\ + } #define VG_MIN( A, B ) ((A)<(B)?(A):(B)) #define VG_MAX( A, B ) ((A)>(B)?(A):(B)) -#define vg_list_size( A ) (sizeof(A)/sizeof(A[0])) +#define VG_ARRAY_LEN( A ) (sizeof(A)/sizeof(A[0])) diff --git a/vg_profiler.c b/vg_profiler.c index d203c92..a4f2cca 100644 --- a/vg_profiler.c +++ b/vg_profiler.c @@ -1,9 +1,9 @@ #include "vg_platform.h" #include "vg_profiler.h" #include "vg_engine.h" -#include "vg_imgui.h" +#include "vg_ui/imgui.h" -int vg_profiler = 0; +struct _vg_profiler _vg_profiler; void vg_profile_begin( struct vg_profile *profile ) { @@ -28,16 +28,16 @@ void vg_profile_end( struct vg_profile *profile ) u64 time_end = SDL_GetPerformanceCounter(), delta = time_end - profile->start; - if( profile->mode == k_profile_mode_frame ){ + if( profile->mode == k_profile_mode_frame ) + { profile->samples[ profile->buffer_current ] = delta; vg_profile_increment( profile ); } - else{ + else profile->samples[ profile->buffer_current ] += delta; - } } -void vg_profile_drawn( struct vg_profile **profiles, u32 count, +void vg_profile_drawn( ui_context *ctx, struct vg_profile **profiles, u32 count, f64 budget, ui_rect panel, int dir, i32 normalize ) { @@ -50,29 +50,33 @@ void vg_profile_drawn( struct vg_profile **profiles, u32 count, f64 sh = (f32)panel[3^dir] / (f32)VG_PROFILE_SAMPLE_COUNT, sw = (f32)panel[2^dir]; - ui_fill( panel, 0xa0000000 ); + ui_fill( ctx, panel, 0xa0000000 ); - if( count > 8 ) vg_fatal_error( "Too many profiles\n" ); + VG_ASSERT( count <= 8 ); f64 avgs[8]; u32 colours[8]; - for( u32 i=0; isamples[i] * rate_mul; } - for( int j=0; jsamples[i] * rate_mul, px = (total / budget) * sw, wx = (sample / budget) * sw; @@ -82,7 +86,7 @@ void vg_profile_drawn( struct vg_profile **profiles, u32 count, block[1^dir] = panel[1^dir] + (f32)i*sh; block[2^dir] = VG_MAX( 1, wx-1 ); block[3^dir] = ceilf(sh)-1; - ui_fill( block, colours[j] ); + ui_fill( ctx, block, colours[j] ); total += sample; avgs[j] += sample; @@ -92,25 +96,74 @@ void vg_profile_drawn( struct vg_profile **profiles, u32 count, char infbuf[64]; snprintf( infbuf, 64, "accuracy: %.7fms", rate_mul ); - ui_text( (ui_rect){ panel[0] + 4, - panel[1] + panel[3] - 14, 500, 30 }, + ui_text( ctx, + (ui_rect){ panel[0], panel[1], panel[2]-4, 24 }, infbuf, 1, - k_ui_align_left, 0 ); + k_ui_align_right, 0 ); - for( int i=0; iname ); - ui_text( (ui_rect){ panel[0] + 4, - panel[1] + panel[3] + 4 + i*14, - panel[2]-8, 14 }, - infbuf, 1, k_ui_align_left, 0 ); + ui_text( ctx, + (ui_rect){ panel[0], panel[1] + 24 + i*14, + panel[2]-4, 14 }, + infbuf, 1, k_ui_align_right, 0 ); } } +void _vg_profile_reg_set( struct vg_profile_set *set ) +{ + VG_ASSERT(_vg_profiler.named_count < VG_ARRAY_LEN(_vg_profiler.named_sets)); + _vg_profiler.named_sets[ _vg_profiler.named_count ++ ] = set; +} + +static void cb_vg_profiler( ui_context *ctx, ui_rect rect, + struct vg_magi_panel *magi ) +{ + struct vg_profile_set *ps = magi->data; + vg_profile_drawn( ctx, ps->list, ps->count, ps->get_budget(), rect, 0, 0 ); +} + +static int cmd_vg_profile( int argc, const char *argv[] ) +{ + if( argc == 1 ) + { + for( u32 i=0; i<_vg_profiler.named_count; i ++ ) + { + struct vg_profile_set *ps = _vg_profiler.named_sets[i]; + if( !strcmp( argv[0], ps->name ) ) + { + ui_px w = 256, h = VG_PROFILE_SAMPLE_COUNT * 2; + struct vg_magi_panel *magi = + _vg_magi_open( w,h, VG_MAGI_MOVEABLE|VG_MAGI_PERSISTENT ); + magi->title = "Profiler"; + magi->data = ps; + magi->ui_cb = cb_vg_profiler; + magi->close_cb = NULL; + return 1; + } + } + } + else + vg_error( "Usage: vg_profile \n" ); + + return 0; +} + +static void cmd_vg_profile_poll( int argc, const char *argv[] ) +{ + const char *term = argv[ argc-1 ]; + + if( argc == 1 ) + for( u32 i=0; i<_vg_profiler.named_count; i ++ ) + console_suggest_score_text( _vg_profiler.named_sets[i]->name,term,0 ); +} + void vg_profiler_init(void) { - VG_VAR_I32( vg_profiler, flags=VG_VAR_PERSISTENT ); + vg_console_reg_cmd( "vg_profile", cmd_vg_profile, cmd_vg_profile_poll ); } diff --git a/vg_profiler.h b/vg_profiler.h index 74190cb..d2a7f36 100644 --- a/vg_profiler.h +++ b/vg_profiler.h @@ -1,10 +1,8 @@ #pragma once #include "vg_platform.h" -#include "vg_imgui.h" +#include "vg_ui/imgui.h" #define VG_PROFILE_SAMPLE_COUNT 128 -extern int vg_profiler; - struct vg_profile { const char *name; @@ -22,10 +20,26 @@ struct vg_profile u64 start; }; +struct _vg_profiler +{ + struct vg_profile_set + { + const char *name; + u32 count; + f64 (*get_budget)(void); + struct vg_profile *list[]; + } + *named_sets[ 8 ]; + u32 named_count; +} +extern _vg_profiler; + void vg_profile_begin( struct vg_profile *profile ); void vg_profile_increment( struct vg_profile *profile ); void vg_profile_end( struct vg_profile *profile ); -void vg_profile_drawn( struct vg_profile **profiles, u32 count, +void vg_profile_drawn( ui_context *ctx, struct vg_profile **profiles, u32 count, f64 budget, ui_rect panel, int dir, i32 normalize ); void vg_profiler_init(void); + +void _vg_profile_reg_set( struct vg_profile_set *set ); diff --git a/vg_render.c b/vg_render.c new file mode 100644 index 0000000..a1fc499 --- /dev/null +++ b/vg_render.c @@ -0,0 +1,181 @@ +#include "vg_render.h" +#include "shaders/blit.h" +#include "shaders/blitblur.h" + +struct vg_postprocess vg_postprocess = +{ + .blur_effect = 1, + .blur_strength = 0.3f, +}; + +struct vg_render _vg_render = +{ + .scale = 1.0f +}; + +static void vg_async_postprocess_init( void *payload, u32 size ) +{ + f32 quad[] = + { + 0.00f,0.00f, 1.00f,1.00f, 0.00f,1.00f, + 0.00f,0.00f, 1.00f,0.00f, 1.00f,1.00f, + }; + + glGenVertexArrays( 1, &vg_postprocess.quad_vao ); + glGenBuffers( 1, &vg_postprocess.quad_vbo ); + glBindVertexArray( vg_postprocess.quad_vao ); + glBindBuffer( GL_ARRAY_BUFFER, vg_postprocess.quad_vbo ); + glBufferData( GL_ARRAY_BUFFER, sizeof(quad), quad, GL_STATIC_DRAW ); + glBindVertexArray( vg_postprocess.quad_vao ); + glVertexAttribPointer( 0, 2, GL_FLOAT, GL_FALSE, sizeof(f32)*2, (void*)0 ); + glEnableVertexAttribArray( 0 ); +} + +void vg_render_init(void) +{ + vg_async_call( vg_async_postprocess_init, NULL, 0 ); + + vg_console_reg_var( "render_scale", &_vg_render.scale, + k_var_dtype_f32, VG_VAR_PERSISTENT ); + +#ifdef VG_3D + vg_console_reg_var( "blur_strength", &vg_postprocess.blur_strength, + k_var_dtype_f32, 0 ); + vg_console_reg_var( "blur_effect", &vg_postprocess.blur_effect, + k_var_dtype_i32, VG_VAR_PERSISTENT ); + + void *alloc = vg_mem.rtmemory; + + /* + * Main framebuffer + */ + _vg_render.fb_main = vg_framebuffer_allocate( alloc, 3, 1 ); + _vg_render.fb_main->display_name = "main"; + _vg_render.fb_main->resolution_div = 1; + _vg_render.fb_main->attachments[0] = (vg_framebuffer_attachment) + { + "colour", k_framebuffer_attachment_type_texture, + + .internalformat = GL_RGB, + .format = GL_RGB, + .type = GL_UNSIGNED_BYTE, + .attachment = GL_COLOR_ATTACHMENT0 + }; + _vg_render.fb_main->attachments[1] = (vg_framebuffer_attachment) + { + "motion", k_framebuffer_attachment_type_texture, + + .quality = k_framebuffer_quality_high_only, + .internalformat = GL_RG16F, + .format = GL_RG, + .type = GL_FLOAT, + .attachment = GL_COLOR_ATTACHMENT1 + }; + _vg_render.fb_main->attachments[2] = (vg_framebuffer_attachment) + { + "depth_stencil", k_framebuffer_attachment_type_texture_depth, + .internalformat = GL_DEPTH24_STENCIL8, + .format = GL_DEPTH_STENCIL, + .type = GL_UNSIGNED_INT_24_8, + .attachment = GL_DEPTH_STENCIL_ATTACHMENT + }; + vg_framebuffer_create( _vg_render.fb_main ); + + /* + * Water reflection + */ + _vg_render.fb_water_reflection = vg_framebuffer_allocate( alloc, 2, 1 ); + _vg_render.fb_water_reflection->display_name = "water_reflection"; + _vg_render.fb_water_reflection->resolution_div = 2; + _vg_render.fb_water_reflection->attachments[0] = (vg_framebuffer_attachment) + { + "colour", k_framebuffer_attachment_type_texture, + .internalformat = GL_RGB, + .format = GL_RGB, + .type = GL_UNSIGNED_BYTE, + .attachment = GL_COLOR_ATTACHMENT0 + }; + _vg_render.fb_water_reflection->attachments[1] = (vg_framebuffer_attachment) + { + "depth_stencil", k_framebuffer_attachment_type_renderbuffer, + .internalformat = GL_DEPTH24_STENCIL8, + .attachment = GL_DEPTH_STENCIL_ATTACHMENT + }; + vg_framebuffer_create( _vg_render.fb_water_reflection ); + + /* + * Thid rendered view from the perspective of the camera, but just + * captures stuff thats under the water + */ + _vg_render.fb_water_beneath = vg_framebuffer_allocate( alloc, 2, 1 ); + _vg_render.fb_water_beneath->display_name = "water_beneath"; + _vg_render.fb_water_beneath->resolution_div = 2; + _vg_render.fb_water_beneath->attachments[0] = (vg_framebuffer_attachment) + { + "colour", k_framebuffer_attachment_type_texture, + .internalformat = GL_RED, + .format = GL_RED, + .type = GL_UNSIGNED_BYTE, + .attachment = GL_COLOR_ATTACHMENT0 + }; + _vg_render.fb_water_beneath->attachments[1] = (vg_framebuffer_attachment) + { + "depth_stencil", k_framebuffer_attachment_type_renderbuffer, + .internalformat = GL_DEPTH24_STENCIL8, + .attachment = GL_DEPTH_STENCIL_ATTACHMENT + }; + vg_framebuffer_create( _vg_render.fb_water_beneath ); +#endif +} + +void vg_render_fullscreen_quad(void) +{ + glBindVertexArray( vg_postprocess.quad_vao ); + glDrawArrays( GL_TRIANGLES, 0, 6 ); +} + +/* + * Utility + */ + +void vg_postprocess_to_screen( vg_framebuffer *fb ) +{ + glBindFramebuffer( GL_FRAMEBUFFER, 0 ); + glViewport( 0,0, vg.window_x, vg.window_y ); + + glDisable(GL_BLEND); + glDisable(GL_DEPTH_TEST); + + v2f inverse; + vg_framebuffer_inverse_ratio( fb, inverse ); + +#ifdef VG_3D + if( vg_postprocess.blur_effect ) + { + shader_blitblur_use(); + shader_blitblur_uTexMain( 0 ); + shader_blitblur_uTexMotion( 1 ); + + f32 s = vg.time_frame_delta*60.0; + shader_blitblur_uBlurStrength( vg_postprocess.blur_strength / s ); + shader_blitblur_uInverseRatio( inverse ); + + inverse[0] -= 0.0001f; + inverse[1] -= 0.0001f; + shader_blitblur_uClampUv( inverse ); + shader_blitblur_uOverrideDir( vg_postprocess.motion_blur_override ); + + vg_framebuffer_bind_texture( fb, 0, 0 ); + vg_framebuffer_bind_texture( fb, 1, 1 ); + } + else +#endif + { + shader_blit_use(); + shader_blit_uTexMain( 0 ); + shader_blit_uInverseRatio( inverse ); + vg_framebuffer_bind_texture( fb, 0, 0 ); + } + + vg_render_fullscreen_quad(); +} diff --git a/vg_render.h b/vg_render.h new file mode 100644 index 0000000..9e9db99 --- /dev/null +++ b/vg_render.h @@ -0,0 +1,32 @@ +#pragma once +#include "vg_engine.h" +#include "vg_framebuffer.h" + +struct vg_postprocess +{ + GLuint quad_vao, + quad_vbo; + +#ifdef VG_3D + i32 blur_effect; + f32 blur_strength; + v2f motion_blur_override; +#endif +} +extern vg_postprocess; + +struct vg_render +{ + f32 scale; + +#ifdef VG_3D + vg_framebuffer *fb_main, + *fb_water_reflection, + *fb_water_beneath; +#endif +} +extern _vg_render; + +void vg_render_init(void); +void vg_render_fullscreen_quad(void); +void vg_postprocess_to_screen( vg_framebuffer *fb ); diff --git a/vg_rigidbody.c b/vg_rigidbody.c index bcfebc5..322f42a 100644 --- a/vg_rigidbody.c +++ b/vg_rigidbody.c @@ -89,11 +89,11 @@ void rb_extrapolate( rigidbody *rb, v3f co, v4f q ) void rb_iter( rigidbody *rb ) { - if( !vg_validf( rb->v[0] ) || - !vg_validf( rb->v[1] ) || - !vg_validf( rb->v[2] ) ) + if( !vg_validf(rb->v[0]) || !vg_validf(rb->v[1]) || !vg_validf(rb->v[2]) ) { - vg_fatal_error( "NaN velocity" ); + vg_fatal_error( + "Aborting the program because velocity has invalid value in one " + "or more components: %f %f %f\n", rb->v[0],rb->v[1],rb->v[2] ); } v3f gravity = { 0.0f, -9.8f, 0.0f }; diff --git a/vg_rigidbody_collision.c b/vg_rigidbody_collision.c index f70a39b..2989a46 100644 --- a/vg_rigidbody_collision.c +++ b/vg_rigidbody_collision.c @@ -262,7 +262,6 @@ int rb_capsule__box( m4x3f mtxA, rb_capsule *ca, v3_sub( c1, p1, d1 ); v3_sub( p1, p0, da ); - /* TODO: ? */ v3_normalize(d0); v3_normalize(d1); v3_normalize(da); @@ -496,7 +495,7 @@ int rb_capsule__triangle( m4x3f mtxA, rb_capsule *c, v3f tri[3], rb_ct *buf ) int rb_global_has_space( void ) { - if( rb_contact_count + 16 > vg_list_size(rb_contact_buffer) ) + if( rb_contact_count + 16 > VG_ARRAY_LEN(rb_contact_buffer) ) return 0; return 1; diff --git a/vg_rigidbody_view.c b/vg_rigidbody_view.c index 962148f..f16f3c6 100644 --- a/vg_rigidbody_view.c +++ b/vg_rigidbody_view.c @@ -134,8 +134,6 @@ static void async_vg_rb_view_init( void *payload, u32 payload_size ) glVertexAttribPointer( 1, 3, GL_FLOAT, GL_FALSE, stride, (void *)offsetof(rb_view_vert, n) ); glEnableVertexAttribArray( 1 ); - - VG_CHECK_GL_ERR(); } void vg_rb_view_init(void) diff --git a/vg_shader.c b/vg_shader.c index c12495c..ace5c17 100644 --- a/vg_shader.c +++ b/vg_shader.c @@ -14,15 +14,28 @@ const char *vg_shader_gl_ver = "#version 330 core\n"; -struct vg_shaders vg_shaders; - -static GLuint vg_shader_subshader( const char *src, GLint gliShaderType ) +struct vg_shaders +{ + vg_shader *shaders[48]; + u32 count; +} +static vg_shaders; + +/* + * Compile OpenGL subshader from GLSL source. Type is subshader type. + * If critical is set to 1, the program will fatal exit on compile failure. + */ +static GLuint vg_compile_opengl_subshader( GLint type, + const char *src, bool critical ) { - GLint shader = glCreateShader( gliShaderType ); + GLuint shader = glCreateShader( type ); - if( shader == GL_NONE ) + if( shader == 0 ) { - vg_error( "Could not 'glCreateShader()'\n" ); + vg_fatal_condition(); + vg_info( "glCreateShader returned 0.\n" ); + vg_opengl_log_errors(); + vg_fatal_exit(); return 0; } @@ -32,120 +45,147 @@ static GLuint vg_shader_subshader( const char *src, GLint gliShaderType ) GLint status; glGetShaderiv( shader, GL_COMPILE_STATUS, &status ); - if( status != GL_TRUE ) - { + if( status == GL_TRUE ) + { + return shader; + } + else + { + if( critical ) vg_fatal_condition(); + GLchar info[1024]; GLsizei len; + glGetShaderInfoLog( shader, sizeof(info), &len, info ); - glGetShaderInfoLog( shader, sizeof(info), &len, info ); - vg_error( "Error info:\n%s\n", info ); - return 0; - } + const char *type_str = "?"; + + if( type == GL_VERTEX_SHADER ) type_str = "GL_VERTEX_SHADER"; + else if( type == GL_FRAGMENT_SHADER ) type_str = "GL_FRAGMENT_SHADER"; + + vg_info( "%s subshader compile error:\n\n%s\n", type_str, info ); - return shader; + if( critical ) vg_fatal_exit(); + return 0; + } } -int vg_shader_compile( struct vg_shader *shader ) +/* + * Final compilation by linking, if critical is 1, a fatal exit will occur on + * link failure + */ +static int vg_link_opengl_program( GLuint program, bool critical ) { - GLuint program, vert, frag; - - /* If we are compiling this again, we obviously need to try to take the src - * from the disk instead. - * - * Only do this if we have filenames set on the shader, so engine shaders - * dont have to do it (text.. etc). - */ - - int use_source_files = 0; - if( shader->compiled ){ - if( shader->vs.orig_file && shader->fs.orig_file ){ - use_source_files = 1; - } - else { - vg_warn( "No source files for shader '%s'\n", shader->name ); - return 1; - } - } + glLinkProgram( program ); + + GLint success; + glGetProgramiv( program, GL_LINK_STATUS, &success ); - vg_info( "Compile shader '%s'\n", shader->name ); - - if( use_source_files ){ - char error[260]; - char path[260]; - - strcpy( path, "../../" ); - strcat( path, shader->vs.orig_file ); - char *vertex_src = stb_include_file( path, "", "../../shaders", error ); - - strcpy( path, "../../" ); - strcat( path, shader->fs.orig_file ); - char *fragment_src = stb_include_file( path, "", "../../shaders", error ); - - if( !vertex_src || !fragment_src ){ - const char *errstr = "Could not find shader source files (%s)\n"; - if( shader->compiled ){ - vg_warn( errstr, shader->vs.orig_file ); - free( vertex_src ); - free( fragment_src ); - return 1; - } - else{ - vg_error( errstr, shader->vs.orig_file ); - free( vertex_src ); - free( fragment_src ); - return 0; - } - } - - vert = vg_shader_subshader( vertex_src, GL_VERTEX_SHADER ); - frag = vg_shader_subshader( fragment_src, GL_FRAGMENT_SHADER ); - - free( vertex_src ); - free( fragment_src ); - } - else{ - vert = vg_shader_subshader( shader->vs.static_src, GL_VERTEX_SHADER ); - frag = vg_shader_subshader( shader->fs.static_src, GL_FRAGMENT_SHADER ); - } - - if( !vert || !frag ) + if( success ) return 1; + else + { + if( critical ) vg_fatal_condition(); + + char info[ 512 ]; + glGetProgramInfoLog( program, sizeof(info), NULL, info ); + vg_info( "Shader program link error:\n\n%s\n", info ); + + if( critical ) vg_fatal_exit(); return 0; - - program = glCreateProgram(); + } +} + +/* + * Compile vg_shader from static source code. Will fatal exit if there is a + * compile error + */ +void vg_compile_shader( struct vg_shader *shader ) +{ + VG_ASSERT( shader->compiled == 0 ); + + const char *vs = shader->vs.static_src, + *fs = shader->fs.static_src; + + GLuint vert = vg_compile_opengl_subshader( GL_VERTEX_SHADER, vs, 1 ), + frag = vg_compile_opengl_subshader( GL_FRAGMENT_SHADER, fs, 1 ), + program = glCreateProgram(); glAttachShader( program, vert ); glAttachShader( program, frag ); - glLinkProgram( program ); + + vg_link_opengl_program( program, 1 ); glDeleteShader( vert ); glDeleteShader( frag ); - - /* Check for link errors */ - char infoLog[ 512 ]; - int success_link = 1; - - glGetProgramiv( program, GL_LINK_STATUS, &success_link ); - if( !success_link ) - { - glGetProgramInfoLog( program, 512, NULL, infoLog ); - vg_error( "Link failed: %s\n", infoLog ); - glDeleteProgram( program ); - return 0; - } - - if( shader->compiled ) - glDeleteProgram( shader->id ); - + shader->id = program; - shader->compiled = 1; - return 1; + shader->compiled = 1; +} + +/* + * Recompile vg_shader from its original source files. This won't work in the + * shipped version of the engine. + */ +void vg_recompile_shader( struct vg_shader *shader ) +{ + VG_ASSERT( shader->compiled == 1 ); + + char error[260]; + char path[260]; + + strcpy( path, "../../" ); + strcat( path, shader->vs.orig_file ); + char *vs = stb_include_file( path, "", "../../shaders", error ); + + strcpy( path, "../../" ); + strcat( path, shader->fs.orig_file ); + char *fs = stb_include_file( path, "", "../../shaders", error ); + + if( !vs || !fs ) + { + vg_warn( "Could not recompile shader due to missing source files:\n" ); + + if( !vs ) vg_info( " Vertex: %s\n", shader->vs.orig_file ); + if( !fs ) vg_info( " Fragment: %s\n", shader->fs.orig_file ); + free( vs ); + free( fs ); + return; + } + + GLuint vert = vg_compile_opengl_subshader( GL_VERTEX_SHADER, vs, 0 ), + frag = vg_compile_opengl_subshader( GL_FRAGMENT_SHADER, fs, 0 ); + + free( vs ); + free( fs ); + + if( !vert || !frag ) return; + + GLuint program = glCreateProgram(); + + glAttachShader( program, vert ); + glAttachShader( program, frag ); + + if( vg_link_opengl_program( program, 0 ) ) + { + /* replace existing */ + glDeleteProgram( shader->id ); + shader->id = program; + } + else + { + /* womp womp */ + glDeleteProgram( program ); + } + + glDeleteShader( vert ); + glDeleteShader( frag ); } -static void vg_free_shader( struct vg_shader *shader ) +void vg_free_shader( struct vg_shader *shader ) { if( shader->compiled ) { glDeleteProgram( shader->id ); + shader->id = 0; shader->compiled = 0; } } @@ -158,25 +198,21 @@ void vg_shaders_compile(void) { vg_info( "Compiling shaders\n" ); - for( int i=0; icompiled = 0; - shader->id = 0; /* TODO: make this an error shader */ + shader->id = 0; /* TODO: make this an error shader */ vg_shaders.shaders[ vg_shaders.count ++ ] = shader; } diff --git a/vg_shader.h b/vg_shader.h index 5e2ad35..9fff67d 100644 --- a/vg_shader.h +++ b/vg_shader.h @@ -1,28 +1,23 @@ #pragma once typedef struct vg_shader vg_shader; - -struct vg_shaders +struct vg_shader { - struct vg_shader - { - GLuint id; - const char *name; + GLuint id; + const char *name; - struct vg_subshader - { - const char *orig_file, - *static_src; - } - vs, fs; - int compiled; + struct vg_subshader + { + const char *orig_file, + *static_src; } - * shaders[48]; - u32 count; -} -extern vg_shaders; + vs, fs; + int compiled; +}; void vg_shaders_compile(void); int vg_shaders_live_recompile(int argc, const char *argv[]); void vg_shader_register( struct vg_shader *shader ); -int vg_shader_compile( struct vg_shader *shader ); +void vg_compile_shader( struct vg_shader *shader ); +void vg_recompile_shader( struct vg_shader *shader ); +void vg_free_shader( struct vg_shader *shader ); diff --git a/vg_steam.c b/vg_steam.c index a021cb1..3c51338 100644 --- a/vg_steam.c +++ b/vg_steam.c @@ -6,7 +6,8 @@ struct vg_steam vg_steam; vg_steam_async_call *vg_alloc_async_steam_api_call(void) { - if( vg_steam.call_count == vg_list_size(vg_steam.calls) ){ + if( vg_steam.call_count == VG_ARRAY_LEN(vg_steam.calls) ) + { vg_fatal_error( "Maximum concurrent API calls exceeded (%u)\n", vg_steam.call_count ); } @@ -17,7 +18,7 @@ vg_steam_async_call *vg_alloc_async_steam_api_call(void) void steam_register_callback( u32 id, void (*p_handler)( CallbackMsg_t *msg ) ) { if( vg_steam.callback_handler_count == - vg_list_size(vg_steam.callback_handlers) ) + VG_ARRAY_LEN(vg_steam.callback_handlers) ) { vg_fatal_error( "Too many steam callback handlers registered (%u)\n", vg_steam.callback_handler_count ); diff --git a/vg_string.c b/vg_string.c index 1261f5b..34b98f7 100644 --- a/vg_string.c +++ b/vg_string.c @@ -6,8 +6,10 @@ i32 vg_str_storage( vg_str *str ) { - if( str->len == -1 ){ - if( str->buffer ){ + if( str->len == 0 ) + { + if( str->buffer ) + { vg_str_dynamic *arr = (vg_str_dynamic *)str->buffer; return (arr-1)->len; } @@ -17,26 +19,27 @@ i32 vg_str_storage( vg_str *str ) } /* - * Reset string. If len is -1 (dynamically allocated), buffer must be either + * Reset string. If len is 0 (dynamically allocated), buffer must be either * NULL or be acquired from malloc or realloc */ void vg_strnull( vg_str *str, char *buffer, i32 len ) { + VG_ASSERT( len >= 0 ); + str->buffer = buffer; if( buffer ) str->buffer[0] = '\0'; str->i = 0; str->len = len; - - if( len == 0 ) - vg_fatal_error( "0 length string allocation\n" ); } void vg_strfree( vg_str *str ) { - if( str->len == -1 ){ - if( str->buffer ){ + if( str->len == 0 ) + { + if( str->buffer ) + { vg_str_dynamic *arr = (vg_str_dynamic *)str->buffer; free( arr-1 ); @@ -52,7 +55,8 @@ void vg_strfree( vg_str *str ) */ static i32 vg_str_dynamic_grow( vg_str *str ) { - if( str->buffer ){ + if( str->buffer ) + { vg_str_dynamic *hdr = ((vg_str_dynamic *)str->buffer) - 1; i32 total = (hdr->len + sizeof(vg_str_dynamic)) * 2; hdr = realloc( hdr, total ); @@ -60,7 +64,8 @@ static i32 vg_str_dynamic_grow( vg_str *str ) str->buffer = (char *)(hdr+1); return hdr->len; } - else { + else + { vg_str_dynamic *hdr = malloc(16); hdr->len = 16-sizeof(vg_str_dynamic); str->buffer = (char *)(hdr+1); @@ -69,37 +74,50 @@ static i32 vg_str_dynamic_grow( vg_str *str ) } } -void vg_strcat( vg_str *str, const char *append ) +static bool _vg_strcatch( vg_str *str, char c ) { - if( !append || (str->i == -1) ) return; - - i32 max = vg_str_storage( str ), - i = 0; + if( str->i == -1 ) return 0; -append: - if( str->i == max ){ - if( str->len == -1 ) + i32 max = vg_str_storage( str ); + if( str->i == max ) + { + if( str->len == 0 ) max = vg_str_dynamic_grow( str ); - else{ + else + { str->i = -1; str->buffer[ max-1 ] = '\0'; - return; + return 0; } } - char c = append[ i ++ ]; - str->buffer[ str->i ] = c; + str->buffer[ str->i ++ ] = c; + return 1; +} + +void vg_strcat( vg_str *str, const char *append ) +{ + if( !append || (str->i == -1) ) return; + + i32 i = 0; + +append:; + char c = append[ i ++ ]; + if( !_vg_strcatch( str, c ) ) return; if( c == '\0' ) + { + str->i --; return; - - str->i ++; - goto append; + } + else goto append; } void vg_strcatch( vg_str *str, char c ) { - vg_strcat( str, (char[]){ c, '\0' } ); + _vg_strcatch( str, c ); + _vg_strcatch( str, '\x00' ); + str->i --; } /* @@ -107,10 +125,12 @@ void vg_strcatch( vg_str *str, char c ) */ void vg_strcati32( vg_str *str, i32 value ) { - if( value ){ + if( value ) + { char temp[32]; int i=0; - while( value && (i<31) ){ + while( value && (i<31) ) + { temp[ i ++ ] = '0' + (value % 10); value /= 10; } @@ -130,7 +150,8 @@ void vg_strcati32r( vg_str *str, i32 value, i32 n, char alt ) { char temp[32]; i32 i=0; - while( value ){ + while( value ) + { if( i>=n ) break; @@ -168,18 +189,25 @@ char *vg_strch( vg_str *str, char c ) u32 vg_strncpy( const char *src, char *dst, u32 len, enum strncpy_behaviour behaviour ) { - for( u32 i=0; i + +void ui_init( ui_context *ctx, + ui_vert *verts_buf, u32 verts_max, + u16 *indices_buf, u32 indices_max ) +{ + ctx->vertex_buffer = verts_buf; + ctx->max_verts = verts_max; + ctx->cur_vert = 0; + ctx->vert_start = 0; + ctx->indice_buffer = indices_buf; + ctx->max_indices = indices_max; + ctx->cur_indice = 0; + ctx->indice_start = 0; + + if( !verts_buf || !indices_buf ) + exit(0); +} + +ui_vert *ui_fill_rect( ui_context *ctx, ui_rect rect, u32 colour, ui_px uv[4] ) +{ + /* this if far from ideal but stops us from crashing */ + if( (ctx->cur_vert + 4 > ctx->max_verts) || + (ctx->cur_indice + 6 > ctx->max_indices)) + { + return &ctx->vertex_buffer[0]; + } + + ui_vert *vertices = &ctx->vertex_buffer[ ctx->cur_vert ]; + u16 *indices = &ctx->indice_buffer[ ctx->cur_indice ]; + + for( int i=0; i<4; i++ ) + vertices[i].colour = colour; + + vertices[0].co[0] = rect[0]; + vertices[0].co[1] = rect[1]; + vertices[0].uv[0] = uv[0]; + vertices[0].uv[1] = uv[1]; + vertices[1].co[0] = rect[0]+rect[2]; + vertices[1].co[1] = rect[1]; + vertices[1].uv[0] = uv[2]; + vertices[1].uv[1] = uv[1]; + vertices[2].co[0] = rect[0]+rect[2]; + vertices[2].co[1] = rect[1]+rect[3]; + vertices[2].uv[0] = uv[2]; + vertices[2].uv[1] = uv[3]; + vertices[3].co[0] = rect[0]; + vertices[3].co[1] = rect[1]+rect[3]; + vertices[3].uv[0] = uv[0]; + vertices[3].uv[1] = uv[3]; + + u16 start = ctx->cur_vert; + u32 mesh[] = { 0,2,1, 0,3,2 }; + + for( u32 i=0; icur_indice += 6; + ctx->cur_vert += 4; + + return vertices; +} + +ui_vert *ui_fill( ui_context *ctx, ui_rect rect, u32 colour ) +{ + return ui_fill_rect( ctx, rect, colour, (ui_px[4]){ 4,4,4,4 } ); +} + +void ui_outline( ui_context *ctx, + ui_rect rect, ui_px thickness, u32 colour, u32 mask ) +{ + /* this if far from ideal but stops us from crashing */ + if( (ctx->cur_vert + 8 > ctx->max_verts) || + (ctx->cur_indice + 24 > ctx->max_indices)) + return; + + ui_vert *vertices = &ctx->vertex_buffer[ ctx->cur_vert ]; + u16 *indices = &ctx->indice_buffer[ ctx->cur_indice ]; + + for( int i=0; i<8; i++ ) + { + vertices[i].uv[0] = 4; + vertices[i].uv[1] = 4; + vertices[i].colour = colour; + } + + vertices[0].co[0] = rect[0]; + vertices[0].co[1] = rect[1]; + vertices[1].co[0] = rect[0]+rect[2]; + vertices[1].co[1] = rect[1]; + vertices[2].co[0] = rect[0]+rect[2]; + vertices[2].co[1] = rect[1]+rect[3]; + vertices[3].co[0] = rect[0]; + vertices[3].co[1] = rect[1]+rect[3]; + vertices[4].co[0] = vertices[0].co[0]-thickness; + vertices[4].co[1] = vertices[0].co[1]-thickness; + vertices[5].co[0] = vertices[1].co[0]+thickness; + vertices[5].co[1] = vertices[1].co[1]-thickness; + vertices[6].co[0] = vertices[2].co[0]+thickness; + vertices[6].co[1] = vertices[2].co[1]+thickness; + vertices[7].co[0] = vertices[3].co[0]-thickness; + vertices[7].co[1] = vertices[3].co[1]+thickness; + + u16 start = ctx->cur_vert; + u32 mesh[] = { 0,5,4, 0,1,5, 1,6,5, 1,2,6, 2,7,6, 2,3,7, 3,4,7, 3,0,4 }; + + if( !mask ) + mask = UI_TOP|UI_LEFT|UI_BOTTOM|UI_RIGHT; + + u32 c = 0; + for( u32 i=0; icur_indice += c; + ctx->cur_vert += 8; +} + +void rect_copy( ui_rect a, ui_rect b ) +{ + for( int i=0; i<4; i++ ) + b[i] = a[i]; +} + +void ui_split( ui_rect rect, enum ui_axis other, ui_px width, ui_px gap, + ui_rect l, ui_rect r ) +{ + enum ui_axis dir = other ^ 0x1; + + if( width < 0 ) width = rect[ 2+dir ] + width; + + ui_rect temp; + rect_copy( rect, temp ); + + l[ dir ] = temp[ dir ]; + r[ dir ] = temp[ dir ] + width + (gap/2); + l[ other ] = temp[ other ]; + r[ other ] = temp[ other ]; + l[ 2+dir ] = width - (gap/2); + r[ 2+dir ] = temp[ 2+dir ] - width - (gap/2); + l[ 2+other ] = temp[ 2+other ]; + r[ 2+other ] = temp[ 2+other ]; +} + +void ui_split_ratio( ui_rect rect, enum ui_axis dir, float ratio, + ui_px gap, ui_rect l, ui_rect r ) +{ + ui_px width = (float)rect[ 2+(dir^0x1) ] * ratio; + ui_split( rect, dir, width, gap, l, r ); +} + +void ui_rect_pad( ui_rect rect, ui_px pad[2] ) +{ + rect[0] += pad[0]; + rect[1] += pad[1]; + rect[2] -= pad[0]*2; + rect[3] -= pad[1]*2; +} + +static ui_px ui_min( ui_px a, ui_px b ){ return ab?a:b; } +static ui_px ui_clamp( ui_px a, ui_px min, ui_px max ) +{ + return ui_min( max, ui_max( a, min ) ); +} + +int ui_clip( ui_rect parent, ui_rect child, ui_rect clipped ) +{ + ui_px parent_max[2], child_max[2]; + parent_max[0] = parent[0]+parent[2]; + parent_max[1] = parent[1]+parent[3]; + child_max[0] = child[0]+child[2]; + child_max[1] = child[1]+child[3]; + + clipped[0] = ui_clamp( child[0], parent[0], parent_max[0] ); + clipped[1] = ui_clamp( child[1], parent[1], parent_max[1] ); + clipped[2] = ui_clamp( child_max[0], parent[0], parent_max[0] ); + clipped[3] = ui_clamp( child_max[1], parent[1], parent_max[1] ); + + if( clipped[0] == clipped[2] || + clipped[1] == clipped[3] ) + return 0; + + clipped[2] -= clipped[0]; + clipped[3] -= clipped[1]; + + return 1; +} + +int ui_inside_rect( ui_rect rect, ui_px co[2] ) +{ + if( co[0] >= rect[0] && + co[1] >= rect[1] && + co[0] < rect[0]+rect[2] && + co[1] < rect[1]+rect[3] ){ + return 1; + } + else + return 0; +} + +int ui_click_down( ui_context *ctx, u32 mask ) +{ + if( ctx->ignore_input_frames ) return 0; + if( (ctx->mouse_state[0] & mask) && + !(ctx->mouse_state[1] & mask) ) + return 1; + else + return 0; +} + +int ui_clicking( ui_context *ctx, u32 mask ) +{ + if( ctx->ignore_input_frames ) return 0; + return ctx->mouse_state[0] & mask; +} + +int ui_click_up( ui_context *ctx, u32 mask ) +{ + if( ctx->ignore_input_frames ) return 0; + if( (ctx->mouse_state[1] & mask) && + !(ctx->mouse_state[0] & mask) ) + return 1; + else + return 0; +} + +void ui_update_mouse( ui_context *ctx, ui_px mouse[2], i32 mouse_state ) +{ + ctx->mouse_state[1] = ctx->mouse_state[0]; + ctx->mouse_state[0] = mouse_state; + ctx->mouse_delta[0] = mouse[0]-ctx->mouse_click[0]; + ctx->mouse_delta[1] = mouse[1]-ctx->mouse_click[1]; + + if( !ctx->mouse_pos_overriden ) + { + ctx->mouse[0] = mouse[0]; + ctx->mouse[1] = mouse[1]; + } + + if( ctx->ignore_input_frames ) + { + ctx->ignore_input_frames --; + return; + } + + if( ui_click_down(ctx, UI_MOUSE_ANY) ) + { + ctx->mouse_click[0] = ctx->mouse[0]; + ctx->mouse_click[1] = ctx->mouse[1]; + } +} + +void ui_prerender( ui_context *ctx ) +{ + ctx->cur_vert = 0; + ctx->cur_indice = 0; + ctx->vert_start = 0; + ctx->indice_start = 0; + ctx->focused_control_hit = 0; + ctx->cursor = k_ui_cursor_default; + ctx->wants_mouse = 0; + ctx->mouse_pos_overriden = 0; +} + +void ui_set_area( ui_context *ctx, i32 width, i32 height ) +{ + ctx->area[0] = width; + ctx->area[1] = height; +} + +void ui_ignore_input_frames( ui_context *ctx, u32 frames ) +{ + ctx->ignore_input_frames = frames; +} + +void ui_capture_mouse( ui_context *ctx, bool on ) +{ + ctx->wants_mouse = on; +} + +void ui_flush( ui_context *ctx, enum ui_shader shader, void *shader_data ) +{ + ui_batch batch; + batch.vert_offset = ctx->vert_start * sizeof(ui_vert); + batch.indice_offset = ctx->indice_start * sizeof(u16); + batch.vert_buf = ctx->vertex_buffer + ctx->vert_start; + batch.vert_count = ctx->cur_vert - ctx->vert_start; + batch.indice_buf = ctx->indice_buffer + ctx->indice_start; + batch.indice_count = ctx->cur_indice - ctx->indice_start; + + ctx->render_batch( ctx, &batch, shader, shader_data ); + + ctx->indice_start = ctx->cur_indice; + ctx->vert_start = ctx->cur_vert; +} + +void ui_rect_center( ui_rect parent, ui_rect rect ) +{ + rect[0] = parent[0] + (parent[2]-rect[2])/2; + rect[1] = parent[1] + (parent[3]-rect[3])/2; +} + +void ui_fit_item( ui_rect rect, ui_px size[2], ui_rect d ) +{ + i32 rp = (i32)rect[2] * (i32)size[1], + rc = (i32)size[0] * (i32)rect[3]; + + enum ui_axis dir, other; + if( rc > rp ) dir = k_ui_axis_h; + else dir = k_ui_axis_v; + other = dir ^ 0x1; + + d[2+dir] = rect[2+dir]; + d[2+other] = (rect[2+dir] * size[other]) / size[dir]; + + ui_rect_center( rect, d ); +} + +ui_px ui_text_line_width( ui_context *ctx, const char *str ) +{ + int length = 0; + const char *_c = str; + u8 c; + + while( (c = *(_c ++)) ){ + if( c >= 32 ) length ++; + else if( c == '\n' ) break; + } + + return length * ctx->font->sx; +} + +ui_px ui_text_string_height( ui_context *ctx, const char *str ) +{ + int height = 1; + const char *_c = str; + u8 c; + + while( (c = *(_c ++)) ) + { + if( c == '\n' ) height ++; + } + + return height * ctx->font->sy; +} + +ui_px ui_text_aligned_x( ui_context *ctx, + const char *str, ui_rect rect, ui_px scale, + enum ui_align align ) +{ + enum ui_align lwr = k_ui_align_lwr & align; + if( lwr == k_ui_align_left ){ + return rect[0]; + } + else{ + ui_px width = ui_text_line_width( ctx, str ) * scale; + + if( lwr == k_ui_align_right ) + return rect[0] + rect[2]-width; + else + return rect[0] + (rect[2]-width)/2; + } +} + +u32 ui_colour( ui_context *ctx, enum ui_scheme_colour id ) +{ + return ctx->scheme[ id ]; +} + +/* get an appropriately contrasting colour given the base */ +u32 ui_colourcont( ui_context *ctx, enum ui_scheme_colour id ) +{ + if ( id < k_ui_bg+6 ) return ui_colour(ctx, k_ui_fg ); + else if( id < k_ui_fg ) return ui_colour(ctx, k_ui_bg+1 ); + else if( id < k_ui_hue ) return ui_colour(ctx, k_ui_bg+3 ); + else if( id < k_ui_red+k_ui_brighter ) return ui_colour(ctx, k_ui_fg ); + else return ui_colour(ctx, k_ui_fg+1 ); +} + +void ui_hex_to_norm( u32 hex, v4f norm ) +{ + norm[0] = ((hex ) & 0xff); + norm[1] = ((hex>>8 ) & 0xff); + norm[2] = ((hex>>16) & 0xff); + norm[3] = ((hex>>24) & 0xff); + v4_muls( norm, 1.0f/255.0f, norm ); +} + +u32 v4f_u32_colour( v4f colour ) +{ + u32 r = colour[0] * 255.0f, + g = colour[1] * 255.0f, + b = colour[2] * 255.0f, + a = colour[3] * 255.0f; + + return r | (g<<8) | (b<<16) | (a<<24); +} + +static void ui_text_glyph( const struct vg_font_face *ff, + u8 glyph, ui_rect out_texcoords ) +{ + const vg_font_char *ch = &ff->map[ glyph ]; + + out_texcoords[0] = ch->x; + out_texcoords[1] = ch->y; + out_texcoords[2] = ch->x + ff->cw; + out_texcoords[3] = ch->y + ff->ch; +} + +u32 ui_opacity( u32 colour, f32 opacity ) +{ + u32 alpha = opacity * 255.0f; + return (colour & 0x00ffffff) | (alpha << 24); +} + +u32 ui_ntext( ui_context *ctx, + ui_rect rect, const char *str, u32 len, ui_px scale, + enum ui_align align, u32 colour ) +{ + ui_px glow_text = 0; + + ui_rect text_cursor; + if( colour == 0 ) colour = ui_colour(ctx, k_ui_fg ); + + colour &= 0x00ffffff; + + const char *_c = str; + u8 c; + + text_cursor[0] = ui_text_aligned_x( ctx, str, rect, scale, align ); + text_cursor[1] = rect[1]; + text_cursor[2] = ctx->font->cw*scale; + text_cursor[3] = ctx->font->ch*scale; + + u32 printed_chars = 0; + + if( align & (k_ui_align_middle|k_ui_align_bottom) ) + { + ui_px height = ui_text_string_height( ctx, str ) * scale; + + if( align & k_ui_align_bottom ) + text_cursor[1] += rect[3]-height; + else + text_cursor[1] += (rect[3]-height)/2; + } + + while( (c = *(_c ++)) ) + { + if( printed_chars >= len ) + { + printed_chars = 0; + text_cursor[1] += ctx->font->sy*scale; + text_cursor[0] = ui_text_aligned_x( ctx, _c, rect, scale, align ); + text_cursor[0] -= ctx->font->sx*scale; + + ui_rect glyph; + ui_text_glyph( ctx->font, '\xb1' /*FIXME*/, glyph ); + ui_fill_rect( ctx, text_cursor, 0x00ffffff, glyph ); + text_cursor[0] += ctx->font->sx*scale; + } + + if( c == '\n' ) + { + text_cursor[1] += ctx->font->sy*scale; + text_cursor[0] = ui_text_aligned_x( ctx, _c, rect, scale, align ); + printed_chars = 0; + continue; + } + else if( c >= 33 ) + { + ui_rect glyph; + ui_text_glyph( ctx->font, c, glyph ); + + ui_rect cursor_clipped; + if( ui_clip( rect, text_cursor, cursor_clipped ) ) + { + if( glow_text ) + { + cursor_clipped[1] += glow_text; + ui_fill_rect( ctx, cursor_clipped, 0x00ffffff, glyph ); + cursor_clipped[1] -= glow_text; + } + + ui_fill_rect( ctx, cursor_clipped, colour, glyph ); + } + } + else if( c == '\x1B' ) + { + /* vt codes */ + _c ++; + u16 colour_id = 0; + for( int i=0; i<3; i ++ ) + { + if( _c[i] ) + { + if( _c[i] == 'm' ) + { + _c = _c + i + 1; + + switch( colour_id ){ + case '0': colour = ui_colour(ctx, k_ui_fg ); break; + case '3'|'0'<<8: colour = ui_colour(ctx, k_ui_bg ); break; + case '3'|'1'<<8: colour = ui_colour(ctx, k_ui_red ); break; + case '3'|'2'<<8: colour = ui_colour(ctx, k_ui_green ); break; + case '3'|'3'<<8: colour = ui_colour(ctx, k_ui_yellow ); break; + case '3'|'4'<<8: colour = ui_colour(ctx, k_ui_blue ); break; + case '3'|'5'<<8: colour = ui_colour(ctx, k_ui_purple ); break; + case '3'|'6'<<8: colour = ui_colour(ctx, k_ui_aqua ); break; + case '3'|'7'<<8: colour = 0xffffffff; break; + } + + colour &= 0x00ffffff; + break; + } + + colour_id |= _c[i] << (i*8); + } + else + { + _c = _c +i; + break; + } + } + + continue; + } + else if( c == '\x06' ) + { + glow_text = *_c; + _c ++; + continue; + } + else if( c == '\t' ) + { + text_cursor[0] += ctx->font->sx*scale*4; + printed_chars += 4; + continue; + } + + text_cursor[0] += ctx->font->sx*scale; + printed_chars ++; + } + + return printed_chars; +} + +u32 ui_text( ui_context *ctx, + ui_rect rect, const char *str, ui_px scale, + enum ui_align align, u32 colour ) +{ + return ui_ntext( ctx, rect, str, 1024, scale, align, colour ); +} + +/* + * Standard layout stuff + * ----------------------------------------------------------------------------- + */ + +void ui_panel( ui_context *ctx, ui_rect in_rect, ui_rect out_panel ) +{ + ui_fill( ctx, in_rect, ui_colour(ctx, k_ui_bg+1 ) ); + ui_outline( ctx, in_rect, 1, ui_colour(ctx, k_ui_bg+7 ), 0 ); + rect_copy( in_rect, out_panel ); + ui_rect_pad( out_panel, NULL ); +} + +void ui_label( ui_context *ctx, + ui_rect rect, const char *text, ui_px size, + ui_px gap, ui_rect r ) +{ + ui_rect l; + ui_px width = (ui_text_line_width(ctx,text)+ctx->font->sx) * size; + ui_split( rect, k_ui_axis_v, width, gap, l, r ); + ui_text( ctx, l, text, 1, k_ui_align_middle_left, 0 ); +} + +ui_px ui_standard_widget_height( ui_context *ctx, ui_px count ) +{ + return (count * ctx->font->sy + 18) * ctx->scale; +} + +void ui_standard_widget( ui_context *ctx, + ui_rect inout_panel, ui_rect out_rect, ui_px count ) +{ + ui_px height = ui_standard_widget_height( ctx, count ); + ui_split( inout_panel, k_ui_axis_h, height, ctx->padding, + out_rect, inout_panel ); +} + +void ui_info( ui_context *ctx, ui_rect inout_panel, const char *text ) +{ + ui_rect box; + ui_standard_widget( ctx, inout_panel, box, 1 ); + ui_text( ctx, box, text, 1, k_ui_align_middle_left, 0 ); +} + +void ui_image( ui_context *ctx, ui_rect rect, void *resource ) +{ + ui_flush( ctx, k_ui_shader_colour, NULL ); + ui_fill_rect( ctx, rect, 0xffffffff, (ui_px[4]){ 0,256,256,0 } ); + + struct ui_batch_shader_data_image inf = { .resource = resource }; + ui_flush( ctx, k_ui_shader_image, &inf ); +} + +void ui_defocus_all( ui_context *ctx ) +{ + if( ctx->focused_control_type == k_ui_control_textbox ) + { + ctx->stop_text_input(); + if( ctx->textbox.callbacks.escape ) + ctx->textbox.callbacks.escape( ctx ); + } + + ctx->focused_control_id = NULL; + ctx->focused_control_hit = 0; + ctx->focused_control_type = k_ui_control_none; +} + +enum ui_button_state ui_button_base( ui_context *ctx, ui_rect rect ) +{ + int clickup= ui_click_up(ctx, UI_MOUSE_LEFT), + click = ui_clicking(ctx, UI_MOUSE_LEFT) | clickup, + target = ui_inside_rect( rect, ctx->mouse_click ) && click, + hover = ui_inside_rect( rect, ctx->mouse ); + + if( ctx->focused_control_type != k_ui_control_none ) + { + clickup = 0; + click = 0; + target = 0; + hover = 0; + } + + if( hover ) + ctx->cursor = k_ui_cursor_hand; + + if( click ) + { + if( target ) + { + if( hover ) + { + if( clickup ) + { + ui_ignore_input_frames( ctx, 2 ); + ui_defocus_all( ctx ); + return k_ui_button_click; + } + else return k_ui_button_holding_inside; + } + else return k_ui_button_holding_outside; + } + else return k_ui_button_none; + } + else + { + if( hover ) return k_ui_button_hover; + else return k_ui_button_none; + } +} + +/* TODO: split this out into a formatless button and one that auto fills */ +enum ui_button_state ui_colourbutton( ui_context *ctx, ui_rect rect, + enum ui_scheme_colour colour, + enum ui_scheme_colour hover_colour, + enum ui_scheme_colour hi_colour ) +{ + enum ui_button_state state = ui_button_base( ctx, rect ); + + u32 col_base = ctx->scheme[ colour ], + col_highlight = ctx->scheme[ hi_colour? hi_colour: k_ui_fg ], + col_hover = ctx->scheme[ hover_colour? hover_colour: + colour + k_ui_brighter ]; + + if( state == k_ui_button_click ) + { + ui_fill( ctx, rect, col_highlight ); + rect_copy( rect, ctx->click_fader ); + rect_copy( rect, ctx->click_fader_end ); + ctx->click_fader_end[3] = 0; + ui_rect_center( rect, ctx->click_fader_end ); + ctx->click_fade_opacity = 1.0f; + } + else if( state == k_ui_button_holding_inside ) + { + ui_fill( ctx, rect, col_highlight ); + } + else if( state == k_ui_button_holding_outside ) + { + ui_fill( ctx, rect, col_base ); + ui_outline( ctx, rect, 1, col_highlight, 0 ); + } + else if( state == k_ui_button_hover ) + { + ui_fill( ctx, rect, col_hover ); + } + else ui_fill( ctx, rect, col_base ); + + return state; +} + +enum ui_button_state ui_colourbutton_text( + ui_context *ctx, + ui_rect rect, const char *string, ui_px scale, + enum ui_scheme_colour colour ) +{ + enum ui_button_state state = ui_colourbutton( ctx, rect, colour, 0, 0 ); + + u32 text_colour = ui_colourcont( ctx, colour ); + if( state == k_ui_button_holding_inside ) + text_colour = colour; + + ui_text( ctx, rect, string, scale, k_ui_align_middle_center, text_colour ); + return state; +} + +enum ui_button_state ui_button_text( ui_context *ctx, ui_rect rect, + const char *string, ui_px scale ) +{ + return ui_colourbutton_text( ctx, rect, string, scale, k_ui_bg+4 ); +} + +enum ui_button_state ui_button( ui_context *ctx, + ui_rect inout_panel, const char *string ) +{ + ui_rect rect; + ui_standard_widget( ctx, inout_panel, rect, 1 ); + return ui_colourbutton_text( ctx, rect, string, 1, k_ui_bg+4 ); +} + +static void ui_enum_post( ui_context *ctx ); +void ui_postrender( ui_context *ctx, f32 delta_time ) +{ + if( ctx->click_fade_opacity > 0.0f ) + { + float scale = ctx->click_fade_opacity; + scale = vg_maxf( 1.0f/255.0f, scale*scale ); + + ctx->click_fade_opacity -= delta_time * 3.8f; + u32 colour = (0x00ffffff & ui_colour(ctx,k_ui_fg))|0x7f000000; + + v4f begin, end, dest; + for( int i=0; i<4; i++ ){ + begin[i] = ctx->click_fader[i]; + end[i] = ctx->click_fader_end[i]+1; + } + + v4_lerp( end, begin, scale, dest ); + + ui_rect rect; + for( int i=0; i<4; i++ ){ + rect[i] = dest[i]; + } + + ui_fill( ctx, rect, colour ); + } + + if( ctx->focused_control_type == k_ui_control_enum ) + { + ui_enum_post( ctx ); + } + else if( ctx->focused_control_type == k_ui_control_modal ) + { + ui_rect screen = { 0,0, ctx->area[0], ctx->area[1] }; + ui_fill( ctx, screen, 0xa0000000 ); + ui_rect box = {0,0,400,200}; + + u32 colour = ui_colour(ctx,k_ui_fg), + type = ctx->modal.options & UI_MODAL_TYPE_BITS; + if ( type == 1 ) colour = ui_colour(ctx,k_ui_green); + else if( type == 2 ) colour = ui_colour(ctx,k_ui_red); + else if( type == 3 ) colour = ui_colour(ctx,k_ui_yellow); + + ui_rect_center( screen, box ); + ui_fill( ctx, box, ui_colour(ctx,k_ui_bg) ); + ui_outline( ctx, box, -1, colour, 0 ); + + ui_rect message; + rect_copy( box, message ); + message[3] = 100; + ui_rect_center( box, message ); + + ui_rect row0, row1, btn; + ui_split_ratio( message, k_ui_axis_h, 0.76f, 0, row0, row1 ); + row0[0] += ctx->font->sx; + ui_ntext( ctx, row0, ctx->modal.message, (box[2]/ctx->font->sx)-2, 1, + k_ui_align_left, colour ); + + rect_copy( row1, btn ); + btn[2] = 86; + btn[3] = 28; + ui_rect_center( row1, btn ); + + ctx->focused_control_type = k_ui_control_none; /* HACK */ + if( ui_button_text( ctx, btn, "OK", 1 ) != 1 ) + ctx->focused_control_hit = 1; + ctx->focused_control_type = k_ui_control_modal; /* HACK */ + ctx->wants_mouse = 1; + } + + ui_flush( ctx, k_ui_shader_colour, NULL ); + + if( !ctx->focused_control_hit ) + { + ui_defocus_all( ctx ); + } +} + +/* + * checkbox + * ----------------------------------------------------------------------------- + */ + +enum ui_button_state ui_checkbox_base( ui_context *ctx, ui_rect box, i32 *data ) +{ + enum ui_button_state state = ui_button_base( ctx, box ); + if( state == k_ui_button_click ) + *data = (*data) ^ 0x1; + return state; +} + +int ui_checkbox( ui_context *ctx, + ui_rect inout_panel, const char *str_label, i32 *data ) +{ + ui_rect rect, label, box; + ui_standard_widget( ctx, inout_panel, rect, 1 ); + + ui_split( rect, k_ui_axis_v, -rect[3], 0, label, box ); + ui_text( ctx, label, str_label, ctx->scale, k_ui_align_middle_left, 0 ); + + enum ui_button_state state = ui_checkbox_base( ctx, box, data ); + + if( state == k_ui_button_holding_inside ) + { + ui_fill( ctx, box, ui_colour(ctx,k_ui_bg+2) ); + ui_outline( ctx, box, 1, ui_colour(ctx,k_ui_fg), 0 ); + } + else if( state == k_ui_button_holding_outside ) + { + ui_fill( ctx, box, ui_colour(ctx,k_ui_bg) ); + ui_outline( ctx, box, 1, ui_colour(ctx,k_ui_fg), 0 ); + } + else if( state == k_ui_button_hover ) + { + ui_fill( ctx, box, ui_colour(ctx,k_ui_bg) ); + ui_outline( ctx, box, 1, ui_colour(ctx,k_ui_fg), 0 ); + } + else + { + ui_fill( ctx, box, ui_colour(ctx,k_ui_bg) ); + ui_outline( ctx, box, 1, ui_colour(ctx,k_ui_bg+4), 0 ); + } + + bool changed = (state == k_ui_button_click); + + if( *data ) + { + ui_rect_pad( box, (ui_px[2]){4,4} ); + ui_fill( ctx, box, ui_colour(ctx, k_ui_orange ) ); + } + + return changed; +} + +/* + * Dropdown / Enum + * ----------------------------------------------------------------------------- + */ + +/* + * unfortunately no return value since we only find out that event in the + * postrender step. + */ +void ui_enum( ui_context *ctx, ui_rect inout_panel, const char *str_label, + struct ui_enum_opt *options, u32 len, i32 *value ) +{ + ui_rect rect, label, box; + ui_standard_widget( ctx, inout_panel, rect, 1 ); + ui_label( ctx, rect, str_label, ctx->scale, 0, box ); + + const char *display = "OUT OF RANGE"; + int valid = 0; + for( u32 i=0; iscale ) == 1 ) + { + ctx->focused_control_type = k_ui_control_enum; + ctx->ptr_enum = value; + ctx->_enum.option_count = len; + ctx->_enum.options = options; + rect_copy( box, ctx->_enum.rect ); + } + + if( !valid ) + ui_outline( ctx, box, 1, ui_colour(ctx,k_ui_red), 0 ); +} + +static void ui_enum_post( ui_context *ctx ) +{ + ui_rect drawer; + rect_copy( ctx->_enum.rect, drawer ); + drawer[3] *= ctx->_enum.option_count; + + int close = 0; + int clickany= ui_click_up( ctx, UI_MOUSE_ANY ), + hover = ui_inside_rect( drawer, ctx->mouse ); + + if( clickany && !hover ) + return; + + /* HACK */ + ctx->focused_control_type = k_ui_control_none; + i32 *value = ctx->ptr_enum; + + for( u32 i=0; i_enum.option_count; i++ ){ + ui_rect button; + ui_split( drawer, k_ui_axis_h, ctx->_enum.rect[3], 0, button,drawer ); + + enum ui_scheme_colour colour = k_ui_bg+3; + if( ctx->_enum.options[i].value == *value ) + colour = k_ui_orange; + + if( ui_colourbutton_text( ctx, button, ctx->_enum.options[i].alias, + ctx->scale, colour ) == 1 ){ + *value = ctx->_enum.options[i].value; + close = 1; + } + } + + /* HACK */ + ctx->focused_control_type = k_ui_control_enum; + + if( !close ) + ctx->focused_control_hit = 1; +} + +/* + * Slider + * ----------------------------------------------------------------------------- + */ + +enum ui_button_state ui_slider_base( ui_context *ctx, + ui_rect box, f32 min, f32 max, f32 *value, f32 *out_t ) +{ + enum ui_button_state mask_using = + k_ui_button_holding_inside | + k_ui_button_holding_outside | + k_ui_button_click, + state = ui_button_base( ctx, box ); + + f32 t; + if( state & mask_using ) + { + t = vg_clampf( (f32)(ctx->mouse[0] - box[0]) / (f32)( box[2] ), 0,1 ); + *value = vg_lerpf( min, max, t ); + } + else + t = vg_clampf( (*value - min) / (max-min), 0.0f, 1.0f ); + + *out_t = t; + + return state; +} + +void ui_slider_text( ui_context *ctx, + ui_rect box, const char *format, f32 value ) +{ + /* TODO: replace this one day with our own function */ + char buf[32]; + snprintf( buf, sizeof(buf), format? format: "%.2f", value ); + ui_text( ctx, box, buf, 1, k_ui_align_middle_center, 0 ); +} + +bool ui_slider_standard( ui_context *ctx, + ui_rect box, f32 min, f32 max, f32 *value, + const char *format ) +{ + f32 t; + + enum ui_button_state mask_using = + k_ui_button_holding_inside | + k_ui_button_holding_outside | + k_ui_button_click, + mask_brighter = mask_using | k_ui_button_hover, + state = ui_slider_base( ctx, box, min, max, value, &t ); + + ui_rect line = { box[0], box[1], t * (f32)box[2], box[3] }; + ui_fill( ctx, line, + ui_colour(ctx,state&mask_brighter? k_ui_bg+4: k_ui_bg+2) ); + + ui_fill( ctx, (ui_rect){ box[0]+line[2], box[1], box[2]-line[2], box[3] }, + ui_colour(ctx, k_ui_bg ) ); + ui_outline( ctx, box, 1, ui_colour(ctx,state? k_ui_fg+3: k_ui_bg+3), 0 ); + ui_slider_text( ctx, box, NULL, *value ); + + return (state & mask_using) && 1; +} + +bool ui_slider( ui_context *ctx, ui_rect inout_panel, const char *str_label, + f32 min, f32 max, f32 *value ) +{ + ui_rect rect, label, box; + ui_standard_widget( ctx, inout_panel, rect, 1 ); + ui_label( ctx, rect, str_label, ctx->scale, 0, box ); + return ui_slider_standard( ctx, box, min, max, value, NULL ); +} + +/* + * Colour picker + * ----------------------------------------------------------------------------- + */ + +void ui_colourpicker( ui_context *ctx, + ui_rect inout_panel, const char *str_label, v4f value ) +{ + ui_rect widget, left, right; + ui_standard_widget( ctx, inout_panel, widget, 8 ); + ui_split_ratio( widget, k_ui_axis_v, 0.5f, 8, left, right ); + + ui_rect sliders[4]; + ui_split_ratio( right, k_ui_axis_h, 0.5f, 2, sliders[0], sliders[2] ); + ui_split_ratio( sliders[0], k_ui_axis_h, 0.5f, 2, sliders[0], sliders[1] ); + ui_split_ratio( sliders[2], k_ui_axis_h, 0.5f, 2, sliders[2], sliders[3] ); + + v4f hsv; + vg_rgb_hsv( value, hsv ); + hsv[3] = value[3]; + + enum ui_button_state modified = 0x00; + + for( u32 i=0; i<4; i ++ ) + { + modified |= ui_slider_standard( ctx, sliders[i], 0.0f, 1.0f, hsv+i, + (const char *[]){ "hue %.2f", + "sat %.2f", + "lum %.2f", + "alpha %.2f" }[i] ); + } + + ui_rect preview, square; + ui_split_ratio( left, k_ui_axis_v, 0.8f, 8, square, preview ); + + u32 state = ui_button_base( ctx, square ); + modified |= state; + + enum ui_button_state + mask_using = + k_ui_button_holding_inside | + k_ui_button_holding_outside | + k_ui_button_click; + + if( state & mask_using ) + { + for( u32 i=0; i<2; i ++ ){ + hsv[1+i] = vg_clampf( + (f32)(ctx->mouse[i] - square[i]) / (f32)(square[2+i]), + 0.0f, 1.0f ); + } + + hsv[2] = 1.0f-hsv[2]; + } + + if( modified & (k_ui_button_click|k_ui_button_holding_inside| + k_ui_button_holding_outside) ) + { + vg_hsv_rgb( hsv, value ); + value[3] = hsv[3]; + } + + ui_outline( ctx, square, + 1, ui_colour(ctx, state? k_ui_fg+3: k_ui_bg+3 ), 0 ); + + /* preview colour */ + v4f colour; + vg_hsv_rgb( hsv, colour ); + colour[3] = 1.0f; + ui_fill( ctx, preview, v4f_u32_colour( colour ) ); + + /* Draw hsv shader thingy */ + ui_flush( ctx, k_ui_shader_colour, NULL ); + ui_fill_rect( ctx, square, 0xffffffff, (ui_px[4]){ 0,256,256,0 } ); + + struct ui_batch_shader_data_hsv inf = { .hue = hsv[0] }; + ui_flush( ctx, k_ui_shader_hsv, &inf ); + + ui_rect marker = { square[0] + hsv[1] * (f32)square[2] - 4, + square[1] + (1.0f-hsv[2]) * (f32)square[3] - 4, + 8, 8 }, + lx = { square[0], + square[1] + (1.0f-hsv[2]) * (f32)square[3], + square[2], 1 }, + ly = { square[0] + hsv[1] * (f32)square[2], + square[1], + 1, square[3] }; + + ui_fill( ctx, marker, ui_colour(ctx, k_ui_fg ) ); + ui_fill( ctx, lx, ui_colour(ctx, k_ui_fg ) ); + ui_fill( ctx, ly, ui_colour(ctx, k_ui_fg ) ); +} + +/* + * Textbox chaos + * ----------------------------------------------------------------------------- + */ + +static void _ui_textbox_make_selection( ui_context *ctx, int *start, int *end ) +{ + *start = VG_MIN( ctx->textbox.cursor_pos, ctx->textbox.cursor_user ); + *end = VG_MAX( ctx->textbox.cursor_pos, ctx->textbox.cursor_user ); +} + +void _ui_textbox_move_cursor( ui_context *ctx, int *cursor0, int *cursor1, + int dir, int snap_together ) +{ + *cursor0 = VG_MAX( 0, ctx->textbox.cursor_user + dir ); + *cursor0 = + VG_MIN( + VG_MIN( ctx->textbox.len-1, strlen( ctx->textbuf )), + *cursor0 ); + + if( snap_together ) + *cursor1 = *cursor0; +} + +static int _ui_textbox_makeroom( ui_context *ctx, int datastart, int length ) +{ + int move_to = VG_MIN( datastart+length, ctx->textbox.len-1 ); + int move_amount = strlen( ctx->textbuf )-datastart; + int move_end = VG_MIN( move_to+move_amount, ctx->textbox.len-1 ); + move_amount = move_end-move_to; + + if( move_amount ) + memmove( &ctx->textbuf[ move_to ], + &ctx->textbuf[ datastart ], + move_end-move_to ); + + ctx->textbuf[ move_end ] = '\0'; + + return VG_MIN( length, ctx->textbox.len-datastart-1 ); +} + +int _ui_textbox_delete_char( ui_context *ctx, int direction ) +{ + int start, end; + _ui_textbox_make_selection( ctx, &start, &end ); + + /* There is no selection */ + if( !(end-start) ){ + if( direction == 1 ) end = VG_MIN( end+1, strlen( ctx->textbuf ) ); + else if( direction == -1 ) start = VG_MAX( start-1, 0 ); + } + + /* Still no selction, no need to do anything */ + if( !(end-start) ) + return start; + + /* Copy the end->terminator to start */ + int remaining_length = strlen( ctx->textbuf )+1-end; + memmove( &ctx->textbuf[ start ], + &ctx->textbuf[ end ], + remaining_length ); + return start; +} + +void _ui_textbox_to_clipboard( ui_context *ctx ) +{ + int start, end; + _ui_textbox_make_selection( ctx, &start, &end ); + char buffer[512]; + + if( end-start ) + { + memcpy( buffer, &ctx->textbuf[ start ], end-start ); + buffer[ end-start ] = 0x00; + ctx->set_clipboard_text( buffer ); + } +} + +static void _ui_textbox_change_callback( ui_context *ctx ) +{ + if( ctx->textbox.callbacks.change ) + { + ctx->textbox.callbacks.change( ctx, ctx->textbuf, ctx->textbox.len ); + + /* we gave permission to modify the buffer in this callback so.. */ + int len = strlen( ctx->textbuf ); + ctx->textbox.cursor_user = VG_MIN( ctx->textbox.cursor_user, len ); + ctx->textbox.cursor_pos = VG_MIN( ctx->textbox.cursor_pos, len ); + } +} + +void ui_start_modal( ui_context *ctx, const char *message, u32 options ); +void _ui_textbox_clipboard_paste( ui_context *ctx ) +{ + if( !ctx->have_clipboard_text() ) + return; + + char *text = ctx->get_clipboard_text(); + + if( !text ) + return; + + int datastart = _ui_textbox_delete_char( ctx, 0 ); + int length = strlen( text ); + + if( (ctx->textbox.len - strlen(ctx->textbuf)) < length ){ + ui_start_modal( ctx, + "Clipboard content exceeds buffer size.", UI_MODAL_BAD ); + return; + } + + int cpylength = _ui_textbox_makeroom( ctx, datastart, length ); + + memcpy( ctx->textbuf + datastart, text, cpylength); + _ui_textbox_move_cursor( ctx, + &ctx->textbox.cursor_user, + &ctx->textbox.cursor_pos, cpylength, 1 ); + + ctx->free_clipboard_text( text ); + _ui_textbox_change_callback( ctx ); +} + +void _ui_textbox_put_char( ui_context *ctx, char c ) +{ + ctx->textbox.cursor_user = _ui_textbox_delete_char(ctx, 0); + if( (ctx->textbox.len - strlen(ctx->textbuf)) <= 1 ) return; + + if( _ui_textbox_makeroom( ctx, ctx->textbox.cursor_user, 1 ) ) + ctx->textbuf[ ctx->textbox.cursor_user ] = c; + + _ui_textbox_move_cursor( ctx, + &ctx->textbox.cursor_user, + &ctx->textbox.cursor_pos, 1, 1 ); +} + +/* Receed secondary cursor */ +void _ui_textbox_left_select( ui_context *ctx ) +{ + _ui_textbox_move_cursor( ctx, &ctx->textbox.cursor_user, NULL, -1, 0 ); +} + +/* Match and receed both cursors */ +void _ui_textbox_left( ui_context *ctx ) +{ + int cursor_diff = ctx->textbox.cursor_pos - ctx->textbox.cursor_user? 0: 1; + + _ui_textbox_move_cursor( ctx, + &ctx->textbox.cursor_user, + &ctx->textbox.cursor_pos, -cursor_diff, 1 ); +} + +void _ui_textbox_up( ui_context *ctx ) +{ + if( ctx->textbox.flags & UI_TEXTBOX_MULTILINE ) + { + int line_begin = ctx->textbox.cursor_user; + + while( line_begin ) + { + if( ctx->textbuf[ line_begin-1 ] == '\n' ) + { + break; + } + + line_begin --; + } + + if( line_begin ) + { + int line_above_begin = line_begin-1; + + while( line_above_begin ) + { + if( ctx->textbuf[ line_above_begin-1 ] == '\n' ) + { + break; + } + + line_above_begin --; + } + + int offset = ctx->textbox.cursor_user - line_begin, + line_length_above = line_begin - line_above_begin -1; + + offset = VG_MIN( line_length_above, offset ); + + ctx->textbox.cursor_user = line_above_begin+offset; + ctx->textbox.cursor_pos = line_above_begin+offset; + } + else + { + ctx->textbox.cursor_user = line_begin; + ctx->textbox.cursor_pos = line_begin; + } + } + else + { + if( ctx->textbox.callbacks.up ) + { + ctx->textbox.callbacks.up( ctx, ctx->textbuf, ctx->textbox.len ); + } + } +} + +void _ui_textbox_down( ui_context *ctx ) +{ + if( ctx->textbox.flags & UI_TEXTBOX_MULTILINE ) + { + int line_begin = ctx->textbox.cursor_user; + + while( line_begin ) + { + if( ctx->textbuf[ line_begin-1 ] == '\n' ) + { + break; + } + + line_begin --; + } + + int line_below_begin = ctx->textbox.cursor_user; + + while(1) + { + if( ctx->textbuf[ line_below_begin ] == '\0' ) + { + ctx->textbox.cursor_user = line_below_begin; + ctx->textbox.cursor_pos = line_below_begin; + return; + } + + if( ctx->textbuf[ line_below_begin ] == '\n' ) + { + line_below_begin ++; + break; + } + + line_below_begin ++; + } + + int line_below_end = line_below_begin; + while(1) + { + if( ctx->textbuf[ line_below_end ] == '\0' || + ctx->textbuf[ line_below_end ] == '\n' ){ + line_below_end ++; + break; + } + line_below_end ++; + } + + int offset = ctx->textbox.cursor_user - line_begin, + line_length_below = line_below_end - line_below_begin -1; + + offset = VG_MIN( line_length_below, offset ); + + ctx->textbox.cursor_user = line_below_begin+offset; + ctx->textbox.cursor_pos = line_below_begin+offset; + } + else + { + if( ctx->textbox.callbacks.down ) + { + ctx->textbox.callbacks.down( ctx, ctx->textbuf, ctx->textbox.len ); + } + } +} + +void _ui_textbox_right_select( ui_context *ctx ) +{ + _ui_textbox_move_cursor( ctx, &ctx->textbox.cursor_user, NULL, 1, 0 ); +} + +void _ui_textbox_right( ui_context *ctx ) +{ + int cursor_diff = ctx->textbox.cursor_pos - ctx->textbox.cursor_user? 0: 1; + + _ui_textbox_move_cursor( ctx, + &ctx->textbox.cursor_user, + &ctx->textbox.cursor_pos, +cursor_diff, 1 ); +} + +void _ui_textbox_backspace( ui_context *ctx ) +{ + if( ctx->focused_control_type == k_ui_control_textbox ) + { + ctx->textbox.cursor_user = _ui_textbox_delete_char( ctx, -1 ); + ctx->textbox.cursor_pos = ctx->textbox.cursor_user; + _ui_textbox_change_callback( ctx ); + } +} + +void _ui_textbox_delete( ui_context *ctx ) +{ + if( ctx->focused_control_type == k_ui_control_textbox ) + { + ctx->textbox.cursor_user = _ui_textbox_delete_char( ctx, 1 ); + ctx->textbox.cursor_pos = ctx->textbox.cursor_user; + _ui_textbox_change_callback( ctx ); + } +} + +void _ui_textbox_home_select( ui_context *ctx ) +{ + i32 start = ctx->textbox.cursor_user; + + if( ctx->textbox.flags & UI_TEXTBOX_MULTILINE ) + { + while( start ) + { + if( ctx->textbuf[start-1] == '\n' ) + break; + else + start --; + } + } + else + start = 0; + + ctx->textbox.cursor_user = start; +} + +void _ui_textbox_home( ui_context *ctx ) +{ + _ui_textbox_home_select( ctx ); + ctx->textbox.cursor_pos = ctx->textbox.cursor_user; +} + +void _ui_textbox_end_select( ui_context *ctx ) +{ + i32 end = ctx->textbox.cursor_user; + + if( ctx->textbox.flags & UI_TEXTBOX_MULTILINE ) + { + while( ctx->textbuf[end] ) + { + if( ctx->textbuf[end] == '\n' ) + break; + else + end ++; + } + } + else + end = VG_MIN( ctx->textbox.len-1, strlen(ctx->textbuf) ); + + ctx->textbox.cursor_user = end; +} + +void _ui_textbox_end( ui_context *ctx ) +{ + _ui_textbox_end_select( ctx ); + ctx->textbox.cursor_pos = ctx->textbox.cursor_user; +} + +void _ui_textbox_select_all( ui_context *ctx ) +{ + _ui_textbox_move_cursor( ctx, &ctx->textbox.cursor_user, NULL, 10000, 0 ); + _ui_textbox_move_cursor( ctx, &ctx->textbox.cursor_pos, NULL, -10000, 0 ); +} + +void _ui_textbox_cut( ui_context *ctx ) +{ + _ui_textbox_to_clipboard( ctx ); + ctx->textbox.cursor_user = _ui_textbox_delete_char( ctx, 0 ); + ctx->textbox.cursor_pos = ctx->textbox.cursor_user; + _ui_textbox_change_callback( ctx ); +} + +void _ui_textbox_enter( ui_context *ctx ) +{ + if( ctx->focused_control_type == k_ui_control_textbox ) + { + ui_ignore_input_frames( ctx, 2 ); + + if( ctx->textbox.callbacks.enter ) + ctx->textbox.callbacks.enter( ctx, ctx->textbuf, ctx->textbox.len ); + + if( ctx->focused_control_type != k_ui_control_textbox ) return; + + if( ctx->textbox.flags & UI_TEXTBOX_MULTILINE ) + { + _ui_textbox_put_char( ctx, '\n' ); + _ui_textbox_change_callback( ctx ); + } + else + { + if( !(ctx->textbox.flags & UI_TEXTBOX_AUTOFOCUS ) ) + ui_defocus_all( ctx ); + } + } +} + +/* + * based on a visual character coordinate relative to the anchor of the textbox, + * this works out the linear place in the buffer that coordinate maps to + * + * input coordinates go in co[0], co[1], and the result index is in co[2] + */ +static void _ui_textbox_calc_index_from_grid( ui_context *ctx, + int co[3], int wrap_length ) +{ + int i[3] = {0,0,0}; + + char c; + while( (c = ctx->textbuf[i[2]]) ) + { + if( i[1]==co[1] && i[0]>=co[0] ) break; + + if( i[0] >= wrap_length ) + { + i[1] ++; + i[0] = 0; + } + + if( c >= 32 && c <= 126 ) + { + i[0] ++; + i[2] ++; + } + else if( c == '\n' ) + { + i[1] ++; + + if( i[1] > co[1] ) break; + + i[2] ++; + i[0] = 0; + } + else i[2] ++; + } + + co[0] = i[0]; + co[1] = i[1]; + co[2] = i[2]; +} + +/* + * based on the index specied in co[2], work out the visual character + * coordinates and store them in co[0], co[1] + */ +static void _ui_textbox_index_calc_coords( ui_context *ctx, + int co[3], int wrap_length ) +{ + co[0] = 0; + co[1] = 0; + + char c; + int i=0; + + while( (c = ctx->textbuf[i ++]) ) + { + if( i > co[2] ) break; + if( co[0] >= wrap_length ) + { + co[1] ++; + co[0] = 0; + } + if( c >= 32 && c <= 126 ) co[0] ++; + else if( c == '\n' ) + { + co[1] ++; + co[0] = 0; + } + } +} + +/* + * calculate the number of characters remaining until either: + * - the wrap_length limit is hit + * - end of the line/string + * + * index must be fully populated with visual X/Y, and linear index + */ +static int _ui_textbox_run_remaining( ui_context *ctx, + int index[3], int wrap_length ) +{ + int i=0, printed_chars=0; + char c; + while( (c = ctx->textbuf[index[2] + (i ++)]) ) + { + if( index[0]+i >= wrap_length ) break; + if( c >= 32 && c <= 126 ) printed_chars ++; + else if( c == '\n' ) break; + } + + return printed_chars+1; +} + +int ui_textbox( ui_context *ctx, ui_rect inout_panel, const char *label, + char *buf, u32 len, u32 lines, u32 flags, + struct ui_textbox_callbacks *callbacks ) +{ + if( lines > 1 ) flags |= UI_TEXTBOX_MULTILINE; + + ui_rect rect; + ui_standard_widget( ctx, inout_panel, rect, lines ); + + if( label ) + ui_label( ctx, rect, label, 1, 0, rect ); + + int clickup= ui_click_up(ctx, UI_MOUSE_LEFT), + clickdown = ui_click_down(ctx, UI_MOUSE_LEFT), + click = ui_clicking(ctx, UI_MOUSE_LEFT) | clickup, + target = ui_inside_rect( rect, ctx->mouse_click ) && click, + hover = ui_inside_rect( rect, ctx->mouse ); + + /* allow instant transitions from textbox->textbox */ + if( (ctx->focused_control_type != k_ui_control_none) && + (ctx->focused_control_type != k_ui_control_textbox) ){ + clickup = 0; + clickdown = 0; + click = 0; + target = 0; + hover = 0; + flags &= ~UI_TEXTBOX_AUTOFOCUS; + } + + u32 col_base = ui_colour(ctx, k_ui_bg ), + col_highlight = ui_colour(ctx, k_ui_fg ), + col_cursor = (0x00ffffff & ui_colour(ctx,k_ui_fg))|0x7f000000; + + ui_px border = -1; + + ui_rect text_rect; + rect_copy( rect, text_rect ); + + if( flags & UI_TEXTBOX_MULTILINE ) text_rect[3] = rect[3]-16; + else text_rect[3] = ctx->font->sy; + + text_rect[2] -= 16; + ui_rect_center( rect, text_rect ); + + ui_px wrap_length = 1024; + + if( flags & UI_TEXTBOX_WRAP ) + wrap_length = text_rect[2] / ctx->font->sx; + + if( hover ) + { + ctx->cursor = k_ui_cursor_ibeam; + } + + if( ctx->focused_control_id == buf ) + { + ui_fill( ctx, rect, col_base ); + ui_ntext( ctx, text_rect, buf, wrap_length, 1, k_ui_align_left, 0 ); + + if( !(flags & UI_TEXTBOX_AUTOFOCUS) && ((clickup||clickdown) && !target)) + { + ui_defocus_all( ctx ); + } + else + { + ctx->focused_control_hit = 1; + if( click && target ) + { + int p0[3] ={ + (ctx->mouse_click[0] - text_rect[0]) / ctx->font->sx, + (ctx->mouse_click[1] - text_rect[1]) / ctx->font->sy, + -1 + }, + p1[3] = { + (ctx->mouse[0] - text_rect[0]) / ctx->font->sx, + (ctx->mouse[1] - text_rect[1]) / ctx->font->sy, + -1 + }; + + if( flags & UI_TEXTBOX_MULTILINE ) + { + _ui_textbox_calc_index_from_grid( ctx, p0, wrap_length ); + _ui_textbox_calc_index_from_grid( ctx, p1, wrap_length ); + + ctx->textbox.cursor_pos = p0[2]; + ctx->textbox.cursor_user = p1[2]; + } + else + { + int max = strlen( buf ); + ctx->textbox.cursor_pos = VG_MAX( 0, VG_MIN( max, p0[0] )), + ctx->textbox.cursor_user = VG_MAX( 0, VG_MIN( max, p1[0] )); + } + } + + ui_outline( ctx, rect, -2, ctx->scheme[ k_ui_orange ], 0 ); + + ui_rect cursor; + + int c0 = ctx->textbox.cursor_pos, + c1 = ctx->textbox.cursor_user, + start = VG_MIN( c0, c1 ), + end = VG_MAX( c0, c1 ), + chars = end-start; + + if( flags & (UI_TEXTBOX_WRAP|UI_TEXTBOX_MULTILINE) ) + { + int pos[3], remaining = chars; + + pos[2] = start; + _ui_textbox_index_calc_coords( ctx, pos, wrap_length ); + + if( start==end ) + { + cursor[0] = text_rect[0] + pos[0]*ctx->font->sx-1; + cursor[1] = text_rect[1] + pos[1]*14; + cursor[2] = 2; + cursor[3] = 13; + ui_fill( ctx, cursor, col_cursor ); + rect_copy( cursor, ctx->click_fader_end ); + } + else + { + while( remaining ) + { + int run = _ui_textbox_run_remaining( ctx, pos, wrap_length ); + run = VG_MIN( run, remaining ); + + cursor[0] = text_rect[0] + pos[0]*ctx->font->sx-1; + cursor[1] = text_rect[1] + pos[1]*14; + cursor[2] = (float)(run)*(float)ctx->font->sx; + cursor[3] = 13; + + ui_fill( ctx, cursor, col_cursor ); + + remaining -= run; + pos[0] = 0; + pos[1] ++; + pos[2] += run; + } + rect_copy( cursor, ctx->click_fader_end ); + } + } + else + { + cursor[0] = text_rect[0] + start*ctx->font->sx-1; + cursor[1] = text_rect[1]; + cursor[3] = 13; + + if( start==end ) + { + cursor[2] = 2; + } + else + { + cursor[2] = (float)(chars)*(float)ctx->font->sx; + } + + if( (ctx->click_fade_opacity<=0.0f) && + ui_clip( rect, cursor, cursor ) ) + { + ui_fill( ctx, cursor, col_cursor ); + } + + rect_copy( cursor, ctx->click_fader_end ); + } + } + + return 0; + } + + if( click || (flags & UI_TEXTBOX_AUTOFOCUS) ) + { + if( (target && hover) || (flags & UI_TEXTBOX_AUTOFOCUS) ) + { + ui_defocus_all( ctx ); + + ui_fill( ctx, rect, col_highlight ); + ui_ignore_input_frames( ctx, 2 ); + rect_copy( rect, ctx->click_fader ); + rect_copy( rect, ctx->click_fader_end ); + + ctx->click_fade_opacity = 1.0f; + ctx->textbuf = buf; + ctx->focused_control_hit = 1; + ctx->focused_control_type = k_ui_control_textbox; + ctx->textbox.len = len; + ctx->textbox.flags = flags; + ctx->textbox.cursor_pos = 0; + ctx->textbox.cursor_user = 0; + + if( callbacks ) + { + ctx->textbox.callbacks = *callbacks; + } + else + { + ctx->textbox.callbacks.change = NULL; + ctx->textbox.callbacks.down = NULL; + ctx->textbox.callbacks.up = NULL; + ctx->textbox.callbacks.enter = NULL; + } + + ctx->start_text_input(); + } + } + + ui_fill( ctx, rect, col_base ); + + if( hover ) + { + ui_outline( ctx, rect, -1, col_highlight, 0 ); + } + + ui_ntext( ctx, text_rect, buf, wrap_length, 1, k_ui_align_left, 0 ); + return 0; +} + +/* + * Tabs + * ----------------------------------------------------------------------------- + */ + +void ui_tabs( ui_context *ctx, ui_rect inout_panel, ui_rect out_content_panel, + const char **titles, u32 count, i32 *page ) +{ + ui_rect bar; + ui_standard_widget( ctx, inout_panel, bar, 1 ); + + i32 cur_page = *page; + + f32 width = (f32)inout_panel[2] / (f32)count; + + ui_px h = (inout_panel[1] + inout_panel[3]) - (bar[1]+bar[3]); + inout_panel[1] = bar[1]+bar[3]; + inout_panel[3] = h; + + ui_fill( ctx, inout_panel, ui_colour(ctx, k_ui_bg+2 ) ); + ui_outline( ctx, inout_panel, 1, ui_colour(ctx, k_ui_bg+5 ), 0 ); + + rect_copy( inout_panel, out_content_panel ); + ui_rect_pad( out_content_panel, + (ui_px[2]){ ctx->padding, ctx->padding } ); + + /* place buttons */ + for( i32 i=0; ifocused_control_type = k_ui_control_modal; + ctx->modal.message = message; + ctx->modal.callbacks.close = NULL; + ctx->modal.options = options; + + u32 type = options & UI_MODAL_TYPE_BITS; + if( type == UI_MODAL_OK ) vg_info( message ); + else if( type == UI_MODAL_WARN ) vg_warn( message ); + else if( type == UI_MODAL_GOOD ) vg_success( message ); + else if( type == UI_MODAL_BAD ) vg_error( message ); +} + +/* + * Input handling + * ----------------------------------------------------------------------------- + */ + +/* + * Callback for text entry mode + */ +void ui_proc_utf8( ui_context *ctx, const char *text ) +{ + if( ctx->focused_control_type == k_ui_control_textbox ) + { + const char *ptr = text; + + while( *ptr ) + { + if( *ptr != '`' ) + _ui_textbox_put_char( ctx, *ptr ); + + ptr ++; + } + + _ui_textbox_change_callback( ctx ); + } +} + +/* + * Development utils + * ----------------------------------------------------------------------------- + */ + +void ui_dev_colourview( ui_context *ctx ) +{ + ui_rect window = {ctx->area[0]-256,0,256,ctx->area[1]}, swatch; + + const char *names[VG_ARRAY_LEN(ctx->scheme)] = { + [k_ui_bg] = "k_ui_bg", "k_ui_bg+1", "k_ui_bg+2", "k_ui_bg+3", + "k_ui_bg+4", "k_ui_bg+5", "k_ui_bg+6", "k_ui_bg+7", + + [k_ui_fg] = "k_ui_fg", "k_ui_fg+1", "k_ui_fg+2", "k_ui_fg+3", + "k_ui_fg+4", "k_ui_fg+5", "k_ui_fg+6", "k_ui_fg+7", + + [k_ui_red] = "k_ui_red", "k_ui_orange", "k_ui_yellow", "k_ui_green", + "k_ui_aqua", "k_ui_blue", "k_ui_purple", "k_ui_gray", + "k_ui_red+8","k_ui_orange+8","k_ui_yellow+8","k_ui_green+8", + "k_ui_aqua+8","k_ui_blue+8","k_ui_purple+8","k_ui_gray+8" }; + + ui_rect col[2]; + ui_split_ratio( window, k_ui_axis_v, 0.5f, 0, col[0], col[1] ); + + for( int i=0; ischeme); i++ ) + { + int which = (i/8)%2; + + ui_split( col[which], k_ui_axis_h, 24, 0, swatch, col[which] ); + ui_fill( ctx, swatch, ui_colour(ctx,i) ); + + if( names[i] ) + { + ui_text( ctx, swatch, names[i], 1, + k_ui_align_middle_left, ui_colourcont(ctx,i)); + } + } +} diff --git a/vg_ui/imgui.h b/vg_ui/imgui.h new file mode 100644 index 0000000..8672cf4 --- /dev/null +++ b/vg_ui/imgui.h @@ -0,0 +1,380 @@ +#pragma once + +#include "vg_platform.h" +#include "vg_m.h" +#include "vg_font.h" +#include "vg_log.h" +#include + +typedef i16 ui_px; +typedef ui_px ui_rect[4]; +typedef ui_px ui_point[2]; +typedef struct ui_vert ui_vert; +typedef struct ui_context ui_context; +typedef struct ui_batch ui_batch; + +enum ui_axis { + k_ui_axis_h = 0x0u, + k_ui_axis_v = 0x1u, +}; + +/* Relative to cursor p0 */ +enum ui_align +{ /* DC BA */ + k_ui_align_lwr = 0xff, + k_ui_align_left = 0x0000| 0x00, + k_ui_align_right = 0x0000| 0x01, + k_ui_align_center = 0x0000| 0x02, + + k_ui_align_middle = 0x0100, + k_ui_align_middle_left = 0x0100| 0x00, + k_ui_align_middle_right = 0x0100| 0x01, + k_ui_align_middle_center = 0x0100| 0x02, + + k_ui_align_bottom = 0x0200, + k_ui_align_bottom_left = 0x0200| 0x00, + k_ui_align_bottom_right = 0x0200| 0x01, + k_ui_align_bottom_center = 0x0200| 0x02, +}; + +enum ui_shader +{ + k_ui_shader_colour, + k_ui_shader_image, + k_ui_shader_hsv, + k_ui_shader_grad +}; + +typedef u32 ui_scheme[8*4]; +enum ui_scheme_colour +{ + k_ui_bg = 0, + k_ui_fg = 8, + k_ui_hue = 16, + k_ui_red = 16, + k_ui_orange, + k_ui_yellow, + k_ui_green, + k_ui_aqua, + k_ui_blue, + k_ui_purple, + k_ui_gray, + k_ui_brighter = 8 +}; + +#define UI_RGB( STDHEX ) 0xff000000 |\ + ((STDHEX&0x000000ff)<<16) |\ + ((STDHEX&0x0000ff00) ) |\ + ((STDHEX&0x00ff0000)>>16) + +#define UI_TEXTBOX_MULTILINE 0x1 +#define UI_TEXTBOX_WRAP 0x2 +#define UI_TEXTBOX_AUTOFOCUS 0x4 + +#define UI_MODAL_OK 0x0 +#define UI_MODAL_GOOD 0x1 +#define UI_MODAL_BAD 0x2 +#define UI_MODAL_WARN 0x3 +#define UI_MODAL_TYPE_BITS 0x3 + +#define UI_MOUSE_LEFT 1 +#define UI_MOUSE_MIDDLE 2 +#define UI_MOUSE_RIGHT 4 +#define UI_MOUSE_ANY (UI_MOUSE_LEFT|UI_MOUSE_RIGHT|UI_MOUSE_MIDDLE) + +#define UI_TOP 0x1 +#define UI_LEFT 0x2 +#define UI_BOTTOM 0x4 +#define UI_RIGHT 0x8 + +#pragma pack(push,1) +struct ui_vert +{ + ui_px co[2]; + u16 uv[2]; + u32 colour; +}; +#pragma pack(pop) + +enum ui_button_state +{ + k_ui_button_none = 0x0, + k_ui_button_click = 0x1, + k_ui_button_holding_inside = 0x2, + k_ui_button_holding_outside = 0x4, + k_ui_button_hover = 0x8 +}; + +struct ui_context +{ + struct ui_vert *vertex_buffer; + u16 *indice_buffer; + u32 max_verts, max_indices, + cur_vert, cur_indice, + vert_start, indice_start; + + ui_px area[2]; + + union + { + void *focused_control_id; /* uses the memory location of various locking + controls as an id */ + char *textbuf; + i32 *ptr_enum; + }; + + u32 focused_control_hit; + enum ui_control_type{ + k_ui_control_none, + k_ui_control_textbox, + k_ui_control_enum, + k_ui_control_modal + } + focused_control_type; + + union /* controls data that can be focused */ + { + struct ui_textbuf + { + int cursor_user, cursor_pos; + u32 len; + u32 flags; + + struct ui_textbox_callbacks + { + void (*enter)( ui_context *ctx, char *, u32 ), + (*up)( ui_context *ctx, char *, u32 ), + (*down)( ui_context *ctx, char *, u32 ), + (*change)( ui_context *ctx, char *, u32 ), + (*escape)( ui_context *ctx ); + } + callbacks; + } + textbox; + + struct ui_enum + { + struct ui_enum_opt + { + i32 value; + const char *alias; + } + *options; + u32 option_count; + ui_rect rect; + } + _enum; + }; + + struct ui_modal + { + const char *message; + u32 options; + + struct ui_modal_callbacks + { + void (*close)(u32); + } + callbacks; + } + modal; + + ui_px mouse[2], mouse_delta[2], mouse_click[2]; + u32 mouse_state[2]; + u32 ignore_input_frames; + + bool mouse_pos_overriden; + int wants_mouse; + + ui_rect click_fader, click_fader_end; + float click_fade_opacity; + + ui_scheme scheme; + const vg_font_face *font; + + enum ui_cursor + { + k_ui_cursor_default, + k_ui_cursor_ibeam, + k_ui_cursor_hand, + k_ui_cursor_max + } + cursor; + + ui_px widget_height, scale, padding; + + void (*render_batch)(ui_context *ctx, ui_batch *batch, + enum ui_shader shader, void *shader_data ); + bool (*have_clipboard_text)(void); + char *(*get_clipboard_text)(void); + void (*free_clipboard_text)(void *text); + int (*set_clipboard_text)(const char *text); + + void (*start_text_input)(void); + void (*stop_text_input)(void); +}; + +struct ui_batch_shader_data_hsv +{ + f32 hue; +}; + +struct ui_batch_shader_data_image +{ + void *resource; +}; + +struct ui_batch +{ + ui_vert *vert_buf; /* base vertex array + [vert_offset] */ + u32 vert_count, + vert_offset; /* in bytes */ + + u16 *indice_buf; /* base indice array + [indice_offset] */ + u32 indice_count, + indice_offset; /* in bytes */ +}; + +/* set the memory buffer and limits of ui context */ +void ui_init( ui_context *ctx, + ui_vert *verts_buf, u32 verts_max, + u16 *indices_buf, u32 indices_max ); + +/* simple fill drawing commands */ +ui_vert *ui_fill_rect( ui_context *ctx, ui_rect rect, u32 colour, ui_px uv[4] ); +ui_vert *ui_fill( ui_context *ctx, ui_rect rect, u32 colour ); +void ui_outline( ui_context *ctx, ui_rect rect, ui_px thickness, + u32 colour, u32 mask ); + +/* rect controls */ +void rect_copy( ui_rect a, ui_rect b ); +void ui_split( ui_rect rect, enum ui_axis other, ui_px width, ui_px gap, + ui_rect l, ui_rect r ); +void ui_split_ratio( ui_rect rect, enum ui_axis dir, float ratio, + ui_px gap, ui_rect l, ui_rect r ); +void ui_rect_pad( ui_rect rect, ui_px pad[2] ); +int ui_clip( ui_rect parent, ui_rect child, ui_rect clipped ); +int ui_inside_rect( ui_rect rect, ui_px co[2] ); +void ui_fit_item( ui_rect rect, ui_px size[2], ui_rect d ); + +/* ui interactions */ +void ui_update_mouse( ui_context *ctx, ui_px mouse[2], i32 mouse_state ); +int ui_click_down( ui_context *ctx, u32 mask ); +int ui_clicking( ui_context *ctx, u32 mask ); +int ui_click_up( ui_context *ctx, u32 mask ); +void ui_ignore_input_frames( ui_context *ctx, u32 frames ); +void ui_capture_mouse( ui_context *ctx, bool on ); + +/* ui cycle */ +void ui_prerender( ui_context *ctx ); +void ui_flush( ui_context *ctx, enum ui_shader shader, void *shader_data ); +void ui_rect_center( ui_rect parent, ui_rect rect ); +void ui_set_area( ui_context *ctx, i32 width, i32 height ); + +/* text drawing */ +ui_px ui_text_line_width( ui_context *ctx, const char *str ); +ui_px ui_text_string_height( ui_context *ctx, const char *str ); +ui_px ui_text_aligned_x( ui_context *ctx, + const char *str, ui_rect rect, ui_px scale, + enum ui_align align ); +u32 ui_ntext( ui_context *ctx, + ui_rect rect, const char *str, u32 len, ui_px scale, + enum ui_align align, u32 colour ); +u32 ui_text( ui_context *ctx, + ui_rect rect, const char *str, ui_px scale, + enum ui_align align, u32 colour ); + +/* colour schemes */ +u32 ui_colour( ui_context *ctx, enum ui_scheme_colour id ); +u32 ui_colourcont( ui_context *ctx, enum ui_scheme_colour id ); + +/* generic colour functions */ +void ui_hex_to_norm( u32 hex, v4f norm ); +u32 v4f_u32_colour( v4f colour ); +u32 ui_opacity( u32 colour, f32 opacity ); + +/* standard widgets & controls */ +ui_px ui_standard_widget_height( ui_context *ctx, ui_px count ); +void ui_standard_widget( ui_context *ctx, + ui_rect inout_panel, ui_rect out_rect, ui_px count ); +void ui_panel( ui_context *ctx, ui_rect in_rect, ui_rect out_panel ); +void ui_label( ui_context *ctx, + ui_rect rect, const char *text, ui_px size, + ui_px gap, ui_rect r ); +void ui_info( ui_context *ctx, ui_rect inout_panel, const char *text ); +void ui_image( ui_context *ctx, ui_rect rect, void *resource ); + +enum ui_button_state ui_button_base( ui_context *ctx, ui_rect rect ); + +/* TODO: split this out into a formatless button and one that auto fills */ +enum ui_button_state ui_colourbutton( ui_context *ctx, ui_rect rect, + enum ui_scheme_colour colour, + enum ui_scheme_colour hover_colour, + enum ui_scheme_colour hi_colour ); +enum ui_button_state ui_colourbutton_text( + ui_context *ctx, + ui_rect rect, const char *string, ui_px scale, + enum ui_scheme_colour colour ); + +enum ui_button_state ui_button_text( ui_context *ctx, ui_rect rect, + const char *string, ui_px scale ); +enum ui_button_state ui_button( ui_context *ctx, + ui_rect inout_panel, const char *string ); +void ui_postrender( ui_context *ctx, f32 delta_time ); +enum ui_button_state ui_checkbox_base( ui_context *ctx, + ui_rect box, i32 *data ); +int ui_checkbox( ui_context *ctx, + ui_rect inout_panel, const char *str_label, i32 *data ); +void ui_enum( ui_context *ctx, ui_rect inout_panel, const char *str_label, + struct ui_enum_opt *options, u32 len, i32 *value ); + +enum ui_button_state ui_slider_base( + ui_context *ctx, + ui_rect box, f32 min, f32 max, f32 *value, f32 *out_t ); +void ui_slider_text( ui_context *ctx, + ui_rect box, const char *format, f32 value ); +bool ui_slider_standard( ui_context *ctx, + ui_rect box, f32 min, f32 max, f32 *value, + const char *format ); +bool ui_slider( ui_context *ctx, ui_rect inout_panel, const char *str_label, + f32 min, f32 max, f32 *value ); +void ui_colourpicker( ui_context *ctx, + ui_rect inout_panel, const char *str_label, v4f value ); +void _ui_textbox_move_cursor( ui_context *ctx, int *cursor0, int *cursor1, + int dir, int snap_together ); +int _ui_textbox_delete_char( ui_context *ctx, int direction ); +void ui_start_modal( ui_context *ctx, const char *message, u32 options ); + +void _ui_textbox_put_char( ui_context *ctx, char c ); +void _ui_textbox_left_select( ui_context *ctx ); +void _ui_textbox_left( ui_context *ctx ); +void _ui_textbox_up( ui_context *ctx ); +void _ui_textbox_down( ui_context *ctx ); +void _ui_textbox_right_select( ui_context *ctx ); +void _ui_textbox_right( ui_context *ctx ); +void _ui_textbox_backspace( ui_context *ctx ); +void _ui_textbox_delete( ui_context *ctx ); +void _ui_textbox_home_select( ui_context *ctx ); +void _ui_textbox_home( ui_context *ctx ); +void _ui_textbox_end_select( ui_context *ctx ); +void _ui_textbox_end( ui_context *ctx ); +void _ui_textbox_select_all( ui_context *ctx ); +void _ui_textbox_cut( ui_context *ctx ); +void _ui_textbox_enter( ui_context *ctx ); +void _ui_textbox_to_clipboard( ui_context *ctx ); +void _ui_textbox_clipboard_paste( ui_context *ctx ); + +int ui_textbox( ui_context *ctx, ui_rect inout_panel, const char *label, + char *buf, u32 len, u32 lines, u32 flags, + struct ui_textbox_callbacks *callbacks ); +void ui_tabs( ui_context *ctx, ui_rect inout_panel, ui_rect out_content_panel, + const char **titles, u32 count, i32 *page ); +void ui_defocus_all( ui_context *ctx ); + +void ui_start_modal( ui_context *ctx, const char *message, u32 options ); + +void ui_proc_utf8( ui_context *ctx, const char *text ); +void ui_dev_colourview( ui_context *ctx ); + +extern vg_font_sheet vg_default_font_sheet; +extern vg_font_face vgf_default_small, vgf_default_large, vgf_default_title; diff --git a/vg_ui/imgui_impl_opengl.c b/vg_ui/imgui_impl_opengl.c new file mode 100644 index 0000000..18edb3e --- /dev/null +++ b/vg_ui/imgui_impl_opengl.c @@ -0,0 +1,575 @@ +#include "vg_opengl.h" +#include "vg_shader.h" +#include "vg_ui/imgui.h" +#include "vg_engine.h" + +struct vg_ui vg_ui = +{ + .ctx = + { + .scheme = + { + [ k_ui_bg+0 ] = UI_RGB( 0x1d2021 ), + [ k_ui_bg+1 ] = UI_RGB( 0x282828 ), + [ k_ui_bg+2 ] = UI_RGB( 0x3c3836 ), + [ k_ui_bg+3 ] = UI_RGB( 0x504945 ), + [ k_ui_bg+4 ] = UI_RGB( 0x665c54 ), + [ k_ui_bg+5 ] = UI_RGB( 0x7c6f64 ), + [ k_ui_bg+6 ] = UI_RGB( 0x928374 ), + [ k_ui_bg+7 ] = UI_RGB( 0xa89984 ), + + [ k_ui_fg+0 ] = UI_RGB( 0xebdbb2 ), + [ k_ui_fg+1 ] = UI_RGB( 0xfbf1c7 ), + [ k_ui_fg+2 ] = UI_RGB( 0xd5c4a1 ), + [ k_ui_fg+3 ] = UI_RGB( 0xbdae93 ), + [ k_ui_fg+4 ] = UI_RGB( 0xa89984 ), + [ k_ui_fg+5 ] = UI_RGB( 0x000000 ), + [ k_ui_fg+6 ] = UI_RGB( 0x000000 ), + [ k_ui_fg+7 ] = UI_RGB( 0x000000 ), + + [ k_ui_red ] = UI_RGB( 0xcc241d ), + [ k_ui_orange ] = UI_RGB( 0xd65d0e ), + [ k_ui_yellow ] = UI_RGB( 0xd79921 ), + [ k_ui_green ] = UI_RGB( 0x98971a ), + [ k_ui_aqua ] = UI_RGB( 0x689d6a ), + [ k_ui_blue ] = UI_RGB( 0x458588 ), + [ k_ui_purple ] = UI_RGB( 0xb16286 ), + [ k_ui_gray ] = UI_RGB( 0x928374 ), + [ k_ui_red + k_ui_brighter ] = UI_RGB( 0xfb4934 ), + [ k_ui_orange + k_ui_brighter ] = UI_RGB( 0xfe8019 ), + [ k_ui_yellow + k_ui_brighter ] = UI_RGB( 0xfabd2f ), + [ k_ui_green + k_ui_brighter ] = UI_RGB( 0xb8bb26 ), + [ k_ui_aqua + k_ui_brighter ] = UI_RGB( 0x8ec07c ), + [ k_ui_blue + k_ui_brighter ] = UI_RGB( 0x83a598 ), + [ k_ui_purple + k_ui_brighter ] = UI_RGB( 0xd3869b ), + [ k_ui_gray + k_ui_brighter ] = UI_RGB( 0xa89984 ), + }, + .font = &vgf_default_small, + .scale = 1, + .padding = 8, + .widget_height = 28 + }, + .colour = {1.0f,1.0f,1.0f,1.0f}, + .bg_inverse_ratio = {1,1}, +}; + +static struct vg_shader _shader_ui ={ + .name = "[vg] ui - transparent", + .vs = { + .orig_file = NULL, + .static_src = + "layout (location=0) in vec2 a_co;" + "layout (location=1) in vec2 a_uv;" + "layout (location=2) in vec4 a_colour;" + "uniform mat3 uPv;" + "uniform vec2 uBGInverseRatio;" + "uniform vec2 uInverseFontSheet;" + "" + "out vec4 aTexCoords;" + "out vec4 aColour;" + "" + "void main(){" + "vec4 proj_pos = vec4( uPv * vec3( a_co, 1.0 ), 1.0 );" + "gl_Position = proj_pos;" + "aTexCoords = vec4( a_uv * uInverseFontSheet, " + " (proj_pos.xy*0.5+0.5) * uBGInverseRatio );" + "aColour = a_colour;" + "}", + }, + .fs = { + .orig_file = NULL, + .static_src = + "uniform sampler2D uTexGlyphs;" + "uniform sampler2D uTexBG;" + "uniform vec4 uColour;" + "uniform float uSpread;" + "out vec4 FragColor;" + "" + "in vec4 aTexCoords;" + "in vec4 aColour;" + "" + "vec2 rand_hash22( vec2 p ){" + "vec3 p3 = fract(vec3(p.xyx) * 213.8976123);" + "p3 += dot(p3, p3.yzx+19.19);" + "return fract(vec2((p3.x + p3.y)*p3.z, (p3.x+p3.z)*p3.y));" + "}" + "" + "void main(){" + "vec4 diffuse = aColour;" + + "vec4 avg = vec4(0.0);" + + "if( aColour.a == 0.0 ){" + "avg = aColour;" + "avg.a = texture( uTexGlyphs, aTexCoords.xy ).r;" + "}" + "else{" + "if( uSpread > 0.0001 ){" + "for( int i=0; i<4; i ++ ){" + "vec2 spread = rand_hash22(aTexCoords.zw+vec2(float(i)));" + "avg += texture( uTexBG, aTexCoords.zw + (spread-0.5)*uSpread );" + "}" + "avg *= 0.25;" + "avg.a = 1.0;" + "avg.rgb = mix( avg.rgb, aColour.rgb, aColour.a );" + "}" + "else{" + "avg = aColour;" + "}" + "}" + + "FragColor = avg * uColour;" + "}" + } +}; + +static struct vg_shader _shader_ui_image = { + .name = "[vg] ui_image", + .vs = + { + .orig_file = NULL, + .static_src = + "layout (location=0) in vec2 a_co;" + "layout (location=1) in vec2 a_uv;" + "layout (location=2) in vec4 a_colour;" + "uniform mat3 uPv;" + + "out vec2 aTexCoords;" + "out vec4 aColour;" + "out vec2 aWsp;" + + "void main()" + "{" + "gl_Position = vec4( uPv * vec3( a_co, 1.0 ), 1.0 );" + "aTexCoords = a_uv * 0.00390625;" + "aColour = a_colour;" + + "aWsp = a_co;" + "}", + }, + .fs = + { + .orig_file = NULL, + .static_src = + "uniform sampler2D uTexImage;" + "uniform vec4 uColour;" + "out vec4 FragColor;" + + "in vec2 aTexCoords;" + "in vec4 aColour;" + "in vec2 aWsp;" + + "void main()" + "{" + "vec4 colour = texture( uTexImage, aTexCoords );" + "FragColor = colour * uColour;" + "}" + } +}; + +static struct vg_shader _shader_ui_image_grad = { + .name = "[vg] ui_image (gradient)", + .vs = + { + .orig_file = NULL, + .static_src = + "layout (location=0) in vec2 a_co;" + "layout (location=1) in vec2 a_uv;" + "layout (location=2) in vec4 a_colour;" + "uniform mat3 uPv;" + + "out vec2 aTexCoords;" + "out vec4 aColour;" + "out vec2 aWsp;" + + "void main()" + "{" + "gl_Position = vec4( uPv * vec3( a_co, 1.0 ), 1.0 );" + "aTexCoords = a_uv * 0.00390625;" + "aColour = a_colour;" + + "aWsp = a_co;" + "}", + }, + .fs = + { + .orig_file = NULL, + .static_src = + "uniform sampler2D uTexImage;" + "uniform vec4 uColour;" + "out vec4 FragColor;" + + "in vec2 aTexCoords;" + "in vec4 aColour;" + "in vec2 aWsp;" + + "void main()" + "{" + "vec4 colour = texture( uTexImage, aTexCoords );" + "FragColor = vec4(vec3(colour.r)*uColour.rgb,1.0);" + "}" + } +}; + +static struct vg_shader _shader_ui_hsv = { + .name = "[vg] ui_hsv", + .vs = { + .orig_file = NULL, + .static_src = + "layout (location=0) in vec2 a_co;" + "layout (location=1) in vec2 a_uv;" + "layout (location=2) in vec4 a_colour;" + "uniform mat3 uPv;" + + "out vec2 aTexCoords;" + "out vec4 aColour;" + "out vec2 aWsp;" + + "void main()" + "{" + "gl_Position = vec4( uPv * vec3( a_co, 1.0 ), 1.0 );" + "aTexCoords = a_uv * 0.00390625;" + "aColour = a_colour;" + + "aWsp = a_co;" + "}", + }, + .fs = + { + .orig_file = NULL, + .static_src = + "uniform float uHue;" + "out vec4 FragColor;" + + "in vec2 aTexCoords;" + "in vec4 aColour;" + "in vec2 aWsp;" + + "void main()" + "{" + "vec3 c = vec3( uHue, aTexCoords );" + "vec4 K = vec4(1.0,2.0/3.0,1.0/3.0,3.0);" + "vec3 p = abs(fract(c.xxx+K.xyz)*6.0 - K.www);" + "vec3 colour = c.z*mix(K.xxx,clamp(p-K.xxx,0.0,1.0),c.y);" + "FragColor = vec4( colour, 1.0 );" + "}" + } +}; + +void vg_ui_set_screen( i32 width, i32 height ) +{ + ui_set_area( &vg_ui.ctx, width, height ); + m3x3_identity( vg_ui.pv ); + m3x3_translate( vg_ui.pv, (v3f){ -1.0f, 1.0f, 0.0f } ); + m3x3_scale( vg_ui.pv, (v3f){ 1.0f/((f32)width*0.5f), + -1.0f/((f32)height*0.5f), 1.0f } ); +} +void ui_impl_render_batch( ui_context *ctx, ui_batch *batch, + enum ui_shader shader, void *shader_data ) +{ + glBindVertexArray( vg_ui.vao ); + glBindBuffer( GL_ARRAY_BUFFER, vg_ui.vbo ); + glBufferSubData( GL_ARRAY_BUFFER, + batch->vert_offset, + batch->vert_count*sizeof(ui_vert), + batch->vert_buf ); + + glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, vg_ui.ebo ); + glBufferSubData( GL_ELEMENT_ARRAY_BUFFER, + batch->indice_offset, + batch->indice_count*sizeof(u16), + batch->indice_buf ); + + glDisable( GL_DEPTH_TEST ); + glDisable( GL_CULL_FACE ); + glEnable( GL_BLEND ); + glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); + glBlendEquation( GL_FUNC_ADD ); + + if( shader == k_ui_shader_colour ) + { + glUseProgram( _shader_ui.id ); + glUniformMatrix3fv( glGetUniformLocation( _shader_ui.id, "uPv" ), 1, + GL_FALSE, (float *)vg_ui.pv ); + glUniform4fv( glGetUniformLocation( _shader_ui.id, "uColour" ), 1, + vg_ui.colour ); + + glActiveTexture( GL_TEXTURE0 ); + glBindTexture( GL_TEXTURE_2D, vg_ui.tex_glyphs ); + glUniform1i( glGetUniformLocation( _shader_ui.id, "uTexGlyphs" ), 0 ); + + glActiveTexture( GL_TEXTURE1 ); + glBindTexture( GL_TEXTURE_2D, vg_ui.tex_bg ); + glUniform1i( glGetUniformLocation( _shader_ui.id, "uTexBG" ), 1 ); + glUniform1f( glGetUniformLocation( _shader_ui.id, "uSpread" ), + vg_ui.frosting ); + glUniform2fv( glGetUniformLocation( _shader_ui.id, "uBGInverseRatio" ), + 1, vg_ui.bg_inverse_ratio ); + glUniform2fv( glGetUniformLocation( _shader_ui.id, "uInverseFontSheet" ), + 1, vg_ui.inverse_font_sheet ); + } + else if( shader == k_ui_shader_image ) + { + glUseProgram( _shader_ui_image.id ); + glUniformMatrix3fv( glGetUniformLocation( _shader_ui_image.id, "uPv" ), 1, + GL_FALSE, (float *)vg_ui.pv ); + glUniform1i( glGetUniformLocation(_shader_ui_image.id,"uTexImage"), 0 ); + glUniform4fv( glGetUniformLocation( _shader_ui_image.id, "uColour" ), 1, + vg_ui.colour ); + + struct ui_batch_shader_data_image *inf = shader_data; + GLuint *image = inf->resource; + + glActiveTexture( GL_TEXTURE0 ); + glBindTexture( GL_TEXTURE_2D, *image ); + } + else if( shader == k_ui_shader_grad ) + { + glUseProgram( _shader_ui_image_grad.id ); + glUniformMatrix3fv( + glGetUniformLocation( _shader_ui_image_grad.id, "uPv" ), 1, + GL_FALSE, (float *)vg_ui.pv ); + glUniform1i( + glGetUniformLocation(_shader_ui_image_grad.id,"uTexImage"), 0 ); + glUniform4fv( glGetUniformLocation( _shader_ui_image.id, "uColour" ), 1, + vg_ui.colour ); + + struct ui_batch_shader_data_image *inf = shader_data; + GLuint *image = inf->resource; + + glActiveTexture( GL_TEXTURE0 ); + glBindTexture( GL_TEXTURE_2D, *image ); + } + else if( shader == k_ui_shader_hsv ) + { + struct ui_batch_shader_data_hsv *inf = shader_data; + glUseProgram( _shader_ui_hsv.id ); + glUniformMatrix3fv( glGetUniformLocation( _shader_ui_hsv.id, "uPv" ), 1, + GL_FALSE, (float *)vg_ui.pv ); + glUniform1f( glGetUniformLocation(_shader_ui_hsv.id,"uHue"), inf->hue ); + } + else + { + vg_fatal_condition(); + vg_info( "Invalid UI shader (%d)\n", shader ); + vg_fatal_exit(); + } + + glDrawElements( GL_TRIANGLES, batch->indice_count, GL_UNSIGNED_SHORT, + (void *)((size_t)batch->indice_offset) ); +} + +void vg_ui_post_update(void) +{ + if( vg_ui.ctx.wants_mouse ) + { + SDL_SetWindowGrab( vg.window, SDL_FALSE ); + SDL_SetRelativeMouseMode( SDL_FALSE ); + } + else + { + SDL_SetWindowGrab( vg.window, SDL_TRUE ); + SDL_SetRelativeMouseMode( SDL_TRUE ); + } + + SDL_SetCursor( vg_ui.cursor_map[ vg_ui.ctx.cursor ] ); + SDL_ShowCursor(1); +} + +void vg_ui_set_mouse_pos( ui_px x, ui_px y ) +{ + SDL_WarpMouseInWindow( vg.window, x, y ); + vg_ui.ctx.mouse[0] = x; + vg_ui.ctx.mouse[1] = y; + vg_ui.ctx.mouse_pos_overriden = 1; +} + +void vg_ui_handle_sdl_key( ui_context *ctx, SDL_Keysym ev ) +{ + if( ctx->focused_control_type != k_ui_control_textbox ) + { + return; + } + + struct textbox_mapping + { + u16 mod; + SDL_Keycode key; + + void (*handler)( ui_context *ctx ); + } + mappings[] = + { + { 0, SDLK_LEFT, _ui_textbox_left }, + { KMOD_SHIFT, SDLK_LEFT, _ui_textbox_left_select }, + { 0, SDLK_RIGHT, _ui_textbox_right }, + { KMOD_SHIFT, SDLK_RIGHT, _ui_textbox_right_select }, + { 0, SDLK_DOWN, _ui_textbox_down }, + { 0, SDLK_UP, _ui_textbox_up }, + { 0, SDLK_BACKSPACE, _ui_textbox_backspace }, + { KMOD_SHIFT, SDLK_BACKSPACE, _ui_textbox_backspace }, + { KMOD_CTRL, SDLK_BACKSPACE, _ui_textbox_backspace }, + { 0, SDLK_DELETE, _ui_textbox_delete }, + { 0, SDLK_HOME, _ui_textbox_home }, + { KMOD_SHIFT, SDLK_HOME, _ui_textbox_home_select }, + { 0, SDLK_END, _ui_textbox_end }, + { KMOD_SHIFT, SDLK_END, _ui_textbox_end_select }, + { KMOD_CTRL, SDLK_a, _ui_textbox_select_all }, + { KMOD_CTRL, SDLK_c, _ui_textbox_to_clipboard }, + { KMOD_CTRL, SDLK_x, _ui_textbox_cut }, + { KMOD_CTRL, SDLK_v, _ui_textbox_clipboard_paste }, + { 0, SDLK_RETURN, _ui_textbox_enter }, + { 0, SDLK_ESCAPE, ui_defocus_all }, + }; + + SDL_Keymod mod = 0; + + if( ev.mod & KMOD_SHIFT ) + mod |= KMOD_SHIFT; + + if( ev.mod & KMOD_CTRL ) + mod |= KMOD_CTRL; + + if( ev.mod & KMOD_ALT ) + mod |= KMOD_ALT; + + for( int i=0; ikey == ev.sym ) + { + if( mapping->mod == 0 ) + { + if( mod == 0 ) + { + mapping->handler( ctx ); + return; + } + } + else if( (mod & mapping->mod) == mapping->mod ) + { + mapping->handler( ctx ); + return; + } + } + } +} + +bool _wrap_sdl_hasclipboard_text(void) +{ + return SDL_HasClipboardText(); +} + +void vg_ui_init(void) +{ + vg_compile_shader( &_shader_ui ); + vg_compile_shader( &_shader_ui_hsv ); + vg_compile_shader( &_shader_ui_image ); + vg_compile_shader( &_shader_ui_image_grad ); + + u32 verts = 30000, indices = 20000; + ui_init( &vg_ui.ctx, + malloc( sizeof(ui_vert)*verts ), verts, + malloc( sizeof(u16)*indices ), indices ); + + /* callbacks */ + vg_ui.ctx.render_batch = ui_impl_render_batch; + + vg_ui.ctx.have_clipboard_text = _wrap_sdl_hasclipboard_text; + vg_ui.ctx.get_clipboard_text = SDL_GetClipboardText; + vg_ui.ctx.free_clipboard_text = SDL_free; + vg_ui.ctx.set_clipboard_text = SDL_SetClipboardText; + vg_ui.ctx.start_text_input = SDL_StartTextInput; + vg_ui.ctx.stop_text_input = SDL_StopTextInput; + + /* Generate the buffer we are gonna be drawing to */ + glGenVertexArrays( 1, &vg_ui.vao ); + glGenBuffers( 1, &vg_ui.vbo ); + glGenBuffers( 1, &vg_ui.ebo ); + + glBindVertexArray( vg_ui.vao ); + glBindBuffer( GL_ARRAY_BUFFER, vg_ui.vbo ); + glBufferData( GL_ARRAY_BUFFER, verts*sizeof(ui_vert), NULL, GL_DYNAMIC_DRAW ); + + glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, vg_ui.ebo ); + glBufferData( GL_ELEMENT_ARRAY_BUFFER, + indices*sizeof(u16), NULL, GL_DYNAMIC_DRAW ); + + /* Set pointers */ + u32 const stride = sizeof(ui_vert); + + /* XY */ + glVertexAttribPointer( 0, 2, GL_SHORT, GL_FALSE, stride, + (void *)offsetof( ui_vert, co ) ); + glEnableVertexAttribArray( 0 ); + + /* UV */ + glVertexAttribPointer( 1, 2, GL_UNSIGNED_SHORT, GL_FALSE, stride, + (void *)offsetof( ui_vert, uv ) ); + glEnableVertexAttribArray( 1 ); + + /* COLOUR */ + glVertexAttribPointer( 2, 4, GL_UNSIGNED_BYTE, GL_TRUE, stride, + (void *)offsetof( ui_vert, colour ) ); + glEnableVertexAttribArray( 2 ); + + vg_ui.cursor_map[ k_ui_cursor_default ] = + SDL_CreateSystemCursor( SDL_SYSTEM_CURSOR_ARROW ); + vg_ui.cursor_map[ k_ui_cursor_hand ] = + SDL_CreateSystemCursor( SDL_SYSTEM_CURSOR_HAND ); + vg_ui.cursor_map[ k_ui_cursor_ibeam ] = + SDL_CreateSystemCursor( SDL_SYSTEM_CURSOR_IBEAM ); + + /* font + * ----------------------------------------------------- + */ + + /* Load default font */ + + vg_font_sheet *sheet = &vg_default_font_sheet; + u32 pixels = 0, + total = sheet->w*sheet->h, + data = 0; + + vg_linear_clear( vg_mem.scratch ); + u8 *image = vg_linear_alloc( vg_mem.scratch, total ); + + while( pixels < total ) + { + for( int b = 31; b >= 0; b-- ) + { + image[ pixels ++ ] = (sheet->bitmap[data] & (0x1u << b))? 0xffu: 0x00u; + + if( pixels >= total ) + { + total = 0; + break; + } + } + data++; + } + + vg_ui.inverse_font_sheet[0] = 1.0/(f64)sheet->w; + vg_ui.inverse_font_sheet[1] = 1.0/(f64)sheet->h; + + glGenTextures( 1, &vg_ui.tex_glyphs ); + glBindTexture( GL_TEXTURE_2D, vg_ui.tex_glyphs ); + glTexImage2D( GL_TEXTURE_2D, 0, GL_R8, sheet->w, sheet->h, 0, + GL_RED, GL_UNSIGNED_BYTE, image ); + + glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE ); + glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE ); + glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST ); + glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST ); +} + +void ui_free(void) +{ + vg_free_shader( &_shader_ui ); + vg_free_shader( &_shader_ui_hsv ); + vg_free_shader( &_shader_ui_image ); + vg_free_shader( &_shader_ui_image_grad ); + free( vg_ui.ctx.indice_buffer ); + free( vg_ui.ctx.vertex_buffer ); +}