The sample applications coming with Hummingbird are available in the Samples folder. The Samples/Common folder contains platform-independent code that can be consulted to see how to integrate Hummingbird in a C++ application. Central in all samples in the CohtmlApplication
class that resides in the Common/CohtmlApplication folder. The class is the one in charge of initializing, running and closing Hummingbird. All the platform-specific code is in the samples themselves for Android, iOS and Windows Desktop.
The Android Studio sample project can be opened by selecting the Samples/Android folder. The iOS sample application can be compiled with the Samples/HummingbirdSample.xcodeproj. For Windows use the Samples/HummingbirdSample.sln. The project requires Visual Studio 2015 with Cross-platform development installed.
Console samples are available and require the console development environments provided by Microsoft and Sony.
- Note
- In C++ code the Hummingbird classes reside in the
cohtml
namespace.
Integration
For a simple and straightforward implementation of the steps required to start Hummingbird check out the MinimalHelloHummingbird sample (for Windows and DirectX 11 only).
In general, an application that uses Hummingbird has to go through the following steps:
On main thread:
- Initialize Hummingbird
- Create Views
- Send events (input) & update UI (loop)
- Destroy Views and De-initialize Hummingbird
On render thread:
- Create System renderer
- Create View renderers
- Paint UI in textures (loop)
- Compose the UI as a HUD or in the game world (loop)
- Free rendering resources and destroy renderers
All the operations are shown in code in the CohtmlApplication.cpp file. Let's explore each one of them:
Building
To build an object using Hummingbird you must:
- Add the Hummingbird headers in the include paths of the project. The headers are in the include/cohtml folder.
- Link against the Hummingbird library (in the Build/xxx_platform folder)
- Link with the Renoir graphics library (in the Build/xxx_platform folder)
- Link with the rendering backend (in the Build/xxx_platform folder)
- On Windows and Android you also have to link with V8 (in the Build/xxx_platform folder)
On Windows the relevant link targets are:
- cohtml.WindowsDesktop.lib (the cohtml.WindowsDesktop.dll must be distributed in the application folder)
- dx11backend.lib (or another Windows rendering backend) You also have to distribute in the application folder the dynamic libraries that Hummingbird uses:
- cohtml.WindowsDesktop.dll
- RenoirCore.WindowsDesktop.dll
- v8.dll
- v8_base.dll
On Xbox One the relevant link targets are:
- cohtml.Durango.lib (distribute cohtml.Durango.dll with the title)
- dx11backend.lib or dx11x_fast_semantics_backend.lib (or another Xbox One rendering backend) You also have to distribute in the title folder the dynamic libraries that Hummingbird uses:
- cohtml.Durango.dll
- RenoirCore.XB1.dll
- CoherentScript.dll
- WTF.dll
On PlayStation 4 the relevant link targets are:
- cohtml.Orbis.a (distribute cohtml.Orbis.dll with the title)
- gnm.a (or another PlayStation 4 rendering backend) You also have to distribute in the title folder the dynamic libraries that Hummingbird uses:
- cohtml.Orbis.prx
- RenoirCore.PS4.prx
- CoherentScript.prx
- WTF.prx
On Android you have to link with:
- libcohtml.Android.a
- RenoirCore.Android
- ft_renoir_android
- v8_base
- v8_libbase
- v8_snapshot
- libgles2backend.a (or another backend)
- Note
- When you build for Android, you might get errors that look similar to (<long-path>/android_native_app_glue.o.d: No such file or directory). This happens because when building, files are generated in intermediate directories that are composed from the path to your NDK, the path to Hummingbird plus some additional directories. If your path to NDK or Hummingbird is too long in characters length, the files in the intermediate directory might exceed the Windows max path length restriction and be unable to produce a build.
On iOS you have to link with:
- libcohtml.iOS.a
- ft_renoir_ios
- JavaScriptCore.framework
- libgles2backend.a (or another backend)
Initialization
bool CohtmlApplication::Initialize(const Options& options)
{
m_Logger.reset(new Logger("TestApp.log"));
gLogger = m_Logger.get();
if (m_TaskSchedulerMode == TaskScheduler::Mode::SimulateTaskSystem)
{
}
if (!m_Library)
{
APP_LOG(Error, "Unable to initialize COHTML Library!");
return false;
}
m_TaskScheduler.reset(new TaskScheduler(m_TaskSchedulerMode, m_Library, m_HasDedicatedLayoutThread));
if (options.ResourceHandler)
{
}
else
{
}
m_System = m_Library->CreateSystem(sysSettings);
if (!m_System)
{
APP_LOG(Error, "Unable to initialize COHTML System!");
return false;
}
m_System->AddFontsFromFolder("Resources/fonts/");
m_System->AddFontsFromFolder("fonts/");
m_System->SetDefaultFallbackFontName("Droid Sans");
m_Views.resize(options.ViewsCount);
for (auto i = 0u; i < options.ViewsCount; ++i)
{
auto& current = m_Views[i];
viewSettings.
Width = options.Width;
viewSettings.
Height = options.Height;
viewSettings.OnViewAdvanceComplete = options.OnViewAdvanceComplete;
current.Listener.Application = this;
viewSettings.
Listener = ¤t.Listener;
current.View = m_System->CreateView(viewSettings);
if (!current.View)
{
APP_LOG(Error, "Unable to create COHTML View!");
return false;
}
current.Listener.View = current.View;
current.ScriptCreatedCallback = options.OnScriptCreated;
current.ShowVirtualKeyboardCallback = options.OnShowVirtualKeyboard;
if (options.InitialURL != nullptr && strlen(options.InitialURL) > 0)
{
current.View->LoadURL(options.InitialURL);
}
else
{
APP_LOG(Error, "Initial URL is not set!");
return false;
}
if (options.RedrawAll)
{
current.View->ContinuousRepaint(true);
}
current.NameplatesPtr.reset(new Nameplates(current.View));
}
APP_LOG(Info, "Initialized application!");
return true;
}
Rendering initialization
bool CohtmlApplication::InitializeRenderThread(const RendererOptions& options)
{
cohtml::SystemRendererSettings sysRendSettings;
m_SystemRenderer = m_System->CreateSystemRenderer(sysRendSettings);
if (!m_SystemRenderer)
{
APP_LOG(Error, "Unable to create COHTML System renderer!");
return false;
}
m_SystemRenderer->RegisterRenderThread(options.Backend);
auto i = 0u;
for (auto& current : m_Views)
{
cohtml::ViewRendererSettings viewRendSettings;
current.ViewRenderer = m_SystemRenderer->CreateViewRenderer(current.View, viewRendSettings);
if (!current.ViewRenderer)
{
APP_LOG(Error, "Unable to create COHTML View renderer!");
return false;
}
if (options.NativeTextures != nullptr)
{
current.ViewRenderer->SetRenderTarget(options.NativeTextures[i].ViewNativeTexture,
options.NativeTextures[i].NativeDepthStencilTexture,
options.NativeTextures[i].ViewNativeTextureWidth,
options.NativeTextures[i].ViewNativeTextureHeight,
options.NativeTextures[i].ViewNativeTextureSamples);
}
++i;
}
m_HasInitializedRenderer = true;
APP_LOG(Info, "Initialized Render Thread!");
return true;
}
Looping
Each frame in the main thread you have to "Advance" the Views - update its internal clock and let animations happen. Update position of nameplates.
void CohtmlApplication::Advance(double timeMiliseconds)
{
if (!m_HasInitializedRenderer)
return;
for (auto& current : m_Views)
{
if (current.View)
{
auto frameId = current.View->Advance(timeMiliseconds);
current.NameplatesPtr->Update(float(timeMiliseconds));
std::lock_guard<std::mutex> l(FramesMutex);
current.FramesToPaint.push(frameId);
}
}
m_System->Advance(timeMiliseconds);
}
At the same time, in the render thread we have to let Hummingbird update the UI textures.
void CohtmlApplication::Render()
{
for (auto& current : m_Views)
{
if (current.ViewRenderer)
{
std::lock_guard<std::mutex> l(FramesMutex);
while (!current.FramesToPaint.empty())
{
auto frameId = current.FramesToPaint.front();
current.FramesToPaint.pop();
current.ViewRenderer->Paint(frameId, true);
}
}
}
}
When the Paint method has finished we compose the UI texture on-top of the application. This is done differently on the platforms. On iOS for instance consult the GameViewController.mm file where in the drawInRect
method the UI texture is copied on the final iOS framebuffer.
- Note
- All Hummingbird samples - minimal sample excluded - run with multithreaded rendering by default - the rendering happens on an auxiliary thread - similar to how that is done in most modern games.
Changing Views
You can load another page in the current View by using the Load
method. It will load all resources in the new page and re-create the scripting context. You will have to re-bind all native methods upon a page change.
Uninitializing
The uninitialization of objects must happen in the following order:
On the render thread:
On the main thread:
All resources that are owned by the application -> resource readers, logger, memory allocator, rendering backend must be destroyed after the Hummingbird objects that use them.
Android Release Apk Signing
When building in debug, your app should be automatically signed with auto-generated debug keystore. When you build for release, however you have to sign the app yourself. To do that follow these steps in Android Studio:
- Click Build > Generate Signed APK on the menu bar.
- If you have a keystore you can use it by providing the path. If you do not have one, then click Create new and fill the required information.
- Then on Generate Signed APK window select the keystore that you have just created, enter the password and click Next.
- Select your apk destination folder, select release in build type and click finish.
- Your should be now able to build your app in release.
Preview files in the HummingbirdPlayer
- To preview the Hummingbird samples in the HummingbirdPlayer just double click on the HBPlayer.bat file (located in the root of the package).
- To preview a different HTML file in the HummingbirdPlayer you need to open a console and enter the path to the file. To preview the file open the Samples folder, open a console and enter the path to the HTML file that you want to preview:
../HBPlayer/HBPlayer.exe --url="coui://uiresources/NameOfFile.html"