CREATING WASM GAUGES

WASM (C/C++) gauges can be developed for Microsoft Flight Simulator 2024 with the Microsoft Flight Simulator Platform Toolset. Those gauges will draw in a bitmap which will replace a texture inside the aircraft.

 

 

MSFS 2024 vs MSFS 2020

The way WASM gauges are implemented has changed between MSFS 2020 and MSFS 2024, and even though the legacy methods still work, we strongly advise you to use the new MSFS 2024 way of working. Thanks to these changes, WASM gauges in Microsoft Flight Simulator 2024 benefit from better optimisations and a much clearer way to define them. These changes have been made to remove the burden of legacy code from Microsoft Flight Simulator X that no longer makes sense in the new engine architecture.

 

In Microsoft Flight Simulator 2020, all gauge functions - update and draw - were executed during the draw time. However, because the update function doesn't draw anything (and must not), it could be moved in Microsoft Flight Simulator 2024 to the actual update time. Thanks to that change, the new gauge system uses the entire frame while the old one used only a small part of it which has led to improved performance.

 

In Microsoft Flight Simulator 2020 and previously, gauge functions could be called before and after a specific timing: pre-update, post-update, pre-draw, post-draw, etc... This made some sense in FSX, but less-so in MSFS 2020 where some pre/post function were called sequentially, and now in MSFS 2024 it makes no sense at all. That is why new callbacks have been created to make the gauge system clearer.

 

 

Creating A New Gauge For Your Project

As explained in the Creating A WASM Project page, you first need to create a MSFS 2024 WASM Module. By doing that, you will get a new project with only the module_init and module_deinit functions.

 

To create a new gauge, right click on your project in the solution explorer and add a new file. In the selection page, choose "MSFS" and pick "Gauge".

 

After selecting the MSFS WASM Module template, you will be presented with the following basic project, which we'll be editing:

MSFS_CALLBACK bool [MODULE_NAME]_gauge_init(FsContext ctx, sGaugeInstallData* pInstallData)
{
    // This is called during initialization of the gauge
    // Width given in p_install_data->iSizeX
    // Height given in p_install_data->iSizeY
    NVGparams params;
    params.userPtr = ctx;
    params.edgeAntiAlias = true;
    NVGcontext* nvgCtx = nvgCreateInternal(&params);
    g_[MODULE_NAME]Vars.m_nvgCtx = nvgCtx;
    // do initialization
    // ...
    // return false if there is any error
    return true; // if successfully intialized
}
  • The [MODULE_NAME]_gauge_init is the initialisation function - the first function to be called in the gauge cycle - that allows you to initialise everything related to the gauge.
    • The pInstallData parameter points to a sGaugeInstallData structure:
      • The iSizeX member gives the width of the gauge bitmap.
      • The iSizeY member gives the height of the gauge bitmap.
      • The strParameters member gives the optional parameter string.

 

MSFS_CALLBACK bool [MODULE_NAME]_gauge_update(FsContext ctx, float dTime)
{
    // This is called each frame
    // dTime is the time between the previous frame and this one
    // return false if there is any error
    return true;
}
  • The [MODULE_NAME]_gauge_update is the update function dedicated to this gauge. It will be called once per frame. This function must not use any draw functions because it will be called at a moment in which draw system in not ready.
    • The dTime parameter represents the time during the previous update and this one.

 

