This tutorial walks through rendering a web-page on the CPU, copying it to an engine texture, and forwarding native mouse and keyboard events.

> 👍 Using the CPU renderer
>
> The CPU renderer is the easiest way to start— for rendering directly on the GPU, see [GPU Renderer Overview](/docs/2.0/gpu-renderer-overview).

## 1. Set Up the Platform

Ultralight delegates most platform-specific tasks to the `Platform` singleton for stuff like loading files (eg, `file://` URLs), copying to the clipboard, and logging.

To help you get started, the (optional) AppCore module provides ready-made helpers for these handlers. (You can use these without calling `App::Create()` to retain control over your run loop.)

<!-- tabs:start -->
```cpp
#include <Ultralight/Ultralight.h>
#include <AppCore/Platform.h>

using namespace ultralight;

void InitPlatform() {
  ///
  /// Use the OS's native font loader.
  ///
  Platform::instance().set_font_loader(GetPlatformFontLoader());

  ///
  /// Use the OS's native file system, with a base directory of "./assets/".
  /// All file:/// URLs will load relative to this base directory.
  ///
  Platform::instance().set_file_system(GetPlatformFileSystem("./assets/"));

  ///
  /// Use the default logger (writes to a log file).
  ///
  Platform::instance().set_logger(GetDefaultLogger("ultralight.log"));
}
```
```c
#include <AppCore/CAPI.h>

void InitPlatform(void) {
  ///
  /// Use the OS's native font loader.
  ///
  ulEnablePlatformFontLoader();

  ///
  /// Use the OS's native file system, with a base directory of "./assets/".
  /// All file:/// URLs will load relative to this base directory.
  ///
  ULString base_dir = ulCreateString("./assets/");
  ulEnablePlatformFileSystem(base_dir);
  ulDestroyString(base_dir);

  ///
  /// Use the default logger (writes to a log file).
  ///
  ULString log_path = ulCreateString("ultralight.log");
  ulEnableDefaultLogger(log_path);
  ulDestroyString(log_path);
}
```
<!-- tabs:end -->

> 🚧 Set handlers before creating the renderer
>
> You should register platform handlers before calling `Renderer::Create()`. If the library needs a font loader or file system and none is set, it exits the process with an error.

> 📘 Logging and custom handlers
>
> The default logger writes to a file— to route messages to an engine log instead, register a custom `Logger` (see [Logging and Console Messages](/docs/2.0/logging-and-console-messages)). To provide a custom font loader or file system, see [Setting Up the Platform](/docs/2.0/setting-up-the-platform).

## 2. Create the Renderer and View

Initialize the `Renderer` singleton, then create an offscreen `View` to load page content.

<!-- tabs:start -->
```cpp
RefPtr<Renderer> renderer;
RefPtr<View> view;

void CreateRendererAndView() {
  ///
  /// Create our Renderer (call this only once per application, after
  /// setting up the Platform).
  ///
  renderer = Renderer::Create();

  ///
  /// Create an offscreen View, 500 by 500 pixels large. Views use the
  /// CPU renderer by default.
  ///
  view = renderer->CreateView(500, 500, ViewConfig(), nullptr);

  ///
  /// Load raw HTML into the View asynchronously.
  ///
  view->LoadHTML("<h1>Hello from your game!</h1>");
}
```
```c
static ULRenderer renderer = NULL;
static ULView view = NULL;

void CreateRendererAndView(void) {
  ///
  /// Create our Renderer (call this only once per application, after
  /// setting up the Platform). The Config is passed here in C, and is
  /// destroyed once the Renderer exists.
  ///
  ULConfig config = ulCreateConfig();
  renderer = ulCreateRenderer(config);
  ulDestroyConfig(config);

  ///
  /// Create an offscreen View, 500 by 500 pixels large. Views use the
  /// CPU renderer by default (NULL selects the default session).
  ///
  ULViewConfig view_config = ulCreateViewConfig();
  view = ulCreateView(renderer, 500, 500, view_config, NULL);
  ulDestroyViewConfig(view_config);

  ///
  /// Load raw HTML into the View asynchronously.
  ///
  ULString html = ulCreateString("<h1>Hello from your game!</h1>");
  ulViewLoadHTML(view, html);
  ulDestroyString(html);
}

void Shutdown(void) {
  ///
  /// Destroy the View, then the Renderer (anything we create, we destroy).
  ///
  ulDestroyView(view);
  ulDestroyRenderer(renderer);
}
```
<!-- tabs:end -->

`Renderer::CreateView()` allocates an offscreen drawing surface at the given pixel dimensions, rendering on the CPU by default.

### Load Content and Shut Down

`View::LoadHTML()` loads raw HTML strings asynchronously into the page. To load local files or remote pages instead, call `View::LoadURL()` (`file:///` requests resolve through the registered file system).

When shutting down, destroy all active `View` instances before releasing the `Renderer`.

## 3. Run the Frame Loop

Updating the renderer inside the game loop requires three calls split between logic updates and frame presentation.

