mirror of
https://github.com/spice2x/spice2x.github.io.git
synced 2026-08-01 22:30:42 -07:00
jb, shogikai: double buffer overlay, run at ~60fps (#805)
## Link to GitHub Issue or related Pull Request, if one exists n/a ## Description of change GDI bit blit overlay used exclusively for jubeat & shogikai was running at 30fps, which caused the Jubeat touch debug UI to not render fast enough. Run it at 60 fps, and also perform double buffering to avoid flickering. This could have been scoped to JB but I made the change for both. XP builds will keep running at 30fps since those people run on cabs with ancient hardware. ## Testing Tested jubeat and shogikai, overlay renders without flickering at 60 fps
This commit is contained in:
@@ -647,6 +647,7 @@ set(SOURCE_FILES ${SOURCE_FILES}
|
|||||||
stubs/stubs.cpp
|
stubs/stubs.cpp
|
||||||
|
|
||||||
# touch
|
# touch
|
||||||
|
touch/gdi_overlay.cpp
|
||||||
touch/touch.cpp
|
touch/touch.cpp
|
||||||
touch/touch_gestures.cpp
|
touch/touch_gestures.cpp
|
||||||
touch/win7.cpp
|
touch/win7.cpp
|
||||||
|
|||||||
@@ -0,0 +1,213 @@
|
|||||||
|
#include "gdi_overlay.h"
|
||||||
|
|
||||||
|
#include <cstddef>
|
||||||
|
|
||||||
|
#include "util/logging.h"
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
// the back buffer matches the window DC; the software buffer has a fixed 32-bit layout
|
||||||
|
enum class BufferType {
|
||||||
|
TargetCompatible,
|
||||||
|
Bgra32,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct GdiBuffer {
|
||||||
|
HDC dc = nullptr;
|
||||||
|
HBITMAP bitmap = nullptr;
|
||||||
|
// bitmap originally selected into the memory DC, restored before cleanup
|
||||||
|
HGDIOBJ old_bitmap = nullptr;
|
||||||
|
int width = 0;
|
||||||
|
int height = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
// back buffer holds the complete frame; overlay buffer holds ImGui software pixels
|
||||||
|
GdiBuffer BACK_BUFFER;
|
||||||
|
GdiBuffer OVERLAY_BUFFER;
|
||||||
|
|
||||||
|
void release_buffer(GdiBuffer &buffer) {
|
||||||
|
// destroy the DC before the bitmap so cleanup is safe even if restoration fails
|
||||||
|
if (buffer.dc != nullptr) {
|
||||||
|
if (buffer.old_bitmap != nullptr && buffer.old_bitmap != HGDI_ERROR) {
|
||||||
|
SelectObject(buffer.dc, buffer.old_bitmap);
|
||||||
|
}
|
||||||
|
DeleteDC(buffer.dc);
|
||||||
|
}
|
||||||
|
if (buffer.bitmap != nullptr) {
|
||||||
|
DeleteObject(buffer.bitmap);
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
// ensures the buffer has a memory DC with a bitmap of the requested size and type
|
||||||
|
// selected into it. a matching allocation is reused; otherwise the old resources are
|
||||||
|
// released and recreated. returns false if the dimensions or any GDI operation fail.
|
||||||
|
bool ensure_buffer(
|
||||||
|
GdiBuffer &buffer,
|
||||||
|
HDC target_dc,
|
||||||
|
int width,
|
||||||
|
int height,
|
||||||
|
BufferType type,
|
||||||
|
const char *name) {
|
||||||
|
if (width <= 0 || height <= 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (buffer.dc != nullptr && buffer.bitmap != nullptr &&
|
||||||
|
buffer.width == width && buffer.height == height) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// keep allocations across frames and recreate only after a size change
|
||||||
|
release_buffer(buffer);
|
||||||
|
buffer.dc = CreateCompatibleDC(target_dc);
|
||||||
|
if (buffer.dc == nullptr) {
|
||||||
|
log_warning("touch", "failed to create {} DC: {}", name, GetLastError());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// compatible bitmaps are fast presentation targets; BGRA bitmaps accept raw pixels
|
||||||
|
if (type == BufferType::TargetCompatible) {
|
||||||
|
buffer.bitmap = CreateCompatibleBitmap(target_dc, width, height);
|
||||||
|
} else {
|
||||||
|
buffer.bitmap = CreateBitmap(width, height, 1, sizeof(uint32_t) * 8, nullptr);
|
||||||
|
}
|
||||||
|
if (buffer.bitmap == nullptr) {
|
||||||
|
log_warning("touch", "failed to create {} bitmap: {}", name, GetLastError());
|
||||||
|
release_buffer(buffer);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer.old_bitmap = SelectObject(buffer.dc, buffer.bitmap);
|
||||||
|
if (buffer.old_bitmap == nullptr || buffer.old_bitmap == HGDI_ERROR) {
|
||||||
|
log_warning("touch", "failed to select {} bitmap: {}", name, GetLastError());
|
||||||
|
release_buffer(buffer);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer.width = width;
|
||||||
|
buffer.height = height;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool update_overlay_buffer(
|
||||||
|
HDC target_dc,
|
||||||
|
const uint32_t *pixels,
|
||||||
|
bool pixels_dirty,
|
||||||
|
int width,
|
||||||
|
int height) {
|
||||||
|
if (pixels == nullptr) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool needs_update = pixels_dirty || OVERLAY_BUFFER.bitmap == nullptr ||
|
||||||
|
OVERLAY_BUFFER.width != width || OVERLAY_BUFFER.height != height;
|
||||||
|
if (!ensure_buffer(
|
||||||
|
OVERLAY_BUFFER,
|
||||||
|
target_dc,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
BufferType::Bgra32,
|
||||||
|
"software overlay")) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!needs_update) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetDIBits requires the destination bitmap not to be selected into a DC
|
||||||
|
HGDIOBJ overlay_bitmap =
|
||||||
|
SelectObject(OVERLAY_BUFFER.dc, OVERLAY_BUFFER.old_bitmap);
|
||||||
|
if (overlay_bitmap == nullptr || overlay_bitmap == HGDI_ERROR) {
|
||||||
|
log_warning("touch", "failed to deselect software overlay bitmap: {}", GetLastError());
|
||||||
|
release_buffer(OVERLAY_BUFFER);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
BITMAPINFO bitmap_info {};
|
||||||
|
bitmap_info.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
|
||||||
|
bitmap_info.bmiHeader.biWidth = width;
|
||||||
|
bitmap_info.bmiHeader.biHeight = -height;
|
||||||
|
bitmap_info.bmiHeader.biPlanes = 1;
|
||||||
|
bitmap_info.bmiHeader.biBitCount = sizeof(uint32_t) * 8;
|
||||||
|
bitmap_info.bmiHeader.biCompression = BI_RGB;
|
||||||
|
|
||||||
|
int copied_lines = SetDIBits(
|
||||||
|
target_dc,
|
||||||
|
OVERLAY_BUFFER.bitmap,
|
||||||
|
0,
|
||||||
|
height,
|
||||||
|
pixels,
|
||||||
|
&bitmap_info,
|
||||||
|
DIB_RGB_COLORS);
|
||||||
|
|
||||||
|
HGDIOBJ old_bitmap = SelectObject(OVERLAY_BUFFER.dc, OVERLAY_BUFFER.bitmap);
|
||||||
|
if (old_bitmap == nullptr || old_bitmap == HGDI_ERROR) {
|
||||||
|
log_warning("touch", "failed to reselect software overlay bitmap: {}", GetLastError());
|
||||||
|
release_buffer(OVERLAY_BUFFER);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
OVERLAY_BUFFER.old_bitmap = old_bitmap;
|
||||||
|
|
||||||
|
if (copied_lines != height) {
|
||||||
|
log_warning("touch", "failed to update software overlay bitmap: {} of {} lines copied",
|
||||||
|
copied_lines, height);
|
||||||
|
release_buffer(OVERLAY_BUFFER);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
HDC touch_gdi_overlay_begin_frame(
|
||||||
|
HDC target_dc,
|
||||||
|
HBRUSH background_brush,
|
||||||
|
int width,
|
||||||
|
int height,
|
||||||
|
const uint32_t *overlay_pixels,
|
||||||
|
bool overlay_pixels_dirty,
|
||||||
|
int overlay_width,
|
||||||
|
int overlay_height) {
|
||||||
|
if (!ensure_buffer(
|
||||||
|
BACK_BUFFER,
|
||||||
|
target_dc,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
BufferType::TargetCompatible,
|
||||||
|
"overlay back buffer")) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
HDC draw_dc = BACK_BUFFER.dc;
|
||||||
|
SetBkMode(draw_dc, TRANSPARENT);
|
||||||
|
|
||||||
|
// start each frame from the transparent color-key background
|
||||||
|
RECT buffer_rect {0, 0, width, height};
|
||||||
|
FillRect(draw_dc, &buffer_rect, background_brush);
|
||||||
|
|
||||||
|
if (update_overlay_buffer(
|
||||||
|
target_dc,
|
||||||
|
overlay_pixels,
|
||||||
|
overlay_pixels_dirty,
|
||||||
|
overlay_width,
|
||||||
|
overlay_height) &&
|
||||||
|
!BitBlt(draw_dc, 0, 0, overlay_width, overlay_height,
|
||||||
|
OVERLAY_BUFFER.dc, 0, 0, SRCCOPY)) {
|
||||||
|
log_warning("touch", "failed to draw software overlay bitmap: {}", GetLastError());
|
||||||
|
}
|
||||||
|
|
||||||
|
return draw_dc;
|
||||||
|
}
|
||||||
|
|
||||||
|
void touch_gdi_overlay_present(HDC target_dc) {
|
||||||
|
// one full-window blit exposes the completed frame without an intermediate erase
|
||||||
|
if (!BitBlt(target_dc, 0, 0, BACK_BUFFER.width, BACK_BUFFER.height,
|
||||||
|
BACK_BUFFER.dc, 0, 0, SRCCOPY)) {
|
||||||
|
log_warning("touch", "failed to present overlay back buffer: {}", GetLastError());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void touch_gdi_overlay_release() {
|
||||||
|
release_buffer(BACK_BUFFER);
|
||||||
|
release_buffer(OVERLAY_BUFFER);
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <windows.h>
|
||||||
|
|
||||||
|
// prepares a complete offscreen frame and returns its drawing DC; returns null on failure
|
||||||
|
HDC touch_gdi_overlay_begin_frame(
|
||||||
|
HDC target_dc,
|
||||||
|
HBRUSH background_brush,
|
||||||
|
int width,
|
||||||
|
int height,
|
||||||
|
const uint32_t *overlay_pixels,
|
||||||
|
bool overlay_pixels_dirty,
|
||||||
|
int overlay_width,
|
||||||
|
int overlay_height);
|
||||||
|
|
||||||
|
// presents the frame prepared by the most recent successful begin call
|
||||||
|
void touch_gdi_overlay_present(HDC target_dc);
|
||||||
|
|
||||||
|
// releases all cached GDI resources
|
||||||
|
void touch_gdi_overlay_release();
|
||||||
+61
-35
@@ -23,6 +23,7 @@
|
|||||||
#include "util/time.h"
|
#include "util/time.h"
|
||||||
#include "util/utils.h"
|
#include "util/utils.h"
|
||||||
|
|
||||||
|
#include "gdi_overlay.h"
|
||||||
#include "handler.h"
|
#include "handler.h"
|
||||||
#include "touch_gestures.h"
|
#include "touch_gestures.h"
|
||||||
#include "win7.h"
|
#include "win7.h"
|
||||||
@@ -38,6 +39,16 @@ static const int TOUCH_EVENT_BUFFER_SIZE = 1024 * 4;
|
|||||||
static const int TOUCH_EVENT_BUFFER_THRESHOLD1 = 1024 * 2;
|
static const int TOUCH_EVENT_BUFFER_THRESHOLD1 = 1024 * 2;
|
||||||
static const int TOUCH_EVENT_BUFFER_THRESHOLD2 = 1024 * 3;
|
static const int TOUCH_EVENT_BUFFER_THRESHOLD2 = 1024 * 3;
|
||||||
|
|
||||||
|
// timer id for the overlay repaint tick
|
||||||
|
static const UINT_PTR SPICETOUCH_OVERLAY_TIMER_ID = 1;
|
||||||
|
|
||||||
|
// overlay repaint interval; the WinXP-compat build stays at 30 FPS
|
||||||
|
#if !SPICE_XP
|
||||||
|
static const int SPICETOUCH_OVERLAY_TIMER_MS = 1000 / 60;
|
||||||
|
#else
|
||||||
|
static const int SPICETOUCH_OVERLAY_TIMER_MS = 1000 / 30;
|
||||||
|
#endif // !SPICE_XP
|
||||||
|
|
||||||
// in mainline spicetools, this was false (show by default)
|
// in mainline spicetools, this was false (show by default)
|
||||||
// in spice2x, this is true (hide by default)
|
// in spice2x, this is true (hide by default)
|
||||||
bool SPICETOUCH_CARD_DISABLE = true;
|
bool SPICETOUCH_CARD_DISABLE = true;
|
||||||
@@ -309,7 +320,10 @@ static LRESULT CALLBACK SpiceTouchWndProc(HWND hWnd, UINT msg, WPARAM wParam, LP
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case WM_TIMER: {
|
case WM_TIMER: {
|
||||||
InvalidateRect(hWnd, NULL, TRUE);
|
|
||||||
|
// request a repaint; the frame is composed into an offscreen buffer, so no
|
||||||
|
// background erase is needed (bErase = FALSE avoids a transparent flash)
|
||||||
|
InvalidateRect(hWnd, NULL, FALSE);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case WM_PAINT: {
|
case WM_PAINT: {
|
||||||
@@ -341,45 +355,47 @@ static LRESULT CALLBACK SpiceTouchWndProc(HWND hWnd, UINT msg, WPARAM wParam, LP
|
|||||||
SWP_NOZORDER | SWP_NOREDRAW | SWP_NOREPOSITION | SWP_NOACTIVATE);
|
SWP_NOZORDER | SWP_NOREDRAW | SWP_NOREPOSITION | SWP_NOACTIVATE);
|
||||||
}
|
}
|
||||||
|
|
||||||
// render the software overlay to a bitmap BEFORE BeginPaint: the imgui
|
// render the software overlay before BeginPaint so only GDI composition
|
||||||
// render is expensive, and running it between the background erase and the
|
// and the final blit happen while the window is being painted
|
||||||
// blit leaves the window transparent long enough to flicker
|
|
||||||
HBITMAP overlay_bitmap = nullptr;
|
|
||||||
int overlay_width = 0, overlay_height = 0;
|
int overlay_width = 0, overlay_height = 0;
|
||||||
|
uint32_t *overlay_pixels = nullptr;
|
||||||
|
bool overlay_pixels_dirty = false;
|
||||||
if (overlay_enabled) {
|
if (overlay_enabled) {
|
||||||
overlay::OVERLAY->update();
|
overlay::OVERLAY->update();
|
||||||
overlay::OVERLAY->new_frame();
|
overlay::OVERLAY->new_frame();
|
||||||
overlay::OVERLAY->render();
|
overlay::OVERLAY->render();
|
||||||
|
|
||||||
uint32_t *pixel_data = overlay::OVERLAY.get()->sw_get_pixel_data(&overlay_width, &overlay_height);
|
overlay_pixels = overlay::OVERLAY.get()->sw_get_pixel_data(
|
||||||
if (overlay_width > 0 && overlay_height > 0) {
|
&overlay_width, &overlay_height);
|
||||||
overlay_bitmap = CreateBitmap(overlay_width, overlay_height, 1, 8 * sizeof(uint32_t), pixel_data);
|
overlay_pixels_dirty = overlay::OVERLAY->sw_pixels_dirty;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
bool overlay_active = overlay_enabled && overlay::OVERLAY->get_active();
|
bool overlay_active = overlay_enabled && overlay::OVERLAY->get_active();
|
||||||
|
|
||||||
// draw everything in a single BeginPaint/EndPaint (a WM_PAINT has one update
|
// compose the whole frame into an offscreen back buffer and present it with a
|
||||||
// region, so a second BeginPaint would get an empty region), keeping only
|
// single blit; the window never shows a half-erased (transparent) surface
|
||||||
// fast blits between the background erase and EndPaint
|
// mid-paint, which is what caused the occasional flicker at higher frame rates
|
||||||
PAINTSTRUCT paint {};
|
PAINTSTRUCT paint {};
|
||||||
HDC hdc = BeginPaint(hWnd, &paint);
|
HDC hdc = BeginPaint(hWnd, &paint);
|
||||||
SetBkMode(hdc, TRANSPARENT);
|
|
||||||
|
|
||||||
// blit the overlay bitmap
|
RECT bufferRect {};
|
||||||
if (overlay_bitmap) {
|
GetClientRect(hWnd, &bufferRect);
|
||||||
|
int buffer_width = bufferRect.right - bufferRect.left;
|
||||||
|
int buffer_height = bufferRect.bottom - bufferRect.top;
|
||||||
|
|
||||||
/*
|
HBRUSH color_key_brush =
|
||||||
* draw bitmap
|
(HBRUSH) GetClassLongPtr(hWnd, GCLP_HBRBACKGROUND);
|
||||||
* - this currently sets the background to black because of SRCCOPY
|
HDC draw_dc = touch_gdi_overlay_begin_frame(
|
||||||
* - SRCPAINT will blend but colors are wrong
|
hdc,
|
||||||
* - once this is figured out we could also try hooking WM_PAINT and
|
color_key_brush,
|
||||||
* draw directly to the game window
|
buffer_width,
|
||||||
*/
|
buffer_height,
|
||||||
HDC hdcMem = CreateCompatibleDC(hdc);
|
overlay_pixels,
|
||||||
SelectObject(hdcMem, overlay_bitmap);
|
overlay_pixels_dirty,
|
||||||
BitBlt(hdc, 0, 0, overlay_width, overlay_height, hdcMem, 0, 0, SRCCOPY);
|
overlay_width,
|
||||||
DeleteDC(hdcMem);
|
overlay_height);
|
||||||
DeleteObject(overlay_bitmap);
|
if (draw_dc == nullptr) {
|
||||||
|
EndPaint(hWnd, &paint);
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// draw the insert-card button below the jubeat debug overlay; it is
|
// draw the insert-card button below the jubeat debug overlay; it is
|
||||||
@@ -414,7 +430,7 @@ static LRESULT CALLBACK SpiceTouchWndProc(HWND hWnd, UINT msg, WPARAM wParam, LP
|
|||||||
SPICETOUCH_CARD_RECT = boxRect;
|
SPICETOUCH_CARD_RECT = boxRect;
|
||||||
|
|
||||||
// draw borders
|
// draw borders
|
||||||
FillRect(hdc, &boxRect, brushBorder);
|
FillRect(draw_dc, &boxRect, brushBorder);
|
||||||
|
|
||||||
// modify box rect
|
// modify box rect
|
||||||
boxRect.left += 1;
|
boxRect.left += 1;
|
||||||
@@ -423,7 +439,7 @@ static LRESULT CALLBACK SpiceTouchWndProc(HWND hWnd, UINT msg, WPARAM wParam, LP
|
|||||||
boxRect.bottom -= 1;
|
boxRect.bottom -= 1;
|
||||||
|
|
||||||
// fill box
|
// fill box
|
||||||
FillRect(hdc, &boxRect, brushFill);
|
FillRect(draw_dc, &boxRect, brushFill);
|
||||||
|
|
||||||
// modify box rect
|
// modify box rect
|
||||||
if (should_rotate) {
|
if (should_rotate) {
|
||||||
@@ -435,19 +451,24 @@ static LRESULT CALLBACK SpiceTouchWndProc(HWND hWnd, UINT msg, WPARAM wParam, LP
|
|||||||
}
|
}
|
||||||
|
|
||||||
// draw text
|
// draw text
|
||||||
SelectObject(hdc, SPICETOUCH_FONT);
|
SelectObject(draw_dc, SPICETOUCH_FONT);
|
||||||
SetTextColor(hdc, RGB(0, 196, 0));
|
SetTextColor(draw_dc, RGB(0, 196, 0));
|
||||||
DrawText(hdc, INSERT_CARD_TEXT, -1, &boxRect, DT_LEFT | DT_BOTTOM | DT_NOCLIP);
|
DrawText(draw_dc, INSERT_CARD_TEXT, -1, &boxRect, DT_LEFT | DT_BOTTOM | DT_NOCLIP);
|
||||||
|
|
||||||
// delete objects
|
// delete objects
|
||||||
DeleteObject(brushFill);
|
DeleteObject(brushFill);
|
||||||
DeleteObject(brushBorder);
|
DeleteObject(brushBorder);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if !SPICE_XP
|
||||||
// draw the jubeat debug overlay on top (hidden while the overlay is active)
|
// draw the jubeat debug overlay on top (hidden while the overlay is active)
|
||||||
if (overlay_enabled && !overlay_active && games::jb::touch_debug_overlay_enabled()) {
|
if (overlay_enabled && !overlay_active && games::jb::touch_debug_overlay_enabled()) {
|
||||||
games::jb::touch_draw_debug_overlay(hdc);
|
games::jb::touch_draw_debug_overlay(draw_dc);
|
||||||
}
|
}
|
||||||
|
#endif // !SPICE_XP
|
||||||
|
|
||||||
|
// present the composed frame in a single blit
|
||||||
|
touch_gdi_overlay_present(hdc);
|
||||||
|
|
||||||
EndPaint(hWnd, &paint);
|
EndPaint(hWnd, &paint);
|
||||||
return 0;
|
return 0;
|
||||||
@@ -480,6 +501,11 @@ static LRESULT CALLBACK SpiceTouchWndProc(HWND hWnd, UINT msg, WPARAM wParam, LP
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
case WM_DESTROY: {
|
case WM_DESTROY: {
|
||||||
|
touch_gdi_overlay_release();
|
||||||
|
if (SPICETOUCH_FONT != nullptr) {
|
||||||
|
DeleteObject(SPICETOUCH_FONT);
|
||||||
|
SPICETOUCH_FONT = nullptr;
|
||||||
|
}
|
||||||
PostQuitMessage(0);
|
PostQuitMessage(0);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -768,8 +794,8 @@ void touch_create_wnd(HWND hWnd, bool overlay) {
|
|||||||
// create instance
|
// create instance
|
||||||
overlay::OVERLAY.reset(new overlay::SpiceOverlay(touch_window));
|
overlay::OVERLAY.reset(new overlay::SpiceOverlay(touch_window));
|
||||||
|
|
||||||
// draw overlay in 30 FPS
|
// draw overlay repaint timer (30 FPS on WinXP, 60 FPS otherwise)
|
||||||
SetTimer(touch_window, 1, 1000 / 30, NULL);
|
SetTimer(touch_window, SPICETOUCH_OVERLAY_TIMER_ID, SPICETOUCH_OVERLAY_TIMER_MS, NULL);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user