MSFS_CALLBACK bool [MODULE_NAME]_gauge_draw(FsContext ctx, sGaugeDrawData* pDrawData)
{
    // This is called each frame, this is the only callback in which it is possible to draw
    // update visual layout
    float pxRatio = (float)p_draw_data->fbWidth / (float)pDrawData->winWidth;
    NVGcontext* nvgCtx = g_[MODULE_NAME]Vars.m_nvgCtx;
    nvgBeginFrame(nvgCtx, pDrawData->winWidth, pDrawData->winHeight, pxRatio);
    {
        // render
        // ...
    }
    nvgEndFrame(nvgCtx);
    // return false if there is any error
    return true;
}
  • The [MODULE_NAME]_gauge_draw is the draw function dedicated to this gauge. It will be called once per frame. All draw function calls must be made in the context of this function.
  • The pDrawData parameter points to a sGaugeDrawData structure:
    • The mx member gives the X-coordinate of the mouse in texture space.
    • The my member gives the Y-coordinate of the mouse in texture space.
    • The t member gives the absolute simulation time.
    • The dt member gives the time elapsed since last frame.
    • The winWidth member gives the width of the gauge bitmap.
    • The winHeight member gives the height of the gauge bitmap.
    • The fbWidth member gives the width of the gauge bitmap.
    • The fbHeight member gives the height of the gauge bitmap.

 

MSFS_CALLBACK bool [MODULE_NAME]_gauge_kill(FsContext ctx)
{
    // This is called before the gauge is destroyed
    NVGcontext* nvgCtx = g_[MODULE_NAME]Vars.m_nvgCtx;
    nvgDeleteInternal(nvgCtx);
    // return false if there is any error
    return true;
}
  • The [MODULE_NAME]_gauge_kill is the de-initialisation function, last function to be called in the gauge cycle, that allow you to de-initialise everything related to the gauge.

 

MSFS_CALLBACK void [MODULE_NAME]_gauge_mouse_handler(FsContext ctx, float fX, float fY, int iFlags)
{
    // This is called when the user click on the gauge, enum for iFlags are in "MSFS/Types/MSFS_EventsEnum.h"
}

 

 

Handling Mouse Events

It may be that you also need your gauge to deal with the input from users, and in that case you will need to add in mouse events. Mouse events are forwarded to your gauge through a mouse callback that is defined as follows:

extern "C" {
    MSFS_CALLBACK void <GAUGENAME>_mouse_callback(float fX, float fY, unsigned int iFlags)
    {
        ...
    }
}

 

The parameters in this callback are:

  • The fX parameter gives the X-coordinate of the mouse in texture space.
  • The fY parameter gives the Y-coordinate of the mouse in texture space.
  • The iFlags parameter is a combination of the various MOUSE_* flags defined in gauges.h. Supported flags are:
    • MOUSE_MOVE: the mouse cursor has moved.
    • MOUSE_LEFTDRAG: left button down.
    • MOUSE_RIGHTDRAG: right button down.
    • MOUSE_MIDDLEDRAG: middle button down.
    • MOUSE_LEFTRELEASE: left button up.
    • MOUSE_RIGHTRELEASE: right button up.
    • MOUSE_MIDDLERELEASE: middle button up.
    • MOUSE_LEFTSINGLE: left button click (sent after a down/up sequence).
    • MOUSE_RIGHTSINGLE: right button click (sent after a down/up sequence).
    • MOUSE_MIDDLESINGLE: middle button click (sent after a down/up sequence).
    • MOUSE_LEFTDOUBLE: left button click (sent after two consecutive clicks).
    • MOUSE_MIDDLEDOUBLE: middle button click (sent after two consecutive clicks).
    • MOUSE_WHEEL_UP: wheel moved up.
    • MOUSE_WHEEL_DOWN: wheel moved down.

 

 

Editing The WASM Module Project

Now you've initialised the new WASM project and setup Visual Studio, you'll need to edit the source code to include the minimum header files - supplied by the SDK - to create the module. You can find all the available files that can be included from the following SDK location:

<SDK Root>\WASM\include\MSFS

Location Of The MSFS.h File

 

These different API's are as follows:

 

 

Adding The Gauge To Your Aircraft

Once you have created the gauge, you need to be able to add it to your aircraft and to test it. This means that you should already have created an aircraft package (as explained here: Creating The Project), and after building the WASM module from Visual Studio, you will want to save it beside the panel.cfg file in the PackageSources directory, as shown in the following image taken from the Gauge Aircraft Sample:

The Location For Saving The WASM Module

 

