// ########## ########## ########## ########## ########## ########## ##########
//
// Copyright (c) 2014, B.G. "Kanalmeister22"
// All rights reserved.
//
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
// ########## ########## ########## ########## ########## ########## ##########
#include "D2Engine.h"
#include <d2d1_1.h>
#include <wincodec.h>
#include "D2RenderObj.h"
struct TimerThreadData {
double timer_interval;
D2Engine* engine;
HANDLE kill_timer_event;
HANDLE timer_killed_event;
};
//
// Direct 2D graphics engine. Handles the creation and rendering of
// several different objects on a window target. Sample usage:
// D2Engine myEngine;
// if (!d2engine_->InitEngine(window_->GetWndHandle(), 1280, 720)) {
// return 1;
// }
// D2Bitmap *bitmap;
// bitmap = d2engine_->CreateBitmap(L"example.png", 0, 1);
// if (!bitmap) {
// return 1;
// }
// bitmap->SetVisibility(true);
//
// D2BitmapSequence *animation;
// animation = d2engine_->CreateBitmapSequence(
// L"res\\animation\\example_",
// L"png",
// 6, // digits
// 200, // frames
// 0, // alignment
// 3); // priority
//
// if (!animation) {
// return 1;
// }
// int w, h;
// animation->GetSize(&w, &h);
// animation->SetScale((float)target_cx / (float)w,
// (float)target_cy / (float)h, 0, 0); // fullscreen
//
// animation->SetFramerate(25.0f);
// animation->Start();
// animation->SetVisibility(true);
//
//
//
// D2Movie* movie = d2engine_->CreateMovie(
// L"res\\animation2\\soundtrack.mp3",
// L"res\\animation2\\example_sequence_",
// L"png",
// 6,
// 600,
// 25.0f,
// 3);
// movie->SetNotifyCallback(MovieFinished, myEngine);
// movie->View();
// ...
// DestroyBitmap(bitmap);
// DestroyBitmapSequence(animation);
// DestroyMovie(movie);
//
//
// NOTE: Couple the engine with a timer in order to have animations/movies.
// There, also the rendering can be done.
// void OnTimer(double elapsed_time_in_ms) {
// myEngine.OnTimer(elapsed_time_in_ms);
// myEngine.Render();
// }
//
D2Engine::D2Engine()
{
timer_data_ = NULL;
d2d1_factory_ = NULL;
wic_imaging_factory_ = NULL;
render_target_ = NULL;
InitializeCriticalSectionAndSpinCount(&renderobjects_critical_section_, 4000);
}
D2Engine::~D2Engine()
{
KillHighPrecisionTimer();
DeleteCriticalSection(&renderobjects_critical_section_);
SafeRelease(&d2d1_factory_);
SafeRelease(&wic_imaging_factory_);
SafeRelease(&render_target_);
}
//
// PURPOSE: Creates a direct 2d factory, a wic factory and a render target.
// Connects the render traget to the passed in window
//
bool D2Engine::InitEngine( HWND window, unsigned int target_width,
unsigned int target_height)
{
if (render_target_) {
return false;
}
target_width_ = target_width;
target_height_ = target_height;
// create a Direct2D factory
if (FAILED(D2D1CreateFactory(D2D1_FACTORY_TYPE_MULTI_THREADED,
__uuidof(ID2D1Factory1),
NULL,
(void**)&d2d1_factory_))) {
return false;
}
// create a WIC factory.
if (FAILED(CoCreateInstance(CLSID_WICImagingFactory1,
NULL,
CLSCTX_INPROC_SERVER,
IID_IWICImagingFactory,
(LPVOID*)&wic_imaging_factory_))) {
return false;
}
// create a Direct2D render target for the passed in window
if (FAILED(d2d1_factory_->CreateHwndRenderTarget(
D2D1::RenderTargetProperties(
D2D1_RENDER_TARGET_TYPE_DEFAULT,
D2D1::PixelFormat(
DXGI_FORMAT_UNKNOWN,
D2D1_ALPHA_MODE_PREMULTIPLIED)),
D2D1::HwndRenderTargetProperties(
window,
D2D1::SizeU(
target_width_,
target_height_)),
&render_target_))) {
return false;
}
return true;
}
//
// PURPOSE: Renders all bitmaps to the render target
//
bool D2Engine::Render()
{
if (!render_target_)
return false;
LARGE_INTEGER time, time_last;
QueryPerformanceFrequency(&time);
QueryPerformanceCounter(&time_last);
double timer_frequency = ((double)time.QuadPart / 1000);
double elapsed_time_in_ms = 0, tick_elapsed_time_in_ms;
EnterCriticalSection(&renderobjects_critical_section_);
render_target_->BeginDraw();
// clear to transparent background
render_target_->Clear(D2D1::ColorF(0, 0.0f));
for (int p = 0; p < PRIORITY_CLASSES_COUNT; p++)
{
for (std::list<D2RenderObj*>::iterator it =
render_objects_list_[p].begin();
it != render_objects_list_[p].end(); it++) {
(*it)->Render(render_target_);
}
}
render_target_->EndDraw();
LeaveCriticalSection(&renderobjects_critical_section_);
QueryPerformanceCounter(&time);
tick_elapsed_time_in_ms = (double)(time.QuadPart
- time_last.QuadPart) / timer_frequency;
time_last.QuadPart = time.QuadPart;
elapsed_time_in_ms += tick_elapsed_time_in_ms;
if (tick_elapsed_time_in_ms > 20) {
//Output("RENDER OVERKILL %0.1f\n", tick_elapsed_time_in_ms);
}
return true;
}
//
// PURPOSE: Creates a bitmap for rendering. Higher priority means higher
// rendering layer
//
D2Bitmap* D2Engine::CreateBitmap(wchar_t* file, unsigned int alignment,
unsigned int priority)
{
D2Bitmap* bitmap = new D2Bitmap();
if (!bitmap->CreateBitmapFromFile(
render_target_,
NULL,
wic_imaging_factory_,
file,
alignment)) {
delete bitmap;
return NULL;
}
EnterCriticalSection(&renderobjects_critical_section_);
render_objects_list_[priority].push_front(bitmap);
bitmap->priority_ = priority;
LeaveCriticalSection(&renderobjects_critical_section_);
return bitmap;
}
//
// PURPOSE: Removes a bitmap from the rendering list and calls its destructor
//
void D2Engine::DestroyBitmap(D2Bitmap* &bitmap)
{
EnterCriticalSection(&renderobjects_critical_section_);
render_objects_list_[bitmap->priority_].remove(bitmap);
delete bitmap;
bitmap = NULL;
LeaveCriticalSection(&renderobjects_critical_section_);
}
//
// PURPOSE: Creates a bitmap sequence for rendering. Higher priority means
// higher rendering layer
//
D2BitmapSequence* D2Engine::CreateBitmapSequence(wchar_t* path,
wchar_t* file_extension,
unsigned int digits,
unsigned int starting_frame,
unsigned int frames,
unsigned int alignment,
unsigned int priority)
{
D2BitmapSequence* sequence = new D2BitmapSequence();
if (!sequence->CreateSequenceFromFiles(render_target_,
wic_imaging_factory_,
path,
file_extension,
digits,
starting_frame,
frames,
alignment)) {
delete sequence;
return NULL;
}
EnterCriticalSection(&renderobjects_critical_section_);
render_objects_list_[priority].push_front(sequence);
sequence->priority_ = priority;
LeaveCriticalSection(&renderobjects_critical_section_);
return sequence;
}
//
// PURPOSE: Removes a bitmap sequence from the rendering list and
// calls its destructor
//
void D2Engine::DestroyBitmapSequence(D2BitmapSequence* &sequence)
{
EnterCriticalSection(&renderobjects_critical_section_);
render_objects_list_[sequence->priority_].remove(sequence);
delete sequence;
sequence = NULL;
LeaveCriticalSection(&renderobjects_critical_section_);
}
//
// PURPOSE: Creates a dynamic bitmap sequence for rendering. Higher priority
// means higher rendering layer
//
D2DynamicSequence* D2Engine::CreateDynamicSequence(wchar_t* path,
wchar_t* file_extension,
unsigned int digits,
unsigned int frames,
unsigned int alignment,
unsigned int priority)
{
D2DynamicSequence* sequence = new D2DynamicSequence();
if (!sequence->InitDynamicSequence(render_target_,
wic_imaging_factory_,
path,
file_extension,
digits,
frames,
alignment)) {
delete sequence;
return NULL;
}
EnterCriticalSection(&renderobjects_critical_section_);
render_objects_list_[priority].push_front(sequence);
sequence->priority_ = priority;
LeaveCriticalSection(&renderobjects_critical_section_);
return sequence;
}
//
// PURPOSE: Removes a dynamic bitmap sequence from the rendering list and
// calls its destructor
//
void D2Engine::DestroyDynamicSequence(D2DynamicSequence* &sequence)
{
EnterCriticalSection(&renderobjects_critical_section_);
render_objects_list_[sequence->priority_].remove(sequence);
delete sequence;
sequence = NULL;
LeaveCriticalSection(&renderobjects_critical_section_);
}
//
// PURPOSE: Creates a movie for rendering. Higher priority means
// higher rendering layer
//
D2Movie* D2Engine::CreateMovie(wchar_t* soundtrack,
wchar_t* path,
wchar_t* file_extension,
unsigned int digits,
unsigned int frames_total,
double frames_per_second,
unsigned int priority)
{
D2Movie* movie = new D2Movie();
if (!movie->InitMovie(this,
soundtrack,
path,
file_extension,
digits,
frames_total,
frames_per_second,
priority)) {
delete movie;
return NULL;
}
return movie;
}
//
// PURPOSE: Removes a bitmap sequence from the rendering list and
// calls its destructor
//
void D2Engine::DestroyMovie(D2Movie* &movie)
{
delete movie;
movie = NULL;
}
//
// PURPOSE: Resizes the render target
//
void D2Engine::ResizeTarget(unsigned int target_width,
unsigned int target_height)
{
target_width_ = target_width;
target_height_ = target_height;
render_target_->Resize(D2D1::SizeU(target_width_, target_height_));
}
//
// PURPOSE: Starts a new high precision timer event that animates sequences
// and renders the scene
//
bool D2Engine::ActivateHighPrecisionTimer(double timer_interval)
{
if (timer_data_) {
return false;
}
timer_data_ =
(TimerThreadData*)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY,
sizeof(TimerThreadData));
timer_data_->kill_timer_event = CreateEvent(NULL, TRUE, FALSE, NULL);
timer_data_->timer_killed_event = CreateEvent(NULL, TRUE, FALSE, NULL);
timer_data_->timer_interval = timer_interval;
timer_data_->engine = this;
// start thread
HANDLE thread = CreateThread(NULL, 0, HighPrecisionTimerEntry,
timer_data_, 0, NULL);
return true;
}
//
// PURPOSE: Kills the high precision timer and returns false when no timer
// was activated before
//
bool D2Engine::KillHighPrecisionTimer()
{
if (!timer_data_) {
return false;
}
SetEvent(timer_data_->kill_timer_event);
WaitForSingleObject(timer_data_->timer_killed_event, INFINITE);
CloseHandle(timer_data_->kill_timer_event);
CloseHandle(timer_data_->timer_killed_event);
HeapFree(GetProcessHeap(), NULL, timer_data_);
timer_data_ = NULL;
return true;
}
//
// PURPOSE: High precision timer thread entry
//
DWORD WINAPI D2Engine::HighPrecisionTimerEntry(void *parameters)
{
TimerThreadData *data =
reinterpret_cast<TimerThreadData*>(parameters);
// timer for rendering
LARGE_INTEGER time, time_last;
QueryPerformanceFrequency(&time);
QueryPerformanceCounter(&time_last);
double timer_frequency = ((double)time.QuadPart / 1000);
double elapsed_time_in_ms = 0, tick_elapsed_time_in_ms;
int factor = 0;
while (WaitForSingleObject(data->kill_timer_event, 0) != WAIT_OBJECT_0) {
// high precision timer
QueryPerformanceCounter(&time);
tick_elapsed_time_in_ms = (double)(time.QuadPart
- time_last.QuadPart) / timer_frequency;
time_last.QuadPart = time.QuadPart;
elapsed_time_in_ms += tick_elapsed_time_in_ms;
if (elapsed_time_in_ms >= data->timer_interval + 1) {
factor = (int)(elapsed_time_in_ms / data->timer_interval);
elapsed_time_in_ms -= data->timer_interval * factor;
data->engine->OnTimer(data->timer_interval * factor);
data->engine->Render();
}
int sleep_time = (int)(data->timer_interval - elapsed_time_in_ms + 1);
if (sleep_time > 0) {
Sleep(sleep_time);
}
if (tick_elapsed_time_in_ms > data->timer_interval + 2) {
Output("TIMER MISS %.1f\n", tick_elapsed_time_in_ms);
}
}
SetEvent(data->timer_killed_event);
return 0;
}
//
// PURPOSE: Receives the timer event and passes it to animations
//
void D2Engine::OnTimer(double elapsed_time_in_ms)
{
EnterCriticalSection(&renderobjects_critical_section_);
for (int p = 0; p < PRIORITY_CLASSES_COUNT; p++)
{
for (std::list<D2RenderObj*>::iterator it =
render_objects_list_[p].begin();
it != render_objects_list_[p].end(); it++) {
(*it)->OnTimer(elapsed_time_in_ms);
}
}
LeaveCriticalSection(&renderobjects_critical_section_);
}