GFX 3.3 Integration Tutorial
GFX 3.3 Integration Tutorial
This document introduces basic GFx usage and 3D engine integration through a DirectX 9 example.
Author:
Version:
Last Edited:
Ben Mowery
2.04
November 11, 2009
Copyright Notice
Autodesk Scaleform 3
2011 Autodesk, Inc. All rights reserved. Except as otherwise permitted by Autodesk, Inc., this
publication, or parts thereof, may not be reproduced in any form, by any method, for any purpose.
Certain materials included in this publication are reprinted with the permission of the copyright holder.
The following are registered trademarks or trademarks of Autodesk, Inc., and/or its subsidiaries and/or
affiliates in the USA and/or other countries: 3DEC (design/logo), 3December, 3December.com, 3ds
Max, Algor, Alias, Alias (swirl design/logo), AliasStudio, Alias|Wavefront (design/logo), ATC, AUGI,
AutoCAD, AutoCAD Learning Assistance, AutoCAD LT, AutoCAD Simulator, AutoCAD SQL Extension,
AutoCAD SQL Interface, Autodesk, Autodesk Intent, Autodesk Inventor, Autodesk MapGuide,
Autodesk Streamline, AutoLISP, AutoSnap, AutoSketch, AutoTrack, Backburner, Backdraft, Beast,
Built with ObjectARX (logo), Burn, Buzzsaw, CAiCE, Civil 3D, Cleaner, Cleaner Central, ClearScale,
Colour Warper, Combustion, Communication Specification, Constructware, Content Explorer, Dancing
Baby (image), DesignCenter, Design Doctor, Designer's Toolkit, DesignKids, DesignProf, DesignServer,
DesignStudio, Design Web Format, Discreet, DWF, DWG, DWG (logo), DWG Extreme, DWG
TrueConvert, DWG TrueView, DXF, Ecotect, Exposure, Extending the Design Team, Face Robot, FBX,
Fempro, Fire, Flame, Flare, Flint, FMDesktop, Freewheel, GDX Driver, Green Building Studio, Headsup Design, Heidi, HumanIK, IDEA Server, i-drop, Illuminate Labs AB (design/logo), ImageModeler,
iMOUT, Incinerator, Inferno, Inventor, Inventor LT, Kynapse, Kynogon, LandXplorer, LiquidLight,
LiquidLight (design/logo), Lustre, MatchMover, Maya, Mechanical Desktop, Moldflow, Moldflow
Plastics Advisers, MPI, Moldflow Plastics Insight, Moldflow Plastics Xpert, Moondust, MotionBuilder,
Movimento, MPA, MPA (design/logo), MPX, MPX (design/logo), Mudbox, Multi-Master Editing,
Navisworks, ObjectARX, ObjectDBX, Opticore, Pipeplus, PolarSnap, PortfolioWall, Powered with
Autodesk Technology, Productstream, ProMaterials, RasterDWG, RealDWG, Real-time Roto,
Recognize, Render Queue, Retimer, Reveal, Revit, RiverCAD, Robot, Scaleform, Scaleform AMP,
Scaleform CLIK, Scaleform GFx, Scaleform IME, Scaleform Video, Showcase, Show Me, ShowMotion,
SketchBook, Smoke, Softimage, Softimage|XSI (design/logo), Sparks, SteeringWheels, Stitcher, Stone,
StormNET, StudioTools, ToolClip, Topobase, Toxik, TrustedDWG, U-Vis, ViewCube, Visual, Visual
LISP, Volo, Vtour, WaterNetworks, Wire, Wiretap, WiretapCentral, XSI.
All other brand names, product names or trademarks belong to their respective holders.
Disclaimer
THIS PUBLICATION AND THE INFORMATION CONTAINED HEREIN IS MADE AVAILABLE BY
AUTODESK, INC. AS IS. AUTODESK, INC. DISCLAIMS ALL WARRANTIES, EITHER EXPRESS OR
Address
Website
Scaleform Corporation
6305 Ivy Lane, Suite 310
Greenbelt, MD 20770, USA
www.scaleform.com
Direct
(301) 446-3200
Fax
(301) 446-3199
Table of Contents
1. Introduction ................................................................................................................................. 1
2. Documentation Overview ........................................................................................................... 2
3. Installation and Build Dependencies ......................................................................................... 3
3.1 Installation ............................................................................................................................... 3
3.2 Compile the Demos................................................................................................................. 4
3.3 Benchmark SWF Playback in GFxPlayer ................................................................................. 5
3.4 Compile the Sample Base ....................................................................................................... 6
3.5 GFx Build Dependencies ......................................................................................................... 7
4. Game Engine Integration.......................................................................................................... 10
4.1 Rendering Flash .................................................................................................................... 10
4.2 Scaling Modes ...................................................................................................................... 19
4.3 Processing Input Events........................................................................................................ 21
4.3.1 Mouse Events ................................................................................................................. 21
4.3.2 Keyboard Events ............................................................................................................ 23
4.3.3 Hit Testing ...................................................................................................................... 24
4.3.4 Keyboard Focus ............................................................................................................. 25
5. Introduction to Localization and Fonts ................................................................................... 26
5.1 Font Overview and Capabilities ............................................................................................. 26
5.2 Font Example: Embedding Chinese Characters .................................................................... 28
6. IME Overview ............................................................................................................................ 32
7. Interfacing C++, Flash, and ActionScript ................................................................................ 34
7.1 ActionScript to C++ ............................................................................................................... 34
7.1.1 FSCommand Callbacks .................................................................................................. 34
7.1.2 ExternalInterface ............................................................................................................. 37
7.2 C++ to ActionScript............................................................................................................... 40
7.2.1 Manipulating ActionScript Variables ............................................................................... 40
7.2.2 Executing ActionScript Subroutines ............................................................................... 42
7.3 Communication Between Multiple Flash Files ....................................................................... 44
8. Pre-processing with GFxExport............................................................................................... 50
9. Next Steps ................................................................................................................................. 51
1. Introduction
Scaleform GFx is a high-performance proven visual user interface (UI) design middleware solution that
allows developers to leverage Flash Studio to quickly and inexpensively create modern GPUaccelerated animated UI and vector graphics, without learning new tools or processes. GFx creates a
seamless visual development path from Flash Studio directly into the game UI.
In addition to UI, developers can use GFx to display Flash content inside the 3D environment as
animating textures mapped onto 3D surfacesthink Doom 3 screens or Flash UI on 3D
objects. Likewise, 3D objects and video can be displayed inside Flash UI. As a result, Scaleform GFx
works well as either a stand-alone UI solution or as a way to enhance an existing front-end game
framework.
This tutorial will walk through the process of installing and using Scaleform GFx. We will enhance the
DirectX ShadowVolume SDK sample with a Flash-based UI.
Note: Scaleform has already been integrated with most major game engines. Scaleform GFx can be
used directly with supported game engines with minimal coding. This guide is primarily targeted at
engineers planning to integrate Scaleform GFx with a custom game engine, or to those looking for an
in-depth technical overview of Scaleform GFxs capabilities.
Note: Please make sure to use the latest version of GFx when following this tutorial. The tutorial works
with GFx version 2.2 and above.
Note: The tutorial may be incompatible with certain older video cards. This is due to the DirectX SDK
ShadowVolume code on which the tutorial is based and not a compatibility issue with GFx. Should a
cannot create renderer error message be encountered when running the tutorial, the source of the
problem can be determined by checking if the GFxPlayer application runs successfully.
2. Documentation Overview
The latest multilingual GFx documentation can be found online in the Scaleform Developer center at:
http://developer.scaleform.com/. Free registration is required to access the site.
Current documentation includes:
Font Overview: Describes the font and text rendering system and provides details on how
to configure both the art assets and GFx C++ APIs for internationalization.
Scale9 Grid: Explains how to use Scale9Grid functionality to create resizable windows,
panels, and buttons.
IME Configuration: Describes how to integrate GFxs IME support into end-user
applications and how to create custom IME text input window styles in Flash.
RenderTexture:
FxPlayer:
Source for the GFxPlayer D3Dx programs. These are SWF/GFx players
that enable viewing and performance benchmarking of hardware
playback of Flash content.
Projects
Visual Studio projects to build the above applications.
Apps\Samples
Sample source code for features and also this tutorial.
Bin
Contains pre-built demo binaries and sample Flash content:
3DDemo:
RenderTexture:
FxPlayer:
IME:
Samples:
Video Demo:
Win32:
gfxexport.exe:
Note: Re-building the demo .sln Visual Studio projects will replace the binaries in the
Win32 directory.
Include
GFx headers.
Lib
GFx libraries.
Resources
CLIK components and tools.
Doc
PDF documentation described in section 2.
This program and the other GFx Player D3Dx applications on the Start All Programs Scaleform
GFx SDK 3.3 Demo Examples folder are hardware-accelerated SWF players. During
development, GFx Flash playback can be tested and benchmarked with these tools.
Also build the FxPlayer_D3D9_Release_Static configuration to generate libraries for the release build
of the example project we will begin developing in section 3.4.
4. To pan the image, hold down the CTRL key and the right mouse button, then move the
mouse.
5. Press CTRL+Z to return to the normal view.
6. Press CTRL+U to toggle full screen playback.
7. Press F2 for statistics on the movie, including memory usage.
Notice how curved edges like the corners of buttons look sharp even when viewed closely. As the
window is made larger or smaller, the Flash content scales. One advantage of vector graphics is that
content scales to any resolution. Traditional bitmap graphics require one set of bitmaps for 800x600
screens and another for 1600x1200 screens.
GFx also supports traditional bitmap graphics, as can be seen in the Scaleform GFx logo in the right
center of the screen. Almost any content that an artist can create in Flash can be rendered by GFx.
Zoom in on the 3D GFx Logo radio button and switch to wireframe mode by pressing CTRL+W.
Notice that the circle has been tessellated into triangles. Press CTRL+A several times to toggle the
anti-aliasing mode. In the Edge Anti-Aliasing mode (EdgeAA), additional sub-pixel triangles are added
around the edges of the circle to create an anti-aliasing effect. This is typically more efficient than the
video cards full-screen anti-aliasing (FSAA), which requires four times the framebuffer video memory
and four times the pixel rendering in order to perform AA. Scaleforms proprietary EdgeAA technology
leverages the objects vector representation to apply anti-aliasing to only those areas of the screen
that can benefit most, typically curved edges and large text. Although the triangle count will increase,
the performance impact is manageable as the number of draw primitives (DP) remains constant. It is
typically more efficient than using the video cards anti-aliasing function, and EdgeAA as well as other
quality settings can be disabled and adjusted.
The GFxPlayer tools are useful for debugging Flash content and performance benchmarking. Open
task manager and look at the CPU usage. The CPU usage will likely be high, as GFx is rendering as
many frames per second as it can for benchmarking purposes. Press CTRL+Y to lock the frame rate
to the display refresh rate (typically 60 frames per second). Notice that the CPU usage declines
significantly.
Note: When benchmarking your own application, make sure to run a release build, as debug GFx
builds do not provide optimal performance.
Studio 2008 in the windows start menu in Scaleform GFx SDK 3.3 Tutorial. Make sure the project
configuration is set to "Debug" and run the application.
"$(GFXSDK)\Src\GRenderer";" $(GFXSDK)\Src\GKernel";"$(GFXSDK)\Src\GFxXML";"
$(GFXSDK)\Include"
The following library directories should be added to the linker search paths for the debug build
configuration:
$(DXSDK_DIR)\Lib\x86
$(GFXSDK)\3rdParty\expat-2.0.1\lib
$(GFXSDK)\Lib\$(PlatformName)\Msvc80\Debug_Static\
$(GFXSDK)\3rdParty\zlib-1.2.3\Lib\$(PlatformName)\Msvc80\Debug
$(GFXSDK)\3rdParty\jpeg-6b\Lib\$(PlatformName)\Msvc80\Debug
Paste the following string into the Additional Library Directories field:
"$(DXSDK_DIR)\Lib\x86";
"$(GFXSDK)\3rdParty\expat-2.0.1\lib";
"$(GFXSDK)\Lib\$(PlatformName)\Msvc80\debug";
"$(GFXSDK)\3rdParty\zlib-1.2.3\Lib\$(PlatformName)\Msvc80\Debug";
"$(GFXSDK)\3rdParty\jpeg-6b\Lib\$(PlatformName)\Msvc80\Debug"
Note: Change Msvc80 to the string corresponding to your version of Visual Studio.
The corresponding release libraries should be added to the linker search paths for the release and
profile build configurations:
$(DXSDK_DIR)\Lib\x86
$(GFXSDK)\3rdParty\expat-2.0.1\lib
$(GFXSDK)\Lib\$(PlatformName)\Msvc80\Release\
$(GFXSDK)\3rdParty\zlib-1.2.3\Lib\$(PlatformName)\Msvc80\Release
$(GFXSDK)\3rdParty\jpeg-6b\Lib\$(PlatformName)\Msvc80\Release
Paste the following string into the Additional Library Directories field (release and profile
configurations):
"$(DXSDK_DIR)\Lib\x86";
"$(GFXSDK)\3rdParty\expat-2.0.1\lib";
"$(GFXSDK)\Lib\$(PlatformName)\Msvc80\Release";
"$(GFXSDK)\3rdParty\zlib-1.2.3\Lib\$(PlatformName)\Msvc80\Release";
"$(GFXSDK)\3rdParty\jpeg-6b\Lib\$(PlatformName)\Msvc80\Release"
Note: Change Msvc80 to the string corresponding to your version of Visual Studio.
10
#include "GFxFontLib.h"
#include "FxPlayerLog.h"
#include "GRendererD3D9.h"
The GFxSystem object must come into scope before the first GFx call and cannot leave
scope until the application is finished using GFx which is why it is placed in WinMain.
GFxSystem as instantiated here uses GFxs default memory allocator but can be
overridden with an applications custom memory allocator. For the purposes of this tutorial
it is sufficient to simply instantiate GFxSystem and take no further action.
GFxSystem must leave scope before the application terminates, meaning that it should not
be a global variable. In this case it will go out of scope when the GFxTutorial object is freed.
11
Depending on the structure of your particular application it may be easier to call the
GFxSystem::Init() and GFxSystem::Destroy() static functions instead of creating the
GFxSystem object instance.
Step #3: Loader and Renderer Creation
The remainder of GFx initialization will be performed right after the applications WinMain
does its own initialization in InitApp(). Add the following code right after the call to InitApp():
gfx = new GFxTutorial();
assert(gfx != NULL);
if(!gfx->InitGFx())
assert(0);
GFxTutorial contains a GFxLoader object. An application typically has only one GFxLoader
object, which is responsible for loading the SWF/GFx content and storing this content in a
resource library, enabling resources to be reused in future references. Separate SWF/GFx
files can share resources such as images and fonts saving memory. GFxLoader also
maintains a set of configuration states such as GFxLog, used for debug logging.
The first step in GFxTutorial::InitGFx() is to set states on GFxLoader. GFxLoader passes
debug tracing to the handler provided by SetLog. Debug output is very helpful when
debugging, since many GFx functions will output the reason for failure to the log. In this
case we use the default GFxPlayerLog handler, which prints messages to the console
window, but integration with a game engines debug logging system can be accomplished
by subclassing GFxLog.
// Initialize logging -- GFx will print errors to the log
// stream.
gfxLoader->SetLog(GPtr<GFxLog>(*new GFxPlayerLog()));
GFxLoader reads content through the GFxFileOpener class. The default implementation
reads from a file on disk, but custom loading from memory or a resource file can be
accomplished by subclassing GFxFileOpener.
// Give the loader the default file opener
GPtr<GFxFileOpener> pfileOpener = *new GFxFileOpener;
gfxLoader->SetFileOpener(pfileOpener);
12
IDirect3Device9 pointer initialized by the game, so that GFx can create DX9 resources and
successfully render UI content.
// Create a GFx renderer and connect it with our D3D state
pRenderer = *GRendererD3D9::CreateRenderer();
// Associate the renderer with the GFxLoader
pRenderConfig = *new GFxRenderConfig(pRenderer);
gfxLoader->SetRenderConfig(pRenderConfig);
GFxLoaders SetRenderConfig method associates the renderer with the GFxLoader. Every
GFxMovieDef created by the loader will inherit the renderer.
The above code uses the default GRendererD3D9 object supplied with GFx. Subclassing
GRenderer enables better control over GFxs rendering behavior and can result in a tighter
integration.
In addition to containing the pointer to the GRendererD3D9 object, GFxRenderConfig also
manages various rendering parameters such as curve tolerance and EdgeAA:
// Use EdgeAA to improve the appearance of the interface without the
// computational expense of full AA through the video card.
pRenderConfig->SetRenderFlags(GFxRenderConfig::RF_EdgeAA);
EdgeAA adds subpixel triangles around the edges of shapes to create a smoother
appearance, but without the computational expense of enabling full anti-aliasing on the
video card. This is one advantage of vector graphics: the shape information enables antialiasing to be selectively applied to only the areas of the screen that stand to benefit most,
such as button edges and large text characters. Although the triangle count increases, the
overall performance impact is manageable because the draw primitive (DP) count does not
increase.
Step #4: Load a Flash Movie
Now the GFxLoader is ready to load a movie. Loaded movies are represented as
GFxMovieDef objects. The GFxMovieDef encompasses all of the shared data for the movie,
such as the geometry and textures. It does not include per-instance information, such as
the state of individual buttons, ActionScript variables, or the current movie frame.
// Load the movie
pUIMovieDef = *gfxLoader.CreateMovie(UIMOVIE_FILENAME,
GFxLoader::LoadKeepBindData |
GFxLoader::LoadWaitFrame1, 0);
13
The LoadKeepBindData flag maintains a copy of texture images in system memory, which
may be useful if the application will re-create the D3D device. This flag is not necessary on
game console systems or under conditions where it is known that textures will not be lost.
LoadWaitFrame1 instructs CreateMovie not to return until the first frame of the movie has
been loaded. This is significant if GFxThreadTaskManager is used.
The last argument is optional and specifies the memory arenas to be used. Please refer to
the Memory System Overview document for information on creating and using memory
arenas.
Step #5: Movie Instance Creation
Before rendering a movie, a GFxMovieView instance must be created from the
GFxMovieDef object. GFxMovieView maintains state associated with a single running
instance of a movie such as the current frame, time in the movie, states of buttons, and
ActionScript variables.
pUIMovie = *pUIMovieDef->CreateInstance(true, 0);
assert(pUIMovie.getPtr() != NULL);
The first argument to CreateInstance determines whether the first frame is to be initialized.
If the argument is false, we have the opportunity to change Flash and ActionScript state
before the ActionScript first frame initialization code is executed. The last argument is
optional and specifies the memory arenas to be used. Please refer to the Memory System
Overview document for information on creating and using memory arenas.
Once the movie instance is created, the first frame is initialized by calling Advance(). This is
only necessary if false was passed to CreateInstance.
// Advance the movie to the first frame
pUIMovie->Advance(0.0f, 0);
// Note the time to determine the amount of time elapsed between
// this frame and the next
MovieLastTime = timeGetTime();
The first argument to Advance is the difference in time, in seconds, between the last frame
of the movie and this frame. The current system time is recorded to enable calculation of
the time difference between this frame and the next.
In order to alpha blend the movie on top of the 3D scene:
pUIMovie->SetBackgroundAlpha(0.0f);
14
Without the above call, the movie will render but will cover the 3D environment with a
background stage color specified by the Flash file.
Step #6: Device Initialization
GFx must be given the handle to the DirectX device and presentation parameters through
GRenderer in order to render. GRenderer::SetDependentVideoMode should be called after
the D3D device is created and before GFx is asked to render. SetDependentVideoMode
should be called again if the D3D device handle changes, which can occur on window
resizes or fullscreen/windowed transitions.
ShadowVolumes OnResetDevice function is called by the DXUT framework after initial
device creation and also after device reset. The following code is added to the
corresponding OnResetDevice method in GFxTutorial:
HWND hWND = DXUTGetHWND();
pRenderer->SetDependentVideoMode(pd3dDevice, &presentParams,
GRendererD3D9::VMConfig_NoSceneCalls, hWND);
The SetDependentVideoMode() call passes the D3D device and presentation parameters to
GFx. The GRendererD3D9::VMConfig_NoSceneCalls flag specifies that no DirectX
BeginScene() end EndScene() calls will be made by GFx. This is necessary because the
ShadowVolume sample already makes those calls for the application in the
OnFrameRender callback.
Step #7: Lost Devices
When the window is resized or the application is switched to fullscreen, the D3D device will
be lost. All D3D surfaces including vertex buffers and textures must be reinitialized.
ShadowVolume releases surfaces in the OnLostDevice callback. GRenderer can be
informed of the lost device and given a chance to free its D3D resources in the
corresponding OnLostDevice method in GFxTutorial:
pRenderer->ResetVideoMode();
This and the previous step explained initialization and lost devices based on the DXUT
frameworks callback system. For an example of a basic Win32/DirectX render loop, see
the GFxPlayerTiny.cpp example with the GFx SDK.
Step #8: Resource Allocation and Cleanup
15
Because all GFx objects are contained in the GFxTutorial object, cleanup is as simple as
deleting the GFxTutorial object at the end of WinMain:
delete gfx;
gfx = NULL;
GMemory::DetectMemoryLeaks();
16
windowWidth, windowHeight);
The first two parameters to SetViewport specify the size of the framebuffer used, typically
the size of the window for PC applications. The next four parameters specify the size of the
viewport within the framebuffer that GFx is to render into.
The framebuffer size arguments are provided for compatibility with OpenGL and other
platforms that may use different orientation of coordinate systems or not provide a way to
query the framebuffer size.
GFx provides functions to control how Flash content is scaled and positioned within the
viewport. We will examine these options in section 4.2 after the application is ready to run.
Step #10: Rendering into the DirectX Scene
Rendering is performed in ShadowVolumes OnFrameRender() function. All D3D rendering
calls are made between the BeginScene() and EndScene() calls. Well call
GFxTutorial::AdvanceAndRender() before the EndScene() call.
void AdvanceAndRender(void)
{
DWORD mtime = timeGetTime();
float deltaTime = ((float)(mtime - MovieLastTime)) / 1000.0f;
MovieLastTime = mtime;
pUIMovie->Advance(deltaTime, 0);
pUIMovie->Display();
}
Advance moves the movie forward by deltaTime seconds. The speed at which the movie is
played is controlled by the application based on the current system time. It is important to
provide real system time to GFxMovie::Advance to ensure the movie plays back correctly
on different hardware configurations.
Step #11: Preserving Rendering States
GFxMovieView::Display makes DirectX calls to render a frame of the movie on the D3D
device. For performance reasons, various D3D device states, such as blending modes and
texture storage settings, are not preserved and the state of the D3D device will be different
after the call to GFxMovieView::Display. Some applications may be adversely affected by
this. The most straightforward solution is to save device state before the call to Display and
restore it afterwards. Greater performance can be achieved by having the game engine reinitalize its required states after GFx rendering. For this tutorial we simply save and restore
state using DX9s state block functions.
17
A DX9 state block is allocated for the life of the application and used before and after the
calls to GFxTutorial::AdvanceAndRender():
// Save DirectX state before calling GFx
g_pStateBlock->Capture();
// Render the frame and advance the time counter
gfx->AdvanceAndRender();
// Restore DirectX state to avoid disturbing game render state
g_pStateBlock->Apply();
18
These functions are very useful for rendering the same content on both 4:3 and widescreen displays.
One of the advantages of GFx is that scalable vector graphics enable content to resize freely to match
any display resolution. Traditional bitmap graphics typically require artists to create large and small
versions of bitmaps for different screen resolutions (e.g., one set for low resolution 800x600 displays
and another set for high resolution 1600x1200 displays). GFx enables the same content to scale to
any resolution. Additionally, traditional bitmap graphics are fully supported for those game elements
where bitmaps are more appropriate.
GFxMovieView::SetViewScaleMode defines how scaling will be performed. To ensure the movie fits in
the viewport without affecting the original aspect ratio the following call can be added to the end of
GFxTutorial::InitGFx() along with the other calls to setup the GFxMovieView object:
pUIMovie->SetViewScaleMode(GFxMovieView::SM_ShowAll);
The four possible arguments to SetViewScaleMode are covered in the online documentation and are:
SM_NoScale
SM_ShowAll
The size of the content is fixed to the native resolution of the Flash stage.
Scales the content to fit the viewport while maintaining the original aspect
ratio.
SM_ExactFit
Scales the content to fill the entire viewport without regard to the original
aspect ratio. The viewport will be filled, but distortion may occur.
SM_NoBorder Scales the content to fill the entire viewport while maintaining the original
aspect ratio. The viewport will be filled, but some clipping may occur.
The complementary SetViewAlignment controls the position of the content relative to the viewport.
When the aspect ratio is maintained, some part of the viewport may be empty when SM_NoScale or
SM_ShowAll are selected. SetViewAlignment decides where to position the content within the
viewport. In this case, the interface buttons should be centered vertically on the far right of the screen:
19
pUIMovie->SetViewAlignment(GFxMovieView::Align_CenterRight);
Try changing the arguments to SetViewScaleMode and SetViewAlignment to see how the application
behavior changes when the window is resized.
The SetViewAlignment function does not have any effect except when SetViewScaleMode is set to the
default of SM_NoScale. For more complex alignment, scaling, and positioning requirements, GFx
supports ActionScript extensions that enable the movie to choose its own size and position. Sample
ActionScript code can be found in d3d9guide.fla.
The scale and alignment parameters can also be set through ActionScript instead of C++.
SetViewScaleMode and SetViewAlignment modify the same properties represented by the
ActionScript Stage class (Stage.scaleMode, Stage.align).
20
21
GFx expects mouse coordinates to be relative to the upper left corner of the specified viewport, not
the native resolution of the movie. The below examples clarify this:
Example #1: Viewport matches screen dimensions
pMovie->SetViewport(screen_width, screen_height, 0, 0,
screen_width, screen_height, 0);
No transformation is necessary in this case: the mouse coordinates from Windows are already relative
to the upper left corner of the movie since the movie is positioned at (0, 0). The coordinates are scaled
internally by GFx from the viewport dimensions to the native movie resolution for internal processing.
Example #2: Viewport smaller than screen, but viewport is positioned in the upper left corner of the
screen
pMovie->SetViewport(screen_width, screen_height, 0, 0,
screen_width / 4, screen_height / 4, 0);
Once again, no transformation is necessary in this case. The size and position of the buttons changes
because the viewport has been scaled down. However, both coordinates used by HandleEvent and
the Windows screen coordinates are still relative to the upper left corner of the window and no
translation is necessary. Scaling of the coordinates from the viewport dimensions to the native movie
resolution is handled internally by GFx.
Example #3: Viewport smaller than screen and centered
movie_width = screen_width / 6;
movie_height = screen_height / 6;
pMovie->SetViewport(screen_width, screen_height,
screen_width / 2 movie_width / 2,
screen_height / 2 movie_height / 2,
movie_width, movie_height);
Translation of the Windows screen coordinates is necessary in this case. The movie is no longer
positioned at (0, 0) so its new position at (screen_width / 2 movie_width / 2, screen_height / 2
movie_height / 2) must be subtracted from the screen coordinates passed in by Windows.
Note that if the Flash content is centered or in some other way aligned by
GFxMovieView::SetViewAlignment these transformations do not have to be performed. As long as the
mouse coordinates are relative to the coordinates given to GFxMovieView::SetViewport, alignment
and scaling performed by SetViewAlignment and SetViewScaleMode will be handled internally by GFx.
22
The c key is pressed down while the SHIFT key is held down: A GFxKeyEvent should be
generated in response to the WM_KEYDOWN message to indicate that:
- The c key was pressed, and the scan code of that key;
- The key was pressed down; and
- The SHIFT key is active.
At the same time a GFxCharEvent should be fired in response to the WM_CHAR message
to pass the cooked ASCII value C to GFx.
Once the c key is released, a GFxKeyEvent should be sent in response to the WM_KEYUP
message. No GFxCharEvent need be sent when a key is released.
The F5 key is pressed: A GFxKeyEvent is sent when the key goes down, and a second
event when the key goes back up. It is not necessary to send a GFxCharEvent because F5
does not correspond to a printable ASCII code.
Separate GFxKeyEvent events are sent for key down and key up events. To enable platform
independence, the key code is defined in GFxEvent.h to match the key codes used internally by Flash.
The GFxPlayerTiny.cpp example and GFxPlayer program both contain code to convert Windows scan
codes to the corresponding Flash codes. The final code for this section includes the ProcessKeyEvent
function that can be reused when integrating with a custom 3D engine:
void ProcessKeyEvent(GFxMovieView *pMovie, UINT uMsg, WPARAM wParam, LPARAM
lParam)
23
Simply call the function from the Windows WndProc function in response to WM_CHAR,
WM_SYSKEYDOWN, WM_SYSKEYUP, WM_KEYDOWN, and WM_KEYUP messages. The
appropriate GFx events will be generated and sent to pMovie.
Sending both GFxKeyEvents and GFxCharEvents is important. For example, most text boxes respond
only to GFxCharEvents because they are interested in printable ASCII characters. Also, a text box
should be able to accept Unicode characters (e.g., from a Chinese Input Method Editor [IME]). In the
case of IME input, raw key codes are not useful and only the final character event (which typically
results from several keystrokes processed by the IME) is of interest to the text box. In contrast, a list
box control would need to intercept the Page Up and Page Down keys through GFxKeyEvent because
these keys do not correspond to printable characters.
The function is included with the final code for this section in Tutorial\Section4.3. Run the program
and move the mouse over buttons. Buttons will highlight correctly, and those that do not require
integration with the 3D engine will work properly. Pressing Settings will transition to the DX9
configuration screen without any C++ code because d3d9guide.fla implements this simple logic using
ActionScript.
To see the keyboard processing code in action click Change Mesh and type into the text input box.
There are some minor issues which will be fixed later on in the tutorial. Notice the animation that
occurs when the Change Mesh button is pressed to open a text input box. This animation is easy to
do in Flash with vector graphics, but impractical with a traditional bitmap-based interface. The
animation would require custom code in addition to additional bitmaps, making the animation
potentially slow to load, costly to render, and most importantly tedious to code.
24
25
26
Pressing CTRL+W to view the wireframe representation of these characters shows that each character
is represented as two texture mapped triangles. Since these are smaller characters it is more efficient
to render them as bitmaps through the dynamic font cache.
Increase the size of the window while staying in wireframe mode. The characters will switch from
rendering with texture maps to rendering as solid color triangles:
For large characters, it is more efficient to render as solid color triangles. Operating on large bitmaps is
costly due to excessive memory bandwidth usage. Only pixels that require color are set, avoiding
wasting processing power on the empty areas of the character. In figures 4c and 4d, GFx detected the
size of the character passed a certain threshold and tessellated it into triangles, rendering the
character as geometry instead of as a bitmap.
Font soft shadow, blur, and other effects are supported. Simply set the appropriate filters on the text
field in Flash to generate the desired effect. Additional details are in the Font and Text Configuration
27
Overview linked to above. Examples can be downloaded from the developer center as
gfx_2.1_texteffects_sample.zip:
28
character glyphs:
29
Step #4: Choose a Chinese SimSun font from the font dropdown box in the properties dialog
that is typically positioned at the bottom of the screen. Then click the Embed button to cause
Flash Studio to export the character glyphs to the movie.
Step #5: Use CTRL+Click to select Chinese (All) (21664 glyphs) from the Character
Embedding dialog, taking care to maintain the original selection which includes the Latin
characters, numerals, and punctuation.
30
Step #6: Save the file and export a new SWF movie by pressing Ctrl+Alt+Shift+S or choosing
File Export Export Movie
Note: Make sure to save the Flash file in the Flash 8 format by going through the File Save
As dialog. Also, when exporting to a SWF choose to export a Flash 8 SWF with ActionScript
2.0.
To confirm that embedding was successful, check the size of the new d3d9guide.swf file. It should
come to about 9MB with the embedded SimSun font.
Restart ShadowVolume and type again. With the embedded fonts Chinese input will now appear
correctly:
6. IME Overview
The example of the previous system used the Windows default Input Method Editor (IME) to enter text.
The Windows default IME character selection window did not follow the cursor as we typed. It was
rendered with a fixed gray style:
Figure 9: GFx IME window and wireframe representation demonstrating the window is rendered
directly into the game environment as triangles. Notice the glow effects applied to the text being
edited.
32
Rendering the IME window in-game as triangles enables IME in full screen games. The standard IME
window would either not work or flicker.
An IME sample is available in the Demo Examples at Start Menu->Programs->Scaleform->GFx SDK
3.3->Demo Examples->IME Demo. This sample can be used to quickly evaluate GFxs IME
capabilities and to test compatibility with third-party IMEs. All Microsoft system IMEs are supported,
as are third-party IMEs that fully implement the Microsoft Text Services Framework (TSF) API. As
many non-Microsoft IMEs do not properly implement TSF, some third-party IMEs may have issues.
Should you encounter a third-party IME that is not compatible with GFx, please email a link to the IME
to our support staff at [email protected].
Full IME documentation including the game integration process can be found in the Input Method
Configuration Overview document.
33
Any non-string arguments to fscommand, such as Booleans or integers, will be converted to strings.
ExternalInterface can directly receive integer arguments.
This passes two strings to the GFx FSCommand handler. An application registers a fscommand
handler by subclassing GFxFSCommandHandler and registering the class with either the GFxLoader
34
or with individual GFxMovieView objects. If a command handler is set on GFxMovieView, it will receive
callbacks for only the fscommand calls performed in that movie instance. The GFxPlayerTiny example
demonstrates this process (search for FxPlayerFSCommandHandler) and we will add similar code to
ShadowVolume. The final code for this section is in Tutorial\Section7.1.
First, subclass GFxFSCommandHandler:
class OurFSCommandHandler : public GFxFSCommandHandler
{
public:
virtual void Callback(GFxMovieView* pmovie,
const char* pcommand, const char* parg)
{
GFxPrintf("FSCommand: %s, Args: %s", pcommand, parg);
}
};
The Callback method receives the two string arguments passed to fscommand in ActionScript as well
as a pointer to the specific movie instance that called fscommand.
Next, register the handler after creating the GFxLoader object in GFxTutorial::InitGFx():
// Register our FSCommand handler
GPtr<GFxFSCommandHandler> pcommandHandler = *new OurFSCommandHandler;
gfxLoader->SetFSCommandHandler(pcommandHandler);
Registering the handler with the GFxLoader causes every GFxMovieView to inherit this handler.
SetFSCommandHandler can be called on individual movie instances to override this default setting.
Our custom handler simply prints each fscommand event to the debug console. Run ShadowVolume
and click the Toggle Fullscreen button. Notice that events are printed whenever a UI event happens:
FSCommand: ToggleFullscreen, Args:
Open d3d9guide.swf in Flash Studio and open the ActionScript Panel (F9). Compare the events
printed to the screen with the fscommand() calls made from the ActionScript block for Symbol
Definition(s) hud var : Frame 1:
35
The DXUT function OnFrameMove is called right before a frame is rendered. Add the corresponding
code in at the end of the the OnFrameMove callback:
if (doToggleFullscreen)
{
doToggleFullscreen = false;
DXUTToggleFullScreen();
}
In general, event handlers should be non-blocking and return to the caller as soon as possible. Event
handles are typically only called during Advance or Invoke calls.
36
7.1.2 ExternalInterface
The Flash ExternalInterface.call method is similar to fscommand but is preferred because it provides
more flexible argument handling and can return values.
Registering an ExternalInterface handler is similar to registering an fscommand handler:
class OurExternalInterfaceHandler : public GFxExternalInterface
{
public:
virtual void Callback(GFxMovieView* pmovieView,
const char* methodName,
const GFxValue* args,
UInt argCount)
{
GFxPrintf("ExternalInterface: %s, %d args: ",
methodName, argCount);
for(UInt i = 0; i < argCount; i++)
{
switch(args[i].GetType())
{
case GFxValue::VT_Null:
GFxPrintf("NULL");
break;
case GFxValue::VT_Boolean:
GFxPrintf("%s", args[i].GetBool() ? "true" : "false");
break;
case GFxValue::VT_Number:
GFxPrintf("%3.3f", args[i].GetNumber());
break;
case GFxValue::VT_String:
GFxPrintf("%s", args[i].GetString());
break;
default:
GFxPrintf("unknown");
break;
}
GFxPrintf("%s", (i == argCount - 1) ? "" : ", ");
}
GFxPrintf("\n");
}
};
37
gfxLoader.SetExternalInterface(pEIHandler);
An external interface call will be made from ActionScript when the text input box gains or loses focus.
Press F9 and look at the ActionScript for Symbol Definitions() hud var : Frame 1:
Figure 11: The MeshPathFocus callback is called whenever the text input box gains or loses focus
The ExternalInterface calls will trigger our ExternalInterface handler and pass it the focus state of the
text input box (true or false) and the arbitrary command string MeshPathFocus. Open the text input,
click on the text area, and then click on an empty area of the screen to move focus away from the text
input. The console output should be similar to:
ExternalInterface: MeshPathFocus, 1 args: false
Focus change:
_level0.hud.text_MeshPath.field => null
ExternalInterface: MeshPathFocus, 1 args: true
Focus change:
null => _level0.hud.text_MeshPath.field
The event handler can be modified to detect when focus is gained or lost and pass that information to
the GFxTutorial object:
if (strcmp(methodName, "MeshPathFocus") == 0 && argCount == 1)
{
if (args[0].GetType() == GFxValue::VT_Boolean)
gfx->SetTextboxFocus(args[0].GetBool());
}
38
GFxTutorial::ProcessEvent will only pass keyboard events to the movie if the textbox has focus. If a
keyboard event is passed to the textbox, a flag is set to prevent it from being passed to the 3D engine:
if (uMsg
uMsg
uMsg
{
if
{
}
}
Space (ASCII code 32) and tab (ASCII code 9) are always passed through as they correspond to the
Toggle UI and Settings buttons.
In order to enable the user to change the mesh being rendered, they click the Change Mesh button
to open the text box, enter a new mesh name, and then press enter. When enter is pressed, the text
box will invoke an ActionScript event handler, which calls ExternalInterface with the name of the new
mesh. The additional code in OurExternalInterfaceHandler::Callback is:
static bool doChangeMesh = false;
static wchar_t changeMeshFilename[MAX_PATH] = L"";
...
if(strcmp(methodName, "MeshPath") == 0 && argCount == 1)
{
doChangeMesh = true;
const char *filename = args[0].GetString();
MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, filename, -1,
changeMeshFilename, _countof(changeMeshFilename));
}
As with the fullscreen toggle, the actual work is done in the DXUT OnFrameMove callback. The code
is based on the event handler for the default DXUT interface.
SetVariableArray passes an entire array of variables to Flash in one operation. This function can be
used for operations such as dynamically populating a dropdown list control. The following code is
placed in GFxTutorial::InitGFx() and sets the value of the _root.SceneData dropdown:
// Initialize the scene dropdown
GFxValue sceneData[3];
sceneData[0].SetString("Scene with shadow");
39
40
GetVariableDouble returns the value of the _root.counter variable. Initially the variable does not exist
and GetVariableDouble returns zero. The counter is incremented and the new value saved to
_root.counter by way of SetVariable. The online documentation for GFxMovie lists the different
variations of GetVariable and SetVariable.
The fxplayer Flash file has two dynamic text fields that can be set to arbitrary text by the application.
The MessageText text field is centered in the middle of the screen and the HUDText variable is
positioned in the upper left corner of the screen. A string is generated based on the value of the
_root.counter variable and SetVariable is used to update the message text.
Performance note: The preferred method to change the value of a Flash dynamic text field is to set
the TextField.text variable or to call GFxMovie::Invoke to run an ActionScript routine to change the text
(more on this in section 7.2.2). Do not bind a dynamic text field to an arbitrary variable and then
change that variable to change the text. Although this works, it incurs a performance penalty as GFx
must check the value of that variable on every frame.
41
The above usage deals with variables directly as strings or numbers. The online documentation
describes an alternate syntax using GFxValue objects to efficiently process variables directly as
integers and eliminate the need for string to integer conversion.
SetVariable has an optional third argument of type GFxMovie::SetVarType that declares the
assignment sticky. This is useful when the variable being assigned has not yet been created on the
Flash timeline. For example, suppose that the text field _root.mytextfield is not created until frame 3 of
the movie. If SetVariable(_root.mytextfield.text, testing, SV_Normal) is called on frame 1, right after
the movie is created, then the assignment would have no effect. If the call is made with SV_Sticky (the
default value) then the request is queued up and applied once the _root.mytextfield.text value
becomes valid on frame 3. This makes it easier to initialize movies from C++. Generally SV_Normal is
more efficient than SV_Sticky so SV_Normal should be used where possible.
42
Notice that the ActionScript code for openMeshPath is placed in frame 1 to ensure that the
ActionScript routine is loaded as soon as the first frame of the movie is played. One common error in
using Invoke is calling an ActionScript routine that is not yet available, in which case an error will be
printed to the GFx log. An ActionScript routine will not become available until the frame it is
associated with has been played or the nested object it is associated with has been loaded. All
ActionScript code in frame 1 will be available as soon as the first call to GFxMovieView::Advance is
made, or if GFxMovieDef::CreateInstance is called with initFirstFrame set to true.
This example used the printf style of Invoke. Other versions of the function use GFxValue to efficiently
process non-string arguments. InvokeArgs is identical to Invoke except that it takes a va_list argument
to enable the application to supply a pointer to a variable argument list. The relationship between
Invoke and InvokeArgs is similar to the relationship between printf and vprintf.
43
For more information on this and other ActionScript functions used here see the Flash documentation.
Movies can be either loaded into a named object or into a specific numbered level:
//
// Load a file into a specific level
function LoadFlashLevel(url, level)
{
trace("LoadFlashLevel(" + url + ", " + level + ")\n");
mclLoader.loadClip(url, level);
44
}
//
// Load a file into a named object
function LoadFlash(url, objectName)
{
trace("LoadFlashLevel(" + url + ", " + objectName + ")\n");
var container:MovieClip = createEmptyMovieClip(objectName,
getNextHighestDepth());
mclLoader.loadClip(url, container);
}
Each numbered level can contain a single movie. Movies in different levels can share data and
access each others variables. The level is related to the Z-order of the movie clips, enabling the UI
designer to choose which clips appear in font and which clips appear behind. Movies in different
levels can share data and access each others variables.
Loading movies with LoadMovieLevel into specific levels has the advantage that the Z-order is
implicitly defined based on the level number. Variables in that movie can be accessed as
_levelN.variableName (e.g., _level6.counter).
Using LoadMovie to load a movie into a specific named clip enables more structured organization of a
complex interface. Movies can be arranged in a tree structure and variables can be addressed
accordingly (e.g., _root.inventoryWindow.widget1.counter).
Many Flash movie clips reference variables based on _root. If a movie is loaded into a specific level,
_root refers to the base of that level. For example, for a movie loaded into level 6, _root.counter and
_level6.counter refer to the same variable. A movie clip that references its own internal variables with
_root will work normally if it is loaded into the base of a specific level with LoadMovieLevel.
The same movie loaded with LoadMovie into _root.myMovie will not work normally, since
_root.counter refers to the counter variable at the base of the tree. Movies organized into a tree
structure should set this:_lockroot = true. Lockroot is an ActionScript property that causes all
references to _root to point to the root of the submovie, not the root of the level. For more on
ActionScript variables, levels, and movie clips see the Adobe Flash documentation.
Regardless of how submovies are organized, movie clips running inside the same GFxMovieView can
access each others variables and operate off of shared state, greatly simplifying creation of complex
interfaces.
Container.fla also contains corresponding functions to unload unneeded movie clips to reduce
memory consumption (e.g., once a user closes a window, the content can be freed).
45
When LoadMovie or LoadMovieLevel returns, the movie has not necessarily finished loading. The
loadClip function only initiates the loading of the movie, which then continues in the background.If
your application must know when the movie has completed loading (e.g., to programmatically initialize
state), MovieClipLoaders listener functions can be used. Container.fla has placeholder
implementations of these functions that can be extended to either process the events in ActionScript
or make an ExternalInterface call to enable the C++ application to take action:
//
//
//
//
//
//
//
//
//
//
//
//
//
46
{
trace("onLoadComplete: " + target_mc);
};
// Register the listener with the MovieClipLoader
mclLoader.addListener(mclListener);
The code in Tutorial\section7.3 has been modified to initially load only container.swf instead of
d3d9guide.swf and fxplayer.swf. Pressing F8 loads the HUD on demand and pressing F9 loads the
main UI:
void GFxTutorial::ProcessEvent(HWND hWnd, UINT uMsg,
WPARAM wParam, LPARAM lParam,
bool *pbNoFurtherProcessing)
{
...
else if (wParam == VK_F8)
{
const char *retval = pShellMovie->Invoke("_root.LoadFlashLevel",
"%s, %d", "fxplayer.swf", 5);
GFxPrintf("_root.LoadFlash returns '%s'\n", retval);
}
else if (wParam == VK_F9)
{
const char *retval = pShellMovie->Invoke("_root.LoadFlashLevel",
"%s, %d", "d3d9guide.swf", 6);
GFxPrintf("_root.LoadFlash returns '%s'\n", retval);
}
else if (wParam == VK_F2)
{
const char *retval = pContainerMovie->Invoke("_root.UnloadFlashLevel",
"%d", 5);
GFxPrintf("_root.UnloadFlash returns '%s'\n", retval);
}
else if (wParam == VK_F3)
{
const char *retval = pContainerMovie->Invoke("_root.UnloadFlashLevel",
"%d", 6);
GFxPrintf("_root.UnloadFlash returns '%s'\n", retval);
}
}
The above code loads the HUD into level 5 and the main interface into level 6. Flash ActionScript
levels are related to the Z order of the scene. Within a single GFxMovieView, movies in a higher level
47
render on top of movies with a lower level. Container.swf, the first movie loaded, is placed in level 0 by
default.
Variables in levels can be referenced from other levels with the _level keyword. For example, the main
interface in _level6 can directly access the HUDs text field by changing _level5.MessageText.text.
Alternatively the two movies can share data in Flashs global variable space through the _global
keyword (e.g., both movies can access _global.counter). This has the advantage that when either
movie is unloaded, variables in the _global namespace will not be lost. Movies can be swapped in and
out of memory and see a consistent _global namespace. For more information on _level, _global, and
the ActionScript variable namespace, please refer to the Adobe Flash documentation.
Run the application and press F8 to load the HUD. Then press F9 to load the main interface. Notice
how there is a delay loading the main interface. This is because of the time required to load the
embedded Chinese font. This process can be accelerated by pre-processing the SWF file into a GFx
file (section 8) and by leveraging GFxs multithreaded loading capabilities to load a shared font file in
the background (see Font and Text Configuration Overview on the developer center website).
Pressing F8 to bring up the HUD and then F5 to increment the counter added in Section 7.2 no longer
works. The SetVariable method refers to _root.counter but the HUD is now loaded into the _level5
namespace so the counter code should be changed as follows:
void GFxTutorial::ProcessEvent(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam,
bool *pbNoFurtherProcessing)
{
int mx = LOWORD(lParam), my = HIWORD(lParam);
if (pHUDMovie && uMsg == WM_KEYDOWN)
{
if (wParam == VK_F5)
{
int counter = (int)pHUDMovie->
GetVariableDouble("_level5.counter");
counter++;
pHUDMovie->SetVariable("_level5.counter",
GFxValue((double)counter));
char str[256];
sprintf_s(str, "testing! counter = %d", counter);
pHUDMovie->SetVariable("_level5.MessageText.text", str);
}
}
...
48
The _level5.counter can also be accessed directly from ActionScript in d3d9guide.swf, which is loaded
into level 6, enabling both interfaces to communicate directly without C++ code in the middle.
Run the application, press F8 to load the HUD and F5 to increment the counter. Press F2 to unload
the HUD, then F8 to reload the HUD and continue incrementing the counter with F5. When the HUD
was unloaded, the value of the counter was lost and counting restarted from zero. This is because the
counter variable was stored in _level5. Change _level5.counter to _global.counter and try again. By
storing the information in _global, it is preserved even when movies are loaded and unloaded.
49
50
9. Next Steps
This tutorial has provided a basic overview of GFxs capabilities. Here are some additional topics we
suggest you explore:
In-game Flash render-to-texture (e.g., Doom 3 consoles). See the GFxPlayer SWF to
Texture SDK sample, the Gamebryo integration demo, and the Unreal Engine 3
integration demo.
Custom loading: in addition to loading from GFx or SWF files, Flash content can be loaded
directly from memory or the games resource manager by subclassing GFxFileOpener.
51