Skip to content

Commit 1b436e0

Browse files
committed
Added a make_surface to make an image_surface for use as a render target. Switched code to draw to the render target surface and then paint in one shot to the Win32 window. Removed flickering by removing CS_HREDRAW and CS_VREDRAW, which in turn required a work around for a bug of unknown provenance that was causing a crash when resizing a window. Window now does not update contents while being resized. Should fix issue #23. Code should run slightly faster and much cleaner now that it handles painting directly when there is no message rather than triggering a WM_PAINT message. Issue #26 changes are complete.
1 parent a7ea59d commit 1b436e0

File tree

3 files changed

+187
-40
lines changed

3 files changed

+187
-40
lines changed

win32/N3888_RefImpl/drawing.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -877,6 +877,7 @@ namespace std {
877877

878878
int format_stride_for_width(format format, int width);
879879
surface make_surface(surface::native_handle_type nh);
880+
surface make_surface(format format, int width, int height);
880881
path _Make_path(path::native_handle_type nh);
881882
}
882883
}

win32/N3888_RefImpl/entrypoint-win32.cpp

Lines changed: 182 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
#include <memory>
1010
#include <functional>
11+
#include <atomic>
1112
#include <wrl.h>
1213
#include "throw_helpers.h"
1314
#include "drawing.h"
@@ -23,40 +24,70 @@ using namespace std::experimental::drawing;
2324

2425
#define MAX_LOADSTRING 100
2526

27+
class ref_counted_bool {
28+
::std::atomic<int> m_refCount;
29+
public:
30+
ref_counted_bool() : m_refCount(0) {}
31+
ref_counted_bool& operator=(bool value) {
32+
if (value) {
33+
m_refCount++;
34+
}
35+
else {
36+
auto count = m_refCount.load(memory_order_acquire);
37+
if (--count < 0) {
38+
count = 0;
39+
}
40+
m_refCount.store(count, memory_order_release);
41+
}
42+
return *this;
43+
}
44+
operator bool() {
45+
return m_refCount.load() != 0;
46+
}
47+
};
48+
2649
// Global Variables:
2750
HINSTANCE hInst; // current instance
2851
TCHAR szTitle[MAX_LOADSTRING]; // The title bar text
2952
TCHAR szWindowClass[MAX_LOADSTRING]; // the main window class name
3053
unique_ptr<surface> g_psurface;
54+
RECT g_previousClientRect;
55+
ref_counted_bool g_doNotPaint;
3156

