Mercurial
view infinite_canvas/web_surface_native.cc @ 276:b55c22cff335
Add interactive infinite canvas prototype
| author | MrJuneJune <me@mrjunejune.com> |
|---|---|
| date | Mon, 17 Aug 2026 16:57:56 -0700 |
| parents | |
| children | 8d560f50ed4c |
line wrap: on
line source
extern "C" { #include "infinite_canvas/web_surface.h" } #include "include/cef_app.h" #include "include/cef_browser.h" #include "include/cef_client.h" #include "include/cef_render_handler.h" #include "include/capi/cef_app_capi.h" #include <algorithm> #include <cmath> #include <cstdio> #include <cstdlib> #include <cstring> #include <filesystem> #include <new> #if defined(OS_LINUX) #include <dlfcn.h> #include <limits.h> #endif namespace { constexpr int32 kResolutionStep = 64; constexpr int32 kMinTextureWidth = 160; constexpr int32 kMinTextureHeight = 120; constexpr int32 kMaxTextureWidth = 1280; constexpr int32 kMaxTextureHeight = 800; int g_argc = 0; char **g_argv = nullptr; char *g_cef_argv[128] = {}; #if defined(OS_LINUX) char g_resources_path[PATH_MAX + 32] = {}; char g_locales_path[PATH_MAX + 32] = {}; char g_resources_switch[PATH_MAX + 64] = {}; char g_locales_switch[PATH_MAX + 64] = {}; #endif void FindResourcePaths(); const Canvas_Entity *FindSurfaceEntity( const Canvas_Scene *scene, uint32 entity_id) { for (size_t index = 0; index < Dowa_Array_Length(scene->p_entities); ++index) { const Canvas_Entity *entity = &scene->p_entities[index]; if ((entity->type == CANVAS_ENTITY_WEB_CONTENT || entity->type == CANVAS_ENTITY_IMAGE) && entity->id == entity_id) { return entity; } } return nullptr; } Rectangle ContentBounds( const Canvas_Camera *camera, const Canvas_Entity *entity) { float frame_inset = CANVAS_WEB_FRAME_INSET / camera->zoom; float resize_handle = CANVAS_WEB_RESIZE_HANDLE / camera->zoom; Vector2 position = Canvas_Camera_World_To_Screen( camera, Vector2{ entity->position.x + frame_inset, entity->position.y + frame_inset}); return Rectangle{ position.x, position.y, (entity->size.x - frame_inset - resize_handle) * camera->zoom, (entity->size.y - frame_inset - resize_handle) * camera->zoom, }; } boolean IsVisible(const Canvas_Camera *camera, Rectangle bounds) { return camera->zoom >= 0.30f && bounds.x + bounds.width > 0.0f && bounds.y + bounds.height > 0.0f && bounds.x < static_cast<float>(camera->viewport_width) && bounds.y < static_cast<float>(camera->viewport_height); } int32 FrameRateForView( const Canvas_Web_Surface *surface, const Canvas_Web_View *view, Rectangle bounds) { if (surface->focused_entity_id == view->entity_id) return 30; float area = std::max(bounds.width, 0.0f) * std::max(bounds.height, 0.0f); return area <= 220000.0f ? 12 : 20; } int32 BucketDimension(float value, int32 minimum, int32 maximum) { int32 rounded = static_cast<int32>( std::ceil(std::max(value, 1.0f) / kResolutionStep)) * kResolutionStep; return std::clamp(rounded, minimum, maximum); } class CanvasCefApp : public CefApp, public CefBrowserProcessHandler { public: CanvasCefApp() = default; CefRefPtr<CefBrowserProcessHandler> GetBrowserProcessHandler() override { return this; } void OnBeforeCommandLineProcessing( const CefString &process_type, CefRefPtr<CefCommandLine> command_line) override { (void)process_type; command_line->AppendSwitch("no-sandbox"); command_line->AppendSwitch("disable-gpu"); command_line->AppendSwitch("disable-gpu-compositing"); command_line->AppendSwitch("allow-file-access-from-files"); command_line->AppendSwitch("allow-universal-access-from-files"); command_line->AppendSwitchWithValue("log-severity", "disable"); #if defined(OS_LINUX) if (g_resources_path[0]) { command_line->AppendSwitchWithValue( "resources-dir-path", g_resources_path); command_line->AppendSwitchWithValue( "locales-dir-path", g_locales_path); } #endif } private: IMPLEMENT_REFCOUNTING(CanvasCefApp); DISALLOW_COPY_AND_ASSIGN(CanvasCefApp); }; class CanvasCefClient : public CefClient, public CefRenderHandler, public CefLifeSpanHandler { public: explicit CanvasCefClient(Canvas_Web_View *view) : view_(view) {} CefRefPtr<CefRenderHandler> GetRenderHandler() override { return this; } CefRefPtr<CefLifeSpanHandler> GetLifeSpanHandler() override { return this; } void GetViewRect(CefRefPtr<CefBrowser> browser, CefRect &rect) override { (void)browser; rect = CefRect(0, 0, view_->texture_width, view_->texture_height); } void OnPaint( CefRefPtr<CefBrowser> browser, PaintElementType type, const RectList &dirty_rects, const void *buffer, int width, int height) override { (void)browser; (void)dirty_rects; if (type != PET_VIEW || !view_->p_pixels || width != view_->texture_width || height != view_->texture_height) { return; } const uint8 *source = static_cast<const uint8 *>(buffer); uint8 *target = view_->p_pixels; const size_t pixel_count = static_cast<size_t>(width) * height; for (size_t index = 0; index < pixel_count; ++index) { target[index * 4 + 0] = source[index * 4 + 2]; target[index * 4 + 1] = source[index * 4 + 1]; target[index * 4 + 2] = source[index * 4 + 0]; target[index * 4 + 3] = source[index * 4 + 3]; } view_->pixels_dirty = TRUE; } void OnAfterCreated(CefRefPtr<CefBrowser> browser) override { browser_ = browser; browser_->GetHost()->WasHidden(!view_->active); } void OnBeforeClose(CefRefPtr<CefBrowser> browser) override { (void)browser; browser_ = nullptr; } CefRefPtr<CefBrowser> browser() const { return browser_; } private: Canvas_Web_View *view_; CefRefPtr<CefBrowser> browser_; IMPLEMENT_REFCOUNTING(CanvasCefClient); DISALLOW_COPY_AND_ASSIGN(CanvasCefClient); }; struct NativeState { CefRefPtr<CanvasCefClient> clients[CANVAS_MAX_WEB_VIEWS]; boolean cef_initialized; }; CefMainArgs MainArgs(int argc, char **argv) { #if defined(OS_WIN) (void)argc; (void)argv; return CefMainArgs(GetModuleHandle(nullptr)); #else return CefMainArgs(argc, argv); #endif } #if defined(OS_LINUX) void FindResourcePaths() { if (g_resources_path[0]) return; Dl_info info = {}; if (!dladdr(reinterpret_cast<void *>(&cef_initialize), &info) || !info.dli_fname) { return; } char root[PATH_MAX] = {}; if (!realpath(info.dli_fname, root)) return; char *release = std::strstr(root, "/Release/libcef.so"); if (!release) return; *release = '\0'; std::snprintf( g_resources_path, sizeof(g_resources_path), "%s/Resources", root); std::snprintf( g_locales_path, sizeof(g_locales_path), "%s/Resources/locales", root); } void ConfigureLinuxResourcePaths(CefSettings &settings) { FindResourcePaths(); CefString(&settings.resources_dir_path) = g_resources_path; CefString(&settings.locales_dir_path) = g_locales_path; } #else void FindResourcePaths() {} #endif int32 ViewIndex( const Canvas_Web_Surface *surface, const Canvas_Web_View *view) { return static_cast<int32>(view - surface->views); } Canvas_Web_View *FindView(Canvas_Web_Surface *surface, uint32 entity_id) { for (int32 index = 0; index < CANVAS_MAX_WEB_VIEWS; ++index) { if (surface->views[index].entity_id == entity_id) { return &surface->views[index]; } } return nullptr; } Canvas_Web_View *FindInactiveView(Canvas_Web_Surface *surface) { for (int32 index = 0; index < CANVAS_MAX_WEB_VIEWS; ++index) { if (!surface->views[index].active) return &surface->views[index]; } return nullptr; } boolean SceneHasVisibleEntity( const Canvas_Scene *scene, const Canvas_Camera *camera, uint32 entity_id) { const Canvas_Entity *entity = FindSurfaceEntity(scene, entity_id); return entity && IsVisible(camera, ContentBounds(camera, entity)); } boolean EnsureViewResolution( Canvas_Web_Surface *surface, Canvas_Web_View *view, Rectangle bounds) { int32 width = BucketDimension( bounds.width, kMinTextureWidth, kMaxTextureWidth); int32 height = BucketDimension( bounds.height, kMinTextureHeight, kMaxTextureHeight); if (view->p_pixels && view->texture_width == width && view->texture_height == height) { return TRUE; } size_t pixel_capacity = static_cast<size_t>(width) * static_cast<size_t>(height) * 4; void *resized_pixels = std::realloc(view->p_pixels, pixel_capacity); if (!resized_pixels) return FALSE; view->p_pixels = static_cast<uint8 *>(resized_pixels); view->pixel_capacity = pixel_capacity; view->texture_width = width; view->texture_height = height; std::memset(view->p_pixels, 255, pixel_capacity); view->pixels_dirty = FALSE; if (view->texture_ready) { UnloadTexture(view->texture); view->texture = {}; view->texture_ready = FALSE; } NativeState *state = static_cast<NativeState *>(surface->p_native); int32 index = ViewIndex(surface, view); CefRefPtr<CanvasCefClient> client = state->clients[index]; if (client && client->browser()) { client->browser()->GetHost()->WasResized(); } return TRUE; } boolean CreateBrowser( Canvas_Web_Surface *surface, Canvas_Web_View *view, const char *url, Rectangle bounds) { NativeState *state = static_cast<NativeState *>(surface->p_native); int32 index = ViewIndex(surface, view); if (!EnsureViewResolution(surface, view, bounds)) return FALSE; state->clients[index] = new CanvasCefClient(view); CefWindowInfo window_info; window_info.SetAsWindowless(kNullWindowHandle); CefBrowserSettings browser_settings; browser_settings.windowless_frame_rate = 30; browser_settings.background_color = CefColorSetARGB(0, 255, 255, 255); const char *initial_url = view->entity_type == CANVAS_ENTITY_IMAGE ? "about:blank" : url; if (!CefBrowserHost::CreateBrowser( window_info, state->clients[index], initial_url, browser_settings, nullptr, nullptr)) { state->clients[index] = nullptr; return FALSE; } view->browser_created = TRUE; return TRUE; } void PercentEncode( const char *source, char *target, size_t target_size) { static const char hex[] = "0123456789ABCDEF"; size_t output = 0; for (size_t index = 0; source[index] && output + 4 < target_size; ++index) { uint8 value = static_cast<uint8>(source[index]); boolean unreserved = (value >= 'a' && value <= 'z') || (value >= 'A' && value <= 'Z') || (value >= '0' && value <= '9') || value == '-' || value == '_' || value == '.' || value == '~'; if (unreserved) { target[output++] = static_cast<char>(value); } else { target[output++] = '%'; target[output++] = hex[value >> 4]; target[output++] = hex[value & 15]; } } target[output] = '\0'; } void LoadImageSource( CefRefPtr<CefBrowser> browser, const char *source) { char resolved[512] = {}; boolean local_file = !std::strstr(source, "://") && std::strncmp(source, "data:", 5) != 0; if (!local_file) { std::snprintf(resolved, sizeof(resolved), "%s", source); } else { std::error_code error; std::filesystem::path absolute = std::filesystem::absolute(source, error); if (error) { std::snprintf(resolved, sizeof(resolved), "%s", source); } else { std::snprintf( resolved, sizeof(resolved), "file://%s", absolute.generic_string().c_str()); } } std::error_code viewer_error; std::filesystem::path viewer_path = std::filesystem::absolute( "infinite_canvas/assets/image-view.html", viewer_error); if (viewer_error) { browser->GetMainFrame()->LoadURL(resolved); return; } char encoded_source[2048] = {}; PercentEncode(resolved, encoded_source, sizeof(encoded_source)); char viewer_url[3072] = {}; std::snprintf( viewer_url, sizeof(viewer_url), "file://%s?src=%s", viewer_path.generic_string().c_str(), encoded_source); browser->GetMainFrame()->LoadURL(viewer_url); } void BindView( Canvas_Web_Surface *surface, Canvas_Web_View *view, const Canvas_Entity *entity, Rectangle bounds) { NativeState *state = static_cast<NativeState *>(surface->p_native); int32 index = ViewIndex(surface, view); boolean type_changed = view->entity_type != entity->type; view->active = TRUE; view->entity_id = entity->id; view->entity_type = entity->type; if (!view->browser_created) { if (!CreateBrowser(surface, view, entity->text, bounds)) { view->active = FALSE; view->entity_id = 0; return; } if (entity->type == CANVAS_ENTITY_WEB_CONTENT) { std::snprintf(view->url, sizeof(view->url), "%s", entity->text); } return; } EnsureViewResolution(surface, view, bounds); if (type_changed || std::strcmp(view->url, entity->text) != 0) { CefRefPtr<CanvasCefClient> client = state->clients[index]; if (client && client->browser()) { if (entity->type == CANVAS_ENTITY_IMAGE) { LoadImageSource(client->browser(), entity->text); } else { client->browser()->GetMainFrame()->LoadURL(entity->text); } std::snprintf(view->url, sizeof(view->url), "%s", entity->text); } } } void ReconcileViews( Canvas_Web_Surface *surface, const Canvas_Camera *camera, const Canvas_Scene *scene) { for (int32 index = 0; index < CANVAS_MAX_WEB_VIEWS; ++index) { Canvas_Web_View *view = &surface->views[index]; view->active = view->entity_id != 0 && SceneHasVisibleEntity(scene, camera, view->entity_id); } for (size_t index = 0; index < Dowa_Array_Length(scene->p_entities); ++index) { const Canvas_Entity *entity = &scene->p_entities[index]; if ((entity->type != CANVAS_ENTITY_WEB_CONTENT && entity->type != CANVAS_ENTITY_IMAGE) || !IsVisible(camera, ContentBounds(camera, entity))) { continue; } Rectangle bounds = ContentBounds(camera, entity); Canvas_Web_View *view = FindView(surface, entity->id); if (view) { BindView(surface, view, entity, bounds); continue; } view = FindInactiveView(surface); if (!view) break; BindView(surface, view, entity, bounds); } if (surface->focused_entity_id != 0) { Canvas_Web_View *focused = FindView(surface, surface->focused_entity_id); if (!focused || !focused->active) surface->focused_entity_id = 0; } NativeState *state = static_cast<NativeState *>(surface->p_native); for (int32 index = 0; index < CANVAS_MAX_WEB_VIEWS; ++index) { CefRefPtr<CanvasCefClient> client = state->clients[index]; if (client && client->browser()) { Canvas_Web_View *view = &surface->views[index]; CefRefPtr<CefBrowserHost> host = client->browser()->GetHost(); host->WasHidden(!view->active); if (view->active) { const Canvas_Entity *entity = FindSurfaceEntity(scene, view->entity_id); if (entity) { host->SetWindowlessFrameRate( FrameRateForView(surface, view, ContentBounds(camera, entity))); } } } } } } // namespace extern "C" int32 Canvas_Web_Surface_Execute_Subprocess( int argc, char **p_argv) { g_argc = argc; FindResourcePaths(); int32 cef_argc = 0; for (; cef_argc < argc && cef_argc < 120; ++cef_argc) { g_cef_argv[cef_argc] = p_argv[cef_argc]; } #if defined(OS_LINUX) if (g_resources_path[0]) { std::snprintf( g_resources_switch, sizeof(g_resources_switch), "--resources-dir-path=%s", g_resources_path); std::snprintf( g_locales_switch, sizeof(g_locales_switch), "--locales-dir-path=%s", g_locales_path); g_cef_argv[cef_argc++] = g_resources_switch; g_cef_argv[cef_argc++] = g_locales_switch; } #endif g_cef_argv[cef_argc++] = const_cast<char *>("--no-sandbox"); g_cef_argv[cef_argc++] = const_cast<char *>("--disable-gpu"); g_cef_argv[cef_argc++] = const_cast<char *>("--disable-gpu-compositing"); g_argc = cef_argc; g_argv = g_cef_argv; CefMainArgs args = MainArgs(g_argc, g_argv); CefRefPtr<CanvasCefApp> app = new CanvasCefApp(); return CefExecuteProcess(args, app, nullptr); } extern "C" boolean Canvas_Web_Surface_Init( Canvas_Web_Surface *p_surface, Dowa_Arena *p_arena) { std::memset(p_surface, 0, sizeof(*p_surface)); p_surface->p_arena = p_arena; NativeState *state = static_cast<NativeState *>( Dowa_Arena_Allocate(p_arena, sizeof(NativeState))); if (!state) return FALSE; new (state) NativeState(); p_surface->p_native = state; CefSettings settings; settings.windowless_rendering_enabled = true; settings.no_sandbox = true; settings.multi_threaded_message_loop = false; settings.external_message_pump = false; settings.log_severity = LOGSEVERITY_DISABLE; const char *cache_path = std::getenv("INFINITE_CANVAS_CEF_CACHE"); if (cache_path) { CefString(&settings.root_cache_path) = cache_path; CefString(&settings.cache_path) = cache_path; } #if defined(OS_LINUX) ConfigureLinuxResourcePaths(settings); #endif CefMainArgs args = MainArgs(g_argc, g_argv); CefRefPtr<CanvasCefApp> app = new CanvasCefApp(); if (!CefInitialize(args, settings, app, nullptr)) { state->~NativeState(); p_surface->p_native = nullptr; return FALSE; } state->cef_initialized = TRUE; p_surface->initialized = TRUE; return TRUE; } extern "C" boolean Canvas_Web_Surface_Update_Input( Canvas_Web_Surface *p_surface, const Canvas_Camera *p_camera, const Canvas_Scene *p_scene) { if (!p_surface->initialized || !p_surface->p_native) return FALSE; NativeState *state = static_cast<NativeState *>(p_surface->p_native); Vector2 mouse = GetMousePosition(); Canvas_Web_View *pointer_view = nullptr; const Canvas_Entity *pointer_entity = nullptr; Rectangle pointer_bounds = {}; for (int32 index = CANVAS_MAX_WEB_VIEWS - 1; index >= 0; --index) { Canvas_Web_View *view = &p_surface->views[index]; if (!view->active) continue; const Canvas_Entity *entity = FindSurfaceEntity(p_scene, view->entity_id); if (!entity || entity->type != CANVAS_ENTITY_WEB_CONTENT) continue; Rectangle bounds = ContentBounds(p_camera, entity); if (CheckCollisionPointRec(mouse, bounds)) { pointer_view = view; pointer_entity = entity; pointer_bounds = bounds; break; } } if (IsMouseButtonPressed(MOUSE_BUTTON_LEFT)) { p_surface->focused_entity_id = pointer_view ? pointer_view->entity_id : 0; for (int32 index = 0; index < CANVAS_MAX_WEB_VIEWS; ++index) { CefRefPtr<CanvasCefClient> client = state->clients[index]; if (client && client->browser()) { client->browser()->GetHost()->SetFocus( pointer_view == &p_surface->views[index]); } } } Canvas_Web_View *focused_view = FindView(p_surface, p_surface->focused_entity_id); Canvas_Web_View *input_view = pointer_view ? pointer_view : focused_view; if (!input_view || !input_view->active) return FALSE; int32 view_index = ViewIndex(p_surface, input_view); CefRefPtr<CanvasCefClient> client = state->clients[view_index]; CefRefPtr<CefBrowser> browser = client ? client->browser() : nullptr; const Canvas_Entity *input_entity = pointer_entity ? pointer_entity : FindSurfaceEntity(p_scene, input_view->entity_id); if (!browser || !input_entity) return pointer_view != nullptr; Rectangle bounds = pointer_view ? pointer_bounds : ContentBounds(p_camera, input_entity); boolean inside = pointer_view == input_view; float scale_x = static_cast<float>(input_view->texture_width) / std::max(bounds.width, 1.0f); float scale_y = static_cast<float>(input_view->texture_height) / std::max(bounds.height, 1.0f); CefMouseEvent event; event.x = static_cast<int>((mouse.x - bounds.x) * scale_x); event.y = static_cast<int>((mouse.y - bounds.y) * scale_y); event.modifiers = 0; CefRefPtr<CefBrowserHost> host = browser->GetHost(); host->SendMouseMoveEvent(event, !inside); if (inside && IsMouseButtonPressed(MOUSE_BUTTON_LEFT)) { host->SendMouseClickEvent(event, MBT_LEFT, false, 1); } if (IsMouseButtonReleased(MOUSE_BUTTON_LEFT) && p_surface->focused_entity_id == input_view->entity_id) { host->SendMouseClickEvent(event, MBT_LEFT, true, 1); } if (inside) { float wheel = GetMouseWheelMove(); if (wheel != 0.0f) { host->SendMouseWheelEvent(event, 0, static_cast<int>(wheel * 80.0f)); } } if (p_surface->focused_entity_id == input_view->entity_id) { int32 codepoint = GetCharPressed(); while (codepoint > 0) { CefKeyEvent key; key.type = KEYEVENT_CHAR; key.windows_key_code = codepoint; key.native_key_code = codepoint; key.character = static_cast<char16_t>(codepoint); key.unmodified_character = static_cast<char16_t>(codepoint); host->SendKeyEvent(key); codepoint = GetCharPressed(); } struct { int32 raylib_key; int32 browser_key; } keys[] = { {KEY_BACKSPACE, 0x08}, {KEY_ENTER, 0x0D}, {KEY_LEFT, 0x25}, {KEY_UP, 0x26}, {KEY_RIGHT, 0x27}, {KEY_DOWN, 0x28}, {KEY_DELETE, 0x2E}, }; for (const auto &key_mapping : keys) { if (!IsKeyPressed(key_mapping.raylib_key)) continue; CefKeyEvent key; key.windows_key_code = key_mapping.browser_key; key.native_key_code = key_mapping.browser_key; key.type = KEYEVENT_RAWKEYDOWN; host->SendKeyEvent(key); key.type = KEYEVENT_KEYUP; host->SendKeyEvent(key); } } return inside || p_surface->focused_entity_id == input_view->entity_id; } extern "C" void Canvas_Web_Surface_Sync( Canvas_Web_Surface *p_surface, const Canvas_Camera *p_camera, const Canvas_Scene *p_scene) { if (!p_surface->initialized) return; CefDoMessageLoopWork(); ReconcileViews(p_surface, p_camera, p_scene); for (int32 index = 0; index < CANVAS_MAX_WEB_VIEWS; ++index) { Canvas_Web_View *view = &p_surface->views[index]; if (!view->browser_created) continue; if (!view->texture_ready && IsWindowReady()) { Image image = { .data = view->p_pixels, .width = view->texture_width, .height = view->texture_height, .mipmaps = 1, .format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8, }; view->texture = LoadTextureFromImage(image); view->texture_ready = view->texture.id != 0 ? TRUE : FALSE; if (view->texture_ready) { SetTextureFilter(view->texture, TEXTURE_FILTER_BILINEAR); } } if (view->texture_ready && view->pixels_dirty) { UpdateTexture(view->texture, view->p_pixels); view->pixels_dirty = FALSE; } } } extern "C" void Canvas_Web_Surface_Draw( const Canvas_Web_Surface *p_surface, const Canvas_Camera *p_camera, const Canvas_Scene *p_scene) { for (int32 index = 0; index < CANVAS_MAX_WEB_VIEWS; ++index) { const Canvas_Web_View *view = &p_surface->views[index]; if (!view->active || !view->texture_ready) continue; const Canvas_Entity *entity = FindSurfaceEntity(p_scene, view->entity_id); if (!entity) continue; Rectangle bounds = ContentBounds(p_camera, entity); float left = std::max(bounds.x, 0.0f); float top = std::max(bounds.y, 0.0f); float right = std::min( bounds.x + bounds.width, static_cast<float>(p_camera->viewport_width)); float bottom = std::min( bounds.y + bounds.height, static_cast<float>(p_camera->viewport_height)); if (right <= left || bottom <= top) continue; BeginScissorMode( static_cast<int>(left), static_cast<int>(top), static_cast<int>(right - left), static_cast<int>(bottom - top)); DrawTexturePro( view->texture, Rectangle{ 0.0f, 0.0f, static_cast<float>(view->texture_width), static_cast<float>(view->texture_height), }, bounds, Vector2{0.0f, 0.0f}, 0.0f, WHITE); EndScissorMode(); } } extern "C" boolean Canvas_Web_Surface_Is_Focused( const Canvas_Web_Surface *p_surface) { return p_surface->focused_entity_id != 0; } extern "C" void Canvas_Web_Surface_Shutdown(Canvas_Web_Surface *p_surface) { if (!p_surface->p_native) return; NativeState *state = static_cast<NativeState *>(p_surface->p_native); for (int32 index = 0; index < CANVAS_MAX_WEB_VIEWS; ++index) { if (state->clients[index] && state->clients[index]->browser()) { state->clients[index]->browser()->GetHost()->CloseBrowser(true); } state->clients[index] = nullptr; if (p_surface->views[index].texture_ready) { UnloadTexture(p_surface->views[index].texture); } Dowa_Free(p_surface->views[index].p_pixels); } CefDoMessageLoopWork(); if (state->cef_initialized) CefShutdown(); state->~NativeState(); p_surface->p_native = nullptr; p_surface->initialized = FALSE; }