vtgl.c (34737B)
1 /* See LICENSE for copyright details */ 2 3 /* TODO(rnp): 4 * [ ]: refactor: instead of having a callback for key input it is probably better for 5 * the platform to pass an in order list of key events last frame 6 * [ ]: refactor: remove os_... includes from vtgl.h 7 * [ ]: refactor: stream_ensure_terminator 8 * [ ]: refactor: push_s8 (into arena); rename push_s8 to draw_s8 9 * [ ]: refactor: resize renderer work item 10 * [ ]: refactor: remove queued_render (just use a work item) 11 * [ ]: refactor: why is there interaction code in both threads? 12 */ 13 14 /* TODO: define this ourselves since we should be loading it at runtime */ 15 /* TODO(rnp): this is only valid on platforms where we can directly link libGL */ 16 #define GL_GLEXT_PROTOTYPES 17 #include <GL/glcorearb.h> 18 19 #include "vtgl.h" 20 21 #include "config.h" 22 23 #include "font.c" 24 #include "terminal.c" 25 26 #define REVERSE_VIDEO_MASK (Colour){.r = 0xff, .g = 0xff, .b = 0xff}.rgba 27 28 #define VERTEX_SHADER_TEXT \ 29 "#version 430 core\n" \ 30 "\n" \ 31 "layout(location = 0) in vec2 vertex_position;\n" \ 32 "layout(location = 1) in vec2 vertex_texture_coordinate;\n" \ 33 "layout(location = 2) in vec4 vertex_colour;\n" \ 34 "\n" \ 35 "layout(location = 0) out vec2 fragment_texture_coordinate;\n" \ 36 "layout(location = 1) out vec4 fragment_colour;\n" \ 37 "\n" \ 38 "layout(location = 0) uniform mat4 u_Pmat;\n" \ 39 "\n" \ 40 "void main()\n" \ 41 "{\n" \ 42 " fragment_texture_coordinate = vertex_texture_coordinate;\n" \ 43 " fragment_colour = vertex_colour;\n" \ 44 "\n" \ 45 " gl_Position = u_Pmat * vec4(vertex_position, 0.0, 1.0);\n" \ 46 "}\n" 47 48 function void 49 set_projection_matrix(GLCtx *gl, u32 stage) 50 { 51 f32 w = gl->window_size.w; 52 f32 h = gl->window_size.h; 53 54 f32 pmat[4 * 4] = { 55 2.0/w, 0.0, 0.0, -1.0, 56 0.0, 2.0/h, 0.0, -1.0, 57 0.0, 0.0, -1.0, 0.0, 58 0.0, 0.0, 0.0, 1.0, 59 }; 60 61 glProgramUniformMatrix4fv(gl->programs[stage], SHADER_PMAT_LOC, 1, GL_TRUE, pmat); 62 } 63 64 function u32 65 compile_shader(Arena a, u32 type, s8 shader, s8 name) 66 { 67 u32 result = glCreateShader(type); 68 glShaderSource(result, 1, (const char **)&shader.data, (int *)&shader.len); 69 glCompileShader(result); 70 71 i32 res = 0; 72 glGetShaderiv(result, GL_COMPILE_STATUS, &res); 73 if (res != GL_TRUE) { 74 Stream s = arena_stream(a); 75 stream_push_s8(&s, name); 76 stream_push_s8(&s, s8(": failed to compile\n")); 77 78 i32 len = 0, out_len = 0; 79 glGetShaderiv(result, GL_INFO_LOG_LENGTH, &len); 80 glGetShaderInfoLog(result, len, &out_len, (c8 *)(s.data + s.count)); 81 stream_commit(&s, out_len); 82 os_write_err_msg(stream_to_s8(&s)); 83 glDeleteShader(result); 84 result = 0; 85 } 86 87 return result; 88 } 89 90 function u32 91 link_shader_program(Arena a, u32 *shader_ids, u32 shader_id_count) 92 { 93 u32 result = glCreateProgram(); 94 for (u32 i = 0; i < shader_id_count; i++) 95 glAttachShader(result, shader_ids[i]); 96 glLinkProgram(result); 97 98 i32 success = 0; 99 glGetProgramiv(result, GL_LINK_STATUS, &success); 100 if (success == GL_FALSE) { 101 Stream s = arena_stream(a); 102 stream_push_s8(&s, s8("shader link error: ")); 103 104 i32 len = 0; 105 glGetProgramInfoLog(result, s.capacity - s.count, &len, (c8 *)(s.data + s.count)); 106 stream_commit(&s, len); 107 os_write_err_msg(stream_to_s8(&s)); 108 glDeleteProgram(result); 109 result = 0; 110 } 111 112 return result; 113 } 114 115 function u32 116 load_shader(Arena arena, s8 vertex_text, s8 fragment_text, s8 info, s8 label) 117 { 118 u32 result = 0; 119 u32 vs_id = compile_shader(arena, GL_VERTEX_SHADER, vertex_text, s8("Vertex Shader")); 120 u32 fs_id = compile_shader(arena, GL_FRAGMENT_SHADER, fragment_text, info); 121 122 if (vs_id && fs_id) result = link_shader_program(arena, (u32 []){vs_id, fs_id}, 2); 123 glDeleteShader(vs_id); 124 glDeleteShader(fs_id); 125 126 if (result) { 127 Stream s = arena_stream(arena); 128 stream_push_s8(&s, s8("loaded: ")); 129 stream_push_s8(&s, info); 130 stream_push_byte(&s, '\n'); 131 os_write_err_msg(stream_to_s8(&s)); 132 LABEL_GL_OBJECT(GL_PROGRAM, result, label); 133 } 134 135 return result; 136 } 137 138 function void 139 update_uniforms(GLCtx *gl, ShaderID stage) 140 { 141 switch (stage) { 142 case SID_POST: { 143 #define X(name) gl->post.name = glGetUniformLocation(gl->programs[stage], "u_" #name); 144 GL_POST_UNIFORMS 145 #undef X 146 } break; 147 default: break; 148 } 149 } 150 151 function void 152 reload_shader(OS *os, GLCtx *gl, ShaderReloadContext *src, Arena a) 153 { 154 s8 fs_text = os->read_file(src->path.data, &a); 155 if (fs_text.len) { 156 u32 new_program = load_shader(a, s8(VERTEX_SHADER_TEXT), fs_text, src->path, src->label); 157 if (new_program) { 158 glDeleteProgram(gl->programs[src->shader]); 159 gl->programs[src->shader] = new_program; 160 update_uniforms(gl, src->shader); 161 set_projection_matrix(gl, src->shader); 162 } 163 } 164 } 165 166 function v4 167 normalize_colour(Colour c) 168 { 169 return (v4){.r = c.r / 255.0f, .g = c.g / 255.0f, .b = c.b / 255.0f, .a = c.a / 255.0f}; 170 } 171 172 function void 173 clear_colour(void) 174 { 175 Colour c = g_colours.data[g_colours.bgidx]; 176 v4 cc = normalize_colour(c); 177 glClearColor(cc.r, cc.g, cc.b, cc.a); 178 glClear(GL_COLOR_BUFFER_BIT); 179 } 180 181 function v2 182 get_occupied_size(Term *t) 183 { 184 v2 cs = fa_cell_size(&t->render_thread.fa); 185 v2 result = {.x = t->size.w * cs.w, .y = t->size.h * cs.h}; 186 return result; 187 } 188 189 function v2 190 get_terminal_top_left(Term *t) 191 { 192 iv2 ws = t->render_thread.gl.window_size; 193 v2 os = get_occupied_size(t); 194 v2 delta = {.x = ws.w - os.w, .y = ws.h - os.h}; 195 v2 result = {.x = delta.x / 2, .y = ws.h - delta.y / 2}; 196 return result; 197 } 198 199 function void 200 resize_terminal(Term *t, OS *os, iv2 window_size) 201 { 202 v2 ws = v2_from_iv2(window_size); 203 ws.w -= 2 * g_term_margin.w; 204 ws.h -= 2 * g_term_margin.h; 205 206 iv2 old_size = t->size; 207 v2 cs = fa_cell_size(&t->render_thread.fa); 208 t->size.w = (i32)(ws.w / cs.w); 209 t->size.h = (i32)(ws.h / cs.h); 210 211 if (t->size.w > ARRAY_COUNT(t->tabs) * 32) { 212 t->size.w = ARRAY_COUNT(t->tabs) * 32u; 213 stream_push_s8(&t->error_stream, s8("resize: max terminal width is ")); 214 stream_push_u64(&t->error_stream, t->size.w); 215 stream_push_s8(&t->error_stream, s8("; clamping\n")); 216 os_write_err_msg(stream_to_s8(&t->error_stream)); 217 stream_reset(&t->error_stream, 0); 218 } 219 220 if (!equal_iv2(old_size, t->size)) { 221 t->size = initialize_framebuffer(&t->views[0].fb, t->size); 222 initialize_framebuffer(&t->views[1].fb, t->size); 223 t->state |= TS_NEEDS_REFILL; 224 } 225 226 os->set_terminal_size(t->child, t->size.h, t->size.w, ws.w, ws.h); 227 228 /* TODO(rnp): queue renderer resize */ 229 t->render_thread.gl.flags |= RESIZE_RENDERER; 230 t->state &= ~TS_NEEDS_RESIZE; 231 } 232 233 function void 234 resize(Term *t, OS *os, iv2 window_size) 235 { 236 GLCtx *gl = &t->render_thread.gl; 237 gl->window_size = window_size; 238 239 glViewport(0, 0, window_size.w, window_size.h); 240 241 glActiveTexture(GL_TEXTURE0 + gl->fb_tex_unit); 242 glBindTexture(GL_TEXTURE_2D, gl->fb_tex); 243 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, window_size.w, window_size.h, 0, 244 GL_RGBA, GL_UNSIGNED_BYTE, 0); 245 246 /* NOTE: reactive the glyph texture unit */ 247 glActiveTexture(GL_TEXTURE0); 248 249 u32 buffer_size = t->size.w * t->size.h * sizeof(RenderCell); 250 glDeleteBuffers(1, &gl->render_shader_ssbo); 251 glGenBuffers(1, &gl->render_shader_ssbo); 252 glBindBuffer(GL_SHADER_STORAGE_BUFFER, gl->render_shader_ssbo); 253 glBufferData(GL_SHADER_STORAGE_BUFFER, buffer_size, 0, GL_DYNAMIC_DRAW); 254 glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, gl->render_shader_ssbo); 255 LABEL_GL_OBJECT(GL_BUFFER, gl->render_shader_ssbo, s8("RenderCells")); 256 gl->queued_render = 1; 257 258 259 FontAtlas *fa = &t->render_thread.fa; 260 v2 cs = fa_cell_size(fa); 261 ShaderParameters *sp = &gl->shader_parameters; 262 sp->cell_size = (iv2){.w = cs.w, .h = cs.h}; 263 sp->strike_min = (fa->info.baseline + 0.40 * fa->info.h); 264 sp->strike_max = (fa->info.baseline + 0.48 * fa->info.h); 265 sp->underline_min = (0.89 * fa->info.h); 266 sp->underline_max = (0.96 * fa->info.h); 267 sp->top_left_margin = g_term_margin; 268 sp->margin_colour = g_colours.data[g_colours.bgidx].rgba; 269 //sp->margin_colour = 0x7f003f00; 270 271 sp->term_size_in_pixels = gl->window_size; 272 sp->term_size_in_cells = t->size; 273 274 for (u32 i = 0; i < SID_LAST; i++) 275 set_projection_matrix(gl, i); 276 277 gl->flags &= ~RESIZE_RENDERER; 278 } 279 280 function RenderCtx 281 make_render_ctx(Arena *a, GLCtx *gl, FontAtlas *fa) 282 { 283 RenderCtx result; 284 result.gl = gl; 285 result.fa = fa; 286 result.rpb = alloc(a, RenderPushBuffer, 1); 287 result.a = sub_arena(a, MB(4)); 288 return result; 289 } 290 291 function iv2 292 get_gpu_texture_position(v2 cs, u32 gpu_tile_index) 293 { 294 uv2 gpu_position = {.x = gpu_tile_index & 0xFFFF, .y = gpu_tile_index >> 16}; 295 iv2 result = {.y = gpu_position.y * cs.y, .x = gpu_position.x * cs.x}; 296 return result; 297 } 298 299 function u32 300 get_gpu_glyph_index(Arena a, GLCtx *gl, FontAtlas *fa, u32 codepoint, u32 font_id, 301 enum face_style style, CachedGlyph **out) 302 { 303 u32 *data = render_glyph(&a, fa, codepoint, font_id, style, out); 304 if (data) { 305 CachedGlyph *cg = *out; 306 cg->uploaded_to_gpu = 1; 307 308 v2 cell_size = fa_cell_size(fa); 309 iv2 glyph_position = get_gpu_texture_position(cell_size, cg->gpu_tile_index); 310 ASSERT(glyph_position.x + cell_size.w * cg->tile_count < gl->glyph_bitmap_dim.x); 311 ASSERT(glyph_position.y + cell_size.h < gl->glyph_bitmap_dim.y); 312 313 glTexSubImage2D(GL_TEXTURE_2D, 0, glyph_position.x, glyph_position.y, 314 cell_size.w * cg->tile_count, cell_size.h, 315 GL_RGBA, GL_UNSIGNED_BYTE, data); 316 } 317 ASSERT((*out)->uploaded_to_gpu); 318 return (*out)->gpu_tile_index; 319 } 320 321 /* NOTE: this function assumes we are drawing quads */ 322 function void 323 flush_render_push_buffer(RenderCtx *rc) 324 { 325 BEGIN_TIMED_BLOCK(); 326 if (rc->rpb->count > 0) { 327 u32 n = rc->rpb->count; 328 GLCtx *gl = rc->gl; 329 RenderPushBuffer *rpb = rc->rpb; 330 ASSERT((n % 4) == 0); 331 332 BEGIN_NAMED_BLOCK(upload); 333 glBindBuffer(GL_ARRAY_BUFFER, gl->vbos[0]); 334 glBufferSubData(GL_ARRAY_BUFFER, 0, n * sizeof(*rpb->positions), rpb->positions); 335 glBindBuffer(GL_ARRAY_BUFFER, gl->vbos[1]); 336 glBufferSubData(GL_ARRAY_BUFFER, 0, n * sizeof(*rpb->texture_coordinates), rpb->texture_coordinates); 337 glBindBuffer(GL_ARRAY_BUFFER, gl->vbos[2]); 338 glBufferSubData(GL_ARRAY_BUFFER, 0, n * sizeof(*rpb->colours), rpb->colours); 339 END_NAMED_BLOCK(upload); 340 341 glDrawElements(GL_TRIANGLES, 6 * n / 4, GL_UNSIGNED_INT, 0); 342 } 343 rc->rpb->count = 0; 344 END_TIMED_BLOCK(); 345 } 346 347 function u32 348 get_render_push_buffer_idx(RenderCtx *rc, u32 count) 349 { 350 if (rc->rpb->count + count > RENDER_PUSH_BUFFER_CAP) 351 flush_render_push_buffer(rc); 352 u32 result = rc->rpb->count; 353 rc->rpb->count += count; 354 return result; 355 } 356 357 function void 358 push_rect_full(RenderCtx *rc, Rect r, v4 colour, v2 min_tex_coord, v2 max_tex_coord) 359 { 360 BEGIN_TIMED_BLOCK(); 361 362 u32 idx = get_render_push_buffer_idx(rc, 4); 363 v2 start = r.pos; 364 v2 end = {.x = r.pos.x + r.size.w, .y = r.pos.y + r.size.h}; 365 366 RenderPushBuffer *rpb = rc->rpb; 367 rpb->positions[idx + 0] = (v2){.x = end.x, .y = end.y }; 368 rpb->positions[idx + 1] = (v2){.x = end.x, .y = start.y}; 369 rpb->positions[idx + 2] = (v2){.x = start.x, .y = start.y}; 370 rpb->positions[idx + 3] = (v2){.x = start.x, .y = end.y }; 371 372 rpb->texture_coordinates[idx + 0] = (v2){.x = max_tex_coord.x, .y = max_tex_coord.y}; 373 rpb->texture_coordinates[idx + 1] = (v2){.x = max_tex_coord.x, .y = min_tex_coord.y}; 374 rpb->texture_coordinates[idx + 2] = (v2){.x = min_tex_coord.x, .y = min_tex_coord.y}; 375 rpb->texture_coordinates[idx + 3] = (v2){.x = min_tex_coord.x, .y = max_tex_coord.y}; 376 377 rpb->colours[idx + 0] = colour; 378 rpb->colours[idx + 1] = colour; 379 rpb->colours[idx + 2] = colour; 380 rpb->colours[idx + 3] = colour; 381 382 END_TIMED_BLOCK(); 383 } 384 385 function void 386 push_rect_textured(RenderCtx *rc, Rect r, v4 colour, b32 flip_texture) 387 { 388 v2 min_tex_coord, max_tex_coord; 389 if (!flip_texture) { 390 max_tex_coord = (v2){.x = 1.0f, .y = 1.0f}; 391 min_tex_coord = (v2){.x = 0.0f, .y = 0.0f}; 392 } else { 393 max_tex_coord = (v2){.x = 1.0f, .y = 0.0f}; 394 min_tex_coord = (v2){.x = 0.0f, .y = 1.0f}; 395 } 396 push_rect_full(rc, r, colour, min_tex_coord, max_tex_coord); 397 } 398 399 function void 400 push_rect(RenderCtx *rc, Rect r, v4 colour) 401 { 402 f32 max_x = 1.0f / rc->gl->glyph_bitmap_dim.x; 403 f32 max_y = 1.0f / rc->gl->glyph_bitmap_dim.y; 404 push_rect_full(rc, r, colour, (v2){0}, (v2){.x = max_x, .y = max_y}); 405 } 406 407 function v2 408 push_s8(RenderCtx *rc, v2 pos, v4 colour, u32 font_id, s8 s) 409 { 410 BEGIN_TIMED_BLOCK(); 411 CachedGlyph *cg; 412 v2 start, end, text_size = {0}; 413 v2 scale = {.x = 1.0f / rc->gl->glyph_bitmap_dim.x, .y = 1.0f / rc->gl->glyph_bitmap_dim.y}; 414 415 /* TODO: implement GPU based glyph rasterizing */ 416 pos.x = (u32)pos.x; 417 pos.y = (u32)pos.y; 418 419 while (s.len) { 420 u32 cp = get_utf8(&s); 421 if (cp == (u32)-1) 422 break; 423 424 get_gpu_glyph_index(rc->a, rc->gl, rc->fa, cp, font_id, FS_NORMAL, &cg); 425 cached_glyph_to_uv(rc->fa, cg, &start, &end, scale); 426 427 Rect r = {.pos = pos, .size = {.x = cg->width, .y = cg->height}}; 428 r.pos.x += cg->x0; 429 r.pos.y -= (cg->height + cg->y0); 430 431 push_rect_full(rc, r, colour, start, end); 432 433 text_size.x += cg->advance; 434 pos.x += cg->advance; 435 if (cg->height > text_size.y) 436 text_size.y = cg->height; 437 } 438 text_size.x -= cg->advance; 439 text_size.w += cg->width; 440 441 END_TIMED_BLOCK(); 442 443 return text_size; 444 } 445 446 function v2 447 measure_text(RenderCtx *rc, u32 font_id, s8 text) 448 { 449 BEGIN_TIMED_BLOCK(); 450 451 v2 result = {0}; 452 453 CachedGlyph *cg; 454 while (text.len) { 455 u32 cp = get_utf8(&text); 456 if (cp == (u32)-1) 457 break; 458 459 get_gpu_glyph_index(rc->a, rc->gl, rc->fa, cp, font_id, FS_NORMAL, &cg); 460 if (cg->height > result.y) 461 result.y = cg->height; 462 result.x += cg->advance; 463 } 464 result.x -= cg->advance; 465 result.x += cg->width; 466 467 END_TIMED_BLOCK(); 468 469 return result; 470 } 471 472 /* TODO: this is outdated */ 473 /* NOTE: In this program we render to an offscreen render target that is later drawn 474 * to the screen as a full window quad. Therefore render_framebuffer must take care 475 * to handle all necessary padding (window and cell). Outside of here everyone should 476 * simply care about the terminal in terms of rows and columns (t->size). */ 477 function void 478 render_framebuffer(Term *t, RenderCell *render_buf, TerminalInput *input, Arena arena) 479 { 480 BEGIN_TIMED_BLOCK(); 481 482 RenderThreadContext *ctx = &t->render_thread; 483 TermView *tv = t->views + t->view_idx; 484 iv2 term_size = t->size; 485 /* NOTE: draw whole framebuffer */ 486 for (i32 row = 0; row < term_size.h; row++) { 487 for (i32 col = 0; col < term_size.w; col++) { 488 Cell c = tv->fb.rows[row][col]; 489 RenderCell *rc = render_buf + (row * term_size.w + col); 490 491 CachedGlyph *cg; 492 rc->gpu_glyph = get_gpu_glyph_index(arena, &ctx->gl, &ctx->fa, c.cp, 0, 493 c.bg & FS_MASK, &cg); 494 rc->fg = c.fg; 495 rc->bg = c.bg; 496 497 /* TODO: there is probably a better way to do this */ 498 u32 tiles = cg->tile_count; 499 if (tiles > 1) { 500 ASSERT(tiles == 2); 501 rc[1].gpu_glyph = rc->gpu_glyph + 1; 502 rc[1].fg = rc->fg; 503 rc[1].bg = rc->bg; 504 col++; 505 } 506 } 507 } 508 509 /* NOTE: draw selection if active */ 510 SelectionIterator si = selection_iterator(t->selection.range, tv->fb.rows, term_size.w); 511 for (Cell *c = selection_next(&si); c; c = selection_next(&si)) { 512 RenderCell *rc = render_buf + si.cursor.y * term_size.w + si.cursor.x; 513 rc->fg ^= SHADER_PACK_ATTR(ATTR_INVERSE); 514 } 515 516 END_TIMED_BLOCK(); 517 } 518 519 function void 520 render_cursor(Term *t, b32 focused, Arena a) 521 { 522 BEGIN_TIMED_BLOCK(); 523 524 RenderThreadContext *ctx = &t->render_thread; 525 iv2 curs = t->cursor.pos; 526 Cell *c = &t->views[t->view_idx].fb.rows[curs.y][curs.x]; 527 RenderCell *rc = alloc(&a, RenderCell, 3); 528 529 iz rc_off = 1; 530 iz length = sizeof(RenderCell); 531 iz offset = sizeof(RenderCell) * (curs.y * t->size.w + curs.x); 532 533 CachedGlyph *cg; 534 rc[1].gpu_glyph = get_gpu_glyph_index(a, &ctx->gl, &ctx->fa, c->cp, 0, c->bg & FS_MASK, &cg); 535 rc[1].fg = c->fg; 536 rc[1].bg = c->bg; 537 538 /* NOTE: draw cursor */ 539 if (focused && (!(t->mode.win & WM_HIDECURSOR) && t->scroll_offset == 0)) { 540 rc[1].fg ^= SHADER_PACK_ATTR(ATTR_INVERSE); 541 //if ((t->mode.term & TM_ALTSCREEN) == 0) 542 // rc[1].fg |= SHADER_PACK_ATTR(ATTR_BLINK); 543 544 if (c->bg & ATTR_WIDE) { 545 length *= 2; 546 rc[2].fg ^= SHADER_PACK_ATTR(ATTR_INVERSE); 547 //if ((t->mode.term & TM_ALTSCREEN) == 0) 548 // rc[2].fg |= SHADER_PACK_ATTR(ATTR_BLINK); 549 } else if (c->bg & ATTR_WDUMMY) { 550 rc_off = 0; 551 length *= 2; 552 offset -= sizeof(RenderCell); 553 rc[0].fg ^= SHADER_PACK_ATTR(ATTR_INVERSE); 554 //if ((t->mode.term & TM_ALTSCREEN) == 0) 555 // rc[0].fg |= SHADER_PACK_ATTR(ATTR_BLINK); 556 } 557 } 558 559 glBufferSubData(GL_SHADER_STORAGE_BUFFER, offset, length, rc + rc_off); 560 561 END_TIMED_BLOCK(); 562 } 563 564 function iv2 565 mouse_to_cell_space(Term *t, v2 mouse) 566 { 567 v2 cell_size = fa_cell_size(&t->render_thread.fa); 568 v2 top_left = get_terminal_top_left(t); 569 570 iv2 result; 571 result.x = (i32)((mouse.x - top_left.x) / cell_size.w + 0.5); 572 result.y = (i32)((top_left.y - mouse.y) / cell_size.h + 0.5); 573 574 CLAMP(result.x, 0, t->size.w - 1); 575 CLAMP(result.y, 0, t->size.h - 1); 576 577 return result; 578 } 579 580 function void 581 stream_push_selection(Stream *s, Row *rows, Range sel, u32 term_width) 582 { 583 s->errors |= !is_valid_range(sel); 584 if (s->errors) return; 585 586 SelectionIterator si = selection_iterator(sel, rows, term_width); 587 u32 last_non_space_idx = 0; 588 for (Cell *c = selection_next(&si); c; c = selection_next(&si)) { 589 if (c->bg & ATTR_WDUMMY) 590 continue; 591 592 stream_push_s8(s, utf8_encode(c->cp)); 593 if (!ISSPACE(c->cp)) 594 last_non_space_idx = s->count; 595 596 if (si.next.y != si.cursor.y) { 597 stream_reset(s, last_non_space_idx); 598 stream_push_byte(s, '\n'); 599 } 600 } 601 602 stream_reset(s, last_non_space_idx); 603 } 604 605 function void 606 begin_selection(Term *t, u32 click_count, v2 mouse) 607 { 608 Selection *sel = &t->selection; 609 sel->state = MIN(click_count, SS_WORDS); 610 sel->range.end = INVALID_RANGE_END; 611 612 iv2 cell = mouse_to_cell_space(t, mouse); 613 614 switch (sel->state) { 615 case SS_WORDS: sel->anchor = sel->range = get_word_around_cell(t, cell); break; 616 case SS_CHAR: sel->anchor = get_char_around_cell(t, cell); break; 617 case SS_NONE: break; 618 } 619 620 t->render_thread.gl.queued_render = 1; 621 } 622 623 function void 624 update_selection(Term *t, TerminalInput *input) 625 { 626 if (!input->keys[MOUSE_LEFT].ended_down) 627 return; 628 629 if (input->mouse.x == input->last_mouse.x && input->mouse.y == input->last_mouse.y) 630 return; 631 632 Selection *sel = &t->selection; 633 iv2 new_p = mouse_to_cell_space(t, input->mouse); 634 Range new, old_range = sel->range; 635 switch (sel->state) { 636 case SS_WORDS: new = get_word_around_cell(t, new_p); break; 637 case SS_CHAR: new = get_char_around_cell(t, new_p); break; 638 case SS_NONE: /*TODO: INVALID_CODE_PATH;*/ return; break; 639 } 640 641 if (sel->anchor.start.y < new.start.y) { 642 sel->range.start = sel->anchor.start; 643 sel->range.end = new.end; 644 } else if (sel->anchor.start.y > new.start.y) { 645 sel->range.start = new.start; 646 sel->range.end = sel->anchor.end; 647 } else { 648 if (new.start.x < sel->anchor.start.x) { 649 sel->range.start = new.start; 650 sel->range.end = sel->anchor.end; 651 } else { 652 sel->range.start = sel->anchor.start; 653 sel->range.end = new.end; 654 } 655 } 656 sel->range = normalize_range(sel->range); 657 658 if (!equal_range(old_range, sel->range)) 659 t->render_thread.gl.queued_render = 1; 660 } 661 662 KEYBIND_FN(copy) 663 { 664 Stream buf = arena_stream(t->arena_for_frame); 665 stream_push_selection(&buf, t->views[t->view_idx].fb.rows, t->selection.range, t->size.w); 666 stream_push_byte(&buf, 0); 667 os->set_clipboard(buf.data, buf.count - 1, a.i); 668 return 1; 669 } 670 671 KEYBIND_FN(paste) 672 { 673 b32 bracketed = t->mode.win & WM_BRACKPASTE; 674 675 /* TODO(rnp): we may need to replace '\n' with '\r\n' */ 676 s8 text = c_str_to_s8((char *)os->get_clipboard(a.i)); 677 if (text.len) { 678 if (bracketed) os->write(t->child, s8("\033[200~")); 679 os->write(t->child, text); 680 if (bracketed) os->write(t->child, s8("\033[201~")); 681 } 682 683 return 1; 684 } 685 686 KEYBIND_FN(scroll) 687 { 688 if (t->mode.term & TM_ALTSCREEN) 689 return 0; 690 691 TermView *tv = t->views + t->view_idx; 692 693 /* NOTE: do nothing if there aren't enough lines to scrollback */ 694 if (tv->lines.filled < t->size.h) 695 return 1; 696 697 t->scroll_offset += a.i; 698 CLAMP(t->scroll_offset, 0, tv->lines.filled - (t->size.h - 1)); 699 700 t->state |= TS_NEEDS_REFILL; 701 702 return 1; 703 } 704 705 KEYBIND_FN(zoom) 706 { 707 shift_font_sizes(&t->render_thread.fa, a.i); 708 font_atlas_update(&t->render_thread.fa, t->render_thread.gl.glyph_bitmap_dim); 709 t->state |= TS_NEEDS_RESIZE; 710 return 1; 711 } 712 713 function void 714 report_mouse(Term *t, TerminalInput *input, b32 released, b32 beginning) 715 { 716 if ((t->mode.win & WM_MOUSE_X10) && released) 717 return; 718 719 iv2 pos = mouse_to_cell_space(t, input->mouse); 720 if ((pos.x > (255 - 32)) || (pos.y > (255 - 32))) 721 return; 722 723 /* TODO: pass the button into this function once they are given in order */ 724 /* TODO: extended mouse buttons (up to button 11) should also be encoded */ 725 i32 button = 0; 726 if (input->keys[MOUSE_LEFT].ended_down) button = 1; 727 else if (input->keys[MOUSE_MIDDLE].ended_down) button = 2; 728 else if (input->keys[MOUSE_RIGHT].ended_down) button = 3; 729 else if (input->mouse_scroll.y > 0) button = 4; 730 else if (input->mouse_scroll.y < 0) button = 5; 731 732 i32 value; 733 if (t->mode.win & WM_MOUSE_TRK && !beginning && !released) { 734 if (equal_iv2(t->interaction.last_cell_report, pos)) 735 return; 736 value = 32; 737 } else { 738 value = 0; 739 } 740 t->interaction.last_cell_report = pos; 741 742 if ((t->mode.win & WM_MOUSE_SGR) && !button) 743 value += 0; 744 else if (!button) 745 value += 3; 746 else if (button >= 4) 747 value += 64 + button - 4; 748 else 749 value += button - 1; 750 751 if (!(t->mode.win & WM_MOUSE_X10)) { 752 value += ((input->modifiers & MOD_SHIFT) ? 4 : 0) 753 + ((input->modifiers & MOD_ALT) ? 8 : 0) 754 + ((input->modifiers & MOD_CONTROL) ? 16 : 0); 755 } 756 757 Stream buf = arena_stream(t->arena_for_frame); 758 if (t->mode.win & WM_MOUSE_SGR) { 759 stream_push_s8(&buf, s8("\x1B[<")); 760 stream_push_i64(&buf, value); 761 stream_push_byte(&buf, ';'); 762 stream_push_i64(&buf, pos.x + 1); 763 stream_push_byte(&buf, ';'); 764 stream_push_i64(&buf, pos.y + 1); 765 stream_push_byte(&buf, released? 'm' : 'M'); 766 } else if ((pos.x < (255 - 32)) && (pos.y < (255 - 32))) { 767 stream_push_s8(&buf, s8("\x1B[M")); 768 stream_push_byte(&buf, 32 + value); 769 stream_push_byte(&buf, 32 + pos.x + 1); 770 stream_push_byte(&buf, 32 + pos.y + 1); 771 } else { 772 INVALID_CODE_PATH; 773 return; 774 } 775 776 t->os->write(t->child, stream_to_s8(&buf)); 777 } 778 779 function void 780 begin_terminal_interaction(Term *t, TerminalInput *input, u32 click_count) 781 { 782 if (t->mode.win & WM_MOUSE_MASK) { 783 report_mouse(t, input, 0, 1); 784 } else { 785 if (pressed_last_frame(input->keys + MOUSE_LEFT)) 786 begin_selection(t, click_count, input->mouse); 787 } 788 } 789 790 function b32 791 terminal_interaction(Term *t, OS *os, TerminalInput *input, u32 click_count) 792 { 793 794 b32 should_end_interaction = all_mouse_up(input); 795 if (t->mode.win & WM_MOUSE_MASK) { 796 if (t->mode.win & WM_MOUSE_TRK) 797 report_mouse(t, input, should_end_interaction, 0); 798 } else { 799 update_selection(t, input); 800 if (pressed_last_frame(input->keys + MOUSE_MIDDLE)) 801 paste(t, os, (Arg){.i = OS_CLIPBOARD_SECONDARY}); 802 803 b32 shift_down = input->modifiers & MOD_SHIFT; 804 if (input->mouse_scroll.y) { 805 if (t->mode.term & TM_ALTSCREEN) { 806 iptr child = t->child; 807 if (input->mouse_scroll.y > 0) { 808 if (shift_down) os->write(child, s8("\x1B[5;2~")); 809 else os->write(child, s8("\x19")); 810 } else { 811 if (shift_down) os->write(child, s8("\x1B[6;2~")); 812 else os->write(child, s8("\x05")); 813 } 814 } else { 815 Arg a = {.i = (i32)input->mouse_scroll.y}; 816 if (shift_down) 817 a.i *= 5; 818 scroll(t, os, a); 819 } 820 } 821 } 822 823 return should_end_interaction; 824 } 825 826 DEBUG_EXPORT VTGL_HANDLE_KEYS_FN(vtgl_handle_keys) 827 { 828 Term *t = memory->memory; 829 iptr child = t->child; 830 831 #ifdef _DEBUG 832 if (key == KEY_F1 && action == BUTTON_PRESS) { 833 dump_lines_to_file(t); 834 return; 835 } 836 if (key == KEY_F11 && action == BUTTON_PRESS) { 837 /* TODO: probably move this into the debug frame start */ 838 DebugState *ds = memory->debug_memory; 839 ds->paused = !ds->paused; 840 return; 841 } 842 if (key == KEY_F12 && action == BUTTON_PRESS) { 843 t->render_thread.gl.flags ^= DRAW_DEBUG_OVERLAY; 844 input->window_refreshed = 1; 845 return; 846 } 847 #endif 848 849 /* NOTE: handle mapped keybindings */ 850 u32 enc = ENCODE_KEY(action, modifiers, key); 851 for (u32 i = 0; i < ARRAY_COUNT(g_hotkeys); i++) { 852 struct hotkey *hk = g_hotkeys + i; 853 if (hk->key == enc) { 854 b32 handled = hk->fn(t, os, hk->arg); 855 if (handled) 856 return; 857 } 858 } 859 860 /* NOTE: send control sequences */ 861 if (modifiers & MOD_CONTROL && action != BUTTON_RELEASE) { 862 /* TODO: this is wrong. look up where 8-bit modifiers should be sent */ 863 if (0 && t->mode.win & WM_8BIT) { 864 if (key < 0x7F) { 865 os->write(child, utf8_encode(key | 0x80)); 866 return; 867 } 868 } else if (BETWEEN(key, 0x40, 0x5F)) { 869 os->write(child, utf8_encode(key - 0x40)); 870 return; 871 } 872 } 873 874 /* TODO: construct a hash table of bound keys */ 875 switch (ENCODE_KEY(action, 0, key)) { 876 case ENCODE_KEY(BUTTON_PRESS, 0, KEY_ESCAPE): 877 case ENCODE_KEY(BUTTON_REPEAT, 0, KEY_ESCAPE): { 878 os->write(child, s8("\x1B")); 879 } break; 880 case ENCODE_KEY(BUTTON_PRESS, 0, KEY_TAB): 881 case ENCODE_KEY(BUTTON_REPEAT, 0, KEY_TAB): { 882 os->write(child, s8("\t")); 883 } break; 884 case ENCODE_KEY(BUTTON_PRESS, 0, KEY_ENTER): 885 case ENCODE_KEY(BUTTON_REPEAT, 0, KEY_ENTER): { 886 os->write(child, s8("\r")); 887 } break; 888 case ENCODE_KEY(BUTTON_PRESS, 0, KEY_BACKSPACE): 889 case ENCODE_KEY(BUTTON_REPEAT, 0, KEY_BACKSPACE): { 890 os->write(child, s8("\x7F")); 891 } break; 892 case ENCODE_KEY(BUTTON_PRESS, 0, KEY_UP): 893 case ENCODE_KEY(BUTTON_REPEAT, 0, KEY_UP): { 894 if (t->mode.win & WM_APPCURSOR) os->write(child, s8("\x1BOA")); 895 else os->write(child, s8("\x1B[A")); 896 } break; 897 case ENCODE_KEY(BUTTON_PRESS, 0, KEY_DOWN): 898 case ENCODE_KEY(BUTTON_REPEAT, 0, KEY_DOWN): { 899 if (t->mode.win & WM_APPCURSOR) os->write(child, s8("\x1BOB")); 900 else os->write(child, s8("\x1B[B")); 901 } break; 902 case ENCODE_KEY(BUTTON_PRESS, 0, KEY_RIGHT): 903 case ENCODE_KEY(BUTTON_REPEAT, 0, KEY_RIGHT): { 904 if (t->mode.win & WM_APPCURSOR) os->write(child, s8("\x1BOC")); 905 else os->write(child, s8("\x1B[C")); 906 } break; 907 case ENCODE_KEY(BUTTON_PRESS, 0, KEY_LEFT): 908 case ENCODE_KEY(BUTTON_REPEAT, 0, KEY_LEFT): { 909 if (t->mode.win & WM_APPCURSOR) os->write(child, s8("\x1BOD")); 910 else os->write(child, s8("\x1B[D")); 911 } break; 912 case ENCODE_KEY(BUTTON_PRESS, 0, KEY_PAGE_UP): 913 case ENCODE_KEY(BUTTON_REPEAT, 0, KEY_PAGE_UP): { 914 if (modifiers & MOD_CONTROL) os->write(child, s8("\x1B[5;5~")); 915 else if (modifiers & MOD_SHIFT) os->write(child, s8("\x1B[5;2~")); 916 else os->write(child, s8("\x1B[5~")); 917 } break; 918 case ENCODE_KEY(BUTTON_PRESS, 0, KEY_PAGE_DOWN): 919 case ENCODE_KEY(BUTTON_REPEAT, 0, KEY_PAGE_DOWN): { 920 if (modifiers & MOD_CONTROL) os->write(child, s8("\x1B[6;5~")); 921 else if (modifiers & MOD_SHIFT) os->write(child, s8("\x1B[6;2~")); 922 else os->write(child, s8("\x1B[6~")); 923 } break; 924 } 925 } 926 927 function b32 928 should_start_interaction(TerminalInput *input) 929 { 930 b32 result = input->mouse_scroll.y || input->mouse_scroll.x || 931 pressed_last_frame(input->keys + MOUSE_LEFT) || 932 pressed_last_frame(input->keys + MOUSE_MIDDLE) || 933 pressed_last_frame(input->keys + MOUSE_RIGHT); 934 return result; 935 } 936 937 function void 938 begin_interaction(Term *t, InteractionState *is, TerminalInput *input) 939 { 940 is->click_count++; 941 if (is->multi_click_t < 0) 942 is->multi_click_t = INTERACTION_MULTI_CLICK_TIME; 943 944 if (is->hot.type != IS_NONE) { 945 if (is->hot.type == IS_AUTO) { 946 //switch (is->hot.var.type) { 947 // /* TODO: start the interaction */ 948 //} 949 } 950 951 is->active = is->hot; 952 953 switch (is->active.type) { 954 case IS_TERM: 955 begin_terminal_interaction(t, input, is->click_count); 956 break; 957 default: 958 break; 959 } 960 } else { 961 is->active = (Interaction){.type = IS_NOP}; 962 } 963 } 964 965 function void 966 end_interaction(InteractionState *is, TerminalInput *input) 967 { 968 is->active = (Interaction){.type = IS_NONE}; 969 } 970 971 function void 972 handle_interactions(Term *t, TerminalInput *input, OS *os) 973 { 974 InteractionState *is = &t->interaction; 975 976 ButtonState *mouse_left = input->keys + MOUSE_LEFT; 977 978 is->multi_click_t -= dt_for_frame; 979 if (!mouse_left->ended_down && is->multi_click_t < 0) { 980 is->click_count = 0; 981 } 982 983 if (is->hot.type != IS_NONE) { 984 if (should_start_interaction(input)) { 985 end_interaction(is, input); 986 begin_interaction(t, is, input); 987 } 988 } 989 990 switch (is->active.type) { 991 case IS_NONE: break; 992 case IS_NOP: break; 993 case IS_SET: end_interaction(is, input); break; 994 case IS_AUTO: /* TODO */ break; 995 case IS_DRAG: /* TODO */ break; 996 case IS_DEBUG: /* TODO */ break; 997 case IS_TERM: { 998 if (terminal_interaction(t, os, input, is->click_count)) 999 end_interaction(is, input); 1000 } break; 1001 } 1002 } 1003 1004 DEBUG_EXPORT VTGL_RENDER_FRAME_FN(vtgl_render_frame) 1005 { 1006 BEGIN_TIMED_BLOCK(); 1007 1008 Term *t = memory->memory; 1009 RenderThreadContext *ctx = &t->render_thread; 1010 1011 Arena arena = ctx->arena; 1012 dt_for_frame = input->dt; 1013 1014 WorkQueueWork *work = work_queue_pop(&ctx->work_queue); 1015 while (work) { 1016 switch (work->kind) { 1017 case WQK_RELOAD_SHADER: { 1018 reload_shader(os, &ctx->gl, work->shader_reload_context, arena); 1019 } break; 1020 case WQK_RELOAD_ALL_SHADERS: { 1021 for (i32 i = 0; i < SID_LAST; i++) { 1022 reload_shader(os, &ctx->gl, ctx->shader_reload_contexts + i, arena); 1023 } 1024 } break; 1025 default: INVALID_CODE_PATH; 1026 } 1027 work_queue_pop_commit(&ctx->work_queue); 1028 work = work_queue_pop(&ctx->work_queue); 1029 } 1030 1031 /* NOTE: default state which can be overwritten later in the frame */ 1032 /* TODO: if (!t->ui_active) */ 1033 { 1034 t->interaction.hot.type = IS_TERM; 1035 } 1036 1037 glBindTexture(GL_TEXTURE_2D, ctx->gl.glyph_bitmap_tex); 1038 1039 BEGIN_NAMED_BLOCK(update_render); 1040 1041 RenderCtx rc = make_render_ctx(&arena, &ctx->gl, &ctx->fa); 1042 1043 glUseProgram(ctx->gl.programs[SID_RENDER]); 1044 glBindFramebuffer(GL_FRAMEBUFFER, ctx->gl.fb); 1045 clear_colour(); 1046 1047 /* NOTE(rnp): spin lock here so that we don't have to deal with rescheduling 1048 * a redraw. Remember that this failing is already unlikely. */ 1049 while (atomic_exchange_n(&t->resize_lock, 1) != 0); 1050 1051 if (ctx->gl.flags & RESIZE_RENDERER) 1052 resize(t, os, input->window_size); 1053 1054 if (ctx->gl.queued_render) { 1055 ctx->gl.queued_render = 0; 1056 u32 cell_count = t->size.h * t->size.w; 1057 RenderCell *render_buf = alloc(&arena, RenderCell, cell_count); 1058 render_framebuffer(t, render_buf, input, arena); 1059 glBufferSubData(GL_SHADER_STORAGE_BUFFER, 0, 1060 cell_count * sizeof(*render_buf), render_buf); 1061 } 1062 render_cursor(t, input->window_focused, arena); 1063 1064 t->resize_lock = 0; 1065 1066 ShaderParameters *sp = &ctx->gl.shader_parameters; 1067 sp->blink_parameter += 2 * PI * g_blink_speed * dt_for_frame; 1068 if (sp->blink_parameter > 2 * PI) sp->blink_parameter -= 2 * PI; 1069 sp->reverse_video_mask = REVERSE_VIDEO_MASK * !!(t->mode.win & WM_REVERSE); 1070 1071 glBufferSubData(GL_UNIFORM_BUFFER, 0, sizeof(*sp), sp); 1072 1073 push_rect_textured(&rc, (Rect){.size = v2_from_iv2(ctx->gl.window_size)}, (v4){0}, 0); 1074 flush_render_push_buffer(&rc); 1075 1076 static f32 param = 0; 1077 static f32 p_scale = 1; 1078 param += p_scale * 0.005 * dt_for_frame; 1079 if (param > 1.0f || param < 0) 1080 p_scale *= -1.0f; 1081 1082 glBindFramebuffer(GL_FRAMEBUFFER, 0); 1083 1084 clear_colour(); 1085 glUseProgram(ctx->gl.programs[SID_POST]); 1086 glUniform1i(ctx->gl.post.texslot, ctx->gl.fb_tex_unit); 1087 glUniform1f(ctx->gl.post.param, param); 1088 1089 push_rect_textured(&rc, (Rect){.size = v2_from_iv2(ctx->gl.window_size)}, (v4){0}, 1); 1090 flush_render_push_buffer(&rc); 1091 END_NAMED_BLOCK(update_render); 1092 1093 /* NOTE: this happens at the end so that ui stuff doesn't go through the post 1094 * processing/effects shader */ 1095 glUseProgram(ctx->gl.programs[SID_RECTS]); 1096 1097 BEGIN_NAMED_BLOCK(debug_overlay); 1098 draw_debug_overlay(memory, input, &rc); 1099 END_NAMED_BLOCK(debug_overlay); 1100 1101 END_TIMED_BLOCK(); 1102 } 1103 1104 DEBUG_EXPORT VTGL_ACTIVE_SELECTION_FN(vtgl_active_selection) 1105 { 1106 Term *t = memory->memory; 1107 Range result = t->selection.range; 1108 if (out) stream_push_selection(out, t->views[t->view_idx].fb.rows, result, t->size.w); 1109 return result; 1110 } 1111 1112 DEBUG_EXPORT VTGL_FRAME_STEP_FN(vtgl_frame_step) 1113 { 1114 BEGIN_NAMED_BLOCK(debug_end_frame); 1115 debug_frame_end(memory, input); 1116 END_NAMED_BLOCK(debug_end_frame); 1117 1118 BEGIN_TIMED_BLOCK(); 1119 1120 Term *t = memory->memory; 1121 1122 dt_for_frame = input->dt; 1123 1124 t->temp_arena = begin_temp_arena(&t->arena_for_frame); 1125 1126 if (t->state & TS_NEEDS_RESIZE || !equal_iv2(input->window_size, t->render_thread.gl.window_size)) { 1127 /* NOTE(rnp): we skip the resize this time through so that we don't add 1128 * input latency waiting for the render thread to release this lock */ 1129 if (atomic_exchange_n(&t->resize_lock, 1) == 0) { 1130 resize_terminal(t, os, input->window_size); 1131 t->resize_lock = 0; 1132 } else { 1133 input->pending_updates = 1; 1134 } 1135 } 1136 1137 if (input->executable_reloaded) { 1138 work_queue_insert(&t->render_thread.work_queue, WQK_RELOAD_ALL_SHADERS, 0); 1139 } 1140 1141 BEGIN_NAMED_BLOCK(mouse_and_keyboard_input); 1142 if (input->character_input.len) { 1143 if (t->scroll_offset) { 1144 t->scroll_offset = 0; 1145 t->state |= TS_NEEDS_REFILL; 1146 } 1147 os->write(t->child, input->character_input); 1148 } 1149 1150 handle_interactions(t, input, os); 1151 END_NAMED_BLOCK(mouse_and_keyboard_input); 1152 1153 if (t->state & TS_NEEDS_REFILL) { 1154 blit_lines(t, t->arena_for_frame); 1155 t->render_thread.gl.queued_render = 1; 1156 } 1157 1158 BEGIN_NAMED_BLOCK(input_from_child); 1159 if (input->data_available) { 1160 OSRingBuffer *rb = &t->views[t->view_idx].log; 1161 s8 buffer = {.len = rb->capacity - t->unprocessed_bytes, .data = rb->data + rb->write_index}; 1162 1163 iz bytes_read = os->read(t->child, buffer); 1164 ASSERT(bytes_read <= rb->capacity); 1165 commit_to_rb(t->views + t->view_idx, bytes_read); 1166 1167 t->unprocessed_bytes += bytes_read; 1168 s8 raw = { 1169 .len = t->unprocessed_bytes, 1170 .data = rb->data + (rb->write_index - t->unprocessed_bytes) 1171 }; 1172 handle_input(t, t->arena_for_frame, raw); 1173 t->render_thread.gl.queued_render = 1; 1174 } 1175 END_NAMED_BLOCK(input_from_child); 1176 1177 end_temp_arena(t->temp_arena); 1178 1179 END_TIMED_BLOCK(); 1180 1181 if (t->render_thread.gl.queued_render || input->window_refreshed || 1182 t->render_thread.gl.flags & DRAW_DEBUG_OVERLAY || 1183 !work_queue_empty(&t->render_thread.work_queue)) 1184 { 1185 os->wake_waiters(t->render_thread.sync); 1186 } 1187 } 1188 1189 #ifdef _DEBUG 1190 #include "debug.c" 1191 #endif