3257
// Everything in the Draw function should be portable C++ code.
3358
void Draw(surface& surface) {
3459
// Create a context that will draw to our surface.
3560
auto context = drawing::context(surface);
3661

3762
// The is a demonstration of how a raster_source_pattern works. We create one that is 100px x 100px
38-
auto pattern = raster_source_pattern(nullptr, content::color_alpha, 100, 100);
39-
pattern.set_acquire(
40-
[pattern](void*, experimental::drawing::surface& target, const rectangle_int& extents) -> experimental::drawing::surface
41-
{
42-
auto result = experimental::drawing::image_surface(target, format::rgb24, extents.width - extents.x, extents.height - extents.y);
43-
vector<unsigned char> data;
44-
const auto dataSize = result.get_stride() * result.get_height();
45-
data.resize(dataSize);
46-
for (auto i = 0; i < dataSize; i += 4) {
47-
data[i + 0] = 255ui8;
48-
data[i + 1] = 0ui8;
49-
data[i + 2] = 0ui8;
50-
data[i + 3] = 0ui8;
51-
}
52-
result.set_data(data);
53-
return result;
54-
},
55-
nullptr
56-
);
57-
pattern.set_extend(extend::repeat);
58-
context.set_source(pattern);
63+
//auto pattern = raster_source_pattern(nullptr, content::color_alpha, 100, 100);
64+
//pattern.set_acquire(
65+
// [pattern](void*, experimental::drawing::surface& target, const rectangle_int& extents) -> experimental::drawing::surface
66+
//{
67+
// auto result = experimental::drawing::image_surface(target, format::rgb24, extents.width - extents.x, extents.height - extents.y);
68+
// vector<unsigned char> data;
69+
// const auto dataSize = result.get_stride() * result.get_height();
70+
// data.resize(dataSize);
71+
// for (auto i = 0; i < dataSize; i += 4) {
72+
// data[i + 0] = 255ui8;
73+
// data[i + 1] = 0ui8;
74+
// data[i + 2] = 0ui8;
75+
// data[i + 3] = 0ui8;
76+
// }
77+
// result.set_data(data);
78+
// return result;
79+
//},
80+
// nullptr
81+
// );
82+
//pattern.set_extend(extend::repeat);
83+
//context.set_source(pattern);
84+
//context.paint();
85+
86+
context.save();
87+
auto scp = solid_color_pattern(0.0, 0.0, 1.0);
88+
context.set_source(scp);
5989
context.paint();
90+
context.restore();
6091

6192
context.save();
6293
const int width = 100;
@@ -97,6 +128,43 @@ void Draw(surface& surface) {
97128
imageSurfaceFromData.finish();
98129
context.restore();
99130

131+
context.save();
132+
auto surfaceForContext2 = image_surface(format::argb32, 100, 100);
133+
auto context2 = drawing::context(surfaceForContext2);
134+
context2.set_source_rgba(0.5, 0.5, 0.5, 0.5);
135+
context2.set_compositing_operator(compositing_operator::clear);
136+
context2.paint_with_alpha(0.0);
137+
context2.set_source_rgba(0.5, 0.5, 0.5, 0.5);
138+
context2.set_compositing_operator(compositing_operator::add);
139+
context2.paint();
140+
surfaceForContext2.flush();
141+
context.set_source_surface(surfaceForContext2, 600.0, 400.0);
142+
context.move_to(600.0, 400.0);
143+
context.rel_line_to(100.0, 0.0);
144+
context.rel_line_to(0.0, 100.0);
145+
context.rel_line_to(-100.0, 0.0);
146+
context.close_path();
147+
context.fill();
148+
context.set_source_surface(surfaceForContext2, 650.0, 400.0);
149+
context.move_to(650.0, 400.0);
150+
context.rel_line_to(100.0, 0.0);
151+
context.rel_line_to(0.0, 100.0);
152+
context.rel_line_to(-100.0, 0.0);
153+
context.close_path();
154+
context.fill();
155+
context.restore();
156+
157+
context.save();
158+
auto subsurface = drawing::surface(surface, 10.5, 11.0, 50.0, 50.0);
159+
auto subcontext = drawing::context(subsurface);
160+
subcontext.set_source_rgb(0.0, 0.0, 0.0);
161+
subcontext.move_to(2.0, 2.0);
162+
subcontext.rel_line_to(48.0, 0.0);
163+
subcontext.set_line_width(3.0);
164+
subcontext.set_line_cap(line_cap::butt);
165+
subcontext.stroke();
166+
context.restore();
167+
100168
context.save();
101169
matrix m;
102170
m.init_translate(300.0, 400.0);
@@ -128,8 +196,6 @@ void Draw(surface& surface) {
128196
context.select_font_face("Segoe UI", font_slant::normal, font_weight::normal);
129197
context.set_font_size(30.0);
130198
context.show_text("Hello C++!");
131-
132-
//surface.finish();
133199
}
134200

135201
void OnPaint(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) {
@@ -139,23 +205,51 @@ void OnPaint(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) {
139205

140206
PAINTSTRUCT ps;
141207
HDC hdc;
208+
RECT updateRect{ };
209+
auto getUpdateRectResult = GetUpdateRect(hWnd, &updateRect, FALSE);
210+
211+
// There's a bug somewhere (possibly cairo?) where if you run this code without the check to make sure that
212+
// updateRect.left and .top are both 0, it crashes and does so in the cairo DLL such that it's immune to
213+
// being caught. Specifically in a Win32/Debug config it throws well inside cairo on the ctxt.paint() call
214+
// on an illegal memory access (actually in pixman-sse2.c in a call to "void save_128_aligned(__m128i*, __m128i);").
215+
if (getUpdateRectResult != FALSE && updateRect.left == 0 && updateRect.top == 0) {
216+
hdc = BeginPaint(hWnd, &ps);
217+
218+
RECT clientRect;
219+
if (!GetClientRect(hWnd, &clientRect)) {
220+
throw_get_last_error<logic_error>("Failed GetClientRect call.");
221+
}
222+
auto width = clientRect.right - clientRect.left;
223+
auto height = clientRect.bottom - clientRect.top;
224+
auto previousWidth = g_previousClientRect.right - g_previousClientRect.left;
225+
auto previousHeight = g_previousClientRect.bottom - g_previousClientRect.top;
226+
227+
// To enable screenshot saving, we are using a global unique_ptr surface. I did not rewrite the boilerplate
228+
// Win32 code so that it'd be a class, hence the globals.
229+
if ((g_psurface == nullptr) || (width != previousWidth) || (height != previousHeight)) {
230+
g_psurface = unique_ptr<surface>(new surface(move(make_surface(format::argb32, width, height))));
231+
g_previousClientRect = clientRect;
232+
}
142233

143-
hdc = BeginPaint(hWnd, &ps);
144-
145-
// To enable screenshot saving, we are using a global unique_ptr surface. I did not rewrite the boilerplate
146-
// Win32 code so that it'd be a class, hence the globals. Note that this would not work without using CS_OWNDC
147-
// when registering the window class since we could get a different HDC each time without that flag.
148-
if (g_psurface == nullptr) {
149-
g_psurface = unique_ptr<surface>(new surface(move(make_surface(cairo_win32_surface_create(hdc)))));
150-
}
151-
152-
// Draw to the off-screen buffer.
153-
Draw(*g_psurface);
234+
// Draw to the off-screen buffer.
235+
Draw(*g_psurface);
154236

155-
// Flush to ensure that it is drawn to the window.
156-
g_psurface->flush();
237+
// Flush to ensure that it is drawn to the window.
238+
g_psurface->flush();
157239

158-
EndPaint(hWnd, &ps);
240+
auto surface = make_surface(cairo_win32_surface_create(hdc));
241+
auto ctxt = context(surface);
242+
ctxt.set_source_surface(*g_psurface, 0.0, 0.0);
243+
ctxt.paint();
244+
surface.flush();
245+
EndPaint(hWnd, &ps);
246+
}
247+
else {
248+
if (getUpdateRectResult != FALSE) {
249+
hdc = BeginPaint(hWnd, &ps);
250+
EndPaint(hWnd, &ps);
251+
}
252+
}
159253
}
160254

161255
int WINAPI wWinMain(
@@ -166,6 +260,7 @@ int WINAPI wWinMain(
166260
) {
167261
UNREFERENCED_PARAMETER(hPrevInstance);
168262
UNREFERENCED_PARAMETER(lpCmdLine);
263+
g_previousClientRect = { }; // Zero out previous client rect.
169264
throw_if_failed_hresult<runtime_error>(
170265
CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED), "Failed call to CoInitializeEx."
171266
);
@@ -191,7 +286,38 @@ int WINAPI wWinMain(
191286
while (msg.message != WM_QUIT) {
192287
if (!PeekMessageW(&msg, NULL, 0, 0, PM_REMOVE)) {
193288
// No message so redraw the window.
194-
RedrawWindow(hWnd, nullptr, nullptr, RDW_INVALIDATE | RDW_UPDATENOW);
289+
if (g_psurface != nullptr && !g_doNotPaint) {
290+
RECT clientRect;
291+
if (!GetClientRect(hWnd, &clientRect)) {
292+
throw_get_last_error<logic_error>("Failed GetClientRect call.");
293+
}
294+
auto width = clientRect.right - clientRect.left;
295+
auto height = clientRect.bottom - clientRect.top;
296+
auto previousWidth = g_previousClientRect.right - g_previousClientRect.left;
297+
auto previousHeight = g_previousClientRect.bottom - g_previousClientRect.top;
298+
299+
// To enable screenshot saving, we are using a global unique_ptr surface. I did not rewrite the boilerplate
300+
// Win32 code so that it'd be a class, hence the globals.
301+
if ((width != previousWidth) || (height != previousHeight)) {
302+
g_psurface = unique_ptr<surface>(new surface(move(make_surface(format::argb32, width, height))));
303+
g_previousClientRect = clientRect;
304+
}
305+
306+
// Draw to the off-screen buffer.
307+
Draw(*g_psurface);
308+
309+
// Flush to ensure that it is drawn to the window.
310+
g_psurface->flush();
311+
auto hdc = GetDC(hWnd);
312+
{
313+
auto surface = make_surface(cairo_win32_surface_create(hdc));
314+
auto ctxt = context(surface);
315+
ctxt.set_source_surface(*g_psurface, 0.0, 0.0);
316+
ctxt.paint();
317+
surface.flush();
318+
}
319+
ReleaseDC(hWnd, hdc);
320+
}
195321
}
196322
else {
197323
if (msg.message != WM_QUIT) {
@@ -216,7 +342,7 @@ ATOM MyRegisterClass(HINSTANCE hInstance) {
216342

217343
wcex.cbSize = sizeof(WNDCLASSEX);
218344

219-
wcex.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;
345+
wcex.style = 0;
220346
wcex.lpfnWndProc = WndProc;
221347
wcex.cbClsExtra = 0;
222348
wcex.cbWndExtra = 0;
@@ -369,6 +495,14 @@ void ShowSaveAsPNGDialog() {
369495
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) {
370496
int wmId, wmEvent;
371497

498+
#if defined(DEBUG_WNDPROC)
499+
wstringstream str;
500+
str << L"Message: 0x" << hex << uppercase << message << nouppercase
501+
<< L". WPARAM: 0x" << hex << uppercase << static_cast<UINT>(wParam) << nouppercase
502+
<< L". LPARAM: 0x" << hex << uppercase << static_cast<UINT>(lParam) << endl;
503+
OutputDebugStringW(str.str().c_str());
504+
#endif
505+
372506
switch (message) {
373507
case WM_COMMAND:
374508
wmId = LOWORD(wParam);
@@ -393,8 +527,16 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
393527
return DefWindowProc(hWnd, message, wParam, lParam);
394528
}
395529
break;
530+
case WM_ENTERSIZEMOVE:
531+
g_doNotPaint = true; // Don't paint while resizing to avoid flicker.
532+
return DefWindowProc(hWnd, message, wParam, lParam);
533+
case WM_EXITSIZEMOVE:
534+
g_doNotPaint = false;
535+
return DefWindowProc(hWnd, message, wParam, lParam);
396536
case WM_PAINT:
397-
OnPaint(hWnd, message, wParam, lParam);
537+
if (!g_doNotPaint) {
538+
OnPaint(hWnd, message, wParam, lParam);
539+
}
398540
break;
399541
case WM_DESTROY:
400542
PostQuitMessage(0);

win32/N3888_RefImpl/standalone_functions.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,10 @@ namespace std {
3030
return surface(nh);
3131
}
3232

33+
surface make_surface(format fmt, int width, int height) {
34+
return image_surface(fmt, width, height);
35+
}
36+
3337
int format_stride_for_width(format format, int width) {
3438
return cairo_format_stride_for_width(_Format_to_cairo_format_t(format), width);
3539
}

0 commit comments

Comments
 (0)