WORK IN PROGRESS
Nwge (for New Game Engine) is a small, light and fast game engine. It is built to be minimal, powerful cross-platform and convenient. The engine is written in C++20 and contained almost entirely within a single shared library, with smaller additional libraries and tools for handling the engine's file formats.
Array, ArrayView, Slice, String, StringView and other types.The engine is split into various systems, that each have very specific purposes:
The engine itself is compiled with Bip build system. It is not necessary to use Bip to compile your Nwge project, as long as you have a supported compiler, the Nwge headers in the compiler's search path and the Nwge libraries available on your system. The Bip build system does not include any facilities to ease the installation of Nwge on your system. It is best to install the engine manually regardless of what build system you choose.
Nwge requires a compiler that supports C++20. The engine is actively tested with Clang on Windows and Linux, and GCC on Linux (including cross-compilation to Windows with MinGW). Microsoft C++ is theoretically supported, but Nwge's headers aren't being actively checked for compatibility with it.
To compile your code with Nwge, you need to have Nwge's public headers. These headers define Nwge's public API, ie. the functions that you use from your app exposed by Nwge's shared library. This manual assumes you have the Nwge headers installed system-wide, such that #include <nwge/version.h> refers to Nwge's public version.h header. The public headers can be found in the public directory of the SDK archive.
To link your code to the Nwge shared library, you will need to have the appropriate files for your compiler available (the import library nwge.lib for Microsoft C++ and the appropriate shared library file for other compilers). This manual assumes you have the Nwge libraries installed system-wide.
A standard installation of Nwge provides you with the following:
libnwge.so or nwge.dll: the main shared library that contains all engine code.<nwge/...>.libwnge_bndl2.so or nwge_bndl2.dll: a shared library providing functions for reading and writing Nwge Bundle (.bndl) files.nwge_bndl2 library are available under <nwge/bndl2/...>.libnwge_cfn2.so or nwge_cfn2.dll: a shared library providing functions for reading nd writing Nwge Compiled Font (.cfn) files.nwge_cfn2 library are available under <nwge/cfn2/...>.libnwge_cli.so or nwge_cli.dll: a small utility shared library providing basic command-line argument parsing, primarily used by Nwge tools.nwge_cli library are available under <nwge/cli/...>.nwge_bndl2.lib for nwge_bndl2.dll)nwgebndl: a command-line tool for inspecting, extracting and creating Nwge Bundle files using the nwge_bndl2 library. Uses the nwge_cli library for argument parsing.nwgecfn: a command-line tool for inspecting, dumping and compiling Nwge Compiled Font files using the nwge_cfn2 library. Uses the nwge_cli library for argument parsing.At its core, every Nwge app is a state machine. The state system defines a few concepts that you should be familiar with while programming with Nwge:
StateEvery state has a lifetime, which refers to various events while the engine is in the process of changing state or while the state is the current main state.
The various events of a state's lifetime are implemented as virtual methods of the nwge::State class, defined in <nwge/State.hpp>. All of these method have default no-op implementations, it is not necessary to override all of them to have a functional state.
State(StringView &label): the constructor can be provided with a label for the state, which can be helpful during debugging. This label will also appear in frame captures e.g. via RenderDoc. You should not interact with engine APIs at this point, as States can be constructed at any point in time, even before engine initialization.bool preload(): called during the preload phase. Enqueue all necessary data within this method. All engine systems are guaranteed to be initialized and fully usable within this method. You may return false, which will cause your app to shut down. Returning false does not inherently indicate an error. You must report the error to the user yourself, e.g. by using dialog functions.bool init(): called during the init phase. All data enqueued in preload() has successfully been loaded. If any of the data enqueued in preload() failed to load, the engine will never call this method and exit immediately. You may returns false, which will cause your app to shut down. Alike preload(), returning false does not indicate an error. You must report the error to the user yourself.bool on(const Event &evt): called once for each event that occurred in a frame. See the Events section for more information. Alike the above methods, returning false causes your app to shut down but does not indicate an error. You must report the error to the user yourself.bool tick(float delta): called once each frame with the amount of time passed since the last frame, in seconds. This method is only called after all events have been handled by on(). This is where all your per-frame logic should run. You should also set up any data you use for rendering here. Alike the above methods, returning false causes your app to shut down but does not indicate an error.void render() const: called once each frame, after tick() has been called. This is where you issue rendering commands to the GPU. Note that this method is const-qualified, while the others are not. You mustn't modify the state in any way in this method. If you need to calculate some data and use it during rendering, it's best to precalculate it in tick() and store it in the state instead.SubStatesSub-states are essentially slightly lighter, little states that run on top of the main state. Sub-states, like the main state, have a lifetime defined by various events that occur while the state is entering, exiting or inside the sub-state stack.
Note that, unlike the main state, there is no preload or init. Sub-states should generally avoid loading new data. You should pass in data from the main state into sub-states in the constructor. Sub-states can only be pushed onto the stack if there is a valid main state, which means you have the ability to simply load the data there and pass it onto the sub-state.
The various events of a sub-state's lifetime are implemented as virtual methods of the nwge::SubState class, defined in <nwge/SubState.hpp>. All of these method have default no-op implementations, it is not necessary to override all of them to have a functional state.
SubState(StringView &label): the constructor can be provided with a label for the sub-state, which can be helpful during debugging. This label will also appear in frame captures e.g. via RenderDoc. You should not interact with engine APIs at this point, as SubStates can be constructed at any point in time, even before engine initialization.bool on(const Event &evt): called once for each event that occurred in a frame. See the Events section for more information. Returning false causes your app to shut down but does not indicate an error. You must report the error to the user yourself.bool tick(float delta): called once each frame with the amount of time passed since the last frame, in seconds. This method is only called after all events have been handled by on(). This is where all your per-frame logic should run. You should also set up any data you use for rendering here. Alike the above method, returning false causes your app to shut down but does not indicate an error.void render() const: called once each frame, after tick() has been called. This is where you issue rendering commands to the GPU. Note that this method is const-qualified, while the others are not. You mustn't modify the sub-state's data in any way in this method. If you need to calculate some data and use it during rendering, it's best to precalculate it in tick() and store it in the state instead.State in the engine can be represented as a stack. This stack can be then broken into two distinct parts:
State isn't actually stored on a stack in the engine. The main state is stored completely separately from the sub-state stack. Thinking of state as one big stack, however, makes it easier to understand the order in which state is processed.
| Index | State | Logic order | Render order |
|---|---|---|---|
| 2 | SubState 1 | First | Third |
| 1 | SubState 0 | Second | Second |
| 0 | State | Third | First |
The engine can process the state in one of two orders: logic order and render order. Logic order dictates that states at the top of the stack are processed first, and states at the bottom of the stack are processed last. Render order is simply the reverse of logic order.
When the engine dispatches an event to the stack, it is received by the states in logic order. When the engine ticks the stack, the states are also ticked in logic order. Only during the rendering of the state, the states are rendered in the reverse order. The order is defined like this because:
The state stack can be modified in many ways. This includes: changing main state and pushing or popping sub-states. Changing the main state implicitly clear the sub-state stack. This means that, after changing the main state, the engine stack is empty except for the main state. When you change the main state, the change doesn't occur immediately. The state you wish to save to is stored within the engine until the end of the current frame. Only in the next frame will the next state be preloaded. After all the data from that new state is loaded the new state is initialized. When state state successfully initializes, the sub-state stack is switched and the main state is properly switched. This is also the point where the previous state is destroyed.
The same applies to any changes to the sub-state stack. When you push or pop a sub-state from the stack, that change is appended to an internal queue. The changes stored in the queue are only enacted at the end of the frame. This prevents any sub-states from being destroyed while they are still being processed, e.g. if you pop inside the on() method, the sub-state will still be ticked and rendered and only then popped from the stack.
When a sub-state is pushed onto the stack, you can provide some additional options as to how the sub-state behaves in relation to other states on the stack (including main state). These options are represented by the nwge::SubState::Options structure, defined in the <nwge/State.hpp> header.
bool tickParent: whether the state below this sub-state should be ticked. Setting this to false causes all the states below this sub-state to be partially "frozen".bool propagateEvents: whether events should be dispatched to the states below this sub-state. Setting this to false prevents states below this one from receiving any events. This can be used to block user input from reaching the states below. In combination with tickParent = false, the states below are completely "frozen".bool receiveEvents: whether this sub-state should receive events. Setting this to false causes the sub-state's on() method to never be called. This does not affect the propagateEvents option in any way.bool renderParent: whether the state below this sub-state should be rendered. Setting this to false causes all the states below this sub-state to not be rendered.float subTimeScale: a time-scale to apply to only this sub-state. This is a scalar applied to the delta time value passed to only this sub-state's tick() method.
An example usage of a sub-state would be a pause menu in a game. Such sub-state would have both tickParent and propagateEvents set to false, effectively pausing the game. Since it would be on top of the stack, it would be rendered after the main state and all the game sub-states have been rendered, this allowing for a pause overlay to be rendered. This sub-state will receive all user input events and prevent these events from reaching states lower on the stack, so you can safely use the events as input to an UI present on the pause menu. Popping the pause menu sub-state from the stack effectively unpauses the game.
Another example usage of sub-states in a game context is to use the main state as a "director" between different screens of the game. Let's say the game has a "crafting" screen and an "inventory" screen. To switch from the "crafting" screen to the "inventory" screen you simply pop the "crafting" sub-state from the stack and push the "inventory" screen back onto the stack (or use the utility swapSubState function). Ensuring to pass a reference to the main state to the next screen, it has access to all the game data and assets stored in the main state, but the crafting logic and the crafting UI rendering is handled by the sub-state instead.
The engine will dispatch various events to the state, including both user input and notifications of the engine doing certain things. User input events include mouse motion, mouse clicks, mouse wheel scrolling and keyboard key presses. The only engine event currently implemented is a post-load event. User input events are handled by the Input System. Refer to the Input System section for more information. The post-load event is handled by the Data System. Refer to the Data System section for more information.
Every event is stored in the nwge::Event structure, defined in the <nwge/Event.hpp> header. The type of event is determined by the type field, which will an enumeration of the nwge::Event::Type enum, defined in the same header. Note that Type is not an enum class, meaning its values can be accessed through Event. For example, you can write Event::KeyDown instead of Event::Type::KeyDown.
struct Event { enum Type: u8 { MouseMotion, MouseDown, MouseUp, MouseScroll, KeyDown, KeyUp, PostLoad, TextInput, Max, /* Used to bound arrays internally. */ } type; union { Motion motion; Click clock; float scroll; KeyPress press; const StringView *text; }; };
Many of nwge's various systems utilize so-called engine objects. Engine objects have all their implementation details stored internally within the engine, and are identified by the app with a handle. This allows for the implementation of the object to change between engine versions, or even engine versions without necessitating any changes to the API.
An object handle is just a 32-bit unsigned integer that identifies a particular object by its type and ordinal. By storing the type of object being referenced the engine can ensure that the right object types are being passed into the correct APIs and avoid hard-to-fix bugs. The ordinal turns retrieving the object's implementation details as simple as an index into an internal array, called the object pool. Each pool contains some limited number of object slots. Each slots keeps track of its own reference count. When you acquire a handle to an object, the object slot the handle is referencing has its reference count incremented. When the handle disappears, the reference count is decremented. Once the reference count reaches zero the object slot is cleared and the object is effectively destroyed.
The use of reference counting not only allows for engine object handles to be copied at zero cost, but also help ensure that objects don't get destroyed as long as something has a reference to it. An example of where this becomes very useful is when playing sound. An audio source needs a buffer to get audio data from. When a buffer is attached to an audio source, that source has a reference to the buffer and thus the buffer's reference count is incremented. Once the source has a different buffer attached to it, or stops existing, the buffer's reference count is decremented. Only then, if the reference count is zero, will the buffer be destroyed.
Object handles are represented by the nwge::Handle class, defined in the <nwge/Object.hpp> header. You should avoid using this type and use the proper handle types for the object type you're interacting with. The Handle class provides no functionality on its own, other than inspecting the 32-bit value of the handle.
Nwge's render system is the part of the engine that allows for graphics to be shown to the user. It provides both simple built-in rendering operations for basic graphics, as well as the ability to create completely custom rendering pipelines for more complex applications.
Rendering in nwge can be performed in different coordinate spaces. Familiarity with basic computer graphics includes the concepts of world space, clip space and screen space. The world space differs for 2D and 3D rendering operations. Where this manual refers to 2D space, it refers to screen space coordinates, normalized between 0 and 1. The top left corner of the screen is at coordinates X=0, Y=0, and the bottom right corner of the screen is at coordinates X=1, Y=1. Where this manual refers to 3D space, it refers to world space, where negative X is left, positive Y is up and negative Z is forward.
Tip: If you are familiar with Godot, both engines use the same 3D coordinate system.
This manual assumes you are familiar with the basics of linear algebra, as needed for computer graphics. This section aims to explain how the various concepts are represented.
Linear algebra functionality is provided by the OpenGL Mathematics library ("glm"). A full copy of the library is provided with all nwge installations in the nwge/glm directory. A lot of nwge's headers include the proper glm headers as needed, thus you will rarely need to include them yourself. When vector types such as vec3 or matrix types such as mat4 are referenced in this manual, they refer to the types defined in the glm namespace, as provided by glm. Where this manual writes vec4, you will have to write glm::vec4 instead. It is recommended, but not necessary, to familiarize yourself with glm's documentation.
The following are data types defined for use with the render system.
Transform
The Transform structure defines the position, rotation and scale of a three-dimensional object. All the 3D rendering functions accept a Transform as a parameter to position the object to be rendered.
Defined in <nwge/render/Transform.hpp>:
struct Transform { // The position of the object in 3D space. vec3 pos; // The yaw, pitch and roll of the object, in radians. vec3 rot; // The scale of the object in 3D space. vec3 scale; };
AABB
The AABB structure represents an axis-aligned bounding box. Its primary use is for frustum culling with the Camera::isInFrustum method. The AABB is defined by the position of its center and the extents in 3D space.
The AABB structure provides the transformed method, which returns the same AABB but adjusted for the provided Transform. You may also construct an AABB from a minimum point and maximum point using the fromMinMax static method.
Defined in <nwge/render/AABB.hpp>:
struct AABB { vec3 center; vec3 extents; // Create an AABB from a min and max vector: static AABB fromMinMan(vec3 min, vec3 max); // Adjust the AABB for a given transform: AABB transformed(const Transform &transform) const; };
Example usage:
// Given these declarations: Camera mCamera; Mesh mMesh; Transform mTransform; // We can do this: AABB aabb = mMesh.aabb().transformed(transform); if(mCamera.isInFrustum(aabb)) { mMesh.draw3D(); }
Primitive
The Primitive enum represents the various primitives that can be used to assemble a stream of vertices into polygons.
Defined in <nwge/render/Primitive.hpp>:
enum Primitive {
Points,
Lines,
LineStrip,
LineLoop,
LinesAdjacency,
Triangles,
TriangleStrip,
TriangleFan,
TriangleStripAdjacency,
TrianglesAdjacency,
PrimitiveMax
};
Vertex
The Vertex structure represents a singular 3D vertex, as expected by built-in shaders and rendering functions. This same vertex format is also used by the Mesh class.
Defined in <nwge/render/Vertex.hpp>:
struct Vertex { // Position of this vertex in 3D space. vec3 pos; // Texture coordinates of this vertex in texture space. vec2 uv; // Normal vector of this vertex. vec3 normal; // Color/tint of this vertex. vec3 color; };
TexCoord
The TexCoord structures allows for texture coordinates to be offset and scaled during rendering. This effectively defines a rectangular region of the texture to be used instead of the entire texture. The pos member defines the top-left corner of that region and the sz member defines the extents of that region from the left corner.
Defined in <nwge/render/Vertex.hpp>:
struct TexCoord { // Offset applied to texture coordinates in texture space. vec2 pos; // Scale of the texture coordinates in texture space. vec2 sz; };
Example usage:
// Top-right quadrant of the texture: TexCoord topLeftQuadrant{ .pos{0.5f, 0.0f}, .sz{0.5f, 0.5f}, }; // Bottom half of the texture: TexCoord bottomHalf{ .pos{0.0f, 0.5f}, .sz{1.0f, 0.5f}, }; // A small square in the center of the texture: TexCoord centerSquare{ .pos{0.45f, 0.45f}, .sz{0.1f, 0.1f}, };
RGB & RGBA
The RGB and RGBA structures define a singular pixel consisting of three or four 8-bit unsigned integers respectively. They are mostly used to interact with the data of Images.
Defined in <nwge/render/Image.hpp>:
struct RGB { u8 r; u8 g; u8 b; }; struct RGBA { u8 r; u8 g; u8 b; u8 a; };
Image
The Image class represents an image, which is essentially a two-dimensional array of pixels. An image consists of between one and four color channels made up of 8-bit pixels. The channels are interpreted as follows:
RGB structureRGBA structureImage object does not have to own the pixels it refers to, in which case it serves as a view over image data instead.
Defined in <nwge/render/Image.hpp>:
class Image { // Create an empty image: Image(); // Create an image with a specific width, height and channel count: Image(u32 width, u32 height, u32 channelCount); Image(uvec2 size, u32 channelCount); Image(ivec2 size, u32 channelCount); // Create an image as a view over preexisting data: Image(u32 width, u32 height, u32 channelCount, u8 *data); // Change the size of the image, not preserving the data within it: void setSize(u32 width, u32 height, u32 channelCount); void setSize(uvec2 size, u32 channelCount); void setSize(ivec2 size, u32 channelCount); // Get the image width: u32 width() const; // Get the image height: u32 height() const; // Get the image's channel count: u32 channelCount() const; // Get the image's bit-depth: u32 bitDepth() const; // Get the total number of pixels in the image: usize pixelCount() const; // Get the total number of bytes in the image: usize byteCount() const; // Get the distance between rows in the image: usize stride() const; // Get a view over the image's data, as bytes: ArrayView<u8> data(); ArrayView<const u8> data() const; // Get a view over the image's pixels, only if RGB: ArrayView<RGB> pixelsRGB(); ArrayView<const RGB> pixelsRGB() const; // Get a view over the image's pixels, only if RGBA: ArrayView<RGBA> pixelsRGBA(); ArrayView<const RGBA> pixelsRGBA() const; // Get a view over a specific pixel in the image: ArrayView<u8> pixelAt(u32 xPos, u32 yPos); ArrayView<const u8> pixelAt(u32 xPos, u32 yPos) const; // Fill the entire image with a color: void fill(RGB color); void fill(RGBA color); // Overwrite a part of another image with this image: void paste(Image &dest, u32 xPos, u32 yPos); const; void paste(Image &dest, u32 xPos, u32 yPos, u32 width, u32 height); const; };
Some parameters of rendering operations are defined as state, meaning they have to be set ahead of time, rather than passed as parameters to rendering functions or methods.
color
The color function changes the draw color (internally called the tint) to a specific RGB or RGBA value. For built-in rendering functions, this causes the drawn geometry to be tinted a specific color, which is achieved by multiplying the original geometry color with the tint color. Custom shaders can access the current draw color by declaring a in_Tint uniform with type vec4.
colorRGB and colorRGBA are additional functions which accept 8-bit integer color values instead of the floating-point values in color. All calls to colorRGB and colorRGBA are equivalent to calls to color with the proper conversion from integer to floating-point.
Defined in <nwge/render/draw.hpp>:
void color(vec4 rgba); void color(vec3 rgb); void color(); /* equivalent to color({1, 1, 1, 1}) */ void colorRGBA(RGBA rgba); void colorRGB(RGB rgb);
enableDepth & disableDepth
The enableDepth and disableDept functions respectively enable and disable depth-testing for following draws. Note that built-in 2D rendering functions will always temporarily disable depth-testing, whereas 3D rendering functions and generic rendering functions will respect whatever option you choose. When changing state, the engine will enable depth-testing. Changing sub-states does not affect whether depth-testing is enabled.
Defined in <nwge/render/draw.hpp>:
void enableDepth(); void disableDepth();
clearDepth
The clearDepth function allows for the depth buffer to be cleared in the middle of a frame. The engine always clears the depth buffer at the start of each frame, it is not necessary to call this function at the beginning of your render() method.
Defined in <nwge/render/draw.hpp>:
void clearDepth();
enableScissor & disableScissor
The enableScissor and disableScissor functions respectively enable and disable the scissor test. The scissor box can be defined with the `scissor` function, described below. The scissor test is disabled by default. When changing state, the engine will also disable the scissor test. Changing sub-states does not affect whether the scissor test is enabled.
Defined in <nwge/render/draw.hpp>:
void enableScissor(); void disableScissor();
scissor
The scissor functions changes the current scissor box. This function may be called while the scissor test is disabled and will take effect once the scissor test is enabled again. By default, the scissor box contains the entire viewport.
Defined in <nwge/render/draw.hpp>:
void scissor(vec2 pos, vec2 size);
CullMode
The CullMode enum defines which faces of a mesh are culled. It offers three values:
Back: the back faces are culled. This is the default setting.Front: the front faces are culled. This allows to render a mesh "inside-out".None: no faces are culled. The mesh effectively becomes double-sided.
Defined in <nwge/render/CullMode.hpp>:
enum class CullMode: u8 { Back, Front, None, Max /* Used to bound arrays internally: do not use! */ };
setCullMode
The setCullMode function, like its name implies, sets the current culling mode. This can be used to pick and choose which meshes have what mode of culling, for example: rendering certain meshes with no culling at all.
Defined in <nwge/render/CullMode.hpp>:
void setCullMode(CullMode mode);
resetCullMode
The resetCullMode function sets the current culling mode back to the default mode. The default culling mode can be changed in config::App.
Defined in <nwge/render/CullMode.hpp>:
void resetCullMode();
Scene
The Scene structure defines the "world" that rendering is performed in. It provides information about whether the color buffer should be cleared on each frame and lighting information to be used by built-in 3D rendering functions.
Defined in <nwge/render/Scene.hpp>:
struct Scene { // Whether the color buffer should be cleared each frame: bool clear; // What color to clear the color buffer to: vec3 clearColor; // Strength of the world light float worldLightStrength; // Position of the world light in the scene, in 3D space: vec3 worldLightPos; // Color of the world light: vec3 worldLightColor; // Strength of ambient lighting: float ambientStrength; // Color of ambient light: vec3 ambientColor; };
setScene
The setScene function sets the scene to be used for rendering. This scene will only apply to rendering operations after the call to setScene and will then carry over to all subsequent frames. It is not necessary to call this function even once, as the engine already provides a default scene. You should only use this function when you want to change the scene from the default one.
Defined in <nwge/render/Scene.hpp>:
void setScene(const Scene &scene);
Nwge provides a set of built-in rendering functions allowing your app to draw basic 2D and 3D graphics without any additional setup. These functions are split into two categories: 2D and 3D rendering. 2D rendering functions render two-dimensional geometry, such as lines, squares, rectangles and text. 3D rendering functions render three-dimensional geometry, such as planes and cubes. All the built-in rendering functions are implemented using internal engine shaders, and are always guaranteed to work regardless of what rendering backend is used. All 2D rendering functions render with depth testing explicitly disabled. If depth testing was enabled before a 2D rendering function runs, then depth testing is only disabled for the specific draws issued by that function.
line2D
The line2D function draws a two-dimensional line on the screen. The start of the line, end of the line and width are provided as parameters to the function. The start and end points are defined in 2D space. The width is defined in pixels. The color of the line is determined by the current draw color. The line is drawn with depth-testing disabled.
Defined in <nwge/render/draw.hpp>:
void line2D(vec2 start, vec2 end, float width); void line2D(vec2 start, vec2 end); /* width = 1 pixel */
square
The square functions draws a two-dimensional square, regardless of the aspect ratio of the window. Internally, this is implemented as a call to rect. The provided position is the center of the square, and the size defines half the length of the side. The position is defined in 2D space. The size is a fraction of the shorter side of the viewport. The square is drawn with depth-testing disabled. If no texture is provided, the color of the square is determined by the current draw color. If a texture is provided, the current draw color is used to tint the texture. Optional texture coordinates may also be provided.
Defined in <nwge/render/draw.hpp>:
// Draw a square with no texture: void square(vec2 pos, float size); // Draw a square with a texture and default texture coordinates: void square(vec2 pos, float size, const Texture &texture); // Draw a square with a texture and specific texture coordinates: void square(vec2 pos, float size, const Texture &texture, const TexCoord &texCoord); // Draw a square with an animated texture and automatic texture coordinates: void square(vec2 pos, float size, const AnimatedTexture &texture);
rect
The rect functions draws a two-dimensional rectangle on the screen. The provided position is the top-left corner of the rectangle. The position is defined in 2D space. The extents define the width and height of the rectangle, extending from the top-left corner. Negative extents will cause the position to be used as the bottom-right corner instead. The extents are defined in 2D space. The rectangle is drawn with depth-testing disabled. If no texture is provided, the color of the rectangle is determined by the current draw color. If a texture is provided, the current draw color is used to tint the texture. Optional texture coordinates may also be provided.
Defined in <nwge/render/draw.hpp>:
// Draw a rectangle with no texture: void rect(vec2 pos, vec2 extents); // Draw a rectangle with a texture and default texture coordinates: void rect(vec2 pos, vec2 extents, const Texture &texture); // Draw a rectangle with a texture and specific texture coordinates: void rect(vec2 pos, vec2 extents, const Texture &texture, const TexCoord &texCoord); // Draw a rectangle with an animated texture and automatic texture coordinates: void rect(vec2 pos, vec2 extents, const AnimatedTexture &texture);
rectOutline
The rectOutline function draws the outline of a two-dimensional triangle as lines. The position of the rectangle, the extents of the rectangle and width of the lines are provided as parameters to the function. The position and extents are defined in 2D space. The width is defined in pixels. The color of the lines is determined by the current draw color. The lines are drawn with depth-testing disabled.
Defined in <nwge/render/draw.hpp>:
void rectOutline(vec2 pos, vec2 extents, float width); void rectOutline(vec2 pos, vec2 extents); /* width = 1 pixel */
text
The text function draws a string of UTF-8 codepoints as two-dimensional rectangles on the screen using the default built-in font. The provided position is the top-left corner of the first glyph. The position is defined in 2D space. The glyphs are correctly scaled for the viewport's aspect ratio and the provided height. The current draw color is used to tint the text.
Defined in <nwge/render/draw.hpp>:
void text(vec2 pos, const StringView &text, float height);
measureText
The measureText function calculates the smallest box that would entirely contain all the glyphs rendered from a given string and height. This can be used to e.g. center text within the viewport.
Defined in <nwge/render/draw.hpp>:
vec2 measureText(const StringView &text, float height);
Example usage:
StringView myString = This text is centered."_sv; float textHeight = 0.05f; glm::vec2 textSize = render::measureText(myString, textHeight); glm::vec2 position = (glm::vec2{1.0f} - textSize) / 2.0f; render::text(position, myString, textHeight);
plane
The plane function renders a three-dimensional plane on the screen. The function requires a Transform, which defines the plane's position, rotation and scale in 3D space. The currently active Camera (or the default camera if one isn't active) determines the on-screen position and projection of the plane. If no texture is provided, the color of the plane is determined by the current draw color. If a texture is provided, the current draw color is used to tint the texture. Optional texture coordinates may also be provided.
Defined in <nwge/render/draw.hpp>:
// Draw a plane with no texture: void plane(const Transform &transform); // Draw a plane with a texture and default texture coordinates: void plane(const Transform &transform, const Texture &texture); // Draw a plane with a texture and specific texture coordinates: void plane(const Transform &transform, const Texture &texture, const TexCoord &texCoord); // Draw a plane with an animated texture and default texture coordinates: void plane(const Transform &transform, const AnimatedTexture &texture);
cube
The cube function renders a three-dimensional cube on the screen. The function requires a Transform, which defines the cube's position, rotation and extents in 3D space. The currently active Camera (or the default camera if one isn't active) determines the on-screen position and projection of the cube. If no texture is provided, the color of the cube is determined by the current draw color. If a texture is provided, the current draw color is used to tint the texture. Optional texture coordinates may also be provided.
Defined in <nwge/render/draw.hpp>:
// Draw a cube with no texture: void cube(const Transform &transform); // Draw a cube with a texture and default texture coordinates: void cube(const Transform &transform, const Texture &texture); // Draw a cube with a texture and specific texture coordinates: void cube(const Transform &transform, const Texture &texture, const TexCoord &texCoord); // Draw a cube with an animated texture and default texture coordinates: void cube(const Transform &transform, const AnimatedTexture &texture);
cubeOutline
The cubeOutline function renders the outlines of a three-dimensional cube as lines on the screen. The function requires a Transform for the cube to be outlined and the width of the lines. The width is defined in pixels. The color of the lines is determined by the current draw color.
Defined in <nwge/render/draw.hpp>:
void cubeOutline(const Transform &transform, float width); void cubeOutline(const Transform &transform); /* width = 1 pixel */
A handful of functions are also provided to manipulate or inspect the window your app is running in.
windowSize
The windowSize function can be used in one of two ways. It can be used to retrieve the current size of the window and to resize the window. The window size is defined in pixels. When the window is resized, it is automatically positioned in the center of the screen. If the window is resized from its default size, either by calling windowSize or resized by the user, the size is saved and restored by the engine automatically the next time your app starts.
Defined in <nwge/render/window.hpp>:
// Retrieve the current window size: ivec2 windowSize(); // Resize the window: void windowSize(ivec2 size); void windowSize(int width, int height);
windowTitle
The windowTitle function changes the window's title. The title must be a C-style NUL-terminated string.
Defined in <nwge/render/window.hpp>:
void windowTitle(ConstCStr title);
windowIcon
The windowIcon function changes the window's icon to the provided image. The image must be either RGB (channelCount = 3) or RGBA (channelCount = 4). This function does nothing on Emscripten.
Defined in <nwge/render/window.hpp>:
void windowIcon(const Image &image);
fullscreen
The fullscreen function changes the window's fullscreen mode. Whether the app is fullscreen or not is remembered across sessions. If the user quits while the window is fullscreen, the window will automatically be set to fullscreen the next time your app is started.
Defined in <nwge/render/window.hpp>:
void fullscreen(bool fullscreen);
toggleFullscreen
The toggleFullscreen function toggles the window's fullscreen mode. Alike fullscreen, the preference will be remembered and restored the next time your app is started.
Defined in <nwge/render/window.hpp>:
void toggleFullscreen();
Nwge's render system provides various objects allowing you to create entirely custom rendering pipelines. Most of these objects map almost 1:1 onto OpenGL objects. Familiarity with OpenGL is highly recommended when working with custom rendering code in nwge.
Render system objects have the following numeric type IDs:
| Type | Numeric ID |
|---|---|
RenderAnimatedTexture | 0x09 |
RenderAspectRatio | 0x0A |
RenderBuffer | 0x0B |
RenderCamera | 0x0C |
RenderFont | 0x0D |
RenderPipeline | 0x0E |
RenderShader | 0x0F |
RenderShaderProgram | 0x10 |
RenderTexture | 0x11 |
RenderVertexArray | 0x12 |
Shader
The Shader class represents a handle to a RenderShader engine object. Nwge Shader objects functions identically to OpenGL shader objects. The getDefaultVertexShader and getDefaultFragmentShader provide some basic default shaders for use with custom rendering.
Defined in <nwge/render/Shader.hpp>:
class Shader { enum Kind: u8 { // Identifies a vertex shader. Vertex, // Identifies a geometry shader. Geometry, // Identifies a fragment shader. Fragment, // Used to bound arrays internally, do not use! KindMax, }; // Create a shader: Shader(Kind kind); // Compile the shader: bool compile(const StringView &src, String &error); bool compile(const StringView &src); // Get a copy of the default vertex shader: static Shader getDefaultVertexShader(); // Get a copy of the default fragment shader: static Shader getDefaultFragmentShader(); // Set the Shader's label: void setLabel(const StringView &label); // Get the Shader's label: StringView getLabel() const; // Get the handle to the underlying engine object: Handle getHandle() const; // Utility to get the name of a Kind: static StringView getKindName(Kind kind); };
ShaderProgram
The ShaderProgram class represents a handle to a RenderShaderProgram engine object. Nwge ShaderProgram objects function identically to OpenGL shader program objects. When a Shader is added to a ShaderProgram, the Shader's reference count is incremented to prevent it from being destroyed while the ShaderProgram still needs it. The reference count is decremented once the ShaderProgram is linked. If all other references to the Shader have disappeared after all ShaderPrograms using the Shader have been linked, the Shader will finally be destroyed.
Defined in <nwge/render/ShaderProgram.hpp>:
class ShaderProgram { // Create a blank ShaderProgram: ShaderProgram(); // Add a Shader to the ShaderProgram: bool add(const Shader &shader); // Link and validate the ShaderProgram: bool link(String &error); // Find a uniform in the linked ShaderProgram: Uniform getUniform(const StringView &name) const; // Set the ShaderProgram's label: void setLabel(const StringView &label); // Get the ShaderProgram's label: StringView getLabel() const; // Get the handle to the underlying engine object: Handle getHandle() const; };
VertexBuffer
The VertexBuffer class represents a handle to a RenderBuffer engine object used to source vertex data during rendering. The class is a template, with the template argument being the type of vertex data stored in the buffer.
Defined in <nwge/render/VertexBuffer.hpp>:
template<typename T> class VertexBuffer { // Create an uninitialized VertexBuffer: VertexBuffer(); // Initialize a VertexBuffer with dynamic storage: void init(BufferUsage usage); // Initialize a VertexBuffer with immutable storage: void init(usize count); // Initialize a VertexBuffer with constant storage: void init(const ArrayView<const T> &data); // Upload data, overwriting the entire buffer: void upload(const ArrayView<const T> &data); // Update a region within the buffer: void update(usize offset, const ArrayView<const T> &data); // Map the buffer into CPU memory: template<BufferAccess Access> BufferMapping<Access, T> map(); template<BufferAccess Access> void map(BufferMapping>Access, T> &mapping); // Set the VertexBuffer's label: void setLabel(const StringView &label); // Get the VertexBuffer's label: StringView getLabel() const; // Get the handle to the underlying engine object: Handle getHandle() const; };
InstanceBuffer
The InstanceBuffer class represents a handle to a RenderBuffer engine object used to source instance data during rendering. The class is a template, with the template argument being the type of instance data stored in the buffer.
Defined in <nwge/render/InstanceBuffer.hpp>:
template<typename T> class InstanceBuffer { // Create an uninitialized InstanceBuffer: InstanceBuffer(); // Initialize a InstanceBuffer with dynamic storage: void init(BufferUsage usage); // Initialize a InstanceBuffer with immutable storage: void init(usize count); // Initialize a InstanceBuffer with constant storage: void init(const ArrayView<const T> &data); // Upload data, overwriting the entire buffer: void upload(const ArrayView<const T> &data); // Update a region within the buffer: void update(usize offset, const ArrayView<const T> &data); // Map the buffer into CPU memory: template<BufferAccess Access> BufferMapping<Access, T> map(); template<BufferAccess Access> void map(BufferMapping>Access, T> &mapping); // Set the InstanceBuffer's label: void setLabel(const StringView &label); // Get the InstanceBuffer's label: StringView getLabel() const; // Get the handle to the underlying engine object: Handle getHandle() const; };
IndexBuffer
The IndexBuffer class represents a handle to a RenderBuffer engine object used to source indices during rendering. The class is a template, with the template argument being the size of the indices. The class provides a Index type, which is the underlying data type used for the indices.
Defined in <nwge/render/IndexBuffer.hpp>:
template<IndexSize Size> class IndexBuffer { // Type of the indices contained in the buffer: using Index = /* u8 | u16 | u32 */; // Create an uninitialized IndexBuffer: IndexBuffer(); // Initialize a IndexBuffer with dynamic storage: void init(BufferUsage usage); // Initialize a IndexBuffer with immutable storage: void init(usize count); // Initialize a IndexBuffer with constant storage: void init(const ArrayView<const Index> &data); // Upload data, overwriting the entire buffer: void upload(const ArrayView<const Index> &data); // Update a region within the buffer: void update(usize offset, const ArrayView<const Index> &data); // Set the IndexBuffer's label: void setLabel(const StringView &label); // Get the IndexBuffer's label: StringView getLabel() const; // Get the handle to the underlying engine object: Handle getHandle() const; };
VertexArray
The VertexArray class represents a handle to a RenderVertexArray engine object. Nwge VertexArray objects function almost identically to OpenGL VAOs. Attaching a VertexBuffer, InstanceBuffer or IndexBuffer to a VertexArray will increment the buffer's reference count for as long as the VertexArray exists. This prevents the buffer from being destroyed while it is needed by the VertexArray. Once all other references to the buffer have disappeared and all VertexArrays using the buffer have been destroyed, the buffer will finally be destroyed.
Defined in <nwge/render/VertexArray.hpp>:
class VertexArray { // Create a blank VertexArray. VertexArray(); // Attach a VertexBuffer to a specific binding index in the VertexArray: template<typename T> void attachVertexBuffer(const VertexBuffer<T> &buffer, u32 binding); // Attach an InstanceBuffer to a specific binding index in the VertexArray: template<typename T> void attachInstanceBuffer(const InstanceBuffer<T> &buffer, u32 binding); // Attach an IndexBuffer to the VertexArray: template<IndexSize Size> void attachIndexBuffer(const IndexBuffer<Size> &buffer); // Set a specific vertex attribute in the VertexArray: void setAttribute(u32 index, u32 binding, const VertexAttribute &attr); // Draw the VertexArray: void draw(const ShaderProgram &program) const; /* primitive = Triangles */ void draw(const ShaderProgram &program, Primitive primitive) const; // Draw the VertexArray with instancing: void drawInstanced(const ShaderProgram &program, usize instanceCount) const; /* primitive = Triangles */ void drawInstanced(const ShaderProgram &program, usize instanceCount, Primitive primitive) const; // Draw the VertexArray with instancing, starting at a specific instance: void drawInstancedBaseInstance(const ShaderProgram &program, usize instanceCount) const; usize baseInstance, /* primitive = Triangles */ void drawInstancedBaseInstance(const ShaderProgram &program, usize instanceCount, usize baseInstance, Primitive primitive) const; // Draw the VertexArray without using indices: void drawArrays(const ShaderProgram &program, Primitive primitive, usize vertexCount); const void drawArrays(const ShaderProgram &program, Primitive primitive, usize vertexCount, usize firstVertex); const // Draw the VertexArray without using indices with instancing: void drawArraysInstanced(const ShaderProgram &program, Primitive primitive, usize instanceCount, usize vertexCount); const void drawArraysInstanced(const ShaderProgram &program, Primitive primitive, usize instanceCount, usize vertexCount, usize firstVertex); const // Draw the VertexArray without using indices with instancing and a base instance: void drawArraysInstancedBaseInstance(const ShaderProgram &program, Primitive primitive, usize instanceCount, usize baseInstance, usize vertexCount); const void drawArraysInstancedBaseInstance(const ShaderProgram &program, Primitive primitive, usize instanceCount, usize baseInstance, usize vertexCount, usize firstVertex); const // Draw the VertexArray using indices: void drawElements(const ShaderProgram &program, Primitive primitive, usize elementCount); const void drawElements(const ShaderProgram &program, Primitive primitive, usize elementCount, usize offset); const // Draw the VertexArray using indices with instancing: void drawElementsInstanced(const ShaderProgram &program, Primitive primitive, usize instanceCount, usize elementCount); const void drawElementsInstanced(const ShaderProgram &program, Primitive primitive, usize instanceCount, usize elementCount, usize offset); const // Draw the VertexArray using indices with instancing and a base instance: void drawElementsInstancedBaseInstance(const ShaderProgram &program, Primitive primitive, usize instanceCount, usize baseInstance, usize elementCount); const void drawElementsInstancedBaseInstance(const ShaderProgram &program, Primitive primitive, usize instanceCount, usize baseInstance, usize elementCount, usize offset); const // Set the VertexArray's label: void setLabel(const StringView &label); // Get the VertexArray's label: StringView getLabel() const; // Set the first few attributes to map to the default vertex layout: void defaultVertexLayout(u32 binding); // Get the handle to the underlying engine object: Handle getHandle() const; };
Nwge's render system can utilize different graphics APIs under the hood, without necessitating any code changes. All the functions provided by the render system map to the appropriate procedures of each underlying graphics API at runtime.
The engine has certain backends it will default to. On Windows and Linux it defaults to GL4. On Emscripten the default is WebGL. You can also specify which backend you'd like the engine to use via the --gl command-line parameter. For example, if you want the engine to use the GL3 backend, run any nwge app with the --gl=GL3 command-line parameter. In case the backend you request is not a known backend or not available in the engine, the default backend is chosen instead.
If the engine cannot initialize the default backend, your app will fail to start. In such cases, you should try running your app with a different backend and updating your OS and graphics drivers. If the issue persists, you should report it to the engine developer for troubleshooting.
GL4
The GL4 backend uses OpenGL 4.6, utilizing modern APIs to provide a boost in speed. The Nwge Render API maps quite closely onto modern OpenGL, and as such this is the most optimal backend to use. This backend is only present in Windows and Linux builds of the engine.
GL3
The GL3 backend uses OpenGL 3.2 with some common extensions, allowing for older hardware that does not support OpenGL 4 to run Nwge apps. It uses some tricks to emulate more modern DSA APIs, which incurs a performance penalty. You should avoid using this backend unless your GPU does not support OpenGL 4. This backend can take advantage of some common extensions, but will work perfectly even if they aren't supported. This backend is only present in Windows and Linux builds of the engine.
GLES
The GLES backend uses OpenGL ES 3.2, primarily intended for an eventual Android port. This backend is only present in Linux builds of the engine.
WebGL
The WebGL backend uses WebGL 2, which itself is based on OpenGL ES 3, intended for web builds. This backend is only present in Emscripten builds of the engine.
Nobody is perfect, which means you'll spend plenty of time debugging your code while working with Nwge.
While a Nwge app is running, pressing F2 will cause the Debug Toolbar to appear at the top of the window. It shows the version of Nwge the app is using as well as buttons that allow access to various tools to inspect or change engine state. Opening the Debug Toolbar also causes the Console to be opened. See the Console section below for more information.
The State button will open the State Information window. It contains some general information about the current main state as well as the entire sub-state stack. It provides the ability to change the time scale, which affects how quickly your app runs (e.g. running it in slow-motion). You may also Pause Updates, which prevents the current states from being ticked or receiving events. When the state is paused, you can use the Tick Once button to advance the state by one tick. The Break and Tick and Break and Render buttons will trigger a breakpoint in your debugger right before the engine invokes your state's tick() or render() method respectively.
The engine provides a simple Console, accessible via the Console button of the Debug Toolbar. The Console is the recommended way for your app to output log messages as well as expose to commands to inspect or alter your app's state. The Console is always automatically opened when the Debug Toolbar is opened with the F2 key.
The nwge::console namespace provides all the functions and types for interacting with the Console. The <nwge/console.hpp> header provides function for outputting to the console, such as console::print, console::warn or console::error. The <nwge/console/Command.hpp> header provides the Command class, which represents a handle to a ConsoleCommand engine object. Such an engine object is a console command that the user can execute. Each command has a unique name and a callback that should be invoked when the user runs the command. The command is only available in the console as long as the corresponding engine object exists.
A slightly different kind of debugging is graphics debugging. Trying to figure out why the rendered graphics don't look like what you want them to can be distressing, which is why the engine provides various tools to try and figure out what's going on.
In the Debug Toolbar, pressing the Renderer button opens the Renderer Information window. It contains information about the GPU and backend API currently in use. Some statistics, such as the number of render passes, the total number of draw calls and the time spent rendering are displayed. You can use the Enable Wireframe toggle to enable or disable an additional wireframe pass, which renders all meshes' wireframe on top of the rendered image.
Running a Nwge app through RenderDoc enabled the engine's integration with the graphics debugger. Pressing Shift+F9 at any moment will trigger a capture. The engine automatically emits debugging information during the render process, which will show up in RenderDoc. All the objects created on the GPU are properly labelled, all render passes are labeled and which draw commands belong to which state or sub-states is also displayed. This manual does not include information on how to use RenderDoc, you should read RenderDoc's documentation instead.
While the engine is running, it will frequently write messages to the Engine Journal. The Journal contains both various information about the system the engine is running on, as well as what the engine is doing at certain moments. The Journal can be used to figure out whether any specific part of the engine is causing issues to occur, and is mostly used to find errors in the engine itself rather than in Nwge apps. The Journal can be viewed in the Journal Viewer window, which can be opened with the Journal button of the Debug Toolbar. You can save the current Journal to a file by pressing F9.
The engine attempts to handle as many errors as possible. When the engine crashes, rather that shutting down straight away, it enters a state called panic. In this state, the entire engine effectively freezes and the user is met with a kill screen. This period of time when the engine is frozen aims to keep the engine state completely unchanged in comparison to the instant in which the crash occurred. The user is encouraged by the kill screen to write down the crash message and instructed to close the window. When the window is closed the engine creates a Engine Dump. The dump contains all engine state in a human-readable textual form, including Console output and the Journal. The engine encourages the user to take the crash message they were given and the dump created by the engine and to send it to the developer. In case you are developing an app with Nwge and get a kill screen, you should send the engine dump to qeaml, preferably over e-mail to qeaml@proton.me.