<!-- tabs:start -->
```cpp
void UpdateLogic() {
  ///
  /// Give the library a chance to handle pending tasks and timers.
  /// Call this as often as you can from your game loop.
  ///
  renderer->Update();
}

void RenderOneFrame() {
  ///
  /// Notify the renderer that the main display has refreshed.
  ///
  renderer->RefreshDisplay(0);

  ///
  /// Render all active Views.
  ///
  renderer->Render();
}
```
```c
void UpdateLogic(void) {
  ///
  /// Give the library a chance to handle pending tasks and timers.
  /// Call this as often as you can from your game loop.
  ///
  ulUpdate(renderer);
}

void RenderOneFrame(void) {
  ///
  /// Notify the renderer that the main display has refreshed.
  ///
  ulRefreshDisplay(renderer, 0);

  ///
  /// Render all active Views.
  ///
  ulRender(renderer);
}
```
<!-- tabs:end -->

### Update Logic

`Renderer::Update()` processes internal timers, network tasks, and JavaScript callbacks. Call this as frequently as possible from your main game loop.

### Present Frames

`Renderer::RefreshDisplay(0)` advances CSS animations, smooth scrolling, and `requestAnimationFrame()` callbacks. Call this once per frame.

`Renderer::Render()` repaints any active views that have changed since the last frame into their pixel surfaces.

For details on display IDs, presentation timestamps, and repaint scheduling, see [Updating and Rendering](/docs/2.0/updating-and-rendering).

## 4. Copy Pixels to an Engine Texture

When using the CPU renderer, check whether the view was repainted and copy the updated pixels to an engine texture.

<!-- tabs:start -->
```cpp
void CopyToTexture() {
  Surface* surface = view->surface();
  if (surface->dirty_bounds().IsEmpty())
    return;  // nothing changed since the last upload

  // The default Surface is a BitmapSurface.
  RefPtr<Bitmap> bitmap = static_cast<BitmapSurface*>(surface)->bitmap();

  auto pixels = bitmap->LockPixelsSafe();  // unlocks at the end of scope
  UploadToEngineTexture(pixels.data(), bitmap->width(), bitmap->height(),
                        bitmap->row_bytes());

  surface->ClearDirtyBounds();
}
```
```c
void CopyToTexture(void) {
  ///
  /// Get the View's Surface and check if the pixels have changed.
  ///
  ULSurface surface = ulViewGetSurface(view);
  if (ulIntRectIsEmpty(ulSurfaceGetDirtyBounds(surface)))
    return;

  ///
  /// Get the underlying Bitmap (the default Surface is a BitmapSurface,
  /// so cast to it). The Bitmap is owned by the Surface-- don't destroy it.
  ///
  ULBitmap bitmap = ulBitmapSurfaceGetBitmap((ULBitmapSurface)surface);

  ///
  /// Lock the Bitmap to access the raw pixels. The format is BGRA,
  /// 8 bits per channel, premultiplied alpha.
  ///
  void* pixels = ulBitmapLockPixels(bitmap);

  ///
  /// Pseudo-code, upload the pixels to one of your engine's textures here.
  ///
  UploadToEngineTexture(pixels, ulBitmapGetWidth(bitmap),
                        ulBitmapGetHeight(bitmap), ulBitmapGetRowBytes(bitmap));

  ///
  /// Unlock the Bitmap and clear the dirty bounds when we're done.
  ///
  ulBitmapUnlockPixels(bitmap);
  ulSurfaceClearDirtyBounds(surface);
}
```
<!-- tabs:end -->

### Pixel Format and Stride

#### Dirty Bounds

`Surface::dirty_bounds()` returns the bounding rectangle of pixels that changed since the last call to `Surface::ClearDirtyBounds()`. If this rectangle is empty, nothing painted and you can skip the texture upload.

#### Pixel Format

Pixels are stored in 32-bit BGRA format with premultiplied alpha. Use `Bitmap::row_bytes()` for the row stride during upload (rows may be padded).

#### Surface Allocation

The `View` owns the `Surface`, so you should not destroy it— to render directly into memory you own, see [Render Surfaces](/docs/2.0/using-a-custom-surface).

## 5. Forward Input

Forward input events from the engine to the `View` so the page can respond to user actions.

```cpp
void OnEngineMouseMove(int x, int y) {
  ///
  /// Create a MouseEvent and pass it to the View.
  ///
  MouseEvent evt;
  evt.type = MouseEvent::kType_MouseMoved;
  evt.x = x;
  evt.y = y;
  evt.button = MouseEvent::kButton_None;

  view->FireMouseEvent(evt);
}
```

`MouseEvent` specifies the event type (`kType_MouseMoved`, `kType_MouseDown`, or `kType_MouseUp`), the button pressed, and the cursor position. Pass the struct to `View::FireMouseEvent()` to dispatch it to the page.

> 📘 Convert to logical pixels
>
> Coordinates in `MouseEvent` must be in logical pixels relative to the top-left corner of the `View`. If the engine uses physical device pixels, divide the coordinates by `View::device_scale()` before firing the event.

### Keyboard and Scroll Input

Keyboard and scroll events follow the same pattern through `View::FireKeyEvent()` and `View::FireScrollEvent()`. For details on constructing those events, see [Mouse and Scroll Input](/docs/2.0/mouse-and-scroll-input) and [Keyboard Input](/docs/2.0/keyboard-input).

## 6. Display the Texture on a Quad

Draw the texture to an on-screen quad to display it in your game (try to align texels with pixels for the sharpest results).

## What's Next

Congrats! You should now have a live page of HTML rendering inside your game!

From here, [Integrating into a Game Engine](/docs/2.0/integrating-into-a-game-engine) walks through GPU rendering and connecting UI events to game state.