Since Microsoft Flight Simulator 2024, it is also possible to put your WASM file in a wasm folder (instead of the panel one). Thanks to that, the same WASM file can be use to handle WASM gauges and WASM systems at the same time. If a WASM system is implemented but the WASM file is located in the panel folder, it will not be accessible.

 

Once you have built the module and saved it to the appropriate location, you need to tell the aircraft to reference it. This is done in the panel.cfg, under the [VCockpitN] header, something like this:

[VCockpit01]
size_mm=1024,768
pixel_size=1024,768
texture=TEXTURE
background_color=0,0,255
htmlgauge00=WasmInstrument/WasmInstrument.html?wasm_module=<MODULENAME>.wasm&wasm_gauge=<GAUGENAME>, 0, 0, 1024, 768, <OPTIONAL PARAMETER STRING>

 

The important thing to note here is that all WASM modules will need a [VCockpitN] definition, and the path (defined by the htmlgauge00 parameter) will always take the same prefixed format:

WasmInstrument/WasmInstrument.html?wasm_module=

 

When you have the file in the correct location, you can go ahead and build the package (as explained here: Building A Package). Once built, you will need to restart the flight to see the module in the simulation. Note that this is the case for every edition you make to the WASM code - afterwards it will need exported, built, and the flight restarted.

 

 

Creating A New Gauge For Your Project - MSFS 2020

The old system (which is now deprecated) works with a unique callback which will receive an enum telling the user when this callback is called and a pointer that must be cast accordingly.

 

extern "C" {
    MSFS_CALLBACK bool [MODULE_NAME]_gauge_callback(FsContext ctx, int service_id, void* pData)
    {
        switch (service_id)
        {
            // further code here ...
        }
        return false;
    }
}

 Check Events Received By The Gauge Callback for more information.

 

 

Events Received By The Gauge Callback

Once the gauge has been loaded by Microsoft Flight Simulator 2020, the following events are sent to the callback function so they can be intercepted and acted on (if required):

 

  • PANEL_SERVICE_PRE_INSTALL: sent before the gauge is installed. The pData parameter points to a sGaugeInstallData structure:
    • The iSizeX member gives the width of the gauge bitmap.
    • The iSizeY member gives the height of the gauge bitmap.
    • The strParameters member gives the optional parameter string.
  • PANEL_SERVICE_POST_INSTALL: sent after the gauge has been installed. The pData parameter points to a sGaugePostInstallData structure:
    • The ctx member is a pointer to the FsContext used by the gauge (it is the same as the ctx parameter provided to the callback function).
  • PANEL_SERVICE_PRE_INITIALIZE: sent before the gauge is initialized. The pData parameter is null.
  • PANEL_SERVICE_POST_INITIALIZE: sent after the gauge has been initialized. The pData parameter is null.
  • PANEL_SERVICE_PRE_UPDATE: sent before the gauge is updated. The pData parameter is null.
  • PANEL_SERVICE_POST_UPDATE: sent after the gauge has been updated. The pData parameter is null.
  • PANEL_SERVICE_PRE_DRAW: sent before the gauge is drawn. The pData parameter points to a sGaugeDrawData structure:
    • The mx member gives the X-coordinate of the mouse in texture space.
    • The my member gives the Y-coordinate of the mouse in texture space.
    • The t member gives the absolute simulation time.
    • The dt member gives the time elapsed since last frame.
    • The winWidth member gives the width of the gauge bitmap.
    • The winHeight member gives the height of the gauge bitmap.
    • The fbWidth member gives the width of the gauge bitmap.
    • The fbHeight member gives the height of the gauge bitmap.
  • PANEL_SERVICE_POST_DRAW: sent after the gauge has been drawn. The pData parameter points to a sGaugeDrawData structure (see above).
  • PANEL_SERVICE_PRE_KILL: sent before the gauge is deleted. The pData parameter is null.
  • PANEL_SERVICE_POST_KILL: sent after the gauge has been deleted. The pData parameter is null.