diff --git a/src/profiler/profiler.cpp b/src/profiler/profiler.cpp new file mode 100644 index 0000000..8e1044b --- /dev/null +++ b/src/profiler/profiler.cpp @@ -0,0 +1,51 @@ +#if 1 + #include "spall.h" + +static SpallProfile spall_ctx; +static SpallBuffer spall_buffer; +double get_time_in_micros(void); + +void BeginProfiler() { + spall_ctx = spall_init_file("hello_world.spall", 1); + + int buffer_size = 1 * 1024 * 1024; + unsigned char *buffer = (unsigned char *)malloc(buffer_size); + spall_buffer = {buffer, (size_t)buffer_size}; + + spall_buffer_init(&spall_ctx, &spall_buffer); +} + +void EndProfiler() { + spall_buffer_quit(&spall_ctx, &spall_buffer); + free(spall_buffer.data); + spall_quit(&spall_ctx); +} + +void _BeginProfileScope(const char *name, int len) { + spall_buffer_begin(&spall_ctx, &spall_buffer, + name, // name of your name + len, // name len minus the null terminator + get_time_in_micros() // timestamp in microseconds -- start of your timing block + ); +} + #define BeginProfileScope(name) _BeginProfileScope(name, sizeof(name) - 1) + +void EndProfileScope() { + spall_buffer_end(&spall_ctx, &spall_buffer, + get_time_in_micros() // timestamp in microseconds -- end of your timing block + ); +} + #define ProfileScope(name) ProfileScope_ PROFILE_SCOPE_VAR_##name(#name, sizeof(#name) - 1) + #define ProfileFunction() ProfileScope_ PROFILE_SCOPE_FUNCTION(__FUNCTION__, sizeof(__FUNCTION__) - 1) +struct ProfileScope_ { + ProfileScope_(const char *name, int len) { _BeginProfileScope(name, len); } + ~ProfileScope_() { EndProfileScope(); } +}; +#else + #define ProfileScope(name) + #define ProfileFunction() + #define BeginProfiler() + #define EndProfiler() + #define BeginProfileScope(name) + #define EndProfileScope() +#endif \ No newline at end of file diff --git a/src/profiler/spall.h b/src/profiler/spall.h new file mode 100644 index 0000000..02b32f3 --- /dev/null +++ b/src/profiler/spall.h @@ -0,0 +1,443 @@ +// SPDX-FileCopyrightText: © 2023 Phillip Trudeau-Tavara +// SPDX-License-Identifier: MIT + +/* + +TODO: Optional Helper APIs: + + - Compression API: would require a mutexed lockable context (yuck...) + - Either using a ZIP library, a name cache + TIDPID cache, or both (but ZIP is likely more than enough!!!) + - begin()/end() writes compressed chunks to a caller-determined destination + - The destination can be the buffered-writing API or a custom user destination + - Ultimately need to take a lock with some granularity... can that be the caller's responsibility? + + - Counter Event: should allow tracking arbitrary named values with a single event, for memory and frame profiling + + - Ring-buffer API + spall_ring_init + spall_ring_emit_begin + spall_ring_emit_end + spall_ring_flush +*/ + +#ifndef SPALL_H +#define SPALL_H + +#if !defined(_MSC_VER) || defined(__clang__) + #define SPALL_NOINSTRUMENT __attribute__((no_instrument_function)) + #define SPALL_FORCEINLINE __attribute__((always_inline)) +#else + #define SPALL_NOINSTRUMENT // Can't noinstrument on MSVC! + #define SPALL_FORCEINLINE __forceinline +#endif + +#include +#include +#include +#include + +#define SPALL_FN static inline SPALL_NOINSTRUMENT + +#define SPALL_MIN(a, b) (((a) < (b)) ? (a) : (b)) + +#pragma pack(push, 1) + +typedef struct SpallHeader { + uint64_t magic_header; // = 0x0BADF00D + uint64_t version; // = 1 + double timestamp_unit; + uint64_t must_be_0; +} SpallHeader; + +enum { + SpallEventType_Invalid = 0, + SpallEventType_Custom_Data = 1, // Basic readers can skip this. + SpallEventType_StreamOver = 2, + + SpallEventType_Begin = 3, + SpallEventType_End = 4, + SpallEventType_Instant = 5, + + SpallEventType_Overwrite_Timestamp = 6, // Retroactively change timestamp units - useful for incrementally improving RDTSC frequency. + SpallEventType_Pad_Skip = 7, +}; + +typedef struct SpallBeginEvent { + uint8_t type; // = SpallEventType_Begin + uint8_t category; + + uint32_t pid; + uint32_t tid; + double when; + + uint8_t name_length; + uint8_t args_length; +} SpallBeginEvent; + +typedef struct SpallBeginEventMax { + SpallBeginEvent event; + char name_bytes[255]; + char args_bytes[255]; +} SpallBeginEventMax; + +typedef struct SpallEndEvent { + uint8_t type; // = SpallEventType_End + uint32_t pid; + uint32_t tid; + double when; +} SpallEndEvent; + +typedef struct SpallPadSkipEvent { + uint8_t type; // = SpallEventType_Pad_Skip + uint32_t size; +} SpallPadSkipEvent; + +#pragma pack(pop) + +typedef struct SpallProfile SpallProfile; + +// Important!: If you define your own callbacks, mark them SPALL_NOINSTRUMENT! +typedef bool (*SpallWriteCallback)(SpallProfile *self, const void *data, size_t length); +typedef bool (*SpallFlushCallback)(SpallProfile *self); +typedef void (*SpallCloseCallback)(SpallProfile *self); + +struct SpallProfile { + double timestamp_unit; + bool is_json; + SpallWriteCallback write; + SpallFlushCallback flush; + SpallCloseCallback close; + void *data; +}; + +// Important!: If you are writing Begin/End events, then do NOT write +// events for the same PID + TID pair on different buffers!!! +typedef struct SpallBuffer { + void *data; + size_t length; + + // Internal data - don't assign this + size_t head; + SpallProfile *ctx; +} SpallBuffer; + +#ifdef __cplusplus +extern "C" { +#endif + +#if defined(SPALL_BUFFER_PROFILING) && !defined(SPALL_BUFFER_PROFILING_GET_TIME) + #error "You must #define SPALL_BUFFER_PROFILING_GET_TIME() to profile buffer flushes." +#endif + +SPALL_FN SPALL_FORCEINLINE void spall__buffer_profile(SpallProfile *ctx, SpallBuffer *wb, double spall_time_begin, double spall_time_end, const char *name, int name_len); +#ifdef SPALL_BUFFER_PROFILING + #define SPALL_BUFFER_PROFILE_BEGIN() double spall_time_begin = (SPALL_BUFFER_PROFILING_GET_TIME()) + // Don't call this with anything other than a string literal + #define SPALL_BUFFER_PROFILE_END(name) spall__buffer_profile(ctx, wb, spall_time_begin, (SPALL_BUFFER_PROFILING_GET_TIME()), "" name "", sizeof("" name "") - 1) +#else + #define SPALL_BUFFER_PROFILE_BEGIN() + #define SPALL_BUFFER_PROFILE_END(name) +#endif + +SPALL_FN SPALL_FORCEINLINE bool spall__file_write(SpallProfile *ctx, const void *p, size_t n) { + if (!ctx->data) return false; +#ifdef SPALL_DEBUG + if (feof((FILE *)ctx->data)) return false; + if (ferror((FILE *)ctx->data)) return false; +#endif + + if (fwrite(p, n, 1, (FILE *)ctx->data) != 1) return false; + return true; +} +SPALL_FN bool spall__file_flush(SpallProfile *ctx) { + if (!ctx->data) return false; + if (fflush((FILE *)ctx->data)) return false; + return true; +} +SPALL_FN void spall__file_close(SpallProfile *ctx) { + if (!ctx->data) return; + + if (ctx->is_json) { +#ifdef SPALL_DEBUG + if (!feof((FILE *)ctx->data) && !ferror((FILE *)ctx->data)) +#endif + { + fseek((FILE *)ctx->data, -2, SEEK_CUR); // seek back to overwrite trailing comma + fwrite("\n]}\n", sizeof("\n]}\n") - 1, 1, (FILE *)ctx->data); + } + } + fflush((FILE *)ctx->data); + fclose((FILE *)ctx->data); + ctx->data = NULL; +} + +SPALL_FN SPALL_FORCEINLINE bool spall__buffer_flush(SpallProfile *ctx, SpallBuffer *wb) { + // precon: wb + // precon: wb->data + // precon: wb->head <= wb->length + // precon: !ctx || ctx->write +#ifdef SPALL_DEBUG + if (wb->ctx != ctx) return false; // Buffer must be bound to this context (or to NULL) +#endif + + if (wb->head && ctx) { + SPALL_BUFFER_PROFILE_BEGIN(); + if (!ctx->write) return false; + if (ctx->write == spall__file_write) { + if (!spall__file_write(ctx, wb->data, wb->head)) return false; + } else { + if (!ctx->write(ctx, wb->data, wb->head)) return false; + } + SPALL_BUFFER_PROFILE_END("Buffer Flush"); + } + wb->head = 0; + return true; +} + +SPALL_FN SPALL_FORCEINLINE bool spall__buffer_write(SpallProfile *ctx, SpallBuffer *wb, void *p, size_t n) { + // precon: !wb || wb->head < wb->length + // precon: !ctx || ctx->write + if (!wb) return ctx->write && ctx->write(ctx, p, n); +#ifdef SPALL_DEBUG + if (wb->ctx != ctx) return false; // Buffer must be bound to this context (or to NULL) +#endif + if (wb->head + n > wb->length && !spall__buffer_flush(ctx, wb)) return false; + if (n > wb->length) { + SPALL_BUFFER_PROFILE_BEGIN(); + if (!ctx->write || !ctx->write(ctx, p, n)) return false; + SPALL_BUFFER_PROFILE_END("Unbuffered Write"); + return true; + } + memcpy((char *)wb->data + wb->head, p, n); + wb->head += n; + return true; +} + +SPALL_FN bool spall_buffer_flush(SpallProfile *ctx, SpallBuffer *wb) { +#ifdef SPALL_DEBUG + if (!wb) return false; + if (!wb->data) return false; +#endif + + if (!spall__buffer_flush(ctx, wb)) return false; + return true; +} + +SPALL_FN bool spall_buffer_init(SpallProfile *ctx, SpallBuffer *wb) { + if (!spall_buffer_flush(NULL, wb)) return false; + wb->ctx = ctx; + return true; +} +SPALL_FN bool spall_buffer_quit(SpallProfile *ctx, SpallBuffer *wb) { + if (!spall_buffer_flush(ctx, wb)) return false; + wb->ctx = NULL; + return true; +} + +SPALL_FN bool spall_buffer_abort(SpallBuffer *wb) { + if (!wb) return false; + wb->ctx = NULL; + if (!spall__buffer_flush(NULL, wb)) return false; + return true; +} + +SPALL_FN size_t spall_build_header(void *buffer, size_t rem_size, double timestamp_unit) { + size_t header_size = sizeof(SpallHeader); + if (header_size > rem_size) { + return 0; + } + + SpallHeader *header = (SpallHeader *)buffer; + header->magic_header = 0x0BADF00D; + header->version = 1; + header->timestamp_unit = timestamp_unit; + header->must_be_0 = 0; + return header_size; +} +SPALL_FN SPALL_FORCEINLINE size_t spall_build_begin(void *buffer, size_t rem_size, const char *name, signed long name_len, const char *args, signed long args_len, double when, uint32_t tid, uint32_t pid) { + SpallBeginEventMax *ev = (SpallBeginEventMax *)buffer; + uint8_t trunc_name_len = (uint8_t)SPALL_MIN(name_len, 255); // will be interpreted as truncated in the app (?) + uint8_t trunc_args_len = (uint8_t)SPALL_MIN(args_len, 255); // will be interpreted as truncated in the app (?) + + size_t ev_size = sizeof(SpallBeginEvent) + trunc_name_len + trunc_args_len; + if (ev_size > rem_size) { + return 0; + } + + ev->event.type = SpallEventType_Begin; + ev->event.category = 0; + ev->event.pid = pid; + ev->event.tid = tid; + ev->event.when = when; + ev->event.name_length = trunc_name_len; + ev->event.args_length = trunc_args_len; + memcpy(ev->name_bytes, name, trunc_name_len); + memcpy(ev->name_bytes + trunc_name_len, args, trunc_args_len); + + return ev_size; +} +SPALL_FN SPALL_FORCEINLINE size_t spall_build_end(void *buffer, size_t rem_size, double when, uint32_t tid, uint32_t pid) { + size_t ev_size = sizeof(SpallEndEvent); + if (ev_size > rem_size) { + return 0; + } + + SpallEndEvent *ev = (SpallEndEvent *)buffer; + ev->type = SpallEventType_End; + ev->pid = pid; + ev->tid = tid; + ev->when = when; + + return ev_size; +} + +SPALL_FN void spall_quit(SpallProfile *ctx) { + if (!ctx) return; + if (ctx->close) ctx->close(ctx); + + memset(ctx, 0, sizeof(*ctx)); +} + +SPALL_FN SpallProfile spall_init_callbacks(double timestamp_unit, + SpallWriteCallback write, + SpallFlushCallback flush, + SpallCloseCallback close, + void *userdata, + bool is_json) { + SpallProfile ctx; + memset(&ctx, 0, sizeof(ctx)); + if (timestamp_unit < 0) return ctx; + ctx.timestamp_unit = timestamp_unit; + ctx.is_json = is_json; + ctx.data = userdata; + ctx.write = write; + ctx.flush = flush; + ctx.close = close; + + if (ctx.is_json) { + if (!ctx.write(&ctx, "{\"traceEvents\":[\n", sizeof("{\"traceEvents\":[\n") - 1)) { + spall_quit(&ctx); + return ctx; + } + } else { + SpallHeader header; + size_t len = spall_build_header(&header, sizeof(header), timestamp_unit); + if (!ctx.write(&ctx, &header, len)) { + spall_quit(&ctx); + return ctx; + } + } + + return ctx; +} + +SPALL_FN SpallProfile spall_init_file_ex(const char *filename, double timestamp_unit, bool is_json) { + SpallProfile ctx; + memset(&ctx, 0, sizeof(ctx)); + if (!filename) return ctx; + ctx.data = fopen(filename, "wb"); // TODO: handle utf8 and long paths on windows + if (ctx.data) { // basically freopen() but we don't want to force users to lug along another macro define + fclose((FILE *)ctx.data); + ctx.data = fopen(filename, "ab"); + } + if (!ctx.data) { + spall_quit(&ctx); + return ctx; + } + ctx = spall_init_callbacks(timestamp_unit, spall__file_write, spall__file_flush, spall__file_close, ctx.data, is_json); + return ctx; +} + +SPALL_FN SpallProfile spall_init_file(const char *filename, double timestamp_unit) { return spall_init_file_ex(filename, timestamp_unit, false); } +SPALL_FN SpallProfile spall_init_file_json(const char *filename, double timestamp_unit) { return spall_init_file_ex(filename, timestamp_unit, true); } + +SPALL_FN bool spall_flush(SpallProfile *ctx) { +#ifdef SPALL_DEBUG + if (!ctx) return false; +#endif + + if (!ctx->flush || !ctx->flush(ctx)) return false; + return true; +} + +SPALL_FN SPALL_FORCEINLINE bool spall_buffer_begin_args(SpallProfile *ctx, SpallBuffer *wb, const char *name, signed long name_len, const char *args, signed long args_len, double when, uint32_t tid, uint32_t pid) { +#ifdef SPALL_DEBUG + if (!ctx) return false; + if (!name) return false; + if (name_len <= 0) return false; + if (!wb) return false; +#endif + + if (ctx->is_json) { + char buf[1024]; + int buf_len = snprintf(buf, sizeof(buf), + "{\"ph\":\"B\",\"ts\":%f,\"pid\":%u,\"tid\":%u,\"name\":\"%.*s\",\"args\":\"%.*s\"},\n", + when * ctx->timestamp_unit, pid, tid, (int)(uint8_t)name_len, name, (int)(uint8_t)args_len, args); + if (buf_len <= 0) return false; + if (buf_len >= sizeof(buf)) return false; + if (!spall__buffer_write(ctx, wb, buf, buf_len)) return false; + } else { + if ((wb->head + sizeof(SpallBeginEventMax)) > wb->length) { + if (!spall__buffer_flush(ctx, wb)) { + return false; + } + } + + wb->head += spall_build_begin((char *)wb->data + wb->head, wb->length - wb->head, name, name_len, args, args_len, when, tid, pid); + } + + return true; +} + +SPALL_FN SPALL_FORCEINLINE bool spall_buffer_begin_ex(SpallProfile *ctx, SpallBuffer *wb, const char *name, signed long name_len, double when, uint32_t tid, uint32_t pid) { + return spall_buffer_begin_args(ctx, wb, name, name_len, "", 0, when, tid, pid); +} + +SPALL_FN bool spall_buffer_begin(SpallProfile *ctx, SpallBuffer *wb, const char *name, signed long name_len, double when) { + return spall_buffer_begin_args(ctx, wb, name, name_len, "", 0, when, 0, 0); +} + +SPALL_FN SPALL_FORCEINLINE bool spall_buffer_end_ex(SpallProfile *ctx, SpallBuffer *wb, double when, uint32_t tid, uint32_t pid) { +#ifdef SPALL_DEBUG + if (!ctx) return false; + if (!wb) return false; +#endif + + if (ctx->is_json) { + char buf[512]; + int buf_len = snprintf(buf, sizeof(buf), + "{\"ph\":\"E\",\"ts\":%f,\"pid\":%u,\"tid\":%u},\n", + when * ctx->timestamp_unit, pid, tid); + if (buf_len <= 0) return false; + if (buf_len >= sizeof(buf)) return false; + if (!spall__buffer_write(ctx, wb, buf, buf_len)) return false; + } else { + if ((wb->head + sizeof(SpallEndEvent)) > wb->length) { + if (!spall__buffer_flush(ctx, wb)) { + return false; + } + } + + wb->head += spall_build_end((char *)wb->data + wb->head, wb->length - wb->head, when, tid, pid); + } + + return true; +} + +SPALL_FN bool spall_buffer_end(SpallProfile *ctx, SpallBuffer *wb, double when) { return spall_buffer_end_ex(ctx, wb, when, 0, 0); } + +SPALL_FN SPALL_FORCEINLINE void spall__buffer_profile(SpallProfile *ctx, SpallBuffer *wb, double spall_time_begin, double spall_time_end, const char *name, int name_len) { + // precon: ctx + // precon: ctx->write + char temp_buffer_data[2048]; + SpallBuffer temp_buffer = {temp_buffer_data, sizeof(temp_buffer_data)}; + if (!spall_buffer_begin_ex(ctx, &temp_buffer, name, name_len, spall_time_begin, (uint32_t)(uintptr_t)wb->data, 4222222222)) return; + if (!spall_buffer_end_ex(ctx, &temp_buffer, spall_time_end, (uint32_t)(uintptr_t)wb->data, 4222222222)) return; + if (ctx->write) ctx->write(ctx, temp_buffer_data, temp_buffer.head); +} + +#ifdef __cplusplus +} +#endif + +#endif // SPALL_H \ No newline at end of file diff --git a/src/text_editor/buffer.cpp b/src/text_editor/buffer.cpp index c50b81b..441a7ad 100644 --- a/src/text_editor/buffer.cpp +++ b/src/text_editor/buffer.cpp @@ -55,8 +55,10 @@ Range GetLineRange(Buffer &buffer, Int line) { } Int CalculateLongestLine(Buffer &buffer) { - // @optimize: this needs to be cached and then only updated when - // we modify the buffer. Maybe some modification ID or something. + // @optimize: this is the bottleneck + if (buffer.change_id == buffer.longest_line_id) { + return buffer.index_of_longest_line; + } Int index_max = 0; Int size_max = 0; for (Int i = 0; i < buffer.line_starts.len; i += 1) { @@ -67,6 +69,8 @@ Int CalculateLongestLine(Buffer &buffer) { size_max = size; } } + buffer.longest_line_id = buffer.change_id; + buffer.index_of_longest_line = index_max; return index_max; } @@ -180,6 +184,7 @@ void ReplaceText(Buffer *buffer, Range range, String16 string) { Assert(range.max >= range.min); Assert(range.max >= 0 && range.max <= buffer->len); Assert(range.min >= 0 && range.min <= buffer->len); + buffer->change_id += 1; Int size_to_remove = range.max - range.min; Int size_to_add = string.len; diff --git a/src/text_editor/buffer.h b/src/text_editor/buffer.h index b0301d7..46f64f6 100644 --- a/src/text_editor/buffer.h +++ b/src/text_editor/buffer.h @@ -6,6 +6,10 @@ struct Buffer { Int len; Int cap; Array line_starts; + Int change_id; + + Int longest_line_id; + Int index_of_longest_line; }; union Range { diff --git a/src/text_editor/text_editor.cpp b/src/text_editor/text_editor.cpp index 294a2f8..6d8f9aa 100644 --- a/src/text_editor/text_editor.cpp +++ b/src/text_editor/text_editor.cpp @@ -5,6 +5,8 @@ #include #include "math.cpp" +#include "../profiler/profiler.cpp" + #include "buffer.h" #include "buffer_helpers.cpp" #include "buffer.cpp" @@ -25,7 +27,6 @@ - Backspace, Delete, Enter - Mouse anchor point and double click - Mouse cursor changes -- Mouse scrolling - Line highlight - Color new lines and end of buffer - Scrollbars @@ -42,6 +43,8 @@ int main(void) { Test(); TestBuffer(); TestBufferMultiCaret(); + BeginProfiler(); + BeginProfileScope("main"); Arena Perm = {}; InitArena(&Perm); @@ -72,7 +75,7 @@ int main(void) { view.buffer = CreateBuffer(Perm); { Scratch scratch; - for (int i = 0; i < 15000; i += 1) { + for (int i = 0; i < 1500000; i += 1) { String s = Format(scratch, "line1: %d line2: %d line3: %d line4: %d line5: %d line6: %d line1: %d line2: %d line3: %d line4: %d line5: %d line6: %d\n", i, i, i, i, i, i, i, i, i, i, i, i); // String s = Format(scratch, "line %d\n", i); String16 s16 = ToString16(scratch, s); @@ -83,6 +86,7 @@ int main(void) { } while (!WindowShouldClose()) { + ProfileScope(game_loop); view.rect = GetScreenRect(); BeginDrawing(); @@ -95,5 +99,7 @@ int main(void) { EndDrawing(); } CloseWindow(); + EndProfileScope(); + EndProfiler(); return 0; } diff --git a/src/text_editor/view.cpp b/src/text_editor/view.cpp index 7977cf3..cd826f5 100644 --- a/src/text_editor/view.cpp +++ b/src/text_editor/view.cpp @@ -106,6 +106,7 @@ Int MovePosByXY(Buffer &buffer, Int pos, XY offset) { } Int MoveCaret(Buffer &buffer, Int pos, int direction, bool ctrl_pressed) { + ProfileFunction(); Assert(direction >= 0 && direction <= 3); if (ctrl_pressed) { switch (direction) { @@ -154,6 +155,7 @@ void AfterEdit(View *view, Array edits) { } void HandleKeybindings(View *_view) { + ProfileFunction(); View &view = *_view; Buffer &buf = *view.buffer; Caret main_caret_on_begin_frame = view.carets[0]; @@ -235,6 +237,7 @@ void HandleKeybindings(View *_view) { } { + ProfileScope(mouse); Vec2 mouse = GetMousePosition(); bool mouse_in_view = CheckCollisionPointRec(mouse, ToRectangle(view.rect)); @@ -301,10 +304,12 @@ void HandleKeybindings(View *_view) { // Clip scroll { + ProfileScope(clip_scroll); Int last_line = LastLine(view.buffer[0]); view.scroll.y = Clamp(view.scroll.y, 0.f, Max(0.f, (last_line - 1) * view.line_spacing)); - Int line_chars = GetCharCountOfLongestLine(view.buffer[0]); - view.scroll.x = Clamp(view.scroll.x, 0.f, Max(0.f, (line_chars - 2) * view.char_spacing)); + // Int line_chars = GetCharCountOfLongestLine(view.buffer[0]); + view.scroll.x = ClampBottom(view.scroll.x, 0.f); + // view.scroll.x = Clamp(view.scroll.x, 0.f, Max(0.f, (line_chars - 2) * view.char_spacing)); } } \ No newline at end of file diff --git a/src/text_editor/view_draw.cpp b/src/text_editor/view_draw.cpp index 8b277fe..046a1a9 100644 --- a/src/text_editor/view_draw.cpp +++ b/src/text_editor/view_draw.cpp @@ -1,4 +1,5 @@ Rect2I GetVisibleCells(const View &view) { + ProfileFunction(); Vec2 size = GetSize(view.rect); float _cx = size.x / view.char_spacing; float _cy = size.y / view.line_spacing; @@ -13,6 +14,7 @@ Rect2I GetVisibleCells(const View &view) { } void DrawVisibleCells(const View &view) { + ProfileFunction(); Rect2I visible = GetVisibleCells(view); for (Int line_index = visible.min.y; line_index < visible.max.y && line_index >= 0 && line_index < view.buffer->line_starts.len; line_index += 1) { Range line_range = GetLineRange(*view.buffer, line_index); @@ -34,6 +36,7 @@ void DrawCaret(const View &view, XY xy, float size, Color color) { } void DrawCaret(const View &view, Caret it) { + ProfileFunction(); Buffer &buf = *view.buffer; Int front = GetFront(it); XY fxy = PosToXY(buf, front);