From ce3a19e7b4066f90046d91379c3a9c6d78d24223 Mon Sep 17 00:00:00 2001 From: Aubrey Buchanan Date: Tue, 10 Jul 2018 08:49:52 -0600 Subject: [PATCH 01/55] Attempt to write to depth buffer --- src/plugin/common/gl_screen.c | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/plugin/common/gl_screen.c b/src/plugin/common/gl_screen.c index bddcd18..7757ddf 100644 --- a/src/plugin/common/gl_screen.c +++ b/src/plugin/common/gl_screen.c @@ -19,6 +19,7 @@ static GLuint program; static GLuint vao; static GLuint texture; +static GLuint depth_texture; static int32_t tex_width; static int32_t tex_height; @@ -155,6 +156,10 @@ void gl_screen_init(struct rdp_config* config) glGenVertexArrays(1, &vao); glBindVertexArray(vao); + // prepare depth texture + glGenTextures(1, &depth_texture); + glBindTexture(GL_TEXTURE_2D, depth_texture); + // prepare texture glGenTextures(1, &texture); glBindTexture(GL_TEXTURE_2D, texture); @@ -176,10 +181,15 @@ void gl_screen_init(struct rdp_config* config) gl_check_errors(); } -bool gl_screen_write(struct rdp_frame_buffer* fb, int32_t output_height) +bool gl_screen_write(struct rdp_frame_buffer* fb, int32_t output_height, struct rdp_frame_buffer* depth) { bool buffer_size_changed = tex_width != fb->width || tex_height != fb->height; + // write the depth to the depthbuffer + glBindTexture(GL_TEXTURE_2D, depth_texture); + glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, depth->width, depth->height, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_INT, depth->pixels) + glBindTexture(GL_TEXTURE_2D, 0); + // check if the framebuffer size has changed if (buffer_size_changed) { tex_width = fb->width; From e8ca4fd35ac55ea93bb397049be044444b597be5 Mon Sep 17 00:00:00 2001 From: Aubrey Buchanan Date: Tue, 10 Jul 2018 08:50:02 -0600 Subject: [PATCH 02/55] Update screen.c --- src/plugin/zilmar/screen.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/plugin/zilmar/screen.c b/src/plugin/zilmar/screen.c index 3f62355..aff8b83 100644 --- a/src/plugin/zilmar/screen.c +++ b/src/plugin/zilmar/screen.c @@ -148,9 +148,9 @@ void screen_init(struct rdp_config* config) gl_screen_init(config); } -void screen_write(struct rdp_frame_buffer* buffer, int32_t output_height) +void screen_write(struct rdp_frame_buffer* buffer, int32_t output_height, struct rdp_frame_buffer* depth_buffer) { - gl_screen_write(buffer, output_height); + gl_screen_write(buffer, output_height, depth_buffer); } void screen_read(struct rdp_frame_buffer* buffer, bool alpha) From 975ee131986883e78326c2448f60970d9ac33c79 Mon Sep 17 00:00:00 2001 From: Aubrey Buchanan Date: Tue, 10 Jul 2018 08:51:10 -0600 Subject: [PATCH 03/55] Update screen.h --- src/core/screen.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/screen.h b/src/core/screen.h index 2f59aee..531fffc 100644 --- a/src/core/screen.h +++ b/src/core/screen.h @@ -8,7 +8,7 @@ void screen_init(struct rdp_config* config); void screen_swap(bool blank); void screen_write(struct rdp_frame_buffer* fb, int32_t output_height); -void screen_read(struct rdp_frame_buffer* fb, bool rgb); +void screen_read(struct rdp_frame_buffer* fb, bool rgb, struct rdp_frame_buffer* db); void screen_set_fullscreen(bool fullscreen); bool screen_get_fullscreen(void); void screen_toggle_fullscreen(void); From b2d3019e5ceea785fc67a988119a251b39086691 Mon Sep 17 00:00:00 2001 From: Aubrey Buchanan Date: Tue, 10 Jul 2018 08:54:58 -0600 Subject: [PATCH 04/55] Creating depth buffers --- src/core/rdp/vi.c | 29 +++++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/src/core/rdp/vi.c b/src/core/rdp/vi.c index 59cc5d3..e2a0cfd 100644 --- a/src/core/rdp/vi.c +++ b/src/core/rdp/vi.c @@ -527,6 +527,10 @@ static void vi_process_end(void) struct rdp_frame_buffer fb; fb.pixels = prescale; fb.pitch = PRESCALE_WIDTH; + + struct rdp_frame_buffer db; + db.pixels = prescale; + db.pitch = PRESCALE_WIDTH; int32_t output_height; @@ -544,12 +548,27 @@ static void vi_process_end(void) fb.height = (ispal ? V_RES_PAL : V_RES_NTSC) >> !ctrl.serrate; output_height = V_RES_NTSC; } + + if (config.vi.hide_overscan) { + // crop away overscan area from prescale + db.width = maxhpass - minhpass; + db.height = vres << ctrl.serrate; + output_height = (vres << 1) * V_SYNC_NTSC / v_sync; + int32_t x = h_start + minhpass; + int32_t y = (v_start + (emucontrolsvicurrent ? lowerfield : 0)) << ctrl.serrate; + db.pixels += x + y * db.pitch; + } else { + // use entire prescale buffer + db.width = PRESCALE_WIDTH; + db.height = (ispal ? V_RES_PAL : V_RES_NTSC) >> !ctrl.serrate; + output_height = V_RES_NTSC; + } if (config.vi.widescreen) { output_height = output_height * 3 / 4; } - screen_write(&fb, output_height); + screen_write(&fb, output_height, &db); } static bool vi_process_start_fast(void) @@ -656,6 +675,12 @@ static void vi_process_end_fast(void) fb.width = hres_raw; fb.height = vres_raw; fb.pitch = hres_raw; + + struct rdp_frame_buffer db; + db.pixels = prescale; + db.width = hres_raw; + db.height = vres_raw; + db.pitch = hres_raw; int32_t filtered_height = (vres << 1) * V_SYNC_NTSC / v_sync; int32_t output_height = hres_raw * filtered_height / hres; @@ -664,7 +689,7 @@ static void vi_process_end_fast(void) output_height = output_height * 3 / 4; } - screen_write(&fb, output_height); + screen_write(&fb, output_height, &db); } void rdp_update_vi(void) From 468c235dc46fe5af22ac0539b70f86f7fde20179 Mon Sep 17 00:00:00 2001 From: Aubrey Buchanan Date: Tue, 10 Jul 2018 10:00:03 -0600 Subject: [PATCH 05/55] Add zrgb value --- src/core/rdp/vi.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/core/rdp/vi.c b/src/core/rdp/vi.c index e2a0cfd..4910a15 100644 --- a/src/core/rdp/vi.c +++ b/src/core/rdp/vi.c @@ -617,7 +617,7 @@ static void vi_process_fast(uint32_t worker_id) uint32_t* dst = prescale + y * hres_raw; for (x = 0; x < hres_raw; x++) { - uint32_t r, g, b; + uint32_t r, g, b, zrgb; switch (config.vi.mode) { case VI_MODE_COLOR: @@ -627,6 +627,7 @@ static void vi_process_fast(uint32_t worker_id) r = RGBA16_R(pix); g = RGBA16_G(pix); b = RGBA16_B(pix); + zrgb = rdram_read_idx16((rdp_states[0].zb_address >> 1) + line + x) >> 8; break; } @@ -635,6 +636,7 @@ static void vi_process_fast(uint32_t worker_id) r = RGBA32_R(pix); g = RGBA32_G(pix); b = RGBA32_B(pix); + zrgb = rdram_read_idx16((rdp_states[0].zb_address >> 1) + line + x) >> 8; break; } @@ -645,6 +647,7 @@ static void vi_process_fast(uint32_t worker_id) case VI_MODE_DEPTH: { r = g = b = rdram_read_idx16((rdp_states[0].zb_address >> 1) + line + x) >> 8; + zrgb = rdram_read_idx16((rdp_states[0].zb_address >> 1) + line + x) >> 8; break; } @@ -654,6 +657,7 @@ static void vi_process_fast(uint32_t worker_id) uint16_t pix; rdram_read_pair16(&pix, &hval, (frame_buffer >> 1) + line + x); r = g = b = (((pix & 1) << 2) | hval) << 5; + zrgb = rdram_read_idx16((rdp_states[0].zb_address >> 1) + line + x) >> 8; break; } From fed52a7c441c95b0e6fe40266f7a76955d826d7f Mon Sep 17 00:00:00 2001 From: Aubrey Buchanan Date: Tue, 10 Jul 2018 10:03:48 -0600 Subject: [PATCH 06/55] Update rdp.h --- src/core/rdp.h | 1 + 1 file changed, 1 insertion(+) diff --git a/src/core/rdp.h b/src/core/rdp.h index ad173e3..0857015 100644 --- a/src/core/rdp.h +++ b/src/core/rdp.h @@ -68,6 +68,7 @@ struct rdp_config struct rdp_frame_buffer { uint32_t* pixels; + uint32_t* depth; uint32_t width; uint32_t height; uint32_t pitch; From 198f05f843cd047511f089c7b23ac1258af40645 Mon Sep 17 00:00:00 2001 From: Aubrey Buchanan Date: Tue, 10 Jul 2018 10:04:43 -0600 Subject: [PATCH 07/55] Update gl_screen.c --- src/plugin/common/gl_screen.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/plugin/common/gl_screen.c b/src/plugin/common/gl_screen.c index 7757ddf..6dcab39 100644 --- a/src/plugin/common/gl_screen.c +++ b/src/plugin/common/gl_screen.c @@ -181,13 +181,13 @@ void gl_screen_init(struct rdp_config* config) gl_check_errors(); } -bool gl_screen_write(struct rdp_frame_buffer* fb, int32_t output_height, struct rdp_frame_buffer* depth) +bool gl_screen_write(struct rdp_frame_buffer* fb, int32_t output_height) { bool buffer_size_changed = tex_width != fb->width || tex_height != fb->height; // write the depth to the depthbuffer glBindTexture(GL_TEXTURE_2D, depth_texture); - glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, depth->width, depth->height, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_INT, depth->pixels) + glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, fb->width, fb->height, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_INT, fb->depth) glBindTexture(GL_TEXTURE_2D, 0); // check if the framebuffer size has changed From 5007a7dd534cb2db9fe47333866e81f7fdba3db3 Mon Sep 17 00:00:00 2001 From: Aubrey Buchanan Date: Tue, 10 Jul 2018 10:05:14 -0600 Subject: [PATCH 08/55] Update screen.c --- src/plugin/zilmar/screen.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/plugin/zilmar/screen.c b/src/plugin/zilmar/screen.c index aff8b83..3f62355 100644 --- a/src/plugin/zilmar/screen.c +++ b/src/plugin/zilmar/screen.c @@ -148,9 +148,9 @@ void screen_init(struct rdp_config* config) gl_screen_init(config); } -void screen_write(struct rdp_frame_buffer* buffer, int32_t output_height, struct rdp_frame_buffer* depth_buffer) +void screen_write(struct rdp_frame_buffer* buffer, int32_t output_height) { - gl_screen_write(buffer, output_height, depth_buffer); + gl_screen_write(buffer, output_height); } void screen_read(struct rdp_frame_buffer* buffer, bool alpha) From c9ab467122da81a1c6a12f476e8c84f0984144df Mon Sep 17 00:00:00 2001 From: Aubrey Buchanan Date: Tue, 10 Jul 2018 10:10:11 -0600 Subject: [PATCH 09/55] Just a guess --- src/core/rdp/vi.c | 36 ++++++++---------------------------- 1 file changed, 8 insertions(+), 28 deletions(-) diff --git a/src/core/rdp/vi.c b/src/core/rdp/vi.c index 4910a15..ff58f1d 100644 --- a/src/core/rdp/vi.c +++ b/src/core/rdp/vi.c @@ -84,6 +84,7 @@ static uint32_t tvfadeoutstate[PRESCALE_HEIGHT]; // prescale buffer static uint32_t prescale[PRESCALE_WIDTH * PRESCALE_HEIGHT]; +static uint32_t prescale_depth[PRESCALE_WIDTH * PRESCALE_HEIGHT]; static uint32_t prescale_ptr; static int32_t linecount; @@ -115,6 +116,7 @@ static void vi_init(void) vi_restore_init(); memset(prescale, 0, sizeof(prescale)); + memset(prescale_depth, 0, sizeof(prescale_depth)) prevvicurrent = 0; emucontrolsvicurrent = -1; @@ -527,10 +529,7 @@ static void vi_process_end(void) struct rdp_frame_buffer fb; fb.pixels = prescale; fb.pitch = PRESCALE_WIDTH; - - struct rdp_frame_buffer db; - db.pixels = prescale; - db.pitch = PRESCALE_WIDTH; + fb.depth = prescale_depth; int32_t output_height; @@ -549,26 +548,11 @@ static void vi_process_end(void) output_height = V_RES_NTSC; } - if (config.vi.hide_overscan) { - // crop away overscan area from prescale - db.width = maxhpass - minhpass; - db.height = vres << ctrl.serrate; - output_height = (vres << 1) * V_SYNC_NTSC / v_sync; - int32_t x = h_start + minhpass; - int32_t y = (v_start + (emucontrolsvicurrent ? lowerfield : 0)) << ctrl.serrate; - db.pixels += x + y * db.pitch; - } else { - // use entire prescale buffer - db.width = PRESCALE_WIDTH; - db.height = (ispal ? V_RES_PAL : V_RES_NTSC) >> !ctrl.serrate; - output_height = V_RES_NTSC; - } - if (config.vi.widescreen) { output_height = output_height * 3 / 4; } - screen_write(&fb, output_height, &db); + screen_write(&fb, output_height); } static bool vi_process_start_fast(void) @@ -614,7 +598,7 @@ static void vi_process_fast(uint32_t worker_id) for (y = y_begin; y < y_end; y += y_inc) { int32_t x; int32_t line = y * vi_width_low; - uint32_t* dst = prescale + y * hres_raw; + uint32_t* d = dst = prescale + y * hres_raw; for (x = 0; x < hres_raw; x++) { uint32_t r, g, b, zrgb; @@ -668,6 +652,7 @@ static void vi_process_fast(uint32_t worker_id) gamma_filters(&r, &g, &b, ctrl, &rdp_states[worker_id].rand_vi); dst[x] = (b << 16) | (g << 8) | r; + d[x] = (zrgb << 16) | (zrgb << 8) | zrgb; } } } @@ -679,12 +664,7 @@ static void vi_process_end_fast(void) fb.width = hres_raw; fb.height = vres_raw; fb.pitch = hres_raw; - - struct rdp_frame_buffer db; - db.pixels = prescale; - db.width = hres_raw; - db.height = vres_raw; - db.pitch = hres_raw; + fb.depth = prescale_depth; int32_t filtered_height = (vres << 1) * V_SYNC_NTSC / v_sync; int32_t output_height = hres_raw * filtered_height / hres; @@ -693,7 +673,7 @@ static void vi_process_end_fast(void) output_height = output_height * 3 / 4; } - screen_write(&fb, output_height, &db); + screen_write(&fb, output_height); } void rdp_update_vi(void) From 0ff599f9f4cc3db9b131183d001a4a7f77d8b0c5 Mon Sep 17 00:00:00 2001 From: Aubrey Buchanan Date: Tue, 10 Jul 2018 10:17:37 -0600 Subject: [PATCH 10/55] Update vi.c --- src/core/rdp/vi.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/core/rdp/vi.c b/src/core/rdp/vi.c index ff58f1d..d549a56 100644 --- a/src/core/rdp/vi.c +++ b/src/core/rdp/vi.c @@ -630,8 +630,7 @@ static void vi_process_fast(uint32_t worker_id) break; case VI_MODE_DEPTH: { - r = g = b = rdram_read_idx16((rdp_states[0].zb_address >> 1) + line + x) >> 8; - zrgb = rdram_read_idx16((rdp_states[0].zb_address >> 1) + line + x) >> 8; + r = g = b = zrgb = rdram_read_idx16((rdp_states[0].zb_address >> 1) + line + x) >> 8; break; } From fe7ce7fa5a3a969e84e15dcaaa9ebbe71c2f8dab Mon Sep 17 00:00:00 2001 From: Aubrey Buchanan Date: Tue, 10 Jul 2018 10:19:02 -0600 Subject: [PATCH 11/55] Update vi.c --- src/core/rdp/vi.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/core/rdp/vi.c b/src/core/rdp/vi.c index d549a56..24d8057 100644 --- a/src/core/rdp/vi.c +++ b/src/core/rdp/vi.c @@ -598,8 +598,9 @@ static void vi_process_fast(uint32_t worker_id) for (y = y_begin; y < y_end; y += y_inc) { int32_t x; int32_t line = y * vi_width_low; - uint32_t* d = dst = prescale + y * hres_raw; - + uint32_t* dst = prescale + y * hres_raw; + uint32_t* d = prescale_depth + y * hres_raw; + for (x = 0; x < hres_raw; x++) { uint32_t r, g, b, zrgb; From fe7bbb54bbdaa4ae76a8e55aa34cdcd982064a74 Mon Sep 17 00:00:00 2001 From: Aubrey Buchanan Date: Tue, 10 Jul 2018 10:21:31 -0600 Subject: [PATCH 12/55] Update screen.h --- src/core/screen.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/screen.h b/src/core/screen.h index 531fffc..2f59aee 100644 --- a/src/core/screen.h +++ b/src/core/screen.h @@ -8,7 +8,7 @@ void screen_init(struct rdp_config* config); void screen_swap(bool blank); void screen_write(struct rdp_frame_buffer* fb, int32_t output_height); -void screen_read(struct rdp_frame_buffer* fb, bool rgb, struct rdp_frame_buffer* db); +void screen_read(struct rdp_frame_buffer* fb, bool rgb); void screen_set_fullscreen(bool fullscreen); bool screen_get_fullscreen(void); void screen_toggle_fullscreen(void); From 0eaf3d9e1f65dfdfad181e2792fb86edfc51147c Mon Sep 17 00:00:00 2001 From: Aubrey Buchanan Date: Tue, 10 Jul 2018 10:49:25 -0600 Subject: [PATCH 13/55] Update gl_screen.c --- src/plugin/common/gl_screen.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/plugin/common/gl_screen.c b/src/plugin/common/gl_screen.c index 6dcab39..21112e2 100644 --- a/src/plugin/common/gl_screen.c +++ b/src/plugin/common/gl_screen.c @@ -187,8 +187,10 @@ bool gl_screen_write(struct rdp_frame_buffer* fb, int32_t output_height) // write the depth to the depthbuffer glBindTexture(GL_TEXTURE_2D, depth_texture); - glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, fb->width, fb->height, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_INT, fb->depth) - glBindTexture(GL_TEXTURE_2D, 0); + glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, fb->width, fb->height, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_INT, fb->depth); + + // switch back to the default texture + glBindTexture(GL_TEXTURE_2D, texture); // check if the framebuffer size has changed if (buffer_size_changed) { @@ -199,8 +201,7 @@ bool gl_screen_write(struct rdp_frame_buffer* fb, int32_t output_height) glPixelStorei(GL_UNPACK_ROW_LENGTH, fb->pitch); // reallocate texture buffer on GPU - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, tex_width, - tex_height, 0, TEX_FORMAT, TEX_TYPE, fb->pixels); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, tex_width, tex_height, 0, TEX_FORMAT, TEX_TYPE, fb->pixels); msg_debug("%s: resized framebuffer texture: %dx%d", __FUNCTION__, tex_width, tex_height); } else { From 321d555f4e5ef68e8d121f7c844b14b8925feb2d Mon Sep 17 00:00:00 2001 From: Aubrey Buchanan Date: Tue, 10 Jul 2018 11:00:28 -0600 Subject: [PATCH 14/55] Update gl_screen.c --- src/plugin/common/gl_screen.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/plugin/common/gl_screen.c b/src/plugin/common/gl_screen.c index 21112e2..658d7da 100644 --- a/src/plugin/common/gl_screen.c +++ b/src/plugin/common/gl_screen.c @@ -187,6 +187,7 @@ bool gl_screen_write(struct rdp_frame_buffer* fb, int32_t output_height) // write the depth to the depthbuffer glBindTexture(GL_TEXTURE_2D, depth_texture); + msg_debug("%s: attempted to attribute depth: %d", __FUNCTION__, fb->depth); glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, fb->width, fb->height, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_INT, fb->depth); // switch back to the default texture From 4c9dc4275163aa8f84b4d376e2e6732ca7a5bb41 Mon Sep 17 00:00:00 2001 From: Aubrey Buchanan Date: Tue, 10 Jul 2018 11:31:20 -0600 Subject: [PATCH 15/55] Trying to write to gl_FragDepth --- src/plugin/common/gl_screen.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/plugin/common/gl_screen.c b/src/plugin/common/gl_screen.c index 658d7da..8b5112e 100644 --- a/src/plugin/common/gl_screen.c +++ b/src/plugin/common/gl_screen.c @@ -19,7 +19,7 @@ static GLuint program; static GLuint vao; static GLuint texture; -static GLuint depth_texture; +static GLuint depth_texture = ; static int32_t tex_width; static int32_t tex_height; @@ -138,12 +138,14 @@ void gl_screen_init(struct rdp_config* config) "in vec2 uv;\n" "layout(location = 0) out vec4 color;\n" "uniform sampler2D tex0;\n" + "uniform sampler2D tex1;\n" "void main(void) {\n" #ifdef GLES " color = texture(tex0, uv);\n" #else " color.bgra = texture(tex0, uv);\n" #endif + " gl_FragDepth = texture(tex1, uv);\n" "}\n"; // compile and link OpenGL program @@ -157,10 +159,12 @@ void gl_screen_init(struct rdp_config* config) glBindVertexArray(vao); // prepare depth texture + glActiveTexture(GL_TEXTURE1); glGenTextures(1, &depth_texture); glBindTexture(GL_TEXTURE_2D, depth_texture); // prepare texture + glActiveTexture(GL_TEXTURE0); glGenTextures(1, &texture); glBindTexture(GL_TEXTURE_2D, texture); @@ -186,11 +190,13 @@ bool gl_screen_write(struct rdp_frame_buffer* fb, int32_t output_height) bool buffer_size_changed = tex_width != fb->width || tex_height != fb->height; // write the depth to the depthbuffer + glActiveTexture(GL_TEXTURE1); glBindTexture(GL_TEXTURE_2D, depth_texture); msg_debug("%s: attempted to attribute depth: %d", __FUNCTION__, fb->depth); glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, fb->width, fb->height, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_INT, fb->depth); // switch back to the default texture + glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, texture); // check if the framebuffer size has changed From 08fd8a90015f04716bba483fbbf819273932bda9 Mon Sep 17 00:00:00 2001 From: Aubrey Buchanan Date: Tue, 10 Jul 2018 11:32:05 -0600 Subject: [PATCH 16/55] Update gl_screen.c --- src/plugin/common/gl_screen.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugin/common/gl_screen.c b/src/plugin/common/gl_screen.c index 8b5112e..f2c3c73 100644 --- a/src/plugin/common/gl_screen.c +++ b/src/plugin/common/gl_screen.c @@ -19,7 +19,7 @@ static GLuint program; static GLuint vao; static GLuint texture; -static GLuint depth_texture = ; +static GLuint depth_texture; static int32_t tex_width; static int32_t tex_height; From 46a34ed8592d2676d0eb25847bc8a57527afdecd Mon Sep 17 00:00:00 2001 From: Aubrey Buchanan Date: Tue, 10 Jul 2018 12:01:48 -0600 Subject: [PATCH 17/55] Update gl_screen.c --- src/plugin/common/gl_screen.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/plugin/common/gl_screen.c b/src/plugin/common/gl_screen.c index f2c3c73..0241546 100644 --- a/src/plugin/common/gl_screen.c +++ b/src/plugin/common/gl_screen.c @@ -190,12 +190,14 @@ bool gl_screen_write(struct rdp_frame_buffer* fb, int32_t output_height) bool buffer_size_changed = tex_width != fb->width || tex_height != fb->height; // write the depth to the depthbuffer + glEnable(GL_DEPTH_TEST); glActiveTexture(GL_TEXTURE1); glBindTexture(GL_TEXTURE_2D, depth_texture); msg_debug("%s: attempted to attribute depth: %d", __FUNCTION__, fb->depth); glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, fb->width, fb->height, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_INT, fb->depth); - + // switch back to the default texture + glDisable(GL_DEPTH_TEST); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, texture); From c889de5e8fb61bf4dade6b9c42b08e8bb4042c15 Mon Sep 17 00:00:00 2001 From: Aubrey Buchanan Date: Tue, 10 Jul 2018 12:34:49 -0600 Subject: [PATCH 18/55] Update gl_screen.c --- src/plugin/common/gl_screen.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugin/common/gl_screen.c b/src/plugin/common/gl_screen.c index 0241546..2b3573d 100644 --- a/src/plugin/common/gl_screen.c +++ b/src/plugin/common/gl_screen.c @@ -194,7 +194,7 @@ bool gl_screen_write(struct rdp_frame_buffer* fb, int32_t output_height) glActiveTexture(GL_TEXTURE1); glBindTexture(GL_TEXTURE_2D, depth_texture); msg_debug("%s: attempted to attribute depth: %d", __FUNCTION__, fb->depth); - glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, fb->width, fb->height, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_INT, fb->depth); + glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, fb->width, fb->height, 0, GL_DEPTH_COMPONENT, TEX_TYPE, fb->depth); // switch back to the default texture glDisable(GL_DEPTH_TEST); From d7d48f0e35a08ecce5c3a719003bebf107dc2d33 Mon Sep 17 00:00:00 2001 From: Aubrey Buchanan Date: Tue, 10 Jul 2018 12:42:53 -0600 Subject: [PATCH 19/55] syntax error fix --- src/core/rdp/vi.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/rdp/vi.c b/src/core/rdp/vi.c index 24d8057..83a638f 100644 --- a/src/core/rdp/vi.c +++ b/src/core/rdp/vi.c @@ -116,7 +116,7 @@ static void vi_init(void) vi_restore_init(); memset(prescale, 0, sizeof(prescale)); - memset(prescale_depth, 0, sizeof(prescale_depth)) + memset(prescale_depth, 0, sizeof(prescale_depth)); prevvicurrent = 0; emucontrolsvicurrent = -1; From 06e00566a10cbd72455fb5122ee67dafa64425ea Mon Sep 17 00:00:00 2001 From: Aubrey Buchanan Date: Tue, 10 Jul 2018 13:12:30 -0600 Subject: [PATCH 20/55] Messing around with opengl --- src/plugin/common/gl_screen.c | 31 ++++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/src/plugin/common/gl_screen.c b/src/plugin/common/gl_screen.c index 2b3573d..85cac19 100644 --- a/src/plugin/common/gl_screen.c +++ b/src/plugin/common/gl_screen.c @@ -145,7 +145,7 @@ void gl_screen_init(struct rdp_config* config) #else " color.bgra = texture(tex0, uv);\n" #endif - " gl_FragDepth = texture(tex1, uv);\n" + " gl_FragDepth = texture(tex1, uv).r;\n" "}\n"; // compile and link OpenGL program @@ -188,24 +188,28 @@ void gl_screen_init(struct rdp_config* config) bool gl_screen_write(struct rdp_frame_buffer* fb, int32_t output_height) { bool buffer_size_changed = tex_width != fb->width || tex_height != fb->height; - - // write the depth to the depthbuffer - glEnable(GL_DEPTH_TEST); - glActiveTexture(GL_TEXTURE1); - glBindTexture(GL_TEXTURE_2D, depth_texture); - msg_debug("%s: attempted to attribute depth: %d", __FUNCTION__, fb->depth); - glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, fb->width, fb->height, 0, GL_DEPTH_COMPONENT, TEX_TYPE, fb->depth); - - // switch back to the default texture - glDisable(GL_DEPTH_TEST); - glActiveTexture(GL_TEXTURE0); - glBindTexture(GL_TEXTURE_2D, texture); // check if the framebuffer size has changed if (buffer_size_changed) { tex_width = fb->width; tex_height = fb->height; + // write the depth to the depthbuffer + glEnable(GL_DEPTH_TEST); + glActiveTexture(GL_TEXTURE1); + glBindTexture(GL_TEXTURE_2D, depth_texture); + msg_debug("%s: attempted to attribute depth: %d", __FUNCTION__, fb->depth); + + // set pitch for all unpacking operations + glPixelStorei(GL_UNPACK_ROW_LENGTH, fb->pitch); + + glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, fb->width, fb->height, 0, GL_DEPTH_COMPONENT, TEX_TYPE, fb->depth); + + // switch back to the default texture + glDisable(GL_DEPTH_TEST); + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, texture); + // set pitch for all unpacking operations glPixelStorei(GL_UNPACK_ROW_LENGTH, fb->pitch); @@ -214,6 +218,7 @@ bool gl_screen_write(struct rdp_frame_buffer* fb, int32_t output_height) msg_debug("%s: resized framebuffer texture: %dx%d", __FUNCTION__, tex_width, tex_height); } else { + // copy local buffer to GPU texture buffer glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, tex_width, tex_height, TEX_FORMAT, TEX_TYPE, fb->pixels); From 72c8e135c22dfa35268204421c62d21eddec13be Mon Sep 17 00:00:00 2001 From: Aubrey Buchanan Date: Tue, 10 Jul 2018 13:15:33 -0600 Subject: [PATCH 21/55] Update gl_screen.c --- src/plugin/common/gl_screen.c | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/src/plugin/common/gl_screen.c b/src/plugin/common/gl_screen.c index 85cac19..29d58fb 100644 --- a/src/plugin/common/gl_screen.c +++ b/src/plugin/common/gl_screen.c @@ -194,22 +194,25 @@ bool gl_screen_write(struct rdp_frame_buffer* fb, int32_t output_height) tex_width = fb->width; tex_height = fb->height; - // write the depth to the depthbuffer - glEnable(GL_DEPTH_TEST); - glActiveTexture(GL_TEXTURE1); - glBindTexture(GL_TEXTURE_2D, depth_texture); - msg_debug("%s: attempted to attribute depth: %d", __FUNCTION__, fb->depth); + // write the depth to the depthbuffer + glDepthMask(GL_TRUE); + glEnable(GL_DEPTH_TEST); + glActiveTexture(GL_TEXTURE1); + glBindTexture(GL_TEXTURE_2D, depth_texture); + msg_debug("%s: attempted to attribute depth: %d", __FUNCTION__, fb->depth); - // set pitch for all unpacking operations - glPixelStorei(GL_UNPACK_ROW_LENGTH, fb->pitch); + // set pitch for all unpacking operations + glPixelStorei(GL_UNPACK_ROW_LENGTH, fb->pitch); - glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, fb->width, fb->height, 0, GL_DEPTH_COMPONENT, TEX_TYPE, fb->depth); + glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, fb->width, fb->height, 0, GL_DEPTH_COMPONENT, TEX_TYPE, fb->depth); - // switch back to the default texture - glDisable(GL_DEPTH_TEST); - glActiveTexture(GL_TEXTURE0); - glBindTexture(GL_TEXTURE_2D, texture); + // switch back to the default texture + glDisable(GL_DEPTH_TEST); + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, texture); + glDepthMask(GL_FALSE); + // set pitch for all unpacking operations glPixelStorei(GL_UNPACK_ROW_LENGTH, fb->pitch); From 384a513bcd7877b82e1563bfe81d918ec0450c75 Mon Sep 17 00:00:00 2001 From: Aubrey Buchanan Date: Tue, 10 Jul 2018 13:53:23 -0600 Subject: [PATCH 22/55] Trying to unpack the depth correctly --- src/plugin/common/gl_screen.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/plugin/common/gl_screen.c b/src/plugin/common/gl_screen.c index 29d58fb..6b0b224 100644 --- a/src/plugin/common/gl_screen.c +++ b/src/plugin/common/gl_screen.c @@ -139,13 +139,18 @@ void gl_screen_init(struct rdp_config* config) "layout(location = 0) out vec4 color;\n" "uniform sampler2D tex0;\n" "uniform sampler2D tex1;\n" + "float UnpackDepth(in vec3 pack)\n" + "{\n" + "float depth = dot( pack, 1.0 / vec3(1.0, 256.0, 256.0*256.0) );\n" + "return depth * (256.0*256.0*256.0) / (256.0*256.0*256.0 - 1.0);\n" + "}\n" "void main(void) {\n" #ifdef GLES " color = texture(tex0, uv);\n" #else " color.bgra = texture(tex0, uv);\n" #endif - " gl_FragDepth = texture(tex1, uv).r;\n" + " gl_FragDepth = UnpackDepth(texture(tex1, uv).rgb);\n" "}\n"; // compile and link OpenGL program @@ -203,7 +208,7 @@ bool gl_screen_write(struct rdp_frame_buffer* fb, int32_t output_height) // set pitch for all unpacking operations glPixelStorei(GL_UNPACK_ROW_LENGTH, fb->pitch); - + glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, fb->width, fb->height, 0, GL_DEPTH_COMPONENT, TEX_TYPE, fb->depth); // switch back to the default texture From f8902b29353bcf46f0b1f2871ae3b17ecb6597ba Mon Sep 17 00:00:00 2001 From: Aubrey Buchanan Date: Tue, 10 Jul 2018 14:18:51 -0600 Subject: [PATCH 23/55] Update gl_screen.c --- src/plugin/common/gl_screen.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/plugin/common/gl_screen.c b/src/plugin/common/gl_screen.c index 6b0b224..4ba6773 100644 --- a/src/plugin/common/gl_screen.c +++ b/src/plugin/common/gl_screen.c @@ -273,7 +273,9 @@ void gl_screen_render(int32_t win_width, int32_t win_height, int32_t win_x, int3 glViewport(win_x, win_y, win_width, win_height); // draw fullscreen triangle + glDepthMask(GL_FALSE); glDrawArrays(GL_TRIANGLES, 0, 3); + glDepthMask(GL_TRUE); // check if there was an error when using any of the commands above gl_check_errors(); From 1091c296296433435d0bbdafb47614841dea37ef Mon Sep 17 00:00:00 2001 From: Aubrey Buchanan Date: Tue, 10 Jul 2018 15:00:22 -0600 Subject: [PATCH 24/55] Update gl_screen.c --- src/plugin/common/gl_screen.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugin/common/gl_screen.c b/src/plugin/common/gl_screen.c index 4ba6773..9467289 100644 --- a/src/plugin/common/gl_screen.c +++ b/src/plugin/common/gl_screen.c @@ -204,7 +204,7 @@ bool gl_screen_write(struct rdp_frame_buffer* fb, int32_t output_height) glEnable(GL_DEPTH_TEST); glActiveTexture(GL_TEXTURE1); glBindTexture(GL_TEXTURE_2D, depth_texture); - msg_debug("%s: attempted to attribute depth: %d", __FUNCTION__, fb->depth); + // set pitch for all unpacking operations glPixelStorei(GL_UNPACK_ROW_LENGTH, fb->pitch); From 42abb7c9e791f348d990faa2c7a6bdd3cc190363 Mon Sep 17 00:00:00 2001 From: Aubrey Buchanan Date: Tue, 10 Jul 2018 15:04:38 -0600 Subject: [PATCH 25/55] Another guess --- src/plugin/common/gl_screen.c | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/plugin/common/gl_screen.c b/src/plugin/common/gl_screen.c index 4ba6773..b0b8f9d 100644 --- a/src/plugin/common/gl_screen.c +++ b/src/plugin/common/gl_screen.c @@ -204,7 +204,6 @@ bool gl_screen_write(struct rdp_frame_buffer* fb, int32_t output_height) glEnable(GL_DEPTH_TEST); glActiveTexture(GL_TEXTURE1); glBindTexture(GL_TEXTURE_2D, depth_texture); - msg_debug("%s: attempted to attribute depth: %d", __FUNCTION__, fb->depth); // set pitch for all unpacking operations glPixelStorei(GL_UNPACK_ROW_LENGTH, fb->pitch); @@ -227,9 +226,25 @@ bool gl_screen_write(struct rdp_frame_buffer* fb, int32_t output_height) msg_debug("%s: resized framebuffer texture: %dx%d", __FUNCTION__, tex_width, tex_height); } else { + + // write the depth to the depthbuffer + glDepthMask(GL_TRUE); + glEnable(GL_DEPTH_TEST); + glActiveTexture(GL_TEXTURE1); + glBindTexture(GL_TEXTURE_2D, depth_texture); + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, tex_width, tex_height, + GL_DEPTH_COMPONENT, TEX_TYPE, fb->depth); + // switch back to the default texture + glDisable(GL_DEPTH_TEST); + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, texture); + + // copy local buffer to GPU texture buffer glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, tex_width, tex_height, TEX_FORMAT, TEX_TYPE, fb->pixels); + + } // update output size From 899d8c293b2e688c3fc7f6e1502edfe09f6cebaf Mon Sep 17 00:00:00 2001 From: Aubrey Buchanan Date: Tue, 10 Jul 2018 19:06:06 -0600 Subject: [PATCH 26/55] Checking in because I think I'm getting somewhere... --- src/plugin/common/gl_screen.c | 74 +++++++++++++++-------------------- 1 file changed, 31 insertions(+), 43 deletions(-) diff --git a/src/plugin/common/gl_screen.c b/src/plugin/common/gl_screen.c index bae3342..b952d0f 100644 --- a/src/plugin/common/gl_screen.c +++ b/src/plugin/common/gl_screen.c @@ -139,18 +139,20 @@ void gl_screen_init(struct rdp_config* config) "layout(location = 0) out vec4 color;\n" "uniform sampler2D tex0;\n" "uniform sampler2D tex1;\n" - "float UnpackDepth(in vec3 pack)\n" - "{\n" - "float depth = dot( pack, 1.0 / vec3(1.0, 256.0, 256.0*256.0) );\n" - "return depth * (256.0*256.0*256.0) / (256.0*256.0*256.0 - 1.0);\n" - "}\n" + + "float unpack8BitVec3IntoFloat(vec3 v, float min, float max) {\n" + "float zeroTo24Bit = v.x + v.y * 256.0 + v.z * 256.0 * 256.0; \n" + "float zeroToOne = zeroTo24Bit / 256.0 / 256.0 / 256.0; \n" + "return zeroToOne * (max - min) + min; \n" + "}\n" + "void main(void) {\n" #ifdef GLES " color = texture(tex0, uv);\n" #else " color.bgra = texture(tex0, uv);\n" #endif - " gl_FragDepth = UnpackDepth(texture(tex1, uv).rgb);\n" + " gl_FragDepth = unpack8BitVec3IntoFloat(texture(tex1, uv).rgb, 0.0, 1.0);\n" "}\n"; // compile and link OpenGL program @@ -159,6 +161,10 @@ void gl_screen_init(struct rdp_config* config) program = gl_shader_link(vert, frag); glUseProgram(program); + //glEnable(GL_DEPTH_CLAMP); + + //glDepthFunc(GL_ALWAYS); + // prepare dummy VAO glGenVertexArrays(1, &vao); glBindVertexArray(vao); @@ -194,32 +200,28 @@ bool gl_screen_write(struct rdp_frame_buffer* fb, int32_t output_height) { bool buffer_size_changed = tex_width != fb->width || tex_height != fb->height; - // check if the framebuffer size has changed - if (buffer_size_changed) { - tex_width = fb->width; - tex_height = fb->height; - + //glClear(GL_DEPTH_BUFFER_BIT); + //glDepthMask(GL_TRUE); + // set pitch for all unpacking operations + glPixelStorei(GL_UNPACK_ROW_LENGTH, fb->pitch); - // write the depth to the depthbuffer - glDepthMask(GL_TRUE); - glEnable(GL_DEPTH_TEST); + //// write the depth to the depthbuffer glActiveTexture(GL_TEXTURE1); glBindTexture(GL_TEXTURE_2D, depth_texture); - // set pitch for all unpacking operations - glPixelStorei(GL_UNPACK_ROW_LENGTH, fb->pitch); - - glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, fb->width, fb->height, 0, GL_DEPTH_COMPONENT, TEX_TYPE, fb->depth); + //glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, tex_width, tex_height, 0, TEX_FORMAT, TEX_TYPE, fb->depth); + + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, tex_width, tex_height, TEX_FORMAT, TEX_TYPE, fb->depth); // switch back to the default texture - glDisable(GL_DEPTH_TEST); + //glDepthMask(GL_FALSE); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, texture); - glDepthMask(GL_FALSE); - - // set pitch for all unpacking operations - glPixelStorei(GL_UNPACK_ROW_LENGTH, fb->pitch); + // check if the framebuffer size has changed + if (buffer_size_changed) { + tex_width = fb->width; + tex_height = fb->height; // reallocate texture buffer on GPU glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, tex_width, tex_height, 0, TEX_FORMAT, TEX_TYPE, fb->pixels); @@ -227,25 +229,9 @@ bool gl_screen_write(struct rdp_frame_buffer* fb, int32_t output_height) msg_debug("%s: resized framebuffer texture: %dx%d", __FUNCTION__, tex_width, tex_height); } else { - - // write the depth to the depthbuffer - glDepthMask(GL_TRUE); - glEnable(GL_DEPTH_TEST); - glActiveTexture(GL_TEXTURE1); - glBindTexture(GL_TEXTURE_2D, depth_texture); - glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, tex_width, tex_height, - GL_DEPTH_COMPONENT, TEX_TYPE, fb->depth); - // switch back to the default texture - glDisable(GL_DEPTH_TEST); - glActiveTexture(GL_TEXTURE0); - glBindTexture(GL_TEXTURE_2D, texture); - - // copy local buffer to GPU texture buffer glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, tex_width, tex_height, - TEX_FORMAT, TEX_TYPE, fb->pixels); - - + TEX_FORMAT, TEX_TYPE, fb->pixels); } // update output size @@ -288,10 +274,12 @@ void gl_screen_render(int32_t win_width, int32_t win_height, int32_t win_x, int3 // configure viewport glViewport(win_x, win_y, win_width, win_height); - // draw fullscreen triangle - glDepthMask(GL_FALSE); + glEnable(GL_DEPTH_TEST); + + // draw fullscreen triangle glDrawArrays(GL_TRIANGLES, 0, 3); - glDepthMask(GL_TRUE); + + glDisable(GL_DEPTH_TEST); // check if there was an error when using any of the commands above gl_check_errors(); From 73b25bfa41e2c8a56b8a75e6d304a0d5f9bc9e90 Mon Sep 17 00:00:00 2001 From: Aubrey Buchanan Date: Tue, 10 Jul 2018 19:16:14 -0600 Subject: [PATCH 27/55] getting REALLY close now --- src/plugin/common/gl_screen.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugin/common/gl_screen.c b/src/plugin/common/gl_screen.c index b952d0f..f9f86d2 100644 --- a/src/plugin/common/gl_screen.c +++ b/src/plugin/common/gl_screen.c @@ -152,7 +152,7 @@ void gl_screen_init(struct rdp_config* config) #else " color.bgra = texture(tex0, uv);\n" #endif - " gl_FragDepth = unpack8BitVec3IntoFloat(texture(tex1, uv).rgb, 0.0, 1.0);\n" + " gl_FragDepth = texture(tex1, uv).z;\n" "}\n"; // compile and link OpenGL program From d3f147f2053569bf87cf369df8fa634ce35de56f Mon Sep 17 00:00:00 2001 From: Aubrey Buchanan Date: Tue, 10 Jul 2018 20:40:36 -0600 Subject: [PATCH 28/55] updates, confusion --- src/core/rdp/vi.c | 12 +++--- src/plugin/common/gl_screen.c | 72 ++++++++++++++++++++++++----------- 2 files changed, 56 insertions(+), 28 deletions(-) diff --git a/src/core/rdp/vi.c b/src/core/rdp/vi.c index 83a638f..a44534b 100644 --- a/src/core/rdp/vi.c +++ b/src/core/rdp/vi.c @@ -602,17 +602,17 @@ static void vi_process_fast(uint32_t worker_id) uint32_t* d = prescale_depth + y * hres_raw; for (x = 0; x < hres_raw; x++) { - uint32_t r, g, b, zrgb; + uint32_t r, g, b, zr, zg, zb; switch (config.vi.mode) { case VI_MODE_COLOR: + zr = zg = zb = rdram_read_idx16((rdp_states[0].zb_address >> 1) + line + x) >> 8; switch (ctrl.type) { case VI_TYPE_RGBA5551: { uint16_t pix = rdram_read_idx16((frame_buffer >> 1) + line + x); r = RGBA16_R(pix); g = RGBA16_G(pix); b = RGBA16_B(pix); - zrgb = rdram_read_idx16((rdp_states[0].zb_address >> 1) + line + x) >> 8; break; } @@ -621,7 +621,6 @@ static void vi_process_fast(uint32_t worker_id) r = RGBA32_R(pix); g = RGBA32_G(pix); b = RGBA32_B(pix); - zrgb = rdram_read_idx16((rdp_states[0].zb_address >> 1) + line + x) >> 8; break; } @@ -631,7 +630,7 @@ static void vi_process_fast(uint32_t worker_id) break; case VI_MODE_DEPTH: { - r = g = b = zrgb = rdram_read_idx16((rdp_states[0].zb_address >> 1) + line + x) >> 8; + r = g = b = zr = zg = zb = rdram_read_idx16((rdp_states[0].zb_address >> 1) + line + x) >> 8; break; } @@ -641,7 +640,7 @@ static void vi_process_fast(uint32_t worker_id) uint16_t pix; rdram_read_pair16(&pix, &hval, (frame_buffer >> 1) + line + x); r = g = b = (((pix & 1) << 2) | hval) << 5; - zrgb = rdram_read_idx16((rdp_states[0].zb_address >> 1) + line + x) >> 8; + zr = zg = zb = rdram_read_idx16((rdp_states[0].zb_address >> 1) + line + x) >> 8; break; } @@ -650,9 +649,10 @@ static void vi_process_fast(uint32_t worker_id) } gamma_filters(&r, &g, &b, ctrl, &rdp_states[worker_id].rand_vi); + gamma_filters(&zr, &zg, &zb, ctrl, &rdp_states[worker_id].rand_vi); dst[x] = (b << 16) | (g << 8) | r; - d[x] = (zrgb << 16) | (zrgb << 8) | zrgb; + d[x] = (zb << 16) | (zg << 8) | zr; } } } diff --git a/src/plugin/common/gl_screen.c b/src/plugin/common/gl_screen.c index f9f86d2..9645d24 100644 --- a/src/plugin/common/gl_screen.c +++ b/src/plugin/common/gl_screen.c @@ -146,12 +146,19 @@ void gl_screen_init(struct rdp_config* config) "return zeroToOne * (max - min) + min; \n" "}\n" + "float color2float(in vec3 c) {\n" + "c *= 255.;\n" + "c = floor(c); // without this value could be shifted for some intervals\n" + "return c.r*256.*256. + c.g*256. + c.b - 8388608.;\n" + "}\n" + "void main(void) {\n" #ifdef GLES " color = texture(tex0, uv);\n" #else " color.bgra = texture(tex0, uv);\n" #endif + " //gl_FragDepth = color2float(texture(tex1, uv).xyz);\n" " gl_FragDepth = texture(tex1, uv).z;\n" "}\n"; @@ -161,10 +168,6 @@ void gl_screen_init(struct rdp_config* config) program = gl_shader_link(vert, frag); glUseProgram(program); - //glEnable(GL_DEPTH_CLAMP); - - //glDepthFunc(GL_ALWAYS); - // prepare dummy VAO glGenVertexArrays(1, &vao); glBindVertexArray(vao); @@ -200,35 +203,47 @@ bool gl_screen_write(struct rdp_frame_buffer* fb, int32_t output_height) { bool buffer_size_changed = tex_width != fb->width || tex_height != fb->height; - //glClear(GL_DEPTH_BUFFER_BIT); - //glDepthMask(GL_TRUE); + glDepthMask(GL_TRUE); + // set pitch for all unpacking operations glPixelStorei(GL_UNPACK_ROW_LENGTH, fb->pitch); - //// write the depth to the depthbuffer - glActiveTexture(GL_TEXTURE1); - glBindTexture(GL_TEXTURE_2D, depth_texture); - - //glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, tex_width, tex_height, 0, TEX_FORMAT, TEX_TYPE, fb->depth); - - glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, tex_width, tex_height, TEX_FORMAT, TEX_TYPE, fb->depth); - - // switch back to the default texture - //glDepthMask(GL_FALSE); - glActiveTexture(GL_TEXTURE0); - glBindTexture(GL_TEXTURE_2D, texture); - // check if the framebuffer size has changed if (buffer_size_changed) { tex_width = fb->width; tex_height = fb->height; + + //// write the depth to the depthbuffer + glActiveTexture(GL_TEXTURE1); + glBindTexture(GL_TEXTURE_2D, depth_texture); + + glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT24, tex_width, tex_height, 0, GL_DEPTH_COMPONENT, TEX_TYPE, fb->depth); + + // switch back to the default texture + glDepthMask(GL_FALSE); + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, texture); + // reallocate texture buffer on GPU glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, tex_width, tex_height, 0, TEX_FORMAT, TEX_TYPE, fb->pixels); msg_debug("%s: resized framebuffer texture: %dx%d", __FUNCTION__, tex_width, tex_height); } else { + //// write the depth to the depthbuffer + glActiveTexture(GL_TEXTURE1); + glBindTexture(GL_TEXTURE_2D, depth_texture); + + + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, tex_width, tex_height, GL_DEPTH_COMPONENT, TEX_TYPE, fb->depth); + + // switch back to the default texture + glDepthMask(GL_FALSE); + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, texture); + + // copy local buffer to GPU texture buffer glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, tex_width, tex_height, TEX_FORMAT, TEX_TYPE, fb->pixels); @@ -237,6 +252,7 @@ bool gl_screen_write(struct rdp_frame_buffer* fb, int32_t output_height) // update output size tex_display_height = output_height; + glDepthMask(GL_TRUE); return buffer_size_changed; } @@ -274,12 +290,24 @@ void gl_screen_render(int32_t win_width, int32_t win_height, int32_t win_x, int3 // configure viewport glViewport(win_x, win_y, win_width, win_height); - glEnable(GL_DEPTH_TEST); + glDepthMask(GL_FALSE); + glDisable(GL_DEPTH_TEST); - // draw fullscreen triangle + // draw fullscreen triangle to the frame buffer? + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, texture); + glBindVertexArray(0); glDrawArrays(GL_TRIANGLES, 0, 3); - glDisable(GL_DEPTH_TEST); + + glDepthMask(GL_TRUE); + glEnable(GL_DEPTH_TEST); + + //// draw to the depth to the depthbuffer + glActiveTexture(GL_TEXTURE1); + glBindTexture(GL_TEXTURE_2D, depth_texture); + glBindVertexArray(vao); + glDrawArrays(GL_TRIANGLES, 0, 3); // check if there was an error when using any of the commands above gl_check_errors(); From 47ac548cd4d7ae5af67906bdab3a38a9a0a9daf6 Mon Sep 17 00:00:00 2001 From: Aubrey Buchanan Date: Tue, 10 Jul 2018 21:02:02 -0600 Subject: [PATCH 29/55] Going to leave off here for today I don't think this works the way I thought it did. --- src/core/rdp/vi.c | 2 +- src/plugin/common/gl_screen.c | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/core/rdp/vi.c b/src/core/rdp/vi.c index a44534b..f697a7b 100644 --- a/src/core/rdp/vi.c +++ b/src/core/rdp/vi.c @@ -606,7 +606,7 @@ static void vi_process_fast(uint32_t worker_id) switch (config.vi.mode) { case VI_MODE_COLOR: - zr = zg = zb = rdram_read_idx16((rdp_states[0].zb_address >> 1) + line + x) >> 8; + zr = zg = zb = rdram_read_idx16((rdp_states[worker_id].zb_address >> 1) + line + x) >> 8; switch (ctrl.type) { case VI_TYPE_RGBA5551: { uint16_t pix = rdram_read_idx16((frame_buffer >> 1) + line + x); diff --git a/src/plugin/common/gl_screen.c b/src/plugin/common/gl_screen.c index 9645d24..aaecc84 100644 --- a/src/plugin/common/gl_screen.c +++ b/src/plugin/common/gl_screen.c @@ -251,8 +251,8 @@ bool gl_screen_write(struct rdp_frame_buffer* fb, int32_t output_height) // update output size tex_display_height = output_height; - glDepthMask(GL_TRUE); + return buffer_size_changed; } @@ -309,6 +309,8 @@ void gl_screen_render(int32_t win_width, int32_t win_height, int32_t win_x, int3 glBindVertexArray(vao); glDrawArrays(GL_TRIANGLES, 0, 3); + + // check if there was an error when using any of the commands above gl_check_errors(); } From 9e8509e35ee6d9c01f83f00471983d1a02d7293a Mon Sep 17 00:00:00 2001 From: Aubrey Buchanan Date: Wed, 11 Jul 2018 03:05:52 -0600 Subject: [PATCH 30/55] Trying to get a grip on opengl flow --- src/plugin/common/gl_screen.c | 109 +++++++++++++++++++++------------- 1 file changed, 68 insertions(+), 41 deletions(-) diff --git a/src/plugin/common/gl_screen.c b/src/plugin/common/gl_screen.c index aaecc84..378aeb8 100644 --- a/src/plugin/common/gl_screen.c +++ b/src/plugin/common/gl_screen.c @@ -18,8 +18,8 @@ static GLuint program; static GLuint vao; -static GLuint texture; -static GLuint depth_texture; +static GLuint texture = 1; +static GLuint depth_texture = 2; static int32_t tex_width; static int32_t tex_height; @@ -168,6 +168,9 @@ void gl_screen_init(struct rdp_config* config) program = gl_shader_link(vert, frag); glUseProgram(program); + // enable depth + glEnable(GL_DEPTH_TEST); + // prepare dummy VAO glGenVertexArrays(1, &vao); glBindVertexArray(vao); @@ -203,55 +206,65 @@ bool gl_screen_write(struct rdp_frame_buffer* fb, int32_t output_height) { bool buffer_size_changed = tex_width != fb->width || tex_height != fb->height; - glDepthMask(GL_TRUE); - - // set pitch for all unpacking operations - glPixelStorei(GL_UNPACK_ROW_LENGTH, fb->pitch); - // check if the framebuffer size has changed if (buffer_size_changed) { tex_width = fb->width; tex_height = fb->height; - - //// write the depth to the depthbuffer - glActiveTexture(GL_TEXTURE1); - glBindTexture(GL_TEXTURE_2D, depth_texture); - - glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT24, tex_width, tex_height, 0, GL_DEPTH_COMPONENT, TEX_TYPE, fb->depth); - // switch back to the default texture - glDepthMask(GL_FALSE); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, texture); + // disable depth + glDepthMask(GL_FALSE); + // set pitch for all unpacking operations + glPixelStorei(GL_UNPACK_ROW_LENGTH, fb->pitch); // reallocate texture buffer on GPU glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, tex_width, tex_height, 0, TEX_FORMAT, TEX_TYPE, fb->pixels); - msg_debug("%s: resized framebuffer texture: %dx%d", __FUNCTION__, tex_width, tex_height); - } else { + glBindTexture(GL_TEXTURE_2D, 0); //// write the depth to the depthbuffer glActiveTexture(GL_TEXTURE1); glBindTexture(GL_TEXTURE_2D, depth_texture); - + // enable depth + glDepthMask(GL_TRUE); + // set pitch for all unpacking operations + glPixelStorei(GL_UNPACK_ROW_LENGTH, fb->pitch); - glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, tex_width, tex_height, GL_DEPTH_COMPONENT, TEX_TYPE, fb->depth); + glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT24, tex_width, tex_height, 0, GL_DEPTH_COMPONENT, TEX_TYPE, fb->depth); + glBindTexture(GL_TEXTURE_2D, 0); + + msg_debug("%s: resized framebuffer texture: %dx%d", __FUNCTION__, tex_width, tex_height); + } else { // switch back to the default texture - glDepthMask(GL_FALSE); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, texture); - + // disable depth + glDepthMask(GL_FALSE); // copy local buffer to GPU texture buffer - glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, tex_width, tex_height, - TEX_FORMAT, TEX_TYPE, fb->pixels); + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, tex_width, tex_height, TEX_FORMAT, TEX_TYPE, fb->pixels); + + /* + * Somehow this breaks rendering anything whatsoever? + */ + //glBindTexture(GL_TEXTURE_2D, 0); + + //// write the depth to the depthbuffer + glActiveTexture(GL_TEXTURE1); + glBindTexture(GL_TEXTURE_2D, depth_texture); + // enable depth + glDepthMask(GL_TRUE); + + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, tex_width, tex_height, GL_DEPTH_COMPONENT, TEX_TYPE, fb->depth); + + glBindTexture(GL_TEXTURE_2D, 0); } // update output size tex_display_height = output_height; - glDepthMask(GL_TRUE); return buffer_size_changed; } @@ -287,29 +300,31 @@ void gl_screen_render(int32_t win_width, int32_t win_height, int32_t win_x, int3 win_height = h_max; } - // configure viewport - glViewport(win_x, win_y, win_width, win_height); - - glDepthMask(GL_FALSE); - glDisable(GL_DEPTH_TEST); + - // draw fullscreen triangle to the frame buffer? - glActiveTexture(GL_TEXTURE0); - glBindTexture(GL_TEXTURE_2D, texture); - glBindVertexArray(0); - glDrawArrays(GL_TRIANGLES, 0, 3); - - - glDepthMask(GL_TRUE); - glEnable(GL_DEPTH_TEST); - - //// draw to the depth to the depthbuffer + //// write the depth to the depthbuffer glActiveTexture(GL_TEXTURE1); glBindTexture(GL_TEXTURE_2D, depth_texture); - glBindVertexArray(vao); + // enable depth + glDepthMask(GL_TRUE); + // configure viewport + glViewport(win_x, win_y, win_width, win_height); + //// draw to the depth to the depthbuffer glDrawArrays(GL_TRIANGLES, 0, 3); + glBindTexture(GL_TEXTURE_2D, 0); + + // switch back to the default texture + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, texture); + // disable depth + glDepthMask(GL_FALSE); + // configure viewport + glViewport(win_x, win_y, win_width, win_height); + // draw fullscreen triangle to the frame buffer? + glDrawArrays(GL_TRIANGLES, 0, 3); + glBindTexture(GL_TEXTURE_2D, 0); // check if there was an error when using any of the commands above gl_check_errors(); @@ -317,7 +332,19 @@ void gl_screen_render(int32_t win_width, int32_t win_height, int32_t win_x, int3 void gl_screen_clear(void) { + glDepthMask(GL_TRUE); + //glActiveTexture(GL_TEXTURE1); + //glBindTexture(GL_TEXTURE_2D, depth_texture); + //glActiveTexture(GL_TEXTURE0); + //glBindTexture(GL_TEXTURE_2D, texture); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + //glActiveTexture(GL_TEXTURE1); + //glBindTexture(GL_TEXTURE_2D, 0); + //glActiveTexture(GL_TEXTURE0); + //glBindTexture(GL_TEXTURE_2D, 0); + glDepthMask(GL_FALSE); } void gl_screen_close(void) From 1624e716748d63bcdcac43ae3c415a3b81d2617d Mon Sep 17 00:00:00 2001 From: Aubrey Buchanan Date: Wed, 11 Jul 2018 04:04:10 -0600 Subject: [PATCH 31/55] Rendering only the depth and not the image --- src/plugin/common/gl_screen.c | 69 ++++++++++++++++++++++++++++------- 1 file changed, 55 insertions(+), 14 deletions(-) diff --git a/src/plugin/common/gl_screen.c b/src/plugin/common/gl_screen.c index 378aeb8..b803d3a 100644 --- a/src/plugin/common/gl_screen.c +++ b/src/plugin/common/gl_screen.c @@ -198,6 +198,8 @@ void gl_screen_init(struct rdp_config* config) glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filter); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filter); + glBindTexture(GL_TEXTURE_2D, 0); + // check if there was an error when using any of the commands above gl_check_errors(); } @@ -220,7 +222,7 @@ bool gl_screen_write(struct rdp_frame_buffer* fb, int32_t output_height) glPixelStorei(GL_UNPACK_ROW_LENGTH, fb->pitch); // reallocate texture buffer on GPU - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, tex_width, tex_height, 0, TEX_FORMAT, TEX_TYPE, fb->pixels); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, tex_width, tex_height, 0, TEX_FORMAT, TEX_TYPE, fb->depth); glBindTexture(GL_TEXTURE_2D, 0); @@ -232,7 +234,8 @@ bool gl_screen_write(struct rdp_frame_buffer* fb, int32_t output_height) // set pitch for all unpacking operations glPixelStorei(GL_UNPACK_ROW_LENGTH, fb->pitch); - glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT24, tex_width, tex_height, 0, GL_DEPTH_COMPONENT, TEX_TYPE, fb->depth); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, tex_width, tex_height, 0, TEX_FORMAT, TEX_TYPE, fb->pixels); + //glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT24, tex_width, tex_height, 0, GL_DEPTH_COMPONENT, TEX_TYPE, fb->depth); glBindTexture(GL_TEXTURE_2D, 0); @@ -245,12 +248,12 @@ bool gl_screen_write(struct rdp_frame_buffer* fb, int32_t output_height) glDepthMask(GL_FALSE); // copy local buffer to GPU texture buffer - glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, tex_width, tex_height, TEX_FORMAT, TEX_TYPE, fb->pixels); + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, tex_width, tex_height, TEX_FORMAT, TEX_TYPE, fb->depth); /* * Somehow this breaks rendering anything whatsoever? */ - //glBindTexture(GL_TEXTURE_2D, 0); + glBindTexture(GL_TEXTURE_2D, 0); //// write the depth to the depthbuffer glActiveTexture(GL_TEXTURE1); @@ -258,11 +261,16 @@ bool gl_screen_write(struct rdp_frame_buffer* fb, int32_t output_height) // enable depth glDepthMask(GL_TRUE); - glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, tex_width, tex_height, GL_DEPTH_COMPONENT, TEX_TYPE, fb->depth); + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, tex_width, tex_height, TEX_FORMAT, TEX_TYPE, fb->pixels); glBindTexture(GL_TEXTURE_2D, 0); } + glActiveTexture(GL_TEXTURE1); + glBindTexture(GL_TEXTURE_2D, depth_texture); + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, texture); + // update output size tex_display_height = output_height; @@ -302,28 +310,61 @@ void gl_screen_render(int32_t win_width, int32_t win_height, int32_t win_x, int3 - //// write the depth to the depthbuffer - glActiveTexture(GL_TEXTURE1); + //// write the pixels + glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, depth_texture); - // enable depth - glDepthMask(GL_TRUE); + glActiveTexture(GL_TEXTURE1); + glBindTexture(GL_TEXTURE_2D, texture); + // disable depth + glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); + glDepthMask(GL_FALSE); // configure viewport glViewport(win_x, win_y, win_width, win_height); //// draw to the depth to the depthbuffer glDrawArrays(GL_TRIANGLES, 0, 3); + // unbind the textures + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, 0); + glActiveTexture(GL_TEXTURE1); glBindTexture(GL_TEXTURE_2D, 0); - // switch back to the default texture + /* + * This block writes the pixels to the depth buffer. + */ + ////// write the depth + //glActiveTexture(GL_TEXTURE0); + //glBindTexture(GL_TEXTURE_2D, texture); + //glActiveTexture(GL_TEXTURE1); + //glBindTexture(GL_TEXTURE_2D, 0); + //// enable depth + //glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); + //glDepthMask(GL_TRUE); + //// configure viewport + //glViewport(win_x, win_y, win_width, win_height); + ////// draw to the depth to the depthbuffer + //glDrawArrays(GL_TRIANGLES, 0, 3); + + /* + * This block writes the depth to the depth buffer. + */ + // write the depth glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, texture); - // disable depth - glDepthMask(GL_FALSE); + glActiveTexture(GL_TEXTURE1); + glBindTexture(GL_TEXTURE_2D, depth_texture); + // enable depth + glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); + glDepthMask(GL_TRUE); // configure viewport glViewport(win_x, win_y, win_width, win_height); - // draw fullscreen triangle to the frame buffer? - glDrawArrays(GL_TRIANGLES, 0, 3); + // draw to the depth to the depthbuffer + glDrawArrays(GL_TRIANGLES, 0, 3); + // unbind the textures + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, 0); + glActiveTexture(GL_TEXTURE1); glBindTexture(GL_TEXTURE_2D, 0); // check if there was an error when using any of the commands above From febfe656f350cf6c45f64d65671dc051c08a4bb6 Mon Sep 17 00:00:00 2001 From: Aubrey Buchanan Date: Wed, 11 Jul 2018 12:07:37 -0600 Subject: [PATCH 32/55] Looks like I've got it --- src/plugin/common/gl_screen.c | 210 +++++++++++++--------------------- 1 file changed, 82 insertions(+), 128 deletions(-) diff --git a/src/plugin/common/gl_screen.c b/src/plugin/common/gl_screen.c index b803d3a..7ace88e 100644 --- a/src/plugin/common/gl_screen.c +++ b/src/plugin/common/gl_screen.c @@ -21,6 +21,9 @@ static GLuint vao; static GLuint texture = 1; static GLuint depth_texture = 2; +static GLint colorValueTextureLocation; +static GLint depthValueTextureLocation; + static int32_t tex_width; static int32_t tex_height; @@ -137,8 +140,9 @@ void gl_screen_init(struct rdp_config* config) SHADER_HEADER "in vec2 uv;\n" "layout(location = 0) out vec4 color;\n" - "uniform sampler2D tex0;\n" - "uniform sampler2D tex1;\n" + + "uniform sampler2D ColorValueTexture;\n" + "uniform sampler2D DepthValueTexture;\n" "float unpack8BitVec3IntoFloat(vec3 v, float min, float max) {\n" "float zeroTo24Bit = v.x + v.y * 256.0 + v.z * 256.0 * 256.0; \n" @@ -154,51 +158,60 @@ void gl_screen_init(struct rdp_config* config) "void main(void) {\n" #ifdef GLES - " color = texture(tex0, uv);\n" + " color = texture(ColorValueTexture, uv);\n" #else - " color.bgra = texture(tex0, uv);\n" + " color.bgra = texture(ColorValueTexture, uv);\n" #endif - " //gl_FragDepth = color2float(texture(tex1, uv).xyz);\n" - " gl_FragDepth = texture(tex1, uv).z;\n" + " //gl_FragDepth = color2float(texture(DepthValueTexture, uv).xyz);\n" + " gl_FragDepth = texture(DepthValueTexture, uv).z;\n" "}\n"; // compile and link OpenGL program GLuint vert = gl_shader_compile(GL_VERTEX_SHADER, vert_shader); GLuint frag = gl_shader_compile(GL_FRAGMENT_SHADER, frag_shader); program = gl_shader_link(vert, frag); + + // get the uniform variables location + depthValueTextureLocation = glGetUniformLocation(program, "DepthValueTexture"); + colorValueTextureLocation = glGetUniformLocation(program, "ColorValueTexture"); + + // specify the shader program to use glUseProgram(program); - // enable depth - glEnable(GL_DEPTH_TEST); + // bind the uniform variables locations + glUniform1i(depthValueTextureLocation, 0); + glUniform1i(colorValueTextureLocation, 1); // prepare dummy VAO glGenVertexArrays(1, &vao); glBindVertexArray(vao); + // select interpolation method + GLint filter; + switch (config->vi.interp) { + case VI_INTERP_LINEAR: + filter = GL_LINEAR; + break; + case VI_INTERP_NEAREST: + default: + filter = GL_NEAREST; + } + // prepare depth texture - glActiveTexture(GL_TEXTURE1); + glActiveTexture(GL_TEXTURE0 + 0); glGenTextures(1, &depth_texture); glBindTexture(GL_TEXTURE_2D, depth_texture); - - // prepare texture - glActiveTexture(GL_TEXTURE0); + // configure interpolation method + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filter); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filter); + + // prepare color texture + glActiveTexture(GL_TEXTURE0 + 1); glGenTextures(1, &texture); glBindTexture(GL_TEXTURE_2D, texture); - - // select interpolation method - GLint filter; - switch (config->vi.interp) { - case VI_INTERP_LINEAR: - filter = GL_LINEAR; - break; - case VI_INTERP_NEAREST: - default: - filter = GL_NEAREST; - } - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filter); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filter); - - glBindTexture(GL_TEXTURE_2D, 0); + // configure interpolation method + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filter); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filter); // check if there was an error when using any of the commands above gl_check_errors(); @@ -213,64 +226,55 @@ bool gl_screen_write(struct rdp_frame_buffer* fb, int32_t output_height) tex_width = fb->width; tex_height = fb->height; - // switch back to the default texture - glActiveTexture(GL_TEXTURE0); + // select the color value binding + glActiveTexture(GL_TEXTURE0 + 1); glBindTexture(GL_TEXTURE_2D, texture); - // disable depth - glDepthMask(GL_FALSE); + // set pitch for all unpacking operations glPixelStorei(GL_UNPACK_ROW_LENGTH, fb->pitch); - // reallocate texture buffer on GPU - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, tex_width, tex_height, 0, TEX_FORMAT, TEX_TYPE, fb->depth); + glDepthMask(false); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, tex_width, tex_height, 0, TEX_FORMAT, TEX_TYPE, fb->pixels); + glDepthMask(true); - glBindTexture(GL_TEXTURE_2D, 0); - - //// write the depth to the depthbuffer - glActiveTexture(GL_TEXTURE1); + // select the depth value binding + glActiveTexture(GL_TEXTURE0 + 0); glBindTexture(GL_TEXTURE_2D, depth_texture); - // enable depth - glDepthMask(GL_TRUE); + // set pitch for all unpacking operations glPixelStorei(GL_UNPACK_ROW_LENGTH, fb->pitch); + // reallocate texture buffer on GPU + glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); + glEnable(GL_DEPTH_TEST); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, tex_width, tex_height, 0, TEX_FORMAT, TEX_TYPE, fb->depth); + glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); + glDisable(GL_DEPTH_TEST); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, tex_width, tex_height, 0, TEX_FORMAT, TEX_TYPE, fb->pixels); - //glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT24, tex_width, tex_height, 0, GL_DEPTH_COMPONENT, TEX_TYPE, fb->depth); - - glBindTexture(GL_TEXTURE_2D, 0); msg_debug("%s: resized framebuffer texture: %dx%d", __FUNCTION__, tex_width, tex_height); } else { - // switch back to the default texture - glActiveTexture(GL_TEXTURE0); + // select the color value binding + glActiveTexture(GL_TEXTURE0 + 1); glBindTexture(GL_TEXTURE_2D, texture); - // disable depth - glDepthMask(GL_FALSE); // copy local buffer to GPU texture buffer - glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, tex_width, tex_height, TEX_FORMAT, TEX_TYPE, fb->depth); - - /* - * Somehow this breaks rendering anything whatsoever? - */ - glBindTexture(GL_TEXTURE_2D, 0); - - //// write the depth to the depthbuffer - glActiveTexture(GL_TEXTURE1); + glDepthMask(false); + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, tex_width, tex_height, TEX_FORMAT, TEX_TYPE, fb->pixels); + glDepthMask(true); + + // select the depth value binding + glActiveTexture(GL_TEXTURE0 + 0); glBindTexture(GL_TEXTURE_2D, depth_texture); - // enable depth - glDepthMask(GL_TRUE); - glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, tex_width, tex_height, TEX_FORMAT, TEX_TYPE, fb->pixels); + // copy local buffer to GPU texture buffer + glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); + glEnable(GL_DEPTH_TEST); + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, tex_width, tex_height, TEX_FORMAT, TEX_TYPE, fb->depth); + glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); + glDisable(GL_DEPTH_TEST); - glBindTexture(GL_TEXTURE_2D, 0); } - glActiveTexture(GL_TEXTURE1); - glBindTexture(GL_TEXTURE_2D, depth_texture); - glActiveTexture(GL_TEXTURE0); - glBindTexture(GL_TEXTURE_2D, texture); - // update output size tex_display_height = output_height; @@ -308,64 +312,26 @@ void gl_screen_render(int32_t win_width, int32_t win_height, int32_t win_x, int3 win_height = h_max; } - - - //// write the pixels - glActiveTexture(GL_TEXTURE0); - glBindTexture(GL_TEXTURE_2D, depth_texture); - glActiveTexture(GL_TEXTURE1); - glBindTexture(GL_TEXTURE_2D, texture); - // disable depth - glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); - glDepthMask(GL_FALSE); // configure viewport glViewport(win_x, win_y, win_width, win_height); - //// draw to the depth to the depthbuffer - glDrawArrays(GL_TRIANGLES, 0, 3); - // unbind the textures - glActiveTexture(GL_TEXTURE0); - glBindTexture(GL_TEXTURE_2D, 0); - glActiveTexture(GL_TEXTURE1); - glBindTexture(GL_TEXTURE_2D, 0); - - /* - * This block writes the pixels to the depth buffer. - */ - ////// write the depth - //glActiveTexture(GL_TEXTURE0); - //glBindTexture(GL_TEXTURE_2D, texture); - //glActiveTexture(GL_TEXTURE1); - //glBindTexture(GL_TEXTURE_2D, 0); - //// enable depth - //glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); - //glDepthMask(GL_TRUE); - //// configure viewport - //glViewport(win_x, win_y, win_width, win_height); - ////// draw to the depth to the depthbuffer - //glDrawArrays(GL_TRIANGLES, 0, 3); - - /* - * This block writes the depth to the depth buffer. - */ - // write the depth - glActiveTexture(GL_TEXTURE0); + + // select the color value binding + glActiveTexture(GL_TEXTURE0 + 1); glBindTexture(GL_TEXTURE_2D, texture); - glActiveTexture(GL_TEXTURE1); + + // draw + glDrawArrays(GL_TRIANGLES, 0, 3); + + // select the depth value binding + glActiveTexture(GL_TEXTURE0 + 0); glBindTexture(GL_TEXTURE_2D, depth_texture); - // enable depth - glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); - glDepthMask(GL_TRUE); - // configure viewport - glViewport(win_x, win_y, win_width, win_height); - // draw to the depth to the depthbuffer + + // draw + glEnable(GL_DEPTH_TEST); glDrawArrays(GL_TRIANGLES, 0, 3); + glDisable(GL_DEPTH_TEST); - // unbind the textures - glActiveTexture(GL_TEXTURE0); - glBindTexture(GL_TEXTURE_2D, 0); - glActiveTexture(GL_TEXTURE1); - glBindTexture(GL_TEXTURE_2D, 0); // check if there was an error when using any of the commands above gl_check_errors(); @@ -373,19 +339,7 @@ void gl_screen_render(int32_t win_width, int32_t win_height, int32_t win_x, int3 void gl_screen_clear(void) { - glDepthMask(GL_TRUE); - //glActiveTexture(GL_TEXTURE1); - //glBindTexture(GL_TEXTURE_2D, depth_texture); - //glActiveTexture(GL_TEXTURE0); - //glBindTexture(GL_TEXTURE_2D, texture); - glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - - //glActiveTexture(GL_TEXTURE1); - //glBindTexture(GL_TEXTURE_2D, 0); - //glActiveTexture(GL_TEXTURE0); - //glBindTexture(GL_TEXTURE_2D, 0); - glDepthMask(GL_FALSE); } void gl_screen_close(void) From 81132009e5287a195fbbf1ae640a0b11c4869dd8 Mon Sep 17 00:00:00 2001 From: Aubrey Buchanan Date: Wed, 11 Jul 2018 12:55:13 -0600 Subject: [PATCH 33/55] Some tidy up --- src/plugin/common/gl_screen.c | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/src/plugin/common/gl_screen.c b/src/plugin/common/gl_screen.c index 7ace88e..cb7896a 100644 --- a/src/plugin/common/gl_screen.c +++ b/src/plugin/common/gl_screen.c @@ -144,26 +144,13 @@ void gl_screen_init(struct rdp_config* config) "uniform sampler2D ColorValueTexture;\n" "uniform sampler2D DepthValueTexture;\n" - "float unpack8BitVec3IntoFloat(vec3 v, float min, float max) {\n" - "float zeroTo24Bit = v.x + v.y * 256.0 + v.z * 256.0 * 256.0; \n" - "float zeroToOne = zeroTo24Bit / 256.0 / 256.0 / 256.0; \n" - "return zeroToOne * (max - min) + min; \n" - "}\n" - - "float color2float(in vec3 c) {\n" - "c *= 255.;\n" - "c = floor(c); // without this value could be shifted for some intervals\n" - "return c.r*256.*256. + c.g*256. + c.b - 8388608.;\n" - "}\n" - "void main(void) {\n" #ifdef GLES " color = texture(ColorValueTexture, uv);\n" #else " color.bgra = texture(ColorValueTexture, uv);\n" #endif - " //gl_FragDepth = color2float(texture(DepthValueTexture, uv).xyz);\n" - " gl_FragDepth = texture(DepthValueTexture, uv).z;\n" + " gl_FragDepth = texture(DepthValueTexture, uv).r;\n" "}\n"; // compile and link OpenGL program From b0f9c263d6cb9bb00b6942d06b59a6e4068d9b95 Mon Sep 17 00:00:00 2001 From: Aubrey Buchanan Date: Sat, 1 Jun 2019 09:16:29 -0600 Subject: [PATCH 34/55] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 675c349..aadbc53 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Angrylion RDP Plus +# Angrylion RDP X This is a conservative fork of angrylion's RDP plugin that aims to improve performance add new features while retaining the accuracy of the original plugin. From d2e3fbfa683c9ce5a550fd3a815229650187b2a8 Mon Sep 17 00:00:00 2001 From: Aubrey Buchanan Date: Sat, 1 Jun 2019 19:15:34 -0600 Subject: [PATCH 35/55] Rename .sln --- msvc/{angrylion-plus.sln => angrylion-plus-x.sln} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename msvc/{angrylion-plus.sln => angrylion-plus-x.sln} (100%) diff --git a/msvc/angrylion-plus.sln b/msvc/angrylion-plus-x.sln similarity index 100% rename from msvc/angrylion-plus.sln rename to msvc/angrylion-plus-x.sln From 19c87a75fd5f77e5966ed1c6e92dda19ca1cd361 Mon Sep 17 00:00:00 2001 From: Aubrey Buchanan Date: Mon, 10 Jun 2019 15:49:56 -0600 Subject: [PATCH 36/55] Update .gitignore --- .gitignore | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/.gitignore b/.gitignore index a4b8ec5..a18d6e3 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,41 @@ src/core/version.h private/ + +# Build results +[Bb]in/ +[Oo]bj/ +[Ii]ntermediate/ +[Bb]uild/ +[Dd]ebug/ +[Rr]elease/ +*.obj +*.cso + +# Visual Studio cache files +.vs/ +ipch/ +packages/ +*.aps +*.VC.db +*.VC.opendb +*.sdf +*.opensdf +*.suo +*.vcxproj.user + +# Visual Studio performance profiling +*.vsp +*.psess + +# Code signing +*.pfx +*.pvk + +# Temporary OS files +.DS_Store +Thumbs.db + +# Versioning +/res/Version.h + From fcf50361a61c0b28c18a4568980a0180c60f6b4c Mon Sep 17 00:00:00 2001 From: Aubrey Buchanan Date: Mon, 10 Jun 2019 16:33:37 -0600 Subject: [PATCH 37/55] merge --- msvc/.editorconfig | 7 + msvc/.gitmodules | 18 + msvc/Common.props | 8 + msvc/LICENSE.md | 9 + msvc/README.md | 22 + msvc/ReShade.vcxproj | 471 +++ msvc/ReShade.vcxproj.filters | 336 ++ msvc/ReShadeFX.vcxproj | 160 + msvc/ReShadeFX.vcxproj.filters | 24 + msvc/ReShadeFXC.vcxproj | 167 + msvc/ReShadeFXC.vcxproj.filters | 9 + msvc/UTF8.cpp | 265 ++ msvc/angrylion-plus-x.sln | 170 + msvc/deps/ImGui.props | 10 + msvc/deps/ImGui.vcxproj | 73 + msvc/deps/MinHook.props | 9 + msvc/deps/MinHook.vcxproj | 76 + msvc/deps/MinHook.vcxproj.filters | 44 + msvc/deps/SPIRV.props | 9 + msvc/deps/Windows.props | 9 + msvc/deps/gl3w.props | 9 + msvc/deps/gl3w.vcxproj | 91 + msvc/deps/stb.props | 10 + msvc/deps/stb.vcxproj | 75 + msvc/deps/stb_image_dds/stb_image_dds.h | 586 ++++ msvc/deps/stb_impl.c | 9 + msvc/deps/utfcpp.props | 9 + msvc/res/exports.def | 433 +++ msvc/res/resource.h | Bin 0 -> 1354 bytes msvc/res/resource.rc | Bin 0 -> 3738 bytes msvc/res/resource.rc2 | Bin 0 -> 2846 bytes msvc/res/shader_copy_ps.hlsl | 8 + msvc/res/shader_fullscreen_vs.hlsl | 6 + msvc/res/shader_imgui_ps.hlsl | 8 + msvc/res/shader_imgui_vs.hlsl | 24 + msvc/res/shader_mipmap_cs.hlsl | 17 + msvc/res/version.h | 14 + msvc/setup/FodyWeavers.xml | 4 + msvc/setup/Glass.cs | 113 + msvc/setup/IniFile.cs | 22 + msvc/setup/PEInfo.cs | 115 + msvc/setup/Packages.config | 7 + msvc/setup/Properties/App.xaml | 5 + msvc/setup/Properties/Assembly.manifest | 24 + msvc/setup/Properties/AssemblyInfo.cs | 9 + msvc/setup/Properties/Icon.ico | Bin 0 -> 68582 bytes msvc/setup/ReShade Setup.csproj | 163 + msvc/setup/Select.xaml | 28 + msvc/setup/Select.xaml.cs | 73 + msvc/setup/Settings.xaml | 55 + msvc/setup/Settings.xaml.cs | 158 + msvc/setup/Wizard.xaml | 87 + msvc/setup/Wizard.xaml.cs | 496 +++ msvc/source/com_ptr.hpp | 121 + msvc/source/d3d10/d3d10.cpp | 100 + msvc/source/d3d10/d3d10_device.cpp | 560 ++++ msvc/source/d3d10/d3d10_device.hpp | 144 + msvc/source/d3d10/draw_call_tracker.cpp | 285 ++ msvc/source/d3d10/draw_call_tracker.hpp | 86 + msvc/source/d3d10/runtime_d3d10.cpp | 1586 +++++++++ msvc/source/d3d10/runtime_d3d10.hpp | 116 + msvc/source/d3d10/state_block.cpp | 117 + msvc/source/d3d10/state_block.hpp | 56 + msvc/source/d3d11/d3d11.cpp | 99 + msvc/source/d3d11/d3d11_device.cpp | 493 +++ msvc/source/d3d11/d3d11_device.hpp | 122 + msvc/source/d3d11/d3d11_device_context.cpp | 857 +++++ msvc/source/d3d11/d3d11_device_context.hpp | 200 ++ msvc/source/d3d11/draw_call_tracker.cpp | 285 ++ msvc/source/d3d11/draw_call_tracker.hpp | 86 + msvc/source/d3d11/runtime_d3d11.cpp | 1596 +++++++++ msvc/source/d3d11/runtime_d3d11.hpp | 119 + msvc/source/d3d11/state_block.cpp | 166 + msvc/source/d3d11/state_block.hpp | 70 + msvc/source/d3d12/d3d12.cpp | 92 + msvc/source/d3d12/d3d12_command_queue.cpp | 126 + msvc/source/d3d12/d3d12_command_queue.hpp | 47 + msvc/source/d3d12/d3d12_device.cpp | 396 +++ msvc/source/d3d12/d3d12_device.hpp | 103 + msvc/source/d3d12/runtime_d3d12.cpp | 1403 ++++++++ msvc/source/d3d12/runtime_d3d12.hpp | 96 + msvc/source/d3d9/d3d9.cpp | 207 ++ msvc/source/d3d9/d3d9_device.cpp | 912 ++++++ msvc/source/d3d9/d3d9_device.hpp | 171 + msvc/source/d3d9/d3d9_profiling.cpp | 48 + msvc/source/d3d9/d3d9_swapchain.cpp | 149 + msvc/source/d3d9/d3d9_swapchain.hpp | 48 + msvc/source/d3d9/runtime_d3d9.cpp | 1711 ++++++++++ msvc/source/d3d9/runtime_d3d9.hpp | 127 + msvc/source/d3d9/state_block.cpp | 61 + msvc/source/d3d9/state_block.hpp | 35 + msvc/source/dllmain.cpp | 406 +++ msvc/source/dxgi/dxgi.cpp | 341 ++ msvc/source/dxgi/dxgi_d3d10.cpp | 38 + msvc/source/dxgi/dxgi_device.cpp | 178 + msvc/source/dxgi/dxgi_device.hpp | 57 + msvc/source/dxgi/dxgi_swapchain.cpp | 479 +++ msvc/source/dxgi/dxgi_swapchain.hpp | 95 + msvc/source/dxgi/format_utils.hpp | 108 + msvc/source/effect_codegen.hpp | 323 ++ msvc/source/effect_codegen_glsl.cpp | 1365 ++++++++ msvc/source/effect_codegen_hlsl.cpp | 1355 ++++++++ msvc/source/effect_expression.cpp | 451 +++ msvc/source/effect_expression.hpp | 381 +++ msvc/source/effect_lexer.cpp | 1004 ++++++ msvc/source/effect_lexer.hpp | 281 ++ msvc/source/effect_parser.cpp | 2872 +++++++++++++++++ msvc/source/effect_parser.hpp | 89 + msvc/source/effect_preprocessor.cpp | 1068 ++++++ msvc/source/effect_preprocessor.hpp | 135 + msvc/source/effect_symbol_table.cpp | 399 +++ msvc/source/effect_symbol_table.hpp | 102 + .../source/effect_symbol_table_intrinsics.inl | 1705 ++++++++++ msvc/source/gui.cpp | 2313 +++++++++++++ msvc/source/gui_code_editor.cpp | 1518 +++++++++ msvc/source/gui_code_editor.hpp | 165 + msvc/source/gui_widgets.cpp | 512 +++ msvc/source/gui_widgets.hpp | 31 + msvc/source/hook.cpp | 74 + msvc/source/hook.hpp | 73 + msvc/source/hook_manager.cpp | 463 +++ msvc/source/hook_manager.hpp | 66 + msvc/source/ini_file.cpp | 145 + msvc/source/ini_file.hpp | 196 ++ msvc/source/input.cpp | 581 ++++ msvc/source/input.hpp | 72 + msvc/source/log.cpp | 73 + msvc/source/log.hpp | 82 + msvc/source/moving_average.hpp | 38 + msvc/source/opengl/opengl.hpp | 1367 ++++++++ msvc/source/opengl/opengl_hooks.cpp | 2163 +++++++++++++ msvc/source/opengl/opengl_hooks.hpp | 195 ++ msvc/source/opengl/opengl_hooks_wgl.cpp | 1088 +++++++ msvc/source/opengl/runtime_opengl.cpp | 1229 +++++++ msvc/source/opengl/runtime_opengl.hpp | 117 + msvc/source/opengl/state_block.cpp | 137 + msvc/source/opengl/state_block.hpp | 54 + msvc/source/resource_loading.cpp | 36 + msvc/source/resource_loading.hpp | 24 + msvc/source/runtime.cpp | 1283 ++++++++ msvc/source/runtime.hpp | 368 +++ msvc/source/runtime_objects.hpp | 151 + msvc/source/update_check.cpp | 59 + msvc/source/windows/user32.cpp | 91 + msvc/source/windows/ws2_32.cpp | 88 + msvc/tools/7za.exe | Bin 0 -> 1144320 bytes msvc/tools/fxc.cpp | 179 + msvc/tools/verbuild.exe | Bin 0 -> 34816 bytes 148 files changed, 45813 insertions(+) create mode 100644 msvc/.editorconfig create mode 100644 msvc/.gitmodules create mode 100644 msvc/Common.props create mode 100644 msvc/LICENSE.md create mode 100644 msvc/README.md create mode 100644 msvc/ReShade.vcxproj create mode 100644 msvc/ReShade.vcxproj.filters create mode 100644 msvc/ReShadeFX.vcxproj create mode 100644 msvc/ReShadeFX.vcxproj.filters create mode 100644 msvc/ReShadeFXC.vcxproj create mode 100644 msvc/ReShadeFXC.vcxproj.filters create mode 100644 msvc/UTF8.cpp create mode 100644 msvc/deps/ImGui.props create mode 100644 msvc/deps/ImGui.vcxproj create mode 100644 msvc/deps/MinHook.props create mode 100644 msvc/deps/MinHook.vcxproj create mode 100644 msvc/deps/MinHook.vcxproj.filters create mode 100644 msvc/deps/SPIRV.props create mode 100644 msvc/deps/Windows.props create mode 100644 msvc/deps/gl3w.props create mode 100644 msvc/deps/gl3w.vcxproj create mode 100644 msvc/deps/stb.props create mode 100644 msvc/deps/stb.vcxproj create mode 100644 msvc/deps/stb_image_dds/stb_image_dds.h create mode 100644 msvc/deps/stb_impl.c create mode 100644 msvc/deps/utfcpp.props create mode 100644 msvc/res/exports.def create mode 100644 msvc/res/resource.h create mode 100644 msvc/res/resource.rc create mode 100644 msvc/res/resource.rc2 create mode 100644 msvc/res/shader_copy_ps.hlsl create mode 100644 msvc/res/shader_fullscreen_vs.hlsl create mode 100644 msvc/res/shader_imgui_ps.hlsl create mode 100644 msvc/res/shader_imgui_vs.hlsl create mode 100644 msvc/res/shader_mipmap_cs.hlsl create mode 100644 msvc/res/version.h create mode 100644 msvc/setup/FodyWeavers.xml create mode 100644 msvc/setup/Glass.cs create mode 100644 msvc/setup/IniFile.cs create mode 100644 msvc/setup/PEInfo.cs create mode 100644 msvc/setup/Packages.config create mode 100644 msvc/setup/Properties/App.xaml create mode 100644 msvc/setup/Properties/Assembly.manifest create mode 100644 msvc/setup/Properties/AssemblyInfo.cs create mode 100644 msvc/setup/Properties/Icon.ico create mode 100644 msvc/setup/ReShade Setup.csproj create mode 100644 msvc/setup/Select.xaml create mode 100644 msvc/setup/Select.xaml.cs create mode 100644 msvc/setup/Settings.xaml create mode 100644 msvc/setup/Settings.xaml.cs create mode 100644 msvc/setup/Wizard.xaml create mode 100644 msvc/setup/Wizard.xaml.cs create mode 100644 msvc/source/com_ptr.hpp create mode 100644 msvc/source/d3d10/d3d10.cpp create mode 100644 msvc/source/d3d10/d3d10_device.cpp create mode 100644 msvc/source/d3d10/d3d10_device.hpp create mode 100644 msvc/source/d3d10/draw_call_tracker.cpp create mode 100644 msvc/source/d3d10/draw_call_tracker.hpp create mode 100644 msvc/source/d3d10/runtime_d3d10.cpp create mode 100644 msvc/source/d3d10/runtime_d3d10.hpp create mode 100644 msvc/source/d3d10/state_block.cpp create mode 100644 msvc/source/d3d10/state_block.hpp create mode 100644 msvc/source/d3d11/d3d11.cpp create mode 100644 msvc/source/d3d11/d3d11_device.cpp create mode 100644 msvc/source/d3d11/d3d11_device.hpp create mode 100644 msvc/source/d3d11/d3d11_device_context.cpp create mode 100644 msvc/source/d3d11/d3d11_device_context.hpp create mode 100644 msvc/source/d3d11/draw_call_tracker.cpp create mode 100644 msvc/source/d3d11/draw_call_tracker.hpp create mode 100644 msvc/source/d3d11/runtime_d3d11.cpp create mode 100644 msvc/source/d3d11/runtime_d3d11.hpp create mode 100644 msvc/source/d3d11/state_block.cpp create mode 100644 msvc/source/d3d11/state_block.hpp create mode 100644 msvc/source/d3d12/d3d12.cpp create mode 100644 msvc/source/d3d12/d3d12_command_queue.cpp create mode 100644 msvc/source/d3d12/d3d12_command_queue.hpp create mode 100644 msvc/source/d3d12/d3d12_device.cpp create mode 100644 msvc/source/d3d12/d3d12_device.hpp create mode 100644 msvc/source/d3d12/runtime_d3d12.cpp create mode 100644 msvc/source/d3d12/runtime_d3d12.hpp create mode 100644 msvc/source/d3d9/d3d9.cpp create mode 100644 msvc/source/d3d9/d3d9_device.cpp create mode 100644 msvc/source/d3d9/d3d9_device.hpp create mode 100644 msvc/source/d3d9/d3d9_profiling.cpp create mode 100644 msvc/source/d3d9/d3d9_swapchain.cpp create mode 100644 msvc/source/d3d9/d3d9_swapchain.hpp create mode 100644 msvc/source/d3d9/runtime_d3d9.cpp create mode 100644 msvc/source/d3d9/runtime_d3d9.hpp create mode 100644 msvc/source/d3d9/state_block.cpp create mode 100644 msvc/source/d3d9/state_block.hpp create mode 100644 msvc/source/dllmain.cpp create mode 100644 msvc/source/dxgi/dxgi.cpp create mode 100644 msvc/source/dxgi/dxgi_d3d10.cpp create mode 100644 msvc/source/dxgi/dxgi_device.cpp create mode 100644 msvc/source/dxgi/dxgi_device.hpp create mode 100644 msvc/source/dxgi/dxgi_swapchain.cpp create mode 100644 msvc/source/dxgi/dxgi_swapchain.hpp create mode 100644 msvc/source/dxgi/format_utils.hpp create mode 100644 msvc/source/effect_codegen.hpp create mode 100644 msvc/source/effect_codegen_glsl.cpp create mode 100644 msvc/source/effect_codegen_hlsl.cpp create mode 100644 msvc/source/effect_expression.cpp create mode 100644 msvc/source/effect_expression.hpp create mode 100644 msvc/source/effect_lexer.cpp create mode 100644 msvc/source/effect_lexer.hpp create mode 100644 msvc/source/effect_parser.cpp create mode 100644 msvc/source/effect_parser.hpp create mode 100644 msvc/source/effect_preprocessor.cpp create mode 100644 msvc/source/effect_preprocessor.hpp create mode 100644 msvc/source/effect_symbol_table.cpp create mode 100644 msvc/source/effect_symbol_table.hpp create mode 100644 msvc/source/effect_symbol_table_intrinsics.inl create mode 100644 msvc/source/gui.cpp create mode 100644 msvc/source/gui_code_editor.cpp create mode 100644 msvc/source/gui_code_editor.hpp create mode 100644 msvc/source/gui_widgets.cpp create mode 100644 msvc/source/gui_widgets.hpp create mode 100644 msvc/source/hook.cpp create mode 100644 msvc/source/hook.hpp create mode 100644 msvc/source/hook_manager.cpp create mode 100644 msvc/source/hook_manager.hpp create mode 100644 msvc/source/ini_file.cpp create mode 100644 msvc/source/ini_file.hpp create mode 100644 msvc/source/input.cpp create mode 100644 msvc/source/input.hpp create mode 100644 msvc/source/log.cpp create mode 100644 msvc/source/log.hpp create mode 100644 msvc/source/moving_average.hpp create mode 100644 msvc/source/opengl/opengl.hpp create mode 100644 msvc/source/opengl/opengl_hooks.cpp create mode 100644 msvc/source/opengl/opengl_hooks.hpp create mode 100644 msvc/source/opengl/opengl_hooks_wgl.cpp create mode 100644 msvc/source/opengl/runtime_opengl.cpp create mode 100644 msvc/source/opengl/runtime_opengl.hpp create mode 100644 msvc/source/opengl/state_block.cpp create mode 100644 msvc/source/opengl/state_block.hpp create mode 100644 msvc/source/resource_loading.cpp create mode 100644 msvc/source/resource_loading.hpp create mode 100644 msvc/source/runtime.cpp create mode 100644 msvc/source/runtime.hpp create mode 100644 msvc/source/runtime_objects.hpp create mode 100644 msvc/source/update_check.cpp create mode 100644 msvc/source/windows/user32.cpp create mode 100644 msvc/source/windows/ws2_32.cpp create mode 100644 msvc/tools/7za.exe create mode 100644 msvc/tools/fxc.cpp create mode 100644 msvc/tools/verbuild.exe diff --git a/msvc/.editorconfig b/msvc/.editorconfig new file mode 100644 index 0000000..8c8cbd5 --- /dev/null +++ b/msvc/.editorconfig @@ -0,0 +1,7 @@ +root = true + +[*] +indent_size = 4 +indent_style = tab +insert_final_newline = true +trim_trailing_whitespace = true diff --git a/msvc/.gitmodules b/msvc/.gitmodules new file mode 100644 index 0000000..a2bbc98 --- /dev/null +++ b/msvc/.gitmodules @@ -0,0 +1,18 @@ +[submodule "imgui"] + path = deps/imgui + url = https://github.com/ocornut/imgui.git +[submodule "minhook"] + path = deps/minhook + url = https://github.com/TsudaKageyu/minhook.git +[submodule "stb"] + path = deps/stb + url = https://github.com/nothings/stb.git +[submodule "gl3w"] + path = deps/gl3w + url = https://github.com/skaslev/gl3w.git +[submodule "utfcpp"] + path = deps/utfcpp + url = https://github.com/nemtrif/utfcpp +[submodule "spirv"] + path = deps/spirv + url = https://github.com/KhronosGroup/SPIRV-Headers/ diff --git a/msvc/Common.props b/msvc/Common.props new file mode 100644 index 0000000..78fd2f3 --- /dev/null +++ b/msvc/Common.props @@ -0,0 +1,8 @@ + + + + + $(SolutionDir)bin\$(Platform)\$(Configuration)\ + $(SolutionDir)intermediate\$(ProjectName)\$(Platform)\$(Configuration)\ + + diff --git a/msvc/LICENSE.md b/msvc/LICENSE.md new file mode 100644 index 0000000..52d2965 --- /dev/null +++ b/msvc/LICENSE.md @@ -0,0 +1,9 @@ +Copyright 2014 Patrick Mours. All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/msvc/README.md b/msvc/README.md new file mode 100644 index 0000000..6f72542 --- /dev/null +++ b/msvc/README.md @@ -0,0 +1,22 @@ +ReShade +======= + +This is a generic post-processing injector for games and video software. It exposes an automated way to access both frame color and depth information and a custom shader language called ReShade FX to write effects like ambient occlusion, depth of field, color correction and more which work everywhere. + +## Building + +You'll need both Git and Visual Studio 2017 or higher to build ReShade. Latter is required since the project makes use of several C++14 and C++17 features. Additionally a Python 2.7.9 or later (Python 3 is supported as well) installation is necessary for the `gl3w` dependency to build. + +1. Clone this repository including all Git submodules +2. Open the Visual Studio solution +3. Select either the "32-bit" or "64-bit" target platform and build the solution (this will build ReShade and all dependencies). + +After the first build, a `version.h` file will show up in the [res](/res) directory. Change the `VERSION_FULL` definition inside to something matching the current release version and rebuild so that shaders from the official repository at https://github.com/crosire/reshade-shaders won't cause a version mismatch error during compilation. + +## Contributing + +Any contributions to the project are welcomed, it's recommended to use GitHub [pull requests](https://help.github.com/articles/using-pull-requests/). + +## License + +All source code in this repository is licensed under a [BSD 3-clause license](LICENSE.md). diff --git a/msvc/ReShade.vcxproj b/msvc/ReShade.vcxproj new file mode 100644 index 0000000..51cee10 --- /dev/null +++ b/msvc/ReShade.vcxproj @@ -0,0 +1,471 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + Debug App + Win32 + + + Debug App + x64 + + + Release + Win32 + + + Release + x64 + + + Release App + Win32 + + + Release App + x64 + + + + {0401ADF5-D085-4A3D-95B2-D9B7896BB338} + Win32Proj + 10.0.17763.0 + reshade + + + + Unicode + v141 + + + ReShade32 + + + ReShade64 + + + DynamicLibrary + true + true + + + Application + true + true + + + DynamicLibrary + false + false + true + + + Application + false + false + true + + + + + + + + + + + + + + + + Level4 + true + Disabled + $(SolutionDir)res;$(SolutionDir)source;%(AdditionalIncludeDirectories) + RESHADE_GUI;RESHADE_VERBOSE_LOG;WIN32_LEAN_AND_MEAN;NOMINMAX;_SILENCE_ALL_CXX17_DEPRECATION_WARNINGS;_GDI32_;WINSOCK_API_LINKAGE=;WIN32;_DEBUG;%(PreprocessorDefinitions) + 4351;%(DisableSpecificWarnings) + $(IntDir)%(RelativeDir) + MultiThreadedDebugDLL + stdcpplatest + + + Windows + true + $(SolutionDir)bin\$(Platform)\$(Configuration);%(AdditionalLibraryDirectories) + res\exports.def + + + 4.0 + $(SolutionDir)res\%(Filename).cso + + + WIN32;_DEBUG;%(PreprocessorDefinitions) + + + tools\verbuild res\version.h -c + + + + + Level4 + true + Disabled + $(SolutionDir)res;$(SolutionDir)source;%(AdditionalIncludeDirectories) + RESHADE_GUI;RESHADE_VERBOSE_LOG;WIN32_LEAN_AND_MEAN;NOMINMAX;_SILENCE_ALL_CXX17_DEPRECATION_WARNINGS;_GDI32_;WINSOCK_API_LINKAGE=;WIN64;_DEBUG;%(PreprocessorDefinitions) + 4351;%(DisableSpecificWarnings) + $(IntDir)%(RelativeDir) + MultiThreadedDebugDLL + stdcpplatest + + + Windows + true + $(SolutionDir)bin\$(Platform)\$(Configuration);%(AdditionalLibraryDirectories) + res\exports.def + + + 4.0 + $(SolutionDir)res\%(Filename).cso + + + WIN64;_DEBUG;%(PreprocessorDefinitions) + + + tools\verbuild res\version.h -c + + + + + Level4 + true + Disabled + $(SolutionDir)res;$(SolutionDir)source;%(AdditionalIncludeDirectories) + RESHADE_GUI;RESHADE_TEST_APPLICATION;RESHADE_VERBOSE_LOG;D3D_DEBUG_INFO;WIN32_LEAN_AND_MEAN;NOMINMAX;_SILENCE_ALL_CXX17_DEPRECATION_WARNINGS;_GDI32_;WINSOCK_API_LINKAGE=;WIN32;_DEBUG;%(PreprocessorDefinitions) + 4351;%(DisableSpecificWarnings) + $(IntDir)%(RelativeDir) + MultiThreadedDebugDLL + stdcpplatest + + + Windows + true + $(SolutionDir)bin\$(Platform)\Debug;%(AdditionalLibraryDirectories) + res\exports.def + + + 4.0 + $(SolutionDir)res\%(Filename).cso + + + WIN32;_DEBUG;%(PreprocessorDefinitions) + + + tools\verbuild res\version.h -c + + + + + Level4 + true + Disabled + $(SolutionDir)res;$(SolutionDir)source;%(AdditionalIncludeDirectories) + RESHADE_GUI;RESHADE_TEST_APPLICATION;RESHADE_VERBOSE_LOG;D3D_DEBUG_INFO;WIN32_LEAN_AND_MEAN;NOMINMAX;_SILENCE_ALL_CXX17_DEPRECATION_WARNINGS;_GDI32_;WINSOCK_API_LINKAGE=;WIN64;_DEBUG;%(PreprocessorDefinitions) + 4351;%(DisableSpecificWarnings) + $(IntDir)%(RelativeDir) + MultiThreadedDebugDLL + stdcpplatest + + + Windows + true + $(SolutionDir)bin\$(Platform)\Debug;%(AdditionalLibraryDirectories) + res\exports.def + + + 4.0 + $(SolutionDir)res\%(Filename).cso + + + WIN64;_DEBUG;%(PreprocessorDefinitions) + + + tools\verbuild res\version.h -c + + + + + Level3 + true + MaxSpeed + true + true + $(SolutionDir)res;$(SolutionDir)source;%(AdditionalIncludeDirectories) + RESHADE_GUI;WIN32_LEAN_AND_MEAN;NOMINMAX;_SILENCE_ALL_CXX17_DEPRECATION_WARNINGS;_GDI32_;WINSOCK_API_LINKAGE=;WIN32;NDEBUG;%(PreprocessorDefinitions) + 4351;%(DisableSpecificWarnings) + $(IntDir)%(RelativeDir) + MultiThreaded + stdcpplatest + + + Windows + false + false + true + $(SolutionDir)bin\$(Platform)\$(Configuration);%(AdditionalLibraryDirectories) + res\exports.def + + + 4.0 + $(SolutionDir)res\%(Filename).cso + + + WIN32;NDEBUG;%(PreprocessorDefinitions) + + + tools\verbuild res\version.h *.*.*.+ -xFP -b0.0.0.0 -s + + + if exist "res\sign.pfx" signtool sign /f "res\sign.pfx" /t http://timestamp.verisign.com/scripts/timstamp.dll "$(TargetPath)" + + + + + Level3 + true + MaxSpeed + true + true + $(SolutionDir)res;$(SolutionDir)source;%(AdditionalIncludeDirectories) + RESHADE_GUI;WIN32_LEAN_AND_MEAN;NOMINMAX;_SILENCE_ALL_CXX17_DEPRECATION_WARNINGS;_GDI32_;WINSOCK_API_LINKAGE=;WIN64;NDEBUG;%(PreprocessorDefinitions) + 4351;%(DisableSpecificWarnings) + $(IntDir)%(RelativeDir) + MultiThreaded + stdcpplatest + + + Windows + false + false + true + $(SolutionDir)bin\$(Platform)\$(Configuration);%(AdditionalLibraryDirectories) + res\exports.def + + + 4.0 + $(SolutionDir)res\%(Filename).cso + + + WIN64;NDEBUG;%(PreprocessorDefinitions) + + + tools\verbuild res\version.h *.*.*.+ -xFP -b0.0.0.0 -s + + + if exist "res\sign.pfx" signtool sign /f "res\sign.pfx" /t http://timestamp.verisign.com/scripts/timstamp.dll "$(TargetPath)" + + + + + Level3 + true + MaxSpeed + true + true + $(SolutionDir)res;$(SolutionDir)source;%(AdditionalIncludeDirectories) + RESHADE_TEST_APPLICATION;RESHADE_VERBOSE_LOG;WIN32_LEAN_AND_MEAN;NOMINMAX;_SILENCE_ALL_CXX17_DEPRECATION_WARNINGS;_GDI32_;WINSOCK_API_LINKAGE=;WIN32;NDEBUG;%(PreprocessorDefinitions) + 4351;%(DisableSpecificWarnings) + $(IntDir)%(RelativeDir) + MultiThreaded + stdcpplatest + + + Windows + false + false + true + $(SolutionDir)bin\$(Platform)\$(Configuration);%(AdditionalLibraryDirectories) + res\exports.def + true + + + 4.0 + $(SolutionDir)res\%(Filename).cso + + + WIN32;NDEBUG;%(PreprocessorDefinitions) + + + + + Level3 + true + MaxSpeed + true + true + $(SolutionDir)res;$(SolutionDir)source;%(AdditionalIncludeDirectories) + RESHADE_TEST_APPLICATION;RESHADE_VERBOSE_LOG;WIN32_LEAN_AND_MEAN;NOMINMAX;_SILENCE_ALL_CXX17_DEPRECATION_WARNINGS;_GDI32_;WINSOCK_API_LINKAGE=;WIN64;NDEBUG;%(PreprocessorDefinitions) + 4351;%(DisableSpecificWarnings) + $(IntDir)%(RelativeDir) + MultiThreaded + stdcpplatest + + + Windows + false + false + true + $(SolutionDir)bin\$(Platform)\$(Configuration);%(AdditionalLibraryDirectories) + res\exports.def + true + + + 4.0 + $(SolutionDir)res\%(Filename).cso + + + WIN64;NDEBUG;%(PreprocessorDefinitions) + + + + + {09c0d610-9b82-40d8-b37e-0d26e3bff77f} + + + {9a62233b-0b70-4b48-91e8-35aa666bc32e} + + + {783fedfb-5124-4f8c-87bc-70aa8490266b} + + + {723bdef8-4a39-4961-bdab-54074012ff47} + + + {d1c2099b-bec7-4993-8947-01d4a1f7eae2} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Pixel + + + Vertex + + + Pixel + + + Vertex + + + Compute + 5.0 + + + + \ No newline at end of file diff --git a/msvc/ReShade.vcxproj.filters b/msvc/ReShade.vcxproj.filters new file mode 100644 index 0000000..3cc78a0 --- /dev/null +++ b/msvc/ReShade.vcxproj.filters @@ -0,0 +1,336 @@ + + + + + {7dea67a5-a2de-4e39-94a8-e8f01b6f070f} + + + {ed3eb34b-86ef-4bb0-b071-44f1163953bf} + + + {ecdb23a8-21dc-4334-b5cf-2ea57d27001d} + + + {d3347d57-7868-467f-a688-fe9c6adc8e5c} + + + {3c3eb495-1eeb-4c7c-b58f-3bbcf24758f6} + + + {63356f14-45b5-4047-9be1-e7e6e8a3f3e5} + + + {d61375df-0b47-4eb2-b2b9-ed8034d7d180} + + + {2ed46921-48e6-4996-a11c-0a246bd56a86} + + + {e905e357-66ed-4246-a0a9-d3b4c84c2d7f} + + + {4d42777e-6ba3-4965-b0dc-88186095f1a9} + + + {78832e2a-8fda-4ae5-aecb-a4e0f5a0df02} + + + {ab5a23b3-e97e-4a41-99a1-89c6f1d54b16} + + + {7f3f7a66-aeb1-4c44-abb4-d7eff03a1247} + + + + + core + + + core\hook + + + core\hook + + + core\runtime + + + core\runtime + + + core\runtime + + + core\runtime + + + core\runtime + + + core\utility + + + core\utility + + + core\utility + + + core\utility + + + hooks\d3d9 + + + hooks\d3d9 + + + hooks\d3d9 + + + hooks\d3d9 + + + hooks\d3d9 + + + hooks\d3d9 + + + hooks\d3d10 + + + hooks\d3d10 + + + hooks\d3d10 + + + hooks\d3d10 + + + hooks\d3d10 + + + hooks\d3d11 + + + hooks\d3d11 + + + hooks\d3d11 + + + hooks\d3d11 + + + hooks\d3d11 + + + hooks\d3d11 + + + hooks\d3d12 + + + hooks\d3d12 + + + hooks\d3d12 + + + hooks\d3d12 + + + hooks\dxgi + + + hooks\dxgi + + + hooks\dxgi + + + hooks\dxgi + + + hooks\opengl + + + hooks\opengl + + + hooks\opengl + + + hooks\opengl + + + hooks\windows + + + hooks\windows + + + core + + + + + core\hook + + + core\hook + + + core\runtime + + + core\runtime + + + core\runtime + + + core\runtime + + + core\runtime + + + core\utility + + + core\utility + + + core\utility + + + core\utility + + + core\utility + + + hooks\d3d9 + + + hooks\d3d9 + + + hooks\d3d9 + + + hooks\d3d9 + + + hooks\d3d10 + + + hooks\d3d10 + + + hooks\d3d10 + + + hooks\d3d10 + + + hooks\d3d11 + + + hooks\d3d11 + + + hooks\d3d11 + + + hooks\d3d11 + + + hooks\d3d11 + + + hooks\d3d12 + + + hooks\d3d12 + + + hooks\d3d12 + + + hooks\dxgi + + + hooks\dxgi + + + hooks\dxgi + + + hooks\opengl + + + hooks\opengl + + + hooks\opengl + + + hooks\opengl + + + resources + + + resources + + + + + resources + + + resources + + + resources + + + resources + + + resources + + + resources + + + resources + + + + + resources + + + resources + + + resources + + + resources + + + resources + + + + + resources + + + \ No newline at end of file diff --git a/msvc/ReShadeFX.vcxproj b/msvc/ReShadeFX.vcxproj new file mode 100644 index 0000000..3d38355 --- /dev/null +++ b/msvc/ReShadeFX.vcxproj @@ -0,0 +1,160 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + {D1C2099B-BEC7-4993-8947-01D4A1F7EAE2} + Win32Proj + 10.0.17763.0 + reshade-fx + + + + StaticLibrary + Unicode + v141 + + + ReShadeFX32 + + + ReShadeFX64 + + + true + true + + + false + false + true + + + + + + + + + + Level4 + true + Disabled + $(SolutionDir)res;$(SolutionDir)source;%(AdditionalIncludeDirectories) + WIN32;WIN32_LEAN_AND_MEAN;NOMINMAX;_DEBUG;%(PreprocessorDefinitions); + 4351;%(DisableSpecificWarnings) + $(IntDir)%(RelativeDir) + MultiThreadedDebugDLL + /std:c++latest %(AdditionalOptions) + + + Windows + true + $(SolutionDir)\bin\$(Platform)\$(Configuration);%(AdditionalLibraryDirectories) + res\exports.def + + + + + Level4 + true + Disabled + $(SolutionDir)res;$(SolutionDir)source;%(AdditionalIncludeDirectories) + WIN64;WIN32_LEAN_AND_MEAN;NOMINMAX;_DEBUG;%(PreprocessorDefinitions); + 4351;%(DisableSpecificWarnings) + $(IntDir)%(RelativeDir) + MultiThreadedDebugDLL + /std:c++latest %(AdditionalOptions) + + + Windows + true + $(SolutionDir)\bin\$(Platform)\$(Configuration);%(AdditionalLibraryDirectories) + res\exports.def + + + + + Level3 + true + MaxSpeed + true + true + $(SolutionDir)res;$(SolutionDir)source;%(AdditionalIncludeDirectories) + WIN32;WIN32_LEAN_AND_MEAN;NOMINMAX;NDEBUG;%(PreprocessorDefinitions); + 4351;%(DisableSpecificWarnings) + $(IntDir)%(RelativeDir) + MultiThreaded + /std:c++latest %(AdditionalOptions) + + + Windows + false + true + true + $(SolutionDir)\bin\$(Platform)\$(Configuration);%(AdditionalLibraryDirectories) + res\exports.def + + + + + Level3 + true + MaxSpeed + true + true + $(SolutionDir)res;$(SolutionDir)source;%(AdditionalIncludeDirectories) + WIN64;WIN32_LEAN_AND_MEAN;NOMINMAX;NDEBUG;%(PreprocessorDefinitions); + 4351;%(DisableSpecificWarnings) + $(IntDir)%(RelativeDir) + MultiThreaded + /std:c++latest %(AdditionalOptions) + + + Windows + false + true + true + $(SolutionDir)\bin\$(Platform)\$(Configuration);%(AdditionalLibraryDirectories) + res\exports.def + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/msvc/ReShadeFX.vcxproj.filters b/msvc/ReShadeFX.vcxproj.filters new file mode 100644 index 0000000..0ae9ca7 --- /dev/null +++ b/msvc/ReShadeFX.vcxproj.filters @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/msvc/ReShadeFXC.vcxproj b/msvc/ReShadeFXC.vcxproj new file mode 100644 index 0000000..1b5c95c --- /dev/null +++ b/msvc/ReShadeFXC.vcxproj @@ -0,0 +1,167 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + {65640687-0740-4681-B018-17DBF33E061C} + Win32Proj + 10.0.17763.0 + reshade-fxc + + + + Application + true + v141 + Unicode + + + Application + true + v141 + Unicode + + + Application + false + v141 + true + Unicode + + + Application + false + v141 + true + Unicode + + + + + + + + + + fxc + + + fxc + + + fxc + + + fxc + + + + Level3 + Disabled + true + _DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + $(SolutionDir)res;$(SolutionDir)source;%(AdditionalIncludeDirectories) + stdcpplatest + + + Console + true + + + RESHADE_FXC;WIN64;_DEBUG;%(PreprocessorDefinitions) + + + + + Level3 + Disabled + true + _DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + $(SolutionDir)res;$(SolutionDir)source;%(AdditionalIncludeDirectories) + stdcpplatest + + + Console + true + + + RESHADE_FXC;WIN32;_DEBUG;%(PreprocessorDefinitions) + + + + + Level3 + MaxSpeed + true + true + true + NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + $(SolutionDir)res;$(SolutionDir)source;%(AdditionalIncludeDirectories) + stdcpplatest + MultiThreaded + + + Console + true + true + false + + + RESHADE_FXC;WIN64;NDEBUG;%(PreprocessorDefinitions) + + + + + Level3 + MaxSpeed + true + true + true + NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + $(SolutionDir)res;$(SolutionDir)source;%(AdditionalIncludeDirectories) + stdcpplatest + MultiThreaded + + + Console + true + true + false + + + RESHADE_FXC;WIN32;NDEBUG;%(PreprocessorDefinitions) + + + + + {d1c2099b-bec7-4993-8947-01d4a1f7eae2} + + + + + + + + + + \ No newline at end of file diff --git a/msvc/ReShadeFXC.vcxproj.filters b/msvc/ReShadeFXC.vcxproj.filters new file mode 100644 index 0000000..4b67128 --- /dev/null +++ b/msvc/ReShadeFXC.vcxproj.filters @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/msvc/UTF8.cpp b/msvc/UTF8.cpp new file mode 100644 index 0000000..57a362f --- /dev/null +++ b/msvc/UTF8.cpp @@ -0,0 +1,265 @@ +// Copyright 2006 Nemanja Trifunovic + +/* +Permission is hereby granted, free of charge, to any person or organization +obtaining a copy of the software and accompanying documentation covered by +this license (the "Software") to use, reproduce, display, distribute, +execute, and transmit the Software, and to prepare derivative works of the +Software, and to permit third-parties to whom the Software is furnished to +do so, all subject to the following: +The copyright notices in the Software and this entire statement, including +the above license grant, this restriction and the following disclaimer, +must be included in all copies of the Software, in whole or in part, and +all derivative works of the Software, unless such copies or derivative +works are solely in the form of machine-executable object code generated by +a source language processor. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT +SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE +FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +*/ + + +#ifndef UTF8_FOR_CPP_UNCHECKED_H_2675DCD0_9480_4c0c_B92A_CC14C027B731 +#define UTF8_FOR_CPP_UNCHECKED_H_2675DCD0_9480_4c0c_B92A_CC14C027B731 +#endif + +#include "core.h" + +namespace utf8 +{ + namespace unchecked + { + template + octet_iterator append(uint32_t cp, octet_iterator result) + { + if (cp < 0x80) // one octet + *(result++) = static_cast(cp); + else if (cp < 0x800) { // two octets + *(result++) = static_cast((cp >> 6) | 0xc0); + *(result++) = static_cast((cp & 0x3f) | 0x80); + } + else if (cp < 0x10000) { // three octets + *(result++) = static_cast((cp >> 12) | 0xe0); + *(result++) = static_cast(((cp >> 6) & 0x3f) | 0x80); + *(result++) = static_cast((cp & 0x3f) | 0x80); + } + else { // four octets + *(result++) = static_cast((cp >> 18) | 0xf0); + *(result++) = static_cast(((cp >> 12) & 0x3f) | 0x80); + *(result++) = static_cast(((cp >> 6) & 0x3f) | 0x80); + *(result++) = static_cast((cp & 0x3f) | 0x80); + } + return result; + } + + template + output_iterator replace_invalid(octet_iterator start, octet_iterator end, output_iterator out, uint32_t replacement) + { + while (start != end) { + octet_iterator sequence_start = start; + internal::utf_error err_code = utf8::internal::validate_next(start, end); + switch (err_code) { + case internal::UTF8_OK: + for (octet_iterator it = sequence_start; it != start; ++it) + *out++ = *it; + break; + case internal::NOT_ENOUGH_ROOM: + out = utf8::unchecked::append(replacement, out); + start = end; + break; + case internal::INVALID_LEAD: + out = utf8::unchecked::append(replacement, out); + ++start; + break; + case internal::INCOMPLETE_SEQUENCE: + case internal::OVERLONG_SEQUENCE: + case internal::INVALID_CODE_POINT: + out = utf8::unchecked::append(replacement, out); + ++start; + // just one replacement mark for the sequence + while (start != end && utf8::internal::is_trail(*start)) + ++start; + break; + } + } + return out; + } + + template + inline output_iterator replace_invalid(octet_iterator start, octet_iterator end, output_iterator out) + { + static const uint32_t replacement_marker = utf8::internal::mask16(0xfffd); + return utf8::unchecked::replace_invalid(start, end, out, replacement_marker); + } + + template + uint32_t next(octet_iterator& it) + { + uint32_t cp = utf8::internal::mask8(*it); + typename std::iterator_traits::difference_type length = utf8::internal::sequence_length(it); + switch (length) { + case 1: + break; + case 2: + it++; + cp = ((cp << 6) & 0x7ff) + ((*it) & 0x3f); + break; + case 3: + ++it; + cp = ((cp << 12) & 0xffff) + ((utf8::internal::mask8(*it) << 6) & 0xfff); + ++it; + cp += (*it) & 0x3f; + break; + case 4: + ++it; + cp = ((cp << 18) & 0x1fffff) + ((utf8::internal::mask8(*it) << 12) & 0x3ffff); + ++it; + cp += (utf8::internal::mask8(*it) << 6) & 0xfff; + ++it; + cp += (*it) & 0x3f; + break; + } + ++it; + return cp; + } + + template + uint32_t peek_next(octet_iterator it) + { + return utf8::unchecked::next(it); + } + + template + uint32_t prior(octet_iterator& it) + { + while (utf8::internal::is_trail(*(--it))); + octet_iterator temp = it; + return utf8::unchecked::next(temp); + } + + template + void advance(octet_iterator& it, distance_type n) + { + const distance_type zero(0); + if (n < zero) { + // backward + for (distance_type i = n; i < zero; ++i) + utf8::unchecked::prior(it); + } + else { + // forward + for (distance_type i = zero; i < n; ++i) + utf8::unchecked::next(it); + } + } + + template + typename std::iterator_traits::difference_type + distance(octet_iterator first, octet_iterator last) + { + typename std::iterator_traits::difference_type dist; + for (dist = 0; first < last; ++dist) + utf8::unchecked::next(first); + return dist; + } + + template + octet_iterator utf16to8(u16bit_iterator start, u16bit_iterator end, octet_iterator result) + { + while (start != end) { + uint32_t cp = utf8::internal::mask16(*start++); + // Take care of surrogate pairs first + if (utf8::internal::is_lead_surrogate(cp)) { + uint32_t trail_surrogate = utf8::internal::mask16(*start++); + cp = (cp << 10) + trail_surrogate + internal::SURROGATE_OFFSET; + } + result = utf8::unchecked::append(cp, result); + } + return result; + } + + template + u16bit_iterator utf8to16(octet_iterator start, octet_iterator end, u16bit_iterator result) + { + while (start < end) { + uint32_t cp = utf8::unchecked::next(start); + if (cp > 0xffff) { //make a surrogate pair + *result++ = static_cast((cp >> 10) + internal::LEAD_OFFSET); + *result++ = static_cast((cp & 0x3ff) + internal::TRAIL_SURROGATE_MIN); + } + else + *result++ = static_cast(cp); + } + return result; + } + + template + octet_iterator utf32to8(u32bit_iterator start, u32bit_iterator end, octet_iterator result) + { + while (start != end) + result = utf8::unchecked::append(*(start++), result); + + return result; + } + + template + u32bit_iterator utf8to32(octet_iterator start, octet_iterator end, u32bit_iterator result) + { + while (start < end) + (*result++) = utf8::unchecked::next(start); + + return result; + } + + // The iterator class + template + class iterator : public std::iterator { + octet_iterator it; + public: + iterator() {} + explicit iterator(const octet_iterator& octet_it) : it(octet_it) {} + // the default "big three" are OK + octet_iterator base() const { return it; } + uint32_t operator * () const + { + octet_iterator temp = it; + return utf8::unchecked::next(temp); + } + bool operator == (const iterator& rhs) const + { + return (it == rhs.it); + } + bool operator != (const iterator& rhs) const + { + return !(operator == (rhs)); + } + iterator& operator ++ () + { + ::std::advance(it, utf8::internal::sequence_length(it)); + return *this; + } + iterator operator ++ (int) + { + iterator temp = *this; + ::std::advance(it, utf8::internal::sequence_length(it)); + return temp; + } + iterator& operator -- () + { + utf8::unchecked::prior(it); + return *this; + } + iterator operator -- (int) + { + iterator temp = *this; + utf8::unchecked::prior(it); + return temp; + } + }; // class iterator + + } // namespace utf8::unchecked +} // namespace utf8 diff --git a/msvc/angrylion-plus-x.sln b/msvc/angrylion-plus-x.sln index 4e9d4fc..c36dbd2 100644 --- a/msvc/angrylion-plus-x.sln +++ b/msvc/angrylion-plus-x.sln @@ -21,50 +21,220 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "plugin-zilmar", "plugin-zil {13F7DDD7-2282-408A-AEF9-72266E0236DC} = {13F7DDD7-2282-408A-AEF9-72266E0236DC} EndProjectSection EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "reshade", "ReShade.vcxproj", "{0401ADF5-D085-4A3D-95B2-D9B7896BB338}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "reshade-fx", "ReShadeFX.vcxproj", "{D1C2099B-BEC7-4993-8947-01D4A1F7EAE2}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "reshade-fxc", "ReShadeFXC.vcxproj", "{65640687-0740-4681-B018-17DBF33E061C}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "deps", "deps", "{02176CFA-E87C-41C3-AAF5-A8353C33FF11}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "gl3w", "deps\gl3w.vcxproj", "{09C0D610-9B82-40D8-B37E-0D26E3BFF77F}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ImGui", "deps\ImGui.vcxproj", "{9A62233B-0B70-4B48-91E8-35AA666BC32E}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "MinHook", "deps\MinHook.vcxproj", "{783FEDFB-5124-4F8C-87BC-70AA8490266B}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "stb", "deps\stb.vcxproj", "{723BDEF8-4A39-4961-BDAB-54074012FF47}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug App|x64 = Debug App|x64 + Debug App|x86 = Debug App|x86 Debug|x64 = Debug|x64 Debug|x86 = Debug|x86 + Release App|x64 = Release App|x64 + Release App|x86 = Release App|x86 Release|x64 = Release|x64 Release|x86 = Release|x86 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution + {D86C6E84-3371-4B20-8620-A703FF3F2CC5}.Debug App|x64.ActiveCfg = Debug|x64 + {D86C6E84-3371-4B20-8620-A703FF3F2CC5}.Debug App|x64.Build.0 = Debug|x64 + {D86C6E84-3371-4B20-8620-A703FF3F2CC5}.Debug App|x86.ActiveCfg = Debug|Win32 + {D86C6E84-3371-4B20-8620-A703FF3F2CC5}.Debug App|x86.Build.0 = Debug|Win32 {D86C6E84-3371-4B20-8620-A703FF3F2CC5}.Debug|x64.ActiveCfg = Debug|x64 {D86C6E84-3371-4B20-8620-A703FF3F2CC5}.Debug|x64.Build.0 = Debug|x64 {D86C6E84-3371-4B20-8620-A703FF3F2CC5}.Debug|x86.ActiveCfg = Debug|Win32 {D86C6E84-3371-4B20-8620-A703FF3F2CC5}.Debug|x86.Build.0 = Debug|Win32 + {D86C6E84-3371-4B20-8620-A703FF3F2CC5}.Release App|x64.ActiveCfg = Release|x64 + {D86C6E84-3371-4B20-8620-A703FF3F2CC5}.Release App|x64.Build.0 = Release|x64 + {D86C6E84-3371-4B20-8620-A703FF3F2CC5}.Release App|x86.ActiveCfg = Release|Win32 + {D86C6E84-3371-4B20-8620-A703FF3F2CC5}.Release App|x86.Build.0 = Release|Win32 {D86C6E84-3371-4B20-8620-A703FF3F2CC5}.Release|x64.ActiveCfg = Release|x64 {D86C6E84-3371-4B20-8620-A703FF3F2CC5}.Release|x64.Build.0 = Release|x64 {D86C6E84-3371-4B20-8620-A703FF3F2CC5}.Release|x86.ActiveCfg = Release|Win32 {D86C6E84-3371-4B20-8620-A703FF3F2CC5}.Release|x86.Build.0 = Release|Win32 + {13F7DDD7-2282-408A-AEF9-72266E0236DC}.Debug App|x64.ActiveCfg = Debug|x64 + {13F7DDD7-2282-408A-AEF9-72266E0236DC}.Debug App|x64.Build.0 = Debug|x64 + {13F7DDD7-2282-408A-AEF9-72266E0236DC}.Debug App|x86.ActiveCfg = Debug|Win32 + {13F7DDD7-2282-408A-AEF9-72266E0236DC}.Debug App|x86.Build.0 = Debug|Win32 {13F7DDD7-2282-408A-AEF9-72266E0236DC}.Debug|x64.ActiveCfg = Debug|x64 {13F7DDD7-2282-408A-AEF9-72266E0236DC}.Debug|x64.Build.0 = Debug|x64 {13F7DDD7-2282-408A-AEF9-72266E0236DC}.Debug|x86.ActiveCfg = Debug|Win32 {13F7DDD7-2282-408A-AEF9-72266E0236DC}.Debug|x86.Build.0 = Debug|Win32 + {13F7DDD7-2282-408A-AEF9-72266E0236DC}.Release App|x64.ActiveCfg = Release|x64 + {13F7DDD7-2282-408A-AEF9-72266E0236DC}.Release App|x64.Build.0 = Release|x64 + {13F7DDD7-2282-408A-AEF9-72266E0236DC}.Release App|x86.ActiveCfg = Release|Win32 + {13F7DDD7-2282-408A-AEF9-72266E0236DC}.Release App|x86.Build.0 = Release|Win32 {13F7DDD7-2282-408A-AEF9-72266E0236DC}.Release|x64.ActiveCfg = Release|x64 {13F7DDD7-2282-408A-AEF9-72266E0236DC}.Release|x64.Build.0 = Release|x64 {13F7DDD7-2282-408A-AEF9-72266E0236DC}.Release|x86.ActiveCfg = Release|Win32 {13F7DDD7-2282-408A-AEF9-72266E0236DC}.Release|x86.Build.0 = Release|Win32 + {54BDC4EF-2526-47F7-A73E-C1C0FBC0237C}.Debug App|x64.ActiveCfg = Debug|x64 + {54BDC4EF-2526-47F7-A73E-C1C0FBC0237C}.Debug App|x64.Build.0 = Debug|x64 + {54BDC4EF-2526-47F7-A73E-C1C0FBC0237C}.Debug App|x86.ActiveCfg = Debug|Win32 + {54BDC4EF-2526-47F7-A73E-C1C0FBC0237C}.Debug App|x86.Build.0 = Debug|Win32 {54BDC4EF-2526-47F7-A73E-C1C0FBC0237C}.Debug|x64.ActiveCfg = Debug|x64 {54BDC4EF-2526-47F7-A73E-C1C0FBC0237C}.Debug|x64.Build.0 = Debug|x64 {54BDC4EF-2526-47F7-A73E-C1C0FBC0237C}.Debug|x86.ActiveCfg = Debug|Win32 {54BDC4EF-2526-47F7-A73E-C1C0FBC0237C}.Debug|x86.Build.0 = Debug|Win32 + {54BDC4EF-2526-47F7-A73E-C1C0FBC0237C}.Release App|x64.ActiveCfg = Release|x64 + {54BDC4EF-2526-47F7-A73E-C1C0FBC0237C}.Release App|x64.Build.0 = Release|x64 + {54BDC4EF-2526-47F7-A73E-C1C0FBC0237C}.Release App|x86.ActiveCfg = Release|Win32 + {54BDC4EF-2526-47F7-A73E-C1C0FBC0237C}.Release App|x86.Build.0 = Release|Win32 {54BDC4EF-2526-47F7-A73E-C1C0FBC0237C}.Release|x64.ActiveCfg = Release|x64 {54BDC4EF-2526-47F7-A73E-C1C0FBC0237C}.Release|x64.Build.0 = Release|x64 {54BDC4EF-2526-47F7-A73E-C1C0FBC0237C}.Release|x86.ActiveCfg = Release|Win32 {54BDC4EF-2526-47F7-A73E-C1C0FBC0237C}.Release|x86.Build.0 = Release|Win32 + {722AD25D-C281-48FB-9A58-7699E50F8E6D}.Debug App|x64.ActiveCfg = Debug|x64 + {722AD25D-C281-48FB-9A58-7699E50F8E6D}.Debug App|x64.Build.0 = Debug|x64 + {722AD25D-C281-48FB-9A58-7699E50F8E6D}.Debug App|x86.ActiveCfg = Debug|Win32 + {722AD25D-C281-48FB-9A58-7699E50F8E6D}.Debug App|x86.Build.0 = Debug|Win32 {722AD25D-C281-48FB-9A58-7699E50F8E6D}.Debug|x64.ActiveCfg = Debug|x64 {722AD25D-C281-48FB-9A58-7699E50F8E6D}.Debug|x64.Build.0 = Debug|x64 {722AD25D-C281-48FB-9A58-7699E50F8E6D}.Debug|x86.ActiveCfg = Debug|Win32 {722AD25D-C281-48FB-9A58-7699E50F8E6D}.Debug|x86.Build.0 = Debug|Win32 + {722AD25D-C281-48FB-9A58-7699E50F8E6D}.Release App|x64.ActiveCfg = Release|x64 + {722AD25D-C281-48FB-9A58-7699E50F8E6D}.Release App|x64.Build.0 = Release|x64 + {722AD25D-C281-48FB-9A58-7699E50F8E6D}.Release App|x86.ActiveCfg = Release|Win32 + {722AD25D-C281-48FB-9A58-7699E50F8E6D}.Release App|x86.Build.0 = Release|Win32 {722AD25D-C281-48FB-9A58-7699E50F8E6D}.Release|x64.ActiveCfg = Release|x64 {722AD25D-C281-48FB-9A58-7699E50F8E6D}.Release|x64.Build.0 = Release|x64 {722AD25D-C281-48FB-9A58-7699E50F8E6D}.Release|x86.ActiveCfg = Release|Win32 {722AD25D-C281-48FB-9A58-7699E50F8E6D}.Release|x86.Build.0 = Release|Win32 + {0401ADF5-D085-4A3D-95B2-D9B7896BB338}.Debug App|x64.ActiveCfg = Debug App|x64 + {0401ADF5-D085-4A3D-95B2-D9B7896BB338}.Debug App|x64.Build.0 = Debug App|x64 + {0401ADF5-D085-4A3D-95B2-D9B7896BB338}.Debug App|x86.ActiveCfg = Debug App|Win32 + {0401ADF5-D085-4A3D-95B2-D9B7896BB338}.Debug App|x86.Build.0 = Debug App|Win32 + {0401ADF5-D085-4A3D-95B2-D9B7896BB338}.Debug|x64.ActiveCfg = Debug|x64 + {0401ADF5-D085-4A3D-95B2-D9B7896BB338}.Debug|x64.Build.0 = Debug|x64 + {0401ADF5-D085-4A3D-95B2-D9B7896BB338}.Debug|x86.ActiveCfg = Debug|Win32 + {0401ADF5-D085-4A3D-95B2-D9B7896BB338}.Debug|x86.Build.0 = Debug|Win32 + {0401ADF5-D085-4A3D-95B2-D9B7896BB338}.Release App|x64.ActiveCfg = Release App|x64 + {0401ADF5-D085-4A3D-95B2-D9B7896BB338}.Release App|x64.Build.0 = Release App|x64 + {0401ADF5-D085-4A3D-95B2-D9B7896BB338}.Release App|x86.ActiveCfg = Release App|Win32 + {0401ADF5-D085-4A3D-95B2-D9B7896BB338}.Release App|x86.Build.0 = Release App|Win32 + {0401ADF5-D085-4A3D-95B2-D9B7896BB338}.Release|x64.ActiveCfg = Release|x64 + {0401ADF5-D085-4A3D-95B2-D9B7896BB338}.Release|x64.Build.0 = Release|x64 + {0401ADF5-D085-4A3D-95B2-D9B7896BB338}.Release|x86.ActiveCfg = Release|Win32 + {0401ADF5-D085-4A3D-95B2-D9B7896BB338}.Release|x86.Build.0 = Release|Win32 + {D1C2099B-BEC7-4993-8947-01D4A1F7EAE2}.Debug App|x64.ActiveCfg = Debug|x64 + {D1C2099B-BEC7-4993-8947-01D4A1F7EAE2}.Debug App|x64.Build.0 = Debug|x64 + {D1C2099B-BEC7-4993-8947-01D4A1F7EAE2}.Debug App|x86.ActiveCfg = Debug|Win32 + {D1C2099B-BEC7-4993-8947-01D4A1F7EAE2}.Debug App|x86.Build.0 = Debug|Win32 + {D1C2099B-BEC7-4993-8947-01D4A1F7EAE2}.Debug|x64.ActiveCfg = Debug|x64 + {D1C2099B-BEC7-4993-8947-01D4A1F7EAE2}.Debug|x64.Build.0 = Debug|x64 + {D1C2099B-BEC7-4993-8947-01D4A1F7EAE2}.Debug|x86.ActiveCfg = Debug|Win32 + {D1C2099B-BEC7-4993-8947-01D4A1F7EAE2}.Debug|x86.Build.0 = Debug|Win32 + {D1C2099B-BEC7-4993-8947-01D4A1F7EAE2}.Release App|x64.ActiveCfg = Release|x64 + {D1C2099B-BEC7-4993-8947-01D4A1F7EAE2}.Release App|x64.Build.0 = Release|x64 + {D1C2099B-BEC7-4993-8947-01D4A1F7EAE2}.Release App|x86.ActiveCfg = Release|Win32 + {D1C2099B-BEC7-4993-8947-01D4A1F7EAE2}.Release App|x86.Build.0 = Release|Win32 + {D1C2099B-BEC7-4993-8947-01D4A1F7EAE2}.Release|x64.ActiveCfg = Release|x64 + {D1C2099B-BEC7-4993-8947-01D4A1F7EAE2}.Release|x64.Build.0 = Release|x64 + {D1C2099B-BEC7-4993-8947-01D4A1F7EAE2}.Release|x86.ActiveCfg = Release|Win32 + {D1C2099B-BEC7-4993-8947-01D4A1F7EAE2}.Release|x86.Build.0 = Release|Win32 + {65640687-0740-4681-B018-17DBF33E061C}.Debug App|x64.ActiveCfg = Debug|x64 + {65640687-0740-4681-B018-17DBF33E061C}.Debug App|x64.Build.0 = Debug|x64 + {65640687-0740-4681-B018-17DBF33E061C}.Debug App|x86.ActiveCfg = Debug|Win32 + {65640687-0740-4681-B018-17DBF33E061C}.Debug App|x86.Build.0 = Debug|Win32 + {65640687-0740-4681-B018-17DBF33E061C}.Debug|x64.ActiveCfg = Debug|x64 + {65640687-0740-4681-B018-17DBF33E061C}.Debug|x64.Build.0 = Debug|x64 + {65640687-0740-4681-B018-17DBF33E061C}.Debug|x86.ActiveCfg = Debug|Win32 + {65640687-0740-4681-B018-17DBF33E061C}.Debug|x86.Build.0 = Debug|Win32 + {65640687-0740-4681-B018-17DBF33E061C}.Release App|x64.ActiveCfg = Release|x64 + {65640687-0740-4681-B018-17DBF33E061C}.Release App|x64.Build.0 = Release|x64 + {65640687-0740-4681-B018-17DBF33E061C}.Release App|x86.ActiveCfg = Release|Win32 + {65640687-0740-4681-B018-17DBF33E061C}.Release App|x86.Build.0 = Release|Win32 + {65640687-0740-4681-B018-17DBF33E061C}.Release|x64.ActiveCfg = Release|x64 + {65640687-0740-4681-B018-17DBF33E061C}.Release|x64.Build.0 = Release|x64 + {65640687-0740-4681-B018-17DBF33E061C}.Release|x86.ActiveCfg = Release|Win32 + {65640687-0740-4681-B018-17DBF33E061C}.Release|x86.Build.0 = Release|Win32 + {09C0D610-9B82-40D8-B37E-0D26E3BFF77F}.Debug App|x64.ActiveCfg = Debug|x64 + {09C0D610-9B82-40D8-B37E-0D26E3BFF77F}.Debug App|x64.Build.0 = Debug|x64 + {09C0D610-9B82-40D8-B37E-0D26E3BFF77F}.Debug App|x86.ActiveCfg = Debug|Win32 + {09C0D610-9B82-40D8-B37E-0D26E3BFF77F}.Debug App|x86.Build.0 = Debug|Win32 + {09C0D610-9B82-40D8-B37E-0D26E3BFF77F}.Debug|x64.ActiveCfg = Debug|x64 + {09C0D610-9B82-40D8-B37E-0D26E3BFF77F}.Debug|x64.Build.0 = Debug|x64 + {09C0D610-9B82-40D8-B37E-0D26E3BFF77F}.Debug|x86.ActiveCfg = Debug|Win32 + {09C0D610-9B82-40D8-B37E-0D26E3BFF77F}.Debug|x86.Build.0 = Debug|Win32 + {09C0D610-9B82-40D8-B37E-0D26E3BFF77F}.Release App|x64.ActiveCfg = Release|x64 + {09C0D610-9B82-40D8-B37E-0D26E3BFF77F}.Release App|x64.Build.0 = Release|x64 + {09C0D610-9B82-40D8-B37E-0D26E3BFF77F}.Release App|x86.ActiveCfg = Release|Win32 + {09C0D610-9B82-40D8-B37E-0D26E3BFF77F}.Release App|x86.Build.0 = Release|Win32 + {09C0D610-9B82-40D8-B37E-0D26E3BFF77F}.Release|x64.ActiveCfg = Release|x64 + {09C0D610-9B82-40D8-B37E-0D26E3BFF77F}.Release|x64.Build.0 = Release|x64 + {09C0D610-9B82-40D8-B37E-0D26E3BFF77F}.Release|x86.ActiveCfg = Release|Win32 + {09C0D610-9B82-40D8-B37E-0D26E3BFF77F}.Release|x86.Build.0 = Release|Win32 + {9A62233B-0B70-4B48-91E8-35AA666BC32E}.Debug App|x64.ActiveCfg = Debug|x64 + {9A62233B-0B70-4B48-91E8-35AA666BC32E}.Debug App|x64.Build.0 = Debug|x64 + {9A62233B-0B70-4B48-91E8-35AA666BC32E}.Debug App|x86.ActiveCfg = Debug|Win32 + {9A62233B-0B70-4B48-91E8-35AA666BC32E}.Debug App|x86.Build.0 = Debug|Win32 + {9A62233B-0B70-4B48-91E8-35AA666BC32E}.Debug|x64.ActiveCfg = Debug|x64 + {9A62233B-0B70-4B48-91E8-35AA666BC32E}.Debug|x64.Build.0 = Debug|x64 + {9A62233B-0B70-4B48-91E8-35AA666BC32E}.Debug|x86.ActiveCfg = Debug|Win32 + {9A62233B-0B70-4B48-91E8-35AA666BC32E}.Debug|x86.Build.0 = Debug|Win32 + {9A62233B-0B70-4B48-91E8-35AA666BC32E}.Release App|x64.ActiveCfg = Release|x64 + {9A62233B-0B70-4B48-91E8-35AA666BC32E}.Release App|x64.Build.0 = Release|x64 + {9A62233B-0B70-4B48-91E8-35AA666BC32E}.Release App|x86.ActiveCfg = Release|Win32 + {9A62233B-0B70-4B48-91E8-35AA666BC32E}.Release App|x86.Build.0 = Release|Win32 + {9A62233B-0B70-4B48-91E8-35AA666BC32E}.Release|x64.ActiveCfg = Release|x64 + {9A62233B-0B70-4B48-91E8-35AA666BC32E}.Release|x64.Build.0 = Release|x64 + {9A62233B-0B70-4B48-91E8-35AA666BC32E}.Release|x86.ActiveCfg = Release|Win32 + {9A62233B-0B70-4B48-91E8-35AA666BC32E}.Release|x86.Build.0 = Release|Win32 + {783FEDFB-5124-4F8C-87BC-70AA8490266B}.Debug App|x64.ActiveCfg = Debug|x64 + {783FEDFB-5124-4F8C-87BC-70AA8490266B}.Debug App|x64.Build.0 = Debug|x64 + {783FEDFB-5124-4F8C-87BC-70AA8490266B}.Debug App|x86.ActiveCfg = Debug|Win32 + {783FEDFB-5124-4F8C-87BC-70AA8490266B}.Debug App|x86.Build.0 = Debug|Win32 + {783FEDFB-5124-4F8C-87BC-70AA8490266B}.Debug|x64.ActiveCfg = Debug|x64 + {783FEDFB-5124-4F8C-87BC-70AA8490266B}.Debug|x64.Build.0 = Debug|x64 + {783FEDFB-5124-4F8C-87BC-70AA8490266B}.Debug|x86.ActiveCfg = Debug|Win32 + {783FEDFB-5124-4F8C-87BC-70AA8490266B}.Debug|x86.Build.0 = Debug|Win32 + {783FEDFB-5124-4F8C-87BC-70AA8490266B}.Release App|x64.ActiveCfg = Release|x64 + {783FEDFB-5124-4F8C-87BC-70AA8490266B}.Release App|x64.Build.0 = Release|x64 + {783FEDFB-5124-4F8C-87BC-70AA8490266B}.Release App|x86.ActiveCfg = Release|Win32 + {783FEDFB-5124-4F8C-87BC-70AA8490266B}.Release App|x86.Build.0 = Release|Win32 + {783FEDFB-5124-4F8C-87BC-70AA8490266B}.Release|x64.ActiveCfg = Release|x64 + {783FEDFB-5124-4F8C-87BC-70AA8490266B}.Release|x64.Build.0 = Release|x64 + {783FEDFB-5124-4F8C-87BC-70AA8490266B}.Release|x86.ActiveCfg = Release|Win32 + {783FEDFB-5124-4F8C-87BC-70AA8490266B}.Release|x86.Build.0 = Release|Win32 + {723BDEF8-4A39-4961-BDAB-54074012FF47}.Debug App|x64.ActiveCfg = Debug|x64 + {723BDEF8-4A39-4961-BDAB-54074012FF47}.Debug App|x64.Build.0 = Debug|x64 + {723BDEF8-4A39-4961-BDAB-54074012FF47}.Debug App|x86.ActiveCfg = Debug|Win32 + {723BDEF8-4A39-4961-BDAB-54074012FF47}.Debug App|x86.Build.0 = Debug|Win32 + {723BDEF8-4A39-4961-BDAB-54074012FF47}.Debug|x64.ActiveCfg = Debug|x64 + {723BDEF8-4A39-4961-BDAB-54074012FF47}.Debug|x64.Build.0 = Debug|x64 + {723BDEF8-4A39-4961-BDAB-54074012FF47}.Debug|x86.ActiveCfg = Debug|Win32 + {723BDEF8-4A39-4961-BDAB-54074012FF47}.Debug|x86.Build.0 = Debug|Win32 + {723BDEF8-4A39-4961-BDAB-54074012FF47}.Release App|x64.ActiveCfg = Release|x64 + {723BDEF8-4A39-4961-BDAB-54074012FF47}.Release App|x64.Build.0 = Release|x64 + {723BDEF8-4A39-4961-BDAB-54074012FF47}.Release App|x86.ActiveCfg = Release|Win32 + {723BDEF8-4A39-4961-BDAB-54074012FF47}.Release App|x86.Build.0 = Release|Win32 + {723BDEF8-4A39-4961-BDAB-54074012FF47}.Release|x64.ActiveCfg = Release|x64 + {723BDEF8-4A39-4961-BDAB-54074012FF47}.Release|x64.Build.0 = Release|x64 + {723BDEF8-4A39-4961-BDAB-54074012FF47}.Release|x86.ActiveCfg = Release|Win32 + {723BDEF8-4A39-4961-BDAB-54074012FF47}.Release|x86.Build.0 = Release|Win32 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {09C0D610-9B82-40D8-B37E-0D26E3BFF77F} = {02176CFA-E87C-41C3-AAF5-A8353C33FF11} + {9A62233B-0B70-4B48-91E8-35AA666BC32E} = {02176CFA-E87C-41C3-AAF5-A8353C33FF11} + {783FEDFB-5124-4F8C-87BC-70AA8490266B} = {02176CFA-E87C-41C3-AAF5-A8353C33FF11} + {723BDEF8-4A39-4961-BDAB-54074012FF47} = {02176CFA-E87C-41C3-AAF5-A8353C33FF11} + EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {2896B5DF-D1A8-4A2B-BB7D-835C07F05863} EndGlobalSection diff --git a/msvc/deps/ImGui.props b/msvc/deps/ImGui.props new file mode 100644 index 0000000..a064474 --- /dev/null +++ b/msvc/deps/ImGui.props @@ -0,0 +1,10 @@ + + + + + + $(SolutionDir)deps\imgui;%(AdditionalIncludeDirectories) + IMGUI_DEFINE_MATH_OPERATORS;IMGUI_DISABLE_OBSOLETE_FUNCTIONS;%(PreprocessorDefinitions) + + + \ No newline at end of file diff --git a/msvc/deps/ImGui.vcxproj b/msvc/deps/ImGui.vcxproj new file mode 100644 index 0000000..2301e1c --- /dev/null +++ b/msvc/deps/ImGui.vcxproj @@ -0,0 +1,73 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + {9A62233B-0B70-4B48-91E8-35AA666BC32E} + 10.0.17763.0 + + + + StaticLibrary + Unicode + v141 + + + + + + + + + MultiThreadedDebugDLL + Disabled + IMGUI_DISABLE_OBSOLETE_FUNCTIONS;%(PreprocessorDefinitions) + + + + + MultiThreadedDebugDLL + Disabled + IMGUI_DISABLE_OBSOLETE_FUNCTIONS;%(PreprocessorDefinitions) + + + + + MultiThreaded + IMGUI_DISABLE_OBSOLETE_FUNCTIONS;NDEBUG;%(PreprocessorDefinitions) + + + + + MultiThreaded + IMGUI_DISABLE_OBSOLETE_FUNCTIONS;NDEBUG;%(PreprocessorDefinitions) + + + + + + + + + + + + + + \ No newline at end of file diff --git a/msvc/deps/MinHook.props b/msvc/deps/MinHook.props new file mode 100644 index 0000000..04ae54a --- /dev/null +++ b/msvc/deps/MinHook.props @@ -0,0 +1,9 @@ + + + + + + $(SolutionDir)deps\minhook\include;%(AdditionalIncludeDirectories) + + + \ No newline at end of file diff --git a/msvc/deps/MinHook.vcxproj b/msvc/deps/MinHook.vcxproj new file mode 100644 index 0000000..df09cf5 --- /dev/null +++ b/msvc/deps/MinHook.vcxproj @@ -0,0 +1,76 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + {783FEDFB-5124-4F8C-87BC-70AA8490266B} + 10.0.17763.0 + + + + StaticLibrary + Unicode + v141 + + + + + + + + + MultiThreadedDebugDLL + Disabled + + + + + MultiThreadedDebugDLL + Disabled + + + + + MultiThreaded + + + + + MultiThreaded + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/msvc/deps/MinHook.vcxproj.filters b/msvc/deps/MinHook.vcxproj.filters new file mode 100644 index 0000000..6717bfb --- /dev/null +++ b/msvc/deps/MinHook.vcxproj.filters @@ -0,0 +1,44 @@ + + + + + {4f094667-e67f-4105-b94d-5ffd7bc80831} + + + {6d8d397a-3296-469c-bfc1-0138956f951d} + + + + + HDE + + + HDE + + + + + + + + HDE + + + HDE + + + HDE + + + HDE + + + HDE + + + API + + + + + \ No newline at end of file diff --git a/msvc/deps/SPIRV.props b/msvc/deps/SPIRV.props new file mode 100644 index 0000000..fe8ba74 --- /dev/null +++ b/msvc/deps/SPIRV.props @@ -0,0 +1,9 @@ + + + + + + $(SolutionDir)deps\spirv\include\spirv\unified1;%(AdditionalIncludeDirectories) + + + \ No newline at end of file diff --git a/msvc/deps/Windows.props b/msvc/deps/Windows.props new file mode 100644 index 0000000..061e297 --- /dev/null +++ b/msvc/deps/Windows.props @@ -0,0 +1,9 @@ + + + + + + Shlwapi.lib;WinInet.lib;%(AdditionalDependencies) + + + \ No newline at end of file diff --git a/msvc/deps/gl3w.props b/msvc/deps/gl3w.props new file mode 100644 index 0000000..f823818 --- /dev/null +++ b/msvc/deps/gl3w.props @@ -0,0 +1,9 @@ + + + + + + $(SolutionDir)deps\gl3w\include;%(AdditionalIncludeDirectories) + + + \ No newline at end of file diff --git a/msvc/deps/gl3w.vcxproj b/msvc/deps/gl3w.vcxproj new file mode 100644 index 0000000..553aa04 --- /dev/null +++ b/msvc/deps/gl3w.vcxproj @@ -0,0 +1,91 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + {09C0D610-9B82-40D8-B37E-0D26E3BFF77F} + 10.0.17763.0 + + + + StaticLibrary + Unicode + v141 + + + + + + + + + gl3w\include;%(AdditionalIncludeDirectories) + + + + + MultiThreadedDebugDLL + _GDI32_;%(PreprocessorDefinitions) + Disabled + + + + + MultiThreadedDebugDLL + _GDI32_;%(PreprocessorDefinitions) + Disabled + + + + + MultiThreaded + _GDI32_;%(PreprocessorDefinitions) + + + + + MultiThreaded + _GDI32_;%(PreprocessorDefinitions) + + + + + + + + + + + + + $(CleanDependsOn); + CleanGL3WSources; + + + GenerateGL3WSources; + $(BuildDependsOn); + + + + + + + + + \ No newline at end of file diff --git a/msvc/deps/stb.props b/msvc/deps/stb.props new file mode 100644 index 0000000..4a5733a --- /dev/null +++ b/msvc/deps/stb.props @@ -0,0 +1,10 @@ + + + + + + $(SolutionDir)deps\stb;$(SolutionDir)deps\stb_image_dds;%(AdditionalIncludeDirectories) + _CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) + + + \ No newline at end of file diff --git a/msvc/deps/stb.vcxproj b/msvc/deps/stb.vcxproj new file mode 100644 index 0000000..1043de7 --- /dev/null +++ b/msvc/deps/stb.vcxproj @@ -0,0 +1,75 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + {723BDEF8-4A39-4961-BDAB-54074012FF47} + 10.0.17763.0 + + + + StaticLibrary + Unicode + v141 + + + + + + + + + stb;stb_image_dds;%(AdditionalIncludeDirectories) + + + + + MultiThreadedDebugDLL + Disabled + + + + + MultiThreadedDebugDLL + Disabled + + + + + MultiThreaded + NDEBUG;%(PreprocessorDefinitions) + + + + + MultiThreaded + NDEBUG;%(PreprocessorDefinitions) + + + + + + + + + + + + + \ No newline at end of file diff --git a/msvc/deps/stb_image_dds/stb_image_dds.h b/msvc/deps/stb_image_dds/stb_image_dds.h new file mode 100644 index 0000000..dc757db --- /dev/null +++ b/msvc/deps/stb_image_dds/stb_image_dds.h @@ -0,0 +1,586 @@ +// DDS loader +// http://lonesock.net/soil.html + +#ifndef HEADER_STB_IMAGE_DDS_AUGMENTATION +#define HEADER_STB_IMAGE_DDS_AUGMENTATION + +#ifdef __cplusplus +extern "C" { +#endif + +extern int stbi_dds_test_memory(stbi_uc const *buffer, int len); +extern stbi_uc *stbi_dds_load(const char *filename, int *x, int *y, int *comp, int req_comp); +extern stbi_uc *stbi_dds_load_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp); +#ifndef STBI_NO_STDIO +extern int stbi_dds_test_file(FILE *f); +extern stbi_uc *stbi_dds_load_from_file(FILE *f, int *x, int *y, int *comp, int req_comp); +#endif + +#ifdef STB_IMAGE_DDS_IMPLEMENTATION + +typedef struct DDS_HEADER +{ + unsigned int dwSize; + unsigned int dwFlags; + unsigned int dwHeight; + unsigned int dwWidth; + unsigned int dwPitchOrLinearSize; + unsigned int dwDepth; + unsigned int dwMipMapCount; + unsigned int dwReserved1[11]; + + struct DDS_PIXELFORMAT + { + unsigned int dwSize; + unsigned int dwFlags; + unsigned int dwFourCC; + unsigned int dwRGBBitCount; + unsigned int dwRBitMask; + unsigned int dwGBitMask; + unsigned int dwBBitMask; + unsigned int dwAlphaBitMask; + } sPixelFormat; + + struct // DDCAPS2 + { + unsigned int dwCaps1; + unsigned int dwCaps2; + unsigned int dwDDSX; + unsigned int dwReserved; + } sCaps; + + unsigned int dwReserved2; +} DDS_HEADER; +typedef struct DDS_HEADER_DXT10 +{ + unsigned int dxgiFormat; + unsigned int resourceDimension; + unsigned int miscFlag; + unsigned int arraySize; + unsigned int miscFlags2; +} DDS_HEADER_DXT10; + +#define DDSD_CAPS 0x00000001 +#define DDSD_HEIGHT 0x00000002 +#define DDSD_WIDTH 0x00000004 +#define DDSD_PITCH 0x00000008 +#define DDSD_PIXELFORMAT 0x00001000 +#define DDSD_MIPMAPCOUNT 0x00020000 +#define DDSD_LINEARSIZE 0x00080000 +#define DDSD_DEPTH 0x00800000 + +#define DDPF_ALPHAPIXELS 0x00000001 +#define DDPF_ALPHA 0x00000002 +#define DDPF_FOURCC 0x00000004 +#define DDPF_INDEXED 0x00000020 +#define DDPF_RGB 0x00000040 +#define DDPF_YUV 0x00000200 +#define DDPF_LUMINANCE 0x00020000 + +#define DDSCAPS_COMPLEX 0x00000008 +#define DDSCAPS_TEXTURE 0x00001000 +#define DDSCAPS_MIPMAP 0x00400000 + +#define DDSCAPS2_CUBEMAP 0x00000200 +#define DDSCAPS2_CUBEMAP_POSITIVEX 0x00000400 +#define DDSCAPS2_CUBEMAP_NEGATIVEX 0x00000800 +#define DDSCAPS2_CUBEMAP_POSITIVEY 0x00001000 +#define DDSCAPS2_CUBEMAP_NEGATIVEY 0x00002000 +#define DDSCAPS2_CUBEMAP_POSITIVEZ 0x00004000 +#define DDSCAPS2_CUBEMAP_NEGATIVEZ 0x00008000 +#define DDSCAPS2_VOLUME 0x00200000 + +#define MAKEFOURCC(ch0, ch1, ch2, ch3) ((stbi__uint32)(stbi_uc)(ch0) | ((stbi__uint32)(stbi_uc)(ch1) << 8) | ((stbi__uint32)(stbi_uc)(ch2) << 16) | ((stbi__uint32)(stbi_uc)(ch3) << 24 )) + +stbi_inline static int stbi__convert_bit_range(int c, int from_bits, int to_bits) +{ + int b = (1 << (from_bits - 1)) + c * ((1 << to_bits) - 1); + + return (b + (b >> from_bits)) >> from_bits; +} +stbi_inline static void stbi__rgb_888_from_565(unsigned int c, int *r, int *g, int *b) +{ + *r = stbi__convert_bit_range((c >> 11) & 31, 5, 8); + *g = stbi__convert_bit_range((c >> 05) & 63, 6, 8); + *b = stbi__convert_bit_range((c >> 00) & 31, 5, 8); +} +void stbi__dxt_decode_DXT1_block(unsigned char uncompressed[16 * 4], unsigned char compressed[8]) +{ + int next_bit = 4 * 8; + int i, r, g, b; + int c0, c1; + unsigned char decode_colors[4 * 4]; + // find the 2 primary colors + c0 = compressed[0] + (compressed[1] << 8); + c1 = compressed[2] + (compressed[3] << 8); + stbi__rgb_888_from_565(c0, &r, &g, &b); + decode_colors[0] = r; + decode_colors[1] = g; + decode_colors[2] = b; + decode_colors[3] = 255; + stbi__rgb_888_from_565(c1, &r, &g, &b); + decode_colors[4] = r; + decode_colors[5] = g; + decode_colors[6] = b; + decode_colors[7] = 255; + if (c0 > c1) + { + // no alpha, 2 interpolated colors + decode_colors[8] = (2 * decode_colors[0] + decode_colors[4]) / 3; + decode_colors[9] = (2 * decode_colors[1] + decode_colors[5]) / 3; + decode_colors[10] = (2 * decode_colors[2] + decode_colors[6]) / 3; + decode_colors[11] = 255; + decode_colors[12] = (decode_colors[0] + 2 * decode_colors[4]) / 3; + decode_colors[13] = (decode_colors[1] + 2 * decode_colors[5]) / 3; + decode_colors[14] = (decode_colors[2] + 2 * decode_colors[6]) / 3; + decode_colors[15] = 255; + } + else + { + // 1 interpolated color, alpha + decode_colors[8] = (decode_colors[0] + decode_colors[4]) / 2; + decode_colors[9] = (decode_colors[1] + decode_colors[5]) / 2; + decode_colors[10] = (decode_colors[2] + decode_colors[6]) / 2; + decode_colors[11] = 255; + decode_colors[12] = 0; + decode_colors[13] = 0; + decode_colors[14] = 0; + decode_colors[15] = 0; + } + // decode the block + for (i = 0; i < 16 * 4; i += 4) + { + int idx = ((compressed[next_bit >> 3] >> (next_bit & 7)) & 3) * 4; + next_bit += 2; + uncompressed[i + 0] = decode_colors[idx + 0]; + uncompressed[i + 1] = decode_colors[idx + 1]; + uncompressed[i + 2] = decode_colors[idx + 2]; + uncompressed[i + 3] = decode_colors[idx + 3]; + } + // done +} +void stbi__dxt_decode_DXT23_alpha_block(unsigned char uncompressed[16 * 4], unsigned char compressed[8]) +{ + int i, next_bit = 0; + + for (i = 3; i < 16 * 4; i += 4) + { + uncompressed[i] = stbi__convert_bit_range((compressed[next_bit >> 3] >> (next_bit & 7)) & 15, 4, 8); + next_bit += 4; + } +} +void stbi__dxt_decode_DXT45_alpha_block(unsigned char uncompressed[16 * 4], unsigned char compressed[8]) +{ + int i, next_bit = 8 * 2; + unsigned char decode_alpha[8]; + // each alpha value gets 3 bits, and the 1st 2 bytes are the range + decode_alpha[0] = compressed[0]; + decode_alpha[1] = compressed[1]; + if (decode_alpha[0] > decode_alpha[1]) + { + // 6 step intermediate + decode_alpha[2] = (6 * decode_alpha[0] + 1 * decode_alpha[1]) / 7; + decode_alpha[3] = (5 * decode_alpha[0] + 2 * decode_alpha[1]) / 7; + decode_alpha[4] = (4 * decode_alpha[0] + 3 * decode_alpha[1]) / 7; + decode_alpha[5] = (3 * decode_alpha[0] + 4 * decode_alpha[1]) / 7; + decode_alpha[6] = (2 * decode_alpha[0] + 5 * decode_alpha[1]) / 7; + decode_alpha[7] = (1 * decode_alpha[0] + 6 * decode_alpha[1]) / 7; + } + else + { + // 4 step intermediate, pluss full and none + decode_alpha[2] = (4 * decode_alpha[0] + 1 * decode_alpha[1]) / 5; + decode_alpha[3] = (3 * decode_alpha[0] + 2 * decode_alpha[1]) / 5; + decode_alpha[4] = (2 * decode_alpha[0] + 3 * decode_alpha[1]) / 5; + decode_alpha[5] = (1 * decode_alpha[0] + 4 * decode_alpha[1]) / 5; + decode_alpha[6] = 0; + decode_alpha[7] = 255; + } + for (i = 3; i < 16 * 4; i += 4) + { + int idx = 0, bit; + bit = (compressed[next_bit >> 3] >> (next_bit & 7)) & 1; + idx += bit << 0; + ++next_bit; + bit = (compressed[next_bit >> 3] >> (next_bit & 7)) & 1; + idx += bit << 1; + ++next_bit; + bit = (compressed[next_bit >> 3] >> (next_bit & 7)) & 1; + idx += bit << 2; + ++next_bit; + uncompressed[i] = decode_alpha[idx & 7]; + } + // done +} +void stbi__dxt_decode_DXT_color_block(unsigned char uncompressed[16 * 4], unsigned char compressed[8]) +{ + int next_bit = 4 * 8; + int i, r, g, b; + int c0, c1; + unsigned char decode_colors[4 * 3]; + // find the 2 primary colors + c0 = compressed[0] + (compressed[1] << 8); + c1 = compressed[2] + (compressed[3] << 8); + stbi__rgb_888_from_565(c0, &r, &g, &b); + decode_colors[0] = r; + decode_colors[1] = g; + decode_colors[2] = b; + stbi__rgb_888_from_565(c1, &r, &g, &b); + decode_colors[3] = r; + decode_colors[4] = g; + decode_colors[5] = b; + // Like DXT1, but no choicees: + // no alpha, 2 interpolated colors + decode_colors[6] = (2 * decode_colors[0] + decode_colors[3]) / 3; + decode_colors[7] = (2 * decode_colors[1] + decode_colors[4]) / 3; + decode_colors[8] = (2 * decode_colors[2] + decode_colors[5]) / 3; + decode_colors[9] = (decode_colors[0] + 2 * decode_colors[3]) / 3; + decode_colors[10] = (decode_colors[1] + 2 * decode_colors[4]) / 3; + decode_colors[11] = (decode_colors[2] + 2 * decode_colors[5]) / 3; + // decode the block + for (i = 0; i < 16 * 4; i += 4) + { + int idx = ((compressed[next_bit >> 3] >> (next_bit & 7)) & 3) * 3; + next_bit += 2; + uncompressed[i + 0] = decode_colors[idx + 0]; + uncompressed[i + 1] = decode_colors[idx + 1]; + uncompressed[i + 2] = decode_colors[idx + 2]; + } + // done +} + +static int stbi__dds_test(stbi__context *s) +{ + // Check the magic number + if (stbi__get8(s) != 'D') + { + return 0; + } + if (stbi__get8(s) != 'D') + { + return 0; + } + if (stbi__get8(s) != 'S') + { + return 0; + } + if (stbi__get8(s) != ' ') + { + return 0; + } + + return 1; +} + +static stbi_uc *stbi__dds_load(stbi__context *s, int *x, int *y, int *comp, int req_comp) +{ + // All variables go up front + stbi_uc *dds_data = NULL; + stbi_uc block[16 * 4]; + stbi_uc compressed[8]; + int flags, DXT_family; + int has_alpha, has_mipmap; + int is_compressed, cubemap_faces; + int block_pitch, num_blocks; + int i, sz, cf; + DDS_HEADER header; + DDS_HEADER_DXT10 header2; + + // Check the magic number + if (!stbi__dds_test(s)) + { + return NULL; + } + + // Load the header + stbi__getn(s, (stbi_uc *)(&header), sizeof(DDS_HEADER)); + + if (header.dwSize != 124) + { + return NULL; + } + + // Checks + flags = DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH | DDSD_PIXELFORMAT; + + if ((header.dwFlags & flags) != flags) + { + return NULL; + } + + if (header.sPixelFormat.dwSize != 32) + { + return NULL; + } + + flags = DDPF_FOURCC | DDPF_RGB | DDPF_LUMINANCE | DDPF_YUV; + + if ((header.sPixelFormat.dwFlags & flags) == 0) + { + return NULL; + } + + if ((header.sCaps.dwCaps1 & DDSCAPS_TEXTURE) == 0) + { + return NULL; + } + + // Get the image information + *x = s->img_x = header.dwWidth; + *y = s->img_y = header.dwHeight; + *comp = s->img_n = 4; + + is_compressed = (header.sPixelFormat.dwFlags & DDPF_FOURCC) / DDPF_FOURCC; + has_alpha = (header.sPixelFormat.dwFlags & DDPF_ALPHAPIXELS) / DDPF_ALPHAPIXELS; + has_mipmap = (header.sCaps.dwCaps1 & DDSCAPS_MIPMAP) && (header.dwMipMapCount > 1); + cubemap_faces = (header.sCaps.dwCaps2 & DDSCAPS2_CUBEMAP) / DDSCAPS2_CUBEMAP; + + // Cubemaps need to have square faces + cubemap_faces &= (s->img_x == s->img_y); + cubemap_faces *= 5; + cubemap_faces += 1; + + block_pitch = (s->img_x + 3) >> 2; + num_blocks = block_pitch * ((s->img_y + 3) >> 2); + + if (is_compressed) + { +#pragma region Compressed + if (header.sPixelFormat.dwFourCC & MAKEFOURCC('D', 'X', '1', '0')) + { + stbi__getn(s, (stbi_uc *)(&header2), sizeof(DDS_HEADER_DXT10)); + } + + /* compressed */ + // note: header.sPixelFormat.dwFourCC is something like (('D'<<0)|('X'<<8)|('T'<<16)|('1'<<24)) + DXT_family = 1 + (header.sPixelFormat.dwFourCC >> 24) - '1'; + if ((DXT_family < 1) || (DXT_family > 5)) return NULL; + /* check the expected size...oops, nevermind... + those non-compliant writers leave + dwPitchOrLinearSize == 0 */ + // passed all the tests, get the RAM for decoding + sz = (s->img_x)*(s->img_y) * 4 * cubemap_faces; + dds_data = (unsigned char*)malloc(sz); + /* do this once for each face */ + for (cf = 0; cf < cubemap_faces; ++cf) + { + // now read and decode all the blocks + for (i = 0; i < num_blocks; ++i) + { + // where are we? + int bx, by, bw = 4, bh = 4; + int ref_x = 4 * (i % block_pitch); + int ref_y = 4 * (i / block_pitch); + // get the next block's worth of compressed data, and decompress it + if (DXT_family == 1) + { + // DXT1 + stbi__getn(s, compressed, 8); + stbi__dxt_decode_DXT1_block(block, compressed); + } + else if (DXT_family < 4) + { + // DXT2/3 + stbi__getn(s, compressed, 8); + stbi__dxt_decode_DXT23_alpha_block(block, compressed); + stbi__getn(s, compressed, 8); + stbi__dxt_decode_DXT_color_block(block, compressed); + } + else + { + // DXT4/5 + stbi__getn(s, compressed, 8); + stbi__dxt_decode_DXT45_alpha_block(block, compressed); + stbi__getn(s, compressed, 8); + stbi__dxt_decode_DXT_color_block(block, compressed); + } + // is this a partial block? + if (ref_x + 4 > (int)s->img_x) + { + bw = s->img_x - ref_x; + } + if (ref_y + 4 > (int)s->img_y) + { + bh = s->img_y - ref_y; + } + // now drop our decompressed data into the buffer + for (by = 0; by < bh; ++by) + { + int idx = 4 * ((ref_y + by + cf*s->img_x)*s->img_x + ref_x); + for (bx = 0; bx < bw * 4; ++bx) + { + + dds_data[idx + bx] = block[by * 16 + bx]; + } + } + } + /* done reading and decoding the main image... + skip MIPmaps if present */ + if (has_mipmap) + { + int block_size = 16; + if (DXT_family == 1) + { + block_size = 8; + } + for (i = 1; i < (int)header.dwMipMapCount; ++i) + { + int mx = s->img_x >> (i + 2); + int my = s->img_y >> (i + 2); + if (mx < 1) + { + mx = 1; + } + if (my < 1) + { + my = 1; + } + stbi__skip(s, mx*my*block_size); + } + } + }/* per cubemap face */ +#pragma endregion + } + else + { +#pragma region Uncompressed + DXT_family = 0; + + if (header.sPixelFormat.dwRGBBitCount != 0) + { + s->img_n = header.sPixelFormat.dwRGBBitCount / 8; + } + else + { + s->img_n = 3; + if (has_alpha) + { + s->img_n = 4; + } + } + *comp = s->img_n; + sz = s->img_x*s->img_y*s->img_n*cubemap_faces; + dds_data = (unsigned char*)malloc(sz); + /* do this once for each face */ + for (cf = 0; cf < cubemap_faces; ++cf) + { + /* read the main image for this face */ + stbi__getn(s, &dds_data[cf*s->img_x*s->img_y*s->img_n], s->img_x*s->img_y*s->img_n); + /* done reading and decoding the main image... + skip MIPmaps if present */ + if (has_mipmap) + { + for (i = 1; i < (int)header.dwMipMapCount; ++i) + { + int mx = s->img_x >> i; + int my = s->img_y >> i; + if (mx < 1) + { + mx = 1; + } + if (my < 1) + { + my = 1; + } + stbi__skip(s, mx*my*s->img_n); + } + } + } + if (s->img_n >= 3) + { + /* data was BGR, I need it RGB */ + for (i = 0; i < sz; i += s->img_n) + { + unsigned char temp = dds_data[i]; + dds_data[i] = dds_data[i + 2]; + dds_data[i + 2] = temp; + } + } +#pragma endregion + } + + // Finished decompressing into RGBA, adjust the y size if we have a cubemap + s->img_y *= cubemap_faces; + *y = s->img_y; + + // Test if all alpha values are 255 (i.e. no transparency) + has_alpha = 0; + + if (s->img_n == 4) + { + for (i = 3; (i < sz) && (has_alpha == 0); i += 4) + { + has_alpha |= (dds_data[i] < 255); + } + } + + if ((req_comp <= 4) && (req_comp >= 1)) + { + if (req_comp != s->img_n) + { + dds_data = stbi__convert_format(dds_data, s->img_n, req_comp, s->img_x, s->img_y); + *comp = s->img_n; + } + } + else + { + if ((has_alpha == 0) && (s->img_n == 4)) + { + dds_data = stbi__convert_format(dds_data, 4, 3, s->img_x, s->img_y); + *comp = 3; + } + } + + return dds_data; +} + +#ifndef STBI_NO_STDIO +int stbi_dds_test_file(FILE *f) +{ + stbi__context s; + int r, n = ftell(f); + stbi__start_file(&s, f); + r = stbi__dds_test(&s); + fseek(f, n, SEEK_SET); + return r; +} +#endif +int stbi_dds_test_memory(stbi_uc const *buffer, int len) +{ + stbi__context s; + stbi__start_mem(&s, buffer, len); + return stbi__dds_test(&s); +} + +#ifndef STBI_NO_STDIO +stbi_uc *stbi_dds_load_from_file(FILE *f, int *x, int *y, int *comp, int req_comp) +{ + stbi__context s; + stbi__start_file(&s, f); + return stbi__dds_load(&s, x, y, comp, req_comp); +} +stbi_uc *stbi_dds_load(const char *filename, int *x, int *y, int *comp, int req_comp) +{ + stbi_uc *data; + FILE *f = stbi__fopen(filename, "rb"); + if (!f) return NULL; + data = stbi_dds_load_from_file(f, x, y, comp, req_comp); + fclose(f); + return data; +} +#endif + +stbi_uc *stbi_dds_load_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp) +{ + stbi__context s; + stbi__start_mem(&s, buffer, len); + return stbi__dds_load(&s, x, y, comp, req_comp); +} + +#endif + +#ifdef __cplusplus +} +#endif + +#endif // HEADER_STB_IMAGE_DDS_AUGMENTATION diff --git a/msvc/deps/stb_impl.c b/msvc/deps/stb_impl.c new file mode 100644 index 0000000..e8053f9 --- /dev/null +++ b/msvc/deps/stb_impl.c @@ -0,0 +1,9 @@ +#define STB_IMAGE_IMPLEMENTATION +#define STB_IMAGE_DDS_IMPLEMENTATION +#define STB_IMAGE_WRITE_IMPLEMENTATION +#define STB_IMAGE_RESIZE_IMPLEMENTATION + +#include "stb_image.h" +#include "stb_image_dds.h" +#include "stb_image_write.h" +#include "stb_image_resize.h" diff --git a/msvc/deps/utfcpp.props b/msvc/deps/utfcpp.props new file mode 100644 index 0000000..ef8b303 --- /dev/null +++ b/msvc/deps/utfcpp.props @@ -0,0 +1,9 @@ + + + + + + $(SolutionDir)deps\utfcpp\source;%(AdditionalIncludeDirectories) + + + \ No newline at end of file diff --git a/msvc/res/exports.def b/msvc/res/exports.def new file mode 100644 index 0000000..117adc9 --- /dev/null +++ b/msvc/res/exports.def @@ -0,0 +1,433 @@ +EXPORTS + ; d3d9.dll + Direct3DCreate9 @37 + Direct3DCreate9Ex @38 + + D3DPERF_BeginEvent @27 + D3DPERF_EndEvent @28 + D3DPERF_GetStatus @29 + D3DPERF_QueryRepeatFrame @30 + D3DPERF_SetMarker @31 + D3DPERF_SetOptions @32 + D3DPERF_SetRegion @33 + + ; d3d10.dll + D3D10CreateDevice @4 + D3D10CreateDeviceAndSwapChain @5 + + ; d3d10_1.dll + D3D10CreateDevice1 + D3D10CreateDeviceAndSwapChain1 + + ; d3d11.dll + D3D11CreateDevice @22 + D3D11CreateDeviceAndSwapChain @23 + + ; d3d12.dll + D3D12CreateDevice @101 + D3D12GetDebugInterface @102 + D3D12CreateRootSignatureDeserializer @107 + D3D12CreateVersionedRootSignatureDeserializer @108 + D3D12EnableExperimentalFeatures @110 + D3D12SerializeRootSignature @115 + D3D12SerializeVersionedRootSignature @116 + + ; dxgi.dll + CreateDXGIFactory @10 + CreateDXGIFactory1 @11 + CreateDXGIFactory2 @12 + + DXGIDumpJournal + DXGIReportAdapterConfiguration + DXGID3D10CreateDevice @13 + DXGID3D10CreateLayeredDevice @14 + DXGID3D10GetLayeredDeviceSize @15 + DXGID3D10RegisterLayers + + ; opengl32.dll + glAccum + glAlphaFunc + glAreTexturesResident + glArrayElement + glBegin + glBindTexture + glBitmap + glBlendFunc + glCallList + glCallLists + glClear + glClearAccum + glClearColor + glClearDepth + glClearIndex + glClearStencil + glClipPlane + glColor3b + glColor3bv + glColor3d + glColor3dv + glColor3f + glColor3fv + glColor3i + glColor3iv + glColor3s + glColor3sv + glColor3ub + glColor3ubv + glColor3ui + glColor3uiv + glColor3us + glColor3usv + glColor4b + glColor4bv + glColor4d + glColor4dv + glColor4f + glColor4fv + glColor4i + glColor4iv + glColor4s + glColor4sv + glColor4ub + glColor4ubv + glColor4ui + glColor4uiv + glColor4us + glColor4usv + glColorMask + glColorMaterial + glColorPointer + glCopyPixels + glCopyTexImage1D + glCopyTexImage2D + glCopyTexSubImage1D + glCopyTexSubImage2D + glCullFace + glDeleteLists + glDeleteTextures + glDepthFunc + glDepthMask + glDepthRange + glDisable + glDisableClientState + glDrawArrays + glDrawBuffer + glDrawElements + glDrawPixels + glEdgeFlag + glEdgeFlagPointer + glEdgeFlagv + glEnable + glEnableClientState + glEnd + glEndList + glEvalCoord1d + glEvalCoord1dv + glEvalCoord1f + glEvalCoord1fv + glEvalCoord2d + glEvalCoord2dv + glEvalCoord2f + glEvalCoord2fv + glEvalMesh1 + glEvalMesh2 + glEvalPoint1 + glEvalPoint2 + glFeedbackBuffer + glFinish + glFlush + glFogf + glFogfv + glFogi + glFogiv + glFrontFace + glFrustum + glGenLists + glGenTextures + glGetBooleanv + glGetClipPlane + glGetDoublev + glGetError + glGetFloatv + glGetIntegerv + glGetLightfv + glGetLightiv + glGetMapdv + glGetMapfv + glGetMapiv + glGetMaterialfv + glGetMaterialiv + glGetPixelMapfv + glGetPixelMapuiv + glGetPixelMapusv + glGetPointerv + glGetPolygonStipple + glGetString + glGetTexEnvfv + glGetTexEnviv + glGetTexGendv + glGetTexGenfv + glGetTexGeniv + glGetTexImage + glGetTexLevelParameterfv + glGetTexLevelParameteriv + glGetTexParameterfv + glGetTexParameteriv + glHint + glIndexMask + glIndexPointer + glIndexd + glIndexdv + glIndexf + glIndexfv + glIndexi + glIndexiv + glIndexs + glIndexsv + glIndexub + glIndexubv + glInitNames + glInterleavedArrays + glIsEnabled + glIsList + glIsTexture + glLightModelf + glLightModelfv + glLightModeli + glLightModeliv + glLightf + glLightfv + glLighti + glLightiv + glLineStipple + glLineWidth + glListBase + glLoadIdentity + glLoadMatrixd + glLoadMatrixf + glLoadName + glLogicOp + glMap1d + glMap1f + glMap2d + glMap2f + glMapGrid1d + glMapGrid1f + glMapGrid2d + glMapGrid2f + glMaterialf + glMaterialfv + glMateriali + glMaterialiv + glMatrixMode + glMultMatrixd + glMultMatrixf + glNewList + glNormal3b + glNormal3bv + glNormal3d + glNormal3dv + glNormal3f + glNormal3fv + glNormal3i + glNormal3iv + glNormal3s + glNormal3sv + glNormalPointer + glOrtho + glPassThrough + glPixelMapfv + glPixelMapuiv + glPixelMapusv + glPixelStoref + glPixelStorei + glPixelTransferf + glPixelTransferi + glPixelZoom + glPointSize + glPolygonMode + glPolygonOffset + glPolygonStipple + glPopAttrib + glPopClientAttrib + glPopMatrix + glPopName + glPrioritizeTextures + glPushAttrib + glPushClientAttrib + glPushMatrix + glPushName + glRasterPos2d + glRasterPos2dv + glRasterPos2f + glRasterPos2fv + glRasterPos2i + glRasterPos2iv + glRasterPos2s + glRasterPos2sv + glRasterPos3d + glRasterPos3dv + glRasterPos3f + glRasterPos3fv + glRasterPos3i + glRasterPos3iv + glRasterPos3s + glRasterPos3sv + glRasterPos4d + glRasterPos4dv + glRasterPos4f + glRasterPos4fv + glRasterPos4i + glRasterPos4iv + glRasterPos4s + glRasterPos4sv + glReadBuffer + glReadPixels + glRectd + glRectdv + glRectf + glRectfv + glRecti + glRectiv + glRects + glRectsv + glRenderMode + glRotated + glRotatef + glScaled + glScalef + glScissor + glSelectBuffer + glShadeModel + glStencilFunc + glStencilMask + glStencilOp + glTexCoord1d + glTexCoord1dv + glTexCoord1f + glTexCoord1fv + glTexCoord1i + glTexCoord1iv + glTexCoord1s + glTexCoord1sv + glTexCoord2d + glTexCoord2dv + glTexCoord2f + glTexCoord2fv + glTexCoord2i + glTexCoord2iv + glTexCoord2s + glTexCoord2sv + glTexCoord3d + glTexCoord3dv + glTexCoord3f + glTexCoord3fv + glTexCoord3i + glTexCoord3iv + glTexCoord3s + glTexCoord3sv + glTexCoord4d + glTexCoord4dv + glTexCoord4f + glTexCoord4fv + glTexCoord4i + glTexCoord4iv + glTexCoord4s + glTexCoord4sv + glTexCoordPointer + glTexEnvf + glTexEnvfv + glTexEnvi + glTexEnviv + glTexGend + glTexGendv + glTexGenf + glTexGenfv + glTexGeni + glTexGeniv + glTexImage1D + glTexImage2D + glTexParameterf + glTexParameterfv + glTexParameteri + glTexParameteriv + glTexSubImage1D + glTexSubImage2D + glTranslated + glTranslatef + glVertex2d + glVertex2dv + glVertex2f + glVertex2fv + glVertex2i + glVertex2iv + glVertex2s + glVertex2sv + glVertex3d + glVertex3dv + glVertex3f + glVertex3fv + glVertex3i + glVertex3iv + glVertex3s + glVertex3sv + glVertex4d + glVertex4dv + glVertex4f + glVertex4fv + glVertex4i + glVertex4iv + glVertex4s + glVertex4sv + glVertexPointer + glViewport + + wglChoosePixelFormat + wglCopyContext + wglCreateContext + wglCreateLayerContext + wglDeleteContext + wglDescribeLayerPlane + wglDescribePixelFormat + wglGetCurrentContext + wglGetCurrentDC + wglGetLayerPaletteEntries + wglGetPixelFormat + wglGetProcAddress + wglMakeCurrent + wglRealizeLayerPalette + wglSetLayerPaletteEntries + wglSetPixelFormat + wglShareLists + wglSwapBuffers + wglSwapLayerBuffers + wglSwapMultipleBuffers + wglUseFontBitmapsA + wglUseFontBitmapsW + wglUseFontOutlinesA + wglUseFontOutlinesW + + ; user32.dll + RegisterClassA = HookRegisterClassA + RegisterClassW = HookRegisterClassW + RegisterClassExA = HookRegisterClassExA + RegisterClassExW = HookRegisterClassExW + RegisterRawInputDevices = HookRegisterRawInputDevices + GetMessageA = HookGetMessageA + GetMessageW = HookGetMessageW + PeekMessageA = HookPeekMessageA + PeekMessageW = HookPeekMessageW + PostMessageA = HookPostMessageA + PostMessageW = HookPostMessageW + GetCursorPos = HookGetCursorPosition + SetCursorPos = HookSetCursorPosition + + ; ws2_32.dll + send = HookSend @19 + sendto = HookSendTo @20 + recv = HookRecv @16 + recvfrom = HookRecvFrom @17 + WSASend = HookWSASend @96 + WSASendTo = HookWSASendTo @99 + WSARecv = HookWSARecv @91 + WSARecvFrom = HookWSARecvFrom @93 diff --git a/msvc/res/resource.h b/msvc/res/resource.h new file mode 100644 index 0000000000000000000000000000000000000000..80c901b981fe1618e33a426398017623bc0c405a GIT binary patch literal 1354 zcmb7^+e*Vg5QhJ2!FLFFDcIKIb#1EzdkBpYy$aekQW1r;@qqa1>NlGP72373Y<72; z{bqLd-+X@5RMQ)+uYppv)lo$6yIEf?g{)$|(>Aq%Z%u)Enrft6kDAdl&9Plzo2k&X z?pd|8O$&6Zi5}=V_BrDi+Z4+PuY%Qtb%y1h+0Ks$39QolMz8qftSnZ+Y~*}xtTM83 z%h8Nw@dEO0YsiUCl&DLK$!W-0>})C7#`ySJLv=M=gjM7g6^nEQE&7x8Gm?>az2uMz zpOSaDR;J%++;e)vduJ819F2VF z$dyh4?tY>dr&rTnhAIoRXZn3|$}OD}XEZH6Gd}7@cg#ol&K$FR38nI;_?NuE zoj)emgq%b7rb41#Fgrtg?ZruL$Af2&DYc3BfW2L(v6yd9$5>3u%M_n3VyEos=#m^# zr}&sX39}YfQ+^Xd!u)r&0%}{=(b{BFf6v{w{VROj0OizOTmGJw^jkAJ%(x? z4u|9Hncdm_?|&}4{&~nsRwAj%REDyYds*>LWR0$iZY3KT$c(Royyc1HM#eJdU7}wj zN9dMFwcF4d(vJAj*`~5!Oh#VID^|%mnMOOOcgED)PK zNK4w%k)l-OtMsKQCA3A_b*ajCG%bE>l1EdeZR;zB(d8`>CA0Zp~(rB8n_tWCT z*tdC;B@JDKug7H08V~!tyuj^|S^oS!c-UxR7787W8EJ)*H$07GZd>Uv zo;o(&0Nc5ECaxaOwl|@%i;cegIEt;poki@?s>PkD{5xR^Q@(H-n8;Az0GM%SU3iqH7dPw8ak{5~4XY?J;jaqQj|R=NH8 zlGWB-DI$~S-oo{q3QrfCU1fMi9y^cJ!mN|UoT2f1iaP`6RAc>D8Q#vrDAAez0){WR Ay8r+H literal 0 HcmV?d00001 diff --git a/msvc/res/resource.rc2 b/msvc/res/resource.rc2 new file mode 100644 index 0000000000000000000000000000000000000000..a025b78b91481e7f8289da611796462e2bc9f545 GIT binary patch literal 2846 zcmd6pU2hUm5QgX4#Q$(YFVv((ijDVBKEz^afI_|6AW+0$A%Kl0{vrRQ`phf`_AH3# zg*BvmcF)JmJMX+Rr$4?Q$yn~BFBADB0~v~wjC2_1vS4M(T2|gO_gEg;-*fq2e~M7C z`O&4~@bf;Bj3sDadQAHRU+9 zSJ%|$?zN`buB~xxpDxO^VJKO=POk9LkP@@!(oXq$NL-$&3Vao$N<_*@OD>FI%|ON2 z)G_9Cd#7@)I=wZ8-f}V`(y}NA)m8l4r=2{72k#*=hBTvP%ba2;W9e&vBWd@USw5l` zCe*@kkDr`;mK;`NoI`46U}!_FH1MdI&@m$OZJu4zK#937vo9o{^3s;K;8vU+_|X$3 z{KTF%Dr~_+k=1Z-p552@xiFP=c`BYF)~e<;M(42Yn5~21+_I7Z$&&ilzA(b;nD~dB zxP$-csuRSg^xQ6Mo7JS%oSeau{?6nb&$y@fy?)$p(eLV@p*qniGZ}DC<)h4`2gWbN z+2d=-{IRX}iDhctY;Lp3xV=3;hIW1IVLy!RhLs<|SsQI>MOWe7tMPDOSB7#x7w*G; zI)*05TlC?cRlsiMcE=Ff{qsn|zH)dxbr6rhJF)D{$crj!L=4MyW;Ac%ij!~TERQ-) zQAw8f0v0BWo6`&X8d^=xaX288{UCgbXvukj_jNr*P?1)Cn=^BIo26}lf`SOHIn#i7-tq8xE)jV$Ga zH?5bx#jO7WXcurceqc`3Ic0~lZlI$gKB1z=mf6GEZLD&t9#kiuZT(8tZ-`XAWy!dz z!sk5fxVu<=iJA^a=8-%ktBUyFv8vvF$7;xOs7gnDT~cL9j<;vRTc$&v=j*Kh&HWPA fLK|y(11wNRFIA;_L&vv|-U+AHVRFAfyTSbvRl|CL literal 0 HcmV?d00001 diff --git a/msvc/res/shader_copy_ps.hlsl b/msvc/res/shader_copy_ps.hlsl new file mode 100644 index 0000000..6f5a644 --- /dev/null +++ b/msvc/res/shader_copy_ps.hlsl @@ -0,0 +1,8 @@ +Texture2D t0 : register(t0); +SamplerState s0 : register(s0); + +void main(float4 vpos : SV_POSITION, float2 uv : TEXCOORD0, out float4 col : SV_TARGET) +{ + col = t0.Sample(s0, uv); + col.a = 1.0; // Clear alpha channel +} diff --git a/msvc/res/shader_fullscreen_vs.hlsl b/msvc/res/shader_fullscreen_vs.hlsl new file mode 100644 index 0000000..48f6162 --- /dev/null +++ b/msvc/res/shader_fullscreen_vs.hlsl @@ -0,0 +1,6 @@ +void main(uint id : SV_VERTEXID, out float4 pos : SV_POSITION, out float2 uv : TEXCOORD0) +{ + uv.x = (id == 2) ? 2.0 : 0.0; + uv.y = (id == 1) ? 2.0 : 0.0; + pos = float4(uv * float2(2.0, -2.0) + float2(-1.0, 1.0), 0.0, 1.0); +} diff --git a/msvc/res/shader_imgui_ps.hlsl b/msvc/res/shader_imgui_ps.hlsl new file mode 100644 index 0000000..26d7e30 --- /dev/null +++ b/msvc/res/shader_imgui_ps.hlsl @@ -0,0 +1,8 @@ +Texture2D t0 : register(t0); +SamplerState s0 : register(s0); + +void main(float4 vpos : SV_POSITION, float4 vcol : COLOR0, float2 uv : TEXCOORD0, out float4 col : SV_TARGET) +{ + col = t0.Sample(s0, uv); + col *= vcol; // Blend vertex color and texture +} diff --git a/msvc/res/shader_imgui_vs.hlsl b/msvc/res/shader_imgui_vs.hlsl new file mode 100644 index 0000000..c47cfe6 --- /dev/null +++ b/msvc/res/shader_imgui_vs.hlsl @@ -0,0 +1,24 @@ +struct VS_INPUT +{ + float2 pos : POSITION; + float4 col : COLOR0; + float2 tex : TEXCOORD0; +}; +struct PS_INPUT +{ + float4 pos : SV_POSITION; + float4 col : COLOR0; + float2 tex : TEXCOORD0; +}; + +cbuffer cb0 : register(b0) +{ + float4x4 ProjectionMatrix; +}; + +void main(VS_INPUT input, out PS_INPUT output) +{ + output.pos = mul(ProjectionMatrix, float4(input.pos.xy, 0.0, 1.0)); + output.col = input.col; + output.tex = input.tex; +} diff --git a/msvc/res/shader_mipmap_cs.hlsl b/msvc/res/shader_mipmap_cs.hlsl new file mode 100644 index 0000000..43eae2c --- /dev/null +++ b/msvc/res/shader_mipmap_cs.hlsl @@ -0,0 +1,17 @@ +SamplerState s0 : register(s0); +Texture2D t0 : register(t0); +RWTexture2D dest : register(u0); + +cbuffer cb0 : register(b0) +{ + float2 texel; // 1.0 / dimension +} + +[numthreads(8, 8, 1)] +void main(uint3 tid : SV_DispatchThreadID) +{ + float2 uv = texel * (tid.xy + 0.5); + + // Sample input texture and write result to the destination mipmap level + dest[tid.xy] = t0.SampleLevel(s0, uv, 0); +} diff --git a/msvc/res/version.h b/msvc/res/version.h new file mode 100644 index 0000000..1dcab77 --- /dev/null +++ b/msvc/res/version.h @@ -0,0 +1,14 @@ +#pragma once + +#define VERSION_DATE "2019-06-10" +#define VERSION_TIME "16:24:17" +#define VERSION_BASEYEAR 0 + +#define VERSION_FULL 0.0.0.1 +#define VERSION_MAJOR 0 +#define VERSION_MINOR 0 +#define VERSION_REVISION 0 +#define VERSION_BUILD 1 + +#define VERSION_STRING_FILE "0.0.0.1" +#define VERSION_STRING_PRODUCT "0.0.0" diff --git a/msvc/setup/FodyWeavers.xml b/msvc/setup/FodyWeavers.xml new file mode 100644 index 0000000..d769be1 --- /dev/null +++ b/msvc/setup/FodyWeavers.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/msvc/setup/Glass.cs b/msvc/setup/Glass.cs new file mode 100644 index 0000000..813fe58 --- /dev/null +++ b/msvc/setup/Glass.cs @@ -0,0 +1,113 @@ +using System; +using System.Runtime.InteropServices; +using System.Windows; +using System.Windows.Interop; +using System.Windows.Media; + +public static class Glass +{ + [StructLayout(LayoutKind.Sequential)] + private struct MARGINS + { + public int cxLeftWidth; + public int cxRightWidth; + public int cyTopHeight; + public int cyBottomHeight; + } + + [DllImport("dwmapi.dll")] + private static extern int DwmIsCompositionEnabled(out bool enabled); + [DllImport("dwmapi.dll")] + private static extern int DwmExtendFrameIntoClientArea(IntPtr hwnd, ref MARGINS pMarInset); + [DllImport("user32.dll")] + private static extern int GetWindowLong(IntPtr hwnd, int index); + [DllImport("user32.dll")] + private static extern int SetWindowLong(IntPtr hwnd, int index, int newStyle); + [DllImport("user32.dll")] + private static extern bool SetWindowPos(IntPtr hwnd, IntPtr hwndInsertAfter, int x, int y, int width, int height, uint flags); + [DllImport("user32.dll")] + private static extern IntPtr SendMessage(IntPtr hWnd, UInt32 Msg, IntPtr wParam, IntPtr lParam); + + public static bool IsEnabled + { + get + { + bool enabled = false; + DwmIsCompositionEnabled(out enabled); + + return enabled; + } + } + + public static void HideIcon(Window window) + { + IntPtr hwnd = new WindowInteropHelper(window).Handle; + + const int WM_SETICON = 0x0080; + + SendMessage(hwnd, WM_SETICON, new IntPtr(1), IntPtr.Zero); + SendMessage(hwnd, WM_SETICON, IntPtr.Zero, IntPtr.Zero); + + const int GWL_EXSTYLE = -20; + const int WS_EX_DLGMODALFRAME = 0x0001; + + SetWindowLong(hwnd, GWL_EXSTYLE, GetWindowLong(hwnd, GWL_EXSTYLE) | WS_EX_DLGMODALFRAME); + + const int SWP_NOSIZE = 0x0001; + const int SWP_NOMOVE = 0x0002; + const int SWP_NOZORDER = 0x0004; + const int SWP_FRAMECHANGED = 0x0020; + + SetWindowPos(hwnd, IntPtr.Zero, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE | SWP_NOZORDER | SWP_FRAMECHANGED); + } + public static void HideSystemMenu(Window window, bool hidden = true) + { + IntPtr hwnd = new WindowInteropHelper(window).Handle; + + const int GWL_STYLE = -16; + const int WS_SYSMENU = 0x80000; + + if (hidden) + { + SetWindowLong(hwnd, GWL_STYLE, GetWindowLong(hwnd, GWL_STYLE) & ~WS_SYSMENU); + } + else + { + SetWindowLong(hwnd, GWL_STYLE, GetWindowLong(hwnd, GWL_STYLE) | WS_SYSMENU); + } + } + + public static bool ExtendFrame(Window window) + { + return ExtendFrame(window, new Thickness(-1, -1, -1, -1)); + } + public static bool ExtendFrame(Window window, Thickness margin) + { + if (!IsEnabled) + { + return false; + } + + IntPtr hwnd = new WindowInteropHelper(window).Handle; + + if (hwnd == IntPtr.Zero) + { + throw new InvalidOperationException("The window must be shown before extending glass effect."); + } + + // Adapted from http://blogs.msdn.com/b/adam_nathan/archive/2006/05/04/589686.aspx + window.Background = Brushes.Transparent; + HwndSource.FromHwnd(hwnd).CompositionTarget.BackgroundColor = Colors.Transparent; + + var margins = new MARGINS { + cxLeftWidth = (int)margin.Left, + cxRightWidth = (int)margin.Right, + cyTopHeight = (int)margin.Top, + cyBottomHeight = (int)margin.Bottom + }; + + DwmExtendFrameIntoClientArea(hwnd, ref margins); + + return true; + } +} diff --git a/msvc/setup/IniFile.cs b/msvc/setup/IniFile.cs new file mode 100644 index 0000000..1c1d16d --- /dev/null +++ b/msvc/setup/IniFile.cs @@ -0,0 +1,22 @@ +using System.Text; +using System.Runtime.InteropServices; + +public static class IniFile +{ + [DllImport("kernel32", CharSet = CharSet.Unicode)] + private static extern int GetPrivateProfileString(string section, string key, string defaultValue, StringBuilder value, int size, string filePath); + [DllImport("kernel32", CharSet = CharSet.Unicode, SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + private static extern bool WritePrivateProfileString(string section, string key, string value, string filePath); + + public static string ReadValue(string file, string section, string key, string defaultValue = "") + { + var value = new StringBuilder(512); + GetPrivateProfileString(section, key, defaultValue, value, value.Capacity, file); + return value.ToString(); + } + public static bool WriteValue(string file, string section, string key, string value) + { + return WritePrivateProfileString(section, key, value, file); + } +} diff --git a/msvc/setup/PEInfo.cs b/msvc/setup/PEInfo.cs new file mode 100644 index 0000000..11b15d0 --- /dev/null +++ b/msvc/setup/PEInfo.cs @@ -0,0 +1,115 @@ +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using System.Security; + +public unsafe class PEInfo +{ + public enum BinaryType : ushort + { + IMAGE_FILE_MACHINE_UNKNOWN = 0x0, + IMAGE_FILE_MACHINE_I386 = 0x14c, + IMAGE_FILE_MACHINE_AMD64 = 0x8664, + } + + [StructLayout(LayoutKind.Sequential)] + private struct LOADED_IMAGE + { + public IntPtr ModuleName; + public IntPtr hFile; + public IntPtr MappedAddress; + public IntPtr FileHeader; + public IntPtr LastRvaSection; + public uint NumberOfSections; + public IntPtr Sections; + public uint Characteristics; + public ushort fSystemImage; + public ushort fDOSImage; + public ushort fReadOnly; + public ushort Version; + public IntPtr Flink; + public IntPtr BLink; + public uint SizeOfImage; + } + [StructLayout(LayoutKind.Explicit)] + private struct IMAGE_NT_HEADERS + { + [FieldOffset(0)] + public uint Signature; + [FieldOffset(4)] + public IMAGE_FILE_HEADER FileHeader; + } + [StructLayout(LayoutKind.Sequential)] + private struct IMAGE_FILE_HEADER + { + public BinaryType Machine; + public ushort NumberOfSections; + public uint TimeDateStamp; + public uint PointerToSymbolTable; + public uint NumberOfSymbols; + public ushort SizeOfOptionalHeader; + public ushort Characteristics; + } + [StructLayout(LayoutKind.Explicit)] + private struct IMAGE_IMPORT_DESCRIPTOR + { + #region union + [FieldOffset(0)] + public UInt32 Characteristics; + [FieldOffset(0)] + public UInt32 OriginalFirstThunk; + #endregion + + [FieldOffset(4)] + public uint TimeDateStamp; + [FieldOffset(8)] + public uint ForwarderChain; + [FieldOffset(12)] + public uint Name; + [FieldOffset(16)] + public uint FirstThunk; + } + + [DllImport("dbghelp.dll"), SuppressUnmanagedCodeSecurity] + private static extern void* ImageDirectoryEntryToData(void* pBase, bool mappedAsImage, ushort directoryEntry, out uint size); + [DllImport("dbghelp.dll"), SuppressUnmanagedCodeSecurity] + private static extern IntPtr ImageRvaToVa(IntPtr pNtHeaders, IntPtr pBase, uint rva, IntPtr pLastRvaSection); + [DllImport("imagehlp.dll"), SuppressUnmanagedCodeSecurity] + private static extern bool MapAndLoad(string imageName, string dllPath, out LOADED_IMAGE loadedImage, bool dotDll, bool readOnly); + + private readonly BinaryType _binaryType = BinaryType.IMAGE_FILE_MACHINE_UNKNOWN; + private readonly List _modules = new List(); + + // Adapted from http://stackoverflow.com/a/4696857/2055880 + public PEInfo(string path) + { + uint size; + LOADED_IMAGE image; + + if (MapAndLoad(path, null, out image, true, true) && image.MappedAddress != IntPtr.Zero) + { + var imports = (IMAGE_IMPORT_DESCRIPTOR*)ImageDirectoryEntryToData((void*)image.MappedAddress, false, 1, out size); + + if (imports != null) + { + while (imports->OriginalFirstThunk != 0) + { + _modules.Add(Marshal.PtrToStringAnsi(ImageRvaToVa(image.FileHeader, image.MappedAddress, imports->Name, IntPtr.Zero))); + + ++imports; + } + } + + _binaryType = ((IMAGE_NT_HEADERS*)image.FileHeader)->FileHeader.Machine; + } + } + + public BinaryType Type + { + get { return _binaryType; } + } + public IEnumerable Modules + { + get { return _modules; } + } +} diff --git a/msvc/setup/Packages.config b/msvc/setup/Packages.config new file mode 100644 index 0000000..28acfc3 --- /dev/null +++ b/msvc/setup/Packages.config @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/msvc/setup/Properties/App.xaml b/msvc/setup/Properties/App.xaml new file mode 100644 index 0000000..4d09c7d --- /dev/null +++ b/msvc/setup/Properties/App.xaml @@ -0,0 +1,5 @@ + diff --git a/msvc/setup/Properties/Assembly.manifest b/msvc/setup/Properties/Assembly.manifest new file mode 100644 index 0000000..75b8067 --- /dev/null +++ b/msvc/setup/Properties/Assembly.manifest @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/msvc/setup/Properties/AssemblyInfo.cs b/msvc/setup/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..6285be9 --- /dev/null +++ b/msvc/setup/Properties/AssemblyInfo.cs @@ -0,0 +1,9 @@ +using System.Reflection; + +[assembly: AssemblyTitle("ReShade Setup")] +[assembly: AssemblyVersion("2.0.0.0")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("crosire")] +[assembly: AssemblyProduct("ReShade")] +[assembly: AssemblyCopyright("Copyright © 2014. All rights reserved.")] diff --git a/msvc/setup/Properties/Icon.ico b/msvc/setup/Properties/Icon.ico new file mode 100644 index 0000000000000000000000000000000000000000..62caf48d28651e11f97baeb81a2e8388656d491e GIT binary patch literal 68582 zcmafab95z7wDpZ|?BvF_ZQHhOXC}67n=_ecV%x^Vb|$ta`tp14-*2tgtGjERRbAa@ zRiCQ8&#rC&01N;HKtTa~mBfGnZ~#CP000mZ|F0bj0|4Ma007L)|7(jQ0RUxi06<{i z|JtHL0Kf_8tHIayU;B;*0Dw*#0EkjnltP5V{kjr>C?hSd`c-~C0e}L8{yG!geVPCO z;L$STBI?|6L&Mj_=0iQlr^z(=OYBAh^h9DhB+x-kzpz9S7b$hI7RwF9 zMJyU=tmkBdiEWc`)%S{(M`0*zkECgfkXU$`PbayjHu#>lG^53v;Qm!l^WvzQ|CNpKJ}q43B#4A$aA&XXw7y56-SfbPoojLG z6J28x@)&eKF?ic=gXmwiEL}M?g;&ibCwISIb{JN=$#Fk@SeS*yiW#!+-JoqI~>=S{fDgBx#G&c+H7!vBy-e zWdnSSeb2W#o!XO+#69f^j&RB0(TS<@!rzf`j8H}rY)rfzHoq3ciK%79lF}jqVTsLk zlI`f~g|_*wD!m2rp}ce3bAb&f7mz<9c$`lK9krm#VQ1DaS$TL15#{A9vT<|35-Ec8 zllR-}ojU%IHG;^@)LIEBW8fP~*+T>(ri&|N+y$m;+9k)I@RQ}$s&jzyoltvdqM$oD zNo^2rJ_4OY4g-dzjQxYmIU}>ztaska3sk2elP&E~<@9${5HdUXcAx^50axscMS~qH z=;`>FcJKkgyIXv9uWp_N#q9db$CAn$$bcJ~&SD{xJ%_lD&x{~$0jQkJxROhUk4r1> z;@+Ovi=N!)GI8JBk*@BaHicQFP-NXi5>**lRX>&DO4Ri!`DH^_S9fKmG|gtfyiGJIn7H3&w8js=hF*cuu-$~yd7Q`27k@5q-z~ z7Xjr$rC%^{+#VE!43W68)?o3l(PrQQ*+gcZ{jc}FzZ(MKipY3p6y;@uxQSR?CJZGz z)XB4H#`(%pfnvv5o6fHZO}(WDJJ2uM>ZVD^=yf07%Fx}8QjaoARTv@?%lclr)i{Er zOf=rRG*K3vMywi1fu;|e39VDmdVZwx!*rQ-eSrEjU>mS%zx468xV*8lw$}Y`FGfn* z3UL;ioO{J1aD-@k%q%D3`Qgg@Bo{QBjtRotT_eT}oiF6j7c}_oG&&eQ@qsC#yah;A zgxSgMtfQBcF5@M&+4NJqa~$T72h5K;3+eg%%VhnAwEZ!i22ii$C|43HXqFeZ?Uz6x z*chspyOp<7QvDSKx7(qmxA!QuI1*~z_CtBp0Qs*PSgcp?H-W2-c%`>`KT*q*a7>!F zB}GF4O3$OTn+@0XYVKwZPH8(aP&h^m#ch~LFtO?3c$#^%0DZ?PJsc*=@bz%w?j>S5 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

6Hc_o~ts}AJX5WkV3eU(g{yN3tLJG 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 literal 0 HcmV?d00001 diff --git a/msvc/setup/ReShade Setup.csproj b/msvc/setup/ReShade Setup.csproj new file mode 100644 index 0000000..812f282 --- /dev/null +++ b/msvc/setup/ReShade Setup.csproj @@ -0,0 +1,163 @@ + + + + + {3B7009FA-0B09-4F27-8126-0885E66A5679} + Debug + AnyCPU + WinExe + ReShade Setup + Properties\Icon.ico + $(SolutionDir)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\intermediate\$(AssemblyName)\$(Platform)\$(Configuration)\ + Properties + ReShade.Setup + v4.6.1 + 512 + Properties\Assembly.manifest + 4 + + + + + + AnyCPU + true + full + false + DEBUG;TRACE + true + true + true + + + AnyCPU + none + true + true + false + false + + + + ..\packages\Costura.Fody.1.6.2\lib\dotnet\Costura.dll + False + + + ..\packages\WindowsAPICodePack-Core.1.1.2\lib\Microsoft.WindowsAPICodePack.dll + + + ..\packages\WindowsAPICodePack-Shell.1.1.1\lib\Microsoft.WindowsAPICodePack.Shell.dll + + + + + + + + + + + + MSBuild:Compile + Designer + + + Settings.xaml + + + + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + Select.xaml + + + Wizard.xaml + + + + + Code + + + + + + Designer + + + + + + + + + + + + + + + + + + (); +var attribute = config.Attribute("ExcludeAssemblies"); +if (attribute != null) + foreach (var item in attribute.Value.Split('|').Select(x => x.Trim()).Where(x => x != string.Empty)) + excludedAssemblies.Add(item); +var element = config.Element("ExcludeAssemblies"); +if (element != null) + foreach (var item in element.Value.Split(new[] { "\r\n", "\n" }, StringSplitOptions.RemoveEmptyEntries).Select(x => x.Trim()).Where(x => x != string.Empty)) + excludedAssemblies.Add(item); + +var filesToCleanup = Files.Select(f => f.ItemSpec).Where(f => !excludedAssemblies.Contains(Path.GetFileNameWithoutExtension(f), StringComparer.InvariantCultureIgnoreCase)); + +foreach (var item in filesToCleanup) + File.Delete(item); +]]> + + + + + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + + + + + +echo Compressing ReShade DLLs ... +"$(SolutionDir)tools\7za" a -y "$(TargetDir)ReShade.zip" "$(SolutionDir)bin\Win32\Release\ReShade32.dll" "$(SolutionDir)bin\x64\Release\ReShade64.dll" + +echo Appending archive to setup executable ... +copy /b "$(TargetPath)" + "$(TargetDir)ReShade.zip" "$(TargetPath)" + +echo Cleaning up and deleting temporary archive ... +del "$(TargetDir)ReShade.zip" + + OnOutputUpdated + + \ No newline at end of file diff --git a/msvc/setup/Select.xaml b/msvc/setup/Select.xaml new file mode 100644 index 0000000..b59d938 --- /dev/null +++ b/msvc/setup/Select.xaml @@ -0,0 +1,28 @@ + + + diff --git a/msvc/setup/Wizard.xaml.cs b/msvc/setup/Wizard.xaml.cs new file mode 100644 index 0000000..27b28c4 --- /dev/null +++ b/msvc/setup/Wizard.xaml.cs @@ -0,0 +1,496 @@ +/** + * Copyright (C) 2014 Patrick Mours. All rights reserved. + * License: https://github.com/crosire/reshade#license + */ + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.IO.Compression; +using System.Linq; +using System.Net; +using System.Reflection; +using System.Windows; +using System.Windows.Input; +using Microsoft.Win32; + +namespace ReShade.Setup +{ + public partial class WizardWindow + { + bool _isHeadless = false; + bool _isElevated = false; + string _configPath = null; + string _targetPath = null; + string _targetModulePath = null; + PEInfo _targetPEInfo = null; + string _tempDownloadPath = null; + + public WizardWindow() + { + InitializeComponent(); + } + + static void MoveFiles(string sourcePath, string targetPath) + { + if (!Directory.Exists(targetPath)) + { + Directory.CreateDirectory(targetPath); + } + + foreach (string source in Directory.GetFiles(sourcePath)) + { + string target = targetPath + source.Replace(sourcePath, string.Empty); + + File.Copy(source, target, true); + } + } + static bool IsWritable(string targetPath) + { + try + { + File.Create(Path.Combine(targetPath, Path.GetRandomFileName()), 1, FileOptions.DeleteOnClose); + return true; + } + catch + { + return false; + } + } + + static ZipArchive ExtractArchive() + { + var output = new FileStream(Path.GetTempFileName(), FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.ReadWrite | FileShare.Delete, 4096, FileOptions.DeleteOnClose); + + using (var input = File.OpenRead(Assembly.GetExecutingAssembly().Location)) + { + byte[] block = new byte[512]; + byte[] signature = { 0x50, 0x4B, 0x03, 0x04, 0x14, 0x00, 0x00, 0x00 }; // PK.. + + // Look for archive at the end of this executable and copy it to a file + while (input.Read(block, 0, block.Length) >= signature.Length) + { + if (block.Take(signature.Length).SequenceEqual(signature)) + { + output.Write(block, 0, block.Length); + input.CopyTo(output); + break; + } + } + } + + return new ZipArchive(output, ZipArchiveMode.Read, false); + } + + void ShowMessage(string title, string message, string description = null, bool done = false, int exitCode = -1) + { + if (done && _isHeadless) + { + Environment.Exit(exitCode); + } + else if (exitCode == 0) + { + message = "Edit ReShade settings"; + SetupButton.IsEnabled = true; + SetupButton.Click -= OnSetupButtonClick; + SetupButton.Click += (object s, RoutedEventArgs e) => new SettingsWindow(_configPath) { Owner = this }.ShowDialog(); + } + + Glass.HideSystemMenu(this, !done); + + Title = title; + Message.Text = message == null ? string.Empty : message; + MessageDescription.Visibility = string.IsNullOrEmpty(description) ? Visibility.Collapsed : Visibility.Visible; + MessageDescription.Text = description; + + } + void RestartAsAdmin() + { + Process.Start(new ProcessStartInfo { Verb = "runas", FileName = Assembly.GetExecutingAssembly().Location, Arguments = $"\"{_targetPath}\" --elevated --left {Left} --top {Top}" }); + + Close(); + } + + void AddSearchPath(List searchPaths, string newPath) + { + string basePath = Path.GetDirectoryName(_targetPath); + Directory.SetCurrentDirectory(basePath); + + bool pathExists = false; + + foreach (var searchPath in searchPaths) + { + if (Path.GetFullPath(searchPath) == Path.GetFullPath(newPath)) + { + pathExists = true; + break; + } + } + + if (!pathExists) + searchPaths.Add(newPath); + } + void WriteSearchPaths(string targetPathShaders, string targetPathTextures) + { + var effectSearchPaths = IniFile.ReadValue(_configPath, "GENERAL", "EffectSearchPaths").Split(',').Where(x => x.Length != 0).ToList(); + var textureSearchPaths = IniFile.ReadValue(_configPath, "GENERAL", "TextureSearchPaths").Split(',').Where(x => x.Length != 0).ToList(); + + AddSearchPath(effectSearchPaths, targetPathShaders); + AddSearchPath(textureSearchPaths, targetPathTextures); + + IniFile.WriteValue(_configPath, "GENERAL", "EffectSearchPaths", string.Join(",", effectSearchPaths)); + IniFile.WriteValue(_configPath, "GENERAL", "TextureSearchPaths", string.Join(",", textureSearchPaths)); + } + + void InstallationStep0() + { + if (!_isElevated && !IsWritable(Path.GetDirectoryName(_targetPath))) + RestartAsAdmin(); + else + InstallationStep1(); + } + void InstallationStep1() + { + SetupButton.IsEnabled = false; + Glass.HideSystemMenu(this); + + var info = FileVersionInfo.GetVersionInfo(_targetPath); + string name = !string.IsNullOrEmpty(info.ProductName) ? info.ProductName : Path.GetFileNameWithoutExtension(_targetPath); + _targetPEInfo = new PEInfo(_targetPath); + + ShowMessage("Working on " + name + " ...", "Analyzing " + name + " ..."); + + string nameModule = _targetPEInfo.Modules.FirstOrDefault(s => + s.StartsWith("d3d8", StringComparison.OrdinalIgnoreCase) || + s.StartsWith("d3d9", StringComparison.OrdinalIgnoreCase) || + s.StartsWith("dxgi", StringComparison.OrdinalIgnoreCase) || + s.StartsWith("opengl32", StringComparison.OrdinalIgnoreCase)); + + if (nameModule == null) + { + nameModule = string.Empty; + } + + bool isApiD3D8 = nameModule.StartsWith("d3d8", StringComparison.OrdinalIgnoreCase); + bool isApiD3D9 = isApiD3D8 || nameModule.StartsWith("d3d9", StringComparison.OrdinalIgnoreCase); + bool isApiDXGI = nameModule.StartsWith("dxgi", StringComparison.OrdinalIgnoreCase); + bool isApiOpenGL = nameModule.StartsWith("opengl32", StringComparison.OrdinalIgnoreCase); + + if (isApiD3D8 && !_isHeadless) + { + MessageBox.Show(this, "It looks like the target application uses Direct3D 8. You'll have to download an additional wrapper from 'http://reshade.me/d3d8to9' which converts all API calls to Direct3D 9 in order to use ReShade.", "Warning", MessageBoxButton.OK, MessageBoxImage.Warning); + } + + Message.Text = "Select the rendering API the game uses:"; + ApiGroup.IsEnabled = true; + ApiDirect3D9.IsChecked = isApiD3D9; + ApiDirectXGI.IsChecked = isApiDXGI; + ApiOpenGL.IsChecked = isApiOpenGL; + } + void InstallationStep2() + { + string nameModule = null; + ApiGroup.IsEnabled = false; + if (ApiDirect3D9.IsChecked == true) + nameModule = "d3d9.dll"; + if (ApiDirectXGI.IsChecked == true) + nameModule = "dxgi.dll"; + if (ApiOpenGL.IsChecked == true) + nameModule = "opengl32.dll"; + + string targetDir = Path.GetDirectoryName(_targetPath); + string pathModule = _targetModulePath = Path.Combine(targetDir, nameModule); + + _configPath = Path.ChangeExtension(pathModule, ".ini"); + if (!File.Exists(_configPath)) + _configPath = Path.Combine(targetDir, "ReShade.ini"); + + if (File.Exists(pathModule) && !_isHeadless) + { + var result = MessageBox.Show(this, "Do you want to overwrite the existing installation or uninstall ReShade?\n\nPress 'Yes' to overwrite or 'No' to uninstall.", string.Empty, MessageBoxButton.YesNoCancel); + + if (result == MessageBoxResult.No) + { + try + { + File.Delete(pathModule); + if (File.Exists(_configPath)) + File.Delete(_configPath); + if (File.Exists(Path.ChangeExtension(pathModule, ".log"))) + File.Delete(Path.ChangeExtension(pathModule, ".log")); + if (Directory.Exists(Path.Combine(targetDir, "reshade-shaders"))) + Directory.Delete(Path.Combine(targetDir, "reshade-shaders"), true); + } + catch (Exception ex) + { + ShowMessage("Failed!", "Unable to delete some files.", ex.Message, true, 1); + return; + } + + ShowMessage("Succeeded!", "Successfully uninstalled.", null, true); + return; + } + else if (result != MessageBoxResult.Yes) + { + ShowMessage("Failed", "Existing installation found.", null, true, 1); + return; + } + } + + try + { + using (ZipArchive zip = ExtractArchive()) + { + if (zip.Entries.Count != 2) + throw new FileFormatException("Expected ReShade archive to contain ReShade DLLs"); + + using (Stream input = zip.Entries[_targetPEInfo.Type == PEInfo.BinaryType.IMAGE_FILE_MACHINE_AMD64 ? 1 : 0].Open()) + using (FileStream output = File.Create(pathModule)) + input.CopyTo(output); + } + } + catch (Exception ex) + { + ShowMessage("Failed!", "Unable to write file \"" + pathModule + "\".", ex.Message, true, 1); + return; + } + + if (_isHeadless || MessageBox.Show(this, "Do you wish to download a collection of standard effects from https://github.com/crosire/reshade-shaders?", string.Empty, MessageBoxButton.YesNo) == MessageBoxResult.Yes) + { + InstallationStep3(); + } + else + { + if (!File.Exists(_configPath)) + { + string targetDirectory = Path.GetDirectoryName(_targetPath); + + WriteSearchPaths(".\\", ".\\"); + } + + ShowMessage("Succeeded!", "Successfully installed.", null, true, 0); + } + } + void InstallationStep3() + { + ShowMessage("Downloading ...", "Downloading ..."); + + _tempDownloadPath = Path.GetTempFileName(); + + var client = new WebClient(); + + client.DownloadFileCompleted += (object sender, System.ComponentModel.AsyncCompletedEventArgs e) => { + if (e.Error != null) + ShowMessage("Failed!", "Unable to download archive.", e.Error.Message, true, 1); + else + InstallationStep4(); + }; + + client.DownloadProgressChanged += (object sender, DownloadProgressChangedEventArgs e) => { + Message.Text = "Downloading ... (" + ((100 * e.BytesReceived) / e.TotalBytesToReceive) + "%)"; + }; + + try + { + client.DownloadFileAsync(new Uri("https://github.com/crosire/reshade-shaders/archive/master.zip"), _tempDownloadPath); + } + catch (Exception ex) + { + ShowMessage("Failed!", "Unable to download archive.", ex.Message); + } + } + void InstallationStep4() + { + Message.Text = "Extracting ..."; + + string tempPath = Path.Combine(Path.GetTempPath(), "reshade-shaders-master"); + string tempPathShaders = Path.Combine(tempPath, "Shaders"); + string tempPathTextures = Path.Combine(tempPath, "Textures"); + string targetPath = Path.Combine(Path.GetDirectoryName(_targetPath), "reshade-shaders"); + string targetPathShaders = Path.Combine(targetPath, "Shaders"); + string targetPathTextures = Path.Combine(targetPath, "Textures"); + + string[] installedEffects = null; + + if (Directory.Exists(targetPath)) + { + installedEffects = Directory.GetFiles(targetPathShaders).ToArray(); + } + + try + { + if (Directory.Exists(tempPath)) // Delete existing directories since extraction fails if the target is not empty + Directory.Delete(tempPath, true); + + ZipFile.ExtractToDirectory(_tempDownloadPath, Path.GetTempPath()); + + MoveFiles(tempPathShaders, targetPathShaders); + MoveFiles(tempPathTextures, targetPathTextures); + + File.Delete(_tempDownloadPath); + Directory.Delete(tempPath, true); + } + catch (Exception ex) + { + ShowMessage("Failed!", "Unable to extract downloaded archive.", ex.Message, true, 1); + return; + } + + if (!_isHeadless) + { + var wnd = new SelectWindow(Directory.GetFiles(targetPathShaders)); + wnd.Owner = this; + + // If there was an existing installation, select the same effects as previously + if (installedEffects != null) + { + foreach (var item in wnd.GetSelection()) + { + item.IsChecked = installedEffects.Contains(item.Path); + } + } + + wnd.ShowDialog(); + + foreach (var item in wnd.GetSelection()) + { + if (!item.IsChecked) + { + try + { + File.Delete(item.Path); + } + catch + { + continue; + } + } + } + } + + WriteSearchPaths(".\\reshade-shaders\\Shaders", ".\\reshade-shaders\\Textures"); + + ShowMessage("Succeeded!", null, null, true, 0); + } + + void OnWindowInit(object sender, EventArgs e) + { + Glass.HideIcon(this); + Glass.ExtendFrame(this); + } + void OnWindowLoaded(object sender, RoutedEventArgs e) + { + var args = Environment.GetCommandLineArgs().Skip(1).ToArray(); + + bool hasApi = false; + bool hasTargetPath = false; + + // Parse command line arguments + for (int i = 0; i < args.Length; i++) + { + if (args[i] == "--headless") + { + _isHeadless = true; + continue; + } + if (args[i] == "--elevated") + { + _isElevated = true; + continue; + } + + if (i + 1 < args.Length) + { + if (args[i] == "--api") + { + hasApi = true; + + string api = args[++i]; + ApiDirect3D9.IsChecked = api == "d3d9"; + ApiDirectXGI.IsChecked = api == "dxgi" || api == "d3d10" || api == "d3d11"; + ApiOpenGL.IsChecked = api == "opengl"; + continue; + } + + if (args[i] == "--top") + { + Top = double.Parse(args[++i]); + continue; + } + if (args[i] == "--left") + { + Left = double.Parse(args[++i]); + continue; + } + } + + if (File.Exists(args[i])) + { + hasTargetPath = true; + + _targetPath = args[i]; + } + } + + if (hasTargetPath) + { + InstallationStep1(); + + if (hasApi) + InstallationStep2(); + } + } + + void OnApiChecked(object sender, RoutedEventArgs e) + { + InstallationStep2(); + } + void OnSetupButtonClick(object sender, RoutedEventArgs e) + { + if (Keyboard.Modifiers == ModifierKeys.Control) + { + try + { + using (ZipArchive zip = ExtractArchive()) + zip.ExtractToDirectory("."); + } + catch (Exception ex) + { + ShowMessage("Failed!", "Unable to extract files.", ex.Message, true); + SetupButton.IsEnabled = false; + return; + } + + Close(); + return; + } + + var dlg = new OpenFileDialog { Filter = "Applications|*.exe", DefaultExt = ".exe", Multiselect = false, ValidateNames = true, CheckFileExists = true }; + + // Open Steam game installation directory by default if it exists + string steamPath = Path.Combine( + Environment.GetFolderPath(Environment.Is64BitOperatingSystem ? Environment.SpecialFolder.ProgramFilesX86 : Environment.SpecialFolder.ProgramFiles), + "Steam", "steamapps", "common"); + + if (Directory.Exists(steamPath)) + dlg.InitialDirectory = steamPath; + + if (dlg.ShowDialog(this) == true) + { + _targetPath = dlg.FileName; + + InstallationStep0(); + } + } + void OnSetupButtonDragDrop(object sender, DragEventArgs e) + { + if (e.Data.GetData(DataFormats.FileDrop, true) is string[] files && files.Length >= 1) + { + _targetPath = files[0]; + + InstallationStep0(); + } + } + } +} diff --git a/msvc/source/com_ptr.hpp b/msvc/source/com_ptr.hpp new file mode 100644 index 0000000..6b5c257 --- /dev/null +++ b/msvc/source/com_ptr.hpp @@ -0,0 +1,121 @@ +/** + * Copyright (C) 2014 Patrick Mours. All rights reserved. + * License: https://github.com/crosire/reshade#license + */ + +#pragma once + +#include + +template +class com_ptr +{ +public: + com_ptr() + : _object(nullptr) {} + com_ptr(std::nullptr_t) + : _object(nullptr) {} + com_ptr(T *object, bool own = false) + : _object(object) + { + if (!own && _object != nullptr) + _object->AddRef(); + } + com_ptr(const com_ptr &ptr) + : _object(nullptr) { reset(ptr._object); } + com_ptr(com_ptr &&ptr) + : _object(nullptr) { operator=(ptr); } + ~com_ptr() { reset(); } + + ///

+ /// Returns the stored pointer to the managed object. + /// + T *get() const { return _object; } + + /// + /// Returns the current COM reference count of the managed object. + /// + unsigned long ref_count() const + { + return _object->AddRef(), _object->Release(); + } + + /// + /// Returns the stored pointer and releases ownership without decreasing the reference count. + /// + T *release() + { + T *const object = _object; + _object = nullptr; + return object; + } + + /// + /// Replaces the managed object. + /// + /// The new object to manage and take ownership and add a reference to. + void reset(T *object = nullptr) + { + if (_object != nullptr) + _object->Release(); + _object = object; + if (_object != nullptr) + _object->AddRef(); + } + + // Overloaded pointer operators which operate on the managed object. + T &operator*() const { assert(_object != nullptr); return *_object; } + T *operator->() const { assert(_object != nullptr); return _object; } + + // This should only be called on uninitialized objects, e.g. when passed into 'QueryInterface' or creation functions. + T **operator&() { assert(_object == nullptr); return &_object; } + + com_ptr &operator=(T *object) + { + reset(object); + return *this; + } + com_ptr &operator=(const com_ptr ©) + { + reset(copy._object); + return *this; + } + com_ptr &operator=(com_ptr &&move) + { + // Clear the current object first + if (_object != nullptr) + _object->Release(); + + _object = move._object; + move._object = nullptr; + + return *this; + } + + bool operator==(const T *rhs) const { return _object == rhs; } + bool operator==(const com_ptr &rhs) const { return _object == rhs._object; } + friend bool operator==(const T *lhs, const com_ptr &rhs) { return rhs.operator==(lhs); } + bool operator!=(const T *rhs) const { return _object != rhs; } + bool operator!=(const com_ptr &rhs) const { return _object != rhs._object; } + friend bool operator!=(const T *lhs, const com_ptr &rhs) { return rhs.operator!=(lhs); } + + // Default operator used for sorting + friend bool operator< (const com_ptr &lhs, const com_ptr &rhs) { return lhs._object < rhs._object; } + +private: + T *_object; +}; + +#include // std::hash + +namespace std +{ + template + struct hash> + { + size_t operator()(const com_ptr &ptr) const + { + return std::hash()(ptr.get()); + } + }; +} diff --git a/msvc/source/d3d10/d3d10.cpp b/msvc/source/d3d10/d3d10.cpp new file mode 100644 index 0000000..56843a2 --- /dev/null +++ b/msvc/source/d3d10/d3d10.cpp @@ -0,0 +1,100 @@ +/** + * Copyright (C) 2014 Patrick Mours. All rights reserved. + * License: https://github.com/crosire/reshade#license + */ + +#include "log.hpp" +#include "hook_manager.hpp" +#include "d3d10_device.hpp" + +HOOK_EXPORT HRESULT WINAPI D3D10CreateDevice(IDXGIAdapter *pAdapter, D3D10_DRIVER_TYPE DriverType, HMODULE Software, UINT Flags, UINT SDKVersion, ID3D10Device **ppDevice) +{ + LOG(INFO) << "Redirecting D3D10CreateDevice" << '(' << pAdapter << ", " << DriverType << ", " << Software << ", " << std::hex << Flags << std::dec << ", " << SDKVersion << ", " << ppDevice << ')' << " ..."; + LOG(INFO) << "> Passing on to D3D10CreateDeviceAndSwapChain1:"; + + // Only 'd3d10.dll' is guaranteed to be loaded at this point, but the 'D3D10CreateDeviceAndSwapChain1' entry point is in 'd3d10_1.dll', so load that now to make sure hooks can be resolved + LoadLibraryW(L"d3d10_1.dll"); + + return D3D10CreateDeviceAndSwapChain1(pAdapter, DriverType, Software, Flags, D3D10_FEATURE_LEVEL_10_0, D3D10_1_SDK_VERSION, nullptr, nullptr, reinterpret_cast(ppDevice)); +} + +HOOK_EXPORT HRESULT WINAPI D3D10CreateDevice1(IDXGIAdapter *pAdapter, D3D10_DRIVER_TYPE DriverType, HMODULE Software, UINT Flags, D3D10_FEATURE_LEVEL1 HardwareLevel, UINT SDKVersion, ID3D10Device1 **ppDevice) +{ + LOG(INFO) << "Redirecting D3D10CreateDevice1" << '(' << pAdapter << ", " << DriverType << ", " << Software << ", " << std::hex << Flags << std::dec << ", " << HardwareLevel << ", " << SDKVersion << ", " << ppDevice << ')' << " ..."; + LOG(INFO) << "> Passing on to D3D10CreateDeviceAndSwapChain1:"; + + return D3D10CreateDeviceAndSwapChain1(pAdapter, DriverType, Software, Flags, HardwareLevel, SDKVersion, nullptr, nullptr, ppDevice); +} + +HOOK_EXPORT HRESULT WINAPI D3D10CreateDeviceAndSwapChain(IDXGIAdapter *pAdapter, D3D10_DRIVER_TYPE DriverType, HMODULE Software, UINT Flags, UINT SDKVersion, DXGI_SWAP_CHAIN_DESC *pSwapChainDesc, IDXGISwapChain **ppSwapChain, ID3D10Device **ppDevice) +{ + LOG(INFO) << "Redirecting D3D10CreateDeviceAndSwapChain" << '(' << pAdapter << ", " << DriverType << ", " << Software << ", " << std::hex << Flags << std::dec << ", " << SDKVersion << ", " << pSwapChainDesc << ", " << ppSwapChain << ", " << ppDevice << ')' << " ..."; + LOG(INFO) << "> Passing on to D3D10CreateDeviceAndSwapChain1:"; + + LoadLibraryW(L"d3d10_1.dll"); + + return D3D10CreateDeviceAndSwapChain1(pAdapter, DriverType, Software, Flags, D3D10_FEATURE_LEVEL_10_0, D3D10_1_SDK_VERSION, pSwapChainDesc, ppSwapChain, reinterpret_cast(ppDevice)); +} + +HOOK_EXPORT HRESULT WINAPI D3D10CreateDeviceAndSwapChain1(IDXGIAdapter *pAdapter, D3D10_DRIVER_TYPE DriverType, HMODULE Software, UINT Flags, D3D10_FEATURE_LEVEL1 HardwareLevel, UINT SDKVersion, DXGI_SWAP_CHAIN_DESC *pSwapChainDesc, IDXGISwapChain **ppSwapChain, ID3D10Device1 **ppDevice) +{ + LOG(INFO) << "Redirecting D3D10CreateDeviceAndSwapChain1" << '(' << pAdapter << ", " << DriverType << ", " << Software << ", " << std::hex << Flags << ", " << HardwareLevel << std::dec << ", " << SDKVersion << ", " << pSwapChainDesc << ", " << ppSwapChain << ", " << ppDevice << ')' << " ..."; + + HRESULT hr = reshade::hooks::call(D3D10CreateDeviceAndSwapChain1)(pAdapter, DriverType, Software, Flags, HardwareLevel, SDKVersion, nullptr, nullptr, ppDevice); + + if (FAILED(hr)) + { + LOG(WARN) << "> D3D10CreateDeviceAndSwapChain1 failed with error code " << std::hex << hr << std::dec << '!'; + return hr; + } + + // It is valid for the device out parameter to be NULL if the application wants to check feature level support, so just return early in that case + if (ppDevice == nullptr) + return hr; + + const auto device = *ppDevice; + + // Query for the DXGI device since we need to reference it in the hooked device + IDXGIDevice1 *dxgi_device = nullptr; + device->QueryInterface(&dxgi_device); + + const auto device_proxy = new D3D10Device(dxgi_device, device); + + // Swap chain creation is piped through the 'IDXGIFactory::CreateSwapChain' function hook + if (pSwapChainDesc != nullptr) + { + assert(ppSwapChain != nullptr); + + com_ptr adapter(pAdapter, false); + // Fall back to the same adapter as the device if it was not explicitly specified in the argument list + if (adapter == nullptr) + { + hr = dxgi_device->GetAdapter(&adapter); + assert(SUCCEEDED(hr)); + } + + // Time to find a factory associated with the target adapter and create a swap chain with it + com_ptr factory; + hr = adapter->GetParent(IID_PPV_ARGS(&factory)); + assert(SUCCEEDED(hr)); + + LOG(INFO) << "> Calling IDXGIFactory::CreateSwapChain:"; + + hr = factory->CreateSwapChain(device_proxy, pSwapChainDesc, ppSwapChain); + } + + if (SUCCEEDED(hr)) + { +#if RESHADE_VERBOSE_LOG + LOG(DEBUG) << "Returning IDXGIDevice1 object " << device_proxy->_dxgi_device << " and ID3D10Device1 object " << device_proxy << '.'; +#endif + *ppDevice = device_proxy; + } + else + { + // Swap chain creation failed, so do clean up + device_proxy->Release(); + } + + return hr; +} diff --git a/msvc/source/d3d10/d3d10_device.cpp b/msvc/source/d3d10/d3d10_device.cpp new file mode 100644 index 0000000..fe11335 --- /dev/null +++ b/msvc/source/d3d10/d3d10_device.cpp @@ -0,0 +1,560 @@ +/** + * Copyright (C) 2014 Patrick Mours. All rights reserved. + * License: https://github.com/crosire/reshade#license + */ + +#include "log.hpp" +#include "d3d10_device.hpp" +#include "../dxgi/dxgi_device.hpp" +#include "runtime_d3d10.hpp" + +D3D10Device::D3D10Device(IDXGIDevice1 *dxgi_device, ID3D10Device1 *original) : + _orig(original), + _dxgi_device(new DXGIDevice(dxgi_device, this)) { + assert(original != nullptr); +} + +void D3D10Device::clear_drawcall_stats() +{ + _draw_call_tracker.reset(); + _active_depthstencil.reset(); + _clear_DSV_iter = 1; +} + +#if RESHADE_DX10_CAPTURE_DEPTH_BUFFERS +bool D3D10Device::save_depth_texture(ID3D10DepthStencilView *pDepthStencilView, bool cleared) +{ + if (_runtimes.empty()) + return false; + + const auto runtime = _runtimes.front(); + + if (!runtime->depth_buffer_before_clear) + return false; + if (!cleared && !runtime->extended_depth_buffer_detection) + return false; + + assert(pDepthStencilView != nullptr); + + // Retrieve texture from depth stencil + com_ptr resource; + pDepthStencilView->GetResource(&resource); + + com_ptr texture; + if (FAILED(resource->QueryInterface(&texture))) + return false; + + D3D10_TEXTURE2D_DESC desc; + texture->GetDesc(&desc); + + // Check if aspect ratio is similar to the back buffer one + const float screen_aspect_ratio = float(runtime->frame_width()) / float(runtime->frame_height()); + const float texture_aspect_ratio = float(desc.Width) / float(desc.Height); + + if (fabs(texture_aspect_ratio - screen_aspect_ratio) > 0.1f || desc.Width > runtime->frame_width()) + return false; + + // In case the depth texture is retrieved, we make a copy of it and store it in an ordered map to use it later in the final rendering stage. + if ((runtime->cleared_depth_buffer_index == 0 && cleared) || (_clear_DSV_iter <= runtime->cleared_depth_buffer_index)) + { + // Select an appropriate destination texture + com_ptr depth_texture_save = runtime->select_depth_texture_save(desc); + if (depth_texture_save == nullptr) + return false; + + // Copy the depth texture. This is necessary because the content of the depth texture is cleared. + // This way, we can retrieve this content in the final rendering stage + CopyResource(depth_texture_save.get(), texture.get()); + + // Store the saved texture in the ordered map. + _draw_call_tracker.track_depth_texture(runtime->depth_buffer_texture_format, _clear_DSV_iter, texture.get(), pDepthStencilView, depth_texture_save, cleared); + } + else + { + // Store a null depth texture in the ordered map in order to display it even if the user chose a previous cleared texture. + // This way the texture will still be visible in the depth buffer selection window and the user can choose it. + _draw_call_tracker.track_depth_texture(runtime->depth_buffer_texture_format, _clear_DSV_iter, texture.get(), pDepthStencilView, nullptr, cleared); + } + + _clear_DSV_iter++; + + return true; +} + +void D3D10Device::track_active_rendertargets(UINT NumViews, ID3D10RenderTargetView *const *ppRenderTargetViews, ID3D10DepthStencilView *pDepthStencilView) +{ + if (pDepthStencilView == nullptr || _runtimes.empty()) + return; + + const auto runtime = _runtimes.front(); + + _draw_call_tracker.track_rendertargets(runtime->depth_buffer_texture_format, pDepthStencilView, NumViews, ppRenderTargetViews); + + save_depth_texture(pDepthStencilView, false); +} +void D3D10Device::track_cleared_depthstencil(ID3D10DepthStencilView *pDepthStencilView) +{ + if (pDepthStencilView == nullptr) + return; + + save_depth_texture(pDepthStencilView, true); +} +#endif + +HRESULT STDMETHODCALLTYPE D3D10Device::QueryInterface(REFIID riid, void **ppvObj) +{ + if (ppvObj == nullptr) + return E_POINTER; + + if (riid == __uuidof(this) || + riid == __uuidof(ID3D10Device) || + riid == __uuidof(ID3D10Device1)) + { + AddRef(); + *ppvObj = this; + return S_OK; + } + + // Note: Objects must have an identity, so use DXGIDevice for IID_IUnknown + // See https://docs.microsoft.com/en-us/windows/desktop/com/rules-for-implementing-queryinterface + if (riid == __uuidof(IUnknown) || + riid == __uuidof(DXGIDevice) || + riid == __uuidof(IDXGIObject) || + riid == __uuidof(IDXGIDevice) || + riid == __uuidof(IDXGIDevice1) || + riid == __uuidof(IDXGIDevice2) || + riid == __uuidof(IDXGIDevice3)) + return _dxgi_device->QueryInterface(riid, ppvObj); + + return _orig->QueryInterface(riid, ppvObj); +} +ULONG STDMETHODCALLTYPE D3D10Device::AddRef() +{ + ++_ref; + + return _orig->AddRef(); +} +ULONG STDMETHODCALLTYPE D3D10Device::Release() +{ + --_ref; + + const ULONG ref = _orig->Release(); + + if (ref != 0 || _ref != 0) + return ref; + else if (ref != 0) + LOG(WARN) << "Reference count for ID3D10Device1 object " << this << " is inconsistent: " << ref << ", but expected 0."; + + assert(_ref <= 0); +#if RESHADE_VERBOSE_LOG + LOG(DEBUG) << "Destroyed ID3D10Device1 object " << this << '.'; +#endif + delete this; + return 0; +} + +void STDMETHODCALLTYPE D3D10Device::VSSetConstantBuffers(UINT StartSlot, UINT NumBuffers, ID3D10Buffer *const *ppConstantBuffers) +{ + _orig->VSSetConstantBuffers(StartSlot, NumBuffers, ppConstantBuffers); +} +void STDMETHODCALLTYPE D3D10Device::PSSetShaderResources(UINT StartSlot, UINT NumViews, ID3D10ShaderResourceView *const *ppShaderResourceViews) +{ + _orig->PSSetShaderResources(StartSlot, NumViews, ppShaderResourceViews); +} +void STDMETHODCALLTYPE D3D10Device::PSSetShader(ID3D10PixelShader *pPixelShader) +{ + _orig->PSSetShader(pPixelShader); +} +void STDMETHODCALLTYPE D3D10Device::PSSetSamplers(UINT StartSlot, UINT NumSamplers, ID3D10SamplerState *const *ppSamplers) +{ + _orig->PSSetSamplers(StartSlot, NumSamplers, ppSamplers); +} +void STDMETHODCALLTYPE D3D10Device::VSSetShader(ID3D10VertexShader *pVertexShader) +{ + _orig->VSSetShader(pVertexShader); +} +void STDMETHODCALLTYPE D3D10Device::DrawIndexed(UINT IndexCount, UINT StartIndexLocation, INT BaseVertexLocation) +{ + _orig->DrawIndexed(IndexCount, StartIndexLocation, BaseVertexLocation); + _draw_call_tracker.on_draw(this, IndexCount); +} +void STDMETHODCALLTYPE D3D10Device::Draw(UINT VertexCount, UINT StartVertexLocation) +{ + _orig->Draw(VertexCount, StartVertexLocation); + _draw_call_tracker.on_draw(this, VertexCount); +} +void STDMETHODCALLTYPE D3D10Device::PSSetConstantBuffers(UINT StartSlot, UINT NumBuffers, ID3D10Buffer *const *ppConstantBuffers) +{ + _orig->PSSetConstantBuffers(StartSlot, NumBuffers, ppConstantBuffers); +} +void STDMETHODCALLTYPE D3D10Device::IASetInputLayout(ID3D10InputLayout *pInputLayout) +{ + _orig->IASetInputLayout(pInputLayout); +} +void STDMETHODCALLTYPE D3D10Device::IASetVertexBuffers(UINT StartSlot, UINT NumBuffers, ID3D10Buffer *const *ppVertexBuffers, const UINT *pStrides, const UINT *pOffsets) +{ + _orig->IASetVertexBuffers(StartSlot, NumBuffers, ppVertexBuffers, pStrides, pOffsets); +} +void STDMETHODCALLTYPE D3D10Device::IASetIndexBuffer(ID3D10Buffer *pIndexBuffer, DXGI_FORMAT Format, UINT Offset) +{ + _orig->IASetIndexBuffer(pIndexBuffer, Format, Offset); +} +void STDMETHODCALLTYPE D3D10Device::DrawIndexedInstanced(UINT IndexCountPerInstance, UINT InstanceCount, UINT StartIndexLocation, INT BaseVertexLocation, UINT StartInstanceLocation) +{ + _orig->DrawIndexedInstanced(IndexCountPerInstance, InstanceCount, StartIndexLocation, BaseVertexLocation, StartInstanceLocation); + _draw_call_tracker.on_draw(this, IndexCountPerInstance * InstanceCount); +} +void STDMETHODCALLTYPE D3D10Device::DrawInstanced(UINT VertexCountPerInstance, UINT InstanceCount, UINT StartVertexLocation, UINT StartInstanceLocation) +{ + _orig->DrawInstanced(VertexCountPerInstance, InstanceCount, StartVertexLocation, StartInstanceLocation); + _draw_call_tracker.on_draw(this, VertexCountPerInstance * InstanceCount); +} +void STDMETHODCALLTYPE D3D10Device::GSSetConstantBuffers(UINT StartSlot, UINT NumBuffers, ID3D10Buffer *const *ppConstantBuffers) +{ + _orig->GSSetConstantBuffers(StartSlot, NumBuffers, ppConstantBuffers); +} +void STDMETHODCALLTYPE D3D10Device::GSSetShader(ID3D10GeometryShader *pShader) +{ + _orig->GSSetShader(pShader); +} +void STDMETHODCALLTYPE D3D10Device::IASetPrimitiveTopology(D3D10_PRIMITIVE_TOPOLOGY Topology) +{ + _orig->IASetPrimitiveTopology(Topology); +} +void STDMETHODCALLTYPE D3D10Device::VSSetShaderResources(UINT StartSlot, UINT NumViews, ID3D10ShaderResourceView *const *ppShaderResourceViews) +{ + _orig->VSSetShaderResources(StartSlot, NumViews, ppShaderResourceViews); +} +void STDMETHODCALLTYPE D3D10Device::VSSetSamplers(UINT StartSlot, UINT NumSamplers, ID3D10SamplerState *const *ppSamplers) +{ + _orig->VSSetSamplers(StartSlot, NumSamplers, ppSamplers); +} +void STDMETHODCALLTYPE D3D10Device::SetPredication(ID3D10Predicate *pPredicate, BOOL PredicateValue) +{ + _orig->SetPredication(pPredicate, PredicateValue); +} +void STDMETHODCALLTYPE D3D10Device::GSSetShaderResources(UINT StartSlot, UINT NumViews, ID3D10ShaderResourceView *const *ppShaderResourceViews) +{ + _orig->GSSetShaderResources(StartSlot, NumViews, ppShaderResourceViews); +} +void STDMETHODCALLTYPE D3D10Device::GSSetSamplers(UINT StartSlot, UINT NumSamplers, ID3D10SamplerState *const *ppSamplers) +{ + _orig->GSSetSamplers(StartSlot, NumSamplers, ppSamplers); +} +void STDMETHODCALLTYPE D3D10Device::OMSetRenderTargets(UINT NumViews, ID3D10RenderTargetView *const *ppRenderTargetViews, ID3D10DepthStencilView *pDepthStencilView) +{ +#if RESHADE_DX10_CAPTURE_DEPTH_BUFFERS + track_active_rendertargets(NumViews, ppRenderTargetViews, pDepthStencilView); +#endif + _orig->OMSetRenderTargets(NumViews, ppRenderTargetViews, pDepthStencilView); +} +void STDMETHODCALLTYPE D3D10Device::OMSetBlendState(ID3D10BlendState *pBlendState, const FLOAT BlendFactor[4], UINT SampleMask) +{ + _orig->OMSetBlendState(pBlendState, BlendFactor, SampleMask); +} +void STDMETHODCALLTYPE D3D10Device::OMSetDepthStencilState(ID3D10DepthStencilState *pDepthStencilState, UINT StencilRef) +{ + _orig->OMSetDepthStencilState(pDepthStencilState, StencilRef); +} +void STDMETHODCALLTYPE D3D10Device::SOSetTargets(UINT NumBuffers, ID3D10Buffer *const *ppSOTargets, const UINT *pOffsets) +{ + _orig->SOSetTargets(NumBuffers, ppSOTargets, pOffsets); +} +void STDMETHODCALLTYPE D3D10Device::DrawAuto() +{ + _orig->DrawAuto(); + _draw_call_tracker.on_draw(this, 0); +} +void STDMETHODCALLTYPE D3D10Device::RSSetState(ID3D10RasterizerState *pRasterizerState) +{ + _orig->RSSetState(pRasterizerState); +} +void STDMETHODCALLTYPE D3D10Device::RSSetViewports(UINT NumViewports, const D3D10_VIEWPORT *pViewports) +{ + _orig->RSSetViewports(NumViewports, pViewports); +} +void STDMETHODCALLTYPE D3D10Device::RSSetScissorRects(UINT NumRects, const D3D10_RECT *pRects) +{ + _orig->RSSetScissorRects(NumRects, pRects); +} +void STDMETHODCALLTYPE D3D10Device::CopySubresourceRegion(ID3D10Resource *pDstResource, UINT DstSubresource, UINT DstX, UINT DstY, UINT DstZ, ID3D10Resource *pSrcResource, UINT SrcSubresource, const D3D10_BOX *pSrcBox) +{ + _orig->CopySubresourceRegion(pDstResource, DstSubresource, DstX, DstY, DstZ, pSrcResource, SrcSubresource, pSrcBox); +} +void STDMETHODCALLTYPE D3D10Device::CopyResource(ID3D10Resource *pDstResource, ID3D10Resource *pSrcResource) +{ + _orig->CopyResource(pDstResource, pSrcResource); +} +void STDMETHODCALLTYPE D3D10Device::UpdateSubresource(ID3D10Resource *pDstResource, UINT DstSubresource, const D3D10_BOX *pDstBox, const void *pSrcData, UINT SrcRowPitch, UINT SrcDepthPitch) +{ + _orig->UpdateSubresource(pDstResource, DstSubresource, pDstBox, pSrcData, SrcRowPitch, SrcDepthPitch); +} +void STDMETHODCALLTYPE D3D10Device::ClearRenderTargetView(ID3D10RenderTargetView *pRenderTargetView, const FLOAT ColorRGBA[4]) +{ + _orig->ClearRenderTargetView(pRenderTargetView, ColorRGBA); +} +void STDMETHODCALLTYPE D3D10Device::ClearDepthStencilView(ID3D10DepthStencilView *pDepthStencilView, UINT ClearFlags, FLOAT Depth, UINT8 Stencil) +{ +#if RESHADE_DX10_CAPTURE_DEPTH_BUFFERS + if (ClearFlags & D3D10_CLEAR_DEPTH) + track_cleared_depthstencil(pDepthStencilView); +#endif + _orig->ClearDepthStencilView(pDepthStencilView, ClearFlags, Depth, Stencil); +} +void STDMETHODCALLTYPE D3D10Device::GenerateMips(ID3D10ShaderResourceView *pShaderResourceView) +{ + _orig->GenerateMips(pShaderResourceView); +} +void STDMETHODCALLTYPE D3D10Device::ResolveSubresource(ID3D10Resource *pDstResource, UINT DstSubresource, ID3D10Resource *pSrcResource, UINT SrcSubresource, DXGI_FORMAT Format) +{ + _orig->ResolveSubresource(pDstResource, DstSubresource, pSrcResource, SrcSubresource, Format); +} +void STDMETHODCALLTYPE D3D10Device::VSGetConstantBuffers(UINT StartSlot, UINT NumBuffers, ID3D10Buffer **ppConstantBuffers) +{ + _orig->VSGetConstantBuffers(StartSlot, NumBuffers, ppConstantBuffers); +} +void STDMETHODCALLTYPE D3D10Device::PSGetShaderResources(UINT StartSlot, UINT NumViews, ID3D10ShaderResourceView **ppShaderResourceViews) +{ + _orig->PSGetShaderResources(StartSlot, NumViews, ppShaderResourceViews); +} +void STDMETHODCALLTYPE D3D10Device::PSGetShader(ID3D10PixelShader **ppPixelShader) +{ + _orig->PSGetShader(ppPixelShader); +} +void STDMETHODCALLTYPE D3D10Device::PSGetSamplers(UINT StartSlot, UINT NumSamplers, ID3D10SamplerState **ppSamplers) +{ + _orig->PSGetSamplers(StartSlot, NumSamplers, ppSamplers); +} +void STDMETHODCALLTYPE D3D10Device::VSGetShader(ID3D10VertexShader **ppVertexShader) +{ + _orig->VSGetShader(ppVertexShader); +} +void STDMETHODCALLTYPE D3D10Device::PSGetConstantBuffers(UINT StartSlot, UINT NumBuffers, ID3D10Buffer **ppConstantBuffers) +{ + _orig->PSGetConstantBuffers(StartSlot, NumBuffers, ppConstantBuffers); +} +void STDMETHODCALLTYPE D3D10Device::IAGetInputLayout(ID3D10InputLayout **ppInputLayout) +{ + _orig->IAGetInputLayout(ppInputLayout); +} +void STDMETHODCALLTYPE D3D10Device::IAGetVertexBuffers(UINT StartSlot, UINT NumBuffers, ID3D10Buffer **ppVertexBuffers, UINT *pStrides, UINT *pOffsets) +{ + _orig->IAGetVertexBuffers(StartSlot, NumBuffers, ppVertexBuffers, pStrides, pOffsets); +} +void STDMETHODCALLTYPE D3D10Device::IAGetIndexBuffer(ID3D10Buffer **pIndexBuffer, DXGI_FORMAT *Format, UINT *Offset) +{ + _orig->IAGetIndexBuffer(pIndexBuffer, Format, Offset); +} +void STDMETHODCALLTYPE D3D10Device::GSGetConstantBuffers(UINT StartSlot, UINT NumBuffers, ID3D10Buffer **ppConstantBuffers) +{ + _orig->GSGetConstantBuffers(StartSlot, NumBuffers, ppConstantBuffers); +} +void STDMETHODCALLTYPE D3D10Device::GSGetShader(ID3D10GeometryShader **ppGeometryShader) +{ + _orig->GSGetShader(ppGeometryShader); +} +void STDMETHODCALLTYPE D3D10Device::IAGetPrimitiveTopology(D3D10_PRIMITIVE_TOPOLOGY *pTopology) +{ + _orig->IAGetPrimitiveTopology(pTopology); +} +void STDMETHODCALLTYPE D3D10Device::VSGetShaderResources(UINT StartSlot, UINT NumViews, ID3D10ShaderResourceView **ppShaderResourceViews) +{ + _orig->VSGetShaderResources(StartSlot, NumViews, ppShaderResourceViews); +} +void STDMETHODCALLTYPE D3D10Device::VSGetSamplers(UINT StartSlot, UINT NumSamplers, ID3D10SamplerState **ppSamplers) +{ + _orig->VSGetSamplers(StartSlot, NumSamplers, ppSamplers); +} +void STDMETHODCALLTYPE D3D10Device::GetPredication(ID3D10Predicate **ppPredicate, BOOL *pPredicateValue) +{ + _orig->GetPredication(ppPredicate, pPredicateValue); +} +void STDMETHODCALLTYPE D3D10Device::GSGetShaderResources(UINT StartSlot, UINT NumViews, ID3D10ShaderResourceView **ppShaderResourceViews) +{ + _orig->GSGetShaderResources(StartSlot, NumViews, ppShaderResourceViews); +} +void STDMETHODCALLTYPE D3D10Device::GSGetSamplers(UINT StartSlot, UINT NumSamplers, ID3D10SamplerState **ppSamplers) +{ + _orig->GSGetSamplers(StartSlot, NumSamplers, ppSamplers); +} +void STDMETHODCALLTYPE D3D10Device::OMGetRenderTargets(UINT NumViews, ID3D10RenderTargetView **ppRenderTargetViews, ID3D10DepthStencilView **ppDepthStencilView) +{ + _orig->OMGetRenderTargets(NumViews, ppRenderTargetViews, ppDepthStencilView); +} +void STDMETHODCALLTYPE D3D10Device::OMGetBlendState(ID3D10BlendState **ppBlendState, FLOAT BlendFactor[4], UINT *pSampleMask) +{ + _orig->OMGetBlendState(ppBlendState, BlendFactor, pSampleMask); +} +void STDMETHODCALLTYPE D3D10Device::OMGetDepthStencilState(ID3D10DepthStencilState **ppDepthStencilState, UINT *pStencilRef) +{ + _orig->OMGetDepthStencilState(ppDepthStencilState, pStencilRef); +} +void STDMETHODCALLTYPE D3D10Device::SOGetTargets(UINT NumBuffers, ID3D10Buffer **ppSOTargets, UINT *pOffsets) +{ + _orig->SOGetTargets(NumBuffers, ppSOTargets, pOffsets); +} +void STDMETHODCALLTYPE D3D10Device::RSGetState(ID3D10RasterizerState **ppRasterizerState) +{ + _orig->RSGetState(ppRasterizerState); +} +void STDMETHODCALLTYPE D3D10Device::RSGetViewports(UINT *NumViewports, D3D10_VIEWPORT *pViewports) +{ + _orig->RSGetViewports(NumViewports, pViewports); +} +void STDMETHODCALLTYPE D3D10Device::RSGetScissorRects(UINT *NumRects, D3D10_RECT *pRects) +{ + _orig->RSGetScissorRects(NumRects, pRects); +} +HRESULT STDMETHODCALLTYPE D3D10Device::GetDeviceRemovedReason() +{ + return _orig->GetDeviceRemovedReason(); +} +HRESULT STDMETHODCALLTYPE D3D10Device::SetExceptionMode(UINT RaiseFlags) +{ + return _orig->SetExceptionMode(RaiseFlags); +} +UINT STDMETHODCALLTYPE D3D10Device::GetExceptionMode() +{ + return _orig->GetExceptionMode(); +} +HRESULT STDMETHODCALLTYPE D3D10Device::GetPrivateData(REFGUID guid, UINT *pDataSize, void *pData) +{ + return _orig->GetPrivateData(guid, pDataSize, pData); +} +HRESULT STDMETHODCALLTYPE D3D10Device::SetPrivateData(REFGUID guid, UINT DataSize, const void *pData) +{ + return _orig->SetPrivateData(guid, DataSize, pData); +} +HRESULT STDMETHODCALLTYPE D3D10Device::SetPrivateDataInterface(REFGUID guid, const IUnknown *pData) +{ + return _orig->SetPrivateDataInterface(guid, pData); +} +void STDMETHODCALLTYPE D3D10Device::ClearState() +{ + _orig->ClearState(); +} +void STDMETHODCALLTYPE D3D10Device::Flush() +{ + _orig->Flush(); +} +HRESULT STDMETHODCALLTYPE D3D10Device::CreateBuffer(const D3D10_BUFFER_DESC *pDesc, const D3D10_SUBRESOURCE_DATA *pInitialData, ID3D10Buffer **ppBuffer) +{ + return _orig->CreateBuffer(pDesc, pInitialData, ppBuffer); +} +HRESULT STDMETHODCALLTYPE D3D10Device::CreateTexture1D(const D3D10_TEXTURE1D_DESC *pDesc, const D3D10_SUBRESOURCE_DATA *pInitialData, ID3D10Texture1D **ppTexture1D) +{ + return _orig->CreateTexture1D(pDesc, pInitialData, ppTexture1D); +} +HRESULT STDMETHODCALLTYPE D3D10Device::CreateTexture2D(const D3D10_TEXTURE2D_DESC *pDesc, const D3D10_SUBRESOURCE_DATA *pInitialData, ID3D10Texture2D **ppTexture2D) +{ + return _orig->CreateTexture2D(pDesc, pInitialData, ppTexture2D); +} +HRESULT STDMETHODCALLTYPE D3D10Device::CreateTexture3D(const D3D10_TEXTURE3D_DESC *pDesc, const D3D10_SUBRESOURCE_DATA *pInitialData, ID3D10Texture3D **ppTexture3D) +{ + return _orig->CreateTexture3D(pDesc, pInitialData, ppTexture3D); +} +HRESULT STDMETHODCALLTYPE D3D10Device::CreateShaderResourceView(ID3D10Resource *pResource, const D3D10_SHADER_RESOURCE_VIEW_DESC *pDesc, ID3D10ShaderResourceView **ppSRView) +{ + return _orig->CreateShaderResourceView(pResource, pDesc, ppSRView); +} +HRESULT STDMETHODCALLTYPE D3D10Device::CreateRenderTargetView(ID3D10Resource *pResource, const D3D10_RENDER_TARGET_VIEW_DESC *pDesc, ID3D10RenderTargetView **ppRTView) +{ + return _orig->CreateRenderTargetView(pResource, pDesc, ppRTView); +} +HRESULT STDMETHODCALLTYPE D3D10Device::CreateDepthStencilView(ID3D10Resource *pResource, const D3D10_DEPTH_STENCIL_VIEW_DESC *pDesc, ID3D10DepthStencilView **ppDepthStencilView) +{ + return _orig->CreateDepthStencilView(pResource, pDesc, ppDepthStencilView); +} +HRESULT STDMETHODCALLTYPE D3D10Device::CreateInputLayout(const D3D10_INPUT_ELEMENT_DESC *pInputElementDescs, UINT NumElements, const void *pShaderBytecodeWithInputSignature, SIZE_T BytecodeLength, ID3D10InputLayout **ppInputLayout) +{ + return _orig->CreateInputLayout(pInputElementDescs, NumElements, pShaderBytecodeWithInputSignature, BytecodeLength, ppInputLayout); +} +HRESULT STDMETHODCALLTYPE D3D10Device::CreateVertexShader(const void *pShaderBytecode, SIZE_T BytecodeLength, ID3D10VertexShader **ppVertexShader) +{ + return _orig->CreateVertexShader(pShaderBytecode, BytecodeLength, ppVertexShader); +} +HRESULT STDMETHODCALLTYPE D3D10Device::CreateGeometryShader(const void *pShaderBytecode, SIZE_T BytecodeLength, ID3D10GeometryShader **ppGeometryShader) +{ + return _orig->CreateGeometryShader(pShaderBytecode, BytecodeLength, ppGeometryShader); +} +HRESULT STDMETHODCALLTYPE D3D10Device::CreateGeometryShaderWithStreamOutput(const void *pShaderBytecode, SIZE_T BytecodeLength, const D3D10_SO_DECLARATION_ENTRY *pSODeclaration, UINT NumEntries, UINT OutputStreamStride, ID3D10GeometryShader **ppGeometryShader) +{ + return _orig->CreateGeometryShaderWithStreamOutput(pShaderBytecode, BytecodeLength, pSODeclaration, NumEntries, OutputStreamStride, ppGeometryShader); +} +HRESULT STDMETHODCALLTYPE D3D10Device::CreatePixelShader(const void *pShaderBytecode, SIZE_T BytecodeLength, ID3D10PixelShader **ppPixelShader) +{ + return _orig->CreatePixelShader(pShaderBytecode, BytecodeLength, ppPixelShader); +} +HRESULT STDMETHODCALLTYPE D3D10Device::CreateBlendState(const D3D10_BLEND_DESC *pBlendStateDesc, ID3D10BlendState **ppBlendState) +{ + return _orig->CreateBlendState(pBlendStateDesc, ppBlendState); +} +HRESULT STDMETHODCALLTYPE D3D10Device::CreateDepthStencilState(const D3D10_DEPTH_STENCIL_DESC *pDepthStencilDesc, ID3D10DepthStencilState **ppDepthStencilState) +{ + return _orig->CreateDepthStencilState(pDepthStencilDesc, ppDepthStencilState); +} +HRESULT STDMETHODCALLTYPE D3D10Device::CreateRasterizerState(const D3D10_RASTERIZER_DESC *pRasterizerDesc, ID3D10RasterizerState **ppRasterizerState) +{ + return _orig->CreateRasterizerState(pRasterizerDesc, ppRasterizerState); +} +HRESULT STDMETHODCALLTYPE D3D10Device::CreateSamplerState(const D3D10_SAMPLER_DESC *pSamplerDesc, ID3D10SamplerState **ppSamplerState) +{ + return _orig->CreateSamplerState(pSamplerDesc, ppSamplerState); +} +HRESULT STDMETHODCALLTYPE D3D10Device::CreateQuery(const D3D10_QUERY_DESC *pQueryDesc, ID3D10Query **ppQuery) +{ + return _orig->CreateQuery(pQueryDesc, ppQuery); +} +HRESULT STDMETHODCALLTYPE D3D10Device::CreatePredicate(const D3D10_QUERY_DESC *pPredicateDesc, ID3D10Predicate **ppPredicate) +{ + return _orig->CreatePredicate(pPredicateDesc, ppPredicate); +} +HRESULT STDMETHODCALLTYPE D3D10Device::CreateCounter(const D3D10_COUNTER_DESC *pCounterDesc, ID3D10Counter **ppCounter) +{ + return _orig->CreateCounter(pCounterDesc, ppCounter); +} +HRESULT STDMETHODCALLTYPE D3D10Device::CheckFormatSupport(DXGI_FORMAT Format, UINT *pFormatSupport) +{ + return _orig->CheckFormatSupport(Format, pFormatSupport); +} +HRESULT STDMETHODCALLTYPE D3D10Device::CheckMultisampleQualityLevels(DXGI_FORMAT Format, UINT SampleCount, UINT *pNumQualityLevels) +{ + return _orig->CheckMultisampleQualityLevels(Format, SampleCount, pNumQualityLevels); +} +void STDMETHODCALLTYPE D3D10Device::CheckCounterInfo(D3D10_COUNTER_INFO *pCounterInfo) +{ + _orig->CheckCounterInfo(pCounterInfo); +} +HRESULT STDMETHODCALLTYPE D3D10Device::CheckCounter(const D3D10_COUNTER_DESC *pDesc, D3D10_COUNTER_TYPE *pType, UINT *pActiveCounters, LPSTR szName, UINT *pNameLength, LPSTR szUnits, UINT *pUnitsLength, LPSTR szDescription, UINT *pDescriptionLength) +{ + return _orig->CheckCounter(pDesc, pType, pActiveCounters, szName, pNameLength, szUnits, pUnitsLength, szDescription, pDescriptionLength); +} +UINT STDMETHODCALLTYPE D3D10Device::GetCreationFlags() +{ + return _orig->GetCreationFlags(); +} +HRESULT STDMETHODCALLTYPE D3D10Device::OpenSharedResource(HANDLE hResource, REFIID ReturnedInterface, void **ppResource) +{ + return _orig->OpenSharedResource(hResource, ReturnedInterface, ppResource); +} +void STDMETHODCALLTYPE D3D10Device::SetTextFilterSize(UINT Width, UINT Height) +{ + _orig->SetTextFilterSize(Width, Height); +} +void STDMETHODCALLTYPE D3D10Device::GetTextFilterSize(UINT *pWidth, UINT *pHeight) +{ + _orig->GetTextFilterSize(pWidth, pHeight); +} + +HRESULT STDMETHODCALLTYPE D3D10Device::CreateShaderResourceView1(ID3D10Resource *pResource, const D3D10_SHADER_RESOURCE_VIEW_DESC1 *pDesc, ID3D10ShaderResourceView1 **ppSRView) +{ + return _orig->CreateShaderResourceView1(pResource, pDesc, ppSRView); +} +HRESULT STDMETHODCALLTYPE D3D10Device::CreateBlendState1(const D3D10_BLEND_DESC1 *pBlendStateDesc, ID3D10BlendState1 **ppBlendState) +{ + return _orig->CreateBlendState1(pBlendStateDesc, ppBlendState); +} +D3D10_FEATURE_LEVEL1 STDMETHODCALLTYPE D3D10Device::GetFeatureLevel() +{ + return _orig->GetFeatureLevel(); +} diff --git a/msvc/source/d3d10/d3d10_device.hpp b/msvc/source/d3d10/d3d10_device.hpp new file mode 100644 index 0000000..8260617 --- /dev/null +++ b/msvc/source/d3d10/d3d10_device.hpp @@ -0,0 +1,144 @@ +/** + * Copyright (C) 2014 Patrick Mours. All rights reserved. + * License: https://github.com/crosire/reshade#license + */ + +#pragma once + +#include +#include "draw_call_tracker.hpp" + +struct DXGIDevice; +namespace reshade::d3d10 { class runtime_d3d10; } + +struct __declspec(uuid("88399375-734F-4892-A95F-70DD42CE7CDD")) D3D10Device : ID3D10Device1 +{ + D3D10Device(IDXGIDevice1 *dxgi_device, ID3D10Device1 *original); + + D3D10Device(const D3D10Device &) = delete; + D3D10Device &operator=(const D3D10Device &) = delete; + + HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void **ppvObj) override; + ULONG STDMETHODCALLTYPE AddRef() override; + ULONG STDMETHODCALLTYPE Release() override; + + #pragma region ID3D10Device + void STDMETHODCALLTYPE VSSetConstantBuffers(UINT StartSlot, UINT NumBuffers, ID3D10Buffer *const *ppConstantBuffers) override; + void STDMETHODCALLTYPE PSSetShaderResources(UINT StartSlot, UINT NumViews, ID3D10ShaderResourceView *const *ppShaderResourceViews) override; + void STDMETHODCALLTYPE PSSetShader(ID3D10PixelShader *pPixelShader) override; + void STDMETHODCALLTYPE PSSetSamplers(UINT StartSlot, UINT NumSamplers, ID3D10SamplerState *const *ppSamplers) override; + void STDMETHODCALLTYPE VSSetShader(ID3D10VertexShader *pVertexShader) override; + void STDMETHODCALLTYPE DrawIndexed(UINT IndexCount, UINT StartIndexLocation, INT BaseVertexLocation) override; + void STDMETHODCALLTYPE Draw(UINT VertexCount, UINT StartVertexLocation) override; + void STDMETHODCALLTYPE PSSetConstantBuffers(UINT StartSlot, UINT NumBuffers, ID3D10Buffer *const *ppConstantBuffers) override; + void STDMETHODCALLTYPE IASetInputLayout(ID3D10InputLayout *pInputLayout) override; + void STDMETHODCALLTYPE IASetVertexBuffers(UINT StartSlot, UINT NumBuffers, ID3D10Buffer *const *ppVertexBuffers, const UINT *pStrides, const UINT *pOffsets) override; + void STDMETHODCALLTYPE IASetIndexBuffer(ID3D10Buffer *pIndexBuffer, DXGI_FORMAT Format, UINT Offset) override; + void STDMETHODCALLTYPE DrawIndexedInstanced(UINT IndexCountPerInstance, UINT InstanceCount, UINT StartIndexLocation, INT BaseVertexLocation, UINT StartInstanceLocation) override; + void STDMETHODCALLTYPE DrawInstanced(UINT VertexCountPerInstance, UINT InstanceCount, UINT StartVertexLocation, UINT StartInstanceLocation) override; + void STDMETHODCALLTYPE GSSetConstantBuffers(UINT StartSlot, UINT NumBuffers, ID3D10Buffer *const *ppConstantBuffers) override; + void STDMETHODCALLTYPE GSSetShader(ID3D10GeometryShader *pShader) override; + void STDMETHODCALLTYPE IASetPrimitiveTopology(D3D10_PRIMITIVE_TOPOLOGY Topology) override; + void STDMETHODCALLTYPE VSSetShaderResources(UINT StartSlot, UINT NumViews, ID3D10ShaderResourceView *const *ppShaderResourceViews) override; + void STDMETHODCALLTYPE VSSetSamplers(UINT StartSlot, UINT NumSamplers, ID3D10SamplerState *const *ppSamplers) override; + void STDMETHODCALLTYPE SetPredication(ID3D10Predicate *pPredicate, BOOL PredicateValue) override; + void STDMETHODCALLTYPE GSSetShaderResources(UINT StartSlot, UINT NumViews, ID3D10ShaderResourceView *const *ppShaderResourceViews) override; + void STDMETHODCALLTYPE GSSetSamplers(UINT StartSlot, UINT NumSamplers, ID3D10SamplerState *const *ppSamplers) override; + void STDMETHODCALLTYPE OMSetRenderTargets(UINT NumViews, ID3D10RenderTargetView *const *ppRenderTargetViews, ID3D10DepthStencilView *pDepthStencilView) override; + void STDMETHODCALLTYPE OMSetBlendState(ID3D10BlendState *pBlendState, const FLOAT BlendFactor[4], UINT SampleMask) override; + void STDMETHODCALLTYPE OMSetDepthStencilState(ID3D10DepthStencilState *pDepthStencilState, UINT StencilRef) override; + void STDMETHODCALLTYPE SOSetTargets(UINT NumBuffers, ID3D10Buffer *const *ppSOTargets, const UINT *pOffsets) override; + void STDMETHODCALLTYPE DrawAuto() override; + void STDMETHODCALLTYPE RSSetState(ID3D10RasterizerState *pRasterizerState) override; + void STDMETHODCALLTYPE RSSetViewports(UINT NumViewports, const D3D10_VIEWPORT *pViewports) override; + void STDMETHODCALLTYPE RSSetScissorRects(UINT NumRects, const D3D10_RECT *pRects) override; + void STDMETHODCALLTYPE CopySubresourceRegion(ID3D10Resource *pDstResource, UINT DstSubresource, UINT DstX, UINT DstY, UINT DstZ, ID3D10Resource *pSrcResource, UINT SrcSubresource, const D3D10_BOX *pSrcBox) override; + void STDMETHODCALLTYPE CopyResource(ID3D10Resource *pDstResource, ID3D10Resource *pSrcResource) override; + void STDMETHODCALLTYPE UpdateSubresource(ID3D10Resource *pDstResource, UINT DstSubresource, const D3D10_BOX *pDstBox, const void *pSrcData, UINT SrcRowPitch, UINT SrcDepthPitch) override; + void STDMETHODCALLTYPE ClearRenderTargetView(ID3D10RenderTargetView *pRenderTargetView, const FLOAT ColorRGBA[4]) override; + void STDMETHODCALLTYPE ClearDepthStencilView(ID3D10DepthStencilView *pDepthStencilView, UINT ClearFlags, FLOAT Depth, UINT8 Stencil) override; + void STDMETHODCALLTYPE GenerateMips(ID3D10ShaderResourceView *pShaderResourceView) override; + void STDMETHODCALLTYPE ResolveSubresource(ID3D10Resource *pDstResource, UINT DstSubresource, ID3D10Resource *pSrcResource, UINT SrcSubresource, DXGI_FORMAT Format) override; + void STDMETHODCALLTYPE VSGetConstantBuffers(UINT StartSlot, UINT NumBuffers, ID3D10Buffer **ppConstantBuffers) override; + void STDMETHODCALLTYPE PSGetShaderResources(UINT StartSlot, UINT NumViews, ID3D10ShaderResourceView **ppShaderResourceViews) override; + void STDMETHODCALLTYPE PSGetShader(ID3D10PixelShader **ppPixelShader) override; + void STDMETHODCALLTYPE PSGetSamplers(UINT StartSlot, UINT NumSamplers, ID3D10SamplerState **ppSamplers) override; + void STDMETHODCALLTYPE VSGetShader(ID3D10VertexShader **ppVertexShader) override; + void STDMETHODCALLTYPE PSGetConstantBuffers(UINT StartSlot, UINT NumBuffers, ID3D10Buffer **ppConstantBuffers) override; + void STDMETHODCALLTYPE IAGetInputLayout(ID3D10InputLayout **ppInputLayout) override; + void STDMETHODCALLTYPE IAGetVertexBuffers(UINT StartSlot, UINT NumBuffers, ID3D10Buffer **ppVertexBuffers, UINT *pStrides, UINT *pOffsets) override; + void STDMETHODCALLTYPE IAGetIndexBuffer(ID3D10Buffer **pIndexBuffer, DXGI_FORMAT *Format, UINT *Offset) override; + void STDMETHODCALLTYPE GSGetConstantBuffers(UINT StartSlot, UINT NumBuffers, ID3D10Buffer **ppConstantBuffers) override; + void STDMETHODCALLTYPE GSGetShader(ID3D10GeometryShader **ppGeometryShader) override; + void STDMETHODCALLTYPE IAGetPrimitiveTopology(D3D10_PRIMITIVE_TOPOLOGY *pTopology) override; + void STDMETHODCALLTYPE VSGetShaderResources(UINT StartSlot, UINT NumViews, ID3D10ShaderResourceView **ppShaderResourceViews) override; + void STDMETHODCALLTYPE VSGetSamplers(UINT StartSlot, UINT NumSamplers, ID3D10SamplerState **ppSamplers) override; + void STDMETHODCALLTYPE GetPredication(ID3D10Predicate **ppPredicate, BOOL *pPredicateValue) override; + void STDMETHODCALLTYPE GSGetShaderResources(UINT StartSlot, UINT NumViews, ID3D10ShaderResourceView **ppShaderResourceViews) override; + void STDMETHODCALLTYPE GSGetSamplers(UINT StartSlot, UINT NumSamplers, ID3D10SamplerState **ppSamplers) override; + void STDMETHODCALLTYPE OMGetRenderTargets(UINT NumViews, ID3D10RenderTargetView **ppRenderTargetViews, ID3D10DepthStencilView **ppDepthStencilView) override; + void STDMETHODCALLTYPE OMGetBlendState(ID3D10BlendState **ppBlendState, FLOAT BlendFactor[4], UINT *pSampleMask) override; + void STDMETHODCALLTYPE OMGetDepthStencilState(ID3D10DepthStencilState **ppDepthStencilState, UINT *pStencilRef) override; + void STDMETHODCALLTYPE SOGetTargets(UINT NumBuffers, ID3D10Buffer **ppSOTargets, UINT *pOffsets) override; + void STDMETHODCALLTYPE RSGetState(ID3D10RasterizerState **ppRasterizerState) override; + void STDMETHODCALLTYPE RSGetViewports(UINT *NumViewports, D3D10_VIEWPORT *pViewports) override; + void STDMETHODCALLTYPE RSGetScissorRects(UINT *NumRects, D3D10_RECT *pRects) override; + HRESULT STDMETHODCALLTYPE GetDeviceRemovedReason() override; + HRESULT STDMETHODCALLTYPE SetExceptionMode(UINT RaiseFlags) override; + UINT STDMETHODCALLTYPE GetExceptionMode() override; + HRESULT STDMETHODCALLTYPE GetPrivateData(REFGUID guid, UINT *pDataSize, void *pData) override; + HRESULT STDMETHODCALLTYPE SetPrivateData(REFGUID guid, UINT DataSize, const void *pData) override; + HRESULT STDMETHODCALLTYPE SetPrivateDataInterface(REFGUID guid, const IUnknown *pData) override; + void STDMETHODCALLTYPE ClearState() override; + void STDMETHODCALLTYPE Flush() override; + HRESULT STDMETHODCALLTYPE CreateBuffer(const D3D10_BUFFER_DESC *pDesc, const D3D10_SUBRESOURCE_DATA *pInitialData, ID3D10Buffer **ppBuffer) override; + HRESULT STDMETHODCALLTYPE CreateTexture1D(const D3D10_TEXTURE1D_DESC *pDesc, const D3D10_SUBRESOURCE_DATA *pInitialData, ID3D10Texture1D **ppTexture1D) override; + HRESULT STDMETHODCALLTYPE CreateTexture2D(const D3D10_TEXTURE2D_DESC *pDesc, const D3D10_SUBRESOURCE_DATA *pInitialData, ID3D10Texture2D **ppTexture2D) override; + HRESULT STDMETHODCALLTYPE CreateTexture3D(const D3D10_TEXTURE3D_DESC *pDesc, const D3D10_SUBRESOURCE_DATA *pInitialData, ID3D10Texture3D **ppTexture3D) override; + HRESULT STDMETHODCALLTYPE CreateShaderResourceView(ID3D10Resource *pResource, const D3D10_SHADER_RESOURCE_VIEW_DESC *pDesc, ID3D10ShaderResourceView **ppSRView) override; + HRESULT STDMETHODCALLTYPE CreateRenderTargetView(ID3D10Resource *pResource, const D3D10_RENDER_TARGET_VIEW_DESC *pDesc, ID3D10RenderTargetView **ppRTView) override; + HRESULT STDMETHODCALLTYPE CreateDepthStencilView(ID3D10Resource *pResource, const D3D10_DEPTH_STENCIL_VIEW_DESC *pDesc, ID3D10DepthStencilView **ppDepthStencilView) override; + HRESULT STDMETHODCALLTYPE CreateInputLayout(const D3D10_INPUT_ELEMENT_DESC *pInputElementDescs, UINT NumElements, const void *pShaderBytecodeWithInputSignature, SIZE_T BytecodeLength, ID3D10InputLayout **ppInputLayout) override; + HRESULT STDMETHODCALLTYPE CreateVertexShader(const void *pShaderBytecode, SIZE_T BytecodeLength, ID3D10VertexShader **ppVertexShader) override; + HRESULT STDMETHODCALLTYPE CreateGeometryShader(const void *pShaderBytecode, SIZE_T BytecodeLength, ID3D10GeometryShader **ppGeometryShader) override; + HRESULT STDMETHODCALLTYPE CreateGeometryShaderWithStreamOutput(const void *pShaderBytecode, SIZE_T BytecodeLength, const D3D10_SO_DECLARATION_ENTRY *pSODeclaration, UINT NumEntries, UINT OutputStreamStride, ID3D10GeometryShader **ppGeometryShader) override; + HRESULT STDMETHODCALLTYPE CreatePixelShader(const void *pShaderBytecode, SIZE_T BytecodeLength, ID3D10PixelShader **ppPixelShader) override; + HRESULT STDMETHODCALLTYPE CreateBlendState(const D3D10_BLEND_DESC *pBlendStateDesc, ID3D10BlendState **ppBlendState) override; + HRESULT STDMETHODCALLTYPE CreateDepthStencilState(const D3D10_DEPTH_STENCIL_DESC *pDepthStencilDesc, ID3D10DepthStencilState **ppDepthStencilState) override; + HRESULT STDMETHODCALLTYPE CreateRasterizerState(const D3D10_RASTERIZER_DESC *pRasterizerDesc, ID3D10RasterizerState **ppRasterizerState) override; + HRESULT STDMETHODCALLTYPE CreateSamplerState(const D3D10_SAMPLER_DESC *pSamplerDesc, ID3D10SamplerState **ppSamplerState) override; + HRESULT STDMETHODCALLTYPE CreateQuery(const D3D10_QUERY_DESC *pQueryDesc, ID3D10Query **ppQuery) override; + HRESULT STDMETHODCALLTYPE CreatePredicate(const D3D10_QUERY_DESC *pPredicateDesc, ID3D10Predicate **ppPredicate) override; + HRESULT STDMETHODCALLTYPE CreateCounter(const D3D10_COUNTER_DESC *pCounterDesc, ID3D10Counter **ppCounter) override; + HRESULT STDMETHODCALLTYPE CheckFormatSupport(DXGI_FORMAT Format, UINT *pFormatSupport) override; + HRESULT STDMETHODCALLTYPE CheckMultisampleQualityLevels(DXGI_FORMAT Format, UINT SampleCount, UINT *pNumQualityLevels) override; + void STDMETHODCALLTYPE CheckCounterInfo(D3D10_COUNTER_INFO *pCounterInfo) override; + HRESULT STDMETHODCALLTYPE CheckCounter(const D3D10_COUNTER_DESC *pDesc, D3D10_COUNTER_TYPE *pType, UINT *pActiveCounters, LPSTR szName, UINT *pNameLength, LPSTR szUnits, UINT *pUnitsLength, LPSTR szDescription, UINT *pDescriptionLength) override; + UINT STDMETHODCALLTYPE GetCreationFlags() override; + HRESULT STDMETHODCALLTYPE OpenSharedResource(HANDLE hResource, REFIID ReturnedInterface, void **ppResource) override; + void STDMETHODCALLTYPE SetTextFilterSize(UINT Width, UINT Height) override; + void STDMETHODCALLTYPE GetTextFilterSize(UINT *pWidth, UINT *pHeight) override; + #pragma endregion + #pragma region ID3D10Device1 + HRESULT STDMETHODCALLTYPE CreateShaderResourceView1(ID3D10Resource *pResource, const D3D10_SHADER_RESOURCE_VIEW_DESC1 *pDesc, ID3D10ShaderResourceView1 **ppSRView) override; + HRESULT STDMETHODCALLTYPE CreateBlendState1(const D3D10_BLEND_DESC1 *pBlendStateDesc, ID3D10BlendState1 **ppBlendState) override; + D3D10_FEATURE_LEVEL1 STDMETHODCALLTYPE GetFeatureLevel() override; + #pragma endregion + + void clear_drawcall_stats(); + +#if RESHADE_DX10_CAPTURE_DEPTH_BUFFERS + bool save_depth_texture(ID3D10DepthStencilView *pDepthStencilView, bool cleared); + + void track_active_rendertargets(UINT NumViews, ID3D10RenderTargetView *const *ppRenderTargetViews, ID3D10DepthStencilView *pDepthStencilView); + void track_cleared_depthstencil(ID3D10DepthStencilView* pDepthStencilView); +#endif + + LONG _ref = 1; + ID3D10Device1 *_orig; + DXGIDevice *const _dxgi_device; + std::vector> _runtimes; + com_ptr _active_depthstencil; + reshade::d3d10::draw_call_tracker _draw_call_tracker; + unsigned int _clear_DSV_iter = 1; +}; diff --git a/msvc/source/d3d10/draw_call_tracker.cpp b/msvc/source/d3d10/draw_call_tracker.cpp new file mode 100644 index 0000000..bc99d79 --- /dev/null +++ b/msvc/source/d3d10/draw_call_tracker.cpp @@ -0,0 +1,285 @@ +#include "draw_call_tracker.hpp" +#include "log.hpp" +#include "dxgi/format_utils.hpp" +#include + +namespace reshade::d3d10 +{ + void draw_call_tracker::merge(const draw_call_tracker& source) + { + _global_counter.vertices += source.total_vertices(); + _global_counter.drawcalls += source.total_drawcalls(); + +#if RESHADE_DX10_CAPTURE_DEPTH_BUFFERS + for (const auto &[depthstencil, snapshot] : source._counters_per_used_depthstencil) + { + _counters_per_used_depthstencil[depthstencil].stats.vertices += snapshot.stats.vertices; + _counters_per_used_depthstencil[depthstencil].stats.drawcalls += snapshot.stats.drawcalls; + } + + for (auto source_entry : source._cleared_depth_textures) + { + const auto destination_entry = _cleared_depth_textures.find(source_entry.first); + + if (destination_entry == _cleared_depth_textures.end()) + _cleared_depth_textures.emplace(source_entry.first, source_entry.second); + } +#endif +#if RESHADE_DX10_CAPTURE_CONSTANT_BUFFERS + for (const auto &[buffer, snapshot] : source._counters_per_constant_buffer) + { + _counters_per_constant_buffer[buffer].vertices += snapshot.vertices; + _counters_per_constant_buffer[buffer].drawcalls += snapshot.drawcalls; + _counters_per_constant_buffer[buffer].ps_uses += snapshot.ps_uses; + _counters_per_constant_buffer[buffer].vs_uses += snapshot.vs_uses; + } +#endif + } + + void draw_call_tracker::reset() + { + _global_counter.vertices = 0; + _global_counter.drawcalls = 0; +#if RESHADE_DX10_CAPTURE_DEPTH_BUFFERS + _counters_per_used_depthstencil.clear(); + _cleared_depth_textures.clear(); +#endif +#if RESHADE_DX10_CAPTURE_CONSTANT_BUFFERS + _counters_per_constant_buffer.clear(); +#endif + } + + void draw_call_tracker::on_map(ID3D10Resource *resource) + { + UNREFERENCED_PARAMETER(resource); + +#if RESHADE_DX10_CAPTURE_CONSTANT_BUFFERS + D3D10_RESOURCE_DIMENSION dim; + resource->GetType(&dim); + + if (dim == D3D10_RESOURCE_DIMENSION_BUFFER) + _counters_per_constant_buffer[static_cast(resource)].mapped += 1; +#endif + } + + void draw_call_tracker::on_draw(ID3D10Device *device, UINT vertices) + { + UNREFERENCED_PARAMETER(device); + + _global_counter.vertices += vertices; + _global_counter.drawcalls += 1; + +#if RESHADE_DX10_CAPTURE_DEPTH_BUFFERS + com_ptr targets[D3D10_SIMULTANEOUS_RENDER_TARGET_COUNT]; + com_ptr depthstencil; + + device->OMGetRenderTargets(D3D10_SIMULTANEOUS_RENDER_TARGET_COUNT, reinterpret_cast(targets), &depthstencil); + + if (depthstencil == nullptr) + // This is a draw call with no depth stencil + return; + + if (const auto intermediate_snapshot = _counters_per_used_depthstencil.find(depthstencil); intermediate_snapshot != _counters_per_used_depthstencil.end()) + { + intermediate_snapshot->second.stats.vertices += vertices; + intermediate_snapshot->second.stats.drawcalls += 1; + + // Find the render targets, if they exist, and update their counts + for (UINT i = 0; i < D3D10_SIMULTANEOUS_RENDER_TARGET_COUNT; i++) + { + // Ignore empty slots + if (targets[i] == nullptr) + continue; + + if (const auto it = intermediate_snapshot->second.additional_views.find(targets[i].get()); it != intermediate_snapshot->second.additional_views.end()) + { + it->second.vertices += vertices; + it->second.drawcalls += 1; + } + else + { + // This shouldn't happen - it means somehow someone has called 'on_draw' with a render target without calling 'track_rendertargets' first + assert(false); + } + } + } +#endif +#if RESHADE_DX10_CAPTURE_CONSTANT_BUFFERS + // Capture constant buffers that are used when depth stencils are drawn + com_ptr vscbuffers[D3D10_COMMONSHADER_CONSTANT_BUFFER_API_SLOT_COUNT]; + device->VSGetConstantBuffers(0, ARRAYSIZE(vscbuffers), reinterpret_cast(vscbuffers)); + + for (UINT i = 0; i < ARRAYSIZE(vscbuffers); i++) + // Uses the default drawcalls = 0 the first time around. + if (vscbuffers[i] != nullptr) + _counters_per_constant_buffer[vscbuffers[i]].vs_uses += 1; + + com_ptr pscbuffers[D3D10_COMMONSHADER_CONSTANT_BUFFER_API_SLOT_COUNT]; + device->PSGetConstantBuffers(0, ARRAYSIZE(pscbuffers), reinterpret_cast(pscbuffers)); + + for (UINT i = 0; i < ARRAYSIZE(pscbuffers); i++) + // Uses the default drawcalls = 0 the first time around. + if (pscbuffers[i] != nullptr) + _counters_per_constant_buffer[pscbuffers[i]].ps_uses += 1; +#endif + } + +#if RESHADE_DX10_CAPTURE_DEPTH_BUFFERS + bool draw_call_tracker::check_depthstencil(ID3D10DepthStencilView *depthstencil) const + { + return _counters_per_used_depthstencil.find(depthstencil) != _counters_per_used_depthstencil.end(); + } + bool draw_call_tracker::check_depth_texture_format(int format_index, ID3D10DepthStencilView *depthstencil) + { + assert(depthstencil != nullptr); + + // Do not check format if all formats are allowed (index zero is DXGI_FORMAT_UNKNOWN) + if (format_index == 0) + return true; + + // Retrieve texture from depth stencil + com_ptr resource; + com_ptr texture; + depthstencil->GetResource(&resource); + if (FAILED(resource->QueryInterface(&texture))) + return false; + + D3D10_TEXTURE2D_DESC desc; + texture->GetDesc(&desc); + + const DXGI_FORMAT depth_texture_formats[] = { + DXGI_FORMAT_UNKNOWN, + DXGI_FORMAT_R16_TYPELESS, + DXGI_FORMAT_R32_TYPELESS, + DXGI_FORMAT_R24G8_TYPELESS, + DXGI_FORMAT_R32G8X24_TYPELESS + }; + + assert(format_index > 0 && format_index < ARRAYSIZE(depth_texture_formats)); + + return make_dxgi_format_typeless(desc.Format) == depth_texture_formats[format_index]; + } + + void draw_call_tracker::track_rendertargets(int format_index, ID3D10DepthStencilView *depthstencil, UINT num_views, ID3D10RenderTargetView *const *views) + { + assert(depthstencil != nullptr); + + if (!check_depth_texture_format(format_index, depthstencil)) + return; + + if (_counters_per_used_depthstencil[depthstencil].depthstencil == nullptr) + _counters_per_used_depthstencil[depthstencil].depthstencil = depthstencil; + + for (UINT i = 0; i < num_views; i++) + // If the render target isn't being tracked, this will create it + _counters_per_used_depthstencil[depthstencil].additional_views[views[i]].drawcalls += 1; + } + void draw_call_tracker::track_depth_texture(int format_index, UINT index, com_ptr src_texture, com_ptr src_depthstencil, com_ptr dest_texture, bool cleared) + { + // Function that keeps track of a cleared depth texture in an ordered map in order to retrieve it at the final rendering stage + assert(src_texture != nullptr); + + if (!check_depth_texture_format(format_index, src_depthstencil.get())) + return; + + // Gather some extra info for later display + D3D10_TEXTURE2D_DESC src_texture_desc; + src_texture->GetDesc(&src_texture_desc); + + // check if it is really a depth texture + assert((src_texture_desc.BindFlags & D3D10_BIND_DEPTH_STENCIL) != 0); + + // fill the ordered map with the saved depth texture + if (const auto it = _cleared_depth_textures.find(index); it == _cleared_depth_textures.end()) + _cleared_depth_textures.emplace(index, depth_texture_save_info { src_texture, src_depthstencil, src_texture_desc, dest_texture, cleared }); + else + it->second = depth_texture_save_info { src_texture, src_depthstencil, src_texture_desc, dest_texture, cleared }; + } + + draw_call_tracker::intermediate_snapshot_info draw_call_tracker::find_best_snapshot(UINT width, UINT height) + { + const float aspect_ratio = float(width) / float(height); + intermediate_snapshot_info best_snapshot; + + for (auto &[depthstencil, snapshot] : _counters_per_used_depthstencil) + { + if (snapshot.stats.drawcalls == 0 || snapshot.stats.vertices == 0) + continue; + + if (snapshot.texture == nullptr) + { + com_ptr resource; + depthstencil->GetResource(&resource); + if (FAILED(resource->QueryInterface(&snapshot.texture))) + continue; + } + + D3D10_TEXTURE2D_DESC desc; + snapshot.texture->GetDesc(&desc); + + assert((desc.BindFlags & D3D10_BIND_DEPTH_STENCIL) != 0); + + // Check aspect ratio + const float width_factor = desc.Width != width ? float(width) / desc.Width : 1.0f; + const float height_factor = desc.Height != height ? float(height) / desc.Height : 1.0f; + const float texture_aspect_ratio = float(desc.Width) / float(desc.Height); + + if (fabs(texture_aspect_ratio - aspect_ratio) > 0.1f || width_factor > 2.0f || height_factor > 2.0f || width_factor < 0.5f || height_factor < 0.5f) + continue; // No match, not a good fit + + if (snapshot.stats.drawcalls >= best_snapshot.stats.drawcalls) + best_snapshot = snapshot; + } + + return best_snapshot; + } + + void draw_call_tracker::keep_cleared_depth_textures() + { + // Function that keeps only the depth textures that has been retrieved before the last depth stencil clearance + std::map::reverse_iterator it = _cleared_depth_textures.rbegin(); + + // Reverse loop on the cleared depth textures map + while (it != _cleared_depth_textures.rend()) + { + // Exit if the last cleared depth stencil is found + if (it->second.cleared) + return; + + // Remove the depth texture if it was retrieved after the last clearance of the depth stencil + it = std::map::reverse_iterator(_cleared_depth_textures.erase(std::next(it).base())); + } + } + + ID3D10Texture2D *draw_call_tracker::find_best_cleared_depth_buffer_texture(UINT clear_index) + { + // Function that selects the best cleared depth texture according to the clearing number defined in the configuration settings + ID3D10Texture2D *best_match = nullptr; + + // Ensure to work only on the depth textures retrieved before the last depth stencil clearance + keep_cleared_depth_textures(); + + for (const auto &it : _cleared_depth_textures) + { + UINT i = it.first; + auto &texture_counter_info = it.second; + + com_ptr texture; + if (texture_counter_info.dest_texture == nullptr) + continue; + texture = texture_counter_info.dest_texture; + + if (clear_index != 0 && i > clear_index) + continue; + + // The _cleared_dept_textures ordered map stores the depth textures, according to the order of clearing + // if clear_index == 0, the auto select mode is defined, so the last cleared depth texture is retrieved + // if the user selects a clearing number and the number of cleared depth textures is greater or equal than it, the texture corresponding to this number is retrieved + // if the user selects a clearing number and the number of cleared depth textures is lower than it, the last cleared depth texture is retrieved + best_match = texture.get(); + } + + return best_match; + } +#endif +} diff --git a/msvc/source/d3d10/draw_call_tracker.hpp b/msvc/source/d3d10/draw_call_tracker.hpp new file mode 100644 index 0000000..1bb2ff1 --- /dev/null +++ b/msvc/source/d3d10/draw_call_tracker.hpp @@ -0,0 +1,86 @@ +#pragma once + +#include +#include +#include "com_ptr.hpp" + +#define RESHADE_DX10_CAPTURE_DEPTH_BUFFERS 1 +#define RESHADE_DX10_CAPTURE_CONSTANT_BUFFERS 0 + +namespace reshade::d3d10 +{ + class draw_call_tracker + { + public: + struct draw_stats + { + UINT vertices = 0; + UINT drawcalls = 0; + UINT mapped = 0; + UINT vs_uses = 0; + UINT ps_uses = 0; + }; + +#if RESHADE_DX10_CAPTURE_DEPTH_BUFFERS + struct intermediate_snapshot_info + { + ID3D10DepthStencilView *depthstencil = nullptr; // No need to use a 'com_ptr' here since '_counters_per_used_depthstencil' already keeps a reference + draw_stats stats; + com_ptr texture; + std::map additional_views; + }; +#endif + + UINT total_vertices() const { return _global_counter.vertices; } + UINT total_drawcalls() const { return _global_counter.drawcalls; } + +#if RESHADE_DX10_CAPTURE_DEPTH_BUFFERS + const auto &depth_buffer_counters() const { return _counters_per_used_depthstencil; } + const auto &cleared_depth_textures() const { return _cleared_depth_textures; } +#endif +#if RESHADE_DX10_CAPTURE_CONSTANT_BUFFERS + const auto &constant_buffer_counters() const { return _counters_per_constant_buffer; } +#endif + + void merge(const draw_call_tracker &source); + void reset(); + + void on_map(ID3D10Resource *pResource); + void on_draw(ID3D10Device *device, UINT vertices); + +#if RESHADE_DX10_CAPTURE_DEPTH_BUFFERS + void track_rendertargets(int format_index, ID3D10DepthStencilView *depthstencil, UINT num_views, ID3D10RenderTargetView *const *views); + void track_depth_texture(int format_index, UINT index, com_ptr src_texture, com_ptr src_depthstencil, com_ptr dest_texture, bool cleared); + + void keep_cleared_depth_textures(); + + intermediate_snapshot_info find_best_snapshot(UINT width, UINT height); + ID3D10Texture2D *find_best_cleared_depth_buffer_texture(UINT clear_index); +#endif + + private: + struct depth_texture_save_info + { + com_ptr src_texture; + com_ptr src_depthstencil; + D3D10_TEXTURE2D_DESC src_texture_desc; + com_ptr dest_texture; + bool cleared = false; + }; + +#if RESHADE_DX10_CAPTURE_DEPTH_BUFFERS + bool check_depthstencil(ID3D10DepthStencilView *depthstencil) const; + bool check_depth_texture_format(int format_index, ID3D10DepthStencilView *depthstencil); +#endif + + draw_stats _global_counter; +#if RESHADE_DX10_CAPTURE_DEPTH_BUFFERS + // Use "std::map" instead of "std::unordered_map" so that the iteration order is guaranteed + std::map, intermediate_snapshot_info> _counters_per_used_depthstencil; + std::map _cleared_depth_textures; +#endif +#if RESHADE_DX10_CAPTURE_CONSTANT_BUFFERS + std::map, draw_stats> _counters_per_constant_buffer; +#endif + }; +} diff --git a/msvc/source/d3d10/runtime_d3d10.cpp b/msvc/source/d3d10/runtime_d3d10.cpp new file mode 100644 index 0000000..fb4432e --- /dev/null +++ b/msvc/source/d3d10/runtime_d3d10.cpp @@ -0,0 +1,1586 @@ +/** + * Copyright (C) 2014 Patrick Mours. All rights reserved. + * License: https://github.com/crosire/reshade#license + */ + +#include "log.hpp" +#include "ini_file.hpp" +#include "runtime_d3d10.hpp" +#include "runtime_objects.hpp" +#include "resource_loading.hpp" +#include "dxgi/format_utils.hpp" +#include +#include + +namespace reshade::d3d10 +{ + struct d3d10_tex_data : base_object + { + com_ptr texture; + com_ptr srv[2]; + com_ptr rtv[2]; + }; + struct d3d10_pass_data : base_object + { + com_ptr vertex_shader; + com_ptr pixel_shader; + com_ptr blend_state; + com_ptr depth_stencil_state; + UINT stencil_reference; + bool clear_render_targets; + com_ptr render_targets[D3D10_SIMULTANEOUS_RENDER_TARGET_COUNT]; + com_ptr render_target_resources[D3D10_SIMULTANEOUS_RENDER_TARGET_COUNT]; + D3D10_VIEWPORT viewport; + std::vector> shader_resources; + }; + struct d3d10_technique_data : base_object + { + bool query_in_flight = false; + com_ptr timestamp_disjoint; + com_ptr timestamp_query_beg; + com_ptr timestamp_query_end; + std::vector> sampler_states; + std::vector> texture_bindings; + ptrdiff_t uniform_storage_offset = 0; + ptrdiff_t uniform_storage_index = -1; + }; +} + +reshade::d3d10::runtime_d3d10::runtime_d3d10(ID3D10Device1 *device, IDXGISwapChain *swapchain) : + _device(device), _swapchain(swapchain), + _app_state(device) +{ + assert(device != nullptr); + assert(swapchain != nullptr); + + com_ptr dxgi_device; + _device->QueryInterface(&dxgi_device); + com_ptr dxgi_adapter; + dxgi_device->GetAdapter(&dxgi_adapter); + + _renderer_id = device->GetFeatureLevel(); + if (DXGI_ADAPTER_DESC desc; SUCCEEDED(dxgi_adapter->GetDesc(&desc))) + _vendor_id = desc.VendorId, _device_id = desc.DeviceId; + +#if RESHADE_GUI + subscribe_to_ui("DX10", [this]() { draw_debug_menu(); }); +#endif + subscribe_to_load_config([this](const ini_file &config) { + config.get("DX10_BUFFER_DETECTION", "DepthBufferRetrievalMode", depth_buffer_before_clear); + config.get("DX10_BUFFER_DETECTION", "DepthBufferTextureFormat", depth_buffer_texture_format); + config.get("DX10_BUFFER_DETECTION", "ExtendedDepthBufferDetection", extended_depth_buffer_detection); + config.get("DX10_BUFFER_DETECTION", "DepthBufferClearingNumber", cleared_depth_buffer_index); + }); + subscribe_to_save_config([this](ini_file &config) { + config.set("DX10_BUFFER_DETECTION", "DepthBufferRetrievalMode", depth_buffer_before_clear); + config.set("DX10_BUFFER_DETECTION", "DepthBufferTextureFormat", depth_buffer_texture_format); + config.set("DX10_BUFFER_DETECTION", "ExtendedDepthBufferDetection", extended_depth_buffer_detection); + config.set("DX10_BUFFER_DETECTION", "DepthBufferClearingNumber", cleared_depth_buffer_index); + }); +} +reshade::d3d10::runtime_d3d10::~runtime_d3d10() +{ + if (_d3d_compiler != nullptr) + FreeLibrary(_d3d_compiler); +} + +bool reshade::d3d10::runtime_d3d10::init_backbuffer_texture() +{ + HRESULT hr = _swapchain->GetBuffer(0, IID_PPV_ARGS(&_backbuffer)); + assert(SUCCEEDED(hr)); + + D3D10_TEXTURE2D_DESC tex_desc = {}; + tex_desc.Width = _width; + tex_desc.Height = _height; + tex_desc.MipLevels = 1; + tex_desc.ArraySize = 1; + tex_desc.Format = make_dxgi_format_typeless(_backbuffer_format); + tex_desc.SampleDesc = { 1, 0 }; + tex_desc.Usage = D3D10_USAGE_DEFAULT; + tex_desc.BindFlags = D3D10_BIND_RENDER_TARGET; + + // Creating a render target view for the back buffer fails on Windows 8+, so use a intermediate texture there + OSVERSIONINFOEX verinfo_windows7 = { sizeof(OSVERSIONINFOEX), 6, 1 }; + const bool is_windows7 = VerifyVersionInfo(&verinfo_windows7, VER_MAJORVERSION | VER_MINORVERSION, + VerSetConditionMask(VerSetConditionMask(0, VER_MAJORVERSION, VER_EQUAL), VER_MINORVERSION, VER_EQUAL)) != FALSE; + + if (_is_multisampling_enabled || + make_dxgi_format_normal(_backbuffer_format) != _backbuffer_format || + !is_windows7) + { + if (hr = _device->CreateTexture2D(&tex_desc, nullptr, &_backbuffer_resolved); FAILED(hr)) + { + LOG(ERROR) << "Failed to create back buffer resolve texture (" + "Width = " << tex_desc.Width << ", " + "Height = " << tex_desc.Height << ", " + "Format = " << tex_desc.Format << ")! HRESULT is '" << std::hex << hr << std::dec << "'."; + return false; + } + + hr = _device->CreateRenderTargetView(_backbuffer.get(), nullptr, &_backbuffer_rtv[2]); + assert(SUCCEEDED(hr)); + } + else + { + _backbuffer_resolved = _backbuffer; + } + + // Create back buffer shader texture + tex_desc.BindFlags = D3D10_BIND_SHADER_RESOURCE; + + if (hr = _device->CreateTexture2D(&tex_desc, nullptr, &_backbuffer_texture); SUCCEEDED(hr)) + { + D3D10_SHADER_RESOURCE_VIEW_DESC srv_desc = {}; + srv_desc.Format = make_dxgi_format_normal(tex_desc.Format); + srv_desc.ViewDimension = D3D10_SRV_DIMENSION_TEXTURE2D; + srv_desc.Texture2D.MipLevels = tex_desc.MipLevels; + + if (SUCCEEDED(hr)) + hr = _device->CreateShaderResourceView(_backbuffer_texture.get(), &srv_desc, &_backbuffer_texture_srv[0]); + else + LOG(ERROR) << "Failed to create back buffer texture resource view (" + "Format = " << srv_desc.Format << ")! HRESULT is '" << std::hex << hr << std::dec << "'."; + + srv_desc.Format = make_dxgi_format_srgb(tex_desc.Format); + + if (SUCCEEDED(hr)) + hr = _device->CreateShaderResourceView(_backbuffer_texture.get(), &srv_desc, &_backbuffer_texture_srv[1]); + else + LOG(ERROR) << "Failed to create back buffer SRGB texture resource view (" + "Format = " << srv_desc.Format << ")! HRESULT is '" << std::hex << hr << std::dec << "'."; + } + else + { + LOG(ERROR) << "Failed to create back buffer texture (" + "Width = " << tex_desc.Width << ", " + "Height = " << tex_desc.Height << ", " + "Format = " << tex_desc.Format << ")! HRESULT is '" << std::hex << hr << std::dec << "'."; + } + + if (FAILED(hr)) + return false; + + D3D10_RENDER_TARGET_VIEW_DESC rtv_desc = {}; + rtv_desc.Format = make_dxgi_format_normal(tex_desc.Format); + rtv_desc.ViewDimension = D3D10_RTV_DIMENSION_TEXTURE2D; + + if (hr = _device->CreateRenderTargetView(_backbuffer_resolved.get(), &rtv_desc, &_backbuffer_rtv[0]); FAILED(hr)) + { + LOG(ERROR) << "Failed to create back buffer render target (" + "Format = " << rtv_desc.Format << ")! HRESULT is '" << std::hex << hr << std::dec << "'."; + return false; + } + + rtv_desc.Format = make_dxgi_format_srgb(tex_desc.Format); + + if (hr = _device->CreateRenderTargetView(_backbuffer_resolved.get(), &rtv_desc, &_backbuffer_rtv[1]); FAILED(hr)) + { + LOG(ERROR) << "Failed to create back buffer SRGB render target (" + "Format = " << rtv_desc.Format << ")! HRESULT is '" << std::hex << hr << std::dec << "'."; + return false; + } + + const resources::data_resource vs = resources::load_data_resource(IDR_FULLSCREEN_VS); + if (hr = _device->CreateVertexShader(vs.data, vs.data_size, &_copy_vertex_shader); FAILED(hr)) + return false; + const resources::data_resource ps = resources::load_data_resource(IDR_COPY_PS); + if (hr = _device->CreatePixelShader(ps.data, ps.data_size, &_copy_pixel_shader); FAILED(hr)) + return false; + + { D3D10_SAMPLER_DESC desc = {}; + desc.Filter = D3D10_FILTER_MIN_MAG_MIP_POINT; + desc.AddressU = D3D10_TEXTURE_ADDRESS_CLAMP; + desc.AddressV = D3D10_TEXTURE_ADDRESS_CLAMP; + desc.AddressW = D3D10_TEXTURE_ADDRESS_CLAMP; + if (hr = _device->CreateSamplerState(&desc, &_copy_sampler); FAILED(hr)) + return false; + } + + { D3D10_RASTERIZER_DESC desc = {}; + desc.FillMode = D3D10_FILL_SOLID; + desc.CullMode = D3D10_CULL_NONE; + desc.DepthClipEnable = TRUE; + if (hr = _device->CreateRasterizerState(&desc, &_effect_rasterizer_state); FAILED(hr)) + return false; + } + + return true; +} +bool reshade::d3d10::runtime_d3d10::init_default_depth_stencil() +{ + const D3D10_TEXTURE2D_DESC tex_desc = { + _width, + _height, + 1, 1, + DXGI_FORMAT_D24_UNORM_S8_UINT, + { 1, 0 }, + D3D10_USAGE_DEFAULT, + D3D10_BIND_DEPTH_STENCIL + }; + + com_ptr depth_stencil_texture; + + if (HRESULT hr = _device->CreateTexture2D(&tex_desc, nullptr, &depth_stencil_texture); FAILED(hr)) + { + LOG(ERROR) << "Failed to create depth stencil texture (" + "Width = " << tex_desc.Width << ", " + "Height = " << tex_desc.Height << ", " + "Format = " << tex_desc.Format << ")! HRESULT is '" << std::hex << hr << std::dec << "'."; + return false; + } + + return SUCCEEDED(_device->CreateDepthStencilView(depth_stencil_texture.get(), nullptr, &_default_depthstencil)); +} + +bool reshade::d3d10::runtime_d3d10::on_init(const DXGI_SWAP_CHAIN_DESC &desc) +{ + RECT window_rect = {}; + GetClientRect(desc.OutputWindow, &window_rect); + + _width = desc.BufferDesc.Width; + _height = desc.BufferDesc.Height; + _window_width = window_rect.right - window_rect.left; + _window_height = window_rect.bottom - window_rect.top; + _backbuffer_format = desc.BufferDesc.Format; + _is_multisampling_enabled = desc.SampleDesc.Count > 1; + + if (!init_backbuffer_texture() || + !init_default_depth_stencil() +#if RESHADE_GUI + || !init_imgui_resources() +#endif + ) + return false; + + return runtime::on_init(desc.OutputWindow); +} +void reshade::d3d10::runtime_d3d10::on_reset() +{ + runtime::on_reset(); + + _backbuffer.reset(); + _backbuffer_resolved.reset(); + _backbuffer_texture.reset(); + _backbuffer_texture_srv[0].reset(); + _backbuffer_texture_srv[1].reset(); + _backbuffer_rtv[0].reset(); + _backbuffer_rtv[1].reset(); + _backbuffer_rtv[2].reset(); + + _depthstencil.reset(); + _depthstencil_replacement.reset(); + _depthstencil_texture.reset(); + _depthstencil_texture_srv.reset(); + + _depth_texture_saves.clear(); + + _default_depthstencil.reset(); + _copy_vertex_shader.reset(); + _copy_pixel_shader.reset(); + _copy_sampler.reset(); + + _effect_rasterizer_state.reset(); + + _imgui_index_buffer_size = 0; + _imgui_index_buffer.reset(); + _imgui_vertex_buffer_size = 0; + _imgui_vertex_buffer.reset(); + _imgui_vertex_shader.reset(); + _imgui_pixel_shader.reset(); + _imgui_input_layout.reset(); + _imgui_constant_buffer.reset(); + _imgui_texture_sampler.reset(); + _imgui_rasterizer_state.reset(); + _imgui_blend_state.reset(); + _imgui_depthstencil_state.reset(); +} + +void reshade::d3d10::runtime_d3d10::on_present(draw_call_tracker &tracker) +{ + if (!_is_initialized) + return; + + _vertices = tracker.total_vertices(); + _drawcalls = tracker.total_drawcalls(); + _current_tracker = &tracker; + +#if RESHADE_DX10_CAPTURE_DEPTH_BUFFERS + detect_depth_source(tracker); +#endif + _app_state.capture(); + + // Resolve MSAA back buffer if MSAA is active + if (_backbuffer_resolved != _backbuffer) + _device->ResolveSubresource(_backbuffer_resolved.get(), 0, _backbuffer.get(), 0, _backbuffer_format); + + update_and_render_effects(); + runtime::on_present(); + + // Stretch main render target back into MSAA back buffer if MSAA is active + if (_backbuffer_resolved != _backbuffer) + { + _device->CopyResource(_backbuffer_texture.get(), _backbuffer_resolved.get()); + + _device->IASetInputLayout(nullptr); + const uintptr_t null = 0; + _device->IASetVertexBuffers(0, 1, reinterpret_cast(&null), reinterpret_cast(&null), reinterpret_cast(&null)); + _device->IASetPrimitiveTopology(D3D10_PRIMITIVE_TOPOLOGY_TRIANGLELIST); + _device->VSSetShader(_copy_vertex_shader.get()); + _device->GSSetShader(nullptr); + _device->PSSetShader(_copy_pixel_shader.get()); + ID3D10SamplerState *const samplers[] = { _copy_sampler.get() }; + _device->PSSetSamplers(0, ARRAYSIZE(samplers), samplers); + ID3D10ShaderResourceView *const srvs[] = { _backbuffer_texture_srv[make_dxgi_format_srgb(_backbuffer_format) == _backbuffer_format].get() }; + _device->PSSetShaderResources(0, ARRAYSIZE(srvs), srvs); + _device->RSSetState(_effect_rasterizer_state.get()); + const D3D10_VIEWPORT viewport = { 0, 0, _width, _height, 0.0f, 1.0f }; + _device->RSSetViewports(1, &viewport); + _device->OMSetBlendState(nullptr, nullptr, D3D10_DEFAULT_SAMPLE_MASK); + _device->OMSetDepthStencilState(nullptr, D3D10_DEFAULT_STENCIL_REFERENCE); + ID3D10RenderTargetView *const render_targets[] = { _backbuffer_rtv[2].get() }; + _device->OMSetRenderTargets(ARRAYSIZE(render_targets), render_targets, nullptr); + + _device->Draw(3, 0); + } + + // Apply previous state from application + _app_state.apply_and_release(); +} + +void reshade::d3d10::runtime_d3d10::capture_screenshot(uint8_t *buffer) const +{ + if (_backbuffer_format != DXGI_FORMAT_R8G8B8A8_UNORM && + _backbuffer_format != DXGI_FORMAT_R8G8B8A8_UNORM_SRGB && + _backbuffer_format != DXGI_FORMAT_B8G8R8A8_UNORM && + _backbuffer_format != DXGI_FORMAT_B8G8R8A8_UNORM_SRGB) + { + LOG(WARN) << "Screenshots are not supported for back buffer format " << _backbuffer_format << '.'; + return; + } + + // Create a texture in system memory, copy back buffer data into it and map it for reading + D3D10_TEXTURE2D_DESC desc = {}; + desc.Width = _width; + desc.Height = _height; + desc.MipLevels = 1; + desc.ArraySize = 1; + desc.Format = _backbuffer_format; + desc.SampleDesc = { 1, 0 }; + desc.Usage = D3D10_USAGE_STAGING; + desc.CPUAccessFlags = D3D10_CPU_ACCESS_READ; + + com_ptr intermediate; + if (FAILED(_device->CreateTexture2D(&desc, nullptr, &intermediate))) + { + LOG(ERROR) << "Failed to create system memory texture for screenshot capture!"; + return; + } + + _device->CopyResource(intermediate.get(), _backbuffer_resolved.get()); + + D3D10_MAPPED_TEXTURE2D mapped; + if (FAILED(intermediate->Map(0, D3D10_MAP_READ, 0, &mapped))) + return; + auto mapped_data = static_cast(mapped.pData); + + for (uint32_t y = 0, pitch = _width * 4; y < _height; y++, buffer += pitch, mapped_data += mapped.RowPitch) + { + memcpy(buffer, mapped_data, pitch); + + for (uint32_t x = 0; x < pitch; x += 4) + { + buffer[x + 3] = 0xFF; // Clear alpha channel + if (_backbuffer_format == DXGI_FORMAT_B8G8R8A8_UNORM || _backbuffer_format == DXGI_FORMAT_B8G8R8A8_UNORM_SRGB) + std::swap(buffer[x + 0], buffer[x + 2]); // Format is BGRA, but output should be RGBA, so flip channels + } + } + + intermediate->Unmap(0); +} + +bool reshade::d3d10::runtime_d3d10::init_texture(texture &info) +{ + info.impl = std::make_unique(); + + if (info.impl_reference != texture_reference::none) + return update_texture_reference(info); + + D3D10_TEXTURE2D_DESC desc = {}; + desc.Width = info.width; + desc.Height = info.height; + desc.MipLevels = info.levels; + desc.ArraySize = 1; + desc.SampleDesc = { 1, 0 }; + desc.Usage = D3D10_USAGE_DEFAULT; + desc.BindFlags = D3D10_BIND_SHADER_RESOURCE | D3D10_BIND_RENDER_TARGET; + desc.MiscFlags = D3D10_RESOURCE_MISC_GENERATE_MIPS; + + switch (info.format) + { + case reshadefx::texture_format::r8: + desc.Format = DXGI_FORMAT_R8_UNORM; + break; + case reshadefx::texture_format::r16f: + desc.Format = DXGI_FORMAT_R16_FLOAT; + break; + case reshadefx::texture_format::r32f: + desc.Format = DXGI_FORMAT_R32_FLOAT; + break; + case reshadefx::texture_format::rg8: + desc.Format = DXGI_FORMAT_R8G8_UNORM; + break; + case reshadefx::texture_format::rg16: + desc.Format = DXGI_FORMAT_R16G16_UNORM; + break; + case reshadefx::texture_format::rg16f: + desc.Format = DXGI_FORMAT_R16G16_FLOAT; + break; + case reshadefx::texture_format::rg32f: + desc.Format = DXGI_FORMAT_R32G32_FLOAT; + break; + case reshadefx::texture_format::rgba8: + desc.Format = DXGI_FORMAT_R8G8B8A8_TYPELESS; + break; + case reshadefx::texture_format::rgba16: + desc.Format = DXGI_FORMAT_R16G16B16A16_UNORM; + break; + case reshadefx::texture_format::rgba16f: + desc.Format = DXGI_FORMAT_R16G16B16A16_FLOAT; + break; + case reshadefx::texture_format::rgba32f: + desc.Format = DXGI_FORMAT_R32G32B32A32_FLOAT; + break; + case reshadefx::texture_format::rgb10a2: + desc.Format = DXGI_FORMAT_R10G10B10A2_UNORM; + break; + } + + const auto texture_data = info.impl->as(); + + if (HRESULT hr = _device->CreateTexture2D(&desc, nullptr, &texture_data->texture); FAILED(hr)) + { + LOG(ERROR) << "Failed to create texture '" << info.unique_name << "' (" + "Width = " << desc.Width << ", " + "Height = " << desc.Height << ", " + "Format = " << desc.Format << ")! " + "HRESULT is '" << std::hex << hr << std::dec << "'."; + return false; + } + + D3D10_SHADER_RESOURCE_VIEW_DESC srv_desc = {}; + srv_desc.Format = make_dxgi_format_normal(desc.Format); + srv_desc.ViewDimension = D3D10_SRV_DIMENSION_TEXTURE2D; + srv_desc.Texture2D.MipLevels = desc.MipLevels; + + if (HRESULT hr = _device->CreateShaderResourceView(texture_data->texture.get(), &srv_desc, &texture_data->srv[0]); FAILED(hr)) + { + LOG(ERROR) << "Failed to create shader resource view for texture '" << info.unique_name << "' (" + "Format = " << srv_desc.Format << ")! " + "HRESULT is '" << std::hex << hr << std::dec << "'."; + return false; + } + + srv_desc.Format = make_dxgi_format_srgb(desc.Format); + + if (srv_desc.Format != desc.Format) + { + if (HRESULT hr = _device->CreateShaderResourceView(texture_data->texture.get(), &srv_desc, &texture_data->srv[1]); FAILED(hr)) + { + LOG(ERROR) << "Failed to create shader resource view for texture '" << info.unique_name << "' (" + "Format = " << srv_desc.Format << ")! " + "HRESULT is '" << std::hex << hr << std::dec << "'."; + return false; + } + } + else + { + texture_data->srv[1] = texture_data->srv[0]; + } + + return true; +} +void reshade::d3d10::runtime_d3d10::upload_texture(texture &texture, const uint8_t *pixels) +{ + assert(texture.impl_reference == texture_reference::none && pixels != nullptr); + + unsigned int upload_pitch; + std::vector upload_data; + + switch (texture.format) + { + case reshadefx::texture_format::r8: + upload_pitch = texture.width; + upload_data.resize(upload_pitch * texture.height); + for (uint32_t i = 0, k = 0; i < texture.width * texture.height * 4; i += 4, k += 1) + upload_data[k] = pixels[i]; + pixels = upload_data.data(); + break; + case reshadefx::texture_format::rg8: + upload_pitch = texture.width * 2; + upload_data.resize(upload_pitch * texture.height); + for (uint32_t i = 0, k = 0; i < texture.width * texture.height * 4; i += 4, k += 2) + upload_data[k + 0] = pixels[i + 0], + upload_data[k + 1] = pixels[i + 1]; + pixels = upload_data.data(); + break; + case reshadefx::texture_format::rgba8: + upload_pitch = texture.width * 4; + break; + default: + LOG(ERROR) << "Texture upload is not supported for format " << static_cast(texture.format) << '!'; + return; + } + + const auto texture_impl = texture.impl->as(); + assert(texture_impl != nullptr); + + _device->UpdateSubresource(texture_impl->texture.get(), 0, nullptr, pixels, upload_pitch, upload_pitch * texture.height); + + if (texture.levels > 1) + _device->GenerateMips(texture_impl->srv[0].get()); +} +bool reshade::d3d10::runtime_d3d10::update_texture_reference(texture &texture) +{ + com_ptr new_reference[2]; + + switch (texture.impl_reference) + { + case texture_reference::back_buffer: + new_reference[0] = _backbuffer_texture_srv[0]; + new_reference[1] = _backbuffer_texture_srv[1]; + break; + case texture_reference::depth_buffer: + new_reference[0] = _depthstencil_texture_srv; + new_reference[1] = _depthstencil_texture_srv; + break; + default: + return false; + } + + const auto texture_impl = texture.impl->as(); + assert(texture_impl != nullptr); + + if (new_reference[0] == texture_impl->srv[0] && + new_reference[1] == texture_impl->srv[1]) + return true; + + // Update references in technique list + for (const auto &technique : _techniques) + for (const auto &pass : technique.passes_data) + for (auto &srv : pass->as()->shader_resources) + if (srv == texture_impl->srv[0]) srv = new_reference[0]; + else if (srv == texture_impl->srv[1]) srv = new_reference[1]; + + texture.width = frame_width(); + texture.height = frame_height(); + + texture_impl->srv[0] = new_reference[0]; + texture_impl->srv[1] = new_reference[1]; + + return true; +} +void reshade::d3d10::runtime_d3d10::update_texture_references(texture_reference type) +{ + for (auto &tex : _textures) + if (tex.impl != nullptr && tex.impl_reference == type) + update_texture_reference(tex); +} + +bool reshade::d3d10::runtime_d3d10::compile_effect(effect_data &effect) +{ + if (_d3d_compiler == nullptr) + _d3d_compiler = LoadLibraryW(L"d3dcompiler_47.dll"); + if (_d3d_compiler == nullptr) + _d3d_compiler = LoadLibraryW(L"d3dcompiler_43.dll"); + + if (_d3d_compiler == nullptr) + { + LOG(ERROR) << "Unable to load HLSL compiler (\"d3dcompiler_47.dll\"). Make sure you have the DirectX end-user runtime (June 2010) installed or a newer version of the library in the application directory."; + return false; + } + + const auto D3DCompile = reinterpret_cast(GetProcAddress(_d3d_compiler, "D3DCompile")); + + const std::string hlsl = effect.preamble + effect.module.hlsl; + std::unordered_map> entry_points; + + // Compile the generated HLSL source code to DX byte code + for (const auto &entry_point : effect.module.entry_points) + { + std::string profile = entry_point.second ? "ps" : "vs"; + + switch (_renderer_id) + { + case D3D10_FEATURE_LEVEL_10_1: + profile += "_4_1"; + break; + default: + case D3D10_FEATURE_LEVEL_10_0: + profile += "_4_0"; + break; + case D3D10_FEATURE_LEVEL_9_1: + case D3D10_FEATURE_LEVEL_9_2: + profile += "_4_0_level_9_1"; + break; + case D3D10_FEATURE_LEVEL_9_3: + profile += "_4_0_level_9_3"; + break; + } + + com_ptr d3d_compiled, d3d_errors; + + HRESULT hr = D3DCompile( + hlsl.c_str(), hlsl.size(), + nullptr, nullptr, nullptr, + entry_point.first.c_str(), + profile.c_str(), + D3DCOMPILE_ENABLE_STRICTNESS | D3DCOMPILE_OPTIMIZATION_LEVEL3, 0, + &d3d_compiled, &d3d_errors); + + if (d3d_errors != nullptr) // Append warnings to the output error string as well + effect.errors.append(static_cast(d3d_errors->GetBufferPointer()), d3d_errors->GetBufferSize() - 1); // Subtracting one to not append the null-terminator as well + + // No need to setup resources if any of the shaders failed to compile + if (FAILED(hr)) + return false; + + // Create runtime shader objects from the compiled DX byte code + if (entry_point.second) + hr = _device->CreatePixelShader(d3d_compiled->GetBufferPointer(), d3d_compiled->GetBufferSize(), reinterpret_cast(&entry_points[entry_point.first])); + else + hr = _device->CreateVertexShader(d3d_compiled->GetBufferPointer(), d3d_compiled->GetBufferSize(), reinterpret_cast(&entry_points[entry_point.first])); + + if (FAILED(hr)) + { + LOG(ERROR) << "Failed to create shader for entry point '" << entry_point.first << "'. " + "HRESULT is '" << std::hex << hr << std::dec << "'."; + return false; + } + } + + if (effect.storage_size != 0) + { + com_ptr cbuffer; + + const D3D10_BUFFER_DESC desc = { static_cast(effect.storage_size), D3D10_USAGE_DYNAMIC, D3D10_BIND_CONSTANT_BUFFER, D3D10_CPU_ACCESS_WRITE }; + const D3D10_SUBRESOURCE_DATA init_data = { _uniform_data_storage.data() + effect.storage_offset, static_cast(effect.storage_size) }; + + if (HRESULT hr = _device->CreateBuffer(&desc, &init_data, &cbuffer); FAILED(hr)) + { + LOG(ERROR) << "Failed to create constant buffer for effect file " << effect.source_file << ". " + "HRESULT is '" << std::hex << hr << std::dec << "'."; + return false; + } + + _constant_buffers.push_back(std::move(cbuffer)); + } + + bool success = true; + + d3d10_technique_data technique_init; + technique_init.uniform_storage_index = _constant_buffers.size() - 1; + technique_init.uniform_storage_offset = effect.storage_offset; + + for (const reshadefx::sampler_info &info : effect.module.samplers) + success &= add_sampler(info, technique_init); + + for (technique &technique : _techniques) + if (technique.impl == nullptr && technique.effect_index == effect.index) + success &= init_technique(technique, technique_init, entry_points); + + return success; +} +void reshade::d3d10::runtime_d3d10::unload_effects() +{ + runtime::unload_effects(); + + _effect_sampler_states.clear(); + _constant_buffers.clear(); +} + +bool reshade::d3d10::runtime_d3d10::add_sampler(const reshadefx::sampler_info &info, d3d10_technique_data &technique_init) +{ + if (info.binding >= D3D10_COMMONSHADER_SAMPLER_SLOT_COUNT) + { + LOG(ERROR) << "Cannot bind sampler '" << info.unique_name << "' since it exceeds the maximum number of allowed sampler slots in D3D10 (" << info.binding << ", allowed are up to " << D3D10_COMMONSHADER_SAMPLER_SLOT_COUNT << ")."; + return false; + } + if (info.texture_binding >= D3D10_COMMONSHADER_INPUT_RESOURCE_SLOT_COUNT) + { + LOG(ERROR) << "Cannot bind texture '" << info.texture_name << "' since it exceeds the maximum number of allowed resource slots in D3D10 (" << info.texture_binding << ", allowed are up to " << D3D10_COMMONSHADER_INPUT_RESOURCE_SLOT_COUNT << ")."; + return false; + } + + const auto existing_texture = std::find_if(_textures.begin(), _textures.end(), + [&texture_name = info.texture_name](const auto &item) { + return item.unique_name == texture_name && item.impl != nullptr; + }); + + if (existing_texture == _textures.end()) + return false; + + D3D10_SAMPLER_DESC desc = {}; + desc.Filter = static_cast(info.filter); + desc.AddressU = static_cast(info.address_u); + desc.AddressV = static_cast(info.address_v); + desc.AddressW = static_cast(info.address_w); + desc.MipLODBias = info.lod_bias; + desc.MaxAnisotropy = 1; + desc.ComparisonFunc = D3D10_COMPARISON_NEVER; + desc.MinLOD = info.min_lod; + desc.MaxLOD = info.max_lod; + + // Generate hash for sampler description + size_t desc_hash = 2166136261; + for (size_t i = 0; i < sizeof(desc); ++i) + desc_hash = (desc_hash * 16777619) ^ reinterpret_cast(&desc)[i]; + + auto it = _effect_sampler_states.find(desc_hash); + if (it == _effect_sampler_states.end()) + { + com_ptr sampler; + + HRESULT hr = _device->CreateSamplerState(&desc, &sampler); + + if (FAILED(hr)) + { + LOG(ERROR) << "Failed to create sampler state for sampler '" << info.unique_name << "' (" + "Filter = " << desc.Filter << ", " + "AddressU = " << desc.AddressU << ", " + "AddressV = " << desc.AddressV << ", " + "AddressW = " << desc.AddressW << ", " + "MipLODBias = " << desc.MipLODBias << ", " + "MinLOD = " << desc.MinLOD << ", " + "MaxLOD = " << desc.MaxLOD << ")! " + "HRESULT is '" << std::hex << hr << std::dec << "'."; + return false; + } + + it = _effect_sampler_states.emplace(desc_hash, std::move(sampler)).first; + } + + technique_init.sampler_states.resize(std::max(technique_init.sampler_states.size(), size_t(info.binding + 1))); + technique_init.texture_bindings.resize(std::max(technique_init.texture_bindings.size(), size_t(info.texture_binding + 1))); + + technique_init.sampler_states[info.binding] = it->second; + technique_init.texture_bindings[info.texture_binding] = existing_texture->impl->as()->srv[info.srgb ? 1 : 0]; + + return true; +} +bool reshade::d3d10::runtime_d3d10::init_technique(technique &technique, const d3d10_technique_data &impl_init, const std::unordered_map> &entry_points) +{ + // Copy construct new technique implementation instead of move because effect may contain multiple techniques + technique.impl = std::make_unique(impl_init); + + const auto technique_data = technique.impl->as(); + + D3D10_QUERY_DESC query_desc = {}; + query_desc.Query = D3D10_QUERY_TIMESTAMP; + _device->CreateQuery(&query_desc, &technique_data->timestamp_query_beg); + _device->CreateQuery(&query_desc, &technique_data->timestamp_query_end); + query_desc.Query = D3D10_QUERY_TIMESTAMP_DISJOINT; + _device->CreateQuery(&query_desc, &technique_data->timestamp_disjoint); + + for (size_t pass_index = 0; pass_index < technique.passes.size(); ++pass_index) + { + technique.passes_data.push_back(std::make_unique()); + + auto &pass = *technique.passes_data.back()->as(); + const auto &pass_info = technique.passes[pass_index]; + + entry_points.at(pass_info.ps_entry_point)->QueryInterface(&pass.pixel_shader); + entry_points.at(pass_info.vs_entry_point)->QueryInterface(&pass.vertex_shader); + + pass.viewport.MaxDepth = 1.0f; + pass.viewport.Width = pass_info.viewport_width; + pass.viewport.Height = pass_info.viewport_height; + + pass.shader_resources = technique_data->texture_bindings; + pass.clear_render_targets = pass_info.clear_render_targets; + + const int target_index = pass_info.srgb_write_enable ? 1 : 0; + pass.render_targets[0] = _backbuffer_rtv[target_index]; + pass.render_target_resources[0] = _backbuffer_texture_srv[target_index]; + + for (unsigned int k = 0; k < 8; k++) + { + if (pass_info.render_target_names[k].empty()) + continue; // Skip unbound render targets + + const auto render_target_texture = std::find_if(_textures.begin(), _textures.end(), + [&render_target = pass_info.render_target_names[k]](const auto &item) { + return item.unique_name == render_target; + }); + + if (render_target_texture == _textures.end()) + return assert(false), false; + + const auto texture_impl = render_target_texture->impl->as(); + assert(texture_impl != nullptr); + + D3D10_TEXTURE2D_DESC desc; + texture_impl->texture->GetDesc(&desc); + + D3D10_RENDER_TARGET_VIEW_DESC rtv_desc = {}; + rtv_desc.Format = pass_info.srgb_write_enable ? make_dxgi_format_srgb(desc.Format) : make_dxgi_format_normal(desc.Format); + rtv_desc.ViewDimension = desc.SampleDesc.Count > 1 ? D3D10_RTV_DIMENSION_TEXTURE2DMS : D3D10_RTV_DIMENSION_TEXTURE2D; + + if (texture_impl->rtv[target_index] == nullptr) + if (HRESULT hr = _device->CreateRenderTargetView(texture_impl->texture.get(), &rtv_desc, &texture_impl->rtv[target_index]); FAILED(hr)) + { + LOG(ERROR) << "Failed to create render target view for texture '" << render_target_texture->unique_name << "' (" + "Format = " << rtv_desc.Format << ")! " + "HRESULT is '" << std::hex << hr << std::dec << "'."; + return false; + } + + pass.render_targets[k] = texture_impl->rtv[target_index]; + pass.render_target_resources[k] = texture_impl->srv[target_index]; + } + + if (pass.viewport.Width == 0) + { + pass.viewport.Width = frame_width(); + pass.viewport.Height = frame_height(); + } + + { D3D10_BLEND_DESC desc = {}; + desc.BlendEnable[0] = pass_info.blend_enable; + + const auto literal_to_blend_func = [](unsigned int value) { + switch (value) { + case 0: + return D3D10_BLEND_ZERO; + default: + case 1: + return D3D10_BLEND_ONE; + case 2: + return D3D10_BLEND_SRC_COLOR; + case 4: + return D3D10_BLEND_INV_SRC_COLOR; + case 3: + return D3D10_BLEND_SRC_ALPHA; + case 5: + return D3D10_BLEND_INV_SRC_ALPHA; + case 6: + return D3D10_BLEND_DEST_ALPHA; + case 7: + return D3D10_BLEND_INV_DEST_ALPHA; + case 8: + return D3D10_BLEND_DEST_COLOR; + case 9: + return D3D10_BLEND_INV_DEST_COLOR; + } + }; + + desc.SrcBlend = literal_to_blend_func(pass_info.src_blend); + desc.DestBlend = literal_to_blend_func(pass_info.dest_blend); + desc.BlendOp = static_cast(pass_info.blend_op); + desc.SrcBlendAlpha = literal_to_blend_func(pass_info.src_blend_alpha); + desc.DestBlendAlpha = literal_to_blend_func(pass_info.dest_blend_alpha); + desc.BlendOpAlpha = static_cast(pass_info.blend_op_alpha); + desc.RenderTargetWriteMask[0] = pass_info.color_write_mask; + + for (UINT i = 1; i < 8; ++i) + { + desc.BlendEnable[i] = desc.BlendEnable[0]; + desc.RenderTargetWriteMask[i] = desc.RenderTargetWriteMask[0]; + } + + if (HRESULT hr = _device->CreateBlendState(&desc, &pass.blend_state); FAILED(hr)) + { + LOG(ERROR) << "Failed to create blend state for pass " << pass_index << " in technique '" << technique.name << "'! " + "HRESULT is '" << std::hex << hr << std::dec << "'."; + return false; + } + } + + // Rasterizer state is the same for all passes + assert(_effect_rasterizer_state != nullptr); + + { D3D10_DEPTH_STENCIL_DESC desc = {}; + desc.DepthEnable = FALSE; + desc.DepthWriteMask = D3D10_DEPTH_WRITE_MASK_ZERO; + desc.DepthFunc = D3D10_COMPARISON_ALWAYS; + + const auto literal_to_stencil_op = [](unsigned int value) { + switch (value) { + default: + case 1: + return D3D10_STENCIL_OP_KEEP; + case 0: + return D3D10_STENCIL_OP_ZERO; + case 3: + return D3D10_STENCIL_OP_REPLACE; + case 4: + return D3D10_STENCIL_OP_INCR_SAT; + case 5: + return D3D10_STENCIL_OP_DECR_SAT; + case 6: + return D3D10_STENCIL_OP_INVERT; + case 7: + return D3D10_STENCIL_OP_INCR; + case 8: + return D3D10_STENCIL_OP_DECR; + } + }; + + desc.StencilEnable = pass_info.stencil_enable; + desc.StencilReadMask = pass_info.stencil_read_mask; + desc.StencilWriteMask = pass_info.stencil_write_mask; + desc.FrontFace.StencilFailOp = literal_to_stencil_op(pass_info.stencil_op_fail); + desc.FrontFace.StencilDepthFailOp = literal_to_stencil_op(pass_info.stencil_op_depth_fail); + desc.FrontFace.StencilPassOp = literal_to_stencil_op(pass_info.stencil_op_pass); + desc.FrontFace.StencilFunc = static_cast(pass_info.stencil_comparison_func); + desc.BackFace = desc.FrontFace; + if (HRESULT hr = _device->CreateDepthStencilState(&desc, &pass.depth_stencil_state); FAILED(hr)) + { + LOG(ERROR) << "Failed to create depth stencil state for pass " << pass_index << " in technique '" << technique.name << "'! " + "HRESULT is '" << std::hex << hr << std::dec << "'."; + return false; + } + + pass.stencil_reference = pass_info.stencil_reference_value; + } + + for (auto &srv : pass.shader_resources) + { + if (srv == nullptr) + continue; + + com_ptr res1; + srv->GetResource(&res1); + + for (const auto &rtv : pass.render_targets) + { + if (rtv == nullptr) + continue; + + com_ptr res2; + rtv->GetResource(&res2); + + if (res1 == res2) + { + srv.reset(); + break; + } + } + } + } + + return true; +} + +void reshade::d3d10::runtime_d3d10::render_technique(technique &technique) +{ + d3d10_technique_data &technique_data = *technique.impl->as(); + + // Evaluate queries + if (technique_data.query_in_flight) + { + UINT64 timestamp0, timestamp1; + D3D10_QUERY_DATA_TIMESTAMP_DISJOINT disjoint; + + if (technique_data.timestamp_disjoint->GetData(&disjoint, sizeof(disjoint), D3D10_ASYNC_GETDATA_DONOTFLUSH) == S_OK && + technique_data.timestamp_query_beg->GetData(×tamp0, sizeof(timestamp0), D3D10_ASYNC_GETDATA_DONOTFLUSH) == S_OK && + technique_data.timestamp_query_end->GetData(×tamp1, sizeof(timestamp1), D3D10_ASYNC_GETDATA_DONOTFLUSH) == S_OK) + { + if (!disjoint.Disjoint) + technique.average_gpu_duration.append((timestamp1 - timestamp0) * 1'000'000'000 / disjoint.Frequency); + technique_data.query_in_flight = false; + } + } + + if (!technique_data.query_in_flight) + { + technique_data.timestamp_disjoint->Begin(); + technique_data.timestamp_query_beg->End(); + } + + bool is_default_depthstencil_cleared = false; + + // Setup vertex input + const uintptr_t null = 0; + _device->IASetPrimitiveTopology(D3D10_PRIMITIVE_TOPOLOGY_TRIANGLELIST); + _device->IASetInputLayout(nullptr); + _device->IASetVertexBuffers(0, 1, reinterpret_cast(&null), reinterpret_cast(&null), reinterpret_cast(&null)); + + _device->RSSetState(_effect_rasterizer_state.get()); + + // Setup samplers + _device->VSSetSamplers(0, static_cast(technique_data.sampler_states.size()), reinterpret_cast(technique_data.sampler_states.data())); + _device->PSSetSamplers(0, static_cast(technique_data.sampler_states.size()), reinterpret_cast(technique_data.sampler_states.data())); + + // Setup shader constants + if (technique_data.uniform_storage_index >= 0) + { + void *data = nullptr; + D3D10_BUFFER_DESC desc = {}; + const auto constant_buffer = _constant_buffers[technique_data.uniform_storage_index].get(); + + constant_buffer->GetDesc(&desc); + const HRESULT hr = constant_buffer->Map(D3D10_MAP_WRITE_DISCARD, 0, &data); + + if (SUCCEEDED(hr)) + { + memcpy(data, _uniform_data_storage.data() + technique_data.uniform_storage_offset, desc.ByteWidth); + + constant_buffer->Unmap(); + } + else + { + LOG(ERROR) << "Failed to map constant buffer! HRESULT is '" << std::hex << hr << std::dec << "'!"; + } + + _device->VSSetConstantBuffers(0, 1, &constant_buffer); + _device->PSSetConstantBuffers(0, 1, &constant_buffer); + } + + // Disable unused shader stages + _device->GSSetShader(nullptr); + + for (const auto &pass_object : technique.passes_data) + { + const d3d10_pass_data &pass = *pass_object->as(); + + // Setup states + _device->VSSetShader(pass.vertex_shader.get()); + _device->PSSetShader(pass.pixel_shader.get()); + + _device->OMSetBlendState(pass.blend_state.get(), nullptr, D3D10_DEFAULT_SAMPLE_MASK); + _device->OMSetDepthStencilState(pass.depth_stencil_state.get(), pass.stencil_reference); + + // Save back buffer of previous pass + _device->CopyResource(_backbuffer_texture.get(), _backbuffer_resolved.get()); + + // Setup shader resources + _device->VSSetShaderResources(0, static_cast(pass.shader_resources.size()), reinterpret_cast(pass.shader_resources.data())); + _device->PSSetShaderResources(0, static_cast(pass.shader_resources.size()), reinterpret_cast(pass.shader_resources.data())); + + // Setup render targets + if (pass.viewport.Width == _width && pass.viewport.Height == _height) + { + _device->OMSetRenderTargets(D3D10_SIMULTANEOUS_RENDER_TARGET_COUNT, reinterpret_cast(pass.render_targets), _default_depthstencil.get()); + + if (!is_default_depthstencil_cleared) + { + is_default_depthstencil_cleared = true; + + _device->ClearDepthStencilView(_default_depthstencil.get(), D3D10_CLEAR_DEPTH | D3D10_CLEAR_STENCIL, 1.0f, 0); + } + } + else + { + _device->OMSetRenderTargets(D3D10_SIMULTANEOUS_RENDER_TARGET_COUNT, reinterpret_cast(pass.render_targets), nullptr); + } + + _device->RSSetViewports(1, &pass.viewport); + + if (pass.clear_render_targets) + { + for (const auto &target : pass.render_targets) + { + if (target != nullptr) + { + const float color[4] = { 0.0f, 0.0f, 0.0f, 0.0f }; + _device->ClearRenderTargetView(target.get(), color); + } + } + } + + // Draw triangle + _device->Draw(3, 0); + + _vertices += 3; + _drawcalls += 1; + + // Reset render targets + _device->OMSetRenderTargets(0, nullptr, nullptr); + + // Reset shader resources + ID3D10ShaderResourceView *null_srv[D3D10_COMMONSHADER_INPUT_RESOURCE_SLOT_COUNT] = { nullptr }; + _device->VSSetShaderResources(0, static_cast(pass.shader_resources.size()), null_srv); + _device->PSSetShaderResources(0, static_cast(pass.shader_resources.size()), null_srv); + + // Update shader resources + for (const auto &resource : pass.render_target_resources) + { + if (resource == nullptr) + continue; + + D3D10_SHADER_RESOURCE_VIEW_DESC resource_desc; + resource->GetDesc(&resource_desc); + + if (resource_desc.Texture2D.MipLevels > 1) + _device->GenerateMips(resource.get()); + } + } + + if (!technique_data.query_in_flight) + { + technique_data.timestamp_query_end->End(); + technique_data.timestamp_disjoint->End(); + technique_data.query_in_flight = true; + } +} + +#if RESHADE_GUI +bool reshade::d3d10::runtime_d3d10::init_imgui_resources() +{ + { const resources::data_resource vs = resources::load_data_resource(IDR_IMGUI_VS); + if (FAILED(_device->CreateVertexShader(vs.data, vs.data_size, &_imgui_vertex_shader))) + return false; + + const D3D10_INPUT_ELEMENT_DESC input_layout[] = { + { "POSITION", 0, DXGI_FORMAT_R32G32_FLOAT, 0, offsetof(ImDrawVert, pos), D3D10_INPUT_PER_VERTEX_DATA, 0 }, + { "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, offsetof(ImDrawVert, uv ), D3D10_INPUT_PER_VERTEX_DATA, 0 }, + { "COLOR", 0, DXGI_FORMAT_R8G8B8A8_UNORM, 0, offsetof(ImDrawVert, col), D3D10_INPUT_PER_VERTEX_DATA, 0 }, + }; + if (FAILED(_device->CreateInputLayout(input_layout, _countof(input_layout), vs.data, vs.data_size, &_imgui_input_layout))) + return false; + } + + { const resources::data_resource ps = resources::load_data_resource(IDR_IMGUI_PS); + if (FAILED(_device->CreatePixelShader(ps.data, ps.data_size, &_imgui_pixel_shader))) + return false; + } + + { D3D10_BUFFER_DESC desc = {}; + desc.ByteWidth = 16 * sizeof(float); + desc.Usage = D3D10_USAGE_IMMUTABLE; + desc.BindFlags = D3D10_BIND_CONSTANT_BUFFER; + + // Setup orthographic projection matrix + const float ortho_projection[16] = { + 2.0f / _width, 0.0f, 0.0f, 0.0f, + 0.0f, -2.0f / _height, 0.0f, 0.0f, + 0.0f, 0.0f, 0.5f, 0.0f, + -1.f, 1.0f, 0.5f, 1.0f + }; + + D3D10_SUBRESOURCE_DATA initial_data = {}; + initial_data.pSysMem = ortho_projection; + initial_data.SysMemPitch = sizeof(ortho_projection); + if (FAILED(_device->CreateBuffer(&desc, &initial_data, &_imgui_constant_buffer))) + return false; + } + + { D3D10_BLEND_DESC desc = {}; + desc.BlendEnable[0] = true; + desc.SrcBlend = D3D10_BLEND_SRC_ALPHA; + desc.DestBlend = D3D10_BLEND_INV_SRC_ALPHA; + desc.BlendOp = D3D10_BLEND_OP_ADD; + desc.SrcBlendAlpha = D3D10_BLEND_INV_SRC_ALPHA; + desc.DestBlendAlpha = D3D10_BLEND_ZERO; + desc.BlendOpAlpha = D3D10_BLEND_OP_ADD; + desc.RenderTargetWriteMask[0] = D3D10_COLOR_WRITE_ENABLE_ALL; + if (FAILED(_device->CreateBlendState(&desc, &_imgui_blend_state))) + return false; + } + + { D3D10_RASTERIZER_DESC desc = {}; + desc.FillMode = D3D10_FILL_SOLID; + desc.CullMode = D3D10_CULL_NONE; + desc.ScissorEnable = true; + desc.DepthClipEnable = true; + if (FAILED(_device->CreateRasterizerState(&desc, &_imgui_rasterizer_state))) + return false; + } + + { D3D10_DEPTH_STENCIL_DESC desc = {}; + desc.DepthEnable = false; + desc.StencilEnable = false; + if (FAILED(_device->CreateDepthStencilState(&desc, &_imgui_depthstencil_state))) + return false; + } + + { D3D10_SAMPLER_DESC desc = {}; + desc.Filter = D3D10_FILTER_MIN_MAG_MIP_LINEAR; + desc.AddressU = D3D10_TEXTURE_ADDRESS_WRAP; + desc.AddressV = D3D10_TEXTURE_ADDRESS_WRAP; + desc.AddressW = D3D10_TEXTURE_ADDRESS_WRAP; + desc.ComparisonFunc = D3D10_COMPARISON_ALWAYS; + if (FAILED(_device->CreateSamplerState(&desc, &_imgui_texture_sampler))) + return false; + } + + return true; +} + +void reshade::d3d10::runtime_d3d10::render_imgui_draw_data(ImDrawData *draw_data) +{ + assert(draw_data->DisplayPos.x == 0 && draw_data->DisplaySize.x == _width); + assert(draw_data->DisplayPos.y == 0 && draw_data->DisplaySize.y == _height); + + // Create and grow vertex/index buffers if needed + if (_imgui_index_buffer_size < draw_data->TotalIdxCount) + { + _imgui_index_buffer.reset(); + _imgui_index_buffer_size = draw_data->TotalIdxCount + 10000; + + D3D10_BUFFER_DESC desc = {}; + desc.Usage = D3D10_USAGE_DYNAMIC; + desc.ByteWidth = _imgui_index_buffer_size * sizeof(ImDrawIdx); + desc.BindFlags = D3D10_BIND_INDEX_BUFFER; + desc.CPUAccessFlags = D3D10_CPU_ACCESS_WRITE; + + if (FAILED(_device->CreateBuffer(&desc, nullptr, &_imgui_index_buffer))) + return; + } + if (_imgui_vertex_buffer_size < draw_data->TotalVtxCount) + { + _imgui_vertex_buffer.reset(); + _imgui_vertex_buffer_size = draw_data->TotalVtxCount + 5000; + + D3D10_BUFFER_DESC desc = {}; + desc.Usage = D3D10_USAGE_DYNAMIC; + desc.ByteWidth = _imgui_vertex_buffer_size * sizeof(ImDrawVert); + desc.BindFlags = D3D10_BIND_VERTEX_BUFFER; + desc.CPUAccessFlags = D3D10_CPU_ACCESS_WRITE; + desc.MiscFlags = 0; + + if (FAILED(_device->CreateBuffer(&desc, nullptr, &_imgui_vertex_buffer))) + return; + } + + ImDrawIdx *idx_dst; ImDrawVert *vtx_dst; + if (FAILED(_imgui_index_buffer->Map(D3D10_MAP_WRITE_DISCARD, 0, reinterpret_cast(&idx_dst))) || + FAILED(_imgui_vertex_buffer->Map(D3D10_MAP_WRITE_DISCARD, 0, reinterpret_cast(&vtx_dst)))) + return; + + for (int n = 0; n < draw_data->CmdListsCount; n++) + { + const ImDrawList *const draw_list = draw_data->CmdLists[n]; + memcpy(idx_dst, draw_list->IdxBuffer.Data, draw_list->IdxBuffer.Size * sizeof(ImDrawIdx)); + memcpy(vtx_dst, draw_list->VtxBuffer.Data, draw_list->VtxBuffer.Size * sizeof(ImDrawVert)); + idx_dst += draw_list->IdxBuffer.Size; + vtx_dst += draw_list->VtxBuffer.Size; + } + + _imgui_index_buffer->Unmap(); + _imgui_vertex_buffer->Unmap(); + + // Setup render state and render draw lists + _device->IASetInputLayout(_imgui_input_layout.get()); + _device->IASetIndexBuffer(_imgui_index_buffer.get(), sizeof(ImDrawIdx) == 2 ? DXGI_FORMAT_R16_UINT : DXGI_FORMAT_R32_UINT, 0); + const UINT stride = sizeof(ImDrawVert), offset = 0; + ID3D10Buffer *const vertex_buffers[] = { _imgui_vertex_buffer.get() }; + _device->IASetVertexBuffers(0, ARRAYSIZE(vertex_buffers), vertex_buffers, &stride, &offset); + _device->IASetPrimitiveTopology(D3D10_PRIMITIVE_TOPOLOGY_TRIANGLELIST); + _device->VSSetShader(_imgui_vertex_shader.get()); + ID3D10Buffer *const constant_buffers[] = { _imgui_constant_buffer.get() }; + _device->VSSetConstantBuffers(0, ARRAYSIZE(constant_buffers), constant_buffers); + _device->GSSetShader(nullptr); + _device->PSSetShader(_imgui_pixel_shader.get()); + ID3D10SamplerState *const samplers[] = { _imgui_texture_sampler.get() }; + _device->PSSetSamplers(0, ARRAYSIZE(samplers), samplers); + _device->RSSetState(_imgui_rasterizer_state.get()); + const D3D10_VIEWPORT viewport = { 0, 0, _width, _height, 0.0f, 1.0f }; + _device->RSSetViewports(1, &viewport); + const FLOAT blend_factor[4] = { 0.f, 0.f, 0.f, 0.f }; + _device->OMSetBlendState(_imgui_blend_state.get(), blend_factor, D3D10_DEFAULT_SAMPLE_MASK); + _device->OMSetDepthStencilState(_imgui_depthstencil_state.get(), 0); + ID3D10RenderTargetView *const render_targets[] = { _backbuffer_rtv[0].get() }; + _device->OMSetRenderTargets(ARRAYSIZE(render_targets), render_targets, nullptr); + + UINT vtx_offset = 0, idx_offset = 0; + for (int n = 0; n < draw_data->CmdListsCount; n++) + { + const ImDrawList *const draw_list = draw_data->CmdLists[n]; + + for (const ImDrawCmd &cmd : draw_list->CmdBuffer) + { + assert(cmd.TextureId != 0); + assert(cmd.UserCallback == nullptr); + + const D3D10_RECT scissor_rect = { + static_cast(cmd.ClipRect.x), + static_cast(cmd.ClipRect.y), + static_cast(cmd.ClipRect.z), + static_cast(cmd.ClipRect.w) + }; + _device->RSSetScissorRects(1, &scissor_rect); + + ID3D10ShaderResourceView *const texture_view = + static_cast(cmd.TextureId)->srv[0].get(); + _device->PSSetShaderResources(0, 1, &texture_view); + + _device->DrawIndexed(cmd.ElemCount, idx_offset, vtx_offset); + + idx_offset += cmd.ElemCount; + } + + vtx_offset += draw_list->VtxBuffer.size(); + } +} + +void reshade::d3d10::runtime_d3d10::draw_debug_menu() +{ + ImGui::Text("MSAA is %s", _is_multisampling_enabled ? "active" : "inactive"); + ImGui::Spacing(); + + assert(_current_tracker != nullptr); + +#if RESHADE_DX10_CAPTURE_DEPTH_BUFFERS + if (ImGui::CollapsingHeader("Depth and Intermediate Buffers", ImGuiTreeNodeFlags_DefaultOpen)) + { + bool modified = false; + modified |= ImGui::Combo("Depth Texture Format", &depth_buffer_texture_format, "All\0D16\0D32F\0D24S8\0D32FS8\0"); + + if (modified) + { + runtime::save_config(); + _current_tracker->reset(); + create_depthstencil_replacement(nullptr, nullptr); + return; + } + + modified |= ImGui::Checkbox("Copy depth before clearing", &depth_buffer_before_clear); + + if (depth_buffer_before_clear) + { + if (ImGui::Checkbox("Extended depth buffer detection", &extended_depth_buffer_detection)) + { + cleared_depth_buffer_index = 0; + modified = true; + } + + _current_tracker->keep_cleared_depth_textures(); + + ImGui::Spacing(); + ImGui::TextUnformatted("Depth Buffers:"); + + unsigned int current_index = 1; + + for (const auto &it : _current_tracker->cleared_depth_textures()) + { + char label[512] = ""; + sprintf_s(label, "%s%2u", (current_index == cleared_depth_buffer_index ? "> " : " "), current_index); + + if (bool value = cleared_depth_buffer_index == current_index; ImGui::Checkbox(label, &value)) + { + cleared_depth_buffer_index = value ? current_index : 0; + modified = true; + } + + ImGui::SameLine(); + + ImGui::Text("=> 0x%p | 0x%p | %ux%u", it.second.src_depthstencil.get(), it.second.src_texture.get(), it.second.src_texture_desc.Width, it.second.src_texture_desc.Height); + + if (it.second.dest_texture != nullptr) + { + ImGui::SameLine(); + + ImGui::Text("=> %p", it.second.dest_texture.get()); + } + + current_index++; + } + } + else if (!_current_tracker->depth_buffer_counters().empty()) + { + ImGui::Spacing(); + ImGui::TextUnformatted("Depth Buffers: (intermediate buffer draw calls in parentheses)"); + + for (const auto &[depthstencil, snapshot] : _current_tracker->depth_buffer_counters()) + { + char label[512] = ""; + sprintf_s(label, "%s0x%p", (depthstencil == _depthstencil ? "> " : " "), depthstencil.get()); + + if (bool value = _best_depth_stencil_overwrite == depthstencil; ImGui::Checkbox(label, &value)) + { + _best_depth_stencil_overwrite = value ? depthstencil.get() : nullptr; + + com_ptr texture = snapshot.texture; + + if (texture == nullptr && _best_depth_stencil_overwrite != nullptr) + { + com_ptr resource; + _best_depth_stencil_overwrite->GetResource(&resource); + + resource->QueryInterface(&texture); + } + + create_depthstencil_replacement(_best_depth_stencil_overwrite, texture.get()); + } + + ImGui::SameLine(); + + std::string additional_view_label; + + if (!snapshot.additional_views.empty()) + { + additional_view_label += '('; + + for (auto const &[view, stats] : snapshot.additional_views) + additional_view_label += std::to_string(stats.drawcalls) + ", "; + + // Remove last ", " from string + additional_view_label.pop_back(); + additional_view_label.pop_back(); + + additional_view_label += ')'; + } + + ImGui::Text("| %5u draw calls ==> %8u vertices, %2u additional render target%c %s", snapshot.stats.drawcalls, snapshot.stats.vertices, snapshot.additional_views.size(), snapshot.additional_views.size() != 1 ? 's' : ' ', additional_view_label.c_str()); + } + } + + if (modified) + { + runtime::save_config(); + } + } +#endif +#if RESHADE_DX10_CAPTURE_CONSTANT_BUFFERS + if (ImGui::CollapsingHeader("Constant Buffers", ImGuiTreeNodeFlags_DefaultOpen)) + { + for (const auto &[buffer, counter] : _current_tracker.constant_buffer_counters()) + { + bool likely_camera_transform_buffer = false; + + D3D10_BUFFER_DESC desc; + buffer->GetDesc(&desc); + + if (counter.ps_uses > 0 && counter.vs_uses > 0 && counter.mapped < .10 * counter.vs_uses && desc.ByteWidth > 1000) + likely_camera_transform_buffer = true; + + ImGui::Text("%s 0x%p | used in %4u vertex shaders and %4u pixel shaders, mapped %3u times, %8u bytes", likely_camera_transform_buffer ? ">" : " ", buffer.get(), counter.vs_uses, counter.ps_uses, counter.mapped, desc.ByteWidth); + } + } +#endif +} +#endif + +#if RESHADE_DX10_CAPTURE_DEPTH_BUFFERS +void reshade::d3d10::runtime_d3d10::detect_depth_source(draw_call_tracker &tracker) +{ + if (depth_buffer_before_clear) + _best_depth_stencil_overwrite = nullptr; + + if (_is_multisampling_enabled || _best_depth_stencil_overwrite != nullptr || (_framecount % 30 && !depth_buffer_before_clear)) + return; + + if (_has_high_network_activity) + { + create_depthstencil_replacement(nullptr, nullptr); + return; + } + + if (depth_buffer_before_clear) + { + // At the final rendering stage, it is fine to rely on the depth stencil to select the best depth texture + // But when we retrieve the depth textures before the final rendering stage, there is chance that one or many different depth textures are associated to the same depth stencil (for instance, in Bioshock 2) + // In this case, we cannot use the depth stencil to determine which depth texture is the good one, so we can use the default depth stencil + // For the moment, the best we can do is retrieve all the depth textures that has been cleared in the rendering pipeline, then select one of them (by default, the last one) + // In the future, maybe we could find a way to retrieve depth texture statistics (number of draw calls and number of vertices), so ReShade could automatically select the best one + ID3D10Texture2D *const best_match_texture = tracker.find_best_cleared_depth_buffer_texture(cleared_depth_buffer_index); + if (best_match_texture != nullptr) + create_depthstencil_replacement(_default_depthstencil.get(), best_match_texture); + return; + } + + const auto best_snapshot = tracker.find_best_snapshot(_width, _height); + if (best_snapshot.depthstencil != nullptr) + create_depthstencil_replacement(best_snapshot.depthstencil, best_snapshot.texture.get()); +} + +bool reshade::d3d10::runtime_d3d10::create_depthstencil_replacement(ID3D10DepthStencilView *depthstencil, ID3D10Texture2D *texture) +{ + _depthstencil.reset(); + _depthstencil_replacement.reset(); + _depthstencil_texture.reset(); + _depthstencil_texture_srv.reset(); + + if (depthstencil != nullptr) + { + assert(texture != nullptr); + _depthstencil = depthstencil; + _depthstencil_texture = texture; + + D3D10_TEXTURE2D_DESC tex_desc; + _depthstencil_texture->GetDesc(&tex_desc); + + HRESULT hr = S_OK; + + if ((tex_desc.BindFlags & D3D10_BIND_SHADER_RESOURCE) == 0) + { + _depthstencil_texture.reset(); + + tex_desc.Format = make_dxgi_format_typeless(tex_desc.Format); + tex_desc.BindFlags = D3D10_BIND_DEPTH_STENCIL | D3D10_BIND_SHADER_RESOURCE; + + hr = _device->CreateTexture2D(&tex_desc, nullptr, &_depthstencil_texture); + + if (SUCCEEDED(hr)) + { + D3D10_DEPTH_STENCIL_VIEW_DESC dsv_desc = {}; + dsv_desc.ViewDimension = D3D10_DSV_DIMENSION_TEXTURE2D; + dsv_desc.Format = make_dxgi_format_dsv(tex_desc.Format); + + hr = _device->CreateDepthStencilView(_depthstencil_texture.get(), &dsv_desc, &_depthstencil_replacement); + } + } + + if (FAILED(hr)) + { + LOG(ERROR) << "Failed to create depth stencil replacement texture! HRESULT is '" << std::hex << hr << std::dec << "'."; + return false; + } + + D3D10_SHADER_RESOURCE_VIEW_DESC srv_desc = {}; + srv_desc.ViewDimension = D3D10_SRV_DIMENSION_TEXTURE2D; + srv_desc.Texture2D.MipLevels = 1; + srv_desc.Format = make_dxgi_format_normal(tex_desc.Format); + + if (hr = _device->CreateShaderResourceView(_depthstencil_texture.get(), &srv_desc, &_depthstencil_texture_srv); FAILED(hr)) + { + LOG(ERROR) << "Failed to create depth stencil replacement resource view! HRESULT is '" << std::hex << hr << std::dec << "'."; + return false; + } + + // Update auto depth stencil + com_ptr current_depthstencil; + com_ptr targets[D3D10_SIMULTANEOUS_RENDER_TARGET_COUNT]; + _device->OMGetRenderTargets(D3D10_SIMULTANEOUS_RENDER_TARGET_COUNT, reinterpret_cast(targets), ¤t_depthstencil); + if (current_depthstencil != nullptr && current_depthstencil == _depthstencil) + _device->OMSetRenderTargets(D3D10_SIMULTANEOUS_RENDER_TARGET_COUNT, reinterpret_cast(targets), _depthstencil_replacement.get()); + } + + update_texture_references(texture_reference::depth_buffer); + + return true; +} + +com_ptr reshade::d3d10::runtime_d3d10::select_depth_texture_save(D3D10_TEXTURE2D_DESC &texture_desc) +{ + // Function that selects the appropriate texture where we want to save the depth texture before it is cleared + // If this texture is null, create it according to the dimensions and the format of the depth texture + // Doing so, we avoid to create a new texture each time the depth texture is saved + + texture_desc.Format = make_dxgi_format_typeless(texture_desc.Format); + + // Create an unique index based on the texture format and dimensions + UINT idx = texture_desc.Format * texture_desc.Width * texture_desc.Height; + + if (const auto it = _depth_texture_saves.find(idx); it != _depth_texture_saves.end()) + return it->second; + + texture_desc.BindFlags = D3D10_BIND_DEPTH_STENCIL | D3D10_BIND_SHADER_RESOURCE; + + // Create the saved texture pointed by the index if it does not already exist + com_ptr depth_texture_save; + + HRESULT hr = _device->CreateTexture2D(&texture_desc, nullptr, &depth_texture_save); + + if (FAILED(hr)) + { + LOG(ERROR) << "Failed to create depth texture copy! HRESULT is '" << std::hex << hr << std::dec << "'."; + return nullptr; + } + + _depth_texture_saves.emplace(idx, depth_texture_save); + + return depth_texture_save; +} +#endif diff --git a/msvc/source/d3d10/runtime_d3d10.hpp b/msvc/source/d3d10/runtime_d3d10.hpp new file mode 100644 index 0000000..16704f2 --- /dev/null +++ b/msvc/source/d3d10/runtime_d3d10.hpp @@ -0,0 +1,116 @@ +/** + * Copyright (C) 2014 Patrick Mours. All rights reserved. + * License: https://github.com/crosire/reshade#license + */ + +#pragma once + +#include "runtime.hpp" +#include "state_block.hpp" +#include "draw_call_tracker.hpp" + +namespace reshade { enum class texture_reference; } +namespace reshadefx { struct sampler_info; } + +namespace reshade::d3d10 +{ + class runtime_d3d10 : public runtime + { + public: + runtime_d3d10(ID3D10Device1 *device, IDXGISwapChain *swapchain); + ~runtime_d3d10(); + + bool on_init(const DXGI_SWAP_CHAIN_DESC &desc); + void on_reset(); + void on_present(draw_call_tracker& tracker); + + void capture_screenshot(uint8_t *buffer) const override; + +#if RESHADE_DX10_CAPTURE_DEPTH_BUFFERS + com_ptr select_depth_texture_save(D3D10_TEXTURE2D_DESC &texture_desc); +#endif + + bool depth_buffer_before_clear = false; + bool extended_depth_buffer_detection = false; + unsigned int cleared_depth_buffer_index = 0; + int depth_buffer_texture_format = 0; // No depth buffer texture format filter by default + + private: + bool init_backbuffer_texture(); + bool init_default_depth_stencil(); + + bool init_texture(texture &info) override; + void upload_texture(texture &texture, const uint8_t *pixels) override; + bool update_texture_reference(texture &texture); + void update_texture_references(texture_reference type); + + bool compile_effect(effect_data &effect) override; + void unload_effects() override; + + bool add_sampler(const reshadefx::sampler_info &info, struct d3d10_technique_data &technique_init); + bool init_technique(technique &info, const struct d3d10_technique_data &technique_init, const std::unordered_map> &entry_points); + + void render_technique(technique &technique) override; + +#if RESHADE_GUI + bool init_imgui_resources(); + void render_imgui_draw_data(ImDrawData *data) override; + void draw_debug_menu(); +#endif + +#if RESHADE_DX10_CAPTURE_DEPTH_BUFFERS + void detect_depth_source(draw_call_tracker& tracker); + bool create_depthstencil_replacement(ID3D10DepthStencilView *depthstencil, ID3D10Texture2D *texture); +#endif + + struct depth_texture_save_info + { + com_ptr src_texture; + D3D10_TEXTURE2D_DESC src_texture_desc; + com_ptr dest_texture; + bool cleared = false; + }; + + com_ptr _device; + com_ptr _swapchain; + + com_ptr _backbuffer_texture; + com_ptr _backbuffer_rtv[3]; + com_ptr _backbuffer_texture_srv[2]; + com_ptr _depthstencil_texture_srv; + std::unordered_map> _effect_sampler_states; + std::vector> _constant_buffers; + + std::map _displayed_depth_textures; + std::unordered_map> _depth_texture_saves; + + bool _is_multisampling_enabled = false; + DXGI_FORMAT _backbuffer_format = DXGI_FORMAT_UNKNOWN; + state_block _app_state; + com_ptr _backbuffer, _backbuffer_resolved; + com_ptr _depthstencil, _depthstencil_replacement; + ID3D10DepthStencilView *_best_depth_stencil_overwrite = nullptr; + com_ptr _depthstencil_texture; + com_ptr _default_depthstencil; + com_ptr _copy_vertex_shader; + com_ptr _copy_pixel_shader; + com_ptr _copy_sampler; + com_ptr _effect_rasterizer_state; + + int _imgui_index_buffer_size = 0; + com_ptr _imgui_index_buffer; + int _imgui_vertex_buffer_size = 0; + com_ptr _imgui_vertex_buffer; + com_ptr _imgui_vertex_shader; + com_ptr _imgui_pixel_shader; + com_ptr _imgui_input_layout; + com_ptr _imgui_constant_buffer; + com_ptr _imgui_texture_sampler; + com_ptr _imgui_rasterizer_state; + com_ptr _imgui_blend_state; + com_ptr _imgui_depthstencil_state; + draw_call_tracker *_current_tracker = nullptr; + + HMODULE _d3d_compiler = nullptr; + }; +} diff --git a/msvc/source/d3d10/state_block.cpp b/msvc/source/d3d10/state_block.cpp new file mode 100644 index 0000000..1b23ee2 --- /dev/null +++ b/msvc/source/d3d10/state_block.cpp @@ -0,0 +1,117 @@ +/** + * Copyright (C) 2014 Patrick Mours. All rights reserved. + * License: https://github.com/crosire/reshade#license + */ + +#include "state_block.hpp" + +template +static inline void safe_release(T *&object) +{ + if (object != nullptr) + { + object->Release(); + object = nullptr; + } +} + +reshade::d3d10::state_block::state_block(ID3D10Device *device) +{ + ZeroMemory(this, sizeof(*this)); + + _device = device; +} +reshade::d3d10::state_block::~state_block() +{ + release_all_device_objects(); +} + +void reshade::d3d10::state_block::capture() +{ + _device->IAGetPrimitiveTopology(&_ia_primitive_topology); + _device->IAGetInputLayout(&_ia_input_layout); + + _device->IAGetVertexBuffers(0, ARRAYSIZE(_ia_vertex_buffers), _ia_vertex_buffers, _ia_vertex_strides, _ia_vertex_offsets); + _device->IAGetIndexBuffer(&_ia_index_buffer, &_ia_index_format, &_ia_index_offset); + + _device->RSGetState(&_rs_state); + _device->RSGetViewports(&_rs_num_viewports, nullptr); + _device->RSGetViewports(&_rs_num_viewports, _rs_viewports); + _device->RSGetScissorRects(&_rs_num_scissor_rects, nullptr); + _device->RSGetScissorRects(&_rs_num_scissor_rects, _rs_scissor_rects); + + _device->VSGetShader(&_vs); + _device->VSGetConstantBuffers(0, ARRAYSIZE(_vs_constant_buffers), _vs_constant_buffers); + _device->VSGetSamplers(0, ARRAYSIZE(_vs_sampler_states), _vs_sampler_states); + _device->VSGetShaderResources(0, ARRAYSIZE(_vs_shader_resources), _vs_shader_resources); + + _device->GSGetShader(&_gs); + + _device->PSGetShader(&_ps); + _device->PSGetConstantBuffers(0, ARRAYSIZE(_ps_constant_buffers), _ps_constant_buffers); + _device->PSGetSamplers(0, ARRAYSIZE(_ps_sampler_states), _ps_sampler_states); + _device->PSGetShaderResources(0, ARRAYSIZE(_ps_shader_resources), _ps_shader_resources); + + _device->OMGetBlendState(&_om_blend_state, _om_blend_factor, &_om_sample_mask); + _device->OMGetDepthStencilState(&_om_depth_stencil_state, &_om_stencil_ref); + _device->OMGetRenderTargets(ARRAYSIZE(_om_render_targets), _om_render_targets, &_om_depth_stencil); +} +void reshade::d3d10::state_block::apply_and_release() +{ + _device->IASetPrimitiveTopology(_ia_primitive_topology); + _device->IASetInputLayout(_ia_input_layout); + + _device->IASetVertexBuffers(0, ARRAYSIZE(_ia_vertex_buffers), _ia_vertex_buffers, _ia_vertex_strides, _ia_vertex_offsets); + _device->IASetIndexBuffer(_ia_index_buffer, _ia_index_format, _ia_index_offset); + + _device->RSSetState(_rs_state); + _device->RSSetViewports(_rs_num_viewports, _rs_viewports); + _device->RSSetScissorRects(_rs_num_scissor_rects, _rs_scissor_rects); + + _device->VSSetShader(_vs); + _device->VSSetConstantBuffers(0, ARRAYSIZE(_vs_constant_buffers), _vs_constant_buffers); + _device->VSSetSamplers(0, ARRAYSIZE(_vs_sampler_states), _vs_sampler_states); + _device->VSSetShaderResources(0, ARRAYSIZE(_vs_shader_resources), _vs_shader_resources); + + _device->GSSetShader(_gs); + + _device->PSSetShader(_ps); + _device->PSSetConstantBuffers(0, ARRAYSIZE(_ps_constant_buffers), _ps_constant_buffers); + _device->PSSetSamplers(0, ARRAYSIZE(_ps_sampler_states), _ps_sampler_states); + _device->PSSetShaderResources(0, ARRAYSIZE(_ps_shader_resources), _ps_shader_resources); + + _device->OMSetBlendState(_om_blend_state, _om_blend_factor, _om_sample_mask); + _device->OMSetDepthStencilState(_om_depth_stencil_state, _om_stencil_ref); + _device->OMSetRenderTargets(ARRAYSIZE(_om_render_targets), _om_render_targets, _om_depth_stencil); + + release_all_device_objects(); +} + +void reshade::d3d10::state_block::release_all_device_objects() +{ + safe_release(_ia_input_layout); + for (auto &vertex_buffer : _ia_vertex_buffers) + safe_release(vertex_buffer); + safe_release(_ia_index_buffer); + safe_release(_vs); + for (auto &constant_buffer : _vs_constant_buffers) + safe_release(constant_buffer); + for (auto &sampler_state : _vs_sampler_states) + safe_release(sampler_state); + for (auto &shader_resource : _vs_shader_resources) + safe_release(shader_resource); + safe_release(_gs); + safe_release(_rs_state); + safe_release(_ps); + for (auto &constant_buffer : _ps_constant_buffers) + safe_release(constant_buffer); + for (auto &sampler_state : _ps_sampler_states) + safe_release(sampler_state); + for (auto &shader_resource : _ps_shader_resources) + safe_release(shader_resource); + safe_release(_om_blend_state); + safe_release(_om_depth_stencil_state); + for (auto &render_target : _om_render_targets) + safe_release(render_target); + safe_release(_om_depth_stencil); +} diff --git a/msvc/source/d3d10/state_block.hpp b/msvc/source/d3d10/state_block.hpp new file mode 100644 index 0000000..399cd5e --- /dev/null +++ b/msvc/source/d3d10/state_block.hpp @@ -0,0 +1,56 @@ +/** + * Copyright (C) 2014 Patrick Mours. All rights reserved. + * License: https://github.com/crosire/reshade#license + */ + +#pragma once + +#include "com_ptr.hpp" +#include + +namespace reshade::d3d10 +{ + class state_block + { + public: + explicit state_block(ID3D10Device *device); + ~state_block(); + + void capture(); + void apply_and_release(); + + private: + void release_all_device_objects(); + + com_ptr _device; + ID3D10InputLayout *_ia_input_layout; + D3D10_PRIMITIVE_TOPOLOGY _ia_primitive_topology; + ID3D10Buffer *_ia_vertex_buffers[D3D10_IA_VERTEX_INPUT_RESOURCE_SLOT_COUNT]; + UINT _ia_vertex_strides[D3D10_IA_VERTEX_INPUT_RESOURCE_SLOT_COUNT]; + UINT _ia_vertex_offsets[D3D10_IA_VERTEX_INPUT_RESOURCE_SLOT_COUNT]; + ID3D10Buffer *_ia_index_buffer; + DXGI_FORMAT _ia_index_format; + UINT _ia_index_offset; + ID3D10VertexShader *_vs; + ID3D10Buffer *_vs_constant_buffers[D3D10_COMMONSHADER_CONSTANT_BUFFER_API_SLOT_COUNT]; + ID3D10SamplerState *_vs_sampler_states[D3D10_COMMONSHADER_SAMPLER_SLOT_COUNT]; + ID3D10ShaderResourceView *_vs_shader_resources[D3D10_COMMONSHADER_INPUT_RESOURCE_SLOT_COUNT]; + ID3D10GeometryShader *_gs; + ID3D10RasterizerState *_rs_state; + UINT _rs_num_viewports; + D3D10_VIEWPORT _rs_viewports[D3D10_VIEWPORT_AND_SCISSORRECT_OBJECT_COUNT_PER_PIPELINE]; + UINT _rs_num_scissor_rects; + D3D10_RECT _rs_scissor_rects[D3D10_VIEWPORT_AND_SCISSORRECT_OBJECT_COUNT_PER_PIPELINE]; + ID3D10PixelShader *_ps; + ID3D10Buffer *_ps_constant_buffers[D3D10_COMMONSHADER_CONSTANT_BUFFER_API_SLOT_COUNT]; + ID3D10SamplerState *_ps_sampler_states[D3D10_COMMONSHADER_SAMPLER_SLOT_COUNT]; + ID3D10ShaderResourceView *_ps_shader_resources[D3D10_COMMONSHADER_INPUT_RESOURCE_SLOT_COUNT]; + ID3D10BlendState *_om_blend_state; + FLOAT _om_blend_factor[4]; + UINT _om_sample_mask; + ID3D10DepthStencilState *_om_depth_stencil_state; + UINT _om_stencil_ref; + ID3D10RenderTargetView *_om_render_targets[D3D10_SIMULTANEOUS_RENDER_TARGET_COUNT]; + ID3D10DepthStencilView *_om_depth_stencil; + }; +} diff --git a/msvc/source/d3d11/d3d11.cpp b/msvc/source/d3d11/d3d11.cpp new file mode 100644 index 0000000..582cd79 --- /dev/null +++ b/msvc/source/d3d11/d3d11.cpp @@ -0,0 +1,99 @@ +/** + * Copyright (C) 2014 Patrick Mours. All rights reserved. + * License: https://github.com/crosire/reshade#license + */ + +#include "log.hpp" +#include "hook_manager.hpp" +#include "d3d11_device.hpp" +#include "d3d11_device_context.hpp" + +HOOK_EXPORT HRESULT WINAPI D3D11CreateDevice(IDXGIAdapter *pAdapter, D3D_DRIVER_TYPE DriverType, HMODULE Software, UINT Flags, const D3D_FEATURE_LEVEL *pFeatureLevels, UINT FeatureLevels, UINT SDKVersion, ID3D11Device **ppDevice, D3D_FEATURE_LEVEL *pFeatureLevel, ID3D11DeviceContext **ppImmediateContext) +{ + LOG(INFO) << "Redirecting D3D11CreateDevice" << '(' << pAdapter << ", " << DriverType << ", " << Software << ", " << std::hex << Flags << std::dec << ", " << pFeatureLevels << ", " << FeatureLevels << ", " << SDKVersion << ", " << ppDevice << ", " << pFeatureLevel << ", " << ppImmediateContext << ')' << " ..."; + LOG(INFO) << "> Passing on to D3D11CreateDeviceAndSwapChain:"; + + return D3D11CreateDeviceAndSwapChain(pAdapter, DriverType, Software, Flags, pFeatureLevels, FeatureLevels, SDKVersion, nullptr, nullptr, ppDevice, pFeatureLevel, ppImmediateContext); +} + +HOOK_EXPORT HRESULT WINAPI D3D11CreateDeviceAndSwapChain(IDXGIAdapter *pAdapter, D3D_DRIVER_TYPE DriverType, HMODULE Software, UINT Flags, const D3D_FEATURE_LEVEL *pFeatureLevels, UINT FeatureLevels, UINT SDKVersion, const DXGI_SWAP_CHAIN_DESC *pSwapChainDesc, IDXGISwapChain **ppSwapChain, ID3D11Device **ppDevice, D3D_FEATURE_LEVEL *pFeatureLevel, ID3D11DeviceContext **ppImmediateContext) +{ + LOG(INFO) << "Redirecting D3D11CreateDeviceAndSwapChain" << '(' << pAdapter << ", " << DriverType << ", " << Software << ", " << std::hex << Flags << std::dec << ", " << pFeatureLevels << ", " << FeatureLevels << ", " << SDKVersion << ", " << pSwapChainDesc << ", " << ppSwapChain << ", " << ppDevice << ", " << pFeatureLevel << ", " << ppImmediateContext << ')' << " ..."; + + // Use local feature level variable in case the application did not pass one in + D3D_FEATURE_LEVEL FeatureLevel = D3D_FEATURE_LEVEL_11_0; + + HRESULT hr = reshade::hooks::call(D3D11CreateDeviceAndSwapChain)(pAdapter, DriverType, Software, Flags, pFeatureLevels, FeatureLevels, SDKVersion, nullptr, nullptr, ppDevice, &FeatureLevel, nullptr); + + if (FAILED(hr)) + { + LOG(WARN) << "> D3D11CreateDeviceAndSwapChain failed with error code " << std::hex << hr << std::dec << '!'; + return hr; + } + + if (pFeatureLevel != nullptr) // Copy feature level value to application variable if the argument exists + *pFeatureLevel = FeatureLevel; + + LOG(INFO) << "> Using feature level " << std::hex << FeatureLevel << std::dec << '.'; + + // It is valid for the device out parameter to be NULL if the application wants to check feature level support, so just return early in that case + if (ppDevice == nullptr) + return hr; + + const auto device = *ppDevice; + + // Query for the DXGI device and immediate device context since we need to reference them in the hooked device + IDXGIDevice1 *dxgi_device = nullptr; + device->QueryInterface(&dxgi_device); + ID3D11DeviceContext *device_context = nullptr; + device->GetImmediateContext(&device_context); + + const auto device_proxy = new D3D11Device(dxgi_device, device, device_context); + + // Swap chain creation is piped through the 'IDXGIFactory::CreateSwapChain' function hook + if (pSwapChainDesc != nullptr) + { + assert(ppSwapChain != nullptr); + + com_ptr adapter(pAdapter, false); + // Fall back to the same adapter as the device if it was not explicitly specified in the argument list + if (adapter == nullptr) + { + hr = dxgi_device->GetAdapter(&adapter); + assert(SUCCEEDED(hr)); // Lets just assume this works =) + } + + // Time to find a factory associated with the target adapter and create a swap chain with it + com_ptr factory; + hr = adapter->GetParent(IID_PPV_ARGS(&factory)); + assert(SUCCEEDED(hr)); + + LOG(INFO) << "> Calling IDXGIFactory::CreateSwapChain:"; + + hr = factory->CreateSwapChain(device_proxy, const_cast(pSwapChainDesc), ppSwapChain); + } + + if (SUCCEEDED(hr)) + { +#if RESHADE_VERBOSE_LOG + LOG(DEBUG) << "Returning IDXGIDevice1 object " << device_proxy->_dxgi_device << " and ID3D11Device object " << device_proxy << '.'; +#endif + *ppDevice = device_proxy; + + if (ppImmediateContext != nullptr) + { +#if RESHADE_VERBOSE_LOG + LOG(DEBUG) << "Returning ID3D11DeviceContext object " << device_proxy->_immediate_context << '.'; +#endif + device_proxy->_immediate_context->AddRef(); // D3D11CreateDeviceAndSwapChain increases the reference count on the returned object + *ppImmediateContext = device_proxy->_immediate_context; + } + } + else + { + // Swap chain creation failed, so do clean up + device_proxy->Release(); + } + + return hr; +} diff --git a/msvc/source/d3d11/d3d11_device.cpp b/msvc/source/d3d11/d3d11_device.cpp new file mode 100644 index 0000000..375e9e9 --- /dev/null +++ b/msvc/source/d3d11/d3d11_device.cpp @@ -0,0 +1,493 @@ +/** + * Copyright (C) 2014 Patrick Mours. All rights reserved. + * License: https://github.com/crosire/reshade#license + */ + +#include "log.hpp" +#include "d3d11_device.hpp" +#include "d3d11_device_context.hpp" +#include "../dxgi/dxgi_device.hpp" +#include "draw_call_tracker.hpp" + +D3D11Device::D3D11Device(IDXGIDevice1 *dxgi_device, ID3D11Device *original, ID3D11DeviceContext *immediate_context) : + _orig(original), + _interface_version(0), + _dxgi_device(new DXGIDevice(dxgi_device, this)), + _immediate_context(new D3D11DeviceContext(this, immediate_context)) { + assert(original != nullptr); +} + +void D3D11Device::add_commandlist_trackers(ID3D11CommandList* command_list, const reshade::d3d11::draw_call_tracker &tracker_source) +{ + assert(command_list != nullptr); + + const std::lock_guard lock(_trackers_per_commandlist_mutex); + + // Merges the counters in counters_source in the counters_per_commandlist for the command list specified + const auto it = _trackers_per_commandlist.find(command_list); + if (it == _trackers_per_commandlist.end()) + _trackers_per_commandlist.emplace(command_list, tracker_source); + else + it->second.merge(tracker_source); +} +void D3D11Device::merge_commandlist_trackers(ID3D11CommandList* command_list, reshade::d3d11::draw_call_tracker &tracker_destination) +{ + assert(command_list != nullptr); + + const std::lock_guard lock(_trackers_per_commandlist_mutex); + + // Merges the counters logged for the specified command list in the counters destination tracker specified + const auto it = _trackers_per_commandlist.find(command_list); + if (it != _trackers_per_commandlist.end()) + tracker_destination.merge(it->second); +} + +void D3D11Device::clear_drawcall_stats() +{ + _immediate_context->clear_drawcall_stats(); + + const std::lock_guard lock(_trackers_per_commandlist_mutex); + + _trackers_per_commandlist.clear(); + _clear_DSV_iter = 1; +} + +bool D3D11Device::check_and_upgrade_interface(REFIID riid) +{ + static const IID iid_lookup[] = { + __uuidof(ID3D11Device), + __uuidof(ID3D11Device1), + __uuidof(ID3D11Device2), + __uuidof(ID3D11Device3), + __uuidof(ID3D11Device4), + __uuidof(ID3D11Device5), + }; + + for (unsigned int new_version = _interface_version + 1; new_version < ARRAYSIZE(iid_lookup); ++new_version) + { + if (riid == iid_lookup[new_version]) + { + IUnknown *new_interface = nullptr; + if (FAILED(_orig->QueryInterface(riid, reinterpret_cast(&new_interface)))) + return false; +#if RESHADE_VERBOSE_LOG + LOG(DEBUG) << "Upgraded ID3D11Device" << _interface_version << " object " << this << " to ID3D11Device" << new_version << '.'; +#endif + _orig->Release(); + _orig = static_cast(new_interface); + _interface_version = new_version; + break; + } + } + + return true; +} + +HRESULT STDMETHODCALLTYPE D3D11Device::QueryInterface(REFIID riid, void **ppvObj) +{ + if (ppvObj == nullptr) + return E_POINTER; + + if (riid == __uuidof(this) || + riid == __uuidof(ID3D11Device) || + riid == __uuidof(ID3D11Device1) || + riid == __uuidof(ID3D11Device2) || + riid == __uuidof(ID3D11Device3) || + riid == __uuidof(ID3D11Device4) || + riid == __uuidof(ID3D11Device5)) + { + if (!check_and_upgrade_interface(riid)) + return E_NOINTERFACE; + + AddRef(); + *ppvObj = this; + return S_OK; + } + + // Note: Objects must have an identity, so use DXGIDevice for IID_IUnknown + // See https://docs.microsoft.com/en-us/windows/desktop/com/rules-for-implementing-queryinterface + if (riid == __uuidof(IUnknown) || + riid == __uuidof(DXGIDevice) || + riid == __uuidof(IDXGIObject) || + riid == __uuidof(IDXGIDevice) || + riid == __uuidof(IDXGIDevice1) || + riid == __uuidof(IDXGIDevice2) || + riid == __uuidof(IDXGIDevice3)) + return _dxgi_device->QueryInterface(riid, ppvObj); + + return _orig->QueryInterface(riid, ppvObj); +} +ULONG STDMETHODCALLTYPE D3D11Device::AddRef() +{ + ++_ref; + + _dxgi_device->AddRef(); + _immediate_context->AddRef(); + + return _orig->AddRef(); +} +ULONG STDMETHODCALLTYPE D3D11Device::Release() +{ + --_ref; + + _dxgi_device->Release(); + _immediate_context->Release(); + + const ULONG ref = _orig->Release(); + + if (ref != 0 && _ref != 0) + return ref; + else if (ref != 0) + LOG(WARN) << "Reference count for ID3D11Device" << _interface_version << " object " << this << " is inconsistent: " << ref << ", but expected 0."; + + assert(_ref <= 0); +#if RESHADE_VERBOSE_LOG + LOG(DEBUG) << "Destroyed ID3D11Device" << _interface_version << " object " << this << '.'; +#endif + delete this; + return 0; +} + +HRESULT STDMETHODCALLTYPE D3D11Device::CreateBuffer(const D3D11_BUFFER_DESC *pDesc, const D3D11_SUBRESOURCE_DATA *pInitialData, ID3D11Buffer **ppBuffer) +{ + return _orig->CreateBuffer(pDesc, pInitialData, ppBuffer); +} +HRESULT STDMETHODCALLTYPE D3D11Device::CreateTexture1D(const D3D11_TEXTURE1D_DESC *pDesc, const D3D11_SUBRESOURCE_DATA *pInitialData, ID3D11Texture1D **ppTexture1D) +{ + return _orig->CreateTexture1D(pDesc, pInitialData, ppTexture1D); +} +HRESULT STDMETHODCALLTYPE D3D11Device::CreateTexture2D(const D3D11_TEXTURE2D_DESC *pDesc, const D3D11_SUBRESOURCE_DATA *pInitialData, ID3D11Texture2D **ppTexture2D) +{ + return _orig->CreateTexture2D(pDesc, pInitialData, ppTexture2D); +} +HRESULT STDMETHODCALLTYPE D3D11Device::CreateTexture3D(const D3D11_TEXTURE3D_DESC *pDesc, const D3D11_SUBRESOURCE_DATA *pInitialData, ID3D11Texture3D **ppTexture3D) +{ + return _orig->CreateTexture3D(pDesc, pInitialData, ppTexture3D); +} +HRESULT STDMETHODCALLTYPE D3D11Device::CreateShaderResourceView(ID3D11Resource *pResource, const D3D11_SHADER_RESOURCE_VIEW_DESC *pDesc, ID3D11ShaderResourceView **ppSRView) +{ + return _orig->CreateShaderResourceView(pResource, pDesc, ppSRView); +} +HRESULT STDMETHODCALLTYPE D3D11Device::CreateUnorderedAccessView(ID3D11Resource *pResource, const D3D11_UNORDERED_ACCESS_VIEW_DESC *pDesc, ID3D11UnorderedAccessView **ppUAView) +{ + return _orig->CreateUnorderedAccessView(pResource, pDesc, ppUAView); +} +HRESULT STDMETHODCALLTYPE D3D11Device::CreateRenderTargetView(ID3D11Resource *pResource, const D3D11_RENDER_TARGET_VIEW_DESC *pDesc, ID3D11RenderTargetView **ppRTView) +{ + return _orig->CreateRenderTargetView(pResource, pDesc, ppRTView); +} +HRESULT STDMETHODCALLTYPE D3D11Device::CreateDepthStencilView(ID3D11Resource *pResource, const D3D11_DEPTH_STENCIL_VIEW_DESC *pDesc, ID3D11DepthStencilView **ppDepthStencilView) +{ + return _orig->CreateDepthStencilView(pResource, pDesc, ppDepthStencilView); +} +HRESULT STDMETHODCALLTYPE D3D11Device::CreateInputLayout(const D3D11_INPUT_ELEMENT_DESC *pInputElementDescs, UINT NumElements, const void *pShaderBytecodeWithInputSignature, SIZE_T BytecodeLength, ID3D11InputLayout **ppInputLayout) +{ + return _orig->CreateInputLayout(pInputElementDescs, NumElements, pShaderBytecodeWithInputSignature, BytecodeLength, ppInputLayout); +} +HRESULT STDMETHODCALLTYPE D3D11Device::CreateVertexShader(const void *pShaderBytecode, SIZE_T BytecodeLength, ID3D11ClassLinkage *pClassLinkage, ID3D11VertexShader **ppVertexShader) +{ + return _orig->CreateVertexShader(pShaderBytecode, BytecodeLength, pClassLinkage, ppVertexShader); +} +HRESULT STDMETHODCALLTYPE D3D11Device::CreateGeometryShader(const void *pShaderBytecode, SIZE_T BytecodeLength, ID3D11ClassLinkage *pClassLinkage, ID3D11GeometryShader **ppGeometryShader) +{ + return _orig->CreateGeometryShader(pShaderBytecode, BytecodeLength, pClassLinkage, ppGeometryShader); +} +HRESULT STDMETHODCALLTYPE D3D11Device::CreateGeometryShaderWithStreamOutput(const void *pShaderBytecode, SIZE_T BytecodeLength, const D3D11_SO_DECLARATION_ENTRY *pSODeclaration, UINT NumEntries, const UINT *pBufferStrides, UINT NumStrides, UINT RasterizedStream, ID3D11ClassLinkage *pClassLinkage, ID3D11GeometryShader **ppGeometryShader) +{ + return _orig->CreateGeometryShaderWithStreamOutput(pShaderBytecode, BytecodeLength, pSODeclaration, NumEntries, pBufferStrides, NumStrides, RasterizedStream, pClassLinkage, ppGeometryShader); +} +HRESULT STDMETHODCALLTYPE D3D11Device::CreatePixelShader(const void *pShaderBytecode, SIZE_T BytecodeLength, ID3D11ClassLinkage *pClassLinkage, ID3D11PixelShader **ppPixelShader) +{ + return _orig->CreatePixelShader(pShaderBytecode, BytecodeLength, pClassLinkage, ppPixelShader); +} +HRESULT STDMETHODCALLTYPE D3D11Device::CreateHullShader(const void *pShaderBytecode, SIZE_T BytecodeLength, ID3D11ClassLinkage *pClassLinkage, ID3D11HullShader **ppHullShader) +{ + return _orig->CreateHullShader(pShaderBytecode, BytecodeLength, pClassLinkage, ppHullShader); +} +HRESULT STDMETHODCALLTYPE D3D11Device::CreateDomainShader(const void *pShaderBytecode, SIZE_T BytecodeLength, ID3D11ClassLinkage *pClassLinkage, ID3D11DomainShader **ppDomainShader) +{ + return _orig->CreateDomainShader(pShaderBytecode, BytecodeLength, pClassLinkage, ppDomainShader); +} +HRESULT STDMETHODCALLTYPE D3D11Device::CreateComputeShader(const void *pShaderBytecode, SIZE_T BytecodeLength, ID3D11ClassLinkage *pClassLinkage, ID3D11ComputeShader **ppComputeShader) +{ + return _orig->CreateComputeShader(pShaderBytecode, BytecodeLength, pClassLinkage, ppComputeShader); +} +HRESULT STDMETHODCALLTYPE D3D11Device::CreateClassLinkage(ID3D11ClassLinkage **ppLinkage) +{ + return _orig->CreateClassLinkage(ppLinkage); +} +HRESULT STDMETHODCALLTYPE D3D11Device::CreateBlendState(const D3D11_BLEND_DESC *pBlendStateDesc, ID3D11BlendState **ppBlendState) +{ + return _orig->CreateBlendState(pBlendStateDesc, ppBlendState); +} +HRESULT STDMETHODCALLTYPE D3D11Device::CreateDepthStencilState(const D3D11_DEPTH_STENCIL_DESC *pDepthStencilDesc, ID3D11DepthStencilState **ppDepthStencilState) +{ + return _orig->CreateDepthStencilState(pDepthStencilDesc, ppDepthStencilState); +} +HRESULT STDMETHODCALLTYPE D3D11Device::CreateRasterizerState(const D3D11_RASTERIZER_DESC *pRasterizerDesc, ID3D11RasterizerState **ppRasterizerState) +{ + return _orig->CreateRasterizerState(pRasterizerDesc, ppRasterizerState); +} +HRESULT STDMETHODCALLTYPE D3D11Device::CreateSamplerState(const D3D11_SAMPLER_DESC *pSamplerDesc, ID3D11SamplerState **ppSamplerState) +{ + return _orig->CreateSamplerState(pSamplerDesc, ppSamplerState); +} +HRESULT STDMETHODCALLTYPE D3D11Device::CreateQuery(const D3D11_QUERY_DESC *pQueryDesc, ID3D11Query **ppQuery) +{ + return _orig->CreateQuery(pQueryDesc, ppQuery); +} +HRESULT STDMETHODCALLTYPE D3D11Device::CreatePredicate(const D3D11_QUERY_DESC *pPredicateDesc, ID3D11Predicate **ppPredicate) +{ + return _orig->CreatePredicate(pPredicateDesc, ppPredicate); +} +HRESULT STDMETHODCALLTYPE D3D11Device::CreateCounter(const D3D11_COUNTER_DESC *pCounterDesc, ID3D11Counter **ppCounter) +{ + return _orig->CreateCounter(pCounterDesc, ppCounter); +} +HRESULT STDMETHODCALLTYPE D3D11Device::CreateDeferredContext(UINT ContextFlags, ID3D11DeviceContext **ppDeferredContext) +{ + LOG(INFO) << "Redirecting ID3D11Device::CreateDeferredContext" << '(' << this << ", " << ContextFlags << ", " << ppDeferredContext << ')' << " ..."; + + if (ppDeferredContext == nullptr) + return E_INVALIDARG; + + const HRESULT hr = _orig->CreateDeferredContext(ContextFlags, ppDeferredContext); + + if (FAILED(hr)) + { + LOG(WARN) << "> ID3D11Device::CreateDeferredContext failed with error code " << std::hex << hr << std::dec << '!'; + return hr; + } + + *ppDeferredContext = new D3D11DeviceContext(this, *ppDeferredContext); + +#if RESHADE_VERBOSE_LOG + LOG(DEBUG) << "Returning ID3D11DeviceContext object " << *ppDeferredContext << '.'; +#endif + return hr; +} +HRESULT STDMETHODCALLTYPE D3D11Device::OpenSharedResource(HANDLE hResource, REFIID ReturnedInterface, void **ppResource) +{ + return _orig->OpenSharedResource(hResource, ReturnedInterface, ppResource); +} +HRESULT STDMETHODCALLTYPE D3D11Device::CheckFormatSupport(DXGI_FORMAT Format, UINT *pFormatSupport) +{ + return _orig->CheckFormatSupport(Format, pFormatSupport); +} +HRESULT STDMETHODCALLTYPE D3D11Device::CheckMultisampleQualityLevels(DXGI_FORMAT Format, UINT SampleCount, UINT *pNumQualityLevels) +{ + return _orig->CheckMultisampleQualityLevels(Format, SampleCount, pNumQualityLevels); +} +void STDMETHODCALLTYPE D3D11Device::CheckCounterInfo(D3D11_COUNTER_INFO *pCounterInfo) +{ + _orig->CheckCounterInfo(pCounterInfo); +} +HRESULT STDMETHODCALLTYPE D3D11Device::CheckCounter(const D3D11_COUNTER_DESC *pDesc, D3D11_COUNTER_TYPE *pType, UINT *pActiveCounters, LPSTR szName, UINT *pNameLength, LPSTR szUnits, UINT *pUnitsLength, LPSTR szDescription, UINT *pDescriptionLength) +{ + return _orig->CheckCounter(pDesc, pType, pActiveCounters, szName, pNameLength, szUnits, pUnitsLength, szDescription, pDescriptionLength); +} +HRESULT STDMETHODCALLTYPE D3D11Device::CheckFeatureSupport(D3D11_FEATURE Feature, void *pFeatureSupportData, UINT FeatureSupportDataSize) +{ + return _orig->CheckFeatureSupport(Feature, pFeatureSupportData, FeatureSupportDataSize); +} +HRESULT STDMETHODCALLTYPE D3D11Device::GetPrivateData(REFGUID guid, UINT *pDataSize, void *pData) +{ + return _orig->GetPrivateData(guid, pDataSize, pData); +} +HRESULT STDMETHODCALLTYPE D3D11Device::SetPrivateData(REFGUID guid, UINT DataSize, const void *pData) +{ + return _orig->SetPrivateData(guid, DataSize, pData); +} +HRESULT STDMETHODCALLTYPE D3D11Device::SetPrivateDataInterface(REFGUID guid, const IUnknown *pData) +{ + return _orig->SetPrivateDataInterface(guid, pData); +} +UINT STDMETHODCALLTYPE D3D11Device::GetCreationFlags() +{ + return _orig->GetCreationFlags(); +} +HRESULT STDMETHODCALLTYPE D3D11Device::GetDeviceRemovedReason() +{ + return _orig->GetDeviceRemovedReason(); +} +void STDMETHODCALLTYPE D3D11Device::GetImmediateContext(ID3D11DeviceContext **ppImmediateContext) +{ + if (ppImmediateContext == nullptr) + return; + + _immediate_context->AddRef(); + *ppImmediateContext = _immediate_context; +} +HRESULT STDMETHODCALLTYPE D3D11Device::SetExceptionMode(UINT RaiseFlags) +{ + return _orig->SetExceptionMode(RaiseFlags); +} +UINT STDMETHODCALLTYPE D3D11Device::GetExceptionMode() +{ + return _orig->GetExceptionMode(); +} +D3D_FEATURE_LEVEL STDMETHODCALLTYPE D3D11Device::GetFeatureLevel() +{ + return _orig->GetFeatureLevel(); +} + +void STDMETHODCALLTYPE D3D11Device::GetImmediateContext1(ID3D11DeviceContext1 **ppImmediateContext) +{ + if (ppImmediateContext == nullptr) + return; + + assert(_interface_version >= 1); + assert(_immediate_context->_interface_version >= 1); + + _immediate_context->AddRef(); + *ppImmediateContext = _immediate_context; +} +HRESULT STDMETHODCALLTYPE D3D11Device::CreateDeferredContext1(UINT ContextFlags, ID3D11DeviceContext1 **ppDeferredContext) +{ + LOG(INFO) << "Redirecting ID3D11Device1::CreateDeferredContext1" << '(' << this << ", " << ContextFlags << ", " << ppDeferredContext << ')' << " ..."; + + if (ppDeferredContext == nullptr) + return E_INVALIDARG; + + assert(_interface_version >= 1); + + const HRESULT hr = static_cast(_orig)->CreateDeferredContext1(ContextFlags, ppDeferredContext); + + if (FAILED(hr)) + { + LOG(WARN) << "> ID3D11Device1::CreateDeferredContext1 failed with error code " << std::hex << hr << std::dec << '!'; + return hr; + } + + *ppDeferredContext = new D3D11DeviceContext(this, *ppDeferredContext); + +#if RESHADE_VERBOSE_LOG + LOG(DEBUG) << "Returning ID3D11DeviceContext1 object " << *ppDeferredContext << '.'; +#endif + return hr; +} +HRESULT STDMETHODCALLTYPE D3D11Device::CreateBlendState1(const D3D11_BLEND_DESC1 *pBlendStateDesc, ID3D11BlendState1 **ppBlendState) +{ + assert(_interface_version >= 1); + return static_cast(_orig)->CreateBlendState1(pBlendStateDesc, ppBlendState); +} +HRESULT STDMETHODCALLTYPE D3D11Device::CreateRasterizerState1(const D3D11_RASTERIZER_DESC1 *pRasterizerDesc, ID3D11RasterizerState1 **ppRasterizerState) +{ + assert(_interface_version >= 1); + return static_cast(_orig)->CreateRasterizerState1(pRasterizerDesc, ppRasterizerState); +} +HRESULT STDMETHODCALLTYPE D3D11Device::CreateDeviceContextState(UINT Flags, const D3D_FEATURE_LEVEL *pFeatureLevels, UINT FeatureLevels, UINT SDKVersion, REFIID EmulatedInterface, D3D_FEATURE_LEVEL *pChosenFeatureLevel, ID3DDeviceContextState **ppContextState) +{ + assert(_interface_version >= 1); + return static_cast(_orig)->CreateDeviceContextState(Flags, pFeatureLevels, FeatureLevels, SDKVersion, EmulatedInterface, pChosenFeatureLevel, ppContextState); +} +HRESULT STDMETHODCALLTYPE D3D11Device::OpenSharedResource1(HANDLE hResource, REFIID returnedInterface, void **ppResource) +{ + assert(_interface_version >= 1); + return static_cast(_orig)->OpenSharedResource1(hResource, returnedInterface, ppResource); +} +HRESULT STDMETHODCALLTYPE D3D11Device::OpenSharedResourceByName(LPCWSTR lpName, DWORD dwDesiredAccess, REFIID returnedInterface, void **ppResource) +{ + assert(_interface_version >= 1); + return static_cast(_orig)->OpenSharedResourceByName(lpName, dwDesiredAccess, returnedInterface, ppResource); +} + + void STDMETHODCALLTYPE D3D11Device::GetImmediateContext2(ID3D11DeviceContext2 **ppImmediateContext) +{ + assert(_interface_version >= 2); + static_cast(_orig)->GetImmediateContext2(ppImmediateContext); +} +HRESULT STDMETHODCALLTYPE D3D11Device::CreateDeferredContext2(UINT ContextFlags, ID3D11DeviceContext2 **ppDeferredContext) +{ + assert(_interface_version >= 2); + return static_cast(_orig)->CreateDeferredContext2(ContextFlags, ppDeferredContext); +} +void STDMETHODCALLTYPE D3D11Device::GetResourceTiling(ID3D11Resource *pTiledResource, UINT *pNumTilesForEntireResource, D3D11_PACKED_MIP_DESC *pPackedMipDesc, D3D11_TILE_SHAPE *pStandardTileShapeForNonPackedMips, UINT *pNumSubresourceTilings, UINT FirstSubresourceTilingToGet, D3D11_SUBRESOURCE_TILING *pSubresourceTilingsForNonPackedMips) +{ + assert(_interface_version >= 2); + static_cast(_orig)->GetResourceTiling(pTiledResource, pNumTilesForEntireResource, pPackedMipDesc, pStandardTileShapeForNonPackedMips, pNumSubresourceTilings, FirstSubresourceTilingToGet, pSubresourceTilingsForNonPackedMips); +} +HRESULT STDMETHODCALLTYPE D3D11Device::CheckMultisampleQualityLevels1(DXGI_FORMAT Format, UINT SampleCount, UINT Flags, UINT *pNumQualityLevels) +{ + assert(_interface_version >= 2); + return static_cast(_orig)->CheckMultisampleQualityLevels1(Format, SampleCount, Flags, pNumQualityLevels); +} + +HRESULT STDMETHODCALLTYPE D3D11Device::CreateTexture2D1(const D3D11_TEXTURE2D_DESC1 *pDesc1, const D3D11_SUBRESOURCE_DATA *pInitialData, ID3D11Texture2D1 **ppTexture2D) +{ + assert(_interface_version >= 3); + return static_cast(_orig)->CreateTexture2D1(pDesc1, pInitialData, ppTexture2D); +} +HRESULT STDMETHODCALLTYPE D3D11Device::CreateTexture3D1(const D3D11_TEXTURE3D_DESC1 *pDesc1, const D3D11_SUBRESOURCE_DATA *pInitialData, ID3D11Texture3D1 **ppTexture3D) +{ + assert(_interface_version >= 3); + return static_cast(_orig)->CreateTexture3D1(pDesc1, pInitialData, ppTexture3D); +} +HRESULT STDMETHODCALLTYPE D3D11Device::CreateRasterizerState2(const D3D11_RASTERIZER_DESC2 *pRasterizerDesc, ID3D11RasterizerState2 **ppRasterizerState) +{ + assert(_interface_version >= 3); + return static_cast(_orig)->CreateRasterizerState2(pRasterizerDesc, ppRasterizerState); +} +HRESULT STDMETHODCALLTYPE D3D11Device::CreateShaderResourceView1(ID3D11Resource *pResource, const D3D11_SHADER_RESOURCE_VIEW_DESC1 *pDesc1, ID3D11ShaderResourceView1 **ppSRView1) +{ + assert(_interface_version >= 3); + return static_cast(_orig)->CreateShaderResourceView1(pResource, pDesc1, ppSRView1); +} +HRESULT STDMETHODCALLTYPE D3D11Device::CreateUnorderedAccessView1(ID3D11Resource *pResource, const D3D11_UNORDERED_ACCESS_VIEW_DESC1 *pDesc1, ID3D11UnorderedAccessView1 **ppUAView1) +{ + assert(_interface_version >= 3); + return static_cast(_orig)->CreateUnorderedAccessView1(pResource, pDesc1, ppUAView1); +} +HRESULT STDMETHODCALLTYPE D3D11Device::CreateRenderTargetView1(ID3D11Resource *pResource, const D3D11_RENDER_TARGET_VIEW_DESC1 *pDesc1, ID3D11RenderTargetView1 **ppRTView1) +{ + assert(_interface_version >= 3); + return static_cast(_orig)->CreateRenderTargetView1(pResource, pDesc1, ppRTView1); +} +HRESULT STDMETHODCALLTYPE D3D11Device::CreateQuery1(const D3D11_QUERY_DESC1 *pQueryDesc1, ID3D11Query1 **ppQuery1) +{ + assert(_interface_version >= 3); + return static_cast(_orig)->CreateQuery1(pQueryDesc1, ppQuery1); +} +void STDMETHODCALLTYPE D3D11Device::GetImmediateContext3(ID3D11DeviceContext3 **ppImmediateContext) +{ + assert(_interface_version >= 3); + static_cast(_orig)->GetImmediateContext3(ppImmediateContext); +} +HRESULT STDMETHODCALLTYPE D3D11Device::CreateDeferredContext3(UINT ContextFlags, ID3D11DeviceContext3 **ppDeferredContext) +{ + assert(_interface_version >= 3); + return static_cast(_orig)->CreateDeferredContext3(ContextFlags, ppDeferredContext); +} +void STDMETHODCALLTYPE D3D11Device::WriteToSubresource(ID3D11Resource *pDstResource, UINT DstSubresource, const D3D11_BOX *pDstBox, const void *pSrcData, UINT SrcRowPitch, UINT SrcDepthPitch) +{ + assert(_interface_version >= 3); + static_cast(_orig)->WriteToSubresource(pDstResource, DstSubresource, pDstBox, pSrcData, SrcRowPitch, SrcDepthPitch); +} +void STDMETHODCALLTYPE D3D11Device::ReadFromSubresource(void *pDstData, UINT DstRowPitch, UINT DstDepthPitch, ID3D11Resource *pSrcResource, UINT SrcSubresource, const D3D11_BOX *pSrcBox) +{ + assert(_interface_version >= 3); + + static_cast(_orig)->ReadFromSubresource(pDstData, DstRowPitch, DstDepthPitch, pSrcResource, SrcSubresource, pSrcBox); +} + +HRESULT STDMETHODCALLTYPE D3D11Device::RegisterDeviceRemovedEvent(HANDLE hEvent, DWORD *pdwCookie) +{ + assert(_interface_version >= 4); + return static_cast(_orig)->RegisterDeviceRemovedEvent(hEvent, pdwCookie); +} +void STDMETHODCALLTYPE D3D11Device::UnregisterDeviceRemoved(DWORD dwCookie) +{ + assert(_interface_version >= 4); + static_cast(_orig)->UnregisterDeviceRemoved(dwCookie); +} + +HRESULT STDMETHODCALLTYPE D3D11Device::OpenSharedFence(HANDLE hFence, REFIID ReturnedInterface, void **ppFence) +{ + assert(_interface_version >= 5); + return static_cast(_orig)->OpenSharedFence(hFence, ReturnedInterface, ppFence); +} +HRESULT STDMETHODCALLTYPE D3D11Device::CreateFence(UINT64 InitialValue, D3D11_FENCE_FLAG Flags, REFIID ReturnedInterface, void **ppFence) +{ + assert(_interface_version >= 5); + return static_cast(_orig)->CreateFence(InitialValue, Flags, ReturnedInterface, ppFence); +} diff --git a/msvc/source/d3d11/d3d11_device.hpp b/msvc/source/d3d11/d3d11_device.hpp new file mode 100644 index 0000000..3285648 --- /dev/null +++ b/msvc/source/d3d11/d3d11_device.hpp @@ -0,0 +1,122 @@ +/** + * Copyright (C) 2014 Patrick Mours. All rights reserved. + * License: https://github.com/crosire/reshade#license + */ + +#pragma once + +#include +#include "draw_call_tracker.hpp" +#include + +struct DXGIDevice; +struct D3D11DeviceContext; +namespace reshade::d3d11 { class runtime_d3d11; } + +struct __declspec(uuid("72299288-2C68-4AD8-945D-2BFB5AA9C609")) D3D11Device : ID3D11Device5 +{ + D3D11Device(IDXGIDevice1 *dxgi_device, ID3D11Device *original, ID3D11DeviceContext *immediate_context); + + D3D11Device(const D3D11Device &) = delete; + D3D11Device &operator=(const D3D11Device &) = delete; + + HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void **ppvObj) override; + ULONG STDMETHODCALLTYPE AddRef() override; + ULONG STDMETHODCALLTYPE Release() override; + + #pragma region ID3D11Device + HRESULT STDMETHODCALLTYPE CreateBuffer(const D3D11_BUFFER_DESC *pDesc, const D3D11_SUBRESOURCE_DATA *pInitialData, ID3D11Buffer **ppBuffer) override; + HRESULT STDMETHODCALLTYPE CreateTexture1D(const D3D11_TEXTURE1D_DESC *pDesc, const D3D11_SUBRESOURCE_DATA *pInitialData, ID3D11Texture1D **ppTexture1D) override; + HRESULT STDMETHODCALLTYPE CreateTexture2D(const D3D11_TEXTURE2D_DESC *pDesc, const D3D11_SUBRESOURCE_DATA *pInitialData, ID3D11Texture2D **ppTexture2D) override; + HRESULT STDMETHODCALLTYPE CreateTexture3D(const D3D11_TEXTURE3D_DESC *pDesc, const D3D11_SUBRESOURCE_DATA *pInitialData, ID3D11Texture3D **ppTexture3D) override; + HRESULT STDMETHODCALLTYPE CreateShaderResourceView(ID3D11Resource *pResource, const D3D11_SHADER_RESOURCE_VIEW_DESC *pDesc, ID3D11ShaderResourceView **ppSRView) override; + HRESULT STDMETHODCALLTYPE CreateUnorderedAccessView(ID3D11Resource *pResource, const D3D11_UNORDERED_ACCESS_VIEW_DESC *pDesc, ID3D11UnorderedAccessView **ppUAView) override; + HRESULT STDMETHODCALLTYPE CreateRenderTargetView(ID3D11Resource *pResource, const D3D11_RENDER_TARGET_VIEW_DESC *pDesc, ID3D11RenderTargetView **ppRTView) override; + HRESULT STDMETHODCALLTYPE CreateDepthStencilView(ID3D11Resource *pResource, const D3D11_DEPTH_STENCIL_VIEW_DESC *pDesc, ID3D11DepthStencilView **ppDepthStencilView) override; + HRESULT STDMETHODCALLTYPE CreateInputLayout(const D3D11_INPUT_ELEMENT_DESC *pInputElementDescs, UINT NumElements, const void *pShaderBytecodeWithInputSignature, SIZE_T BytecodeLength, ID3D11InputLayout **ppInputLayout) override; + HRESULT STDMETHODCALLTYPE CreateVertexShader(const void *pShaderBytecode, SIZE_T BytecodeLength, ID3D11ClassLinkage *pClassLinkage, ID3D11VertexShader **ppVertexShader) override; + HRESULT STDMETHODCALLTYPE CreateGeometryShader(const void *pShaderBytecode, SIZE_T BytecodeLength, ID3D11ClassLinkage *pClassLinkage, ID3D11GeometryShader **ppGeometryShader) override; + HRESULT STDMETHODCALLTYPE CreateGeometryShaderWithStreamOutput(const void *pShaderBytecode, SIZE_T BytecodeLength, const D3D11_SO_DECLARATION_ENTRY *pSODeclaration, UINT NumEntries, const UINT *pBufferStrides, UINT NumStrides, UINT RasterizedStream, ID3D11ClassLinkage *pClassLinkage, ID3D11GeometryShader **ppGeometryShader) override; + HRESULT STDMETHODCALLTYPE CreatePixelShader(const void *pShaderBytecode, SIZE_T BytecodeLength, ID3D11ClassLinkage *pClassLinkage, ID3D11PixelShader **ppPixelShader) override; + HRESULT STDMETHODCALLTYPE CreateHullShader(const void *pShaderBytecode, SIZE_T BytecodeLength, ID3D11ClassLinkage *pClassLinkage, ID3D11HullShader **ppHullShader) override; + HRESULT STDMETHODCALLTYPE CreateDomainShader(const void *pShaderBytecode, SIZE_T BytecodeLength, ID3D11ClassLinkage *pClassLinkage, ID3D11DomainShader **ppDomainShader) override; + HRESULT STDMETHODCALLTYPE CreateComputeShader(const void *pShaderBytecode, SIZE_T BytecodeLength, ID3D11ClassLinkage *pClassLinkage, ID3D11ComputeShader **ppComputeShader) override; + HRESULT STDMETHODCALLTYPE CreateClassLinkage(ID3D11ClassLinkage **ppLinkage) override; + HRESULT STDMETHODCALLTYPE CreateBlendState(const D3D11_BLEND_DESC *pBlendStateDesc, ID3D11BlendState **ppBlendState) override; + HRESULT STDMETHODCALLTYPE CreateDepthStencilState(const D3D11_DEPTH_STENCIL_DESC *pDepthStencilDesc, ID3D11DepthStencilState **ppDepthStencilState) override; + HRESULT STDMETHODCALLTYPE CreateRasterizerState(const D3D11_RASTERIZER_DESC *pRasterizerDesc, ID3D11RasterizerState **ppRasterizerState) override; + HRESULT STDMETHODCALLTYPE CreateSamplerState(const D3D11_SAMPLER_DESC *pSamplerDesc, ID3D11SamplerState **ppSamplerState) override; + HRESULT STDMETHODCALLTYPE CreateQuery(const D3D11_QUERY_DESC *pQueryDesc, ID3D11Query **ppQuery) override; + HRESULT STDMETHODCALLTYPE CreatePredicate(const D3D11_QUERY_DESC *pPredicateDesc, ID3D11Predicate **ppPredicate) override; + HRESULT STDMETHODCALLTYPE CreateCounter(const D3D11_COUNTER_DESC *pCounterDesc, ID3D11Counter **ppCounter) override; + HRESULT STDMETHODCALLTYPE CreateDeferredContext(UINT ContextFlags, ID3D11DeviceContext **ppDeferredContext) override; + HRESULT STDMETHODCALLTYPE OpenSharedResource(HANDLE hResource, REFIID ReturnedInterface, void **ppResource) override; + HRESULT STDMETHODCALLTYPE CheckFormatSupport(DXGI_FORMAT Format, UINT *pFormatSupport) override; + HRESULT STDMETHODCALLTYPE CheckMultisampleQualityLevels(DXGI_FORMAT Format, UINT SampleCount, UINT *pNumQualityLevels) override; + void STDMETHODCALLTYPE CheckCounterInfo(D3D11_COUNTER_INFO *pCounterInfo) override; + HRESULT STDMETHODCALLTYPE CheckCounter(const D3D11_COUNTER_DESC *pDesc, D3D11_COUNTER_TYPE *pType, UINT *pActiveCounters, LPSTR szName, UINT *pNameLength, LPSTR szUnits, UINT *pUnitsLength, LPSTR szDescription, UINT *pDescriptionLength) override; + HRESULT STDMETHODCALLTYPE CheckFeatureSupport(D3D11_FEATURE Feature, void *pFeatureSupportData, UINT FeatureSupportDataSize) override; + HRESULT STDMETHODCALLTYPE GetPrivateData(REFGUID guid, UINT *pDataSize, void *pData) override; + HRESULT STDMETHODCALLTYPE SetPrivateData(REFGUID guid, UINT DataSize, const void *pData) override; + HRESULT STDMETHODCALLTYPE SetPrivateDataInterface(REFGUID guid, const IUnknown *pData) override; + UINT STDMETHODCALLTYPE GetCreationFlags() override; + HRESULT STDMETHODCALLTYPE GetDeviceRemovedReason() override; + void STDMETHODCALLTYPE GetImmediateContext(ID3D11DeviceContext **ppImmediateContext) override; + HRESULT STDMETHODCALLTYPE SetExceptionMode(UINT RaiseFlags) override; + UINT STDMETHODCALLTYPE GetExceptionMode() override; + D3D_FEATURE_LEVEL STDMETHODCALLTYPE GetFeatureLevel() override; + #pragma endregion + #pragma region ID3D11Device1 + void STDMETHODCALLTYPE GetImmediateContext1(ID3D11DeviceContext1 **ppImmediateContext) override; + HRESULT STDMETHODCALLTYPE CreateDeferredContext1(UINT ContextFlags, ID3D11DeviceContext1 **ppDeferredContext) override; + HRESULT STDMETHODCALLTYPE CreateBlendState1(const D3D11_BLEND_DESC1 *pBlendStateDesc, ID3D11BlendState1 **ppBlendState); + HRESULT STDMETHODCALLTYPE CreateRasterizerState1(const D3D11_RASTERIZER_DESC1 *pRasterizerDesc, ID3D11RasterizerState1 **ppRasterizerState) override; + HRESULT STDMETHODCALLTYPE CreateDeviceContextState(UINT Flags, const D3D_FEATURE_LEVEL *pFeatureLevels, UINT FeatureLevels, UINT SDKVersion, REFIID EmulatedInterface, D3D_FEATURE_LEVEL *pChosenFeatureLevel, ID3DDeviceContextState **ppContextState) override; + HRESULT STDMETHODCALLTYPE OpenSharedResource1(HANDLE hResource, REFIID returnedInterface, void **ppResource) override; + HRESULT STDMETHODCALLTYPE OpenSharedResourceByName(LPCWSTR lpName, DWORD dwDesiredAccess, REFIID returnedInterface, void **ppResource) override; + #pragma endregion + #pragma region ID3D11Device2 + void STDMETHODCALLTYPE GetImmediateContext2(ID3D11DeviceContext2 **ppImmediateContext) override; + HRESULT STDMETHODCALLTYPE CreateDeferredContext2(UINT ContextFlags, ID3D11DeviceContext2 **ppDeferredContext) override; + void STDMETHODCALLTYPE GetResourceTiling(ID3D11Resource *pTiledResource, UINT *pNumTilesForEntireResource, D3D11_PACKED_MIP_DESC *pPackedMipDesc, D3D11_TILE_SHAPE *pStandardTileShapeForNonPackedMips, UINT *pNumSubresourceTilings, UINT FirstSubresourceTilingToGet, D3D11_SUBRESOURCE_TILING *pSubresourceTilingsForNonPackedMips) override; + HRESULT STDMETHODCALLTYPE CheckMultisampleQualityLevels1(DXGI_FORMAT Format, UINT SampleCount, UINT Flags, UINT *pNumQualityLevels) override; + #pragma endregion + #pragma region ID3D11Device3 + HRESULT STDMETHODCALLTYPE CreateTexture2D1(const D3D11_TEXTURE2D_DESC1 *pDesc1, const D3D11_SUBRESOURCE_DATA *pInitialData, ID3D11Texture2D1 **ppTexture2D) override; + HRESULT STDMETHODCALLTYPE CreateTexture3D1(const D3D11_TEXTURE3D_DESC1 *pDesc1, const D3D11_SUBRESOURCE_DATA *pInitialData, ID3D11Texture3D1 **ppTexture3D) override; + HRESULT STDMETHODCALLTYPE CreateRasterizerState2(const D3D11_RASTERIZER_DESC2 *pRasterizerDesc, ID3D11RasterizerState2 **ppRasterizerState) override; + HRESULT STDMETHODCALLTYPE CreateShaderResourceView1(ID3D11Resource *pResource, const D3D11_SHADER_RESOURCE_VIEW_DESC1 *pDesc1, ID3D11ShaderResourceView1 **ppSRView1) override; + HRESULT STDMETHODCALLTYPE CreateUnorderedAccessView1(ID3D11Resource *pResource, const D3D11_UNORDERED_ACCESS_VIEW_DESC1 *pDesc1, ID3D11UnorderedAccessView1 **ppUAView1) override; + HRESULT STDMETHODCALLTYPE CreateRenderTargetView1(ID3D11Resource *pResource, const D3D11_RENDER_TARGET_VIEW_DESC1 *pDesc1, ID3D11RenderTargetView1 **ppRTView1) override; + HRESULT STDMETHODCALLTYPE CreateQuery1(const D3D11_QUERY_DESC1 *pQueryDesc1, ID3D11Query1 **ppQuery1) override; + void STDMETHODCALLTYPE GetImmediateContext3(ID3D11DeviceContext3 **ppImmediateContext) override; + HRESULT STDMETHODCALLTYPE CreateDeferredContext3(UINT ContextFlags, ID3D11DeviceContext3 **ppDeferredContext) override; + void STDMETHODCALLTYPE WriteToSubresource(ID3D11Resource *pDstResource, UINT DstSubresource, const D3D11_BOX *pDstBox, const void *pSrcData, UINT SrcRowPitch, UINT SrcDepthPitch) override; + void STDMETHODCALLTYPE ReadFromSubresource(void *pDstData, UINT DstRowPitch, UINT DstDepthPitch, ID3D11Resource *pSrcResource, UINT SrcSubresource, const D3D11_BOX *pSrcBox) override; + #pragma endregion + #pragma region ID3D11Device4 + HRESULT STDMETHODCALLTYPE RegisterDeviceRemovedEvent( HANDLE hEvent, DWORD *pdwCookie) override; + void STDMETHODCALLTYPE UnregisterDeviceRemoved(DWORD dwCookie) override; + #pragma endregion + #pragma region ID3D11Device5 + HRESULT STDMETHODCALLTYPE OpenSharedFence(HANDLE hFence, REFIID ReturnedInterface, void **ppFence) override; + HRESULT STDMETHODCALLTYPE CreateFence(UINT64 InitialValue, D3D11_FENCE_FLAG Flags, REFIID ReturnedInterface, void **ppFence) override; + #pragma endregion + + void add_commandlist_trackers(ID3D11CommandList* command_list, const reshade::d3d11::draw_call_tracker &tracker_source); + void merge_commandlist_trackers(ID3D11CommandList* command_list, reshade::d3d11::draw_call_tracker &tracker_destination); + + void clear_drawcall_stats(); + + bool check_and_upgrade_interface(REFIID riid); + + LONG _ref = 1; + ID3D11Device *_orig; + unsigned int _interface_version; + DXGIDevice *const _dxgi_device; + D3D11DeviceContext *const _immediate_context; + std::vector> _runtimes; + std::unordered_map _trackers_per_commandlist; + std::mutex _trackers_per_commandlist_mutex; + unsigned int _clear_DSV_iter = 1; +}; diff --git a/msvc/source/d3d11/d3d11_device_context.cpp b/msvc/source/d3d11/d3d11_device_context.cpp new file mode 100644 index 0000000..a02b341 --- /dev/null +++ b/msvc/source/d3d11/d3d11_device_context.cpp @@ -0,0 +1,857 @@ +/** + * Copyright (C) 2014 Patrick Mours. All rights reserved. + * License: https://github.com/crosire/reshade#license + */ + +#include "log.hpp" +#include "d3d11_device.hpp" +#include "d3d11_device_context.hpp" +#include "runtime_d3d11.hpp" + +D3D11DeviceContext::D3D11DeviceContext(D3D11Device *device, ID3D11DeviceContext *original) : + _orig(original), + _interface_version(0), + _device(device) { + assert(original != nullptr); +} +D3D11DeviceContext::D3D11DeviceContext(D3D11Device *device, ID3D11DeviceContext1 *original) : + _orig(original), + _interface_version(1), + _device(device) { + assert(original != nullptr); +} +D3D11DeviceContext::D3D11DeviceContext(D3D11Device *device, ID3D11DeviceContext2 *original) : + _orig(original), + _interface_version(2), + _device(device) { + assert(original != nullptr); +} +D3D11DeviceContext::D3D11DeviceContext(D3D11Device *device, ID3D11DeviceContext3 *original) : + _orig(original), + _interface_version(3), + _device(device) { + assert(original != nullptr); +} + +void D3D11DeviceContext::clear_drawcall_stats() +{ + _draw_call_tracker.reset(); +} + +#if RESHADE_DX11_CAPTURE_DEPTH_BUFFERS +bool D3D11DeviceContext::save_depth_texture(ID3D11DepthStencilView *pDepthStencilView, bool cleared) +{ + if (_device->_runtimes.empty()) + return false; + + const auto runtime = _device->_runtimes.front(); + + if (!runtime->depth_buffer_before_clear) + return false; + if (!cleared && !runtime->extended_depth_buffer_detection) + return false; + + assert(pDepthStencilView != nullptr); + + // Retrieve texture from depth stencil + com_ptr resource; + pDepthStencilView->GetResource(&resource); + + com_ptr texture; + if (FAILED(resource->QueryInterface(&texture))) + return false; + + D3D11_TEXTURE2D_DESC desc; + texture->GetDesc(&desc); + + // Check if aspect ratio is similar to the back buffer one + const float screen_aspect_ratio = float(runtime->frame_width()) / float(runtime->frame_height()); + const float texture_aspect_ratio = float(desc.Width) / float(desc.Height); + + if (fabs(texture_aspect_ratio - screen_aspect_ratio) > 0.1f || desc.Width > runtime->frame_width()) + return false; + + // In case the depth texture is retrieved, we make a copy of it and store it in an ordered map to use it later in the final rendering stage. + if ((runtime->cleared_depth_buffer_index == 0 && cleared) || (_device->_clear_DSV_iter <= runtime->cleared_depth_buffer_index)) + { + // Select an appropriate destination texture + com_ptr depth_texture_save = runtime->select_depth_texture_save(desc); + if (depth_texture_save == nullptr) + return false; + + // Copy the depth texture. This is necessary because the content of the depth texture is cleared. + // This way, we can retrieve this content in the final rendering stage + this->CopyResource(depth_texture_save.get(), texture.get()); + + // Store the saved texture in the ordered map. + _draw_call_tracker.track_depth_texture(runtime->depth_buffer_texture_format, _device->_clear_DSV_iter, texture.get(), pDepthStencilView, depth_texture_save, cleared); + } + else + { + // Store a null depth texture in the ordered map in order to display it even if the user chose a previous cleared texture. + // This way the texture will still be visible in the depth buffer selection window and the user can choose it. + _draw_call_tracker.track_depth_texture(runtime->depth_buffer_texture_format, _device->_clear_DSV_iter, texture.get(), pDepthStencilView, nullptr, cleared); + } + + // TODO: This is unsafe if multiple device contexts are used on multiple threads + _device->_clear_DSV_iter++; + + return true; +} + +void D3D11DeviceContext::track_active_rendertargets(UINT NumViews, ID3D11RenderTargetView *const *ppRenderTargetViews, ID3D11DepthStencilView *pDepthStencilView) +{ + if (pDepthStencilView == nullptr || _device->_runtimes.empty()) + return; + + const auto runtime = _device->_runtimes.front(); + + _draw_call_tracker.track_rendertargets(runtime->depth_buffer_texture_format, pDepthStencilView, NumViews, ppRenderTargetViews); + + save_depth_texture(pDepthStencilView, false); +} +void D3D11DeviceContext::track_cleared_depthstencil(ID3D11DepthStencilView *pDepthStencilView) +{ + if (pDepthStencilView == nullptr) + return; + + save_depth_texture(pDepthStencilView, true); +} +#endif + +bool D3D11DeviceContext::check_and_upgrade_interface(REFIID riid) +{ + static const IID iid_lookup[] = { + __uuidof(ID3D11DeviceContext), + __uuidof(ID3D11DeviceContext1), + __uuidof(ID3D11DeviceContext2), + __uuidof(ID3D11DeviceContext3), + __uuidof(ID3D11DeviceContext4), + }; + + for (unsigned int new_version = _interface_version + 1; new_version < ARRAYSIZE(iid_lookup); ++new_version) + { + if (riid == iid_lookup[new_version]) + { + IUnknown *new_interface = nullptr; + if (FAILED(_orig->QueryInterface(riid, reinterpret_cast(&new_interface)))) + return false; +#if RESHADE_VERBOSE_LOG + LOG(DEBUG) << "Upgraded ID3D11DeviceContext" << _interface_version << " object " << this << " to ID3D11DeviceContext" << new_version << '.'; +#endif + _orig->Release(); + _orig = static_cast(new_interface); + _interface_version = new_version; + break; + } + } + + return true; +} + +HRESULT STDMETHODCALLTYPE D3D11DeviceContext::QueryInterface(REFIID riid, void **ppvObj) +{ + if (ppvObj == nullptr) + return E_POINTER; + + if (riid == __uuidof(this) || + riid == __uuidof(IUnknown) || + riid == __uuidof(ID3D11DeviceChild) || + riid == __uuidof(ID3D11DeviceContext) || + riid == __uuidof(ID3D11DeviceContext1) || + riid == __uuidof(ID3D11DeviceContext2) || + riid == __uuidof(ID3D11DeviceContext3) || + riid == __uuidof(ID3D11DeviceContext4)) + { + if (!check_and_upgrade_interface(riid)) + return E_NOINTERFACE; + + AddRef(); + *ppvObj = this; + return S_OK; + } + + return _orig->QueryInterface(riid, ppvObj); +} +ULONG STDMETHODCALLTYPE D3D11DeviceContext::AddRef() +{ + ++_ref; + + return _orig->AddRef(); +} +ULONG STDMETHODCALLTYPE D3D11DeviceContext::Release() +{ + --_ref; + + const ULONG ref = _orig->Release(); + + if (ref != 0 && _ref != 0) + return ref; + else if (ref != 0) + LOG(WARN) << "Reference count for ID3D11DeviceContext" << _interface_version << " object " << this << " is inconsistent: " << ref << ", but expected 0."; + + assert(_ref <= 0); +#if RESHADE_VERBOSE_LOG + LOG(DEBUG) << "Destroyed ID3D11DeviceContext" << _interface_version << " object " << this << '.'; +#endif + delete this; + return 0; +} + +void STDMETHODCALLTYPE D3D11DeviceContext::GetDevice(ID3D11Device **ppDevice) +{ + if (ppDevice == nullptr) + return; + + _device->AddRef(); + *ppDevice = _device; +} +HRESULT STDMETHODCALLTYPE D3D11DeviceContext::GetPrivateData(REFGUID guid, UINT *pDataSize, void *pData) +{ + return _orig->GetPrivateData(guid, pDataSize, pData); +} +HRESULT STDMETHODCALLTYPE D3D11DeviceContext::SetPrivateData(REFGUID guid, UINT DataSize, const void *pData) +{ + return _orig->SetPrivateData(guid, DataSize, pData); +} +HRESULT STDMETHODCALLTYPE D3D11DeviceContext::SetPrivateDataInterface(REFGUID guid, const IUnknown *pData) +{ + return _orig->SetPrivateDataInterface(guid, pData); +} + +void STDMETHODCALLTYPE D3D11DeviceContext::VSSetConstantBuffers(UINT StartSlot, UINT NumBuffers, ID3D11Buffer *const *ppConstantBuffers) +{ + _orig->VSSetConstantBuffers(StartSlot, NumBuffers, ppConstantBuffers); +} +void STDMETHODCALLTYPE D3D11DeviceContext::PSSetShaderResources(UINT StartSlot, UINT NumViews, ID3D11ShaderResourceView *const *ppShaderResourceViews) +{ + _orig->PSSetShaderResources(StartSlot, NumViews, ppShaderResourceViews); +} +void STDMETHODCALLTYPE D3D11DeviceContext::PSSetShader(ID3D11PixelShader *pPixelShader, ID3D11ClassInstance *const *ppClassInstances, UINT NumClassInstances) +{ + _orig->PSSetShader(pPixelShader, ppClassInstances, NumClassInstances); +} +void STDMETHODCALLTYPE D3D11DeviceContext::PSSetSamplers(UINT StartSlot, UINT NumSamplers, ID3D11SamplerState *const *ppSamplers) +{ + _orig->PSSetSamplers(StartSlot, NumSamplers, ppSamplers); +} +void STDMETHODCALLTYPE D3D11DeviceContext::VSSetShader(ID3D11VertexShader *pVertexShader, ID3D11ClassInstance *const *ppClassInstances, UINT NumClassInstances) +{ + _orig->VSSetShader(pVertexShader, ppClassInstances, NumClassInstances); +} +void STDMETHODCALLTYPE D3D11DeviceContext::DrawIndexed(UINT IndexCount, UINT StartIndexLocation, INT BaseVertexLocation) +{ + _orig->DrawIndexed(IndexCount, StartIndexLocation, BaseVertexLocation); + _draw_call_tracker.on_draw(this, IndexCount); +} +void STDMETHODCALLTYPE D3D11DeviceContext::Draw(UINT VertexCount, UINT StartVertexLocation) +{ + _orig->Draw(VertexCount, StartVertexLocation); + _draw_call_tracker.on_draw(this, VertexCount); +} +HRESULT STDMETHODCALLTYPE D3D11DeviceContext::Map(ID3D11Resource *pResource, UINT Subresource, D3D11_MAP MapType, UINT MapFlags, D3D11_MAPPED_SUBRESOURCE *pMappedResource) +{ +#if RESHADE_DX11_CAPTURE_CONSTANT_BUFFERS + _draw_call_tracker.on_map(pResource); +#endif + return _orig->Map(pResource, Subresource, MapType, MapFlags, pMappedResource); +} +void STDMETHODCALLTYPE D3D11DeviceContext::Unmap(ID3D11Resource *pResource, UINT Subresource) +{ + _orig->Unmap(pResource, Subresource); +} +void STDMETHODCALLTYPE D3D11DeviceContext::PSSetConstantBuffers(UINT StartSlot, UINT NumBuffers, ID3D11Buffer *const *ppConstantBuffers) +{ + _orig->PSSetConstantBuffers(StartSlot, NumBuffers, ppConstantBuffers); +} +void STDMETHODCALLTYPE D3D11DeviceContext::IASetInputLayout(ID3D11InputLayout *pInputLayout) +{ + _orig->IASetInputLayout(pInputLayout); +} +void STDMETHODCALLTYPE D3D11DeviceContext::IASetVertexBuffers(UINT StartSlot, UINT NumBuffers, ID3D11Buffer *const *ppVertexBuffers, const UINT *pStrides, const UINT *pOffsets) +{ + _orig->IASetVertexBuffers(StartSlot, NumBuffers, ppVertexBuffers, pStrides, pOffsets); +} +void STDMETHODCALLTYPE D3D11DeviceContext::IASetIndexBuffer(ID3D11Buffer *pIndexBuffer, DXGI_FORMAT Format, UINT Offset) +{ + _orig->IASetIndexBuffer(pIndexBuffer, Format, Offset); +} +void STDMETHODCALLTYPE D3D11DeviceContext::DrawIndexedInstanced(UINT IndexCountPerInstance, UINT InstanceCount, UINT StartIndexLocation, INT BaseVertexLocation, UINT StartInstanceLocation) +{ + _orig->DrawIndexedInstanced(IndexCountPerInstance, InstanceCount, StartIndexLocation, BaseVertexLocation, StartInstanceLocation); + _draw_call_tracker.on_draw(this, IndexCountPerInstance * InstanceCount); +} +void STDMETHODCALLTYPE D3D11DeviceContext::DrawInstanced(UINT VertexCountPerInstance, UINT InstanceCount, UINT StartVertexLocation, UINT StartInstanceLocation) +{ + _orig->DrawInstanced(VertexCountPerInstance, InstanceCount, StartVertexLocation, StartInstanceLocation); + _draw_call_tracker.on_draw(this, VertexCountPerInstance * InstanceCount); +} +void STDMETHODCALLTYPE D3D11DeviceContext::GSSetConstantBuffers(UINT StartSlot, UINT NumBuffers, ID3D11Buffer *const *ppConstantBuffers) +{ + _orig->GSSetConstantBuffers(StartSlot, NumBuffers, ppConstantBuffers); +} +void STDMETHODCALLTYPE D3D11DeviceContext::GSSetShader(ID3D11GeometryShader *pShader, ID3D11ClassInstance *const *ppClassInstances, UINT NumClassInstances) +{ + _orig->GSSetShader(pShader, ppClassInstances, NumClassInstances); +} +void STDMETHODCALLTYPE D3D11DeviceContext::IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY Topology) +{ + _orig->IASetPrimitiveTopology(Topology); +} +void STDMETHODCALLTYPE D3D11DeviceContext::VSSetShaderResources(UINT StartSlot, UINT NumViews, ID3D11ShaderResourceView *const *ppShaderResourceViews) +{ + _orig->VSSetShaderResources(StartSlot, NumViews, ppShaderResourceViews); +} +void STDMETHODCALLTYPE D3D11DeviceContext::VSSetSamplers(UINT StartSlot, UINT NumSamplers, ID3D11SamplerState *const *ppSamplers) +{ + _orig->VSSetSamplers(StartSlot, NumSamplers, ppSamplers); +} +void STDMETHODCALLTYPE D3D11DeviceContext::Begin(ID3D11Asynchronous *pAsync) +{ + _orig->Begin(pAsync); +} +void STDMETHODCALLTYPE D3D11DeviceContext::End(ID3D11Asynchronous *pAsync) +{ + _orig->End(pAsync); +} +HRESULT STDMETHODCALLTYPE D3D11DeviceContext::GetData(ID3D11Asynchronous *pAsync, void *pData, UINT DataSize, UINT GetDataFlags) +{ + return _orig->GetData(pAsync, pData, DataSize, GetDataFlags); +} +void STDMETHODCALLTYPE D3D11DeviceContext::SetPredication(ID3D11Predicate *pPredicate, BOOL PredicateValue) +{ + _orig->SetPredication(pPredicate, PredicateValue); +} +void STDMETHODCALLTYPE D3D11DeviceContext::GSSetShaderResources(UINT StartSlot, UINT NumViews, ID3D11ShaderResourceView *const *ppShaderResourceViews) +{ + _orig->GSSetShaderResources(StartSlot, NumViews, ppShaderResourceViews); +} +void STDMETHODCALLTYPE D3D11DeviceContext::GSSetSamplers(UINT StartSlot, UINT NumSamplers, ID3D11SamplerState *const *ppSamplers) +{ + _orig->GSSetSamplers(StartSlot, NumSamplers, ppSamplers); +} +void STDMETHODCALLTYPE D3D11DeviceContext::OMSetRenderTargets(UINT NumViews, ID3D11RenderTargetView *const *ppRenderTargetViews, ID3D11DepthStencilView *pDepthStencilView) +{ +#if RESHADE_DX11_CAPTURE_DEPTH_BUFFERS + track_active_rendertargets(NumViews, ppRenderTargetViews, pDepthStencilView); +#endif + _orig->OMSetRenderTargets(NumViews, ppRenderTargetViews, pDepthStencilView); +} +void STDMETHODCALLTYPE D3D11DeviceContext::OMSetRenderTargetsAndUnorderedAccessViews(UINT NumRTVs, ID3D11RenderTargetView *const *ppRenderTargetViews, ID3D11DepthStencilView *pDepthStencilView, UINT UAVStartSlot, UINT NumUAVs, ID3D11UnorderedAccessView *const *ppUnorderedAccessViews, const UINT *pUAVInitialCounts) +{ +#if RESHADE_DX11_CAPTURE_DEPTH_BUFFERS + track_active_rendertargets(NumRTVs, ppRenderTargetViews, pDepthStencilView); +#endif + _orig->OMSetRenderTargetsAndUnorderedAccessViews(NumRTVs, ppRenderTargetViews, pDepthStencilView, UAVStartSlot, NumUAVs, ppUnorderedAccessViews, pUAVInitialCounts); +} +void STDMETHODCALLTYPE D3D11DeviceContext::OMSetBlendState(ID3D11BlendState *pBlendState, const FLOAT BlendFactor[4], UINT SampleMask) +{ + _orig->OMSetBlendState(pBlendState, BlendFactor, SampleMask); +} +void STDMETHODCALLTYPE D3D11DeviceContext::OMSetDepthStencilState(ID3D11DepthStencilState *pDepthStencilState, UINT StencilRef) +{ + _orig->OMSetDepthStencilState(pDepthStencilState, StencilRef); +} +void STDMETHODCALLTYPE D3D11DeviceContext::SOSetTargets(UINT NumBuffers, ID3D11Buffer *const *ppSOTargets, const UINT *pOffsets) +{ + _orig->SOSetTargets(NumBuffers, ppSOTargets, pOffsets); +} +void STDMETHODCALLTYPE D3D11DeviceContext::DrawAuto() +{ + _orig->DrawAuto(); + _draw_call_tracker.on_draw(this, 0); +} +void STDMETHODCALLTYPE D3D11DeviceContext::DrawIndexedInstancedIndirect(ID3D11Buffer *pBufferForArgs, UINT AlignedByteOffsetForArgs) +{ + _orig->DrawIndexedInstancedIndirect(pBufferForArgs, AlignedByteOffsetForArgs); + _draw_call_tracker.on_draw(this, 0); +} +void STDMETHODCALLTYPE D3D11DeviceContext::DrawInstancedIndirect(ID3D11Buffer *pBufferForArgs, UINT AlignedByteOffsetForArgs) +{ + _orig->DrawInstancedIndirect(pBufferForArgs, AlignedByteOffsetForArgs); + _draw_call_tracker.on_draw(this, 0); +} +void STDMETHODCALLTYPE D3D11DeviceContext::Dispatch(UINT ThreadGroupCountX, UINT ThreadGroupCountY, UINT ThreadGroupCountZ) +{ + _orig->Dispatch(ThreadGroupCountX, ThreadGroupCountY, ThreadGroupCountZ); +} +void STDMETHODCALLTYPE D3D11DeviceContext::DispatchIndirect(ID3D11Buffer *pBufferForArgs, UINT AlignedByteOffsetForArgs) +{ + _orig->DispatchIndirect(pBufferForArgs, AlignedByteOffsetForArgs); +} +void STDMETHODCALLTYPE D3D11DeviceContext::RSSetState(ID3D11RasterizerState *pRasterizerState) +{ + _orig->RSSetState(pRasterizerState); +} +void STDMETHODCALLTYPE D3D11DeviceContext::RSSetViewports(UINT NumViewports, const D3D11_VIEWPORT *pViewports) +{ + _orig->RSSetViewports(NumViewports, pViewports); +} +void STDMETHODCALLTYPE D3D11DeviceContext::RSSetScissorRects(UINT NumRects, const D3D11_RECT *pRects) +{ + _orig->RSSetScissorRects(NumRects, pRects); +} +void STDMETHODCALLTYPE D3D11DeviceContext::CopySubresourceRegion(ID3D11Resource *pDstResource, UINT DstSubresource, UINT DstX, UINT DstY, UINT DstZ, ID3D11Resource *pSrcResource, UINT SrcSubresource, const D3D11_BOX *pSrcBox) +{ + _orig->CopySubresourceRegion(pDstResource, DstSubresource, DstX, DstY, DstZ, pSrcResource, SrcSubresource, pSrcBox); +} +void STDMETHODCALLTYPE D3D11DeviceContext::CopyResource(ID3D11Resource *pDstResource, ID3D11Resource *pSrcResource) +{ + _orig->CopyResource(pDstResource, pSrcResource); +} +void STDMETHODCALLTYPE D3D11DeviceContext::UpdateSubresource(ID3D11Resource *pDstResource, UINT DstSubresource, const D3D11_BOX *pDstBox, const void *pSrcData, UINT SrcRowPitch, UINT SrcDepthPitch) +{ + _orig->UpdateSubresource(pDstResource, DstSubresource, pDstBox, pSrcData, SrcRowPitch, SrcDepthPitch); +} +void STDMETHODCALLTYPE D3D11DeviceContext::CopyStructureCount(ID3D11Buffer *pDstBuffer, UINT DstAlignedByteOffset, ID3D11UnorderedAccessView *pSrcView) +{ + _orig->CopyStructureCount(pDstBuffer, DstAlignedByteOffset, pSrcView); +} +void STDMETHODCALLTYPE D3D11DeviceContext::ClearRenderTargetView(ID3D11RenderTargetView *pRenderTargetView, const FLOAT ColorRGBA[4]) +{ + _orig->ClearRenderTargetView(pRenderTargetView, ColorRGBA); +} +void STDMETHODCALLTYPE D3D11DeviceContext::ClearUnorderedAccessViewUint(ID3D11UnorderedAccessView *pUnorderedAccessView, const UINT Values[4]) +{ + _orig->ClearUnorderedAccessViewUint(pUnorderedAccessView, Values); +} +void STDMETHODCALLTYPE D3D11DeviceContext::ClearUnorderedAccessViewFloat(ID3D11UnorderedAccessView *pUnorderedAccessView, const FLOAT Values[4]) +{ + _orig->ClearUnorderedAccessViewFloat(pUnorderedAccessView, Values); +} +void STDMETHODCALLTYPE D3D11DeviceContext::ClearDepthStencilView(ID3D11DepthStencilView *pDepthStencilView, UINT ClearFlags, FLOAT Depth, UINT8 Stencil) +{ +#if RESHADE_DX11_CAPTURE_DEPTH_BUFFERS + if (ClearFlags & D3D11_CLEAR_DEPTH) + track_cleared_depthstencil(pDepthStencilView); +#endif + _orig->ClearDepthStencilView(pDepthStencilView, ClearFlags, Depth, Stencil); +} +void STDMETHODCALLTYPE D3D11DeviceContext::GenerateMips(ID3D11ShaderResourceView *pShaderResourceView) +{ + _orig->GenerateMips(pShaderResourceView); +} +void STDMETHODCALLTYPE D3D11DeviceContext::SetResourceMinLOD(ID3D11Resource *pResource, FLOAT MinLOD) +{ + _orig->SetResourceMinLOD(pResource, MinLOD); +} +FLOAT STDMETHODCALLTYPE D3D11DeviceContext::GetResourceMinLOD(ID3D11Resource *pResource) +{ + return _orig->GetResourceMinLOD(pResource); +} +void STDMETHODCALLTYPE D3D11DeviceContext::ResolveSubresource(ID3D11Resource *pDstResource, UINT DstSubresource, ID3D11Resource *pSrcResource, UINT SrcSubresource, DXGI_FORMAT Format) +{ + _orig->ResolveSubresource(pDstResource, DstSubresource, pSrcResource, SrcSubresource, Format); +} +void STDMETHODCALLTYPE D3D11DeviceContext::ExecuteCommandList(ID3D11CommandList *pCommandList, BOOL RestoreContextState) +{ + if (pCommandList != nullptr) + _device->merge_commandlist_trackers(pCommandList, _draw_call_tracker); + + _orig->ExecuteCommandList(pCommandList, RestoreContextState); +} +void STDMETHODCALLTYPE D3D11DeviceContext::HSSetShaderResources(UINT StartSlot, UINT NumViews, ID3D11ShaderResourceView *const *ppShaderResourceViews) +{ + _orig->HSSetShaderResources(StartSlot, NumViews, ppShaderResourceViews); +} +void STDMETHODCALLTYPE D3D11DeviceContext::HSSetShader(ID3D11HullShader *pHullShader, ID3D11ClassInstance *const *ppClassInstances, UINT NumClassInstances) +{ + _orig->HSSetShader(pHullShader, ppClassInstances, NumClassInstances); +} +void STDMETHODCALLTYPE D3D11DeviceContext::HSSetSamplers(UINT StartSlot, UINT NumSamplers, ID3D11SamplerState *const *ppSamplers) +{ + _orig->HSSetSamplers(StartSlot, NumSamplers, ppSamplers); +} +void STDMETHODCALLTYPE D3D11DeviceContext::HSSetConstantBuffers(UINT StartSlot, UINT NumBuffers, ID3D11Buffer *const *ppConstantBuffers) +{ + _orig->HSSetConstantBuffers(StartSlot, NumBuffers, ppConstantBuffers); +} +void STDMETHODCALLTYPE D3D11DeviceContext::DSSetShaderResources(UINT StartSlot, UINT NumViews, ID3D11ShaderResourceView *const *ppShaderResourceViews) +{ + _orig->DSSetShaderResources(StartSlot, NumViews, ppShaderResourceViews); +} +void STDMETHODCALLTYPE D3D11DeviceContext::DSSetShader(ID3D11DomainShader *pDomainShader, ID3D11ClassInstance *const *ppClassInstances, UINT NumClassInstances) +{ + _orig->DSSetShader(pDomainShader, ppClassInstances, NumClassInstances); +} +void STDMETHODCALLTYPE D3D11DeviceContext::DSSetSamplers(UINT StartSlot, UINT NumSamplers, ID3D11SamplerState *const *ppSamplers) +{ + _orig->DSSetSamplers(StartSlot, NumSamplers, ppSamplers); +} +void STDMETHODCALLTYPE D3D11DeviceContext::DSSetConstantBuffers(UINT StartSlot, UINT NumBuffers, ID3D11Buffer *const *ppConstantBuffers) +{ + _orig->DSSetConstantBuffers(StartSlot, NumBuffers, ppConstantBuffers); +} +void STDMETHODCALLTYPE D3D11DeviceContext::CSSetShaderResources(UINT StartSlot, UINT NumViews, ID3D11ShaderResourceView *const *ppShaderResourceViews) +{ + _orig->CSSetShaderResources(StartSlot, NumViews, ppShaderResourceViews); +} +void STDMETHODCALLTYPE D3D11DeviceContext::CSSetUnorderedAccessViews(UINT StartSlot, UINT NumUAVs, ID3D11UnorderedAccessView *const *ppUnorderedAccessViews, const UINT *pUAVInitialCounts) +{ + _orig->CSSetUnorderedAccessViews(StartSlot, NumUAVs, ppUnorderedAccessViews, pUAVInitialCounts); +} +void STDMETHODCALLTYPE D3D11DeviceContext::CSSetShader(ID3D11ComputeShader *pComputeShader, ID3D11ClassInstance *const *ppClassInstances, UINT NumClassInstances) +{ + _orig->CSSetShader(pComputeShader, ppClassInstances, NumClassInstances); +} +void STDMETHODCALLTYPE D3D11DeviceContext::CSSetSamplers(UINT StartSlot, UINT NumSamplers, ID3D11SamplerState *const *ppSamplers) +{ + _orig->CSSetSamplers(StartSlot, NumSamplers, ppSamplers); +} +void STDMETHODCALLTYPE D3D11DeviceContext::CSSetConstantBuffers(UINT StartSlot, UINT NumBuffers, ID3D11Buffer *const *ppConstantBuffers) +{ + _orig->CSSetConstantBuffers(StartSlot, NumBuffers, ppConstantBuffers); +} +void STDMETHODCALLTYPE D3D11DeviceContext::VSGetConstantBuffers(UINT StartSlot, UINT NumBuffers, ID3D11Buffer **ppConstantBuffers) +{ + _orig->VSGetConstantBuffers(StartSlot, NumBuffers, ppConstantBuffers); +} +void STDMETHODCALLTYPE D3D11DeviceContext::PSGetShaderResources(UINT StartSlot, UINT NumViews, ID3D11ShaderResourceView **ppShaderResourceViews) +{ + _orig->PSGetShaderResources(StartSlot, NumViews, ppShaderResourceViews); +} +void STDMETHODCALLTYPE D3D11DeviceContext::PSGetShader(ID3D11PixelShader **ppPixelShader, ID3D11ClassInstance **ppClassInstances, UINT *pNumClassInstances) +{ + _orig->PSGetShader(ppPixelShader, ppClassInstances, pNumClassInstances); +} +void STDMETHODCALLTYPE D3D11DeviceContext::PSGetSamplers(UINT StartSlot, UINT NumSamplers, ID3D11SamplerState **ppSamplers) +{ + _orig->PSGetSamplers(StartSlot, NumSamplers, ppSamplers); +} +void STDMETHODCALLTYPE D3D11DeviceContext::VSGetShader(ID3D11VertexShader **ppVertexShader, ID3D11ClassInstance **ppClassInstances, UINT *pNumClassInstances) +{ + _orig->VSGetShader(ppVertexShader, ppClassInstances, pNumClassInstances); +} +void STDMETHODCALLTYPE D3D11DeviceContext::PSGetConstantBuffers(UINT StartSlot, UINT NumBuffers, ID3D11Buffer **ppConstantBuffers) +{ + _orig->PSGetConstantBuffers(StartSlot, NumBuffers, ppConstantBuffers); +} +void STDMETHODCALLTYPE D3D11DeviceContext::IAGetInputLayout(ID3D11InputLayout **ppInputLayout) +{ + _orig->IAGetInputLayout(ppInputLayout); +} +void STDMETHODCALLTYPE D3D11DeviceContext::IAGetVertexBuffers(UINT StartSlot, UINT NumBuffers, ID3D11Buffer **ppVertexBuffers, UINT *pStrides, UINT *pOffsets) +{ + _orig->IAGetVertexBuffers(StartSlot, NumBuffers, ppVertexBuffers, pStrides, pOffsets); +} +void STDMETHODCALLTYPE D3D11DeviceContext::IAGetIndexBuffer(ID3D11Buffer **pIndexBuffer, DXGI_FORMAT *Format, UINT *Offset) +{ + _orig->IAGetIndexBuffer(pIndexBuffer, Format, Offset); +} +void STDMETHODCALLTYPE D3D11DeviceContext::GSGetConstantBuffers(UINT StartSlot, UINT NumBuffers, ID3D11Buffer **ppConstantBuffers) +{ + _orig->GSGetConstantBuffers(StartSlot, NumBuffers, ppConstantBuffers); +} +void STDMETHODCALLTYPE D3D11DeviceContext::GSGetShader(ID3D11GeometryShader **ppGeometryShader, ID3D11ClassInstance **ppClassInstances, UINT *pNumClassInstances) +{ + _orig->GSGetShader(ppGeometryShader, ppClassInstances, pNumClassInstances); +} +void STDMETHODCALLTYPE D3D11DeviceContext::IAGetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY *pTopology) +{ + _orig->IAGetPrimitiveTopology(pTopology); +} +void STDMETHODCALLTYPE D3D11DeviceContext::VSGetShaderResources(UINT StartSlot, UINT NumViews, ID3D11ShaderResourceView **ppShaderResourceViews) +{ + _orig->VSGetShaderResources(StartSlot, NumViews, ppShaderResourceViews); +} +void STDMETHODCALLTYPE D3D11DeviceContext::VSGetSamplers(UINT StartSlot, UINT NumSamplers, ID3D11SamplerState **ppSamplers) +{ + _orig->VSGetSamplers(StartSlot, NumSamplers, ppSamplers); +} +void STDMETHODCALLTYPE D3D11DeviceContext::GetPredication(ID3D11Predicate **ppPredicate, BOOL *pPredicateValue) +{ + _orig->GetPredication(ppPredicate, pPredicateValue); +} +void STDMETHODCALLTYPE D3D11DeviceContext::GSGetShaderResources(UINT StartSlot, UINT NumViews, ID3D11ShaderResourceView **ppShaderResourceViews) +{ + _orig->GSGetShaderResources(StartSlot, NumViews, ppShaderResourceViews); +} +void STDMETHODCALLTYPE D3D11DeviceContext::GSGetSamplers(UINT StartSlot, UINT NumSamplers, ID3D11SamplerState **ppSamplers) +{ + _orig->GSGetSamplers(StartSlot, NumSamplers, ppSamplers); +} +void STDMETHODCALLTYPE D3D11DeviceContext::OMGetRenderTargets(UINT NumViews, ID3D11RenderTargetView **ppRenderTargetViews, ID3D11DepthStencilView **ppDepthStencilView) +{ + _orig->OMGetRenderTargets(NumViews, ppRenderTargetViews, ppDepthStencilView); +} +void STDMETHODCALLTYPE D3D11DeviceContext::OMGetRenderTargetsAndUnorderedAccessViews(UINT NumRTVs, ID3D11RenderTargetView **ppRenderTargetViews, ID3D11DepthStencilView **ppDepthStencilView, UINT UAVStartSlot, UINT NumUAVs, ID3D11UnorderedAccessView **ppUnorderedAccessViews) +{ + _orig->OMGetRenderTargetsAndUnorderedAccessViews(NumRTVs, ppRenderTargetViews, ppDepthStencilView, UAVStartSlot, NumUAVs, ppUnorderedAccessViews); +} +void STDMETHODCALLTYPE D3D11DeviceContext::OMGetBlendState(ID3D11BlendState **ppBlendState, FLOAT BlendFactor[4], UINT *pSampleMask) +{ + _orig->OMGetBlendState(ppBlendState, BlendFactor, pSampleMask); +} +void STDMETHODCALLTYPE D3D11DeviceContext::OMGetDepthStencilState(ID3D11DepthStencilState **ppDepthStencilState, UINT *pStencilRef) +{ + _orig->OMGetDepthStencilState(ppDepthStencilState, pStencilRef); +} +void STDMETHODCALLTYPE D3D11DeviceContext::SOGetTargets(UINT NumBuffers, ID3D11Buffer **ppSOTargets) +{ + _orig->SOGetTargets(NumBuffers, ppSOTargets); +} +void STDMETHODCALLTYPE D3D11DeviceContext::RSGetState(ID3D11RasterizerState **ppRasterizerState) +{ + _orig->RSGetState(ppRasterizerState); +} +void STDMETHODCALLTYPE D3D11DeviceContext::RSGetViewports(UINT *pNumViewports, D3D11_VIEWPORT *pViewports) +{ + _orig->RSGetViewports(pNumViewports, pViewports); +} +void STDMETHODCALLTYPE D3D11DeviceContext::RSGetScissorRects(UINT *pNumRects, D3D11_RECT *pRects) +{ + _orig->RSGetScissorRects(pNumRects, pRects); +} +void STDMETHODCALLTYPE D3D11DeviceContext::HSGetShaderResources(UINT StartSlot, UINT NumViews, ID3D11ShaderResourceView **ppShaderResourceViews) +{ + _orig->HSGetShaderResources(StartSlot, NumViews, ppShaderResourceViews); +} +void STDMETHODCALLTYPE D3D11DeviceContext::HSGetShader(ID3D11HullShader **ppHullShader, ID3D11ClassInstance **ppClassInstances, UINT *pNumClassInstances) +{ + _orig->HSGetShader(ppHullShader, ppClassInstances, pNumClassInstances); +} +void STDMETHODCALLTYPE D3D11DeviceContext::HSGetSamplers(UINT StartSlot, UINT NumSamplers, ID3D11SamplerState **ppSamplers) +{ + _orig->HSGetSamplers(StartSlot, NumSamplers, ppSamplers); +} +void STDMETHODCALLTYPE D3D11DeviceContext::HSGetConstantBuffers(UINT StartSlot, UINT NumBuffers, ID3D11Buffer **ppConstantBuffers) +{ + _orig->HSGetConstantBuffers(StartSlot, NumBuffers, ppConstantBuffers); +} +void STDMETHODCALLTYPE D3D11DeviceContext::DSGetShaderResources(UINT StartSlot, UINT NumViews, ID3D11ShaderResourceView **ppShaderResourceViews) +{ + _orig->DSGetShaderResources(StartSlot, NumViews, ppShaderResourceViews); +} +void STDMETHODCALLTYPE D3D11DeviceContext::DSGetShader(ID3D11DomainShader **ppDomainShader, ID3D11ClassInstance **ppClassInstances, UINT *pNumClassInstances) +{ + _orig->DSGetShader(ppDomainShader, ppClassInstances, pNumClassInstances); +} +void STDMETHODCALLTYPE D3D11DeviceContext::DSGetSamplers(UINT StartSlot, UINT NumSamplers, ID3D11SamplerState **ppSamplers) +{ + _orig->DSGetSamplers(StartSlot, NumSamplers, ppSamplers); +} +void STDMETHODCALLTYPE D3D11DeviceContext::DSGetConstantBuffers(UINT StartSlot, UINT NumBuffers, ID3D11Buffer **ppConstantBuffers) +{ + _orig->DSGetConstantBuffers(StartSlot, NumBuffers, ppConstantBuffers); +} +void STDMETHODCALLTYPE D3D11DeviceContext::CSGetShaderResources(UINT StartSlot, UINT NumViews, ID3D11ShaderResourceView **ppShaderResourceViews) +{ + _orig->CSGetShaderResources(StartSlot, NumViews, ppShaderResourceViews); +} +void STDMETHODCALLTYPE D3D11DeviceContext::CSGetUnorderedAccessViews(UINT StartSlot, UINT NumUAVs, ID3D11UnorderedAccessView **ppUnorderedAccessViews) +{ + _orig->CSGetUnorderedAccessViews(StartSlot, NumUAVs, ppUnorderedAccessViews); +} +void STDMETHODCALLTYPE D3D11DeviceContext::CSGetShader(ID3D11ComputeShader **ppComputeShader, ID3D11ClassInstance **ppClassInstances, UINT *pNumClassInstances) +{ + _orig->CSGetShader(ppComputeShader, ppClassInstances, pNumClassInstances); +} +void STDMETHODCALLTYPE D3D11DeviceContext::CSGetSamplers(UINT StartSlot, UINT NumSamplers, ID3D11SamplerState **ppSamplers) +{ + _orig->CSGetSamplers(StartSlot, NumSamplers, ppSamplers); +} +void STDMETHODCALLTYPE D3D11DeviceContext::CSGetConstantBuffers(UINT StartSlot, UINT NumBuffers, ID3D11Buffer **ppConstantBuffers) +{ + _orig->CSGetConstantBuffers(StartSlot, NumBuffers, ppConstantBuffers); +} +void STDMETHODCALLTYPE D3D11DeviceContext::ClearState() +{ + _orig->ClearState(); +} +void STDMETHODCALLTYPE D3D11DeviceContext::Flush() +{ + _orig->Flush(); +} +UINT STDMETHODCALLTYPE D3D11DeviceContext::GetContextFlags() +{ + return _orig->GetContextFlags(); +} +HRESULT STDMETHODCALLTYPE D3D11DeviceContext::FinishCommandList(BOOL RestoreDeferredContextState, ID3D11CommandList **ppCommandList) +{ + const HRESULT hr = _orig->FinishCommandList(RestoreDeferredContextState, ppCommandList); + + if (SUCCEEDED(hr) && ppCommandList != nullptr) + _device->add_commandlist_trackers(*ppCommandList, _draw_call_tracker); + + _draw_call_tracker.reset(); + + return hr; +} +D3D11_DEVICE_CONTEXT_TYPE STDMETHODCALLTYPE D3D11DeviceContext::GetType() +{ + return _orig->GetType(); +} + +void STDMETHODCALLTYPE D3D11DeviceContext::CopySubresourceRegion1(ID3D11Resource *pDstResource, UINT DstSubresource, UINT DstX, UINT DstY, UINT DstZ, ID3D11Resource *pSrcResource, UINT SrcSubresource, const D3D11_BOX *pSrcBox, UINT CopyFlags) +{ + assert(_interface_version >= 1); + static_cast(_orig)->CopySubresourceRegion1(pDstResource, DstSubresource, DstX, DstY, DstZ, pSrcResource, SrcSubresource, pSrcBox, CopyFlags); +} +void STDMETHODCALLTYPE D3D11DeviceContext::UpdateSubresource1(ID3D11Resource *pDstResource, UINT DstSubresource, const D3D11_BOX *pDstBox, const void *pSrcData, UINT SrcRowPitch, UINT SrcDepthPitch, UINT CopyFlags) +{ + assert(_interface_version >= 1); + static_cast(_orig)->UpdateSubresource1(pDstResource, DstSubresource, pDstBox, pSrcData, SrcRowPitch, SrcDepthPitch, CopyFlags); +} +void STDMETHODCALLTYPE D3D11DeviceContext::DiscardResource(ID3D11Resource *pResource) +{ + assert(_interface_version >= 1); + static_cast(_orig)->DiscardResource(pResource); +} +void STDMETHODCALLTYPE D3D11DeviceContext::DiscardView(ID3D11View *pResourceView) +{ + assert(_interface_version >= 1); + static_cast(_orig)->DiscardView(pResourceView); +} +void STDMETHODCALLTYPE D3D11DeviceContext::VSSetConstantBuffers1(UINT StartSlot, UINT NumBuffers, ID3D11Buffer *const *ppConstantBuffers, const UINT *pFirstConstant, const UINT *pNumConstants) +{ + assert(_interface_version >= 1); + static_cast(_orig)->VSSetConstantBuffers1(StartSlot, NumBuffers, ppConstantBuffers, pFirstConstant, pNumConstants); +} +void STDMETHODCALLTYPE D3D11DeviceContext::HSSetConstantBuffers1(UINT StartSlot, UINT NumBuffers, ID3D11Buffer *const *ppConstantBuffers, const UINT *pFirstConstant, const UINT *pNumConstants) +{ + assert(_interface_version >= 1); + static_cast(_orig)->HSSetConstantBuffers1(StartSlot, NumBuffers, ppConstantBuffers, pFirstConstant, pNumConstants); +} +void STDMETHODCALLTYPE D3D11DeviceContext::DSSetConstantBuffers1(UINT StartSlot, UINT NumBuffers, ID3D11Buffer *const *ppConstantBuffers, const UINT *pFirstConstant, const UINT *pNumConstants) +{ + assert(_interface_version >= 1); + static_cast(_orig)->DSSetConstantBuffers1(StartSlot, NumBuffers, ppConstantBuffers, pFirstConstant, pNumConstants); +} +void STDMETHODCALLTYPE D3D11DeviceContext::GSSetConstantBuffers1(UINT StartSlot, UINT NumBuffers, ID3D11Buffer *const *ppConstantBuffers, const UINT *pFirstConstant, const UINT *pNumConstants) +{ + assert(_interface_version >= 1); + static_cast(_orig)->GSSetConstantBuffers1(StartSlot, NumBuffers, ppConstantBuffers, pFirstConstant, pNumConstants); +} +void STDMETHODCALLTYPE D3D11DeviceContext::PSSetConstantBuffers1(UINT StartSlot, UINT NumBuffers, ID3D11Buffer *const *ppConstantBuffers, const UINT *pFirstConstant, const UINT *pNumConstants) +{ + assert(_interface_version >= 1); + static_cast(_orig)->PSSetConstantBuffers1(StartSlot, NumBuffers, ppConstantBuffers, pFirstConstant, pNumConstants); +} +void STDMETHODCALLTYPE D3D11DeviceContext::CSSetConstantBuffers1(UINT StartSlot, UINT NumBuffers, ID3D11Buffer *const *ppConstantBuffers, const UINT *pFirstConstant, const UINT *pNumConstants) +{ + assert(_interface_version >= 1); + static_cast(_orig)->CSSetConstantBuffers1(StartSlot, NumBuffers, ppConstantBuffers, pFirstConstant, pNumConstants); +} +void STDMETHODCALLTYPE D3D11DeviceContext::VSGetConstantBuffers1(UINT StartSlot, UINT NumBuffers, ID3D11Buffer **ppConstantBuffers, UINT *pFirstConstant, UINT *pNumConstants) +{ + assert(_interface_version >= 1); + static_cast(_orig)->VSGetConstantBuffers1(StartSlot, NumBuffers, ppConstantBuffers, pFirstConstant, pNumConstants); +} +void STDMETHODCALLTYPE D3D11DeviceContext::HSGetConstantBuffers1(UINT StartSlot, UINT NumBuffers, ID3D11Buffer **ppConstantBuffers, UINT *pFirstConstant, UINT *pNumConstants) +{ + assert(_interface_version >= 1); + static_cast(_orig)->HSGetConstantBuffers1(StartSlot, NumBuffers, ppConstantBuffers, pFirstConstant, pNumConstants); +} +void STDMETHODCALLTYPE D3D11DeviceContext::DSGetConstantBuffers1(UINT StartSlot, UINT NumBuffers, ID3D11Buffer **ppConstantBuffers, UINT *pFirstConstant, UINT *pNumConstants) +{ + assert(_interface_version >= 1); + static_cast(_orig)->DSGetConstantBuffers1(StartSlot, NumBuffers, ppConstantBuffers, pFirstConstant, pNumConstants); +} +void STDMETHODCALLTYPE D3D11DeviceContext::GSGetConstantBuffers1(UINT StartSlot, UINT NumBuffers, ID3D11Buffer **ppConstantBuffers, UINT *pFirstConstant, UINT *pNumConstants) +{ + assert(_interface_version >= 1); + static_cast(_orig)->GSGetConstantBuffers1(StartSlot, NumBuffers, ppConstantBuffers, pFirstConstant, pNumConstants); +} +void STDMETHODCALLTYPE D3D11DeviceContext::PSGetConstantBuffers1(UINT StartSlot, UINT NumBuffers, ID3D11Buffer **ppConstantBuffers, UINT *pFirstConstant, UINT *pNumConstants) +{ + assert(_interface_version >= 1); + static_cast(_orig)->PSGetConstantBuffers1(StartSlot, NumBuffers, ppConstantBuffers, pFirstConstant, pNumConstants); +} +void STDMETHODCALLTYPE D3D11DeviceContext::CSGetConstantBuffers1(UINT StartSlot, UINT NumBuffers, ID3D11Buffer **ppConstantBuffers, UINT *pFirstConstant, UINT *pNumConstants) +{ + assert(_interface_version >= 1); + static_cast(_orig)->CSGetConstantBuffers1(StartSlot, NumBuffers, ppConstantBuffers, pFirstConstant, pNumConstants); +} +void STDMETHODCALLTYPE D3D11DeviceContext::SwapDeviceContextState(ID3DDeviceContextState *pState, ID3DDeviceContextState **ppPreviousState) +{ + assert(_interface_version >= 1); + static_cast(_orig)->SwapDeviceContextState(pState, ppPreviousState); +} +void STDMETHODCALLTYPE D3D11DeviceContext::ClearView(ID3D11View *pView, const FLOAT Color[4], const D3D11_RECT *pRect, UINT NumRects) +{ + assert(_interface_version >= 1); + static_cast(_orig)->ClearView(pView, Color, pRect, NumRects); +} +void STDMETHODCALLTYPE D3D11DeviceContext::DiscardView1(ID3D11View *pResourceView, const D3D11_RECT *pRects, UINT NumRects) +{ + assert(_interface_version >= 1); + static_cast(_orig)->DiscardView1(pResourceView, pRects, NumRects); +} + +HRESULT STDMETHODCALLTYPE D3D11DeviceContext::UpdateTileMappings(ID3D11Resource *pTiledResource, UINT NumTiledResourceRegions, const D3D11_TILED_RESOURCE_COORDINATE *pTiledResourceRegionStartCoordinates, const D3D11_TILE_REGION_SIZE *pTiledResourceRegionSizes, ID3D11Buffer *pTilePool, UINT NumRanges, const UINT *pRangeFlags, const UINT *pTilePoolStartOffsets, const UINT *pRangeTileCounts, UINT Flags) +{ + assert(_interface_version >= 2); + return static_cast(_orig)->UpdateTileMappings(pTiledResource, NumTiledResourceRegions, pTiledResourceRegionStartCoordinates, pTiledResourceRegionSizes, pTilePool, NumRanges, pRangeFlags, pTilePoolStartOffsets, pRangeTileCounts, Flags); +} +HRESULT STDMETHODCALLTYPE D3D11DeviceContext::CopyTileMappings(ID3D11Resource *pDestTiledResource, const D3D11_TILED_RESOURCE_COORDINATE *pDestRegionStartCoordinate, ID3D11Resource *pSourceTiledResource, const D3D11_TILED_RESOURCE_COORDINATE *pSourceRegionStartCoordinate, const D3D11_TILE_REGION_SIZE *pTileRegionSize, UINT Flags) +{ + assert(_interface_version >= 2); + return static_cast(_orig)->CopyTileMappings(pDestTiledResource, pDestRegionStartCoordinate, pSourceTiledResource, pSourceRegionStartCoordinate, pTileRegionSize, Flags); +} +void STDMETHODCALLTYPE D3D11DeviceContext::CopyTiles(ID3D11Resource *pTiledResource, const D3D11_TILED_RESOURCE_COORDINATE *pTileRegionStartCoordinate, const D3D11_TILE_REGION_SIZE *pTileRegionSize, ID3D11Buffer *pBuffer, UINT64 BufferStartOffsetInBytes, UINT Flags) +{ + assert(_interface_version >= 2); + static_cast(_orig)->CopyTiles(pTiledResource, pTileRegionStartCoordinate, pTileRegionSize, pBuffer, BufferStartOffsetInBytes, Flags); +} +void STDMETHODCALLTYPE D3D11DeviceContext::UpdateTiles(ID3D11Resource *pDestTiledResource, const D3D11_TILED_RESOURCE_COORDINATE *pDestTileRegionStartCoordinate, const D3D11_TILE_REGION_SIZE *pDestTileRegionSize, const void *pSourceTileData, UINT Flags) +{ + assert(_interface_version >= 2); + static_cast(_orig)->UpdateTiles(pDestTiledResource, pDestTileRegionStartCoordinate, pDestTileRegionSize, pSourceTileData, Flags); +} +HRESULT STDMETHODCALLTYPE D3D11DeviceContext::ResizeTilePool(ID3D11Buffer *pTilePool, UINT64 NewSizeInBytes) +{ + assert(_interface_version >= 2); + return static_cast(_orig)->ResizeTilePool(pTilePool, NewSizeInBytes); +} +void STDMETHODCALLTYPE D3D11DeviceContext::TiledResourceBarrier(ID3D11DeviceChild *pTiledResourceOrViewAccessBeforeBarrier, ID3D11DeviceChild *pTiledResourceOrViewAccessAfterBarrier) +{ + assert(_interface_version >= 2); + static_cast(_orig)->TiledResourceBarrier(pTiledResourceOrViewAccessBeforeBarrier, pTiledResourceOrViewAccessAfterBarrier); +} +BOOL STDMETHODCALLTYPE D3D11DeviceContext::IsAnnotationEnabled() +{ + assert(_interface_version >= 2); + return static_cast(_orig)->IsAnnotationEnabled(); +} +void STDMETHODCALLTYPE D3D11DeviceContext::SetMarkerInt(LPCWSTR pLabel, INT Data) +{ + assert(_interface_version >= 2); + static_cast(_orig)->SetMarkerInt(pLabel, Data); +} +void STDMETHODCALLTYPE D3D11DeviceContext::BeginEventInt(LPCWSTR pLabel, INT Data) +{ + assert(_interface_version >= 2); + static_cast(_orig)->BeginEventInt(pLabel, Data); +} +void STDMETHODCALLTYPE D3D11DeviceContext::EndEvent() +{ + assert(_interface_version >= 2); + static_cast(_orig)->EndEvent(); +} + +void STDMETHODCALLTYPE D3D11DeviceContext::Flush1(D3D11_CONTEXT_TYPE ContextType, HANDLE hEvent) +{ + assert(_interface_version >= 3); + static_cast(_orig)->Flush1(ContextType, hEvent); +} +void STDMETHODCALLTYPE D3D11DeviceContext::SetHardwareProtectionState(BOOL HwProtectionEnable) +{ + assert(_interface_version >= 3); + static_cast(_orig)->SetHardwareProtectionState(HwProtectionEnable); +} +void STDMETHODCALLTYPE D3D11DeviceContext::GetHardwareProtectionState(BOOL *pHwProtectionEnable) +{ + assert(_interface_version >= 3); + static_cast(_orig)->GetHardwareProtectionState(pHwProtectionEnable); +} + +HRESULT STDMETHODCALLTYPE D3D11DeviceContext::Signal(ID3D11Fence *pFence, UINT64 Value) +{ + assert(_interface_version >= 4); + return static_cast(_orig)->Signal(pFence, Value); +} +HRESULT STDMETHODCALLTYPE D3D11DeviceContext::Wait(ID3D11Fence *pFence, UINT64 Value) +{ + assert(_interface_version >= 4); + return static_cast(_orig)->Wait(pFence, Value); +} diff --git a/msvc/source/d3d11/d3d11_device_context.hpp b/msvc/source/d3d11/d3d11_device_context.hpp new file mode 100644 index 0000000..ebabe79 --- /dev/null +++ b/msvc/source/d3d11/d3d11_device_context.hpp @@ -0,0 +1,200 @@ +/** + * Copyright (C) 2014 Patrick Mours. All rights reserved. + * License: https://github.com/crosire/reshade#license + */ + +#pragma once + +#include +#include "draw_call_tracker.hpp" + +struct __declspec(uuid("27B0246B-2152-4D42-AD11-32489472238F")) D3D11DeviceContext : ID3D11DeviceContext4 +{ + D3D11DeviceContext(D3D11Device *device, ID3D11DeviceContext *original); + D3D11DeviceContext(D3D11Device *device, ID3D11DeviceContext1 *original); + D3D11DeviceContext(D3D11Device *device, ID3D11DeviceContext2 *original); + D3D11DeviceContext(D3D11Device *device, ID3D11DeviceContext3 *original); + + D3D11DeviceContext(const D3D11DeviceContext &) = delete; + D3D11DeviceContext &operator=(const D3D11DeviceContext &) = delete; + + HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void **ppvObj) override; + ULONG STDMETHODCALLTYPE AddRef() override; + ULONG STDMETHODCALLTYPE Release() override; + + #pragma region ID3D11DeviceChild + void STDMETHODCALLTYPE GetDevice(ID3D11Device **ppDevice) override; + HRESULT STDMETHODCALLTYPE GetPrivateData(REFGUID guid, UINT *pDataSize, void *pData) override; + HRESULT STDMETHODCALLTYPE SetPrivateData(REFGUID guid, UINT DataSize, const void *pData) override; + HRESULT STDMETHODCALLTYPE SetPrivateDataInterface(REFGUID guid, const IUnknown *pData) override; + #pragma endregion + #pragma region ID3D11DeviceContext + void STDMETHODCALLTYPE VSSetConstantBuffers(UINT StartSlot, UINT NumBuffers, ID3D11Buffer *const *ppConstantBuffers) override; + void STDMETHODCALLTYPE PSSetShaderResources(UINT StartSlot, UINT NumViews, ID3D11ShaderResourceView *const *ppShaderResourceViews) override; + void STDMETHODCALLTYPE PSSetShader(ID3D11PixelShader *pPixelShader, ID3D11ClassInstance *const *ppClassInstances, UINT NumClassInstances) override; + void STDMETHODCALLTYPE PSSetSamplers(UINT StartSlot, UINT NumSamplers, ID3D11SamplerState *const *ppSamplers) override; + void STDMETHODCALLTYPE VSSetShader(ID3D11VertexShader *pVertexShader, ID3D11ClassInstance *const *ppClassInstances, UINT NumClassInstances) override; + void STDMETHODCALLTYPE DrawIndexed(UINT IndexCount, UINT StartIndexLocation, INT BaseVertexLocation) override; + void STDMETHODCALLTYPE Draw(UINT VertexCount, UINT StartVertexLocation) override; + HRESULT STDMETHODCALLTYPE Map(ID3D11Resource *pResource, UINT Subresource, D3D11_MAP MapType, UINT MapFlags, D3D11_MAPPED_SUBRESOURCE *pMappedResource) override; + void STDMETHODCALLTYPE Unmap(ID3D11Resource *pResource, UINT Subresource) override; + void STDMETHODCALLTYPE PSSetConstantBuffers(UINT StartSlot, UINT NumBuffers, ID3D11Buffer *const *ppConstantBuffers) override; + void STDMETHODCALLTYPE IASetInputLayout(ID3D11InputLayout *pInputLayout) override; + void STDMETHODCALLTYPE IASetVertexBuffers(UINT StartSlot, UINT NumBuffers, ID3D11Buffer *const *ppVertexBuffers, const UINT *pStrides, const UINT *pOffsets) override; + void STDMETHODCALLTYPE IASetIndexBuffer(ID3D11Buffer *pIndexBuffer, DXGI_FORMAT Format, UINT Offset) override; + void STDMETHODCALLTYPE DrawIndexedInstanced(UINT IndexCountPerInstance, UINT InstanceCount, UINT StartIndexLocation, INT BaseVertexLocation, UINT StartInstanceLocation) override; + void STDMETHODCALLTYPE DrawInstanced(UINT VertexCountPerInstance, UINT InstanceCount, UINT StartVertexLocation, UINT StartInstanceLocation) override; + void STDMETHODCALLTYPE GSSetConstantBuffers(UINT StartSlot, UINT NumBuffers, ID3D11Buffer *const *ppConstantBuffers) override; + void STDMETHODCALLTYPE GSSetShader(ID3D11GeometryShader *pShader, ID3D11ClassInstance *const *ppClassInstances, UINT NumClassInstances) override; + void STDMETHODCALLTYPE IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY Topology) override; + void STDMETHODCALLTYPE VSSetShaderResources(UINT StartSlot, UINT NumViews, ID3D11ShaderResourceView *const *ppShaderResourceViews) override; + void STDMETHODCALLTYPE VSSetSamplers(UINT StartSlot, UINT NumSamplers, ID3D11SamplerState *const *ppSamplers) override; + void STDMETHODCALLTYPE Begin(ID3D11Asynchronous *pAsync) override; + void STDMETHODCALLTYPE End(ID3D11Asynchronous *pAsync) override; + HRESULT STDMETHODCALLTYPE GetData(ID3D11Asynchronous *pAsync, void *pData, UINT DataSize, UINT GetDataFlags) override; + void STDMETHODCALLTYPE SetPredication(ID3D11Predicate *pPredicate, BOOL PredicateValue) override; + void STDMETHODCALLTYPE GSSetShaderResources(UINT StartSlot, UINT NumViews, ID3D11ShaderResourceView *const *ppShaderResourceViews) override; + void STDMETHODCALLTYPE GSSetSamplers(UINT StartSlot, UINT NumSamplers, ID3D11SamplerState *const *ppSamplers) override; + void STDMETHODCALLTYPE OMSetRenderTargets(UINT NumViews, ID3D11RenderTargetView *const *ppRenderTargetViews, ID3D11DepthStencilView *pDepthStencilView) override; + void STDMETHODCALLTYPE OMSetRenderTargetsAndUnorderedAccessViews(UINT NumRTVs, ID3D11RenderTargetView *const *ppRenderTargetViews, ID3D11DepthStencilView *pDepthStencilView, UINT UAVStartSlot, UINT NumUAVs, ID3D11UnorderedAccessView *const *ppUnorderedAccessViews, const UINT *pUAVInitialCounts) override; + void STDMETHODCALLTYPE OMSetBlendState(ID3D11BlendState *pBlendState, const FLOAT BlendFactor[4], UINT SampleMask) override; + void STDMETHODCALLTYPE OMSetDepthStencilState(ID3D11DepthStencilState *pDepthStencilState, UINT StencilRef) override; + void STDMETHODCALLTYPE SOSetTargets(UINT NumBuffers, ID3D11Buffer *const *ppSOTargets, const UINT *pOffsets) override; + void STDMETHODCALLTYPE DrawAuto() override; + void STDMETHODCALLTYPE DrawIndexedInstancedIndirect(ID3D11Buffer *pBufferForArgs, UINT AlignedByteOffsetForArgs) override; + void STDMETHODCALLTYPE DrawInstancedIndirect(ID3D11Buffer *pBufferForArgs, UINT AlignedByteOffsetForArgs) override; + void STDMETHODCALLTYPE Dispatch(UINT ThreadGroupCountX, UINT ThreadGroupCountY, UINT ThreadGroupCountZ) override; + void STDMETHODCALLTYPE DispatchIndirect(ID3D11Buffer *pBufferForArgs, UINT AlignedByteOffsetForArgs) override; + void STDMETHODCALLTYPE RSSetState(ID3D11RasterizerState *pRasterizerState) override; + void STDMETHODCALLTYPE RSSetViewports(UINT NumViewports, const D3D11_VIEWPORT *pViewports) override; + void STDMETHODCALLTYPE RSSetScissorRects(UINT NumRects, const D3D11_RECT *pRects) override; + void STDMETHODCALLTYPE CopySubresourceRegion(ID3D11Resource *pDstResource, UINT DstSubresource, UINT DstX, UINT DstY, UINT DstZ, ID3D11Resource *pSrcResource, UINT SrcSubresource, const D3D11_BOX *pSrcBox) override; + void STDMETHODCALLTYPE CopyResource(ID3D11Resource *pDstResource, ID3D11Resource *pSrcResource) override; + void STDMETHODCALLTYPE UpdateSubresource(ID3D11Resource *pDstResource, UINT DstSubresource, const D3D11_BOX *pDstBox, const void *pSrcData, UINT SrcRowPitch, UINT SrcDepthPitch) override; + void STDMETHODCALLTYPE CopyStructureCount(ID3D11Buffer *pDstBuffer, UINT DstAlignedByteOffset, ID3D11UnorderedAccessView *pSrcView) override; + void STDMETHODCALLTYPE ClearRenderTargetView(ID3D11RenderTargetView *pRenderTargetView, const FLOAT ColorRGBA[4]) override; + void STDMETHODCALLTYPE ClearUnorderedAccessViewUint(ID3D11UnorderedAccessView *pUnorderedAccessView, const UINT Values[4]) override; + void STDMETHODCALLTYPE ClearUnorderedAccessViewFloat(ID3D11UnorderedAccessView *pUnorderedAccessView, const FLOAT Values[4]) override; + void STDMETHODCALLTYPE ClearDepthStencilView(ID3D11DepthStencilView *pDepthStencilView, UINT ClearFlags, FLOAT Depth, UINT8 Stencil) override; + void STDMETHODCALLTYPE GenerateMips(ID3D11ShaderResourceView *pShaderResourceView) override; + void STDMETHODCALLTYPE SetResourceMinLOD(ID3D11Resource *pResource, FLOAT MinLOD) override; + FLOAT STDMETHODCALLTYPE GetResourceMinLOD(ID3D11Resource *pResource) override; + void STDMETHODCALLTYPE ResolveSubresource(ID3D11Resource *pDstResource, UINT DstSubresource, ID3D11Resource *pSrcResource, UINT SrcSubresource, DXGI_FORMAT Format) override; + void STDMETHODCALLTYPE ExecuteCommandList(ID3D11CommandList *pCommandList, BOOL RestoreContextState) override; + void STDMETHODCALLTYPE HSSetShaderResources(UINT StartSlot, UINT NumViews, ID3D11ShaderResourceView *const *ppShaderResourceViews) override; + void STDMETHODCALLTYPE HSSetShader(ID3D11HullShader *pHullShader, ID3D11ClassInstance *const *ppClassInstances, UINT NumClassInstances) override; + void STDMETHODCALLTYPE HSSetSamplers(UINT StartSlot, UINT NumSamplers, ID3D11SamplerState *const *ppSamplers) override; + void STDMETHODCALLTYPE HSSetConstantBuffers(UINT StartSlot, UINT NumBuffers, ID3D11Buffer *const *ppConstantBuffers) override; + void STDMETHODCALLTYPE DSSetShaderResources(UINT StartSlot, UINT NumViews, ID3D11ShaderResourceView *const *ppShaderResourceViews) override; + void STDMETHODCALLTYPE DSSetShader(ID3D11DomainShader *pDomainShader, ID3D11ClassInstance *const *ppClassInstances, UINT NumClassInstances) override; + void STDMETHODCALLTYPE DSSetSamplers(UINT StartSlot, UINT NumSamplers, ID3D11SamplerState *const *ppSamplers) override; + void STDMETHODCALLTYPE DSSetConstantBuffers(UINT StartSlot, UINT NumBuffers, ID3D11Buffer *const *ppConstantBuffers) override; + void STDMETHODCALLTYPE CSSetShaderResources(UINT StartSlot, UINT NumViews, ID3D11ShaderResourceView *const *ppShaderResourceViews) override; + void STDMETHODCALLTYPE CSSetUnorderedAccessViews(UINT StartSlot, UINT NumUAVs, ID3D11UnorderedAccessView *const *ppUnorderedAccessViews, const UINT *pUAVInitialCounts) override; + void STDMETHODCALLTYPE CSSetShader(ID3D11ComputeShader *pComputeShader, ID3D11ClassInstance *const *ppClassInstances, UINT NumClassInstances) override; + void STDMETHODCALLTYPE CSSetSamplers(UINT StartSlot, UINT NumSamplers, ID3D11SamplerState *const *ppSamplers) override; + void STDMETHODCALLTYPE CSSetConstantBuffers(UINT StartSlot, UINT NumBuffers, ID3D11Buffer *const *ppConstantBuffers) override; + void STDMETHODCALLTYPE VSGetConstantBuffers(UINT StartSlot, UINT NumBuffers, ID3D11Buffer **ppConstantBuffers) override; + void STDMETHODCALLTYPE PSGetShaderResources(UINT StartSlot, UINT NumViews, ID3D11ShaderResourceView **ppShaderResourceViews) override; + void STDMETHODCALLTYPE PSGetShader(ID3D11PixelShader **ppPixelShader, ID3D11ClassInstance **ppClassInstances, UINT *pNumClassInstances) override; + void STDMETHODCALLTYPE PSGetSamplers(UINT StartSlot, UINT NumSamplers, ID3D11SamplerState **ppSamplers) override; + void STDMETHODCALLTYPE VSGetShader(ID3D11VertexShader **ppVertexShader, ID3D11ClassInstance **ppClassInstances, UINT *pNumClassInstances) override; + void STDMETHODCALLTYPE PSGetConstantBuffers(UINT StartSlot, UINT NumBuffers, ID3D11Buffer **ppConstantBuffers) override; + void STDMETHODCALLTYPE IAGetInputLayout(ID3D11InputLayout **ppInputLayout) override; + void STDMETHODCALLTYPE IAGetVertexBuffers(UINT StartSlot, UINT NumBuffers, ID3D11Buffer **ppVertexBuffers, UINT *pStrides, UINT *pOffsets) override; + void STDMETHODCALLTYPE IAGetIndexBuffer(ID3D11Buffer **pIndexBuffer, DXGI_FORMAT *Format, UINT *Offset) override; + void STDMETHODCALLTYPE GSGetConstantBuffers(UINT StartSlot, UINT NumBuffers, ID3D11Buffer **ppConstantBuffers) override; + void STDMETHODCALLTYPE GSGetShader(ID3D11GeometryShader **ppGeometryShader, ID3D11ClassInstance **ppClassInstances, UINT *pNumClassInstances) override; + void STDMETHODCALLTYPE IAGetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY *pTopology) override; + void STDMETHODCALLTYPE VSGetShaderResources(UINT StartSlot, UINT NumViews, ID3D11ShaderResourceView **ppShaderResourceViews) override; + void STDMETHODCALLTYPE VSGetSamplers(UINT StartSlot, UINT NumSamplers, ID3D11SamplerState **ppSamplers) override; + void STDMETHODCALLTYPE GetPredication(ID3D11Predicate **ppPredicate, BOOL *pPredicateValue) override; + void STDMETHODCALLTYPE GSGetShaderResources(UINT StartSlot, UINT NumViews, ID3D11ShaderResourceView **ppShaderResourceViews) override; + void STDMETHODCALLTYPE GSGetSamplers(UINT StartSlot, UINT NumSamplers, ID3D11SamplerState **ppSamplers) override; + void STDMETHODCALLTYPE OMGetRenderTargets(UINT NumViews, ID3D11RenderTargetView **ppRenderTargetViews, ID3D11DepthStencilView **ppDepthStencilView) override; + void STDMETHODCALLTYPE OMGetRenderTargetsAndUnorderedAccessViews(UINT NumRTVs, ID3D11RenderTargetView **ppRenderTargetViews, ID3D11DepthStencilView **ppDepthStencilView, UINT UAVStartSlot, UINT NumUAVs, ID3D11UnorderedAccessView **ppUnorderedAccessViews) override; + void STDMETHODCALLTYPE OMGetBlendState(ID3D11BlendState **ppBlendState, FLOAT BlendFactor[4], UINT *pSampleMask) override; + void STDMETHODCALLTYPE OMGetDepthStencilState(ID3D11DepthStencilState **ppDepthStencilState, UINT *pStencilRef) override; + void STDMETHODCALLTYPE SOGetTargets(UINT NumBuffers, ID3D11Buffer **ppSOTargets) override; + void STDMETHODCALLTYPE RSGetState(ID3D11RasterizerState **ppRasterizerState) override; + void STDMETHODCALLTYPE RSGetViewports(UINT *pNumViewports, D3D11_VIEWPORT *pViewports) override; + void STDMETHODCALLTYPE RSGetScissorRects(UINT *pNumRects, D3D11_RECT *pRects) override; + void STDMETHODCALLTYPE HSGetShaderResources(UINT StartSlot, UINT NumViews, ID3D11ShaderResourceView **ppShaderResourceViews) override; + void STDMETHODCALLTYPE HSGetShader(ID3D11HullShader **ppHullShader, ID3D11ClassInstance **ppClassInstances, UINT *pNumClassInstances) override; + void STDMETHODCALLTYPE HSGetSamplers(UINT StartSlot, UINT NumSamplers, ID3D11SamplerState **ppSamplers) override; + void STDMETHODCALLTYPE HSGetConstantBuffers(UINT StartSlot, UINT NumBuffers, ID3D11Buffer **ppConstantBuffers) override; + void STDMETHODCALLTYPE DSGetShaderResources(UINT StartSlot, UINT NumViews, ID3D11ShaderResourceView **ppShaderResourceViews) override; + void STDMETHODCALLTYPE DSGetShader(ID3D11DomainShader **ppDomainShader, ID3D11ClassInstance **ppClassInstances, UINT *pNumClassInstances) override; + void STDMETHODCALLTYPE DSGetSamplers(UINT StartSlot, UINT NumSamplers, ID3D11SamplerState **ppSamplers) override; + void STDMETHODCALLTYPE DSGetConstantBuffers(UINT StartSlot, UINT NumBuffers, ID3D11Buffer **ppConstantBuffers) override; + void STDMETHODCALLTYPE CSGetShaderResources(UINT StartSlot, UINT NumViews, ID3D11ShaderResourceView **ppShaderResourceViews) override; + void STDMETHODCALLTYPE CSGetUnorderedAccessViews(UINT StartSlot, UINT NumUAVs, ID3D11UnorderedAccessView **ppUnorderedAccessViews) override; + void STDMETHODCALLTYPE CSGetShader(ID3D11ComputeShader **ppComputeShader, ID3D11ClassInstance **ppClassInstances, UINT *pNumClassInstances) override; + void STDMETHODCALLTYPE CSGetSamplers(UINT StartSlot, UINT NumSamplers, ID3D11SamplerState **ppSamplers) override; + void STDMETHODCALLTYPE CSGetConstantBuffers(UINT StartSlot, UINT NumBuffers, ID3D11Buffer **ppConstantBuffers) override; + void STDMETHODCALLTYPE ClearState() override; + void STDMETHODCALLTYPE Flush() override; + UINT STDMETHODCALLTYPE GetContextFlags() override; + HRESULT STDMETHODCALLTYPE FinishCommandList(BOOL RestoreDeferredContextState, ID3D11CommandList **ppCommandList) override; + D3D11_DEVICE_CONTEXT_TYPE STDMETHODCALLTYPE GetType() override; + #pragma endregion + #pragma region ID3D11DeviceContext1 + void STDMETHODCALLTYPE CopySubresourceRegion1(ID3D11Resource *pDstResource, UINT DstSubresource, UINT DstX, UINT DstY, UINT DstZ, ID3D11Resource *pSrcResource, UINT SrcSubresource, const D3D11_BOX *pSrcBox, UINT CopyFlags) override; + void STDMETHODCALLTYPE UpdateSubresource1(ID3D11Resource *pDstResource, UINT DstSubresource, const D3D11_BOX *pDstBox, const void *pSrcData, UINT SrcRowPitch, UINT SrcDepthPitch, UINT CopyFlags) override; + void STDMETHODCALLTYPE DiscardResource(ID3D11Resource *pResource) override; + void STDMETHODCALLTYPE DiscardView(ID3D11View *pResourceView) override; + void STDMETHODCALLTYPE VSSetConstantBuffers1(UINT StartSlot, UINT NumBuffers, ID3D11Buffer *const *ppConstantBuffers, const UINT *pFirstConstant, const UINT *pNumConstants) override; + void STDMETHODCALLTYPE HSSetConstantBuffers1(UINT StartSlot, UINT NumBuffers, ID3D11Buffer *const *ppConstantBuffers, const UINT *pFirstConstant, const UINT *pNumConstants) override; + void STDMETHODCALLTYPE DSSetConstantBuffers1(UINT StartSlot, UINT NumBuffers, ID3D11Buffer *const *ppConstantBuffers, const UINT *pFirstConstant, const UINT *pNumConstants) override; + void STDMETHODCALLTYPE GSSetConstantBuffers1(UINT StartSlot, UINT NumBuffers, ID3D11Buffer *const *ppConstantBuffers, const UINT *pFirstConstant, const UINT *pNumConstants) override; + void STDMETHODCALLTYPE PSSetConstantBuffers1(UINT StartSlot, UINT NumBuffers, ID3D11Buffer *const *ppConstantBuffers, const UINT *pFirstConstant, const UINT *pNumConstants) override; + void STDMETHODCALLTYPE CSSetConstantBuffers1(UINT StartSlot, UINT NumBuffers, ID3D11Buffer *const *ppConstantBuffers, const UINT *pFirstConstant, const UINT *pNumConstants) override; + void STDMETHODCALLTYPE VSGetConstantBuffers1(UINT StartSlot, UINT NumBuffers, ID3D11Buffer **ppConstantBuffers, UINT *pFirstConstant, UINT *pNumConstants) override; + void STDMETHODCALLTYPE HSGetConstantBuffers1(UINT StartSlot, UINT NumBuffers, ID3D11Buffer **ppConstantBuffers, UINT *pFirstConstant, UINT *pNumConstants) override; + void STDMETHODCALLTYPE DSGetConstantBuffers1(UINT StartSlot, UINT NumBuffers, ID3D11Buffer **ppConstantBuffers, UINT *pFirstConstant, UINT *pNumConstants) override; + void STDMETHODCALLTYPE GSGetConstantBuffers1(UINT StartSlot, UINT NumBuffers, ID3D11Buffer **ppConstantBuffers, UINT *pFirstConstant, UINT *pNumConstants) override; + void STDMETHODCALLTYPE PSGetConstantBuffers1(UINT StartSlot, UINT NumBuffers, ID3D11Buffer **ppConstantBuffers, UINT *pFirstConstant, UINT *pNumConstants) override; + void STDMETHODCALLTYPE CSGetConstantBuffers1(UINT StartSlot, UINT NumBuffers, ID3D11Buffer **ppConstantBuffers, UINT *pFirstConstant, UINT *pNumConstants) override; + void STDMETHODCALLTYPE SwapDeviceContextState(ID3DDeviceContextState *pState, ID3DDeviceContextState **ppPreviousState) override; + void STDMETHODCALLTYPE ClearView(ID3D11View *pView, const FLOAT Color[4], const D3D11_RECT *pRect, UINT NumRects) override; + void STDMETHODCALLTYPE DiscardView1(ID3D11View *pResourceView, const D3D11_RECT *pRects, UINT NumRects) override; + #pragma endregion + #pragma region ID3D11DeviceContext2 + HRESULT STDMETHODCALLTYPE UpdateTileMappings(ID3D11Resource *pTiledResource, UINT NumTiledResourceRegions, const D3D11_TILED_RESOURCE_COORDINATE *pTiledResourceRegionStartCoordinates, const D3D11_TILE_REGION_SIZE *pTiledResourceRegionSizes, ID3D11Buffer *pTilePool, UINT NumRanges, const UINT *pRangeFlags, const UINT *pTilePoolStartOffsets, const UINT *pRangeTileCounts, UINT Flags) override; + HRESULT STDMETHODCALLTYPE CopyTileMappings(ID3D11Resource *pDestTiledResource, const D3D11_TILED_RESOURCE_COORDINATE *pDestRegionStartCoordinate, ID3D11Resource *pSourceTiledResource, const D3D11_TILED_RESOURCE_COORDINATE *pSourceRegionStartCoordinate, const D3D11_TILE_REGION_SIZE *pTileRegionSize, UINT Flags) override; + void STDMETHODCALLTYPE CopyTiles(ID3D11Resource *pTiledResource, const D3D11_TILED_RESOURCE_COORDINATE *pTileRegionStartCoordinate, const D3D11_TILE_REGION_SIZE *pTileRegionSize, ID3D11Buffer *pBuffer, UINT64 BufferStartOffsetInBytes, UINT Flags) override; + void STDMETHODCALLTYPE UpdateTiles(ID3D11Resource *pDestTiledResource, const D3D11_TILED_RESOURCE_COORDINATE *pDestTileRegionStartCoordinate, const D3D11_TILE_REGION_SIZE *pDestTileRegionSize, const void *pSourceTileData, UINT Flags) override; + HRESULT STDMETHODCALLTYPE ResizeTilePool(ID3D11Buffer *pTilePool, UINT64 NewSizeInBytes) override; + void STDMETHODCALLTYPE TiledResourceBarrier(ID3D11DeviceChild *pTiledResourceOrViewAccessBeforeBarrier, ID3D11DeviceChild *pTiledResourceOrViewAccessAfterBarrier) override; + BOOL STDMETHODCALLTYPE IsAnnotationEnabled() override; + void STDMETHODCALLTYPE SetMarkerInt(LPCWSTR pLabel, INT Data) override; + void STDMETHODCALLTYPE BeginEventInt(LPCWSTR pLabel, INT Data) override; + void STDMETHODCALLTYPE EndEvent() override; + #pragma endregion + #pragma region ID3D11DeviceContext3 + void STDMETHODCALLTYPE Flush1(D3D11_CONTEXT_TYPE ContextType, HANDLE hEvent) override; + void STDMETHODCALLTYPE SetHardwareProtectionState(BOOL HwProtectionEnable) override; + void STDMETHODCALLTYPE GetHardwareProtectionState(BOOL *pHwProtectionEnable) override; + #pragma endregion + #pragma region ID3D11DeviceContext4 + HRESULT STDMETHODCALLTYPE Signal(ID3D11Fence *pFence, UINT64 Value) override; + HRESULT STDMETHODCALLTYPE Wait(ID3D11Fence *pFence, UINT64 Value) override; + #pragma endregion + + void clear_drawcall_stats(); + +#if RESHADE_DX11_CAPTURE_DEPTH_BUFFERS + bool save_depth_texture(ID3D11DepthStencilView *pDepthStencilView, bool cleared); + + void track_active_rendertargets(UINT NumViews, ID3D11RenderTargetView *const *ppRenderTargetViews, ID3D11DepthStencilView *pDepthStencilView); + void track_cleared_depthstencil(ID3D11DepthStencilView* pDepthStencilView); +#endif + + bool check_and_upgrade_interface(REFIID riid); + + LONG _ref = 1; + ID3D11DeviceContext *_orig; + unsigned int _interface_version; + D3D11Device *const _device; + reshade::d3d11::draw_call_tracker _draw_call_tracker; +}; diff --git a/msvc/source/d3d11/draw_call_tracker.cpp b/msvc/source/d3d11/draw_call_tracker.cpp new file mode 100644 index 0000000..f6622a2 --- /dev/null +++ b/msvc/source/d3d11/draw_call_tracker.cpp @@ -0,0 +1,285 @@ +#include "draw_call_tracker.hpp" +#include "log.hpp" +#include "dxgi/format_utils.hpp" +#include + +namespace reshade::d3d11 +{ + void draw_call_tracker::merge(const draw_call_tracker& source) + { + _global_counter.vertices += source.total_vertices(); + _global_counter.drawcalls += source.total_drawcalls(); + +#if RESHADE_DX11_CAPTURE_DEPTH_BUFFERS + for (const auto &[depthstencil, snapshot] : source._counters_per_used_depthstencil) + { + _counters_per_used_depthstencil[depthstencil].stats.vertices += snapshot.stats.vertices; + _counters_per_used_depthstencil[depthstencil].stats.drawcalls += snapshot.stats.drawcalls; + } + + for (auto source_entry : source._cleared_depth_textures) + { + const auto destination_entry = _cleared_depth_textures.find(source_entry.first); + + if (destination_entry == _cleared_depth_textures.end()) + _cleared_depth_textures.emplace(source_entry.first, source_entry.second); + } +#endif +#if RESHADE_DX11_CAPTURE_CONSTANT_BUFFERS + for (const auto &[buffer, snapshot] : source._counters_per_constant_buffer) + { + _counters_per_constant_buffer[buffer].vertices += snapshot.vertices; + _counters_per_constant_buffer[buffer].drawcalls += snapshot.drawcalls; + _counters_per_constant_buffer[buffer].ps_uses += snapshot.ps_uses; + _counters_per_constant_buffer[buffer].vs_uses += snapshot.vs_uses; + } +#endif + } + + void draw_call_tracker::reset() + { + _global_counter.vertices = 0; + _global_counter.drawcalls = 0; +#if RESHADE_DX11_CAPTURE_DEPTH_BUFFERS + _counters_per_used_depthstencil.clear(); + _cleared_depth_textures.clear(); +#endif +#if RESHADE_DX11_CAPTURE_CONSTANT_BUFFERS + _counters_per_constant_buffer.clear(); +#endif + } + + void draw_call_tracker::on_map(ID3D11Resource *resource) + { + UNREFERENCED_PARAMETER(resource); + +#if RESHADE_DX11_CAPTURE_CONSTANT_BUFFERS + D3D11_RESOURCE_DIMENSION dim; + resource->GetType(&dim); + + if (dim == D3D11_RESOURCE_DIMENSION_BUFFER) + _counters_per_constant_buffer[static_cast(resource)].mapped += 1; +#endif + } + + void draw_call_tracker::on_draw(ID3D11DeviceContext *context, UINT vertices) + { + UNREFERENCED_PARAMETER(context); + + _global_counter.vertices += vertices; + _global_counter.drawcalls += 1; + +#if RESHADE_DX11_CAPTURE_DEPTH_BUFFERS + com_ptr targets[D3D11_SIMULTANEOUS_RENDER_TARGET_COUNT]; + com_ptr depthstencil; + + context->OMGetRenderTargets(D3D11_SIMULTANEOUS_RENDER_TARGET_COUNT, reinterpret_cast(targets), &depthstencil); + + if (depthstencil == nullptr) + // This is a draw call with no depth stencil + return; + + if (const auto intermediate_snapshot = _counters_per_used_depthstencil.find(depthstencil); intermediate_snapshot != _counters_per_used_depthstencil.end()) + { + intermediate_snapshot->second.stats.vertices += vertices; + intermediate_snapshot->second.stats.drawcalls += 1; + + // Find the render targets, if they exist, and update their counts + for (UINT i = 0; i < D3D11_SIMULTANEOUS_RENDER_TARGET_COUNT; i++) + { + // Ignore empty slots + if (targets[i] == nullptr) + continue; + + if (const auto it = intermediate_snapshot->second.additional_views.find(targets[i].get()); it != intermediate_snapshot->second.additional_views.end()) + { + it->second.vertices += vertices; + it->second.drawcalls += 1; + } + else + { + // This shouldn't happen - it means somehow someone has called 'on_draw' with a render target without calling 'track_rendertargets' first + assert(false); + } + } + } +#endif +#if RESHADE_DX11_CAPTURE_CONSTANT_BUFFERS + // Capture constant buffers that are used when depth stencils are drawn + com_ptr vscbuffers[D3D11_COMMONSHADER_CONSTANT_BUFFER_API_SLOT_COUNT]; + context->VSGetConstantBuffers(0, ARRAYSIZE(vscbuffers), reinterpret_cast(vscbuffers)); + + for (UINT i = 0; i < ARRAYSIZE(vscbuffers); i++) + // Uses the default drawcalls = 0 the first time around. + if (vscbuffers[i] != nullptr) + _counters_per_constant_buffer[vscbuffers[i]].vs_uses += 1; + + com_ptr pscbuffers[D3D11_COMMONSHADER_CONSTANT_BUFFER_API_SLOT_COUNT]; + context->PSGetConstantBuffers(0, ARRAYSIZE(pscbuffers), reinterpret_cast(pscbuffers)); + + for (UINT i = 0; i < ARRAYSIZE(pscbuffers); i++) + // Uses the default drawcalls = 0 the first time around. + if (pscbuffers[i] != nullptr) + _counters_per_constant_buffer[pscbuffers[i]].ps_uses += 1; +#endif + } + +#if RESHADE_DX11_CAPTURE_DEPTH_BUFFERS + bool draw_call_tracker::check_depthstencil(ID3D11DepthStencilView *depthstencil) const + { + return _counters_per_used_depthstencil.find(depthstencil) != _counters_per_used_depthstencil.end(); + } + bool draw_call_tracker::check_depth_texture_format(int format_index, ID3D11DepthStencilView *depthstencil) + { + assert(depthstencil != nullptr); + + // Do not check format if all formats are allowed (index zero is DXGI_FORMAT_UNKNOWN) + if (format_index == 0) + return true; + + // Retrieve texture from depth stencil + com_ptr resource; + com_ptr texture; + depthstencil->GetResource(&resource); + if (FAILED(resource->QueryInterface(&texture))) + return false; + + D3D11_TEXTURE2D_DESC desc; + texture->GetDesc(&desc); + + const DXGI_FORMAT depth_texture_formats[] = { + DXGI_FORMAT_UNKNOWN, + DXGI_FORMAT_R16_TYPELESS, + DXGI_FORMAT_R32_TYPELESS, + DXGI_FORMAT_R24G8_TYPELESS, + DXGI_FORMAT_R32G8X24_TYPELESS + }; + + assert(format_index > 0 && format_index < ARRAYSIZE(depth_texture_formats)); + + return make_dxgi_format_typeless(desc.Format) == depth_texture_formats[format_index]; + } + + void draw_call_tracker::track_rendertargets(int format_index, ID3D11DepthStencilView *depthstencil, UINT num_views, ID3D11RenderTargetView *const *views) + { + assert(depthstencil != nullptr); + + if (!check_depth_texture_format(format_index, depthstencil)) + return; + + if (_counters_per_used_depthstencil[depthstencil].depthstencil == nullptr) + _counters_per_used_depthstencil[depthstencil].depthstencil = depthstencil; + + for (UINT i = 0; i < num_views; i++) + // If the render target isn't being tracked, this will create it + _counters_per_used_depthstencil[depthstencil].additional_views[views[i]].drawcalls += 1; + } + void draw_call_tracker::track_depth_texture(int format_index, UINT index, com_ptr src_texture, com_ptr src_depthstencil, com_ptr dest_texture, bool cleared) + { + // Function that keeps track of a cleared depth texture in an ordered map in order to retrieve it at the final rendering stage + assert(src_texture != nullptr); + + if (!check_depth_texture_format(format_index, src_depthstencil.get())) + return; + + // Gather some extra info for later display + D3D11_TEXTURE2D_DESC src_texture_desc; + src_texture->GetDesc(&src_texture_desc); + + // check if it is really a depth texture + assert((src_texture_desc.BindFlags & D3D11_BIND_DEPTH_STENCIL) != 0); + + // fill the ordered map with the saved depth texture + if (const auto it = _cleared_depth_textures.find(index); it == _cleared_depth_textures.end()) + _cleared_depth_textures.emplace(index, depth_texture_save_info { src_texture, src_depthstencil, src_texture_desc, dest_texture, cleared }); + else + it->second = depth_texture_save_info { src_texture, src_depthstencil, src_texture_desc, dest_texture, cleared }; + } + + draw_call_tracker::intermediate_snapshot_info draw_call_tracker::find_best_snapshot(UINT width, UINT height) + { + const float aspect_ratio = float(width) / float(height); + intermediate_snapshot_info best_snapshot; + + for (auto &[depthstencil, snapshot] : _counters_per_used_depthstencil) + { + if (snapshot.stats.drawcalls == 0 || snapshot.stats.vertices == 0) + continue; + + if (snapshot.texture == nullptr) + { + com_ptr resource; + depthstencil->GetResource(&resource); + if (FAILED(resource->QueryInterface(&snapshot.texture))) + continue; + } + + D3D11_TEXTURE2D_DESC desc; + snapshot.texture->GetDesc(&desc); + + assert((desc.BindFlags & D3D11_BIND_DEPTH_STENCIL) != 0); + + // Check aspect ratio + const float width_factor = desc.Width != width ? float(width) / desc.Width : 1.0f; + const float height_factor = desc.Height != height ? float(height) / desc.Height : 1.0f; + const float texture_aspect_ratio = float(desc.Width) / float(desc.Height); + + if (fabs(texture_aspect_ratio - aspect_ratio) > 0.1f || width_factor > 2.0f || height_factor > 2.0f || width_factor < 0.5f || height_factor < 0.5f) + continue; // No match, not a good fit + + if (snapshot.stats.drawcalls >= best_snapshot.stats.drawcalls) + best_snapshot = snapshot; + } + + return best_snapshot; + } + + void draw_call_tracker::keep_cleared_depth_textures() + { + // Function that keeps only the depth textures that has been retrieved before the last depth stencil clearance + std::map::reverse_iterator it = _cleared_depth_textures.rbegin(); + + // Reverse loop on the cleared depth textures map + while (it != _cleared_depth_textures.rend()) + { + // Exit if the last cleared depth stencil is found + if (it->second.cleared) + return; + + // Remove the depth texture if it was retrieved after the last clearance of the depth stencil + it = std::map::reverse_iterator(_cleared_depth_textures.erase(std::next(it).base())); + } + } + + ID3D11Texture2D *draw_call_tracker::find_best_cleared_depth_buffer_texture(UINT clear_index) + { + // Function that selects the best cleared depth texture according to the clearing number defined in the configuration settings + ID3D11Texture2D *best_match = nullptr; + + // Ensure to work only on the depth textures retrieved before the last depth stencil clearance + keep_cleared_depth_textures(); + + for (const auto &it : _cleared_depth_textures) + { + UINT i = it.first; + auto &texture_counter_info = it.second; + + com_ptr texture; + if (texture_counter_info.dest_texture == nullptr) + continue; + texture = texture_counter_info.dest_texture; + + if (clear_index != 0 && i > clear_index) + continue; + + // The _cleared_dept_textures ordered map stores the depth textures, according to the order of clearing + // if clear_index == 0, the auto select mode is defined, so the last cleared depth texture is retrieved + // if the user selects a clearing number and the number of cleared depth textures is greater or equal than it, the texture corresponding to this number is retrieved + // if the user selects a clearing number and the number of cleared depth textures is lower than it, the last cleared depth texture is retrieved + best_match = texture.get(); + } + + return best_match; + } +#endif +} diff --git a/msvc/source/d3d11/draw_call_tracker.hpp b/msvc/source/d3d11/draw_call_tracker.hpp new file mode 100644 index 0000000..fa97511 --- /dev/null +++ b/msvc/source/d3d11/draw_call_tracker.hpp @@ -0,0 +1,86 @@ +#pragma once + +#include +#include +#include "com_ptr.hpp" + +#define RESHADE_DX11_CAPTURE_DEPTH_BUFFERS 1 +#define RESHADE_DX11_CAPTURE_CONSTANT_BUFFERS 0 + +namespace reshade::d3d11 +{ + class draw_call_tracker + { + public: + struct draw_stats + { + UINT vertices = 0; + UINT drawcalls = 0; + UINT mapped = 0; + UINT vs_uses = 0; + UINT ps_uses = 0; + }; + +#if RESHADE_DX11_CAPTURE_DEPTH_BUFFERS + struct intermediate_snapshot_info + { + ID3D11DepthStencilView *depthstencil = nullptr; // No need to use a 'com_ptr' here since '_counters_per_used_depthstencil' already keeps a reference + draw_stats stats; + com_ptr texture; + std::map additional_views; + }; +#endif + + UINT total_vertices() const { return _global_counter.vertices; } + UINT total_drawcalls() const { return _global_counter.drawcalls; } + +#if RESHADE_DX11_CAPTURE_DEPTH_BUFFERS + const auto &depth_buffer_counters() const { return _counters_per_used_depthstencil; } + const auto &cleared_depth_textures() const { return _cleared_depth_textures; } +#endif +#if RESHADE_DX11_CAPTURE_CONSTANT_BUFFERS + const auto &constant_buffer_counters() const { return _counters_per_constant_buffer; } +#endif + + void merge(const draw_call_tracker &source); + void reset(); + + void on_map(ID3D11Resource *pResource); + void on_draw(ID3D11DeviceContext *context, UINT vertices); + +#if RESHADE_DX11_CAPTURE_DEPTH_BUFFERS + void track_rendertargets(int format_index, ID3D11DepthStencilView *depthstencil, UINT num_views, ID3D11RenderTargetView *const *views); + void track_depth_texture(int format_index, UINT index, com_ptr src_texture, com_ptr src_depthstencil, com_ptr dest_texture, bool cleared); + + void keep_cleared_depth_textures(); + + intermediate_snapshot_info find_best_snapshot(UINT width, UINT height); + ID3D11Texture2D *find_best_cleared_depth_buffer_texture(UINT clear_index); +#endif + + private: + struct depth_texture_save_info + { + com_ptr src_texture; + com_ptr src_depthstencil; + D3D11_TEXTURE2D_DESC src_texture_desc; + com_ptr dest_texture; + bool cleared = false; + }; + +#if RESHADE_DX11_CAPTURE_DEPTH_BUFFERS + bool check_depthstencil(ID3D11DepthStencilView *depthstencil) const; + bool check_depth_texture_format(int format_index, ID3D11DepthStencilView *depthstencil); +#endif + + draw_stats _global_counter; +#if RESHADE_DX11_CAPTURE_DEPTH_BUFFERS + // Use "std::map" instead of "std::unordered_map" so that the iteration order is guaranteed + std::map, intermediate_snapshot_info> _counters_per_used_depthstencil; + std::map _cleared_depth_textures; +#endif +#if RESHADE_DX11_CAPTURE_CONSTANT_BUFFERS + std::map, draw_stats> _counters_per_constant_buffer; +#endif + }; +} diff --git a/msvc/source/d3d11/runtime_d3d11.cpp b/msvc/source/d3d11/runtime_d3d11.cpp new file mode 100644 index 0000000..d2b12c3 --- /dev/null +++ b/msvc/source/d3d11/runtime_d3d11.cpp @@ -0,0 +1,1596 @@ +/** + * Copyright (C) 2014 Patrick Mours. All rights reserved. + * License: https://github.com/crosire/reshade#license + */ + +#include "log.hpp" +#include "ini_file.hpp" +#include "runtime_d3d11.hpp" +#include "runtime_objects.hpp" +#include "resource_loading.hpp" +#include "dxgi/format_utils.hpp" +#include +#include + +namespace reshade::d3d11 +{ + struct d3d11_tex_data : base_object + { + com_ptr texture; + com_ptr srv[2]; + com_ptr rtv[2]; + }; + struct d3d11_pass_data : base_object + { + com_ptr vertex_shader; + com_ptr pixel_shader; + com_ptr blend_state; + com_ptr depth_stencil_state; + UINT stencil_reference; + bool clear_render_targets; + com_ptr render_targets[D3D11_SIMULTANEOUS_RENDER_TARGET_COUNT]; + com_ptr render_target_resources[D3D11_SIMULTANEOUS_RENDER_TARGET_COUNT]; + D3D11_VIEWPORT viewport; + std::vector> shader_resources; + }; + struct d3d11_technique_data : base_object + { + bool query_in_flight = false; + com_ptr timestamp_disjoint; + com_ptr timestamp_query_beg; + com_ptr timestamp_query_end; + std::vector> sampler_states; + std::vector> texture_bindings; + ptrdiff_t uniform_storage_offset = 0; + ptrdiff_t uniform_storage_index = -1; + }; +} + +reshade::d3d11::runtime_d3d11::runtime_d3d11(ID3D11Device *device, IDXGISwapChain *swapchain) : + _device(device), _swapchain(swapchain), + _app_state(device) +{ + assert(device != nullptr); + assert(swapchain != nullptr); + + _device->GetImmediateContext(&_immediate_context); + + com_ptr dxgi_device; + _device->QueryInterface(&dxgi_device); + com_ptr dxgi_adapter; + dxgi_device->GetAdapter(&dxgi_adapter); + + _renderer_id = device->GetFeatureLevel(); + if (DXGI_ADAPTER_DESC desc; SUCCEEDED(dxgi_adapter->GetDesc(&desc))) + _vendor_id = desc.VendorId, _device_id = desc.DeviceId; + +#if RESHADE_GUI + subscribe_to_ui("DX11", [this]() { draw_debug_menu(); }); +#endif + subscribe_to_load_config([this](const ini_file &config) { + config.get("DX11_BUFFER_DETECTION", "DepthBufferRetrievalMode", depth_buffer_before_clear); + config.get("DX11_BUFFER_DETECTION", "DepthBufferTextureFormat", depth_buffer_texture_format); + config.get("DX11_BUFFER_DETECTION", "ExtendedDepthBufferDetection", extended_depth_buffer_detection); + config.get("DX11_BUFFER_DETECTION", "DepthBufferClearingNumber", cleared_depth_buffer_index); + }); + subscribe_to_save_config([this](ini_file &config) { + config.set("DX11_BUFFER_DETECTION", "DepthBufferRetrievalMode", depth_buffer_before_clear); + config.set("DX11_BUFFER_DETECTION", "DepthBufferTextureFormat", depth_buffer_texture_format); + config.set("DX11_BUFFER_DETECTION", "ExtendedDepthBufferDetection", extended_depth_buffer_detection); + config.set("DX11_BUFFER_DETECTION", "DepthBufferClearingNumber", cleared_depth_buffer_index); + }); +} +reshade::d3d11::runtime_d3d11::~runtime_d3d11() +{ + if (_d3d_compiler != nullptr) + FreeLibrary(_d3d_compiler); +} + +bool reshade::d3d11::runtime_d3d11::init_backbuffer_texture() +{ + HRESULT hr = _swapchain->GetBuffer(0, IID_PPV_ARGS(&_backbuffer)); + assert(SUCCEEDED(hr)); + + D3D11_TEXTURE2D_DESC tex_desc = {}; + tex_desc.Width = _width; + tex_desc.Height = _height; + tex_desc.MipLevels = 1; + tex_desc.ArraySize = 1; + tex_desc.Format = make_dxgi_format_typeless(_backbuffer_format); + tex_desc.SampleDesc = { 1, 0 }; + tex_desc.Usage = D3D11_USAGE_DEFAULT; + tex_desc.BindFlags = D3D11_BIND_RENDER_TARGET; + + // Creating a render target view for the back buffer fails on Windows 8+, so use a intermediate texture there + OSVERSIONINFOEX verinfo_windows7 = { sizeof(OSVERSIONINFOEX), 6, 1 }; + const bool is_windows7 = VerifyVersionInfo(&verinfo_windows7, VER_MAJORVERSION | VER_MINORVERSION, + VerSetConditionMask(VerSetConditionMask(0, VER_MAJORVERSION, VER_EQUAL), VER_MINORVERSION, VER_EQUAL)) != FALSE; + + if (_is_multisampling_enabled || + make_dxgi_format_normal(_backbuffer_format) != _backbuffer_format || + !is_windows7) + { + if (hr = _device->CreateTexture2D(&tex_desc, nullptr, &_backbuffer_resolved); FAILED(hr)) + { + LOG(ERROR) << "Failed to create back buffer resolve texture (" + "Width = " << tex_desc.Width << ", " + "Height = " << tex_desc.Height << ", " + "Format = " << tex_desc.Format << ")! HRESULT is '" << std::hex << hr << std::dec << "'."; + return false; + } + + hr = _device->CreateRenderTargetView(_backbuffer.get(), nullptr, &_backbuffer_rtv[2]); + assert(SUCCEEDED(hr)); + } + else + { + _backbuffer_resolved = _backbuffer; + } + + // Create back buffer shader texture + tex_desc.BindFlags = D3D11_BIND_SHADER_RESOURCE; + + if (hr = _device->CreateTexture2D(&tex_desc, nullptr, &_backbuffer_texture); SUCCEEDED(hr)) + { + D3D11_SHADER_RESOURCE_VIEW_DESC srv_desc = {}; + srv_desc.Format = make_dxgi_format_normal(tex_desc.Format); + srv_desc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D; + srv_desc.Texture2D.MipLevels = tex_desc.MipLevels; + + if (SUCCEEDED(hr)) + hr = _device->CreateShaderResourceView(_backbuffer_texture.get(), &srv_desc, &_backbuffer_texture_srv[0]); + else + LOG(ERROR) << "Failed to create back buffer texture resource view (" + "Format = " << srv_desc.Format << ")! HRESULT is '" << std::hex << hr << std::dec << "'."; + + srv_desc.Format = make_dxgi_format_srgb(tex_desc.Format); + + if (SUCCEEDED(hr)) + hr = _device->CreateShaderResourceView(_backbuffer_texture.get(), &srv_desc, &_backbuffer_texture_srv[1]); + else + LOG(ERROR) << "Failed to create back buffer SRGB texture resource view (" + "Format = " << srv_desc.Format << ")! HRESULT is '" << std::hex << hr << std::dec << "'."; + } + else + { + LOG(ERROR) << "Failed to create back buffer texture (" + "Width = " << tex_desc.Width << ", " + "Height = " << tex_desc.Height << ", " + "Format = " << tex_desc.Format << ")! HRESULT is '" << std::hex << hr << std::dec << "'."; + } + + if (FAILED(hr)) + return false; + + D3D11_RENDER_TARGET_VIEW_DESC rtv_desc = {}; + rtv_desc.Format = make_dxgi_format_normal(tex_desc.Format); + rtv_desc.ViewDimension = D3D11_RTV_DIMENSION_TEXTURE2D; + + if (hr = _device->CreateRenderTargetView(_backbuffer_resolved.get(), &rtv_desc, &_backbuffer_rtv[0]); FAILED(hr)) + { + LOG(ERROR) << "Failed to create back buffer render target (" + "Format = " << rtv_desc.Format << ")! HRESULT is '" << std::hex << hr << std::dec << "'."; + return false; + } + + rtv_desc.Format = make_dxgi_format_srgb(tex_desc.Format); + + if (hr = _device->CreateRenderTargetView(_backbuffer_resolved.get(), &rtv_desc, &_backbuffer_rtv[1]); FAILED(hr)) + { + LOG(ERROR) << "Failed to create back buffer SRGB render target (" + "Format = " << rtv_desc.Format << ")! HRESULT is '" << std::hex << hr << std::dec << "'."; + return false; + } + + const resources::data_resource vs = resources::load_data_resource(IDR_FULLSCREEN_VS); + if (hr = _device->CreateVertexShader(vs.data, vs.data_size, nullptr, &_copy_vertex_shader); FAILED(hr)) + return false; + const resources::data_resource ps = resources::load_data_resource(IDR_COPY_PS); + if (hr = _device->CreatePixelShader(ps.data, ps.data_size, nullptr, &_copy_pixel_shader); FAILED(hr)) + return false; + + { D3D11_SAMPLER_DESC desc = {}; + desc.Filter = D3D11_FILTER_MIN_MAG_MIP_POINT; + desc.AddressU = D3D11_TEXTURE_ADDRESS_CLAMP; + desc.AddressV = D3D11_TEXTURE_ADDRESS_CLAMP; + desc.AddressW = D3D11_TEXTURE_ADDRESS_CLAMP; + if (hr = _device->CreateSamplerState(&desc, &_copy_sampler); FAILED(hr)) + return false; + } + + { D3D11_RASTERIZER_DESC desc = {}; + desc.FillMode = D3D11_FILL_SOLID; + desc.CullMode = D3D11_CULL_NONE; + desc.DepthClipEnable = TRUE; + if (hr = _device->CreateRasterizerState(&desc, &_effect_rasterizer_state); FAILED(hr)) + return false; + } + + return true; +} +bool reshade::d3d11::runtime_d3d11::init_default_depth_stencil() +{ + const D3D11_TEXTURE2D_DESC tex_desc = { + _width, + _height, + 1, 1, + DXGI_FORMAT_D24_UNORM_S8_UINT, + { 1, 0 }, + D3D11_USAGE_DEFAULT, + D3D11_BIND_DEPTH_STENCIL + }; + + com_ptr depth_stencil_texture; + + if (HRESULT hr = _device->CreateTexture2D(&tex_desc, nullptr, &depth_stencil_texture); FAILED(hr)) + { + LOG(ERROR) << "Failed to create depth stencil texture (" + "Width = " << tex_desc.Width << ", " + "Height = " << tex_desc.Height << ", " + "Format = " << tex_desc.Format << ")! HRESULT is '" << std::hex << hr << std::dec << "'."; + return false; + } + + return SUCCEEDED(_device->CreateDepthStencilView(depth_stencil_texture.get(), nullptr, &_default_depthstencil)); +} + +bool reshade::d3d11::runtime_d3d11::on_init(const DXGI_SWAP_CHAIN_DESC &desc) +{ + RECT window_rect = {}; + GetClientRect(desc.OutputWindow, &window_rect); + + _width = desc.BufferDesc.Width; + _height = desc.BufferDesc.Height; + _window_width = window_rect.right - window_rect.left; + _window_height = window_rect.bottom - window_rect.top; + _backbuffer_format = desc.BufferDesc.Format; + _is_multisampling_enabled = desc.SampleDesc.Count > 1; + + if (!init_backbuffer_texture() || + !init_default_depth_stencil() +#if RESHADE_GUI + || !init_imgui_resources() +#endif + ) + return false; + + // Clear reference count to make UnrealEngine happy + _backbuffer->Release(); + + return runtime::on_init(desc.OutputWindow); +} +void reshade::d3d11::runtime_d3d11::on_reset() +{ + runtime::on_reset(); + + // Reset reference count to make UnrealEngine happy + _backbuffer->AddRef(); + + _backbuffer.reset(); + _backbuffer_resolved.reset(); + _backbuffer_texture.reset(); + _backbuffer_texture_srv[0].reset(); + _backbuffer_texture_srv[1].reset(); + _backbuffer_rtv[0].reset(); + _backbuffer_rtv[1].reset(); + _backbuffer_rtv[2].reset(); + + _depthstencil.reset(); + _depthstencil_replacement.reset(); + _depthstencil_texture.reset(); + _depthstencil_texture_srv.reset(); + + _depth_texture_saves.clear(); + + _default_depthstencil.reset(); + _copy_vertex_shader.reset(); + _copy_pixel_shader.reset(); + _copy_sampler.reset(); + + _effect_rasterizer_state.reset(); + + _imgui_index_buffer_size = 0; + _imgui_index_buffer.reset(); + _imgui_vertex_buffer_size = 0; + _imgui_vertex_buffer.reset(); + _imgui_vertex_shader.reset(); + _imgui_pixel_shader.reset(); + _imgui_input_layout.reset(); + _imgui_constant_buffer.reset(); + _imgui_texture_sampler.reset(); + _imgui_rasterizer_state.reset(); + _imgui_blend_state.reset(); + _imgui_depthstencil_state.reset(); +} + +void reshade::d3d11::runtime_d3d11::on_present(draw_call_tracker &tracker) +{ + if (!_is_initialized) + return; + + _vertices = tracker.total_vertices(); + _drawcalls = tracker.total_drawcalls(); + _current_tracker = &tracker; + +#if RESHADE_DX11_CAPTURE_DEPTH_BUFFERS + detect_depth_source(tracker); +#endif + _app_state.capture(_immediate_context.get()); + + // Resolve MSAA back buffer if MSAA is active + if (_backbuffer_resolved != _backbuffer) + _immediate_context->ResolveSubresource(_backbuffer_resolved.get(), 0, _backbuffer.get(), 0, _backbuffer_format); + + update_and_render_effects(); + runtime::on_present(); + + // Stretch main render target back into MSAA back buffer if MSAA is active + if (_backbuffer_resolved != _backbuffer) + { + _immediate_context->CopyResource(_backbuffer_texture.get(), _backbuffer_resolved.get()); + + _immediate_context->IASetInputLayout(nullptr); + const uintptr_t null = 0; + _immediate_context->IASetVertexBuffers(0, 1, reinterpret_cast(&null), reinterpret_cast(&null), reinterpret_cast(&null)); + _immediate_context->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST); + _immediate_context->VSSetShader(_copy_vertex_shader.get(), nullptr, 0); + _immediate_context->HSSetShader(nullptr, nullptr, 0); + _immediate_context->DSSetShader(nullptr, nullptr, 0); + _immediate_context->GSSetShader(nullptr, nullptr, 0); + _immediate_context->PSSetShader(_copy_pixel_shader.get(), nullptr, 0); + ID3D11SamplerState *const samplers[] = { _copy_sampler.get() }; + _immediate_context->PSSetSamplers(0, ARRAYSIZE(samplers), samplers); + ID3D11ShaderResourceView *const srvs[] = { _backbuffer_texture_srv[make_dxgi_format_srgb(_backbuffer_format) == _backbuffer_format].get() }; + _immediate_context->PSSetShaderResources(0, ARRAYSIZE(srvs), srvs); + _immediate_context->RSSetState(_effect_rasterizer_state.get()); + const D3D11_VIEWPORT viewport = { 0, 0, FLOAT(_width), FLOAT(_height), 0.0f, 1.0f }; + _immediate_context->RSSetViewports(1, &viewport); + _immediate_context->OMSetBlendState(nullptr, nullptr, D3D11_DEFAULT_SAMPLE_MASK); + _immediate_context->OMSetDepthStencilState(nullptr, D3D11_DEFAULT_STENCIL_REFERENCE); + ID3D11RenderTargetView *const render_targets[] = { _backbuffer_rtv[2].get() }; + _immediate_context->OMSetRenderTargets(ARRAYSIZE(render_targets), render_targets, nullptr); + + _immediate_context->Draw(3, 0); + } + + // Apply previous state from application + _app_state.apply_and_release(); +} + +void reshade::d3d11::runtime_d3d11::capture_screenshot(uint8_t *buffer) const +{ + if (_backbuffer_format != DXGI_FORMAT_R8G8B8A8_UNORM && + _backbuffer_format != DXGI_FORMAT_R8G8B8A8_UNORM_SRGB && + _backbuffer_format != DXGI_FORMAT_B8G8R8A8_UNORM && + _backbuffer_format != DXGI_FORMAT_B8G8R8A8_UNORM_SRGB) + { + LOG(WARN) << "Screenshots are not supported for back buffer format " << _backbuffer_format << '.'; + return; + } + + // Create a texture in system memory, copy back buffer data into it and map it for reading + D3D11_TEXTURE2D_DESC desc = {}; + desc.Width = _width; + desc.Height = _height; + desc.MipLevels = 1; + desc.ArraySize = 1; + desc.Format = _backbuffer_format; + desc.SampleDesc = { 1, 0 }; + desc.Usage = D3D11_USAGE_STAGING; + desc.CPUAccessFlags = D3D11_CPU_ACCESS_READ; + + com_ptr intermediate; + if (FAILED(_device->CreateTexture2D(&desc, nullptr, &intermediate))) + { + LOG(ERROR) << "Failed to create system memory texture for screenshot capture!"; + return; + } + + _immediate_context->CopyResource(intermediate.get(), _backbuffer_resolved.get()); + + D3D11_MAPPED_SUBRESOURCE mapped; + if (FAILED(_immediate_context->Map(intermediate.get(), 0, D3D11_MAP_READ, 0, &mapped))) + return; + auto mapped_data = static_cast(mapped.pData); + + for (uint32_t y = 0, pitch = _width * 4; y < _height; y++, buffer += pitch, mapped_data += mapped.RowPitch) + { + memcpy(buffer, mapped_data, pitch); + + for (uint32_t x = 0; x < pitch; x += 4) + { + buffer[x + 3] = 0xFF; // Clear alpha channel + if (_backbuffer_format == DXGI_FORMAT_B8G8R8A8_UNORM || _backbuffer_format == DXGI_FORMAT_B8G8R8A8_UNORM_SRGB) + std::swap(buffer[x + 0], buffer[x + 2]); // Format is BGRA, but output should be RGBA, so flip channels + } + } + + _immediate_context->Unmap(intermediate.get(), 0); +} + +bool reshade::d3d11::runtime_d3d11::init_texture(texture &info) +{ + info.impl = std::make_unique(); + + if (info.impl_reference != texture_reference::none) + return update_texture_reference(info); + + D3D11_TEXTURE2D_DESC desc = {}; + desc.Width = info.width; + desc.Height = info.height; + desc.MipLevels = info.levels; + desc.ArraySize = 1; + desc.SampleDesc = { 1, 0 }; + desc.Usage = D3D11_USAGE_DEFAULT; + desc.BindFlags = D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_RENDER_TARGET; + desc.MiscFlags = D3D11_RESOURCE_MISC_GENERATE_MIPS; + + switch (info.format) + { + case reshadefx::texture_format::r8: + desc.Format = DXGI_FORMAT_R8_UNORM; + break; + case reshadefx::texture_format::r16f: + desc.Format = DXGI_FORMAT_R16_FLOAT; + break; + case reshadefx::texture_format::r32f: + desc.Format = DXGI_FORMAT_R32_FLOAT; + break; + case reshadefx::texture_format::rg8: + desc.Format = DXGI_FORMAT_R8G8_UNORM; + break; + case reshadefx::texture_format::rg16: + desc.Format = DXGI_FORMAT_R16G16_UNORM; + break; + case reshadefx::texture_format::rg16f: + desc.Format = DXGI_FORMAT_R16G16_FLOAT; + break; + case reshadefx::texture_format::rg32f: + desc.Format = DXGI_FORMAT_R32G32_FLOAT; + break; + case reshadefx::texture_format::rgba8: + desc.Format = DXGI_FORMAT_R8G8B8A8_TYPELESS; + break; + case reshadefx::texture_format::rgba16: + desc.Format = DXGI_FORMAT_R16G16B16A16_UNORM; + break; + case reshadefx::texture_format::rgba16f: + desc.Format = DXGI_FORMAT_R16G16B16A16_FLOAT; + break; + case reshadefx::texture_format::rgba32f: + desc.Format = DXGI_FORMAT_R32G32B32A32_FLOAT; + break; + case reshadefx::texture_format::rgb10a2: + desc.Format = DXGI_FORMAT_R10G10B10A2_UNORM; + break; + } + + const auto texture_data = info.impl->as(); + + if (HRESULT hr = _device->CreateTexture2D(&desc, nullptr, &texture_data->texture); FAILED(hr)) + { + LOG(ERROR) << "Failed to create texture '" << info.unique_name << "' (" + "Width = " << desc.Width << ", " + "Height = " << desc.Height << ", " + "Format = " << desc.Format << ")! " + "HRESULT is '" << std::hex << hr << std::dec << "'."; + return false; + } + + D3D11_SHADER_RESOURCE_VIEW_DESC srv_desc = {}; + srv_desc.Format = make_dxgi_format_normal(desc.Format); + srv_desc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D; + srv_desc.Texture2D.MipLevels = desc.MipLevels; + + if (HRESULT hr = _device->CreateShaderResourceView(texture_data->texture.get(), &srv_desc, &texture_data->srv[0]); FAILED(hr)) + { + LOG(ERROR) << "Failed to create shader resource view for texture '" << info.unique_name << "' (" + "Format = " << srv_desc.Format << ")! " + "HRESULT is '" << std::hex << hr << std::dec << "'."; + return false; + } + + srv_desc.Format = make_dxgi_format_srgb(desc.Format); + + if (srv_desc.Format != desc.Format) + { + if (HRESULT hr = _device->CreateShaderResourceView(texture_data->texture.get(), &srv_desc, &texture_data->srv[1]); FAILED(hr)) + { + LOG(ERROR) << "Failed to create shader resource view for texture '" << info.unique_name << "' (" + "Format = " << srv_desc.Format << ")! " + "HRESULT is '" << std::hex << hr << std::dec << "'."; + return false; + } + } + else + { + texture_data->srv[1] = texture_data->srv[0]; + } + + return true; +} +void reshade::d3d11::runtime_d3d11::upload_texture(texture &texture, const uint8_t *pixels) +{ + assert(texture.impl_reference == texture_reference::none && pixels != nullptr); + + unsigned int upload_pitch; + std::vector upload_data; + + switch (texture.format) + { + case reshadefx::texture_format::r8: + upload_pitch = texture.width; + upload_data.resize(upload_pitch * texture.height); + for (uint32_t i = 0, k = 0; i < texture.width * texture.height * 4; i += 4, k += 1) + upload_data[k] = pixels[i]; + pixels = upload_data.data(); + break; + case reshadefx::texture_format::rg8: + upload_pitch = texture.width * 2; + upload_data.resize(upload_pitch * texture.height); + for (uint32_t i = 0, k = 0; i < texture.width * texture.height * 4; i += 4, k += 2) + upload_data[k + 0] = pixels[i + 0], + upload_data[k + 1] = pixels[i + 1]; + pixels = upload_data.data(); + break; + case reshadefx::texture_format::rgba8: + upload_pitch = texture.width * 4; + break; + default: + LOG(ERROR) << "Texture upload is not supported for format " << static_cast(texture.format) << '!'; + return; + } + + const auto texture_impl = texture.impl->as(); + assert(texture_impl != nullptr); + + _immediate_context->UpdateSubresource(texture_impl->texture.get(), 0, nullptr, pixels, upload_pitch, upload_pitch * texture.height); + + if (texture.levels > 1) + _immediate_context->GenerateMips(texture_impl->srv[0].get()); +} +bool reshade::d3d11::runtime_d3d11::update_texture_reference(texture &texture) +{ + com_ptr new_reference[2]; + + switch (texture.impl_reference) + { + case texture_reference::back_buffer: + new_reference[0] = _backbuffer_texture_srv[0]; + new_reference[1] = _backbuffer_texture_srv[1]; + break; + case texture_reference::depth_buffer: + new_reference[0] = _depthstencil_texture_srv; + new_reference[1] = _depthstencil_texture_srv; + break; + default: + return false; + } + + const auto texture_impl = texture.impl->as(); + assert(texture_impl != nullptr); + + if (new_reference[0] == texture_impl->srv[0] && + new_reference[1] == texture_impl->srv[1]) + return true; + + // Update references in technique list + for (const auto &technique : _techniques) + for (const auto &pass : technique.passes_data) + for (auto &srv : pass->as()->shader_resources) + if (srv == texture_impl->srv[0]) srv = new_reference[0]; + else if (srv == texture_impl->srv[1]) srv = new_reference[1]; + + texture.width = frame_width(); + texture.height = frame_height(); + + texture_impl->srv[0] = new_reference[0]; + texture_impl->srv[1] = new_reference[1]; + + return true; +} +void reshade::d3d11::runtime_d3d11::update_texture_references(texture_reference type) +{ + for (auto &tex : _textures) + if (tex.impl != nullptr && tex.impl_reference == type) + update_texture_reference(tex); +} + +bool reshade::d3d11::runtime_d3d11::compile_effect(effect_data &effect) +{ + if (_d3d_compiler == nullptr) + _d3d_compiler = LoadLibraryW(L"d3dcompiler_47.dll"); + if (_d3d_compiler == nullptr) + _d3d_compiler = LoadLibraryW(L"d3dcompiler_43.dll"); + + if (_d3d_compiler == nullptr) + { + LOG(ERROR) << "Unable to load HLSL compiler (\"d3dcompiler_47.dll\"). Make sure you have the DirectX end-user runtime (June 2010) installed or a newer version of the library in the application directory."; + return false; + } + + const auto D3DCompile = reinterpret_cast(GetProcAddress(_d3d_compiler, "D3DCompile")); + + const std::string hlsl = effect.preamble + effect.module.hlsl; + std::unordered_map> entry_points; + + // Compile the generated HLSL source code to DX byte code + for (const auto &entry_point : effect.module.entry_points) + { + std::string profile = entry_point.second ? "ps" : "vs"; + + switch (_renderer_id) + { + default: + case D3D_FEATURE_LEVEL_11_0: + profile += "_5_0"; + break; + case D3D_FEATURE_LEVEL_10_1: + profile += "_4_1"; + break; + case D3D_FEATURE_LEVEL_10_0: + profile += "_4_0"; + break; + case D3D_FEATURE_LEVEL_9_1: + case D3D_FEATURE_LEVEL_9_2: + profile += "_4_0_level_9_1"; + break; + case D3D_FEATURE_LEVEL_9_3: + profile += "_4_0_level_9_3"; + break; + } + + com_ptr d3d_compiled, d3d_errors; + + HRESULT hr = D3DCompile( + hlsl.c_str(), hlsl.size(), + nullptr, nullptr, nullptr, + entry_point.first.c_str(), + profile.c_str(), + D3DCOMPILE_ENABLE_STRICTNESS | D3DCOMPILE_OPTIMIZATION_LEVEL3, 0, + &d3d_compiled, &d3d_errors); + + if (d3d_errors != nullptr) // Append warnings to the output error string as well + effect.errors.append(static_cast(d3d_errors->GetBufferPointer()), d3d_errors->GetBufferSize() - 1); // Subtracting one to not append the null-terminator as well + + // No need to setup resources if any of the shaders failed to compile + if (FAILED(hr)) + return false; + + // Create runtime shader objects from the compiled DX byte code + if (entry_point.second) + hr = _device->CreatePixelShader(d3d_compiled->GetBufferPointer(), d3d_compiled->GetBufferSize(), nullptr, reinterpret_cast(&entry_points[entry_point.first])); + else + hr = _device->CreateVertexShader(d3d_compiled->GetBufferPointer(), d3d_compiled->GetBufferSize(), nullptr, reinterpret_cast(&entry_points[entry_point.first])); + + if (FAILED(hr)) + { + LOG(ERROR) << "Failed to create shader for entry point '" << entry_point.first << "'. " + "HRESULT is '" << std::hex << hr << std::dec << "'."; + return false; + } + } + + if (effect.storage_size != 0) + { + com_ptr cbuffer; + + const D3D11_BUFFER_DESC desc = { static_cast(effect.storage_size), D3D11_USAGE_DYNAMIC, D3D11_BIND_CONSTANT_BUFFER, D3D11_CPU_ACCESS_WRITE }; + const D3D11_SUBRESOURCE_DATA init_data = { _uniform_data_storage.data() + effect.storage_offset, static_cast(effect.storage_size) }; + + if (const HRESULT hr = _device->CreateBuffer(&desc, &init_data, &cbuffer); FAILED(hr)) + { + LOG(ERROR) << "Failed to create constant buffer for effect file " << effect.source_file << ". " + "HRESULT is '" << std::hex << hr << std::dec << "'."; + return false; + } + + _constant_buffers.push_back(std::move(cbuffer)); + } + + bool success = true; + + d3d11_technique_data technique_init; + technique_init.uniform_storage_index = _constant_buffers.size() - 1; + technique_init.uniform_storage_offset = effect.storage_offset; + + for (const reshadefx::sampler_info &info : effect.module.samplers) + success &= add_sampler(info, technique_init); + + for (technique &technique : _techniques) + if (technique.impl == nullptr && technique.effect_index == effect.index) + success &= init_technique(technique, technique_init, entry_points); + + return success; +} +void reshade::d3d11::runtime_d3d11::unload_effects() +{ + runtime::unload_effects(); + + _effect_sampler_states.clear(); + _constant_buffers.clear(); +} + +bool reshade::d3d11::runtime_d3d11::add_sampler(const reshadefx::sampler_info &info, d3d11_technique_data &technique_init) +{ + if (info.binding >= D3D11_COMMONSHADER_SAMPLER_SLOT_COUNT) + { + LOG(ERROR) << "Cannot bind sampler '" << info.unique_name << "' since it exceeds the maximum number of allowed sampler slots in D3D11 (" << info.binding << ", allowed are up to " << D3D11_COMMONSHADER_SAMPLER_SLOT_COUNT << ")."; + return false; + } + if (info.texture_binding >= D3D11_COMMONSHADER_INPUT_RESOURCE_SLOT_COUNT) + { + LOG(ERROR) << "Cannot bind texture '" << info.texture_name << "' since it exceeds the maximum number of allowed resource slots in D3D11 (" << info.texture_binding << ", allowed are up to " << D3D11_COMMONSHADER_INPUT_RESOURCE_SLOT_COUNT << ")."; + return false; + } + + const auto existing_texture = std::find_if(_textures.begin(), _textures.end(), + [&texture_name = info.texture_name](const auto &item) { + return item.unique_name == texture_name && item.impl != nullptr; + }); + + if (existing_texture == _textures.end()) + return false; + + D3D11_SAMPLER_DESC desc = {}; + desc.Filter = static_cast(info.filter); + desc.AddressU = static_cast(info.address_u); + desc.AddressV = static_cast(info.address_v); + desc.AddressW = static_cast(info.address_w); + desc.MipLODBias = info.lod_bias; + desc.MaxAnisotropy = 1; + desc.ComparisonFunc = D3D11_COMPARISON_NEVER; + desc.MinLOD = info.min_lod; + desc.MaxLOD = info.max_lod; + + // Generate hash for sampler description + size_t desc_hash = 2166136261; + for (size_t i = 0; i < sizeof(desc); ++i) + desc_hash = (desc_hash * 16777619) ^ reinterpret_cast(&desc)[i]; + + auto it = _effect_sampler_states.find(desc_hash); + if (it == _effect_sampler_states.end()) + { + com_ptr sampler; + + HRESULT hr = _device->CreateSamplerState(&desc, &sampler); + + if (FAILED(hr)) + { + LOG(ERROR) << "Failed to create sampler state for sampler '" << info.unique_name << "' (" + "Filter = " << desc.Filter << ", " + "AddressU = " << desc.AddressU << ", " + "AddressV = " << desc.AddressV << ", " + "AddressW = " << desc.AddressW << ", " + "MipLODBias = " << desc.MipLODBias << ", " + "MinLOD = " << desc.MinLOD << ", " + "MaxLOD = " << desc.MaxLOD << ")! " + "HRESULT is '" << std::hex << hr << std::dec << "'."; + return false; + } + + it = _effect_sampler_states.emplace(desc_hash, std::move(sampler)).first; + } + + technique_init.sampler_states.resize(std::max(technique_init.sampler_states.size(), size_t(info.binding + 1))); + technique_init.texture_bindings.resize(std::max(technique_init.texture_bindings.size(), size_t(info.texture_binding + 1))); + + technique_init.sampler_states[info.binding] = it->second; + technique_init.texture_bindings[info.texture_binding] = existing_texture->impl->as()->srv[info.srgb ? 1 : 0]; + + return true; +} +bool reshade::d3d11::runtime_d3d11::init_technique(technique &technique, const d3d11_technique_data &impl_init, const std::unordered_map> &entry_points) +{ + // Copy construct new technique implementation instead of move because effect may contain multiple techniques + technique.impl = std::make_unique(impl_init); + + const auto technique_data = technique.impl->as(); + + D3D11_QUERY_DESC query_desc = {}; + query_desc.Query = D3D11_QUERY_TIMESTAMP; + _device->CreateQuery(&query_desc, &technique_data->timestamp_query_beg); + _device->CreateQuery(&query_desc, &technique_data->timestamp_query_end); + query_desc.Query = D3D11_QUERY_TIMESTAMP_DISJOINT; + _device->CreateQuery(&query_desc, &technique_data->timestamp_disjoint); + + for (size_t pass_index = 0; pass_index < technique.passes.size(); ++pass_index) + { + technique.passes_data.push_back(std::make_unique()); + + auto &pass = *technique.passes_data.back()->as(); + const auto &pass_info = technique.passes[pass_index]; + + entry_points.at(pass_info.ps_entry_point)->QueryInterface(&pass.pixel_shader); + entry_points.at(pass_info.vs_entry_point)->QueryInterface(&pass.vertex_shader); + + pass.viewport.MaxDepth = 1.0f; + pass.viewport.Width = static_cast(pass_info.viewport_width); + pass.viewport.Height = static_cast(pass_info.viewport_height); + + pass.shader_resources = technique_data->texture_bindings; + pass.clear_render_targets = pass_info.clear_render_targets; + + const int target_index = pass_info.srgb_write_enable ? 1 : 0; + pass.render_targets[0] = _backbuffer_rtv[target_index]; + pass.render_target_resources[0] = _backbuffer_texture_srv[target_index]; + + for (unsigned int k = 0; k < 8; k++) + { + if (pass_info.render_target_names[k].empty()) + continue; // Skip unbound render targets + + const auto render_target_texture = std::find_if(_textures.begin(), _textures.end(), + [&render_target = pass_info.render_target_names[k]](const auto &item) { + return item.unique_name == render_target; + }); + + if (render_target_texture == _textures.end()) + return assert(false), false; + + const auto texture_impl = render_target_texture->impl->as(); + assert(texture_impl != nullptr); + + D3D11_TEXTURE2D_DESC desc; + texture_impl->texture->GetDesc(&desc); + + D3D11_RENDER_TARGET_VIEW_DESC rtv_desc = {}; + rtv_desc.Format = pass_info.srgb_write_enable ? make_dxgi_format_srgb(desc.Format) : make_dxgi_format_normal(desc.Format); + rtv_desc.ViewDimension = desc.SampleDesc.Count > 1 ? D3D11_RTV_DIMENSION_TEXTURE2DMS : D3D11_RTV_DIMENSION_TEXTURE2D; + + if (texture_impl->rtv[target_index] == nullptr) + if (HRESULT hr = _device->CreateRenderTargetView(texture_impl->texture.get(), &rtv_desc, &texture_impl->rtv[target_index]); FAILED(hr)) + { + LOG(ERROR) << "Failed to create render target view for texture '" << render_target_texture->unique_name << "' (" + "Format = " << rtv_desc.Format << ")! " + "HRESULT is '" << std::hex << hr << std::dec << "'."; + return false; + } + + pass.render_targets[k] = texture_impl->rtv[target_index]; + pass.render_target_resources[k] = texture_impl->srv[target_index]; + } + + if (pass.viewport.Width == 0) + { + pass.viewport.Width = static_cast(frame_width()); + pass.viewport.Height = static_cast(frame_height()); + } + + { D3D11_BLEND_DESC desc = {}; + desc.RenderTarget[0].BlendEnable = pass_info.blend_enable; + + const auto literal_to_blend_func = [](unsigned int value) { + switch (value) { + case 0: + return D3D11_BLEND_ZERO; + default: + case 1: + return D3D11_BLEND_ONE; + case 2: + return D3D11_BLEND_SRC_COLOR; + case 4: + return D3D11_BLEND_INV_SRC_COLOR; + case 3: + return D3D11_BLEND_SRC_ALPHA; + case 5: + return D3D11_BLEND_INV_SRC_ALPHA; + case 6: + return D3D11_BLEND_DEST_ALPHA; + case 7: + return D3D11_BLEND_INV_DEST_ALPHA; + case 8: + return D3D11_BLEND_DEST_COLOR; + case 9: + return D3D11_BLEND_INV_DEST_COLOR; + } + }; + + desc.RenderTarget[0].SrcBlend = literal_to_blend_func(pass_info.src_blend); + desc.RenderTarget[0].DestBlend = literal_to_blend_func(pass_info.dest_blend); + desc.RenderTarget[0].BlendOp = static_cast(pass_info.blend_op); + desc.RenderTarget[0].SrcBlendAlpha = literal_to_blend_func(pass_info.src_blend_alpha); + desc.RenderTarget[0].DestBlendAlpha = literal_to_blend_func(pass_info.dest_blend_alpha); + desc.RenderTarget[0].BlendOpAlpha = static_cast(pass_info.blend_op_alpha); + desc.RenderTarget[0].RenderTargetWriteMask = pass_info.color_write_mask; + if (HRESULT hr = _device->CreateBlendState(&desc, &pass.blend_state); FAILED(hr)) + { + LOG(ERROR) << "Failed to create blend state for pass " << pass_index << " in technique '" << technique.name << "'! " + "HRESULT is '" << std::hex << hr << std::dec << "'."; + return false; + } + } + + // Rasterizer state is the same for all passes + assert(_effect_rasterizer_state != nullptr); + + { D3D11_DEPTH_STENCIL_DESC desc = {}; + desc.DepthEnable = FALSE; + desc.DepthWriteMask = D3D11_DEPTH_WRITE_MASK_ZERO; + desc.DepthFunc = D3D11_COMPARISON_ALWAYS; + + const auto literal_to_stencil_op = [](unsigned int value) { + switch (value) { + default: + case 1: + return D3D11_STENCIL_OP_KEEP; + case 0: + return D3D11_STENCIL_OP_ZERO; + case 3: + return D3D11_STENCIL_OP_REPLACE; + case 4: + return D3D11_STENCIL_OP_INCR_SAT; + case 5: + return D3D11_STENCIL_OP_DECR_SAT; + case 6: + return D3D11_STENCIL_OP_INVERT; + case 7: + return D3D11_STENCIL_OP_INCR; + case 8: + return D3D11_STENCIL_OP_DECR; + } + }; + + desc.StencilEnable = pass_info.stencil_enable; + desc.StencilReadMask = pass_info.stencil_read_mask; + desc.StencilWriteMask = pass_info.stencil_write_mask; + desc.FrontFace.StencilFailOp = literal_to_stencil_op(pass_info.stencil_op_fail); + desc.FrontFace.StencilDepthFailOp = literal_to_stencil_op(pass_info.stencil_op_depth_fail); + desc.FrontFace.StencilPassOp = literal_to_stencil_op(pass_info.stencil_op_pass); + desc.FrontFace.StencilFunc = static_cast(pass_info.stencil_comparison_func); + desc.BackFace = desc.FrontFace; + if (HRESULT hr = _device->CreateDepthStencilState(&desc, &pass.depth_stencil_state); FAILED(hr)) + { + LOG(ERROR) << "Failed to create depth stencil state for pass " << pass_index << " in technique '" << technique.name << "'! " + "HRESULT is '" << std::hex << hr << std::dec << "'."; + return false; + } + + pass.stencil_reference = pass_info.stencil_reference_value; + } + + for (auto &srv : pass.shader_resources) + { + if (srv == nullptr) + continue; + + com_ptr res1; + srv->GetResource(&res1); + + for (const auto &rtv : pass.render_targets) + { + if (rtv == nullptr) + continue; + + com_ptr res2; + rtv->GetResource(&res2); + + if (res1 == res2) + { + srv.reset(); + break; + } + } + } + } + + return true; +} + +void reshade::d3d11::runtime_d3d11::render_technique(technique &technique) +{ + d3d11_technique_data &technique_data = *technique.impl->as(); + + // Evaluate queries + if (technique_data.query_in_flight) + { + UINT64 timestamp0, timestamp1; + D3D11_QUERY_DATA_TIMESTAMP_DISJOINT disjoint; + + if (_immediate_context->GetData(technique_data.timestamp_disjoint.get(), &disjoint, sizeof(disjoint), D3D11_ASYNC_GETDATA_DONOTFLUSH) == S_OK && + _immediate_context->GetData(technique_data.timestamp_query_beg.get(), ×tamp0, sizeof(timestamp0), D3D11_ASYNC_GETDATA_DONOTFLUSH) == S_OK && + _immediate_context->GetData(technique_data.timestamp_query_end.get(), ×tamp1, sizeof(timestamp1), D3D11_ASYNC_GETDATA_DONOTFLUSH) == S_OK) + { + if (!disjoint.Disjoint) + technique.average_gpu_duration.append((timestamp1 - timestamp0) * 1'000'000'000 / disjoint.Frequency); + technique_data.query_in_flight = false; + } + } + + if (!technique_data.query_in_flight) + { + _immediate_context->Begin(technique_data.timestamp_disjoint.get()); + _immediate_context->End(technique_data.timestamp_query_beg.get()); + } + + bool is_default_depthstencil_cleared = false; + + // Setup vertex input + const uintptr_t null = 0; + _immediate_context->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST); + _immediate_context->IASetInputLayout(nullptr); + _immediate_context->IASetVertexBuffers(0, 1, reinterpret_cast(&null), reinterpret_cast(&null), reinterpret_cast(&null)); + + _immediate_context->RSSetState(_effect_rasterizer_state.get()); + + // Setup samplers + _immediate_context->VSSetSamplers(0, static_cast(technique_data.sampler_states.size()), reinterpret_cast(technique_data.sampler_states.data())); + _immediate_context->PSSetSamplers(0, static_cast(technique_data.sampler_states.size()), reinterpret_cast(technique_data.sampler_states.data())); + + // Setup shader constants + if (technique_data.uniform_storage_index >= 0) + { + const auto constant_buffer = _constant_buffers[technique_data.uniform_storage_index].get(); + D3D11_MAPPED_SUBRESOURCE mapped; + + const HRESULT hr = _immediate_context->Map(constant_buffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mapped); + + if (SUCCEEDED(hr)) + { + memcpy(mapped.pData, _uniform_data_storage.data() + technique_data.uniform_storage_offset, mapped.RowPitch); + + _immediate_context->Unmap(constant_buffer, 0); + } + else + { + LOG(ERROR) << "Failed to map constant buffer! HRESULT is '" << std::hex << hr << std::dec << "'!"; + } + + _immediate_context->VSSetConstantBuffers(0, 1, &constant_buffer); + _immediate_context->PSSetConstantBuffers(0, 1, &constant_buffer); + } + + // Disable unused pipeline stages + _immediate_context->HSSetShader(nullptr, nullptr, 0); + _immediate_context->DSSetShader(nullptr, nullptr, 0); + _immediate_context->GSSetShader(nullptr, nullptr, 0); + + for (const auto &pass_object : technique.passes_data) + { + const d3d11_pass_data &pass = *pass_object->as(); + + // Setup states + _immediate_context->VSSetShader(pass.vertex_shader.get(), nullptr, 0); + _immediate_context->PSSetShader(pass.pixel_shader.get(), nullptr, 0); + + _immediate_context->OMSetBlendState(pass.blend_state.get(), nullptr, D3D11_DEFAULT_SAMPLE_MASK); + _immediate_context->OMSetDepthStencilState(pass.depth_stencil_state.get(), pass.stencil_reference); + + // Save back buffer of previous pass + _immediate_context->CopyResource(_backbuffer_texture.get(), _backbuffer_resolved.get()); + + // Setup shader resources + _immediate_context->VSSetShaderResources(0, static_cast(pass.shader_resources.size()), reinterpret_cast(pass.shader_resources.data())); + _immediate_context->PSSetShaderResources(0, static_cast(pass.shader_resources.size()), reinterpret_cast(pass.shader_resources.data())); + + // Setup render targets + if (static_cast(pass.viewport.Width) == _width && static_cast(pass.viewport.Height) == _height) + { + _immediate_context->OMSetRenderTargets(D3D11_SIMULTANEOUS_RENDER_TARGET_COUNT, reinterpret_cast(pass.render_targets), _default_depthstencil.get()); + + if (!is_default_depthstencil_cleared) + { + is_default_depthstencil_cleared = true; + + _immediate_context->ClearDepthStencilView(_default_depthstencil.get(), D3D11_CLEAR_DEPTH | D3D11_CLEAR_STENCIL, 1.0f, 0); + } + } + else + { + _immediate_context->OMSetRenderTargets(D3D11_SIMULTANEOUS_RENDER_TARGET_COUNT, reinterpret_cast(pass.render_targets), nullptr); + } + + _immediate_context->RSSetViewports(1, &pass.viewport); + + if (pass.clear_render_targets) + { + for (const auto &target : pass.render_targets) + { + if (target != nullptr) + { + const float color[4] = { 0.0f, 0.0f, 0.0f, 0.0f }; + _immediate_context->ClearRenderTargetView(target.get(), color); + } + } + } + + // Draw triangle + _immediate_context->Draw(3, 0); + + _vertices += 3; + _drawcalls += 1; + + // Reset render targets + _immediate_context->OMSetRenderTargets(0, nullptr, nullptr); + + // Reset shader resources + ID3D11ShaderResourceView *null_srv[D3D11_COMMONSHADER_INPUT_RESOURCE_SLOT_COUNT] = { nullptr }; + _immediate_context->VSSetShaderResources(0, static_cast(pass.shader_resources.size()), null_srv); + _immediate_context->PSSetShaderResources(0, static_cast(pass.shader_resources.size()), null_srv); + + // Update shader resources + for (const auto &resource : pass.render_target_resources) + { + if (resource == nullptr) + continue; + + D3D11_SHADER_RESOURCE_VIEW_DESC resource_desc; + resource->GetDesc(&resource_desc); + + if (resource_desc.Texture2D.MipLevels > 1) + _immediate_context->GenerateMips(resource.get()); + } + } + + if (!technique_data.query_in_flight) + { + _immediate_context->End(technique_data.timestamp_query_end.get()); + _immediate_context->End(technique_data.timestamp_disjoint.get()); + technique_data.query_in_flight = true; + } +} + +#if RESHADE_GUI +bool reshade::d3d11::runtime_d3d11::init_imgui_resources() +{ + { const resources::data_resource vs = resources::load_data_resource(IDR_IMGUI_VS); + if (FAILED(_device->CreateVertexShader(vs.data, vs.data_size, nullptr, &_imgui_vertex_shader))) + return false; + + const D3D11_INPUT_ELEMENT_DESC input_layout[] = { + { "POSITION", 0, DXGI_FORMAT_R32G32_FLOAT, 0, offsetof(ImDrawVert, pos), D3D11_INPUT_PER_VERTEX_DATA, 0 }, + { "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, offsetof(ImDrawVert, uv ), D3D11_INPUT_PER_VERTEX_DATA, 0 }, + { "COLOR", 0, DXGI_FORMAT_R8G8B8A8_UNORM, 0, offsetof(ImDrawVert, col), D3D11_INPUT_PER_VERTEX_DATA, 0 }, + }; + if (FAILED(_device->CreateInputLayout(input_layout, _countof(input_layout), vs.data, vs.data_size, &_imgui_input_layout))) + return false; + } + + { const resources::data_resource ps = resources::load_data_resource(IDR_IMGUI_PS); + if (FAILED(_device->CreatePixelShader(ps.data, ps.data_size, nullptr, &_imgui_pixel_shader))) + return false; + } + + { D3D11_BUFFER_DESC desc = {}; + desc.ByteWidth = 16 * sizeof(float); + desc.Usage = D3D11_USAGE_IMMUTABLE; + desc.BindFlags = D3D11_BIND_CONSTANT_BUFFER; + + // Setup orthographic projection matrix + const float ortho_projection[16] = { + 2.0f / _width, 0.0f, 0.0f, 0.0f, + 0.0f, -2.0f / _height, 0.0f, 0.0f, + 0.0f, 0.0f, 0.5f, 0.0f, + -1.f, 1.0f, 0.5f, 1.0f + }; + + D3D11_SUBRESOURCE_DATA initial_data = {}; + initial_data.pSysMem = ortho_projection; + initial_data.SysMemPitch = sizeof(ortho_projection); + if (FAILED(_device->CreateBuffer(&desc, &initial_data, &_imgui_constant_buffer))) + return false; + } + + { D3D11_BLEND_DESC desc = {}; + desc.RenderTarget[0].BlendEnable = true; + desc.RenderTarget[0].SrcBlend = D3D11_BLEND_SRC_ALPHA; + desc.RenderTarget[0].DestBlend = D3D11_BLEND_INV_SRC_ALPHA; + desc.RenderTarget[0].BlendOp = D3D11_BLEND_OP_ADD; + desc.RenderTarget[0].SrcBlendAlpha = D3D11_BLEND_INV_SRC_ALPHA; + desc.RenderTarget[0].DestBlendAlpha = D3D11_BLEND_ZERO; + desc.RenderTarget[0].BlendOpAlpha = D3D11_BLEND_OP_ADD; + desc.RenderTarget[0].RenderTargetWriteMask = D3D11_COLOR_WRITE_ENABLE_ALL; + if (FAILED(_device->CreateBlendState(&desc, &_imgui_blend_state))) + return false; + } + + { D3D11_RASTERIZER_DESC desc = {}; + desc.FillMode = D3D11_FILL_SOLID; + desc.CullMode = D3D11_CULL_NONE; + desc.ScissorEnable = true; + desc.DepthClipEnable = true; + if (FAILED(_device->CreateRasterizerState(&desc, &_imgui_rasterizer_state))) + return false; + } + + { D3D11_DEPTH_STENCIL_DESC desc = {}; + desc.DepthEnable = false; + desc.StencilEnable = false; + if (FAILED(_device->CreateDepthStencilState(&desc, &_imgui_depthstencil_state))) + return false; + } + + { D3D11_SAMPLER_DESC desc = {}; + desc.Filter = D3D11_FILTER_MIN_MAG_MIP_LINEAR; + desc.AddressU = D3D11_TEXTURE_ADDRESS_WRAP; + desc.AddressV = D3D11_TEXTURE_ADDRESS_WRAP; + desc.AddressW = D3D11_TEXTURE_ADDRESS_WRAP; + desc.ComparisonFunc = D3D11_COMPARISON_ALWAYS; + if (FAILED(_device->CreateSamplerState(&desc, &_imgui_texture_sampler))) + return false; + } + + return true; +} + +void reshade::d3d11::runtime_d3d11::render_imgui_draw_data(ImDrawData *draw_data) +{ + assert(draw_data->DisplayPos.x == 0 && draw_data->DisplaySize.x == _width); + assert(draw_data->DisplayPos.y == 0 && draw_data->DisplaySize.y == _height); + + // Create and grow vertex/index buffers if needed + if (_imgui_index_buffer_size < draw_data->TotalIdxCount) + { + _imgui_index_buffer.reset(); + _imgui_index_buffer_size = draw_data->TotalIdxCount + 10000; + + D3D11_BUFFER_DESC desc = {}; + desc.Usage = D3D11_USAGE_DYNAMIC; + desc.ByteWidth = _imgui_index_buffer_size * sizeof(ImDrawIdx); + desc.BindFlags = D3D11_BIND_INDEX_BUFFER; + desc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE; + + if (FAILED(_device->CreateBuffer(&desc, nullptr, &_imgui_index_buffer))) + return; + } + if (_imgui_vertex_buffer_size < draw_data->TotalVtxCount) + { + _imgui_vertex_buffer.reset(); + _imgui_vertex_buffer_size = draw_data->TotalVtxCount + 5000; + + D3D11_BUFFER_DESC desc = {}; + desc.Usage = D3D11_USAGE_DYNAMIC; + desc.ByteWidth = _imgui_vertex_buffer_size * sizeof(ImDrawVert); + desc.BindFlags = D3D11_BIND_VERTEX_BUFFER; + desc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE; + desc.MiscFlags = 0; + + if (FAILED(_device->CreateBuffer(&desc, nullptr, &_imgui_vertex_buffer))) + return; + } + + D3D11_MAPPED_SUBRESOURCE vtx_resource, idx_resource; + if (FAILED(_immediate_context->Map(_imgui_index_buffer.get(), 0, D3D11_MAP_WRITE_DISCARD, 0, &idx_resource)) || + FAILED(_immediate_context->Map(_imgui_vertex_buffer.get(), 0, D3D11_MAP_WRITE_DISCARD, 0, &vtx_resource))) + return; + + auto idx_dst = static_cast(idx_resource.pData); + auto vtx_dst = static_cast(vtx_resource.pData); + for (int n = 0; n < draw_data->CmdListsCount; n++) + { + const ImDrawList *const draw_list = draw_data->CmdLists[n]; + memcpy(idx_dst, draw_list->IdxBuffer.Data, draw_list->IdxBuffer.Size * sizeof(ImDrawIdx)); + memcpy(vtx_dst, draw_list->VtxBuffer.Data, draw_list->VtxBuffer.Size * sizeof(ImDrawVert)); + idx_dst += draw_list->IdxBuffer.Size; + vtx_dst += draw_list->VtxBuffer.Size; + } + + _immediate_context->Unmap(_imgui_index_buffer.get(), 0); + _immediate_context->Unmap(_imgui_vertex_buffer.get(), 0); + + // Setup render state and render draw lists + _immediate_context->IASetInputLayout(_imgui_input_layout.get()); + _immediate_context->IASetIndexBuffer(_imgui_index_buffer.get(), sizeof(ImDrawIdx) == 2 ? DXGI_FORMAT_R16_UINT : DXGI_FORMAT_R32_UINT, 0); + const UINT stride = sizeof(ImDrawVert), offset = 0; + ID3D11Buffer *const vertex_buffers[] = { _imgui_vertex_buffer.get() }; + _immediate_context->IASetVertexBuffers(0, ARRAYSIZE(vertex_buffers), vertex_buffers, &stride, &offset); + _immediate_context->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST); + _immediate_context->VSSetShader(_imgui_vertex_shader.get(), nullptr, 0); + ID3D11Buffer *const constant_buffers[] = { _imgui_constant_buffer.get() }; + _immediate_context->VSSetConstantBuffers(0, ARRAYSIZE(constant_buffers), constant_buffers); + _immediate_context->HSSetShader(nullptr, nullptr, 0); + _immediate_context->DSSetShader(nullptr, nullptr, 0); + _immediate_context->GSSetShader(nullptr, nullptr, 0); + _immediate_context->PSSetShader(_imgui_pixel_shader.get(), nullptr, 0); + ID3D11SamplerState *const samplers[] = { _imgui_texture_sampler.get() }; + _immediate_context->PSSetSamplers(0, ARRAYSIZE(samplers), samplers); + _immediate_context->RSSetState(_imgui_rasterizer_state.get()); + const D3D11_VIEWPORT viewport = { 0, 0, FLOAT(_width), FLOAT(_height), 0.0f, 1.0f }; + _immediate_context->RSSetViewports(1, &viewport); + const FLOAT blend_factor[4] = { 0.f, 0.f, 0.f, 0.f }; + _immediate_context->OMSetBlendState(_imgui_blend_state.get(), blend_factor, D3D11_DEFAULT_SAMPLE_MASK); + _immediate_context->OMSetDepthStencilState(_imgui_depthstencil_state.get(), 0); + ID3D11RenderTargetView *const render_targets[] = { _backbuffer_rtv[0].get() }; + _immediate_context->OMSetRenderTargets(ARRAYSIZE(render_targets), render_targets, nullptr); + + UINT vtx_offset = 0, idx_offset = 0; + for (int n = 0; n < draw_data->CmdListsCount; ++n) + { + const ImDrawList *const draw_list = draw_data->CmdLists[n]; + + for (const ImDrawCmd &cmd : draw_list->CmdBuffer) + { + assert(cmd.TextureId != 0); + assert(cmd.UserCallback == nullptr); + + const D3D11_RECT scissor_rect = { + static_cast(cmd.ClipRect.x), + static_cast(cmd.ClipRect.y), + static_cast(cmd.ClipRect.z), + static_cast(cmd.ClipRect.w) + }; + _immediate_context->RSSetScissorRects(1, &scissor_rect); + + ID3D11ShaderResourceView *const texture_view = + static_cast(cmd.TextureId)->srv[0].get(); + _immediate_context->PSSetShaderResources(0, 1, &texture_view); + + _immediate_context->DrawIndexed(cmd.ElemCount, idx_offset, vtx_offset); + + idx_offset += cmd.ElemCount; + } + + vtx_offset += draw_list->VtxBuffer.size(); + } +} + +void reshade::d3d11::runtime_d3d11::draw_debug_menu() +{ + ImGui::Text("MSAA is %s", _is_multisampling_enabled ? "active" : "inactive"); + ImGui::Spacing(); + + assert(_current_tracker != nullptr); + +#if RESHADE_DX11_CAPTURE_DEPTH_BUFFERS + if (ImGui::CollapsingHeader("Depth and Intermediate Buffers", ImGuiTreeNodeFlags_DefaultOpen)) + { + bool modified = false; + modified |= ImGui::Combo("Depth Texture Format", &depth_buffer_texture_format, "All\0D16\0D32F\0D24S8\0D32FS8\0"); + + if (modified) + { + runtime::save_config(); + _current_tracker->reset(); + create_depthstencil_replacement(nullptr, nullptr); + return; + } + + modified |= ImGui::Checkbox("Copy depth before clearing", &depth_buffer_before_clear); + + if (depth_buffer_before_clear) + { + if (ImGui::Checkbox("Extended depth buffer detection", &extended_depth_buffer_detection)) + { + cleared_depth_buffer_index = 0; + modified = true; + } + + _current_tracker->keep_cleared_depth_textures(); + + ImGui::Spacing(); + ImGui::TextUnformatted("Depth Buffers:"); + + unsigned int current_index = 1; + + for (const auto &it : _current_tracker->cleared_depth_textures()) + { + char label[512] = ""; + sprintf_s(label, "%s%2u", (current_index == cleared_depth_buffer_index ? "> " : " "), current_index); + + if (bool value = cleared_depth_buffer_index == current_index; ImGui::Checkbox(label, &value)) + { + cleared_depth_buffer_index = value ? current_index : 0; + modified = true; + } + + ImGui::SameLine(); + + ImGui::Text("=> 0x%p | 0x%p | %ux%u", it.second.src_depthstencil.get(), it.second.src_texture.get(), it.second.src_texture_desc.Width, it.second.src_texture_desc.Height); + + if (it.second.dest_texture != nullptr) + { + ImGui::SameLine(); + + ImGui::Text("=> %p", it.second.dest_texture.get()); + } + + current_index++; + } + } + else if (!_current_tracker->depth_buffer_counters().empty()) + { + ImGui::Spacing(); + ImGui::TextUnformatted("Depth Buffers: (intermediate buffer draw calls in parentheses)"); + + for (const auto &[depthstencil, snapshot] : _current_tracker->depth_buffer_counters()) + { + char label[512] = ""; + sprintf_s(label, "%s0x%p", (depthstencil == _depthstencil ? "> " : " "), depthstencil.get()); + + if (bool value = _best_depth_stencil_overwrite == depthstencil; ImGui::Checkbox(label, &value)) + { + _best_depth_stencil_overwrite = value ? depthstencil.get() : nullptr; + + com_ptr texture = snapshot.texture; + + if (texture == nullptr && _best_depth_stencil_overwrite != nullptr) + { + com_ptr resource; + _best_depth_stencil_overwrite->GetResource(&resource); + + resource->QueryInterface(&texture); + } + + create_depthstencil_replacement(_best_depth_stencil_overwrite, texture.get()); + } + + ImGui::SameLine(); + + std::string additional_view_label; + + if (!snapshot.additional_views.empty()) + { + additional_view_label += '('; + + for (auto const &[view, stats] : snapshot.additional_views) + additional_view_label += std::to_string(stats.drawcalls) + ", "; + + // Remove last ", " from string + additional_view_label.pop_back(); + additional_view_label.pop_back(); + + additional_view_label += ')'; + } + + ImGui::Text("| %5u draw calls ==> %8u vertices, %2u additional render target%c %s", snapshot.stats.drawcalls, snapshot.stats.vertices, snapshot.additional_views.size(), snapshot.additional_views.size() != 1 ? 's' : ' ', additional_view_label.c_str()); + } + } + + if (modified) + { + runtime::save_config(); + } + } +#endif +#if RESHADE_DX11_CAPTURE_CONSTANT_BUFFERS + if (ImGui::CollapsingHeader("Constant Buffers", ImGuiTreeNodeFlags_DefaultOpen)) + { + for (const auto &[buffer, counter] : _current_tracker->constant_buffer_counters()) + { + bool likely_camera_transform_buffer = false; + + D3D11_BUFFER_DESC desc; + buffer->GetDesc(&desc); + + if (counter.ps_uses > 0 && counter.vs_uses > 0 && counter.mapped < .10 * counter.vs_uses && desc.ByteWidth > 1000) + likely_camera_transform_buffer = true; + + ImGui::Text("%s 0x%p | used in %4u vertex shaders and %4u pixel shaders, mapped %3u times, %8u bytes", likely_camera_transform_buffer ? ">" : " ", buffer.get(), counter.vs_uses, counter.ps_uses, counter.mapped, desc.ByteWidth); + } + } +#endif +} +#endif + +#if RESHADE_DX11_CAPTURE_DEPTH_BUFFERS +void reshade::d3d11::runtime_d3d11::detect_depth_source(draw_call_tracker &tracker) +{ + if (depth_buffer_before_clear) + _best_depth_stencil_overwrite = nullptr; + + if (_is_multisampling_enabled || _best_depth_stencil_overwrite != nullptr || (_framecount % 30 && !depth_buffer_before_clear)) + return; + + if (_has_high_network_activity) + { + create_depthstencil_replacement(nullptr, nullptr); + return; + } + + if (depth_buffer_before_clear) + { + // At the final rendering stage, it is fine to rely on the depth stencil to select the best depth texture + // But when we retrieve the depth textures before the final rendering stage, there is chance that one or many different depth textures are associated to the same depth stencil (for instance, in Bioshock 2) + // In this case, we cannot use the depth stencil to determine which depth texture is the good one, so we can use the default depth stencil + // For the moment, the best we can do is retrieve all the depth textures that has been cleared in the rendering pipeline, then select one of them (by default, the last one) + // In the future, maybe we could find a way to retrieve depth texture statistics (number of draw calls and number of vertices), so ReShade could automatically select the best one + ID3D11Texture2D *const best_match_texture = tracker.find_best_cleared_depth_buffer_texture(cleared_depth_buffer_index); + if (best_match_texture != nullptr) + create_depthstencil_replacement(_default_depthstencil.get(), best_match_texture); + return; + } + + const auto best_snapshot = tracker.find_best_snapshot(_width, _height); + if (best_snapshot.depthstencil != nullptr) + create_depthstencil_replacement(best_snapshot.depthstencil, best_snapshot.texture.get()); +} + +bool reshade::d3d11::runtime_d3d11::create_depthstencil_replacement(ID3D11DepthStencilView *depthstencil, ID3D11Texture2D *texture) +{ + _depthstencil.reset(); + _depthstencil_replacement.reset(); + _depthstencil_texture.reset(); + _depthstencil_texture_srv.reset(); + + if (depthstencil != nullptr) + { + assert(texture != nullptr); + _depthstencil = depthstencil; + _depthstencil_texture = texture; + + D3D11_TEXTURE2D_DESC tex_desc; + _depthstencil_texture->GetDesc(&tex_desc); + + HRESULT hr = S_OK; + + if ((tex_desc.BindFlags & D3D11_BIND_SHADER_RESOURCE) == 0) + { + _depthstencil_texture.reset(); + + tex_desc.Format = make_dxgi_format_typeless(tex_desc.Format); + tex_desc.BindFlags = D3D11_BIND_DEPTH_STENCIL | D3D11_BIND_SHADER_RESOURCE; + + hr = _device->CreateTexture2D(&tex_desc, nullptr, &_depthstencil_texture); + + if (SUCCEEDED(hr)) + { + D3D11_DEPTH_STENCIL_VIEW_DESC dsv_desc = {}; + dsv_desc.ViewDimension = D3D11_DSV_DIMENSION_TEXTURE2D; + dsv_desc.Format = make_dxgi_format_dsv(tex_desc.Format); + + hr = _device->CreateDepthStencilView(_depthstencil_texture.get(), &dsv_desc, &_depthstencil_replacement); + } + } + + if (FAILED(hr)) + { + LOG(ERROR) << "Failed to create depth stencil replacement texture! HRESULT is '" << std::hex << hr << std::dec << "'."; + return false; + } + + D3D11_SHADER_RESOURCE_VIEW_DESC srv_desc = {}; + srv_desc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D; + srv_desc.Texture2D.MipLevels = 1; + srv_desc.Format = make_dxgi_format_normal(tex_desc.Format); + + if (hr = _device->CreateShaderResourceView(_depthstencil_texture.get(), &srv_desc, &_depthstencil_texture_srv); FAILED(hr)) + { + LOG(ERROR) << "Failed to create depth stencil replacement resource view! HRESULT is '" << std::hex << hr << std::dec << "'."; + return false; + } + + // Update auto depth stencil + com_ptr current_depthstencil; + com_ptr targets[D3D11_SIMULTANEOUS_RENDER_TARGET_COUNT]; + _immediate_context->OMGetRenderTargets(D3D11_SIMULTANEOUS_RENDER_TARGET_COUNT, reinterpret_cast(targets), ¤t_depthstencil); + if (current_depthstencil != nullptr && current_depthstencil == _depthstencil) + _immediate_context->OMSetRenderTargets(D3D11_SIMULTANEOUS_RENDER_TARGET_COUNT, reinterpret_cast(targets), _depthstencil_replacement.get()); + } + + update_texture_references(texture_reference::depth_buffer); + + return true; +} + +com_ptr reshade::d3d11::runtime_d3d11::select_depth_texture_save(D3D11_TEXTURE2D_DESC &texture_desc) +{ + // Function that selects the appropriate texture where we want to save the depth texture before it is cleared + // If this texture is null, create it according to the dimensions and the format of the depth texture + // Doing so, we avoid to create a new texture each time the depth texture is saved + + texture_desc.Format = make_dxgi_format_typeless(texture_desc.Format); + + // Create an unique index based on the texture format and dimensions + UINT idx = texture_desc.Format * texture_desc.Width * texture_desc.Height; + + if (const auto it = _depth_texture_saves.find(idx); it != _depth_texture_saves.end()) + return it->second; + + texture_desc.BindFlags = D3D11_BIND_DEPTH_STENCIL | D3D11_BIND_SHADER_RESOURCE; + + // Create the saved texture pointed by the index if it does not already exist + com_ptr depth_texture_save; + + HRESULT hr = _device->CreateTexture2D(&texture_desc, nullptr, &depth_texture_save); + + if (FAILED(hr)) + { + LOG(ERROR) << "Failed to create depth texture copy! HRESULT is '" << std::hex << hr << std::dec << "'."; + return nullptr; + } + + _depth_texture_saves.emplace(idx, depth_texture_save); + + return depth_texture_save; +} +#endif diff --git a/msvc/source/d3d11/runtime_d3d11.hpp b/msvc/source/d3d11/runtime_d3d11.hpp new file mode 100644 index 0000000..8c4113c --- /dev/null +++ b/msvc/source/d3d11/runtime_d3d11.hpp @@ -0,0 +1,119 @@ +/** + * Copyright (C) 2014 Patrick Mours. All rights reserved. + * License: https://github.com/crosire/reshade#license + */ + +#pragma once + +#include "runtime.hpp" +#include "state_block.hpp" +#include "draw_call_tracker.hpp" +#include + +namespace reshade { enum class texture_reference; } +namespace reshadefx { struct sampler_info; } + +namespace reshade::d3d11 +{ + class runtime_d3d11 : public runtime + { + public: + runtime_d3d11(ID3D11Device *device, IDXGISwapChain *swapchain); + ~runtime_d3d11(); + + bool on_init(const DXGI_SWAP_CHAIN_DESC &desc); + void on_reset(); + void on_present(draw_call_tracker& tracker); + + void capture_screenshot(uint8_t *buffer) const override; + +#if RESHADE_DX11_CAPTURE_DEPTH_BUFFERS + com_ptr select_depth_texture_save(D3D11_TEXTURE2D_DESC &texture_desc); +#endif + + bool depth_buffer_before_clear = false; + bool extended_depth_buffer_detection = false; + unsigned int cleared_depth_buffer_index = 0; + int depth_buffer_texture_format = 0; // No depth buffer texture format filter by default + + private: + bool init_backbuffer_texture(); + bool init_default_depth_stencil(); + + bool init_texture(texture &info) override; + void upload_texture(texture &texture, const uint8_t *pixels) override; + bool update_texture_reference(texture &texture); + void update_texture_references(texture_reference type); + + bool compile_effect(effect_data &effect) override; + void unload_effects() override; + + bool add_sampler(const reshadefx::sampler_info &info, struct d3d11_technique_data &technique_init); + bool init_technique(technique &info, const struct d3d11_technique_data &technique_init, const std::unordered_map> &entry_points); + + void render_technique(technique &technique) override; + +#if RESHADE_GUI + bool init_imgui_resources(); + void render_imgui_draw_data(ImDrawData *data) override; + void draw_debug_menu(); +#endif + +#if RESHADE_DX11_CAPTURE_DEPTH_BUFFERS + void detect_depth_source(draw_call_tracker& tracker); + bool create_depthstencil_replacement(ID3D11DepthStencilView *depthstencil, ID3D11Texture2D *texture); +#endif + + struct depth_texture_save_info + { + com_ptr src_texture; + D3D11_TEXTURE2D_DESC src_texture_desc; + com_ptr dest_texture; + bool cleared = false; + }; + + com_ptr _device; + com_ptr _immediate_context; + com_ptr _swapchain; + + com_ptr _backbuffer_texture; + com_ptr _backbuffer_rtv[3]; + com_ptr _backbuffer_texture_srv[2]; + com_ptr _depthstencil_texture_srv; + std::unordered_map> _effect_sampler_states; + std::vector> _constant_buffers; + + std::map _displayed_depth_textures; + std::unordered_map> _depth_texture_saves; + + bool _is_multisampling_enabled = false; + DXGI_FORMAT _backbuffer_format = DXGI_FORMAT_UNKNOWN; + state_block _app_state; + com_ptr _backbuffer, _backbuffer_resolved; + com_ptr _depthstencil, _depthstencil_replacement; + ID3D11DepthStencilView *_best_depth_stencil_overwrite = nullptr; + com_ptr _depthstencil_texture; + com_ptr _default_depthstencil; + com_ptr _copy_vertex_shader; + com_ptr _copy_pixel_shader; + com_ptr _copy_sampler; + std::mutex _mutex; + com_ptr _effect_rasterizer_state; + + int _imgui_index_buffer_size = 0; + com_ptr _imgui_index_buffer; + int _imgui_vertex_buffer_size = 0; + com_ptr _imgui_vertex_buffer; + com_ptr _imgui_vertex_shader; + com_ptr _imgui_pixel_shader; + com_ptr _imgui_input_layout; + com_ptr _imgui_constant_buffer; + com_ptr _imgui_texture_sampler; + com_ptr _imgui_rasterizer_state; + com_ptr _imgui_blend_state; + com_ptr _imgui_depthstencil_state; + draw_call_tracker *_current_tracker = nullptr; + + HMODULE _d3d_compiler = nullptr; + }; +} diff --git a/msvc/source/d3d11/state_block.cpp b/msvc/source/d3d11/state_block.cpp new file mode 100644 index 0000000..e437748 --- /dev/null +++ b/msvc/source/d3d11/state_block.cpp @@ -0,0 +1,166 @@ +/** + * Copyright (C) 2014 Patrick Mours. All rights reserved. + * License: https://github.com/crosire/reshade#license + */ + +#include "state_block.hpp" + +template +static inline void safe_release(T *&object) +{ + if (object != nullptr) + { + object->Release(); + object = nullptr; + } +} + +reshade::d3d11::state_block::state_block(ID3D11Device *device) +{ + ZeroMemory(this, sizeof(*this)); + + _device = device; + _device_feature_level = device->GetFeatureLevel(); +} +reshade::d3d11::state_block::~state_block() +{ + release_all_device_objects(); +} + +void reshade::d3d11::state_block::capture(ID3D11DeviceContext *devicecontext) +{ + _device_context = devicecontext; + + _device_context->IAGetPrimitiveTopology(&_ia_primitive_topology); + _device_context->IAGetInputLayout(&_ia_input_layout); + + if (_device_feature_level > D3D_FEATURE_LEVEL_10_0) + _device_context->IAGetVertexBuffers(0, D3D11_IA_VERTEX_INPUT_RESOURCE_SLOT_COUNT, _ia_vertex_buffers, _ia_vertex_strides, _ia_vertex_offsets); + else + _device_context->IAGetVertexBuffers(0, D3D10_IA_VERTEX_INPUT_RESOURCE_SLOT_COUNT, _ia_vertex_buffers, _ia_vertex_strides, _ia_vertex_offsets); + + _device_context->IAGetIndexBuffer(&_ia_index_buffer, &_ia_index_format, &_ia_index_offset); + + _device_context->RSGetState(&_rs_state); + _device_context->RSGetViewports(&_rs_num_viewports, nullptr); + _device_context->RSGetViewports(&_rs_num_viewports, _rs_viewports); + _device_context->RSGetScissorRects(&_rs_num_scissor_rects, nullptr); + _device_context->RSGetScissorRects(&_rs_num_scissor_rects, _rs_scissor_rects); + + _vs_num_class_instances = ARRAYSIZE(_vs_class_instances); + _device_context->VSGetShader(&_vs, _vs_class_instances, &_vs_num_class_instances); + _device_context->VSGetConstantBuffers(0, ARRAYSIZE(_vs_constant_buffers), _vs_constant_buffers); + _device_context->VSGetSamplers(0, ARRAYSIZE(_vs_sampler_states), _vs_sampler_states); + _device_context->VSGetShaderResources(0, ARRAYSIZE(_vs_shader_resources), _vs_shader_resources); + + if (_device_feature_level >= D3D_FEATURE_LEVEL_10_0) + { + if (_device_feature_level >= D3D_FEATURE_LEVEL_11_0) + { + _hs_num_class_instances = ARRAYSIZE(_hs_class_instances); + _device_context->HSGetShader(&_hs, _hs_class_instances, &_hs_num_class_instances); + + _ds_num_class_instances = ARRAYSIZE(_ds_class_instances); + _device_context->DSGetShader(&_ds, _ds_class_instances, &_ds_num_class_instances); + } + + _gs_num_class_instances = ARRAYSIZE(_gs_class_instances); + _device_context->GSGetShader(&_gs, _gs_class_instances, &_gs_num_class_instances); + } + + _ps_num_class_instances = ARRAYSIZE(_ps_class_instances); + _device_context->PSGetShader(&_ps, _ps_class_instances, &_ps_num_class_instances); + _device_context->PSGetConstantBuffers(0, ARRAYSIZE(_ps_constant_buffers), _ps_constant_buffers); + _device_context->PSGetSamplers(0, ARRAYSIZE(_ps_sampler_states), _ps_sampler_states); + _device_context->PSGetShaderResources(0, ARRAYSIZE(_ps_shader_resources), _ps_shader_resources); + + _device_context->OMGetBlendState(&_om_blend_state, _om_blend_factor, &_om_sample_mask); + _device_context->OMGetDepthStencilState(&_om_depth_stencil_state, &_om_stencil_ref); + _device_context->OMGetRenderTargets(ARRAYSIZE(_om_render_targets), _om_render_targets, &_om_depth_stencil); +} +void reshade::d3d11::state_block::apply_and_release() +{ + _device_context->IASetPrimitiveTopology(_ia_primitive_topology); + _device_context->IASetInputLayout(_ia_input_layout); + + if (_device_feature_level > D3D_FEATURE_LEVEL_10_0) + _device_context->IASetVertexBuffers(0, D3D11_IA_VERTEX_INPUT_RESOURCE_SLOT_COUNT, _ia_vertex_buffers, _ia_vertex_strides, _ia_vertex_offsets); + else + _device_context->IASetVertexBuffers(0, D3D10_IA_VERTEX_INPUT_RESOURCE_SLOT_COUNT, _ia_vertex_buffers, _ia_vertex_strides, _ia_vertex_offsets); + + _device_context->IASetIndexBuffer(_ia_index_buffer, _ia_index_format, _ia_index_offset); + + _device_context->RSSetState(_rs_state); + _device_context->RSSetViewports(_rs_num_viewports, _rs_viewports); + _device_context->RSSetScissorRects(_rs_num_scissor_rects, _rs_scissor_rects); + + _device_context->VSSetShader(_vs, _vs_class_instances, _vs_num_class_instances); + _device_context->VSSetConstantBuffers(0, ARRAYSIZE(_vs_constant_buffers), _vs_constant_buffers); + _device_context->VSSetSamplers(0, ARRAYSIZE(_vs_sampler_states), _vs_sampler_states); + _device_context->VSSetShaderResources(0, ARRAYSIZE(_vs_shader_resources), _vs_shader_resources); + + if (_device_feature_level >= D3D_FEATURE_LEVEL_10_0) + { + if (_device_feature_level >= D3D_FEATURE_LEVEL_11_0) + { + _device_context->HSSetShader(_hs, _hs_class_instances, _hs_num_class_instances); + _device_context->DSSetShader(_ds, _ds_class_instances, _ds_num_class_instances); + } + + _device_context->GSSetShader(_gs, _gs_class_instances, _gs_num_class_instances); + } + + _device_context->PSSetShader(_ps, _ps_class_instances, _ps_num_class_instances); + _device_context->PSSetConstantBuffers(0, ARRAYSIZE(_ps_constant_buffers), _ps_constant_buffers); + _device_context->PSSetSamplers(0, ARRAYSIZE(_ps_sampler_states), _ps_sampler_states); + _device_context->PSSetShaderResources(0, ARRAYSIZE(_ps_shader_resources), _ps_shader_resources); + + _device_context->OMSetBlendState(_om_blend_state, _om_blend_factor, _om_sample_mask); + _device_context->OMSetDepthStencilState(_om_depth_stencil_state, _om_stencil_ref); + _device_context->OMSetRenderTargets(ARRAYSIZE(_om_render_targets), _om_render_targets, _om_depth_stencil); + + release_all_device_objects(); + + _device_context.reset(); +} + +void reshade::d3d11::state_block::release_all_device_objects() +{ + safe_release(_ia_input_layout); + for (auto &vertex_buffer : _ia_vertex_buffers) + safe_release(vertex_buffer); + safe_release(_ia_index_buffer); + safe_release(_vs); + for (UINT i = 0; i < _vs_num_class_instances; i++) + safe_release(_vs_class_instances[i]); + for (auto &constant_buffer : _vs_constant_buffers) + safe_release(constant_buffer); + for (auto &sampler_state : _vs_sampler_states) + safe_release(sampler_state); + for (auto &shader_resource : _vs_shader_resources) + safe_release(shader_resource); + safe_release(_hs); + for (UINT i = 0; i < _hs_num_class_instances; i++) + safe_release(_hs_class_instances[i]); + safe_release(_ds); + for (UINT i = 0; i < _ds_num_class_instances; i++) + safe_release(_ds_class_instances[i]); + safe_release(_gs); + for (UINT i = 0; i < _gs_num_class_instances; i++) + safe_release(_gs_class_instances[i]); + safe_release(_rs_state); + safe_release(_ps); + for (UINT i = 0; i < _ps_num_class_instances; i++) + safe_release(_ps_class_instances[i]); + for (auto &constant_buffer : _ps_constant_buffers) + safe_release(constant_buffer); + for (auto &sampler_state : _ps_sampler_states) + safe_release(sampler_state); + for (auto &shader_resource : _ps_shader_resources) + safe_release(shader_resource); + safe_release(_om_blend_state); + safe_release(_om_depth_stencil_state); + for (auto &render_target : _om_render_targets) + safe_release(render_target); + safe_release(_om_depth_stencil); +} diff --git a/msvc/source/d3d11/state_block.hpp b/msvc/source/d3d11/state_block.hpp new file mode 100644 index 0000000..304b31b --- /dev/null +++ b/msvc/source/d3d11/state_block.hpp @@ -0,0 +1,70 @@ +/** + * Copyright (C) 2014 Patrick Mours. All rights reserved. + * License: https://github.com/crosire/reshade#license + */ + +#pragma once + +#include "com_ptr.hpp" +#include + +namespace reshade::d3d11 +{ + class state_block + { + public: + explicit state_block(ID3D11Device *device); + ~state_block(); + + void capture(ID3D11DeviceContext *devicecontext); + void apply_and_release(); + + private: + void release_all_device_objects(); + + D3D_FEATURE_LEVEL _device_feature_level; + com_ptr _device; + com_ptr _device_context; + ID3D11InputLayout *_ia_input_layout; + D3D11_PRIMITIVE_TOPOLOGY _ia_primitive_topology; + ID3D11Buffer *_ia_vertex_buffers[D3D11_IA_VERTEX_INPUT_RESOURCE_SLOT_COUNT]; + UINT _ia_vertex_strides[D3D11_IA_VERTEX_INPUT_RESOURCE_SLOT_COUNT]; + UINT _ia_vertex_offsets[D3D11_IA_VERTEX_INPUT_RESOURCE_SLOT_COUNT]; + ID3D11Buffer *_ia_index_buffer; + DXGI_FORMAT _ia_index_format; + UINT _ia_index_offset; + ID3D11VertexShader *_vs; + UINT _vs_num_class_instances; + ID3D11ClassInstance *_vs_class_instances[256]; + ID3D11Buffer *_vs_constant_buffers[D3D11_COMMONSHADER_CONSTANT_BUFFER_API_SLOT_COUNT]; + ID3D11SamplerState *_vs_sampler_states[D3D11_COMMONSHADER_SAMPLER_SLOT_COUNT]; + ID3D11ShaderResourceView *_vs_shader_resources[D3D11_COMMONSHADER_INPUT_RESOURCE_SLOT_COUNT]; + ID3D11HullShader *_hs; + UINT _hs_num_class_instances; + ID3D11ClassInstance *_hs_class_instances[256]; + ID3D11DomainShader *_ds; + UINT _ds_num_class_instances; + ID3D11ClassInstance *_ds_class_instances[256]; + ID3D11GeometryShader *_gs; + UINT _gs_num_class_instances; + ID3D11ClassInstance *_gs_class_instances[256]; + ID3D11RasterizerState *_rs_state; + UINT _rs_num_viewports; + D3D11_VIEWPORT _rs_viewports[D3D11_VIEWPORT_AND_SCISSORRECT_OBJECT_COUNT_PER_PIPELINE]; + UINT _rs_num_scissor_rects; + D3D11_RECT _rs_scissor_rects[D3D11_VIEWPORT_AND_SCISSORRECT_OBJECT_COUNT_PER_PIPELINE]; + ID3D11PixelShader *_ps; + UINT _ps_num_class_instances; + ID3D11ClassInstance *_ps_class_instances[256]; + ID3D11Buffer *_ps_constant_buffers[D3D11_COMMONSHADER_CONSTANT_BUFFER_API_SLOT_COUNT]; + ID3D11SamplerState *_ps_sampler_states[D3D11_COMMONSHADER_SAMPLER_SLOT_COUNT]; + ID3D11ShaderResourceView *_ps_shader_resources[D3D11_COMMONSHADER_INPUT_RESOURCE_SLOT_COUNT]; + ID3D11BlendState *_om_blend_state; + FLOAT _om_blend_factor[4]; + UINT _om_sample_mask; + ID3D11DepthStencilState *_om_depth_stencil_state; + UINT _om_stencil_ref; + ID3D11RenderTargetView *_om_render_targets[D3D11_SIMULTANEOUS_RENDER_TARGET_COUNT]; + ID3D11DepthStencilView *_om_depth_stencil; + }; +} diff --git a/msvc/source/d3d12/d3d12.cpp b/msvc/source/d3d12/d3d12.cpp new file mode 100644 index 0000000..cbe08f9 --- /dev/null +++ b/msvc/source/d3d12/d3d12.cpp @@ -0,0 +1,92 @@ +/** + * Copyright (C) 2014 Patrick Mours. All rights reserved. + * License: https://github.com/crosire/reshade#license + */ + +#include "log.hpp" +#include "hook_manager.hpp" +#include "d3d12_device.hpp" + +extern reshade::log::message &operator<<(reshade::log::message &m, REFIID riid); + +HOOK_EXPORT HRESULT WINAPI D3D12CreateDevice( + IUnknown *pAdapter, + D3D_FEATURE_LEVEL MinimumFeatureLevel, + REFIID riid, + void **ppDevice) +{ + LOG(INFO) << "Redirecting D3D12CreateDevice" << '(' << pAdapter << ", " << std::hex << MinimumFeatureLevel << std::dec << ", " << riid << ", " << ppDevice << ')' << " ..."; + + const HRESULT hr = reshade::hooks::call(D3D12CreateDevice)(pAdapter, MinimumFeatureLevel, riid, ppDevice); + + if (FAILED(hr)) + { + LOG(WARN) << "> D3D12CreateDevice failed with error code " << std::hex << hr << std::dec << '!'; + return hr; + } + + if (ppDevice == nullptr) + return hr; + + // The returned device should alway implement the 'ID3D12Device' base interface + const auto device_proxy = new D3D12Device(static_cast(*ppDevice)); + device_proxy->check_and_upgrade_interface(riid); // Upgrade to the actual interface version requested here + + *ppDevice = device_proxy; + +#if RESHADE_VERBOSE_LOG + LOG(DEBUG) << "Returning ID3D12Device" << device_proxy->_interface_version << " object " << device_proxy << '.'; +#endif + return hr; +} + +HOOK_EXPORT HRESULT WINAPI D3D12GetDebugInterface( + REFIID riid, + void **ppvDebug) +{ + return reshade::hooks::call(D3D12GetDebugInterface)(riid, ppvDebug); +} + +HOOK_EXPORT HRESULT WINAPI D3D12CreateRootSignatureDeserializer( + LPCVOID pSrcData, + SIZE_T SrcDataSizeInBytes, + REFIID pRootSignatureDeserializerInterface, + void **ppRootSignatureDeserializer) +{ + return reshade::hooks::call(D3D12CreateRootSignatureDeserializer)(pSrcData, SrcDataSizeInBytes, pRootSignatureDeserializerInterface, ppRootSignatureDeserializer); +} + +HOOK_EXPORT HRESULT WINAPI D3D12CreateVersionedRootSignatureDeserializer( + LPCVOID pSrcData, + SIZE_T SrcDataSizeInBytes, + REFIID pRootSignatureDeserializerInterface, + void **ppRootSignatureDeserializer) +{ + return reshade::hooks::call(D3D12CreateVersionedRootSignatureDeserializer)(pSrcData, SrcDataSizeInBytes, pRootSignatureDeserializerInterface, ppRootSignatureDeserializer); +} + +HOOK_EXPORT HRESULT WINAPI D3D12EnableExperimentalFeatures( + UINT NumFeatures, + const IID *pIIDs, + void *pConfigurationStructs, + UINT *pConfigurationStructSizes) +{ + return reshade::hooks::call(D3D12EnableExperimentalFeatures)(NumFeatures, pIIDs, pConfigurationStructs, pConfigurationStructSizes); +} + +HOOK_EXPORT HRESULT WINAPI D3D12SerializeRootSignature( + const D3D12_ROOT_SIGNATURE_DESC *pRootSignature, + D3D_ROOT_SIGNATURE_VERSION Version, + ID3DBlob **ppBlob, + ID3DBlob **ppErrorBlob) +{ + return reshade::hooks::call(D3D12SerializeRootSignature)(pRootSignature, Version, ppBlob, ppErrorBlob); +} + +HOOK_EXPORT HRESULT WINAPI D3D12SerializeVersionedRootSignature( + const D3D12_VERSIONED_ROOT_SIGNATURE_DESC *pRootSignature, + ID3DBlob **ppBlob, + ID3DBlob **ppErrorBlob) +{ + return reshade::hooks::call(D3D12SerializeVersionedRootSignature)(pRootSignature, ppBlob, ppErrorBlob); +} diff --git a/msvc/source/d3d12/d3d12_command_queue.cpp b/msvc/source/d3d12/d3d12_command_queue.cpp new file mode 100644 index 0000000..ff5785a --- /dev/null +++ b/msvc/source/d3d12/d3d12_command_queue.cpp @@ -0,0 +1,126 @@ +/** + * Copyright (C) 2014 Patrick Mours. All rights reserved. + * License: https://github.com/crosire/reshade#license + */ + +#include "log.hpp" +#include "d3d12_device.hpp" +#include "d3d12_command_queue.hpp" +#include + +D3D12CommandQueue::D3D12CommandQueue(D3D12Device *device, ID3D12CommandQueue *original) : + _orig(original), + _device(device) { + assert(original != nullptr); +} + +HRESULT STDMETHODCALLTYPE D3D12CommandQueue::QueryInterface(REFIID riid, void **ppvObj) +{ + if (ppvObj == nullptr) + return E_POINTER; + + if (riid == __uuidof(this) || + riid == __uuidof(IUnknown) || + riid == __uuidof(ID3D12Object) || + riid == __uuidof(ID3D12DeviceChild) || + riid == __uuidof(ID3D12Pageable) || + riid == __uuidof(ID3D12CommandQueue)) + { + AddRef(); + *ppvObj = this; + return S_OK; + } + + return _orig->QueryInterface(riid, ppvObj); +} +ULONG STDMETHODCALLTYPE D3D12CommandQueue::AddRef() +{ + ++_ref; + + return _orig->AddRef(); +} +ULONG STDMETHODCALLTYPE D3D12CommandQueue::Release() +{ + --_ref; + + const ULONG ref = _orig->Release(); + + if (ref != 0 && _ref != 0) + return ref; + else if (ref != 0) + LOG(WARN) << "Reference count for ID3D12CommandQueue object " << this << " is inconsistent: " << ref << ", but expected 0."; + + assert(_ref <= 0); +#if RESHADE_VERBOSE_LOG + LOG(DEBUG) << "Destroyed ID3D12CommandQueue object " << this << "."; +#endif + delete this; + return 0; +} + +HRESULT STDMETHODCALLTYPE D3D12CommandQueue::GetPrivateData(REFGUID guid, UINT *pDataSize, void *pData) +{ + return _orig->GetPrivateData(guid, pDataSize, pData); +} +HRESULT STDMETHODCALLTYPE D3D12CommandQueue::SetPrivateData(REFGUID guid, UINT DataSize, const void *pData) +{ + return _orig->SetPrivateData(guid, DataSize, pData); +} +HRESULT STDMETHODCALLTYPE D3D12CommandQueue::SetPrivateDataInterface(REFGUID guid, const IUnknown *pData) +{ + return _orig->SetPrivateDataInterface(guid, pData); +} +HRESULT STDMETHODCALLTYPE D3D12CommandQueue::SetName(LPCWSTR Name) +{ + return _orig->SetName(Name); +} + +HRESULT STDMETHODCALLTYPE D3D12CommandQueue::GetDevice(REFIID riid, void **ppvDevice) +{ + return _device->QueryInterface(riid, ppvDevice); +} + +void STDMETHODCALLTYPE D3D12CommandQueue::UpdateTileMappings(ID3D12Resource *pResource, UINT NumResourceRegions, const D3D12_TILED_RESOURCE_COORDINATE *pResourceRegionStartCoordinates, const D3D12_TILE_REGION_SIZE *pResourceRegionSizes, ID3D12Heap *pHeap, UINT NumRanges, const D3D12_TILE_RANGE_FLAGS *pRangeFlags, const UINT *pHeapRangeStartOffsets, const UINT *pRangeTileCounts, D3D12_TILE_MAPPING_FLAGS Flags) +{ + _orig->UpdateTileMappings(pResource, NumResourceRegions, pResourceRegionStartCoordinates, pResourceRegionSizes, pHeap, NumRanges, pRangeFlags, pHeapRangeStartOffsets, pRangeTileCounts, Flags); +} +void STDMETHODCALLTYPE D3D12CommandQueue::CopyTileMappings(ID3D12Resource *pDstResource, const D3D12_TILED_RESOURCE_COORDINATE *pDstRegionStartCoordinate, ID3D12Resource *pSrcResource, const D3D12_TILED_RESOURCE_COORDINATE *pSrcRegionStartCoordinate, const D3D12_TILE_REGION_SIZE *pRegionSize, D3D12_TILE_MAPPING_FLAGS Flags) +{ + _orig->CopyTileMappings(pDstResource, pDstRegionStartCoordinate, pSrcResource, pSrcRegionStartCoordinate, pRegionSize, Flags); +} +void STDMETHODCALLTYPE D3D12CommandQueue::ExecuteCommandLists(UINT NumCommandLists, ID3D12CommandList *const *ppCommandLists) +{ + _orig->ExecuteCommandLists(NumCommandLists, ppCommandLists); +} +void STDMETHODCALLTYPE D3D12CommandQueue::SetMarker(UINT Metadata, const void *pData, UINT Size) +{ + _orig->SetMarker(Metadata, pData, Size); +} +void STDMETHODCALLTYPE D3D12CommandQueue::BeginEvent(UINT Metadata, const void *pData, UINT Size) +{ + _orig->BeginEvent(Metadata, pData, Size); +} +void STDMETHODCALLTYPE D3D12CommandQueue::EndEvent() +{ + _orig->EndEvent(); +} +HRESULT STDMETHODCALLTYPE D3D12CommandQueue::Signal(ID3D12Fence *pFence, UINT64 Value) +{ + return _orig->Signal(pFence, Value); +} +HRESULT STDMETHODCALLTYPE D3D12CommandQueue::Wait(ID3D12Fence *pFence, UINT64 Value) +{ + return _orig->Wait(pFence, Value); +} +HRESULT STDMETHODCALLTYPE D3D12CommandQueue::GetTimestampFrequency(UINT64 *pFrequency) +{ + return _orig->GetTimestampFrequency(pFrequency); +} +HRESULT STDMETHODCALLTYPE D3D12CommandQueue::GetClockCalibration(UINT64 *pGpuTimestamp, UINT64 *pCpuTimestamp) +{ + return _orig->GetClockCalibration(pGpuTimestamp, pCpuTimestamp); +} +D3D12_COMMAND_QUEUE_DESC STDMETHODCALLTYPE D3D12CommandQueue::GetDesc() +{ + return _orig->GetDesc(); +} diff --git a/msvc/source/d3d12/d3d12_command_queue.hpp b/msvc/source/d3d12/d3d12_command_queue.hpp new file mode 100644 index 0000000..b97a9ba --- /dev/null +++ b/msvc/source/d3d12/d3d12_command_queue.hpp @@ -0,0 +1,47 @@ +/** + * Copyright (C) 2014 Patrick Mours. All rights reserved. + * License: https://github.com/crosire/reshade#license + */ + +#pragma once + +#include + +struct __declspec(uuid("2C576D2A-0C1C-4D1D-AD7C-BC4FAEC15ABC")) D3D12CommandQueue : ID3D12CommandQueue +{ + D3D12CommandQueue(D3D12Device *device, ID3D12CommandQueue *original); + + D3D12CommandQueue(const D3D12CommandQueue &) = delete; + D3D12CommandQueue &operator=(const D3D12CommandQueue &) = delete; + + HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void **ppvObj) override; + ULONG STDMETHODCALLTYPE AddRef() override; + ULONG STDMETHODCALLTYPE Release() override; + + #pragma region ID3D12Object + HRESULT STDMETHODCALLTYPE GetPrivateData(REFGUID guid, UINT *pDataSize, void *pData) override; + HRESULT STDMETHODCALLTYPE SetPrivateData(REFGUID guid, UINT DataSize, const void *pData) override; + HRESULT STDMETHODCALLTYPE SetPrivateDataInterface(REFGUID guid, const IUnknown *pData) override; + HRESULT STDMETHODCALLTYPE SetName(LPCWSTR Name) override; + #pragma endregion + #pragma region ID3D12DeviceChild + HRESULT STDMETHODCALLTYPE GetDevice(REFIID riid, void **ppvDevice) override; + #pragma endregion + #pragma region ID3D12CommandQueue + void STDMETHODCALLTYPE UpdateTileMappings(ID3D12Resource *pResource, UINT NumResourceRegions, const D3D12_TILED_RESOURCE_COORDINATE *pResourceRegionStartCoordinates, const D3D12_TILE_REGION_SIZE *pResourceRegionSizes, ID3D12Heap *pHeap, UINT NumRanges, const D3D12_TILE_RANGE_FLAGS *pRangeFlags, const UINT *pHeapRangeStartOffsets, const UINT *pRangeTileCounts, D3D12_TILE_MAPPING_FLAGS Flags) override; + void STDMETHODCALLTYPE CopyTileMappings(ID3D12Resource *pDstResource, const D3D12_TILED_RESOURCE_COORDINATE *pDstRegionStartCoordinate, ID3D12Resource *pSrcResource, const D3D12_TILED_RESOURCE_COORDINATE *pSrcRegionStartCoordinate, const D3D12_TILE_REGION_SIZE *pRegionSize, D3D12_TILE_MAPPING_FLAGS Flags) override; + void STDMETHODCALLTYPE ExecuteCommandLists(UINT NumCommandLists, ID3D12CommandList *const *ppCommandLists) override; + void STDMETHODCALLTYPE SetMarker(UINT Metadata, const void *pData, UINT Size) override; + void STDMETHODCALLTYPE BeginEvent(UINT Metadata, const void *pData, UINT Size) override; + void STDMETHODCALLTYPE EndEvent() override; + HRESULT STDMETHODCALLTYPE Signal(ID3D12Fence *pFence, UINT64 Value) override; + HRESULT STDMETHODCALLTYPE Wait(ID3D12Fence *pFence, UINT64 Value) override; + HRESULT STDMETHODCALLTYPE GetTimestampFrequency(UINT64 *pFrequency) override; + HRESULT STDMETHODCALLTYPE GetClockCalibration(UINT64 *pGpuTimestamp, UINT64 *pCpuTimestamp) override; + D3D12_COMMAND_QUEUE_DESC STDMETHODCALLTYPE GetDesc() override; + #pragma endregion + + ULONG _ref = 1; + ID3D12CommandQueue *const _orig; + D3D12Device *const _device; +}; diff --git a/msvc/source/d3d12/d3d12_device.cpp b/msvc/source/d3d12/d3d12_device.cpp new file mode 100644 index 0000000..6fb30fc --- /dev/null +++ b/msvc/source/d3d12/d3d12_device.cpp @@ -0,0 +1,396 @@ +/** + * Copyright (C) 2014 Patrick Mours. All rights reserved. + * License: https://github.com/crosire/reshade#license + */ + +#include "log.hpp" +#include "d3d12_device.hpp" +#include "d3d12_command_queue.hpp" +#include "../dxgi/dxgi_device.hpp" +#include + +extern reshade::log::message &operator<<(reshade::log::message &m, REFIID riid); + +D3D12Device::D3D12Device(ID3D12Device *original) : + _orig(original), + _interface_version(0) { + assert(original != nullptr); +} + +bool D3D12Device::check_and_upgrade_interface(REFIID riid) +{ + static const IID iid_lookup[] = { + __uuidof(ID3D12Device), + __uuidof(ID3D12Device1), + __uuidof(ID3D12Device2), + __uuidof(ID3D12Device3), + __uuidof(ID3D12Device4), + __uuidof(ID3D12Device5), + }; + + for (unsigned int new_version = _interface_version + 1; new_version < ARRAYSIZE(iid_lookup); ++new_version) + { + if (riid == iid_lookup[new_version]) + { + IUnknown *new_interface = nullptr; + if (FAILED(_orig->QueryInterface(riid, reinterpret_cast(&new_interface)))) + return false; +#if RESHADE_VERBOSE_LOG + LOG(DEBUG) << "Upgraded ID3D12Device" << _interface_version << " object " << this << " to ID3D12Device" << new_version << '.'; +#endif + _orig->Release(); + _orig = static_cast(new_interface); + _interface_version = new_version; + break; + } + } + + return true; +} + +HRESULT STDMETHODCALLTYPE D3D12Device::QueryInterface(REFIID riid, void **ppvObj) +{ + if (ppvObj == nullptr) + return E_POINTER; + + if (riid == __uuidof(this) || + riid == __uuidof(IUnknown) || + riid == __uuidof(ID3D12Object) || + riid == __uuidof(ID3D12Device) || + riid == __uuidof(ID3D12Device1) || + riid == __uuidof(ID3D12Device2) || + riid == __uuidof(ID3D12Device3) || + riid == __uuidof(ID3D12Device4) || + riid == __uuidof(ID3D12Device5)) + { + if (!check_and_upgrade_interface(riid)) + return E_NOINTERFACE; + + AddRef(); + *ppvObj = this; + return S_OK; + } + + return _orig->QueryInterface(riid, ppvObj); +} +ULONG STDMETHODCALLTYPE D3D12Device::AddRef() +{ + ++_ref; + + return _orig->AddRef(); +} +ULONG STDMETHODCALLTYPE D3D12Device::Release() +{ + --_ref; + + const ULONG ref = _orig->Release(); + + if (ref != 0 && _ref != 0) + return ref; + else if (ref != 0) + LOG(WARN) << "Reference count for ID3D12Device" << _interface_version << " object " << this << " is inconsistent: " << ref << ", but expected 0."; + + assert(_ref <= 0); +#if RESHADE_VERBOSE_LOG + LOG(DEBUG) << "Destroyed ID3D12Device" << _interface_version << " object " << this << "."; +#endif + delete this; + return 0; +} + +HRESULT STDMETHODCALLTYPE D3D12Device::GetPrivateData(REFGUID guid, UINT *pDataSize, void *pData) +{ + return _orig->GetPrivateData(guid, pDataSize, pData); +} +HRESULT STDMETHODCALLTYPE D3D12Device::SetPrivateData(REFGUID guid, UINT DataSize, const void *pData) +{ + return _orig->SetPrivateData(guid, DataSize, pData); +} +HRESULT STDMETHODCALLTYPE D3D12Device::SetPrivateDataInterface(REFGUID guid, const IUnknown *pData) +{ + return _orig->SetPrivateDataInterface(guid, pData); +} +HRESULT STDMETHODCALLTYPE D3D12Device::SetName(LPCWSTR Name) +{ + return _orig->SetName(Name); +} + +UINT STDMETHODCALLTYPE D3D12Device::GetNodeCount() +{ + return _orig->GetNodeCount(); +} +HRESULT STDMETHODCALLTYPE D3D12Device::CreateCommandQueue(const D3D12_COMMAND_QUEUE_DESC *pDesc, REFIID riid, void **ppCommandQueue) +{ + LOG(INFO) << "Redirecting ID3D12Device::CreateCommandQueue" << '(' << this << ", " << pDesc << ", " << riid << ", " << ppCommandQueue << ')' << " ..."; + + if (ppCommandQueue == nullptr) + return E_INVALIDARG; + + const HRESULT hr = _orig->CreateCommandQueue(pDesc, riid, ppCommandQueue); + + if (FAILED(hr)) + { + LOG(WARN) << "> 'ID3D12Device::CreateCommandQueue' failed with error code " << std::hex << hr << std::dec << "!"; + return hr; + } + + if (riid == __uuidof(ID3D12CommandQueue)) + { + *ppCommandQueue = new D3D12CommandQueue(this, static_cast(*ppCommandQueue)); + } + +#if RESHADE_VERBOSE_LOG + LOG(DEBUG) << "Returning ID3D12CommandQueue object " << *ppCommandQueue << '.'; +#endif + return hr; +} +HRESULT STDMETHODCALLTYPE D3D12Device::CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE type, REFIID riid, void **ppCommandAllocator) +{ + return _orig->CreateCommandAllocator(type, riid, ppCommandAllocator); +} +HRESULT STDMETHODCALLTYPE D3D12Device::CreateGraphicsPipelineState(const D3D12_GRAPHICS_PIPELINE_STATE_DESC *pDesc, REFIID riid, void **ppPipelineState) +{ + return _orig->CreateGraphicsPipelineState(pDesc, riid, ppPipelineState); +} +HRESULT STDMETHODCALLTYPE D3D12Device::CreateComputePipelineState(const D3D12_COMPUTE_PIPELINE_STATE_DESC *pDesc, REFIID riid, void **ppPipelineState) +{ + return _orig->CreateComputePipelineState(pDesc, riid, ppPipelineState); +} +HRESULT STDMETHODCALLTYPE D3D12Device::CreateCommandList(UINT nodeMask, D3D12_COMMAND_LIST_TYPE type, ID3D12CommandAllocator *pCommandAllocator, ID3D12PipelineState *pInitialState, REFIID riid, void **ppCommandList) +{ + return _orig->CreateCommandList(nodeMask, type, pCommandAllocator, pInitialState, riid, ppCommandList); +} +HRESULT STDMETHODCALLTYPE D3D12Device::CheckFeatureSupport(D3D12_FEATURE Feature, void *pFeatureSupportData, UINT FeatureSupportDataSize) +{ + return _orig->CheckFeatureSupport(Feature, pFeatureSupportData, FeatureSupportDataSize); +} +HRESULT STDMETHODCALLTYPE D3D12Device::CreateDescriptorHeap(const D3D12_DESCRIPTOR_HEAP_DESC *pDescriptorHeapDesc, REFIID riid, void **ppvHeap) +{ + return _orig->CreateDescriptorHeap(pDescriptorHeapDesc, riid, ppvHeap); +} +UINT STDMETHODCALLTYPE D3D12Device::GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE DescriptorHeapType) +{ + return _orig->GetDescriptorHandleIncrementSize(DescriptorHeapType); +} +HRESULT STDMETHODCALLTYPE D3D12Device::CreateRootSignature(UINT nodeMask, const void *pBlobWithRootSignature, SIZE_T blobLengthInBytes, REFIID riid, void **ppvRootSignature) +{ + return _orig->CreateRootSignature(nodeMask, pBlobWithRootSignature, blobLengthInBytes, riid, ppvRootSignature); +} +void STDMETHODCALLTYPE D3D12Device::CreateConstantBufferView(const D3D12_CONSTANT_BUFFER_VIEW_DESC *pDesc, D3D12_CPU_DESCRIPTOR_HANDLE DestDescriptor) +{ + _orig->CreateConstantBufferView(pDesc, DestDescriptor); +} +void STDMETHODCALLTYPE D3D12Device::CreateShaderResourceView(ID3D12Resource *pResource, const D3D12_SHADER_RESOURCE_VIEW_DESC *pDesc, D3D12_CPU_DESCRIPTOR_HANDLE DestDescriptor) +{ + _orig->CreateShaderResourceView(pResource, pDesc, DestDescriptor); +} +void STDMETHODCALLTYPE D3D12Device::CreateUnorderedAccessView(ID3D12Resource *pResource, ID3D12Resource *pCounterResource, const D3D12_UNORDERED_ACCESS_VIEW_DESC *pDesc, D3D12_CPU_DESCRIPTOR_HANDLE DestDescriptor) +{ + _orig->CreateUnorderedAccessView(pResource, pCounterResource, pDesc, DestDescriptor); +} +void STDMETHODCALLTYPE D3D12Device::CreateRenderTargetView(ID3D12Resource *pResource, const D3D12_RENDER_TARGET_VIEW_DESC *pDesc, D3D12_CPU_DESCRIPTOR_HANDLE DestDescriptor) +{ + _orig->CreateRenderTargetView(pResource, pDesc, DestDescriptor); +} +void STDMETHODCALLTYPE D3D12Device::CreateDepthStencilView(ID3D12Resource *pResource, const D3D12_DEPTH_STENCIL_VIEW_DESC *pDesc, D3D12_CPU_DESCRIPTOR_HANDLE DestDescriptor) +{ + _orig->CreateDepthStencilView(pResource, pDesc, DestDescriptor); +} +void STDMETHODCALLTYPE D3D12Device::CreateSampler(const D3D12_SAMPLER_DESC *pDesc, D3D12_CPU_DESCRIPTOR_HANDLE DestDescriptor) +{ + _orig->CreateSampler(pDesc, DestDescriptor); +} +void STDMETHODCALLTYPE D3D12Device::CopyDescriptors(UINT NumDestDescriptorRanges, const D3D12_CPU_DESCRIPTOR_HANDLE *pDestDescriptorRangeStarts, const UINT *pDestDescriptorRangeSizes, UINT NumSrcDescriptorRanges, const D3D12_CPU_DESCRIPTOR_HANDLE *pSrcDescriptorRangeStarts, const UINT *pSrcDescriptorRangeSizes, D3D12_DESCRIPTOR_HEAP_TYPE DescriptorHeapsType) +{ + _orig->CopyDescriptors(NumDestDescriptorRanges, pDestDescriptorRangeStarts, pDestDescriptorRangeSizes, NumSrcDescriptorRanges, pSrcDescriptorRangeStarts, pSrcDescriptorRangeSizes, DescriptorHeapsType); +} +void STDMETHODCALLTYPE D3D12Device::CopyDescriptorsSimple(UINT NumDescriptors, D3D12_CPU_DESCRIPTOR_HANDLE DestDescriptorRangeStart, D3D12_CPU_DESCRIPTOR_HANDLE SrcDescriptorRangeStart, D3D12_DESCRIPTOR_HEAP_TYPE DescriptorHeapsType) +{ + _orig->CopyDescriptorsSimple(NumDescriptors, DestDescriptorRangeStart, SrcDescriptorRangeStart, DescriptorHeapsType); +} +D3D12_RESOURCE_ALLOCATION_INFO STDMETHODCALLTYPE D3D12Device::GetResourceAllocationInfo(UINT visibleMask, UINT numResourceDescs, const D3D12_RESOURCE_DESC *pResourceDescs) +{ + return _orig->GetResourceAllocationInfo(visibleMask, numResourceDescs, pResourceDescs); +} +D3D12_HEAP_PROPERTIES STDMETHODCALLTYPE D3D12Device::GetCustomHeapProperties(UINT nodeMask, D3D12_HEAP_TYPE heapType) +{ + return _orig->GetCustomHeapProperties(nodeMask, heapType); +} +HRESULT STDMETHODCALLTYPE D3D12Device::CreateCommittedResource(const D3D12_HEAP_PROPERTIES *pHeapProperties, D3D12_HEAP_FLAGS HeapFlags, const D3D12_RESOURCE_DESC *pResourceDesc, D3D12_RESOURCE_STATES InitialResourceState, const D3D12_CLEAR_VALUE *pOptimizedClearValue, REFIID riidResource, void **ppvResource) +{ + return _orig->CreateCommittedResource(pHeapProperties, HeapFlags, pResourceDesc, InitialResourceState, pOptimizedClearValue, riidResource, ppvResource); +} +HRESULT STDMETHODCALLTYPE D3D12Device::CreateHeap(const D3D12_HEAP_DESC *pDesc, REFIID riid, void **ppvHeap) +{ + return _orig->CreateHeap(pDesc, riid, ppvHeap); +} +HRESULT STDMETHODCALLTYPE D3D12Device::CreatePlacedResource(ID3D12Heap *pHeap, UINT64 HeapOffset, const D3D12_RESOURCE_DESC *pDesc, D3D12_RESOURCE_STATES InitialState, const D3D12_CLEAR_VALUE *pOptimizedClearValue, REFIID riid, void **ppvResource) +{ + return _orig->CreatePlacedResource(pHeap, HeapOffset, pDesc, InitialState, pOptimizedClearValue, riid, ppvResource); +} +HRESULT STDMETHODCALLTYPE D3D12Device::CreateReservedResource(const D3D12_RESOURCE_DESC *pDesc, D3D12_RESOURCE_STATES InitialState, const D3D12_CLEAR_VALUE *pOptimizedClearValue, REFIID riid, void **ppvResource) +{ + return _orig->CreateReservedResource(pDesc, InitialState, pOptimizedClearValue, riid, ppvResource); +} +HRESULT STDMETHODCALLTYPE D3D12Device::CreateSharedHandle(ID3D12DeviceChild *pObject, const SECURITY_ATTRIBUTES *pAttributes, DWORD Access, LPCWSTR Name, HANDLE *pHandle) +{ + return _orig->CreateSharedHandle(pObject, pAttributes, Access, Name, pHandle); +} +HRESULT STDMETHODCALLTYPE D3D12Device::OpenSharedHandle(HANDLE NTHandle, REFIID riid, void **ppvObj) +{ + return _orig->OpenSharedHandle(NTHandle, riid, ppvObj); +} +HRESULT STDMETHODCALLTYPE D3D12Device::OpenSharedHandleByName(LPCWSTR Name, DWORD Access, HANDLE *pNTHandle) +{ + return _orig->OpenSharedHandleByName(Name, Access, pNTHandle); +} +HRESULT STDMETHODCALLTYPE D3D12Device::MakeResident(UINT NumObjects, ID3D12Pageable *const *ppObjects) +{ + return _orig->MakeResident(NumObjects, ppObjects); +} +HRESULT STDMETHODCALLTYPE D3D12Device::Evict(UINT NumObjects, ID3D12Pageable *const *ppObjects) +{ + return _orig->Evict(NumObjects, ppObjects); +} +HRESULT STDMETHODCALLTYPE D3D12Device::CreateFence(UINT64 InitialValue, D3D12_FENCE_FLAGS Flags, REFIID riid, void **ppFence) +{ + return _orig->CreateFence(InitialValue, Flags, riid, ppFence); +} +HRESULT STDMETHODCALLTYPE D3D12Device::GetDeviceRemovedReason() +{ + return _orig->GetDeviceRemovedReason(); +} +void STDMETHODCALLTYPE D3D12Device::GetCopyableFootprints(const D3D12_RESOURCE_DESC *pResourceDesc, UINT FirstSubresource, UINT NumSubresources, UINT64 BaseOffset, D3D12_PLACED_SUBRESOURCE_FOOTPRINT *pLayouts, UINT *pNumRows, UINT64 *pRowSizeInBytes, UINT64 *pTotalBytes) +{ + _orig->GetCopyableFootprints(pResourceDesc, FirstSubresource, NumSubresources, BaseOffset, pLayouts, pNumRows, pRowSizeInBytes, pTotalBytes); +} +HRESULT STDMETHODCALLTYPE D3D12Device::CreateQueryHeap(const D3D12_QUERY_HEAP_DESC *pDesc, REFIID riid, void **ppvHeap) +{ + return _orig->CreateQueryHeap(pDesc, riid, ppvHeap); +} +HRESULT STDMETHODCALLTYPE D3D12Device::SetStablePowerState(BOOL Enable) +{ + return _orig->SetStablePowerState(Enable); +} +HRESULT STDMETHODCALLTYPE D3D12Device::CreateCommandSignature(const D3D12_COMMAND_SIGNATURE_DESC *pDesc, ID3D12RootSignature *pRootSignature, REFIID riid, void **ppvCommandSignature) +{ + return _orig->CreateCommandSignature(pDesc, pRootSignature, riid, ppvCommandSignature); +} +void STDMETHODCALLTYPE D3D12Device::GetResourceTiling(ID3D12Resource *pTiledResource, UINT *pNumTilesForEntireResource, D3D12_PACKED_MIP_INFO *pPackedMipDesc, D3D12_TILE_SHAPE *pStandardTileShapeForNonPackedMips, UINT *pNumSubresourceTilings, UINT FirstSubresourceTilingToGet, D3D12_SUBRESOURCE_TILING *pSubresourceTilingsForNonPackedMips) +{ + _orig->GetResourceTiling(pTiledResource, pNumTilesForEntireResource, pPackedMipDesc, pStandardTileShapeForNonPackedMips, pNumSubresourceTilings, FirstSubresourceTilingToGet, pSubresourceTilingsForNonPackedMips); +} +LUID STDMETHODCALLTYPE D3D12Device::GetAdapterLuid() +{ + return _orig->GetAdapterLuid(); +} + +HRESULT STDMETHODCALLTYPE D3D12Device::CreatePipelineLibrary(const void *pLibraryBlob, SIZE_T BlobLength, REFIID riid, void **ppPipelineLibrary) +{ + assert(_interface_version >= 1); + return static_cast(_orig)->CreatePipelineLibrary(pLibraryBlob, BlobLength, riid, ppPipelineLibrary); +} +HRESULT STDMETHODCALLTYPE D3D12Device::SetEventOnMultipleFenceCompletion(ID3D12Fence *const *ppFences, const UINT64 *pFenceValues, UINT NumFences, D3D12_MULTIPLE_FENCE_WAIT_FLAGS Flags, HANDLE hEvent) +{ + assert(_interface_version >= 1); + return static_cast(_orig)->SetEventOnMultipleFenceCompletion(ppFences, pFenceValues, NumFences, Flags, hEvent); +} +HRESULT STDMETHODCALLTYPE D3D12Device::SetResidencyPriority(UINT NumObjects, ID3D12Pageable *const *ppObjects, const D3D12_RESIDENCY_PRIORITY *pPriorities) +{ + assert(_interface_version >= 1); + return static_cast(_orig)->SetResidencyPriority(NumObjects, ppObjects, pPriorities); +} + +HRESULT STDMETHODCALLTYPE D3D12Device::CreatePipelineState(const D3D12_PIPELINE_STATE_STREAM_DESC *pDesc, REFIID riid, void **ppPipelineState) +{ + assert(_interface_version >= 2); + return static_cast(_orig)->CreatePipelineState(pDesc, riid, ppPipelineState); +} + +HRESULT STDMETHODCALLTYPE D3D12Device::OpenExistingHeapFromAddress(const void *pAddress, REFIID riid, void **ppvHeap) +{ + assert(_interface_version >= 3); + return static_cast(_orig)->OpenExistingHeapFromAddress(pAddress, riid, ppvHeap); +} +HRESULT STDMETHODCALLTYPE D3D12Device::OpenExistingHeapFromFileMapping(HANDLE hFileMapping, REFIID riid, void **ppvHeap) +{ + assert(_interface_version >= 3); + return static_cast(_orig)->OpenExistingHeapFromFileMapping(hFileMapping, riid, ppvHeap); +} +HRESULT STDMETHODCALLTYPE D3D12Device::EnqueueMakeResident(D3D12_RESIDENCY_FLAGS Flags, UINT NumObjects, ID3D12Pageable *const *ppObjects, ID3D12Fence *pFenceToSignal, UINT64 FenceValueToSignal) +{ + assert(_interface_version >= 3); + return static_cast(_orig)->EnqueueMakeResident(Flags, NumObjects, ppObjects, pFenceToSignal, FenceValueToSignal); +} + +HRESULT STDMETHODCALLTYPE D3D12Device::CreateCommandList1(UINT NodeMask, D3D12_COMMAND_LIST_TYPE Type, D3D12_COMMAND_LIST_FLAGS Flags, REFIID riid, void **ppCommandList) +{ + assert(_interface_version >= 4); + return static_cast(_orig)->CreateCommandList1(NodeMask, Type, Flags, riid, ppCommandList); +} +HRESULT STDMETHODCALLTYPE D3D12Device::CreateProtectedResourceSession(const D3D12_PROTECTED_RESOURCE_SESSION_DESC *pDesc, REFIID riid, void **ppSession) +{ + assert(_interface_version >= 4); + return static_cast(_orig)->CreateProtectedResourceSession(pDesc, riid, ppSession); +} +HRESULT STDMETHODCALLTYPE D3D12Device::CreateCommittedResource1(const D3D12_HEAP_PROPERTIES *pHeapProperties, D3D12_HEAP_FLAGS HeapFlags, const D3D12_RESOURCE_DESC *pDesc, D3D12_RESOURCE_STATES InitialResourceState, const D3D12_CLEAR_VALUE *pOptimizedClearValue, ID3D12ProtectedResourceSession *pProtectedSession, REFIID riidResource, void **ppvResource) +{ + assert(_interface_version >= 4); + return static_cast(_orig)->CreateCommittedResource1(pHeapProperties, HeapFlags, pDesc, InitialResourceState, pOptimizedClearValue, pProtectedSession, riidResource, ppvResource); +} +HRESULT STDMETHODCALLTYPE D3D12Device::CreateHeap1(const D3D12_HEAP_DESC *pDesc, ID3D12ProtectedResourceSession *pProtectedSession, REFIID riid, void **ppvHeap) +{ + assert(_interface_version >= 4); + return static_cast(_orig)->CreateHeap1(pDesc, pProtectedSession, riid, ppvHeap); +} +HRESULT STDMETHODCALLTYPE D3D12Device::CreateReservedResource1(const D3D12_RESOURCE_DESC *pDesc, D3D12_RESOURCE_STATES InitialState, const D3D12_CLEAR_VALUE *pOptimizedClearValue, ID3D12ProtectedResourceSession *pProtectedSession, REFIID riid, void **ppvResource) +{ + assert(_interface_version >= 4); + return static_cast(_orig)->CreateReservedResource1(pDesc, InitialState, pOptimizedClearValue, pProtectedSession, riid, ppvResource); +} +D3D12_RESOURCE_ALLOCATION_INFO STDMETHODCALLTYPE D3D12Device::GetResourceAllocationInfo1(UINT VisibleMask, UINT NumResourceDescs, const D3D12_RESOURCE_DESC *pResourceDescs, D3D12_RESOURCE_ALLOCATION_INFO1 *pResourceAllocationInfo1) +{ + assert(_interface_version >= 4); + return static_cast(_orig)->GetResourceAllocationInfo1(VisibleMask, NumResourceDescs, pResourceDescs, pResourceAllocationInfo1); +} + +HRESULT STDMETHODCALLTYPE D3D12Device::CreateLifetimeTracker(ID3D12LifetimeOwner *pOwner, REFIID riid, void **ppvTracker) +{ + assert(_interface_version >= 5); + return static_cast(_orig)->CreateLifetimeTracker(pOwner, riid, ppvTracker); +} +void STDMETHODCALLTYPE D3D12Device::RemoveDevice() +{ + assert(_interface_version >= 5); + static_cast(_orig)->RemoveDevice(); +} +HRESULT STDMETHODCALLTYPE D3D12Device::EnumerateMetaCommands(UINT *pNumMetaCommands, D3D12_META_COMMAND_DESC *pDescs) +{ + assert(_interface_version >= 5); + return static_cast(_orig)->EnumerateMetaCommands(pNumMetaCommands, pDescs); +} +HRESULT STDMETHODCALLTYPE D3D12Device::EnumerateMetaCommandParameters(REFGUID CommandId, D3D12_META_COMMAND_PARAMETER_STAGE Stage, UINT *pTotalStructureSizeInBytes, UINT *pParameterCount, D3D12_META_COMMAND_PARAMETER_DESC *pParameterDescs) +{ + assert(_interface_version >= 5); + return static_cast(_orig)->EnumerateMetaCommandParameters(CommandId, Stage, pTotalStructureSizeInBytes, pParameterCount, pParameterDescs); +} +HRESULT STDMETHODCALLTYPE D3D12Device::CreateMetaCommand(REFGUID CommandId, UINT NodeMask, const void *pCreationParametersData, SIZE_T CreationParametersDataSizeInBytes, REFIID riid, void **ppMetaCommand) +{ + assert(_interface_version >= 5); + return static_cast(_orig)->CreateMetaCommand(CommandId, NodeMask, pCreationParametersData, CreationParametersDataSizeInBytes, riid, ppMetaCommand); +} +HRESULT STDMETHODCALLTYPE D3D12Device::CreateStateObject(const D3D12_STATE_OBJECT_DESC *pDesc, REFIID riid, void **ppStateObject) +{ + assert(_interface_version >= 5); + return static_cast(_orig)->CreateStateObject(pDesc, riid, ppStateObject); +} +void STDMETHODCALLTYPE D3D12Device::GetRaytracingAccelerationStructurePrebuildInfo(const D3D12_BUILD_RAYTRACING_ACCELERATION_STRUCTURE_INPUTS *pDesc, D3D12_RAYTRACING_ACCELERATION_STRUCTURE_PREBUILD_INFO *pInfo) +{ + assert(_interface_version >= 5); + static_cast(_orig)->GetRaytracingAccelerationStructurePrebuildInfo(pDesc, pInfo); +} +D3D12_DRIVER_MATCHING_IDENTIFIER_STATUS STDMETHODCALLTYPE D3D12Device::CheckDriverMatchingIdentifier(D3D12_SERIALIZED_DATA_TYPE SerializedDataType, const D3D12_SERIALIZED_DATA_DRIVER_MATCHING_IDENTIFIER *pIdentifierToCheck) +{ + assert(_interface_version >= 5); + return static_cast(_orig)->CheckDriverMatchingIdentifier(SerializedDataType, pIdentifierToCheck); +} diff --git a/msvc/source/d3d12/d3d12_device.hpp b/msvc/source/d3d12/d3d12_device.hpp new file mode 100644 index 0000000..cd1a364 --- /dev/null +++ b/msvc/source/d3d12/d3d12_device.hpp @@ -0,0 +1,103 @@ +/** + * Copyright (C) 2014 Patrick Mours. All rights reserved. + * License: https://github.com/crosire/reshade#license + */ + +#pragma once + +#include + +struct __declspec(uuid("2523AFF4-978B-4939-BA16-8EE876A4CB2A")) D3D12Device : ID3D12Device5 +{ + D3D12Device(ID3D12Device *original); + + D3D12Device(const D3D12Device &) = delete; + D3D12Device &operator=(const D3D12Device &) = delete; + + HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void **ppvObj) override; + ULONG STDMETHODCALLTYPE AddRef() override; + ULONG STDMETHODCALLTYPE Release() override; + + #pragma region ID3D12Object + HRESULT STDMETHODCALLTYPE GetPrivateData(REFGUID guid, UINT *pDataSize, void *pData) override; + HRESULT STDMETHODCALLTYPE SetPrivateData(REFGUID guid, UINT DataSize, const void *pData) override; + HRESULT STDMETHODCALLTYPE SetPrivateDataInterface(REFGUID guid, const IUnknown *pData) override; + HRESULT STDMETHODCALLTYPE SetName(LPCWSTR Name) override; + #pragma endregion + #pragma region ID3D12Device + UINT STDMETHODCALLTYPE GetNodeCount() override; + HRESULT STDMETHODCALLTYPE CreateCommandQueue(const D3D12_COMMAND_QUEUE_DESC *pDesc, REFIID riid, void **ppCommandQueue) override; + HRESULT STDMETHODCALLTYPE CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE type, REFIID riid, void **ppCommandAllocator) override; + HRESULT STDMETHODCALLTYPE CreateGraphicsPipelineState(const D3D12_GRAPHICS_PIPELINE_STATE_DESC *pDesc, REFIID riid, void **ppPipelineState) override; + HRESULT STDMETHODCALLTYPE CreateComputePipelineState(const D3D12_COMPUTE_PIPELINE_STATE_DESC *pDesc, REFIID riid, void **ppPipelineState) override; + HRESULT STDMETHODCALLTYPE CreateCommandList(UINT nodeMask, D3D12_COMMAND_LIST_TYPE type, ID3D12CommandAllocator *pCommandAllocator, ID3D12PipelineState *pInitialState, REFIID riid, void **ppCommandList) override; + HRESULT STDMETHODCALLTYPE CheckFeatureSupport(D3D12_FEATURE Feature, void *pFeatureSupportData, UINT FeatureSupportDataSize) override; + HRESULT STDMETHODCALLTYPE CreateDescriptorHeap(const D3D12_DESCRIPTOR_HEAP_DESC *pDescriptorHeapDesc, REFIID riid, void **ppvHeap) override; + UINT STDMETHODCALLTYPE GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE DescriptorHeapType) override; + HRESULT STDMETHODCALLTYPE CreateRootSignature(UINT nodeMask, const void *pBlobWithRootSignature, SIZE_T blobLengthInBytes, REFIID riid, void **ppvRootSignature) override; + void STDMETHODCALLTYPE CreateConstantBufferView(const D3D12_CONSTANT_BUFFER_VIEW_DESC *pDesc, D3D12_CPU_DESCRIPTOR_HANDLE DestDescriptor) override; + void STDMETHODCALLTYPE CreateShaderResourceView(ID3D12Resource *pResource, const D3D12_SHADER_RESOURCE_VIEW_DESC *pDesc, D3D12_CPU_DESCRIPTOR_HANDLE DestDescriptor) override; + void STDMETHODCALLTYPE CreateUnorderedAccessView(ID3D12Resource *pResource, ID3D12Resource *pCounterResource, const D3D12_UNORDERED_ACCESS_VIEW_DESC *pDesc, D3D12_CPU_DESCRIPTOR_HANDLE DestDescriptor) override; + void STDMETHODCALLTYPE CreateRenderTargetView(ID3D12Resource *pResource, const D3D12_RENDER_TARGET_VIEW_DESC *pDesc, D3D12_CPU_DESCRIPTOR_HANDLE DestDescriptor) override; + void STDMETHODCALLTYPE CreateDepthStencilView(ID3D12Resource *pResource, const D3D12_DEPTH_STENCIL_VIEW_DESC *pDesc, D3D12_CPU_DESCRIPTOR_HANDLE DestDescriptor) override; + void STDMETHODCALLTYPE CreateSampler(const D3D12_SAMPLER_DESC *pDesc, D3D12_CPU_DESCRIPTOR_HANDLE DestDescriptor) override; + void STDMETHODCALLTYPE CopyDescriptors(UINT NumDestDescriptorRanges, const D3D12_CPU_DESCRIPTOR_HANDLE *pDestDescriptorRangeStarts, const UINT *pDestDescriptorRangeSizes, UINT NumSrcDescriptorRanges, const D3D12_CPU_DESCRIPTOR_HANDLE *pSrcDescriptorRangeStarts, const UINT *pSrcDescriptorRangeSizes, D3D12_DESCRIPTOR_HEAP_TYPE DescriptorHeapsType) override; + void STDMETHODCALLTYPE CopyDescriptorsSimple(UINT NumDescriptors, D3D12_CPU_DESCRIPTOR_HANDLE DestDescriptorRangeStart, D3D12_CPU_DESCRIPTOR_HANDLE SrcDescriptorRangeStart, D3D12_DESCRIPTOR_HEAP_TYPE DescriptorHeapsType) override; + D3D12_RESOURCE_ALLOCATION_INFO STDMETHODCALLTYPE GetResourceAllocationInfo(UINT visibleMask, UINT numResourceDescs, const D3D12_RESOURCE_DESC *pResourceDescs) override; + D3D12_HEAP_PROPERTIES STDMETHODCALLTYPE GetCustomHeapProperties(UINT nodeMask, D3D12_HEAP_TYPE heapType) override; + HRESULT STDMETHODCALLTYPE CreateCommittedResource(const D3D12_HEAP_PROPERTIES *pHeapProperties, D3D12_HEAP_FLAGS HeapFlags, const D3D12_RESOURCE_DESC *pResourceDesc, D3D12_RESOURCE_STATES InitialResourceState, const D3D12_CLEAR_VALUE *pOptimizedClearValue, REFIID riidResource, void **ppvResource) override; + HRESULT STDMETHODCALLTYPE CreateHeap(const D3D12_HEAP_DESC *pDesc, REFIID riid, void **ppvHeap) override; + HRESULT STDMETHODCALLTYPE CreatePlacedResource(ID3D12Heap *pHeap, UINT64 HeapOffset, const D3D12_RESOURCE_DESC *pDesc, D3D12_RESOURCE_STATES InitialState, const D3D12_CLEAR_VALUE *pOptimizedClearValue, REFIID riid, void **ppvResource) override; + HRESULT STDMETHODCALLTYPE CreateReservedResource(const D3D12_RESOURCE_DESC *pDesc, D3D12_RESOURCE_STATES InitialState, const D3D12_CLEAR_VALUE *pOptimizedClearValue, REFIID riid, void **ppvResource) override; + HRESULT STDMETHODCALLTYPE CreateSharedHandle(ID3D12DeviceChild *pObject, const SECURITY_ATTRIBUTES *pAttributes, DWORD Access, LPCWSTR Name, HANDLE *pHandle) override; + HRESULT STDMETHODCALLTYPE OpenSharedHandle(HANDLE NTHandle, REFIID riid, void **ppvObj) override; + HRESULT STDMETHODCALLTYPE OpenSharedHandleByName(LPCWSTR Name, DWORD Access, HANDLE *pNTHandle) override; + HRESULT STDMETHODCALLTYPE MakeResident(UINT NumObjects, ID3D12Pageable *const *ppObjects) override; + HRESULT STDMETHODCALLTYPE Evict(UINT NumObjects, ID3D12Pageable *const *ppObjects) override; + HRESULT STDMETHODCALLTYPE CreateFence(UINT64 InitialValue, D3D12_FENCE_FLAGS Flags, REFIID riid, void **ppFence) override; + HRESULT STDMETHODCALLTYPE GetDeviceRemovedReason() override; + void STDMETHODCALLTYPE GetCopyableFootprints(const D3D12_RESOURCE_DESC *pResourceDesc, UINT FirstSubresource, UINT NumSubresources, UINT64 BaseOffset, D3D12_PLACED_SUBRESOURCE_FOOTPRINT *pLayouts, UINT *pNumRows, UINT64 *pRowSizeInBytes, UINT64 *pTotalBytes) override; + HRESULT STDMETHODCALLTYPE CreateQueryHeap(const D3D12_QUERY_HEAP_DESC *pDesc, REFIID riid, void **ppvHeap) override; + HRESULT STDMETHODCALLTYPE SetStablePowerState(BOOL Enable) override; + HRESULT STDMETHODCALLTYPE CreateCommandSignature(const D3D12_COMMAND_SIGNATURE_DESC *pDesc, ID3D12RootSignature *pRootSignature, REFIID riid, void **ppvCommandSignature) override; + void STDMETHODCALLTYPE GetResourceTiling(ID3D12Resource *pTiledResource, UINT *pNumTilesForEntireResource, D3D12_PACKED_MIP_INFO *pPackedMipDesc, D3D12_TILE_SHAPE *pStandardTileShapeForNonPackedMips, UINT *pNumSubresourceTilings, UINT FirstSubresourceTilingToGet, D3D12_SUBRESOURCE_TILING *pSubresourceTilingsForNonPackedMips) override; + LUID STDMETHODCALLTYPE GetAdapterLuid() override; + #pragma endregion + #pragma region ID3D12Device1 + HRESULT STDMETHODCALLTYPE CreatePipelineLibrary(const void *pLibraryBlob, SIZE_T BlobLength, REFIID riid, void **ppPipelineLibrary) override; + HRESULT STDMETHODCALLTYPE SetEventOnMultipleFenceCompletion(ID3D12Fence *const *ppFences, const UINT64 *pFenceValues, UINT NumFences, D3D12_MULTIPLE_FENCE_WAIT_FLAGS Flags, HANDLE hEvent) override; + HRESULT STDMETHODCALLTYPE SetResidencyPriority(UINT NumObjects, ID3D12Pageable *const *ppObjects, const D3D12_RESIDENCY_PRIORITY *pPriorities) override; + #pragma endregion + #pragma region ID3D12Device2 + HRESULT STDMETHODCALLTYPE CreatePipelineState(const D3D12_PIPELINE_STATE_STREAM_DESC *pDesc, REFIID riid, void **ppPipelineState) override; + #pragma endregion + #pragma region ID3D12Device3 + HRESULT STDMETHODCALLTYPE OpenExistingHeapFromAddress(const void *pAddress, REFIID riid, void **ppvHeap) override; + HRESULT STDMETHODCALLTYPE OpenExistingHeapFromFileMapping(HANDLE hFileMapping, REFIID riid, void **ppvHeap) override; + HRESULT STDMETHODCALLTYPE EnqueueMakeResident(D3D12_RESIDENCY_FLAGS Flags, UINT NumObjects, ID3D12Pageable *const *ppObjects, ID3D12Fence *pFenceToSignal, UINT64 FenceValueToSignal) override; + #pragma endregion + #pragma region ID3D12Device4 + HRESULT STDMETHODCALLTYPE CreateCommandList1(UINT NodeMask, D3D12_COMMAND_LIST_TYPE Type, D3D12_COMMAND_LIST_FLAGS Flags, REFIID riid, void **ppCommandList) override; + HRESULT STDMETHODCALLTYPE CreateProtectedResourceSession(const D3D12_PROTECTED_RESOURCE_SESSION_DESC *pDesc, REFIID riid, void **ppSession) override; + HRESULT STDMETHODCALLTYPE CreateCommittedResource1(const D3D12_HEAP_PROPERTIES *pHeapProperties, D3D12_HEAP_FLAGS HeapFlags, const D3D12_RESOURCE_DESC *pDesc, D3D12_RESOURCE_STATES InitialResourceState, const D3D12_CLEAR_VALUE *pOptimizedClearValue, ID3D12ProtectedResourceSession *pProtectedSession, REFIID riidResource, void **ppvResource) override; + HRESULT STDMETHODCALLTYPE CreateHeap1(const D3D12_HEAP_DESC *pDesc, ID3D12ProtectedResourceSession *pProtectedSession, REFIID riid, void **ppvHeap) override; + HRESULT STDMETHODCALLTYPE CreateReservedResource1(const D3D12_RESOURCE_DESC *pDesc, D3D12_RESOURCE_STATES InitialState, const D3D12_CLEAR_VALUE *pOptimizedClearValue, ID3D12ProtectedResourceSession *pProtectedSession, REFIID riid, void **ppvResource) override; + D3D12_RESOURCE_ALLOCATION_INFO STDMETHODCALLTYPE GetResourceAllocationInfo1(UINT VisibleMask, UINT NumResourceDescs, const D3D12_RESOURCE_DESC *pResourceDescs, D3D12_RESOURCE_ALLOCATION_INFO1 *pResourceAllocationInfo1) override; + #pragma endregion + #pragma region ID3D12Device5 + HRESULT STDMETHODCALLTYPE CreateLifetimeTracker(ID3D12LifetimeOwner *pOwner, REFIID riid, void **ppvTracker) override; + void STDMETHODCALLTYPE RemoveDevice() override; + HRESULT STDMETHODCALLTYPE EnumerateMetaCommands(UINT *pNumMetaCommands, D3D12_META_COMMAND_DESC *pDescs) override; + HRESULT STDMETHODCALLTYPE EnumerateMetaCommandParameters(REFGUID CommandId, D3D12_META_COMMAND_PARAMETER_STAGE Stage, UINT *pTotalStructureSizeInBytes, UINT *pParameterCount, D3D12_META_COMMAND_PARAMETER_DESC *pParameterDescs) override; + HRESULT STDMETHODCALLTYPE CreateMetaCommand(REFGUID CommandId, UINT NodeMask, const void *pCreationParametersData, SIZE_T CreationParametersDataSizeInBytes, REFIID riid, void **ppMetaCommand) override; + HRESULT STDMETHODCALLTYPE CreateStateObject(const D3D12_STATE_OBJECT_DESC *pDesc, REFIID riid, void **ppStateObject) override; + void STDMETHODCALLTYPE GetRaytracingAccelerationStructurePrebuildInfo(const D3D12_BUILD_RAYTRACING_ACCELERATION_STRUCTURE_INPUTS *pDesc, D3D12_RAYTRACING_ACCELERATION_STRUCTURE_PREBUILD_INFO *pInfo) override; + D3D12_DRIVER_MATCHING_IDENTIFIER_STATUS STDMETHODCALLTYPE CheckDriverMatchingIdentifier(D3D12_SERIALIZED_DATA_TYPE SerializedDataType, const D3D12_SERIALIZED_DATA_DRIVER_MATCHING_IDENTIFIER *pIdentifierToCheck) override; + #pragma endregion + + bool check_and_upgrade_interface(REFIID riid); + + LONG _ref = 1; + ID3D12Device *_orig; + unsigned int _interface_version; +}; diff --git a/msvc/source/d3d12/runtime_d3d12.cpp b/msvc/source/d3d12/runtime_d3d12.cpp new file mode 100644 index 0000000..108652f --- /dev/null +++ b/msvc/source/d3d12/runtime_d3d12.cpp @@ -0,0 +1,1403 @@ +/** + * Copyright (C) 2014 Patrick Mours. All rights reserved. + * License: https://github.com/crosire/reshade#license + */ + +#include "log.hpp" +#include "hook_manager.hpp" +#include "runtime_d3d12.hpp" +#include "runtime_objects.hpp" +#include "resource_loading.hpp" +#include "dxgi/format_utils.hpp" +#include +#include + +namespace reshade::d3d12 +{ + struct d3d12_tex_data : base_object + { + D3D12_RESOURCE_STATES state; + com_ptr resource; + com_ptr descriptors; + }; + struct d3d12_pass_data : base_object + { + com_ptr pipeline; + D3D12_VIEWPORT viewport; + UINT num_render_targets; + D3D12_CPU_DESCRIPTOR_HANDLE render_targets; + }; + struct d3d12_technique_data : base_object + { + }; + struct d3d12_effect_data + { + com_ptr cb; + com_ptr signature; + com_ptr srv_heap; + com_ptr rtv_heap; + com_ptr sampler_heap; + + UINT16 sampler_list = 0; + SIZE_T storage_offset, storage_size; + D3D12_GPU_VIRTUAL_ADDRESS cbv_gpu_address; + D3D12_CPU_DESCRIPTOR_HANDLE srv_cpu_base; + D3D12_GPU_DESCRIPTOR_HANDLE srv_gpu_base; + D3D12_CPU_DESCRIPTOR_HANDLE rtv_cpu_base; + D3D12_CPU_DESCRIPTOR_HANDLE sampler_cpu_base; + D3D12_GPU_DESCRIPTOR_HANDLE sampler_gpu_base; + }; + + static uint32_t float_as_uint(float value) + { + return *reinterpret_cast(&value); + } + + static void transition_state( + const com_ptr &list, + const com_ptr &res, + D3D12_RESOURCE_STATES from, D3D12_RESOURCE_STATES to, + UINT subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES) + { + D3D12_RESOURCE_BARRIER transition = { D3D12_RESOURCE_BARRIER_TYPE_TRANSITION }; + transition.Transition.pResource = res.get(); + transition.Transition.Subresource = subresource; + transition.Transition.StateBefore = from; + transition.Transition.StateAfter = to; + list->ResourceBarrier(1, &transition); + } +} + +reshade::d3d12::runtime_d3d12::runtime_d3d12(ID3D12Device *device, ID3D12CommandQueue *queue, IDXGISwapChain3 *swapchain) : + _device(device), _commandqueue(queue), _swapchain(swapchain) +{ + assert(queue != nullptr); + assert(device != nullptr); + assert(swapchain != nullptr); + + _renderer_id = D3D_FEATURE_LEVEL_12_0; + + if (com_ptr factory; + SUCCEEDED(swapchain->GetParent(IID_PPV_ARGS(&factory)))) + { + const LUID luid = device->GetAdapterLuid(); + + if (com_ptr adapter; + factory->EnumAdapterByLuid(luid, IID_PPV_ARGS(&adapter))) + { + DXGI_ADAPTER_DESC desc; + adapter->GetDesc(&desc); + _vendor_id = desc.VendorId; + _device_id = desc.DeviceId; + } + } +} +reshade::d3d12::runtime_d3d12::~runtime_d3d12() +{ + if (_d3d_compiler != nullptr) + FreeLibrary(_d3d_compiler); +} + +bool reshade::d3d12::runtime_d3d12::init_backbuffer_textures(UINT num_buffers) +{ + _srv_handle_size = _device->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV); + _rtv_handle_size = _device->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_RTV); + _sampler_handle_size = _device->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER); + + { D3D12_DESCRIPTOR_HEAP_DESC desc = { D3D12_DESCRIPTOR_HEAP_TYPE_RTV }; + desc.NumDescriptors = num_buffers * 2; + + if (FAILED(_device->CreateDescriptorHeap(&desc, IID_PPV_ARGS(&_backbuffer_rtvs)))) + return false; + } + { D3D12_DESCRIPTOR_HEAP_DESC desc = { D3D12_DESCRIPTOR_HEAP_TYPE_DSV }; + desc.NumDescriptors = 1; + + if (FAILED(_device->CreateDescriptorHeap(&desc, IID_PPV_ARGS(&_depthstencil_dsvs)))) + return false; + } + + _backbuffers.resize(num_buffers); + + D3D12_CPU_DESCRIPTOR_HANDLE rtv_handle = _backbuffer_rtvs->GetCPUDescriptorHandleForHeapStart(); + + for (unsigned int i = 0; i < num_buffers; ++i) + { + if (FAILED(_swapchain->GetBuffer(i, IID_PPV_ARGS(&_backbuffers[i])))) + return false; +#ifdef _DEBUG + _backbuffers[i]->SetName(L"Backbuffer"); +#endif + + for (unsigned int srgb_write_enable = 0; srgb_write_enable < 2; ++srgb_write_enable, rtv_handle.ptr += _rtv_handle_size) + { + D3D12_RENDER_TARGET_VIEW_DESC rtv_desc = {}; + rtv_desc.Format = srgb_write_enable ? + make_dxgi_format_srgb(_backbuffer_format) : + make_dxgi_format_normal(_backbuffer_format); + rtv_desc.ViewDimension = D3D12_RTV_DIMENSION_TEXTURE2D; + + _device->CreateRenderTargetView(_backbuffers[i].get(), &rtv_desc, rtv_handle); + } + } + + { D3D12_RESOURCE_DESC desc = { D3D12_RESOURCE_DIMENSION_TEXTURE2D }; + desc.Width = _width; + desc.Height = _height; + desc.DepthOrArraySize = 1; + desc.MipLevels = 1; + desc.Format = make_dxgi_format_typeless(_backbuffer_format); + desc.SampleDesc = { 1, 0 }; + D3D12_HEAP_PROPERTIES props = { D3D12_HEAP_TYPE_DEFAULT }; + + if (FAILED(_device->CreateCommittedResource(&props, D3D12_HEAP_FLAG_NONE, &desc, D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE, nullptr, IID_PPV_ARGS(&_backbuffer_texture)))) + return false; +#ifdef _DEBUG + _backbuffer_texture->SetName(L"ReShade Backbuffer Texture"); +#endif + } + + return true; +} +bool reshade::d3d12::runtime_d3d12::init_default_depth_stencil() +{ + { D3D12_RESOURCE_DESC desc = { D3D12_RESOURCE_DIMENSION_TEXTURE2D }; + desc.Width = _width; + desc.Height = _height; + desc.DepthOrArraySize = 1; + desc.MipLevels = 1; + desc.Format = DXGI_FORMAT_D24_UNORM_S8_UINT; + desc.SampleDesc = { 1, 0 }; + desc.Flags = D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL; + D3D12_HEAP_PROPERTIES props = { D3D12_HEAP_TYPE_DEFAULT }; + + D3D12_CLEAR_VALUE clear_value = {}; + clear_value.Format = desc.Format; + clear_value.DepthStencil = { 1.0f, 0x0 }; + + if (FAILED(_device->CreateCommittedResource(&props, D3D12_HEAP_FLAG_NONE, &desc, D3D12_RESOURCE_STATE_DEPTH_WRITE, &clear_value, IID_PPV_ARGS(&_default_depthstencil)))) + return false; +#ifdef _DEBUG + _default_depthstencil->SetName(L"ReShade Default Depth-Stencil"); +#endif + _device->CreateDepthStencilView(_default_depthstencil.get(), nullptr, _depthstencil_dsvs->GetCPUDescriptorHandleForHeapStart()); + } + + return true; +} +bool reshade::d3d12::runtime_d3d12::init_mipmap_pipeline() +{ + { D3D12_DESCRIPTOR_RANGE srv_range = {}; + srv_range.RangeType = D3D12_DESCRIPTOR_RANGE_TYPE_SRV; + srv_range.NumDescriptors = 1; + srv_range.BaseShaderRegister = 0; // t0 + D3D12_DESCRIPTOR_RANGE uav_range = {}; + uav_range.RangeType = D3D12_DESCRIPTOR_RANGE_TYPE_UAV; + uav_range.NumDescriptors = 1; + uav_range.BaseShaderRegister = 0; // u0 + + D3D12_ROOT_PARAMETER params[3] = {}; + params[0].ParameterType = D3D12_ROOT_PARAMETER_TYPE_32BIT_CONSTANTS; + params[0].Constants.ShaderRegister = 0; // b0 + params[0].Constants.Num32BitValues = 2; + params[0].ShaderVisibility = D3D12_SHADER_VISIBILITY_ALL; + params[1].ParameterType = D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE; + params[1].DescriptorTable.NumDescriptorRanges = 1; + params[1].DescriptorTable.pDescriptorRanges = &srv_range; + params[1].ShaderVisibility = D3D12_SHADER_VISIBILITY_ALL; + params[2].ParameterType = D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE; + params[2].DescriptorTable.NumDescriptorRanges = 1; + params[2].DescriptorTable.pDescriptorRanges = &uav_range; + params[2].ShaderVisibility = D3D12_SHADER_VISIBILITY_ALL; + + D3D12_STATIC_SAMPLER_DESC samplers[1] = {}; + samplers[0].Filter = D3D12_FILTER_MIN_MAG_LINEAR_MIP_POINT; + samplers[0].AddressU = D3D12_TEXTURE_ADDRESS_MODE_CLAMP; + samplers[0].AddressV = D3D12_TEXTURE_ADDRESS_MODE_CLAMP; + samplers[0].AddressW = D3D12_TEXTURE_ADDRESS_MODE_CLAMP; + samplers[0].ComparisonFunc = D3D12_COMPARISON_FUNC_ALWAYS; + samplers[0].ShaderRegister = 0; // s0 + samplers[0].ShaderVisibility = D3D12_SHADER_VISIBILITY_ALL; + + D3D12_ROOT_SIGNATURE_DESC desc = {}; + desc.NumParameters = ARRAYSIZE(params); + desc.pParameters = params; + desc.NumStaticSamplers = ARRAYSIZE(samplers); + desc.pStaticSamplers = samplers; + + _mipmap_signature = create_root_signature(desc); + } + + { D3D12_COMPUTE_PIPELINE_STATE_DESC pso_desc = {}; + pso_desc.pRootSignature = _mipmap_signature.get(); + + const resources::data_resource cs = resources::load_data_resource(IDR_MIPMAP_CS); + pso_desc.CS = { cs.data, cs.data_size }; + + if (FAILED(_device->CreateComputePipelineState(&pso_desc, IID_PPV_ARGS(&_mipmap_pipeline)))) + return false; + } + + return true; +} + +bool reshade::d3d12::runtime_d3d12::on_init(const DXGI_SWAP_CHAIN_DESC &desc) +{ + RECT window_rect = {}; + GetClientRect(desc.OutputWindow, &window_rect); + + _width = desc.BufferDesc.Width; + _height = desc.BufferDesc.Height; + _window_width = window_rect.right - window_rect.left; + _window_height = window_rect.bottom - window_rect.top; + _backbuffer_format = desc.BufferDesc.Format; + + // Create multiple command allocators to buffer for multiple frames + _cmd_alloc.resize(desc.BufferCount); + for (UINT i = 0; i < desc.BufferCount; ++i) + if (FAILED(_device->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(&_cmd_alloc[i])))) + return false; + if (FAILED(_device->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, _cmd_alloc[0].get(), nullptr, IID_PPV_ARGS(&_cmd_list)))) + return false; + _cmd_list->Close(); // Immediately close since it will be reset on first use + + // Create fences for synchronization + _fence_event = CreateEvent(nullptr, FALSE, FALSE, nullptr); + if (_fence_event == nullptr) + return false; + _fence.resize(desc.BufferCount); + _fence_value.resize(desc.BufferCount); + for (UINT i = 0; i < desc.BufferCount; ++i) + if (FAILED(_device->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&_fence[i])))) + return false; + + if (!init_backbuffer_textures(desc.BufferCount) || + !init_default_depth_stencil() || + !init_mipmap_pipeline() +#if RESHADE_GUI + || !init_imgui_resources() +#endif + ) + return false; + + return runtime::on_init(desc.OutputWindow); +} +void reshade::d3d12::runtime_d3d12::on_reset() +{ + runtime::on_reset(); + + _cmd_list.reset(); + _cmd_alloc.clear(); + + CloseHandle(_fence_event); + _fence.clear(); + _fence_value.clear(); + + _backbuffers.clear(); + _backbuffer_rtvs.reset(); + _backbuffer_texture.reset(); + _depthstencil_dsvs.reset(); + _default_depthstencil.reset(); + + _mipmap_pipeline.reset(); + _mipmap_signature.reset(); + +#if RESHADE_GUI + for (unsigned int resource_index = 0; resource_index < 3; ++resource_index) + { + _imgui_index_buffer[resource_index].reset(); + _imgui_index_buffer_size[resource_index] = 0; + _imgui_vertex_buffer[resource_index].reset(); + _imgui_vertex_buffer_size[resource_index] = 0; + } + + _imgui_pipeline.reset(); + _imgui_signature.reset(); +#endif +} + +void reshade::d3d12::runtime_d3d12::on_present() +{ + if (!_is_initialized) + return; + + _swap_index = _swapchain->GetCurrentBackBufferIndex(); + + // Make sure all commands for this command allocator have finished executing before reseting it + if (_fence[_swap_index]->GetCompletedValue() < _fence_value[_swap_index]) + { + _fence[_swap_index]->SetEventOnCompletion(_fence_value[_swap_index], _fence_event); + WaitForSingleObject(_fence_event, INFINITE); + } + + // Reset command allocator before using it this frame again + _cmd_alloc[_swap_index]->Reset(); + + update_and_render_effects(); + runtime::on_present(); + + _commandqueue->Signal(_fence[_swap_index].get(), ++_fence_value[_swap_index]); +} + +void reshade::d3d12::runtime_d3d12::capture_screenshot(uint8_t *buffer) const +{ + if (_backbuffer_format != DXGI_FORMAT_R8G8B8A8_UNORM && + _backbuffer_format != DXGI_FORMAT_R8G8B8A8_UNORM_SRGB && + _backbuffer_format != DXGI_FORMAT_B8G8R8A8_UNORM && + _backbuffer_format != DXGI_FORMAT_B8G8R8A8_UNORM_SRGB) + { + LOG(WARN) << "Screenshots are not supported for back buffer format " << _backbuffer_format << '.'; + return; + } + + const uint32_t data_pitch = _width * 4; + const uint32_t download_pitch = (data_pitch + D3D12_TEXTURE_DATA_PITCH_ALIGNMENT - 1u) & ~(D3D12_TEXTURE_DATA_PITCH_ALIGNMENT - 1u); + + D3D12_RESOURCE_DESC desc = { D3D12_RESOURCE_DIMENSION_BUFFER }; + desc.Width = _height * download_pitch; + desc.Height = 1; + desc.DepthOrArraySize = 1; + desc.MipLevels = 1; + desc.SampleDesc = { 1, 0 }; + desc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR; + D3D12_HEAP_PROPERTIES props = { D3D12_HEAP_TYPE_READBACK }; + + com_ptr intermediate; + if (FAILED(_device->CreateCommittedResource(&props, D3D12_HEAP_FLAG_NONE, &desc, D3D12_RESOURCE_STATE_COPY_DEST, nullptr, IID_PPV_ARGS(&intermediate)))) + { + LOG(ERROR) << "Failed to create system memory texture for screenshot capture!"; + return; + } + +#ifdef _DEBUG + intermediate->SetName(L"ReShade screenshot texture"); +#endif + + const com_ptr cmd_list = create_command_list(); + if (cmd_list == nullptr) + return; + + transition_state(cmd_list, _backbuffers[_swap_index], D3D12_RESOURCE_STATE_PRESENT, D3D12_RESOURCE_STATE_COPY_SOURCE, 0); + { // Copy data from upload buffer into target texture + D3D12_TEXTURE_COPY_LOCATION src_location = { _backbuffers[_swap_index].get() }; + src_location.Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX; + src_location.SubresourceIndex = 0; + + D3D12_TEXTURE_COPY_LOCATION dst_location = { intermediate.get() }; + dst_location.Type = D3D12_TEXTURE_COPY_TYPE_PLACED_FOOTPRINT; + dst_location.PlacedFootprint.Footprint.Width = _width; + dst_location.PlacedFootprint.Footprint.Height = _height; + dst_location.PlacedFootprint.Footprint.Depth = 1; + dst_location.PlacedFootprint.Footprint.Format = DXGI_FORMAT_R8G8B8A8_UNORM; + dst_location.PlacedFootprint.Footprint.RowPitch = download_pitch; + + cmd_list->CopyTextureRegion(&dst_location, 0, 0, 0, &src_location, nullptr); + } + transition_state(cmd_list, _backbuffers[_swap_index], D3D12_RESOURCE_STATE_COPY_SOURCE, D3D12_RESOURCE_STATE_PRESENT, 0); + + // Execute and wait for completion + execute_command_list(cmd_list); + wait_for_command_queue(); + + // Copy data from system memory texture into output buffer + uint8_t *mapped_data; + if (FAILED(intermediate->Map(0, nullptr, reinterpret_cast(&mapped_data)))) + return; + + for (uint32_t y = 0; y < _height; y++, buffer += data_pitch, mapped_data += download_pitch) + { + memcpy(buffer, mapped_data, data_pitch); + for (uint32_t x = 0; x < data_pitch; x += 4) + buffer[x + 3] = 0xFF; // Clear alpha channel + } + + intermediate->Unmap(0, nullptr); +} + +bool reshade::d3d12::runtime_d3d12::init_texture(texture &info) +{ + info.impl = std::make_unique(); + + // Do not create resource if it is a reference, it is set in 'render_technique' + if (info.impl_reference != texture_reference::none) + return true; + + D3D12_RESOURCE_DESC desc = { D3D12_RESOURCE_DIMENSION_TEXTURE2D }; + desc.Width = info.width; + desc.Height = info.height; + desc.DepthOrArraySize = 1; + desc.MipLevels = static_cast(info.levels); + desc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; + desc.SampleDesc = { 1, 0 }; + desc.Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN; + desc.Flags = D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET; // Textures may be bound as render target + + if (info.levels > 1) // Need UAV for mipmap generation + desc.Flags |= D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS; + + switch (info.format) + { + case reshadefx::texture_format::r8: + desc.Format = DXGI_FORMAT_R8_UNORM; + break; + case reshadefx::texture_format::r16f: + desc.Format = DXGI_FORMAT_R16_FLOAT; + break; + case reshadefx::texture_format::r32f: + desc.Format = DXGI_FORMAT_R32_FLOAT; + break; + case reshadefx::texture_format::rg8: + desc.Format = DXGI_FORMAT_R8G8_UNORM; + break; + case reshadefx::texture_format::rg16: + desc.Format = DXGI_FORMAT_R16G16_UNORM; + break; + case reshadefx::texture_format::rg16f: + desc.Format = DXGI_FORMAT_R16G16_FLOAT; + break; + case reshadefx::texture_format::rg32f: + desc.Format = DXGI_FORMAT_R32G32_FLOAT; + break; + case reshadefx::texture_format::rgba8: + desc.Format = DXGI_FORMAT_R8G8B8A8_TYPELESS; + break; + case reshadefx::texture_format::rgba16: + desc.Format = DXGI_FORMAT_R16G16B16A16_UNORM; + break; + case reshadefx::texture_format::rgba16f: + desc.Format = DXGI_FORMAT_R16G16B16A16_FLOAT; + break; + case reshadefx::texture_format::rgba32f: + desc.Format = DXGI_FORMAT_R32G32B32A32_FLOAT; + break; + case reshadefx::texture_format::rgb10a2: + desc.Format = DXGI_FORMAT_R10G10B10A2_UNORM; + break; + } + + D3D12_HEAP_PROPERTIES props = { D3D12_HEAP_TYPE_DEFAULT }; + + // Render targets are always either cleared to zero or not cleared at all (see 'ClearRenderTargets' pass state), so can set the optimized clear value here to zero + D3D12_CLEAR_VALUE clear_value = {}; + clear_value.Format = make_dxgi_format_normal(desc.Format); + + const auto texture_data = info.impl->as(); + texture_data->state = D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE; + + if (HRESULT hr = _device->CreateCommittedResource(&props, D3D12_HEAP_FLAG_NONE, &desc, texture_data->state, &clear_value, IID_PPV_ARGS(&texture_data->resource)); FAILED(hr)) + { + LOG(ERROR) << "Failed to create texture '" << info.unique_name << "' (" + "Width = " << desc.Width << ", " + "Height = " << desc.Height << ", " + "Format = " << desc.Format << ")! " + "HRESULT is '" << std::hex << hr << std::dec << "'."; + return false; + } + +#ifdef _DEBUG + std::wstring debug_name; + debug_name.reserve(info.unique_name.size()); + utf8::unchecked::utf8to16(info.unique_name.begin(), info.unique_name.end(), std::back_inserter(debug_name)); + texture_data->resource->SetName(debug_name.c_str()); +#endif + + { D3D12_DESCRIPTOR_HEAP_DESC heap_desc = { D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV }; + heap_desc.NumDescriptors = info.levels /* SRV */ + info.levels - 1 /* UAV */; + heap_desc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE; + + if (FAILED(_device->CreateDescriptorHeap(&heap_desc, IID_PPV_ARGS(&texture_data->descriptors)))) + return false; + } + + D3D12_CPU_DESCRIPTOR_HANDLE srv_cpu_handle = texture_data->descriptors->GetCPUDescriptorHandleForHeapStart(); + + for (uint32_t level = 0; level < info.levels; ++level, srv_cpu_handle.ptr += _srv_handle_size) + { + D3D12_SHADER_RESOURCE_VIEW_DESC srv_desc = {}; + srv_desc.Format = make_dxgi_format_normal(desc.Format); + srv_desc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D; + srv_desc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING; + srv_desc.Texture2D.MipLevels = 1; + srv_desc.Texture2D.MostDetailedMip = level; + + _device->CreateShaderResourceView(texture_data->resource.get(), &srv_desc, srv_cpu_handle); + } + + // Generate UAVs for mipmap generation + for (uint32_t level = 1; level < info.levels; ++level, srv_cpu_handle.ptr += _srv_handle_size) + { + D3D12_UNORDERED_ACCESS_VIEW_DESC uav_desc = {}; + uav_desc.Format = make_dxgi_format_normal(desc.Format); + uav_desc.ViewDimension = D3D12_UAV_DIMENSION_TEXTURE2D; + uav_desc.Texture2D.MipSlice = level; + + _device->CreateUnorderedAccessView(texture_data->resource.get(), nullptr, &uav_desc, srv_cpu_handle); + } + + return true; +} +void reshade::d3d12::runtime_d3d12::upload_texture(texture &texture, const uint8_t *pixels) +{ + assert(texture.impl_reference == texture_reference::none); + + const uint32_t data_pitch = texture.width * 4; + const uint32_t upload_pitch = (data_pitch + D3D12_TEXTURE_DATA_PITCH_ALIGNMENT - 1u) & ~(D3D12_TEXTURE_DATA_PITCH_ALIGNMENT - 1u); + + D3D12_RESOURCE_DESC desc = { D3D12_RESOURCE_DIMENSION_BUFFER }; + desc.Width = texture.height * upload_pitch; + desc.Height = 1; + desc.DepthOrArraySize = 1; + desc.MipLevels = 1; + desc.SampleDesc = { 1, 0 }; + desc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR; + D3D12_HEAP_PROPERTIES props = { D3D12_HEAP_TYPE_UPLOAD }; + + com_ptr intermediate; + if (FAILED(_device->CreateCommittedResource(&props, D3D12_HEAP_FLAG_NONE, &desc, D3D12_RESOURCE_STATE_GENERIC_READ, nullptr, IID_PPV_ARGS(&intermediate)))) + { + LOG(ERROR) << "Failed to create system memory texture for texture updating!"; + return; + } + +#ifdef _DEBUG + intermediate->SetName(L"ReShade upload texture"); +#endif + + // Fill upload buffer with pixel data + uint8_t *mapped_data; + if (FAILED(intermediate->Map(0, nullptr, reinterpret_cast(&mapped_data)))) + return; + + switch (texture.format) + { + case reshadefx::texture_format::r8: + for (uint32_t y = 0; y < texture.height; ++y, mapped_data += upload_pitch, pixels += data_pitch) + for (uint32_t x = 0; x < texture.width; ++x) + mapped_data[x] = pixels[x * 4]; + break; + case reshadefx::texture_format::rg8: + for (uint32_t y = 0; y < texture.height; ++y, mapped_data += upload_pitch, pixels += data_pitch) + for (uint32_t x = 0; x < texture.width; ++x) + mapped_data[x * 2 + 0] = pixels[x * 4 + 0], + mapped_data[x * 2 + 1] = pixels[x * 4 + 1]; + break; + case reshadefx::texture_format::rgba8: + for (uint32_t y = 0; y < texture.height; ++y, mapped_data += upload_pitch, pixels += data_pitch) + memcpy(mapped_data, pixels, data_pitch); + break; + default: + LOG(ERROR) << "Texture upload is not supported for format " << static_cast(texture.format) << '!'; + break; + } + + intermediate->Unmap(0, nullptr); + + const auto texture_impl = texture.impl->as(); + + assert(pixels != nullptr); + assert(texture_impl != nullptr); + + const com_ptr cmd_list = create_command_list(); + if (cmd_list == nullptr) + return; + + transition_state(cmd_list, texture_impl->resource, texture_impl->state, D3D12_RESOURCE_STATE_COPY_DEST, 0); + { // Copy data from upload buffer into target texture + D3D12_TEXTURE_COPY_LOCATION src_location = { intermediate.get() }; + src_location.Type = D3D12_TEXTURE_COPY_TYPE_PLACED_FOOTPRINT; + src_location.PlacedFootprint.Footprint.Width = texture.width; + src_location.PlacedFootprint.Footprint.Height = texture.height; + src_location.PlacedFootprint.Footprint.Depth = 1; + src_location.PlacedFootprint.Footprint.Format = texture_impl->resource->GetDesc().Format; + src_location.PlacedFootprint.Footprint.RowPitch = upload_pitch; + + D3D12_TEXTURE_COPY_LOCATION dst_location = { texture_impl->resource.get() }; + dst_location.Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX; + dst_location.SubresourceIndex = 0; + + cmd_list->CopyTextureRegion(&dst_location, 0, 0, 0, &src_location, nullptr); + } + transition_state(cmd_list, texture_impl->resource, D3D12_RESOURCE_STATE_COPY_DEST, texture_impl->state, 0); + + generate_mipmaps(cmd_list, texture); + + // Execute and wait for completion + execute_command_list(cmd_list); + wait_for_command_queue(); +} + +void reshade::d3d12::runtime_d3d12::generate_mipmaps(const com_ptr &cmd_list, texture &texture) +{ + if (texture.levels <= 1) + return; // No need to generate mipmaps when texture does not have any + + const auto texture_impl = texture.impl->as(); + assert(texture_impl != nullptr); + + cmd_list->SetComputeRootSignature(_mipmap_signature.get()); + cmd_list->SetPipelineState(_mipmap_pipeline.get()); + ID3D12DescriptorHeap *const descriptor_heap = texture_impl->descriptors.get(); + cmd_list->SetDescriptorHeaps(1, &descriptor_heap); + + transition_state(cmd_list, texture_impl->resource, texture_impl->state, D3D12_RESOURCE_STATE_UNORDERED_ACCESS); + for (uint32_t level = 1; level < texture.levels; ++level) + { + const uint32_t width = std::max(1u, texture.width >> level); + const uint32_t height = std::max(1u, texture.height >> level); + + cmd_list->SetComputeRoot32BitConstant(0, float_as_uint(1.0f / width), 0); + cmd_list->SetComputeRoot32BitConstant(0, float_as_uint(1.0f / height), 1); + // Bind next higher mipmap level as input + cmd_list->SetComputeRootDescriptorTable(1, { texture_impl->descriptors->GetGPUDescriptorHandleForHeapStart().ptr + _srv_handle_size * (level - 1) }); + // There is no UAV for level 0, so substract one + cmd_list->SetComputeRootDescriptorTable(2, { texture_impl->descriptors->GetGPUDescriptorHandleForHeapStart().ptr + _srv_handle_size * (texture.levels + level - 1) }); + + cmd_list->Dispatch(std::max(1u, (width + 7) / 8), std::max(1u, (height + 7) / 8), 1); + + // Wait for all accesses to be finished, since the result will be the input for the next mipmap + D3D12_RESOURCE_BARRIER barrier = { D3D12_RESOURCE_BARRIER_TYPE_UAV }; + barrier.UAV.pResource = texture_impl->resource.get(); + cmd_list->ResourceBarrier(1, &barrier); + } + transition_state(cmd_list, texture_impl->resource, D3D12_RESOURCE_STATE_UNORDERED_ACCESS, texture_impl->state); +} + +com_ptr reshade::d3d12::runtime_d3d12::create_root_signature(const D3D12_ROOT_SIGNATURE_DESC &desc) const +{ + com_ptr signature; + if (com_ptr blob; SUCCEEDED(hooks::call(D3D12SerializeRootSignature)(&desc, D3D_ROOT_SIGNATURE_VERSION_1, &blob, nullptr))) + _device->CreateRootSignature(0, blob->GetBufferPointer(), blob->GetBufferSize(), IID_PPV_ARGS(&signature)); + return signature; +} +com_ptr reshade::d3d12::runtime_d3d12::create_command_list(const com_ptr &state) const +{ + // Reset command list using current command allocator and put it into the recording state + return SUCCEEDED(_cmd_list->Reset(_cmd_alloc[_swap_index].get(), state.get())) ? _cmd_list : nullptr; +} +void reshade::d3d12::runtime_d3d12::execute_command_list(const com_ptr &list) const +{ + if (FAILED(list->Close())) + return; + + ID3D12CommandList *const cmd_lists[] = { list.get() }; + _commandqueue->ExecuteCommandLists(ARRAYSIZE(cmd_lists), cmd_lists); +} +void reshade::d3d12::runtime_d3d12::wait_for_command_queue() const +{ + const UINT64 sync_value = ++_fence_value[_swap_index]; + _commandqueue->Signal(_fence[_swap_index].get(), sync_value); + _fence[_swap_index]->SetEventOnCompletion(sync_value, _fence_event); + WaitForSingleObject(_fence_event, INFINITE); +} + +bool reshade::d3d12::runtime_d3d12::compile_effect(effect_data &effect) +{ + if (_d3d_compiler == nullptr) + _d3d_compiler = LoadLibraryW(L"d3dcompiler_47.dll"); + + if (_d3d_compiler == nullptr) + { + LOG(ERROR) << "Unable to load HLSL compiler (\"d3dcompiler_47.dll\")."; + return false; + } + + const auto D3DCompile = reinterpret_cast(GetProcAddress(_d3d_compiler, "D3DCompile")); + + const std::string hlsl = effect.preamble + effect.module.hlsl; + std::unordered_map> entry_points; + + // Compile the generated HLSL source code to DX byte code + for (const auto &entry_point : effect.module.entry_points) + { + com_ptr d3d_errors; + + const HRESULT hr = D3DCompile( + hlsl.c_str(), hlsl.size(), + nullptr, nullptr, nullptr, + entry_point.first.c_str(), + entry_point.second ? "ps_5_0" : "vs_5_0", + D3DCOMPILE_ENABLE_STRICTNESS | D3DCOMPILE_OPTIMIZATION_LEVEL3, 0, + &entry_points[entry_point.first], &d3d_errors); + + if (d3d_errors != nullptr) // Append warnings to the output error string as well + effect.errors.append(static_cast(d3d_errors->GetBufferPointer()), d3d_errors->GetBufferSize() - 1); // Subtracting one to not append the null-terminator as well + + // No need to setup resources if any of the shaders failed to compile + if (FAILED(hr)) + return false; + } + + if (_effect_data.size() <= effect.index) + _effect_data.resize(effect.index + 1); + + d3d12_effect_data &effect_data = _effect_data[effect.index]; + effect_data.storage_size = effect.storage_size; + effect_data.storage_offset = effect.storage_offset; + + { D3D12_DESCRIPTOR_RANGE srv_range = {}; + srv_range.RangeType = D3D12_DESCRIPTOR_RANGE_TYPE_SRV; + srv_range.NumDescriptors = effect.module.num_texture_bindings; + srv_range.BaseShaderRegister = 0; + D3D12_DESCRIPTOR_RANGE sampler_range = {}; + sampler_range.RangeType = D3D12_DESCRIPTOR_RANGE_TYPE_SAMPLER; + sampler_range.NumDescriptors = effect.module.num_sampler_bindings; + sampler_range.BaseShaderRegister = 0; + + D3D12_ROOT_PARAMETER params[3] = {}; + params[0].ParameterType = D3D12_ROOT_PARAMETER_TYPE_CBV; + params[0].Descriptor.ShaderRegister = 0; // b0 (global constant buffer) + params[0].ShaderVisibility = D3D12_SHADER_VISIBILITY_ALL; + params[1].ParameterType = D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE; + params[1].DescriptorTable.NumDescriptorRanges = 1; + params[1].DescriptorTable.pDescriptorRanges = &srv_range; + params[1].ShaderVisibility = D3D12_SHADER_VISIBILITY_ALL; + params[2].ParameterType = D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE; + params[2].DescriptorTable.NumDescriptorRanges = 1; + params[2].DescriptorTable.pDescriptorRanges = &sampler_range; + params[2].ShaderVisibility = D3D12_SHADER_VISIBILITY_ALL; + + D3D12_ROOT_SIGNATURE_DESC desc = {}; + desc.NumParameters = ARRAYSIZE(params); + desc.pParameters = params; + + effect_data.signature = create_root_signature(desc); + } + + if (effect.storage_size != 0) + { + D3D12_RESOURCE_DESC desc = { D3D12_RESOURCE_DIMENSION_BUFFER }; + desc.Width = effect.storage_size; + desc.Height = 1; + desc.DepthOrArraySize = 1; + desc.MipLevels = 1; + desc.SampleDesc = { 1, 0 }; + desc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR; + D3D12_HEAP_PROPERTIES props = { D3D12_HEAP_TYPE_UPLOAD }; + + if (FAILED(_device->CreateCommittedResource(&props, D3D12_HEAP_FLAG_NONE, &desc, D3D12_RESOURCE_STATE_GENERIC_READ, nullptr, IID_PPV_ARGS(&effect_data.cb)))) + return false; +#ifdef _DEBUG + effect_data.cb->SetName(L"ReShade Global CB"); +#endif + effect_data.cbv_gpu_address = effect_data.cb->GetGPUVirtualAddress(); + } + + { D3D12_DESCRIPTOR_HEAP_DESC desc = { D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV }; + desc.NumDescriptors = effect.module.num_texture_bindings; + desc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE; + + if (FAILED(_device->CreateDescriptorHeap(&desc, IID_PPV_ARGS(&effect_data.srv_heap)))) + return false; + + effect_data.srv_cpu_base = effect_data.srv_heap->GetCPUDescriptorHandleForHeapStart(); + effect_data.srv_gpu_base = effect_data.srv_heap->GetGPUDescriptorHandleForHeapStart(); + } + + { D3D12_DESCRIPTOR_HEAP_DESC desc = { D3D12_DESCRIPTOR_HEAP_TYPE_RTV }; + for (auto &info : effect.module.techniques) + desc.NumDescriptors += static_cast(8 * info.passes.size()); + + if (FAILED(_device->CreateDescriptorHeap(&desc, IID_PPV_ARGS(&effect_data.rtv_heap)))) + return false; + + effect_data.rtv_cpu_base = effect_data.rtv_heap->GetCPUDescriptorHandleForHeapStart(); + } + + { D3D12_DESCRIPTOR_HEAP_DESC desc = { D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER }; + desc.NumDescriptors = effect.module.num_sampler_bindings; + desc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE; + + if (FAILED(_device->CreateDescriptorHeap(&desc, IID_PPV_ARGS(&effect_data.sampler_heap)))) + return false; + + effect_data.sampler_cpu_base = effect_data.sampler_heap->GetCPUDescriptorHandleForHeapStart(); + effect_data.sampler_gpu_base = effect_data.sampler_heap->GetGPUDescriptorHandleForHeapStart(); + } + + bool success = true; + + for (const reshadefx::sampler_info &info : effect.module.samplers) + { + if (info.binding >= D3D12_COMMONSHADER_SAMPLER_SLOT_COUNT) + { + LOG(ERROR) << "Cannot bind sampler '" << info.unique_name << "' since it exceeds the maximum number of allowed sampler slots in D3D12 (" << info.binding << ", allowed are up to " << D3D12_COMMONSHADER_SAMPLER_SLOT_COUNT << ")."; + return false; + } + if (info.texture_binding >= D3D12_COMMONSHADER_INPUT_RESOURCE_SLOT_COUNT) + { + LOG(ERROR) << "Cannot bind texture '" << info.texture_name << "' since it exceeds the maximum number of allowed resource slots in D3D12 (" << info.texture_binding << ", allowed are up to " << D3D12_COMMONSHADER_INPUT_RESOURCE_SLOT_COUNT << ")."; + return false; + } + + const auto existing_texture = std::find_if(_textures.begin(), _textures.end(), + [&texture_name = info.texture_name](const auto &item) { + return item.unique_name == texture_name && item.impl != nullptr; + }); + if (existing_texture == _textures.end()) + return false; + + com_ptr resource; + switch (existing_texture->impl_reference) + { + case texture_reference::back_buffer: + resource = _backbuffer_texture; + break; + case texture_reference::depth_buffer: + break; // TODO + default: + resource = existing_texture->impl->as()->resource; + break; + } + + if (resource == nullptr) + continue; + + { D3D12_SHADER_RESOURCE_VIEW_DESC desc = {}; + desc.Format = info.srgb ? + make_dxgi_format_srgb(resource->GetDesc().Format) : + make_dxgi_format_normal(resource->GetDesc().Format); + desc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D; + desc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING; + desc.Texture2D.MipLevels = existing_texture->levels; + + D3D12_CPU_DESCRIPTOR_HANDLE srv_handle = effect_data.srv_cpu_base; + srv_handle.ptr += info.texture_binding * _srv_handle_size; + + _device->CreateShaderResourceView(resource.get(), &desc, srv_handle); + } + + // Only initialize sampler if it has not been created before + if (0 == (effect_data.sampler_list & (1 << info.binding))) + { + effect_data.sampler_list |= (1 << info.binding); // D3D12_COMMONSHADER_SAMPLER_SLOT_COUNT is 16, so a 16-bit integer is enough to hold all bindings + + D3D12_SAMPLER_DESC desc = {}; + desc.Filter = static_cast(info.filter); + desc.AddressU = static_cast(info.address_u); + desc.AddressV = static_cast(info.address_v); + desc.AddressW = static_cast(info.address_w); + desc.MipLODBias = info.lod_bias; + desc.MaxAnisotropy = 1; + desc.ComparisonFunc = D3D12_COMPARISON_FUNC_NEVER; + desc.MinLOD = info.min_lod; + desc.MaxLOD = info.max_lod; + + D3D12_CPU_DESCRIPTOR_HANDLE sampler_handle = effect_data.sampler_cpu_base; + sampler_handle.ptr += info.binding * _sampler_handle_size; + + _device->CreateSampler(&desc, sampler_handle); + } + } + + for (technique &technique : _techniques) + if (technique.impl == nullptr && technique.effect_index == effect.index) + success &= init_technique(technique, effect_data, entry_points); + + return success; +} +void reshade::d3d12::runtime_d3d12::unload_effects() +{ + // Wait for all GPU operations to finish so resources are no longer referenced + wait_for_command_queue(); + + runtime::unload_effects(); + + _effect_data.clear(); +} + +bool reshade::d3d12::runtime_d3d12::init_technique(technique &technique, const d3d12_effect_data &effect_data, const std::unordered_map> &entry_points) +{ + technique.impl = std::make_unique(); + + for (size_t pass_index = 0; pass_index < technique.passes.size(); ++pass_index) + { + technique.passes_data.push_back(std::make_unique()); + + auto &pass_data = *technique.passes_data.back()->as(); + const auto &pass_info = technique.passes[pass_index]; + + D3D12_GRAPHICS_PIPELINE_STATE_DESC pso_desc = {}; + pso_desc.pRootSignature = _effect_data[technique.effect_index].signature.get(); + + const auto &VS = entry_points.at(pass_info.vs_entry_point); + pso_desc.VS = { VS->GetBufferPointer(), VS->GetBufferSize() }; + const auto &PS = entry_points.at(pass_info.ps_entry_point); + pso_desc.PS = { PS->GetBufferPointer(), PS->GetBufferSize() }; + + pass_data.viewport.Width = pass_info.viewport_width ? FLOAT(pass_info.viewport_width) : FLOAT(frame_width()); + pass_data.viewport.Height = pass_info.viewport_height ? FLOAT(pass_info.viewport_height) : FLOAT(frame_height()); + pass_data.viewport.MaxDepth = 1.0f; + + D3D12_CPU_DESCRIPTOR_HANDLE rtv_handle = effect_data.rtv_cpu_base; + rtv_handle.ptr += pass_index * 8 * _rtv_handle_size; + pass_data.render_targets = rtv_handle; + + pso_desc.DSVFormat = DXGI_FORMAT_D24_UNORM_S8_UINT; + + for (unsigned int k = 0; k < 8; k++) + { + if (pass_info.render_target_names[k].empty()) + continue; // Skip unbound render targets + + const auto render_target_texture = std::find_if(_textures.begin(), _textures.end(), + [&render_target = pass_info.render_target_names[k]](const auto &item) { + return item.unique_name == render_target; + }); + if (render_target_texture == _textures.end()) + return assert(false), false; + + const auto texture_impl = render_target_texture->impl->as(); + assert(texture_impl != nullptr); + + rtv_handle.ptr += k * _rtv_handle_size; + + D3D12_RENDER_TARGET_VIEW_DESC desc = {}; + desc.Format = pass_info.srgb_write_enable ? + make_dxgi_format_srgb(texture_impl->resource->GetDesc().Format) : + make_dxgi_format_normal(texture_impl->resource->GetDesc().Format); + desc.ViewDimension = D3D12_RTV_DIMENSION_TEXTURE2D; + + _device->CreateRenderTargetView(texture_impl->resource.get(), &desc, rtv_handle); + + pso_desc.RTVFormats[k] = desc.Format; + pso_desc.NumRenderTargets = k + 1; + } + + pass_data.num_render_targets = pso_desc.NumRenderTargets; + + if (pso_desc.NumRenderTargets == 0) + { + pso_desc.NumRenderTargets = 1; + pso_desc.RTVFormats[0] = pass_info.srgb_write_enable ? + make_dxgi_format_srgb(_backbuffer_format) : + make_dxgi_format_normal(_backbuffer_format); + } + + pso_desc.SampleMask = UINT_MAX; + pso_desc.SampleDesc = { 1, 0 }; + pso_desc.PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE; + pso_desc.NodeMask = 1; + + { D3D12_BLEND_DESC &desc = pso_desc.BlendState; + desc.RenderTarget[0].BlendEnable = pass_info.blend_enable; + + const auto literal_to_blend_func = [](unsigned int value) { + switch (value) { + default: + case 1: return D3D12_BLEND_ONE; + case 0: return D3D12_BLEND_ZERO; + case 2: return D3D12_BLEND_SRC_COLOR; + case 4: return D3D12_BLEND_INV_SRC_COLOR; + case 3: return D3D12_BLEND_SRC_ALPHA; + case 5: return D3D12_BLEND_INV_SRC_ALPHA; + case 6: return D3D12_BLEND_DEST_ALPHA; + case 7: return D3D12_BLEND_INV_DEST_ALPHA; + case 8: return D3D12_BLEND_DEST_COLOR; + case 9: return D3D12_BLEND_INV_DEST_COLOR; + } + }; + + desc.RenderTarget[0].SrcBlend = literal_to_blend_func(pass_info.src_blend); + desc.RenderTarget[0].DestBlend = literal_to_blend_func(pass_info.dest_blend); + desc.RenderTarget[0].BlendOp = static_cast(pass_info.blend_op); + desc.RenderTarget[0].SrcBlendAlpha = literal_to_blend_func(pass_info.src_blend_alpha); + desc.RenderTarget[0].DestBlendAlpha = literal_to_blend_func(pass_info.dest_blend_alpha); + desc.RenderTarget[0].BlendOpAlpha = static_cast(pass_info.blend_op_alpha); + desc.RenderTarget[0].RenderTargetWriteMask = pass_info.color_write_mask; + } + + { D3D12_RASTERIZER_DESC &desc = pso_desc.RasterizerState; + desc.FillMode = D3D12_FILL_MODE_SOLID; + desc.CullMode = D3D12_CULL_MODE_NONE; + desc.DepthClipEnable = true; + } + + { D3D12_DEPTH_STENCIL_DESC &desc = pso_desc.DepthStencilState; + desc.DepthEnable = FALSE; + desc.DepthWriteMask = D3D12_DEPTH_WRITE_MASK_ZERO; + desc.DepthFunc = D3D12_COMPARISON_FUNC_ALWAYS; + + const auto literal_to_stencil_op = [](unsigned int value) { + switch (value) { + default: + case 1: return D3D12_STENCIL_OP_KEEP; + case 0: return D3D12_STENCIL_OP_ZERO; + case 3: return D3D12_STENCIL_OP_REPLACE; + case 4: return D3D12_STENCIL_OP_INCR_SAT; + case 5: return D3D12_STENCIL_OP_DECR_SAT; + case 6: return D3D12_STENCIL_OP_INVERT; + case 7: return D3D12_STENCIL_OP_INCR; + case 8: return D3D12_STENCIL_OP_DECR; + } + }; + + desc.StencilEnable = pass_info.stencil_enable; + desc.StencilReadMask = pass_info.stencil_read_mask; + desc.StencilWriteMask = pass_info.stencil_write_mask; + desc.FrontFace.StencilFailOp = literal_to_stencil_op(pass_info.stencil_op_fail); + desc.FrontFace.StencilDepthFailOp = literal_to_stencil_op(pass_info.stencil_op_depth_fail); + desc.FrontFace.StencilPassOp = literal_to_stencil_op(pass_info.stencil_op_pass); + desc.FrontFace.StencilFunc = static_cast(pass_info.stencil_comparison_func); + desc.BackFace = desc.FrontFace; + } + + if (HRESULT hr = _device->CreateGraphicsPipelineState(&pso_desc, IID_PPV_ARGS(&pass_data.pipeline)); FAILED(hr)) + { + LOG(ERROR) << "Failed to create pipeline for pass " << pass_index << " in technique '" << technique.name << "'! " + "HRESULT is '" << std::hex << hr << std::dec << "'."; + return false; + } + } + + return true; +} + +void reshade::d3d12::runtime_d3d12::render_technique(technique &technique) +{ + d3d12_effect_data &effect_data = _effect_data[technique.effect_index]; + + const com_ptr cmd_list = create_command_list(); + if (cmd_list == nullptr) + return; + + ID3D12DescriptorHeap *const descriptor_heaps[] = { effect_data.srv_heap.get(), effect_data.sampler_heap.get() }; + cmd_list->SetDescriptorHeaps(ARRAYSIZE(descriptor_heaps), descriptor_heaps); + cmd_list->SetGraphicsRootSignature(effect_data.signature.get()); + + // Setup vertex input + cmd_list->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST); + + // Setup shader constants + if (effect_data.storage_size != 0) + { + void *mapped; + effect_data.cb->Map(0, nullptr, &mapped); + memcpy(mapped, _uniform_data_storage.data() + effect_data.storage_offset, effect_data.storage_size); + effect_data.cb->Unmap(0, nullptr); + + cmd_list->SetGraphicsRootConstantBufferView(0, effect_data.cbv_gpu_address); + } + + // Setup shader resources + cmd_list->SetGraphicsRootDescriptorTable(1, effect_data.srv_gpu_base); + + // Setup samplers + cmd_list->SetGraphicsRootDescriptorTable(2, effect_data.sampler_gpu_base); + + // Clear default depth stencil + const D3D12_CPU_DESCRIPTOR_HANDLE default_depth_stencil = _depthstencil_dsvs->GetCPUDescriptorHandleForHeapStart(); + cmd_list->ClearDepthStencilView(default_depth_stencil, D3D12_CLEAR_FLAG_DEPTH | D3D12_CLEAR_FLAG_STENCIL, 1.0f, 0, 0, nullptr); + + transition_state(cmd_list, _backbuffers[_swap_index], D3D12_RESOURCE_STATE_PRESENT, D3D12_RESOURCE_STATE_RENDER_TARGET); + + for (size_t i = 0; i < technique.passes.size(); ++i) + { + const auto &pass_info = technique.passes[i]; + const auto &pass_data = *technique.passes_data[i]->as(); + + // Transition render targets + for (unsigned int k = 0; k < pass_data.num_render_targets; ++k) + { + const auto texture_impl = std::find_if(_textures.begin(), _textures.end(), + [&render_target = pass_info.render_target_names[k]](const auto &item) { + return item.unique_name == render_target; + })->impl->as(); + + if (texture_impl->state != D3D12_RESOURCE_STATE_RENDER_TARGET) + transition_state(cmd_list, texture_impl->resource, texture_impl->state, D3D12_RESOURCE_STATE_RENDER_TARGET); + texture_impl->state = D3D12_RESOURCE_STATE_RENDER_TARGET; + } + + // Save back buffer of previous pass + transition_state(cmd_list, _backbuffer_texture, D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE, D3D12_RESOURCE_STATE_COPY_DEST); + transition_state(cmd_list, _backbuffers[_swap_index], D3D12_RESOURCE_STATE_RENDER_TARGET, D3D12_RESOURCE_STATE_COPY_SOURCE); + cmd_list->CopyResource(_backbuffer_texture.get(), _backbuffers[_swap_index].get()); + transition_state(cmd_list, _backbuffer_texture, D3D12_RESOURCE_STATE_COPY_DEST, D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE); + transition_state(cmd_list, _backbuffers[_swap_index], D3D12_RESOURCE_STATE_COPY_SOURCE, D3D12_RESOURCE_STATE_RENDER_TARGET); + + // Setup states + cmd_list->SetPipelineState(pass_data.pipeline.get()); + cmd_list->OMSetStencilRef(pass_info.stencil_reference_value); + + // Setup render targets + const float clear_color[4] = { 0.0f, 0.0f, 0.0f, 0.0f }; + + if (pass_data.num_render_targets == 0) + { + D3D12_CPU_DESCRIPTOR_HANDLE render_target = { _backbuffer_rtvs->GetCPUDescriptorHandleForHeapStart().ptr + (_swap_index * 2 + pass_info.srgb_write_enable) * _rtv_handle_size }; + cmd_list->OMSetRenderTargets(1, &render_target, false, pass_info.stencil_enable ? &default_depth_stencil : nullptr); + + if (pass_info.clear_render_targets) + cmd_list->ClearRenderTargetView(render_target, clear_color, 0, nullptr); + } + else if (_width == UINT(pass_data.viewport.Width) && _height == UINT(pass_data.viewport.Height)) + { + cmd_list->OMSetRenderTargets(pass_data.num_render_targets, &pass_data.render_targets, true, pass_info.stencil_enable ? &default_depth_stencil : nullptr); + + if (pass_info.clear_render_targets) + for (UINT k = 0; k < pass_data.num_render_targets; ++k) + cmd_list->ClearRenderTargetView({ pass_data.render_targets.ptr + k * _rtv_handle_size }, clear_color, 0, nullptr); + } + else + { + assert(!pass_info.stencil_enable); + + cmd_list->OMSetRenderTargets(pass_data.num_render_targets, &pass_data.render_targets, true, nullptr); + + if (pass_info.clear_render_targets) + for (UINT k = 0; k < pass_data.num_render_targets; ++k) + cmd_list->ClearRenderTargetView({ pass_data.render_targets.ptr + k * _rtv_handle_size }, clear_color, 0, nullptr); + } + + cmd_list->RSSetViewports(1, &pass_data.viewport); + + D3D12_RECT scissor_rect = { 0, 0, LONG(pass_data.viewport.Width), LONG(pass_data.viewport.Height) }; + cmd_list->RSSetScissorRects(1, &scissor_rect); + + // Draw triangle + cmd_list->DrawInstanced(3, 1, 0, 0); + + _vertices += 3; + _drawcalls += 1; + + // Generate mipmaps + for (unsigned int k = 0; k < pass_data.num_render_targets; ++k) + { + const auto render_target_texture = std::find_if(_textures.begin(), _textures.end(), + [&render_target = pass_info.render_target_names[k]](const auto &item) { + return item.unique_name == render_target; + }); + + generate_mipmaps(cmd_list, *render_target_texture); + } + } + + transition_state(cmd_list, _backbuffers[_swap_index], D3D12_RESOURCE_STATE_RENDER_TARGET, D3D12_RESOURCE_STATE_PRESENT); + + execute_command_list(cmd_list); +} + +#if RESHADE_GUI +bool reshade::d3d12::runtime_d3d12::init_imgui_resources() +{ + { D3D12_DESCRIPTOR_RANGE srv_range = {}; + srv_range.RangeType = D3D12_DESCRIPTOR_RANGE_TYPE_SRV; + srv_range.NumDescriptors = 1; + srv_range.BaseShaderRegister = 0; // t0 + + D3D12_ROOT_PARAMETER params[2] = {}; + params[0].ParameterType = D3D12_ROOT_PARAMETER_TYPE_32BIT_CONSTANTS; + params[0].Constants.ShaderRegister = 0; // b0 + params[0].Constants.Num32BitValues = 16; + params[0].ShaderVisibility = D3D12_SHADER_VISIBILITY_VERTEX; + params[1].ParameterType = D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE; + params[1].DescriptorTable.NumDescriptorRanges = 1; + params[1].DescriptorTable.pDescriptorRanges = &srv_range; + params[1].ShaderVisibility = D3D12_SHADER_VISIBILITY_PIXEL; + + D3D12_STATIC_SAMPLER_DESC samplers[1] = {}; + samplers[0].Filter = D3D12_FILTER_MIN_MAG_MIP_LINEAR; + samplers[0].AddressU = D3D12_TEXTURE_ADDRESS_MODE_WRAP; + samplers[0].AddressV = D3D12_TEXTURE_ADDRESS_MODE_WRAP; + samplers[0].AddressW = D3D12_TEXTURE_ADDRESS_MODE_WRAP; + samplers[0].ComparisonFunc = D3D12_COMPARISON_FUNC_ALWAYS; + samplers[0].ShaderRegister = 0; // s0 + samplers[0].ShaderVisibility = D3D12_SHADER_VISIBILITY_PIXEL; + + D3D12_ROOT_SIGNATURE_DESC desc = {}; + desc.NumParameters = ARRAYSIZE(params); + desc.pParameters = params; + desc.NumStaticSamplers = ARRAYSIZE(samplers); + desc.pStaticSamplers = samplers; + desc.Flags = D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT; + + _imgui_signature = create_root_signature(desc); + } + + D3D12_GRAPHICS_PIPELINE_STATE_DESC pso_desc = {}; + pso_desc.pRootSignature = _imgui_signature.get(); + pso_desc.SampleMask = UINT_MAX; + pso_desc.NumRenderTargets = 1; + pso_desc.RTVFormats[0] = _backbuffer_format; + pso_desc.SampleDesc = { 1, 0 }; + pso_desc.PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE; + pso_desc.NodeMask = 1; + + { const resources::data_resource vs = resources::load_data_resource(IDR_IMGUI_VS); + pso_desc.VS = { vs.data, vs.data_size }; + + static const D3D12_INPUT_ELEMENT_DESC input_layout[] = { + { "POSITION", 0, DXGI_FORMAT_R32G32_FLOAT, 0, offsetof(ImDrawVert, pos), D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 }, + { "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, offsetof(ImDrawVert, uv ), D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 }, + { "COLOR", 0, DXGI_FORMAT_R8G8B8A8_UNORM, 0, offsetof(ImDrawVert, col), D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 }, + }; + pso_desc.InputLayout = { input_layout, ARRAYSIZE(input_layout) }; + } + { const resources::data_resource ps = resources::load_data_resource(IDR_IMGUI_PS); + pso_desc.PS = { ps.data, ps.data_size }; + } + + { D3D12_BLEND_DESC &desc = pso_desc.BlendState; + desc.RenderTarget[0].BlendEnable = true; + desc.RenderTarget[0].SrcBlend = D3D12_BLEND_SRC_ALPHA; + desc.RenderTarget[0].DestBlend = D3D12_BLEND_INV_SRC_ALPHA; + desc.RenderTarget[0].BlendOp = D3D12_BLEND_OP_ADD; + desc.RenderTarget[0].SrcBlendAlpha = D3D12_BLEND_INV_SRC_ALPHA; + desc.RenderTarget[0].DestBlendAlpha = D3D12_BLEND_ZERO; + desc.RenderTarget[0].BlendOpAlpha = D3D12_BLEND_OP_ADD; + desc.RenderTarget[0].RenderTargetWriteMask = D3D12_COLOR_WRITE_ENABLE_ALL; + } + + { D3D12_RASTERIZER_DESC &desc = pso_desc.RasterizerState; + desc.FillMode = D3D12_FILL_MODE_SOLID; + desc.CullMode = D3D12_CULL_MODE_NONE; + desc.DepthClipEnable = true; + } + + { D3D12_DEPTH_STENCIL_DESC &desc = pso_desc.DepthStencilState; + desc.DepthEnable = false; + desc.StencilEnable = false; + } + + return SUCCEEDED(_device->CreateGraphicsPipelineState(&pso_desc, IID_PPV_ARGS(&_imgui_pipeline))); +} + +void reshade::d3d12::runtime_d3d12::render_imgui_draw_data(ImDrawData *draw_data) +{ + const unsigned int resource_index = _framecount % 3; + + // Create and grow vertex/index buffers if needed + if (_imgui_index_buffer_size[resource_index] < UINT(draw_data->TotalIdxCount)) + { + _imgui_index_buffer[resource_index].reset(); + + const UINT new_size = draw_data->TotalIdxCount + 10000; + D3D12_RESOURCE_DESC desc = { D3D12_RESOURCE_DIMENSION_BUFFER }; + desc.Width = new_size * sizeof(ImDrawIdx); + desc.Height = 1; + desc.DepthOrArraySize = 1; + desc.MipLevels = 1; + desc.SampleDesc = { 1, 0 }; + desc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR; + D3D12_HEAP_PROPERTIES props = { D3D12_HEAP_TYPE_UPLOAD }; + + if (FAILED(_device->CreateCommittedResource(&props, D3D12_HEAP_FLAG_NONE, &desc, D3D12_RESOURCE_STATE_GENERIC_READ, NULL, IID_PPV_ARGS(&_imgui_index_buffer[resource_index])))) + return; +#ifdef _DEBUG + _imgui_index_buffer[resource_index]->SetName(L"ImGui Index Buffer"); +#endif + _imgui_index_buffer_size[resource_index] = new_size; + } + if (_imgui_vertex_buffer_size[resource_index] < UINT(draw_data->TotalVtxCount)) + { + _imgui_vertex_buffer[resource_index].reset(); + + const UINT new_size = draw_data->TotalVtxCount + 5000; + D3D12_RESOURCE_DESC desc = { D3D12_RESOURCE_DIMENSION_BUFFER }; + desc.Width = new_size * sizeof(ImDrawVert); + desc.Height = 1; + desc.DepthOrArraySize = 1; + desc.MipLevels = 1; + desc.SampleDesc = { 1, 0 }; + desc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR; + D3D12_HEAP_PROPERTIES props = { D3D12_HEAP_TYPE_UPLOAD }; + + if (FAILED(_device->CreateCommittedResource(&props, D3D12_HEAP_FLAG_NONE, &desc, D3D12_RESOURCE_STATE_GENERIC_READ, NULL, IID_PPV_ARGS(&_imgui_vertex_buffer[resource_index])))) + return; +#ifdef _DEBUG + _imgui_index_buffer[resource_index]->SetName(L"ImGui Vertex Buffer"); +#endif + _imgui_vertex_buffer_size[resource_index] = new_size; + } + + ImDrawIdx *idx_dst; ImDrawVert *vtx_dst; + if (FAILED(_imgui_index_buffer[resource_index]->Map(0, nullptr, reinterpret_cast(&idx_dst))) || + FAILED(_imgui_vertex_buffer[resource_index]->Map(0, nullptr, reinterpret_cast(&vtx_dst)))) + return; + + for (int n = 0; n < draw_data->CmdListsCount; n++) + { + const ImDrawList *draw_list = draw_data->CmdLists[n]; + CopyMemory(idx_dst, draw_list->IdxBuffer.Data, draw_list->IdxBuffer.Size * sizeof(ImDrawIdx)); + CopyMemory(vtx_dst, draw_list->VtxBuffer.Data, draw_list->VtxBuffer.Size * sizeof(ImDrawVert)); + idx_dst += draw_list->IdxBuffer.Size; + vtx_dst += draw_list->VtxBuffer.Size; + } + + _imgui_index_buffer[resource_index]->Unmap(0, nullptr); + _imgui_vertex_buffer[resource_index]->Unmap(0, nullptr); + + const com_ptr cmd_list = create_command_list(_imgui_pipeline); + if (cmd_list == nullptr) + return; + + // Transition render target + transition_state(cmd_list, _backbuffers[_swap_index], D3D12_RESOURCE_STATE_PRESENT, D3D12_RESOURCE_STATE_RENDER_TARGET); + + // Setup orthographic projection matrix + const float ortho_projection[16] = { + 2.0f / draw_data->DisplaySize.x, 0.0f, 0.0f, 0.0f, + 0.0f, -2.0f / draw_data->DisplaySize.y, 0.0f, 0.0f, + 0.0f, 0.0f, 0.5f, 0.0f, + -(2 * draw_data->DisplayPos.x + draw_data->DisplaySize.x) / draw_data->DisplaySize.x, + +(2 * draw_data->DisplayPos.y + draw_data->DisplaySize.y) / draw_data->DisplaySize.y, 0.5f, 1.0f, + }; + + // Setup render state and render draw lists + const D3D12_INDEX_BUFFER_VIEW index_buffer_view = { + _imgui_index_buffer[resource_index]->GetGPUVirtualAddress(), _imgui_index_buffer_size[resource_index] * sizeof(ImDrawIdx), sizeof(ImDrawIdx) == 2 ? DXGI_FORMAT_R16_UINT : DXGI_FORMAT_R32_UINT }; + cmd_list->IASetIndexBuffer(&index_buffer_view); + const D3D12_VERTEX_BUFFER_VIEW vertex_buffer_view = { + _imgui_vertex_buffer[resource_index]->GetGPUVirtualAddress(), _imgui_vertex_buffer_size[resource_index] * sizeof(ImDrawVert), sizeof(ImDrawVert) }; + cmd_list->IASetVertexBuffers(0, 1, &vertex_buffer_view); + cmd_list->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST); + cmd_list->SetGraphicsRootSignature(_imgui_signature.get()); + cmd_list->SetGraphicsRoot32BitConstants(0, sizeof(ortho_projection) / 4, ortho_projection, 0); + const D3D12_VIEWPORT viewport = { 0, 0, draw_data->DisplaySize.x, draw_data->DisplaySize.y, 0.0f, 1.0f }; + cmd_list->RSSetViewports(1, &viewport); + const FLOAT blend_factor[4] = { 0.f, 0.f, 0.f, 0.f }; + cmd_list->OMSetBlendFactor(blend_factor); + D3D12_CPU_DESCRIPTOR_HANDLE render_target = { _backbuffer_rtvs->GetCPUDescriptorHandleForHeapStart().ptr + _swap_index * 2 * _rtv_handle_size }; + cmd_list->OMSetRenderTargets(1, &render_target, false, nullptr); + + UINT vtx_offset = 0, idx_offset = 0; + for (int n = 0; n < draw_data->CmdListsCount; ++n) + { + const ImDrawList *const draw_list = draw_data->CmdLists[n]; + + for (const ImDrawCmd &cmd : draw_list->CmdBuffer) + { + assert(cmd.TextureId != 0); + assert(cmd.UserCallback == nullptr); + + const D3D12_RECT scissor_rect = { + static_cast(cmd.ClipRect.x - draw_data->DisplayPos.x), + static_cast(cmd.ClipRect.y - draw_data->DisplayPos.y), + static_cast(cmd.ClipRect.z - draw_data->DisplayPos.x), + static_cast(cmd.ClipRect.w - draw_data->DisplayPos.y) + }; + cmd_list->RSSetScissorRects(1, &scissor_rect); + + const auto tex_data = static_cast(cmd.TextureId); + // TODO: Transition resource state of the user texture? + assert(tex_data->state == D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE); + + // First descriptor in resource-specific descriptor heap is SRV to top-most mipmap level + ID3D12DescriptorHeap *const descriptor_heap = { tex_data->descriptors.get() }; + cmd_list->SetDescriptorHeaps(1, &descriptor_heap); + cmd_list->SetGraphicsRootDescriptorTable(1, descriptor_heap->GetGPUDescriptorHandleForHeapStart()); + + cmd_list->DrawIndexedInstanced(cmd.ElemCount, 1, idx_offset, vtx_offset, 0); + + idx_offset += cmd.ElemCount; + } + + vtx_offset += draw_list->VtxBuffer.Size; + } + + // Transition render target back to previous state + transition_state(cmd_list, _backbuffers[_swap_index], D3D12_RESOURCE_STATE_RENDER_TARGET, D3D12_RESOURCE_STATE_PRESENT); + + execute_command_list(cmd_list); +} +#endif diff --git a/msvc/source/d3d12/runtime_d3d12.hpp b/msvc/source/d3d12/runtime_d3d12.hpp new file mode 100644 index 0000000..5c01e8e --- /dev/null +++ b/msvc/source/d3d12/runtime_d3d12.hpp @@ -0,0 +1,96 @@ +/** + * Copyright (C) 2014 Patrick Mours. All rights reserved. + * License: https://github.com/crosire/reshade#license + */ + +#pragma once + +#include "runtime.hpp" +#include "com_ptr.hpp" +#include +#include + +namespace reshadefx { struct sampler_info; } + +namespace reshade::d3d12 +{ + class runtime_d3d12 : public runtime + { + public: + runtime_d3d12(ID3D12Device *device, ID3D12CommandQueue *queue, IDXGISwapChain3 *swapchain); + ~runtime_d3d12(); + + bool on_init(const DXGI_SWAP_CHAIN_DESC &desc); + void on_reset(); + void on_present(); + + void capture_screenshot(uint8_t *buffer) const override; + + private: + bool init_backbuffer_textures(UINT num_buffers); + bool init_default_depth_stencil(); + bool init_mipmap_pipeline(); + + bool init_texture(texture &info) override; + void upload_texture(texture &texture, const uint8_t *pixels) override; + + bool compile_effect(effect_data &effect) override; + void unload_effects() override; + + bool init_technique(technique &technique, const struct d3d12_effect_data &effect_data, const std::unordered_map> &entry_points); + + void render_technique(technique &technique) override; + +#if RESHADE_GUI + bool init_imgui_resources(); + void render_imgui_draw_data(ImDrawData *data) override; +#endif + + void generate_mipmaps(const com_ptr &list, texture &texture); + + com_ptr create_root_signature(const D3D12_ROOT_SIGNATURE_DESC &desc) const; + com_ptr create_command_list(const com_ptr &state = nullptr) const; + void execute_command_list(const com_ptr &list) const; + void wait_for_command_queue() const; + + com_ptr _device; + com_ptr _commandqueue; + com_ptr _swapchain; + + UINT _swap_index = 0; + HANDLE _fence_event = nullptr; + mutable std::vector _fence_value; + std::vector> _fence; + + com_ptr _cmd_list; + std::vector> _cmd_alloc; + + DXGI_FORMAT _backbuffer_format = DXGI_FORMAT_UNKNOWN; + com_ptr _backbuffer_rtvs; + com_ptr _depthstencil_dsvs; + std::vector> _backbuffers; + + com_ptr _backbuffer_texture; + com_ptr _default_depthstencil; + + com_ptr _mipmap_pipeline; + com_ptr _mipmap_signature; + + UINT _srv_handle_size = 0; + UINT _rtv_handle_size = 0; + UINT _sampler_handle_size = 0; + + std::vector _effect_data; + + HMODULE _d3d_compiler = nullptr; + +#if RESHADE_GUI + unsigned int _imgui_index_buffer_size[3] = {}; + com_ptr _imgui_index_buffer[3]; + unsigned int _imgui_vertex_buffer_size[3] = {}; + com_ptr _imgui_vertex_buffer[3]; + com_ptr _imgui_pipeline; + com_ptr _imgui_signature; +#endif + }; +} diff --git a/msvc/source/d3d9/d3d9.cpp b/msvc/source/d3d9/d3d9.cpp new file mode 100644 index 0000000..a53f6d1 --- /dev/null +++ b/msvc/source/d3d9/d3d9.cpp @@ -0,0 +1,207 @@ +/** + * Copyright (C) 2014 Patrick Mours. All rights reserved. + * License: https://github.com/crosire/reshade#license + */ + +#include "log.hpp" +#include "hook_manager.hpp" +#include "d3d9_device.hpp" +#include "d3d9_swapchain.hpp" +#include "runtime_d3d9.hpp" + +// These are defined in d3d9.h, but we want to use them as function names below +#undef IDirect3D9_CreateDevice +#undef IDirect3D9Ex_CreateDeviceEx + +void dump_present_parameters(const D3DPRESENT_PARAMETERS &pp) +{ + LOG(INFO) << "> Dumping presentation parameters:"; + LOG(INFO) << " +-----------------------------------------+-----------------------------------------+"; + LOG(INFO) << " | Parameter | Value |"; + LOG(INFO) << " +-----------------------------------------+-----------------------------------------+"; + LOG(INFO) << " | BackBufferWidth | " << std::setw(39) << pp.BackBufferWidth << " |"; + LOG(INFO) << " | BackBufferHeight | " << std::setw(39) << pp.BackBufferHeight << " |"; + LOG(INFO) << " | BackBufferFormat | " << std::setw(39) << pp.BackBufferFormat << " |"; + LOG(INFO) << " | BackBufferCount | " << std::setw(39) << pp.BackBufferCount << " |"; + LOG(INFO) << " | MultiSampleType | " << std::setw(39) << pp.MultiSampleType << " |"; + LOG(INFO) << " | MultiSampleQuality | " << std::setw(39) << pp.MultiSampleQuality << " |"; + LOG(INFO) << " | SwapEffect | " << std::setw(39) << pp.SwapEffect << " |"; + LOG(INFO) << " | DeviceWindow | " << std::setw(39) << pp.hDeviceWindow << " |"; + LOG(INFO) << " | Windowed | " << std::setw(39) << (pp.Windowed != FALSE ? "TRUE" : "FALSE") << " |"; + LOG(INFO) << " | EnableAutoDepthStencil | " << std::setw(39) << (pp.EnableAutoDepthStencil ? "TRUE" : "FALSE") << " |"; + LOG(INFO) << " | AutoDepthStencilFormat | " << std::setw(39) << pp.AutoDepthStencilFormat << " |"; + LOG(INFO) << " | Flags | " << std::setw(39) << std::hex << pp.Flags << std::dec << " |"; + LOG(INFO) << " | FullScreen_RefreshRateInHz | " << std::setw(39) << pp.FullScreen_RefreshRateInHz << " |"; + LOG(INFO) << " | PresentationInterval | " << std::setw(39) << std::hex << pp.PresentationInterval << std::dec << " |"; + LOG(INFO) << " +-----------------------------------------+-----------------------------------------+"; + + if (pp.MultiSampleType != D3DMULTISAMPLE_NONE) + LOG(WARN) << "> Multisampling is enabled. This is not compatible with depth buffer access, which was therefore disabled."; +} + +template +static void init_runtime_d3d(T *&device, D3DDEVTYPE device_type, D3DPRESENT_PARAMETERS pp, bool use_software_rendering) +{ + // Enable software vertex processing if the application requested a software device + if (use_software_rendering) + device->SetSoftwareVertexProcessing(TRUE); + + // TODO: Make this configurable, since it prevents ReShade from being applied to video players. + if (pp.Flags & D3DPRESENTFLAG_VIDEO) + { + LOG(WARN) << "> Skipping device due to video swapchain."; + return; + } + + if (device_type == D3DDEVTYPE_NULLREF) + { + LOG(WARN) << "> Skipping device due to device type being 'D3DDEVTYPE_NULLREF'."; + return; + } + + IDirect3DSwapChain9 *swapchain = nullptr; + device->GetSwapChain(0, &swapchain); + assert(swapchain != nullptr); + + swapchain->GetPresentParameters(&pp); + + const auto runtime = std::make_shared(device, swapchain); + + if (!runtime->on_init(pp)) + LOG(ERROR) << "Failed to initialize Direct3D 9 runtime environment on runtime " << runtime.get() << '.'; + + const auto device_proxy = new Direct3DDevice9(device); + const auto swapchain_proxy = new Direct3DSwapChain9(device_proxy, swapchain, runtime); + + device_proxy->_implicit_swapchain = swapchain_proxy; + device_proxy->_use_software_rendering = use_software_rendering; + + // Get and set depth stencil surface so that the depth detection callbacks are called with the auto depth stencil surface + if (pp.EnableAutoDepthStencil) + { + device->GetDepthStencilSurface(&device_proxy->_auto_depthstencil); + device_proxy->SetDepthStencilSurface(device_proxy->_auto_depthstencil.get()); + } + + // Overwrite returned device with hooked one + device = device_proxy; + + // Upgrade to extended interface if available to prevent compatibility issues with some games + com_ptr deviceex; + device_proxy->QueryInterface(IID_PPV_ARGS(&deviceex)); + +#if RESHADE_VERBOSE_LOG + LOG(DEBUG) << "Returning IDirect3DDevice9" << (device_proxy->_extended_interface ? "Ex" : "") << " object " << device << '.'; +#endif +} + +HRESULT STDMETHODCALLTYPE IDirect3D9_CreateDevice(IDirect3D9 *pD3D, UINT Adapter, D3DDEVTYPE DeviceType, HWND hFocusWindow, DWORD BehaviorFlags, D3DPRESENT_PARAMETERS *pPresentationParameters, IDirect3DDevice9 **ppReturnedDeviceInterface) +{ + LOG(INFO) << "Redirecting IDirect3D9::CreateDevice" << '(' << pD3D << ", " << Adapter << ", " << DeviceType << ", " << hFocusWindow << ", " << std::hex << BehaviorFlags << std::dec << ", " << pPresentationParameters << ", " << ppReturnedDeviceInterface << ')' << " ..."; + + if (pPresentationParameters == nullptr) + return D3DERR_INVALIDCALL; + + if ((BehaviorFlags & D3DCREATE_ADAPTERGROUP_DEVICE) != 0) + { + LOG(WARN) << "Adapter group devices are unsupported."; + return D3DERR_NOTAVAILABLE; + } + + dump_present_parameters(*pPresentationParameters); + + const bool use_software_rendering = (BehaviorFlags & D3DCREATE_SOFTWARE_VERTEXPROCESSING) != 0; + if (use_software_rendering) + { + LOG(WARN) << "> Replacing 'D3DCREATE_SOFTWARE_VERTEXPROCESSING' flag with 'D3DCREATE_MIXED_VERTEXPROCESSING' to allow for hardware rendering ..."; + BehaviorFlags = (BehaviorFlags & ~D3DCREATE_SOFTWARE_VERTEXPROCESSING) | D3DCREATE_MIXED_VERTEXPROCESSING; + } + + const HRESULT hr = reshade::hooks::call(IDirect3D9_CreateDevice, vtable_from_instance(pD3D) + 16)(pD3D, Adapter, DeviceType, hFocusWindow, BehaviorFlags, pPresentationParameters, ppReturnedDeviceInterface); + + if (FAILED(hr)) + { + LOG(WARN) << "> IDirect3D9::CreateDevice failed with error code " << std::hex << hr << std::dec << '!'; + return hr; + } + + init_runtime_d3d(*ppReturnedDeviceInterface, DeviceType, *pPresentationParameters, use_software_rendering); + + return hr; +} + +HRESULT STDMETHODCALLTYPE IDirect3D9Ex_CreateDeviceEx(IDirect3D9Ex *pD3D, UINT Adapter, D3DDEVTYPE DeviceType, HWND hFocusWindow, DWORD BehaviorFlags, D3DPRESENT_PARAMETERS *pPresentationParameters, D3DDISPLAYMODEEX *pFullscreenDisplayMode, IDirect3DDevice9Ex **ppReturnedDeviceInterface) +{ + LOG(INFO) << "Redirecting IDirect3D9Ex::CreateDeviceEx" << '(' << pD3D << ", " << Adapter << ", " << DeviceType << ", " << hFocusWindow << ", " << std::hex << BehaviorFlags << std::dec << ", " << pPresentationParameters << ", " << pFullscreenDisplayMode << ", " << ppReturnedDeviceInterface << ')' << " ..."; + + if (pPresentationParameters == nullptr) + return D3DERR_INVALIDCALL; + + if ((BehaviorFlags & D3DCREATE_ADAPTERGROUP_DEVICE) != 0) + { + LOG(WARN) << "Adapter group devices are unsupported."; + return D3DERR_NOTAVAILABLE; + } + + dump_present_parameters(*pPresentationParameters); + + const bool use_software_rendering = (BehaviorFlags & D3DCREATE_SOFTWARE_VERTEXPROCESSING) != 0; + if (use_software_rendering) + { + LOG(WARN) << "> Replacing 'D3DCREATE_SOFTWARE_VERTEXPROCESSING' flag with 'D3DCREATE_MIXED_VERTEXPROCESSING' to allow for hardware rendering ..."; + BehaviorFlags = (BehaviorFlags & ~D3DCREATE_SOFTWARE_VERTEXPROCESSING) | D3DCREATE_MIXED_VERTEXPROCESSING; + } + + const HRESULT hr = reshade::hooks::call(IDirect3D9Ex_CreateDeviceEx, vtable_from_instance(pD3D) + 20)(pD3D, Adapter, DeviceType, hFocusWindow, BehaviorFlags, pPresentationParameters, pFullscreenDisplayMode, ppReturnedDeviceInterface); + + if (FAILED(hr)) + { + LOG(WARN) << "> IDirect3D9Ex::CreateDeviceEx failed with error code " << std::hex << hr << std::dec << '!'; + return hr; + } + + init_runtime_d3d(*ppReturnedDeviceInterface, DeviceType, *pPresentationParameters, use_software_rendering); + + return hr; +} + +HOOK_EXPORT IDirect3D9 *WINAPI Direct3DCreate9(UINT SDKVersion) +{ + LOG(INFO) << "Redirecting Direct3DCreate9" << '(' << SDKVersion << ')' << " ..."; + + IDirect3D9 *const res = reshade::hooks::call(Direct3DCreate9)(SDKVersion); + + if (res == nullptr) + { + LOG(WARN) << "> Direct3DCreate9 failed!"; + return nullptr; + } + + reshade::hooks::install("IDirect3D9::CreateDevice", vtable_from_instance(res), 16, reinterpret_cast(&IDirect3D9_CreateDevice)); + +#if RESHADE_VERBOSE_LOG + LOG(DEBUG) << "Returning IDirect3D9 object " << res << '.'; +#endif + return res; +} + +HOOK_EXPORT HRESULT WINAPI Direct3DCreate9Ex(UINT SDKVersion, IDirect3D9Ex **ppD3D) +{ + LOG(INFO) << "Redirecting Direct3DCreate9Ex" << '(' << SDKVersion << ", " << ppD3D << ')' << " ..."; + + const HRESULT hr = reshade::hooks::call(Direct3DCreate9Ex)(SDKVersion, ppD3D); + + if (FAILED(hr)) + { + LOG(WARN) << "> Direct3DCreate9Ex failed with error code " << std::hex << hr << std::dec << '!'; + return hr; + } + + reshade::hooks::install("IDirect3D9::CreateDevice", vtable_from_instance(*ppD3D), 16, reinterpret_cast(&IDirect3D9_CreateDevice)); + reshade::hooks::install("IDirect3D9Ex::CreateDeviceEx", vtable_from_instance(*ppD3D), 20, reinterpret_cast(&IDirect3D9Ex_CreateDeviceEx)); + +#if RESHADE_VERBOSE_LOG + LOG(DEBUG) << "Returning IDirect3D9Ex object " << *ppD3D << '.'; +#endif + return hr; +} diff --git a/msvc/source/d3d9/d3d9_device.cpp b/msvc/source/d3d9/d3d9_device.cpp new file mode 100644 index 0000000..1e8ea50 --- /dev/null +++ b/msvc/source/d3d9/d3d9_device.cpp @@ -0,0 +1,912 @@ +/** + * Copyright (C) 2014 Patrick Mours. All rights reserved. + * License: https://github.com/crosire/reshade#license + */ + +#include "log.hpp" +#include "d3d9_device.hpp" +#include "d3d9_swapchain.hpp" +#include "runtime_d3d9.hpp" + +extern void dump_present_parameters(const D3DPRESENT_PARAMETERS &pp); + +Direct3DDevice9::Direct3DDevice9(IDirect3DDevice9 *original) : + _orig(original), + _extended_interface(0) {} +Direct3DDevice9::Direct3DDevice9(IDirect3DDevice9Ex *original) : + _orig(original), + _extended_interface(1) {} + +bool Direct3DDevice9::check_and_upgrade_interface(REFIID riid) +{ + if (_extended_interface || riid != __uuidof(IDirect3DDevice9Ex)) + return true; + + IDirect3DDevice9Ex *new_interface = nullptr; + if (FAILED(_orig->QueryInterface(IID_PPV_ARGS(&new_interface)))) + return false; +#if RESHADE_VERBOSE_LOG + LOG(DEBUG) << "Upgraded IDirect3DDevice9 object " << this << " to IDirect3DDevice9Ex."; +#endif + _orig = new_interface; + _extended_interface = true; + + return true; +} + +HRESULT STDMETHODCALLTYPE Direct3DDevice9::QueryInterface(REFIID riid, void **ppvObj) +{ + if (ppvObj == nullptr) + return E_POINTER; + + if (riid == __uuidof(this) || + riid == __uuidof(IUnknown) || + riid == __uuidof(IDirect3DDevice9) || + riid == __uuidof(IDirect3DDevice9Ex)) + { + if (!check_and_upgrade_interface(riid)) + return E_NOINTERFACE; + + AddRef(); + *ppvObj = this; + return S_OK; + } + + return _orig->QueryInterface(riid, ppvObj); +} +ULONG STDMETHODCALLTYPE Direct3DDevice9::AddRef() +{ + ++_ref; + + return _orig->AddRef(); +} +ULONG STDMETHODCALLTYPE Direct3DDevice9::Release() +{ + if (--_ref == 0) + { + _auto_depthstencil.reset(); + assert(_implicit_swapchain != nullptr); + _implicit_swapchain->Release(); + } + + const ULONG ref = _orig->Release(); + + if (ref != 0 && _ref != 0) + return ref; + else if (ref != 0) + LOG(WARN) << "Reference count for IDirect3DDevice9" << (_extended_interface ? "Ex" : "") << " object " << this << " is inconsistent: " << ref << ", but expected 0."; + + assert(_ref <= 0); +#if RESHADE_VERBOSE_LOG + LOG(DEBUG) << "Destroyed IDirect3DDevice9" << (_extended_interface ? "Ex" : "") << " object " << this << '.'; +#endif + delete this; + return 0; +} + +HRESULT STDMETHODCALLTYPE Direct3DDevice9::TestCooperativeLevel() +{ + return _orig->TestCooperativeLevel(); +} +UINT STDMETHODCALLTYPE Direct3DDevice9::GetAvailableTextureMem() +{ + return _orig->GetAvailableTextureMem(); +} +HRESULT STDMETHODCALLTYPE Direct3DDevice9::EvictManagedResources() +{ + return _orig->EvictManagedResources(); +} +HRESULT STDMETHODCALLTYPE Direct3DDevice9::GetDirect3D(IDirect3D9 **ppD3D9) +{ + return _orig->GetDirect3D(ppD3D9); +} +HRESULT STDMETHODCALLTYPE Direct3DDevice9::GetDeviceCaps(D3DCAPS9 *pCaps) +{ + return _orig->GetDeviceCaps(pCaps); +} +HRESULT STDMETHODCALLTYPE Direct3DDevice9::GetDisplayMode(UINT iSwapChain, D3DDISPLAYMODE *pMode) +{ + if (iSwapChain != 0) + { + LOG(WARN) << "Access to multi-head swap chain at index " << iSwapChain << " is unsupported."; + return D3DERR_INVALIDCALL; + } + + assert(_implicit_swapchain != nullptr); + return _implicit_swapchain->GetDisplayMode(pMode); +} +HRESULT STDMETHODCALLTYPE Direct3DDevice9::GetCreationParameters(D3DDEVICE_CREATION_PARAMETERS *pParameters) +{ + return _orig->GetCreationParameters(pParameters); +} +HRESULT STDMETHODCALLTYPE Direct3DDevice9::SetCursorProperties(UINT XHotSpot, UINT YHotSpot, IDirect3DSurface9 *pCursorBitmap) +{ + return _orig->SetCursorProperties(XHotSpot, YHotSpot, pCursorBitmap); +} +void STDMETHODCALLTYPE Direct3DDevice9::SetCursorPosition(int X, int Y, DWORD Flags) +{ + return _orig->SetCursorPosition(X, Y, Flags); +} +BOOL STDMETHODCALLTYPE Direct3DDevice9::ShowCursor(BOOL bShow) +{ + return _orig->ShowCursor(bShow); +} +HRESULT STDMETHODCALLTYPE Direct3DDevice9::CreateAdditionalSwapChain(D3DPRESENT_PARAMETERS *pPresentationParameters, IDirect3DSwapChain9 **ppSwapChain) +{ + LOG(INFO) << "Redirecting IDirect3DDevice9::CreateAdditionalSwapChain" << '(' << this << ", " << pPresentationParameters << ", " << ppSwapChain << ')' << " ..."; + + if (pPresentationParameters == nullptr) + return D3DERR_INVALIDCALL; + + dump_present_parameters(*pPresentationParameters); + + const HRESULT hr = _orig->CreateAdditionalSwapChain(pPresentationParameters, ppSwapChain); + + if (FAILED(hr)) + { + LOG(WARN) << "> IDirect3DDevice9::CreateAdditionalSwapChain failed with error code " << std::hex << hr << std::dec << '!'; + return hr; + } + + IDirect3DDevice9 *const device = _orig; + IDirect3DSwapChain9 *const swapchain = *ppSwapChain; + assert(swapchain != nullptr); + + D3DPRESENT_PARAMETERS pp; + swapchain->GetPresentParameters(&pp); + + const auto runtime = std::make_shared(device, swapchain); + + if (!runtime->on_init(pp)) + LOG(ERROR) << "Failed to initialize Direct3D 9 runtime environment on runtime " << runtime.get() << '.'; + + AddRef(); // Add reference which is released when the swap chain is destroyed (see 'Direct3DSwapChain9::Release') + + const auto swapchain_proxy = new Direct3DSwapChain9(this, swapchain, runtime); + + _additional_swapchains.push_back(swapchain_proxy); + *ppSwapChain = swapchain_proxy; + +#if RESHADE_VERBOSE_LOG + LOG(DEBUG) << "Returning IDirect3DSwapChain9 object: " << *ppSwapChain << '.'; +#endif + return D3D_OK; +} +HRESULT STDMETHODCALLTYPE Direct3DDevice9::GetSwapChain(UINT iSwapChain, IDirect3DSwapChain9 **ppSwapChain) +{ + if (iSwapChain != 0) + { + LOG(WARN) << "Access to multi-head swap chain at index " << iSwapChain << " is unsupported."; + return D3DERR_INVALIDCALL; + } + + if (ppSwapChain == nullptr) + return D3DERR_INVALIDCALL; + + assert(_implicit_swapchain != nullptr); + _implicit_swapchain->AddRef(); + *ppSwapChain = _implicit_swapchain; + + return D3D_OK; +} +UINT STDMETHODCALLTYPE Direct3DDevice9::GetNumberOfSwapChains() +{ + return 1; +} +HRESULT STDMETHODCALLTYPE Direct3DDevice9::Reset(D3DPRESENT_PARAMETERS *pPresentationParameters) +{ + LOG(INFO) << "Redirecting IDirect3DDevice9::Reset" << '(' << this << ", " << pPresentationParameters << ')' << " ..."; + + if (pPresentationParameters == nullptr) + return D3DERR_INVALIDCALL; + + dump_present_parameters(*pPresentationParameters); + + assert(_implicit_swapchain != nullptr); + assert(_implicit_swapchain->_runtime != nullptr); + const auto runtime = _implicit_swapchain->_runtime; + + runtime->on_reset(); + + _auto_depthstencil.reset(); + + const HRESULT hr = _orig->Reset(pPresentationParameters); + + if (FAILED(hr)) + { + LOG(ERROR) << "> IDirect3DDevice9::Reset failed with error code " << std::hex << hr << std::dec << '!'; + return hr; + } + + D3DPRESENT_PARAMETERS pp; + _implicit_swapchain->GetPresentParameters(&pp); + + if (!runtime->on_init(pp)) + LOG(ERROR) << "Failed to recreate Direct3D 9 runtime environment on runtime " << runtime.get() << '.'; + + if (pp.EnableAutoDepthStencil) + { + _orig->GetDepthStencilSurface(&_auto_depthstencil); + SetDepthStencilSurface(_auto_depthstencil.get()); + } + + return hr; +} +HRESULT STDMETHODCALLTYPE Direct3DDevice9::Present(const RECT *pSourceRect, const RECT *pDestRect, HWND hDestWindowOverride, const RGNDATA *pDirtyRegion) +{ + assert(_implicit_swapchain != nullptr); + assert(_implicit_swapchain->_runtime != nullptr); + _implicit_swapchain->_runtime->on_present(); + + return _orig->Present(pSourceRect, pDestRect, hDestWindowOverride, pDirtyRegion); +} +HRESULT STDMETHODCALLTYPE Direct3DDevice9::GetBackBuffer(UINT iSwapChain, UINT iBackBuffer, D3DBACKBUFFER_TYPE Type, IDirect3DSurface9 **ppBackBuffer) +{ + if (iSwapChain != 0) + { + LOG(WARN) << "Access to multi-head swap chain at index " << iSwapChain << " is unsupported."; + return D3DERR_INVALIDCALL; + } + + assert(_implicit_swapchain != nullptr); + return _implicit_swapchain->GetBackBuffer(iBackBuffer, Type, ppBackBuffer); +} +HRESULT STDMETHODCALLTYPE Direct3DDevice9::GetRasterStatus(UINT iSwapChain, D3DRASTER_STATUS *pRasterStatus) +{ + if (iSwapChain != 0) + { + LOG(WARN) << "Access to multi-head swap chain at index " << iSwapChain << " is unsupported."; + return D3DERR_INVALIDCALL; + } + + assert(_implicit_swapchain != nullptr); + return _implicit_swapchain->GetRasterStatus(pRasterStatus); +} +HRESULT STDMETHODCALLTYPE Direct3DDevice9::SetDialogBoxMode(BOOL bEnableDialogs) +{ + return _orig->SetDialogBoxMode(bEnableDialogs); +} +void STDMETHODCALLTYPE Direct3DDevice9::SetGammaRamp(UINT iSwapChain, DWORD Flags, const D3DGAMMARAMP *pRamp) +{ + if (iSwapChain != 0) + { + LOG(WARN) << "Access to multi-head swap chain at index " << iSwapChain << " is unsupported."; + return; + } + + return _orig->SetGammaRamp(0, Flags, pRamp); +} +void STDMETHODCALLTYPE Direct3DDevice9::GetGammaRamp(UINT iSwapChain, D3DGAMMARAMP *pRamp) +{ + if (iSwapChain != 0) + { + LOG(WARN) << "Access to multi-head swap chain at index " << iSwapChain << " is unsupported."; + return; + } + + return _orig->GetGammaRamp(0, pRamp); +} +HRESULT STDMETHODCALLTYPE Direct3DDevice9::CreateTexture(UINT Width, UINT Height, UINT Levels, DWORD Usage, D3DFORMAT Format, D3DPOOL Pool, IDirect3DTexture9 **ppTexture, HANDLE *pSharedHandle) +{ + return _orig->CreateTexture(Width, Height, Levels, Usage, Format, Pool, ppTexture, pSharedHandle); +} +HRESULT STDMETHODCALLTYPE Direct3DDevice9::CreateVolumeTexture(UINT Width, UINT Height, UINT Depth, UINT Levels, DWORD Usage, D3DFORMAT Format, D3DPOOL Pool, IDirect3DVolumeTexture9 **ppVolumeTexture, HANDLE *pSharedHandle) +{ + return _orig->CreateVolumeTexture(Width, Height, Depth, Levels, Usage, Format, Pool, ppVolumeTexture, pSharedHandle); +} +HRESULT STDMETHODCALLTYPE Direct3DDevice9::CreateCubeTexture(UINT EdgeLength, UINT Levels, DWORD Usage, D3DFORMAT Format, D3DPOOL Pool, IDirect3DCubeTexture9 **ppCubeTexture, HANDLE *pSharedHandle) +{ + return _orig->CreateCubeTexture(EdgeLength, Levels, Usage, Format, Pool, ppCubeTexture, pSharedHandle); +} +HRESULT STDMETHODCALLTYPE Direct3DDevice9::CreateVertexBuffer(UINT Length, DWORD Usage, DWORD FVF, D3DPOOL Pool, IDirect3DVertexBuffer9 **ppVertexBuffer, HANDLE *pSharedHandle) +{ + if (_use_software_rendering) + Usage |= D3DUSAGE_SOFTWAREPROCESSING; + + return _orig->CreateVertexBuffer(Length, Usage, FVF, Pool, ppVertexBuffer, pSharedHandle); +} +HRESULT STDMETHODCALLTYPE Direct3DDevice9::CreateIndexBuffer(UINT Length, DWORD Usage, D3DFORMAT Format, D3DPOOL Pool, IDirect3DIndexBuffer9 **ppIndexBuffer, HANDLE *pSharedHandle) +{ + if (_use_software_rendering) + Usage |= D3DUSAGE_SOFTWAREPROCESSING; + + return _orig->CreateIndexBuffer(Length, Usage, Format, Pool, ppIndexBuffer, pSharedHandle); +} +HRESULT STDMETHODCALLTYPE Direct3DDevice9::CreateRenderTarget(UINT Width, UINT Height, D3DFORMAT Format, D3DMULTISAMPLE_TYPE MultiSample, DWORD MultisampleQuality, BOOL Lockable, IDirect3DSurface9 **ppSurface, HANDLE *pSharedHandle) +{ + return _orig->CreateRenderTarget(Width, Height, Format, MultiSample, MultisampleQuality, Lockable, ppSurface, pSharedHandle); +} +HRESULT STDMETHODCALLTYPE Direct3DDevice9::CreateDepthStencilSurface(UINT Width, UINT Height, D3DFORMAT Format, D3DMULTISAMPLE_TYPE MultiSample, DWORD MultisampleQuality, BOOL Discard, IDirect3DSurface9 **ppSurface, HANDLE *pSharedHandle) +{ + return _orig->CreateDepthStencilSurface(Width, Height, Format, MultiSample, MultisampleQuality, Discard, ppSurface, pSharedHandle); +} +HRESULT STDMETHODCALLTYPE Direct3DDevice9::UpdateSurface(IDirect3DSurface9 *pSourceSurface, const RECT *pSourceRect, IDirect3DSurface9 *pDestinationSurface, const POINT *pDestPoint) +{ + return _orig->UpdateSurface(pSourceSurface, pSourceRect, pDestinationSurface, pDestPoint); +} +HRESULT STDMETHODCALLTYPE Direct3DDevice9::UpdateTexture(IDirect3DBaseTexture9 *pSourceTexture, IDirect3DBaseTexture9 *pDestinationTexture) +{ + return _orig->UpdateTexture(pSourceTexture, pDestinationTexture); +} +HRESULT STDMETHODCALLTYPE Direct3DDevice9::GetRenderTargetData(IDirect3DSurface9 *pRenderTarget, IDirect3DSurface9 *pDestSurface) +{ + return _orig->GetRenderTargetData(pRenderTarget, pDestSurface); +} +HRESULT STDMETHODCALLTYPE Direct3DDevice9::GetFrontBufferData(UINT iSwapChain, IDirect3DSurface9 *pDestSurface) +{ + if (iSwapChain != 0) + { + LOG(WARN) << "Access to multi-head swap chain at index " << iSwapChain << " is unsupported."; + return D3DERR_INVALIDCALL; + } + + assert(_implicit_swapchain != nullptr); + return _implicit_swapchain->GetFrontBufferData(pDestSurface); +} +HRESULT STDMETHODCALLTYPE Direct3DDevice9::StretchRect(IDirect3DSurface9 *pSourceSurface, const RECT *pSourceRect, IDirect3DSurface9 *pDestSurface, const RECT *pDestRect, D3DTEXTUREFILTERTYPE Filter) +{ + return _orig->StretchRect(pSourceSurface, pSourceRect, pDestSurface, pDestRect, Filter); +} +HRESULT STDMETHODCALLTYPE Direct3DDevice9::ColorFill(IDirect3DSurface9 *pSurface, const RECT *pRect, D3DCOLOR color) +{ + return _orig->ColorFill(pSurface, pRect, color); +} +HRESULT STDMETHODCALLTYPE Direct3DDevice9::CreateOffscreenPlainSurface(UINT Width, UINT Height, D3DFORMAT Format, D3DPOOL Pool, IDirect3DSurface9 **ppSurface, HANDLE *pSharedHandle) +{ + return _orig->CreateOffscreenPlainSurface(Width, Height, Format, Pool, ppSurface, pSharedHandle); +} +HRESULT STDMETHODCALLTYPE Direct3DDevice9::SetRenderTarget(DWORD RenderTargetIndex, IDirect3DSurface9 *pRenderTarget) +{ + return _orig->SetRenderTarget(RenderTargetIndex, pRenderTarget); +} +HRESULT STDMETHODCALLTYPE Direct3DDevice9::GetRenderTarget(DWORD RenderTargetIndex, IDirect3DSurface9 **ppRenderTarget) +{ + return _orig->GetRenderTarget(RenderTargetIndex, ppRenderTarget); +} +HRESULT STDMETHODCALLTYPE Direct3DDevice9::SetDepthStencilSurface(IDirect3DSurface9 *pNewZStencil) +{ + if (pNewZStencil != nullptr) + { + assert(_implicit_swapchain != nullptr); + assert(_implicit_swapchain->_runtime != nullptr); + _implicit_swapchain->_runtime->on_set_depthstencil_surface(pNewZStencil); + + for (auto swapchain : _additional_swapchains) + { + assert(swapchain->_runtime != nullptr); + swapchain->_runtime->on_set_depthstencil_surface(pNewZStencil); + } + } + + return _orig->SetDepthStencilSurface(pNewZStencil); +} +HRESULT STDMETHODCALLTYPE Direct3DDevice9::GetDepthStencilSurface(IDirect3DSurface9 **ppZStencilSurface) +{ + const HRESULT hr = _orig->GetDepthStencilSurface(ppZStencilSurface); + + if (FAILED(hr)) + { + return hr; + } + else if (*ppZStencilSurface != nullptr) + { + assert(_implicit_swapchain != nullptr); + assert(_implicit_swapchain->_runtime != nullptr); + _implicit_swapchain->_runtime->on_get_depthstencil_surface(*ppZStencilSurface); + + for (auto swapchain : _additional_swapchains) + { + assert(swapchain->_runtime); + swapchain->_runtime->on_get_depthstencil_surface(*ppZStencilSurface); + } + } + + return D3D_OK; +} +HRESULT STDMETHODCALLTYPE Direct3DDevice9::BeginScene() +{ + return _orig->BeginScene(); +} +HRESULT STDMETHODCALLTYPE Direct3DDevice9::EndScene() +{ + return _orig->EndScene(); +} +HRESULT STDMETHODCALLTYPE Direct3DDevice9::Clear(DWORD Count, const D3DRECT *pRects, DWORD Flags, D3DCOLOR Color, float Z, DWORD Stencil) +{ + if (Flags & D3DCLEAR_ZBUFFER) + { + com_ptr depthstencil; + _orig->GetDepthStencilSurface(&depthstencil); + + assert(_implicit_swapchain != nullptr); + assert(_implicit_swapchain->_runtime != nullptr); + _implicit_swapchain->_runtime->on_clear_depthstencil_surface(depthstencil.get()); + } + + return _orig->Clear(Count, pRects, Flags, Color, Z, Stencil); +} +HRESULT STDMETHODCALLTYPE Direct3DDevice9::SetTransform(D3DTRANSFORMSTATETYPE State, const D3DMATRIX *pMatrix) +{ + return _orig->SetTransform(State, pMatrix); +} +HRESULT STDMETHODCALLTYPE Direct3DDevice9::GetTransform(D3DTRANSFORMSTATETYPE State, D3DMATRIX *pMatrix) +{ + return _orig->GetTransform(State, pMatrix); +} +HRESULT STDMETHODCALLTYPE Direct3DDevice9::MultiplyTransform(D3DTRANSFORMSTATETYPE State, const D3DMATRIX *pMatrix) +{ + return _orig->MultiplyTransform(State, pMatrix); +} +HRESULT STDMETHODCALLTYPE Direct3DDevice9::SetViewport(const D3DVIEWPORT9 *pViewport) +{ + return _orig->SetViewport(pViewport); +} +HRESULT STDMETHODCALLTYPE Direct3DDevice9::GetViewport(D3DVIEWPORT9 *pViewport) +{ + return _orig->GetViewport(pViewport); +} +HRESULT STDMETHODCALLTYPE Direct3DDevice9::SetMaterial(const D3DMATERIAL9 *pMaterial) +{ + return _orig->SetMaterial(pMaterial); +} +HRESULT STDMETHODCALLTYPE Direct3DDevice9::GetMaterial(D3DMATERIAL9 *pMaterial) +{ + return _orig->GetMaterial(pMaterial); +} +HRESULT STDMETHODCALLTYPE Direct3DDevice9::SetLight(DWORD Index, const D3DLIGHT9 *pLight) +{ + return _orig->SetLight(Index, pLight); +} +HRESULT STDMETHODCALLTYPE Direct3DDevice9::GetLight(DWORD Index, D3DLIGHT9 *pLight) +{ + return _orig->GetLight(Index, pLight); +} +HRESULT STDMETHODCALLTYPE Direct3DDevice9::LightEnable(DWORD Index, BOOL Enable) +{ + return _orig->LightEnable(Index, Enable); +} +HRESULT STDMETHODCALLTYPE Direct3DDevice9::GetLightEnable(DWORD Index, BOOL *pEnable) +{ + return _orig->GetLightEnable(Index, pEnable); +} +HRESULT STDMETHODCALLTYPE Direct3DDevice9::SetClipPlane(DWORD Index, const float *pPlane) +{ + return _orig->SetClipPlane(Index, pPlane); +} +HRESULT STDMETHODCALLTYPE Direct3DDevice9::GetClipPlane(DWORD Index, float *pPlane) +{ + return _orig->GetClipPlane(Index, pPlane); +} +HRESULT STDMETHODCALLTYPE Direct3DDevice9::SetRenderState(D3DRENDERSTATETYPE State, DWORD Value) +{ + return _orig->SetRenderState(State, Value); +} +HRESULT STDMETHODCALLTYPE Direct3DDevice9::GetRenderState(D3DRENDERSTATETYPE State, DWORD *pValue) +{ + return _orig->GetRenderState(State, pValue); +} +HRESULT STDMETHODCALLTYPE Direct3DDevice9::CreateStateBlock(D3DSTATEBLOCKTYPE Type, IDirect3DStateBlock9 **ppSB) +{ + return _orig->CreateStateBlock(Type, ppSB); +} +HRESULT STDMETHODCALLTYPE Direct3DDevice9::BeginStateBlock() +{ + return _orig->BeginStateBlock(); +} +HRESULT STDMETHODCALLTYPE Direct3DDevice9::EndStateBlock(IDirect3DStateBlock9 **ppSB) +{ + return _orig->EndStateBlock(ppSB); +} +HRESULT STDMETHODCALLTYPE Direct3DDevice9::SetClipStatus(const D3DCLIPSTATUS9 *pClipStatus) +{ + return _orig->SetClipStatus(pClipStatus); +} +HRESULT STDMETHODCALLTYPE Direct3DDevice9::GetClipStatus(D3DCLIPSTATUS9 *pClipStatus) +{ + return _orig->GetClipStatus(pClipStatus); +} +HRESULT STDMETHODCALLTYPE Direct3DDevice9::GetTexture(DWORD Stage, IDirect3DBaseTexture9 **ppTexture) +{ + return _orig->GetTexture(Stage, ppTexture); +} +HRESULT STDMETHODCALLTYPE Direct3DDevice9::SetTexture(DWORD Stage, IDirect3DBaseTexture9 *pTexture) +{ + return _orig->SetTexture(Stage, pTexture); +} +HRESULT STDMETHODCALLTYPE Direct3DDevice9::GetTextureStageState(DWORD Stage, D3DTEXTURESTAGESTATETYPE Type, DWORD *pValue) +{ + return _orig->GetTextureStageState(Stage, Type, pValue); +} +HRESULT STDMETHODCALLTYPE Direct3DDevice9::SetTextureStageState(DWORD Stage, D3DTEXTURESTAGESTATETYPE Type, DWORD Value) +{ + return _orig->SetTextureStageState(Stage, Type, Value); +} +HRESULT STDMETHODCALLTYPE Direct3DDevice9::GetSamplerState(DWORD Sampler, D3DSAMPLERSTATETYPE Type, DWORD *pValue) +{ + return _orig->GetSamplerState(Sampler, Type, pValue); +} +HRESULT STDMETHODCALLTYPE Direct3DDevice9::SetSamplerState(DWORD Sampler, D3DSAMPLERSTATETYPE Type, DWORD Value) +{ + return _orig->SetSamplerState(Sampler, Type, Value); +} +HRESULT STDMETHODCALLTYPE Direct3DDevice9::ValidateDevice(DWORD *pNumPasses) +{ + return _orig->ValidateDevice(pNumPasses); +} +HRESULT STDMETHODCALLTYPE Direct3DDevice9::SetPaletteEntries(UINT PaletteNumber, const PALETTEENTRY *pEntries) +{ + return _orig->SetPaletteEntries(PaletteNumber, pEntries); +} +HRESULT STDMETHODCALLTYPE Direct3DDevice9::GetPaletteEntries(UINT PaletteNumber, PALETTEENTRY *pEntries) +{ + return _orig->GetPaletteEntries(PaletteNumber, pEntries); +} +HRESULT STDMETHODCALLTYPE Direct3DDevice9::SetCurrentTexturePalette(UINT PaletteNumber) +{ + return _orig->SetCurrentTexturePalette(PaletteNumber); +} +HRESULT STDMETHODCALLTYPE Direct3DDevice9::GetCurrentTexturePalette(UINT *PaletteNumber) +{ + return _orig->GetCurrentTexturePalette(PaletteNumber); +} +HRESULT STDMETHODCALLTYPE Direct3DDevice9::SetScissorRect(const RECT *pRect) +{ + return _orig->SetScissorRect(pRect); +} +HRESULT STDMETHODCALLTYPE Direct3DDevice9::GetScissorRect(RECT *pRect) +{ + return _orig->GetScissorRect(pRect); +} +HRESULT STDMETHODCALLTYPE Direct3DDevice9::SetSoftwareVertexProcessing(BOOL bSoftware) +{ + return _orig->SetSoftwareVertexProcessing(bSoftware); +} +BOOL STDMETHODCALLTYPE Direct3DDevice9::GetSoftwareVertexProcessing() +{ + return _orig->GetSoftwareVertexProcessing(); +} +HRESULT STDMETHODCALLTYPE Direct3DDevice9::SetNPatchMode(float nSegments) +{ + return _orig->SetNPatchMode(nSegments); +} +float STDMETHODCALLTYPE Direct3DDevice9::GetNPatchMode() +{ + return _orig->GetNPatchMode(); +} +HRESULT STDMETHODCALLTYPE Direct3DDevice9::DrawPrimitive(D3DPRIMITIVETYPE PrimitiveType, UINT StartVertex, UINT PrimitiveCount) +{ + assert(_implicit_swapchain != nullptr); + assert(_implicit_swapchain->_runtime != nullptr); + _implicit_swapchain->_runtime->on_draw_primitive(PrimitiveType, StartVertex, PrimitiveCount); + + for (auto swapchain : _additional_swapchains) + { + assert(swapchain->_runtime != nullptr); + swapchain->_runtime->on_draw_primitive(PrimitiveType, StartVertex, PrimitiveCount); + } + + return _orig->DrawPrimitive(PrimitiveType, StartVertex, PrimitiveCount); +} +HRESULT STDMETHODCALLTYPE Direct3DDevice9::DrawIndexedPrimitive(D3DPRIMITIVETYPE PrimitiveType, INT BaseVertexIndex, UINT MinVertexIndex, UINT NumVertices, UINT StartIndex, UINT PrimitiveCount) +{ + assert(_implicit_swapchain != nullptr); + assert(_implicit_swapchain->_runtime != nullptr); + _implicit_swapchain->_runtime->on_draw_indexed_primitive(PrimitiveType, BaseVertexIndex, MinVertexIndex, NumVertices, StartIndex, PrimitiveCount); + + for (auto swapchain : _additional_swapchains) + { + assert(swapchain->_runtime != nullptr); + swapchain->_runtime->on_draw_indexed_primitive(PrimitiveType, BaseVertexIndex, MinVertexIndex, NumVertices, StartIndex, PrimitiveCount); + } + + return _orig->DrawIndexedPrimitive(PrimitiveType, BaseVertexIndex, MinVertexIndex, NumVertices, StartIndex, PrimitiveCount); +} +HRESULT STDMETHODCALLTYPE Direct3DDevice9::DrawPrimitiveUP(D3DPRIMITIVETYPE PrimitiveType, UINT PrimitiveCount, const void *pVertexStreamZeroData, UINT VertexStreamZeroStride) +{ + assert(_implicit_swapchain != nullptr); + assert(_implicit_swapchain->_runtime != nullptr); + + _implicit_swapchain->_runtime->on_draw_primitive_up(PrimitiveType, PrimitiveCount, pVertexStreamZeroData, VertexStreamZeroStride); + + for (auto swapchain : _additional_swapchains) + { + assert(swapchain->_runtime != nullptr); + swapchain->_runtime->on_draw_primitive_up(PrimitiveType, PrimitiveCount, pVertexStreamZeroData, VertexStreamZeroStride); + } + + return _orig->DrawPrimitiveUP(PrimitiveType, PrimitiveCount, pVertexStreamZeroData, VertexStreamZeroStride); +} +HRESULT STDMETHODCALLTYPE Direct3DDevice9::DrawIndexedPrimitiveUP(D3DPRIMITIVETYPE PrimitiveType, UINT MinVertexIndex, UINT NumVertices, UINT PrimitiveCount, const void *pIndexData, D3DFORMAT IndexDataFormat, const void *pVertexStreamZeroData, UINT VertexStreamZeroStride) +{ + assert(_implicit_swapchain != nullptr); + assert(_implicit_swapchain->_runtime != nullptr); + + _implicit_swapchain->_runtime->on_draw_indexed_primitive_up(PrimitiveType, MinVertexIndex, NumVertices, PrimitiveCount, pIndexData, IndexDataFormat, pVertexStreamZeroData, VertexStreamZeroStride); + + for (auto swapchain : _additional_swapchains) + { + assert(swapchain->_runtime != nullptr); + swapchain->_runtime->on_draw_indexed_primitive_up(PrimitiveType, MinVertexIndex, NumVertices, PrimitiveCount, pIndexData, IndexDataFormat, pVertexStreamZeroData, VertexStreamZeroStride); + } + + return _orig->DrawIndexedPrimitiveUP(PrimitiveType, MinVertexIndex, NumVertices, PrimitiveCount, pIndexData, IndexDataFormat, pVertexStreamZeroData, VertexStreamZeroStride); +} +HRESULT STDMETHODCALLTYPE Direct3DDevice9::ProcessVertices(UINT SrcStartIndex, UINT DestIndex, UINT VertexCount, IDirect3DVertexBuffer9 *pDestBuffer, IDirect3DVertexDeclaration9 *pVertexDecl, DWORD Flags) +{ + return _orig->ProcessVertices(SrcStartIndex, DestIndex, VertexCount, pDestBuffer, pVertexDecl, Flags); +} +HRESULT STDMETHODCALLTYPE Direct3DDevice9::CreateVertexDeclaration(const D3DVERTEXELEMENT9 *pVertexElements, IDirect3DVertexDeclaration9 **ppDecl) +{ + return _orig->CreateVertexDeclaration(pVertexElements, ppDecl); +} +HRESULT STDMETHODCALLTYPE Direct3DDevice9::SetVertexDeclaration(IDirect3DVertexDeclaration9 *pDecl) +{ + return _orig->SetVertexDeclaration(pDecl); +} +HRESULT STDMETHODCALLTYPE Direct3DDevice9::GetVertexDeclaration(IDirect3DVertexDeclaration9 **ppDecl) +{ + return _orig->GetVertexDeclaration(ppDecl); +} +HRESULT STDMETHODCALLTYPE Direct3DDevice9::SetFVF(DWORD FVF) +{ + return _orig->SetFVF(FVF); +} +HRESULT STDMETHODCALLTYPE Direct3DDevice9::GetFVF(DWORD *pFVF) +{ + return _orig->GetFVF(pFVF); +} +HRESULT STDMETHODCALLTYPE Direct3DDevice9::CreateVertexShader(const DWORD *pFunction, IDirect3DVertexShader9 **ppShader) +{ + return _orig->CreateVertexShader(pFunction, ppShader); +} +HRESULT STDMETHODCALLTYPE Direct3DDevice9::SetVertexShader(IDirect3DVertexShader9 *pShader) +{ + return _orig->SetVertexShader(pShader); +} +HRESULT STDMETHODCALLTYPE Direct3DDevice9::GetVertexShader(IDirect3DVertexShader9 **ppShader) +{ + return _orig->GetVertexShader(ppShader); +} +HRESULT STDMETHODCALLTYPE Direct3DDevice9::SetVertexShaderConstantF(UINT StartRegister, const float *pConstantData, UINT Vector4fCount) +{ + return _orig->SetVertexShaderConstantF(StartRegister, pConstantData, Vector4fCount); +} +HRESULT STDMETHODCALLTYPE Direct3DDevice9::GetVertexShaderConstantF(UINT StartRegister, float *pConstantData, UINT Vector4fCount) +{ + return _orig->GetVertexShaderConstantF(StartRegister, pConstantData, Vector4fCount); +} +HRESULT STDMETHODCALLTYPE Direct3DDevice9::SetVertexShaderConstantI(UINT StartRegister, const int *pConstantData, UINT Vector4iCount) +{ + return _orig->SetVertexShaderConstantI(StartRegister, pConstantData, Vector4iCount); +} +HRESULT STDMETHODCALLTYPE Direct3DDevice9::GetVertexShaderConstantI(UINT StartRegister, int *pConstantData, UINT Vector4iCount) +{ + return _orig->GetVertexShaderConstantI(StartRegister, pConstantData, Vector4iCount); +} +HRESULT STDMETHODCALLTYPE Direct3DDevice9::SetVertexShaderConstantB(UINT StartRegister, const BOOL *pConstantData, UINT BoolCount) +{ + return _orig->SetVertexShaderConstantB(StartRegister, pConstantData, BoolCount); +} +HRESULT STDMETHODCALLTYPE Direct3DDevice9::GetVertexShaderConstantB(UINT StartRegister, BOOL *pConstantData, UINT BoolCount) +{ + return _orig->GetVertexShaderConstantB(StartRegister, pConstantData, BoolCount); +} +HRESULT STDMETHODCALLTYPE Direct3DDevice9::SetStreamSource(UINT StreamNumber, IDirect3DVertexBuffer9 *pStreamData, UINT OffsetInBytes, UINT Stride) +{ + return _orig->SetStreamSource(StreamNumber, pStreamData, OffsetInBytes, Stride); +} +HRESULT STDMETHODCALLTYPE Direct3DDevice9::GetStreamSource(UINT StreamNumber, IDirect3DVertexBuffer9 **ppStreamData, UINT *OffsetInBytes, UINT *pStride) +{ + return _orig->GetStreamSource(StreamNumber, ppStreamData, OffsetInBytes, pStride); +} +HRESULT STDMETHODCALLTYPE Direct3DDevice9::SetStreamSourceFreq(UINT StreamNumber, UINT Divider) +{ + return _orig->SetStreamSourceFreq(StreamNumber, Divider); +} +HRESULT STDMETHODCALLTYPE Direct3DDevice9::GetStreamSourceFreq(UINT StreamNumber, UINT *Divider) +{ + return _orig->GetStreamSourceFreq(StreamNumber, Divider); +} +HRESULT STDMETHODCALLTYPE Direct3DDevice9::SetIndices(IDirect3DIndexBuffer9 *pIndexData) +{ + return _orig->SetIndices(pIndexData); +} +HRESULT STDMETHODCALLTYPE Direct3DDevice9::GetIndices(IDirect3DIndexBuffer9 **ppIndexData) +{ + return _orig->GetIndices(ppIndexData); +} +HRESULT STDMETHODCALLTYPE Direct3DDevice9::CreatePixelShader(const DWORD *pFunction, IDirect3DPixelShader9 **ppShader) +{ + return _orig->CreatePixelShader(pFunction, ppShader); +} +HRESULT STDMETHODCALLTYPE Direct3DDevice9::SetPixelShader(IDirect3DPixelShader9 *pShader) +{ + return _orig->SetPixelShader(pShader); +} +HRESULT STDMETHODCALLTYPE Direct3DDevice9::GetPixelShader(IDirect3DPixelShader9 **ppShader) +{ + return _orig->GetPixelShader(ppShader); +} +HRESULT STDMETHODCALLTYPE Direct3DDevice9::SetPixelShaderConstantF(UINT StartRegister, const float *pConstantData, UINT Vector4fCount) +{ + return _orig->SetPixelShaderConstantF(StartRegister, pConstantData, Vector4fCount); +} +HRESULT STDMETHODCALLTYPE Direct3DDevice9::GetPixelShaderConstantF(UINT StartRegister, float *pConstantData, UINT Vector4fCount) +{ + return _orig->GetPixelShaderConstantF(StartRegister, pConstantData, Vector4fCount); +} +HRESULT STDMETHODCALLTYPE Direct3DDevice9::SetPixelShaderConstantI(UINT StartRegister, const int *pConstantData, UINT Vector4iCount) +{ + return _orig->SetPixelShaderConstantI(StartRegister, pConstantData, Vector4iCount); +} +HRESULT STDMETHODCALLTYPE Direct3DDevice9::GetPixelShaderConstantI(UINT StartRegister, int *pConstantData, UINT Vector4iCount) +{ + return _orig->GetPixelShaderConstantI(StartRegister, pConstantData, Vector4iCount); +} +HRESULT STDMETHODCALLTYPE Direct3DDevice9::SetPixelShaderConstantB(UINT StartRegister, const BOOL *pConstantData, UINT BoolCount) +{ + return _orig->SetPixelShaderConstantB(StartRegister, pConstantData, BoolCount); +} +HRESULT STDMETHODCALLTYPE Direct3DDevice9::GetPixelShaderConstantB(UINT StartRegister, BOOL *pConstantData, UINT BoolCount) +{ + return _orig->GetPixelShaderConstantB(StartRegister, pConstantData, BoolCount); +} +HRESULT STDMETHODCALLTYPE Direct3DDevice9::DrawRectPatch(UINT Handle, const float *pNumSegs, const D3DRECTPATCH_INFO *pRectPatchInfo) +{ + return _orig->DrawRectPatch(Handle, pNumSegs, pRectPatchInfo); +} +HRESULT STDMETHODCALLTYPE Direct3DDevice9::DrawTriPatch(UINT Handle, const float *pNumSegs, const D3DTRIPATCH_INFO *pTriPatchInfo) +{ + return _orig->DrawTriPatch(Handle, pNumSegs, pTriPatchInfo); +} +HRESULT STDMETHODCALLTYPE Direct3DDevice9::DeletePatch(UINT Handle) +{ + return _orig->DeletePatch(Handle); +} +HRESULT STDMETHODCALLTYPE Direct3DDevice9::CreateQuery(D3DQUERYTYPE Type, IDirect3DQuery9 **ppQuery) +{ + return _orig->CreateQuery(Type, ppQuery); +} + +HRESULT STDMETHODCALLTYPE Direct3DDevice9::SetConvolutionMonoKernel(UINT width, UINT height, float *rows, float *columns) +{ + assert(_extended_interface); + + return static_cast(_orig)->SetConvolutionMonoKernel(width, height, rows, columns); +} +HRESULT STDMETHODCALLTYPE Direct3DDevice9::ComposeRects(IDirect3DSurface9 *pSrc, IDirect3DSurface9 *pDst, IDirect3DVertexBuffer9 *pSrcRectDescs, UINT NumRects, IDirect3DVertexBuffer9 *pDstRectDescs, D3DCOMPOSERECTSOP Operation, int Xoffset, int Yoffset) +{ + assert(_extended_interface); + + return static_cast(_orig)->ComposeRects(pSrc, pDst, pSrcRectDescs, NumRects, pDstRectDescs, Operation, Xoffset, Yoffset); +} +HRESULT STDMETHODCALLTYPE Direct3DDevice9::PresentEx(const RECT *pSourceRect, const RECT *pDestRect, HWND hDestWindowOverride, const RGNDATA *pDirtyRegion, DWORD dwFlags) +{ + assert(_extended_interface); + assert(_implicit_swapchain != nullptr); + assert(_implicit_swapchain->_runtime != nullptr); + _implicit_swapchain->_runtime->on_present(); + + return static_cast(_orig)->PresentEx(pSourceRect, pDestRect, hDestWindowOverride, pDirtyRegion, dwFlags); +} +HRESULT STDMETHODCALLTYPE Direct3DDevice9::GetGPUThreadPriority(INT *pPriority) +{ + assert(_extended_interface); + + return static_cast(_orig)->GetGPUThreadPriority(pPriority); +} +HRESULT STDMETHODCALLTYPE Direct3DDevice9::SetGPUThreadPriority(INT Priority) +{ + assert(_extended_interface); + + return static_cast(_orig)->SetGPUThreadPriority(Priority); +} +HRESULT STDMETHODCALLTYPE Direct3DDevice9::WaitForVBlank(UINT iSwapChain) +{ + assert(_extended_interface); + + if (iSwapChain != 0) + { + LOG(WARN) << "Access to multi-head swap chain at index " << iSwapChain << " is unsupported."; + return D3DERR_INVALIDCALL; + } + + return static_cast(_orig)->WaitForVBlank(0); +} +HRESULT STDMETHODCALLTYPE Direct3DDevice9::CheckResourceResidency(IDirect3DResource9 **pResourceArray, UINT32 NumResources) +{ + assert(_extended_interface); + + return static_cast(_orig)->CheckResourceResidency(pResourceArray, NumResources); +} +HRESULT STDMETHODCALLTYPE Direct3DDevice9::SetMaximumFrameLatency(UINT MaxLatency) +{ + assert(_extended_interface); + + return static_cast(_orig)->SetMaximumFrameLatency(MaxLatency); +} +HRESULT STDMETHODCALLTYPE Direct3DDevice9::GetMaximumFrameLatency(UINT *pMaxLatency) +{ + assert(_extended_interface); + + return static_cast(_orig)->GetMaximumFrameLatency(pMaxLatency); +} +HRESULT STDMETHODCALLTYPE Direct3DDevice9::CheckDeviceState(HWND hDestinationWindow) +{ + assert(_extended_interface); + + return static_cast(_orig)->CheckDeviceState(hDestinationWindow); +} +HRESULT STDMETHODCALLTYPE Direct3DDevice9::CreateRenderTargetEx(UINT Width, UINT Height, D3DFORMAT Format, D3DMULTISAMPLE_TYPE MultiSample, DWORD MultisampleQuality, BOOL Lockable, IDirect3DSurface9 **ppSurface, HANDLE *pSharedHandle, DWORD Usage) +{ + assert(_extended_interface); + + return static_cast(_orig)->CreateRenderTargetEx(Width, Height, Format, MultiSample, MultisampleQuality, Lockable, ppSurface, pSharedHandle, Usage); +} +HRESULT STDMETHODCALLTYPE Direct3DDevice9::CreateOffscreenPlainSurfaceEx(UINT Width, UINT Height, D3DFORMAT Format, D3DPOOL Pool, IDirect3DSurface9 **ppSurface, HANDLE *pSharedHandle, DWORD Usage) +{ + assert(_extended_interface); + + return static_cast(_orig)->CreateOffscreenPlainSurfaceEx(Width, Height, Format, Pool, ppSurface, pSharedHandle, Usage); +} +HRESULT STDMETHODCALLTYPE Direct3DDevice9::CreateDepthStencilSurfaceEx(UINT Width, UINT Height, D3DFORMAT Format, D3DMULTISAMPLE_TYPE MultiSample, DWORD MultisampleQuality, BOOL Discard, IDirect3DSurface9 **ppSurface, HANDLE *pSharedHandle, DWORD Usage) +{ + assert(_extended_interface); + + return static_cast(_orig)->CreateDepthStencilSurfaceEx(Width, Height, Format, MultiSample, MultisampleQuality, Discard, ppSurface, pSharedHandle, Usage); +} +HRESULT STDMETHODCALLTYPE Direct3DDevice9::ResetEx(D3DPRESENT_PARAMETERS *pPresentationParameters, D3DDISPLAYMODEEX *pFullscreenDisplayMode) +{ + assert(_extended_interface); + + LOG(INFO) << "Redirecting IDirect3DDevice9Ex::ResetEx" << '(' << this << ", " << pPresentationParameters << ", " << pFullscreenDisplayMode << ')' << " ..."; + + if (pPresentationParameters == nullptr) + return D3DERR_INVALIDCALL; + + dump_present_parameters(*pPresentationParameters); + + assert(_implicit_swapchain != nullptr); + assert(_implicit_swapchain->_runtime != nullptr); + const auto runtime = _implicit_swapchain->_runtime; + + runtime->on_reset(); + + _auto_depthstencil.reset(); + + const HRESULT hr = static_cast(_orig)->ResetEx(pPresentationParameters, pFullscreenDisplayMode); + + if (FAILED(hr)) + { + LOG(ERROR) << "> IDirect3DDevice9Ex::ResetEx failed with error code " << std::hex << hr << std::dec << '!'; + return hr; + } + + D3DPRESENT_PARAMETERS pp; + _implicit_swapchain->GetPresentParameters(&pp); + + if (!runtime->on_init(pp)) + LOG(ERROR) << "Failed to recreate Direct3D 9 runtime environment on runtime " << runtime.get() << '.'; + + if (pp.EnableAutoDepthStencil) + { + _orig->GetDepthStencilSurface(&_auto_depthstencil); + SetDepthStencilSurface(_auto_depthstencil.get()); + } + + return hr; +} +HRESULT STDMETHODCALLTYPE Direct3DDevice9::GetDisplayModeEx(UINT iSwapChain, D3DDISPLAYMODEEX *pMode, D3DDISPLAYROTATION *pRotation) +{ + assert(_extended_interface); + + if (iSwapChain != 0) + { + LOG(WARN) << "Access to multi-head swap chain at index " << iSwapChain << " is unsupported."; + return D3DERR_INVALIDCALL; + } + + assert(_implicit_swapchain != nullptr); + assert(_implicit_swapchain->_extended_interface); + return static_cast(_implicit_swapchain)->GetDisplayModeEx(pMode, pRotation); +} diff --git a/msvc/source/d3d9/d3d9_device.hpp b/msvc/source/d3d9/d3d9_device.hpp new file mode 100644 index 0000000..d4aeec0 --- /dev/null +++ b/msvc/source/d3d9/d3d9_device.hpp @@ -0,0 +1,171 @@ +/** + * Copyright (C) 2014 Patrick Mours. All rights reserved. + * License: https://github.com/crosire/reshade#license + */ + +#pragma once + +#include +#include "com_ptr.hpp" +#include + +struct Direct3DSwapChain9; + +struct __declspec(uuid("F1006E9A-1C51-4AF4-ACEF-3605D2D4C8EE")) Direct3DDevice9 : IDirect3DDevice9Ex +{ + explicit Direct3DDevice9(IDirect3DDevice9 *original); + explicit Direct3DDevice9(IDirect3DDevice9Ex *original); + + Direct3DDevice9(const Direct3DDevice9 &) = delete; + Direct3DDevice9 &operator=(const Direct3DDevice9 &) = delete; + + HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void **ppvObj) override; + ULONG STDMETHODCALLTYPE AddRef() override; + ULONG STDMETHODCALLTYPE Release() override; + + #pragma region IDirect3DDevice9 + HRESULT STDMETHODCALLTYPE TestCooperativeLevel() override; + UINT STDMETHODCALLTYPE GetAvailableTextureMem() override; + HRESULT STDMETHODCALLTYPE EvictManagedResources() override; + HRESULT STDMETHODCALLTYPE GetDirect3D(IDirect3D9 **ppD3D9) override; + HRESULT STDMETHODCALLTYPE GetDeviceCaps(D3DCAPS9 *pCaps) override; + HRESULT STDMETHODCALLTYPE GetDisplayMode(UINT iSwapChain, D3DDISPLAYMODE *pMode) override; + HRESULT STDMETHODCALLTYPE GetCreationParameters(D3DDEVICE_CREATION_PARAMETERS *pParameters) override; + HRESULT STDMETHODCALLTYPE SetCursorProperties(UINT XHotSpot, UINT YHotSpot, IDirect3DSurface9 *pCursorBitmap) override; + void STDMETHODCALLTYPE SetCursorPosition(int X, int Y, DWORD Flags) override; + BOOL STDMETHODCALLTYPE ShowCursor(BOOL bShow) override; + HRESULT STDMETHODCALLTYPE CreateAdditionalSwapChain(D3DPRESENT_PARAMETERS *pPresentationParameters, IDirect3DSwapChain9 **ppSwapChain) override; + HRESULT STDMETHODCALLTYPE GetSwapChain(UINT iSwapChain, IDirect3DSwapChain9 **ppSwapChain) override; + UINT STDMETHODCALLTYPE GetNumberOfSwapChains() override; + HRESULT STDMETHODCALLTYPE Reset(D3DPRESENT_PARAMETERS *pPresentationParameters) override; + HRESULT STDMETHODCALLTYPE Present(const RECT *pSourceRect, const RECT *pDestRect, HWND hDestWindowOverride, const RGNDATA *pDirtyRegion) override; + HRESULT STDMETHODCALLTYPE GetBackBuffer(UINT iSwapChain, UINT iBackBuffer, D3DBACKBUFFER_TYPE Type, IDirect3DSurface9 **ppBackBuffer) override; + HRESULT STDMETHODCALLTYPE GetRasterStatus(UINT iSwapChain, D3DRASTER_STATUS *pRasterStatus) override; + HRESULT STDMETHODCALLTYPE SetDialogBoxMode(BOOL bEnableDialogs) override; + void STDMETHODCALLTYPE SetGammaRamp(UINT iSwapChain, DWORD Flags, const D3DGAMMARAMP *pRamp) override; + void STDMETHODCALLTYPE GetGammaRamp(UINT iSwapChain, D3DGAMMARAMP *pRamp) override; + HRESULT STDMETHODCALLTYPE CreateTexture(UINT Width, UINT Height, UINT Levels, DWORD Usage, D3DFORMAT Format, D3DPOOL Pool, IDirect3DTexture9 **ppTexture, HANDLE *pSharedHandle) override; + HRESULT STDMETHODCALLTYPE CreateVolumeTexture(UINT Width, UINT Height, UINT Depth, UINT Levels, DWORD Usage, D3DFORMAT Format, D3DPOOL Pool, IDirect3DVolumeTexture9 **ppVolumeTexture, HANDLE *pSharedHandle) override; + HRESULT STDMETHODCALLTYPE CreateCubeTexture(UINT EdgeLength, UINT Levels, DWORD Usage, D3DFORMAT Format, D3DPOOL Pool, IDirect3DCubeTexture9 **ppCubeTexture, HANDLE *pSharedHandle) override; + HRESULT STDMETHODCALLTYPE CreateVertexBuffer(UINT Length, DWORD Usage, DWORD FVF, D3DPOOL Pool, IDirect3DVertexBuffer9 **ppVertexBuffer, HANDLE *pSharedHandle) override; + HRESULT STDMETHODCALLTYPE CreateIndexBuffer(UINT Length, DWORD Usage, D3DFORMAT Format, D3DPOOL Pool, IDirect3DIndexBuffer9 **ppIndexBuffer, HANDLE *pSharedHandle) override; + HRESULT STDMETHODCALLTYPE CreateRenderTarget(UINT Width, UINT Height, D3DFORMAT Format, D3DMULTISAMPLE_TYPE MultiSample, DWORD MultisampleQuality, BOOL Lockable, IDirect3DSurface9 **ppSurface, HANDLE *pSharedHandle) override; + HRESULT STDMETHODCALLTYPE CreateDepthStencilSurface(UINT Width, UINT Height, D3DFORMAT Format, D3DMULTISAMPLE_TYPE MultiSample, DWORD MultisampleQuality, BOOL Discard, IDirect3DSurface9 **ppSurface, HANDLE *pSharedHandle) override; + HRESULT STDMETHODCALLTYPE UpdateSurface(IDirect3DSurface9 *pSourceSurface, const RECT *pSourceRect, IDirect3DSurface9 *pDestinationSurface, const POINT *pDestPoint) override; + HRESULT STDMETHODCALLTYPE UpdateTexture(IDirect3DBaseTexture9 *pSourceTexture, IDirect3DBaseTexture9 *pDestinationTexture) override; + HRESULT STDMETHODCALLTYPE GetRenderTargetData(IDirect3DSurface9 *pRenderTarget, IDirect3DSurface9 *pDestSurface) override; + HRESULT STDMETHODCALLTYPE GetFrontBufferData(UINT iSwapChain, IDirect3DSurface9 *pDestSurface) override; + HRESULT STDMETHODCALLTYPE StretchRect(IDirect3DSurface9 *pSourceSurface, const RECT *pSourceRect, IDirect3DSurface9 *pDestSurface, const RECT *pDestRect, D3DTEXTUREFILTERTYPE Filter) override; + HRESULT STDMETHODCALLTYPE ColorFill(IDirect3DSurface9 *pSurface, const RECT *pRect, D3DCOLOR color) override; + HRESULT STDMETHODCALLTYPE CreateOffscreenPlainSurface(UINT Width, UINT Height, D3DFORMAT Format, D3DPOOL Pool, IDirect3DSurface9 **ppSurface, HANDLE *pSharedHandle) override; + HRESULT STDMETHODCALLTYPE SetRenderTarget(DWORD RenderTargetIndex, IDirect3DSurface9 *pRenderTarget) override; + HRESULT STDMETHODCALLTYPE GetRenderTarget(DWORD RenderTargetIndex, IDirect3DSurface9 **ppRenderTarget) override; + HRESULT STDMETHODCALLTYPE SetDepthStencilSurface(IDirect3DSurface9 *pNewZStencil) override; + HRESULT STDMETHODCALLTYPE GetDepthStencilSurface(IDirect3DSurface9 **ppZStencilSurface) override; + HRESULT STDMETHODCALLTYPE BeginScene() override; + HRESULT STDMETHODCALLTYPE EndScene() override; + HRESULT STDMETHODCALLTYPE Clear(DWORD Count, const D3DRECT *pRects, DWORD Flags, D3DCOLOR Color, float Z, DWORD Stencil) override; + HRESULT STDMETHODCALLTYPE SetTransform(D3DTRANSFORMSTATETYPE State, const D3DMATRIX *pMatrix) override; + HRESULT STDMETHODCALLTYPE GetTransform(D3DTRANSFORMSTATETYPE State, D3DMATRIX *pMatrix) override; + HRESULT STDMETHODCALLTYPE MultiplyTransform(D3DTRANSFORMSTATETYPE State, const D3DMATRIX *pMatrix) override; + HRESULT STDMETHODCALLTYPE SetViewport(const D3DVIEWPORT9 *pViewport) override; + HRESULT STDMETHODCALLTYPE GetViewport(D3DVIEWPORT9 *pViewport) override; + HRESULT STDMETHODCALLTYPE SetMaterial(const D3DMATERIAL9 *pMaterial) override; + HRESULT STDMETHODCALLTYPE GetMaterial(D3DMATERIAL9 *pMaterial) override; + HRESULT STDMETHODCALLTYPE SetLight(DWORD Index, const D3DLIGHT9 *pLight) override; + HRESULT STDMETHODCALLTYPE GetLight(DWORD Index, D3DLIGHT9 *pLight) override; + HRESULT STDMETHODCALLTYPE LightEnable(DWORD Index, BOOL Enable) override; + HRESULT STDMETHODCALLTYPE GetLightEnable(DWORD Index, BOOL *pEnable) override; + HRESULT STDMETHODCALLTYPE SetClipPlane(DWORD Index, const float *pPlane) override; + HRESULT STDMETHODCALLTYPE GetClipPlane(DWORD Index, float *pPlane) override; + HRESULT STDMETHODCALLTYPE SetRenderState(D3DRENDERSTATETYPE State, DWORD Value) override; + HRESULT STDMETHODCALLTYPE GetRenderState(D3DRENDERSTATETYPE State, DWORD *pValue) override; + HRESULT STDMETHODCALLTYPE CreateStateBlock(D3DSTATEBLOCKTYPE Type, IDirect3DStateBlock9 **ppSB) override; + HRESULT STDMETHODCALLTYPE BeginStateBlock() override; + HRESULT STDMETHODCALLTYPE EndStateBlock(IDirect3DStateBlock9 **ppSB) override; + HRESULT STDMETHODCALLTYPE SetClipStatus(const D3DCLIPSTATUS9 *pClipStatus) override; + HRESULT STDMETHODCALLTYPE GetClipStatus(D3DCLIPSTATUS9 *pClipStatus) override; + HRESULT STDMETHODCALLTYPE GetTexture(DWORD Stage, IDirect3DBaseTexture9 **ppTexture) override; + HRESULT STDMETHODCALLTYPE SetTexture(DWORD Stage, IDirect3DBaseTexture9 *pTexture) override; + HRESULT STDMETHODCALLTYPE GetTextureStageState(DWORD Stage, D3DTEXTURESTAGESTATETYPE Type, DWORD *pValue) override; + HRESULT STDMETHODCALLTYPE SetTextureStageState(DWORD Stage, D3DTEXTURESTAGESTATETYPE Type, DWORD Value) override; + HRESULT STDMETHODCALLTYPE GetSamplerState(DWORD Sampler, D3DSAMPLERSTATETYPE Type, DWORD *pValue) override; + HRESULT STDMETHODCALLTYPE SetSamplerState(DWORD Sampler, D3DSAMPLERSTATETYPE Type, DWORD Value) override; + HRESULT STDMETHODCALLTYPE ValidateDevice(DWORD *pNumPasses) override; + HRESULT STDMETHODCALLTYPE SetPaletteEntries(UINT PaletteNumber, const PALETTEENTRY *pEntries) override; + HRESULT STDMETHODCALLTYPE GetPaletteEntries(UINT PaletteNumber, PALETTEENTRY *pEntries) override; + HRESULT STDMETHODCALLTYPE SetCurrentTexturePalette(UINT PaletteNumber) override; + HRESULT STDMETHODCALLTYPE GetCurrentTexturePalette(UINT *PaletteNumber) override; + HRESULT STDMETHODCALLTYPE SetScissorRect(const RECT *pRect) override; + HRESULT STDMETHODCALLTYPE GetScissorRect(RECT *pRect) override; + HRESULT STDMETHODCALLTYPE SetSoftwareVertexProcessing(BOOL bSoftware) override; + BOOL STDMETHODCALLTYPE GetSoftwareVertexProcessing() override; + HRESULT STDMETHODCALLTYPE SetNPatchMode(float nSegments) override; + float STDMETHODCALLTYPE GetNPatchMode() override; + HRESULT STDMETHODCALLTYPE DrawPrimitive(D3DPRIMITIVETYPE PrimitiveType, UINT StartVertex, UINT PrimitiveCount) override; + HRESULT STDMETHODCALLTYPE DrawIndexedPrimitive(D3DPRIMITIVETYPE PrimitiveType, INT BaseVertexIndex, UINT MinVertexIndex, UINT NumVertices, UINT startIndex, UINT primCount) override; + HRESULT STDMETHODCALLTYPE DrawPrimitiveUP(D3DPRIMITIVETYPE PrimitiveType, UINT PrimitiveCount, const void *pVertexStreamZeroData, UINT VertexStreamZeroStride) override; + HRESULT STDMETHODCALLTYPE DrawIndexedPrimitiveUP(D3DPRIMITIVETYPE PrimitiveType, UINT MinVertexIndex, UINT NumVertices, UINT PrimitiveCount, const void *pIndexData, D3DFORMAT IndexDataFormat, const void *pVertexStreamZeroData, UINT VertexStreamZeroStride) override; + HRESULT STDMETHODCALLTYPE ProcessVertices(UINT SrcStartIndex, UINT DestIndex, UINT VertexCount, IDirect3DVertexBuffer9 *pDestBuffer, IDirect3DVertexDeclaration9 *pVertexDecl, DWORD Flags) override; + HRESULT STDMETHODCALLTYPE CreateVertexDeclaration(const D3DVERTEXELEMENT9 *pVertexElements, IDirect3DVertexDeclaration9 **ppDecl) override; + HRESULT STDMETHODCALLTYPE SetVertexDeclaration(IDirect3DVertexDeclaration9 *pDecl) override; + HRESULT STDMETHODCALLTYPE GetVertexDeclaration(IDirect3DVertexDeclaration9 **ppDecl) override; + HRESULT STDMETHODCALLTYPE SetFVF(DWORD FVF) override; + HRESULT STDMETHODCALLTYPE GetFVF(DWORD *pFVF) override; + HRESULT STDMETHODCALLTYPE CreateVertexShader(const DWORD *pFunction, IDirect3DVertexShader9 **ppShader) override; + HRESULT STDMETHODCALLTYPE SetVertexShader(IDirect3DVertexShader9 *pShader) override; + HRESULT STDMETHODCALLTYPE GetVertexShader(IDirect3DVertexShader9 **ppShader) override; + HRESULT STDMETHODCALLTYPE SetVertexShaderConstantF(UINT StartRegister, const float *pConstantData, UINT Vector4fCount) override; + HRESULT STDMETHODCALLTYPE GetVertexShaderConstantF(UINT StartRegister, float *pConstantData, UINT Vector4fCount) override; + HRESULT STDMETHODCALLTYPE SetVertexShaderConstantI(UINT StartRegister, const int *pConstantData, UINT Vector4iCount) override; + HRESULT STDMETHODCALLTYPE GetVertexShaderConstantI(UINT StartRegister, int *pConstantData, UINT Vector4iCount) override; + HRESULT STDMETHODCALLTYPE SetVertexShaderConstantB(UINT StartRegister, const BOOL *pConstantData, UINT BoolCount) override; + HRESULT STDMETHODCALLTYPE GetVertexShaderConstantB(UINT StartRegister, BOOL *pConstantData, UINT BoolCount) override; + HRESULT STDMETHODCALLTYPE SetStreamSource(UINT StreamNumber, IDirect3DVertexBuffer9 *pStreamData, UINT OffsetInBytes, UINT Stride) override; + HRESULT STDMETHODCALLTYPE GetStreamSource(UINT StreamNumber, IDirect3DVertexBuffer9 **ppStreamData, UINT *OffsetInBytes, UINT *pStride) override; + HRESULT STDMETHODCALLTYPE SetStreamSourceFreq(UINT StreamNumber, UINT Divider) override; + HRESULT STDMETHODCALLTYPE GetStreamSourceFreq(UINT StreamNumber, UINT *Divider) override; + HRESULT STDMETHODCALLTYPE SetIndices(IDirect3DIndexBuffer9 *pIndexData) override; + HRESULT STDMETHODCALLTYPE GetIndices(IDirect3DIndexBuffer9 **ppIndexData) override; + HRESULT STDMETHODCALLTYPE CreatePixelShader(const DWORD *pFunction, IDirect3DPixelShader9 **ppShader) override; + HRESULT STDMETHODCALLTYPE SetPixelShader(IDirect3DPixelShader9 *pShader) override; + HRESULT STDMETHODCALLTYPE GetPixelShader(IDirect3DPixelShader9 **ppShader) override; + HRESULT STDMETHODCALLTYPE SetPixelShaderConstantF(UINT StartRegister, const float *pConstantData, UINT Vector4fCount) override; + HRESULT STDMETHODCALLTYPE GetPixelShaderConstantF(UINT StartRegister, float *pConstantData, UINT Vector4fCount) override; + HRESULT STDMETHODCALLTYPE SetPixelShaderConstantI(UINT StartRegister, const int *pConstantData, UINT Vector4iCount) override; + HRESULT STDMETHODCALLTYPE GetPixelShaderConstantI(UINT StartRegister, int *pConstantData, UINT Vector4iCount) override; + HRESULT STDMETHODCALLTYPE SetPixelShaderConstantB(UINT StartRegister, const BOOL *pConstantData, UINT BoolCount) override; + HRESULT STDMETHODCALLTYPE GetPixelShaderConstantB(UINT StartRegister, BOOL *pConstantData, UINT BoolCount) override; + HRESULT STDMETHODCALLTYPE DrawRectPatch(UINT Handle, const float *pNumSegs, const D3DRECTPATCH_INFO *pRectPatchInfo) override; + HRESULT STDMETHODCALLTYPE DrawTriPatch(UINT Handle, const float *pNumSegs, const D3DTRIPATCH_INFO *pTriPatchInfo) override; + HRESULT STDMETHODCALLTYPE DeletePatch(UINT Handle) override; + HRESULT STDMETHODCALLTYPE CreateQuery(D3DQUERYTYPE Type, IDirect3DQuery9 **ppQuery) override; + #pragma endregion + #pragma region IDirect3DDevice9Ex + HRESULT STDMETHODCALLTYPE SetConvolutionMonoKernel(UINT width, UINT height, float *rows, float *columns) override; + HRESULT STDMETHODCALLTYPE ComposeRects(IDirect3DSurface9 *pSrc, IDirect3DSurface9 *pDst, IDirect3DVertexBuffer9 *pSrcRectDescs, UINT NumRects, IDirect3DVertexBuffer9 *pDstRectDescs, D3DCOMPOSERECTSOP Operation, int Xoffset, int Yoffset) override; + HRESULT STDMETHODCALLTYPE PresentEx(const RECT *pSourceRect, const RECT *pDestRect, HWND hDestWindowOverride, const RGNDATA *pDirtyRegion, DWORD dwFlags) override; + HRESULT STDMETHODCALLTYPE GetGPUThreadPriority(INT *pPriority) override; + HRESULT STDMETHODCALLTYPE SetGPUThreadPriority(INT Priority) override; + HRESULT STDMETHODCALLTYPE WaitForVBlank(UINT iSwapChain) override; + HRESULT STDMETHODCALLTYPE CheckResourceResidency(IDirect3DResource9 **pResourceArray, UINT32 NumResources) override; + HRESULT STDMETHODCALLTYPE SetMaximumFrameLatency(UINT MaxLatency) override; + HRESULT STDMETHODCALLTYPE GetMaximumFrameLatency(UINT *pMaxLatency) override; + HRESULT STDMETHODCALLTYPE CheckDeviceState(HWND hDestinationWindow) override; + HRESULT STDMETHODCALLTYPE CreateRenderTargetEx(UINT Width, UINT Height, D3DFORMAT Format, D3DMULTISAMPLE_TYPE MultiSample, DWORD MultisampleQuality, BOOL Lockable, IDirect3DSurface9 **ppSurface, HANDLE *pSharedHandle, DWORD Usage) override; + HRESULT STDMETHODCALLTYPE CreateOffscreenPlainSurfaceEx(UINT Width, UINT Height, D3DFORMAT Format, D3DPOOL Pool, IDirect3DSurface9 **ppSurface, HANDLE *pSharedHandle, DWORD Usage) override; + HRESULT STDMETHODCALLTYPE CreateDepthStencilSurfaceEx(UINT Width, UINT Height, D3DFORMAT Format, D3DMULTISAMPLE_TYPE MultiSample, DWORD MultisampleQuality, BOOL Discard, IDirect3DSurface9 **ppSurface, HANDLE *pSharedHandle, DWORD Usage) override; + HRESULT STDMETHODCALLTYPE ResetEx(D3DPRESENT_PARAMETERS *pPresentationParameters, D3DDISPLAYMODEEX *pFullscreenDisplayMode) override; + HRESULT STDMETHODCALLTYPE GetDisplayModeEx(UINT iSwapChain, D3DDISPLAYMODEEX *pMode, D3DDISPLAYROTATION *pRotation) override; + #pragma endregion + + bool check_and_upgrade_interface(REFIID riid); + + LONG _ref = 1; + IDirect3DDevice9 *_orig; + bool _extended_interface; + Direct3DSwapChain9 *_implicit_swapchain = nullptr; + std::vector _additional_swapchains; + com_ptr _auto_depthstencil; + bool _use_software_rendering = false; +}; diff --git a/msvc/source/d3d9/d3d9_profiling.cpp b/msvc/source/d3d9/d3d9_profiling.cpp new file mode 100644 index 0000000..59db1b1 --- /dev/null +++ b/msvc/source/d3d9/d3d9_profiling.cpp @@ -0,0 +1,48 @@ +/** + * Copyright (C) 2014 Patrick Mours. All rights reserved. + * License: https://github.com/crosire/reshade#license + */ + +#include "hook_manager.hpp" +#include + +HOOK_EXPORT int WINAPI D3DPERF_BeginEvent(D3DCOLOR col, LPCWSTR wszName) +{ + UNREFERENCED_PARAMETER(col); + UNREFERENCED_PARAMETER(wszName); + + return 0; +} +HOOK_EXPORT int WINAPI D3DPERF_EndEvent() +{ + return 0; +} + +HOOK_EXPORT void WINAPI D3DPERF_SetMarker(D3DCOLOR col, LPCWSTR wszName) +{ + UNREFERENCED_PARAMETER(col); + UNREFERENCED_PARAMETER(wszName); +} +HOOK_EXPORT void WINAPI D3DPERF_SetRegion(D3DCOLOR col, LPCWSTR wszName) +{ + UNREFERENCED_PARAMETER(col); + UNREFERENCED_PARAMETER(wszName); +} +HOOK_EXPORT void WINAPI D3DPERF_SetOptions(DWORD dwOptions) +{ + UNREFERENCED_PARAMETER(dwOptions); + +#ifdef _DEBUG // Enable PIX in debug builds (calling 'D3DPERF_SetOptions(1)' disables profiling/analysis tools, so revert that) + reshade::hooks::call(D3DPERF_SetOptions)(0); +#endif +} + +HOOK_EXPORT BOOL WINAPI D3DPERF_QueryRepeatFrame() +{ + return FALSE; +} + +HOOK_EXPORT DWORD WINAPI D3DPERF_GetStatus() +{ + return 0; +} diff --git a/msvc/source/d3d9/d3d9_swapchain.cpp b/msvc/source/d3d9/d3d9_swapchain.cpp new file mode 100644 index 0000000..e9a2627 --- /dev/null +++ b/msvc/source/d3d9/d3d9_swapchain.cpp @@ -0,0 +1,149 @@ +/** + * Copyright (C) 2014 Patrick Mours. All rights reserved. + * License: https://github.com/crosire/reshade#license + */ + +#include "log.hpp" +#include "d3d9_device.hpp" +#include "d3d9_swapchain.hpp" +#include "runtime_d3d9.hpp" + +Direct3DSwapChain9::Direct3DSwapChain9(Direct3DDevice9 *device, IDirect3DSwapChain9 *original, const std::shared_ptr &runtime) : + _orig(original), + _extended_interface(0), + _device(device), + _runtime(runtime) {} +Direct3DSwapChain9::Direct3DSwapChain9(Direct3DDevice9 *device, IDirect3DSwapChain9Ex *original, const std::shared_ptr &runtime) : + _orig(original), + _extended_interface(1), + _device(device), + _runtime(runtime) {} + +bool Direct3DSwapChain9::check_and_upgrade_interface(REFIID riid) +{ + if (_extended_interface || riid != __uuidof(IDirect3DSwapChain9Ex)) + return true; + + IDirect3DSwapChain9Ex *new_interface = nullptr; + if (FAILED(_orig->QueryInterface(IID_PPV_ARGS(&new_interface)))) + return false; +#if RESHADE_VERBOSE_LOG + LOG(DEBUG) << "Upgraded IDirect3DSwapChain9 object " << this << " to IDirect3DSwapChain9Ex."; +#endif + _orig->Release(); + _orig = new_interface; + _extended_interface = true; + + return true; +} + +HRESULT STDMETHODCALLTYPE Direct3DSwapChain9::QueryInterface(REFIID riid, void **ppvObj) +{ + if (ppvObj == nullptr) + return E_POINTER; + + if (riid == __uuidof(this) || + riid == __uuidof(IUnknown) || + riid == __uuidof(IDirect3DSwapChain9) || + riid == __uuidof(IDirect3DSwapChain9Ex)) + { + if (!check_and_upgrade_interface(riid)) + return E_NOINTERFACE; + + AddRef(); + *ppvObj = this; + return S_OK; + } + + return _orig->QueryInterface(riid, ppvObj); +} +ULONG STDMETHODCALLTYPE Direct3DSwapChain9::AddRef() +{ + ++_ref; + + return _orig->AddRef(); +} +ULONG STDMETHODCALLTYPE Direct3DSwapChain9::Release() +{ + if (--_ref == 0) + { + assert(_runtime != nullptr); + _runtime->on_reset(); + _runtime.reset(); + + const auto it = std::find(_device->_additional_swapchains.begin(), _device->_additional_swapchains.end(), this); + if (it != _device->_additional_swapchains.end()) + { + _device->_additional_swapchains.erase(it); + _device->Release(); // Remove the reference that was added in 'Direct3DDevice9::CreateAdditionalSwapChain' + } + } + + const ULONG ref = _orig->Release(); + + if (ref != 0 && _ref != 0) + return ref; + else if (ref != 0) + LOG(WARN) << "Reference count for IDirect3DSwapChain9" << (_extended_interface ? "Ex" : "") << " object " << this << " is inconsistent: " << ref << ", but expected 0."; + + assert(_ref <= 0); +#if RESHADE_VERBOSE_LOG + LOG(DEBUG) << "Destroyed IDirect3DSwapChain9" << (_extended_interface ? "Ex" : "") << " object " << this << '.'; +#endif + delete this; + return 0; +} + +HRESULT STDMETHODCALLTYPE Direct3DSwapChain9::Present(const RECT *pSourceRect, const RECT *pDestRect, HWND hDestWindowOverride, const RGNDATA *pDirtyRegion, DWORD dwFlags) +{ + assert(_runtime != nullptr); + _runtime->on_present(); + + return _orig->Present(pSourceRect, pDestRect, hDestWindowOverride, pDirtyRegion, dwFlags); +} +HRESULT STDMETHODCALLTYPE Direct3DSwapChain9::GetFrontBufferData(IDirect3DSurface9 *pDestSurface) +{ + return _orig->GetFrontBufferData(pDestSurface); +} +HRESULT STDMETHODCALLTYPE Direct3DSwapChain9::GetBackBuffer(UINT iBackBuffer, D3DBACKBUFFER_TYPE Type, IDirect3DSurface9 **ppBackBuffer) +{ + return _orig->GetBackBuffer(iBackBuffer, Type, ppBackBuffer); +} +HRESULT STDMETHODCALLTYPE Direct3DSwapChain9::GetRasterStatus(D3DRASTER_STATUS *pRasterStatus) +{ + return _orig->GetRasterStatus(pRasterStatus); +} +HRESULT STDMETHODCALLTYPE Direct3DSwapChain9::GetDisplayMode(D3DDISPLAYMODE *pMode) +{ + return _orig->GetDisplayMode(pMode); +} +HRESULT STDMETHODCALLTYPE Direct3DSwapChain9::GetDevice(IDirect3DDevice9 **ppDevice) +{ + if (ppDevice == nullptr) + return D3DERR_INVALIDCALL; + + _device->AddRef(); + *ppDevice = _device; + + return D3D_OK; +} +HRESULT STDMETHODCALLTYPE Direct3DSwapChain9::GetPresentParameters(D3DPRESENT_PARAMETERS *pPresentationParameters) +{ + return _orig->GetPresentParameters(pPresentationParameters); +} + +HRESULT STDMETHODCALLTYPE Direct3DSwapChain9::GetLastPresentCount(UINT *pLastPresentCount) +{ + assert(_extended_interface); + return static_cast(_orig)->GetLastPresentCount(pLastPresentCount); +} +HRESULT STDMETHODCALLTYPE Direct3DSwapChain9::GetPresentStats(D3DPRESENTSTATS *pPresentationStatistics) +{ + assert(_extended_interface); + return static_cast(_orig)->GetPresentStats(pPresentationStatistics); +} +HRESULT STDMETHODCALLTYPE Direct3DSwapChain9::GetDisplayModeEx(D3DDISPLAYMODEEX *pMode, D3DDISPLAYROTATION *pRotation) +{ + assert(_extended_interface); + return static_cast(_orig)->GetDisplayModeEx(pMode, pRotation); +} diff --git a/msvc/source/d3d9/d3d9_swapchain.hpp b/msvc/source/d3d9/d3d9_swapchain.hpp new file mode 100644 index 0000000..a1b2146 --- /dev/null +++ b/msvc/source/d3d9/d3d9_swapchain.hpp @@ -0,0 +1,48 @@ +/** + * Copyright (C) 2014 Patrick Mours. All rights reserved. + * License: https://github.com/crosire/reshade#license + */ + +#pragma once + +#include +#include + +struct Direct3DDevice9; +namespace reshade::d3d9 { class runtime_d3d9; } + +struct __declspec(uuid("BC52FCE4-1EAC-40C8-84CF-863600BBAA01")) Direct3DSwapChain9 : IDirect3DSwapChain9Ex +{ + Direct3DSwapChain9(Direct3DDevice9 *device, IDirect3DSwapChain9 *original, const std::shared_ptr &runtime); + Direct3DSwapChain9(Direct3DDevice9 *device, IDirect3DSwapChain9Ex *original, const std::shared_ptr &runtime); + + Direct3DSwapChain9(const Direct3DSwapChain9 &) = delete; + Direct3DSwapChain9 &operator=(const Direct3DSwapChain9 &) = delete; + + HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void **ppvObj) override; + ULONG STDMETHODCALLTYPE AddRef() override; + ULONG STDMETHODCALLTYPE Release() override; + + #pragma region IDirect3DSwapChain9 + HRESULT STDMETHODCALLTYPE Present(const RECT *pSourceRect, const RECT *pDestRect, HWND hDestWindowOverride, const RGNDATA *pDirtyRegion, DWORD dwFlags) override; + HRESULT STDMETHODCALLTYPE GetFrontBufferData(IDirect3DSurface9 *pDestSurface) override; + HRESULT STDMETHODCALLTYPE GetBackBuffer(UINT iBackBuffer, D3DBACKBUFFER_TYPE Type, IDirect3DSurface9 **ppBackBuffer) override; + HRESULT STDMETHODCALLTYPE GetRasterStatus(D3DRASTER_STATUS *pRasterStatus) override; + HRESULT STDMETHODCALLTYPE GetDisplayMode(D3DDISPLAYMODE *pMode) override; + HRESULT STDMETHODCALLTYPE GetDevice(IDirect3DDevice9 **ppDevice) override; + HRESULT STDMETHODCALLTYPE GetPresentParameters(D3DPRESENT_PARAMETERS *pPresentationParameters) override; + #pragma endregion + #pragma region IDirect3DSwapChain9Ex + HRESULT STDMETHODCALLTYPE GetLastPresentCount(UINT *pLastPresentCount) override; + HRESULT STDMETHODCALLTYPE GetPresentStats(D3DPRESENTSTATS *pPresentationStatistics) override; + HRESULT STDMETHODCALLTYPE GetDisplayModeEx(D3DDISPLAYMODEEX *pMode, D3DDISPLAYROTATION *pRotation) override; + #pragma endregion + + bool check_and_upgrade_interface(REFIID riid); + + LONG _ref = 1; + IDirect3DSwapChain9 *_orig; + bool _extended_interface; + Direct3DDevice9 *const _device; + std::shared_ptr _runtime; +}; diff --git a/msvc/source/d3d9/runtime_d3d9.cpp b/msvc/source/d3d9/runtime_d3d9.cpp new file mode 100644 index 0000000..6f1cd31 --- /dev/null +++ b/msvc/source/d3d9/runtime_d3d9.cpp @@ -0,0 +1,1711 @@ +/** + * Copyright (C) 2014 Patrick Mours. All rights reserved. + * License: https://github.com/crosire/reshade#license + */ + +#include "log.hpp" +#include "ini_file.hpp" +#include "runtime_d3d9.hpp" +#include "runtime_objects.hpp" +#include +#include + +constexpr auto D3DFMT_INTZ = static_cast(MAKEFOURCC('I', 'N', 'T', 'Z')); +constexpr auto D3DFMT_DF16 = static_cast(MAKEFOURCC('D', 'F', '1', '6')); +constexpr auto D3DFMT_DF24 = static_cast(MAKEFOURCC('D', 'F', '2', '4')); +constexpr auto D3DFMT_ATI1 = static_cast(MAKEFOURCC('A', 'T', 'I', '1')); +constexpr auto D3DFMT_ATI2 = static_cast(MAKEFOURCC('A', 'T', 'I', '2')); + +namespace reshade::d3d9 +{ + struct d3d9_tex_data : base_object + { + com_ptr texture; + com_ptr surface; + }; + struct d3d9_pass_data : base_object + { + com_ptr vertex_shader; + com_ptr pixel_shader; + com_ptr stateblock; + bool clear_render_targets = false; + IDirect3DSurface9 *render_targets[8] = {}; + IDirect3DTexture9 *sampler_textures[16] = {}; + }; + struct d3d9_technique_data : base_object + { + DWORD num_samplers = 0; + DWORD sampler_states[16][12] = {}; + IDirect3DTexture9 *sampler_textures[16] = {}; + DWORD constant_register_count = 0; + size_t uniform_storage_offset = 0; + }; +} + +reshade::d3d9::runtime_d3d9::runtime_d3d9(IDirect3DDevice9 *device, IDirect3DSwapChain9 *swapchain) : + _device(device), _swapchain(swapchain), + _app_state(device) +{ + assert(device != nullptr); + assert(swapchain != nullptr); + + _device->GetDirect3D(&_d3d); + assert(_d3d != nullptr); + + D3DCAPS9 caps = {}; + _device->GetDeviceCaps(&caps); + D3DDEVICE_CREATION_PARAMETERS creation_params = {}; + _device->GetCreationParameters(&creation_params); + D3DADAPTER_IDENTIFIER9 adapter_desc = {}; + _d3d->GetAdapterIdentifier(creation_params.AdapterOrdinal, 0, &adapter_desc); + + _vendor_id = adapter_desc.VendorId; + _device_id = adapter_desc.DeviceId; + _renderer_id = 0x9000; + + _num_samplers = caps.MaxSimultaneousTextures; + _num_simultaneous_rendertargets = std::min(caps.NumSimultaneousRTs, DWORD(8)); + _behavior_flags = creation_params.BehaviorFlags; + +#if RESHADE_GUI + subscribe_to_ui("DX9", [this]() { draw_debug_menu(); }); +#endif + subscribe_to_load_config([this](const ini_file &config) { + config.get("DX9_BUFFER_DETECTION", "DisableINTZ", _disable_intz); + config.get("DX9_BUFFER_DETECTION", "PreserveDepthBuffer", _preserve_depth_buffer); + config.get("DX9_BUFFER_DETECTION", "PreserveDepthBufferIndex", _preserve_starting_index); + config.get("DX9_BUFFER_DETECTION", "AutoPreserve", _auto_preserve); + config.get("DX9_BUFFER_DETECTION", "SourceEngineFix", _source_engine_fix); + config.get("DX9_BUFFER_DETECTION", "FocusOnBestOriginalDepthstencilSource", _focus_on_best_original_depthstencil_source); + config.get("DX9_BUFFER_DETECTION", "BruteForceFix", _brute_force_fix); + }); + subscribe_to_save_config([this](ini_file &config) { + config.set("DX9_BUFFER_DETECTION", "DisableINTZ", _disable_intz); + config.set("DX9_BUFFER_DETECTION", "PreserveDepthBuffer", _preserve_depth_buffer); + config.set("DX9_BUFFER_DETECTION", "PreserveDepthBufferIndex", _preserve_starting_index); + config.set("DX9_BUFFER_DETECTION", "AutoPreserve", _auto_preserve); + config.set("DX9_BUFFER_DETECTION", "SourceEngineFix", _source_engine_fix); + config.set("DX9_BUFFER_DETECTION", "FocusOnBestOriginalDepthstencilSource", _focus_on_best_original_depthstencil_source); + config.set("DX9_BUFFER_DETECTION", "BruteForceFix", _brute_force_fix); + }); +} +reshade::d3d9::runtime_d3d9::~runtime_d3d9() +{ + if (_d3d_compiler != nullptr) + FreeLibrary(_d3d_compiler); +} + +bool reshade::d3d9::runtime_d3d9::init_backbuffer_texture() +{ + // Get back buffer surface + HRESULT hr = _swapchain->GetBackBuffer(0, D3DBACKBUFFER_TYPE_MONO, &_backbuffer); + assert(SUCCEEDED(hr)); + + if (_is_multisampling_enabled || + (_backbuffer_format == D3DFMT_X8R8G8B8 || _backbuffer_format == D3DFMT_X8B8G8R8)) + { + switch (_backbuffer_format) + { + case D3DFMT_X8R8G8B8: + _backbuffer_format = D3DFMT_A8R8G8B8; + break; + case D3DFMT_X8B8G8R8: + _backbuffer_format = D3DFMT_A8B8G8R8; + break; + } + + if (hr = _device->CreateRenderTarget(_width, _height, _backbuffer_format, D3DMULTISAMPLE_NONE, 0, FALSE, &_backbuffer_resolved, nullptr); FAILED(hr)) + { + LOG(ERROR) << "Failed to create back buffer resolve texture! HRESULT is '" << std::hex << hr << std::dec << "'."; + return false; + } + } + else + { + _backbuffer_resolved = _backbuffer; + } + + // Create back buffer shader texture + if (hr = _device->CreateTexture(_width, _height, 1, D3DUSAGE_RENDERTARGET, _backbuffer_format, D3DPOOL_DEFAULT, &_backbuffer_texture, nullptr); FAILED(hr)) + { + LOG(ERROR) << "Failed to create back buffer texture! HRESULT is '" << std::hex << hr << std::dec << "'."; + return false; + } + + _backbuffer_texture->GetSurfaceLevel(0, &_backbuffer_texture_surface); + + return true; +} +bool reshade::d3d9::runtime_d3d9::init_default_depth_stencil() +{ + if (HRESULT hr = _device->CreateDepthStencilSurface(_width, _height, D3DFMT_D24S8, D3DMULTISAMPLE_NONE, 0, FALSE, &_default_depthstencil, nullptr); FAILED(hr)) + { + LOG(ERROR) << "Failed to create default depth stencil! HRESULT is '" << std::hex << hr << std::dec << "'."; + return false; + } + + return true; +} +bool reshade::d3d9::runtime_d3d9::init_fullscreen_triangle_resources() +{ + if (HRESULT hr = _device->CreateVertexBuffer(3 * sizeof(float), D3DUSAGE_WRITEONLY, 0, D3DPOOL_DEFAULT, &_effect_triangle_buffer, nullptr); FAILED(hr)) + { + LOG(ERROR) << "Failed to create effect vertex buffer! HRESULT is '" << std::hex << hr << std::dec << "'."; + return false; + } + + float *data = nullptr; + _effect_triangle_buffer->Lock(0, 3 * sizeof(float), reinterpret_cast(&data), 0); + for (unsigned int i = 0; i < 3; i++) + data[i] = static_cast(i); + _effect_triangle_buffer->Unlock(); + + const D3DVERTEXELEMENT9 declaration[] = { + { 0, 0, D3DDECLTYPE_FLOAT1, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 0 }, + D3DDECL_END() + }; + + if (HRESULT hr = _device->CreateVertexDeclaration(declaration, &_effect_triangle_layout); FAILED(hr)) + { + LOG(ERROR) << "Failed to create effect vertex declaration! HRESULT is '" << std::hex << hr << std::dec << "'."; + return false; + } + + return true; +} + +bool reshade::d3d9::runtime_d3d9::on_init(const D3DPRESENT_PARAMETERS &pp) +{ + RECT window_rect = {}; + GetClientRect(pp.hDeviceWindow, &window_rect); + + _width = pp.BackBufferWidth; + _height = pp.BackBufferHeight; + _window_width = window_rect.right - window_rect.left; + _window_height = window_rect.bottom - window_rect.top; + _backbuffer_format = pp.BackBufferFormat; + _is_multisampling_enabled = pp.MultiSampleType != D3DMULTISAMPLE_NONE; + + if (!_app_state.init_state_block() || + !init_backbuffer_texture() || + !init_default_depth_stencil() || + !init_fullscreen_triangle_resources() +#if RESHADE_GUI + || !init_imgui_resources() +#endif + ) + return false; + + return runtime::on_init(pp.hDeviceWindow); +} +void reshade::d3d9::runtime_d3d9::on_reset() +{ + runtime::on_reset(); + + _app_state.release_state_block(); + + _backbuffer.reset(); + _backbuffer_resolved.reset(); + _backbuffer_texture.reset(); + _backbuffer_texture_surface.reset(); + + _depthstencil.reset(); + _depthstencil_replacement.reset(); + _depthstencil_texture.reset(); + + _default_depthstencil.reset(); + + _effect_triangle_buffer.reset(); + _effect_triangle_layout.reset(); + + _imgui_vertex_buffer.reset(); + _imgui_index_buffer.reset(); + _imgui_vertex_buffer_size = 0; + _imgui_index_buffer_size = 0; + + _imgui_state.reset(); + + _depth_source_table.clear(); + _depth_buffer_table.clear(); + + _db_vertices = 0; + _db_drawcalls = 0; + _current_db_vertices = 0; + _current_db_drawcalls = 0; + + _disable_depth_buffer_size_restriction = false; + _init_depthbuffer_detection = true; +} + +void reshade::d3d9::runtime_d3d9::on_present() +{ + if (!_is_initialized || FAILED(_device->BeginScene())) + return; + + detect_depth_source(); + + /** Vanquish fix (and other games using bigger depth buffer surface than the viewport) **/ + // if the depthstencil_replacement surface detection fails on the first attempt, try to detect it + // in some bigger resolutions + if (_depthstencil_replacement == nullptr) + _disable_depth_buffer_size_restriction = true; + // if the depthstencil_replacement surface detection succeeds by retrieving bigger resolutions candidates + // the texture is cropped to the actual viewport, so we can go back to the standard resolution filter + // and recheck for a depthstencil replacement candidate using the good resolution + else if (_disable_depth_buffer_size_restriction) + { + _depth_source_table.clear(); + _disable_depth_buffer_size_restriction = false; + } + + _init_depthbuffer_detection = false; + + _app_state.capture(); + BOOL software_rendering_enabled = FALSE; + if ((_behavior_flags & D3DCREATE_MIXED_VERTEXPROCESSING) != 0) + software_rendering_enabled = _device->GetSoftwareVertexProcessing(), + _device->SetSoftwareVertexProcessing(FALSE); // Disable software vertex processing since it is incompatible with programmable shaders + + // Resolve MSAA back buffer if MSAA is active + if (_backbuffer_resolved != _backbuffer) + _device->StretchRect(_backbuffer.get(), nullptr, _backbuffer_resolved.get(), nullptr, D3DTEXF_NONE); + + update_and_render_effects(); + runtime::on_present(); + + // Stretch main render target back into MSAA back buffer if MSAA is active + if (_backbuffer_resolved != _backbuffer) + _device->StretchRect(_backbuffer_resolved.get(), nullptr, _backbuffer.get(), nullptr, D3DTEXF_NONE); + + // Apply previous state from application + _app_state.apply_and_release(); + if ((_behavior_flags & D3DCREATE_MIXED_VERTEXPROCESSING) != 0) + _device->SetSoftwareVertexProcessing(software_rendering_enabled); + + _device->EndScene(); + + if (_preserve_depth_buffer && !_depth_buffer_table.empty()) + { + _depth_buffer_table.clear(); + + com_ptr depthstencil; + _device->GetDepthStencilSurface(&depthstencil); + + // Ensure that the main depth buffer replacement surface (and texture) is cleared before the next frame + _device->SetDepthStencilSurface(_depthstencil_replacement.get()); + _device->Clear(0, nullptr, D3DCLEAR_ZBUFFER, 0, 1.0f, 0); + + _device->SetDepthStencilSurface(depthstencil.get()); + } +} + +void reshade::d3d9::runtime_d3d9::on_draw_primitive(D3DPRIMITIVETYPE PrimitiveType, UINT StartVertex, UINT PrimitiveCount) +{ + com_ptr depthstencil; + _device->GetDepthStencilSurface(&depthstencil); + + if (depthstencil != nullptr) + { + // Resolve pointer to original depth stencil + if (_depthstencil_replacement == depthstencil) + depthstencil = _depthstencil; + } + + // fix to display user weapon and cockpit in some games + weapon_or_cockpit_fix(depthstencil, PrimitiveType, StartVertex, PrimitiveCount); + + on_draw_call(depthstencil, PrimitiveType, PrimitiveCount); +} +void reshade::d3d9::runtime_d3d9::on_draw_indexed_primitive(D3DPRIMITIVETYPE PrimitiveType, INT BaseVertexIndex, UINT MinVertexIndex, UINT NumVertices, UINT StartIndex, UINT PrimitiveCount) +{ + com_ptr depthstencil; + _device->GetDepthStencilSurface(&depthstencil); + + if (depthstencil != nullptr) + { + // Resolve pointer to original depth stencil + if (_depthstencil_replacement == depthstencil) + depthstencil = _depthstencil; + } + + // fix to display user weapon and cockpit in some games + weapon_or_cockpit_fix(depthstencil, PrimitiveType, BaseVertexIndex, MinVertexIndex, NumVertices, StartIndex, PrimitiveCount); + + on_draw_call(depthstencil, PrimitiveType, PrimitiveCount); +} +void reshade::d3d9::runtime_d3d9::on_draw_primitive_up(D3DPRIMITIVETYPE PrimitiveType, UINT PrimitiveCount, const void *pVertexStreamZeroData, UINT VertexStreamZeroStride) +{ + com_ptr depthstencil; + _device->GetDepthStencilSurface(&depthstencil); + + if (depthstencil != nullptr) + { + // Resolve pointer to original depth stencil + if (_depthstencil_replacement == depthstencil) + depthstencil = _depthstencil; + } + + on_draw_call(depthstencil, PrimitiveType, PrimitiveCount); +} +void reshade::d3d9::runtime_d3d9::on_draw_indexed_primitive_up(D3DPRIMITIVETYPE PrimitiveType, UINT MinVertexIndex, UINT NumVertices, UINT PrimitiveCount, const void *pIndexData, D3DFORMAT IndexDataFormat, const void *pVertexStreamZeroData, UINT VertexStreamZeroStride) +{ + com_ptr depthstencil; + _device->GetDepthStencilSurface(&depthstencil); + + if (depthstencil != nullptr) + { + // Resolve pointer to original depth stencil + if (_depthstencil_replacement == depthstencil) + depthstencil = _depthstencil; + } + + on_draw_call(depthstencil, PrimitiveType, PrimitiveCount); +} +void reshade::d3d9::runtime_d3d9::on_draw_call(com_ptr depthstencil, D3DPRIMITIVETYPE type, unsigned int vertices) +{ + switch (type) + { + case D3DPT_LINELIST: + vertices *= 2; + break; + case D3DPT_LINESTRIP: + vertices += 1; + break; + case D3DPT_TRIANGLELIST: + vertices *= 3; + break; + case D3DPT_TRIANGLESTRIP: + case D3DPT_TRIANGLEFAN: + vertices += 2; + break; + } + + _vertices += vertices; + _drawcalls += 1; + + if (depthstencil != nullptr) + { + // Resolve pointer to original depth stencil + if (_depthstencil_replacement == depthstencil) + depthstencil = _depthstencil; + + // Update draw statistics for tracked depth stencil surfaces + const auto it = _depth_source_table.find(depthstencil.get()); + if (it != _depth_source_table.end()) + { + it->second.drawcall_count = _drawcalls; + it->second.vertices_count += vertices; + } + else + { + D3DSURFACE_DESC desc; + depthstencil->GetDesc(&desc); + + if (!check_depthstencil_size(desc)) // Ignore unlikely candidates + return; + + _depth_source_table.emplace(depthstencil, depth_source_info{ nullptr, desc.Width, desc.Height, _drawcalls, _vertices }); + } + } + + if (_preserve_depth_buffer && _depthstencil_replacement != nullptr) + { + _device->SetDepthStencilSurface(depthstencil.get()); + + D3DSURFACE_DESC desc, depthstencil_desc; + D3DVIEWPORT9 pViewport; + + _device->GetViewport(&pViewport); + + desc.Width = pViewport.Width; + desc.Height = pViewport.Height; + desc.MultiSampleType = D3DMULTISAMPLE_NONE; + _is_good_viewport = true; + + if (_depthstencil_replacement == nullptr) + _is_good_viewport = check_depthstencil_size(desc); + else + { + _depthstencil_replacement->GetDesc(&depthstencil_desc); + _is_good_viewport = check_depthstencil_size(desc, depthstencil_desc); + } + + // remove parasite items + if (!_is_good_viewport) + return; + + // check that the drawcall is done on the best original depthstencil source (the one from which the depthstencil_replaceent was created) + if (_focus_on_best_original_depthstencil_source && !_is_best_original_depthstencil_source) + return; + + _current_db_vertices += vertices, + _current_db_drawcalls += 1; + + if (_depthstencil_replacement != depthstencil && _depth_buffer_table.size() <= _adjusted_preserve_starting_index) + _device->SetDepthStencilSurface(_depthstencil_replacement.get()); + } +} +void reshade::d3d9::runtime_d3d9::on_set_depthstencil_surface(IDirect3DSurface9 *&depthstencil) +{ + _is_best_original_depthstencil_source = (depthstencil == _depthstencil); + + // Keep track of all used depth stencil surfaces + if (_depth_source_table.find(depthstencil) == _depth_source_table.end()) + { + D3DSURFACE_DESC desc; + depthstencil->GetDesc(&desc); + if (!check_depthstencil_size(desc)) // Ignore unlikely candidates + return; + + _depth_source_table.emplace(depthstencil, depth_source_info { nullptr, desc.Width, desc.Height }); + } + + if (_depthstencil_replacement != nullptr && depthstencil == _depthstencil) + depthstencil = _depthstencil_replacement.get(); // Replace application depth stencil surface with our custom one +} +void reshade::d3d9::runtime_d3d9::on_get_depthstencil_surface(IDirect3DSurface9 *&depthstencil) +{ + if (_depthstencil_replacement != nullptr && depthstencil == _depthstencil_replacement) + depthstencil->Release(), depthstencil = _depthstencil.get(), depthstencil->AddRef(); // Return original application depth stencil surface +} +void reshade::d3d9::runtime_d3d9::on_clear_depthstencil_surface(IDirect3DSurface9 *depthstencil) +{ + if (!_preserve_depth_buffer || depthstencil != _depthstencil_replacement) + return; + + const unsigned int min_db_drawcalls = 4; + const unsigned int min_db_vertices = 20; + + D3DSURFACE_DESC desc; + depthstencil->GetDesc(&desc); + if (!check_depthstencil_size(desc)) // Ignore unlikely candidates + return; + + // Check if any draw calls have been registered since the last clear operation + if (_current_db_drawcalls > min_db_drawcalls && _current_db_vertices > min_db_vertices) + { + _depth_buffer_table.push_back({ + _depthstencil_replacement, + desc.Width, + desc.Height, + _current_db_drawcalls, + _current_db_vertices }); + } + + _current_db_vertices = 0; + _current_db_drawcalls = 0; + + if (_depth_buffer_table.empty() || _depth_buffer_table.size() <= _adjusted_preserve_starting_index) + return; + + // If the current depth buffer replacement texture has to be preserved, replace the set surface with the original one, so that the replacement texture will not be cleared + _device->SetDepthStencilSurface(_depthstencil.get()); +} + +void reshade::d3d9::runtime_d3d9::capture_screenshot(uint8_t *buffer) const +{ + if (_backbuffer_format != D3DFMT_X8R8G8B8 && + _backbuffer_format != D3DFMT_X8B8G8R8 && + _backbuffer_format != D3DFMT_A8R8G8B8 && + _backbuffer_format != D3DFMT_A8B8G8R8) + { + LOG(WARN) << "Screenshots are not supported for back buffer format " << _backbuffer_format << '.'; + return; + } + + // Create a surface in system memory, copy back buffer data into it and lock it for reading + com_ptr intermediate; + if (FAILED(_device->CreateOffscreenPlainSurface(_width, _height, _backbuffer_format, D3DPOOL_SYSTEMMEM, &intermediate, nullptr))) + { + LOG(ERROR) << "Failed to create system memory texture for screenshot capture!"; + return; + } + + _device->GetRenderTargetData(_backbuffer_resolved.get(), intermediate.get()); + + D3DLOCKED_RECT mapped; + if (FAILED(intermediate->LockRect(&mapped, nullptr, D3DLOCK_READONLY))) + return; + auto mapped_data = static_cast(mapped.pBits); + + for (uint32_t y = 0, pitch = _width * 4; y < _height; y++, buffer += pitch, mapped_data += mapped.Pitch) + { + std::memcpy(buffer, mapped_data, pitch); + + for (uint32_t x = 0; x < pitch; x += 4) + { + buffer[x + 3] = 0xFF; // Clear alpha channel + if (_backbuffer_format == D3DFMT_A8R8G8B8 || _backbuffer_format == D3DFMT_X8R8G8B8) + std::swap(buffer[x + 0], buffer[x + 2]); // Format is BGRA, but output should be RGBA, so flip channels + } + } + + intermediate->UnlockRect(); +} + +bool reshade::d3d9::runtime_d3d9::init_texture(texture &texture) +{ + texture.impl = std::make_unique(); + + const auto texture_data = texture.impl->as(); + + if (texture.impl_reference != texture_reference::none) + return update_texture_reference(texture); + + UINT levels = texture.levels; + DWORD usage = 0; + D3DFORMAT format = D3DFMT_UNKNOWN; + D3DDEVICE_CREATION_PARAMETERS cp; + _device->GetCreationParameters(&cp); + + switch (texture.format) + { + case reshadefx::texture_format::r8: + format = D3DFMT_A8R8G8B8; + break; + case reshadefx::texture_format::r16f: + format = D3DFMT_R16F; + break; + case reshadefx::texture_format::r32f: + format = D3DFMT_R32F; + break; + case reshadefx::texture_format::rg8: + format = D3DFMT_A8R8G8B8; + break; + case reshadefx::texture_format::rg16: + format = D3DFMT_G16R16; + break; + case reshadefx::texture_format::rg16f: + format = D3DFMT_G16R16F; + break; + case reshadefx::texture_format::rg32f: + format = D3DFMT_G32R32F; + break; + case reshadefx::texture_format::rgba8: + format = D3DFMT_A8R8G8B8; + break; + case reshadefx::texture_format::rgba16: + format = D3DFMT_A16B16G16R16; + break; + case reshadefx::texture_format::rgba16f: + format = D3DFMT_A16B16G16R16F; + break; + case reshadefx::texture_format::rgba32f: + format = D3DFMT_A32B32G32R32F; + break; + case reshadefx::texture_format::rgb10a2: + format = D3DFMT_A2B10G10R10; + break; + } + + if (levels > 1) + { + // Enable auto-generated mipmaps if the format supports it + if (_d3d->CheckDeviceFormat(cp.AdapterOrdinal, cp.DeviceType, D3DFMT_X8R8G8B8, D3DUSAGE_AUTOGENMIPMAP, D3DRTYPE_TEXTURE, format) == D3D_OK) + { + usage |= D3DUSAGE_AUTOGENMIPMAP; + levels = 0; + } + else + { + LOG(WARN) << "Auto-generated mipmap levels are not supported for the format of texture '" << texture.unique_name << "'."; + } + } + + // Make texture a render target if format allows it + HRESULT hr = _d3d->CheckDeviceFormat(cp.AdapterOrdinal, cp.DeviceType, D3DFMT_X8R8G8B8, D3DUSAGE_RENDERTARGET, D3DRTYPE_TEXTURE, format); + if (SUCCEEDED(hr)) + usage |= D3DUSAGE_RENDERTARGET; + + hr = _device->CreateTexture(texture.width, texture.height, levels, usage, format, D3DPOOL_DEFAULT, &texture_data->texture, nullptr); + if (FAILED(hr)) + { + LOG(ERROR) << "Failed to create texture '" << texture.unique_name << "' (" + "Width = " << texture.width << ", " + "Height = " << texture.height << ", " + "Levels = " << levels << ", " + "Usage = " << usage << ", " + "Format = " << format << ")! " + "HRESULT is '" << std::hex << hr << std::dec << "'."; + return false; + } + + hr = texture_data->texture->GetSurfaceLevel(0, &texture_data->surface); + assert(SUCCEEDED(hr)); + + return true; +} +void reshade::d3d9::runtime_d3d9::upload_texture(texture &texture, const uint8_t *pixels) +{ + assert(texture.impl_reference == texture_reference::none && pixels != nullptr); + + const auto texture_impl = texture.impl->as(); + assert(texture_impl != nullptr); + + D3DSURFACE_DESC desc; texture_impl->texture->GetLevelDesc(0, &desc); // Get D3D texture format + com_ptr intermediate; + if (FAILED(_device->CreateTexture(texture.width, texture.height, 1, 0, desc.Format, D3DPOOL_SYSTEMMEM, &intermediate, nullptr))) + { + LOG(ERROR) << "Failed to create system memory texture for texture updating!"; + return; + } + + D3DLOCKED_RECT mapped; + if (FAILED(intermediate->LockRect(0, &mapped, nullptr, 0))) + return; + auto mapped_data = static_cast(mapped.pBits); + + switch (texture.format) + { + case reshadefx::texture_format::r8: // These are actually D3DFMT_A8R8G8B8, see 'init_texture' + for (uint32_t y = 0, pitch = texture.width * 4; y < texture.height; ++y, mapped_data += mapped.Pitch, pixels += pitch) + for (uint32_t x = 0; x < pitch; x += 4) + mapped_data[x + 0] = 0, // Set green and blue channel to zero + mapped_data[x + 1] = 0, + mapped_data[x + 2] = pixels[x + 0], + mapped_data[x + 3] = 0xFF; + break; + case reshadefx::texture_format::rg8: + for (uint32_t y = 0, pitch = texture.width * 4; y < texture.height; ++y, mapped_data += mapped.Pitch, pixels += pitch) + for (uint32_t x = 0; x < pitch; x += 4) + mapped_data[x + 0] = 0, // Set blue channel to zero + mapped_data[x + 1] = pixels[x + 1], + mapped_data[x + 2] = pixels[x + 0], + mapped_data[x + 3] = 0xFF; + break; + case reshadefx::texture_format::rgba8: + for (uint32_t y = 0, pitch = texture.width * 4; y < texture.height; ++y, mapped_data += mapped.Pitch, pixels += pitch) + for (uint32_t x = 0; x < pitch; x += 4) + mapped_data[x + 0] = pixels[x + 2], // Flip RGBA input to BGRA + mapped_data[x + 1] = pixels[x + 1], + mapped_data[x + 2] = pixels[x + 0], + mapped_data[x + 3] = pixels[x + 3]; + break; + default: + LOG(ERROR) << "Texture upload is not supported for format " << static_cast(texture.format) << '!'; + break; + } + + intermediate->UnlockRect(0); + + if (HRESULT hr = _device->UpdateTexture(intermediate.get(), texture_impl->texture.get()); FAILED(hr)) + { + LOG(ERROR) << "Failed to update texture from system memory texture! HRESULT is '" << std::hex << hr << std::dec << "'."; + return; + } +} +bool reshade::d3d9::runtime_d3d9::update_texture_reference(texture &texture) +{ + com_ptr new_reference; + + switch (texture.impl_reference) + { + case texture_reference::back_buffer: + new_reference = _backbuffer_texture; + break; + case texture_reference::depth_buffer: + new_reference = _depthstencil_texture; + break; + default: + return false; + } + + const auto texture_impl = texture.impl->as(); + assert(texture_impl != nullptr); + + if (new_reference == texture_impl->texture) + return true; + + // Update references in technique list + for (const auto &technique : _techniques) + for (const auto &pass : technique.passes_data) + for (auto &tex : pass->as()->sampler_textures) + if (tex == texture_impl->texture) tex = new_reference.get(); + + texture_impl->surface.reset(); + + if (new_reference == nullptr) + { + texture_impl->texture.reset(); + + texture.width = texture.height = texture.levels = 0; + texture.format = reshadefx::texture_format::unknown; + } + else + { + texture_impl->texture = new_reference; + new_reference->GetSurfaceLevel(0, &texture_impl->surface); + + D3DSURFACE_DESC desc; + texture_impl->surface->GetDesc(&desc); + + texture.width = desc.Width; + texture.height = desc.Height; + texture.format = reshadefx::texture_format::unknown; + texture.levels = static_cast(new_reference->GetLevelCount()); + } + + return true; +} +void reshade::d3d9::runtime_d3d9::update_texture_references(texture_reference type) +{ + for (auto &tex : _textures) + if (tex.impl != nullptr && tex.impl_reference == type) + update_texture_reference(tex); +} + +bool reshade::d3d9::runtime_d3d9::compile_effect(effect_data &effect) +{ + if (_d3d_compiler == nullptr) + _d3d_compiler = LoadLibraryW(L"d3dcompiler_47.dll"); + if (_d3d_compiler == nullptr) + _d3d_compiler = LoadLibraryW(L"d3dcompiler_43.dll"); + + if (_d3d_compiler == nullptr) + { + LOG(ERROR) << "Unable to load HLSL compiler (\"d3dcompiler_47.dll\"). Make sure you have the DirectX end-user runtime (June 2010) installed or a newer version of the library in the application directory."; + return false; + } + + const auto D3DCompile = reinterpret_cast(GetProcAddress(_d3d_compiler, "D3DCompile")); + + // Add specialization constant defines to source code + effect.preamble += "#define COLOR_PIXEL_SIZE " + std::to_string(1.0f / _width) + ", " + std::to_string(1.0f / _height) + "\n" + "#define DEPTH_PIXEL_SIZE COLOR_PIXEL_SIZE\n" + "#define SV_TARGET_PIXEL_SIZE COLOR_PIXEL_SIZE\n" + "#define SV_DEPTH_PIXEL_SIZE COLOR_PIXEL_SIZE\n"; + + const std::string hlsl_vs = effect.preamble + effect.module.hlsl; + const std::string hlsl_ps = effect.preamble + "#define POSITION VPOS\n" + effect.module.hlsl; + + std::unordered_map> entry_points; + + // Compile the generated HLSL source code to DX byte code + for (const auto &entry_point : effect.module.entry_points) + { + const std::string &hlsl = entry_point.second ? hlsl_ps : hlsl_vs; + + com_ptr compiled, d3d_errors; + + HRESULT hr = D3DCompile( + hlsl.c_str(), hlsl.size(), + nullptr, nullptr, nullptr, + entry_point.first.c_str(), + entry_point.second ? "ps_3_0" : "vs_3_0", + D3DCOMPILE_OPTIMIZATION_LEVEL3, 0, + &compiled, &d3d_errors); + + if (d3d_errors != nullptr) // Append warnings to the output error string as well + effect.errors.append(static_cast(d3d_errors->GetBufferPointer()), d3d_errors->GetBufferSize() - 1); // Subtracting one to not append the null-terminator as well + + // No need to setup resources if any of the shaders failed to compile + if (FAILED(hr)) + return false; + + // Create runtime shader objects from the compiled DX byte code + if (entry_point.second) + hr = _device->CreatePixelShader(static_cast(compiled->GetBufferPointer()), reinterpret_cast(&entry_points[entry_point.first])); + else + hr = _device->CreateVertexShader(static_cast(compiled->GetBufferPointer()), reinterpret_cast(&entry_points[entry_point.first])); + + if (FAILED(hr)) + { + LOG(ERROR) << "Failed to create shader for entry point '" << entry_point.first << "'. " + "HRESULT is '" << std::hex << hr << std::dec << "'."; + return false; + } + } + + bool success = true; + + d3d9_technique_data technique_init; + technique_init.constant_register_count = static_cast((effect.storage_size + 15) / 16); + technique_init.uniform_storage_offset = effect.storage_offset; + + for (const reshadefx::sampler_info &info : effect.module.samplers) + success &= add_sampler(info, technique_init); + + for (technique &technique : _techniques) + if (technique.impl == nullptr && technique.effect_index == effect.index) + success &= init_technique(technique, technique_init, entry_points); + + return success; +} + +bool reshade::d3d9::runtime_d3d9::add_sampler(const reshadefx::sampler_info &info, d3d9_technique_data &technique_init) +{ + const auto existing_texture = std::find_if(_textures.begin(), _textures.end(), + [&texture_name = info.texture_name](const auto &item) { + return item.unique_name == texture_name && item.impl != nullptr; + }); + + if (existing_texture == _textures.end() || info.binding > ARRAYSIZE(technique_init.sampler_states)) + return false; + + // Since textures with auto-generated mipmap levels do not have a mipmap maximum, limit the bias here so this is not as obvious + assert(existing_texture->levels > 0); + const float lod_bias = std::min(existing_texture->levels - 1.0f, info.lod_bias); + + technique_init.num_samplers = std::max(technique_init.num_samplers, DWORD(info.binding + 1)); + + technique_init.sampler_states[info.binding][D3DSAMP_ADDRESSU] = static_cast(info.address_u); + technique_init.sampler_states[info.binding][D3DSAMP_ADDRESSV] = static_cast(info.address_v); + technique_init.sampler_states[info.binding][D3DSAMP_ADDRESSW] = static_cast(info.address_w); + technique_init.sampler_states[info.binding][D3DSAMP_BORDERCOLOR] = 0; + technique_init.sampler_states[info.binding][D3DSAMP_MAGFILTER] = 1 + ((static_cast(info.filter) & 0x0C) >> 2); + technique_init.sampler_states[info.binding][D3DSAMP_MINFILTER] = 1 + ((static_cast(info.filter) & 0x30) >> 4); + technique_init.sampler_states[info.binding][D3DSAMP_MIPFILTER] = 1 + ((static_cast(info.filter) & 0x03)); + technique_init.sampler_states[info.binding][D3DSAMP_MIPMAPLODBIAS] = *reinterpret_cast(&lod_bias); + technique_init.sampler_states[info.binding][D3DSAMP_MAXMIPLEVEL] = static_cast(std::max(0.0f, info.min_lod)); + technique_init.sampler_states[info.binding][D3DSAMP_MAXANISOTROPY] = 1; + technique_init.sampler_states[info.binding][D3DSAMP_SRGBTEXTURE] = info.srgb; + + technique_init.sampler_textures[info.binding] = existing_texture->impl->as()->texture.get(); + + return true; +} +bool reshade::d3d9::runtime_d3d9::init_technique(technique &technique, const d3d9_technique_data &impl_init, const std::unordered_map> &entry_points) +{ + // Copy construct new technique implementation instead of move because effect may contain multiple techniques + technique.impl = std::make_unique(impl_init); + + const auto technique_data = technique.impl->as(); + + for (size_t pass_index = 0; pass_index < technique.passes.size(); ++pass_index) + { + technique.passes_data.push_back(std::make_unique()); + + auto &pass = *technique.passes_data.back()->as(); + const auto &pass_info = technique.passes[pass_index]; + + entry_points.at(pass_info.ps_entry_point)->QueryInterface(&pass.pixel_shader); + entry_points.at(pass_info.vs_entry_point)->QueryInterface(&pass.vertex_shader); + + pass.render_targets[0] = _backbuffer_resolved.get(); + pass.clear_render_targets = pass_info.clear_render_targets; + + for (size_t k = 0; k < ARRAYSIZE(pass.sampler_textures); ++k) + pass.sampler_textures[k] = technique_data->sampler_textures[k]; + + HRESULT hr = _device->BeginStateBlock(); + + if (SUCCEEDED(hr)) + { + _device->SetVertexShader(pass.vertex_shader.get()); + _device->SetPixelShader(pass.pixel_shader.get()); + + const auto literal_to_blend_func = [](unsigned int value) { + switch (value) + { + case 0: + return D3DBLEND_ZERO; + default: + case 1: + return D3DBLEND_ONE; + case 2: + return D3DBLEND_SRCCOLOR; + case 4: + return D3DBLEND_INVSRCCOLOR; + case 3: + return D3DBLEND_SRCALPHA; + case 5: + return D3DBLEND_INVSRCALPHA; + case 6: + return D3DBLEND_DESTALPHA; + case 7: + return D3DBLEND_INVDESTALPHA; + case 8: + return D3DBLEND_DESTCOLOR; + case 9: + return D3DBLEND_INVDESTCOLOR; + } + }; + const auto literal_to_stencil_op = [](unsigned int value) { + switch (value) + { + default: + case 1: + return D3DSTENCILOP_KEEP; + case 0: + return D3DSTENCILOP_ZERO; + case 3: + return D3DSTENCILOP_REPLACE; + case 4: + return D3DSTENCILOP_INCRSAT; + case 5: + return D3DSTENCILOP_DECRSAT; + case 6: + return D3DSTENCILOP_INVERT; + case 7: + return D3DSTENCILOP_INCR; + case 8: + return D3DSTENCILOP_DECR; + } + }; + + _device->SetRenderState(D3DRS_ZENABLE, false); + _device->SetRenderState(D3DRS_SPECULARENABLE, false); + _device->SetRenderState(D3DRS_FILLMODE, D3DFILL_SOLID); + _device->SetRenderState(D3DRS_SHADEMODE, D3DSHADE_GOURAUD); + _device->SetRenderState(D3DRS_ZWRITEENABLE, true); + _device->SetRenderState(D3DRS_ALPHATESTENABLE, false); + _device->SetRenderState(D3DRS_LASTPIXEL, true); + _device->SetRenderState(D3DRS_SRCBLEND, literal_to_blend_func(pass_info.src_blend)); + _device->SetRenderState(D3DRS_DESTBLEND, literal_to_blend_func(pass_info.dest_blend)); + _device->SetRenderState(D3DRS_ALPHAREF, 0); + _device->SetRenderState(D3DRS_ALPHAFUNC, D3DCMP_ALWAYS); + _device->SetRenderState(D3DRS_DITHERENABLE, false); + _device->SetRenderState(D3DRS_FOGSTART, 0); + _device->SetRenderState(D3DRS_FOGEND, 1); + _device->SetRenderState(D3DRS_FOGDENSITY, 1); + _device->SetRenderState(D3DRS_ALPHABLENDENABLE, pass_info.blend_enable); + _device->SetRenderState(D3DRS_DEPTHBIAS, 0); + _device->SetRenderState(D3DRS_STENCILENABLE, pass_info.stencil_enable); + _device->SetRenderState(D3DRS_STENCILPASS, literal_to_stencil_op(pass_info.stencil_op_pass)); + _device->SetRenderState(D3DRS_STENCILFAIL, literal_to_stencil_op(pass_info.stencil_op_fail)); + _device->SetRenderState(D3DRS_STENCILZFAIL, literal_to_stencil_op(pass_info.stencil_op_depth_fail)); + _device->SetRenderState(D3DRS_STENCILFUNC, static_cast(pass_info.stencil_comparison_func)); + _device->SetRenderState(D3DRS_STENCILREF, pass_info.stencil_reference_value); + _device->SetRenderState(D3DRS_STENCILMASK, pass_info.stencil_read_mask); + _device->SetRenderState(D3DRS_STENCILWRITEMASK, pass_info.stencil_write_mask); + _device->SetRenderState(D3DRS_TEXTUREFACTOR, 0xFFFFFFFF); + _device->SetRenderState(D3DRS_LOCALVIEWER, true); + _device->SetRenderState(D3DRS_EMISSIVEMATERIALSOURCE, D3DMCS_MATERIAL); + _device->SetRenderState(D3DRS_AMBIENTMATERIALSOURCE, D3DMCS_MATERIAL); + _device->SetRenderState(D3DRS_DIFFUSEMATERIALSOURCE, D3DMCS_COLOR1); + _device->SetRenderState(D3DRS_SPECULARMATERIALSOURCE, D3DMCS_COLOR2); + _device->SetRenderState(D3DRS_COLORWRITEENABLE, pass_info.color_write_mask); + _device->SetRenderState(D3DRS_BLENDOP, static_cast(pass_info.blend_op)); + _device->SetRenderState(D3DRS_SCISSORTESTENABLE, false); + _device->SetRenderState(D3DRS_SLOPESCALEDEPTHBIAS, 0); + _device->SetRenderState(D3DRS_ANTIALIASEDLINEENABLE, false); + _device->SetRenderState(D3DRS_TWOSIDEDSTENCILMODE, false); + _device->SetRenderState(D3DRS_CCW_STENCILFAIL, D3DSTENCILOP_KEEP); + _device->SetRenderState(D3DRS_CCW_STENCILZFAIL, D3DSTENCILOP_KEEP); + _device->SetRenderState(D3DRS_CCW_STENCILPASS, D3DSTENCILOP_KEEP); + _device->SetRenderState(D3DRS_CCW_STENCILFUNC, D3DCMP_ALWAYS); + _device->SetRenderState(D3DRS_COLORWRITEENABLE1, 0x0000000F); + _device->SetRenderState(D3DRS_COLORWRITEENABLE2, 0x0000000F); + _device->SetRenderState(D3DRS_COLORWRITEENABLE3, 0x0000000F); + _device->SetRenderState(D3DRS_BLENDFACTOR, 0xFFFFFFFF); + _device->SetRenderState(D3DRS_SRGBWRITEENABLE, pass_info.srgb_write_enable); + _device->SetRenderState(D3DRS_SEPARATEALPHABLENDENABLE, false); + _device->SetRenderState(D3DRS_SRCBLENDALPHA, literal_to_blend_func(pass_info.src_blend_alpha)); + _device->SetRenderState(D3DRS_DESTBLENDALPHA, literal_to_blend_func(pass_info.dest_blend_alpha)); + _device->SetRenderState(D3DRS_BLENDOPALPHA, static_cast(pass_info.blend_op_alpha)); + _device->SetRenderState(D3DRS_FOGENABLE, false); + _device->SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE); + _device->SetRenderState(D3DRS_LIGHTING, false); + + hr = _device->EndStateBlock(&pass.stateblock); + } + + if (FAILED(hr)) + { + LOG(ERROR) << "Failed to create state block for pass " << pass_index << " in technique '" << technique.name << "'. " + "HRESULT is '" << std::hex << hr << std::dec << "'."; + return false; + } + + for (unsigned int k = 0; k < 8; ++k) + { + if (pass_info.render_target_names[k].empty()) + continue; // Skip unbound render targets + + if (k > _num_simultaneous_rendertargets) + { + LOG(WARN) << "Device only supports " << _num_simultaneous_rendertargets << " simultaneous render targets, but pass " << pass_index << " in technique '" << technique.name << "' uses more, which are ignored"; + break; + } + + const auto render_target_texture = std::find_if(_textures.begin(), _textures.end(), + [&render_target = pass_info.render_target_names[k]](const auto &item) { + return item.unique_name == render_target; + }); + + if (render_target_texture == _textures.end()) + return assert(false), false; + + const auto texture_impl = render_target_texture->impl->as(); + + assert(texture_impl != nullptr); + + // Unset textures that are used as render target + for (DWORD s = 0; s < technique_data->num_samplers; ++s) + if (pass.sampler_textures[s] == texture_impl->texture) + pass.sampler_textures[s] = nullptr; + + pass.render_targets[k] = texture_impl->surface.get(); + } + } + + return true; +} + +void reshade::d3d9::runtime_d3d9::render_technique(technique &technique) +{ + auto &technique_data = *technique.impl->as(); + + bool is_default_depthstencil_cleared = false; + + // Setup vertex input + _device->SetStreamSource(0, _effect_triangle_buffer.get(), 0, sizeof(float)); + _device->SetVertexDeclaration(_effect_triangle_layout.get()); + + // Setup shader constants + if (technique_data.constant_register_count > 0) + { + const auto uniform_storage_data = reinterpret_cast(_uniform_data_storage.data() + technique_data.uniform_storage_offset); + _device->SetPixelShaderConstantF(0, uniform_storage_data, technique_data.constant_register_count); + _device->SetVertexShaderConstantF(0, uniform_storage_data, technique_data.constant_register_count); + } + + for (const auto &pass_object : technique.passes_data) + { + const d3d9_pass_data &pass = *pass_object->as(); + + // Setup states + pass.stateblock->Apply(); + + // Save back buffer of previous pass + _device->StretchRect(_backbuffer_resolved.get(), nullptr, _backbuffer_texture_surface.get(), nullptr, D3DTEXF_NONE); + + // Setup shader resources + for (DWORD s = 0; s < technique_data.num_samplers; s++) + { + _device->SetTexture(s, pass.sampler_textures[s]); + + for (DWORD state = D3DSAMP_ADDRESSU; state <= D3DSAMP_SRGBTEXTURE; state++) + _device->SetSamplerState(s, static_cast(state), technique_data.sampler_states[s][state]); + } + + // Setup render targets + for (DWORD target = 0; target < _num_simultaneous_rendertargets; target++) + _device->SetRenderTarget(target, pass.render_targets[target]); + + D3DVIEWPORT9 viewport; + _device->GetViewport(&viewport); + + const float texelsize[4] = { -1.0f / viewport.Width, 1.0f / viewport.Height }; + _device->SetVertexShaderConstantF(255, texelsize, 1); + + const bool is_viewport_sized = viewport.Width == _width && viewport.Height == _height; + + _device->SetDepthStencilSurface(is_viewport_sized ? _default_depthstencil.get() : nullptr); + + if (is_viewport_sized && !is_default_depthstencil_cleared) + { + is_default_depthstencil_cleared = true; + + _device->Clear(0, nullptr, (pass.clear_render_targets ? D3DCLEAR_TARGET : 0) | D3DCLEAR_ZBUFFER | D3DCLEAR_STENCIL, 0, 1.0f, 0); + } + else if (pass.clear_render_targets) + { + _device->Clear(0, nullptr, D3DCLEAR_TARGET, 0, 0.0f, 0); + } + + // Draw triangle + _device->DrawPrimitive(D3DPT_TRIANGLELIST, 0, 1); + + _vertices += 3; + _drawcalls += 1; + + // Update shader resources + for (const auto target : pass.render_targets) + { + if (target == nullptr || target == _backbuffer_resolved) + continue; + + com_ptr texture; + + if (SUCCEEDED(target->GetContainer(IID_PPV_ARGS(&texture))) && texture->GetLevelCount() > 1) + { + texture->SetAutoGenFilterType(D3DTEXF_LINEAR); + texture->GenerateMipSubLevels(); + } + } + } +} + +#if RESHADE_GUI +bool reshade::d3d9::runtime_d3d9::init_imgui_resources() +{ + HRESULT hr = _device->BeginStateBlock(); + + if (SUCCEEDED(hr)) + { + _device->SetFVF(D3DFVF_XYZ | D3DFVF_DIFFUSE | D3DFVF_TEX1); + _device->SetPixelShader(nullptr); + _device->SetVertexShader(nullptr); + _device->SetRenderState(D3DRS_ZENABLE, false); + _device->SetRenderState(D3DRS_FILLMODE, D3DFILL_SOLID); + _device->SetRenderState(D3DRS_ALPHATESTENABLE, false); + _device->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA); + _device->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA); + _device->SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE); + _device->SetRenderState(D3DRS_ALPHABLENDENABLE, true); + _device->SetRenderState(D3DRS_FOGENABLE, false); + _device->SetRenderState(D3DRS_STENCILENABLE, false); + _device->SetRenderState(D3DRS_CLIPPING, false); + _device->SetRenderState(D3DRS_LIGHTING, false); + _device->SetRenderState(D3DRS_COLORWRITEENABLE, D3DCOLORWRITEENABLE_RED | D3DCOLORWRITEENABLE_GREEN | D3DCOLORWRITEENABLE_BLUE | D3DCOLORWRITEENABLE_ALPHA); + _device->SetRenderState(D3DRS_SCISSORTESTENABLE, true); + _device->SetRenderState(D3DRS_BLENDOP, D3DBLENDOP_ADD); + _device->SetRenderState(D3DRS_SRGBWRITEENABLE, false); + _device->SetRenderState(D3DRS_BLENDOPALPHA, D3DBLENDOP_ADD); + _device->SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_MODULATE); + _device->SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_TEXTURE); + _device->SetTextureStageState(0, D3DTSS_COLORARG2, D3DTA_DIFFUSE); + _device->SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_MODULATE); + _device->SetTextureStageState(0, D3DTSS_ALPHAARG1, D3DTA_TEXTURE); + _device->SetTextureStageState(0, D3DTSS_ALPHAARG2, D3DTA_DIFFUSE); + _device->SetTextureStageState(0, D3DTSS_TEXCOORDINDEX, 0); + _device->SetTextureStageState(0, D3DTSS_TEXTURETRANSFORMFLAGS, D3DTTFF_DISABLE); + _device->SetSamplerState(0, D3DSAMP_ADDRESSU, D3DTADDRESS_WRAP); + _device->SetSamplerState(0, D3DSAMP_ADDRESSV, D3DTADDRESS_WRAP); + _device->SetSamplerState(0, D3DSAMP_ADDRESSW, D3DTADDRESS_WRAP); + _device->SetSamplerState(0, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR); + _device->SetSamplerState(0, D3DSAMP_MINFILTER, D3DTEXF_LINEAR); + _device->SetSamplerState(0, D3DSAMP_MIPFILTER, D3DTEXF_NONE); + _device->SetSamplerState(0, D3DSAMP_MIPMAPLODBIAS, 0); + _device->SetSamplerState(0, D3DSAMP_MAXMIPLEVEL, 0); + _device->SetSamplerState(0, D3DSAMP_SRGBTEXTURE, 0); + + hr = _device->EndStateBlock(&_imgui_state); + } + + if (FAILED(hr)) + { + LOG(ERROR) << "Failed to create state block! HRESULT is '" << std::hex << hr << std::dec << "'."; + return false; + } + + return true; +} + +void reshade::d3d9::runtime_d3d9::render_imgui_draw_data(ImDrawData *draw_data) +{ + // Fixed-function vertex layout + struct ImDrawVert9 + { + float x, y, z; + D3DCOLOR col; + float u, v; + }; + + // Create and grow vertex/index buffers if needed + if (_imgui_index_buffer_size < draw_data->TotalIdxCount) + { + _imgui_index_buffer.reset(); + _imgui_index_buffer_size = draw_data->TotalIdxCount + 10000; + + if (FAILED(_device->CreateIndexBuffer(_imgui_index_buffer_size * sizeof(ImDrawIdx), D3DUSAGE_DYNAMIC | D3DUSAGE_WRITEONLY, sizeof(ImDrawIdx) == 2 ? D3DFMT_INDEX16 : D3DFMT_INDEX32, D3DPOOL_DEFAULT, &_imgui_index_buffer, nullptr))) + return; + } + if (_imgui_vertex_buffer_size < draw_data->TotalVtxCount) + { + _imgui_vertex_buffer.reset(); + _imgui_vertex_buffer_size = draw_data->TotalVtxCount + 5000; + + if (FAILED(_device->CreateVertexBuffer(_imgui_vertex_buffer_size * sizeof(ImDrawVert9), D3DUSAGE_DYNAMIC | D3DUSAGE_WRITEONLY, D3DFVF_XYZ | D3DFVF_DIFFUSE | D3DFVF_TEX1, D3DPOOL_DEFAULT, &_imgui_vertex_buffer, nullptr))) + return; + } + + ImDrawVert9 *vtx_dst; ImDrawIdx *idx_dst; + if (FAILED(_imgui_index_buffer->Lock(0, draw_data->TotalIdxCount * sizeof(ImDrawIdx), reinterpret_cast(&idx_dst), D3DLOCK_DISCARD)) || + FAILED(_imgui_vertex_buffer->Lock(0, draw_data->TotalVtxCount * sizeof(ImDrawVert9), reinterpret_cast(&vtx_dst), D3DLOCK_DISCARD))) + return; + + for (int n = 0; n < draw_data->CmdListsCount; n++) + { + const ImDrawList *const draw_list = draw_data->CmdLists[n]; + + for (const ImDrawVert &vtx : draw_list->VtxBuffer) + { + vtx_dst->x = vtx.pos.x; + vtx_dst->y = vtx.pos.y; + vtx_dst->z = 0.0f; + + // RGBA --> ARGB for Direct3D 9 + vtx_dst->col = (vtx.col & 0xFF00FF00) | ((vtx.col & 0xFF0000) >> 16) | ((vtx.col & 0xFF) << 16); + + vtx_dst->u = vtx.uv.x; + vtx_dst->v = vtx.uv.y; + + ++vtx_dst; // Next vertex + } + + memcpy(idx_dst, draw_list->IdxBuffer.Data, draw_list->IdxBuffer.size() * sizeof(ImDrawIdx)); + idx_dst += draw_list->IdxBuffer.size(); + } + + _imgui_index_buffer->Unlock(); + _imgui_vertex_buffer->Unlock(); + + // Setup render state: fixed-pipeline, alpha-blending, no face culling, no depth testing + _imgui_state->Apply(); + _device->SetIndices(_imgui_index_buffer.get()); + _device->SetStreamSource(0, _imgui_vertex_buffer.get(), 0, sizeof(ImDrawVert9)); + for (unsigned int i = 0; i < _num_samplers; i++) + _device->SetTexture(i, nullptr); + _device->SetRenderTarget(0, _backbuffer_resolved.get()); + for (unsigned int i = 1; i < _num_simultaneous_rendertargets; i++) + _device->SetRenderTarget(i, nullptr); + _device->SetDepthStencilSurface(nullptr); + + const D3DMATRIX identity_mat = { + 1.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 1.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 1.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f + }; + const D3DMATRIX ortho_projection = { + 2.0f / draw_data->DisplaySize.x, 0.0f, 0.0f, 0.0f, + 0.0f, -2.0f / draw_data->DisplaySize.y, 0.0f, 0.0f, + 0.0f, 0.0f, 0.5f, 0.0f, + -(2 * draw_data->DisplayPos.x + draw_data->DisplaySize.x + 1.0f) / draw_data->DisplaySize.x, // Bake half-pixel offset into projection matrix + +(2 * draw_data->DisplayPos.y + draw_data->DisplaySize.y + 1.0f) / draw_data->DisplaySize.y, 0.5f, 1.0f + }; + + _device->SetTransform(D3DTS_VIEW, &identity_mat); + _device->SetTransform(D3DTS_WORLD, &identity_mat); + _device->SetTransform(D3DTS_PROJECTION, &ortho_projection); + + UINT vtx_offset = 0, idx_offset = 0; + for (int n = 0; n < draw_data->CmdListsCount; n++) + { + const ImDrawList *const draw_list = draw_data->CmdLists[n]; + + for (const ImDrawCmd &cmd : draw_list->CmdBuffer) + { + assert(cmd.TextureId != 0); + assert(cmd.UserCallback == nullptr); + + const RECT scissor_rect = { + static_cast(cmd.ClipRect.x - draw_data->DisplayPos.x), + static_cast(cmd.ClipRect.y - draw_data->DisplayPos.y), + static_cast(cmd.ClipRect.z - draw_data->DisplayPos.x), + static_cast(cmd.ClipRect.w - draw_data->DisplayPos.y) + }; + _device->SetScissorRect(&scissor_rect); + + _device->SetTexture(0, static_cast(cmd.TextureId)->texture.get()); + + _device->DrawIndexedPrimitive(D3DPT_TRIANGLELIST, vtx_offset, 0, draw_list->VtxBuffer.size(), idx_offset, cmd.ElemCount / 3); + + idx_offset += cmd.ElemCount; + } + + vtx_offset += draw_list->VtxBuffer.size(); + } +} + +void reshade::d3d9::runtime_d3d9::draw_debug_menu() +{ + ImGui::Text("MSAA is %s", _is_multisampling_enabled ? "active" : "inactive"); + ImGui::Spacing(); + + if (ImGui::CollapsingHeader("Buffer Detection", ImGuiTreeNodeFlags_DefaultOpen)) + { + if (ImGui::Checkbox("Preserve depth buffer from being cleared", &_preserve_depth_buffer)) + { + runtime::save_config(); + + // Force depth-stencil replacement recreation + _depthstencil = _default_depthstencil; + // Force depth source table recreation + _depth_buffer_table.clear(); + _depth_source_table.clear(); + _depthstencil_replacement.reset(); + _init_depthbuffer_detection = true; + + if (_preserve_depth_buffer) + _disable_intz = false; + } + + ImGui::Spacing(); + ImGui::Spacing(); + + if (!_preserve_depth_buffer) + { + if (ImGui::Checkbox("Disable replacement with INTZ format", &_disable_intz)) + { + runtime::save_config(); + + // Force depth-stencil replacement recreation + _depthstencil = _default_depthstencil; + // Force depth source table recreation + _depth_source_table.clear(); + } + } + + if (_preserve_depth_buffer) + { + ImGui::Spacing(); + + bool modified = false; + + if (ImGui::Checkbox("Auto preserve", &_auto_preserve)) + { + modified = true; + _preserve_starting_index = std::numeric_limits::max(); + _adjusted_preserve_starting_index = _depth_buffer_table.size() - 1; + } + + ImGui::Spacing(); + + for (size_t i = 0; i < _depth_buffer_table.size(); ++i) + { + char label[512] = ""; + sprintf_s(label, "%s%2zu", (i == _adjusted_preserve_starting_index ? "> " : " "), i); + + if (!_auto_preserve) + { + if (bool value = (_preserve_starting_index == i && !_auto_preserve); ImGui::Checkbox(label, &value)) + { + _adjusted_preserve_starting_index = _preserve_starting_index = value ? i : std::numeric_limits::max(); + if(_preserve_starting_index == std::numeric_limits::max()) + _adjusted_preserve_starting_index = _depth_buffer_table.size() - 1; + modified = true; + } + } + else + { + ImGui::Text("%s", label); + } + + ImGui::SameLine(); + ImGui::Text("0x%p | %ux%u : %u draw calls ==> %u vertices", + _depth_buffer_table[i].depthstencil.get(), + _depth_buffer_table[i].width, + _depth_buffer_table[i].height, + _depth_buffer_table[i].drawcall_count, + _depth_buffer_table[i].vertices_count); + ImGui::Spacing(); + } + + ImGui::Spacing(); + ImGui::Spacing(); + + // this feature can help resolving source_engine wrong depth buffer detection + if (ImGui::Checkbox("Fix for source engine games", &_source_engine_fix)) + { + modified = true; + } + ImGui::Text("Tip: in source engine games, the background can have more vertices than the main scene, so it leads to select the wrong depth buffer in auto preserve mode"); + + // this feature can help resolving user weapon or cockpit not appearing in the depth buffer + if (ImGui::Checkbox("Fix for user weapon or cockpit", &_brute_force_fix)) + { + modified = true; + } + ImGui::Text("Tip: in some games, can help display user weapon or cockpit correctly in the depth buffer"); + + ImGui::Spacing(); + ImGui::Spacing(); + + // this feature can help resolving weapons or cockpits not appearing in the depth buffer + if (ImGui::Checkbox("Focus on the best original depthstencil source", &_focus_on_best_original_depthstencil_source)) + { + modified = true; + } + ImGui::Text("Tip: in some games, can help remove undesired UI elements from the depth buffer"); + + if (modified) + { + runtime::save_config(); + + // Force depth-stencil replacement recreation + _depthstencil = _default_depthstencil; + // Force depth source table recreation + _depth_buffer_table.clear(); + _depth_source_table.clear(); + _depthstencil_replacement.reset(); + _init_depthbuffer_detection = true; + } + } + else + { + for (const auto &it : _depth_source_table) + { + ImGui::Text("%s0x%p | %u draw calls ==> %u vertices", (it.first == _depthstencil ? "> " : " "), it.first.get(), it.second.drawcall_count, it.second.vertices_count); + } + } + } +} +#endif + +void reshade::d3d9::runtime_d3d9::detect_depth_source() +{ + if (_preserve_depth_buffer) + { + // check if we draw calls have been registered since the last cleaning + if (_depthstencil_replacement != nullptr && _current_db_drawcalls > 0 && _current_db_vertices > 0) + { + D3DSURFACE_DESC desc; + _depthstencil_replacement->GetDesc(&desc); + + _depth_buffer_table.push_back({ _depthstencil_replacement, desc.Width, desc.Height, _current_db_drawcalls, _current_db_vertices }); + } + + unsigned int compared_vertices = 0; + _db_vertices = 0; + _db_drawcalls = 0; + _current_db_vertices = 0; + _current_db_drawcalls = 0; + + if (_auto_preserve) + { + // if auto preserve mode is enabled, try to detect the best depth buffer clearing instance from which the depth buffer texture could be preserved + _adjusted_preserve_starting_index = _preserve_starting_index = 0; + + for (size_t i = 0; i != _depth_buffer_table.size(); i++) + { + const auto &it = _depth_buffer_table[i]; + // fix for source engine games: add a weight in order not to select the first db instance if it is related to the backgroud scene + int mult = (_source_engine_fix && i > 0) ? 10 : 1; + if (mult*it.vertices_count >= compared_vertices) + { + _db_vertices = it.vertices_count; + _db_drawcalls = it.drawcall_count; + _adjusted_preserve_starting_index = _preserve_starting_index = i; + compared_vertices = mult * it.vertices_count; + } + } + } + else + { + _adjusted_preserve_starting_index = _preserve_starting_index; + if (_preserve_starting_index == std::numeric_limits::max()) + _adjusted_preserve_starting_index = _depth_buffer_table.size() - 1; + } + } + + if (!_init_depthbuffer_detection && (_framecount % 30 || _is_multisampling_enabled || _depth_source_table.empty())) + return; + + if (_has_high_network_activity) + { + // Force depth source table recreation + _depth_buffer_table.clear(); + _depth_source_table.clear(); + create_depthstencil_replacement(nullptr); + return; + } + + depth_source_info best_info = {}; + com_ptr best_match; + + for (auto it = _depth_source_table.begin(); it != _depth_source_table.end();) + { + auto &depthstencil_info = it->second; + const auto &depthstencil = it->first; + + // Remove unreferenced depth stencil surfaces from the list (application is no longer using it if we are the only ones who still hold a reference) + if (!_preserve_depth_buffer && depthstencil.ref_count() == 1) + { + it = _depth_source_table.erase(it); + continue; + } + else + { + ++it; + } + + if (depthstencil_info.drawcall_count == 0 && !_preserve_depth_buffer) + continue; + + if ((depthstencil_info.vertices_count * (1.2f - float(depthstencil_info.drawcall_count) / _drawcalls)) >= (best_info.vertices_count * (1.2f - float(best_info.drawcall_count) / _drawcalls))) + { + best_info = depthstencil_info; + best_match = depthstencil; + } + + // Reset statistics to zero for next frame + depthstencil_info.drawcall_count = depthstencil_info.vertices_count = 0; + } + + if (best_match != nullptr && _depthstencil != best_match) + create_depthstencil_replacement(best_match.get()); +} + +bool reshade::d3d9::runtime_d3d9::check_depthstencil_size(const D3DSURFACE_DESC &desc) +{ + if (desc.MultiSampleType != D3DMULTISAMPLE_NONE) + return false; // MSAA depth buffers are not supported since they would have to be moved into a plain surface before attaching to a shader slot + + if (_disable_depth_buffer_size_restriction) + { + // Allow depth buffers with greater dimensions than the viewport (e.g. in games like Vanquish) + return desc.Width >= floor(_width * 0.95) && desc.Height >= ceil(_height * 0.95); + } + else + { + return (desc.Width >= floor(_width * 0.95) && desc.Width <= ceil(_width * 1.05)) + && (desc.Height >= floor(_height * 0.95) && desc.Height <= ceil(_height * 1.05)); + } +} + +bool reshade::d3d9::runtime_d3d9::check_depthstencil_size(const D3DSURFACE_DESC &desc, const D3DSURFACE_DESC &compared_desc) +{ + if (desc.MultiSampleType != D3DMULTISAMPLE_NONE) + return false; // MSAA depth buffers are not supported since they would have to be moved into a plain surface before attaching to a shader slot + + if (_disable_depth_buffer_size_restriction) + { + // Allow depth buffers with greater dimensions than the viewport (e.g. in games like Vanquish) + return desc.Width >= floor(compared_desc.Width * 0.95) && desc.Height >= ceil(compared_desc.Height * 0.95); + } + else + { + return (desc.Width >= floor(compared_desc.Width * 0.95) && desc.Width <= ceil(compared_desc.Width * 1.05)) + && (desc.Height >= floor(compared_desc.Height * 0.95) && desc.Height <= ceil(compared_desc.Height * 1.05)); + } +} + +bool reshade::d3d9::runtime_d3d9::create_depthstencil_replacement(const com_ptr &depthstencil) +{ + _depthstencil.reset(); + _depthstencil_replacement.reset(); + _depthstencil_texture.reset(); + + if (depthstencil != nullptr) + { + D3DSURFACE_DESC desc; + depthstencil->GetDesc(&desc); + + _depthstencil = depthstencil; + + if (_preserve_depth_buffer || + (!_disable_intz && + desc.Format != D3DFMT_INTZ && + desc.Format != D3DFMT_DF16 && + desc.Format != D3DFMT_DF24)) + { + D3DDISPLAYMODE displaymode; + _swapchain->GetDisplayMode(&displaymode); + D3DDEVICE_CREATION_PARAMETERS creation_params; + _device->GetCreationParameters(&creation_params); + + desc.Format = D3DFMT_UNKNOWN; + const D3DFORMAT formats[] = { D3DFMT_INTZ, D3DFMT_DF24, D3DFMT_DF16 }; + + for (const auto format : formats) + { + if (SUCCEEDED(_d3d->CheckDeviceFormat(creation_params.AdapterOrdinal, creation_params.DeviceType, displaymode.Format, D3DUSAGE_DEPTHSTENCIL, D3DRTYPE_TEXTURE, format))) + { + desc.Format = format; + break; + } + } + + if (desc.Format == D3DFMT_UNKNOWN) + { + LOG(ERROR) << "Your graphics card is missing support for at least one of the 'INTZ', 'DF24' or 'DF16' texture formats. Cannot create depth replacement texture."; + return false; + } + + const unsigned int width = _disable_depth_buffer_size_restriction ? _width : desc.Width; + const unsigned int height = _disable_depth_buffer_size_restriction ? _height : desc.Height; + + if (HRESULT hr = _device->CreateTexture(width, height, 1, D3DUSAGE_DEPTHSTENCIL, desc.Format, D3DPOOL_DEFAULT, &_depthstencil_texture, nullptr); FAILED(hr)) + { + LOG(ERROR) << "Failed to create depth replacement texture! HRESULT is '" << std::hex << hr << std::dec << "'."; + return false; + } + + _depthstencil_texture->GetSurfaceLevel(0, &_depthstencil_replacement); + + // Update auto depth stencil + com_ptr current_depthstencil; + _device->GetDepthStencilSurface(¤t_depthstencil); + + if (!_preserve_depth_buffer && current_depthstencil != nullptr && current_depthstencil == _depthstencil) + _device->SetDepthStencilSurface(_depthstencil_replacement.get()); + } + else + { + _depthstencil_replacement = _depthstencil; + + if (HRESULT hr = _depthstencil_replacement->GetContainer(IID_PPV_ARGS(&_depthstencil_texture)); FAILED(hr)) + { + LOG(ERROR) << "Failed to retrieve texture from depth surface! HRESULT is '" << std::hex << hr << std::dec << "'."; + return false; + } + } + } + + update_texture_references(texture_reference::depth_buffer); + + return true; +} + +void reshade::d3d9::runtime_d3d9::weapon_or_cockpit_fix(const com_ptr depthstencil, D3DPRIMITIVETYPE PrimitiveType, UINT StartVertex, UINT PrimitiveCount) +{ + if (_brute_force_fix && + depthstencil != _depthstencil_replacement && + _is_good_viewport && + _is_best_original_depthstencil_source && + _depth_buffer_table.size() > _adjusted_preserve_starting_index) + { + D3DVIEWPORT9 mViewport; // Holds viewport data + _device->GetViewport(&mViewport); // retrieve current viewport + + // Viewport work around (help resolving z-fighting issues) + create_fixed_viewport(mViewport); + _device->SetDepthStencilSurface(_depthstencil_replacement.get()); + + if (FAILED(_device->DrawPrimitive(PrimitiveType, StartVertex, PrimitiveCount))) + { + // Original viewport is reloaded + _device->SetViewport(&mViewport); + return; + } + + // Original viewport is reloaded + _device->SetViewport(&mViewport); + } +} + +void reshade::d3d9::runtime_d3d9::weapon_or_cockpit_fix(const com_ptr depthstencil, D3DPRIMITIVETYPE PrimitiveType, INT BaseVertexIndex, UINT MinVertexIndex, UINT NumVertices, UINT StartIndex, UINT PrimitiveCount) +{ + if (_brute_force_fix && + depthstencil != _depthstencil_replacement && + _is_good_viewport && + _is_best_original_depthstencil_source && + _depth_buffer_table.size() > _adjusted_preserve_starting_index) + { + D3DVIEWPORT9 mViewport; // Holds viewport data + _device->GetViewport(&mViewport); // retrieve current viewport + + // Viewport work around (help resolving z-fighting issues) + create_fixed_viewport(mViewport); + _device->SetDepthStencilSurface(_depthstencil_replacement.get()); + + if (FAILED(_device->DrawIndexedPrimitive(PrimitiveType, BaseVertexIndex, MinVertexIndex, NumVertices, StartIndex, PrimitiveCount))) + { + // Original viewport is reloaded + _device->SetViewport(&mViewport); + return; + } + + // Original viewport is reloaded + _device->SetViewport(&mViewport); + } +} + +void reshade::d3d9::runtime_d3d9::create_fixed_viewport(const D3DVIEWPORT9 mViewport) +{ + D3DVIEWPORT9 mNewViewport; // Holds new viewport data + float g_fViewportBias = 0.5f; + + // Copy old Viewport to new + mNewViewport = mViewport; + + // Change by the bias + mNewViewport.MinZ -= g_fViewportBias; + mNewViewport.MaxZ -= g_fViewportBias; + + // The new viewport is loaded + _device->SetViewport(&mNewViewport); +} diff --git a/msvc/source/d3d9/runtime_d3d9.hpp b/msvc/source/d3d9/runtime_d3d9.hpp new file mode 100644 index 0000000..2fc5d22 --- /dev/null +++ b/msvc/source/d3d9/runtime_d3d9.hpp @@ -0,0 +1,127 @@ +/** + * Copyright (C) 2014 Patrick Mours. All rights reserved. + * License: https://github.com/crosire/reshade#license + */ + +#pragma once + +#include "runtime.hpp" +#include "effect_expression.hpp" +#include "state_block.hpp" + +namespace reshade { enum class texture_reference; } +namespace reshadefx { struct sampler_info; } + +namespace reshade::d3d9 +{ + class runtime_d3d9 : public runtime + { + public: + runtime_d3d9(IDirect3DDevice9 *device, IDirect3DSwapChain9 *swapchain); + ~runtime_d3d9(); + + bool on_init(const D3DPRESENT_PARAMETERS &pp); + void on_reset(); + void on_present(); + + void on_draw_primitive(D3DPRIMITIVETYPE PrimitiveType, UINT StartVertex, UINT PrimitiveCount); + void on_draw_indexed_primitive(D3DPRIMITIVETYPE PrimitiveType, INT BaseVertexIndex, UINT MinVertexIndex, UINT NumVertices, UINT StartIndex, UINT PrimitiveCount); + void on_draw_primitive_up(D3DPRIMITIVETYPE PrimitiveType, UINT PrimitiveCount, const void *pVertexStreamZeroData, UINT VertexStreamZeroStride); + void on_draw_indexed_primitive_up(D3DPRIMITIVETYPE PrimitiveType, UINT MinVertexIndex, UINT NumVertices, UINT PrimitiveCount, const void *pIndexData, D3DFORMAT IndexDataFormat, const void *pVertexStreamZeroData, UINT VertexStreamZeroStride); + void on_draw_call(com_ptr depthstencil, D3DPRIMITIVETYPE type, unsigned int count); + + void on_set_depthstencil_surface(IDirect3DSurface9 *&depthstencil); + void on_get_depthstencil_surface(IDirect3DSurface9 *&depthstencil); + void on_clear_depthstencil_surface(IDirect3DSurface9 *depthstencil); + + void capture_screenshot(uint8_t *buffer) const override; + + private: + struct depth_source_info + { + com_ptr depthstencil; + UINT width, height; + UINT drawcall_count, vertices_count; + }; + + bool init_backbuffer_texture(); + bool init_default_depth_stencil(); + bool init_fullscreen_triangle_resources(); + + bool init_texture(texture &info) override; + void upload_texture(texture &texture, const uint8_t *pixels) override; + bool update_texture_reference(texture &texture); + void update_texture_references(texture_reference type); + + bool compile_effect(effect_data &effect) override; + + bool add_sampler(const reshadefx::sampler_info &info, struct d3d9_technique_data &technique_init); + bool init_technique(technique &info, const struct d3d9_technique_data &technique_init, const std::unordered_map> &entry_points); + + void render_technique(technique &technique) override; + + bool check_depthstencil_size(const D3DSURFACE_DESC &desc); + bool check_depthstencil_size(const D3DSURFACE_DESC &desc, const D3DSURFACE_DESC &compared_desc); + +#if RESHADE_GUI + bool init_imgui_resources(); + void render_imgui_draw_data(ImDrawData *data) override; + void draw_debug_menu(); +#endif + + void detect_depth_source(); + bool create_depthstencil_replacement(const com_ptr &depthstencil); + + void weapon_or_cockpit_fix(const com_ptr depthstencil, D3DPRIMITIVETYPE PrimitiveType, UINT StartVertex, UINT PrimitiveCount); + void weapon_or_cockpit_fix(const com_ptr depthstencil, D3DPRIMITIVETYPE PrimitiveType, INT BaseVertexIndex, UINT MinVertexIndex, UINT NumVertices, UINT StartIndex, UINT PrimitiveCount); + void create_fixed_viewport(const D3DVIEWPORT9 mViewport); + + com_ptr _d3d; + com_ptr _device; + com_ptr _swapchain; + + com_ptr _backbuffer; + com_ptr _backbuffer_resolved; + com_ptr _backbuffer_texture; + com_ptr _backbuffer_texture_surface; + com_ptr _depthstencil_texture; + + unsigned int _num_samplers; + unsigned int _num_simultaneous_rendertargets; + unsigned int _behavior_flags; + bool _disable_intz = false; + bool _preserve_depth_buffer = false; + bool _auto_preserve = true; + size_t _preserve_starting_index = 0; + size_t _adjusted_preserve_starting_index = 0; + bool _source_engine_fix = false; + bool _brute_force_fix = false; + bool _focus_on_best_original_depthstencil_source = false; + bool _disable_depth_buffer_size_restriction = false; + bool _init_depthbuffer_detection = true; + bool _is_good_viewport = true; + bool _is_best_original_depthstencil_source = true; + unsigned int _db_vertices = 0; + unsigned int _db_drawcalls = 0; + unsigned int _current_db_vertices = 0; + unsigned int _current_db_drawcalls = 0; + bool _is_multisampling_enabled = false; + D3DFORMAT _backbuffer_format = D3DFMT_UNKNOWN; + state_block _app_state; + com_ptr _depthstencil; + com_ptr _depthstencil_replacement; + com_ptr _default_depthstencil; + std::unordered_map, depth_source_info> _depth_source_table; + std::vector _depth_buffer_table; + + com_ptr _effect_triangle_buffer; + com_ptr _effect_triangle_layout; + + com_ptr _imgui_state; + com_ptr _imgui_vertex_buffer; + com_ptr _imgui_index_buffer; + int _imgui_vertex_buffer_size = 0, _imgui_index_buffer_size = 0; + + HMODULE _d3d_compiler = nullptr; + }; +} diff --git a/msvc/source/d3d9/state_block.cpp b/msvc/source/d3d9/state_block.cpp new file mode 100644 index 0000000..2ef9466 --- /dev/null +++ b/msvc/source/d3d9/state_block.cpp @@ -0,0 +1,61 @@ +/** + * Copyright (C) 2014 Patrick Mours. All rights reserved. + * License: https://github.com/crosire/reshade#license + */ + +#include "state_block.hpp" +#include + +reshade::d3d9::state_block::state_block(IDirect3DDevice9 *device) +{ + ZeroMemory(this, sizeof(*this)); + + _device = device; + + D3DCAPS9 caps; + device->GetDeviceCaps(&caps); + _num_simultaneous_rendertargets = std::min(caps.NumSimultaneousRTs, DWORD(8)); +} +reshade::d3d9::state_block::~state_block() +{ + release_all_device_objects(); +} + +void reshade::d3d9::state_block::capture() +{ + _state_block->Capture(); + + _device->GetViewport(&_viewport); + + for (DWORD target = 0; target < _num_simultaneous_rendertargets; target++) + _device->GetRenderTarget(target, &_render_targets[target]); + _device->GetDepthStencilSurface(&_depth_stencil); +} +void reshade::d3d9::state_block::apply_and_release() +{ + _state_block->Apply(); + + for (DWORD target = 0; target < _num_simultaneous_rendertargets; target++) + _device->SetRenderTarget(target, _render_targets[target].get()); + _device->SetDepthStencilSurface(_depth_stencil.get()); + + // Set viewport after render targets have been set, since 'SetRenderTarget' causes the viewport to be set to the full size of the render target + _device->SetViewport(&_viewport); + + release_all_device_objects(); +} + +bool reshade::d3d9::state_block::init_state_block() +{ + return SUCCEEDED(_device->CreateStateBlock(D3DSBT_ALL, &_state_block)); +} +void reshade::d3d9::state_block::release_state_block() +{ + _state_block.reset(); +} +void reshade::d3d9::state_block::release_all_device_objects() +{ + _depth_stencil.reset(); + for (auto &render_target : _render_targets) + render_target.reset(); +} diff --git a/msvc/source/d3d9/state_block.hpp b/msvc/source/d3d9/state_block.hpp new file mode 100644 index 0000000..49bdb9c --- /dev/null +++ b/msvc/source/d3d9/state_block.hpp @@ -0,0 +1,35 @@ +/** + * Copyright (C) 2014 Patrick Mours. All rights reserved. + * License: https://github.com/crosire/reshade#license + */ + +#pragma once + +#include "com_ptr.hpp" +#include + +namespace reshade::d3d9 +{ + class state_block + { + public: + explicit state_block(IDirect3DDevice9 *device); + ~state_block(); + + bool init_state_block(); + void release_state_block(); + + void capture(); + void apply_and_release(); + + private: + void release_all_device_objects(); + + com_ptr _device; + com_ptr _state_block; + UINT _num_simultaneous_rendertargets; + D3DVIEWPORT9 _viewport; + com_ptr _depth_stencil; + com_ptr _render_targets[8]; + }; +} diff --git a/msvc/source/dllmain.cpp b/msvc/source/dllmain.cpp new file mode 100644 index 0000000..e8d7a11 --- /dev/null +++ b/msvc/source/dllmain.cpp @@ -0,0 +1,406 @@ +/** + * Copyright (C) 2014 Patrick Mours. All rights reserved. + * License: https://github.com/crosire/reshade#license + */ + +#include "log.hpp" +#include "hook_manager.hpp" +#include "version.h" +#include + +HMODULE g_module_handle = nullptr; + +std::filesystem::path g_reshade_dll_path; +std::filesystem::path g_target_executable_path; + +extern std::filesystem::path get_system_path() +{ + static std::filesystem::path system_path; + if (!system_path.empty()) + return system_path; + TCHAR buf[MAX_PATH] = {}; + GetSystemDirectory(buf, ARRAYSIZE(buf)); + return system_path = buf; +} +static inline std::filesystem::path get_module_path(HMODULE module) +{ + TCHAR buf[MAX_PATH] = {}; + GetModuleFileName(module, buf, ARRAYSIZE(buf)); + return buf; +} + +#if defined(RESHADE_TEST_APPLICATION) + +#include "d3d9/runtime_d3d9.hpp" +#include "d3d11/runtime_d3d11.hpp" +#include "d3d12/runtime_d3d12.hpp" +#include "opengl/runtime_opengl.hpp" + +#define HCHECK(exp) assert(SUCCEEDED(exp)) + +int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE, LPSTR lpCmdLine, int nCmdShow) +{ + using namespace reshade; + + g_module_handle = hInstance; + g_reshade_dll_path = get_module_path(nullptr); + g_target_executable_path = get_module_path(nullptr); + + log::open(std::filesystem::path(g_reshade_dll_path).replace_extension(".log")); + + hooks::register_module("user32.dll"); + + static UINT s_resize_w = 0, s_resize_h = 0; + + // Register window class + WNDCLASS wc = { sizeof(wc) }; + wc.hInstance = hInstance; + wc.style = CS_HREDRAW | CS_VREDRAW; + wc.lpszClassName = TEXT("Test"); + wc.lpfnWndProc = + [](HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam) -> LRESULT { + switch (Msg) + { + case WM_DESTROY: + PostQuitMessage(EXIT_SUCCESS); + break; + case WM_SIZE: + s_resize_w = LOWORD(lParam); + s_resize_h = HIWORD(lParam); + break; + } + + return DefWindowProc(hWnd, Msg, wParam, lParam); + }; + + RegisterClass(&wc); + + // Create and show window instance + const HWND window_handle = CreateWindow( + wc.lpszClassName, TEXT("ReShade ") TEXT(VERSION_STRING_FILE) TEXT(" by crosire"), WS_OVERLAPPEDWINDOW, + 0, 0, 1024, 800, nullptr, nullptr, hInstance, nullptr); + + if (window_handle == nullptr) + return 0; + + ShowWindow(window_handle, nCmdShow); + + // Avoid resize caused by 'ShowWindow' call + s_resize_w = 0; + s_resize_h = 0; + + MSG msg = {}; + + #pragma region D3D9 Implementation + if (strstr(lpCmdLine, "-d3d9")) + { + hooks::register_module("d3d9.dll"); + const auto d3d9_module = LoadLibrary(TEXT("d3d9.dll")); + + D3DPRESENT_PARAMETERS pp = {}; + pp.SwapEffect = D3DSWAPEFFECT_DISCARD; + pp.hDeviceWindow = window_handle; + pp.Windowed = true; + pp.PresentationInterval = D3DPRESENT_INTERVAL_ONE; + + // Initialize Direct3D 9 + com_ptr d3d(Direct3DCreate9(D3D_SDK_VERSION), true); + com_ptr device; + + HCHECK(d3d->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, window_handle, D3DCREATE_HARDWARE_VERTEXPROCESSING, &pp, &device)); + + while (msg.message != WM_QUIT) + { + while (msg.message != WM_QUIT && + PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE)) + DispatchMessage(&msg); + + if (s_resize_w != 0) + { + pp.BackBufferWidth = s_resize_w; + pp.BackBufferHeight = s_resize_h; + + HCHECK(device->Reset(&pp)); + + s_resize_w = s_resize_h = 0; + } + + HCHECK(device->Clear(0, nullptr, D3DCLEAR_TARGET, 0xFF7F7F7F, 0, 0)); + HCHECK(device->Present(nullptr, nullptr, nullptr, nullptr)); + } + + reshade::hooks::uninstall(); + + FreeLibrary(d3d9_module); + + return static_cast(msg.wParam); + } + #pragma endregion + + #pragma region D3D11 Implementation + if (strstr(lpCmdLine, "-d3d11")) + { + hooks::register_module("dxgi.dll"); + hooks::register_module("d3d11.dll"); + const auto d3d11_module = LoadLibrary(TEXT("d3d11.dll")); + + // Initialize Direct3D 11 + com_ptr device; + com_ptr immediate_context; + com_ptr swapchain; + + { DXGI_SWAP_CHAIN_DESC desc = {}; + desc.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; + desc.SampleDesc = { 1, 0 }; + desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; + desc.BufferCount = 1; + desc.OutputWindow = window_handle; + desc.Windowed = true; + desc.SwapEffect = DXGI_SWAP_EFFECT_DISCARD; + + HCHECK(D3D11CreateDeviceAndSwapChain(nullptr, D3D_DRIVER_TYPE_HARDWARE, nullptr, D3D11_CREATE_DEVICE_DEBUG, nullptr, 0, D3D11_SDK_VERSION, &desc, &swapchain, &device, nullptr, &immediate_context)); + } + + com_ptr backbuffer; + HCHECK(swapchain->GetBuffer(0, IID_PPV_ARGS(&backbuffer))); + com_ptr target; + HCHECK(device->CreateRenderTargetView(backbuffer.get(), nullptr, &target)); + + while (msg.message != WM_QUIT) + { + while (msg.message != WM_QUIT && + PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE)) + DispatchMessage(&msg); + + if (s_resize_w != 0) + { + target.reset(); + backbuffer.reset(); + + HCHECK(swapchain->ResizeBuffers(1, s_resize_w, s_resize_h, DXGI_FORMAT_UNKNOWN, 0)); + + HCHECK(swapchain->GetBuffer(0, IID_PPV_ARGS(&backbuffer))); + HCHECK(device->CreateRenderTargetView(backbuffer.get(), nullptr, &target)); + + s_resize_w = s_resize_h = 0; + } + + const float color[4] = { 0.5f, 0.5f, 0.5f, 1.0f }; + immediate_context->ClearRenderTargetView(target.get(), color); + + HCHECK(swapchain->Present(1, 0)); + } + + reshade::hooks::uninstall(); + + FreeLibrary(d3d11_module); + + return static_cast(msg.wParam); + } + #pragma endregion + + #pragma region D3D12 Implementation + if (strstr(lpCmdLine, "-d3d12")) + { + hooks::register_module("dxgi.dll"); + hooks::register_module("d3d12.dll"); + const auto d3d12_module = LoadLibrary(TEXT("d3d12.dll")); + + // Enable D3D debug layer + { com_ptr debug_iface; + HCHECK(D3D12GetDebugInterface(IID_PPV_ARGS(&debug_iface))); + debug_iface->EnableDebugLayer(); + } + + // Initialize Direct3D 12 + com_ptr device; + com_ptr command_queue; + com_ptr swapchain; + com_ptr backbuffers[3]; + com_ptr rtv_heap; + com_ptr cmd_alloc; + com_ptr cmd_lists[3]; + + HCHECK(D3D12CreateDevice(nullptr, D3D_FEATURE_LEVEL_12_0, IID_PPV_ARGS(&device))); + + { D3D12_COMMAND_QUEUE_DESC desc = { D3D12_COMMAND_LIST_TYPE_DIRECT }; + HCHECK(device->CreateCommandQueue(&desc, IID_PPV_ARGS(&command_queue))); + } + + { DXGI_SWAP_CHAIN_DESC1 desc = {}; + desc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; + desc.SampleDesc = { 1, 0 }; + desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; + desc.BufferCount = ARRAYSIZE(backbuffers); + desc.Scaling = DXGI_SCALING_STRETCH; + desc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD; + + com_ptr dxgi_factory; + com_ptr dxgi_swapchain; + HCHECK(CreateDXGIFactory2(DXGI_CREATE_FACTORY_DEBUG, IID_PPV_ARGS(&dxgi_factory))); + HCHECK(dxgi_factory->CreateSwapChainForHwnd(command_queue.get(), window_handle, &desc, nullptr, nullptr, &dxgi_swapchain)); + HCHECK(dxgi_swapchain->QueryInterface(&swapchain)); + } + + { D3D12_DESCRIPTOR_HEAP_DESC desc = { D3D12_DESCRIPTOR_HEAP_TYPE_RTV }; + desc.NumDescriptors = ARRAYSIZE(backbuffers); + HCHECK(device->CreateDescriptorHeap(&desc, IID_PPV_ARGS(&rtv_heap))); + } + + const UINT rtv_handle_size = device->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_RTV); + D3D12_CPU_DESCRIPTOR_HANDLE rtv_handle = rtv_heap->GetCPUDescriptorHandleForHeapStart(); + + HCHECK(device->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(&cmd_alloc))); + + for (UINT i = 0; i < ARRAYSIZE(backbuffers); ++i, rtv_handle.ptr += rtv_handle_size) + { + HCHECK(swapchain->GetBuffer(i, IID_PPV_ARGS(&backbuffers[i]))); + + device->CreateRenderTargetView(backbuffers[i].get(), nullptr, rtv_handle); + + HCHECK(device->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, cmd_alloc.get(), nullptr, IID_PPV_ARGS(&cmd_lists[i]))); + + D3D12_RESOURCE_BARRIER barrier = { D3D12_RESOURCE_BARRIER_TYPE_TRANSITION }; + barrier.Transition.pResource = backbuffers[i].get(); + barrier.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES; + barrier.Transition.StateBefore = D3D12_RESOURCE_STATE_PRESENT; + barrier.Transition.StateAfter = D3D12_RESOURCE_STATE_RENDER_TARGET; + cmd_lists[i]->ResourceBarrier(1, &barrier); + + const float color[4] = { 0.5f, 0.5f, 0.5f, 1.0f }; + cmd_lists[i]->ClearRenderTargetView(rtv_handle, color, 0, nullptr); + + barrier.Transition.StateBefore = D3D12_RESOURCE_STATE_RENDER_TARGET; + barrier.Transition.StateAfter = D3D12_RESOURCE_STATE_PRESENT; + cmd_lists[i]->ResourceBarrier(1, &barrier); + + HCHECK(cmd_lists[i]->Close()); + } + + while (msg.message != WM_QUIT) + { + while (msg.message != WM_QUIT && + PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE)) + DispatchMessage(&msg); + + const UINT swap_index = swapchain->GetCurrentBackBufferIndex(); + + ID3D12CommandList *const cmd_list = cmd_lists[swap_index].get(); + command_queue->ExecuteCommandLists(1, &cmd_list); + + // Synchronization is handled in "runtime_d3d12::on_present" + swapchain->Present(1, 0); + } + + reshade::hooks::uninstall(); + + FreeLibrary(d3d12_module); + + return static_cast(msg.wParam); + } + #pragma endregion + + #pragma region OpenGL Implementation + if (strstr(lpCmdLine, "-opengl")) + { + hooks::register_module("opengl32.dll"); + const auto opengl_module = LoadLibrary(TEXT("opengl32.dll")); + + // Initialize OpenGL + const HDC hdc = GetDC(window_handle); + + PIXELFORMATDESCRIPTOR pfd = { sizeof(pfd) }; + pfd.nVersion = 1; + pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER; + pfd.iPixelType = PFD_TYPE_RGBA; + pfd.cColorBits = 32; + + const int pf = ChoosePixelFormat(hdc, &pfd); + SetPixelFormat(hdc, pf, &pfd); + + const HGLRC hglrc = wglCreateContext(hdc); + if (hglrc == nullptr) + return 0; + + wglMakeCurrent(hdc, hglrc); + + while (msg.message != WM_QUIT) + { + while (msg.message != WM_QUIT && + PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE)) + DispatchMessage(&msg); + + if (s_resize_w != 0) + { + glViewport(0, 0, s_resize_w, s_resize_h); + + s_resize_w = s_resize_h = 0; + } + + glClearColor(0.5f, 0.5f, 0.5f, 1.0f); + glClear(GL_COLOR_BUFFER_BIT); + + SwapBuffers(hdc); + } + + wglMakeCurrent(nullptr, nullptr); + wglDeleteContext(hglrc); + + reshade::hooks::uninstall(); + + FreeLibrary(opengl_module); + + return static_cast(msg.wParam); + } + #pragma endregion + + return EXIT_FAILURE; +} + +#else + +BOOL APIENTRY DllMain(HMODULE hModule, DWORD fdwReason, LPVOID) +{ + using namespace reshade; + + switch (fdwReason) + { + case DLL_PROCESS_ATTACH: + g_module_handle = hModule; + g_reshade_dll_path = get_module_path(hModule); + g_target_executable_path = get_module_path(nullptr); + + log::open(std::filesystem::path(g_reshade_dll_path).replace_extension(".log")); + +#ifdef WIN64 + LOG(INFO) << "Initializing crosire's ReShade version '" VERSION_STRING_FILE "' (64-bit) built on '" VERSION_DATE " " VERSION_TIME "' loaded from " << g_reshade_dll_path << " to " << g_target_executable_path << " ..."; +#else + LOG(INFO) << "Initializing crosire's ReShade version '" VERSION_STRING_FILE "' (32-bit) built on '" VERSION_DATE " " VERSION_TIME "' loaded from " << g_reshade_dll_path << " to " << g_target_executable_path << " ..."; +#endif + + hooks::register_module("user32.dll"); + hooks::register_module("ws2_32.dll"); + + hooks::register_module(get_system_path() / "d3d9.dll"); + hooks::register_module(get_system_path() / "d3d10.dll"); + hooks::register_module(get_system_path() / "d3d10_1.dll"); + hooks::register_module(get_system_path() / "d3d11.dll"); + hooks::register_module(get_system_path() / "d3d12.dll"); + hooks::register_module(get_system_path() / "dxgi.dll"); + hooks::register_module(get_system_path() / "opengl32.dll"); + + LOG(INFO) << "Initialized."; + break; + case DLL_PROCESS_DETACH: + LOG(INFO) << "Exiting ..."; + + hooks::uninstall(); + + LOG(INFO) << "Exited."; + break; + } + + return TRUE; +} + +#endif diff --git a/msvc/source/dxgi/dxgi.cpp b/msvc/source/dxgi/dxgi.cpp new file mode 100644 index 0000000..a8f51bf --- /dev/null +++ b/msvc/source/dxgi/dxgi.cpp @@ -0,0 +1,341 @@ +/** + * Copyright (C) 2014 Patrick Mours. All rights reserved. + * License: https://github.com/crosire/reshade#license + */ + +#include "log.hpp" +#include "hook_manager.hpp" +#include "dxgi_device.hpp" +#include "dxgi_swapchain.hpp" +#include "d3d10/d3d10_device.hpp" +#include "d3d10/runtime_d3d10.hpp" +#include "d3d11/d3d11_device.hpp" +#include "d3d11/runtime_d3d11.hpp" +#include "d3d12/d3d12_device.hpp" +#include "d3d12/d3d12_command_queue.hpp" +#include "d3d12/runtime_d3d12.hpp" + +static void query_device(IUnknown *&device, com_ptr &device_d3d10, com_ptr &device_d3d11, com_ptr &command_queue_d3d12) +{ + if (SUCCEEDED(device->QueryInterface(&device_d3d10))) + device = device_d3d10->_orig; + else if (SUCCEEDED(device->QueryInterface(&device_d3d11))) + device = device_d3d11->_orig; // Set device pointer back to original object so that the swapchain creation functions work as expected + else if (SUCCEEDED(device->QueryInterface(&command_queue_d3d12))) + device = command_queue_d3d12->_orig; +} + +static void dump_swapchain_desc(const DXGI_SWAP_CHAIN_DESC &desc) +{ + LOG(INFO) << "> Dumping swap chain description:"; + LOG(INFO) << " +-----------------------------------------+-----------------------------------------+"; + LOG(INFO) << " | Parameter | Value |"; + LOG(INFO) << " +-----------------------------------------+-----------------------------------------+"; + LOG(INFO) << " | Width | " << std::setw(39) << desc.BufferDesc.Width << " |"; + LOG(INFO) << " | Height | " << std::setw(39) << desc.BufferDesc.Height << " |"; + LOG(INFO) << " | RefreshRate | " << std::setw(19) << desc.BufferDesc.RefreshRate.Numerator << ' ' << std::setw(19) << desc.BufferDesc.RefreshRate.Denominator << " |"; + LOG(INFO) << " | Format | " << std::setw(39) << desc.BufferDesc.Format << " |"; + LOG(INFO) << " | ScanlineOrdering | " << std::setw(39) << desc.BufferDesc.ScanlineOrdering << " |"; + LOG(INFO) << " | Scaling | " << std::setw(39) << desc.BufferDesc.Scaling << " |"; + LOG(INFO) << " | SampleCount | " << std::setw(39) << desc.SampleDesc.Count << " |"; + LOG(INFO) << " | SampleQuality | " << std::setw(39) << desc.SampleDesc.Quality << " |"; + LOG(INFO) << " | BufferUsage | " << std::setw(39) << desc.BufferUsage << " |"; + LOG(INFO) << " | BufferCount | " << std::setw(39) << desc.BufferCount << " |"; + LOG(INFO) << " | OutputWindow | " << std::setw(39) << desc.OutputWindow << " |"; + LOG(INFO) << " | Windowed | " << std::setw(39) << (desc.Windowed != FALSE ? "TRUE" : "FALSE") << " |"; + LOG(INFO) << " | SwapEffect | " << std::setw(39) << desc.SwapEffect << " |"; + LOG(INFO) << " | Flags | " << std::setw(39) << std::hex << desc.Flags << std::dec << " |"; + LOG(INFO) << " +-----------------------------------------+-----------------------------------------+"; + + if (desc.SampleDesc.Count > 1) + LOG(WARN) << "> Multisampling is enabled. This is not compatible with depth buffer access, which was therefore disabled."; +} +static void dump_swapchain_desc(const DXGI_SWAP_CHAIN_DESC1 &desc) +{ + LOG(INFO) << "> Dumping swap chain description:"; + LOG(INFO) << " +-----------------------------------------+-----------------------------------------+"; + LOG(INFO) << " | Parameter | Value |"; + LOG(INFO) << " +-----------------------------------------+-----------------------------------------+"; + LOG(INFO) << " | Width | " << std::setw(39) << desc.Width << " |"; + LOG(INFO) << " | Height | " << std::setw(39) << desc.Height << " |"; + LOG(INFO) << " | Format | " << std::setw(39) << desc.Format << " |"; + LOG(INFO) << " | Stereo | " << std::setw(39) << (desc.Stereo != FALSE ? "TRUE" : "FALSE") << " |"; + LOG(INFO) << " | SampleCount | " << std::setw(39) << desc.SampleDesc.Count << " |"; + LOG(INFO) << " | SampleQuality | " << std::setw(39) << desc.SampleDesc.Quality << " |"; + LOG(INFO) << " | BufferUsage | " << std::setw(39) << desc.BufferUsage << " |"; + LOG(INFO) << " | BufferCount | " << std::setw(39) << desc.BufferCount << " |"; + LOG(INFO) << " | Scaling | " << std::setw(39) << desc.Scaling << " |"; + LOG(INFO) << " | SwapEffect | " << std::setw(39) << desc.SwapEffect << " |"; + LOG(INFO) << " | AlphaMode | " << std::setw(39) << desc.AlphaMode << " |"; + LOG(INFO) << " | Flags | " << std::setw(39) << std::hex << desc.Flags << std::dec << " |"; + LOG(INFO) << " +-----------------------------------------+-----------------------------------------+"; + + if (desc.SampleDesc.Count > 1) + LOG(WARN) << "> Multisampling is enabled. This is not compatible with depth buffer access, which was therefore disabled."; +} + +template +static void init_reshade_runtime_d3d(T *&swapchain, com_ptr &device_d3d10, com_ptr &device_d3d11, com_ptr &command_queue_d3d12) +{ + DXGI_SWAP_CHAIN_DESC desc; + if (FAILED(swapchain->GetDesc(&desc))) + return; // This doesn't happen, but let's be safe + + if ((desc.BufferUsage & DXGI_USAGE_RENDER_TARGET_OUTPUT) == 0) + { + LOG(WARN) << "> Skipping swap chain due to missing 'DXGI_USAGE_RENDER_TARGET_OUTPUT' flag."; + } + else if (device_d3d10 != nullptr) + { + const auto runtime = std::make_shared(device_d3d10->_orig, swapchain); + + if (!runtime->on_init(desc)) + LOG(ERROR) << "Failed to initialize Direct3D 10 runtime environment on runtime " << runtime.get() << '.'; + + device_d3d10->_runtimes.push_back(runtime); + + swapchain = new DXGISwapChain(device_d3d10.get(), swapchain, runtime); + } + else if (device_d3d11 != nullptr) + { + const auto runtime = std::make_shared(device_d3d11->_orig, swapchain); + + if (!runtime->on_init(desc)) + LOG(ERROR) << "Failed to initialize Direct3D 11 runtime environment on runtime " << runtime.get() << '.'; + + device_d3d11->_runtimes.push_back(runtime); + + swapchain = new DXGISwapChain(device_d3d11.get(), swapchain, runtime); // Overwrite returned swapchain pointer with hooked object + } + else if (command_queue_d3d12 != nullptr) + { + if (com_ptr swapchain3; SUCCEEDED(swapchain->QueryInterface(&swapchain3))) + { + const auto runtime = std::make_shared(command_queue_d3d12->_device->_orig, command_queue_d3d12->_orig, swapchain3.get()); + + if (!runtime->on_init(desc)) + LOG(ERROR) << "Failed to initialize Direct3D 12 runtime environment on runtime " << runtime.get() << '.'; + + swapchain = new DXGISwapChain(command_queue_d3d12->_device, swapchain3.get(), runtime); + } + else + { + LOG(WARN) << "> Skipping swap chain because it is missing support for the IDXGISwapChain3 interface."; + } + } + else + { + LOG(WARN) << "> Skipping swap chain because it was created without a (hooked) Direct3D device."; + } + +#if RESHADE_VERBOSE_LOG + LOG(DEBUG) << "Returning IDXGISwapChain object " << swapchain << '.'; +#endif +} + +reshade::log::message &operator<<(reshade::log::message &m, REFIID riid) +{ + OLECHAR riid_string[40]; + StringFromGUID2(riid, riid_string, ARRAYSIZE(riid_string)); + return m << riid_string; +} + +HRESULT STDMETHODCALLTYPE IDXGIFactory_CreateSwapChain(IDXGIFactory *pFactory, IUnknown *pDevice, DXGI_SWAP_CHAIN_DESC *pDesc, IDXGISwapChain **ppSwapChain) +{ + LOG(INFO) << "Redirecting IDXGIFactory::CreateSwapChain" << '(' << pFactory << ", " << pDevice << ", " << pDesc << ", " << ppSwapChain << ')' << " ..."; + + if (pDevice == nullptr || pDesc == nullptr || ppSwapChain == nullptr) + return DXGI_ERROR_INVALID_CALL; + + com_ptr device_d3d10; + com_ptr device_d3d11; + com_ptr command_queue_d3d12; + query_device(pDevice, + device_d3d10, + device_d3d11, + command_queue_d3d12); + dump_swapchain_desc(*pDesc); + + const HRESULT hr = reshade::hooks::call(IDXGIFactory_CreateSwapChain, vtable_from_instance(pFactory) + 10)(pFactory, pDevice, pDesc, ppSwapChain); + + if (FAILED(hr)) + { + LOG(WARN) << "> IDXGIFactory::CreateSwapChain failed with error code " << std::hex << hr << std::dec << '!'; + return hr; + } + + init_reshade_runtime_d3d(*ppSwapChain, + device_d3d10, + device_d3d11, + command_queue_d3d12); + + return hr; +} + +HRESULT STDMETHODCALLTYPE IDXGIFactory2_CreateSwapChainForHwnd(IDXGIFactory2 *pFactory, IUnknown *pDevice, HWND hWnd, const DXGI_SWAP_CHAIN_DESC1 *pDesc, const DXGI_SWAP_CHAIN_FULLSCREEN_DESC *pFullscreenDesc, IDXGIOutput *pRestrictToOutput, IDXGISwapChain1 **ppSwapChain) +{ + LOG(INFO) << "Redirecting IDXGIFactory2::CreateSwapChainForHwnd" << '(' << pFactory << ", " << pDevice << ", " << hWnd << ", " << pDesc << ", " << pFullscreenDesc << ", " << pRestrictToOutput << ", " << ppSwapChain << ')' << " ..."; + + if (pDevice == nullptr || pDesc == nullptr || ppSwapChain == nullptr) + return DXGI_ERROR_INVALID_CALL; + + com_ptr device_d3d10; + com_ptr device_d3d11; + com_ptr command_queue_d3d12; + query_device(pDevice, + device_d3d10, + device_d3d11, + command_queue_d3d12); + dump_swapchain_desc(*pDesc); + + const HRESULT hr = reshade::hooks::call(IDXGIFactory2_CreateSwapChainForHwnd, vtable_from_instance(pFactory) + 15)(pFactory, pDevice, hWnd, pDesc, pFullscreenDesc, pRestrictToOutput, ppSwapChain); + + if (FAILED(hr)) + { + LOG(WARN) << "> IDXGIFactory2::CreateSwapChainForHwnd failed with error code " << std::hex << hr << std::dec << '!'; + return hr; + } + + init_reshade_runtime_d3d(*ppSwapChain, + device_d3d10, + device_d3d11, + command_queue_d3d12); + + return hr; +} +HRESULT STDMETHODCALLTYPE IDXGIFactory2_CreateSwapChainForCoreWindow(IDXGIFactory2 *pFactory, IUnknown *pDevice, IUnknown *pWindow, const DXGI_SWAP_CHAIN_DESC1 *pDesc, IDXGIOutput *pRestrictToOutput, IDXGISwapChain1 **ppSwapChain) +{ + LOG(INFO) << "Redirecting IDXGIFactory2::CreateSwapChainForCoreWindow" << '(' << pFactory << ", " << pDevice << ", " << pWindow << ", " << pDesc << ", " << pRestrictToOutput << ", " << ppSwapChain << ')' << " ..."; + + if (pDevice == nullptr || pDesc == nullptr || ppSwapChain == nullptr) + return DXGI_ERROR_INVALID_CALL; + + com_ptr device_d3d10; + com_ptr device_d3d11; + com_ptr command_queue_d3d12; + query_device(pDevice, + device_d3d10, + device_d3d11, + command_queue_d3d12); + dump_swapchain_desc(*pDesc); + + const HRESULT hr = reshade::hooks::call(IDXGIFactory2_CreateSwapChainForCoreWindow, vtable_from_instance(pFactory + 16))(pFactory, pDevice, pWindow, pDesc, pRestrictToOutput, ppSwapChain); + + if (FAILED(hr)) + { + LOG(WARN) << "> IDXGIFactory2::CreateSwapChainForCoreWindow failed with error code " << std::hex << hr << std::dec << '!'; + return hr; + } + + init_reshade_runtime_d3d(*ppSwapChain, + device_d3d10, + device_d3d11, + command_queue_d3d12); + + return hr; +} +HRESULT STDMETHODCALLTYPE IDXGIFactory2_CreateSwapChainForComposition(IDXGIFactory2 *pFactory, IUnknown *pDevice, const DXGI_SWAP_CHAIN_DESC1 *pDesc, IDXGIOutput *pRestrictToOutput, IDXGISwapChain1 **ppSwapChain) +{ + LOG(INFO) << "Redirecting IDXGIFactory2::CreateSwapChainForComposition" << '(' << pFactory << ", " << pDevice << ", " << pDesc << ", " << pRestrictToOutput << ", " << ppSwapChain << ')' << " ..."; + + if (pDevice == nullptr || pDesc == nullptr || ppSwapChain == nullptr) + return DXGI_ERROR_INVALID_CALL; + + com_ptr device_d3d10; + com_ptr device_d3d11; + com_ptr command_queue_d3d12; + query_device(pDevice, + device_d3d10, + device_d3d11, + command_queue_d3d12); + dump_swapchain_desc(*pDesc); + + const HRESULT hr = reshade::hooks::call(IDXGIFactory2_CreateSwapChainForComposition, vtable_from_instance(pFactory) + 24)(pFactory, pDevice, pDesc, pRestrictToOutput, ppSwapChain); + + if (FAILED(hr)) + { + LOG(WARN) << "> IDXGIFactory2::CreateSwapChainForComposition failed with error code " << std::hex << hr << std::dec << '!'; + return hr; + } + + init_reshade_runtime_d3d(*ppSwapChain, + device_d3d10, + device_d3d11, + command_queue_d3d12); + + return hr; +} + +HOOK_EXPORT HRESULT WINAPI CreateDXGIFactory(REFIID riid, void **ppFactory) +{ + LOG(INFO) << "Redirecting CreateDXGIFactory" << '(' << riid << ", " << ppFactory << ')' << " ..."; + LOG(INFO) << "> Passing on to CreateDXGIFactory1:"; + + // DXGI1.1 should always be available, so to simplify code just 'CreateDXGIFactory' which is otherwise identical + return CreateDXGIFactory1(riid, ppFactory); +} +HOOK_EXPORT HRESULT WINAPI CreateDXGIFactory1(REFIID riid, void **ppFactory) +{ + LOG(INFO) << "Redirecting CreateDXGIFactory1" << '(' << riid << ", " << ppFactory << ')' << " ..."; + + const HRESULT hr = reshade::hooks::call(CreateDXGIFactory1)(riid, ppFactory); + + if (FAILED(hr)) + { + LOG(WARN) << "> CreateDXGIFactory1 failed with error code " << std::hex << hr << std::dec << '!'; + return hr; + } + + IDXGIFactory *const factory = static_cast(*ppFactory); + + reshade::hooks::install("IDXGIFactory::CreateSwapChain", vtable_from_instance(factory), 10, reinterpret_cast(&IDXGIFactory_CreateSwapChain)); + + // Check for DXGI1.2 support and install IDXGIFactory2 hooks if it exists + if (com_ptr factory2; SUCCEEDED(factory->QueryInterface(&factory2))) + { + reshade::hooks::install("IDXGIFactory2::CreateSwapChainForHwnd", vtable_from_instance(factory2.get()), 15, reinterpret_cast(&IDXGIFactory2_CreateSwapChainForHwnd)); + reshade::hooks::install("IDXGIFactory2::CreateSwapChainForCoreWindow", vtable_from_instance(factory2.get()), 16, reinterpret_cast(&IDXGIFactory2_CreateSwapChainForCoreWindow)); + reshade::hooks::install("IDXGIFactory2::CreateSwapChainForComposition", vtable_from_instance(factory2.get()), 24, reinterpret_cast(&IDXGIFactory2_CreateSwapChainForComposition)); + } + +#if RESHADE_VERBOSE_LOG + LOG(DEBUG) << "Returning IDXGIFactory1 object " << *ppFactory << '.'; +#endif + return hr; +} +HOOK_EXPORT HRESULT WINAPI CreateDXGIFactory2(UINT flags, REFIID riid, void **ppFactory) +{ + LOG(INFO) << "Redirecting CreateDXGIFactory2" << '(' << flags << ", " << riid << ", " << ppFactory << ')' << " ..."; + + static const auto trampoline = reshade::hooks::call(CreateDXGIFactory2); + + // CreateDXGIFactory2 is not available on Windows 7, so fall back to CreateDXGIFactory1 if the application calls it + // This needs to happen because some applications only check if CreateDXGIFactory2 exists, which is always the case if they load ReShade, to decide whether to call it or CreateDXGIFactory1 + if (trampoline == nullptr) + { + LOG(INFO) << "> Passing on to CreateDXGIFactory1:"; + + return CreateDXGIFactory1(riid, ppFactory); + } + + const HRESULT hr = trampoline(flags, riid, ppFactory); + + if (FAILED(hr)) + { + LOG(WARN) << "> CreateDXGIFactory2 failed with error code " << std::hex << hr << std::dec << '!'; + return hr; + } + + // We can pretty much assume support for DXGI1.2 at this point + IDXGIFactory2 *const factory = static_cast(*ppFactory); + + reshade::hooks::install("IDXGIFactory::CreateSwapChain", vtable_from_instance(factory), 10, reinterpret_cast(&IDXGIFactory_CreateSwapChain)); + reshade::hooks::install("IDXGIFactory2::CreateSwapChainForHwnd", vtable_from_instance(factory), 15, reinterpret_cast(&IDXGIFactory2_CreateSwapChainForHwnd)); + reshade::hooks::install("IDXGIFactory2::CreateSwapChainForCoreWindow", vtable_from_instance(factory), 16, reinterpret_cast(&IDXGIFactory2_CreateSwapChainForCoreWindow)); + reshade::hooks::install("IDXGIFactory2::CreateSwapChainForComposition", vtable_from_instance(factory), 24, reinterpret_cast(&IDXGIFactory2_CreateSwapChainForComposition)); + +#if RESHADE_VERBOSE_LOG + LOG(DEBUG) << "Returning IDXGIFactory2 object " << *ppFactory << '.'; +#endif + return hr; +} diff --git a/msvc/source/dxgi/dxgi_d3d10.cpp b/msvc/source/dxgi/dxgi_d3d10.cpp new file mode 100644 index 0000000..a402c65 --- /dev/null +++ b/msvc/source/dxgi/dxgi_d3d10.cpp @@ -0,0 +1,38 @@ +/** + * Copyright (C) 2014 Patrick Mours. All rights reserved. + * License: https://github.com/crosire/reshade#license + */ + +#include "hook_manager.hpp" +#include +#include + +// These are filtered out during hook installation (see hook_manager.cpp) +HOOK_EXPORT HRESULT WINAPI DXGIDumpJournal() +{ + assert(false); + return E_NOTIMPL; +} +HOOK_EXPORT HRESULT WINAPI DXGIReportAdapterConfiguration() +{ + assert(false); + return E_NOTIMPL; +} + +// These are actually called internally by the Direct3D driver on some versions of Windows, so just pass them through +HOOK_EXPORT HRESULT WINAPI DXGID3D10CreateDevice(HMODULE hModule, IDXGIFactory *pFactory, IDXGIAdapter *pAdapter, UINT Flags, void *pUnknown, void **ppDevice) +{ + return reshade::hooks::call(DXGID3D10CreateDevice)(hModule, pFactory, pAdapter, Flags, pUnknown, ppDevice); +} +HOOK_EXPORT HRESULT WINAPI DXGID3D10CreateLayeredDevice(void *pUnknown1, void *pUnknown2, void *pUnknown3, void *pUnknown4, void *pUnknown5) +{ + return reshade::hooks::call(DXGID3D10CreateLayeredDevice)(pUnknown1, pUnknown2, pUnknown3, pUnknown4, pUnknown5); +} +HOOK_EXPORT SIZE_T WINAPI DXGID3D10GetLayeredDeviceSize(const void *pLayers, UINT NumLayers) +{ + return reshade::hooks::call(DXGID3D10GetLayeredDeviceSize)(pLayers, NumLayers); +} +HOOK_EXPORT HRESULT WINAPI DXGID3D10RegisterLayers(const void *pLayers, UINT NumLayers) +{ + return reshade::hooks::call(DXGID3D10RegisterLayers)(pLayers, NumLayers); +} diff --git a/msvc/source/dxgi/dxgi_device.cpp b/msvc/source/dxgi/dxgi_device.cpp new file mode 100644 index 0000000..5184b33 --- /dev/null +++ b/msvc/source/dxgi/dxgi_device.cpp @@ -0,0 +1,178 @@ +/** + * Copyright (C) 2014 Patrick Mours. All rights reserved. + * License: https://github.com/crosire/reshade#license + */ + +#include "log.hpp" +#include "dxgi_device.hpp" +#include "dxgi_swapchain.hpp" +#include + +DXGIDevice::DXGIDevice(IDXGIDevice1 *original, IUnknown *direct3d_device) : + _orig(original), + _interface_version(1), + _direct3d_device(direct3d_device) { + assert(original != nullptr); +} + +bool DXGIDevice::check_and_upgrade_interface(REFIID riid) +{ + static const IID iid_lookup[] = { + __uuidof(IDXGIDevice ), + __uuidof(IDXGIDevice1), + __uuidof(IDXGIDevice2), + __uuidof(IDXGIDevice3), + __uuidof(IDXGIDevice4), + }; + + for (unsigned int new_version = _interface_version + 1; new_version < ARRAYSIZE(iid_lookup); ++new_version) + { + if (riid == iid_lookup[new_version]) + { + IUnknown *new_interface = nullptr; + if (FAILED(_orig->QueryInterface(riid, reinterpret_cast(&new_interface)))) + return false; +#if RESHADE_VERBOSE_LOG + LOG(DEBUG) << "Upgraded IDXGIDevice" << _interface_version << " object " << this << " to IDXGIDevice" << new_version << '.'; +#endif + _orig->Release(); + _orig = static_cast(new_interface); + _interface_version = new_version; + break; + } + } + + return true; +} + +HRESULT STDMETHODCALLTYPE DXGIDevice::QueryInterface(REFIID riid, void **ppvObj) +{ + if (ppvObj == nullptr) + return E_POINTER; + + if (riid == __uuidof(this) || + riid == __uuidof(IUnknown) || // This is the IID_IUnknown identity object + riid == __uuidof(IDXGIObject) || + riid == __uuidof(IDXGIDevice) || + riid == __uuidof(IDXGIDevice1) || + riid == __uuidof(IDXGIDevice2) || + riid == __uuidof(IDXGIDevice3) || + riid == __uuidof(IDXGIDevice4)) + { + if (!check_and_upgrade_interface(riid)) + return E_NOINTERFACE; + + AddRef(); + *ppvObj = this; + return S_OK; + } + + return _direct3d_device->QueryInterface(riid, ppvObj); +} +ULONG STDMETHODCALLTYPE DXGIDevice::AddRef() +{ + ++_ref; + + return _orig->AddRef(); +} +ULONG STDMETHODCALLTYPE DXGIDevice::Release() +{ + --_ref; + + const ULONG ref = _orig->Release(); + + // The D3D device still holds a reference, so check reference count against one instead of zero + if (ref > 1 && _ref != 0) + return ref; + else if (ref != 1) + LOG(WARN) << "Reference count for IDXGIDevice" << _interface_version << " object " << this << " is inconsistent: " << ref << ", but expected 1."; + + assert(_ref <= 0); +#if RESHADE_VERBOSE_LOG + LOG(DEBUG) << "Destroyed IDXGIDevice" << _interface_version << " object " << this << '.'; +#endif + delete this; + return 0; +} + +HRESULT STDMETHODCALLTYPE DXGIDevice::SetPrivateData(REFGUID Name, UINT DataSize, const void *pData) +{ + return _orig->SetPrivateData(Name, DataSize, pData); +} +HRESULT STDMETHODCALLTYPE DXGIDevice::SetPrivateDataInterface(REFGUID Name, const IUnknown *pUnknown) +{ + return _orig->SetPrivateDataInterface(Name, pUnknown); +} +HRESULT STDMETHODCALLTYPE DXGIDevice::GetPrivateData(REFGUID Name, UINT *pDataSize, void *pData) +{ + return _orig->GetPrivateData(Name, pDataSize, pData); +} +HRESULT STDMETHODCALLTYPE DXGIDevice::GetParent(REFIID riid, void **ppParent) +{ + return _orig->GetParent(riid, ppParent); +} + +HRESULT STDMETHODCALLTYPE DXGIDevice::GetAdapter(IDXGIAdapter **pAdapter) +{ + return _orig->GetAdapter(pAdapter); +} +HRESULT STDMETHODCALLTYPE DXGIDevice::CreateSurface(const DXGI_SURFACE_DESC *pDesc, UINT NumSurfaces, DXGI_USAGE Usage, const DXGI_SHARED_RESOURCE *pSharedResource, IDXGISurface **ppSurface) +{ + return _orig->CreateSurface(pDesc, NumSurfaces, Usage, pSharedResource, ppSurface); +} +HRESULT STDMETHODCALLTYPE DXGIDevice::QueryResourceResidency(IUnknown *const *ppResources, DXGI_RESIDENCY *pResidencyStatus, UINT NumResources) +{ + return _orig->QueryResourceResidency(ppResources, pResidencyStatus, NumResources); +} +HRESULT STDMETHODCALLTYPE DXGIDevice::SetGPUThreadPriority(INT Priority) +{ + return _orig->SetGPUThreadPriority(Priority); +} +HRESULT STDMETHODCALLTYPE DXGIDevice::GetGPUThreadPriority(INT *pPriority) +{ + return _orig->GetGPUThreadPriority(pPriority); +} + +HRESULT STDMETHODCALLTYPE DXGIDevice::SetMaximumFrameLatency(UINT MaxLatency) +{ + assert(_interface_version >= 1); + return _orig->SetMaximumFrameLatency(MaxLatency); +} +HRESULT STDMETHODCALLTYPE DXGIDevice::GetMaximumFrameLatency(UINT *pMaxLatency) +{ + assert(_interface_version >= 1); + return _orig->GetMaximumFrameLatency(pMaxLatency); +} + +HRESULT STDMETHODCALLTYPE DXGIDevice::OfferResources(UINT NumResources, IDXGIResource *const *ppResources, DXGI_OFFER_RESOURCE_PRIORITY Priority) +{ + assert(_interface_version >= 2); + return static_cast(_orig)->OfferResources(NumResources, ppResources, Priority); +} +HRESULT STDMETHODCALLTYPE DXGIDevice::ReclaimResources(UINT NumResources, IDXGIResource *const *ppResources, BOOL *pDiscarded) +{ + assert(_interface_version >= 2); + return static_cast(_orig)->ReclaimResources(NumResources, ppResources, pDiscarded); +} +HRESULT STDMETHODCALLTYPE DXGIDevice::EnqueueSetEvent(HANDLE hEvent) +{ + assert(_interface_version >= 2); + return static_cast(_orig)->EnqueueSetEvent(hEvent); +} + +void STDMETHODCALLTYPE DXGIDevice::Trim() +{ + assert(_interface_version >= 3); + return static_cast(_orig)->Trim(); +} + +HRESULT STDMETHODCALLTYPE DXGIDevice::OfferResources1(UINT NumResources, IDXGIResource *const *ppResources, DXGI_OFFER_RESOURCE_PRIORITY Priority, UINT Flags) +{ + assert(_interface_version >= 4); + return static_cast(_orig)->OfferResources1(NumResources, ppResources, Priority, Flags); +} +HRESULT STDMETHODCALLTYPE DXGIDevice::ReclaimResources1(UINT NumResources, IDXGIResource *const *ppResources, DXGI_RECLAIM_RESOURCE_RESULTS *pResults) +{ + assert(_interface_version >= 4); + return static_cast(_orig)->ReclaimResources1(NumResources, ppResources, pResults); +} diff --git a/msvc/source/dxgi/dxgi_device.hpp b/msvc/source/dxgi/dxgi_device.hpp new file mode 100644 index 0000000..d37ed43 --- /dev/null +++ b/msvc/source/dxgi/dxgi_device.hpp @@ -0,0 +1,57 @@ +/** + * Copyright (C) 2014 Patrick Mours. All rights reserved. + * License: https://github.com/crosire/reshade#license + */ + +#pragma once + +#include + +struct __declspec(uuid("CB285C3B-3677-4332-98C7-D6339B9782B1")) DXGIDevice : IDXGIDevice4 +{ + DXGIDevice(IDXGIDevice1 *original, IUnknown *direct3d_device); + + DXGIDevice(const DXGIDevice &) = delete; + DXGIDevice &operator=(const DXGIDevice &) = delete; + + HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void **ppvObj) override; + ULONG STDMETHODCALLTYPE AddRef() override; + ULONG STDMETHODCALLTYPE Release() override; + + #pragma region IDXGIObject + HRESULT STDMETHODCALLTYPE SetPrivateData(REFGUID Name, UINT DataSize, const void *pData) override; + HRESULT STDMETHODCALLTYPE SetPrivateDataInterface(REFGUID Name, const IUnknown *pUnknown) override; + HRESULT STDMETHODCALLTYPE GetPrivateData(REFGUID Name, UINT *pDataSize, void *pData) override; + HRESULT STDMETHODCALLTYPE GetParent(REFIID riid, void **ppParent) override; + #pragma endregion + #pragma region IDXGIDevice + HRESULT STDMETHODCALLTYPE GetAdapter(IDXGIAdapter **pAdapter) override; + HRESULT STDMETHODCALLTYPE CreateSurface(const DXGI_SURFACE_DESC *pDesc, UINT NumSurfaces, DXGI_USAGE Usage, const DXGI_SHARED_RESOURCE *pSharedResource, IDXGISurface **ppSurface) override; + HRESULT STDMETHODCALLTYPE QueryResourceResidency(IUnknown *const *ppResources, DXGI_RESIDENCY *pResidencyStatus, UINT NumResources) override; + HRESULT STDMETHODCALLTYPE SetGPUThreadPriority(INT Priority) override; + HRESULT STDMETHODCALLTYPE GetGPUThreadPriority(INT *pPriority) override; + #pragma endregion + #pragma region IDXGIDevice1 + HRESULT STDMETHODCALLTYPE SetMaximumFrameLatency(UINT MaxLatency) override; + HRESULT STDMETHODCALLTYPE GetMaximumFrameLatency(UINT *pMaxLatency) override; + #pragma endregion + #pragma region IDXGIDevice2 + HRESULT STDMETHODCALLTYPE OfferResources(UINT NumResources, IDXGIResource *const *ppResources, DXGI_OFFER_RESOURCE_PRIORITY Priority) override; + HRESULT STDMETHODCALLTYPE ReclaimResources(UINT NumResources, IDXGIResource *const *ppResources, BOOL *pDiscarded) override; + HRESULT STDMETHODCALLTYPE EnqueueSetEvent(HANDLE hEvent) override; + #pragma endregion + #pragma region IDXGIDevice3 + void STDMETHODCALLTYPE Trim() override; + #pragma endregion + #pragma region IDXGIDevice4 + HRESULT STDMETHODCALLTYPE OfferResources1(UINT NumResources, IDXGIResource *const *ppResources, DXGI_OFFER_RESOURCE_PRIORITY Priority, UINT Flags) override; + HRESULT STDMETHODCALLTYPE ReclaimResources1(UINT NumResources, IDXGIResource *const *ppResources, DXGI_RECLAIM_RESOURCE_RESULTS *pResults) override; + #pragma endregion + + bool check_and_upgrade_interface(REFIID riid); + + LONG _ref = 1; + IDXGIDevice1 *_orig; + unsigned int _interface_version; + IUnknown *const _direct3d_device; +}; diff --git a/msvc/source/dxgi/dxgi_swapchain.cpp b/msvc/source/dxgi/dxgi_swapchain.cpp new file mode 100644 index 0000000..26845b5 --- /dev/null +++ b/msvc/source/dxgi/dxgi_swapchain.cpp @@ -0,0 +1,479 @@ +/** + * Copyright (C) 2014 Patrick Mours. All rights reserved. + * License: https://github.com/crosire/reshade#license + */ + +#include "log.hpp" +#include "dxgi_swapchain.hpp" +#include "d3d10/d3d10_device.hpp" +#include "d3d10/runtime_d3d10.hpp" +#include "d3d11/d3d11_device.hpp" +#include "d3d11/d3d11_device_context.hpp" +#include "d3d11/runtime_d3d11.hpp" +#include "d3d12/d3d12_device.hpp" +#include "d3d12/d3d12_command_queue.hpp" +#include "d3d12/runtime_d3d12.hpp" +#include + +DXGISwapChain::DXGISwapChain(D3D10Device *device, IDXGISwapChain *original, const std::shared_ptr &runtime) : + _orig(original), + _interface_version(0), + _direct3d_device(device, false), // Explicitly add a reference to the device + _direct3d_version(10), + _runtime(runtime) {} +DXGISwapChain::DXGISwapChain(D3D10Device *device, IDXGISwapChain1 *original, const std::shared_ptr &runtime) : + _orig(original), + _interface_version(1), + _direct3d_device(device, false), + _direct3d_version(10), + _runtime(runtime) {} +DXGISwapChain::DXGISwapChain(D3D11Device *device, IDXGISwapChain *original, const std::shared_ptr &runtime) : + _orig(original), + _interface_version(0), + _direct3d_device(device, false), + _direct3d_version(11), + _runtime(runtime) {} +DXGISwapChain::DXGISwapChain(D3D11Device *device, IDXGISwapChain1 *original, const std::shared_ptr &runtime) : + _orig(original), + _interface_version(1), + _direct3d_device(device, false), + _direct3d_version(11), + _runtime(runtime) {} +DXGISwapChain::DXGISwapChain(D3D12Device *device, IDXGISwapChain3 *original, const std::shared_ptr &runtime) : + _orig(original), + _interface_version(3), + _direct3d_device(device, false), + _direct3d_version(12), + _runtime(runtime) {} + +void DXGISwapChain::perform_present(UINT PresentFlags) +{ + // Some D3D11 games test presentation for timing and composition purposes. + // These calls are not rendering related, but rather a status request for the D3D runtime and as such should be ignored. + if (PresentFlags & DXGI_PRESENT_TEST) + return; + + assert(_runtime != nullptr); + + switch (_direct3d_version) + { + case 10: { + const auto device = static_cast(_direct3d_device.get()); + std::static_pointer_cast(_runtime)->on_present(device->_draw_call_tracker); + device->clear_drawcall_stats(); + break; } + case 11: { + const auto device = static_cast(_direct3d_device.get()); + std::static_pointer_cast(_runtime)->on_present(device->_immediate_context->_draw_call_tracker); + device->clear_drawcall_stats(); + break; } + case 12: + std::static_pointer_cast(_runtime)->on_present(); + break; + } +} + +bool DXGISwapChain::check_and_upgrade_interface(REFIID riid) +{ + static const IID iid_lookup[] = { + __uuidof(IDXGISwapChain), + __uuidof(IDXGISwapChain1), + __uuidof(IDXGISwapChain2), + __uuidof(IDXGISwapChain3), + __uuidof(IDXGISwapChain4), + }; + + for (unsigned int new_version = _interface_version + 1; new_version < ARRAYSIZE(iid_lookup); ++new_version) + { + if (riid == iid_lookup[new_version]) + { + IUnknown *new_interface = nullptr; + if (FAILED(_orig->QueryInterface(riid, reinterpret_cast(&new_interface)))) + return false; + _orig->Release(); + _orig = static_cast(new_interface); + _interface_version = new_version; +#if RESHADE_VERBOSE_LOG + LOG(DEBUG) << "Upgraded IDXGISwapChain" << _interface_version << " object " << this << " to IDXGISwapChain" << new_version << '.'; +#endif + break; + } + } + + return true; +} + +HRESULT STDMETHODCALLTYPE DXGISwapChain::QueryInterface(REFIID riid, void **ppvObj) +{ + if (ppvObj == nullptr) + return E_POINTER; + + if (riid == __uuidof(this) || + riid == __uuidof(IUnknown) || + riid == __uuidof(IDXGIObject) || + riid == __uuidof(IDXGIDeviceSubObject) || + riid == __uuidof(IDXGISwapChain) || + riid == __uuidof(IDXGISwapChain1) || + riid == __uuidof(IDXGISwapChain2) || + riid == __uuidof(IDXGISwapChain3) || + riid == __uuidof(IDXGISwapChain4)) + { + if (!check_and_upgrade_interface(riid)) + return E_NOINTERFACE; + + AddRef(); + *ppvObj = this; + return S_OK; + } + + return _orig->QueryInterface(riid, ppvObj); +} +ULONG STDMETHODCALLTYPE DXGISwapChain::AddRef() +{ + ++_ref; + + return _orig->AddRef(); +} +ULONG STDMETHODCALLTYPE DXGISwapChain::Release() +{ + if (--_ref == 0) + { + switch (_direct3d_version) + { + case 10: { + assert(_runtime != nullptr); + const auto device = static_cast(_direct3d_device.get()); + const auto runtime = std::static_pointer_cast(_runtime); + runtime->on_reset(); + device->clear_drawcall_stats(); + device->_runtimes.erase(std::remove(device->_runtimes.begin(), device->_runtimes.end(), runtime), device->_runtimes.end()); + break; } + case 11: { + assert(_runtime != nullptr); + const auto device = static_cast(_direct3d_device.get()); + const auto runtime = std::static_pointer_cast(_runtime); + runtime->on_reset(); + device->clear_drawcall_stats(); // Release any live references to depth buffers etc. + device->_runtimes.erase(std::remove(device->_runtimes.begin(), device->_runtimes.end(), runtime), device->_runtimes.end()); + break; } + case 12: { + const auto runtime = std::static_pointer_cast(_runtime); + runtime->on_reset(); + break; } + } + + _runtime.reset(); + _direct3d_device.reset(); + } + + const ULONG ref = _orig->Release(); + + if (ref != 0 && _ref != 0) + return ref; + else if (ref != 0) + LOG(WARN) << "Reference count for IDXGISwapChain" << _interface_version << " object " << this << " is inconsistent: " << ref << ", but expected 0."; + + assert(_ref <= 0); +#if RESHADE_VERBOSE_LOG + LOG(DEBUG) << "Destroyed IDXGISwapChain" << _interface_version << " object " << this << '.'; +#endif + delete this; + return 0; +} + +HRESULT STDMETHODCALLTYPE DXGISwapChain::SetPrivateData(REFGUID Name, UINT DataSize, const void *pData) +{ + return _orig->SetPrivateData(Name, DataSize, pData); +} +HRESULT STDMETHODCALLTYPE DXGISwapChain::SetPrivateDataInterface(REFGUID Name, const IUnknown *pUnknown) +{ + return _orig->SetPrivateDataInterface(Name, pUnknown); +} +HRESULT STDMETHODCALLTYPE DXGISwapChain::GetPrivateData(REFGUID Name, UINT *pDataSize, void *pData) +{ + return _orig->GetPrivateData(Name, pDataSize, pData); +} +HRESULT STDMETHODCALLTYPE DXGISwapChain::GetParent(REFIID riid, void **ppParent) +{ + return _orig->GetParent(riid, ppParent); +} + +HRESULT STDMETHODCALLTYPE DXGISwapChain::GetDevice(REFIID riid, void **ppDevice) +{ + return _direct3d_device->QueryInterface(riid, ppDevice); +} + +HRESULT STDMETHODCALLTYPE DXGISwapChain::Present(UINT SyncInterval, UINT Flags) +{ + perform_present(Flags); + return _orig->Present(SyncInterval, Flags); +} +HRESULT STDMETHODCALLTYPE DXGISwapChain::GetBuffer(UINT Buffer, REFIID riid, void **ppSurface) +{ + return _orig->GetBuffer(Buffer, riid, ppSurface); +} +HRESULT STDMETHODCALLTYPE DXGISwapChain::SetFullscreenState(BOOL Fullscreen, IDXGIOutput *pTarget) +{ + LOG(INFO) << "Redirecting IDXGISwapChain::SetFullscreenState" << '(' << this << ", " << (Fullscreen != FALSE ? "TRUE" : "FALSE") << ", " << pTarget << ')' << " ..."; + + return _orig->SetFullscreenState(Fullscreen, pTarget); +} +HRESULT STDMETHODCALLTYPE DXGISwapChain::GetFullscreenState(BOOL *pFullscreen, IDXGIOutput **ppTarget) +{ + return _orig->GetFullscreenState(pFullscreen, ppTarget); +} +HRESULT STDMETHODCALLTYPE DXGISwapChain::GetDesc(DXGI_SWAP_CHAIN_DESC *pDesc) +{ + return _orig->GetDesc(pDesc); +} +HRESULT STDMETHODCALLTYPE DXGISwapChain::ResizeBuffers(UINT BufferCount, UINT Width, UINT Height, DXGI_FORMAT NewFormat, UINT SwapChainFlags) +{ + LOG(INFO) << "Redirecting IDXGISwapChain::ResizeBuffers" << '(' << this << ", " << BufferCount << ", " << Width << ", " << Height << ", " << NewFormat << ", " << std::hex << SwapChainFlags << std::dec << ')' << " ..."; + + switch (_direct3d_version) + { + case 10: + assert(_runtime != nullptr); + static_cast(_direct3d_device.get())->clear_drawcall_stats(); + std::static_pointer_cast(_runtime)->on_reset(); + break; + case 11: + assert(_runtime != nullptr); + static_cast(_direct3d_device.get())->clear_drawcall_stats(); + std::static_pointer_cast(_runtime)->on_reset(); + break; + case 12: + assert(_runtime != nullptr); + std::static_pointer_cast(_runtime)->on_reset(); + break; + } + + const HRESULT hr = _orig->ResizeBuffers(BufferCount, Width, Height, NewFormat, SwapChainFlags); + + if (hr == DXGI_ERROR_INVALID_CALL) // Ignore invalid call errors since the device is still in a usable state afterwards + { + LOG(WARN) << "> IDXGISwapChain::ResizeBuffers failed with 'DXGI_ERROR_INVALID_CALL'!"; + } + else if (FAILED(hr)) + { + LOG(ERROR) << "> IDXGISwapChain::ResizeBuffers failed with error code " << std::hex << hr << std::dec << '!'; + return hr; + } + + DXGI_SWAP_CHAIN_DESC desc; + _orig->GetDesc(&desc); + + bool initialized = false; + switch (_direct3d_version) + { + case 10: + assert(_runtime != nullptr); + initialized = std::static_pointer_cast(_runtime)->on_init(desc); + break; + case 11: + assert(_runtime != nullptr); + initialized = std::static_pointer_cast(_runtime)->on_init(desc); + break; + case 12: + assert(_runtime != nullptr); + initialized = std::static_pointer_cast(_runtime)->on_init(desc); + break; + } + + if (!initialized) + LOG(ERROR) << "Failed to recreate Direct3D " << _direct3d_version << " runtime environment on runtime " << _runtime.get() << '.'; + + return hr; +} +HRESULT STDMETHODCALLTYPE DXGISwapChain::ResizeTarget(const DXGI_MODE_DESC *pNewTargetParameters) +{ + return _orig->ResizeTarget(pNewTargetParameters); +} +HRESULT STDMETHODCALLTYPE DXGISwapChain::GetContainingOutput(IDXGIOutput **ppOutput) +{ + return _orig->GetContainingOutput(ppOutput); +} +HRESULT STDMETHODCALLTYPE DXGISwapChain::GetFrameStatistics(DXGI_FRAME_STATISTICS *pStats) +{ + return _orig->GetFrameStatistics(pStats); +} +HRESULT STDMETHODCALLTYPE DXGISwapChain::GetLastPresentCount(UINT *pLastPresentCount) +{ + return _orig->GetLastPresentCount(pLastPresentCount); +} + +HRESULT STDMETHODCALLTYPE DXGISwapChain::GetDesc1(DXGI_SWAP_CHAIN_DESC1 *pDesc) +{ + assert(_interface_version >= 1); + return static_cast(_orig)->GetDesc1(pDesc); +} +HRESULT STDMETHODCALLTYPE DXGISwapChain::GetFullscreenDesc(DXGI_SWAP_CHAIN_FULLSCREEN_DESC *pDesc) +{ + assert(_interface_version >= 1); + return static_cast(_orig)->GetFullscreenDesc(pDesc); +} +HRESULT STDMETHODCALLTYPE DXGISwapChain::GetHwnd(HWND *pHwnd) +{ + assert(_interface_version >= 1); + return static_cast(_orig)->GetHwnd(pHwnd); +} +HRESULT STDMETHODCALLTYPE DXGISwapChain::GetCoreWindow(REFIID refiid, void **ppUnk) +{ + assert(_interface_version >= 1); + return static_cast(_orig)->GetCoreWindow(refiid, ppUnk); +} +HRESULT STDMETHODCALLTYPE DXGISwapChain::Present1(UINT SyncInterval, UINT PresentFlags, const DXGI_PRESENT_PARAMETERS *pPresentParameters) +{ + assert(_interface_version >= 1); + perform_present(PresentFlags); + return static_cast(_orig)->Present1(SyncInterval, PresentFlags, pPresentParameters); +} +BOOL STDMETHODCALLTYPE DXGISwapChain::IsTemporaryMonoSupported() +{ + assert(_interface_version >= 1); + return static_cast(_orig)->IsTemporaryMonoSupported(); +} +HRESULT STDMETHODCALLTYPE DXGISwapChain::GetRestrictToOutput(IDXGIOutput **ppRestrictToOutput) +{ + assert(_interface_version >= 1); + return static_cast(_orig)->GetRestrictToOutput(ppRestrictToOutput); +} +HRESULT STDMETHODCALLTYPE DXGISwapChain::SetBackgroundColor(const DXGI_RGBA *pColor) +{ + assert(_interface_version >= 1); + return static_cast(_orig)->SetBackgroundColor(pColor); +} +HRESULT STDMETHODCALLTYPE DXGISwapChain::GetBackgroundColor(DXGI_RGBA *pColor) +{ + assert(_interface_version >= 1); + return static_cast(_orig)->GetBackgroundColor(pColor); +} +HRESULT STDMETHODCALLTYPE DXGISwapChain::SetRotation(DXGI_MODE_ROTATION Rotation) +{ + assert(_interface_version >= 1); + return static_cast(_orig)->SetRotation(Rotation); +} +HRESULT STDMETHODCALLTYPE DXGISwapChain::GetRotation(DXGI_MODE_ROTATION *pRotation) +{ + assert(_interface_version >= 1); + return static_cast(_orig)->GetRotation(pRotation); +} + +HRESULT STDMETHODCALLTYPE DXGISwapChain::SetSourceSize(UINT Width, UINT Height) +{ + assert(_interface_version >= 2); + return static_cast(_orig)->SetSourceSize(Width, Height); +} +HRESULT STDMETHODCALLTYPE DXGISwapChain::GetSourceSize(UINT *pWidth, UINT *pHeight) +{ + assert(_interface_version >= 2); + return static_cast(_orig)->GetSourceSize(pWidth, pHeight); +} +HRESULT STDMETHODCALLTYPE DXGISwapChain::SetMaximumFrameLatency(UINT MaxLatency) +{ + assert(_interface_version >= 2); + return static_cast(_orig)->SetMaximumFrameLatency(MaxLatency); +} +HRESULT STDMETHODCALLTYPE DXGISwapChain::GetMaximumFrameLatency(UINT *pMaxLatency) +{ + assert(_interface_version >= 2); + return static_cast(_orig)->GetMaximumFrameLatency(pMaxLatency); +} +HANDLE STDMETHODCALLTYPE DXGISwapChain::GetFrameLatencyWaitableObject() +{ + assert(_interface_version >= 2); + return static_cast(_orig)->GetFrameLatencyWaitableObject(); +} +HRESULT STDMETHODCALLTYPE DXGISwapChain::SetMatrixTransform(const DXGI_MATRIX_3X2_F *pMatrix) +{ + assert(_interface_version >= 2); + return static_cast(_orig)->SetMatrixTransform(pMatrix); +} +HRESULT STDMETHODCALLTYPE DXGISwapChain::GetMatrixTransform(DXGI_MATRIX_3X2_F *pMatrix) +{ + assert(_interface_version >= 2); + return static_cast(_orig)->GetMatrixTransform(pMatrix); +} + +UINT STDMETHODCALLTYPE DXGISwapChain::GetCurrentBackBufferIndex() +{ + assert(_interface_version >= 3); + return static_cast(_orig)->GetCurrentBackBufferIndex(); +} +HRESULT STDMETHODCALLTYPE DXGISwapChain::CheckColorSpaceSupport(DXGI_COLOR_SPACE_TYPE ColorSpace, UINT *pColorSpaceSupport) +{ + assert(_interface_version >= 3); + return static_cast(_orig)->CheckColorSpaceSupport(ColorSpace, pColorSpaceSupport); +} +HRESULT STDMETHODCALLTYPE DXGISwapChain::SetColorSpace1(DXGI_COLOR_SPACE_TYPE ColorSpace) +{ + assert(_interface_version >= 3); + return static_cast(_orig)->SetColorSpace1(ColorSpace); +} +HRESULT STDMETHODCALLTYPE DXGISwapChain::ResizeBuffers1(UINT BufferCount, UINT Width, UINT Height, DXGI_FORMAT Format, UINT SwapChainFlags, const UINT *pCreationNodeMask, IUnknown *const *ppPresentQueue) +{ + assert(_interface_version >= 3); + + LOG(INFO) << "Redirecting IDXGISwapChain3::ResizeBuffers1" << '(' << this << ", " << BufferCount << ", " << Width << ", " << Height << ", " << Format << ", " << std::hex << SwapChainFlags << std::dec << ", " << pCreationNodeMask << ", " << ppPresentQueue << ')' << " ..."; + + switch (_direct3d_version) + { + case 10: + assert(_runtime != nullptr); + static_cast(_direct3d_device.get())->clear_drawcall_stats(); + std::static_pointer_cast(_runtime)->on_reset(); + break; + case 11: + assert(_runtime != nullptr); + static_cast(_direct3d_device.get())->clear_drawcall_stats(); + std::static_pointer_cast(_runtime)->on_reset(); + break; + case 12: + assert(_runtime != nullptr); + std::static_pointer_cast(_runtime)->on_reset(); + break; + } + + const HRESULT hr = static_cast(_orig)->ResizeBuffers1(BufferCount, Width, Height, Format, SwapChainFlags, pCreationNodeMask, ppPresentQueue); + + if (hr == DXGI_ERROR_INVALID_CALL) + { + LOG(WARN) << "> IDXGISwapChain3::ResizeBuffers1 failed with 'DXGI_ERROR_INVALID_CALL'!"; + } + else if (FAILED(hr)) + { + LOG(ERROR) << "> IDXGISwapChain3::ResizeBuffers1 failed with error code " << std::hex << hr << std::dec << '!'; + return hr; + } + + DXGI_SWAP_CHAIN_DESC desc; + _orig->GetDesc(&desc); + + bool initialized = false; + switch (_direct3d_version) + { + case 10: + assert(_runtime != nullptr); + initialized = std::static_pointer_cast(_runtime)->on_init(desc); + break; + case 11: + assert(_runtime != nullptr); + initialized = std::static_pointer_cast(_runtime)->on_init(desc); + break; + case 12: + assert(_runtime != nullptr); + initialized = std::static_pointer_cast(_runtime)->on_init(desc); + break; + } + + if (!initialized) + LOG(ERROR) << "Failed to recreate Direct3D " << _direct3d_version << " runtime environment on runtime " << _runtime.get() << '.'; + + return hr; +} + +HRESULT STDMETHODCALLTYPE DXGISwapChain::SetHDRMetaData(DXGI_HDR_METADATA_TYPE Type, UINT Size, void *pMetaData) +{ + assert(_interface_version >= 4); + return static_cast(_orig)->SetHDRMetaData(Type, Size, pMetaData); +} diff --git a/msvc/source/dxgi/dxgi_swapchain.hpp b/msvc/source/dxgi/dxgi_swapchain.hpp new file mode 100644 index 0000000..4f411f3 --- /dev/null +++ b/msvc/source/dxgi/dxgi_swapchain.hpp @@ -0,0 +1,95 @@ +/** + * Copyright (C) 2014 Patrick Mours. All rights reserved. + * License: https://github.com/crosire/reshade#license + */ + +#pragma once + +#include +#include "com_ptr.hpp" +#include + +struct D3D10Device; +struct D3D11Device; +struct D3D12Device; +namespace reshade { class runtime; } + +struct __declspec(uuid("1F445F9F-9887-4C4C-9055-4E3BADAFCCA8")) DXGISwapChain : IDXGISwapChain4 +{ + DXGISwapChain(D3D10Device *device, IDXGISwapChain *original, const std::shared_ptr &runtime); + DXGISwapChain(D3D10Device *device, IDXGISwapChain1 *original, const std::shared_ptr &runtime); + DXGISwapChain(D3D11Device *device, IDXGISwapChain *original, const std::shared_ptr &runtime); + DXGISwapChain(D3D11Device *device, IDXGISwapChain1 *original, const std::shared_ptr &runtime); + DXGISwapChain(D3D12Device *device, IDXGISwapChain3 *original, const std::shared_ptr &runtime); + + DXGISwapChain(const DXGISwapChain &) = delete; + DXGISwapChain &operator=(const DXGISwapChain &) = delete; + + HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void **ppvObj) override; + ULONG STDMETHODCALLTYPE AddRef() override; + ULONG STDMETHODCALLTYPE Release() override; + + #pragma region IDXGIObject + HRESULT STDMETHODCALLTYPE SetPrivateData(REFGUID Name, UINT DataSize, const void *pData) override; + HRESULT STDMETHODCALLTYPE SetPrivateDataInterface(REFGUID Name, const IUnknown *pUnknown) override; + HRESULT STDMETHODCALLTYPE GetPrivateData(REFGUID Name, UINT *pDataSize, void *pData) override; + HRESULT STDMETHODCALLTYPE GetParent(REFIID riid, void **ppParent) override; + #pragma endregion + #pragma region IDXGIDeviceSubObject + HRESULT STDMETHODCALLTYPE GetDevice(REFIID riid, void **ppDevice) override; + #pragma endregion + #pragma region IDXGISwapChain + HRESULT STDMETHODCALLTYPE Present(UINT SyncInterval, UINT Flags) override; + HRESULT STDMETHODCALLTYPE GetBuffer(UINT Buffer, REFIID riid, void **ppSurface) override; + HRESULT STDMETHODCALLTYPE SetFullscreenState(BOOL Fullscreen, IDXGIOutput *pTarget) override; + HRESULT STDMETHODCALLTYPE GetFullscreenState(BOOL *pFullscreen, IDXGIOutput **ppTarget) override; + HRESULT STDMETHODCALLTYPE GetDesc(DXGI_SWAP_CHAIN_DESC *pDesc) override; + HRESULT STDMETHODCALLTYPE ResizeBuffers(UINT BufferCount, UINT Width, UINT Height, DXGI_FORMAT NewFormat, UINT SwapChainFlags) override; + HRESULT STDMETHODCALLTYPE ResizeTarget(const DXGI_MODE_DESC *pNewTargetParameters) override; + HRESULT STDMETHODCALLTYPE GetContainingOutput(IDXGIOutput **ppOutput) override; + HRESULT STDMETHODCALLTYPE GetFrameStatistics(DXGI_FRAME_STATISTICS *pStats) override; + HRESULT STDMETHODCALLTYPE GetLastPresentCount(UINT *pLastPresentCount) override; + #pragma endregion + #pragma region IDXGISwapChain1 + HRESULT STDMETHODCALLTYPE GetDesc1(DXGI_SWAP_CHAIN_DESC1 *pDesc) override; + HRESULT STDMETHODCALLTYPE GetFullscreenDesc(DXGI_SWAP_CHAIN_FULLSCREEN_DESC *pDesc) override; + HRESULT STDMETHODCALLTYPE GetHwnd(HWND *pHwnd) override; + HRESULT STDMETHODCALLTYPE GetCoreWindow(REFIID refiid, void **ppUnk) override; + HRESULT STDMETHODCALLTYPE Present1(UINT SyncInterval, UINT PresentFlags, const DXGI_PRESENT_PARAMETERS *pPresentParameters) override; + BOOL STDMETHODCALLTYPE IsTemporaryMonoSupported() override; + HRESULT STDMETHODCALLTYPE GetRestrictToOutput(IDXGIOutput **ppRestrictToOutput) override; + HRESULT STDMETHODCALLTYPE SetBackgroundColor(const DXGI_RGBA *pColor) override; + HRESULT STDMETHODCALLTYPE GetBackgroundColor(DXGI_RGBA *pColor) override; + HRESULT STDMETHODCALLTYPE SetRotation(DXGI_MODE_ROTATION Rotation) override; + HRESULT STDMETHODCALLTYPE GetRotation(DXGI_MODE_ROTATION *pRotation) override; + #pragma endregion + #pragma region IDXGISwapChain2 + HRESULT STDMETHODCALLTYPE SetSourceSize(UINT Width, UINT Height) override; + HRESULT STDMETHODCALLTYPE GetSourceSize(UINT *pWidth, UINT *pHeight) override; + HRESULT STDMETHODCALLTYPE SetMaximumFrameLatency(UINT MaxLatency) override; + HRESULT STDMETHODCALLTYPE GetMaximumFrameLatency(UINT *pMaxLatency) override; + HANDLE STDMETHODCALLTYPE GetFrameLatencyWaitableObject() override; + HRESULT STDMETHODCALLTYPE SetMatrixTransform(const DXGI_MATRIX_3X2_F *pMatrix) override; + HRESULT STDMETHODCALLTYPE GetMatrixTransform(DXGI_MATRIX_3X2_F *pMatrix) override; + #pragma endregion + #pragma region IDXGISwapChain3 + UINT STDMETHODCALLTYPE GetCurrentBackBufferIndex() override; + HRESULT STDMETHODCALLTYPE CheckColorSpaceSupport(DXGI_COLOR_SPACE_TYPE ColorSpace, UINT *pColorSpaceSupport) override; + HRESULT STDMETHODCALLTYPE SetColorSpace1(DXGI_COLOR_SPACE_TYPE ColorSpace) override; + HRESULT STDMETHODCALLTYPE ResizeBuffers1(UINT BufferCount, UINT Width, UINT Height, DXGI_FORMAT Format, UINT SwapChainFlags, const UINT *pCreationNodeMask, IUnknown *const *ppPresentQueue) override; + #pragma endregion + #pragma region IDXGISwapChain4 + HRESULT STDMETHODCALLTYPE SetHDRMetaData(DXGI_HDR_METADATA_TYPE Type, UINT Size, void *pMetaData) override; + #pragma endregion + + void perform_present(UINT PresentFlags); + + bool check_and_upgrade_interface(REFIID riid); + + LONG _ref = 1; + IDXGISwapChain *_orig; + unsigned int _interface_version; + com_ptr _direct3d_device; + const unsigned int _direct3d_version; + std::shared_ptr _runtime; +}; diff --git a/msvc/source/dxgi/format_utils.hpp b/msvc/source/dxgi/format_utils.hpp new file mode 100644 index 0000000..fbbccf2 --- /dev/null +++ b/msvc/source/dxgi/format_utils.hpp @@ -0,0 +1,108 @@ +/** + * Copyright (C) 2014 Patrick Mours. All rights reserved. + * License: https://github.com/crosire/reshade#license + */ + +#pragma once + +#include + +inline DXGI_FORMAT make_dxgi_format_dsv(DXGI_FORMAT format) +{ + switch (format) + { + case DXGI_FORMAT_R32G8X24_TYPELESS: + return DXGI_FORMAT_D32_FLOAT_S8X24_UINT; + case DXGI_FORMAT_R32_TYPELESS: + return DXGI_FORMAT_D32_FLOAT; + case DXGI_FORMAT_R24G8_TYPELESS: + return DXGI_FORMAT_D24_UNORM_S8_UINT; + case DXGI_FORMAT_R16_TYPELESS: + return DXGI_FORMAT_D16_UNORM; + default: + return format; + } +} + +inline DXGI_FORMAT make_dxgi_format_srgb(DXGI_FORMAT format) +{ + switch (format) + { + case DXGI_FORMAT_R8G8B8A8_TYPELESS: + case DXGI_FORMAT_R8G8B8A8_UNORM: + return DXGI_FORMAT_R8G8B8A8_UNORM_SRGB; + case DXGI_FORMAT_BC1_TYPELESS: + case DXGI_FORMAT_BC1_UNORM: + return DXGI_FORMAT_BC1_UNORM_SRGB; + case DXGI_FORMAT_BC2_TYPELESS: + case DXGI_FORMAT_BC2_UNORM: + return DXGI_FORMAT_BC2_UNORM_SRGB; + case DXGI_FORMAT_BC3_TYPELESS: + case DXGI_FORMAT_BC3_UNORM: + return DXGI_FORMAT_BC3_UNORM_SRGB; + default: + return format; + } +} + +inline DXGI_FORMAT make_dxgi_format_normal(DXGI_FORMAT format) +{ + switch (format) + { + case DXGI_FORMAT_R32G8X24_TYPELESS: + return DXGI_FORMAT_R32_FLOAT_X8X24_TYPELESS; + case DXGI_FORMAT_R8G8B8A8_TYPELESS: + case DXGI_FORMAT_R8G8B8A8_UNORM_SRGB: + return DXGI_FORMAT_R8G8B8A8_UNORM; + case DXGI_FORMAT_R32_TYPELESS: + return DXGI_FORMAT_R32_FLOAT; + case DXGI_FORMAT_R24G8_TYPELESS: + return DXGI_FORMAT_R24_UNORM_X8_TYPELESS; + case DXGI_FORMAT_R16_TYPELESS: + return DXGI_FORMAT_R16_FLOAT; + case DXGI_FORMAT_BC1_TYPELESS: + case DXGI_FORMAT_BC1_UNORM_SRGB: + return DXGI_FORMAT_BC1_UNORM; + case DXGI_FORMAT_BC2_TYPELESS: + case DXGI_FORMAT_BC2_UNORM_SRGB: + return DXGI_FORMAT_BC2_UNORM; + case DXGI_FORMAT_BC3_TYPELESS: + case DXGI_FORMAT_BC3_UNORM_SRGB: + return DXGI_FORMAT_BC3_UNORM; + default: + return format; + } +} + +inline DXGI_FORMAT make_dxgi_format_typeless(DXGI_FORMAT format) +{ + switch (format) + { + case DXGI_FORMAT_D32_FLOAT_S8X24_UINT: + case DXGI_FORMAT_R32_FLOAT_X8X24_TYPELESS: + return DXGI_FORMAT_R32G8X24_TYPELESS; + case DXGI_FORMAT_R8G8B8A8_UNORM: + case DXGI_FORMAT_R8G8B8A8_UNORM_SRGB: + return DXGI_FORMAT_R8G8B8A8_TYPELESS; + case DXGI_FORMAT_D32_FLOAT: + case DXGI_FORMAT_R32_FLOAT: + return DXGI_FORMAT_R32_TYPELESS; + case DXGI_FORMAT_D24_UNORM_S8_UINT: + case DXGI_FORMAT_R24_UNORM_X8_TYPELESS: + return DXGI_FORMAT_R24G8_TYPELESS; + case DXGI_FORMAT_D16_UNORM: + case DXGI_FORMAT_R16_FLOAT: + return DXGI_FORMAT_R16_TYPELESS; + case DXGI_FORMAT_BC1_UNORM: + case DXGI_FORMAT_BC1_UNORM_SRGB: + return DXGI_FORMAT_BC1_TYPELESS; + case DXGI_FORMAT_BC2_UNORM: + case DXGI_FORMAT_BC2_UNORM_SRGB: + return DXGI_FORMAT_BC2_TYPELESS; + case DXGI_FORMAT_BC3_UNORM: + case DXGI_FORMAT_BC3_UNORM_SRGB: + return DXGI_FORMAT_BC3_TYPELESS; + default: + return format; + } +} diff --git a/msvc/source/effect_codegen.hpp b/msvc/source/effect_codegen.hpp new file mode 100644 index 0000000..39d7b90 --- /dev/null +++ b/msvc/source/effect_codegen.hpp @@ -0,0 +1,323 @@ +/** + * Copyright (C) 2014 Patrick Mours. All rights reserved. + * License: https://github.com/crosire/reshade#license + */ + +#pragma once + +#include "effect_lexer.hpp" +#include +#include + +namespace reshadefx +{ + /// + /// A SSA code generation back-end interface for the parser to call into. + /// + class codegen abstract + { + public: + /// + /// Virtual destructor to guarantee that memory of the implementations deriving from this interface is properly destroyed. + /// + virtual ~codegen() {} + + /// + /// Write result of the code generation to the specified . + /// + /// The target module to fill. + virtual void write_result(module &module) = 0; + + public: + /// + /// An opaque ID referring to a SSA value or basic block. + /// + using id = uint32_t; + + /// + /// Define a new struct type. + /// + /// Source location matching this definition (for debugging). + /// The struct type description. + /// New SSA ID of the type. + virtual id define_struct(const location &loc, struct_info &info) = 0; + /// + /// Define a new texture binding. + /// + /// Source location matching this definition (for debugging). + /// The texture description. + /// New SSA ID of the binding. + virtual id define_texture(const location &loc, texture_info &info) = 0; + /// + /// Define a new sampler binding. + /// + /// Source location matching this definition (for debugging). + /// The sampler description. + /// New SSA ID of the binding. + virtual id define_sampler(const location &loc, sampler_info &info) = 0; + /// + /// Define a new uniform variable. + /// + /// Source location matching this definition (for debugging). + /// The uniform variable description. + /// New SSA ID of the variable. + virtual id define_uniform(const location &loc, uniform_info &info) = 0; + /// + /// Define a new variable. + /// + /// Source location matching this definition (for debugging). + /// The variable type. + /// The variable name. + /// true if this variable is in global scope, false otherwise. + /// SSA ID of an optional initializer value. + /// New SSA ID of the variable. + virtual id define_variable(const location &loc, const type &type, std::string name = std::string(), bool global = false, id initializer_value = 0) = 0; + /// + /// Define a new function and its function parameters and make it current. Any code added after this call is added to this function. + /// + /// Source location matching this definition (for debugging). + /// The function description. + /// New SSA ID of the function. + virtual id define_function(const location &loc, function_info &info) = 0; + + /// + /// Define a new effect technique. + /// + /// Source location matching this definition (for debugging). + /// The technique description. + void define_technique(technique_info &info) { _module.techniques.push_back(info); } + /// + /// Make a function a shader entry point. + /// + /// The function to use as entry point. + /// true if this is a pixel shader, false if it is a vertex shader. + virtual void define_entry_point(const function_info &function, bool is_ps) = 0; + + /// + /// Resolve the access chain and add a load operation to the output. + /// + /// The access chain pointing to the variable to load from. + /// New SSA ID with the loaded value. + virtual id emit_load(const expression &chain) = 0; + /// + /// Resolve the access chain and add a store operation to the output. + /// + /// The access chain pointing to the variable to store to. + /// The SSA ID of the value to store. + virtual void emit_store(const expression &chain, id value) = 0; + + /// + /// Create a SSA constant value. + /// + /// The data type of the constant. + /// The actual constant data to convert into a SSA ID. + /// New SSA ID with the constant value. + virtual id emit_constant(const type &type, const constant &data) = 0; + + /// + /// Add an unary operation to the output (built-in operation with one argument). + /// + /// Source location matching this operation (for debugging). + /// The unary operator to use. + /// The data type of the input value. + /// The SSA ID of value to perform the operation on. + /// New SSA ID with the result of the operation. + virtual id emit_unary_op(const location &loc, tokenid op, const type &type, id val) = 0; + /// + /// Add a binary operation to the output (built-in operation with two arguments). + /// + /// Source location matching this operation (for debugging). + /// The binary operator to use. + /// The data type of the result. + /// The data type of the input values. + /// The SSA ID of the value on the left-hand side of the binary operation. + /// The SSA ID of the value on the right-hand side of the binary operation. + /// New SSA ID with the result of the operation. + virtual id emit_binary_op(const location &loc, tokenid op, const type &res_type, const type &type, id lhs, id rhs) = 0; + id emit_binary_op(const location &loc, tokenid op, const type &type, id lhs, id rhs) { return emit_binary_op(loc, op, type, type, lhs, rhs); } + /// + /// Add a ternary operation to the output (built-in operation with three arguments). + /// + /// Source location matching this operation (for debugging). + /// The ternary operator to use. + /// The data type of the input values. + /// The SSA ID of the condition value of the ternary operation. + /// The SSA ID of the first value of the ternary operation. + /// The SSA ID of the second value of the ternary operation. + /// New SSA ID with the result of the operation. + virtual id emit_ternary_op(const location &loc, tokenid op, const type &type, id condition, id true_value, id false_value) = 0; + /// + /// Add a function call to the output. + /// + /// Source location matching this operation (for debugging). + /// The SSA ID of the function to call. + /// The data type of the call result. + /// A list of SSA IDs representing the call arguments. + /// New SSA ID with the result of the function call. + virtual id emit_call(const location &loc, id function, const type &res_type, const std::vector &args) = 0; + /// + /// Add an intrinsic function call to the output. + /// + /// Source location matching this operation (for debugging). + /// The intrinsic to call. + /// The data type of the call result. + /// A list of SSA IDs representing the call arguments. + /// New SSA ID with the result of the function call. + virtual id emit_call_intrinsic(const location &loc, id function, const type &res_type, const std::vector &args) = 0; + /// + /// Add a type constructor call to the output. + /// + /// The data type to construct. + /// A list of SSA IDs representing the scalar constructor arguments. + /// New SSA ID with the constructed value. + virtual id emit_construct(const location &loc, const type &type, const std::vector &args) = 0; + + /// + /// Add a structured branch control flow to the output. + /// + /// Source location matching this branch (for debugging). + /// 0 - default, 1 - flatten, 2 - do not flatten + virtual void emit_if(const location &loc, id condition_value, id condition_block, id true_statement_block, id false_statement_block, unsigned int flags) = 0; + /// + /// Add a branch control flow with a SSA phi operation to the output. + /// + /// Source location matching this branch (for debugging). + /// New SSA ID with the result of the phi operation. + virtual id emit_phi(const location &loc, id condition_value, id condition_block, id true_value, id true_statement_block, id false_value, id false_statement_block, const type &type) = 0; + /// + /// Add a structured loop control flow to the output. + /// + /// Source location matching this loop (for debugging). + /// 0 - default, 1 - unroll, 2 - do not unroll + virtual void emit_loop(const location &loc, id condition_value, id prev_block, id header_block, id condition_block, id loop_block, id continue_block, unsigned int flags) = 0; + /// + /// Add a structured switch control flow to the output. + /// + /// Source location matching this switch (for debugging). + /// 0 - default, 1 - flatten, 2 - do not flatten + virtual void emit_switch(const location &loc, id selector_value, id selector_block, id default_label, const std::vector &case_literal_and_labels, unsigned int flags) = 0; + + /// + /// Returns true if code is currently added to a basic block. + /// + bool is_in_block() const { return _current_block != 0; } + /// + /// Returns true if code is currently added to a function. + /// + virtual bool is_in_function() const { return is_in_block(); } + + /// + /// Create a new basic block. + /// + /// New SSA ID of the basic block. + virtual id create_block() { return make_id(); } + /// + /// Overwrite the current block ID. + /// + /// The ID of the block to make current. + /// The ID of the previous basic block. + virtual id set_block(id id) = 0; + /// + /// Create a new basic block and make it current. + /// + /// The ID of the basic block to create and make current. + virtual void enter_block(id id) = 0; + /// + /// Return from the current basic block and kill the shader invocation. + /// + /// The ID of the current basic block. + virtual id leave_block_and_kill() = 0; + /// + /// Return from the current basic block and hand control flow over to the function call side. + /// + /// Optional SSA ID of a return value. + /// The ID of the current basic block. + virtual id leave_block_and_return(id value = 0) = 0; + /// + /// Diverge the current control flow and enter a switch. + /// + /// SSA ID of the selector value to decide the switch path. + /// The ID of the current basic block. + virtual id leave_block_and_switch(id value, id default_target) = 0; + /// + /// Diverge the current control flow and jump to the specified target block. + /// + /// The ID of the basic block to jump to. + /// True if this corresponds to a loop continue statement. + /// The ID of the current basic block. + virtual id leave_block_and_branch(id target, unsigned int loop_flow = 0) = 0; + /// + /// Diverge the current control flow and jump to one of the specified target blocks, depending on the condition. + /// + /// The SSA ID of a value used to choose which path to take. + /// The ID of the basic block to jump to when the condition is true. + /// The ID of the basic block to jump to when the condition is false. + /// The ID of the current basic block. + virtual id leave_block_and_branch_conditional(id condition, id true_target, id false_target) = 0; + /// + /// Leave the current function. Any code added after this call is added in the global scope. + /// + virtual void leave_function() = 0; + + /// + /// Look up an existing struct definition. + /// + /// The SSA ID of the struct type to find. + /// A reference to the struct description. + struct_info &find_struct(id id) + { + return *std::find_if(_structs.begin(), _structs.end(), + [id](const auto &it) { return it.definition == id; }); + } + /// + /// Look up an existing texture definition. + /// + /// The SSA ID of the texture variable to find. + /// A reference to the texture description. + texture_info &find_texture(id id) + { + return *std::find_if(_module.textures.begin(), _module.textures.end(), + [id](const auto &it) { return it.id == id; }); + } + /// + /// Look up an existing function definition. + /// + /// The SSA ID of the function variable to find. + /// A reference to the function description. + function_info &find_function(id id) + { + return *std::find_if(_functions.begin(), _functions.end(), + [id](const auto &it) { return it->definition == id; })->get(); + } + + protected: + id make_id() { return _next_id++; } + + module _module; + std::vector _structs; + std::vector> _functions; + id _next_id = 1; + id _last_block = 0; + id _current_block = 0; + }; + + /// + /// Create a back-end implementation for GLSL code generation. + /// + /// Whether to append debug information like line directives to the generated code. + /// Whether to convert uniform variables to specialization constants. + codegen *create_codegen_glsl(bool debug_info, bool uniforms_to_spec_constants); + /// + /// Create a back-end implementation for HLSL code generation. + /// + /// The HLSL shader model version (e.g. 30, 41, 50, 60, ...) + /// Whether to append debug information like line directives to the generated code. + /// Whether to convert uniform variables to specialization constants. + codegen *create_codegen_hlsl(unsigned int shader_model, bool debug_info, bool uniforms_to_spec_constants); + /// + /// Create a back-end implementation for SPIR-V code generation. + /// + /// Whether to append debug information like line directives to the generated code. + /// Whether to convert uniform variables to specialization constants. + codegen *create_codegen_spirv(bool debug_info, bool uniforms_to_spec_constants); +} diff --git a/msvc/source/effect_codegen_glsl.cpp b/msvc/source/effect_codegen_glsl.cpp new file mode 100644 index 0000000..9714420 --- /dev/null +++ b/msvc/source/effect_codegen_glsl.cpp @@ -0,0 +1,1365 @@ +/** + * Copyright (C) 2014 Patrick Mours. All rights reserved. + * License: https://github.com/crosire/reshade#license + */ + +#include "effect_parser.hpp" +#include "effect_codegen.hpp" +#include +#include + +using namespace reshadefx; + +class codegen_glsl final : public codegen +{ +public: + codegen_glsl(bool debug_info, bool uniforms_to_spec_constants) + : _debug_info(debug_info), _uniforms_to_spec_constants(uniforms_to_spec_constants) + { + // Create default block and reserve a memory block to avoid frequent reallocations + std::string &block = _blocks.emplace(0, std::string()).first->second; + block.reserve(8192); + } + +private: + enum class naming + { + // After escaping, will be numbered when clashing with another name + general, + // After escaping, name should already be unique, so no additional steps are taken + unique, + // This is a special name that is not modified and should be unique + reserved, + }; + + std::string _ubo_block; + std::unordered_map _names; + std::unordered_map _blocks; + bool _debug_info = false; + bool _uniforms_to_spec_constants = false; + unsigned int _current_ubo_offset = 0; + std::unordered_map _remapped_sampler_variables; + + void write_result(module &module) override + { + module = std::move(_module); + + module.hlsl += + "float hlsl_fmod(float x, float y) { return x - y * trunc(x / y); }\n" + " vec2 hlsl_fmod( vec2 x, vec2 y) { return x - y * trunc(x / y); }\n" + " vec3 hlsl_fmod( vec3 x, vec3 y) { return x - y * trunc(x / y); }\n" + " vec4 hlsl_fmod( vec4 x, vec4 y) { return x - y * trunc(x / y); }\n" + " mat2 hlsl_fmod( mat2 x, mat2 y) { return x - matrixCompMult(y, mat2(trunc(x[0] / y[0]), trunc(x[1] / y[1]))); }\n" + " mat3 hlsl_fmod( mat3 x, mat3 y) { return x - matrixCompMult(y, mat3(trunc(x[0] / y[0]), trunc(x[1] / y[1]), trunc(x[2] / y[2]))); }\n" + " mat4 hlsl_fmod( mat4 x, mat4 y) { return x - matrixCompMult(y, mat4(trunc(x[0] / y[0]), trunc(x[1] / y[1]), trunc(x[2] / y[2]), trunc(x[3] / y[3]))); }\n"; + + if (!_ubo_block.empty()) + module.hlsl += "layout(std140, binding = 0) uniform _Globals {\n" + _ubo_block + "};\n"; + module.hlsl += _blocks.at(0); + } + + template + void write_type(std::string &s, const type &type) const + { + if constexpr (is_decl) + { + if (type.has(type::q_precise)) + s += "precise "; + } + + if constexpr (is_interface) + { + if (type.has(type::q_linear)) + s += "smooth "; + if (type.has(type::q_noperspective)) + s += "noperspective "; + if (type.has(type::q_centroid)) + s += "centroid "; + if (type.has(type::q_nointerpolation)) + s += "flat "; + } + + if constexpr (is_interface || is_param) + { + if (type.has(type::q_inout)) + s += "inout "; + else if (type.has(type::q_in)) + s += "in "; + else if (type.has(type::q_out)) + s += "out "; + } + + switch (type.base) + { + case type::t_void: + s += "void"; + break; + case type::t_bool: + if (type.cols > 1) + s += "mat" + std::to_string(type.rows) + 'x' + std::to_string(type.cols); + else if (type.rows > 1) + s += "bvec" + std::to_string(type.rows); + else + s += "bool"; + break; + case type::t_int: + if (type.cols > 1) + s += "mat" + std::to_string(type.rows) + 'x' + std::to_string(type.cols); + else if (type.rows > 1) + s += "ivec" + std::to_string(type.rows); + else + s += "int"; + break; + case type::t_uint: + if (type.cols > 1) + s += "mat" + std::to_string(type.rows) + 'x' + std::to_string(type.cols); + else if (type.rows > 1) + s += "uvec" + std::to_string(type.rows); + else + s += "uint"; + break; + case type::t_float: + if (type.cols > 1) + s += "mat" + std::to_string(type.rows) + 'x' + std::to_string(type.cols); + else if (type.rows > 1) + s += "vec" + std::to_string(type.rows); + else + s += "float"; + break; + case type::t_struct: + s += id_to_name(type.definition); + break; + case type::t_sampler: + s += "sampler2D"; + break; + default: + assert(false); + } + } + void write_constant(std::string &s, const type &type, const constant &data) const + { + if (type.is_array()) + { + auto elem_type = type; + elem_type.array_length = 0; + + write_type(s, elem_type); + s += "[]("; + + for (int i = 0; i < type.array_length; ++i) + { + write_constant(s, elem_type, i < static_cast(data.array_data.size()) ? data.array_data[i] : constant()); + + if (i < type.array_length - 1) + s += ", "; + } + + s += ')'; + return; + } + + // There can only be numeric constants + assert(type.is_numeric()); + + if (!type.is_scalar()) + { + if (type.is_matrix()) + s += "transpose("; + + write_type(s, type); + s += '('; + } + + for (unsigned int i = 0, components = type.components(); i < components; ++i) + { + switch (type.base) + { + case type::t_bool: + s += data.as_uint[i] ? "true" : "false"; + break; + case type::t_int: + s += std::to_string(data.as_int[i]); + break; + case type::t_uint: + s += std::to_string(data.as_uint[i]) + 'u'; + break; + case type::t_float: + std::string temp(_scprintf("%.8f", data.as_float[i]), '\0'); + sprintf_s(temp.data(), temp.size() + 1, "%.8f", data.as_float[i]); + s += temp; + break; + } + + if (i < components - 1) + s += ", "; + } + + if (!type.is_scalar()) + { + if (type.is_matrix()) + s += ')'; + + s += ')'; + } + } + void write_location(std::string &s, const location &loc) const + { + if (loc.source.empty() || !_debug_info) + return; + + s += "#line " + std::to_string(loc.line) + '\n'; + } + + std::string id_to_name(id id) const + { + if (const auto it = _remapped_sampler_variables.find(id); it != _remapped_sampler_variables.end()) + id = it->second; + assert(id != 0); + if (const auto it = _names.find(id); it != _names.end()) + return it->second; + return '_' + std::to_string(id); + } + + template + void define_name(const id id, std::string name) + { + if constexpr (naming != naming::reserved) + escape_name(name); + if constexpr (naming == naming::general) + if (std::find_if(_names.begin(), _names.end(), [&name](const auto &it) { return it.second == name; }) != _names.end()) + name += '_' + std::to_string(id); + // Remove double underscore symbols from name which can occur due to namespaces but are not allowed in GLSL + for (size_t pos = 0; (pos = name.find("__", pos)) != std::string::npos; pos += 3) + name.replace(pos, 2, "_US"); + _names[id] = std::move(name); + } + + static void escape_name(std::string &name) + { + static const std::unordered_set s_reserverd_names = { + "common", "partition", "input", "output", "active", "filter", "superp", "invariant", + "lowp", "mediump", "highp", "precision", "patch", "subroutine", + "abs", "sign", "all", "any", "sin", "sinh", "cos", "cosh", "tan", "tanh", "asin", "acos", "atan", + "exp", "exp2", "log", "log2", "sqrt", "inversesqrt", "ceil", "floor", "fract", "trunc", "round", + "radians", "degrees", "length", "normalize", "transpose", "determinant", "intBitsToFloat", "uintBitsToFloat", + "floatBitsToInt", "floatBitsToUint", "matrixCompMult", "not", "lessThan", "greaterThan", "lessThanEqual", + "greaterThanEqual", "equal", "notEqual", "dot", "cross", "distance", "pow", "modf", "frexp", "ldexp", + "min", "max", "step", "reflect", "texture", "textureOffset", "fma", "mix", "clamp", "smoothstep", "refract", + "faceforward", "textureLod", "textureLodOffset", "texelFetch", "main" + }; + + if (name.compare(0, 3, "gl_") == 0 || s_reserverd_names.count(name)) + name += '_'; + } + + static void increase_indentation_level(std::string &block) + { + if (block.empty()) + return; + + for (size_t pos = 0; (pos = block.find("\n\t", pos)) != std::string::npos; pos += 3) + block.replace(pos, 2, "\n\t\t"); + + block.insert(block.begin(), '\t'); + } + + id define_struct(const location &loc, struct_info &info) override + { + info.definition = make_id(); + define_name(info.definition, info.unique_name); + + _structs.push_back(info); + + std::string &code = _blocks.at(_current_block); + + write_location(code, loc); + + code += "struct " + id_to_name(info.definition) + "\n{\n"; + + for (const auto &member : info.member_list) + { + code += '\t'; + write_type(code, member.type); // GLSL does not allow interpolation attributes on struct members + code += ' ' + member.name; + if (member.type.is_array()) + code += '[' + std::to_string(member.type.array_length) + ']'; + code += ";\n"; + } + + if (info.member_list.empty()) + code += "float _dummy;\n"; + + code += "};\n"; + + return info.definition; + } + id define_texture(const location &, texture_info &info) override + { + info.id = make_id(); + + _module.textures.push_back(info); + + return info.id; + } + id define_sampler(const location &loc, sampler_info &info) override + { + info.id = make_id(); + info.binding = _module.num_sampler_bindings++; + + define_name(info.id, info.unique_name); + + _module.samplers.push_back(info); + + std::string &code = _blocks.at(_current_block); + + write_location(code, loc); + + code += "layout(binding = " + std::to_string(info.binding) + ") uniform sampler2D " + id_to_name(info.id) + ";\n"; + + return info.id; + } + id define_uniform(const location &loc, uniform_info &info) override + { + const id res = make_id(); + + define_name(res, "_Globals_" + info.name); + + if (_uniforms_to_spec_constants && info.has_initializer_value) + { + std::string &code = _blocks.at(_current_block); + + write_location(code, loc); + + code += "const "; + write_type(code, info.type); + code += ' ' + id_to_name(res) + " = "; + if (!info.type.is_scalar()) + write_type(code, info.type); + code += "(SPEC_CONSTANT_" + info.name + ");\n"; + + _module.spec_constants.push_back(info); + } + else + { + // GLSL specification on std140 layout: + // 1. If the member is a scalar consuming N basic machine units, the base alignment is N. + // 2. If the member is a two- or four-component vector with components consuming N basic machine units, the base alignment is 2N or 4N, respectively. + // 3. If the member is a three-component vector with components consuming N basic machine units, the base alignment is 4N. + const unsigned int size = 4 * info.type.rows * info.type.cols * std::max(1, info.type.array_length); + const unsigned int alignment = 4 * (info.type.rows == 3 ? 4 : info.type.rows) * info.type.cols * std::max(1, info.type.array_length); + + info.size = size; + info.offset = (_current_ubo_offset % alignment != 0) ? _current_ubo_offset + alignment - _current_ubo_offset % alignment : _current_ubo_offset; + _current_ubo_offset = info.offset + info.size; + + write_location(_ubo_block, loc); + + _ubo_block += '\t'; + write_type(_ubo_block, info.type); + _ubo_block += ' ' + id_to_name(res) + ";\n"; + + _module.uniforms.push_back(info); + } + + return res; + } + id define_variable(const location &loc, const type &type, std::string name, bool global, id initializer_value) override + { + const id res = make_id(); + + // GLSL does not allow local sampler variables, so try to remap those + if (!global && type.is_sampler()) + return (_remapped_sampler_variables[res] = 0), res; + + if (!name.empty()) + define_name(res, name); + + std::string &code = _blocks.at(_current_block); + + write_location(code, loc); + + if (!global) + code += '\t'; + + write_type(code, type); + code += ' ' + id_to_name(res); + + if (type.is_array()) + code += '[' + std::to_string(type.array_length) + ']'; + + if (initializer_value != 0) + code += " = " + id_to_name(initializer_value); + + code += ";\n"; + + return res; + } + id define_function(const location &loc, function_info &info) override + { + return define_function(loc, info, false); + } + id define_function(const location &loc, function_info &info, bool is_entry_point) + { + info.definition = make_id(); + + if (is_entry_point) + define_name(info.definition, "main"); + else + define_name(info.definition, info.unique_name); + + std::string &code = _blocks.at(_current_block); + + write_location(code, loc); + + write_type(code, info.return_type); + code += ' ' + id_to_name(info.definition) + '('; + + assert(info.parameter_list.empty() || !is_entry_point); + + for (size_t i = 0, num_params = info.parameter_list.size(); i < num_params; ++i) + { + auto ¶m = info.parameter_list[i]; + + param.definition = make_id(); + define_name(param.definition, param.name); + + code += '\n'; + write_location(code, param.location); + code += '\t'; + write_type(code, param.type); // GLSL does not allow interpolation attributes on function parameters + code += ' ' + id_to_name(param.definition); + + if (param.type.is_array()) + code += '[' + std::to_string(param.type.array_length) + ']'; + + if (i < num_params - 1) + code += ','; + } + + code += ")\n"; + + _functions.push_back(std::make_unique(info)); + + return info.definition; + } + void define_entry_point(const function_info &func, bool is_ps) override + { + if (const auto it = std::find_if(_module.entry_points.begin(), _module.entry_points.end(), + [&func](const auto &ep) { return ep.first == func.unique_name; }); it != _module.entry_points.end()) + return; + + _module.entry_points.push_back({ func.unique_name, is_ps }); + + _blocks.at(0) += "#ifdef ENTRY_POINT_" + func.unique_name + '\n'; + + function_info entry_point; + entry_point.return_type = { type::t_void }; + + const auto is_color_semantic = [](const std::string &semantic) { return semantic.compare(0, 9, "SV_TARGET") == 0 || semantic.compare(0, 5, "COLOR") == 0; }; + + const auto escape_name_with_builtins = [this, is_ps](std::string name, const std::string &semantic) -> std::string + { + if (semantic == "SV_VERTEXID" || semantic == "VERTEXID") + return "gl_VertexID"; + else if (semantic == "SV_POSITION" || semantic == "POSITION" || semantic == "VPOS") + return is_ps ? "gl_FragCoord" : "gl_Position"; + else if (semantic == "SV_DEPTH" || semantic == "DEPTH") + return "gl_FragDepth"; + + escape_name(name); + return name; + }; + const auto visit_shader_param = [this, is_ps, &escape_name_with_builtins](type type, unsigned int quals, const std::string &name, const std::string &semantic) { + type.qualifiers = quals; + + // OpenGL does not allow varying of type boolean + if (type.base == type::t_bool) + type.base = type::t_float; + + std::string &code = _blocks.at(_current_block); + + unsigned long location = 0; + + for (int i = 0, array_length = std::max(1, type.array_length); i < array_length; ++i) + { + if (!escape_name_with_builtins(std::string(), semantic).empty()) + continue; + else if (semantic.compare(0, 5, "COLOR") == 0) + location = strtoul(semantic.c_str() + 5, nullptr, 10); + else if (semantic.compare(0, 8, "TEXCOORD") == 0) + location = strtoul(semantic.c_str() + 8, nullptr, 10) + 1; + else if (semantic.compare(0, 9, "SV_TARGET") == 0) + location = strtoul(semantic.c_str() + 9, nullptr, 10); + + code += "layout(location = " + std::to_string(location + i) + ") "; + write_type(code, type); + code += ' ' + name; + if (type.is_array()) + code += std::to_string(i); + code += ";\n"; + } + }; + + // Translate function parameters to input/output variables + if (func.return_type.is_struct()) + for (const auto &member : find_struct(func.return_type.definition).member_list) + visit_shader_param(member.type, type::q_out, "_return_" + member.name, member.semantic); + else if (!func.return_type.is_void()) + visit_shader_param(func.return_type, type::q_out, "_return", func.return_semantic); + + for (const auto ¶m : func.parameter_list) + if (param.type.is_struct()) + for (const auto &member : find_struct(param.type.definition).member_list) + visit_shader_param(member.type, param.type.qualifiers | member.type.qualifiers, "_param_" + param.name + '_' + member.name, member.semantic); + else + visit_shader_param(param.type, param.type.qualifiers, "_param_" + param.name, param.semantic); + + define_function({}, entry_point, true); + enter_block(create_block()); + + std::string &code = _blocks.at(_current_block); + + // Handle input parameters + for (const auto ¶m : func.parameter_list) + { + for (int i = 0, array_length = std::max(1, param.type.array_length); i < array_length; i++) + { + // Build struct from separate member input variables + if (param.type.is_struct()) + { + code += '\t'; + write_type(code, param.type); + code += " _param_" + param.name; + if (param.type.is_array()) + code += std::to_string(i); + code += " = "; + write_type(code, param.type); + code += '('; + + for (const auto &member : find_struct(param.type.definition).member_list) + code += escape_name_with_builtins("_param_" + param.name + '_' + member.name + (param.type.is_array() ? std::to_string(i) : std::string()), member.semantic) + ", "; + + code.pop_back(); + code.pop_back(); + + code += ");\n"; + } + } + + if (param.type.is_array()) + { + code += '\t'; + write_type(code, param.type); + code += " _param_" + param.name + "[] = "; + write_type(code, param.type); + code += "[]("; + + for (int i = 0; i < param.type.array_length; ++i) + { + code += "_param_" + param.name + std::to_string(i); + if (i < param.type.array_length - 1) + code += ", "; + } + + code += ");\n"; + } + } + + code += '\t'; + // Structs cannot be output variables, so have to write to a temporary first and then output each member separately + if (func.return_type.is_struct()) + write_type(code, func.return_type), code += " _return = "; + // All other output types can write to the output variable directly + else if (!func.return_type.is_void()) + code += "_return = "; + + // Call the function this entry point refers to + code += id_to_name(func.definition) + '('; + + for (size_t i = 0, num_params = func.parameter_list.size(); i < num_params; ++i) + { + code += escape_name_with_builtins("_param_" + func.parameter_list[i].name, func.parameter_list[i].semantic); + + if (i < num_params - 1) + code += ", "; + } + + code += ");\n"; + + // Handle output parameters + for (const auto ¶m : func.parameter_list) + { + if (!param.type.has(type::q_out)) + continue; + + for (int i = 0; i < param.type.array_length; i++) + code += "\t_param_" + param.name + std::to_string(i) + " = _param_" + param.name + '[' + std::to_string(i) + "];\n"; + + for (int i = 0; i < std::max(1, param.type.array_length); i++) + if (param.type.is_struct()) + for (const auto &member : find_struct(param.type.definition).member_list) + if (param.type.is_array()) + code += "\t_param_" + param.name + '_' + member.name + std::to_string(i) + " = _param_" + param.name + '.' + member.name + '[' + std::to_string(i) + "];\n"; + else + code += "\t_param_" + param.name + '_' + member.name + " = _param_" + param.name + '.' + member.name + ";\n"; + } + + // Handle return struct output variables + if (func.return_type.is_struct()) + for (const auto &member : find_struct(func.return_type.definition).member_list) + code += '\t' + escape_name_with_builtins("_return_" + member.name, member.semantic) + " = _return." + member.name + ";\n"; + + leave_block_and_return(0); + leave_function(); + + _blocks.at(0) += "#endif\n"; + } + + id emit_load(const expression &exp) override + { + if (exp.is_constant) + return emit_constant(exp.type, exp.constant); + else if (exp.chain.empty()) // Can refer to values without access chain directly + return exp.base; + + const id res = make_id(); + + std::string &code = _blocks.at(_current_block); + + write_location(code, exp.location); + + code += '\t'; + write_type(code, exp.type); + code += ' ' + id_to_name(res); + + if (exp.type.is_array()) + code += '[' + std::to_string(exp.type.array_length) + ']'; + + code += " = "; + + std::string newcode = id_to_name(exp.base); + + for (const auto &op : exp.chain) + { + switch (op.op) + { + case expression::operation::op_cast: + { std::string type; write_type(type, op.to); + newcode = type + '(' + newcode + ')'; } + break; + case expression::operation::op_member: + newcode += '.'; + newcode += find_struct(op.from.definition).member_list[op.index].name; + break; + case expression::operation::op_dynamic_index: + newcode += '[' + id_to_name(op.index) + ']'; + break; + case expression::operation::op_constant_index: + newcode += '[' + std::to_string(op.index) + ']'; + break; + case expression::operation::op_swizzle: + if (op.from.is_matrix()) + { + if (op.swizzle[1] < 0) + { + const unsigned int row = op.swizzle[0] % 4; + const unsigned int col = (op.swizzle[0] - row) / 4; + + newcode += '[' + std::to_string(row) + "][" + std::to_string(col) + ']'; + } + else + { + // TODO: Implement matrix to vector swizzles + assert(false); + } + } + else + { + newcode += '.'; + for (unsigned int i = 0; i < 4 && op.swizzle[i] >= 0; ++i) + newcode += "xyzw"[op.swizzle[i]]; + } + break; + } + } + + code += newcode; + code += ";\n"; + + return res; + } + void emit_store(const expression &exp, id value) override + { + if (const auto it = _remapped_sampler_variables.find(exp.base); it != _remapped_sampler_variables.end()) + { + assert(it->second == 0); + it->second = value; + return; + } + + std::string &code = _blocks.at(_current_block); + + write_location(code, exp.location); + + code += '\t' + id_to_name(exp.base); + + for (const auto &op : exp.chain) + { + switch (op.op) + { + case expression::operation::op_member: + code += '.'; + code += find_struct(op.from.definition).member_list[op.index].name; + break; + case expression::operation::op_dynamic_index: + code += '[' + id_to_name(op.index) + ']'; + break; + case expression::operation::op_constant_index: + code += '[' + std::to_string(op.index) + ']'; + break; + case expression::operation::op_swizzle: + if (op.from.is_matrix()) + { + if (op.swizzle[1] < 0) + { + const unsigned int row = op.swizzle[0] % 4; + const unsigned int col = (op.swizzle[0] - row) / 4; + + code += '[' + std::to_string(row) + "][" + std::to_string(col) + ']'; + } + else + { + // TODO: Implement matrix to vector swizzles + assert(false); + } + } + else + { + code += '.'; + for (unsigned int i = 0; i < 4 && op.swizzle[i] >= 0; ++i) + code += "xyzw"[op.swizzle[i]]; + } + break; + } + } + + code += " = " + id_to_name(value) + ";\n"; + } + + id emit_constant(const type &type, const constant &data) override + { + const id res = make_id(); + + std::string &code = _blocks.at(_current_block); + + // Struct initialization is not supported right now + if (type.is_struct()) + { + code += '\t'; + write_type(code, type); + code += ' ' + id_to_name(res) + ";\n"; + return res; + } + + code += "\tconst "; + write_type(code, type); + code += ' ' + id_to_name(res); + + if (type.is_array()) + code += '[' + std::to_string(type.array_length) + ']'; + + code += " = "; + write_constant(code, type, data); + code += ";\n"; + + return res; + } + + id emit_unary_op(const location &loc, tokenid op, const type &res_type, id val) override + { + const id res = make_id(); + + std::string &code = _blocks.at(_current_block); + + write_location(code, loc); + + code += '\t'; + write_type(code, res_type); + code += ' ' + id_to_name(res) + " = "; + + switch (op) + { + case tokenid::minus: + code += '-'; + break; + case tokenid::tilde: + code += '~'; + break; + case tokenid::exclaim: + if (res_type.is_vector()) + code += "not"; + else + code += "!bool"; + break; + default: + assert(false); + } + + code += '(' + id_to_name(val) + ");\n"; + + return res; + } + id emit_binary_op(const location &loc, tokenid op, const type &res_type, const type &type, id lhs, id rhs) override + { + const id res = make_id(); + + std::string &code = _blocks.at(_current_block); + + write_location(code, loc); + + code += '\t'; + write_type(code, res_type); + code += ' ' + id_to_name(res) + " = "; + + std::string intrinsic, operator_code; + + switch (op) + { + case tokenid::plus: + case tokenid::plus_plus: + case tokenid::plus_equal: + operator_code = '+'; + break; + case tokenid::minus: + case tokenid::minus_minus: + case tokenid::minus_equal: + operator_code = '-'; + break; + case tokenid::star: + case tokenid::star_equal: + if (type.is_matrix()) + intrinsic = "matrixCompMult"; + else + operator_code = '*'; + break; + case tokenid::slash: + case tokenid::slash_equal: + operator_code = '/'; + break; + case tokenid::percent: + case tokenid::percent_equal: + if (type.is_floating_point()) + intrinsic = "hlsl_fmod"; + else + operator_code = '%'; + break; + case tokenid::caret: + case tokenid::caret_equal: + operator_code = '^'; + break; + case tokenid::pipe: + case tokenid::pipe_equal: + operator_code = '|'; + break; + case tokenid::ampersand: + case tokenid::ampersand_equal: + operator_code = '&'; + break; + case tokenid::less_less: + case tokenid::less_less_equal: + operator_code = "<<"; + break; + case tokenid::greater_greater: + case tokenid::greater_greater_equal: + operator_code = ">>"; + break; + case tokenid::pipe_pipe: + operator_code = "||"; + break; + case tokenid::ampersand_ampersand: + operator_code = "&&"; + break; + case tokenid::less: + if (type.is_vector()) + intrinsic = "lessThan"; + else + operator_code = '<'; + break; + case tokenid::less_equal: + if (type.is_vector()) + intrinsic = "lessThanEqual"; + else + operator_code = "<="; + break; + case tokenid::greater: + if (type.is_vector()) + intrinsic = "greaterThan"; + else + operator_code = '>'; + break; + case tokenid::greater_equal: + if (type.is_vector()) + intrinsic = "greaterThanEqual"; + else + operator_code = ">="; + break; + case tokenid::equal_equal: + if (type.is_vector()) + intrinsic = "equal"; + else + operator_code = "=="; + break; + case tokenid::exclaim_equal: + if (type.is_vector()) + intrinsic = "notEqual"; + else + operator_code = "!="; + break; + default: + assert(false); + } + + if (!intrinsic.empty()) + code += intrinsic + '(' + id_to_name(lhs) + ", " + id_to_name(rhs) + ')'; + else + code += id_to_name(lhs) + ' ' + operator_code + ' ' + id_to_name(rhs); + + code += ";\n"; + + return res; + } + id emit_ternary_op(const location &loc, tokenid op, const type &res_type, id condition, id true_value, id false_value) override + { + assert(op == tokenid::question); + + const id res = make_id(); + + std::string &code = _blocks.at(_current_block); + + write_location(code, loc); + + code += '\t'; + write_type(code, res_type); + code += ' ' + id_to_name(res); + + if (res_type.is_array()) + code += '[' + std::to_string(res_type.array_length) + ']'; + + code += " = "; + + // GLSL requires the selection first expression to be a scalar boolean + if (!res_type.is_scalar()) + code += "all(" + id_to_name(condition) + ')'; + else + code += id_to_name(condition); + + code += " ? " + id_to_name(true_value) + " : " + id_to_name(false_value) + ";\n"; + + return res; + } + id emit_call(const location &loc, id function, const type &res_type, const std::vector &args) override + { + for (const auto &arg : args) + assert(arg.chain.empty() && arg.base != 0); + + const id res = make_id(); + + std::string &code = _blocks.at(_current_block); + + write_location(code, loc); + + code += '\t'; + + if (!res_type.is_void()) + { + write_type(code, res_type); + code += ' ' + id_to_name(res); + + if (res_type.is_array()) + code += '[' + std::to_string(res_type.array_length) + ']'; + + code += " = "; + } + + code += id_to_name(function) + '('; + + for (size_t i = 0, num_args = args.size(); i < num_args; ++i) + { + code += id_to_name(args[i].base); + + if (i < num_args - 1) + code += ", "; + } + + code += ");\n"; + + return res; + } + id emit_call_intrinsic(const location &loc, id intrinsic, const type &res_type, const std::vector &args) override + { + for (const auto &arg : args) + assert(arg.chain.empty() && arg.base != 0); + + const id res = make_id(); + + std::string &code = _blocks.at(_current_block); + + write_location(code, loc); + + code += '\t'; + + if (!res_type.is_void()) + { + write_type(code, res_type); + code += ' ' + id_to_name(res) + " = "; + } + + enum + { +#define IMPLEMENT_INTRINSIC_GLSL(name, i, code) name##i, +#include "effect_symbol_table_intrinsics.inl" + }; + + switch (intrinsic) + { +#define IMPLEMENT_INTRINSIC_GLSL(name, i, code) case name##i: code break; +#include "effect_symbol_table_intrinsics.inl" + default: + assert(false); + } + + code += ";\n"; + + return res; + } + id emit_construct(const location &loc, const type &type, const std::vector &args) override + { + for (const auto &arg : args) + assert((arg.type.is_scalar() || type.is_array()) && arg.chain.empty() && arg.base != 0); + + const id res = make_id(); + + std::string &code = _blocks.at(_current_block); + + write_location(code, loc); + + code += '\t'; + write_type(code, type); + code += ' ' + id_to_name(res); + + if (type.is_array()) + code += '[' + std::to_string(type.array_length) + ']'; + + code += " = "; + + if (!type.is_array() && type.is_matrix()) + code += "transpose("; + + write_type(code, type); + + if (type.is_array()) + code += '[' + std::to_string(type.array_length) + ']'; + + code += '('; + + for (size_t i = 0, num_args = args.size(); i < num_args; ++i) + { + code += id_to_name(args[i].base); + + if (i < num_args - 1) + code += ", "; + } + + if (!type.is_array() && type.is_matrix()) + code += ')'; + + code += ");\n"; + + return res; + } + + void emit_if(const location &loc, id condition_value, id condition_block, id true_statement_block, id false_statement_block, unsigned int) override + { + assert(condition_value != 0 && condition_block != 0 && true_statement_block != 0 && false_statement_block != 0); + + std::string &code = _blocks.at(_current_block); + + std::string &true_statement_data = _blocks.at(true_statement_block); + std::string &false_statement_data = _blocks.at(false_statement_block); + + increase_indentation_level(true_statement_data); + increase_indentation_level(false_statement_data); + + code += _blocks.at(condition_block); + + write_location(code, loc); + + code += "\tif (" + id_to_name(condition_value) + ")\n\t{\n"; + code += true_statement_data; + code += "\t}\n"; + + if (!false_statement_data.empty()) + { + code += "\telse\n\t{\n"; + code += false_statement_data; + code += "\t}\n"; + } + + // Remove consumed blocks to save memory + _blocks.erase(condition_block); + _blocks.erase(true_statement_block); + _blocks.erase(false_statement_block); + } + id emit_phi(const location &loc, id condition_value, id condition_block, id true_value, id true_statement_block, id false_value, id false_statement_block, const type &type) override + { + assert(condition_value != 0 && condition_block != 0 && true_value != 0 && true_statement_block != 0 && false_value != 0 && false_statement_block != 0); + + std::string &code = _blocks.at(_current_block); + + std::string &true_statement_data = _blocks.at(true_statement_block); + std::string &false_statement_data = _blocks.at(false_statement_block); + + increase_indentation_level(true_statement_data); + increase_indentation_level(false_statement_data); + + const id res = make_id(); + + code += _blocks.at(condition_block); + + code += '\t'; + write_type(code, type); + code += ' ' + id_to_name(res) + ";\n"; + + write_location(code, loc); + + code += "\tif (" + id_to_name(condition_value) + ")\n\t{\n"; + code += (true_statement_block != condition_block ? true_statement_data : std::string()); + code += "\t\t" + id_to_name(res) + " = " + id_to_name(true_value) + ";\n"; + code += "\t}\n\telse\n\t{\n"; + code += (false_statement_block != condition_block ? false_statement_data : std::string()); + code += "\t\t" + id_to_name(res) + " = " + id_to_name(false_value) + ";\n"; + code += "\t}\n"; + + // Remove consumed blocks to save memory + _blocks.erase(condition_block); + _blocks.erase(true_statement_block); + _blocks.erase(false_statement_block); + + return res; + } + void emit_loop(const location &loc, id condition_value, id prev_block, id header_block, id condition_block, id loop_block, id continue_block, unsigned int) override + { + assert(condition_value != 0 && prev_block != 0 && header_block != 0 && loop_block != 0 && continue_block != 0); + + std::string &code = _blocks.at(_current_block); + + std::string &loop_data = _blocks.at(loop_block); + std::string &continue_data = _blocks.at(continue_block); + + increase_indentation_level(loop_data); + increase_indentation_level(loop_data); + increase_indentation_level(continue_data); + + code += _blocks.at(prev_block); + + if (condition_block == 0) + code += "\tbool " + id_to_name(condition_value) + ";\n"; + else + code += _blocks.at(condition_block); + + write_location(code, loc); + + code += '\t'; + + if (condition_block == 0) + { + // Convert variable initializer to assignment statement + auto pos_assign = continue_data.rfind(id_to_name(condition_value)); + auto pos_prev_assign = continue_data.rfind('\t', pos_assign); + continue_data.erase(pos_prev_assign + 1, pos_assign - pos_prev_assign - 1); + + // We need to add the continue block to all "continue" statements as well + const std::string continue_id = "__CONTINUE__" + std::to_string(continue_block); + for (size_t offset = 0; (offset = loop_data.find(continue_id, offset)) != std::string::npos; offset += continue_data.size()) + loop_data.replace(offset, continue_id.size(), continue_data); + + code += "do\n\t{\n\t\t{\n"; + code += loop_data; // Encapsulate loop body into another scope, so not to confuse any local variables with the current iteration variable accessed in the continue block below + code += "\t\t}\n"; + code += continue_data; + code += "\t}\n\twhile (" + id_to_name(condition_value) + ");\n"; + } + else + { + std::string &condition_data = _blocks.at(condition_block); + + increase_indentation_level(condition_data); + + // Convert variable initializer to assignment statement + auto pos_assign = condition_data.rfind(id_to_name(condition_value)); + auto pos_prev_assign = condition_data.rfind('\t', pos_assign); + condition_data.erase(pos_prev_assign + 1, pos_assign - pos_prev_assign - 1); + + const std::string continue_id = "__CONTINUE__" + std::to_string(continue_block); + for (size_t offset = 0; (offset = loop_data.find(continue_id, offset)) != std::string::npos; offset += continue_data.size()) + loop_data.replace(offset, continue_id.size(), continue_data + condition_data); + + code += "while (" + id_to_name(condition_value) + ")\n\t{\n\t\t{\n"; + code += loop_data; + code += "\t\t}\n"; + code += continue_data; + code += condition_data; + code += "\t}\n"; + + _blocks.erase(condition_block); + } + + // Remove consumed blocks to save memory + _blocks.erase(prev_block); + _blocks.erase(header_block); + _blocks.erase(loop_block); + _blocks.erase(continue_block); + } + void emit_switch(const location &loc, id selector_value, id selector_block, id default_label, const std::vector &case_literal_and_labels, unsigned int) override + { + assert(selector_value != 0 && selector_block != 0 && default_label != 0); + + std::string &code = _blocks.at(_current_block); + + code += _blocks.at(selector_block); + + write_location(code, loc); + + code += "\tswitch (" + id_to_name(selector_value) + ")\n\t{\n"; + + for (size_t i = 0; i < case_literal_and_labels.size(); i += 2) + { + assert(case_literal_and_labels[i + 1] != 0); + + std::string &case_data = _blocks.at(case_literal_and_labels[i + 1]); + + increase_indentation_level(case_data); + + code += "\tcase " + std::to_string(case_literal_and_labels[i]) + ": {\n"; + code += case_data; + code += "\t}\n"; + } + + if (default_label != _current_block) + { + std::string &default_data = _blocks.at(default_label); + + increase_indentation_level(default_data); + + code += "\tdefault: {\n"; + code += default_data; + code += "\t}\n"; + + _blocks.erase(default_label); + } + + code += "\t}\n"; + + // Remove consumed blocks to save memory + _blocks.erase(selector_block); + for (size_t i = 0; i < case_literal_and_labels.size(); i += 2) + _blocks.erase(case_literal_and_labels[i + 1]); + } + + id create_block() override + { + const id res = make_id(); + + std::string &block = _blocks.emplace(res, std::string()).first->second; + // Reserve a decently big enough memory block to avoid frequent reallocations + block.reserve(4096); + + return res; + } + id set_block(id id) override + { + _last_block = _current_block; + _current_block = id; + + return _last_block; + } + void enter_block(id id) override + { + _current_block = id; + } + id leave_block_and_kill() override + { + if (!is_in_block()) + return 0; + + std::string &code = _blocks.at(_current_block); + + code += "\tdiscard;\n"; + + return set_block(0); + } + id leave_block_and_return(id value) override + { + if (!is_in_block()) + return 0; + + // Skip implicit return statement + if (!_functions.back()->return_type.is_void() && value == 0) + return set_block(0); + + std::string &code = _blocks.at(_current_block); + + code += "\treturn"; + + if (value != 0) + code += ' ' + id_to_name(value); + + code += ";\n"; + + return set_block(0); + } + id leave_block_and_switch(id, id) override + { + if (!is_in_block()) + return _last_block; + + return set_block(0); + } + id leave_block_and_branch(id target, unsigned int loop_flow) override + { + if (!is_in_block()) + return _last_block; + + std::string &code = _blocks.at(_current_block); + + switch (loop_flow) + { + case 1: + code += "\tbreak;\n"; + break; + case 2: // Keep track of continue target block, so we can insert its code here later + code += "__CONTINUE__" + std::to_string(target) + "\tcontinue;\n"; + break; + } + + return set_block(0); + } + id leave_block_and_branch_conditional(id, id, id) override + { + if (!is_in_block()) + return _last_block; + + return set_block(0); + } + void leave_function() override + { + assert(_last_block != 0); + + _blocks.at(0) += "{\n" + _blocks.at(_last_block) + "}\n"; + } +}; + +codegen *reshadefx::create_codegen_glsl(bool debug_info, bool uniforms_to_spec_constants) +{ + return new codegen_glsl(debug_info, uniforms_to_spec_constants); +} diff --git a/msvc/source/effect_codegen_hlsl.cpp b/msvc/source/effect_codegen_hlsl.cpp new file mode 100644 index 0000000..f569d92 --- /dev/null +++ b/msvc/source/effect_codegen_hlsl.cpp @@ -0,0 +1,1355 @@ +/** + * Copyright (C) 2014 Patrick Mours. All rights reserved. + * License: https://github.com/crosire/reshade#license + */ + +#include "effect_parser.hpp" +#include "effect_codegen.hpp" +#include + +using namespace reshadefx; + +class codegen_hlsl final : public codegen +{ +public: + codegen_hlsl(unsigned int shader_model, bool debug_info, bool uniforms_to_spec_constants) + : _shader_model(shader_model), _debug_info(debug_info), _uniforms_to_spec_constants(uniforms_to_spec_constants) + { + // Create default block and reserve a memory block to avoid frequent reallocations + std::string &block = _blocks.emplace(0, std::string()).first->second; + block.reserve(8192); + } + +private: + enum class naming + { + // Will be numbered when clashing with another name + general, + // Name should already be unique, so no additional steps are taken + unique, + }; + + std::string _cbuffer_block; + std::string _current_location; + std::unordered_map _names; + std::unordered_map _blocks; + bool _debug_info = false; + bool _uniforms_to_spec_constants = false; + unsigned int _shader_model = 0; + unsigned int _current_cbuffer_size = 0; + + void write_result(module &module) override + { + module = std::move(_module); + + if (_shader_model >= 40) + { + module.hlsl += "struct __sampler2D { Texture2D t; SamplerState s; };\n"; + + if (!_cbuffer_block.empty()) + module.hlsl += "cbuffer _Globals {\n" + _cbuffer_block + "};\n"; + } + else + { + module.hlsl += "struct __sampler2D { sampler2D s; float2 pixelsize; };\nuniform float2 __TEXEL_SIZE__ : register(c255);\n"; + + if (!_cbuffer_block.empty()) + module.hlsl += _cbuffer_block; + } + + module.hlsl += _blocks.at(0); + } + + template + void write_type(std::string &s, const type &type) const + { + if constexpr (is_decl) + { + if (type.has(type::q_precise)) + s += "precise "; + } + + if constexpr (is_param) + { + if (type.has(type::q_linear)) + s += "linear "; + if (type.has(type::q_noperspective)) + s += "noperspective "; + if (type.has(type::q_centroid)) + s += "centroid "; + if (type.has(type::q_nointerpolation)) + s += "nointerpolation "; + + if (type.has(type::q_inout)) + s += "inout "; + else if (type.has(type::q_in)) + s += "in "; + else if (type.has(type::q_out)) + s += "out "; + } + + switch (type.base) + { + case type::t_void: + s += "void"; + break; + case type::t_bool: + s += "bool"; + break; + case type::t_int: + s += "int"; + break; + case type::t_uint: + // In shader model 3, uints can only be used with known-positive values, so use ints instead + s += _shader_model >= 40 ? "uint" : "int"; + break; + case type::t_float: + s += "float"; + break; + case type::t_struct: + s += id_to_name(type.definition); + break; + case type::t_sampler: + s += "__sampler2D"; + break; + default: + assert(false); + } + + if (type.rows > 1) + s += std::to_string(type.rows); + if (type.cols > 1) + s += 'x' + std::to_string(type.cols); + } + void write_constant(std::string &s, const type &type, const constant &data) const + { + if (type.is_array()) + { + auto elem_type = type; + elem_type.array_length = 0; + + s += "{ "; + + for (int i = 0; i < type.array_length; ++i) + { + write_constant(s, elem_type, i < static_cast(data.array_data.size()) ? data.array_data[i] : constant()); + + if (i < type.array_length - 1) + s += ", "; + } + + s += " }"; + return; + } + + if (type.is_struct()) + { + assert(data.as_uint[0] == 0); + + s += '(' + id_to_name(type.definition) + ")0"; + return; + } + + // There can only be numeric constants + assert(type.is_numeric()); + + if (!type.is_scalar()) + { + write_type(s, type); + s += '('; + } + + for (unsigned int i = 0, components = type.components(); i < components; ++i) + { + switch (type.base) + { + case type::t_bool: + s += data.as_uint[i] ? "true" : "false"; + break; + case type::t_int: + s += std::to_string(data.as_int[i]); + break; + case type::t_uint: + s += std::to_string(data.as_uint[i]); + break; + case type::t_float: + std::string temp(_scprintf("%.8f", data.as_float[i]), '\0'); + sprintf_s(temp.data(), temp.size() + 1, "%.8f", data.as_float[i]); + s += temp; + break; + } + + if (i < components - 1) + s += ", "; + } + + if (!type.is_scalar()) + { + s += ')'; + } + } + template + void write_location(std::string &s, const location &loc) + { + if (loc.source.empty() || !_debug_info) + return; + + s += "#line " + std::to_string(loc.line); + + // Avoid writing the file name every time to reduce output text size + if constexpr (force_source) + { + s += " \"" + loc.source + '\"'; + } + else if (loc.source != _current_location) + { + s += " \"" + loc.source + '\"'; + + _current_location = loc.source; + } + + s += '\n'; + } + + std::string convert_semantic(const std::string &semantic) const + { + if (_shader_model < 40) + { + if (semantic == "SV_VERTEXID" || semantic == "VERTEXID") + return "TEXCOORD0 /* VERTEXID */"; + else if (semantic == "SV_POSITION") + return "POSITION"; + else if (semantic.compare(0, 9, "SV_TARGET") == 0) + return "COLOR" + semantic.substr(9); + else if (semantic == "SV_DEPTH") + return "DEPTH"; + } + else + { + if (semantic == "VERTEXID") + return "SV_VERTEXID"; + else if (semantic == "POSITION" || semantic == "VPOS") + return "SV_POSITION"; + else if (semantic.compare(0, 5, "COLOR") == 0) + return "SV_TARGET" + semantic.substr(5); + else if (semantic == "DEPTH") + return "SV_DEPTH"; + } + + return semantic; + } + + std::string id_to_name(id id) const + { + if (const auto it = _names.find(id); it != _names.end()) + return it->second; + return '_' + std::to_string(id); + } + + template + void define_name(const id id, std::string name) + { + if constexpr (naming == naming::general) + if (std::find_if(_names.begin(), _names.end(), [&name](const auto &it) { return it.second == name; }) != _names.end()) + name += '_' + std::to_string(id); + _names[id] = std::move(name); + } + + static void increase_indentation_level(std::string &block) + { + if (block.empty()) + return; + + for (size_t pos = 0; (pos = block.find("\n\t", pos)) != std::string::npos; pos += 3) + block.replace(pos, 2, "\n\t\t"); + + block.insert(block.begin(), '\t'); + } + + id define_struct(const location &loc, struct_info &info) override + { + info.definition = make_id(); + define_name(info.definition, info.unique_name); + + _structs.push_back(info); + + std::string &code = _blocks.at(_current_block); + + write_location(code, loc); + + code += "struct " + id_to_name(info.definition) + "\n{\n"; + + for (const auto &member : info.member_list) + { + code += '\t'; + write_type(code, member.type); // HLSL allows interpolation attributes on struct members, so handle this like a parameter + code += ' ' + member.name; + if (member.type.is_array()) + code += '[' + std::to_string(member.type.array_length) + ']'; + if (!member.semantic.empty()) + code += " : " + convert_semantic(member.semantic); + code += ";\n"; + } + + code += "};\n"; + + return info.definition; + } + id define_texture(const location &loc, texture_info &info) override + { + info.id = make_id(); + info.binding = _module.num_texture_bindings; + + _module.textures.push_back(info); + + std::string &code = _blocks.at(_current_block); + + if (_shader_model >= 40) + { + write_location(code, loc); + + code += "Texture2D " + info.unique_name + " : register(t" + std::to_string(info.binding + 0) + ");\n"; + code += "Texture2D __srgb" + info.unique_name + " : register(t" + std::to_string(info.binding + 1) + ");\n"; + + _module.num_texture_bindings += 2; + } + + return info.id; + } + id define_sampler(const location &loc, sampler_info &info) override + { + info.id = make_id(); + + define_name(info.id, info.unique_name); + + const auto texture = std::find_if(_module.textures.begin(), _module.textures.end(), + [&info](const auto &it) { return it.unique_name == info.texture_name; }); + assert(texture != _module.textures.end()); + + std::string &code = _blocks.at(_current_block); + + if (_shader_model >= 40) + { + // Try and reuse a sampler binding with the same sampler description + const auto existing_sampler = std::find_if(_module.samplers.begin(), _module.samplers.end(), + [&info](const auto &it) { return it.filter == info.filter && it.address_u == info.address_u && it.address_v == info.address_v && it.address_w == info.address_w && it.min_lod == info.min_lod && it.max_lod == info.max_lod && it.lod_bias == info.lod_bias; }); + + if (existing_sampler != _module.samplers.end()) + { + info.binding = existing_sampler->binding; + } + else + { + info.binding = _module.num_sampler_bindings++; + + code += "SamplerState __s" + std::to_string(info.binding) + " : register(s" + std::to_string(info.binding) + ");\n"; + } + + assert(info.srgb == 0 || info.srgb == 1); + info.texture_binding = texture->binding + info.srgb; // Offset binding by one to choose the SRGB variant + + write_location(code, loc); + + code += "static const __sampler2D " + id_to_name(info.id) + " = { " + (info.srgb ? "__srgb" : "") + info.texture_name + ", __s" + std::to_string(info.binding) + " };\n"; + } + else + { + info.binding = _module.num_sampler_bindings++; + + code += "sampler2D __" + info.unique_name + "_s : register(s" + std::to_string(info.binding) + ");\n"; + + write_location(code, loc); + + code += "static const __sampler2D " + id_to_name(info.id) + " = { __" + info.unique_name + "_s, float2("; + + if (texture->semantic.empty()) + code += std::to_string(1.0f / texture->width) + ", " + std::to_string(1.0f / texture->height); + else + code += texture->semantic + "_PIXEL_SIZE"; // Expect application to set inverse texture size via a define if it is not known here + + code += ") }; \n"; + } + + _module.samplers.push_back(info); + + return info.id; + } + id define_uniform(const location &loc, uniform_info &info) override + { + const id res = make_id(); + + define_name(res, "_Globals_" + info.name); + + if (_uniforms_to_spec_constants && info.has_initializer_value) + { + std::string &code = _blocks.at(_current_block); + + write_location(code, loc); + + code += "static const "; + write_type(code, info.type); + code += ' ' + id_to_name(res) + " = "; + if (!info.type.is_scalar()) + write_type(code, info.type); + code += "(SPEC_CONSTANT_" + info.name + ");\n"; + + _module.spec_constants.push_back(info); + } + else + { + const unsigned int size = info.type.rows * info.type.cols * std::max(1, info.type.array_length) * 4; + const unsigned int alignment = 16 - (_current_cbuffer_size % 16); + + _current_cbuffer_size += (size > alignment && (alignment != 16 || size <= 16)) ? size + alignment : size; + + info.size = size; + info.offset = _current_cbuffer_size - size; + + write_location(_cbuffer_block, loc); + + if (_shader_model < 40) + { + type type = info.type; + // The HLSL compiler tries to evaluate boolean values with temporary registers, which breaks branches, so force it to use constant float registers + if (type.is_boolean()) + type.base = type::t_float; + + // Simply put each uniform into a separate constant register in shader model 3 for now + info.offset *= 4; + + // Every constant register is 16 bytes wide, so divide memory offset by 16 to get the constant register index + write_type(_cbuffer_block, type); + _cbuffer_block += ' ' + id_to_name(res) + " : register(c" + std::to_string(info.offset / 16) + ");\n"; + } + else + { + _cbuffer_block += '\t'; + write_type(_cbuffer_block, info.type); + _cbuffer_block += ' ' + id_to_name(res) + ";\n"; + } + + _module.uniforms.push_back(info); + } + + return res; + } + id define_variable(const location &loc, const type &type, std::string name, bool global, id initializer_value) override + { + const id res = make_id(); + + if (!name.empty()) + define_name(res, name); + + std::string &code = _blocks.at(_current_block); + + write_location(code, loc); + + if (!global) + code += '\t'; + + write_type(code, type); + code += ' ' + id_to_name(res); + + if (type.is_array()) + code += '[' + std::to_string(type.array_length) + ']'; + + if (initializer_value != 0) + code += " = " + id_to_name(initializer_value); + + code += ";\n"; + + return res; + } + id define_function(const location &loc, function_info &info) override + { + return define_function(loc, info, _shader_model >= 40); + } + id define_function(const location &loc, function_info &info, bool is_entry_point) + { + std::string name = info.unique_name; + if (!is_entry_point) + name += '_'; + + info.definition = make_id(); + define_name(info.definition, name); + + std::string &code = _blocks.at(_current_block); + + write_location(code, loc); + + write_type(code, info.return_type); + code += ' ' + id_to_name(info.definition) + '('; + + for (size_t i = 0, num_params = info.parameter_list.size(); i < num_params; ++i) + { + auto ¶m = info.parameter_list[i]; + + param.definition = make_id(); + define_name(param.definition, param.name); + + code += '\n'; + write_location(code, param.location); + code += '\t'; + write_type(code, param.type); + code += ' ' + id_to_name(param.definition); + + if (param.type.is_array()) + code += '[' + std::to_string(param.type.array_length) + ']'; + + if (is_entry_point && !param.semantic.empty()) + code += " : " + convert_semantic(param.semantic); + + if (i < num_params - 1) + code += ','; + } + + code += ')'; + + if (is_entry_point && !info.return_semantic.empty()) + code += " : " + convert_semantic(info.return_semantic); + + code += '\n'; + + _functions.push_back(std::make_unique(info)); + + return info.definition; + } + void define_entry_point(const function_info &func, bool is_ps) override + { + if (const auto it = std::find_if(_module.entry_points.begin(), _module.entry_points.end(), + [&func](const auto &ep) { return ep.first == func.unique_name; }); it != _module.entry_points.end()) + return; + + _module.entry_points.push_back({ func.unique_name, is_ps }); + + // Only have to rewrite the entry point function signature in shader model 3 + if (_shader_model >= 40) + return; + + auto entry_point = func; + + const auto is_color_semantic = [](const std::string &semantic) { return semantic.compare(0, 9, "SV_TARGET") == 0 || semantic.compare(0, 5, "COLOR") == 0; }; + const auto is_position_semantic = [](const std::string &semantic) { return semantic == "SV_POSITION" || semantic == "POSITION"; }; + + const auto ret = make_id(); + define_name(ret, "ret"); + + std::string position_variable_name; + + if (func.return_type.is_struct() && !is_ps) + // If this function returns a struct which contains a position output, keep track of its member name + for (const auto &member : find_struct(func.return_type.definition).member_list) + if (is_position_semantic(member.semantic)) + position_variable_name = id_to_name(ret) + '.' + member.name; + if (is_color_semantic(func.return_semantic)) + // The COLOR output semantic has to be a four-component vector in shader model 3, so enforce that + entry_point.return_type.rows = 4; + else if (is_position_semantic(func.return_semantic) && !is_ps) + position_variable_name = id_to_name(ret); + + for (auto ¶m : entry_point.parameter_list) + if (is_color_semantic(param.semantic)) + param.type.rows = 4; + else if (is_position_semantic(param.semantic)) + if (is_ps) // Change the position input semantic in pixel shaders + param.semantic = "VPOS"; + else // Keep track of the position output variable + position_variable_name = param.name; + + define_function({}, entry_point, true); + enter_block(create_block()); + + std::string &code = _blocks.at(_current_block); + + // Clear all color output parameters so no component is left uninitialized + for (auto ¶m : entry_point.parameter_list) + if (is_color_semantic(param.semantic)) + code += '\t' + param.name + " = float4(0.0, 0.0, 0.0, 0.0);\n"; + + code += '\t'; + if (is_color_semantic(func.return_semantic)) + code += "const float4 " + id_to_name(ret) + " = float4("; + else if (!func.return_type.is_void()) + write_type(code, func.return_type), code += ' ' + id_to_name(ret) + " = "; + + // Call the function this entry point refers to + code += id_to_name(func.definition) + '('; + + for (size_t i = 0, num_params = func.parameter_list.size(); i < num_params; ++i) + { + code += func.parameter_list[i].name; + + if (is_color_semantic(func.parameter_list[i].semantic)) + { + code += '.'; + for (unsigned int k = 0; k < func.parameter_list[i].type.rows; k++) + code += "xyzw"[k]; + } + + if (i < num_params - 1) + code += ", "; + } + + code += ')'; + + // Cast the output value to a four-component vector + if (is_color_semantic(func.return_semantic)) + { + for (unsigned int i = 0; i < 4 - func.return_type.rows; i++) + code += ", 0.0"; + code += ')'; + } + + code += ";\n"; + + // Shift everything by half a viewport pixel to workaround the different half-pixel offset in D3D9 (https://aras-p.info/blog/2016/04/08/solving-dx9-half-pixel-offset/) + if (!position_variable_name.empty() && !is_ps) // Check if we are in a vertex shader definition + code += '\t' + position_variable_name + ".xy += __TEXEL_SIZE__ * " + position_variable_name + ".ww;\n"; + + leave_block_and_return(func.return_type.is_void() ? 0 : ret); + leave_function(); + } + + id emit_load(const expression &exp) override + { + if (exp.is_constant) + return emit_constant(exp.type, exp.constant); + else if (exp.chain.empty()) // Can refer to values without access chain directly + return exp.base; + + const id res = make_id(); + + std::string &code = _blocks.at(_current_block); + + write_location(code, exp.location); + + code += '\t'; + write_type(code, exp.type); + code += ' ' + id_to_name(res); + + if (exp.type.is_array()) + code += '[' + std::to_string(exp.type.array_length) + ']'; + + code += " = "; + + static const char s_matrix_swizzles[16][5] = { + "_m00", "_m01", "_m02", "_m03", + "_m10", "_m11", "_m12", "_m13", + "_m20", "_m21", "_m22", "_m23", + "_m30", "_m31", "_m32", "_m33" + }; + + std::string newcode = id_to_name(exp.base); + + for (const auto &op : exp.chain) + { + switch (op.op) + { + case expression::operation::op_cast: + { std::string type; write_type(type, op.to); + newcode = "((" + type + ')' + newcode + ')'; } // Cast in parentheses so that a subsequent operation operates on the casted value + break; + case expression::operation::op_member: + newcode += '.'; + newcode += find_struct(op.from.definition).member_list[op.index].name; + break; + case expression::operation::op_constant_index: + newcode += '[' + std::to_string(op.index) + ']'; + break; + case expression::operation::op_dynamic_index: + newcode += '[' + id_to_name(op.index) + ']'; + break; + case expression::operation::op_swizzle: + newcode += '.'; + for (unsigned int i = 0; i < 4 && op.swizzle[i] >= 0; ++i) + if (op.from.is_matrix()) + newcode += s_matrix_swizzles[op.swizzle[i]]; + else + newcode += "xyzw"[op.swizzle[i]]; + break; + } + } + + code += newcode; + code += ";\n"; + + return res; + } + void emit_store(const expression &exp, id value) override + { + std::string &code = _blocks.at(_current_block); + + write_location(code, exp.location); + + code += '\t' + id_to_name(exp.base); + + static const char s_matrix_swizzles[16][5] = { + "_m00", "_m01", "_m02", "_m03", + "_m10", "_m11", "_m12", "_m13", + "_m20", "_m21", "_m22", "_m23", + "_m30", "_m31", "_m32", "_m33" + }; + + for (const auto &op : exp.chain) + { + switch (op.op) + { + case expression::operation::op_member: + code += '.'; + code += find_struct(op.from.definition).member_list[op.index].name; + break; + case expression::operation::op_dynamic_index: + code += '[' + id_to_name(op.index) + ']'; + break; + case expression::operation::op_constant_index: + code += '[' + std::to_string(op.index) + ']'; + break; + case expression::operation::op_swizzle: + code += '.'; + for (unsigned int i = 0; i < 4 && op.swizzle[i] >= 0; ++i) + if (op.from.is_matrix()) + code += s_matrix_swizzles[op.swizzle[i]]; + else + code += "xyzw"[op.swizzle[i]]; + break; + } + } + + code += " = " + id_to_name(value) + ";\n"; + } + + id emit_constant(const type &type, const constant &data) override + { + const id res = make_id(); + + std::string &code = _blocks.at(_current_block); + + code += "\tconst "; + write_type(code, type); + code += ' ' + id_to_name(res); + + if (type.is_array()) + code += '[' + std::to_string(type.array_length) + ']'; + + code += " = "; + write_constant(code, type, data); + code += ";\n"; + + return res; + } + + id emit_unary_op(const location &loc, tokenid op, const type &res_type, id val) override + { + const id res = make_id(); + + std::string &code = _blocks.at(_current_block); + + write_location(code, loc); + + code += '\t'; + write_type(code, res_type); + code += ' ' + id_to_name(res) + " = "; + + if (_shader_model < 40 && op == tokenid::tilde) + code += "0xFFFFFFFF - "; // Emulate bitwise not operator on shader model 3 + else + code += char(op); + + code += id_to_name(val) + ";\n"; + + return res; + } + id emit_binary_op(const location &loc, tokenid op, const type &res_type, const type &, id lhs, id rhs) override + { + const id res = make_id(); + + std::string &code = _blocks.at(_current_block); + + write_location(code, loc); + + code += '\t'; + write_type(code, res_type); + code += ' ' + id_to_name(res) + " = "; + + if (_shader_model < 40) + { + // See bitwise shift operator emulation below + if (op == tokenid::less_less || op == tokenid::less_less_equal) + code += '('; + else if (op == tokenid::greater_greater || op == tokenid::greater_greater_equal) + code += "floor("; + } + + code += id_to_name(lhs) + ' '; + + switch (op) + { + case tokenid::plus: + case tokenid::plus_plus: + case tokenid::plus_equal: + code += '+'; + break; + case tokenid::minus: + case tokenid::minus_minus: + case tokenid::minus_equal: + code += '-'; + break; + case tokenid::star: + case tokenid::star_equal: + code += '*'; + break; + case tokenid::slash: + case tokenid::slash_equal: + code += '/'; + break; + case tokenid::percent: + case tokenid::percent_equal: + code += '%'; + break; + case tokenid::caret: + case tokenid::caret_equal: + code += '^'; + break; + case tokenid::pipe: + case tokenid::pipe_equal: + code += '|'; + break; + case tokenid::ampersand: + case tokenid::ampersand_equal: + code += '&'; + break; + case tokenid::less_less: + case tokenid::less_less_equal: + code += _shader_model >= 40 ? "<<" : ") * exp2("; // Emulate bitwise shift operators on shader model 3 + break; + case tokenid::greater_greater: + case tokenid::greater_greater_equal: + code += _shader_model >= 40 ? ">>" : ") / exp2("; + break; + case tokenid::pipe_pipe: + code += "||"; + break; + case tokenid::ampersand_ampersand: + code += "&&"; + break; + case tokenid::less: + code += '<'; + break; + case tokenid::less_equal: + code += "<="; + break; + case tokenid::greater: + code += '>'; + break; + case tokenid::greater_equal: + code += ">="; + break; + case tokenid::equal_equal: + code += "=="; + break; + case tokenid::exclaim_equal: + code += "!="; + break; + default: + assert(false); + } + + code += ' ' + id_to_name(rhs); + + if (_shader_model < 40) + { + // See bitwise shift operator emulation above + if (op == tokenid::less_less || op == tokenid::less_less_equal || + op == tokenid::greater_greater || op == tokenid::greater_greater_equal) + code += ')'; + } + + code += ";\n"; + + return res; + } + id emit_ternary_op(const location &loc, tokenid op, const type &res_type, id condition, id true_value, id false_value) override + { + assert(op == tokenid::question); + + const id res = make_id(); + + std::string &code = _blocks.at(_current_block); + + write_location(code, loc); + + code += '\t'; + write_type(code, res_type); + code += ' ' + id_to_name(res); + + if (res_type.is_array()) + code += '[' + std::to_string(res_type.array_length) + ']'; + + code += " = " + id_to_name(condition) + " ? " + id_to_name(true_value) + " : " + id_to_name(false_value) + ";\n"; + + return res; + } + id emit_call(const location &loc, id function, const type &res_type, const std::vector &args) override + { + for (const auto &arg : args) + assert(arg.chain.empty() && arg.base != 0); + + const id res = make_id(); + + std::string &code = _blocks.at(_current_block); + + write_location(code, loc); + + code += '\t'; + + if (!res_type.is_void()) + { + write_type(code, res_type); + code += ' ' + id_to_name(res); + + if (res_type.is_array()) + code += '[' + std::to_string(res_type.array_length) + ']'; + + code += " = "; + } + + code += id_to_name(function) + '('; + + for (size_t i = 0, num_args = args.size(); i < num_args; ++i) + { + code += id_to_name(args[i].base); + + if (i < num_args - 1) + code += ", "; + } + + code += ");\n"; + + return res; + } + id emit_call_intrinsic(const location &loc, id intrinsic, const type &res_type, const std::vector &args) override + { + for (const auto &arg : args) + assert(arg.chain.empty() && arg.base != 0); + + const id res = make_id(); + + std::string &code = _blocks.at(_current_block); + + write_location(code, loc); + + code += '\t'; + + enum + { +#define IMPLEMENT_INTRINSIC_HLSL(name, i, code) name##i, +#include "effect_symbol_table_intrinsics.inl" + }; + + if (_shader_model >= 40 && (intrinsic == tex2Dsize0 || intrinsic == tex2Dsize1)) + { + // Implementation of the 'tex2Dsize' intrinsic passes the result variable into 'GetDimensions' as output argument + write_type(code, res_type); + code += ' ' + id_to_name(res) + "; "; + } + else if (!res_type.is_void()) + { + write_type(code, res_type); + code += ' ' + id_to_name(res) + " = "; + } + + switch (intrinsic) + { +#define IMPLEMENT_INTRINSIC_HLSL(name, i, code) case name##i: code break; +#include "effect_symbol_table_intrinsics.inl" + default: + assert(false); + } + + code += ";\n"; + + return res; + } + id emit_construct(const location &loc, const type &type, const std::vector &args) override + { + for (const auto &arg : args) + assert((arg.type.is_scalar() || type.is_array()) && arg.chain.empty() && arg.base != 0); + + const id res = make_id(); + + std::string &code = _blocks.at(_current_block); + + write_location(code, loc); + + code += '\t'; + write_type(code, type); + code += ' ' + id_to_name(res); + + if (type.is_array()) + code += '[' + std::to_string(type.array_length) + ']'; + + code += " = "; + + if (type.is_array()) + code += "{ "; + else + write_type(code, type), code += '('; + + for (size_t i = 0, num_args = args.size(); i < num_args; ++i) + { + code += id_to_name(args[i].base); + + if (i < num_args - 1) + code += ", "; + } + + if (type.is_array()) + code += " }"; + else + code += ')'; + + code += ";\n"; + + return res; + } + + void emit_if(const location &loc, id condition_value, id condition_block, id true_statement_block, id false_statement_block, unsigned int flags) override + { + assert(condition_value != 0 && condition_block != 0 && true_statement_block != 0 && false_statement_block != 0); + + std::string &code = _blocks.at(_current_block); + + std::string &true_statement_data = _blocks.at(true_statement_block); + std::string &false_statement_data = _blocks.at(false_statement_block); + + increase_indentation_level(true_statement_data); + increase_indentation_level(false_statement_data); + + code += _blocks.at(condition_block); + + write_location(code, loc); + + code += '\t'; + + if (flags & 0x1) code += "[flatten] "; + if (flags & 0x2) code += "[branch] "; + + code += "if (" + id_to_name(condition_value) + ")\n\t{\n"; + code += true_statement_data; + code += "\t}\n"; + + if (!false_statement_data.empty()) + { + code += "\telse\n\t{\n"; + code += false_statement_data; + code += "\t}\n"; + } + + // Remove consumed blocks to save memory + _blocks.erase(condition_block); + _blocks.erase(true_statement_block); + _blocks.erase(false_statement_block); + } + id emit_phi(const location &loc, id condition_value, id condition_block, id true_value, id true_statement_block, id false_value, id false_statement_block, const type &type) override + { + assert(condition_value != 0 && condition_block != 0 && true_value != 0 && true_statement_block != 0 && false_value != 0 && false_statement_block != 0); + + std::string &code = _blocks.at(_current_block); + + std::string &true_statement_data = _blocks.at(true_statement_block); + std::string &false_statement_data = _blocks.at(false_statement_block); + + increase_indentation_level(true_statement_data); + increase_indentation_level(false_statement_data); + + const id res = make_id(); + + code += _blocks.at(condition_block); + + code += '\t'; + write_type(code, type); + code += ' ' + id_to_name(res) + ";\n"; + + write_location(code, loc); + + code += "\tif (" + id_to_name(condition_value) + ")\n\t{\n"; + code += (true_statement_block != condition_block ? true_statement_data : std::string()); + code += "\t\t" + id_to_name(res) + " = " + id_to_name(true_value) + ";\n"; + code += "\t}\n\telse\n\t{\n"; + code += (false_statement_block != condition_block ? false_statement_data : std::string()); + code += "\t\t" + id_to_name(res) + " = " + id_to_name(false_value) + ";\n"; + code += "\t}\n"; + + // Remove consumed blocks to save memory + _blocks.erase(condition_block); + _blocks.erase(true_statement_block); + _blocks.erase(false_statement_block); + + return res; + } + void emit_loop(const location &loc, id condition_value, id prev_block, id header_block, id condition_block, id loop_block, id continue_block, unsigned int flags) override + { + assert(condition_value != 0 && prev_block != 0 && header_block != 0 && loop_block != 0 && continue_block != 0); + + std::string &code = _blocks.at(_current_block); + + std::string &loop_data = _blocks.at(loop_block); + std::string &continue_data = _blocks.at(continue_block); + + increase_indentation_level(loop_data); + increase_indentation_level(loop_data); + increase_indentation_level(continue_data); + + code += _blocks.at(prev_block); + + if (condition_block == 0) + code += "\tbool " + id_to_name(condition_value) + ";\n"; + else + code += _blocks.at(condition_block); + + write_location(code, loc); + + code += '\t'; + + if (flags & 0x1) code += "[unroll] "; + if (flags & 0x2) code += "[loop] "; + + if (condition_block == 0) + { + // Convert variable initializer to assignment statement + auto pos_assign = continue_data.rfind(id_to_name(condition_value)); + auto pos_prev_assign = continue_data.rfind('\t', pos_assign); + continue_data.erase(pos_prev_assign + 1, pos_assign - pos_prev_assign - 1); + + // We need to add the continue block to all "continue" statements as well + const std::string continue_id = "__CONTINUE__" + std::to_string(continue_block); + for (size_t offset = 0; (offset = loop_data.find(continue_id, offset)) != std::string::npos; offset += continue_data.size()) + loop_data.replace(offset, continue_id.size(), continue_data); + + code += "do\n\t{\n\t\t{\n"; + code += loop_data; // Encapsulate loop body into another scope, so not to confuse any local variables with the current iteration variable accessed in the continue block below + code += "\t\t}\n"; + code += continue_data; + code += "\t}\n\twhile (" + id_to_name(condition_value) + ");\n"; + } + else + { + std::string &condition_data = _blocks.at(condition_block); + + increase_indentation_level(condition_data); + + // Convert variable initializer to assignment statement + auto pos_assign = condition_data.rfind(id_to_name(condition_value)); + auto pos_prev_assign = condition_data.rfind('\t', pos_assign); + condition_data.erase(pos_prev_assign + 1, pos_assign - pos_prev_assign - 1); + + const std::string continue_id = "__CONTINUE__" + std::to_string(continue_block); + for (size_t offset = 0; (offset = loop_data.find(continue_id, offset)) != std::string::npos; offset += continue_data.size()) + loop_data.replace(offset, continue_id.size(), continue_data + condition_data); + + code += "while (" + id_to_name(condition_value) + ")\n\t{\n\t\t{\n"; + code += loop_data; + code += "\t\t}\n"; + code += continue_data; + code += condition_data; + code += "\t}\n"; + + _blocks.erase(condition_block); + } + + // Remove consumed blocks to save memory + _blocks.erase(prev_block); + _blocks.erase(header_block); + _blocks.erase(loop_block); + _blocks.erase(continue_block); + } + void emit_switch(const location &loc, id selector_value, id selector_block, id default_label, const std::vector &case_literal_and_labels, unsigned int flags) override + { + assert(selector_value != 0 && selector_block != 0 && default_label != 0); + + std::string &code = _blocks.at(_current_block); + + code += _blocks.at(selector_block); + + // Switch statements do not work correctly in shader model 3 if a constant is used as selector value (this is a D3DCompiler bug), so replace them with if statements instead there + if (_shader_model >= 40) + { + write_location(code, loc); + + code += '\t'; + + if (flags & 0x1) code += "[flatten] "; + if (flags & 0x2) code += "[branch] "; + + code += "switch (" + id_to_name(selector_value) + ")\n\t{\n"; + + for (size_t i = 0; i < case_literal_and_labels.size(); i += 2) + { + assert(case_literal_and_labels[i + 1] != 0); + + std::string &case_data = _blocks.at(case_literal_and_labels[i + 1]); + + increase_indentation_level(case_data); + + code += "\tcase " + std::to_string(case_literal_and_labels[i]) + ": {\n"; + code += case_data; + code += "\t}\n"; + } + + if (default_label != _current_block) + { + std::string &default_data = _blocks.at(default_label); + + increase_indentation_level(default_data); + + code += "\tdefault: {\n"; + code += default_data; + code += "\t}\n"; + + _blocks.erase(default_label); + } + + code += "\t}\n"; + } + else + { + write_location(code, loc); + + code += "\t[unroll] do { "; // This dummy loop makes "break" statements work + + if (flags & 0x1) code += "[flatten] "; + if (flags & 0x2) code += "[branch] "; + + for (size_t i = 0; i < case_literal_and_labels.size(); i += 2) + { + assert(case_literal_and_labels[i + 1] != 0); + + std::string &case_data = _blocks.at(case_literal_and_labels[i + 1]); + + increase_indentation_level(case_data); + + code += "if (" + id_to_name(selector_value) + " == " + std::to_string(case_literal_and_labels[i]) + ")\n\t{\n"; + code += case_data; + code += "\t}\n\telse\n\t"; + + } + + code += "{\n"; + + if (default_label != _current_block) + { + std::string &default_data = _blocks.at(default_label); + + increase_indentation_level(default_data); + + code += default_data; + + _blocks.erase(default_label); + } + + code += "\t} } while (false);\n"; + } + + // Remove consumed blocks to save memory + _blocks.erase(selector_block); + for (size_t i = 0; i < case_literal_and_labels.size(); i += 2) + _blocks.erase(case_literal_and_labels[i + 1]); + } + + id create_block() override + { + const id res = make_id(); + + std::string &block = _blocks.emplace(res, std::string()).first->second; + // Reserve a decently big enough memory block to avoid frequent reallocations + block.reserve(4096); + + return res; + } + id set_block(id id) override + { + _last_block = _current_block; + _current_block = id; + + return _last_block; + } + void enter_block(id id) override + { + _current_block = id; + } + id leave_block_and_kill() override + { + if (!is_in_block()) + return 0; + + std::string &code = _blocks.at(_current_block); + + code += "\tdiscard;\n"; + + return set_block(0); + } + id leave_block_and_return(id value) override + { + if (!is_in_block()) + return 0; + + // Skip implicit return statement + if (!_functions.back()->return_type.is_void() && value == 0) + return set_block(0); + + std::string &code = _blocks.at(_current_block); + + code += "\treturn"; + + if (value != 0) + code += ' ' + id_to_name(value); + + code += ";\n"; + + return set_block(0); + } + id leave_block_and_switch(id, id) override + { + if (!is_in_block()) + return _last_block; + + return set_block(0); + } + id leave_block_and_branch(id target, unsigned int loop_flow) override + { + if (!is_in_block()) + return _last_block; + + std::string &code = _blocks.at(_current_block); + + switch (loop_flow) + { + case 1: + code += "\tbreak;\n"; + break; + case 2: // Keep track of continue target block, so we can insert its code here later + code += "__CONTINUE__" + std::to_string(target) + "\tcontinue;\n"; + break; + } + + return set_block(0); + } + id leave_block_and_branch_conditional(id, id, id) override + { + if (!is_in_block()) + return _last_block; + + return set_block(0); + } + void leave_function() override + { + assert(_last_block != 0); + + _blocks.at(0) += "{\n" + _blocks.at(_last_block) + "}\n"; + } +}; + +codegen *reshadefx::create_codegen_hlsl(unsigned int shader_model, bool debug_info, bool uniforms_to_spec_constants) +{ + return new codegen_hlsl(shader_model, debug_info, uniforms_to_spec_constants); +} diff --git a/msvc/source/effect_expression.cpp b/msvc/source/effect_expression.cpp new file mode 100644 index 0000000..42c7017 --- /dev/null +++ b/msvc/source/effect_expression.cpp @@ -0,0 +1,451 @@ +/** + * Copyright (C) 2014 Patrick Mours. All rights reserved. + * License: https://github.com/crosire/reshade#license + */ + +#include "effect_expression.hpp" +#include "effect_lexer.hpp" +#include "effect_codegen.hpp" +#include + +reshadefx::type reshadefx::type::merge(const type &lhs, const type &rhs) +{ + type result = { std::max(lhs.base, rhs.base) }; + + // If one side of the expression is scalar, it needs to be promoted to the same dimension as the other side + if ((lhs.rows == 1 && lhs.cols == 1) || (rhs.rows == 1 && rhs.cols == 1)) + { + result.rows = std::max(lhs.rows, rhs.rows); + result.cols = std::max(lhs.cols, rhs.cols); + } + else // Otherwise dimensions match or one side is truncated to match the other one + { + result.rows = std::min(lhs.rows, rhs.rows); + result.cols = std::min(lhs.cols, rhs.cols); + } + + // Some qualifiers propagate to the result + result.qualifiers = (lhs.qualifiers & type::q_precise) | (rhs.qualifiers & type::q_precise); + + return result; +} + +void reshadefx::expression::reset_to_lvalue(const reshadefx::location &loc, reshadefx::codegen::id in_base, const reshadefx::type &in_type) +{ + type = in_type; + base = in_base; + location = loc; + is_lvalue = true; + is_constant = false; + chain.clear(); +} +void reshadefx::expression::reset_to_rvalue(const reshadefx::location &loc, reshadefx::codegen::id in_base, const reshadefx::type &in_type) +{ + type = in_type; + type.qualifiers |= type::q_const; + base = in_base; + location = loc; + is_lvalue = false; + is_constant = false; + chain.clear(); +} + +void reshadefx::expression::reset_to_rvalue_constant(const reshadefx::location &loc, bool data) +{ + type = { type::t_bool, 1, 1, type::q_const }; + base = 0; constant = {}; constant.as_uint[0] = data; + location = loc; + is_lvalue = false; + is_constant = true; + chain.clear(); +} +void reshadefx::expression::reset_to_rvalue_constant(const reshadefx::location &loc, float data) +{ + type = { type::t_float, 1, 1, type::q_const }; + base = 0; constant = {}; constant.as_float[0] = data; + location = loc; + is_lvalue = false; + is_constant = true; + chain.clear(); +} +void reshadefx::expression::reset_to_rvalue_constant(const reshadefx::location &loc, int32_t data) +{ + type = { type::t_int, 1, 1, type::q_const }; + base = 0; constant = {}; constant.as_int[0] = data; + location = loc; + is_lvalue = false; + is_constant = true; + chain.clear(); +} +void reshadefx::expression::reset_to_rvalue_constant(const reshadefx::location &loc, uint32_t data) +{ + type = { type::t_uint, 1, 1, type::q_const }; + base = 0; constant = {}; constant.as_uint[0] = data; + location = loc; + is_lvalue = false; + is_constant = true; + chain.clear(); +} +void reshadefx::expression::reset_to_rvalue_constant(const reshadefx::location &loc, std::string data) +{ + type = { type::t_string, 0, 0, type::q_const }; + base = 0; constant = {}; constant.string_data = std::move(data); + location = loc; + is_lvalue = false; + is_constant = true; + chain.clear(); +} +void reshadefx::expression::reset_to_rvalue_constant(const reshadefx::location &loc, reshadefx::constant data, const reshadefx::type &in_type) +{ + type = in_type; + type.qualifiers |= type::q_const; + base = 0; constant = std::move(data); + location = loc; + is_lvalue = false; + is_constant = true; + chain.clear(); +} + +void reshadefx::expression::add_cast_operation(const reshadefx::type &cast_type) +{ + // First try to simplify the cast with a swizzle operation (only works with scalars and vectors) + if (type.cols == 1 && cast_type.cols == 1 && type.rows != cast_type.rows) + { + signed char swizzle[] = { 0, 1, 2, 3 }; + // Ignore components in a demotion cast + for (unsigned int i = cast_type.rows; i < 4; ++i) + swizzle[i] = -1; + // Use the last component to fill in a promotion cast + for (unsigned int i = type.rows; i < cast_type.rows; ++i) + swizzle[i] = swizzle[type.rows - 1]; + + add_swizzle_access(swizzle, cast_type.rows); + } + + if (type == cast_type) + return; // There is nothing more to do if the expression is already of the target type at this point + + if (is_constant) + { + const auto cast_constant = [](reshadefx::constant &constant, const reshadefx::type &from, const reshadefx::type &to) { + // Handle scalar to vector promotion first + if (from.is_scalar() && !to.is_scalar()) + for (unsigned int i = 1; i < to.components(); ++i) + constant.as_uint[i] = constant.as_uint[0]; + + // Next check whether the type needs casting as well (and don't convert between signed/unsigned, since that is handled by the union) + if (from.base == to.base || from.is_floating_point() == to.is_floating_point()) + return; + + if (!to.is_floating_point()) + for (unsigned int i = 0; i < to.components(); ++i) + constant.as_uint[i] = static_cast(constant.as_float[i]); + else + for (unsigned int i = 0; i < to.components(); ++i) + constant.as_float[i] = static_cast(constant.as_int[i]); + }; + + for (auto &element : constant.array_data) + cast_constant(element, type, cast_type); + + cast_constant(constant, type, cast_type); + } + else + { + assert(!type.is_array() && !cast_type.is_array()); + + chain.push_back({ operation::op_cast, type, cast_type }); + } + + type = cast_type; +} +void reshadefx::expression::add_member_access(unsigned int index, const reshadefx::type &in_type) +{ + assert(type.is_struct()); + + chain.push_back({ operation::op_member, type, in_type, index }); + + // The type is now the type of the member that was accessed + type = in_type; + is_constant = false; +} +void reshadefx::expression::add_dynamic_index_access(reshadefx::codegen::id index_expression) +{ + assert(type.is_numeric() && !is_constant); + + auto prev_type = type; + + if (type.is_array()) + { + type.array_length = 0; + } + else if (type.is_matrix()) + { + type.rows = type.cols; + type.cols = 1; + } + else if (type.is_vector()) + { + type.rows = 1; + } + + chain.push_back({ operation::op_dynamic_index, prev_type, type, index_expression }); +} +void reshadefx::expression::add_constant_index_access(unsigned int index) +{ + assert(type.is_numeric() && !type.is_scalar()); + + auto prev_type = type; + + if (type.is_array()) + { + type.array_length = 0; + } + else if (type.is_matrix()) + { + type.rows = type.cols; + type.cols = 1; + } + else if (type.is_vector()) + { + type.rows = 1; + } + + if (is_constant) + { + if (prev_type.is_array()) + { + constant = constant.array_data[index]; + } + else if (prev_type.is_matrix()) // Indexing into a matrix returns a row of it as a vector + { + for (unsigned int i = 0; i < 4; ++i) + constant.as_uint[i] = constant.as_uint[index * 4 + i]; + } + else // Indexing into a vector returns the element as a scalar + { + constant.as_uint[0] = constant.as_uint[index]; + } + } + else + { + chain.push_back({ operation::op_constant_index, prev_type, type, index }); + } +} +void reshadefx::expression::add_swizzle_access(const signed char swizzle[4], unsigned int length) +{ + assert(type.is_numeric() && !type.is_array()); + + const auto prev_type = type; + + type.rows = length; + type.cols = 1; + + if (is_constant) + { + assert(constant.array_data.empty()); + + uint32_t data[16]; + memcpy(data, &constant.as_uint[0], sizeof(data)); + for (unsigned int i = 0; i < length; ++i) + constant.as_uint[i] = data[swizzle[i]]; + memset(&constant.as_uint[length], 0, sizeof(uint32_t) * (16 - length)); // Clear the rest of the constant + } + else if (length == 1 && prev_type.is_vector()) // Use indexing when possible since the code generation logic is simpler in SPIR-V + { + chain.push_back({ operation::op_constant_index, prev_type, type, static_cast(swizzle[0]) }); + } + else + { + chain.push_back({ operation::op_swizzle, prev_type, type, 0, { swizzle[0], swizzle[1], swizzle[2], swizzle[3] } }); + } +} + +bool reshadefx::expression::evaluate_constant_expression(reshadefx::tokenid op) +{ + if (!is_constant) + return false; + + switch (op) + { + case tokenid::exclaim: + for (unsigned int i = 0; i < type.components(); ++i) + constant.as_uint[i] = !constant.as_uint[i]; + break; + case tokenid::minus: + if (type.is_floating_point()) + for (unsigned int i = 0; i < type.components(); ++i) + constant.as_float[i] = -constant.as_float[i]; + else + for (unsigned int i = 0; i < type.components(); ++i) + constant.as_int[i] = -constant.as_int[i]; + break; + case tokenid::tilde: + for (unsigned int i = 0; i < type.components(); ++i) + constant.as_uint[i] = ~constant.as_uint[i]; + break; + } + + return true; +} +bool reshadefx::expression::evaluate_constant_expression(reshadefx::tokenid op, const reshadefx::constant &rhs) +{ + if (!is_constant) + return false; + + switch (op) + { + case tokenid::percent: + if (type.is_floating_point()) { + for (unsigned int i = 0; i < type.components(); ++i) + if (rhs.as_float[i] != 0) + constant.as_float[i] = fmodf(constant.as_float[i], rhs.as_float[i]); + } + else if (type.is_signed()) { + for (unsigned int i = 0; i < type.components(); ++i) + if (rhs.as_int[i] != 0) + constant.as_int[i] %= rhs.as_int[i]; + } + else { + for (unsigned int i = 0; i < type.components(); ++i) + if (rhs.as_uint[i] != 0) + constant.as_uint[i] %= rhs.as_uint[i]; + } + break; + case tokenid::star: + if (type.is_floating_point()) + for (unsigned int i = 0; i < type.components(); ++i) + constant.as_float[i] *= rhs.as_float[i]; + else + for (unsigned int i = 0; i < type.components(); ++i) + constant.as_uint[i] *= rhs.as_uint[i]; + break; + case tokenid::plus: + if (type.is_floating_point()) + for (unsigned int i = 0; i < type.components(); ++i) + constant.as_float[i] += rhs.as_float[i]; + else + for (unsigned int i = 0; i < type.components(); ++i) + constant.as_uint[i] += rhs.as_uint[i]; + break; + case tokenid::minus: + if (type.is_floating_point()) + for (unsigned int i = 0; i < type.components(); ++i) + constant.as_float[i] -= rhs.as_float[i]; + else + for (unsigned int i = 0; i < type.components(); ++i) + constant.as_uint[i] -= rhs.as_uint[i]; + break; + case tokenid::slash: + if (type.is_floating_point()) { + for (unsigned int i = 0; i < type.components(); ++i) + if (rhs.as_float[i] != 0) // TODO: Maybe throw an error on divide by zero? + constant.as_float[i] /= rhs.as_float[i]; + } + else if (type.is_signed()) { + for (unsigned int i = 0; i < type.components(); ++i) + if (rhs.as_int[i] != 0) + constant.as_int[i] /= rhs.as_int[i]; + } + else { + for (unsigned int i = 0; i < type.components(); ++i) + if (rhs.as_uint[i] != 0) + constant.as_uint[i] /= rhs.as_uint[i]; + } + break; + case tokenid::ampersand: + case tokenid::ampersand_ampersand: + for (unsigned int i = 0; i < type.components(); ++i) + constant.as_uint[i] &= rhs.as_uint[i]; + break; + case tokenid::pipe: + case tokenid::pipe_pipe: + for (unsigned int i = 0; i < type.components(); ++i) + constant.as_uint[i] |= rhs.as_uint[i]; + break; + case tokenid::caret: + for (unsigned int i = 0; i < type.components(); ++i) + constant.as_uint[i] ^= rhs.as_uint[i]; + break; + case tokenid::less: + if (type.is_floating_point()) + for (unsigned int i = 0; i < type.components(); ++i) + constant.as_uint[i] = constant.as_float[i] < rhs.as_float[i]; + else if (type.is_signed()) + for (unsigned int i = 0; i < type.components(); ++i) + constant.as_uint[i] = constant.as_int[i] < rhs.as_int[i]; + else + for (unsigned int i = 0; i < type.components(); ++i) + constant.as_uint[i] = constant.as_uint[i] < rhs.as_uint[i]; + type.base = type::t_bool; // Logic operations change the type to boolean + break; + case tokenid::less_equal: + if (type.is_floating_point()) + for (unsigned int i = 0; i < type.components(); ++i) + constant.as_uint[i] = constant.as_float[i] <= rhs.as_float[i]; + else if (type.is_signed()) + for (unsigned int i = 0; i < type.components(); ++i) + constant.as_uint[i] = constant.as_int[i] <= rhs.as_int[i]; + else + for (unsigned int i = 0; i < type.components(); ++i) + constant.as_uint[i] = constant.as_uint[i] <= rhs.as_uint[i]; + type.base = type::t_bool; + break; + case tokenid::greater: + if (type.is_floating_point()) + for (unsigned int i = 0; i < type.components(); ++i) + constant.as_uint[i] = constant.as_float[i] > rhs.as_float[i]; + else if (type.is_signed()) + for (unsigned int i = 0; i < type.components(); ++i) + constant.as_uint[i] = constant.as_int[i] > rhs.as_int[i]; + else + for (unsigned int i = 0; i < type.components(); ++i) + constant.as_uint[i] = constant.as_uint[i] > rhs.as_uint[i]; + type.base = type::t_bool; + break; + case tokenid::greater_equal: + if (type.is_floating_point()) + for (unsigned int i = 0; i < type.components(); ++i) + constant.as_uint[i] = constant.as_float[i] >= rhs.as_float[i]; + else if (type.is_signed()) + for (unsigned int i = 0; i < type.components(); ++i) + constant.as_uint[i] = constant.as_int[i] >= rhs.as_int[i]; + else + for (unsigned int i = 0; i < type.components(); ++i) + constant.as_uint[i] = constant.as_uint[i] >= rhs.as_uint[i]; + type.base = type::t_bool; + break; + case tokenid::equal_equal: + if (type.is_floating_point()) + for (unsigned int i = 0; i < type.components(); ++i) + constant.as_uint[i] = constant.as_float[i] == rhs.as_float[i]; + else + for (unsigned int i = 0; i < type.components(); ++i) + constant.as_uint[i] = constant.as_uint[i] == rhs.as_uint[i]; + type.base = type::t_bool; + break; + case tokenid::exclaim_equal: + if (type.is_floating_point()) + for (unsigned int i = 0; i < type.components(); ++i) + constant.as_uint[i] = constant.as_float[i] != rhs.as_float[i]; + else + for (unsigned int i = 0; i < type.components(); ++i) + constant.as_uint[i] = constant.as_uint[i] != rhs.as_uint[i]; + type.base = type::t_bool; + break; + case tokenid::less_less: + for (unsigned int i = 0; i < type.components(); ++i) + constant.as_uint[i] <<= rhs.as_uint[i]; + break; + case tokenid::greater_greater: + if (type.is_signed()) + for (unsigned int i = 0; i < type.components(); ++i) + constant.as_int[i] >>= rhs.as_int[i]; + else + for (unsigned int i = 0; i < type.components(); ++i) + constant.as_uint[i] >>= rhs.as_uint[i]; + break; + } + + return true; +} diff --git a/msvc/source/effect_expression.hpp b/msvc/source/effect_expression.hpp new file mode 100644 index 0000000..ac9da19 --- /dev/null +++ b/msvc/source/effect_expression.hpp @@ -0,0 +1,381 @@ +/** + * Copyright (C) 2014 Patrick Mours. All rights reserved. + * License: https://github.com/crosire/reshade#license + */ + +#pragma once + +#include +#include +#include + +namespace reshadefx +{ + /// + /// Structure which keeps track of a code location + /// + struct location + { + location() : line(1), column(1) { } + explicit location(unsigned int line, unsigned int column = 1) : line(line), column(column) { } + explicit location(std::string source, unsigned int line, unsigned int column = 1) : source(std::move(source)), line(line), column(column) { } + + std::string source; + unsigned int line, column; + }; + + /// + /// Structure which encapsulates a parsed value type + /// + struct type + { + enum datatype : uint8_t + { + t_void, + t_bool, + t_int, + t_uint, + t_float, + t_string, + t_struct, + t_sampler, + t_texture, + t_function, + }; + enum qualifier : uint32_t + { + q_extern = 1 << 0, + q_static = 1 << 1, + q_uniform = 1 << 2, + q_volatile = 1 << 3, + q_precise = 1 << 4, + q_in = 1 << 5, + q_out = 1 << 6, + q_inout = q_in | q_out, + q_const = 1 << 8, + q_linear = 1 << 10, + q_noperspective = 1 << 11, + q_centroid = 1 << 12, + q_nointerpolation = 1 << 13, + }; + + /// + /// Get the result type of an operation involving the two input types. + /// + static type merge(const type &lhs, const type &rhs); + + /// + /// Calculate the ranking between two types which can be used to select the best matching function overload. The higher the rank, the better the match. A value of zero indicates that the types are not compatible. + /// + static unsigned int rank(const type &src, const type &dst); + + bool has(qualifier x) const { return (qualifiers & x) == x; } + bool is_array() const { return array_length != 0; } + bool is_scalar() const { return !is_array() && !is_matrix() && !is_vector() && is_numeric(); } + bool is_vector() const { return rows > 1 && cols == 1; } + bool is_matrix() const { return rows >= 1 && cols > 1; } + bool is_signed() const { return base == t_int || base == t_float; } + bool is_numeric() const { return is_integral() || is_floating_point(); } + bool is_void() const { return base == t_void; } + bool is_boolean() const { return base == t_bool; } + bool is_integral() const { return base == t_bool || base == t_int || base == t_uint; } + bool is_floating_point() const { return base == t_float; } + bool is_struct() const { return base == t_struct; } + bool is_texture() const { return base == t_texture; } + bool is_sampler() const { return base == t_sampler; } + bool is_function() const { return base == t_function; } + + unsigned int components() const { return rows * cols; } + + friend inline bool operator==(const type &lhs, const type &rhs) + { + return lhs.base == rhs.base && lhs.rows == rhs.rows && lhs.cols == rhs.cols && lhs.array_length == rhs.array_length && lhs.definition == rhs.definition; + } + friend inline bool operator!=(const type &lhs, const type &rhs) + { + return !operator==(lhs, rhs); + } + + datatype base = t_void; // Underlying base type ('int', 'float', ...) + unsigned int rows = 0; // Number of rows if this is a vector type + unsigned int cols = 0; // Number of columns if this is a matrix type + unsigned int qualifiers = 0; // Bit mask of all the qualifiers decorating the type + int array_length = 0; // Negative if an unsized array, otherwise the number of elements if this is an array type + uint32_t definition = 0; // ID of the matching struct if this is a struct type + }; + + /// + /// Structure which encapsulates a parsed constant value + /// + struct constant + { + union + { + float as_float[16]; + int32_t as_int[16]; + uint32_t as_uint[16]; + }; + + // Optional string associated with this constant + std::string string_data; + // Optional additional elements if this is an array constant + std::vector array_data; + }; + + /// + /// Structures which keeps track of the access chain of an expression + /// + struct expression + { + struct operation + { + enum op_type + { + op_cast, + op_member, + op_dynamic_index, + op_constant_index, + op_swizzle, + }; + + op_type op; + type from, to; + uint32_t index; + signed char swizzle[4]; + }; + + type type = {}; + uint32_t base = 0; + constant constant = {}; + bool is_lvalue = false; + bool is_constant = false; + location location; + std::vector chain; + + /// + /// Initialize the expression to a l-value. + /// + /// The code location of the expression. + /// The ID of the l-value. + /// The value type of the expression result. + void reset_to_lvalue(const reshadefx::location &loc, uint32_t base, const reshadefx::type &type); + /// + /// Initialize the expression to a r-value. + /// + /// The code location of the expression. + /// The ID of the r-value. + /// The value type of the expression result. + void reset_to_rvalue(const reshadefx::location &loc, uint32_t base, const reshadefx::type &type); + + /// + /// Initialize the expression to a constant value. + /// + /// The code location of the constant expression. + /// The constant value. + void reset_to_rvalue_constant(const reshadefx::location &loc, bool data); + void reset_to_rvalue_constant(const reshadefx::location &loc, float data); + void reset_to_rvalue_constant(const reshadefx::location &loc, int32_t data); + void reset_to_rvalue_constant(const reshadefx::location &loc, uint32_t data); + void reset_to_rvalue_constant(const reshadefx::location &loc, std::string data); + void reset_to_rvalue_constant(const reshadefx::location &loc, reshadefx::constant data, const reshadefx::type &type); + + /// + /// Add a cast operation to the current access chain. + /// + /// The type to cast the expression to. + void add_cast_operation(const reshadefx::type &type); + /// + /// Add a struct member lookup to the current access chain. + /// + /// The index of the member to dereference. + /// The value type of the member. + void add_member_access(unsigned int index, const reshadefx::type &type); + /// + /// Add an index operation to the current access chain. + /// + /// The SSA ID of the indexing value. + void add_dynamic_index_access(uint32_t index_expression); + /// + /// Add an constant index operation to the current access chain. + /// + /// The constant indexing value. + void add_constant_index_access(unsigned int index); + /// + /// Add a swizzle operation to the current access chain. + /// + /// The swizzle for each component. -1 = unused, 0 = x, 1 = y, 2 = z, 3 = w. + /// The number of components in the swizzle. The maximum is 4. + void add_swizzle_access(const signed char swizzle[4], unsigned int length); + + /// + /// Apply an unary operation to this constant expression. + /// + /// The unary operator to apply. + bool evaluate_constant_expression(enum class tokenid op); + /// + /// Apply a binary operation to this constant expression. + /// + /// The binary operator to apply. + /// The constant to use as right-hand side of the binary operation. + bool evaluate_constant_expression(enum class tokenid op, const reshadefx::constant &rhs); + }; + + + struct struct_info + { + std::string name; + std::string unique_name; + std::vector member_list; + uint32_t definition = 0; + }; + + struct struct_member_info + { + type type; + std::string name; + std::string semantic; + location location; + uint32_t definition = 0; + }; + + struct uniform_info + { + std::string name; + type type; + uint32_t size = 0; + uint32_t offset = 0; + std::unordered_map> annotations; + bool has_initializer_value = false; + constant initializer_value; + }; + + enum class texture_filter + { + min_mag_mip_point = 0, + min_mag_point_mip_linear = 0x1, + min_point_mag_linear_mip_point = 0x4, + min_point_mag_mip_linear = 0x5, + min_linear_mag_mip_point = 0x10, + min_linear_mag_point_mip_linear = 0x11, + min_mag_linear_mip_point = 0x14, + min_mag_mip_linear = 0x15 + }; + + enum class texture_format + { + unknown, + + r8, + r16f, + r32f, + rg8, + rg16, + rg16f, + rg32f, + rgba8, + rgba16, + rgba16f, + rgba32f, + rgb10a2, + }; + + enum class texture_address_mode + { + wrap = 1, + mirror = 2, + clamp = 3, + border = 4 + }; + + struct texture_info + { + uint32_t id = 0; + uint32_t binding = 0; + std::string semantic; + std::string unique_name; + std::unordered_map> annotations; + uint32_t width = 1; + uint32_t height = 1; + uint32_t levels = 1; + texture_format format = texture_format::rgba8; + }; + + struct sampler_info + { + uint32_t id = 0; + uint32_t binding = 0; + uint32_t texture_binding = 0; + std::string unique_name; + std::string texture_name; + std::unordered_map> annotations; + texture_filter filter = texture_filter::min_mag_mip_linear; + texture_address_mode address_u = texture_address_mode::clamp; + texture_address_mode address_v = texture_address_mode::clamp; + texture_address_mode address_w = texture_address_mode::clamp; + float min_lod = -FLT_MAX; + float max_lod = +FLT_MAX; + float lod_bias = 0.0f; + uint8_t srgb = false; + }; + + struct function_info + { + uint32_t definition; + std::string name; + std::string unique_name; + type return_type; + std::string return_semantic; + std::vector parameter_list; + }; + + struct pass_info + { + std::string render_target_names[8] = {}; + std::string vs_entry_point; + std::string ps_entry_point; + uint8_t clear_render_targets = false; + uint8_t srgb_write_enable = false; + uint8_t blend_enable = false; + uint8_t stencil_enable = false; + uint8_t color_write_mask = 0xF; + uint8_t stencil_read_mask = 0xFF; + uint8_t stencil_write_mask = 0xFF; + uint32_t blend_op = 1; // ADD + uint32_t blend_op_alpha = 1; // ADD + uint32_t src_blend = 1; // ONE + uint32_t dest_blend = 0; // ZERO + uint32_t src_blend_alpha = 1; // ONE + uint32_t dest_blend_alpha = 0; // ZERO + uint32_t stencil_comparison_func = 8; // ALWAYS + uint32_t stencil_reference_value = 0; + uint32_t stencil_op_pass = 1; // KEEP + uint32_t stencil_op_fail = 1; // KEEP + uint32_t stencil_op_depth_fail = 1; // KEEP + uint32_t viewport_width = 0; + uint32_t viewport_height = 0; + }; + + struct technique_info + { + std::string name; + std::vector passes; + std::unordered_map> annotations; + }; + + + /// + /// In-memory representation of an effect file. + /// + struct module + { + std::string hlsl; + std::vector spirv; + std::vector textures; + std::vector samplers; + std::vector uniforms, spec_constants; + std::vector techniques; + std::vector> entry_points; + uint32_t num_sampler_bindings = 0; + uint32_t num_texture_bindings = 0; + }; +} diff --git a/msvc/source/effect_lexer.cpp b/msvc/source/effect_lexer.cpp new file mode 100644 index 0000000..d248165 --- /dev/null +++ b/msvc/source/effect_lexer.cpp @@ -0,0 +1,1004 @@ +/** + * Copyright (C) 2014 Patrick Mours. All rights reserved. + * License: https://github.com/crosire/reshade#license + */ + +#include "effect_lexer.hpp" +#include + +using namespace reshadefx; + +enum token_type +{ + DIGIT = '0', + IDENT = 'A', + SPACE = ' ', +}; + +// Lookup table which translates a given char to a token type +static const unsigned type_lookup[256] = { + 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, SPACE, + '\n', SPACE, SPACE, SPACE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, SPACE, '!', '"', '#', '$', '%', '&', '\'', + '(', ')', '*', '+', ',', '-', '.', '/', DIGIT, DIGIT, + DIGIT, DIGIT, DIGIT, DIGIT, DIGIT, DIGIT, DIGIT, DIGIT, ':', ';', + '<', '=', '>', '?', '@', IDENT, IDENT, IDENT, IDENT, IDENT, + IDENT, IDENT, IDENT, IDENT, IDENT, IDENT, IDENT, IDENT, IDENT, IDENT, + IDENT, IDENT, IDENT, IDENT, IDENT, IDENT, IDENT, IDENT, IDENT, IDENT, + IDENT, '[', '\\', ']', '^', IDENT, 0x00, IDENT, IDENT, IDENT, + IDENT, IDENT, IDENT, IDENT, IDENT, IDENT, IDENT, IDENT, IDENT, IDENT, + IDENT, IDENT, IDENT, IDENT, IDENT, IDENT, IDENT, IDENT, IDENT, IDENT, + IDENT, IDENT, IDENT, '{', '|', '}', '~', 0x00, 0x00, 0x00, +}; + +// Lookup tables which translate a given string literal to a token and backwards +static const std::unordered_map token_lookup = { + { tokenid::end_of_file, "end of file" }, + { tokenid::exclaim, "!" }, + { tokenid::hash, "#" }, + { tokenid::dollar, "$" }, + { tokenid::percent, "%" }, + { tokenid::ampersand, "&" }, + { tokenid::parenthesis_open, "(" }, + { tokenid::parenthesis_close, ")" }, + { tokenid::star, "*" }, + { tokenid::plus, "+" }, + { tokenid::comma, "," }, + { tokenid::minus, "-" }, + { tokenid::dot, "." }, + { tokenid::slash, "/" }, + { tokenid::colon, ":" }, + { tokenid::semicolon, ";" }, + { tokenid::less, "<" }, + { tokenid::equal, "=" }, + { tokenid::greater, ">" }, + { tokenid::question, "?" }, + { tokenid::at, "@" }, + { tokenid::bracket_open, "[" }, + { tokenid::backslash, "\\" }, + { tokenid::bracket_close, "]" }, + { tokenid::caret, "^" }, + { tokenid::brace_open, "{" }, + { tokenid::pipe, "|" }, + { tokenid::brace_close, "}" }, + { tokenid::tilde, "~" }, + { tokenid::exclaim_equal, "!=" }, + { tokenid::percent_equal, "%=" }, + { tokenid::ampersand_ampersand, "&&" }, + { tokenid::ampersand_equal, "&=" }, + { tokenid::star_equal, "*=" }, + { tokenid::plus_plus, "++" }, + { tokenid::plus_equal, "+=" }, + { tokenid::minus_minus, "--" }, + { tokenid::minus_equal, "-=" }, + { tokenid::arrow, "->" }, + { tokenid::ellipsis, "..." }, + { tokenid::slash_equal, "|=" }, + { tokenid::colon_colon, "::" }, + { tokenid::less_less_equal, "<<=" }, + { tokenid::less_less, "<<" }, + { tokenid::less_equal, "<=" }, + { tokenid::equal_equal, "==" }, + { tokenid::greater_greater_equal, ">>=" }, + { tokenid::greater_greater, ">>" }, + { tokenid::greater_equal, ">=" }, + { tokenid::caret_equal, "^=" }, + { tokenid::pipe_equal, "|=" }, + { tokenid::pipe_pipe, "||" }, + { tokenid::identifier, "identifier" }, + { tokenid::reserved, "reserved word" }, + { tokenid::true_literal, "true" }, + { tokenid::false_literal, "false" }, + { tokenid::int_literal, "integral literal" }, + { tokenid::uint_literal, "integral literal" }, + { tokenid::float_literal, "floating point literal" }, + { tokenid::double_literal, "floating point literal" }, + { tokenid::string_literal, "string literal" }, + { tokenid::namespace_, "namespace" }, + { tokenid::struct_, "struct" }, + { tokenid::technique, "technique" }, + { tokenid::pass, "pass" }, + { tokenid::for_, "for" }, + { tokenid::while_, "while" }, + { tokenid::do_, "do" }, + { tokenid::if_, "if" }, + { tokenid::else_, "else" }, + { tokenid::switch_, "switch" }, + { tokenid::case_, "case" }, + { tokenid::default_, "default" }, + { tokenid::break_, "break" }, + { tokenid::continue_, "continue" }, + { tokenid::return_, "return" }, + { tokenid::discard_, "discard" }, + { tokenid::extern_, "extern" }, + { tokenid::static_, "static" }, + { tokenid::uniform_, "uniform" }, + { tokenid::volatile_, "volatile" }, + { tokenid::precise, "precise" }, + { tokenid::in, "in" }, + { tokenid::out, "out" }, + { tokenid::inout, "inout" }, + { tokenid::const_, "const" }, + { tokenid::linear, "linear" }, + { tokenid::noperspective, "noperspective" }, + { tokenid::centroid, "centroid" }, + { tokenid::nointerpolation, "nointerpolation" }, + { tokenid::void_, "void" }, + { tokenid::bool_, "bool" }, + { tokenid::bool2, "bool2" }, + { tokenid::bool3, "bool3" }, + { tokenid::bool4, "bool4" }, + { tokenid::bool2x2, "bool2x2" }, + { tokenid::bool3x3, "bool3x3" }, + { tokenid::bool4x4, "bool4x4" }, + { tokenid::int_, "int" }, + { tokenid::int2, "int2" }, + { tokenid::int3, "int3" }, + { tokenid::int4, "int4" }, + { tokenid::int2x2, "int2x2" }, + { tokenid::int3x3, "int3x3" }, + { tokenid::int4x4, "int4x4" }, + { tokenid::uint_, "uint" }, + { tokenid::uint2, "uint2" }, + { tokenid::uint3, "uint3" }, + { tokenid::uint4, "uint4" }, + { tokenid::uint2x2, "uint2x2" }, + { tokenid::uint3x3, "uint3x3" }, + { tokenid::uint4x4, "uint4x4" }, + { tokenid::float_, "float" }, + { tokenid::float2, "float2" }, + { tokenid::float3, "float3" }, + { tokenid::float4, "float4" }, + { tokenid::float2x2, "float2x2" }, + { tokenid::float3x3, "float3x3" }, + { tokenid::float4x4, "float4x4" }, + { tokenid::vector, "vector" }, + { tokenid::matrix, "matrix" }, + { tokenid::string_, "string" }, + { tokenid::texture, "texture" }, + { tokenid::sampler, "sampler" }, +}; +static const std::unordered_map keyword_lookup = { + { "asm", tokenid::reserved }, + { "asm_fragment", tokenid::reserved }, + { "auto", tokenid::reserved }, + { "bool", tokenid::bool_ }, + { "bool2", tokenid::bool2 }, + { "bool2x2", tokenid::bool2x2 }, + { "bool3", tokenid::bool3 }, + { "bool3x3", tokenid::bool3x3 }, + { "bool4", tokenid::bool4 }, + { "bool4x4", tokenid::bool4x4 }, + { "break", tokenid::break_ }, + { "case", tokenid::case_ }, + { "cast", tokenid::reserved }, + { "catch", tokenid::reserved }, + { "centroid", tokenid::reserved }, + { "char", tokenid::reserved }, + { "class", tokenid::reserved }, + { "column_major", tokenid::reserved }, + { "compile", tokenid::reserved }, + { "const", tokenid::const_ }, + { "const_cast", tokenid::reserved }, + { "continue", tokenid::continue_ }, + { "default", tokenid::default_ }, + { "delete", tokenid::reserved }, + { "discard", tokenid::discard_ }, + { "do", tokenid::do_ }, + { "double", tokenid::reserved }, + { "dword", tokenid::uint_ }, + { "dword2", tokenid::uint2 }, + { "dword2x2", tokenid::uint2x2 }, + { "dword3", tokenid::uint3, }, + { "dword3x3", tokenid::uint3x3 }, + { "dword4", tokenid::uint4 }, + { "dword4x4", tokenid::uint4x4 }, + { "dynamic_cast", tokenid::reserved }, + { "else", tokenid::else_ }, + { "enum", tokenid::reserved }, + { "explicit", tokenid::reserved }, + { "extern", tokenid::extern_ }, + { "external", tokenid::reserved }, + { "false", tokenid::false_literal }, + { "FALSE", tokenid::false_literal }, + { "float", tokenid::float_ }, + { "float2", tokenid::float2 }, + { "float2x2", tokenid::float2x2 }, + { "float3", tokenid::float3 }, + { "float3x3", tokenid::float3x3 }, + { "float4", tokenid::float4 }, + { "float4x4", tokenid::float4x4 }, + { "for", tokenid::for_ }, + { "foreach", tokenid::reserved }, + { "friend", tokenid::reserved }, + { "globallycoherent", tokenid::reserved }, + { "goto", tokenid::reserved }, + { "groupshared", tokenid::reserved }, + { "half", tokenid::reserved }, + { "half2", tokenid::reserved }, + { "half2x2", tokenid::reserved }, + { "half3", tokenid::reserved }, + { "half3x3", tokenid::reserved }, + { "half4", tokenid::reserved }, + { "half4x4", tokenid::reserved }, + { "if", tokenid::if_ }, + { "in", tokenid::in }, + { "inline", tokenid::reserved }, + { "inout", tokenid::inout }, + { "int", tokenid::int_ }, + { "int2", tokenid::int2 }, + { "int2x2", tokenid::int2x2 }, + { "int3", tokenid::int3 }, + { "int3x3", tokenid::int3x3 }, + { "int4", tokenid::int4 }, + { "int4x4", tokenid::int4x4 }, + { "interface", tokenid::reserved }, + { "linear", tokenid::linear }, + { "long", tokenid::reserved }, + { "matrix", tokenid::matrix }, + { "mutable", tokenid::reserved }, + { "namespace", tokenid::namespace_ }, + { "new", tokenid::reserved }, + { "noinline", tokenid::reserved }, + { "nointerpolation", tokenid::nointerpolation }, + { "noperspective", tokenid::noperspective }, + { "operator", tokenid::reserved }, + { "out", tokenid::out }, + { "packed", tokenid::reserved }, + { "packoffset", tokenid::reserved }, + { "pass", tokenid::pass }, + { "precise", tokenid::precise }, + { "private", tokenid::reserved }, + { "protected", tokenid::reserved }, + { "public", tokenid::reserved }, + { "register", tokenid::reserved }, + { "reinterpret_cast", tokenid::reserved }, + { "return", tokenid::return_ }, + { "row_major", tokenid::reserved }, + { "sample", tokenid::reserved }, + { "sampler", tokenid::sampler }, + { "sampler1D", tokenid::sampler }, + { "sampler1DArray", tokenid::reserved }, + { "sampler1DArrayShadow", tokenid::reserved }, + { "sampler1DShadow", tokenid::reserved }, + { "sampler2D", tokenid::sampler }, + { "sampler2DArray", tokenid::reserved }, + { "sampler2DArrayShadow", tokenid::reserved }, + { "sampler2DMS", tokenid::reserved }, + { "sampler2DMSArray", tokenid::reserved }, + { "sampler2DShadow", tokenid::reserved }, + { "sampler3D", tokenid::sampler }, + { "sampler_state", tokenid::reserved }, + { "samplerCUBE", tokenid::reserved }, + { "samplerRECT", tokenid::reserved }, + { "SamplerState", tokenid::reserved }, + { "shared", tokenid::reserved }, + { "short", tokenid::reserved }, + { "signed", tokenid::reserved }, + { "sizeof", tokenid::reserved }, + { "snorm", tokenid::reserved }, + { "static", tokenid::static_ }, + { "static_cast", tokenid::reserved }, + { "string", tokenid::string_ }, + { "struct", tokenid::struct_ }, + { "switch", tokenid::switch_ }, + { "technique", tokenid::technique }, + { "template", tokenid::reserved }, + { "texture", tokenid::texture }, + { "Texture1D", tokenid::reserved }, + { "texture1D", tokenid::texture }, + { "Texture1DArray", tokenid::reserved }, + { "Texture2D", tokenid::reserved }, + { "texture2D", tokenid::texture }, + { "Texture2DArray", tokenid::reserved }, + { "Texture2DMS", tokenid::reserved }, + { "Texture2DMSArray", tokenid::reserved }, + { "Texture3D", tokenid::reserved }, + { "texture3D", tokenid::texture }, + { "textureCUBE", tokenid::reserved }, + { "TextureCube", tokenid::reserved }, + { "TextureCubeArray", tokenid::reserved }, + { "textureRECT", tokenid::reserved }, + { "this", tokenid::reserved }, + { "true", tokenid::true_literal }, + { "TRUE", tokenid::true_literal }, + { "try", tokenid::reserved }, + { "typedef", tokenid::reserved }, + { "uint", tokenid::uint_ }, + { "uint2", tokenid::uint2 }, + { "uint2x2", tokenid::uint2x2 }, + { "uint3", tokenid::uint3 }, + { "uint3x3", tokenid::uint3x3 }, + { "uint4", tokenid::uint4 }, + { "uint4x4", tokenid::uint4x4 }, + { "uniform", tokenid::uniform_ }, + { "union", tokenid::reserved }, + { "unorm", tokenid::reserved }, + { "unsigned", tokenid::reserved }, + { "vector", tokenid::vector }, + { "virtual", tokenid::reserved }, + { "void", tokenid::void_ }, + { "volatile", tokenid::volatile_ }, + { "while", tokenid::while_ } +}; +static const std::unordered_map pp_directive_lookup = { + { "define", tokenid::hash_def }, + { "undef", tokenid::hash_undef }, + { "if", tokenid::hash_if }, + { "ifdef", tokenid::hash_ifdef }, + { "ifndef", tokenid::hash_ifndef }, + { "else", tokenid::hash_else }, + { "elif", tokenid::hash_elif }, + { "endif", tokenid::hash_endif }, + { "error", tokenid::hash_error }, + { "warning", tokenid::hash_warning }, + { "pragma", tokenid::hash_pragma }, + { "include", tokenid::hash_include }, +}; + +inline bool is_octal_digit(char c) +{ + return static_cast(c - '0') < 8; +} +inline bool is_decimal_digit(char c) +{ + return static_cast(c - '0') < 10; +} +inline bool is_hexadecimal_digit(char c) +{ + return is_decimal_digit(c) || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'); +} +bool is_digit(char c, int radix) +{ + switch (radix) + { + case 8: + return is_octal_digit(c); + case 10: + return is_decimal_digit(c); + case 16: + return is_hexadecimal_digit(c); + } + + return false; +} +inline long long octal_to_decimal(long long n) +{ + long long m = 0; + + while (n != 0) + { + m *= 8; + m += n & 7; + n >>= 3; + } + + while (m != 0) + { + n *= 10; + n += m & 7; + m >>= 3; + } + + return n; +} + +std::string reshadefx::token::id_to_name(tokenid id) +{ + const auto it = token_lookup.find(id); + + return it != token_lookup.end() ? it->second : "unknown"; +} + +reshadefx::token reshadefx::lexer::lex() +{ + bool is_at_line_begin = _cur_location.column <= 1; + + token tok; +next_token: + // Reset token data + tok.location = _cur_location; + tok.offset = _cur - _input.data(); + tok.length = 1; + tok.literal_as_double = 0; + + // Do a character type lookup for the current character + switch (type_lookup[static_cast(*_cur)]) + { + case 0xFF: // EOF + tok.id = tokenid::end_of_file; + return tok; + case SPACE: + skip_space(); + if (_ignore_whitespace || is_at_line_begin || *_cur == '\n') + goto next_token; + tok.id = tokenid::space; + tok.length = _cur - _input.data() - tok.offset; + return tok; + case '\n': + _cur++; + _cur_location.line++; + _cur_location.column = 1; + is_at_line_begin = true; + if (_ignore_whitespace) + goto next_token; + tok.id = tokenid::end_of_line; + return tok; + case DIGIT: + parse_numeric_literal(tok); + break; + case IDENT: + parse_identifier(tok); + break; + case '!': + if (_cur[1] == '=') + tok.id = tokenid::exclaim_equal, + tok.length = 2; + else + tok.id = tokenid::exclaim; + break; + case '"': + parse_string_literal(tok, _escape_string_literals); + break; + case '#': + if (is_at_line_begin) + { + if (!parse_pp_directive(tok) || _ignore_pp_directives) + { + skip_to_next_line(); + goto next_token; + } + } // These braces are important so the 'else' is matched to the right 'if' statement + else + tok.id = tokenid::hash; + break; + case '$': + tok.id = tokenid::dollar; + break; + case '%': + if (_cur[1] == '=') + tok.id = tokenid::percent_equal, + tok.length = 2; + else + tok.id = tokenid::percent; + break; + case '&': + if (_cur[1] == '&') + tok.id = tokenid::ampersand_ampersand, + tok.length = 2; + else if (_cur[1] == '=') + tok.id = tokenid::ampersand_equal, + tok.length = 2; + else + tok.id = tokenid::ampersand; + break; + case '(': + tok.id = tokenid::parenthesis_open; + break; + case ')': + tok.id = tokenid::parenthesis_close; + break; + case '*': + if (_cur[1] == '=') + tok.id = tokenid::star_equal, + tok.length = 2; + else + tok.id = tokenid::star; + break; + case '+': + if (_cur[1] == '+') + tok.id = tokenid::plus_plus, + tok.length = 2; + else if (_cur[1] == '=') + tok.id = tokenid::plus_equal, + tok.length = 2; + else + tok.id = tokenid::plus; + break; + case ',': + tok.id = tokenid::comma; + break; + case '-': + if (_cur[1] == '-') + tok.id = tokenid::minus_minus, + tok.length = 2; + else if (_cur[1] == '=') + tok.id = tokenid::minus_equal, + tok.length = 2; + else if (_cur[1] == '>') + tok.id = tokenid::arrow, + tok.length = 2; + else + tok.id = tokenid::minus; + break; + case '.': + if (type_lookup[_cur[1]] == DIGIT) + parse_numeric_literal(tok); + else if (_cur[1] == '.' && _cur[2] == '.') + tok.id = tokenid::ellipsis, + tok.length = 3; + else + tok.id = tokenid::dot; + break; + case '/': + if (_cur[1] == '/') + { + skip_to_next_line(); + if (_ignore_comments) + goto next_token; + tok.id = tokenid::single_line_comment; + tok.length = _cur - _input.data() - tok.offset; + return tok; + } + else if (_cur[1] == '*') + { + while (_cur < _end) + { + if (*_cur == '\n') + { + _cur_location.line++; + _cur_location.column = 1; + } + else if (_cur[0] == '*' && _cur[1] == '/') + { + skip(2); + break; + } + skip(1); + } + if (_ignore_comments) + goto next_token; + tok.id = tokenid::multi_line_comment; + tok.length = _cur - _input.data() - tok.offset; + return tok; + } + else if (_cur[1] == '=') + tok.id = tokenid::slash_equal, + tok.length = 2; + else + tok.id = tokenid::slash; + break; + case ':': + if (_cur[1] == ':') + tok.id = tokenid::colon_colon, + tok.length = 2; + else + tok.id = tokenid::colon; + break; + case ';': + tok.id = tokenid::semicolon; + break; + case '<': + if (_cur[1] == '<') + if (_cur[2] == '=') + tok.id = tokenid::less_less_equal, + tok.length = 3; + else + tok.id = tokenid::less_less, + tok.length = 2; + else if (_cur[1] == '=') + tok.id = tokenid::less_equal, + tok.length = 2; + else + tok.id = tokenid::less; + break; + case '=': + if (_cur[1] == '=') + tok.id = tokenid::equal_equal, + tok.length = 2; + else + tok.id = tokenid::equal; + break; + case '>': + if (_cur[1] == '>') + if (_cur[2] == '=') + tok.id = tokenid::greater_greater_equal, + tok.length = 3; + else + tok.id = tokenid::greater_greater, + tok.length = 2; + else if (_cur[1] == '=') + tok.id = tokenid::greater_equal, + tok.length = 2; + else + tok.id = tokenid::greater; + break; + case '?': + tok.id = tokenid::question; + break; + case '@': + tok.id = tokenid::at; + break; + case '[': + tok.id = tokenid::bracket_open; + break; + case '\\': + tok.id = tokenid::backslash; + break; + case ']': + tok.id = tokenid::bracket_close; + break; + case '^': + if (_cur[1] == '=') + tok.id = tokenid::caret_equal, + tok.length = 2; + else + tok.id = tokenid::caret; + break; + case '{': + tok.id = tokenid::brace_open; + break; + case '|': + if (_cur[1] == '=') + tok.id = tokenid::pipe_equal, + tok.length = 2; + else if (_cur[1] == '|') + tok.id = tokenid::pipe_pipe, + tok.length = 2; + else + tok.id = tokenid::pipe; + break; + case '}': + tok.id = tokenid::brace_close; + break; + case '~': + tok.id = tokenid::tilde; + break; + default: + tok.id = tokenid::unknown; + break; + } + + skip(tok.length); + + return tok; +} + +void reshadefx::lexer::skip(size_t length) +{ + _cur += length; + _cur_location.column += static_cast(length); +} +void reshadefx::lexer::skip_space() +{ + // Skip each character until a space is found + while (type_lookup[*_cur] == SPACE && _cur < _end) + skip(1); +} +void reshadefx::lexer::skip_to_next_line() +{ + // Skip each character until a new line feed is found + while (*_cur != '\n' && _cur < _end) + skip(1); +} + +void reshadefx::lexer::parse_identifier(token &tok) const +{ + auto *const begin = _cur, *end = begin; + + // Skip to the end of the identifier sequence + do end++; while (type_lookup[*end] == IDENT || type_lookup[*end] == DIGIT); + + tok.id = tokenid::identifier; + tok.offset = begin - _input.data(); + tok.length = end - begin; + tok.literal_as_string.assign(begin, end); + + if (_ignore_keywords) + return; + + const auto it = keyword_lookup.find(tok.literal_as_string); + + if (it != keyword_lookup.end()) + { + tok.id = it->second; + } +} +bool reshadefx::lexer::parse_pp_directive(token &tok) +{ + skip(1); // Skip the '#' + skip_space(); // Skip any space between the '#' and directive + parse_identifier(tok); + + const auto it = pp_directive_lookup.find(tok.literal_as_string); + + if (it != pp_directive_lookup.end()) + { + tok.id = it->second; + + return true; + } + else if (!_ignore_line_directives && tok.literal_as_string == "line") // The #line directive needs special handling + { + skip(tok.length); // The 'parse_identifier' does not update the pointer to the current character, so do that now + skip_space(); + parse_numeric_literal(tok); + skip(tok.length); + + _cur_location.line = tok.literal_as_int; + + // Need to subtract one since the line containing #line does not count into the statistics + if (_cur_location.line != 0) + _cur_location.line--; + + skip_space(); + + // Check if this #line directive has an filename attached to it + if (_cur[0] == '"') + { + token temptok; + parse_string_literal(temptok, false); + + _cur_location.source = std::move(temptok.literal_as_string); + } + + // Do not return the #line directive as token to the caller + return false; + } + + tok.id = tokenid::hash_unknown; + + return true; +} +void reshadefx::lexer::parse_string_literal(token &tok, bool escape) const +{ + auto *const begin = _cur, *end = begin + 1; + + for (auto c = *end; c != '"'; c = *++end) + { + if (c == '\n' || end >= _end) + { + // Line feed reached, the string literal is done (technically this should be an error, but the lexer does not report errors, so ignore it) + end--; + break; + } + if (c == '\\' && end[1] == '\n') + { + // Escape character found at end of line, the string literal continues on to the next line + end++; + continue; + } + + // Handle escape sequences + if (c == '\\' && escape) + { + unsigned int n = 0; + + // Any character following the '\' is not parsed as usual, so increment pointer here (this makes sure '\"' does not abort the outer loop as well) + switch (c = *++end) + { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + for (unsigned int i = 0; i < 3 && is_octal_digit(*end) && end < _end; i++) + { + c = *end++; + n = (n << 3) | (c - '0'); + } + // For simplicity the number is limited to what fits in a single character + c = n & 0xFF; + // The octal parsing loop above incremented one pass the escape sequence, so step back + end--; + break; + case 'a': + c = '\a'; + break; + case 'b': + c = '\b'; + break; + case 'f': + c = '\f'; + break; + case 'n': + c = '\n'; + break; + case 'r': + c = '\r'; + break; + case 't': + c = '\t'; + break; + case 'v': + c = '\v'; + break; + case 'x': + if (is_hexadecimal_digit(*++end)) + { + while (is_hexadecimal_digit(*end) && end < _end) + { + c = *end++; + n = (n << 4) | (is_decimal_digit(c) ? c - '0' : c - 55 - 32 * (c & 0x20)); + } + + // For simplicity the number is limited to what fits in a single character + c = n & 0xFF; + } + // The hexadecimal parsing loop and check above incremented one pass the escape sequence, so step back + end--; + break; + } + } + + tok.literal_as_string += c; + } + + tok.id = tokenid::string_literal; + tok.length = end - begin + 1; +} +void reshadefx::lexer::parse_numeric_literal(token &tok) const +{ + // This routine handles both integer and floating point numbers + auto *const begin = _cur, *end = _cur; + int mantissa_size = 0, decimal_location = -1, radix = 10; + long long fraction = 0, exponent = 0; + + // If a literal starts with '0' it is either an octal or hexadecimal ('0x') value + if (begin[0] == '0') + { + if (begin[1] == 'x' || begin[1] == 'X') + { + end = begin + 2; + radix = 16; + } + else + { + radix = 8; + } + } + + for (; mantissa_size <= 18; mantissa_size++, end++) + { + auto c = *end; + + if (is_decimal_digit(c)) + { + c -= '0'; + + if (c >= radix) + break; + } + else if (radix == 16) + { + // Hexadecimal values can contain the letters A to F + if (c >= 'A' && c <= 'F') + c -= 'A' - 10; + else if (c >= 'a' && c <= 'f') + c -= 'a' - 10; + else + break; + } + else + { + if (c != '.' || decimal_location >= 0) + break; + + // Found a decimal character, as such convert current values + if (radix == 8) + { + radix = 10; + fraction = octal_to_decimal(fraction); + } + + decimal_location = mantissa_size; + continue; + } + + fraction *= radix; + fraction += c; + } + + // Ignore additional digits that cannot affect the value + while (is_digit(*end, radix)) + { + end++; + } + + // If a decimal character was found, this is a floating point value, otherwise an integer one + if (decimal_location < 0) + { + tok.id = tokenid::int_literal; + + decimal_location = mantissa_size; + } + else + { + tok.id = tokenid::float_literal; + + mantissa_size -= 1; + } + + // Literals can be followed by an exponent + if (*end == 'E' || *end == 'e') + { + auto tmp = end + 1; + const bool negative = *tmp == '-'; + + if (negative || *tmp == '+') + tmp++; + + if (is_decimal_digit(*tmp)) + { + end = tmp; + + tok.id = tokenid::float_literal; + + do { + exponent *= 10; + exponent += *end++ - '0'; + } while (is_decimal_digit(*end)); + + if (negative) + exponent = -exponent; + } + } + + // Various suffixes force specific literal types + if (*end == 'F' || *end == 'f') + { + end++; // Consume the suffix + + tok.id = tokenid::float_literal; + } + else if (*end == 'L' || *end == 'l') + { + end++; // Consume the suffix + + tok.id = tokenid::double_literal; + } + else if (tok.id == tokenid::int_literal && (*end == 'U' || *end == 'u')) // The 'u' suffix is only valid on integers and needs to be ignored otherwise + { + end++; // Consume the suffix + + tok.id = tokenid::uint_literal; + } + + if (tok.id == tokenid::float_literal || tok.id == tokenid::double_literal) + { + exponent += decimal_location - mantissa_size; + + const bool exponent_negative = exponent < 0; + + if (exponent_negative) + exponent = -exponent; + + // Limit exponent + if (exponent > 511) + exponent = 511; + + // Quick exponent calculation + double e = 1.0; + const double powers_of_10[] = { + 10., + 100., + 1.0e4, + 1.0e8, + 1.0e16, + 1.0e32, + 1.0e64, + 1.0e128, + 1.0e256 + }; + + for (auto d = powers_of_10; exponent != 0; exponent >>= 1, d++) + if (exponent & 1) + e *= *d; + + if (tok.id == tokenid::float_literal) + tok.literal_as_float = exponent_negative ? fraction / static_cast(e) : fraction * static_cast(e); + else + tok.literal_as_double = exponent_negative ? fraction / e : fraction * e; + } + else + { + // Limit the maximum value to what fits into our token structure + tok.literal_as_uint = static_cast(fraction & 0xFFFFFFFF); + } + + tok.length = end - begin; +} diff --git a/msvc/source/effect_lexer.hpp b/msvc/source/effect_lexer.hpp new file mode 100644 index 0000000..01171db --- /dev/null +++ b/msvc/source/effect_lexer.hpp @@ -0,0 +1,281 @@ +/** + * Copyright (C) 2014 Patrick Mours. All rights reserved. + * License: https://github.com/crosire/reshade#license + */ + +#pragma once + +#include "effect_expression.hpp" + +namespace reshadefx +{ + /// + /// A collection of identifiers for various possible tokens. + /// + enum class tokenid + { + unknown = -1, + end_of_file = 0, + end_of_line = '\n', + + // operators + space = ' ', + exclaim = '!', + hash = '#', + dollar = '$', + percent = '%', + ampersand = '&', + parenthesis_open = '(', + parenthesis_close = ')', + star = '*', + plus = '+', + comma = ',', + minus = '-', + dot = '.', + slash = '/', + colon = ':', + semicolon = ';', + less = '<', + equal = '=', + greater = '>', + question = '?', + at = '@', + bracket_open = '[', + backslash = '\\', + bracket_close = ']', + caret = '^', + brace_open = '{', + pipe = '|', + brace_close = '}', + tilde = '~', + exclaim_equal = 256 /* != */, + percent_equal /* %= */, + ampersand_ampersand /* && */, + ampersand_equal /* &= */, + star_equal /* *= */, + plus_plus /* ++*/, + plus_equal /* += */, + minus_minus /* -- */, + minus_equal /* -= */, + arrow /* -> */, + ellipsis /* ... */, + slash_equal /* /= */, + colon_colon /* :: */, + less_less_equal /* <<= */, + less_less /* << */, + less_equal /* <= */, + equal_equal /* == */, + greater_greater_equal /* >>= */, + greater_greater /* >> */, + greater_equal /* >= */, + caret_equal /* ^= */, + pipe_equal /* |= */, + pipe_pipe /* || */, + + // identifiers + reserved, + identifier, + + // literals + true_literal, + false_literal, + int_literal, + uint_literal, + float_literal, + double_literal, + string_literal, + + // keywords + namespace_, + struct_, + technique, + pass, + for_, + while_, + do_, + if_, + else_, + switch_, + case_, + default_, + break_, + continue_, + return_, + discard_, + extern_, + static_, + uniform_, + volatile_, + precise, + in, + out, + inout, + const_, + linear, + noperspective, + centroid, + nointerpolation, + + void_, + bool_, + bool2, + bool3, + bool4, + bool2x2, + bool3x3, + bool4x4, + int_, + int2, + int3, + int4, + int2x2, + int3x3, + int4x4, + uint_, + uint2, + uint3, + uint4, + uint2x2, + uint3x3, + uint4x4, + float_, + float2, + float3, + float4, + float2x2, + float3x3, + float4x4, + vector, + matrix, + string_, + texture, + sampler, + + // preprocessor directives + hash_def, + hash_undef, + hash_if, + hash_ifdef, + hash_ifndef, + hash_else, + hash_elif, + hash_endif, + hash_error, + hash_warning, + hash_pragma, + hash_include, + hash_unknown, + + single_line_comment, + multi_line_comment, + }; + + /// + /// A structure describing a single token in the input string. + /// + struct token + { + tokenid id; + location location; + size_t offset, length; + union + { + int literal_as_int; + unsigned int literal_as_uint; + float literal_as_float; + double literal_as_double; + }; + std::string literal_as_string; + + inline operator tokenid() const { return id; } + + static std::string id_to_name(tokenid id); + }; + + /// + /// A lexical analyzer for C-like languages. + /// + class lexer + { + public: + explicit lexer( + std::string input, + bool ignore_comments = true, + bool ignore_whitespace = true, + bool ignore_pp_directives = true, + bool ignore_line_directives = false, + bool ignore_keywords = false, + bool escape_string_literals = true) : + _input(std::move(input)), + _ignore_comments(ignore_comments), + _ignore_whitespace(ignore_whitespace), + _ignore_pp_directives(ignore_pp_directives), + _ignore_line_directives(ignore_line_directives), + _ignore_keywords(ignore_keywords), + _escape_string_literals(escape_string_literals) + { + _cur = _input.data(); + _end = _cur + _input.size(); + } + + lexer(const lexer &lexer) { operator=(lexer); } + lexer &operator=(const lexer &lexer) + { + _input = lexer._input; + _cur_location = lexer._cur_location; + _cur = _input.data() + (lexer._cur - lexer._input.data()); + _end = _input.data() + _input.size(); + _ignore_comments = lexer._ignore_comments; + _ignore_whitespace = lexer._ignore_whitespace; + _ignore_pp_directives = lexer._ignore_pp_directives; + _ignore_keywords = lexer._ignore_keywords; + _escape_string_literals = lexer._escape_string_literals; + _ignore_line_directives = lexer._ignore_line_directives; + + return *this; + } + + /// + /// Get the input string this lexical analyzer works on. + /// + /// A constant reference to the input string. + const std::string &input_string() const { return _input; } + + /// + /// Perform lexical analysis on the input string and return the next token in sequence. + /// + /// The next token from the input string. + token lex(); + + /// + /// Advances to the next token that is not whitespace. + /// + void skip_space(); + /// + /// Advances to the next new line, ignoring all tokens. + /// + void skip_to_next_line(); + + private: + /// + /// Skips an arbitrary amount of characters in the input string. + /// + /// The number of input characters to skip. + void skip(size_t length); + + void parse_identifier(token &tok) const; + bool parse_pp_directive(token &tok); + void parse_string_literal(token &tok, bool escape) const; + void parse_numeric_literal(token &tok) const; + + std::string _input; + location _cur_location; + const std::string::value_type *_cur, *_end; + bool _ignore_comments; + bool _ignore_whitespace; + bool _ignore_pp_directives; + bool _ignore_line_directives; + bool _ignore_keywords; + bool _escape_string_literals; + }; +} diff --git a/msvc/source/effect_parser.cpp b/msvc/source/effect_parser.cpp new file mode 100644 index 0000000..f2dd78e --- /dev/null +++ b/msvc/source/effect_parser.cpp @@ -0,0 +1,2872 @@ +/** + * Copyright (C) 2014 Patrick Mours. All rights reserved. + * License: https://github.com/crosire/reshade#license + */ + +#include "effect_parser.hpp" +#include "effect_codegen.hpp" +#include +#include +#include + +struct on_scope_exit +{ + template + on_scope_exit(F lambda) : leave(lambda) { } + ~on_scope_exit() { leave(); } + std::function leave; +}; + +bool reshadefx::parser::parse(std::string input, codegen *backend) +{ + _lexer.reset(new lexer(std::move(input))); + _lexer_backup.reset(); + + // Set backend for subsequent code-generation + _codegen = backend; + + consume(); + + bool success = true; + while (!peek(tokenid::end_of_file)) + if (!parse_top()) + success = false; + + return success; +} + +// -- Error Handling -- // + +void reshadefx::parser::error(const location &location, unsigned int code, const std::string &message) +{ + _errors += location.source; + _errors += '(' + std::to_string(location.line) + ", " + std::to_string(location.column) + ')' + ": error"; + _errors += (code == 0) ? ": " : " X" + std::to_string(code) + ": "; + _errors += message; + _errors += '\n'; +} +void reshadefx::parser::warning(const location &location, unsigned int code, const std::string &message) +{ + _errors += location.source; + _errors += '(' + std::to_string(location.line) + ", " + std::to_string(location.column) + ')' + ": warning"; + _errors += (code == 0) ? ": " : " X" + std::to_string(code) + ": "; + _errors += message; + _errors += '\n'; +} + +// -- Token Management -- // + +void reshadefx::parser::backup() +{ + _lexer.swap(_lexer_backup); + _lexer.reset(new lexer(*_lexer_backup)); + _token_backup = _token_next; +} +void reshadefx::parser::restore() +{ + _lexer.swap(_lexer_backup); + _token_next = _token_backup; +} + +void reshadefx::parser::consume() +{ + _token = std::move(_token_next); + _token_next = _lexer->lex(); +} +void reshadefx::parser::consume_until(tokenid tokid) +{ + while (!accept(tokid) && !peek(tokenid::end_of_file)) + { + consume(); + } +} + +bool reshadefx::parser::accept(tokenid tokid) +{ + if (peek(tokid)) + { + consume(); + return true; + } + + return false; +} +bool reshadefx::parser::expect(tokenid tokid) +{ + if (!accept(tokid)) + { + error(_token_next.location, 3000, "syntax error: unexpected '" + token::id_to_name(_token_next.id) + "', expected '" + token::id_to_name(tokid) + '\''); + return false; + } + + return true; +} + +// -- Type Parsing -- // + +bool reshadefx::parser::accept_type_class(type &type) +{ + type.rows = type.cols = 0; + + if (peek(tokenid::identifier)) + { + type.base = type::t_struct; + + const symbol symbol = find_symbol(_token_next.literal_as_string); + + if (symbol.id && symbol.op == symbol_type::structure) + { + type.definition = symbol.id; + + consume(); + return true; + } + + return false; + } + else if (accept(tokenid::vector)) + { + type.base = type::t_float; // Default to float4 unless a type is specified (see below) + type.rows = 4, type.cols = 1; + + if (accept('<')) + { + if (!accept_type_class(type)) // This overwrites the base type again + return error(_token_next.location, 3000, "syntax error: unexpected '" + token::id_to_name(_token_next.id) + "', expected vector element type"), false; + else if (!type.is_scalar()) + return error(_token.location, 3122, "vector element type must be a scalar type"), false; + + if (!expect(',') || !expect(tokenid::int_literal)) + return false; + else if (_token.literal_as_int < 1 || _token.literal_as_int > 4) + return error(_token.location, 3052, "vector dimension must be between 1 and 4"), false; + + type.rows = _token.literal_as_int; + + if (!expect('>')) + return false; + } + + return true; + } + else if (accept(tokenid::matrix)) + { + type.base = type::t_float; // Default to float4x4 unless a type is specified (see below) + type.rows = 4, type.cols = 4; + + if (accept('<')) + { + if (!accept_type_class(type)) // This overwrites the base type again + return error(_token_next.location, 3000, "syntax error: unexpected '" + token::id_to_name(_token_next.id) + "', expected matrix element type"), false; + else if (!type.is_scalar()) + return error(_token.location, 3123, "matrix element type must be a scalar type"), false; + + if (!expect(',') || !expect(tokenid::int_literal)) + return false; + else if (_token.literal_as_int < 1 || _token.literal_as_int > 4) + return error(_token.location, 3053, "matrix dimensions must be between 1 and 4"), false; + + type.rows = _token.literal_as_int; + + if (!expect(',') || !expect(tokenid::int_literal)) + return false; + else if (_token.literal_as_int < 1 || _token.literal_as_int > 4) + return error(_token.location, 3053, "matrix dimensions must be between 1 and 4"), false; + + type.cols = _token.literal_as_int; + + if (!expect('>')) + return false; + } + + return true; + } + + switch (_token_next.id) + { + case tokenid::void_: + type.base = type::t_void; + break; + case tokenid::bool_: + case tokenid::bool2: + case tokenid::bool3: + case tokenid::bool4: + type.base = type::t_bool; + type.rows = 1 + unsigned int(_token_next.id) - unsigned int(tokenid::bool_); + type.cols = 1; + break; + case tokenid::bool2x2: + case tokenid::bool3x3: + case tokenid::bool4x4: + type.base = type::t_bool; + type.rows = 2 + unsigned int(_token_next.id) - unsigned int(tokenid::bool2x2); + type.cols = type.rows; + break; + case tokenid::int_: + case tokenid::int2: + case tokenid::int3: + case tokenid::int4: + type.base = type::t_int; + type.rows = 1 + unsigned int(_token_next.id) - unsigned int(tokenid::int_); + type.cols = 1; + break; + case tokenid::int2x2: + case tokenid::int3x3: + case tokenid::int4x4: + type.base = type::t_int; + type.rows = 2 + unsigned int(_token_next.id) - unsigned int(tokenid::int2x2); + type.cols = type.rows; + break; + case tokenid::uint_: + case tokenid::uint2: + case tokenid::uint3: + case tokenid::uint4: + type.base = type::t_uint; + type.rows = 1 + unsigned int(_token_next.id) - unsigned int(tokenid::uint_); + type.cols = 1; + break; + case tokenid::uint2x2: + case tokenid::uint3x3: + case tokenid::uint4x4: + type.base = type::t_uint; + type.rows = 2 + unsigned int(_token_next.id) - unsigned int(tokenid::uint2x2); + type.cols = type.rows; + break; + case tokenid::float_: + case tokenid::float2: + case tokenid::float3: + case tokenid::float4: + type.base = type::t_float; + type.rows = 1 + unsigned int(_token_next.id) - unsigned int(tokenid::float_); + type.cols = 1; + break; + case tokenid::float2x2: + case tokenid::float3x3: + case tokenid::float4x4: + type.base = type::t_float; + type.rows = 2 + unsigned int(_token_next.id) - unsigned int(tokenid::float2x2); + type.cols = type.rows; + break; + case tokenid::string_: + type.base = type::t_string; + break; + case tokenid::texture: + type.base = type::t_texture; + break; + case tokenid::sampler: + type.base = type::t_sampler; + break; + default: + return false; + } + + consume(); + + return true; +} +bool reshadefx::parser::accept_type_qualifiers(type &type) +{ + unsigned int qualifiers = 0; + + // Storage + if (accept(tokenid::extern_)) + qualifiers |= type::q_extern; + if (accept(tokenid::static_)) + qualifiers |= type::q_static; + if (accept(tokenid::uniform_)) + qualifiers |= type::q_uniform; + if (accept(tokenid::volatile_)) + qualifiers |= type::q_volatile; + if (accept(tokenid::precise)) + qualifiers |= type::q_precise; + + if (accept(tokenid::in)) + qualifiers |= type::q_in; + if (accept(tokenid::out)) + qualifiers |= type::q_out; + if (accept(tokenid::inout)) + qualifiers |= type::q_inout; + + // Modifiers + if (accept(tokenid::const_)) + qualifiers |= type::q_const; + + // Interpolation + if (accept(tokenid::linear)) + qualifiers |= type::q_linear; + if (accept(tokenid::noperspective)) + qualifiers |= type::q_noperspective; + if (accept(tokenid::centroid)) + qualifiers |= type::q_centroid; + if (accept(tokenid::nointerpolation)) + qualifiers |= type::q_nointerpolation; + + if (qualifiers == 0) + return false; + if ((type.qualifiers & qualifiers) == qualifiers) + warning(_token.location, 3048, "duplicate usages specified"); + + type.qualifiers |= qualifiers; + + // Continue parsing potential additional qualifiers until no more are found + accept_type_qualifiers(type); + + return true; +} + +bool reshadefx::parser::parse_type(type &type) +{ + type.qualifiers = 0; + + accept_type_qualifiers(type); + + if (!accept_type_class(type)) + return false; + + if (type.is_integral() && (type.has(type::q_centroid) || type.has(type::q_noperspective))) + return error(_token.location, 4576, "signature specifies invalid interpolation mode for integer component type"), false; + else if (type.has(type::q_centroid) && !type.has(type::q_noperspective)) + type.qualifiers |= type::q_linear; + + return true; +} + +bool reshadefx::parser::parse_array_size(type &type) +{ + // Reset array length to zero before checking if one exists + type.array_length = 0; + + if (accept('[')) + { + if (accept(']')) + { + // No length expression, so this is an unsized array + type.array_length = -1; + } + else if (expression expression; parse_expression(expression) && expect(']')) + { + if (!expression.is_constant || !(expression.type.is_scalar() && expression.type.is_integral())) + return error(expression.location, 3058, "array dimensions must be literal scalar expressions"), false; + + type.array_length = expression.constant.as_uint[0]; + + if (type.array_length < 1 || type.array_length > 65536) + return error(expression.location, 3059, "array dimension must be between 1 and 65536"), false; + } + else + { + return false; + } + } + + return true; +} + +// -- Expression Parsing -- // + +bool reshadefx::parser::accept_unary_op() +{ + switch (_token_next.id) + { + case tokenid::exclaim: // !x (logical not) + case tokenid::plus: // +x + case tokenid::minus: // -x (negate) + case tokenid::tilde: // ~x (bitwise not) + case tokenid::plus_plus: // ++x + case tokenid::minus_minus: // --x + break; + default: + return false; + } + + consume(); + + return true; +} +bool reshadefx::parser::accept_postfix_op() +{ + switch (_token_next.id) + { + case tokenid::plus_plus: // ++x + case tokenid::minus_minus: // --x + break; + default: + return false; + } + + consume(); + + return true; +} +bool reshadefx::parser::peek_multary_op(unsigned int &precedence) const +{ + // Precedence values taken from https://cppreference.com/w/cpp/language/operator_precedence + switch (_token_next.id) + { + case tokenid::question: precedence = 1; break; // x ? a : b + case tokenid::pipe_pipe: precedence = 2; break; // a || b (logical or) + case tokenid::ampersand_ampersand: precedence = 3; break; // a && b (logical and) + case tokenid::pipe: precedence = 4; break; // a | b (bitwise or) + case tokenid::caret: precedence = 5; break; // a ^ b (bitwise xor) + case tokenid::ampersand: precedence = 6; break; // a & b (bitwise and) + case tokenid::equal_equal: precedence = 7; break; // a == b (equal) + case tokenid::exclaim_equal: precedence = 7; break; // a != b (not equal) + case tokenid::less: precedence = 8; break; // a < b + case tokenid::greater: precedence = 8; break; // a > b + case tokenid::less_equal: precedence = 8; break; // a <= b + case tokenid::greater_equal: precedence = 8; break; // a >= b + case tokenid::less_less: precedence = 9; break; // a << b (left shift) + case tokenid::greater_greater: precedence = 9; break; // a >> b (right shift) + case tokenid::plus: precedence = 10; break; // a + b (add) + case tokenid::minus: precedence = 10; break; // a - b (subtract) + case tokenid::star: precedence = 11; break; // a * b (multiply) + case tokenid::slash: precedence = 11; break; // a / b (divide) + case tokenid::percent: precedence = 11; break; // a % b (modulo) + default: + return false; + } + + // Do not consume token yet since the expression may be skipped due to precedence + return true; +} +bool reshadefx::parser::accept_assignment_op() +{ + switch (_token_next.id) + { + case tokenid::equal: // a = b + case tokenid::percent_equal: // a %= b + case tokenid::ampersand_equal: // a &= b + case tokenid::star_equal: // a *= b + case tokenid::plus_equal: // a += b + case tokenid::minus_equal: // a -= b + case tokenid::slash_equal: // a /= b + case tokenid::less_less_equal: // a <<= b + case tokenid::greater_greater_equal: // a >>= b + case tokenid::caret_equal: // a ^= b + case tokenid::pipe_equal: // a |= b + break; + default: + return false; + } + + consume(); + + return true; +} + +bool reshadefx::parser::parse_expression(expression &exp) +{ + // Parse first expression + if (!parse_expression_assignment(exp)) + return false; + + // Continue parsing if an expression sequence is next (in the form "a, b, c, ...") + while (accept(',')) + // Overwrite 'exp' since conveniently the last expression in the sequence is the result + if (!parse_expression_assignment(exp)) + return false; + + return true; +} + +bool reshadefx::parser::parse_expression_unary(expression &exp) +{ + auto location = _token_next.location; + + #pragma region Prefix Expression + // Check if a prefix operator exists + if (accept_unary_op()) + { + // Remember the operator token before parsing the expression that follows it + const tokenid op = _token.id; + + // Parse the actual expression + if (!parse_expression_unary(exp)) + return false; + + // Unary operators are only valid on basic types + if (!exp.type.is_scalar() && !exp.type.is_vector() && !exp.type.is_matrix()) + return error(exp.location, 3022, "scalar, vector, or matrix expected"), false; + + // Special handling for the "++" and "--" operators + if (op == tokenid::plus_plus || op == tokenid::minus_minus) + { + if (exp.type.has(type::q_const) || exp.type.has(type::q_uniform) || !exp.is_lvalue) + return error(location, 3025, "l-value specifies const object"), false; + + // Create a constant one in the type of the expression + constant one = {}; + for (unsigned int i = 0; i < exp.type.components(); ++i) + if (exp.type.is_floating_point()) one.as_float[i] = 1.0f; else one.as_uint[i] = 1u; + + const auto value = _codegen->emit_load(exp); + const auto result = _codegen->emit_binary_op(location, op, exp.type, value, + _codegen->emit_constant(exp.type, one)); + + // The "++" and "--" operands modify the source variable, so store result back into it + _codegen->emit_store(exp, result); + } + else if (op != tokenid::plus) // Ignore "+" operator since it does not actually do anything + { + // The "~" bitwise operator is only valid on integral types + if (op == tokenid::tilde && !exp.type.is_integral()) + return error(exp.location, 3082, "int or unsigned int type required"), false; + // The logical not operator expects a boolean type as input, so perform cast if necessary + if (op == tokenid::exclaim && !exp.type.is_boolean()) + exp.add_cast_operation({ type::t_bool, exp.type.rows, exp.type.cols }); // Note: The result will be boolean as well + + // Constant expressions can be evaluated at compile time + if (!exp.evaluate_constant_expression(op)) + { + const auto value = _codegen->emit_load(exp); + const auto result = _codegen->emit_unary_op(location, op, exp.type, value); + + exp.reset_to_rvalue(location, result, exp.type); + } + } + } + else if (accept('(')) + { + backup(); + + // Check if this is a C-style cast expression + if (type cast_type; accept_type_class(cast_type)) + { + if (peek('(')) + { + // This is not a C-style cast but a constructor call, so need to roll-back and parse that instead + restore(); + } + else if (expect(')')) + { + // Parse the expression behind cast operator + if (!parse_expression_unary(exp)) + return false; + + // Check if the types already match, in which case there is nothing to do + if (exp.type == cast_type) + return true; + + // Check if a cast between these types is valid + if (!type::rank(exp.type, cast_type)) + return error(location, 3017, "cannot convert these types"), false; + + exp.add_cast_operation(cast_type); + return true; + } + else + { + // Type name was not followed by a closing parenthesis + return false; + } + } + + // Parse expression between the parentheses + if (!parse_expression(exp) || !expect(')')) + return false; + } + else if (accept('{')) + { + bool is_constant = true; + std::vector elements; + type composite_type = { type::t_bool, 1, 1 }; + + while (!peek('}')) + { + // There should be a comma between arguments + if (!elements.empty() && !expect(',')) + return consume_until('}'), false; + + // Initializer lists might contain a comma at the end, so break out of the loop if nothing follows afterwards + if (peek('}')) + break; + + // Parse the argument expression + if (!parse_expression_assignment(elements.emplace_back())) + return consume_until('}'), false; + + expression &element = elements.back(); + + is_constant &= element.is_constant; // Result is only constant if all arguments are constant + composite_type = type::merge(composite_type, element.type); + } + + // Constant arrays can be constructed at compile time + if (is_constant) + { + constant res = {}; + for (expression &element : elements) + { + element.add_cast_operation(composite_type); + res.array_data.push_back(element.constant); + } + + composite_type.array_length = static_cast(elements.size()); + + exp.reset_to_rvalue_constant(location, std::move(res), composite_type); + } + else + { + composite_type.array_length = static_cast(elements.size()); + + // Resolve all access chains + for (expression &element : elements) + { + element.reset_to_rvalue(element.location, _codegen->emit_load(element), element.type); + } + + const auto result = _codegen->emit_construct(location, composite_type, elements); + + exp.reset_to_rvalue(location, result, composite_type); + } + + return expect('}'); + } + else if (accept(tokenid::true_literal)) + { + exp.reset_to_rvalue_constant(location, true); + } + else if (accept(tokenid::false_literal)) + { + exp.reset_to_rvalue_constant(location, false); + } + else if (accept(tokenid::int_literal)) + { + exp.reset_to_rvalue_constant(location, _token.literal_as_int); + } + else if (accept(tokenid::uint_literal)) + { + exp.reset_to_rvalue_constant(location, _token.literal_as_uint); + } + else if (accept(tokenid::float_literal)) + { + exp.reset_to_rvalue_constant(location, _token.literal_as_float); + } + else if (accept(tokenid::double_literal)) + { + // Convert double literal to float literal for now + warning(location, 5000, "double literal truncated to float literal"); + + exp.reset_to_rvalue_constant(location, static_cast(_token.literal_as_double)); + } + else if (accept(tokenid::string_literal)) + { + std::string value = std::move(_token.literal_as_string); + + // Multiple string literals in sequence are concatenated into a single string literal + while (accept(tokenid::string_literal)) + value += _token.literal_as_string; + + exp.reset_to_rvalue_constant(location, std::move(value)); + } + else if (type type; accept_type_class(type)) // Check if this is a constructor call expression + { + if (!expect('(')) + return false; + if (!type.is_numeric()) + return error(location, 3037, "constructors only defined for numeric base types"), false; + + // Empty constructors do not exist + if (accept(')')) + return error(location, 3014, "incorrect number of arguments to numeric-type constructor"), false; + + // Parse entire argument expression list + bool is_constant = true; + unsigned int num_components = 0; + std::vector arguments; + + while (!peek(')')) + { + // There should be a comma between arguments + if (!arguments.empty() && !expect(',')) + return false; + + // Parse the argument expression + if (!parse_expression_assignment(arguments.emplace_back())) + return false; + + expression &argument = arguments.back(); + + // Constructors are only defined for numeric base types + if (!argument.type.is_numeric()) + return error(argument.location, 3017, "cannot convert non-numeric types"), false; + + is_constant &= argument.is_constant; // Result is only constant if all arguments are constant + num_components += argument.type.components(); + } + + // The list should be terminated with a parenthesis + if (!expect(')')) + return false; + + // The total number of argument elements needs to match the number of elements in the result type + if (num_components != type.components()) + return error(location, 3014, "incorrect number of arguments to numeric-type constructor"), false; + + assert(num_components > 0 && num_components <= 16 && !type.is_array()); + + if (is_constant) // Constants can be converted at compile time + { + constant res = {}; + unsigned int i = 0; + for (expression &argument : arguments) + { + argument.add_cast_operation({ type.base, argument.type.rows, argument.type.cols }); + for (unsigned int k = 0; k < argument.type.components(); ++k) + res.as_uint[i++] = argument.constant.as_uint[k]; + } + + exp.reset_to_rvalue_constant(location, std::move(res), type); + } + else if (arguments.size() > 1) + { + // Flatten all arguments to a list of scalars + for (auto it = arguments.begin(); it != arguments.end();) + { + // Argument is a scalar already, so only need to cast it + if (it->type.is_scalar()) + { + expression &argument = *it++; + + auto scalar_type = argument.type; + scalar_type.base = type.base; + argument.add_cast_operation(scalar_type); + + argument.reset_to_rvalue(argument.location, _codegen->emit_load(argument), scalar_type); + } + else + { + const expression argument = *it; + it = arguments.erase(it); + + // Convert to a scalar value and re-enter the loop in the next iteration (in case a cast is necessary too) + for (unsigned int i = argument.type.components(); i > 0; --i) + { + expression scalar = argument; + scalar.add_constant_index_access(i - 1); + + it = arguments.insert(it, scalar); + } + } + } + + const auto result = _codegen->emit_construct(location, type, arguments); + + exp.reset_to_rvalue(location, result, type); + } + else // A constructor call with a single argument is identical to a cast + { + assert(!arguments.empty()); + + // Reset expression to only argument and add cast to expression access chain + exp = std::move(arguments[0]); exp.add_cast_operation(type); + } + } + else // At this point only identifiers are left to check and resolve + { + // Starting an identifier with '::' restricts the symbol search to the global namespace level + const bool exclusive = accept(tokenid::colon_colon); + + std::string identifier; + + if (exclusive ? expect(tokenid::identifier) : accept(tokenid::identifier)) + identifier = std::move(_token.literal_as_string); + else + return false; // Warning: This may leave the expression path without issuing an error, so need to catch that at the call side! + + // Can concatenate multiple '::' to force symbol search for a specific namespace level + while (accept(tokenid::colon_colon)) + { + if (!expect(tokenid::identifier)) + return false; + identifier += "::" + std::move(_token.literal_as_string); + } + + // Figure out which scope to start searching in + scope scope = { "::", 0, 0 }; + if (!exclusive) scope = current_scope(); + + // Lookup name in the symbol table + symbol symbol = find_symbol(identifier, scope, exclusive); + + // Check if this is a function call or variable reference + if (accept('(')) + { + // Can only call symbols that are functions, but do not abort yet if no symbol was found since the identifier may reference an intrinsic + if (symbol.id && symbol.op != symbol_type::function) + return error(location, 3005, "identifier '" + identifier + "' represents a variable, not a function"), false; + + // Parse entire argument expression list + std::vector arguments; + + while (!peek(')')) + { + // There should be a comma between arguments + if (!arguments.empty() && !expect(',')) + return false; + + // Parse the argument expression + if (!parse_expression_assignment(arguments.emplace_back())) + return false; + } + + // The list should be terminated with a parenthesis + if (!expect(')')) + return false; + + // Try to resolve the call by searching through both function symbols and intrinsics + bool undeclared = !symbol.id, ambiguous = false; + + if (!resolve_function_call(identifier, arguments, scope, symbol, ambiguous)) + { + if (undeclared) + error(location, 3004, "undeclared identifier or no matching intrinsic overload for '" + identifier + '\''); + else if (ambiguous) + error(location, 3067, "ambiguous function call to '" + identifier + '\''); + else + error(location, 3013, "no matching function overload for '" + identifier + '\''); + return false; + } + + assert(symbol.function != nullptr); + + std::vector parameters(arguments.size()); + + // We need to allocate some temporary variables to pass in and load results from pointer parameters + for (size_t i = 0; i < arguments.size(); ++i) + { + const auto ¶m_type = symbol.function->parameter_list[i].type; + + if (arguments[i].type.components() > param_type.components()) + warning(arguments[i].location, 3206, "implicit truncation of vector type"); + + arguments[i].add_cast_operation(param_type); + + if (symbol.op == symbol_type::function || param_type.has(type::q_out)) + { + // All user-defined functions actually accept pointers as arguments, same applies to intrinsics with 'out' parameters + const auto temp_variable = _codegen->define_variable(arguments[i].location, param_type); + parameters[i].reset_to_lvalue(arguments[i].location, temp_variable, param_type); + } + else + { + parameters[i].reset_to_rvalue(arguments[i].location, _codegen->emit_load(arguments[i]), param_type); + } + } + + // Copy in parameters from the argument access chains to parameter variables + for (size_t i = 0; i < arguments.size(); ++i) + if (parameters[i].is_lvalue && parameters[i].type.has(type::q_in)) // Only do this for pointer parameters as discovered above + _codegen->emit_store(parameters[i], _codegen->emit_load(arguments[i])); + + // Check if the call resolving found an intrinsic or function and invoke the corresponding code + const auto result = symbol.op == symbol_type::function ? + _codegen->emit_call(location, symbol.id, symbol.type, parameters) : + _codegen->emit_call_intrinsic(location, symbol.id, symbol.type, parameters); + + exp.reset_to_rvalue(location, result, symbol.type); + + // Copy out parameters from parameter variables back to the argument access chains + for (size_t i = 0; i < arguments.size(); ++i) + if (parameters[i].is_lvalue && parameters[i].type.has(type::q_out)) // Only do this for pointer parameters as discovered above + _codegen->emit_store(arguments[i], _codegen->emit_load(parameters[i])); + } + else if (symbol.op == symbol_type::invalid) + { + // Show error if no symbol matching the identifier was found + return error(location, 3004, "undeclared identifier '" + identifier + '\''), false; + } + else if (symbol.op == symbol_type::variable) + { + assert(symbol.id != 0); + // Simply return the pointer to the variable, dereferencing is done on site where necessary + exp.reset_to_lvalue(location, symbol.id, symbol.type); + } + else if (symbol.op == symbol_type::constant) + { + // Constants are loaded into the access chain + exp.reset_to_rvalue_constant(location, symbol.constant, symbol.type); + } + else + { + // Can only reference variables and constants by name, functions need to be called + return error(location, 3005, "identifier '" + identifier + "' represents a function, not a variable"), false; + } + } + #pragma endregion + + #pragma region Postfix Expression + while (!peek(tokenid::end_of_file)) + { + location = _token_next.location; + + // Check if a postfix operator exists + if (accept_postfix_op()) + { + // Unary operators are only valid on basic types + if (!exp.type.is_scalar() && !exp.type.is_vector() && !exp.type.is_matrix()) + return error(exp.location, 3022, "scalar, vector, or matrix expected"), false; + if (exp.type.has(type::q_const) || exp.type.has(type::q_uniform) || !exp.is_lvalue) + return error(exp.location, 3025, "l-value specifies const object"), false; + + // Create a constant one in the type of the expression + constant one = {}; + for (unsigned int i = 0; i < exp.type.components(); ++i) + if (exp.type.is_floating_point()) one.as_float[i] = 1.0f; else one.as_uint[i] = 1u; + + const auto value = _codegen->emit_load(exp); + const auto result = _codegen->emit_binary_op(location, _token.id, exp.type, value, _codegen->emit_constant(exp.type, one)); + + // The "++" and "--" operands modify the source variable, so store result back into it + _codegen->emit_store(exp, result); + + // All postfix operators return a r-value rather than a l-value to the variable + exp.reset_to_rvalue(location, result, exp.type); + } + else if (accept('.')) + { + if (!expect(tokenid::identifier)) + return false; + + location = std::move(_token.location); + const auto subscript = std::move(_token.literal_as_string); + + if (accept('(')) // Methods (function calls on types) are not supported right now + { + if (!exp.type.is_struct() || exp.type.is_array()) + error(location, 3087, "object does not have methods"); + else + error(location, 3088, "structures do not have methods"); + return false; + } + else if (exp.type.is_array()) // Arrays do not have subscripts + { + error(location, 3018, "invalid subscript on array"); + return false; + } + else if (exp.type.is_vector()) + { + const size_t length = subscript.size(); + if (length > 4) + return error(location, 3018, "invalid subscript '" + subscript + "', swizzle too long"), false; + + bool is_const = false; + signed char offsets[4] = { -1, -1, -1, -1 }; + enum { xyzw, rgba, stpq } set[4]; + + for (size_t i = 0; i < length; ++i) + { + switch (subscript[i]) + { + case 'x': offsets[i] = 0, set[i] = xyzw; break; + case 'y': offsets[i] = 1, set[i] = xyzw; break; + case 'z': offsets[i] = 2, set[i] = xyzw; break; + case 'w': offsets[i] = 3, set[i] = xyzw; break; + case 'r': offsets[i] = 0, set[i] = rgba; break; + case 'g': offsets[i] = 1, set[i] = rgba; break; + case 'b': offsets[i] = 2, set[i] = rgba; break; + case 'a': offsets[i] = 3, set[i] = rgba; break; + case 's': offsets[i] = 0, set[i] = stpq; break; + case 't': offsets[i] = 1, set[i] = stpq; break; + case 'p': offsets[i] = 2, set[i] = stpq; break; + case 'q': offsets[i] = 3, set[i] = stpq; break; + default: + return error(location, 3018, "invalid subscript '" + subscript + '\''), false; + } + + if (i > 0 && (set[i] != set[i - 1])) + return error(location, 3018, "invalid subscript '" + subscript + "', mixed swizzle sets"), false; + if (static_cast(offsets[i]) >= exp.type.rows) + return error(location, 3018, "invalid subscript '" + subscript + "', swizzle out of range"), false; + + // The result is not modifiable if a swizzle appears multiple times + for (size_t k = 0; k < i; ++k) + if (offsets[k] == offsets[i]) { + is_const = true; + break; + } + } + + // Add swizzle to current access chain + exp.add_swizzle_access(offsets, static_cast(length)); + + if (is_const || exp.type.has(type::q_uniform)) + exp.type.qualifiers = (exp.type.qualifiers | type::q_const) & ~type::q_uniform; + } + else if (exp.type.is_matrix()) + { + const size_t length = subscript.size(); + if (length < 3) + return error(location, 3018, "invalid subscript '" + subscript + '\''), false; + + bool is_const = false; + signed char offsets[4] = { -1, -1, -1, -1 }; + const unsigned int set = subscript[1] == 'm'; + const int coefficient = !set; + + for (size_t i = 0, j = 0; i < length; i += 3 + set, ++j) + { + if (subscript[i] != '_' || subscript[i + set + 1] < '0' + coefficient || subscript[i + set + 1] > '3' + coefficient || subscript[i + set + 2] < '0' + coefficient || subscript[i + set + 2] > '3' + coefficient) + return error(location, 3018, "invalid subscript '" + subscript + '\''), false; + if (set && subscript[i + 1] != 'm') + return error(location, 3018, "invalid subscript '" + subscript + "', mixed swizzle sets"), false; + + const unsigned int row = subscript[i + set + 1] - '0' - coefficient; + const unsigned int col = subscript[i + set + 2] - '0' - coefficient; + + if ((row >= exp.type.rows || col >= exp.type.cols) || j > 3) + return error(location, 3018, "invalid subscript '" + subscript + "', swizzle out of range"), false; + + offsets[j] = static_cast(row * 4 + col); + + // The result is not modifiable if a swizzle appears multiple times + for (size_t k = 0; k < j; ++k) + if (offsets[k] == offsets[j]) { + is_const = true; + break; + } + } + + // Add swizzle to current access chain + exp.add_swizzle_access(offsets, static_cast(length / (3 + set))); + + if (is_const || exp.type.has(type::q_uniform)) + exp.type.qualifiers = (exp.type.qualifiers | type::q_const) & ~type::q_uniform; + } + else if (exp.type.is_struct()) + { + const auto &member_list = _codegen->find_struct(exp.type.definition).member_list; + + // Find member with matching name is structure definition + uint32_t member_index = 0; + for (const struct_member_info &member : member_list) { + if (member.name == subscript) + break; + ++member_index; + } + + if (member_index >= member_list.size()) + return error(location, 3018, "invalid subscript '" + subscript + '\''), false; + + // Add field index to current access chain + exp.add_member_access(member_index, member_list[member_index].type); + + if (exp.type.has(type::q_uniform)) // Member access to uniform structure is not modifiable + exp.type.qualifiers = (exp.type.qualifiers | type::q_const) & ~type::q_uniform; + } + else if (exp.type.is_scalar()) + { + const size_t length = subscript.size(); + if (length > 4) + return error(location, 3018, "invalid subscript '" + subscript + "', swizzle too long"), false; + + for (size_t i = 0; i < length; ++i) + if ((subscript[i] != 'x' && subscript[i] != 'r' && subscript[i] != 's') || i > 3) + return error(location, 3018, "invalid subscript '" + subscript + '\''), false; + + // Promote scalar to vector type using cast + auto target_type = exp.type; + target_type.rows = static_cast(length); + + exp.add_cast_operation(target_type); + } + else + { + error(location, 3018, "invalid subscript '" + subscript + '\''); + return false; + } + } + else if (accept('[')) + { + if (!exp.type.is_array() && !exp.type.is_vector() && !exp.type.is_matrix()) + return error(_token.location, 3121, "array, matrix, vector, or indexable object type expected in index expression"), false; + + // Parse index expression + expression index; + if (!parse_expression(index) || !expect(']')) + return false; + else if (!index.type.is_scalar() || !index.type.is_integral()) + return error(index.location, 3120, "invalid type for index - index must be an integer scalar"), false; + + // Add index expression to current access chain + if (index.is_constant) + { + // Check array bounds if known + if (exp.type.array_length > 0 && index.constant.as_uint[0] >= static_cast(exp.type.array_length)) + return error(index.location, 3504, "array index out of bounds"), false; + + exp.add_constant_index_access(index.constant.as_uint[0]); + } + else + { + if (exp.is_constant) + { + // To handle a dynamic index into a constant means we need to create a local variable first or else any of the indexing instructions do not work + const auto temp_variable = _codegen->define_variable(location, exp.type, std::string(), false, _codegen->emit_constant(exp.type, exp.constant)); + exp.reset_to_lvalue(exp.location, temp_variable, exp.type); + } + + exp.add_dynamic_index_access(_codegen->emit_load(index)); + } + } + else + { + break; + } + } + #pragma endregion + + return true; +} + +bool reshadefx::parser::parse_expression_multary(expression &lhs, unsigned int left_precedence) +{ + // Parse left hand side of the expression + if (!parse_expression_unary(lhs)) + return false; + + // Check if an operator exists so that this is a binary or ternary expression + unsigned int right_precedence; + + while (peek_multary_op(right_precedence)) + { + // Only process this operator if it has a lower precedence than the current operation, otherwise leave it for later and abort + if (right_precedence <= left_precedence) + break; + + // Finally consume the operator token + consume(); + + const tokenid op = _token.id; + + // Check if this is a binary or ternary operation + if (op != tokenid::question) + { + #pragma region Binary Expression +#if RESHADEFX_SHORT_CIRCUIT + codegen::id lhs_block = 0; + codegen::id rhs_block = 0; + codegen::id merge_block = 0; + + // Switch block to a new one before parsing right-hand side value in case it needs to be skipped during short-circuiting + if (op == tokenid::ampersand_ampersand || op == tokenid::pipe_pipe) + { + lhs_block = _codegen->set_block(0); + rhs_block = _codegen->create_block(); + merge_block = _codegen->create_block(); + + _codegen->enter_block(rhs_block); + } +#endif + // Parse the right hand side of the binary operation + expression rhs; + if (!parse_expression_multary(rhs, right_precedence)) + return false; + + // Deduce the result base type based on implicit conversion rules + type type = type::merge(lhs.type, rhs.type); + bool is_bool_result = false; + + // Do some error checking depending on the operator + if (op == tokenid::equal_equal || op == tokenid::exclaim_equal) + { + // Equality checks return a boolean value + is_bool_result = true; + + // Cannot check equality between incompatible types + if (lhs.type.is_array() || rhs.type.is_array() || lhs.type.definition != rhs.type.definition) + return error(rhs.location, 3020, "type mismatch"), false; + } + else if (op == tokenid::ampersand || op == tokenid::pipe || op == tokenid::caret) + { + // Cannot perform bitwise operations on non-integral types + if (!lhs.type.is_integral()) + return error(lhs.location, 3082, "int or unsigned int type required"), false; + if (!rhs.type.is_integral()) + return error(rhs.location, 3082, "int or unsigned int type required"), false; + } + else + { + if (op == tokenid::ampersand_ampersand || op == tokenid::pipe_pipe) + type.base = type::t_bool; + + // Logical operations return a boolean value + if (op == tokenid::less || op == tokenid::less_equal || op == tokenid::greater || op == tokenid::greater_equal) + is_bool_result = true; + + // Cannot perform arithmetic operations on non-basic types + if (!lhs.type.is_scalar() && !lhs.type.is_vector() && !lhs.type.is_matrix()) + return error(lhs.location, 3022, "scalar, vector, or matrix expected"), false; + if (!rhs.type.is_scalar() && !rhs.type.is_vector() && !rhs.type.is_matrix()) + return error(rhs.location, 3022, "scalar, vector, or matrix expected"), false; + } + + // Perform implicit type conversion + if (lhs.type.components() > type.components()) + warning(lhs.location, 3206, "implicit truncation of vector type"); + if (rhs.type.components() > type.components()) + warning(rhs.location, 3206, "implicit truncation of vector type"); + + lhs.add_cast_operation(type); + rhs.add_cast_operation(type); + +#if RESHADEFX_SHORT_CIRCUIT + // Reset block to left-hand side since the load of the left-hand side value has to happen in there + if (op == tokenid::ampersand_ampersand || op == tokenid::pipe_pipe) + _codegen->set_block(lhs_block); +#endif + + // Constant expressions can be evaluated at compile time + if (rhs.is_constant && lhs.evaluate_constant_expression(op, rhs.constant)) + continue; + + const auto lhs_value = _codegen->emit_load(lhs); + +#if RESHADEFX_SHORT_CIRCUIT + // Short circuit for logical && and || operators + if (op == tokenid::ampersand_ampersand || op == tokenid::pipe_pipe) + { + // Emit "if ( lhs) result = rhs" for && expression + codegen::id condition_value = lhs_value; + // Emit "if (!lhs) result = rhs" for || expression + if (op == tokenid::pipe_pipe) + condition_value = _codegen->emit_unary_op(lhs.location, tokenid::exclaim, type, lhs_value); + + _codegen->leave_block_and_branch_conditional(condition_value, rhs_block, merge_block); + + _codegen->set_block(rhs_block); + // Only load value of right hand side expression after entering the second block + const auto rhs_value = _codegen->emit_load(rhs); + _codegen->leave_block_and_branch(merge_block); + + _codegen->enter_block(merge_block); + + const auto result_value = _codegen->emit_phi(lhs.location, condition_value, lhs_block, rhs_value, rhs_block, lhs_value, lhs_block, type); + + lhs.reset_to_rvalue(lhs.location, result_value, type); + continue; + } +#endif + const auto rhs_value = _codegen->emit_load(rhs); + + // Certain operations return a boolean type instead of the type of the input expressions + if (is_bool_result) + type = { type::t_bool, type.rows, type.cols }; + + const auto result_value = _codegen->emit_binary_op(lhs.location, op, type, lhs.type, lhs_value, rhs_value); + + lhs.reset_to_rvalue(lhs.location, result_value, type); + #pragma endregion + } + else + { + #pragma region Ternary Expression + // A conditional expression needs a scalar or vector type condition + if (!lhs.type.is_scalar() && !lhs.type.is_vector()) + return error(lhs.location, 3022, "boolean or vector expression expected"), false; + +#if RESHADEFX_SHORT_CIRCUIT + // Switch block to a new one before parsing first part in case it needs to be skipped during short-circuiting + const codegen::id merge_block = _codegen->create_block(); + const codegen::id condition_block = _codegen->set_block(0); + codegen::id true_block = _codegen->create_block(); + codegen::id false_block = _codegen->create_block(); + + _codegen->enter_block(true_block); +#endif + // Parse the first part of the right hand side of the ternary operation + expression true_exp; + if (!parse_expression(true_exp)) + return false; + + if (!expect(':')) + return false; + +#if RESHADEFX_SHORT_CIRCUIT + // Switch block to a new one before parsing second part in case it needs to be skipped during short-circuiting + _codegen->set_block(0); + _codegen->enter_block(false_block); +#endif + // Parse the second part of the right hand side of the ternary operation + expression false_exp; + if (!parse_expression_assignment(false_exp)) + return false; + + // Check that the condition dimension matches that of at least one side + if (lhs.type.is_vector() && lhs.type.rows != true_exp.type.rows && lhs.type.cols != true_exp.type.cols) + return error(lhs.location, 3020, "dimension of conditional does not match value"), false; + + // Check that the two value expressions can be converted between each other + if (true_exp.type.array_length != false_exp.type.array_length || true_exp.type.definition != false_exp.type.definition) + return error(false_exp.location, 3020, "type mismatch between conditional values"), false; + + // Deduce the result base type based on implicit conversion rules + const type type = type::merge(true_exp.type, false_exp.type); + + if (true_exp.type.components() > type.components()) + warning(true_exp.location, 3206, "implicit truncation of vector type"); + if (false_exp.type.components() > type.components()) + warning(false_exp.location, 3206, "implicit truncation of vector type"); + +#if RESHADEFX_SHORT_CIRCUIT + // Reset block to left-hand side since the load of the condition value has to happen in there + _codegen->set_block(condition_block); +#else + // The conditional operator instruction expects the condition to be a boolean type + lhs.add_cast_operation({ type::t_bool, type.rows, 1 }); +#endif + true_exp.add_cast_operation(type); + false_exp.add_cast_operation(type); + + // Load condition value from expression + const auto condition_value = _codegen->emit_load(lhs); + +#if RESHADEFX_SHORT_CIRCUIT + _codegen->leave_block_and_branch_conditional(condition_value, true_block, false_block); + + _codegen->set_block(true_block); + // Only load true expression value after entering the first block + const auto true_value = _codegen->emit_load(true_exp); + true_block = _codegen->leave_block_and_branch(merge_block); + + _codegen->set_block(false_block); + // Only load false expression value after entering the second block + const auto false_value = _codegen->emit_load(false_exp); + false_block = _codegen->leave_block_and_branch(merge_block); + + _codegen->enter_block(merge_block); + + const auto result_value = _codegen->emit_phi(lhs.location, condition_value, condition_block, true_value, true_block, false_value, false_block, type); +#else + const auto true_value = _codegen->emit_load(true_exp); + const auto false_value = _codegen->emit_load(false_exp); + + const auto result_value = _codegen->emit_ternary_op(lhs.location, op, type, condition_value, true_value, false_value); +#endif + lhs.reset_to_rvalue(lhs.location, result_value, type); + #pragma endregion + } + } + + return true; +} + +bool reshadefx::parser::parse_expression_assignment(expression &lhs) +{ + // Parse left hand side of the expression + if (!parse_expression_multary(lhs)) + return false; + + // Check if an operator exists so that this is an assignment + if (accept_assignment_op()) + { + // Remember the operator token before parsing the expression that follows it + const tokenid op = _token.id; + + // Parse right hand side of the assignment expression + expression rhs; + if (!parse_expression_multary(rhs)) + return false; + + // Check if the assignment is valid + if (lhs.type.has(type::q_const) || lhs.type.has(type::q_uniform) || !lhs.is_lvalue) + return error(lhs.location, 3025, "l-value specifies const object"), false; + if (!type::rank(lhs.type, rhs.type)) + return error(rhs.location, 3020, "cannot convert these types"), false; + + // Cannot perform bitwise operations on non-integral types + if (!lhs.type.is_integral() && (op == tokenid::ampersand_equal || op == tokenid::pipe_equal || op == tokenid::caret_equal)) + return error(lhs.location, 3082, "int or unsigned int type required"), false; + + // Perform implicit type conversion of right hand side value + if (rhs.type.components() > lhs.type.components()) + warning(rhs.location, 3206, "implicit truncation of vector type"); + + rhs.add_cast_operation(lhs.type); + + auto result = _codegen->emit_load(rhs); + + // Check if this is an assignment with an additional arithmetic instruction + if (op != tokenid::equal) + { + // Load value for modification + const auto value = _codegen->emit_load(lhs); + + // Handle arithmetic assignment operation + result = _codegen->emit_binary_op(lhs.location, op, lhs.type, value, result); + } + + // Write result back to variable + _codegen->emit_store(lhs, result); + + // Return the result value since you can write assignments within expressions + lhs.reset_to_rvalue(lhs.location, result, lhs.type); + } + + return true; +} + +bool reshadefx::parser::parse_annotations(std::unordered_map> &annotations) +{ + // Check if annotations exist and return early if none do + if (!accept('<')) + return true; + + bool parse_success = true; + + while (!peek('>')) + { + if (type type; accept_type_class(type)) + warning(_token.location, 4717, "type prefixes for annotations are deprecated and ignored"); + + if (!expect(tokenid::identifier)) + return false; + + const auto name = std::move(_token.literal_as_string); + + if (expression expression; !expect('=') || !parse_expression_unary(expression) || !expect(';')) + return consume_until('>'), false; // Probably a syntax error, so abort parsing + else if (expression.is_constant) + annotations[name] = { expression.type, expression.constant }; + else // Continue parsing annotations despite this not being a constant, since the syntax is still correct + error(expression.location, 3011, "value must be a literal expression"), parse_success = false; + } + + return expect('>') && parse_success; +} + +// -- Statement & Declaration Parsing -- // + +bool reshadefx::parser::parse_statement(bool scoped) +{ + if (!_codegen->is_in_block()) + return error(_token_next.location, 0, "unreachable code"), false; + + unsigned int loop_control = 0; + unsigned int selection_control = 0; + + // Read any loop and branch control attributes first + while (accept('[')) + { + enum control_mask + { + unroll = 0x1, + dont_unroll = 0x2, + flatten = 0x4, + dont_flatten = 0x8, + }; + + const auto attribute = std::move(_token_next.literal_as_string); + + if (!expect(tokenid::identifier) || !expect(']')) + return false; + + if (attribute == "unroll") + loop_control |= unroll; + else if (attribute == "loop" || attribute == "fastopt") + loop_control |= dont_unroll; + else if (attribute == "flatten") + selection_control |= flatten; + else if (attribute == "branch") + selection_control |= dont_flatten; + else + warning(_token.location, 0, "unknown attribute"); + + if ((loop_control & (unroll | dont_unroll)) == (unroll | dont_unroll)) + return error(_token.location, 3524, "can't use loop and unroll attributes together"), false; + if ((selection_control & (flatten | dont_flatten)) == (flatten | dont_flatten)) + return error(_token.location, 3524, "can't use branch and flatten attributes together"), false; + } + + // Shift by two so that the possible values are 0x01 for 'flatten' and 0x02 for 'dont_flatten', equivalent to 'unroll' and 'dont_unroll' + selection_control >>= 2; + + if (peek('{')) // Parse statement block + return parse_statement_block(scoped); + else if (accept(';')) // Ignore empty statements + return true; + + // Most statements with the exception of declarations are only valid inside functions + if (_codegen->is_in_function()) + { + const auto location = _token_next.location; + + #pragma region If + if (accept(tokenid::if_)) + { + codegen::id true_block = _codegen->create_block(); // Block which contains the statements executed when the condition is true + codegen::id false_block = _codegen->create_block(); // Block which contains the statements executed when the condition is false + const codegen::id merge_block = _codegen->create_block(); // Block that is executed after the branch re-merged with the current control flow + + expression condition; + if (!expect('(') || !parse_expression(condition) || !expect(')')) + return false; + else if (!condition.type.is_scalar()) + return error(condition.location, 3019, "if statement conditional expressions must evaluate to a scalar"), false; + + // Load condition and convert to boolean value as required by 'OpBranchConditional' + condition.add_cast_operation({ type::t_bool, 1, 1 }); + + const codegen::id condition_value = _codegen->emit_load(condition); + const codegen::id condition_block = _codegen->leave_block_and_branch_conditional(condition_value, true_block, false_block); + + { // Then block of the if statement + _codegen->enter_block(true_block); + + if (!parse_statement(true)) + return false; + + true_block = _codegen->leave_block_and_branch(merge_block); + } + { // Else block of the if statement + _codegen->enter_block(false_block); + + if (accept(tokenid::else_) && !parse_statement(true)) + return false; + + false_block = _codegen->leave_block_and_branch(merge_block); + } + + _codegen->enter_block(merge_block); + + // Emit structured control flow for an if statement and connect all basic blocks + _codegen->emit_if(location, condition_value, condition_block, true_block, false_block, selection_control); + + return true; + } + #pragma endregion + + #pragma region Switch + if (accept(tokenid::switch_)) + { + const codegen::id merge_block = _codegen->create_block(); // Block that is executed after the switch re-merged with the current control flow + + expression selector_exp; + if (!expect('(') || !parse_expression(selector_exp) || !expect(')')) + return false; + else if (!selector_exp.type.is_scalar()) + return error(selector_exp.location, 3019, "switch statement expression must evaluate to a scalar"), false; + + // Load selector and convert to integral value as required by switch instruction + selector_exp.add_cast_operation({ type::t_int, 1, 1 }); + + const auto selector_value = _codegen->emit_load(selector_exp); + const auto selector_block = _codegen->leave_block_and_switch(selector_value, merge_block); + + if (!expect('{')) + return false; + + _loop_break_target_stack.push_back(merge_block); + on_scope_exit _([this]() { _loop_break_target_stack.pop_back(); }); + + bool success = true; + codegen::id default_label = merge_block; // The default case jumps to the end of the switch statement if not overwritten + std::vector case_literal_and_labels; + size_t last_case_label_index = 0; + + // Enter first switch statement body block + _codegen->enter_block(_codegen->create_block()); + + while (!peek(tokenid::end_of_file)) + { + while (accept(tokenid::case_) || accept(tokenid::default_)) + { + if (_token.id == tokenid::case_) + { + expression case_label; + if (!parse_expression(case_label)) + return consume_until('}'), false; + else if (!case_label.type.is_scalar() || !case_label.type.is_integral() || !case_label.is_constant) + return error(case_label.location, 3020, "invalid type for case expression - value must be an integer scalar"), consume_until('}'), false; + + // Check for duplicate case values + for (size_t i = 0; i < case_literal_and_labels.size(); i += 2) + { + if (case_literal_and_labels[i] == case_label.constant.as_uint[0]) + { + error(case_label.location, 3532, "duplicate case " + std::to_string(case_label.constant.as_uint[0])); + success = false; + break; + } + } + + case_literal_and_labels.push_back(case_label.constant.as_uint[0]); + case_literal_and_labels.emplace_back(); // This is set to the actual block below + } + else + { + // Check if the default label was already changed by a previous 'default' statement + if (default_label != merge_block) + { + error(_token.location, 3532, "duplicate default in switch statement"); + success = false; + } + + default_label = 0; // This is set to the actual block below + } + + if (!expect(':')) + return consume_until('}'), false; + } + + // It is valid for no statement to follow if this is the last label in the switch body + const bool end_of_switch = peek('}'); + + if (!end_of_switch && !parse_statement(true)) + return consume_until('}'), false; + + // Handle fall-through case and end of switch statement + if (peek(tokenid::case_) || peek(tokenid::default_) || end_of_switch) + { + if (_codegen->is_in_block()) // Disallow fall-through for now + { + error(_token_next.location, 3533, "non-empty case statements must have break or return"); + success = false; + } + + const codegen::id next_block = end_of_switch ? merge_block : _codegen->create_block(); + const codegen::id current_block = _codegen->leave_block_and_branch(next_block); + + assert(current_block != 0); + + if (default_label == 0) + default_label = current_block; + else + for (size_t i = last_case_label_index; i < case_literal_and_labels.size(); i += 2) + case_literal_and_labels[i + 1] = current_block; + + _codegen->enter_block(next_block); + + if (end_of_switch) // We reached the end, nothing more to do + break; + + last_case_label_index = case_literal_and_labels.size(); + } + } + + if (case_literal_and_labels.empty() && default_label == merge_block) + warning(location, 5002, "switch statement contains no 'case' or 'default' labels"); + + // Emit structured control flow for a switch statement and connect all basic blocks + _codegen->emit_switch(location, selector_value, selector_block, default_label, case_literal_and_labels, selection_control); + + return expect('}') && success; + } + #pragma endregion + + #pragma region For + if (accept(tokenid::for_)) + { + if (!expect('(')) + return false; + + enter_scope(); + on_scope_exit _([this]() { leave_scope(); }); + + // Parse initializer first + if (type type; parse_type(type)) + { + unsigned int count = 0; + do { // There may be multiple declarations behind a type, so loop through them + if (count++ > 0 && !expect(',')) + return false; + if (!expect(tokenid::identifier) || !parse_variable(type, std::move(_token.literal_as_string))) + return false; + } while (!peek(';')); + } + else // Initializer can also contain an expression if not a variable declaration list + { + expression expression; + parse_expression(expression); // It is valid for there to be no initializer expression, so ignore result + } + + if (!expect(';')) + return false; + + const codegen::id merge_block = _codegen->create_block(); // Block that is executed after the loop + const codegen::id header_label = _codegen->create_block(); // Pointer to the loop merge instruction + const codegen::id continue_label = _codegen->create_block(); // Pointer to the continue block + codegen::id loop_block = _codegen->create_block(); // Pointer to the main loop body block + codegen::id condition_block = _codegen->create_block(); // Pointer to the condition check + codegen::id condition_value = 0; + + // End current block by branching to the next label + const codegen::id prev_block = _codegen->leave_block_and_branch(header_label); + + { // Begin loop block (this header is used for explicit structured control flow) + _codegen->enter_block(header_label); + + _codegen->leave_block_and_branch(condition_block); + } + + { // Parse condition block + _codegen->enter_block(condition_block); + + if (expression condition; parse_expression(condition)) + { + if (!condition.type.is_scalar()) + return error(condition.location, 3019, "scalar value expected"), false; + + // Evaluate condition and branch to the right target + condition.add_cast_operation({ type::t_bool, 1, 1 }); + + condition_value = _codegen->emit_load(condition); + + condition_block = _codegen->leave_block_and_branch_conditional(condition_value, loop_block, merge_block); + } + else // It is valid for there to be no condition expression + { + condition_block = _codegen->leave_block_and_branch(loop_block); + } + + if (!expect(';')) + return false; + } + + { // Parse loop continue block into separate block so it can be appended to the end down the line + _codegen->enter_block(continue_label); + + expression continue_exp; + parse_expression(continue_exp); // It is valid for there to be no continue expression, so ignore result + + if (!expect(')')) + return false; + + // Branch back to the loop header at the end of the continue block + _codegen->leave_block_and_branch(header_label); + } + + { // Parse loop body block + _codegen->enter_block(loop_block); + + _loop_break_target_stack.push_back(merge_block); + _loop_continue_target_stack.push_back(continue_label); + + const bool parse_success = parse_statement(false); + + _loop_break_target_stack.pop_back(); + _loop_continue_target_stack.pop_back(); + + if (!parse_success) + return false; + + loop_block = _codegen->leave_block_and_branch(continue_label); + } + + // Add merge block label to the end of the loop + _codegen->enter_block(merge_block); + + // Emit structured control flow for a loop statement and connect all basic blocks + _codegen->emit_loop(location, condition_value, prev_block, header_label, condition_block, loop_block, continue_label, loop_control); + + return true; + } + #pragma endregion + + #pragma region While + if (accept(tokenid::while_)) + { + enter_scope(); + on_scope_exit _([this]() { leave_scope(); }); + + const codegen::id merge_block = _codegen->create_block(); + const codegen::id header_label = _codegen->create_block(); + const codegen::id continue_label = _codegen->create_block(); + codegen::id loop_block = _codegen->create_block(); + codegen::id condition_block = _codegen->create_block(); + codegen::id condition_value = 0; + + // End current block by branching to the next label + const codegen::id prev_block = _codegen->leave_block_and_branch(header_label); + + { // Begin loop block + _codegen->enter_block(header_label); + + _codegen->leave_block_and_branch(loop_block); + } + + { // Parse condition block + _codegen->enter_block(condition_block); + + expression condition; + if (!expect('(') || !parse_expression(condition) || !expect(')')) + return false; + else if (!condition.type.is_scalar()) + return error(condition.location, 3019, "scalar value expected"), false; + + // Evaluate condition and branch to the right target + condition.add_cast_operation({ type::t_bool, 1, 1 }); + + condition_value = _codegen->emit_load(condition); + + condition_block = _codegen->leave_block_and_branch_conditional(condition_value, loop_block, merge_block); + } + + { // Parse loop body block + _codegen->enter_block(loop_block); + + _loop_break_target_stack.push_back(merge_block); + _loop_continue_target_stack.push_back(continue_label); + + const bool parse_success = parse_statement(false); + + _loop_break_target_stack.pop_back(); + _loop_continue_target_stack.pop_back(); + + if (!parse_success) + return false; + + loop_block = _codegen->leave_block_and_branch(continue_label); + } + + { // Branch back to the loop header in empty continue block + _codegen->enter_block(continue_label); + + _codegen->leave_block_and_branch(header_label); + } + + // Add merge block label to the end of the loop + _codegen->enter_block(merge_block); + + // Emit structured control flow for a loop statement and connect all basic blocks + _codegen->emit_loop(location, condition_value, prev_block, header_label, condition_block, loop_block, continue_label, loop_control); + + return true; + } + #pragma endregion + + #pragma region DoWhile + if (accept(tokenid::do_)) + { + const codegen::id merge_block = _codegen->create_block(); + const codegen::id header_label = _codegen->create_block(); + const codegen::id continue_label = _codegen->create_block(); + codegen::id loop_block = _codegen->create_block(); + codegen::id condition_value = 0; + + // End current block by branching to the next label + const codegen::id prev_block = _codegen->leave_block_and_branch(header_label); + + { // Begin loop block + _codegen->enter_block(header_label); + + _codegen->leave_block_and_branch(loop_block); + } + + { // Parse loop body block + _codegen->enter_block(loop_block); + + _loop_break_target_stack.push_back(merge_block); + _loop_continue_target_stack.push_back(continue_label); + + const bool parse_success = parse_statement(true); + + _loop_break_target_stack.pop_back(); + _loop_continue_target_stack.pop_back(); + + if (!parse_success) + return false; + + loop_block = _codegen->leave_block_and_branch(continue_label); + } + + { // Continue block does the condition evaluation + _codegen->enter_block(continue_label); + + expression condition; + if (!expect(tokenid::while_) || !expect('(') || !parse_expression(condition) || !expect(')') || !expect(';')) + return false; + else if (!condition.type.is_scalar()) + return error(condition.location, 3019, "scalar value expected"), false; + + // Evaluate condition and branch to the right target + condition.add_cast_operation({ type::t_bool, 1, 1 }); + + condition_value = _codegen->emit_load(condition); + + _codegen->leave_block_and_branch_conditional(condition_value, header_label, merge_block); + } + + // Add merge block label to the end of the loop + _codegen->enter_block(merge_block); + + // Emit structured control flow for a loop statement and connect all basic blocks + _codegen->emit_loop(location, condition_value, prev_block, header_label, 0, loop_block, continue_label, loop_control); + + return true; + } + #pragma endregion + + #pragma region Break + if (accept(tokenid::break_)) + { + if (_loop_break_target_stack.empty()) + return error(location, 3518, "break must be inside loop"), false; + + // Branch to the break target of the inner most loop on the stack + _codegen->leave_block_and_branch(_loop_break_target_stack.back(), 1); + + return expect(';'); + } + #pragma endregion + + #pragma region Continue + if (accept(tokenid::continue_)) + { + if (_loop_continue_target_stack.empty()) + return error(location, 3519, "continue must be inside loop"), false; + + // Branch to the continue target of the inner most loop on the stack + _codegen->leave_block_and_branch(_loop_continue_target_stack.back(), 2); + + return expect(';'); + } + #pragma endregion + + #pragma region Return + if (accept(tokenid::return_)) + { + const type &ret_type = _current_return_type; + + if (!peek(';')) + { + expression expression; + if (!parse_expression(expression)) + return error(_token_next.location, 3000, "syntax error: unexpected '" + token::id_to_name(_token_next.id) + "', expected expression"), consume_until(';'), false; + + // Cannot return to void + if (ret_type.is_void()) + // Consume the semicolon that follows the return expression so that parsing may continue + return error(location, 3079, "void functions cannot return a value"), accept(';'), false; + + // Cannot return arrays from a function + if (expression.type.is_array() || !type::rank(expression.type, ret_type)) + return error(location, 3017, "expression does not match function return type"), accept(';'), false; + + // Load return value and perform implicit cast to function return type + if (expression.type.components() > ret_type.components()) + warning(expression.location, 3206, "implicit truncation of vector type"); + + expression.add_cast_operation(ret_type); + + const auto return_value = _codegen->emit_load(expression); + + _codegen->leave_block_and_return(return_value); + } + else if (!ret_type.is_void()) + { + // No return value was found, but the function expects one + error(location, 3080, "function must return a value"); + + // Consume the semicolon that follows the return expression so that parsing may continue + accept(';'); + + return false; + } + else + { + _codegen->leave_block_and_return(); + } + + return expect(';'); + } + #pragma endregion + + #pragma region Discard + if (accept(tokenid::discard_)) + { + // Leave the current function block + _codegen->leave_block_and_kill(); + + return expect(';'); + } + #pragma endregion + } + + #pragma region Declaration + // Handle variable declarations + if (type type; parse_type(type)) + { + unsigned int count = 0; + do { // There may be multiple declarations behind a type, so loop through them + if (count++ > 0 && !expect(',')) + // Try to consume the rest of the declaration so that parsing may continue despite the error + return consume_until(';'), false; + if (!expect(tokenid::identifier) || !parse_variable(type, std::move(_token.literal_as_string))) + return consume_until(';'), false; + } while (!peek(';')); + + return expect(';'); + } + #pragma endregion + + // Handle expression statements + if (expression expression; parse_expression(expression)) + return expect(';'); // A statement has to be terminated with a semicolon + + // No token should come through here, since all statements and expressions should have been handled above, so this is an error in the syntax + error(_token_next.location, 3000, "syntax error: unexpected '" + token::id_to_name(_token_next.id) + '\''); + + // Gracefully consume any remaining characters until the statement would usually end, so that parsing may continue despite the error + consume_until(';'); + + return false; +} + +bool reshadefx::parser::parse_statement_block(bool scoped) +{ + if (!expect('{')) + return false; + + if (scoped) + enter_scope(); + + // Parse statements until the end of the block is reached + while (!peek('}') && !peek(tokenid::end_of_file)) + { + if (!parse_statement(true)) + { + if (scoped) + leave_scope(); + + // Ignore the rest of this block + unsigned int level = 0; + + while (!peek(tokenid::end_of_file)) + { + if (accept('{')) + { + ++level; + } + else if (accept('}')) + { + if (level-- == 0) + break; + } // These braces are necessary to match the 'else' to the correct 'if' statement + else + { + consume(); + } + } + + return false; + } + } + + if (scoped) + leave_scope(); + + return expect('}'); +} + +bool reshadefx::parser::parse_top() +{ + if (accept(tokenid::namespace_)) + { + // Anonymous namespaces are not supported right now, so an identifier is a must + if (!expect(tokenid::identifier)) + return false; + + const auto name = std::move(_token.literal_as_string); + + if (!expect('{')) + return false; + + enter_namespace(name); + + bool success = true; + // Recursively parse top level statements until the namespace is closed again + while (!peek('}')) // Empty namespaces are valid + if (!parse_top()) + success = false; // Continue parsing even after encountering an error + + leave_namespace(); + + return expect('}') && success; + } + else if (accept(tokenid::struct_)) // Structure keyword found, parse the structure definition + { + if (!parse_struct() || !expect(';')) // Structure definitions are terminated with a semicolon + return false; + } + else if (accept(tokenid::technique)) // Technique keyword found, parse the technique definition + { + if (!parse_technique()) + return false; + } + else if (type type; parse_type(type)) // Type found, this can be either a variable or a function declaration + { + if (!expect(tokenid::identifier)) + return false; + + if (peek('(')) + { + const auto name = std::move(_token.literal_as_string); + // This is definitely a function declaration, so parse it + if (!parse_function(type, name)) { + // Insert dummy function into symbol table, so later references can be resolved despite the error + insert_symbol(name, { symbol_type::function, ~0u, { type::t_function } }, true); + return false; + } + } + else + { + // There may be multiple variable names after the type, handle them all + unsigned int count = 0; + do { + if (count++ > 0 && !(expect(',') && expect(tokenid::identifier))) + return false; + const auto name = std::move(_token.literal_as_string); + if (!parse_variable(type, name, true)) { + // Insert dummy variable into symbol table, so later references can be resolved despite the error + insert_symbol(name, { symbol_type::variable, ~0u, type }, true); + return consume_until(';'), false; // Skip the rest of the statement in case of an error + } + } while (!peek(';')); + + if (!expect(';')) // Variable declarations are terminated with a semicolon + return false; + } + } + else if (!accept(';')) // Ignore single semicolons in the source + { + consume(); // Unexpected token in source stream, consume and report an error about it + error(_token.location, 3000, "syntax error: unexpected '" + token::id_to_name(_token.id) + '\''); + return false; + } + + return true; +} + +bool reshadefx::parser::parse_struct() +{ + const auto location = std::move(_token.location); + + struct_info info; + // The structure name is optional + if (accept(tokenid::identifier)) + info.name = std::move(_token.literal_as_string); + else + info.name = "_anonymous_struct_" + std::to_string(location.line) + '_' + std::to_string(location.column); + + info.unique_name = 'S' + current_scope().name + info.name; + std::replace(info.unique_name.begin(), info.unique_name.end(), ':', '_'); + + if (!expect('{')) + return false; + + while (!peek('}')) // Empty structures are possible + { + struct_member_info member; + + if (!parse_type(member.type)) + return error(_token_next.location, 3000, "syntax error: unexpected '" + token::id_to_name(_token_next.id) + "', expected struct member type"), consume_until('}'), false; + + if (member.type.is_void()) + return error(_token_next.location, 3038, "struct members cannot be void"), consume_until('}'), false; + if (member.type.has(type::q_in) || member.type.has(type::q_out)) + return error(_token_next.location, 3055, "struct members cannot be declared 'in' or 'out'"), consume_until('}'), false; + + if (member.type.is_struct()) // Nesting structures would make input/output argument flattening more complicated, so prevent it for now + return error(_token_next.location, 3090, "nested struct members are not supported"), consume_until('}'), false; + + unsigned int count = 0; + do { + if (count++ > 0 && !expect(',')) + return consume_until('}'), false; + + if (!expect(tokenid::identifier)) + return consume_until('}'), false; + + member.name = std::move(_token.literal_as_string); + member.location = std::move(_token.location); + + // Modify member specific type, so that following members in the declaration list are not affected by this + if (!parse_array_size(member.type)) + return consume_until('}'), false; + else if (member.type.array_length < 0) + return error(member.location, 3072, '\'' + member.name + "': array dimensions of struct members must be explicit"), consume_until('}'), false; + + // Structure members may have semantics to use them as input/output types + if (accept(':')) + { + if (!expect(tokenid::identifier)) + return consume_until('}'), false; + + member.semantic = std::move(_token.literal_as_string); + // Make semantic upper case to simplify comparison later on + std::transform(member.semantic.begin(), member.semantic.end(), member.semantic.begin(), [](char c) { return static_cast(toupper(c)); }); + } + + // Save member name and type for book keeping + info.member_list.push_back(member); + } while (!peek(';')); + + if (!expect(';')) + return consume_until('}'), false; + } + + // Empty structures are valid, but not usually intended, so emit a warning + if (info.member_list.empty()) + warning(location, 5001, "struct has no members"); + + // Define the structure now that information about all the member types was gathered + const auto id = _codegen->define_struct(location, info); + + // Insert the symbol into the symbol table + const symbol symbol = { symbol_type::structure, id }; + + if (!insert_symbol(info.name, symbol, true)) + return error(location, 3003, "redefinition of '" + info.name + '\''), false; + + return expect('}'); +} + +bool reshadefx::parser::parse_function(type type, std::string name) +{ + const auto location = std::move(_token.location); + + if (!expect('(')) // Functions always have a parameter list + return false; + if (type.qualifiers != 0) + return error(location, 3047, '\'' + name + "': function return type cannot have any qualifiers"), false; + + function_info info; + info.name = name; + info.unique_name = 'F' + current_scope().name + name; + std::replace(info.unique_name.begin(), info.unique_name.end(), ':', '_'); + + info.return_type = type; + _current_return_type = info.return_type; + + // Enter function scope + enter_scope(); on_scope_exit _([this]() { leave_scope(); _codegen->leave_function(); }); + + while (!peek(')')) + { + if (!info.parameter_list.empty() && !expect(',')) + return false; + + struct_member_info param; + + if (!parse_type(param.type)) + return error(_token_next.location, 3000, "syntax error: unexpected '" + token::id_to_name(_token_next.id) + "', expected parameter type"), false; + + if (!expect(tokenid::identifier)) + return false; + + param.name = std::move(_token.literal_as_string); + param.location = std::move(_token.location); + + if (param.type.is_void()) + return error(param.location, 3038, '\'' + param.name + "': function parameters cannot be void"), false; + if (param.type.has(type::q_extern)) + return error(param.location, 3006, '\'' + param.name + "': function parameters cannot be declared 'extern'"), false; + if (param.type.has(type::q_static)) + return error(param.location, 3007, '\'' + param.name + "': function parameters cannot be declared 'static'"), false; + if (param.type.has(type::q_uniform)) + return error(param.location, 3047, '\'' + param.name + "': function parameters cannot be declared 'uniform', consider placing in global scope instead"), false; + + if (param.type.has(type::q_out) && param.type.has(type::q_const)) + return error(param.location, 3046, '\'' + param.name + "': output parameters cannot be declared 'const'"), false; + else if (!param.type.has(type::q_out)) + param.type.qualifiers |= type::q_in; // Function parameters are implicitly 'in' if not explicitly defined as 'out' + + if (!parse_array_size(param.type)) + return false; + else if (param.type.array_length < 0) + return error(param.location, 3072, '\'' + param.name + "': array dimensions of function parameters must be explicit"), false; + + // Handle parameter type semantic + if (accept(':')) + { + if (!expect(tokenid::identifier)) + return false; + + param.semantic = std::move(_token.literal_as_string); + // Make semantic upper case to simplify comparison later on + std::transform(param.semantic.begin(), param.semantic.end(), param.semantic.begin(), [](char c) { return static_cast(toupper(c)); }); + } + + info.parameter_list.push_back(std::move(param)); + } + + if (!expect(')')) + return false; + + // Handle return type semantic + if (accept(':')) + { + if (!expect(tokenid::identifier)) + return false; + if (type.is_void()) + return error(_token.location, 3076, '\'' + name + "': void function cannot have a semantic"), false; + + info.return_semantic = std::move(_token.literal_as_string); + // Make semantic upper case to simplify comparison later on + std::transform(info.return_semantic.begin(), info.return_semantic.end(), info.return_semantic.begin(), [](char c) { return static_cast(toupper(c)); }); + } + + // Check if this is a function declaration without a body + if (accept(';')) + return error(location, 3510, '\'' + name + "': function is missing an implementation"), false; + + // Define the function now that information about the declaration was gathered + const auto id = _codegen->define_function(location, info); + + // Insert the function and parameter symbols into the symbol table + symbol symbol = { symbol_type::function, id, { type::t_function } }; + symbol.function = &_codegen->find_function(id); + + if (!insert_symbol(name, symbol, true)) + return error(location, 3003, "redefinition of '" + name + '\''), false; + + for (const auto ¶m : info.parameter_list) + if (!insert_symbol(param.name, { symbol_type::variable, param.definition, param.type })) + return error(param.location, 3003, "redefinition of '" + param.name + '\''), false; + + // A function has to start with a new block + _codegen->enter_block(_codegen->create_block()); + + const bool parse_success = parse_statement_block(false); + + // Add implicit return statement to the end of functions + if (_codegen->is_in_block()) + _codegen->leave_block_and_return(); + + return parse_success; +} + +bool reshadefx::parser::parse_variable(type type, std::string name, bool global) +{ + const auto location = std::move(_token.location); + + if (type.is_void()) + return error(location, 3038, '\'' + name + "': variables cannot be void"), false; + if (type.has(type::q_in) || type.has(type::q_out)) + return error(location, 3055, '\'' + name + "': variables cannot be declared 'in' or 'out'"), false; + + // Local and global variables have different requirements + if (global) + { + // Check that type qualifier combinations are valid + if (type.has(type::q_static)) + { + // Global variables that are 'static' cannot be of another storage class + if (type.has(type::q_uniform)) + return error(location, 3007, '\'' + name + "': uniform global variables cannot be declared 'static'"), false; + // The 'volatile qualifier is only valid memory object declarations that are storage images or uniform blocks + if (type.has(type::q_volatile)) + return error(location, 3008, '\'' + name + "': global variables cannot be declared 'volatile'"), false; + } + else + { + // Make all global variables 'uniform' by default, since they should be externally visible without the 'static' keyword + if (!type.has(type::q_uniform) && !(type.is_texture() || type.is_sampler())) + warning(location, 5000, '\'' + name + "': global variables are considered 'uniform' by default"); + + // Global variables that are not 'static' are always 'extern' and 'uniform' + type.qualifiers |= type::q_extern | type::q_uniform; + + // It is invalid to make 'uniform' variables constant, since they can be modified externally + if (type.has(type::q_const)) + return error(location, 3035, '\'' + name + "': variables which are 'uniform' cannot be declared 'const'"), false; + } + } + else + { + if (type.has(type::q_extern)) + return error(location, 3006, '\'' + name + "': local variables cannot be declared 'extern'"), false; + if (type.has(type::q_uniform)) + return error(location, 3047, '\'' + name + "': local variables cannot be declared 'uniform'"), false; + + if (type.is_texture() || type.is_sampler()) + return error(location, 3038, '\'' + name + "': local variables cannot be textures or samplers"), false; + } + + // The variable name may be followed by an optional array size expression + if (!parse_array_size(type)) + return false; + + expression initializer; + texture_info texture_info; + sampler_info sampler_info; + + if (accept(':')) + { + if (!expect(tokenid::identifier)) + return false; + else if (!global) // Only global variables can have a semantic + return error(_token.location, 3043, '\'' + name + "': local variables cannot have semantics"), false; + + std::string &semantic = texture_info.semantic; + semantic = std::move(_token.literal_as_string); + + // Make semantic upper case to simplify comparison later on + std::transform(semantic.begin(), semantic.end(), semantic.begin(), [](char c) { return static_cast(toupper(c)); }); + } + else + { + // Global variables can have optional annotations + if (global && !parse_annotations(texture_info.annotations)) + return false; + + // Variables without a semantic may have an optional initializer + if (accept('=')) + { + if (!parse_expression_assignment(initializer)) + return error(_token_next.location, 3000, "syntax error: unexpected '" + token::id_to_name(_token_next.id) + "', expected expression"), false; + + if (global && !initializer.is_constant) // TODO: This could be resolved by initializing these at the beginning of the entry point + return error(initializer.location, 3011, '\'' + name + "': initial value must be a literal expression"), false; + + // Check type compatibility + if ((type.array_length >= 0 && initializer.type.array_length != type.array_length) || !type::rank(initializer.type, type)) + return error(initializer.location, 3017, '\'' + name + "': initial value does not match variable type"), false; + if ((initializer.type.rows < type.rows || initializer.type.cols < type.cols) && !initializer.type.is_scalar()) + return error(initializer.location, 3017, "cannot implicitly convert these vector types"), false; + + // Deduce array size from the initializer expression + if (initializer.type.is_array()) + type.array_length = initializer.type.array_length; + + // Perform implicit cast from initializer expression to variable type + if (initializer.type.components() > type.components()) + warning(initializer.location, 3206, "implicit truncation of vector type"); + + initializer.add_cast_operation(type); + } + else if (type.is_numeric() || type.is_struct()) // Numeric variables without an initializer need special handling + { + if (type.has(type::q_const)) // Constants have to have an initial value + return error(location, 3012, '\'' + name + "': missing initial value"), false; + else if (!type.has(type::q_uniform)) // Zero initialize all global variables + initializer.reset_to_rvalue_constant(location, {}, type); + } + else if (global && accept('{')) // Textures and samplers can have a property block attached to their declaration + { + if (type.has(type::q_const)) // Non-numeric variables cannot be constants + return error(location, 3035, '\'' + name + "': this variable type cannot be declared 'const'"), false; + + while (!peek('}')) + { + if (!expect(tokenid::identifier)) + return consume_until('}'), false; + + const auto property_name = std::move(_token.literal_as_string); + const auto property_location = std::move(_token.location); + + if (!expect('=')) + return consume_until('}'), false; + + backup(); + + expression expression; + + if (accept(tokenid::identifier)) // Handle special enumeration names for property values + { + // Transform identifier to uppercase to do case-insensitive comparison + std::transform(_token.literal_as_string.begin(), _token.literal_as_string.end(), _token.literal_as_string.begin(), [](char c) { return static_cast(toupper(c)); }); + + static const std::pair s_values[] = { + { "NONE", 0 }, { "POINT", 0 }, + { "LINEAR", 1 }, + { "WRAP", uint32_t(texture_address_mode::wrap) }, { "REPEAT", uint32_t(texture_address_mode::wrap) }, + { "MIRROR", uint32_t(texture_address_mode::mirror) }, + { "CLAMP", uint32_t(texture_address_mode::clamp) }, + { "BORDER", uint32_t(texture_address_mode::border) }, + { "R8", uint32_t(texture_format::r8) }, + { "R16F", uint32_t(texture_format::r16f) }, + { "R32F", uint32_t(texture_format::r32f) }, + { "RG8", uint32_t(texture_format::rg8) }, { "R8G8", uint32_t(texture_format::rg8) }, + { "RG16", uint32_t(texture_format::rg16) }, { "R16G16", uint32_t(texture_format::rg16) }, + { "RG16F", uint32_t(texture_format::rg16f) }, { "R16G16F", uint32_t(texture_format::rg16f) }, + { "RG32F", uint32_t(texture_format::rg32f) }, { "R32G32F", uint32_t(texture_format::rg32f) }, + { "RGBA8", uint32_t(texture_format::rgba8) }, { "R8G8B8A8", uint32_t(texture_format::rgba8) }, + { "RGBA16", uint32_t(texture_format::rgba16) }, { "R16G16B16A16", uint32_t(texture_format::rgba16) }, + { "RGBA16F", uint32_t(texture_format::rgba16f) }, { "R16G16B16A16F", uint32_t(texture_format::rgba16f) }, + { "RGBA32F", uint32_t(texture_format::rgba32f) }, { "R32G32B32A32F", uint32_t(texture_format::rgba32f) }, + { "RGB10A2", uint32_t(texture_format::rgb10a2) }, { "R10G10B10A2", uint32_t(texture_format::rgb10a2) }, + }; + + // Look up identifier in list of possible enumeration names + const auto it = std::find_if(std::begin(s_values), std::end(s_values), + [this](const auto &it) { return it.first == _token.literal_as_string; }); + + if (it != std::end(s_values)) + expression.reset_to_rvalue_constant(_token.location, it->second); + else // No match found, so rewind to parser state before the identifier was consumed and try parsing it as a normal expression + restore(); + } + + // Parse right hand side as normal expression if no special enumeration name was matched already + if (!expression.is_constant && !parse_expression_multary(expression)) + return error(_token_next.location, 3000, "syntax error: unexpected '" + token::id_to_name(_token_next.id) + "', expected expression"), consume_until('}'), false; + + if (property_name == "Texture") + { + // Ignore invalid symbols that were added during error recovery + if (expression.base == 0xFFFFFFFF) + return consume_until('}'), false; + + if (!expression.type.is_texture()) + return error(expression.location, 3020, "type mismatch, expected texture name"), consume_until('}'), false; + + sampler_info.texture_name = _codegen->find_texture(expression.base).unique_name; + } + else + { + if (!expression.is_constant || !expression.type.is_scalar()) + return error(expression.location, 3538, "value must be a literal scalar expression"), consume_until('}'), false; + + // All states below expect the value to be of an unsigned integer type + expression.add_cast_operation({ type::t_uint, 1, 1 }); + const unsigned int value = expression.constant.as_uint[0]; + + if (property_name == "Width") + texture_info.width = value > 0 ? value : 1; + else if (property_name == "Height") + texture_info.height = value > 0 ? value : 1; + else if (property_name == "MipLevels") + texture_info.levels = value > 0 ? value : 1; + else if (property_name == "Format") + texture_info.format = static_cast(value); + else if (property_name == "SRGBTexture" || property_name == "SRGBReadEnable") + sampler_info.srgb = value != 0; + else if (property_name == "AddressU") + sampler_info.address_u = static_cast(value); + else if (property_name == "AddressV") + sampler_info.address_v = static_cast(value); + else if (property_name == "AddressW") + sampler_info.address_w = static_cast(value); + else if (property_name == "MinFilter") + sampler_info.filter = static_cast((uint32_t(sampler_info.filter) & 0x0F) | ((value << 4) & 0x30)); // Combine sampler filter components into a single filter enumeration value + else if (property_name == "MagFilter") + sampler_info.filter = static_cast((uint32_t(sampler_info.filter) & 0x33) | ((value << 2) & 0x0C)); + else if (property_name == "MipFilter") + sampler_info.filter = static_cast((uint32_t(sampler_info.filter) & 0x3C) | (value & 0x03)); + else if (property_name == "MinLOD" || property_name == "MaxMipLevel") + sampler_info.min_lod = static_cast(value); + else if (property_name == "MaxLOD") + sampler_info.max_lod = static_cast(value); + else if (property_name == "MipLODBias" || property_name == "MipMapLodBias") + sampler_info.lod_bias = static_cast(value); + else + return error(property_location, 3004, "unrecognized property '" + property_name + '\''), consume_until('}'), false; + } + + if (!expect(';')) + return consume_until('}'), false; + } + + if (!expect('}')) + return false; + } + } + + // At this point the array size should be known (either from the declaration or the initializer) + if (type.array_length < 0) + return error(location, 3074, '\'' + name + "': implicit array missing initial value"), false; + + symbol symbol; + + if (type.is_numeric() && type.has(type::q_const) && initializer.is_constant) // Variables with a constant initializer and constant type are named constants + { + // Named constants are special symbols + symbol = { symbol_type::constant, 0, type, initializer.constant }; + } + else if (type.is_texture()) + { + assert(global); + + // Add namespace scope to avoid name clashes + texture_info.unique_name = 'V' + current_scope().name + name; + std::replace(texture_info.unique_name.begin(), texture_info.unique_name.end(), ':', '_'); + + symbol = { symbol_type::variable, 0, type }; + symbol.id = _codegen->define_texture(location, texture_info); + } + else if (type.is_sampler()) // Samplers are actually combined image samplers + { + assert(global); + + if (sampler_info.texture_name.empty()) + return error(location, 3012, '\'' + name + "': missing 'Texture' property"), false; + + // Add namespace scope to avoid name clashes + sampler_info.unique_name = 'V' + current_scope().name + name; + std::replace(sampler_info.unique_name.begin(), sampler_info.unique_name.end(), ':', '_'); + + sampler_info.annotations = std::move(texture_info.annotations); + + symbol = { symbol_type::variable, 0, type }; + symbol.id = _codegen->define_sampler(location, sampler_info); + } + else if (type.has(type::q_uniform)) // Uniform variables are put into a global uniform buffer structure + { + assert(global); + + uniform_info uniform_info; + uniform_info.name = name; + uniform_info.type = type; + + uniform_info.annotations = std::move(texture_info.annotations); + + uniform_info.initializer_value = std::move(initializer.constant); + uniform_info.has_initializer_value = initializer.is_constant; + + symbol = { symbol_type::variable, 0, type }; + symbol.id = _codegen->define_uniform(location, uniform_info); + } + else // All other variables are separate entities + { + symbol = { symbol_type::variable, 0, type }; + + // Update global variable names to contain the namespace scope to avoid name clashes + std::string unique_name = global ? 'V' + current_scope().name + name : name; + std::replace(unique_name.begin(), unique_name.end(), ':', '_'); + + // The initializer expression for variables must be a constant + // Also, only use the variable initializer on global variables, since local variables for e.g. "for" statements need to be assigned in their respective scope and not their declaration + if (global && initializer.is_constant) + { + symbol.id = _codegen->define_variable(location, type, std::move(unique_name), global, _codegen->emit_constant(initializer.type, initializer.constant)); + } + else // Non-constant initializers are explicitly stored in the variable at the definition location instead + { + const auto initializer_value = _codegen->emit_load(initializer); + + symbol.id = _codegen->define_variable(location, type, std::move(unique_name), global); + + if (initializer_value != 0) + { + assert(!global); // Global variables cannot have a dynamic initializer + + expression variable; variable.reset_to_lvalue(location, symbol.id, type); + + _codegen->emit_store(variable, initializer_value); + } + } + } + + // Insert the symbol into the symbol table + if (!insert_symbol(name, symbol, global)) + return error(location, 3003, "redefinition of '" + name + '\''), false; + + return true; +} + +bool reshadefx::parser::parse_technique() +{ + if (!expect(tokenid::identifier)) + return false; + + technique_info info; + info.name = std::move(_token.literal_as_string); + + if (!parse_annotations(info.annotations) || !expect('{')) + return false; + + while (!peek('}')) + { + if (pass_info pass; parse_technique_pass(pass)) + info.passes.push_back(std::move(pass)); + else if (!peek(tokenid::pass)) // If there is another pass definition following, try to parse that despite the error + return consume_until('}'), false; + } + + _codegen->define_technique(info); + + return expect('}'); +} + +bool reshadefx::parser::parse_technique_pass(pass_info &info) +{ + if (!expect(tokenid::pass)) + return false; + + // Passes can have an optional name, so consume and ignore that if it exists + accept(tokenid::identifier); + + if (!expect('{')) + return false; + + while (!peek('}')) + { + // Parse pass states + if (!expect(tokenid::identifier)) + return consume_until('}'), false; + + auto location = std::move(_token.location); + const auto state = std::move(_token.literal_as_string); + + if (!expect('=')) + return consume_until('}'), false; + + const bool is_shader_state = state == "VertexShader" || state == "PixelShader"; + const bool is_texture_state = state.compare(0, 12, "RenderTarget") == 0 && (state.size() == 12 || (state[12] >= '0' && state[12] < '8')); + + // Shader and render target assignment looks up values in the symbol table, so handle those separately from the other states + if (is_shader_state || is_texture_state) + { + // Starting an identifier with '::' restricts the symbol search to the global namespace level + const bool exclusive = accept(tokenid::colon_colon); + + std::string identifier; + + if (expect(tokenid::identifier)) + identifier = std::move(_token.literal_as_string); + else + return consume_until('}'), false; + + // Can concatenate multiple '::' to force symbol search for a specific namespace level + while (accept(tokenid::colon_colon)) + { + if (!expect(tokenid::identifier)) + return consume_until('}'), false; + + identifier += "::" + std::move(_token.literal_as_string); + } + + location = std::move(_token.location); + + // Figure out which scope to start searching in + scope scope = { "::", 0, 0 }; + if (!exclusive) scope = current_scope(); + + // Lookup name in the symbol table + const symbol symbol = find_symbol(identifier, scope, exclusive); + + // Ignore invalid symbols that were added during error recovery + if (symbol.id == 0xFFFFFFFF) + return consume_until('}'), false; + + if (is_shader_state) + { + if (!symbol.id) + return error(location, 3501, "undeclared identifier '" + identifier + "', expected function name"), consume_until('}'), false; + else if (!symbol.type.is_function()) + return error(location, 3020, "type mismatch, expected function name"), consume_until('}'), false; + + const bool is_vs = state[0] == 'V'; + const bool is_ps = state[0] == 'P'; + + // Look up the matching function info for this function definition + function_info &function_info = _codegen->find_function(symbol.id); + + // We potentially need to generate a special entry point function which translates between function parameters and input/output variables + _codegen->define_entry_point(function_info, is_ps); + + if (is_vs) + info.vs_entry_point = function_info.unique_name; + if (is_ps) + info.ps_entry_point = function_info.unique_name; + } + else + { + assert(is_texture_state); + + if (!symbol.id) + return error(location, 3004, "undeclared identifier '" + identifier + "', expected texture name"), consume_until('}'), false; + else if (!symbol.type.is_texture()) + return error(location, 3020, "type mismatch, expected texture name"), consume_until('}'), false; + + const texture_info &target_info = _codegen->find_texture(symbol.id); + + // Verify that all render targets in this pass have the same dimensions + if (info.viewport_width != 0 && info.viewport_height != 0 && (target_info.width != info.viewport_width || target_info.height != info.viewport_height)) + return error(location, 4545, "cannot use multiple render targets with different texture dimensions (is " + std::to_string(target_info.width) + 'x' + std::to_string(target_info.height) + ", but expected " + std::to_string(info.viewport_width) + 'x' + std::to_string(info.viewport_height) + ')'), false; + + info.viewport_width = target_info.width; + info.viewport_height = target_info.height; + + const size_t target_index = state.size() > 12 ? (state[12] - '0') : 0; + + info.render_target_names[target_index] = target_info.unique_name; + } + } + else // Handle the rest of the pass states + { + backup(); + + expression expression; + + if (accept(tokenid::identifier)) // Handle special enumeration names for pass states + { + // Transform identifier to uppercase to do case-insensitive comparison + std::transform(_token.literal_as_string.begin(), _token.literal_as_string.end(), _token.literal_as_string.begin(), [](char c) { return static_cast(toupper(c)); }); + + static const std::pair s_enum_values[] = { + { "NONE", 0 }, { "ZERO", 0 }, { "ONE", 1 }, + { "SRCCOLOR", 2 }, { "SRCALPHA", 3 }, { "INVSRCCOLOR", 4 }, { "INVSRCALPHA", 5 }, { "DESTCOLOR", 8 }, { "DESTALPHA", 6 }, { "INVDESTCOLOR", 9 }, { "INVDESTALPHA", 7 }, + { "ADD", 1 }, { "SUBTRACT", 2 }, { "REVSUBTRACT", 3 }, { "MIN", 4 }, { "MAX", 5 }, + { "KEEP", 1 }, { "REPLACE", 3 }, { "INVERT", 6 }, { "INCR", 7 }, { "INCRSAT", 4 }, { "DECR", 8 }, { "DECRSAT", 5 }, + { "NEVER", 1 }, { "ALWAYS", 8 }, { "LESS", 2 }, { "GREATER", 5 }, { "LEQUAL", 4 }, { "LESSEQUAL", 4 }, { "GEQUAL", 7 }, { "GREATEREQUAL", 7 }, { "EQUAL", 3 }, { "NEQUAL", 6 }, { "NOTEQUAL", 6 }, + }; + + // Look up identifier in list of possible enumeration names + const auto it = std::find_if(std::begin(s_enum_values), std::end(s_enum_values), + [this](const auto &it) { return it.first == _token.literal_as_string; }); + + if (it != std::end(s_enum_values)) + expression.reset_to_rvalue_constant(_token.location, it->second); + else // No match found, so rewind to parser state before the identifier was consumed and try parsing it as a normal expression + restore(); + } + + // Parse right hand side as normal expression if no special enumeration name was matched already + if (!expression.is_constant && !parse_expression_multary(expression)) + return error(_token_next.location, 3000, "syntax error: unexpected '" + token::id_to_name(_token_next.id) + "', expected expression"), consume_until('}'), false; + else if (!expression.is_constant || !expression.type.is_scalar()) + return error(expression.location, 3011, "pass state value must be a literal scalar expression"), consume_until('}'), false; + + // All states below expect the value to be of an unsigned integer type + expression.add_cast_operation({ type::t_uint, 1, 1 }); + const unsigned int value = expression.constant.as_uint[0]; + + if (state == "SRGBWriteEnable") + info.srgb_write_enable = value != 0; + else if (state == "BlendEnable") + info.blend_enable = value != 0; + else if (state == "StencilEnable") + info.stencil_enable = value != 0; + else if (state == "ClearRenderTargets") + info.clear_render_targets = value != 0; + else if (state == "RenderTargetWriteMask" || state == "ColorWriteMask") + info.color_write_mask = value & 0xFF; + else if (state == "StencilReadMask" || state == "StencilMask") + info.stencil_read_mask = value & 0xFF; + else if (state == "StencilWriteMask") + info.stencil_write_mask = value & 0xFF; + else if (state == "BlendOp") + info.blend_op = value; + else if (state == "BlendOpAlpha") + info.blend_op_alpha = value; + else if (state == "SrcBlend") + info.src_blend = value; + else if (state == "SrcBlendAlpha") + info.src_blend_alpha = value; + else if (state == "DestBlend") + info.dest_blend = value; + else if (state == "DestBlendAlpha") + info.dest_blend_alpha = value; + else if (state == "StencilFunc") + info.stencil_comparison_func = value; + else if (state == "StencilRef") + info.stencil_reference_value = value; + else if (state == "StencilPass" || state == "StencilPassOp") + info.stencil_op_pass = value; + else if (state == "StencilFail" || state == "StencilFailOp") + info.stencil_op_fail = value; + else if (state == "StencilZFail" || state == "StencilDepthFail" || state == "StencilDepthFailOp") + info.stencil_op_depth_fail = value; + else + return error(location, 3004, "unrecognized pass state '" + state + '\''), consume_until('}'), false; + } + + if (!expect(';')) + return consume_until('}'), false; + } + + return expect('}'); +} diff --git a/msvc/source/effect_parser.hpp b/msvc/source/effect_parser.hpp new file mode 100644 index 0000000..51dbbb8 --- /dev/null +++ b/msvc/source/effect_parser.hpp @@ -0,0 +1,89 @@ +/** + * Copyright (C) 2014 Patrick Mours. All rights reserved. + * License: https://github.com/crosire/reshade#license + */ + +#pragma once + +#include "effect_lexer.hpp" +#include "effect_symbol_table.hpp" +#include + +namespace reshadefx +{ + /// + /// A parser for the ReShade FX shader language. + /// + class parser : symbol_table + { + public: + /// + /// Parse the provided input string. + /// + /// The string to analyze. + /// The code generation implementation to use. + /// A boolean value indicating whether parsing was successful or not. + bool parse(std::string source, class codegen *backend); + + /// + /// Get the list of error messages. + /// + std::string &errors() { return _errors; } + const std::string &errors() const { return _errors; } + + private: + void error(const location &location, unsigned int code, const std::string &message); + void warning(const location &location, unsigned int code, const std::string &message); + + void backup(); + void restore(); + + bool peek(char tok) const { return _token_next.id == static_cast(tok); } + bool peek(tokenid tokid) const { return _token_next.id == tokid; } + void consume(); + void consume_until(char tok) { return consume_until(static_cast(tok)); } + void consume_until(tokenid tokid); + bool accept(char tok) { return accept(static_cast(tok)); } + bool accept(tokenid tokid); + bool expect(char tok) { return expect(static_cast(tok)); } + bool expect(tokenid tokid); + + bool accept_type_class(type &type); + bool accept_type_qualifiers(type &type); + + bool parse_type(type &type); + + bool parse_array_size(type &type); + + bool accept_unary_op(); + bool accept_postfix_op(); + bool peek_multary_op(unsigned int &precedence) const; + bool accept_assignment_op(); + + bool parse_expression(expression &expression); + bool parse_expression_unary(expression &expression); + bool parse_expression_multary(expression &expression, unsigned int precedence = 0); + bool parse_expression_assignment(expression &expression); + + bool parse_annotations(std::unordered_map> &annotations); + + bool parse_statement(bool scoped); + bool parse_statement_block(bool scoped); + + bool parse_top(); + bool parse_struct(); + bool parse_function(type type, std::string name); + bool parse_variable(type type, std::string name, bool global = false); + bool parse_technique(); + bool parse_technique_pass(pass_info &info); + + std::string _errors; + token _token, _token_next, _token_backup; + std::unique_ptr _lexer, _lexer_backup; + codegen *_codegen = nullptr; + + std::vector _loop_break_target_stack; + std::vector _loop_continue_target_stack; + type _current_return_type; + }; +} diff --git a/msvc/source/effect_preprocessor.cpp b/msvc/source/effect_preprocessor.cpp new file mode 100644 index 0000000..606e10d --- /dev/null +++ b/msvc/source/effect_preprocessor.cpp @@ -0,0 +1,1068 @@ +/** + * Copyright (C) 2014 Patrick Mours. All rights reserved. + * License: https://github.com/crosire/reshade#license + */ + +#include "effect_preprocessor.hpp" +#include + +enum macro_replacement +{ + macro_replacement_start = '\x00', + macro_replacement_argument = '\xFA', + macro_replacement_concat = '\xFF', + macro_replacement_stringize = '\xFE', + macro_replacement_space = '\xFD', + macro_replacement_break = '\xFC', + macro_replacement_expand = '\xFB', +}; + +static bool read_file(const std::filesystem::path &path, std::string &data) +{ + FILE *file = nullptr; + if (_wfopen_s(&file, path.c_str(), L"rb") != 0) + return false; + + // Read file contents into memory + std::vector mem(static_cast(std::filesystem::file_size(path) + 1)); + const size_t eof = fread(mem.data(), 1, mem.size() - 1, file); + + // Append a new line feed to the end of the input string to avoid issues with parsing + mem[eof] = '\n'; + + // No longer need to have a handle open to the file, since all data was read, so can safely close it + fclose(file); + + std::string_view filedata(mem.data(), mem.size()); + + // Remove BOM (0xefbbbf means 0xfeff) + if (filedata.size() >= 3 && + static_cast(filedata[0]) == 0xef && + static_cast(filedata[1]) == 0xbb && + static_cast(filedata[2]) == 0xbf) + filedata = std::string_view(filedata.data() + 3, filedata.size() - 3); + + data = filedata; + return true; +} + +void reshadefx::preprocessor::add_include_path(const std::filesystem::path &path) +{ + assert(!path.empty()); + + _include_paths.push_back(path); +} +bool reshadefx::preprocessor::add_macro_definition(const std::string &name, const macro ¯o) +{ + assert(!name.empty()); + + return _macros.emplace(name, macro).second; +} +bool reshadefx::preprocessor::add_macro_definition(const std::string &name, std::string value) +{ + macro macro; + macro.replacement_list = std::move(value); + + return add_macro_definition(name, macro); +} + +bool reshadefx::preprocessor::append_file(const std::filesystem::path &path) +{ + std::string data; + if (!read_file(path, data)) + return false; + + _success = true; + push(std::move(data), path.u8string()); + parse(); + + return _success; +} +bool reshadefx::preprocessor::append_string(const std::string &source_code) +{ + // Enforce all input strings to end with a line feed + assert(!source_code.empty() && source_code.back() == '\n'); + + _success = true; + push(source_code); + parse(); + + return _success; +} + +void reshadefx::preprocessor::error(const location &location, const std::string &message) +{ + _errors += location.source + '(' + std::to_string(location.line) + ", " + std::to_string(location.column) + ')' + ": preprocessor error: " + message + '\n'; + _success = false; +} +void reshadefx::preprocessor::warning(const location &location, const std::string &message) +{ + _errors += location.source + '(' + std::to_string(location.line) + ", " + std::to_string(location.column) + ')' + ": preprocessor warning: " + message + '\n'; +} + +reshadefx::lexer &reshadefx::preprocessor::current_lexer() +{ + assert(!_input_stack.empty()); + + return *_input_stack.top().lexer; +} +std::stack &reshadefx::preprocessor::current_if_stack() +{ + assert(!_input_stack.empty()); + + return _input_stack.top().if_stack; +} + +void reshadefx::preprocessor::push(std::string input, const std::string &name) +{ + input_level level = {}; + level.name = name; + level.lexer.reset(new lexer(std::move(input), true, false, false, false, true, false)); + level.parent = _input_stack.empty() ? nullptr : &_input_stack.top(); + level.next_token.id = tokenid::unknown; + + if (name.empty()) + { + if (level.parent != nullptr) + level.name = level.parent->name; + } + else + { + _output_location.source = name; + + _output += "#line 1 \"" + name + "\"\n"; + } + + _input_stack.push(std::move(level)); + + consume(); +} + +bool reshadefx::preprocessor::peek(tokenid token) const +{ + assert(!_input_stack.empty()); + + return _input_stack.top().next_token == token; +} +void reshadefx::preprocessor::consume() +{ + assert(!_input_stack.empty()); + + auto &input_level = _input_stack.top(); + const auto &input_string = input_level.lexer->input_string(); + + _token = std::move(input_level.next_token); + _token.location.source = _output_location.source; + _current_token_raw_data = input_string.substr(_token.offset, _token.length); + + // Get the next token + input_level.next_token = + input_level.lexer->lex(); + + // Pop input level if lexical analysis has reached the end of it + while (_input_stack.top().next_token == tokenid::end_of_file) + { + if (!current_if_stack().empty()) + error(current_if_stack().top().token.location, "unterminated #if"); + + _input_stack.pop(); + + if (_input_stack.empty()) + break; + + const auto &top = _input_stack.top(); + + if (top.name != _output_location.source) + { + _output_location.line = 1; + _output_location.source = top.name; + + _output += "#line 1 \"" + top.name + "\"\n"; + } + } +} +void reshadefx::preprocessor::consume_until(tokenid token) +{ + while (!accept(token) && !peek(tokenid::end_of_file)) + { + consume(); + } +} +bool reshadefx::preprocessor::accept(tokenid token) +{ + while (peek(tokenid::space)) + { + consume(); + } + + if (peek(token)) + { + consume(); + + return true; + } + + return false; +} +bool reshadefx::preprocessor::expect(tokenid token) +{ + if (!accept(token)) + { + assert(!_input_stack.empty()); + + auto actual_token = _input_stack.top().next_token; + actual_token.location.source = _output_location.source; + + error(actual_token.location, "syntax error: unexpected token '" + current_lexer().input_string().substr(actual_token.offset, actual_token.length) + "'"); + + return false; + } + + return true; +} + +void reshadefx::preprocessor::parse() +{ + std::string line; + + while (!_input_stack.empty()) + { + _recursion_count = 0; + + const bool skip = !current_if_stack().empty() && current_if_stack().top().skipping; + + consume(); + + switch (_token) + { + case tokenid::hash_if: + parse_if(); + if (!expect(tokenid::end_of_line)) + consume_until(tokenid::end_of_line); + continue; + case tokenid::hash_ifdef: + parse_ifdef(); + if (!expect(tokenid::end_of_line)) + consume_until(tokenid::end_of_line); + continue; + case tokenid::hash_ifndef: + parse_ifndef(); + if (!expect(tokenid::end_of_line)) + consume_until(tokenid::end_of_line); + continue; + case tokenid::hash_else: + parse_else(); + if (!expect(tokenid::end_of_line)) + consume_until(tokenid::end_of_line); + continue; + case tokenid::hash_elif: + parse_elif(); + if (!expect(tokenid::end_of_line)) + consume_until(tokenid::end_of_line); + continue; + case tokenid::hash_endif: + parse_endif(); + if (!expect(tokenid::end_of_line)) + consume_until(tokenid::end_of_line); + continue; + } + + if (skip) + continue; + + switch (_token) + { + case tokenid::hash_def: + parse_def(); + if (!expect(tokenid::end_of_line)) + consume_until(tokenid::end_of_line); + continue; + case tokenid::hash_undef: + parse_undef(); + if (!expect(tokenid::end_of_line)) + consume_until(tokenid::end_of_line); + continue; + case tokenid::hash_error: + parse_error(); + if (!expect(tokenid::end_of_line)) + consume_until(tokenid::end_of_line); + continue; + case tokenid::hash_warning: + parse_warning(); + if (!expect(tokenid::end_of_line)) + consume_until(tokenid::end_of_line); + continue; + case tokenid::hash_pragma: + parse_pragma(); + if (!expect(tokenid::end_of_line)) + consume_until(tokenid::end_of_line); + continue; + case tokenid::hash_include: + parse_include(); + continue; + case tokenid::hash_unknown: + error(_token.location, "unrecognized preprocessing directive '" + _token.literal_as_string + "'"); + consume_until(tokenid::end_of_line); + continue; + case tokenid::end_of_line: + if (line.empty()) + continue; + if (++_output_location.line != _token.location.line) + _output += "#line " + std::to_string(_output_location.line = _token.location.line) + '\n'; + _output += line; + _output += '\n'; + line.clear(); + continue; + case tokenid::identifier: + if (evaluate_identifier_as_macro()) + continue; + default: + line += _current_token_raw_data; + break; + } + } + + _output += line; +} + +void reshadefx::preprocessor::parse_def() +{ + if (!expect(tokenid::identifier)) + return; + else if (_token.literal_as_string == "defined") + return warning(_token.location, "macro name 'defined' is reserved"); + + macro m; + const auto location = std::move(_token.location); + const auto macro_name = std::move(_token.literal_as_string); + const auto macro_name_end_offset = _token.offset + _token.length; + + if (current_lexer().input_string()[macro_name_end_offset] == '(') + { + accept(tokenid::parenthesis_open); + + m.is_function_like = true; + + while (accept(tokenid::identifier)) + { + m.parameters.push_back(_token.literal_as_string); + + if (!accept(tokenid::comma)) + break; + } + + if (accept(tokenid::ellipsis)) + { + m.is_variadic = true; + m.parameters.push_back("__VA_ARGS__"); + + // TODO: Implement variadic macros + error(_token.location, "variadic macros are not currently supported"); + return; + } + + if (!expect(tokenid::parenthesis_close)) + return; + } + + create_macro_replacement_list(m); + + if (!add_macro_definition(macro_name, m)) + return error(location, "redefinition of '" + macro_name + "'"); +} +void reshadefx::preprocessor::parse_undef() +{ + if (!expect(tokenid::identifier)) + return; + else if (_token.literal_as_string == "defined") + return warning(_token.location, "macro name 'defined' is reserved"); + + _macros.erase(_token.literal_as_string); +} + +void reshadefx::preprocessor::parse_if() +{ + const bool condition_result = evaluate_expression(); + + if_level level; + level.token = _token; + level.value = condition_result; + level.parent = current_if_stack().empty() ? nullptr : ¤t_if_stack().top(); + level.skipping = (level.parent != nullptr && level.parent->skipping) || !level.value; + + current_if_stack().push(level); +} +void reshadefx::preprocessor::parse_ifdef() +{ + if_level level; + level.token = _token; + + if (!expect(tokenid::identifier)) + return; + + level.value = _macros.find(_token.literal_as_string) != _macros.end(); + level.parent = current_if_stack().empty() ? nullptr : ¤t_if_stack().top(); + level.skipping = (level.parent != nullptr && level.parent->skipping) || !level.value; + + current_if_stack().push(level); +} +void reshadefx::preprocessor::parse_ifndef() +{ + if_level level; + level.token = _token; + + if (!expect(tokenid::identifier)) + return; + + level.value = _macros.find(_token.literal_as_string) == _macros.end(); + level.parent = current_if_stack().empty() ? nullptr : ¤t_if_stack().top(); + level.skipping = (level.parent != nullptr && level.parent->skipping) || !level.value; + + current_if_stack().push(level); +} +void reshadefx::preprocessor::parse_elif() +{ + if (current_if_stack().empty()) + return error(_token.location, "missing #if for #elif"); + + if_level &level = current_if_stack().top(); + + if (level.token == tokenid::hash_else) + return error(_token.location, "#elif is not allowed after #else"); + + const bool condition_result = evaluate_expression(); + + level.token = _token; + level.skipping = (level.parent != nullptr && level.parent->skipping) || level.value || !condition_result; + + if (!level.value) level.value = condition_result; +} +void reshadefx::preprocessor::parse_else() +{ + if (current_if_stack().empty()) + return error(_token.location, "missing #if for #else"); + + if_level &level = current_if_stack().top(); + + if (level.token == tokenid::hash_else) + return error(_token.location, "#else is not allowed after #else"); + + level.token = _token; + level.skipping = (level.parent != nullptr && level.parent->skipping) || level.value; + + if (!level.value) level.value = true; +} +void reshadefx::preprocessor::parse_endif() +{ + if (current_if_stack().empty()) + return error(_token.location, "missing #if for #endif"); + + current_if_stack().pop(); +} + +void reshadefx::preprocessor::parse_error() +{ + const auto keyword_location = std::move(_token.location); + + if (!expect(tokenid::string_literal)) + return; + + error(keyword_location, _token.literal_as_string); +} +void reshadefx::preprocessor::parse_warning() +{ + const auto keyword_location = std::move(_token.location); + + if (!expect(tokenid::string_literal)) + return; + + warning(keyword_location, _token.literal_as_string); +} + +void reshadefx::preprocessor::parse_pragma() +{ + const auto keyword_location = std::move(_token.location); + + if (!expect(tokenid::identifier)) + return; + + std::string pragma = std::move(_token.literal_as_string); + + while (!peek(tokenid::end_of_line) && !peek(tokenid::end_of_file)) + { + consume(); + + if (_token == tokenid::identifier && evaluate_identifier_as_macro()) + continue; + + pragma += _current_token_raw_data; + } + + if (pragma == "once") + { + if (const auto it = _filecache.find(_output_location.source); it != _filecache.end()) + it->second.clear(); + return; + } + + warning(keyword_location, "unknown pragma ignored"); +} + +void reshadefx::preprocessor::parse_include() +{ + const auto keyword_location = std::move(_token.location); + + while (accept(tokenid::identifier)) + { + if (evaluate_identifier_as_macro()) + continue; + + error(_token.location, "syntax error: unexpected identifier in #include"); + consume_until(tokenid::end_of_line); + return; + } + + if (!expect(tokenid::string_literal)) + { + consume_until(tokenid::end_of_line); + return; + } + + const std::filesystem::path filename = std::filesystem::u8path(_token.literal_as_string); + + std::error_code ec; + std::filesystem::path filepath = std::filesystem::u8path(_output_location.source); + filepath.replace_filename(filename); + + if (!std::filesystem::exists(filepath, ec)) + for (const auto &include_path : _include_paths) + if (std::filesystem::exists(filepath = include_path / filename, ec)) + break; + + auto it = _filecache.find(filepath.u8string()); + + if (it == _filecache.end()) + { + std::string data; + if (!read_file(filepath, data)) + { + error(keyword_location, "could not open included file '" + filepath.u8string() + "'"); + consume_until(tokenid::end_of_line); + return; + } + + it = _filecache.emplace(filepath.u8string(), std::move(data)).first; + } + + push(it->second, filepath.u8string()); +} + +bool reshadefx::preprocessor::evaluate_expression() +{ + enum op_type + { + op_none = -1, + + op_or, + op_and, + op_bitor, + op_bitxor, + op_bitand, + op_not_equal, + op_equal, + op_less, + op_greater, + op_less_equal, + op_greater_equal, + op_leftshift, + op_rightshift, + op_add, + op_subtract, + op_modulo, + op_divide, + op_multiply, + op_plus, + op_negate, + op_not, + op_bitnot, + op_parentheses + }; + struct rpn_token + { + bool is_op; + int value; + }; + + int stack[128]; + rpn_token rpn[128]; + size_t stack_count = 0, rpn_count = 0; + tokenid previous_token = _token; + const int precedence[] = { 0, 1, 2, 3, 4, 5, 6, 7, 7, 7, 7, 8, 8, 9, 9, 10, 10, 10, 11, 11, 11, 11 }; + + // Run shunting-yard algorithm + while (!peek(tokenid::end_of_line)) + { + if (stack_count >= _countof(stack) || rpn_count >= _countof(rpn)) + { + error(_token.location, "expression evaluator ran out of stack space"); + return false; + } + int op = op_none; + bool is_left_associative = true; + + consume(); + + switch (_token) + { + case tokenid::space: + continue; + case tokenid::backslash: + // Skip to next line if the line ends with a backslash + if (accept(tokenid::end_of_line)) + continue; + else // Otherwise continue on processing the token (it is not valid here, but make that an error below) + break; + case tokenid::exclaim: + op = op_not; + is_left_associative = false; + break; + case tokenid::percent: + op = op_modulo; + break; + case tokenid::ampersand: + op = op_bitand; + break; + case tokenid::star: + op = op_multiply; + break; + case tokenid::plus: + is_left_associative = + previous_token == tokenid::int_literal || + previous_token == tokenid::uint_literal || + previous_token == tokenid::identifier || + previous_token == tokenid::parenthesis_close; + op = is_left_associative ? op_add : op_plus; + break; + case tokenid::minus: + is_left_associative = + previous_token == tokenid::int_literal || + previous_token == tokenid::uint_literal || + previous_token == tokenid::identifier || + previous_token == tokenid::parenthesis_close; + op = is_left_associative ? op_subtract : op_negate; + break; + case tokenid::slash: + op = op_divide; + break; + case tokenid::less: + op = op_less; + break; + case tokenid::greater: + op = op_greater; + break; + case tokenid::caret: + op = op_bitxor; + break; + case tokenid::pipe: + op = op_bitor; + break; + case tokenid::tilde: + op = op_bitnot; + is_left_associative = false; + break; + case tokenid::exclaim_equal: + op = op_not_equal; + break; + case tokenid::ampersand_ampersand: + op = op_and; + break; + case tokenid::less_less: + op = op_leftshift; + break; + case tokenid::less_equal: + op = op_less_equal; + break; + case tokenid::equal_equal: + op = op_equal; + break; + case tokenid::greater_greater: + op = op_rightshift; + break; + case tokenid::greater_equal: + op = op_greater_equal; + break; + case tokenid::pipe_pipe: + op = op_or; + break; + } + + switch (_token) + { + case tokenid::parenthesis_open: + stack[stack_count++] = op_parentheses; + break; + case tokenid::parenthesis_close: + { + bool matched = false; + + while (stack_count > 0) + { + const int op2 = stack[--stack_count]; + + if (op2 == op_parentheses) + { + matched = true; + break; + } + + rpn[rpn_count].is_op = true; + rpn[rpn_count++].value = op2; + } + + if (!matched) + { + error(_token.location, "unmatched ')'"); + return false; + } + break; + } + case tokenid::identifier: + { + if (evaluate_identifier_as_macro()) + { + continue; + } + else if (_token.literal_as_string == "exists") + { + const bool has_parentheses = accept(tokenid::parenthesis_open); + + while (accept(tokenid::identifier)) + { + if (!evaluate_identifier_as_macro()) + { + error(_token.location, "syntax error: unexpected identifier after 'exists'"); + return false; + } + } + + if (!expect(tokenid::string_literal)) + return false; + + const std::filesystem::path filename = std::filesystem::u8path(_token.literal_as_string); + + if (has_parentheses && !expect(tokenid::parenthesis_close)) + return false; + + std::error_code ec; + std::filesystem::path filepath = std::filesystem::u8path(_output_location.source); + filepath.replace_filename(filename); + + if (!std::filesystem::exists(filepath, ec)) + for (const auto &include_path : _include_paths) + if (std::filesystem::exists(filepath = include_path / filename, ec)) + break; + + rpn[rpn_count].is_op = false; + rpn[rpn_count++].value = std::filesystem::exists(filepath, ec); + continue; + } + else if (_token.literal_as_string == "defined") + { + const bool has_parentheses = accept(tokenid::parenthesis_open); + + if (!expect(tokenid::identifier)) + return false; + + const bool is_macro_defined = _macros.find(_token.literal_as_string) != _macros.end(); + + if (has_parentheses && !expect(tokenid::parenthesis_close)) + return false; + + rpn[rpn_count].is_op = false; + rpn[rpn_count++].value = is_macro_defined; + continue; + } + + // An identifier that cannot be replaced with a number becomes zero + rpn[rpn_count].is_op = false; + rpn[rpn_count++].value = 0; + break; + } + case tokenid::int_literal: + case tokenid::uint_literal: + { + rpn[rpn_count].is_op = false; + rpn[rpn_count++].value = _token.literal_as_int; + break; + } + default: + { + if (op == op_none) + { + error(_token.location, "invalid expression"); + return false; + } + + const int precedence1 = precedence[op]; + + while (stack_count > 0) + { + const int op2 = stack[stack_count - 1]; + + if (op2 == op_parentheses) + break; + + const int precedence2 = precedence[op2]; + + if ((is_left_associative && (precedence1 <= precedence2)) || (!is_left_associative && (precedence1 < precedence2))) + { + stack_count--; + rpn[rpn_count].is_op = true; + rpn[rpn_count++].value = op2; + } + else + { + break; + } + } + + stack[stack_count++] = op; + break; + } + } + + previous_token = _token; + } + + while (stack_count > 0) + { + const int op = stack[--stack_count]; + + if (op == op_parentheses) + { + error(_token.location, "unmatched ')'"); + return false; + } + + rpn[rpn_count].is_op = true; + rpn[rpn_count++].value = static_cast(op); + } + + // Evaluate reverse polish notation output + for (rpn_token *token = rpn; rpn_count-- != 0; token++) + { + if (token->is_op) + { +#define UNARY_OPERATION(op) { \ + if (stack_count < 1) \ + return error(_token.location, "invalid expression"), 0; \ + stack[stack_count - 1] = op stack[stack_count - 1]; \ + } +#define BINARY_OPERATION(op) { \ + if (stack_count < 2) \ + return error(_token.location, "invalid expression"), 0; \ + stack[stack_count - 2] = stack[stack_count - 2] op stack[stack_count - 1]; \ + stack_count--; \ + } + switch (token->value) + { + case op_or: BINARY_OPERATION(||); break; + case op_and: BINARY_OPERATION(&&); break; + case op_bitor: BINARY_OPERATION(|); break; + case op_bitxor: BINARY_OPERATION(^); break; + case op_bitand: BINARY_OPERATION(&); break; + case op_not_equal: BINARY_OPERATION(!=); break; + case op_equal: BINARY_OPERATION(==); break; + case op_less: BINARY_OPERATION(<); break; + case op_greater: BINARY_OPERATION(>); break; + case op_less_equal: BINARY_OPERATION(<=); break; + case op_greater_equal: BINARY_OPERATION(>=); break; + case op_leftshift: BINARY_OPERATION(<<); break; + case op_rightshift: BINARY_OPERATION(>>); break; + case op_add: BINARY_OPERATION(+); break; + case op_subtract: BINARY_OPERATION(-); break; + case op_modulo: BINARY_OPERATION(%); break; + case op_divide: BINARY_OPERATION(/); break; + case op_multiply: BINARY_OPERATION(*); break; + case op_plus: UNARY_OPERATION(+); break; + case op_negate: UNARY_OPERATION(-); break; + case op_not: UNARY_OPERATION(!); break; + case op_bitnot: UNARY_OPERATION(~); break; + } + } + else + { + stack[stack_count++] = token->value; + } + } + + if (stack_count != 1) + { + error(_token.location, "invalid expression"); + return false; + } + + return stack[0] != 0; +} +bool reshadefx::preprocessor::evaluate_identifier_as_macro() +{ + if (_recursion_count++ >= 256) + { + error(_token.location, "macro recursion too high"); + return false; + } + + const auto it = _macros.find(_token.literal_as_string); + + if (it == _macros.end()) + return false; + + const auto ¯o = it->second; + std::vector arguments; + + if (macro.is_function_like) + { + if (!accept(tokenid::parenthesis_open)) + return false; + + while (true) + { + int parentheses_level = 0; + std::string argument; + + while (true) + { + consume(); + + if (_token == tokenid::parenthesis_open) + parentheses_level++; + else if ( + (_token == tokenid::parenthesis_close && --parentheses_level < 0) || + (_token == tokenid::comma && parentheses_level == 0)) + break; + + argument += _current_token_raw_data; + } + + if (!argument.empty() && argument.back() == ' ') + argument.pop_back(); + if (!argument.empty() && argument.front() == ' ') + argument.erase(0, 1); + + arguments.push_back(argument); + + if (parentheses_level < 0) + break; + } + } + + std::string input; + expand_macro(it->second, arguments, input); + push(std::move(input)); + + return true; +} + +void reshadefx::preprocessor::expand_macro(const macro ¯o, const std::vector &arguments, std::string &out) +{ + for (auto it = macro.replacement_list.begin(); it != macro.replacement_list.end(); ++it) + { + if (*it != macro_replacement_start) + { + out += *it; + continue; + } + + switch (*++it) + { + case macro_replacement_concat: + continue; + case macro_replacement_stringize: + out += '"' + arguments.at(*++it) + '"'; + break; + case macro_replacement_argument: + push(arguments.at(*++it) + static_cast(macro_replacement_argument)); + while (!accept(tokenid::unknown)) + { + consume(); + if (_token == tokenid::identifier && evaluate_identifier_as_macro()) + continue; + out += _current_token_raw_data; + } + assert(_current_token_raw_data[0] == macro_replacement_argument); + break; + } + } +} +void reshadefx::preprocessor::create_macro_replacement_list(macro ¯o) +{ + // Since the number of parameters is encoded in the string, it may not exceed the available size of a char + if (macro.parameters.size() >= std::numeric_limits::max()) + return error(_token.location, "too many macro parameters"); + + while (!peek(tokenid::end_of_file) && !peek(tokenid::end_of_line)) + { + consume(); + + switch (_token) + { + case tokenid::hash: + { + if (accept(tokenid::hash)) + { + if (peek(tokenid::end_of_line)) + { + error(_token.location, "## cannot appear at end of macro text"); + return; + } + + // the ## token concatenation operator + macro.replacement_list += macro_replacement_start; + macro.replacement_list += macro_replacement_concat; + continue; + } + else if (macro.is_function_like) + { + if (!expect(tokenid::identifier)) + return; + + const auto it = std::find(macro.parameters.begin(), macro.parameters.end(), _token.literal_as_string); + + if (it == macro.parameters.end()) + return error(_token.location, "# must be followed by parameter name"); + + // the # stringize operator + macro.replacement_list += macro_replacement_start; + macro.replacement_list += macro_replacement_stringize; + macro.replacement_list += static_cast(std::distance(macro.parameters.begin(), it)); + continue; + } + break; + } + case tokenid::backslash: + { + if (peek(tokenid::end_of_line)) + { + consume(); + continue; + } + break; + } + case tokenid::identifier: + { + const auto it = std::find(macro.parameters.begin(), macro.parameters.end(), _token.literal_as_string); + + if (it != macro.parameters.end()) + { + macro.replacement_list += macro_replacement_start; + macro.replacement_list += macro_replacement_argument; + macro.replacement_list += static_cast(std::distance(macro.parameters.begin(), it)); + continue; + } + break; + } + } + + macro.replacement_list += _current_token_raw_data; + } +} diff --git a/msvc/source/effect_preprocessor.hpp b/msvc/source/effect_preprocessor.hpp new file mode 100644 index 0000000..0a9c8f7 --- /dev/null +++ b/msvc/source/effect_preprocessor.hpp @@ -0,0 +1,135 @@ +/** + * Copyright (C) 2014 Patrick Mours. All rights reserved. + * License: https://github.com/crosire/reshade#license + */ + +#pragma once + +#include +#include +#include +#include +#include +#include "effect_lexer.hpp" + +namespace reshadefx +{ + /// + /// A C-style preprocessor implementation. + /// + class preprocessor + { + public: + struct macro + { + std::string replacement_list; + bool is_function_like = false, is_variadic = false; + std::vector parameters; + }; + + /// + /// Add an include directory to the list of search paths used when resolving #include directives. + /// + /// The path to the directory to add. + void add_include_path(const std::filesystem::path &path); + + /// + /// Add a new macro definition. This is equal to appending '#define name macro' to this preprocessor instance. + /// + /// The name of the macro to define. + /// The definition of the macro function or value. + /// + bool add_macro_definition(const std::string &name, const macro ¯o); + /// + /// Add a new macro value definition. This is equal to appending '#define name macro' to this preprocessor instance. + /// + /// The name of the macro to define. + /// The value to define that macro to. + /// + bool add_macro_definition(const std::string &name, std::string value = "1"); + + /// + /// Open the specified file, parse its contents and append them to the output. + /// + /// The path to the file to parse. + /// A boolean value indicating whether parsing was successful or not. + bool append_file(const std::filesystem::path &path); + /// + /// Parse the specified string and append it to the output. + /// + /// The string to parse. + /// A boolean value indicating whether parsing was successful or not. + bool append_string(const std::string &source_code); + + /// + /// Get the list of error messages. + /// + std::string &errors() { return _errors; } + const std::string &errors() const { return _errors; } + /// + /// Get the current pre-processed output string. + /// + std::string &output() { return _output; } + const std::string &output() const { return _output; } + + private: + struct if_level + { + token token; + bool value, skipping; + if_level *parent; + }; + struct input_level + { + std::string name; + std::unique_ptr lexer; + token next_token; + std::stack if_stack; + input_level *parent; + }; + + void error(const location &location, const std::string &message); + void warning(const location &location, const std::string &message); + + lexer ¤t_lexer(); + std::stack ¤t_if_stack(); + + void push(std::string input, const std::string &name = std::string()); + + bool peek(tokenid token) const; + void consume(); + void consume_until(tokenid token); + bool accept(tokenid token); + bool expect(tokenid token); + + void parse(); + void parse_def(); + void parse_undef(); + void parse_if(); + void parse_ifdef(); + void parse_ifndef(); + void parse_elif(); + void parse_else(); + void parse_endif(); + void parse_error(); + void parse_warning(); + void parse_pragma(); + void parse_include(); + + bool evaluate_expression(); + bool evaluate_identifier_as_macro(); + + void expand_macro(const macro ¯o, const std::vector &arguments, std::string &out); + void create_macro_replacement_list(macro ¯o); + + bool _success = true; + token _token; + std::stack _input_stack; + location _output_location; + std::string _output, _errors, _current_token_raw_data; + int _recursion_count = 0; + std::unordered_map _macros; + std::vector _include_paths; + std::unordered_map _filecache; + }; +} diff --git a/msvc/source/effect_symbol_table.cpp b/msvc/source/effect_symbol_table.cpp new file mode 100644 index 0000000..7520e48 --- /dev/null +++ b/msvc/source/effect_symbol_table.cpp @@ -0,0 +1,399 @@ +/** + * Copyright (C) 2014 Patrick Mours. All rights reserved. + * License: https://github.com/crosire/reshade#license + */ + +#include "effect_parser.hpp" +#include "effect_symbol_table.hpp" +#include +#include +#include + +#pragma region Import intrinsic functions + +using namespace reshadefx; + +struct intrinsic +{ + intrinsic(const char *name, unsigned int id, const type &ret_type, std::initializer_list arg_types) : id(id) + { + function.name = name; + function.return_type = ret_type; + function.parameter_list.reserve(arg_types.size()); + for (const type &arg_type : arg_types) + function.parameter_list.push_back({ arg_type }); + } + + unsigned int id; + function_info function; +}; + +// Import intrinsic callback functions +#define IMPLEMENT_INTRINSIC_SPIRV(name, i, code) name##i, +enum { +#include "effect_symbol_table_intrinsics.inl" +}; + +#define void { reshadefx::type::t_void } +#define bool { reshadefx::type::t_bool, 1, 1 } +#define bool2 { reshadefx::type::t_bool, 2, 1 } +#define bool3 { reshadefx::type::t_bool, 3, 1 } +#define bool4 { reshadefx::type::t_bool, 4, 1 } +#define int { reshadefx::type::t_int, 1, 1 } +#define int2 { reshadefx::type::t_int, 2, 1 } +#define int3 { reshadefx::type::t_int, 3, 1 } +#define int4 { reshadefx::type::t_int, 4, 1 } +#define uint { reshadefx::type::t_uint, 1, 1 } +#define uint2 { reshadefx::type::t_uint, 2, 1 } +#define uint3 { reshadefx::type::t_uint, 3, 1 } +#define uint4 { reshadefx::type::t_uint, 4, 1 } +#define float { reshadefx::type::t_float, 1, 1 } +#define float2 { reshadefx::type::t_float, 2, 1 } +#define float3 { reshadefx::type::t_float, 3, 1 } +#define float4 { reshadefx::type::t_float, 4, 1 } +#define float2x2 { reshadefx::type::t_float, 2, 2 } +#define float3x3 { reshadefx::type::t_float, 3, 3 } +#define float4x4 { reshadefx::type::t_float, 4, 4 } +#define out_float { reshadefx::type::t_float, 1, 1, reshadefx::type::q_out } +#define out_float2 { reshadefx::type::t_float, 2, 1, reshadefx::type::q_out } +#define out_float3 { reshadefx::type::t_float, 3, 1, reshadefx::type::q_out } +#define out_float4 { reshadefx::type::t_float, 4, 1, reshadefx::type::q_out } +#define sampler { reshadefx::type::t_sampler } + +// Import intrinsic function definitions +#define DEFINE_INTRINSIC(name, i, ret_type, ...) intrinsic(#name, name##i, ret_type, { __VA_ARGS__ }), +static const intrinsic s_intrinsics[] = { +#include "effect_symbol_table_intrinsics.inl" +}; + +#undef void +#undef bool +#undef bool2 +#undef bool3 +#undef bool4 +#undef int +#undef int2 +#undef int3 +#undef int4 +#undef uint +#undef uint2 +#undef uint3 +#undef uint4 +#undef float1 +#undef float2 +#undef float3 +#undef float4 +#undef float2x2 +#undef float3x3 +#undef float4x4 +#undef out_float +#undef out_float2 +#undef out_float3 +#undef out_float4 +#undef sampler + +#pragma endregion + +unsigned int reshadefx::type::rank(const type &src, const type &dst) +{ + if (src.is_array() != dst.is_array() || (src.array_length != dst.array_length && src.array_length > 0 && dst.array_length > 0)) + return 0; // Arrays of different sizes are not compatible + if (src.is_struct() || dst.is_struct()) + return src.definition == dst.definition ? 32 : 0; // Structs are only compatible if they are the same type + if (!src.is_numeric() || !dst.is_numeric()) + return src.base == dst.base ? 32 : 0; // Numeric values are not compatible with other types + + // This table is based on the following rules: + // - Floating point has a higher rank than integer types + // - Integer to floating point promotion has a higher rank than floating point to integer conversion + // - Signed to unsigned integer conversion has a higher rank than unsigned to signed integer conversion + static const int ranks[4][4] = { + { 5, 4, 4, 4 }, + { 3, 5, 2, 4 }, + { 3, 1, 5, 4 }, + { 3, 3, 3, 6 } + }; + + assert(src.base > 0 && src.base <= 4); + assert(dst.base > 0 && dst.base <= 4); + + const int rank = ranks[src.base - 1][dst.base - 1] << 2; + + if (src.is_scalar() && dst.is_vector()) + return rank >> 1; // Scalar to vector promotion has a lower rank + if (src.is_vector() && dst.is_scalar() || (src.is_vector() == dst.is_vector() && src.rows > dst.rows && src.cols >= dst.cols)) + return rank >> 2; // Vector to scalar conversion has an even lower rank + if (src.is_vector() != dst.is_vector() || src.is_matrix() != dst.is_matrix() || src.components() != dst.components()) + return 0; // If components weren't converted at this point, the types are not compatible + + return rank * src.components(); // More components causes a higher rank +} + +reshadefx::symbol_table::symbol_table() +{ + _current_scope.name = "::"; + _current_scope.level = 0; + _current_scope.namespace_level = 0; +} + +void reshadefx::symbol_table::enter_scope() +{ + _current_scope.level++; +} +void reshadefx::symbol_table::enter_namespace(const std::string &name) +{ + _current_scope.name += name + "::"; + _current_scope.level++; + _current_scope.namespace_level++; +} +void reshadefx::symbol_table::leave_scope() +{ + assert(_current_scope.level > 0); + + for (auto &symbol : _symbol_stack) + { + std::vector &scope_list = symbol.second; + + for (auto scope_it = scope_list.begin(); scope_it != scope_list.end();) + { + if (scope_it->scope.level > scope_it->scope.namespace_level && + scope_it->scope.level >= _current_scope.level) + { + scope_it = scope_list.erase(scope_it); + } + else + { + ++scope_it; + } + } + } + + _current_scope.level--; +} +void reshadefx::symbol_table::leave_namespace() +{ + assert(_current_scope.level > 0); + assert(_current_scope.namespace_level > 0); + + _current_scope.name.erase(_current_scope.name.substr(0, _current_scope.name.size() - 2).rfind("::") + 2); + _current_scope.level--; + _current_scope.namespace_level--; +} + +bool reshadefx::symbol_table::insert_symbol(const std::string &name, const symbol &symbol, bool global) +{ + assert(symbol.id != 0 || symbol.op == symbol_type::constant); + + // Make sure the symbol does not exist yet + if (symbol.op != symbol_type::function && find_symbol(name, _current_scope, true).id != 0) + return false; + + // Insertion routine which keeps the symbol stack sorted by namespace level + const auto insert_sorted = [](auto &vec, const auto &item) { + return vec.insert( + std::upper_bound(vec.begin(), vec.end(), item, + [](auto lhs, auto rhs) { + return lhs.scope.namespace_level < rhs.scope.namespace_level; + }), item); + }; + + // Global symbols are accessible from every scope + if (global) + { + scope scope = { "", 0, 0 }; + + // Walk scope chain from global scope back to current one + for (size_t pos = 0; pos != std::string::npos; pos = _current_scope.name.find("::", pos)) + { + // Extract scope name + scope.name = _current_scope.name.substr(0, pos += 2); + const auto previous_scope_name = _current_scope.name.substr(pos); + + // Insert symbol into this scope + insert_sorted(_symbol_stack[previous_scope_name + name], scoped_symbol { symbol, scope }); + + // Continue walking up the scope chain + scope.level = ++scope.namespace_level; + } + } + else + { + // This is a local symbol so it's sufficient to update the symbol stack with just the current scope + insert_sorted(_symbol_stack[name], scoped_symbol { symbol, _current_scope }); + } + + return true; +} + +reshadefx::symbol reshadefx::symbol_table::find_symbol(const std::string &name) const +{ + // Default to start search with current scope and walk back the scope chain + return find_symbol(name, _current_scope, false); +} +reshadefx::symbol reshadefx::symbol_table::find_symbol(const std::string &name, const scope &scope, bool exclusive) const +{ + const auto stack_it = _symbol_stack.find(name); + + // Check if symbol does exist + if (stack_it == _symbol_stack.end() || stack_it->second.empty()) + return {}; + + // Walk up the scope chain starting at the requested scope level and find a matching symbol + symbol result = {}; + + for (auto it = stack_it->second.rbegin(), end = stack_it->second.rend(); it != end; ++it) + { + if (it->scope.level > scope.level || + it->scope.namespace_level > scope.namespace_level || (it->scope.namespace_level == scope.namespace_level && it->scope.name != scope.name)) + continue; + if (exclusive && it->scope.level < scope.level) + continue; + + if (it->op == symbol_type::constant || it->op == symbol_type::variable || it->op == symbol_type::structure) + return *it; // Variables and structures have the highest priority and are always picked immediately + else if (result.id == 0) + result = *it; // Function names have a lower priority, so continue searching in case a variable with the same name exists + } + + return result; +} + +static int compare_functions(const std::vector &arguments, const reshadefx::function_info *function1, const reshadefx::function_info *function2) +{ + const size_t num_arguments = arguments.size(); + + // Check if the first function matches the argument types + bool function1_viable = true; + const auto function1_ranks = static_cast(alloca(num_arguments * sizeof(unsigned int))); + for (size_t i = 0; i < num_arguments; ++i) + if ((function1_ranks[i] = reshadefx::type::rank(arguments[i].type, function1->parameter_list[i].type)) == 0) + { + function1_viable = false; + break; + } + + // Catch case where the second function does not exist + if (function2 == nullptr) + return function1_viable ? -1 : 1; // If the first function is not viable, this compare fails + + // Check if the second function matches the argument types + bool function2_viable = true; + const auto function2_ranks = static_cast(alloca(num_arguments * sizeof(unsigned int))); + for (size_t i = 0; i < num_arguments; ++i) + if ((function2_ranks[i] = reshadefx::type::rank(arguments[i].type, function2->parameter_list[i].type)) == 0) + { + function2_viable = false; + break; + } + + // If one of the functions is not viable, then the other one automatically wins + if (!function1_viable || !function2_viable) + return function2_viable - function1_viable; + + // Both functions are possible, so find the one with the higher ranking + std::sort(function1_ranks, function1_ranks + num_arguments, std::greater()); + std::sort(function2_ranks, function2_ranks + num_arguments, std::greater()); + + for (size_t i = 0; i < num_arguments; ++i) + if (function1_ranks[i] > function2_ranks[i]) + return -1; // Left function wins + else if (function2_ranks[i] > function1_ranks[i]) + return +1; // Right function wins + + return 0; // Both functions are equally viable +} + +bool reshadefx::symbol_table::resolve_function_call(const std::string &name, const std::vector &arguments, const scope &scope, symbol &out_data, bool &is_ambiguous) const +{ + out_data.op = symbol_type::function; + + const function_info *result = nullptr; + unsigned int num_overloads = 0; + unsigned int overload_namespace = scope.namespace_level; + + // Look up function name in the symbol stack and loop through the associated symbols + const auto stack_it = _symbol_stack.find(name); + + if (stack_it != _symbol_stack.end() && !stack_it->second.empty()) + { + for (auto it = stack_it->second.rbegin(), end = stack_it->second.rend(); it != end; ++it) + { + if (it->scope.level > scope.level || + it->scope.namespace_level > scope.namespace_level || + it->op != symbol_type::function) + continue; + + const function_info *const function = it->function; + + if (function == nullptr) + continue; + + if (function->parameter_list.empty()) + { + if (arguments.empty()) + { + out_data.id = it->id; + out_data.type = function->return_type; + out_data.function = result = function; + num_overloads = 1; + break; + } + else + { + continue; + } + } + else if (arguments.size() != function->parameter_list.size()) + { + continue; + } + + // A new possibly-matching function was found, compare it against the current result + const int comparison = compare_functions(arguments, function, result); + + if (comparison < 0) // The new function is a better match + { + out_data.id = it->id; + out_data.type = function->return_type; + out_data.function = result = function; + num_overloads = 1; + overload_namespace = it->scope.namespace_level; + } + else if (comparison == 0 && overload_namespace == it->scope.namespace_level) // Both functions are equally viable, so the call is ambiguous + { + ++num_overloads; + } + } + } + + // Try matching against intrinsic functions if no matching user-defined function was found up to this point + if (num_overloads == 0) + { + for (const intrinsic &intrinsic : s_intrinsics) + { + if (intrinsic.function.name != name || intrinsic.function.parameter_list.size() != arguments.size()) + continue; + + // A new possibly-matching intrinsic function was found, compare it against the current result + const int comparison = compare_functions(arguments, &intrinsic.function, result); + + if (comparison < 0) // The new function is a better match + { + out_data.op = symbol_type::intrinsic; + out_data.id = intrinsic.id; + out_data.type = intrinsic.function.return_type; + out_data.function = &intrinsic.function; + result = out_data.function; + num_overloads = 1; + } + else if (comparison == 0 && overload_namespace == 0) // Both functions are equally viable, so the call is ambiguous (intrinsics are always in the global namespace) + { + ++num_overloads; + } + } + } + + is_ambiguous = num_overloads > 1; + + return num_overloads == 1; +} diff --git a/msvc/source/effect_symbol_table.hpp b/msvc/source/effect_symbol_table.hpp new file mode 100644 index 0000000..9952d77 --- /dev/null +++ b/msvc/source/effect_symbol_table.hpp @@ -0,0 +1,102 @@ +/** + * Copyright (C) 2014 Patrick Mours. All rights reserved. + * License: https://github.com/crosire/reshade#license + */ + +#pragma once + +#include "effect_expression.hpp" + +namespace reshadefx +{ + /// + /// A scope encapsulating symbols + /// + struct scope + { + std::string name; + unsigned int level, namespace_level; + }; + + /// + /// Enumeration of all possible symbol types + /// + enum class symbol_type + { + invalid, + variable, + constant, + function, + intrinsic, + structure, + }; + + /// + /// A single symbol in the symbol table + /// + struct symbol + { + symbol_type op = symbol_type::invalid; + uint32_t id = 0; + type type = {}; + constant constant = {}; + const function_info *function = nullptr; + }; + + /// + /// A symbol table managing a list of scopes and symbols + /// + class symbol_table + { + public: + symbol_table(); + + /// + /// Enter a new scope as child of the current one. + /// + void enter_scope(); + /// + /// Enter a new namespace as child of the current one. + /// + void enter_namespace(const std::string &name); + /// + /// Leave the current scope and enter the parent one. + /// + void leave_scope(); + /// + /// Leave the current namespace and enter the parent one. + /// + void leave_namespace(); + + /// + /// Get the current scope the symbol table operates in. + /// + /// + const scope ¤t_scope() const { return _current_scope; } + + /// + /// Insert an new symbol in the symbol table. Returns false if a symbol by that name and type already exists. + /// + bool insert_symbol(const std::string &name, const symbol &symbol, bool global = false); + + /// + /// Look for an existing symbol with the specified . + /// + symbol find_symbol(const std::string &name) const; + symbol find_symbol(const std::string &name, const scope &scope, bool exclusive) const; + + /// + /// Search for the best function or intrinsic overload matching the argument list. + /// + bool resolve_function_call(const std::string &name, const std::vector &args, const scope &scope, symbol &data, bool &ambiguous) const; + + private: + struct scoped_symbol : symbol { + scope scope; // Store scope with symbol data + }; + + scope _current_scope; + std::unordered_map> _symbol_stack; + }; +} diff --git a/msvc/source/effect_symbol_table_intrinsics.inl b/msvc/source/effect_symbol_table_intrinsics.inl new file mode 100644 index 0000000..143f069 --- /dev/null +++ b/msvc/source/effect_symbol_table_intrinsics.inl @@ -0,0 +1,1705 @@ +/** + * Copyright (C) 2014 Patrick Mours. All rights reserved. + * License: https://github.com/crosire/reshade#license + */ + +#ifdef __INTELLISENSE__ +#undef DEFINE_INTRINSIC +#undef IMPLEMENT_INTRINSIC_GLSL +#undef IMPLEMENT_INTRINSIC_HLSL +#undef IMPLEMENT_INTRINSIC_SPIRV +#endif + +#ifndef DEFINE_INTRINSIC +#define DEFINE_INTRINSIC(name, i, ret_type, ...) +#endif +#ifndef IMPLEMENT_INTRINSIC_GLSL +#define IMPLEMENT_INTRINSIC_GLSL(name, i, code) +#endif +#ifndef IMPLEMENT_INTRINSIC_HLSL +#define IMPLEMENT_INTRINSIC_HLSL(name, i, code) +#endif +#ifndef IMPLEMENT_INTRINSIC_SPIRV +#define IMPLEMENT_INTRINSIC_SPIRV(name, i, code) +#endif + +// ret abs(x) +DEFINE_INTRINSIC(abs, 0, int, int) +DEFINE_INTRINSIC(abs, 0, int2, int2) +DEFINE_INTRINSIC(abs, 0, int3, int3) +DEFINE_INTRINSIC(abs, 0, int4, int4) +DEFINE_INTRINSIC(abs, 1, float, float) +DEFINE_INTRINSIC(abs, 1, float2, float2) +DEFINE_INTRINSIC(abs, 1, float3, float3) +DEFINE_INTRINSIC(abs, 1, float4, float4) +IMPLEMENT_INTRINSIC_GLSL(abs, 0, { + code += "abs(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_GLSL(abs, 1, { + code += "abs(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(abs, 0, { + code += "abs(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(abs, 1, { + code += "abs(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_SPIRV(abs, 0, { + return add_instruction(spv::OpExtInst, convert_type(res_type)) + .add(_glsl_ext) + .add(spv::GLSLstd450SAbs) + .add(args[0].base) + .result; + }) +IMPLEMENT_INTRINSIC_SPIRV(abs, 1, { + return add_instruction(spv::OpExtInst, convert_type(res_type)) + .add(_glsl_ext) + .add(spv::GLSLstd450FAbs) + .add(args[0].base) + .result; + }) + +// ret all(x) +DEFINE_INTRINSIC(all, 0, bool, bool) +DEFINE_INTRINSIC(all, 1, bool, bool2) +DEFINE_INTRINSIC(all, 1, bool, bool3) +DEFINE_INTRINSIC(all, 1, bool, bool4) +IMPLEMENT_INTRINSIC_GLSL(all, 0, { + code += id_to_name(args[0].base); + }) +IMPLEMENT_INTRINSIC_GLSL(all, 1, { + code += "all(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(all, 0, { + code += id_to_name(args[0].base); + }) +IMPLEMENT_INTRINSIC_HLSL(all, 1, { + code += "all(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_SPIRV(all, 0, { + return args[0].base; + }) +IMPLEMENT_INTRINSIC_SPIRV(all, 1, { + return add_instruction(spv::OpAll, convert_type(res_type)) + .add(args[0].base) + .result; + }) + +// ret any(x) +DEFINE_INTRINSIC(any, 0, bool, bool) +DEFINE_INTRINSIC(any, 1, bool, bool2) +DEFINE_INTRINSIC(any, 1, bool, bool3) +DEFINE_INTRINSIC(any, 1, bool, bool4) +IMPLEMENT_INTRINSIC_GLSL(any, 0, { + code += id_to_name(args[0].base); + }) +IMPLEMENT_INTRINSIC_GLSL(any, 1, { + code += "any(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(any, 0, { + code += id_to_name(args[0].base); + }) +IMPLEMENT_INTRINSIC_HLSL(any, 1, { + code += "any(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_SPIRV(any, 0, { + return args[0].base; + }) +IMPLEMENT_INTRINSIC_SPIRV(any, 1, { + return add_instruction(spv::OpAny, convert_type(res_type)) + .add(args[0].base) + .result; + }) + +// ret asin(x) +DEFINE_INTRINSIC(asin, 0, float, float) +DEFINE_INTRINSIC(asin, 0, float2, float2) +DEFINE_INTRINSIC(asin, 0, float3, float3) +DEFINE_INTRINSIC(asin, 0, float4, float4) +IMPLEMENT_INTRINSIC_GLSL(asin, 0, { + code += "asin(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(asin, 0, { + code += "asin(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_SPIRV(asin, 0, { + return add_instruction(spv::OpExtInst, convert_type(res_type)) + .add(_glsl_ext) + .add(spv::GLSLstd450Asin) + .add(args[0].base) + .result; + }) + +// ret acos(x) +DEFINE_INTRINSIC(acos, 0, float, float) +DEFINE_INTRINSIC(acos, 0, float2, float2) +DEFINE_INTRINSIC(acos, 0, float3, float3) +DEFINE_INTRINSIC(acos, 0, float4, float4) +IMPLEMENT_INTRINSIC_GLSL(acos, 0, { + code += "acos(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(acos, 0, { + code += "acos(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_SPIRV(acos, 0, { + return add_instruction(spv::OpExtInst, convert_type(res_type)) + .add(_glsl_ext) + .add(spv::GLSLstd450Acos) + .add(args[0].base) + .result; + }) + +// ret atan(x) +DEFINE_INTRINSIC(atan, 0, float, float) +DEFINE_INTRINSIC(atan, 0, float2, float2) +DEFINE_INTRINSIC(atan, 0, float3, float3) +DEFINE_INTRINSIC(atan, 0, float4, float4) +IMPLEMENT_INTRINSIC_GLSL(atan, 0, { + code += "atan(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(atan, 0, { + code += "atan(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_SPIRV(atan, 0, { + return add_instruction(spv::OpExtInst, convert_type(res_type)) + .add(_glsl_ext) + .add(spv::GLSLstd450Atan) + .add(args[0].base) + .result; + }) + +// ret atan2(x, y) +DEFINE_INTRINSIC(atan2, 0, float, float, float) +DEFINE_INTRINSIC(atan2, 0, float2, float2, float2) +DEFINE_INTRINSIC(atan2, 0, float3, float3, float3) +DEFINE_INTRINSIC(atan2, 0, float4, float4, float4) +IMPLEMENT_INTRINSIC_GLSL(atan2, 0, { + code += "atan(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(atan2, 0, { + code += "atan2(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ')'; + }) +IMPLEMENT_INTRINSIC_SPIRV(atan2, 0, { + return add_instruction(spv::OpExtInst, convert_type(res_type)) + .add(_glsl_ext) + .add(spv::GLSLstd450Atan2) + .add(args[0].base) + .add(args[1].base) + .result; + }) + +// ret sin(x) +DEFINE_INTRINSIC(sin, 0, float, float) +DEFINE_INTRINSIC(sin, 0, float2, float2) +DEFINE_INTRINSIC(sin, 0, float3, float3) +DEFINE_INTRINSIC(sin, 0, float4, float4) +IMPLEMENT_INTRINSIC_GLSL(sin, 0, { + code += "sin(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(sin, 0, { + code += "sin(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_SPIRV(sin, 0, { + return add_instruction(spv::OpExtInst, convert_type(res_type)) + .add(_glsl_ext) + .add(spv::GLSLstd450Sin) + .add(args[0].base) + .result; + }) + +// ret sinh(x) +DEFINE_INTRINSIC(sinh, 0, float, float) +DEFINE_INTRINSIC(sinh, 0, float2, float2) +DEFINE_INTRINSIC(sinh, 0, float3, float3) +DEFINE_INTRINSIC(sinh, 0, float4, float4) +IMPLEMENT_INTRINSIC_GLSL(sinh, 0, { + code += "sinh(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(sinh, 0, { + code += "sinh(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_SPIRV(sinh, 0, { + return add_instruction(spv::OpExtInst, convert_type(res_type)) + .add(_glsl_ext) + .add(spv::GLSLstd450Sinh) + .add(args[0].base) + .result; + }) + +// ret cos(x) +DEFINE_INTRINSIC(cos, 0, float, float) +DEFINE_INTRINSIC(cos, 0, float2, float2) +DEFINE_INTRINSIC(cos, 0, float3, float3) +DEFINE_INTRINSIC(cos, 0, float4, float4) +IMPLEMENT_INTRINSIC_GLSL(cos, 0, { + code += "cos(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(cos, 0, { + code += "cos(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_SPIRV(cos, 0, { + return add_instruction(spv::OpExtInst, convert_type(res_type)) + .add(_glsl_ext) + .add(spv::GLSLstd450Cos) + .add(args[0].base) + .result; + }) + +// ret cosh(x) +DEFINE_INTRINSIC(cosh, 0, float, float) +DEFINE_INTRINSIC(cosh, 0, float2, float2) +DEFINE_INTRINSIC(cosh, 0, float3, float3) +DEFINE_INTRINSIC(cosh, 0, float4, float4) +IMPLEMENT_INTRINSIC_GLSL(cosh, 0, { + code += "cosh(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(cosh, 0, { + code += "cosh(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_SPIRV(cosh, 0, { + return add_instruction(spv::OpExtInst, convert_type(res_type)) + .add(_glsl_ext) + .add(spv::GLSLstd450Cosh) + .add(args[0].base) + .result; + }) + +// ret tan(x) +DEFINE_INTRINSIC(tan, 0, float, float) +DEFINE_INTRINSIC(tan, 0, float2, float2) +DEFINE_INTRINSIC(tan, 0, float3, float3) +DEFINE_INTRINSIC(tan, 0, float4, float4) +IMPLEMENT_INTRINSIC_GLSL(tan, 0, { + code += "tan(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(tan, 0, { + code += "tan(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_SPIRV(tan, 0, { + return add_instruction(spv::OpExtInst, convert_type(res_type)) + .add(_glsl_ext) + .add(spv::GLSLstd450Tan) + .add(args[0].base) + .result; + }) + +// ret tanh(x) +DEFINE_INTRINSIC(tanh, 0, float, float) +DEFINE_INTRINSIC(tanh, 0, float2, float2) +DEFINE_INTRINSIC(tanh, 0, float3, float3) +DEFINE_INTRINSIC(tanh, 0, float4, float4) +IMPLEMENT_INTRINSIC_GLSL(tanh, 0, { + code += "tanh(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(tanh, 0, { + code += "tanh(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_SPIRV(tanh, 0, { + return add_instruction(spv::OpExtInst, convert_type(res_type)) + .add(_glsl_ext) + .add(spv::GLSLstd450Tanh) + .add(args[0].base) + .result; + }) + +// sincos(x, out s, out c) +DEFINE_INTRINSIC(sincos, 0, void, float, out_float, out_float) +DEFINE_INTRINSIC(sincos, 0, void, float2, out_float2, out_float2) +DEFINE_INTRINSIC(sincos, 0, void, float3, out_float3, out_float3) +DEFINE_INTRINSIC(sincos, 0, void, float4, out_float4, out_float4) +IMPLEMENT_INTRINSIC_GLSL(sincos, 0, { + code += id_to_name(args[1].base) + " = sin(" + id_to_name(args[0].base) + "), " + id_to_name(args[2].base) + " = cos(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(sincos, 0, { + code += id_to_name(args[1].base) + " = sin(" + id_to_name(args[0].base) + "), " + id_to_name(args[2].base) + " = cos(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_SPIRV(sincos, 0, { + const spv::Id sin_result = add_instruction(spv::OpExtInst, convert_type(args[0].type)) + .add(_glsl_ext) + .add(spv::GLSLstd450Sin) + .add(args[0].base) + .result; + const spv::Id cos_result = add_instruction(spv::OpExtInst, convert_type(args[0].type)) + .add(_glsl_ext) + .add(spv::GLSLstd450Cos) + .add(args[0].base) + .result; + + add_instruction_without_result(spv::OpStore) + .add(args[1].base) + .add(sin_result); + add_instruction_without_result(spv::OpStore) + .add(args[2].base) + .add(cos_result); + + return 0; + }) + +// ret asint(x) +DEFINE_INTRINSIC(asint, 0, int, float) +DEFINE_INTRINSIC(asint, 0, int2, float2) +DEFINE_INTRINSIC(asint, 0, int3, float3) +DEFINE_INTRINSIC(asint, 0, int4, float4) +IMPLEMENT_INTRINSIC_GLSL(asint, 0, { + code += "floatBitsToInt(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(asint, 0, { + code += "asint(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_SPIRV(asint, 0, { + return add_instruction(spv::OpBitcast, convert_type(res_type)) + .add(args[0].base) + .result; + }) + +// ret asuint(x) +DEFINE_INTRINSIC(asuint, 0, uint, float) +DEFINE_INTRINSIC(asuint, 0, uint2, float2) +DEFINE_INTRINSIC(asuint, 0, uint3, float3) +DEFINE_INTRINSIC(asuint, 0, uint4, float4) +IMPLEMENT_INTRINSIC_GLSL(asuint, 0, { + code += "floatBitsToUint(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(asuint, 0, { + code += "asuint(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_SPIRV(asuint, 0, { + return add_instruction(spv::OpBitcast, convert_type(res_type)) + .add(args[0].base) + .result; + }) + +// ret asfloat(x) +DEFINE_INTRINSIC(asfloat, 0, float, int) +DEFINE_INTRINSIC(asfloat, 0, float2, int2) +DEFINE_INTRINSIC(asfloat, 0, float3, int3) +DEFINE_INTRINSIC(asfloat, 0, float4, int4) +DEFINE_INTRINSIC(asfloat, 1, float, uint) +DEFINE_INTRINSIC(asfloat, 1, float2, uint2) +DEFINE_INTRINSIC(asfloat, 1, float3, uint3) +DEFINE_INTRINSIC(asfloat, 1, float4, uint4) +IMPLEMENT_INTRINSIC_GLSL(asfloat, 0, { + code += "intBitsToFloat(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_GLSL(asfloat, 1, { + code += "uintBitsToFloat(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(asfloat, 0, { + code += "asfloat(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(asfloat, 1, { + code += "asfloat(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_SPIRV(asfloat, 0, { + return add_instruction(spv::OpBitcast, convert_type(res_type)) + .add(args[0].base) + .result; + }) +IMPLEMENT_INTRINSIC_SPIRV(asfloat, 1, { + return add_instruction(spv::OpBitcast, convert_type(res_type)) + .add(args[0].base) + .result; + }) + +// ret ceil(x) +DEFINE_INTRINSIC(ceil, 0, float, float) +DEFINE_INTRINSIC(ceil, 0, float2, float2) +DEFINE_INTRINSIC(ceil, 0, float3, float3) +DEFINE_INTRINSIC(ceil, 0, float4, float4) +IMPLEMENT_INTRINSIC_GLSL(ceil, 0, { + code += "ceil(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(ceil, 0, { + code += "ceil(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_SPIRV(ceil, 0, { + return add_instruction(spv::OpExtInst, convert_type(res_type)) + .add(_glsl_ext) + .add(spv::GLSLstd450Ceil) + .add(args[0].base) + .result; + }) + +// ret floor(x) +DEFINE_INTRINSIC(floor, 0, float, float) +DEFINE_INTRINSIC(floor, 0, float2, float2) +DEFINE_INTRINSIC(floor, 0, float3, float3) +DEFINE_INTRINSIC(floor, 0, float4, float4) +IMPLEMENT_INTRINSIC_GLSL(floor, 0, { + code += "floor(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(floor, 0, { + code += "floor(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_SPIRV(floor, 0, { + return add_instruction(spv::OpExtInst, convert_type(res_type)) + .add(_glsl_ext) + .add(spv::GLSLstd450Floor) + .add(args[0].base) + .result; + }) + +// ret clamp(x, min, max) +DEFINE_INTRINSIC(clamp, 0, int, int, int, int) +DEFINE_INTRINSIC(clamp, 0, int2, int2, int2, int2) +DEFINE_INTRINSIC(clamp, 0, int3, int3, int3, int3) +DEFINE_INTRINSIC(clamp, 0, int4, int4, int4, int4) +DEFINE_INTRINSIC(clamp, 1, uint, uint, uint, uint) +DEFINE_INTRINSIC(clamp, 1, uint2, uint2, uint2, uint2) +DEFINE_INTRINSIC(clamp, 1, uint3, uint3, uint3, uint3) +DEFINE_INTRINSIC(clamp, 1, uint4, uint4, uint4, uint4) +DEFINE_INTRINSIC(clamp, 2, float, float, float, float) +DEFINE_INTRINSIC(clamp, 2, float2, float2, float2, float2) +DEFINE_INTRINSIC(clamp, 2, float3, float3, float3, float3) +DEFINE_INTRINSIC(clamp, 2, float4, float4, float4, float4) +IMPLEMENT_INTRINSIC_GLSL(clamp, 0, { + code += "clamp(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ", " + id_to_name(args[2].base) + ')'; + }) +IMPLEMENT_INTRINSIC_GLSL(clamp, 1, { + code += "clamp(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ", " + id_to_name(args[2].base) + ')'; + }) +IMPLEMENT_INTRINSIC_GLSL(clamp, 2, { + code += "clamp(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ", " + id_to_name(args[2].base) + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(clamp, 0, { + code += "clamp(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ", " + id_to_name(args[2].base) + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(clamp, 1, { + code += "clamp(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ", " + id_to_name(args[2].base) + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(clamp, 2, { + code += "clamp(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ", " + id_to_name(args[2].base) + ')'; + }) +IMPLEMENT_INTRINSIC_SPIRV(clamp, 0, { + return add_instruction(spv::OpExtInst, convert_type(res_type)) + .add(_glsl_ext) + .add(spv::GLSLstd450SClamp) + .add(args[0].base) + .add(args[1].base) + .add(args[2].base) + .result; + }) +IMPLEMENT_INTRINSIC_SPIRV(clamp, 1, { + return add_instruction(spv::OpExtInst, convert_type(res_type)) + .add(_glsl_ext) + .add(spv::GLSLstd450UClamp) + .add(args[0].base) + .add(args[1].base) + .add(args[2].base) + .result; + }) +IMPLEMENT_INTRINSIC_SPIRV(clamp, 2, { + return add_instruction(spv::OpExtInst, convert_type(res_type)) + .add(_glsl_ext) + .add(spv::GLSLstd450FClamp) + .add(args[0].base) + .add(args[1].base) + .add(args[2].base) + .result; + }) + +// ret saturate(x) +DEFINE_INTRINSIC(saturate, 0, float, float) +DEFINE_INTRINSIC(saturate, 0, float2, float2) +DEFINE_INTRINSIC(saturate, 0, float3, float3) +DEFINE_INTRINSIC(saturate, 0, float4, float4) +IMPLEMENT_INTRINSIC_GLSL(saturate, 0, { + code += "clamp(" + id_to_name(args[0].base) + ", 0.0, 1.0)"; + }) +IMPLEMENT_INTRINSIC_HLSL(saturate, 0, { + code += "saturate(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_SPIRV(saturate, 0, { + const spv::Id constant_one = emit_constant(args[0].type, { 1.0f, 1.0f, 1.0f, 1.0f }); + const spv::Id constant_zero = emit_constant(args[0].type, { 0.0f, 0.0f, 0.0f, 0.0f }); + + return add_instruction(spv::OpExtInst, convert_type(res_type)) + .add(_glsl_ext) + .add(spv::GLSLstd450FClamp) + .add(args[0].base) + .add(constant_zero) + .add(constant_one) + .result; + }) + +// ret mad(mvalue, avalue, bvalue) +DEFINE_INTRINSIC(mad, 0, float, float, float, float) +DEFINE_INTRINSIC(mad, 0, float2, float2, float2, float2) +DEFINE_INTRINSIC(mad, 0, float3, float3, float3, float3) +DEFINE_INTRINSIC(mad, 0, float4, float4, float4, float4) +IMPLEMENT_INTRINSIC_GLSL(mad, 0, { + code += "fma(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ", " + id_to_name(args[2].base) + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(mad, 0, { + if (_shader_model >= 50u) + code += "mad(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ", " + id_to_name(args[2].base) + ')'; + else + code += id_to_name(args[0].base) + " * " + id_to_name(args[1].base) + " + " + id_to_name(args[2].base); + }) +IMPLEMENT_INTRINSIC_SPIRV(mad, 0, { + return add_instruction(spv::OpExtInst, convert_type(res_type)) + .add(_glsl_ext) + .add(spv::GLSLstd450Fma) + .add(args[0].base) + .add(args[1].base) + .add(args[2].base) + .result; + }) + +// ret rcp(x) +DEFINE_INTRINSIC(rcp, 0, float, float) +DEFINE_INTRINSIC(rcp, 0, float2, float2) +DEFINE_INTRINSIC(rcp, 0, float3, float3) +DEFINE_INTRINSIC(rcp, 0, float4, float4) +IMPLEMENT_INTRINSIC_GLSL(rcp, 0, { + code += "1.0 / " + id_to_name(args[0].base); + }) +IMPLEMENT_INTRINSIC_HLSL(rcp, 0, { + if (_shader_model >= 50u) + code += "rcp(" + id_to_name(args[0].base) + ')'; + else + code += "1.0 / " + id_to_name(args[0].base); + }) +IMPLEMENT_INTRINSIC_SPIRV(rcp, 0, { + const spv::Id constant_one = emit_constant(args[0].type, { 1.0f, 1.0f, 1.0f, 1.0f }); + + return add_instruction(spv::OpFDiv, convert_type(res_type)) + .add(constant_one) + .add(args[0].base) + .result; + }) + +// ret pow(x, y) +DEFINE_INTRINSIC(pow, 0, float, float, float) +DEFINE_INTRINSIC(pow, 0, float2, float2, float2) +DEFINE_INTRINSIC(pow, 0, float3, float3, float3) +DEFINE_INTRINSIC(pow, 0, float4, float4, float4) +IMPLEMENT_INTRINSIC_GLSL(pow, 0, { + code += "pow(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(pow, 0, { + code += "pow(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ')'; + }) +IMPLEMENT_INTRINSIC_SPIRV(pow, 0, { + return add_instruction(spv::OpExtInst, convert_type(res_type)) + .add(_glsl_ext) + .add(spv::GLSLstd450Pow) + .add(args[0].base) + .add(args[1].base) + .result; + }) + +// ret exp(x) +DEFINE_INTRINSIC(exp, 0, float, float) +DEFINE_INTRINSIC(exp, 0, float2, float2) +DEFINE_INTRINSIC(exp, 0, float3, float3) +DEFINE_INTRINSIC(exp, 0, float4, float4) +IMPLEMENT_INTRINSIC_GLSL(exp, 0, { + code += "exp(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(exp, 0, { + code += "exp(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_SPIRV(exp, 0, { + return add_instruction(spv::OpExtInst, convert_type(res_type)) + .add(_glsl_ext) + .add(spv::GLSLstd450Exp) + .add(args[0].base) + .result; + }) + +// ret exp2(x) +DEFINE_INTRINSIC(exp2, 0, float, float) +DEFINE_INTRINSIC(exp2, 0, float2, float2) +DEFINE_INTRINSIC(exp2, 0, float3, float3) +DEFINE_INTRINSIC(exp2, 0, float4, float4) +IMPLEMENT_INTRINSIC_GLSL(exp2, 0, { + code += "exp2(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(exp2, 0, { + code += "exp2(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_SPIRV(exp2, 0, { + return add_instruction(spv::OpExtInst, convert_type(res_type)) + .add(_glsl_ext) + .add(spv::GLSLstd450Exp2) + .add(args[0].base) + .result; + }) + +// ret log(x) +DEFINE_INTRINSIC(log, 0, float, float) +DEFINE_INTRINSIC(log, 0, float2, float2) +DEFINE_INTRINSIC(log, 0, float3, float3) +DEFINE_INTRINSIC(log, 0, float4, float4) +IMPLEMENT_INTRINSIC_GLSL(log, 0, { + code += "log(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(log, 0, { + code += "log(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_SPIRV(log, 0, { + return add_instruction(spv::OpExtInst, convert_type(res_type)) + .add(_glsl_ext) + .add(spv::GLSLstd450Log) + .add(args[0].base) + .result; + }) + +// ret log2(x) +DEFINE_INTRINSIC(log2, 0, float, float) +DEFINE_INTRINSIC(log2, 0, float2, float2) +DEFINE_INTRINSIC(log2, 0, float3, float3) +DEFINE_INTRINSIC(log2, 0, float4, float4) +IMPLEMENT_INTRINSIC_GLSL(log2, 0, { + code += "log2(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(log2, 0, { + code += "log2(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_SPIRV(log2, 0, { + return add_instruction(spv::OpExtInst, convert_type(res_type)) + .add(_glsl_ext) + .add(spv::GLSLstd450Log2) + .add(args[0].base) + .result; + }) + +// ret log10(x) +DEFINE_INTRINSIC(log10, 0, float, float) +DEFINE_INTRINSIC(log10, 0, float2, float2) +DEFINE_INTRINSIC(log10, 0, float3, float3) +DEFINE_INTRINSIC(log10, 0, float4, float4) +IMPLEMENT_INTRINSIC_GLSL(log10, 0, { + code += "(log2(" + id_to_name(args[0].base) + ") / 2.302585093)"; + }) +IMPLEMENT_INTRINSIC_HLSL(log10, 0, { + code += "(log2(" + id_to_name(args[0].base) + ") / 2.302585093)"; + }) +IMPLEMENT_INTRINSIC_SPIRV(log10, 0, { + const spv::Id log2 = add_instruction(spv::OpExtInst, convert_type(res_type)) + .add(_glsl_ext) + .add(spv::GLSLstd450Log2) + .add(args[0].base) + .result; + + const spv::Id log10 = emit_constant(args[0].type, + { 2.302585093f, 2.302585093f, 2.302585093f, 2.302585093f }); + + return add_instruction(spv::OpFDiv, convert_type(res_type)) + .add(log2) + .add(log10) + .result; }) + +// ret sign(x) +DEFINE_INTRINSIC(sign, 0, int, int) +DEFINE_INTRINSIC(sign, 0, int2, int2) +DEFINE_INTRINSIC(sign, 0, int3, int3) +DEFINE_INTRINSIC(sign, 0, int4, int4) +DEFINE_INTRINSIC(sign, 1, float, float) +DEFINE_INTRINSIC(sign, 1, float2, float2) +DEFINE_INTRINSIC(sign, 1, float3, float3) +DEFINE_INTRINSIC(sign, 1, float4, float4) +IMPLEMENT_INTRINSIC_GLSL(sign, 0, { + code += "sign(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_GLSL(sign, 1, { + code += "sign(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(sign, 0, { + code += "sign(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(sign, 1, { + code += "sign(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_SPIRV(sign, 0, { + return add_instruction(spv::OpExtInst, convert_type(res_type)) + .add(_glsl_ext) + .add(spv::GLSLstd450SSign) + .add(args[0].base) + .result; + }) +IMPLEMENT_INTRINSIC_SPIRV(sign, 1, { + return add_instruction(spv::OpExtInst, convert_type(res_type)) + .add(_glsl_ext) + .add(spv::GLSLstd450FSign) + .add(args[0].base) + .result; + }) + +// ret sqrt(x) +DEFINE_INTRINSIC(sqrt, 0, float, float) +DEFINE_INTRINSIC(sqrt, 0, float2, float2) +DEFINE_INTRINSIC(sqrt, 0, float3, float3) +DEFINE_INTRINSIC(sqrt, 0, float4, float4) +IMPLEMENT_INTRINSIC_GLSL(sqrt, 0, { + code += "sqrt(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(sqrt, 0, { + code += "sqrt(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_SPIRV(sqrt, 0, { + return add_instruction(spv::OpExtInst, convert_type(res_type)) + .add(_glsl_ext) + .add(spv::GLSLstd450Sqrt) + .add(args[0].base) + .result; + }) + +// ret rsqrt(x) +DEFINE_INTRINSIC(rsqrt, 0, float, float) +DEFINE_INTRINSIC(rsqrt, 0, float2, float2) +DEFINE_INTRINSIC(rsqrt, 0, float3, float3) +DEFINE_INTRINSIC(rsqrt, 0, float4, float4) +IMPLEMENT_INTRINSIC_GLSL(rsqrt, 0, { + code += "inversesqrt(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(rsqrt, 0, { + code += "rsqrt(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_SPIRV(rsqrt, 0, { + return add_instruction(spv::OpExtInst, convert_type(res_type)) + .add(_glsl_ext) + .add(spv::GLSLstd450InverseSqrt) + .add(args[0].base) + .result; + }) + +// ret lerp(x, y, s) +DEFINE_INTRINSIC(lerp, 0, float, float, float, float) +DEFINE_INTRINSIC(lerp, 0, float2, float2, float2, float2) +DEFINE_INTRINSIC(lerp, 0, float3, float3, float3, float3) +DEFINE_INTRINSIC(lerp, 0, float4, float4, float4, float4) +IMPLEMENT_INTRINSIC_GLSL(lerp, 0, { + code += "mix(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ", " + id_to_name(args[2].base) + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(lerp, 0, { + code += "lerp(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ", " + id_to_name(args[2].base) + ')'; + }) +IMPLEMENT_INTRINSIC_SPIRV(lerp, 0, { + return add_instruction(spv::OpExtInst, convert_type(res_type)) + .add(_glsl_ext) + .add(spv::GLSLstd450FMix) + .add(args[0].base) + .add(args[1].base) + .add(args[2].base) + .result; + }) + +// ret step(y, x) +DEFINE_INTRINSIC(step, 0, float, float, float) +DEFINE_INTRINSIC(step, 0, float2, float2, float2) +DEFINE_INTRINSIC(step, 0, float3, float3, float3) +DEFINE_INTRINSIC(step, 0, float4, float4, float4) +IMPLEMENT_INTRINSIC_GLSL(step, 0, { + code += "step(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(step, 0, { + code += "step(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ')'; + }) +IMPLEMENT_INTRINSIC_SPIRV(step, 0, { + return add_instruction(spv::OpExtInst, convert_type(res_type)) + .add(_glsl_ext) + .add(spv::GLSLstd450Step) + .add(args[0].base) + .add(args[1].base) + .result; + }) + +// ret smoothstep(min, max, x) +DEFINE_INTRINSIC(smoothstep, 0, float, float, float, float) +DEFINE_INTRINSIC(smoothstep, 0, float2, float2, float2, float2) +DEFINE_INTRINSIC(smoothstep, 0, float3, float3, float3, float3) +DEFINE_INTRINSIC(smoothstep, 0, float4, float4, float4, float4) +IMPLEMENT_INTRINSIC_GLSL(smoothstep, 0, { + code += "smoothstep(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ", " + id_to_name(args[2].base) + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(smoothstep, 0, { + code += "smoothstep(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ", " + id_to_name(args[2].base) + ')'; + }) +IMPLEMENT_INTRINSIC_SPIRV(smoothstep, 0, { + return add_instruction(spv::OpExtInst, convert_type(args[2].type)) + .add(_glsl_ext) + .add(spv::GLSLstd450SmoothStep) + .add(args[0].base) + .add(args[1].base) + .add(args[2].base) + .result; + }) + +// ret frac(x) +DEFINE_INTRINSIC(frac, 0, float, float) +DEFINE_INTRINSIC(frac, 0, float2, float2) +DEFINE_INTRINSIC(frac, 0, float3, float3) +DEFINE_INTRINSIC(frac, 0, float4, float4) +IMPLEMENT_INTRINSIC_GLSL(frac, 0, { + code += "fract(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(frac, 0, { + code += "frac(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_SPIRV(frac, 0, { + return add_instruction(spv::OpExtInst, convert_type(res_type)) + .add(_glsl_ext) + .add(spv::GLSLstd450Fract) + .add(args[0].base) + .result; + }) + +// ret ldexp(x, exp) +DEFINE_INTRINSIC(ldexp, 0, float, float, int) +DEFINE_INTRINSIC(ldexp, 0, float2, float2, int2) +DEFINE_INTRINSIC(ldexp, 0, float3, float3, int3) +DEFINE_INTRINSIC(ldexp, 0, float4, float4, int4) +IMPLEMENT_INTRINSIC_GLSL(ldexp, 0, { + code += "ldexp(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(ldexp, 0, { + code += "ldexp(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ')'; + }) +IMPLEMENT_INTRINSIC_SPIRV(ldexp, 0, { + return add_instruction(spv::OpExtInst, convert_type(res_type)) + .add(_glsl_ext) + .add(spv::GLSLstd450Ldexp) + .add(args[0].base) + .add(args[1].base) + .result; + }) + +// ret modf(x, out ip) +DEFINE_INTRINSIC(modf, 0, float, float, out_float) +DEFINE_INTRINSIC(modf, 0, float2, float2, out_float2) +DEFINE_INTRINSIC(modf, 0, float3, float3, out_float3) +DEFINE_INTRINSIC(modf, 0, float4, float4, out_float4) +IMPLEMENT_INTRINSIC_GLSL(modf, 0, { + code += "modf(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(modf, 0, { + code += "modf(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ')'; + }) +IMPLEMENT_INTRINSIC_SPIRV(modf, 0, { + return add_instruction(spv::OpExtInst, convert_type(res_type)) + .add(_glsl_ext) + .add(spv::GLSLstd450Modf) + .add(args[0].base) + .add(args[1].base) + .result; + }) + +// ret frexp(x, out exp) +DEFINE_INTRINSIC(frexp, 0, float, float, out_float) +DEFINE_INTRINSIC(frexp, 0, float2, float2, out_float2) +DEFINE_INTRINSIC(frexp, 0, float3, float3, out_float3) +DEFINE_INTRINSIC(frexp, 0, float4, float4, out_float4) +IMPLEMENT_INTRINSIC_GLSL(frexp, 0, { + code += "frexp(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(frexp, 0, { + code += "frexp(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ')'; + }) +IMPLEMENT_INTRINSIC_SPIRV(frexp, 0, { + return add_instruction(spv::OpExtInst, convert_type(res_type)) + .add(_glsl_ext) + .add(spv::GLSLstd450Frexp) + .add(args[0].base) + .add(args[1].base) + .result; + }) + +// ret trunc(x) +DEFINE_INTRINSIC(trunc, 0, float, float) +DEFINE_INTRINSIC(trunc, 0, float2, float2) +DEFINE_INTRINSIC(trunc, 0, float3, float3) +DEFINE_INTRINSIC(trunc, 0, float4, float4) +IMPLEMENT_INTRINSIC_GLSL(trunc, 0, { + code += "trunc(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(trunc, 0, { + code += "trunc(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_SPIRV(trunc, 0, { + return add_instruction(spv::OpExtInst, convert_type(res_type)) + .add(_glsl_ext) + .add(spv::GLSLstd450Trunc) + .add(args[0].base) + .result; + }) + +// ret round(x) +DEFINE_INTRINSIC(round, 0, float, float) +DEFINE_INTRINSIC(round, 0, float2, float2) +DEFINE_INTRINSIC(round, 0, float3, float3) +DEFINE_INTRINSIC(round, 0, float4, float4) +IMPLEMENT_INTRINSIC_GLSL(round, 0, { + code += "round(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(round, 0, { + code += "round(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_SPIRV(round, 0, { + return add_instruction(spv::OpExtInst, convert_type(res_type)) + .add(_glsl_ext) + .add(spv::GLSLstd450Round) + .add(args[0].base) + .result; + }) + +// ret min(x, y) +DEFINE_INTRINSIC(min, 0, int, int, int) +DEFINE_INTRINSIC(min, 0, int2, int2, int2) +DEFINE_INTRINSIC(min, 0, int3, int3, int3) +DEFINE_INTRINSIC(min, 0, int4, int4, int4) +DEFINE_INTRINSIC(min, 1, float, float, float) +DEFINE_INTRINSIC(min, 1, float2, float2, float2) +DEFINE_INTRINSIC(min, 1, float3, float3, float3) +DEFINE_INTRINSIC(min, 1, float4, float4, float4) +IMPLEMENT_INTRINSIC_GLSL(min, 0, { + code += "min(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ')'; + }) +IMPLEMENT_INTRINSIC_GLSL(min, 1, { + code += "min(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(min, 0, { + code += "min(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(min, 1, { + code += "min(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ')'; + }) +IMPLEMENT_INTRINSIC_SPIRV(min, 0, { + return add_instruction(spv::OpExtInst, convert_type(res_type)) + .add(_glsl_ext) + .add(spv::GLSLstd450SMin) + .add(args[0].base) + .add(args[1].base) + .result; + }) +IMPLEMENT_INTRINSIC_SPIRV(min, 1, { + return add_instruction(spv::OpExtInst, convert_type(res_type)) + .add(_glsl_ext) + .add(spv::GLSLstd450FMin) + .add(args[0].base) + .add(args[1].base) + .result; + }) + +// ret max(x, y) +DEFINE_INTRINSIC(max, 0, int, int, int) +DEFINE_INTRINSIC(max, 0, int2, int2, int2) +DEFINE_INTRINSIC(max, 0, int3, int3, int3) +DEFINE_INTRINSIC(max, 0, int4, int4, int4) +DEFINE_INTRINSIC(max, 1, float, float, float) +DEFINE_INTRINSIC(max, 1, float2, float2, float2) +DEFINE_INTRINSIC(max, 1, float3, float3, float3) +DEFINE_INTRINSIC(max, 1, float4, float4, float4) +IMPLEMENT_INTRINSIC_GLSL(max, 0, { + code += "max(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ')'; + }) +IMPLEMENT_INTRINSIC_GLSL(max, 1, { + code += "max(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(max, 0, { + code += "max(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(max, 1, { + code += "max(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ')'; + }) +IMPLEMENT_INTRINSIC_SPIRV(max, 0, { + return add_instruction(spv::OpExtInst, convert_type(res_type)) + .add(_glsl_ext) + .add(spv::GLSLstd450SMax) + .add(args[0].base) + .add(args[1].base) + .result; + }) +IMPLEMENT_INTRINSIC_SPIRV(max, 1, { + return add_instruction(spv::OpExtInst, convert_type(res_type)) + .add(_glsl_ext) + .add(spv::GLSLstd450FMax) + .add(args[0].base) + .add(args[1].base) + .result; + }) + +// ret degree(x) +DEFINE_INTRINSIC(degrees, 0, float, float) +DEFINE_INTRINSIC(degrees, 0, float2, float2) +DEFINE_INTRINSIC(degrees, 0, float3, float3) +DEFINE_INTRINSIC(degrees, 0, float4, float4) +IMPLEMENT_INTRINSIC_GLSL(degrees, 0, { + code += "degrees(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(degrees, 0, { + code += "degrees(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_SPIRV(degrees, 0, { + return add_instruction(spv::OpExtInst, convert_type(res_type)) + .add(_glsl_ext) + .add(spv::GLSLstd450Degrees) + .add(args[0].base) + .result; + }) + +// ret radians(x) +DEFINE_INTRINSIC(radians, 0, float, float) +DEFINE_INTRINSIC(radians, 0, float2, float2) +DEFINE_INTRINSIC(radians, 0, float3, float3) +DEFINE_INTRINSIC(radians, 0, float4, float4) +IMPLEMENT_INTRINSIC_GLSL(radians, 0, { + code += "radians(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(radians, 0, { + code += "radians(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_SPIRV(radians, 0, { + return add_instruction(spv::OpExtInst, convert_type(res_type)) + .add(_glsl_ext) + .add(spv::GLSLstd450Radians) + .add(args[0].base) + .result; + }) + +// ret ddx(x) +DEFINE_INTRINSIC(ddx, 0, float, float) +DEFINE_INTRINSIC(ddx, 0, float2, float2) +DEFINE_INTRINSIC(ddx, 0, float3, float3) +DEFINE_INTRINSIC(ddx, 0, float4, float4) +IMPLEMENT_INTRINSIC_GLSL(ddx, 0, { + code += "dFdx(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(ddx, 0, { + code += "ddx(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_SPIRV(ddx, 0, { + return add_instruction(spv::OpDPdx, convert_type(res_type)) + .add(args[0].base) + .result; + }) + +// ret ddy(x) +DEFINE_INTRINSIC(ddy, 0, float, float) +DEFINE_INTRINSIC(ddy, 0, float2, float2) +DEFINE_INTRINSIC(ddy, 0, float3, float3) +DEFINE_INTRINSIC(ddy, 0, float4, float4) +IMPLEMENT_INTRINSIC_GLSL(ddy, 0, { + code += "dFdy(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(ddy, 0, { + code += "ddy(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_SPIRV(ddy, 0, { + return add_instruction(spv::OpDPdy, convert_type(res_type)) + .add(args[0].base) + .result; + }) + +// ret fwidth(x) +DEFINE_INTRINSIC(fwidth, 0, float, float) +DEFINE_INTRINSIC(fwidth, 0, float2, float2) +DEFINE_INTRINSIC(fwidth, 0, float3, float3) +DEFINE_INTRINSIC(fwidth, 0, float4, float4) +IMPLEMENT_INTRINSIC_GLSL(fwidth, 0, { + code += "fwidth(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(fwidth, 0, { + code += "fwidth(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_SPIRV(fwidth, 0, { + return add_instruction(spv::OpFwidth, convert_type(res_type)) + .add(args[0].base) + .result; + }) + +// ret dot(x, y) +DEFINE_INTRINSIC(dot, 0, float, float2, float2) +DEFINE_INTRINSIC(dot, 0, float, float3, float3) +DEFINE_INTRINSIC(dot, 0, float, float4, float4) +IMPLEMENT_INTRINSIC_GLSL(dot, 0, { + code += "dot(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(dot, 0, { + code += "dot(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ')'; + }) +IMPLEMENT_INTRINSIC_SPIRV(dot, 0, { + return add_instruction(spv::OpDot, convert_type(res_type)) + .add(args[0].base) + .add(args[1].base) + .result; + }) + +// ret cross(x, y) +DEFINE_INTRINSIC(cross, 0, float3, float3, float3) +IMPLEMENT_INTRINSIC_GLSL(cross, 0, { + code += "cross(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(cross, 0, { + code += "cross(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ')'; + }) +IMPLEMENT_INTRINSIC_SPIRV(cross, 0, { + return add_instruction(spv::OpExtInst, convert_type(res_type)) + .add(_glsl_ext) + .add(spv::GLSLstd450Cross) + .add(args[0].base) + .add(args[1].base) + .result; + }) + +// ret length(x) +DEFINE_INTRINSIC(length, 0, float, float) +DEFINE_INTRINSIC(length, 0, float, float2) +DEFINE_INTRINSIC(length, 0, float, float3) +DEFINE_INTRINSIC(length, 0, float, float4) +IMPLEMENT_INTRINSIC_GLSL(length, 0, { + code += "length(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(length, 0, { + code += "length(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_SPIRV(length, 0, { + return add_instruction(spv::OpExtInst, convert_type(res_type)) + .add(_glsl_ext) + .add(spv::GLSLstd450Length) + .add(args[0].base) + .result; + }) + +// ret distance(x, y) +DEFINE_INTRINSIC(distance, 0, float, float, float) +DEFINE_INTRINSIC(distance, 0, float, float2, float2) +DEFINE_INTRINSIC(distance, 0, float, float3, float3) +DEFINE_INTRINSIC(distance, 0, float, float4, float4) +IMPLEMENT_INTRINSIC_GLSL(distance, 0, { + code += "distance(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(distance, 0, { + code += "distance(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ')'; + }) +IMPLEMENT_INTRINSIC_SPIRV(distance, 0, { + return add_instruction(spv::OpExtInst, convert_type(res_type)) + .add(_glsl_ext) + .add(spv::GLSLstd450Distance) + .add(args[0].base) + .add(args[1].base) + .result; + }) + +// ret normalize(x) +DEFINE_INTRINSIC(normalize, 0, float2, float2) +DEFINE_INTRINSIC(normalize, 0, float3, float3) +DEFINE_INTRINSIC(normalize, 0, float4, float4) +IMPLEMENT_INTRINSIC_GLSL(normalize, 0, { + code += "normalize(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(normalize, 0, { + code += "normalize(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_SPIRV(normalize, 0, { + return add_instruction(spv::OpExtInst, convert_type(res_type)) + .add(_glsl_ext) + .add(spv::GLSLstd450Normalize) + .add(args[0].base) + .result; + }) + +// ret transpose(x) +DEFINE_INTRINSIC(transpose, 0, float2x2, float2x2) +DEFINE_INTRINSIC(transpose, 0, float3x3, float3x3) +DEFINE_INTRINSIC(transpose, 0, float4x4, float4x4) +IMPLEMENT_INTRINSIC_GLSL(transpose, 0, { + code += "transpose(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(transpose, 0, { + code += "transpose(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_SPIRV(transpose, 0, { + return add_instruction(spv::OpTranspose, convert_type(res_type)) + .add(args[0].base) + .result; + }) + +// ret determinant(m) +DEFINE_INTRINSIC(determinant, 0, float, float2x2) +DEFINE_INTRINSIC(determinant, 0, float, float3x3) +DEFINE_INTRINSIC(determinant, 0, float, float4x4) +IMPLEMENT_INTRINSIC_GLSL(determinant, 0, { + code += "determinant(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(determinant, 0, { + code += "determinant(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_SPIRV(determinant, 0, { + return add_instruction(spv::OpExtInst, convert_type(res_type)) + .add(_glsl_ext) + .add(spv::GLSLstd450Determinant) + .add(args[0].base) + .result; + }) + +// ret reflect(i, n) +DEFINE_INTRINSIC(reflect, 0, float2, float2, float2) +DEFINE_INTRINSIC(reflect, 0, float3, float3, float3) +DEFINE_INTRINSIC(reflect, 0, float4, float4, float4) +IMPLEMENT_INTRINSIC_GLSL(reflect, 0, { + code += "reflect(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(reflect, 0, { + code += "reflect(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ')'; + }) +IMPLEMENT_INTRINSIC_SPIRV(reflect, 0, { + return add_instruction(spv::OpExtInst, convert_type(res_type)) + .add(_glsl_ext) + .add(spv::GLSLstd450Reflect) + .add(args[0].base) + .add(args[1].base) + .result; + }) + +// ret refract(i, n, eta) +DEFINE_INTRINSIC(refract, 0, float2, float2, float2, float) +DEFINE_INTRINSIC(refract, 0, float3, float3, float3, float) +DEFINE_INTRINSIC(refract, 0, float4, float4, float4, float) +IMPLEMENT_INTRINSIC_GLSL(refract, 0, { + code += "refract(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ", " + id_to_name(args[1].base) + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(refract, 0, { + code += "refract(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ", " + id_to_name(args[1].base) + ')'; + }) +IMPLEMENT_INTRINSIC_SPIRV(refract, 0, { + return add_instruction(spv::OpExtInst, convert_type(res_type)) + .add(_glsl_ext) + .add(spv::GLSLstd450Refract) + .add(args[0].base) + .add(args[1].base) + .add(args[2].base) + .result; + }) + +// ret faceforward(n, i, ng) +DEFINE_INTRINSIC(faceforward, 0, float, float, float, float) +DEFINE_INTRINSIC(faceforward, 0, float2, float2, float2, float2) +DEFINE_INTRINSIC(faceforward, 0, float3, float3, float3, float3) +DEFINE_INTRINSIC(faceforward, 0, float4, float4, float4, float4) +IMPLEMENT_INTRINSIC_GLSL(faceforward, 0, { + code += "faceforward(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ", " + id_to_name(args[1].base) + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(faceforward, 0, { + code += "faceforward(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ", " + id_to_name(args[1].base) + ')'; + }) +IMPLEMENT_INTRINSIC_SPIRV(faceforward, 0, { + return add_instruction(spv::OpExtInst, convert_type(res_type)) + .add(_glsl_ext) + .add(spv::GLSLstd450FaceForward) + .add(args[0].base) + .add(args[1].base) + .add(args[2].base) + .result; + }) + +// ret mul(x, y) +DEFINE_INTRINSIC(mul, 0, float2, float, float2) +DEFINE_INTRINSIC(mul, 0, float3, float, float3) +DEFINE_INTRINSIC(mul, 0, float4, float, float4) +IMPLEMENT_INTRINSIC_GLSL(mul, 0, { + code += '(' + id_to_name(args[0].base) + " * " + id_to_name(args[1].base) + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(mul, 0, { + code += '(' + id_to_name(args[0].base) + " * " + id_to_name(args[1].base) + ')'; + }) +IMPLEMENT_INTRINSIC_SPIRV(mul, 0, { + return add_instruction(spv::OpVectorTimesScalar, convert_type(res_type)) + .add(args[1].base) + .add(args[0].base) + .result; + }) +DEFINE_INTRINSIC(mul, 1, float2, float2, float) +DEFINE_INTRINSIC(mul, 1, float3, float3, float) +DEFINE_INTRINSIC(mul, 1, float4, float4, float) +IMPLEMENT_INTRINSIC_GLSL(mul, 1, { + code += '(' + id_to_name(args[0].base) + " * " + id_to_name(args[1].base) + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(mul, 1, { + code += '(' + id_to_name(args[0].base) + " * " + id_to_name(args[1].base) + ')'; + }) +IMPLEMENT_INTRINSIC_SPIRV(mul, 1, { + return add_instruction(spv::OpVectorTimesScalar, convert_type(res_type)) + .add(args[0].base) + .add(args[1].base) + .result; + }) + +DEFINE_INTRINSIC(mul, 2, float2x2, float, float2x2) +DEFINE_INTRINSIC(mul, 2, float3x3, float, float3x3) +DEFINE_INTRINSIC(mul, 2, float4x4, float, float4x4) +IMPLEMENT_INTRINSIC_GLSL(mul, 2, { + code += '(' + id_to_name(args[0].base) + " * " + id_to_name(args[1].base) + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(mul, 2, { + code += '(' + id_to_name(args[0].base) + " * " + id_to_name(args[1].base) + ')'; + }) +IMPLEMENT_INTRINSIC_SPIRV(mul, 2, { + return add_instruction(spv::OpMatrixTimesScalar, convert_type(res_type)) + .add(args[1].base) + .add(args[0].base) + .result; + }) +DEFINE_INTRINSIC(mul, 3, float2x2, float2x2, float) +DEFINE_INTRINSIC(mul, 3, float3x3, float3x3, float) +DEFINE_INTRINSIC(mul, 3, float4x4, float4x4, float) +IMPLEMENT_INTRINSIC_GLSL(mul, 3, { + code += '(' + id_to_name(args[0].base) + " * " + id_to_name(args[1].base) + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(mul, 3, { + code += '(' + id_to_name(args[0].base) + " * " + id_to_name(args[1].base) + ')'; + }) +IMPLEMENT_INTRINSIC_SPIRV(mul, 3, { + return add_instruction(spv::OpMatrixTimesScalar, convert_type(res_type)) + .add(args[0].base) + .add(args[1].base) + .result; + }) + +DEFINE_INTRINSIC(mul, 4, float2, float2, float2x2) +DEFINE_INTRINSIC(mul, 4, float3, float3, float3x3) +DEFINE_INTRINSIC(mul, 4, float4, float4, float4x4) +IMPLEMENT_INTRINSIC_GLSL(mul, 4, { + code += '(' + id_to_name(args[0].base) + " * " + id_to_name(args[1].base) + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(mul, 4, { + code += "mul(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ')'; + }) +IMPLEMENT_INTRINSIC_SPIRV(mul, 4, { + return add_instruction(spv::OpVectorTimesMatrix, convert_type(res_type)) + .add(args[0].base) + .add(args[1].base) + .result; + }) +DEFINE_INTRINSIC(mul, 5, float2, float2x2, float2) +DEFINE_INTRINSIC(mul, 5, float3, float3x3, float3) +DEFINE_INTRINSIC(mul, 5, float4, float4x4, float4) +IMPLEMENT_INTRINSIC_GLSL(mul, 5, { + code += '(' + id_to_name(args[0].base) + " * " + id_to_name(args[1].base) + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(mul, 5, { + code += "mul(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ')'; + }) +IMPLEMENT_INTRINSIC_SPIRV(mul, 5, { + return add_instruction(spv::OpMatrixTimesVector, convert_type(res_type)) + .add(args[0].base) + .add(args[1].base) + .result; + }) + +DEFINE_INTRINSIC(mul, 6, float2x2, float2x2, float2x2) +DEFINE_INTRINSIC(mul, 6, float3x3, float3x3, float3x3) +DEFINE_INTRINSIC(mul, 6, float4x4, float4x4, float4x4) +IMPLEMENT_INTRINSIC_GLSL(mul, 6, { + code += '(' + id_to_name(args[0].base) + " * " + id_to_name(args[1].base) + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(mul, 6, { + code += "mul(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ')'; + }) +IMPLEMENT_INTRINSIC_SPIRV(mul, 6, { + return add_instruction(spv::OpMatrixTimesMatrix, convert_type(res_type)) + .add(args[0].base) + .add(args[1].base) + .result; + }) + +// ret isinf(x) +DEFINE_INTRINSIC(isinf, 0, bool, float) +DEFINE_INTRINSIC(isinf, 0, bool2, float2) +DEFINE_INTRINSIC(isinf, 0, bool3, float3) +DEFINE_INTRINSIC(isinf, 0, bool4, float4) +IMPLEMENT_INTRINSIC_GLSL(isinf, 0, { + code += "isinf(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(isinf, 0, { + code += "isinf(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_SPIRV(isinf, 0, { + return add_instruction(spv::OpIsInf, convert_type(res_type)) + .add(args[0].base) + .result; + }) + +// ret isnan(x) +DEFINE_INTRINSIC(isnan, 0, bool, float) +DEFINE_INTRINSIC(isnan, 0, bool2, float2) +DEFINE_INTRINSIC(isnan, 0, bool3, float3) +DEFINE_INTRINSIC(isnan, 0, bool4, float4) +IMPLEMENT_INTRINSIC_GLSL(isnan, 0, { + code += "isnan(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(isnan, 0, { + code += "isnan(" + id_to_name(args[0].base) + ')'; + }) +IMPLEMENT_INTRINSIC_SPIRV(isnan, 0, { + return add_instruction(spv::OpIsNan, convert_type(res_type)) + .add(args[0].base) + .result; + }) + +// ret tex2D(s, coords) +DEFINE_INTRINSIC(tex2D, 0, float4, sampler, float2) +IMPLEMENT_INTRINSIC_GLSL(tex2D, 0, { + code += "texture(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + " * vec2(1.0, -1.0) + vec2(0.0, 1.0))"; + }) +IMPLEMENT_INTRINSIC_HLSL(tex2D, 0, { + if (_shader_model >= 40u) + code += id_to_name(args[0].base) + ".t.Sample(" + id_to_name(args[0].base) + ".s, " + id_to_name(args[1].base) + ')'; + else + code += "tex2D(" + id_to_name(args[0].base) + ".s, " + id_to_name(args[1].base) + ')'; + }) +IMPLEMENT_INTRINSIC_SPIRV(tex2D, 0, { + return add_instruction(spv::OpImageSampleImplicitLod, convert_type(res_type)) + .add(args[0].base) + .add(args[1].base) + .add(spv::ImageOperandsMaskNone) + .result; + }) +// ret tex2Doffset(s, coords, offset) +DEFINE_INTRINSIC(tex2Doffset, 0, float4, sampler, float2, int2) +IMPLEMENT_INTRINSIC_GLSL(tex2Doffset, 0, { + code += "textureOffset(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + " * vec2(1.0, -1.0) + vec2(0.0, 1.0), " + id_to_name(args[2].base) + " * ivec2(1, -1))"; + }) +IMPLEMENT_INTRINSIC_HLSL(tex2Doffset, 0, { + if (_shader_model >= 40u) + code += id_to_name(args[0].base) + ".t.Sample(" + id_to_name(args[0].base) + ".s, " + id_to_name(args[1].base) + ", " + id_to_name(args[2].base) + ')'; + else + code += "tex2D(" + id_to_name(args[0].base) + ".s, " + id_to_name(args[1].base) + " + " + id_to_name(args[2].base) + " * " + id_to_name(args[0].base) + ".pixelsize)"; + }) +IMPLEMENT_INTRINSIC_SPIRV(tex2Doffset, 0, { + add_capability(spv::CapabilityImageGatherExtended); + + return add_instruction(spv::OpImageSampleImplicitLod, convert_type(res_type)) + .add(args[0].base) + .add(args[1].base) + .add(spv::ImageOperandsOffsetMask) + .add(args[2].base) + .result; + }) + +// ret tex2Dlod(s, coords) +DEFINE_INTRINSIC(tex2Dlod, 0, float4, sampler, float4) +IMPLEMENT_INTRINSIC_GLSL(tex2Dlod, 0, { + code += "textureLod(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ".xy * vec2(1.0, -1.0) + vec2(0.0, 1.0), " + id_to_name(args[1].base) + ".w)"; + }) +IMPLEMENT_INTRINSIC_HLSL(tex2Dlod, 0, { + if (_shader_model >= 40u) + code += id_to_name(args[0].base) + ".t.SampleLevel(" + id_to_name(args[0].base) + ".s, " + id_to_name(args[1].base) + ".xy, " + id_to_name(args[1].base) + ".w)"; + else + code += "tex2Dlod(" + id_to_name(args[0].base) + ".s, " + id_to_name(args[1].base) + ')'; + }) +IMPLEMENT_INTRINSIC_SPIRV(tex2Dlod, 0, { + const spv::Id xy = add_instruction(spv::OpVectorShuffle, convert_type({ type::t_float, 2, 1 })) + .add(args[1].base) + .add(args[1].base) + .add(0) // .x + .add(1) // .y + .result; + const spv::Id lod = add_instruction(spv::OpCompositeExtract, convert_type({ type::t_float, 1, 1 })) + .add(args[1].base) + .add(3) // .w + .result; + + return add_instruction(spv::OpImageSampleExplicitLod, convert_type(res_type)) + .add(args[0].base) + .add(xy) + .add(spv::ImageOperandsLodMask) + .add(lod) + .result; + }) +// ret tex2Dlodoffset(s, coords, offset) +DEFINE_INTRINSIC(tex2Dlodoffset, 0, float4, sampler, float4, int2) +IMPLEMENT_INTRINSIC_GLSL(tex2Dlodoffset, 0, { + code += "textureLodOffset(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ".xy * vec2(1.0, -1.0) + vec2(0.0, 1.0), " + id_to_name(args[1].base) + ".w, " + id_to_name(args[2].base) + " * ivec2(1, -1))"; + }) +IMPLEMENT_INTRINSIC_HLSL(tex2Dlodoffset, 0, { + if (_shader_model >= 40u) + code += id_to_name(args[0].base) + ".t.SampleLevel(" + id_to_name(args[0].base) + ".s, " + id_to_name(args[1].base) + ".xy, " + id_to_name(args[1].base) + ".w, " + id_to_name(args[2].base) + ')'; + else + code += "tex2Dlod(" + id_to_name(args[0].base) + ".s, " + id_to_name(args[1].base) + " + float4(" + id_to_name(args[2].base) + " * " + id_to_name(args[0].base) + ".pixelsize, 0, 0))"; + }) +IMPLEMENT_INTRINSIC_SPIRV(tex2Dlodoffset, 0, { + add_capability(spv::CapabilityImageGatherExtended); + + const spv::Id xy = add_instruction(spv::OpVectorShuffle, convert_type({ type::t_float, 2, 1 })) + .add(args[1].base) + .add(args[1].base) + .add(0) // .x + .add(1) // .y + .result; + const spv::Id lod = add_instruction(spv::OpCompositeExtract, convert_type({ type::t_float, 1, 1 })) + .add(args[1].base) + .add(3) // .w + .result; + + return add_instruction(spv::OpImageSampleExplicitLod, convert_type(res_type)) + .add(args[0].base) + .add(xy) + .add(spv::ImageOperandsLodMask | spv::ImageOperandsOffsetMask) + .add(lod) + .add(args[2].base) + .result; + }) + +// ret tex2Dsize(s) +// ret tex2Dsize(s, lod) +DEFINE_INTRINSIC(tex2Dsize, 0, int2, sampler) +DEFINE_INTRINSIC(tex2Dsize, 1, int2, sampler, int) +IMPLEMENT_INTRINSIC_GLSL(tex2Dsize, 0, { + code += "textureSize(" + id_to_name(args[0].base) + ", 0)"; + }) +IMPLEMENT_INTRINSIC_GLSL(tex2Dsize, 1, { + code += "textureSize(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(tex2Dsize, 0, { + if (_shader_model >= 40u) + code += id_to_name(args[0].base) + ".t.GetDimensions(" + id_to_name(res) + ".x, " + id_to_name(res) + ".y)"; + else + code += "int2(1.0 / " + id_to_name(args[0].base) + ".pixelsize)"; + }) +IMPLEMENT_INTRINSIC_HLSL(tex2Dsize, 1, { + if (_shader_model >= 40u) + code += "uint temp" + std::to_string(res) + "; " + id_to_name(args[0].base) + ".t.GetDimensions(" + id_to_name(args[1].base) + ", " + id_to_name(res) + ".x, " + id_to_name(res) + ".y, temp" + std::to_string(res) + ')'; + else + code += "int2(1.0 / " + id_to_name(args[0].base) + ".pixelsize) / exp2(" + id_to_name(args[1].base) + ')'; + }) +IMPLEMENT_INTRINSIC_SPIRV(tex2Dsize, 0, { + add_capability(spv::CapabilityImageQuery); + + const spv::Id image = add_instruction(spv::OpImage, convert_type({ type::t_texture })) + .add(args[0].base) + .result; + + return add_instruction(spv::OpImageQuerySize, convert_type(res_type)) + .add(image) + .result; + }) +IMPLEMENT_INTRINSIC_SPIRV(tex2Dsize, 1, { + add_capability(spv::CapabilityImageQuery); + + const spv::Id image = add_instruction(spv::OpImage, convert_type({ type::t_texture })) + .add(args[0].base) + .result; + + return add_instruction(spv::OpImageQuerySizeLod, convert_type(res_type)) + .add(image) + .add(args[1].base) + .result; + }) + +// ret tex2Dfetch(s, coords) +DEFINE_INTRINSIC(tex2Dfetch, 0, float4, sampler, int4) +IMPLEMENT_INTRINSIC_GLSL(tex2Dfetch, 0, { + code += "texelFetch(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ".xy - ivec2(vec2(0, 1.0 - 1.0 / exp2(float(" + id_to_name(args[1].base) + ".w))) * textureSize(" + id_to_name(args[0].base) + ", 0)), " + id_to_name(args[1].base) + ".w)"; + }) +IMPLEMENT_INTRINSIC_HLSL(tex2Dfetch, 0, { + if (_shader_model >= 40u) + code += id_to_name(args[0].base) + ".t.Load(" + id_to_name(args[1].base) + ".xyw)"; + else + code += "tex2Dlod(" + id_to_name(args[0].base) + ".s, float4((" + id_to_name(args[1].base) + ".xy * exp2(" + id_to_name(args[1].base) + ".w) + 0.5 /* half-pixel offset */) * " + id_to_name(args[0].base) + ".pixelsize, 0, " + id_to_name(args[1].base) + ".w))"; + }) +IMPLEMENT_INTRINSIC_SPIRV(tex2Dfetch, 0, { + const spv::Id xy = add_instruction(spv::OpVectorShuffle, convert_type({ type::t_int, 2, 1 })) + .add(args[1].base) + .add(args[1].base) + .add(0) // .x + .add(1) // .y + .result; + const spv::Id lod = add_instruction(spv::OpCompositeExtract, convert_type({ type::t_int, 1, 1 })) + .add(args[1].base) + .add(3) // .w + .result; + + const spv::Id image = add_instruction(spv::OpImage, convert_type({ type::t_texture })) + .add(args[0].base) + .result; + + return add_instruction(spv::OpImageFetch, convert_type(res_type)) + .add(image) + .add(xy) + .add(spv::ImageOperandsLodMask) + .add(lod) + .result; + }) + +#define COMMA , + +// ret tex2Dgather(s, coords, component) +DEFINE_INTRINSIC(tex2Dgather, 0, float4, sampler, float2, int) +IMPLEMENT_INTRINSIC_GLSL(tex2Dgather, 0, { + code += "textureGather(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + " * vec2(1.0, -1.0) + vec2(0.0, 1.0), " + id_to_name(args[2].base) + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(tex2Dgather, 0, { + if (_shader_model >= 50u) { + const char *const names[4] = { "GatherRed" COMMA "GatherGreen" COMMA "GatherBlue" COMMA "GatherAlpha" }; + for (unsigned int c = 0; c < 4; ++c) + code += id_to_name(args[2].base) + " == " + std::to_string(c) + " ? " + id_to_name(args[0].base) + ".t." + names[c] + '(' + id_to_name(args[0].base) + ".s, " + id_to_name(args[1].base) + ") : "; + } else if (_shader_model >= 40u) { + for (unsigned int c = 0; c < 4; ++c) + code += id_to_name(args[2].base) + " == " + std::to_string(c) + " ? float4(" + + id_to_name(args[0].base) + ".t.SampleLevel(" + id_to_name(args[0].base) + ".s, " + id_to_name(args[1].base) + ", 0, int2(0, 0))." + "rgba"[c] + ", " + + id_to_name(args[0].base) + ".t.SampleLevel(" + id_to_name(args[0].base) + ".s, " + id_to_name(args[1].base) + ", 0, int2(0, 1))." + "rgba"[c] + ", " + + id_to_name(args[0].base) + ".t.SampleLevel(" + id_to_name(args[0].base) + ".s, " + id_to_name(args[1].base) + ", 0, int2(1, 1))." + "rgba"[c] + ", " + + id_to_name(args[0].base) + ".t.SampleLevel(" + id_to_name(args[0].base) + ".s, " + id_to_name(args[1].base) + ", 0, int2(1, 0))." + "rgba"[c] + ") : "; + } else { + for (unsigned int c = 0; c < 4; ++c) + code += id_to_name(args[2].base) + " == " + std::to_string(c) + " ? float4(" + "tex2Dlod(" + id_to_name(args[0].base) + ".s, float4(" + id_to_name(args[1].base) + " + float2(0, 0) * s.pixelsize, 0, 0))." + "rgba"[c] + ", " + "tex2Dlod(" + id_to_name(args[0].base) + ".s, float4(" + id_to_name(args[1].base) + " + float2(0, 1) * s.pixelsize, 0, 0))." + "rgba"[c] + ", " + "tex2Dlod(" + id_to_name(args[0].base) + ".s, float4(" + id_to_name(args[1].base) + " + float2(1, 1) * s.pixelsize, 0, 0))." + "rgba"[c] + ", " + "tex2Dlod(" + id_to_name(args[0].base) + ".s, float4(" + id_to_name(args[1].base) + " + float2(1, 0) * s.pixelsize, 0, 0))." + "rgba"[c] + ") : "; + } + code += '0'; + }) +IMPLEMENT_INTRINSIC_SPIRV(tex2Dgather, 0, { + return add_instruction(spv::OpImageGather, convert_type(res_type)) + .add(args[0].base) + .add(args[1].base) + .add(args[2].base) + .add(spv::ImageOperandsMaskNone) + .result; + }) +// ret tex2Dgatheroffset(s, coords, offset, component) +DEFINE_INTRINSIC(tex2Dgatheroffset, 0, float4, sampler, float2, int2, int) +IMPLEMENT_INTRINSIC_GLSL(tex2Dgatheroffset, 0, { + code += "textureGatherOffset(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + " * vec2(1.0, -1.0) + vec2(0.0, 1.0), " + id_to_name(args[2].base) + " * ivec2(1, -1), " + id_to_name(args[3].base) + ')'; + }) +IMPLEMENT_INTRINSIC_HLSL(tex2Dgatheroffset, 0, { + if (_shader_model >= 50u) { + const char *const names[4] = { "GatherRed" COMMA "GatherGreen" COMMA "GatherBlue" COMMA "GatherAlpha" }; + for (unsigned int c = 0; c < 4; ++c) + code += id_to_name(args[3].base) + " == " + std::to_string(c) + " ? " + id_to_name(args[0].base) + ".t." + names[c] + '(' + id_to_name(args[0].base) + ".s, " + id_to_name(args[1].base) + ", " + id_to_name(args[2].base) + ") : "; + } else if (_shader_model >= 40u) { + for (unsigned int c = 0; c < 4; ++c) + code += id_to_name(args[3].base) + " == " + std::to_string(c) + " ? float4(" + + id_to_name(args[0].base) + ".t.SampleLevel(" + id_to_name(args[0].base) + ".s, " + id_to_name(args[1].base) + ", 0, " + id_to_name(args[2].base) + " + int2(0, 0))." + "rgba"[c] + ", " + + id_to_name(args[0].base) + ".t.SampleLevel(" + id_to_name(args[0].base) + ".s, " + id_to_name(args[1].base) + ", 0, " + id_to_name(args[2].base) + " + int2(0, 1))." + "rgba"[c] + ", " + + id_to_name(args[0].base) + ".t.SampleLevel(" + id_to_name(args[0].base) + ".s, " + id_to_name(args[1].base) + ", 0, " + id_to_name(args[2].base) + " + int2(1, 1))." + "rgba"[c] + ", " + + id_to_name(args[0].base) + ".t.SampleLevel(" + id_to_name(args[0].base) + ".s, " + id_to_name(args[1].base) + ", 0, " + id_to_name(args[2].base) + " + int2(1, 0))." + "rgba"[c] + ") : "; + } else { + for (unsigned int c = 0; c < 4; ++c) + code += id_to_name(args[3].base) + " == " + std::to_string(c) + " ? float4(" + + "tex2Dlod(" + id_to_name(args[0].base) + ".s, float4(" + id_to_name(args[1].base) + " + (" + id_to_name(args[2].base) + " + float2(0, 0)) * s.pixelsize, 0, 0))." + "rgba"[c] + ", " + "tex2Dlod(" + id_to_name(args[0].base) + ".s, float4(" + id_to_name(args[1].base) + " + (" + id_to_name(args[2].base) + " + float2(0, 1)) * s.pixelsize, 0, 0))." + "rgba"[c] + ", " + "tex2Dlod(" + id_to_name(args[0].base) + ".s, float4(" + id_to_name(args[1].base) + " + (" + id_to_name(args[2].base) + " + float2(1, 1)) * s.pixelsize, 0, 0))." + "rgba"[c] + ", " + "tex2Dlod(" + id_to_name(args[0].base) + ".s, float4(" + id_to_name(args[1].base) + " + (" + id_to_name(args[2].base) + " + float2(1, 0)) * s.pixelsize, 0, 0))." + "rgba"[c] + ')'; + } + code += '0'; + }) +IMPLEMENT_INTRINSIC_SPIRV(tex2Dgatheroffset, 0, { + add_capability(spv::CapabilityImageGatherExtended); + + return add_instruction(spv::OpImageGather, convert_type(res_type)) + .add(args[0].base) + .add(args[1].base) + .add(args[3].base) + .add(spv::ImageOperandsOffsetMask) + .add(args[2].base) + .result; + }) + +#undef DEFINE_INTRINSIC +#undef IMPLEMENT_INTRINSIC_GLSL +#undef IMPLEMENT_INTRINSIC_HLSL +#undef IMPLEMENT_INTRINSIC_SPIRV diff --git a/msvc/source/gui.cpp b/msvc/source/gui.cpp new file mode 100644 index 0000000..4493314 --- /dev/null +++ b/msvc/source/gui.cpp @@ -0,0 +1,2313 @@ +/** + * Copyright (C) 2014 Patrick Mours. All rights reserved. + * License: https://github.com/crosire/reshade#license + */ + +#if RESHADE_GUI + +#include "log.hpp" +#include "version.h" +#include "runtime.hpp" +#include "runtime_objects.hpp" +#include "input.hpp" +#include "ini_file.hpp" +#include "gui_widgets.hpp" +#include +#include +#include +#include + +extern volatile long g_network_traffic; +extern std::filesystem::path g_reshade_dll_path; +extern std::filesystem::path g_target_executable_path; +static char g_reshadegui_ini_path[260 * 3] = {}; + +const ImVec4 COLOR_RED = ImColor(240, 100, 100); +const ImVec4 COLOR_YELLOW = ImColor(204, 204, 0); + +void reshade::runtime::init_ui() +{ + (g_reshade_dll_path.parent_path() / "ReShadeGUI.ini").u8string() + .copy(g_reshadegui_ini_path, sizeof(g_reshadegui_ini_path)); + + // Default shortcut: Home + _menu_key_data[0] = 0x24; + _menu_key_data[1] = false; + _menu_key_data[2] = false; + _menu_key_data[3] = false; + + _variable_editor_height = 300; + + _imgui_context = ImGui::CreateContext(); + + auto &imgui_io = _imgui_context->IO; + auto &imgui_style = _imgui_context->Style; + imgui_io.IniFilename = nullptr; + imgui_io.KeyMap[ImGuiKey_Tab] = 0x09; // VK_TAB + imgui_io.KeyMap[ImGuiKey_LeftArrow] = 0x25; // VK_LEFT + imgui_io.KeyMap[ImGuiKey_RightArrow] = 0x27; // VK_RIGHT + imgui_io.KeyMap[ImGuiKey_UpArrow] = 0x26; // VK_UP + imgui_io.KeyMap[ImGuiKey_DownArrow] = 0x28; // VK_DOWN + imgui_io.KeyMap[ImGuiKey_PageUp] = 0x21; // VK_PRIOR + imgui_io.KeyMap[ImGuiKey_PageDown] = 0x22; // VK_NEXT + imgui_io.KeyMap[ImGuiKey_Home] = 0x24; // VK_HOME + imgui_io.KeyMap[ImGuiKey_End] = 0x23; // VK_END + imgui_io.KeyMap[ImGuiKey_Insert] = 0x2D; // VK_INSERT + imgui_io.KeyMap[ImGuiKey_Delete] = 0x2E; // VK_DELETE + imgui_io.KeyMap[ImGuiKey_Backspace] = 0x08; // VK_BACK + imgui_io.KeyMap[ImGuiKey_Space] = 0x20; // VK_SPACE + imgui_io.KeyMap[ImGuiKey_Enter] = 0x0D; // VK_RETURN + imgui_io.KeyMap[ImGuiKey_Escape] = 0x1B; // VK_ESCAPE + imgui_io.KeyMap[ImGuiKey_A] = 'A'; + imgui_io.KeyMap[ImGuiKey_C] = 'C'; + imgui_io.KeyMap[ImGuiKey_V] = 'V'; + imgui_io.KeyMap[ImGuiKey_X] = 'X'; + imgui_io.KeyMap[ImGuiKey_Y] = 'Y'; + imgui_io.KeyMap[ImGuiKey_Z] = 'Z'; + imgui_io.ConfigFlags = ImGuiConfigFlags_DockingEnable | ImGuiConfigFlags_NavEnableKeyboard; + imgui_io.BackendFlags = ImGuiBackendFlags_HasMouseCursors; + + // Disable rounding by default + imgui_style.GrabRounding = 0.0f; + imgui_style.FrameRounding = 0.0f; + imgui_style.ChildRounding = 0.0f; + imgui_style.ScrollbarRounding = 0.0f; + imgui_style.WindowRounding = 0.0f; + imgui_style.WindowBorderSize = 0.0f; + + ImGui::SetCurrentContext(nullptr); + + subscribe_to_ui("Home", [this]() { draw_overlay_menu_home(); }); + subscribe_to_ui("Settings", [this]() { draw_overlay_menu_settings(); }); + subscribe_to_ui("Statistics", [this]() { draw_overlay_menu_statistics(); }); + subscribe_to_ui("Log", [this]() { draw_overlay_menu_log(); }); + subscribe_to_ui("About", [this]() { draw_overlay_menu_about(); }); + + _load_config_callables.push_back([this](const ini_file &config) { + bool save_imgui_window_state = false; + + config.get("INPUT", "KeyMenu", _menu_key_data); + config.get("INPUT", "InputProcessing", _input_processing_mode); + + config.get("GENERAL", "ShowClock", _show_clock); + config.get("GENERAL", "ShowFPS", _show_fps); + config.get("GENERAL", "ShowFrameTime", _show_frametime); + config.get("GENERAL", "ShowScreenshotMessage", _show_screenshot_message); + config.get("GENERAL", "ClockFormat", _clock_format); + config.get("GENERAL", "NoFontScaling", _no_font_scaling); + config.get("GENERAL", "SaveWindowState", save_imgui_window_state); + config.get("GENERAL", "TutorialProgress", _tutorial_index); + config.get("GENERAL", "NewVariableUI", _variable_editor_tabs); + + config.get("STYLE", "Alpha", _imgui_context->Style.Alpha); + config.get("STYLE", "GrabRounding", _imgui_context->Style.GrabRounding); + config.get("STYLE", "FrameRounding", _imgui_context->Style.FrameRounding); + config.get("STYLE", "ChildRounding", _imgui_context->Style.ChildRounding); + config.get("STYLE", "PopupRounding", _imgui_context->Style.PopupRounding); + config.get("STYLE", "WindowRounding", _imgui_context->Style.WindowRounding); + config.get("STYLE", "ScrollbarRounding", _imgui_context->Style.ScrollbarRounding); + config.get("STYLE", "TabRounding", _imgui_context->Style.TabRounding); + config.get("STYLE", "FPSScale", _fps_scale); + config.get("STYLE", "ColFPSText", _fps_col); + config.get("STYLE", "Font", _font); + config.get("STYLE", "FontSize", _font_size); + config.get("STYLE", "EditorFont", _editor_font); + config.get("STYLE", "EditorFontSize", _editor_font_size); + config.get("STYLE", "StyleIndex", _style_index); + config.get("STYLE", "EditorStyleIndex", _editor_style_index); + + _imgui_context->IO.IniFilename = save_imgui_window_state ? g_reshadegui_ini_path : nullptr; + + // For compatibility with older versions, set the alpha value if it is missing + if (_fps_col[3] == 0.0f) _fps_col[3] = 1.0f; + + ImVec4 *const colors = _imgui_context->Style.Colors; + switch (_style_index) + { + case 0: + ImGui::StyleColorsDark(&_imgui_context->Style); + break; + case 1: + ImGui::StyleColorsLight(&_imgui_context->Style); + break; + case 2: + colors[ImGuiCol_Text] = ImVec4(0.862745f, 0.862745f, 0.862745f, 1.00f); + colors[ImGuiCol_TextDisabled] = ImVec4(0.862745f, 0.862745f, 0.862745f, 0.58f); + colors[ImGuiCol_WindowBg] = ImVec4(0.117647f, 0.117647f, 0.117647f, 1.00f); + colors[ImGuiCol_ChildBg] = ImVec4(0.156863f, 0.156863f, 0.156863f, 0.00f); + colors[ImGuiCol_Border] = ImVec4(0.862745f, 0.862745f, 0.862745f, 0.30f); + colors[ImGuiCol_FrameBg] = ImVec4(0.156863f, 0.156863f, 0.156863f, 1.00f); + colors[ImGuiCol_FrameBgHovered] = ImVec4(0.392157f, 0.588235f, 0.941176f, 0.470588f); + colors[ImGuiCol_FrameBgActive] = ImVec4(0.392157f, 0.588235f, 0.941176f, 0.588235f); + colors[ImGuiCol_TitleBg] = ImVec4(0.392157f, 0.588235f, 0.941176f, 0.45f); + colors[ImGuiCol_TitleBgCollapsed] = ImVec4(0.392157f, 0.588235f, 0.941176f, 0.35f); + colors[ImGuiCol_TitleBgActive] = ImVec4(0.392157f, 0.588235f, 0.941176f, 0.58f); + colors[ImGuiCol_MenuBarBg] = ImVec4(0.156863f, 0.156863f, 0.156863f, 0.57f); + colors[ImGuiCol_ScrollbarBg] = ImVec4(0.156863f, 0.156863f, 0.156863f, 1.00f); + colors[ImGuiCol_ScrollbarGrab] = ImVec4(0.392157f, 0.588235f, 0.941176f, 0.31f); + colors[ImGuiCol_ScrollbarGrabHovered] = ImVec4(0.392157f, 0.588235f, 0.941176f, 0.78f); + colors[ImGuiCol_ScrollbarGrabActive] = ImVec4(0.392157f, 0.588235f, 0.941176f, 1.00f); + colors[ImGuiCol_PopupBg] = ImVec4(0.117647f, 0.117647f, 0.117647f, 0.92f); + colors[ImGuiCol_CheckMark] = ImVec4(0.392157f, 0.588235f, 0.941176f, 0.80f); + colors[ImGuiCol_SliderGrab] = ImVec4(0.392157f, 0.588235f, 0.941176f, 0.784314f); + colors[ImGuiCol_SliderGrabActive] = ImVec4(0.392157f, 0.588235f, 0.941176f, 1.00f); + colors[ImGuiCol_Button] = ImVec4(0.392157f, 0.588235f, 0.941176f, 0.44f); + colors[ImGuiCol_ButtonHovered] = ImVec4(0.392157f, 0.588235f, 0.941176f, 0.86f); + colors[ImGuiCol_ButtonActive] = ImVec4(0.392157f, 0.588235f, 0.941176f, 1.00f); + colors[ImGuiCol_Header] = ImVec4(0.392157f, 0.588235f, 0.941176f, 0.76f); + colors[ImGuiCol_HeaderHovered] = ImVec4(0.392157f, 0.588235f, 0.941176f, 0.86f); + colors[ImGuiCol_HeaderActive] = ImVec4(0.392157f, 0.588235f, 0.941176f, 1.00f); + colors[ImGuiCol_Separator] = ImVec4(0.862745f, 0.862745f, 0.862745f, 0.32f); + colors[ImGuiCol_SeparatorHovered] = ImVec4(0.862745f, 0.862745f, 0.862745f, 0.78f); + colors[ImGuiCol_SeparatorActive] = ImVec4(0.862745f, 0.862745f, 0.862745f, 1.00f); + colors[ImGuiCol_ResizeGrip] = ImVec4(0.392157f, 0.588235f, 0.941176f, 0.20f); + colors[ImGuiCol_ResizeGripHovered] = ImVec4(0.392157f, 0.588235f, 0.941176f, 0.78f); + colors[ImGuiCol_ResizeGripActive] = ImVec4(0.392157f, 0.588235f, 0.941176f, 1.00f); + colors[ImGuiCol_Tab] = colors[ImGuiCol_Button]; + colors[ImGuiCol_TabActive] = colors[ImGuiCol_ButtonActive]; + colors[ImGuiCol_TabHovered] = colors[ImGuiCol_ButtonHovered]; + colors[ImGuiCol_TabUnfocused] = ImLerp(colors[ImGuiCol_Tab], colors[ImGuiCol_TitleBg], 0.80f); + colors[ImGuiCol_TabUnfocusedActive] = ImLerp(colors[ImGuiCol_TabActive], colors[ImGuiCol_TitleBg], 0.40f); + colors[ImGuiCol_DockingPreview] = colors[ImGuiCol_Header] * ImVec4(1.0f, 1.0f, 1.0f, 0.7f); + colors[ImGuiCol_DockingEmptyBg] = ImVec4(0.20f, 0.20f, 0.20f, 1.00f); + colors[ImGuiCol_PlotLines] = ImVec4(0.862745f, 0.862745f, 0.862745f, 0.63f); + colors[ImGuiCol_PlotLinesHovered] = ImVec4(0.392157f, 0.588235f, 0.941176f, 1.00f); + colors[ImGuiCol_PlotHistogram] = ImVec4(0.862745f, 0.862745f, 0.862745f, 0.63f); + colors[ImGuiCol_PlotHistogramHovered] = ImVec4(0.392157f, 0.588235f, 0.941176f, 1.00f); + colors[ImGuiCol_TextSelectedBg] = ImVec4(0.392157f, 0.588235f, 0.941176f, 0.43f); + break; + default: + for (ImGuiCol i = 0; i < ImGuiCol_COUNT; i++) + config.get("STYLE", ImGui::GetStyleColorName(i), (float(&)[4])colors[i]); + break; + } + + switch (_editor_style_index) + { + case 0: + _editor.set_palette({ // Dark + 0xffffffff, 0xffd69c56, 0xff00ff00, 0xff7070e0, 0xffffffff, 0xff409090, 0xffaaaaaa, + 0xff9bc64d, 0xffc040a0, 0xff206020, 0xff406020, 0xff101010, 0xffe0e0e0, 0x80a06020, + 0x800020ff, 0x8000ffff, 0xff707000, 0x40000000, 0x40808080, 0x40a0a0a0 }); + break; + case 1: + _editor.set_palette({ // Light + 0xff000000, 0xffff0c06, 0xff008000, 0xff2020a0, 0xff000000, 0xff409090, 0xff404040, + 0xff606010, 0xffc040a0, 0xff205020, 0xff405020, 0xffffffff, 0xff000000, 0x80600000, + 0xa00010ff, 0x8000ffff, 0xff505000, 0x40000000, 0x40808080, 0x40000000 }); + break; + default: + ImVec4 value; // Note: This expects that all colors exist in the config + for (ImGuiCol i = 0; i < imgui_code_editor::color_palette_max; i++) + config.get("STYLE", imgui_code_editor::get_palette_color_name(i), (float(&)[4])value), + _editor.get_palette_index(i) = ImGui::ColorConvertFloat4ToU32(value); + break; + } + }); + _save_config_callables.push_back([this](ini_file &config) { + config.set("INPUT", "KeyMenu", _menu_key_data); + config.set("INPUT", "InputProcessing", _input_processing_mode); + + config.set("GENERAL", "ShowClock", _show_clock); + config.set("GENERAL", "ShowFPS", _show_fps); + config.set("GENERAL", "ShowFrameTime", _show_frametime); + config.set("GENERAL", "ShowScreenshotMessage", _show_screenshot_message); + config.set("GENERAL", "ClockFormat", _clock_format); + config.set("GENERAL", "NoFontScaling", _no_font_scaling); + config.set("GENERAL", "SaveWindowState", _imgui_context->IO.IniFilename != nullptr); + config.set("GENERAL", "TutorialProgress", _tutorial_index); + config.set("GENERAL", "NewVariableUI", _variable_editor_tabs); + + config.set("STYLE", "Alpha", _imgui_context->Style.Alpha); + config.set("STYLE", "GrabRounding", _imgui_context->Style.GrabRounding); + config.set("STYLE", "FrameRounding", _imgui_context->Style.FrameRounding); + config.set("STYLE", "ChildRounding", _imgui_context->Style.ChildRounding); + config.set("STYLE", "PopupRounding", _imgui_context->Style.PopupRounding); + config.set("STYLE", "WindowRounding", _imgui_context->Style.WindowRounding); + config.set("STYLE", "ScrollbarRounding", _imgui_context->Style.ScrollbarRounding); + config.set("STYLE", "TabRounding", _imgui_context->Style.TabRounding); + config.set("STYLE", "FPSScale", _fps_scale); + config.set("STYLE", "ColFPSText", _fps_col); + config.set("STYLE", "Font", _font); + config.set("STYLE", "FontSize", _font_size); + config.set("STYLE", "EditorFont", _editor_font); + config.set("STYLE", "EditorFontSize", _editor_font_size); + config.set("STYLE", "StyleIndex", _style_index); + config.set("STYLE", "EditorStyleIndex", _editor_style_index); + + if (_style_index > 2) + { + for (ImGuiCol i = 0; i < ImGuiCol_COUNT; i++) + config.set("STYLE", ImGui::GetStyleColorName(i), (const float(&)[4])_imgui_context->Style.Colors[i]); + } + + if (_editor_style_index > 1) + { + ImVec4 value; + for (ImGuiCol i = 0; i < imgui_code_editor::color_palette_max; i++) + value = ImGui::ColorConvertU32ToFloat4(_editor.get_palette_index(i)), + config.set("STYLE", imgui_code_editor::get_palette_color_name(i), (const float(&)[4])value); + } + }); +} +void reshade::runtime::deinit_ui() +{ + ImGui::DestroyContext(_imgui_context); +} + +void reshade::runtime::build_font_atlas() +{ + ImGui::SetCurrentContext(_imgui_context); + + const auto atlas = _imgui_context->IO.Fonts; + + atlas->Clear(); + + for (unsigned int i = 0; i < 2; ++i) + { + ImFontConfig cfg; + cfg.SizePixels = static_cast(i == 0 ? _font_size : _editor_font_size); + + const std::filesystem::path &font_path = i == 0 ? _font : _editor_font; + + if (std::error_code ec; !std::filesystem::is_regular_file(font_path, ec) || !atlas->AddFontFromFileTTF(font_path.u8string().c_str(), cfg.SizePixels)) + atlas->AddFontDefault(&cfg); // Use default font if custom font failed to load or does not exist + } + + // If unable to build font atlas due to an invalid font, revert to the default font + if (!atlas->Build()) + { + _font.clear(); + _editor_font.clear(); + + atlas->Clear(); + + for (unsigned int i = 0; i < 2; ++i) + { + ImFontConfig cfg; + cfg.SizePixels = static_cast(i == 0 ? _font_size : _editor_font_size); + + atlas->AddFontDefault(&cfg); + atlas->AddFontDefault(&cfg); + } + } + + destroy_font_atlas(); + + _show_splash = true; + _rebuild_font_atlas = false; + + int width, height; + unsigned char *pixels; + atlas->GetTexDataAsRGBA32(&pixels, &width, &height); + + ImGui::SetCurrentContext(nullptr); + + _imgui_font_atlas = std::make_unique(); + _imgui_font_atlas->width = width; + _imgui_font_atlas->height = height; + _imgui_font_atlas->format = reshadefx::texture_format::rgba8; + _imgui_font_atlas->unique_name = "ImGUI Font Atlas"; + if (init_texture(*_imgui_font_atlas)) + upload_texture(*_imgui_font_atlas, pixels); +} +void reshade::runtime::destroy_font_atlas() +{ + _imgui_font_atlas.reset(); +} + +void reshade::runtime::draw_ui() +{ + const bool show_splash = _show_splash && (_reload_remaining_effects != std::numeric_limits::max() || !_reload_compile_queue.empty() || (_last_present_time - _last_reload_time) < std::chrono::seconds(5)); + const bool show_screenshot_message = _show_screenshot_message && _last_present_time - _last_screenshot_time < std::chrono::seconds(_screenshot_save_success ? 3 : 5); + + if (_show_menu && !_ignore_shortcuts && !_imgui_context->IO.NavVisible && _input->is_key_pressed(0x1B /* VK_ESCAPE */)) + _show_menu = false; // Close when pressing the escape button and not currently navigating with the keyboard + else if (!_ignore_shortcuts && _input->is_key_pressed(_menu_key_data)) + _show_menu = !_show_menu; + + _ignore_shortcuts = false; + _effects_expanded_state &= 2; + + if (_rebuild_font_atlas) + build_font_atlas(); + if (_reload_remaining_effects != std::numeric_limits::max()) + _selected_effect = std::numeric_limits::max(), + _selected_effect_changed = true, // Force editor to clear text after effects where reloaded + _effect_filter_buffer[0] = '\0'; // And reset filter too, since the list of techniques might have changed + + ImGui::SetCurrentContext(_imgui_context); + auto &imgui_io = _imgui_context->IO; + imgui_io.DeltaTime = _last_frame_duration.count() * 1e-9f; + imgui_io.MouseDrawCursor = _show_menu; + imgui_io.DisplaySize.x = static_cast(_width); + imgui_io.DisplaySize.y = static_cast(_height); + imgui_io.Fonts->TexID = _imgui_font_atlas->impl.get(); + + // Scale mouse position in case render resolution does not match the window size + imgui_io.MousePos.x = _input->mouse_position_x() * (imgui_io.DisplaySize.x / _window_width); + imgui_io.MousePos.y = _input->mouse_position_y() * (imgui_io.DisplaySize.y / _window_height); + + // Add wheel delta to the current absolute mouse wheel position + imgui_io.MouseWheel += _input->mouse_wheel_delta(); + + // Update all the button states + imgui_io.KeyAlt = _input->is_key_down(0x12); // VK_MENU + imgui_io.KeyCtrl = _input->is_key_down(0x11); // VK_CONTROL + imgui_io.KeyShift = _input->is_key_down(0x10); // VK_SHIFT + for (unsigned int i = 0; i < 256; i++) + imgui_io.KeysDown[i] = _input->is_key_down(i); + for (unsigned int i = 0; i < 5; i++) + imgui_io.MouseDown[i] = _input->is_mouse_button_down(i); + for (wchar_t c : _input->text_input()) + imgui_io.AddInputCharacter(c); + + ImGui::NewFrame(); + + ImVec2 viewport_offset = ImVec2(0, 0); + + // Create ImGui widgets and windows + if (show_splash || show_screenshot_message || (!_show_menu && _tutorial_index == 0)) + { + ImGui::SetNextWindowPos(ImVec2(10, 10)); + ImGui::SetNextWindowSize(ImVec2(imgui_io.DisplaySize.x - 20.0f, 0.0f)); + ImGui::PushStyleVar(ImGuiStyleVar_Alpha, 1.0f); + ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.862745f, 0.862745f, 0.862745f, 1.0f)); + ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0.117647f, 0.117647f, 0.117647f, 0.7f)); + ImGui::Begin("Splash Screen", nullptr, + ImGuiWindowFlags_NoDecoration | + ImGuiWindowFlags_NoNav | + ImGuiWindowFlags_NoMove | + ImGuiWindowFlags_NoInputs | + ImGuiWindowFlags_NoSavedSettings | + ImGuiWindowFlags_NoDocking | + ImGuiWindowFlags_NoFocusOnAppearing); + + if (show_screenshot_message) + { + if (!_screenshot_save_success) + ImGui::TextColored(COLOR_RED, "Unable to save screenshot because path doesn't exist: %s", _screenshot_path.u8string().c_str()); + else + ImGui::Text("Screenshot successfully saved to %s", _last_screenshot_file.u8string().c_str()); + } + else + { + ImGui::TextUnformatted("ReShade " VERSION_STRING_FILE " by crosire"); + + if (_needs_update) + { + ImGui::TextColored(COLOR_YELLOW, + "An update is available! Please visit https://reshade.me and install the new version (v%lu.%lu.%lu).", + _latest_version[0], _latest_version[1], _latest_version[2]); + } + else + { + ImGui::TextUnformatted("Visit https://reshade.me for news, updates, shaders and discussion."); + } + + ImGui::Spacing(); + + if (_reload_remaining_effects != 0 && _reload_remaining_effects != std::numeric_limits::max()) + { + ImGui::ProgressBar(1.0f - _reload_remaining_effects / float(_reload_total_effects), ImVec2(-1, 0), ""); + ImGui::SameLine(15); + ImGui::Text( + "Loading (%zu effects remaining) ... " + "This might take a while. The application could become unresponsive for some time.", + _reload_remaining_effects.load()); + } + else if (!_reload_compile_queue.empty()) + { + ImGui::ProgressBar(1.0f - _reload_compile_queue.size() / float(_reload_total_effects), ImVec2(-1, 0), ""); + ImGui::SameLine(15); + ImGui::Text( + "Compiling (%zu effects remaining) ... " + "This might take a while. The application could become unresponsive for some time.", + _reload_compile_queue.size()); + } + else if (_tutorial_index == 0) + { + ImGui::Text( + "ReShade is now installed successfully! Press '%s' to start the tutorial.", input::key_name(_menu_key_data).c_str()); + } + else + { + ImGui::Text( + "Press '%s' to open the configuration menu.", input::key_name(_menu_key_data).c_str()); + ImGui::Dummy(ImVec2(0, _imgui_context->Style.FramePadding.y - 1.0f)); // Add small offset to align with progress bar from reload screen + } + + if (!_last_reload_successful) + { + ImGui::Spacing(); + ImGui::TextColored(COLOR_RED, + "There were errors compiling some shaders. " + "Open the configuration menu and switch to the 'Statistics' tab for more details."); + } + } + + viewport_offset.y += ImGui::GetWindowHeight() + 10; // Add small space between windows + + ImGui::End(); + ImGui::PopStyleColor(2); + ImGui::PopStyleVar(); + } + else if (_show_clock || _show_fps || _show_frametime) + { + ImGui::SetNextWindowPos(ImVec2(imgui_io.DisplaySize.x - 200.0f, 5)); + ImGui::SetNextWindowSize(ImVec2(200.0f, 200.0f)); + ImGui::PushStyleColor(ImGuiCol_Text, (const ImVec4 &)_fps_col); + ImGui::Begin("FPS", nullptr, + ImGuiWindowFlags_NoDecoration | + ImGuiWindowFlags_NoNav | + ImGuiWindowFlags_NoMove | + ImGuiWindowFlags_NoInputs | + ImGuiWindowFlags_NoSavedSettings | + ImGuiWindowFlags_NoDocking | + ImGuiWindowFlags_NoFocusOnAppearing | + ImGuiWindowFlags_NoBackground); + + ImGui::SetWindowFontScale(_fps_scale); + + char temp[512]; + + if (_show_clock) + { + const int hour = _date[3] / 3600; + const int minute = (_date[3] - hour * 3600) / 60; + const int seconds = _date[3] - hour * 3600 - minute * 60; + + ImFormatString(temp, sizeof(temp), _clock_format != 0 ? " %02u:%02u:%02u" : " %02u:%02u", hour, minute, seconds); + ImGui::SetCursorPosX(ImGui::GetWindowContentRegionWidth() - ImGui::CalcTextSize(temp).x); + ImGui::TextUnformatted(temp); + } + if (_show_fps) + { + ImFormatString(temp, sizeof(temp), "%.0f fps", imgui_io.Framerate); + ImGui::SetCursorPosX(ImGui::GetWindowContentRegionWidth() - ImGui::CalcTextSize(temp).x); + ImGui::TextUnformatted(temp); + } + if (_show_frametime) + { + ImFormatString(temp, sizeof(temp), "%5.2f ms", 1000.0f / imgui_io.Framerate); + ImGui::SetCursorPosX(ImGui::GetWindowContentRegionWidth() - ImGui::CalcTextSize(temp).x); + ImGui::TextUnformatted(temp); + } + + ImGui::End(); + ImGui::PopStyleColor(); + } + + if (_show_menu && _reload_remaining_effects == std::numeric_limits::max()) + { + // Change font size if user presses the control key and moves the mouse wheel + if (imgui_io.KeyCtrl && imgui_io.MouseWheel != 0 && !_no_font_scaling) + { + _font_size = ImClamp(_font_size + static_cast(imgui_io.MouseWheel), 8, 32); + _editor_font_size = ImClamp(_editor_font_size + static_cast(imgui_io.MouseWheel), 8, 32); + _rebuild_font_atlas = true; + save_config(); + } + + const ImGuiID root_space_id = ImGui::GetID("Dockspace"); + const ImGuiViewport *const viewport = ImGui::GetMainViewport(); + + // Set up default dock layout if this was not done yet + const bool init_window_layout = !ImGui::DockBuilderGetNode(root_space_id); + if (init_window_layout) + { + // Add the root node + ImGui::DockBuilderAddNode(root_space_id, ImGuiDockNodeFlags_Dockspace); + ImGui::DockBuilderSetNodeSize(root_space_id, viewport->Size); + + // Split root node into two spaces + ImGuiID main_space_id = 0; + ImGuiID right_space_id = 0; + ImGui::DockBuilderSplitNode(root_space_id, ImGuiDir_Left, 0.35f, &main_space_id, &right_space_id); + + // Attach most windows to the main dock space + for (const auto &widget : _menu_callables) + ImGui::DockBuilderDockWindow(widget.first.c_str(), main_space_id); + + // Attach editor window to the remaining dock space + ImGui::DockBuilderDockWindow("###editor", right_space_id); + + // Commit the layout + ImGui::DockBuilderFinish(root_space_id); + } + + ImGui::SetNextWindowPos(viewport->Pos + viewport_offset); + ImGui::SetNextWindowSize(viewport->Size - viewport_offset); + ImGui::SetNextWindowViewport(viewport->ID); + ImGui::Begin("Viewport", nullptr, + ImGuiWindowFlags_NoDecoration | + ImGuiWindowFlags_NoNav | + ImGuiWindowFlags_NoMove | + ImGuiWindowFlags_NoDocking | // This is the background viewport, the docking space is a child of it + ImGuiWindowFlags_NoFocusOnAppearing | + ImGuiWindowFlags_NoBringToFrontOnFocus | + ImGuiWindowFlags_NoBackground); + ImGui::DockSpace(root_space_id, ImVec2(0, 0), ImGuiDockNodeFlags_PassthruDockspace); + ImGui::End(); + + for (const auto &widget : _menu_callables) + { + if (ImGui::Begin(widget.first.c_str(), nullptr, ImGuiWindowFlags_NoFocusOnAppearing)) // No focus so that window state is preserved between opening/closing the UI + widget.second(); + ImGui::End(); + } + + if (_show_code_editor) + { + const std::string title = _selected_effect < _loaded_effects.size() ? + "Editing " + _loaded_effects[_selected_effect].source_file.filename().u8string() + " ###editor" : "Viewing code###editor"; + + if (ImGui::Begin(title.c_str(), &_show_code_editor)) + draw_code_editor(); + ImGui::End(); + } + + for (auto it = _texture_previews.begin(); it != _texture_previews.end();) + { + const texture *texture = *it; + + bool open = true; + char window_name[64]; + sprintf_s(window_name, "%s##%p", texture->unique_name.c_str(), *it); + + ImGui::Begin(window_name, &open, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoDocking); + imgui_image_with_checkerboard_background(texture->impl.get(), ImVec2(std::max(texture->width * 0.5f, 500.0f), std::max(texture->height * 0.5f, texture->height * 500.0f / texture->width))); + ImGui::End(); + + if (!open) // Close window by removing it from the list so it is not rendered next frame + it = _texture_previews.erase(it); + else ++it; + } + } + + // Render ImGui widgets and windows + ImGui::Render(); + + _input->block_mouse_input(_input_processing_mode != 0 && _show_menu && (imgui_io.WantCaptureMouse || _input_processing_mode == 2)); + _input->block_keyboard_input(_input_processing_mode != 0 && _show_menu && (imgui_io.WantCaptureKeyboard || _input_processing_mode == 2)); + + if (const auto draw_data = ImGui::GetDrawData(); draw_data != nullptr && draw_data->CmdListsCount != 0 && draw_data->TotalVtxCount != 0) + { + render_imgui_draw_data(draw_data); + } +} + +void reshade::runtime::draw_overlay_menu_home() +{ + if (!_effects_enabled) + ImGui::Text("Effects are disabled. Press '%s' to enable them again.", input::key_name(_effects_key_data).c_str()); + + const char *tutorial_text = + "Welcome! Since this is the first time you start ReShade, we'll go through a quick tutorial covering the most important features.\n\n" + "Before we continue: If you have difficulties reading this text, press the 'Ctrl' key and adjust the font size with your mouse wheel. " + "The window size is variable as well, just grab the bottom right corner and move it around.\n\n" + "You can also use the keyboard for navigation in case mouse input does not work. Use the arrow keys to navigate, space bar to confirm an action or enter a control and the 'Esc' key to leave a control. " + "Press 'Ctrl + Tab' to switch between tabs and windows.\n\n" + "Click on the 'Continue' button to continue the tutorial."; + + // It is not possible to follow some of the tutorial steps while performance mode is active, so skip them + if (_performance_mode && _tutorial_index <= 3) + _tutorial_index = 4; + + if (_tutorial_index > 0) + { + if (_tutorial_index == 1) + { + tutorial_text = + "This is the preset selection. All changes will be saved to the selected file.\n\n" + "Click on the '+' button to name and add a new one.\n" + "Make sure you always have a preset selected here before starting to tweak any values later, or else your changes won't be saved!"; + + ImGui::PushStyleColor(ImGuiCol_FrameBg, COLOR_RED); + ImGui::PushStyleColor(ImGuiCol_Button, COLOR_RED); + } + + draw_preset_explorer(); + + if (_tutorial_index == 1) + ImGui::PopStyleColor(2); + } + + if (_tutorial_index > 1) + { + ImGui::Spacing(); + ImGui::Separator(); + ImGui::Spacing(); + + const bool show_clear_button = strcmp(_effect_filter_buffer, "Search") != 0 && _effect_filter_buffer[0] != '\0'; + ImGui::PushItemWidth((_variable_editor_tabs ? -130.0f : -260.0f) - (show_clear_button ? ImGui::GetFrameHeight() + _imgui_context->Style.ItemSpacing.x : 0)); + + if (ImGui::InputText("##filter", _effect_filter_buffer, sizeof(_effect_filter_buffer), ImGuiInputTextFlags_AutoSelectAll)) + { + _effects_expanded_state = 3; + + if (_effect_filter_buffer[0] == '\0') + { + // Reset visibility state + for (technique &technique : _techniques) + technique.hidden = technique.annotation_as_int("hidden") != 0; + } + else + { + const std::string filter = _effect_filter_buffer; + + for (technique &technique : _techniques) + technique.hidden = technique.annotation_as_int("hidden") != 0 || + std::search(technique.name.begin(), technique.name.end(), filter.begin(), filter.end(), + [](auto c1, auto c2) { return tolower(c1) == tolower(c2); }) == technique.name.end() && _loaded_effects[technique.effect_index].source_file.filename().u8string().find(filter) == std::string::npos; + } + } + else if (!ImGui::IsItemActive() && _effect_filter_buffer[0] == '\0') + { + strcpy(_effect_filter_buffer, "Search"); + } + + ImGui::PopItemWidth(); + + ImGui::SameLine(); + + if (show_clear_button && ImGui::Button("X", ImVec2(ImGui::GetFrameHeight(), 0))) + { + strcpy(_effect_filter_buffer, "Search"); + // Reset visibility state + for (technique &technique : _techniques) + technique.hidden = technique.annotation_as_int("hidden") != 0; + } + + ImGui::SameLine(); + + if (ImGui::Button("Active to top", ImVec2(130 - _imgui_context->Style.ItemSpacing.x, 0))) + { + for (auto i = _techniques.begin(); i != _techniques.end(); ++i) + { + if (!i->enabled && i->toggle_key_data[0] == 0) + { + for (auto k = i + 1; k != _techniques.end(); ++k) + { + if (k->enabled || k->toggle_key_data[0] != 0) + { + std::iter_swap(i, k); + break; + } + } + } + } + + if (const auto it = std::find_if_not(_techniques.begin(), _techniques.end(), [](const reshade::technique &a) { + return a.enabled || a.toggle_key_data[0] != 0; + }); it != _techniques.end()) + { + std::stable_sort(it, _techniques.end(), [](const reshade::technique &lhs, const reshade::technique &rhs) { + std::string lhs_label(lhs.annotation_as_string("ui_label")); + if (lhs_label.empty()) lhs_label = lhs.name; + std::transform(lhs_label.begin(), lhs_label.end(), lhs_label.begin(), tolower); + std::string rhs_label(rhs.annotation_as_string("ui_label")); + if (rhs_label.empty()) rhs_label = rhs.name; + std::transform(rhs_label.begin(), rhs_label.end(), rhs_label.begin(), tolower); + return lhs_label < rhs_label; + }); + } + + save_current_preset(); + } + + ImGui::SameLine(); + + if (ImGui::Button(_effects_expanded_state & 2 ? "Collapse all" : "Expand all", ImVec2(130 - _imgui_context->Style.ItemSpacing.x, 0))) + _effects_expanded_state = (~_effects_expanded_state & 2) | 1; + + if (_tutorial_index == 2) + { + tutorial_text = + "This is the list of techniques. It contains all techniques in the effect files (*.fx) that were found in the effect search paths as specified in the settings.\n" + "Enter text in the box at the top to filter it and search for specific techniques.\n\n" + "Click on a technique to enable or disable it or drag it to a new location in the list to change the order in which the effects are applied.\n" + "Use the right mouse button and click on an item to open the context menu with additional options.\n\n"; + + ImGui::PushStyleColor(ImGuiCol_Border, COLOR_RED); + } + + ImGui::Spacing(); + + const float bottom_height = _performance_mode ? ImGui::GetFrameHeightWithSpacing() + _imgui_context->Style.ItemSpacing.y : (_variable_editor_height + (_tutorial_index == 3 ? 175 : 0)); + + if (ImGui::BeginChild("##techniques", ImVec2(0, -bottom_height), true)) + draw_overlay_technique_editor(); + ImGui::EndChild(); + + if (_tutorial_index == 2) + ImGui::PopStyleColor(); + } + + if (_tutorial_index > 2 && !_performance_mode) + { + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0, 0)); + ImGui::ButtonEx("##splitter", ImVec2(ImGui::GetContentRegionAvailWidth(), 5)); + ImGui::PopStyleVar(); + + if (ImGui::IsItemHovered()) + ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeNS); + if (ImGui::IsItemActive()) + _variable_editor_height -= _imgui_context->IO.MouseDelta.y; + + if (_tutorial_index == 3) + { + tutorial_text = + "This is the list of variables. It contains all tweakable options the effects expose. All values here apply in real-time. Press 'Ctrl' and click on a widget to manually edit the value.\n\n" + "Enter text in the box at the top to filter it and search for specific variables.\n\n" + "Use the right mouse button and click on an item to open the context menu with additional options.\n\n" + "Once you have finished tweaking your preset, be sure to enable the 'Performance Mode' check box. " + "This will recompile all shaders into a more optimal representation that can give a performance boost, but will disable variable tweaking and this list."; + + ImGui::PushStyleColor(ImGuiCol_Border, COLOR_RED); + } + + const float bottom_height = ImGui::GetFrameHeightWithSpacing() + _imgui_context->Style.ItemSpacing.y + (_tutorial_index == 3 ? 175 : 0); + + if (ImGui::BeginChild("##variables", ImVec2(0, -bottom_height), true)) + draw_overlay_variable_editor(); + ImGui::EndChild(); + + if (_tutorial_index == 3) + ImGui::PopStyleColor(); + } + + if (_tutorial_index > 3) + { + ImGui::Spacing(); + + if (ImGui::Button("Reload", ImVec2(-150, 0))) + { + _show_splash = true; + _effect_filter_buffer[0] = '\0'; // Reset filter + + load_effects(); + } + + ImGui::SameLine(); + + if (ImGui::Checkbox("Performance Mode", &_performance_mode)) + { + _show_splash = true; + _effect_filter_buffer[0] = '\0'; // Reset filter + + save_config(); + load_effects(); // Reload effects after switching + } + } + else + { + ImGui::BeginChildFrame(ImGui::GetID("tutorial"), ImVec2(0, 175)); + ImGui::TextWrapped(tutorial_text); + ImGui::EndChildFrame(); + + const float max_button_width = ImGui::GetContentRegionAvailWidth(); + + if (ImGui::Button(_tutorial_index == 3 ? "Finish" : "Continue", ImVec2(max_button_width * 0.66666666f, 0))) + { + // Disable font scaling after tutorial + if (_tutorial_index++ == 3) + _no_font_scaling = true; + + save_config(); + } + + ImGui::SameLine(); + + if (ImGui::Button("Skip Tutorial", ImVec2(max_button_width * 0.33333333f - _imgui_context->Style.ItemSpacing.x, 0))) + { + _tutorial_index = 4; + _no_font_scaling = true; + + save_config(); + } + } +} + +void reshade::runtime::draw_overlay_menu_settings() +{ + bool modified = false; + bool reload_style = false; + const float button_size = ImGui::GetFrameHeight(); + const float button_spacing = _imgui_context->Style.ItemInnerSpacing.x; + + if (ImGui::CollapsingHeader("General", ImGuiTreeNodeFlags_DefaultOpen)) + { + modified |= imgui_key_input("Overlay Key", _menu_key_data, *_input); + _ignore_shortcuts |= ImGui::IsItemActive(); + + modified |= imgui_key_input("Effect Reload Key", _reload_key_data, *_input); + _ignore_shortcuts |= ImGui::IsItemActive(); + modified |= imgui_key_input("Effect Toggle Key", _effects_key_data, *_input); + _ignore_shortcuts |= ImGui::IsItemActive(); + + modified |= ImGui::Combo("Input Processing", &_input_processing_mode, + "Pass on all input\0" + "Block input when cursor is on overlay\0" + "Block all input when overlay is visible\0"); + + modified |= imgui_path_list("Effect Search Paths", _effect_search_paths, _file_selection_path, g_reshade_dll_path.parent_path()); + modified |= imgui_path_list("Texture Search Paths", _texture_search_paths, _file_selection_path, g_reshade_dll_path.parent_path()); + + if (ImGui::Button("Restart Tutorial", ImVec2(ImGui::CalcItemWidth(), 0))) + _tutorial_index = 0; + } + + if (ImGui::CollapsingHeader("Screenshots", ImGuiTreeNodeFlags_DefaultOpen)) + { + modified |= imgui_key_input("Screenshot Key", _screenshot_key_data, *_input); + _ignore_shortcuts |= ImGui::IsItemActive(); + + modified |= imgui_directory_input_box("Screenshot Path", _screenshot_path, _file_selection_path); + modified |= ImGui::Combo("Screenshot Format", &_screenshot_format, "Bitmap (*.bmp)\0Portable Network Graphics (*.png)\0"); + modified |= ImGui::Checkbox("Include Current Preset", &_screenshot_include_preset); + } + + if (ImGui::CollapsingHeader("User Interface", ImGuiTreeNodeFlags_DefaultOpen)) + { + modified |= ImGui::Checkbox("Show Screenshot Message", &_show_screenshot_message); + + bool save_imgui_window_state = _imgui_context->IO.IniFilename != nullptr; + if (ImGui::Checkbox("Save Window State (ReShadeGUI.ini)", &save_imgui_window_state)) + { + modified = true; + _imgui_context->IO.IniFilename = save_imgui_window_state ? g_reshadegui_ini_path : nullptr; + } + + modified |= ImGui::Checkbox("Group effect files with tabs instead of a tree", &_variable_editor_tabs); + + #pragma region Style + if (ImGui::Combo("Style", &_style_index, "Dark\0Light\0Default\0Custom Simple\0Custom Advanced\0")) + { + modified = true; + reload_style = true; + } + + if (_style_index == 3) // Custom Simple + { + ImVec4 *const colors = _imgui_context->Style.Colors; + + if (ImGui::BeginChild("##colors", ImVec2(0, 105), true, ImGuiWindowFlags_AlwaysVerticalScrollbar | ImGuiWindowFlags_NavFlattened)) + { + ImGui::PushItemWidth(-160); + modified |= ImGui::ColorEdit3("Background", &colors[ImGuiCol_WindowBg].x); + modified |= ImGui::ColorEdit3("ItemBackground", &colors[ImGuiCol_FrameBg].x); + modified |= ImGui::ColorEdit3("Text", &colors[ImGuiCol_Text].x); + modified |= ImGui::ColorEdit3("ActiveItem", &colors[ImGuiCol_ButtonActive].x); + ImGui::PopItemWidth(); + } ImGui::EndChild(); + + // Change all colors using the above as base + if (modified) + { + colors[ImGuiCol_PopupBg] = colors[ImGuiCol_WindowBg]; colors[ImGuiCol_PopupBg].w = 0.92f; + + colors[ImGuiCol_ChildBg] = colors[ImGuiCol_FrameBg]; colors[ImGuiCol_ChildBg].w = 0.00f; + colors[ImGuiCol_MenuBarBg] = colors[ImGuiCol_FrameBg]; colors[ImGuiCol_MenuBarBg].w = 0.57f; + colors[ImGuiCol_ScrollbarBg] = colors[ImGuiCol_FrameBg]; colors[ImGuiCol_ScrollbarBg].w = 1.00f; + + colors[ImGuiCol_TextDisabled] = colors[ImGuiCol_Text]; colors[ImGuiCol_TextDisabled].w = 0.58f; + colors[ImGuiCol_Border] = colors[ImGuiCol_Text]; colors[ImGuiCol_Border].w = 0.30f; + colors[ImGuiCol_Separator] = colors[ImGuiCol_Text]; colors[ImGuiCol_Separator].w = 0.32f; + colors[ImGuiCol_SeparatorHovered] = colors[ImGuiCol_Text]; colors[ImGuiCol_SeparatorHovered].w = 0.78f; + colors[ImGuiCol_SeparatorActive] = colors[ImGuiCol_Text]; colors[ImGuiCol_SeparatorActive].w = 1.00f; + colors[ImGuiCol_PlotLines] = colors[ImGuiCol_Text]; colors[ImGuiCol_PlotLines].w = 0.63f; + colors[ImGuiCol_PlotHistogram] = colors[ImGuiCol_Text]; colors[ImGuiCol_PlotHistogram].w = 0.63f; + + colors[ImGuiCol_FrameBgHovered] = colors[ImGuiCol_ButtonActive]; colors[ImGuiCol_FrameBgHovered].w = 0.68f; + colors[ImGuiCol_FrameBgActive] = colors[ImGuiCol_ButtonActive]; colors[ImGuiCol_FrameBgActive].w = 1.00f; + colors[ImGuiCol_TitleBg] = colors[ImGuiCol_ButtonActive]; colors[ImGuiCol_TitleBg].w = 0.45f; + colors[ImGuiCol_TitleBgCollapsed] = colors[ImGuiCol_ButtonActive]; colors[ImGuiCol_TitleBgCollapsed].w = 0.35f; + colors[ImGuiCol_TitleBgActive] = colors[ImGuiCol_ButtonActive]; colors[ImGuiCol_TitleBgActive].w = 0.58f; + colors[ImGuiCol_ScrollbarGrab] = colors[ImGuiCol_ButtonActive]; colors[ImGuiCol_ScrollbarGrab].w = 0.31f; + colors[ImGuiCol_ScrollbarGrabHovered] = colors[ImGuiCol_ButtonActive]; colors[ImGuiCol_ScrollbarGrabHovered].w = 0.78f; + colors[ImGuiCol_ScrollbarGrabActive] = colors[ImGuiCol_ButtonActive]; colors[ImGuiCol_ScrollbarGrabActive].w = 1.00f; + colors[ImGuiCol_CheckMark] = colors[ImGuiCol_ButtonActive]; colors[ImGuiCol_CheckMark].w = 0.80f; + colors[ImGuiCol_SliderGrab] = colors[ImGuiCol_ButtonActive]; colors[ImGuiCol_SliderGrab].w = 0.24f; + colors[ImGuiCol_SliderGrabActive] = colors[ImGuiCol_ButtonActive]; colors[ImGuiCol_SliderGrabActive].w = 1.00f; + colors[ImGuiCol_Button] = colors[ImGuiCol_ButtonActive]; colors[ImGuiCol_Button].w = 0.44f; + colors[ImGuiCol_ButtonHovered] = colors[ImGuiCol_ButtonActive]; colors[ImGuiCol_ButtonHovered].w = 0.86f; + colors[ImGuiCol_Header] = colors[ImGuiCol_ButtonActive]; colors[ImGuiCol_Header].w = 0.76f; + colors[ImGuiCol_HeaderHovered] = colors[ImGuiCol_ButtonActive]; colors[ImGuiCol_HeaderHovered].w = 0.86f; + colors[ImGuiCol_HeaderActive] = colors[ImGuiCol_ButtonActive]; colors[ImGuiCol_HeaderActive].w = 1.00f; + colors[ImGuiCol_ResizeGrip] = colors[ImGuiCol_ButtonActive]; colors[ImGuiCol_ResizeGrip].w = 0.20f; + colors[ImGuiCol_ResizeGripHovered] = colors[ImGuiCol_ButtonActive]; colors[ImGuiCol_ResizeGripHovered].w = 0.78f; + colors[ImGuiCol_ResizeGripActive] = colors[ImGuiCol_ButtonActive]; colors[ImGuiCol_ResizeGripActive].w = 1.00f; + colors[ImGuiCol_PlotLinesHovered] = colors[ImGuiCol_ButtonActive]; colors[ImGuiCol_PlotLinesHovered].w = 1.00f; + colors[ImGuiCol_PlotHistogramHovered] = colors[ImGuiCol_ButtonActive]; colors[ImGuiCol_PlotHistogramHovered].w = 1.00f; + colors[ImGuiCol_TextSelectedBg] = colors[ImGuiCol_ButtonActive]; colors[ImGuiCol_TextSelectedBg].w = 0.43f; + + colors[ImGuiCol_Tab] = colors[ImGuiCol_Button]; + colors[ImGuiCol_TabActive] = colors[ImGuiCol_ButtonActive]; + colors[ImGuiCol_TabHovered] = colors[ImGuiCol_ButtonHovered]; + colors[ImGuiCol_TabUnfocused] = ImLerp(colors[ImGuiCol_Tab], colors[ImGuiCol_TitleBg], 0.80f); + colors[ImGuiCol_TabUnfocusedActive] = ImLerp(colors[ImGuiCol_TabActive], colors[ImGuiCol_TitleBg], 0.40f); + colors[ImGuiCol_DockingPreview] = colors[ImGuiCol_Header] * ImVec4(1.0f, 1.0f, 1.0f, 0.7f); + colors[ImGuiCol_DockingEmptyBg] = ImVec4(0.20f, 0.20f, 0.20f, 1.00f); + } + } + if (_style_index == 4) // Custom Advanced + { + if (ImGui::BeginChild("##colors", ImVec2(0, 300), true, ImGuiWindowFlags_AlwaysVerticalScrollbar | ImGuiWindowFlags_NavFlattened)) + { + ImGui::PushItemWidth(-160); + for (ImGuiCol i = 0; i < ImGuiCol_COUNT; i++) + { + ImGui::PushID(i); + modified |= ImGui::ColorEdit4("##color", &_imgui_context->Style.Colors[i].x, ImGuiColorEditFlags_AlphaBar | ImGuiColorEditFlags_AlphaPreview); + ImGui::SameLine(); ImGui::TextUnformatted(ImGui::GetStyleColorName(i)); + ImGui::PopID(); + } + ImGui::PopItemWidth(); + } ImGui::EndChild(); + } + #pragma endregion + + #pragma region Editor Style + if (ImGui::Combo("Editor Style", &_editor_style_index, "Dark\0Light\0Custom\0")) + { + modified = true; + reload_style = true; + } + + if (_editor_style_index == 2) + { + if (ImGui::BeginChild("##editor_colors", ImVec2(0, 300), true, ImGuiWindowFlags_AlwaysVerticalScrollbar | ImGuiWindowFlags_NavFlattened)) + { + ImGui::PushItemWidth(-160); + for (ImGuiCol i = 0; i < imgui_code_editor::color_palette_max; i++) + { + ImVec4 color = ImGui::ColorConvertU32ToFloat4(_editor.get_palette_index(i)); + ImGui::PushID(i); + modified |= ImGui::ColorEdit4("##editor_color", &color.x, ImGuiColorEditFlags_AlphaBar | ImGuiColorEditFlags_AlphaPreview); + ImGui::SameLine(); ImGui::TextUnformatted(imgui_code_editor::get_palette_color_name(i)); + ImGui::PopID(); + _editor.get_palette_index(i) = ImGui::ColorConvertFloat4ToU32(color); + } + ImGui::PopItemWidth(); + } ImGui::EndChild(); + } + #pragma endregion + + if (imgui_font_select("Font", _font, _font_size)) + { + modified = true; + _rebuild_font_atlas = true; + } + + if (imgui_font_select("Editor Font", _editor_font, _editor_font_size)) + { + modified = true; + _rebuild_font_atlas = true; + } + + if (float &alpha = _imgui_context->Style.Alpha; ImGui::SliderFloat("Global Alpha", &alpha, 0.1f, 1.0f, "%.2f")) + { + // Prevent user from setting alpha to zero + alpha = std::max(alpha, 0.1f); + modified = true; + } + + if (float &rounding = _imgui_context->Style.FrameRounding; ImGui::SliderFloat("Frame Rounding", &rounding, 0.0f, 12.0f, "%.0f")) + { + // Apply the same rounding to everything + _imgui_context->Style.GrabRounding = _imgui_context->Style.TabRounding = _imgui_context->Style.ScrollbarRounding = rounding; + _imgui_context->Style.WindowRounding = _imgui_context->Style.ChildRounding = _imgui_context->Style.PopupRounding = rounding; + modified = true; + } + + modified |= ImGui::Checkbox("Show Clock", &_show_clock); + ImGui::SameLine(0, 10); modified |= ImGui::Checkbox("Show FPS", &_show_fps); + ImGui::SameLine(0, 10); modified |= ImGui::Checkbox("Show Frame Time", &_show_frametime); + modified |= ImGui::Combo("Clock Format", &_clock_format, "HH:MM\0HH:MM:SS\0"); + modified |= ImGui::SliderFloat("FPS Text Size", &_fps_scale, 0.2f, 2.5f, "%.1f"); + modified |= ImGui::ColorEdit4("FPS Text Color", _fps_col, ImGuiColorEditFlags_AlphaBar | ImGuiColorEditFlags_AlphaPreview); + } + + if (modified) + save_config(); + if (reload_style) // Style is applied in "load_config()". + load_config(); +} + +void reshade::runtime::draw_overlay_menu_statistics() +{ + uint64_t post_processing_time_cpu = 0; + uint64_t post_processing_time_gpu = 0; + + for (const auto &technique : _techniques) + { + post_processing_time_cpu += technique.average_cpu_duration; + post_processing_time_gpu += technique.average_gpu_duration; + } + + if (ImGui::CollapsingHeader("General", ImGuiTreeNodeFlags_DefaultOpen)) + { + ImGui::PushItemWidth(ImGui::GetContentRegionAvailWidth()); + ImGui::PlotLines("##framerate", + _imgui_context->FramerateSecPerFrame, 120, + _imgui_context->FramerateSecPerFrameIdx, + nullptr, + _imgui_context->FramerateSecPerFrameAccum / 120 * 0.5f, + _imgui_context->FramerateSecPerFrameAccum / 120 * 1.5f, + ImVec2(0, 50)); + ImGui::PopItemWidth(); + + ImGui::BeginGroup(); + + ImGui::TextUnformatted("Application:"); + ImGui::TextUnformatted("Date:"); + ImGui::TextUnformatted("Device:"); + ImGui::TextUnformatted("FPS:"); + ImGui::TextUnformatted("Post-Processing:"); + ImGui::TextUnformatted("Draw Calls:"); + ImGui::Text("Frame %llu:", _framecount + 1); + ImGui::TextUnformatted("Timer:"); + ImGui::TextUnformatted("Network:"); + + ImGui::EndGroup(); + ImGui::SameLine(ImGui::GetWindowWidth() * 0.33333333f); + ImGui::BeginGroup(); + + ImGui::Text("%X", std::hash()(g_target_executable_path.stem().u8string())); + ImGui::Text("%d-%d-%d %d", _date[0], _date[1], _date[2], _date[3]); + ImGui::Text("%X %d", _vendor_id, _device_id); + ImGui::Text("%.2f", _imgui_context->IO.Framerate); + ImGui::Text("%f ms (CPU)", post_processing_time_cpu * 1e-6f); + ImGui::Text("%u (%u vertices)", _drawcalls, _vertices); + ImGui::Text("%f ms", _last_frame_duration.count() * 1e-6f); + ImGui::Text("%f ms", std::chrono::duration_cast(_last_present_time - _start_time).count() * 1e-6f); + ImGui::Text("%u B", g_network_traffic); + + ImGui::EndGroup(); + ImGui::SameLine(ImGui::GetWindowWidth() * 0.66666666f); + ImGui::BeginGroup(); + + ImGui::NewLine(); + ImGui::NewLine(); + ImGui::NewLine(); + ImGui::NewLine(); + if (post_processing_time_gpu != 0) + ImGui::Text("%f ms (GPU)", (post_processing_time_gpu * 1e-6f)); + + ImGui::EndGroup(); + } + + if (ImGui::CollapsingHeader("Effects", ImGuiTreeNodeFlags_DefaultOpen)) + { + std::vector current_textures; + current_textures.reserve(_textures.size()); + std::vector current_techniques; + current_techniques.reserve(_techniques.size()); + + ImGui::Checkbox("Show only active techniques", &_statistics_effects_show_enabled); + + for (size_t index = 0; index < _loaded_effects.size(); ++index) + { + const effect_data &effect = _loaded_effects[index]; + + // Ignore unloaded effects, filter for active effects if enabled + if (effect.source_file.empty() || (_statistics_effects_show_enabled && effect.rendering == 0)) + continue; + + ImGui::PushID(static_cast(index)); + + ImGui::AlignTextToFramePadding(); + + if (_selected_effect == index && _show_code_editor) + { + ImGui::TextUnformatted(">"); + ImGui::SameLine(); + } + + const float button_spacing = _imgui_context->Style.ItemInnerSpacing.x; + const float button_offset = ImGui::GetWindowContentRegionWidth() - (50 + button_spacing + 120); + + auto tree_id = ImGui::GetID("tree_open"); + bool tree_open = ImGui::GetStateStorage()->GetInt(tree_id, true); + bool tree_toggle = false; + + // Hide parent path if window is small + if (ImGui::CalcTextSize(effect.source_file.u8string().c_str()).x < button_offset) + { + ImGui::TextDisabled("%s%lc", effect.source_file.parent_path().u8string().c_str(), std::filesystem::path::preferred_separator); + tree_toggle = ImGui::IsItemClicked(); + ImGui::SameLine(0, 0); + } + + ImGui::TextUnformatted(effect.source_file.filename().u8string().c_str()); + tree_toggle |= ImGui::IsItemClicked(); + + // Update tree state + if (tree_toggle) + ImGui::GetStateStorage()->SetInt(tree_id, tree_open = !tree_open); + + ImGui::SameLine(button_offset + _imgui_context->Style.ItemSpacing.x, 0); + if (ImGui::Button("Edit", ImVec2(50, 0))) + { + _selected_effect = index; + _selected_effect_changed = true; + _show_code_editor = true; + } + + ImGui::SameLine(0, button_spacing); + if (ImGui::Button("Show HLSL/GLSL", ImVec2(120, 0))) + { + const std::string source_code = effect.preamble + effect.module.hlsl; + + // Act as a toggle when already showing the generated code + if (_show_code_editor + && _selected_effect == std::numeric_limits::max() + && _editor.get_text() == source_code) + { + _show_code_editor = false; + } + else + { + _editor.set_text(source_code); + _selected_effect = std::numeric_limits::max(); + _selected_effect_changed = false; // Prevent editor from being cleared, since we already set the text here + _show_code_editor = true; + } + } + + if (tree_open) + { + if (!effect.errors.empty()) + { + ImGui::PushStyleColor(ImGuiCol_Text, effect.errors.find("error") != std::string::npos ? COLOR_RED : COLOR_YELLOW); + ImGui::PushTextWrapPos(); + ImGui::TextUnformatted(effect.errors.c_str()); + ImGui::PopTextWrapPos(); + ImGui::PopStyleColor(); + ImGui::Spacing(); + } + + #pragma region Techniques + current_techniques.clear(); + for (const auto &technique : _techniques) + if (technique.effect_index == index && technique.impl != nullptr) + current_techniques.push_back(&technique); + + if (!current_techniques.empty()) + { + if (ImGui::BeginChild("Techniques", ImVec2(0, current_techniques.size() * ImGui::GetTextLineHeightWithSpacing() + _imgui_context->Style.FramePadding.y * 4), true, ImGuiWindowFlags_NoScrollWithMouse)) + { + ImGui::BeginGroup(); + + for (const auto &technique : current_techniques) + { + ImGui::PushStyleColor(ImGuiCol_Text, _imgui_context->Style.Colors[technique->enabled ? ImGuiCol_Text : ImGuiCol_TextDisabled]); + + if (technique->passes.size() > 1) + ImGui::Text("%s (%zu passes)", technique->name.c_str(), technique->passes.size()); + else + ImGui::TextUnformatted(technique->name.c_str()); + + ImGui::PopStyleColor(); + } + + ImGui::EndGroup(); + ImGui::SameLine(ImGui::GetWindowWidth() * 0.33333333f); + ImGui::BeginGroup(); + + for (const technique *technique : current_techniques) + if (technique->enabled) + ImGui::Text("%f ms (CPU) (%.0f%%)", technique->average_cpu_duration * 1e-6f, 100 * (technique->average_cpu_duration * 1e-6f) / (post_processing_time_cpu * 1e-6f)); + else + ImGui::NewLine(); + + ImGui::EndGroup(); + ImGui::SameLine(ImGui::GetWindowWidth() * 0.66666666f); + ImGui::BeginGroup(); + + for (const technique *technique : current_techniques) + if (technique->enabled && technique->average_gpu_duration != 0) + ImGui::Text("%f ms (GPU) (%.0f%%)", technique->average_gpu_duration * 1e-6f, 100 * (technique->average_gpu_duration * 1e-6f) / (post_processing_time_gpu * 1e-6f)); + else + ImGui::NewLine(); + + ImGui::EndGroup(); + } ImGui::EndChild(); + } + #pragma endregion + + #pragma region Textures + current_textures.clear(); + for (const auto &texture : _textures) + if (texture.effect_index == index && texture.impl != nullptr && texture.impl_reference == texture_reference::none) + current_textures.push_back(&texture); + + if (!current_textures.empty()) + { + if (ImGui::BeginChild("Textures", ImVec2(0, current_textures.size() * ImGui::GetTextLineHeightWithSpacing() + _imgui_context->Style.FramePadding.y * 4), true, ImGuiWindowFlags_NoScrollWithMouse)) + { + const char *texture_formats[] = { + "unknown", + "R8", "R16F", "R32F", "RG8", "RG16", "RG16F", "RG32F", "RGBA8", "RGBA16", "RGBA16F", "RGBA32F", "RGB10A2" + }; + + static_assert(_countof(texture_formats) - 1 == static_cast(reshadefx::texture_format::rgb10a2)); + + for (const texture *texture : current_textures) + { + ImGui::Text("%s (%ux%u +%u %s)", texture->unique_name.c_str(), texture->width, texture->height, (texture->levels - 1), texture_formats[static_cast(texture->format)]); + + if (std::find(_texture_previews.begin(), _texture_previews.end(), texture) != _texture_previews.end()) + continue; + + if (ImGui::IsItemClicked()) + { + _texture_previews.push_back(texture); + } + else if (ImGui::IsItemHovered()) + { + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f)); + ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f); + ImGui::PushStyleColor(ImGuiCol_PopupBg, IM_COL32(204, 204, 204, 255)); + ImGui::BeginTooltip(); + imgui_image_with_checkerboard_background(texture->impl.get(), ImVec2(std::max(texture->width * 0.5f, 500.0f), std::max(texture->height * 0.5f, texture->height * 500.0f / texture->width))); + ImGui::EndTooltip(); + ImGui::PopStyleColor(); + ImGui::PopStyleVar(2); + } + } + } ImGui::EndChild(); + } + #pragma endregion + } + + ImGui::PopID(); + + ImGui::Spacing(); + } + } +} + +void reshade::runtime::draw_overlay_menu_log() +{ + if (ImGui::Button("Clear Log")) + reshade::log::lines.clear(); + + ImGui::SameLine(); + ImGui::Checkbox("Word Wrap", &_log_wordwrap); + ImGui::SameLine(); + + static ImGuiTextFilter filter; // TODO: Better make this a member of the runtime class, in case there are multiple instances. + filter.Draw("Filter (inc, -exc)", -150); + + if (ImGui::BeginChild("log", ImVec2(0, 0), true, _log_wordwrap ? 0 : ImGuiWindowFlags_AlwaysHorizontalScrollbar)) + { + std::vector lines; + for (auto &line : reshade::log::lines) + if (filter.PassFilter(line.c_str())) + lines.push_back(line); + + ImGuiListClipper clipper(static_cast(lines.size()), ImGui::GetTextLineHeightWithSpacing()); + + while (clipper.Step()) + { + for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; ++i) + { + ImVec4 textcol = _imgui_context->Style.Colors[ImGuiCol_Text]; + + if (lines[i].find("ERROR |") != std::string::npos) + textcol = COLOR_RED; + else if (lines[i].find("WARN |") != std::string::npos) + textcol = COLOR_YELLOW; + else if (lines[i].find("DEBUG |") != std::string::npos) + textcol = ImColor(100, 100, 255); + + ImGui::PushStyleColor(ImGuiCol_Text, textcol); + if (_log_wordwrap) ImGui::PushTextWrapPos(); + + ImGui::TextUnformatted(lines[i].c_str()); + + if (_log_wordwrap) ImGui::PopTextWrapPos(); + ImGui::PopStyleColor(); + } + } + } ImGui::EndChild(); +} + +void reshade::runtime::draw_overlay_menu_about() +{ + ImGui::TextUnformatted("ReShade " VERSION_STRING_FILE); + + ImGui::PushTextWrapPos(); + ImGui::TextUnformatted(R"(Copyright (C) 2014 Patrick Mours. All rights reserved. + +https://github.com/crosire/reshade + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.)"); + + if (ImGui::CollapsingHeader("MinHook")) + { + ImGui::TextUnformatted(R"(Copyright (C) 2009-2016 Tsuda Kageyu. All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.)"); + } + if (ImGui::CollapsingHeader("Hacker Disassembler Engine 32/64 C")) + { + ImGui::TextUnformatted(R"(Copyright (C) 2008-2009 Vyacheslav Patkov. All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.)"); + } + if (ImGui::CollapsingHeader("dear imgui")) + { + ImGui::TextUnformatted(R"(Copyright (C) 2014-2015 Omar Cornut and ImGui contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.)"); + } + if (ImGui::CollapsingHeader("ImGuiColorTextEdit")) + { + ImGui::TextUnformatted(R"(Copyright (C) 2017 BalazsJako + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.)"); + } + if (ImGui::CollapsingHeader("gl3w")) + { + ImGui::TextUnformatted("Slavomir Kaslev"); + } + if (ImGui::CollapsingHeader("stb_image, stb_image_write")) + { + ImGui::TextUnformatted("Sean Barrett and contributors"); + } + if (ImGui::CollapsingHeader("DDS loading from SOIL")) + { + ImGui::TextUnformatted("Jonathan \"lonesock\" Dummer"); + } + + ImGui::PopTextWrapPos(); +} + +void reshade::runtime::draw_code_editor() +{ + const auto parse_errors = [this](const std::string &errors) { + _editor.clear_errors(); + + for (size_t offset = 0, next; offset != std::string::npos; offset = next) + { + const size_t pos_error = errors.find(": ", offset); + const size_t pos_error_line = errors.rfind('(', pos_error); // Paths can contain '(', but no ": ", so search backwards from th error location to find the line info + if (pos_error == std::string::npos || pos_error_line == std::string::npos) + break; + + const size_t pos_linefeed = errors.find('\n', pos_error); + + next = pos_linefeed != std::string::npos ? pos_linefeed + 1 : std::string::npos; + + // Ignore errors that aren't in the main source file + if (const std::string_view error_file(errors.c_str() + offset, pos_error_line - offset); + error_file != _loaded_effects[_selected_effect].source_file.u8string()) + continue; + + const int error_line = std::strtol(errors.c_str() + pos_error_line + 1, nullptr, 10); + const std::string error_text = errors.substr(pos_error + 2 /* skip space */, pos_linefeed - pos_error - 2); + + _editor.add_error(error_line, error_text, error_text.find("warning") != std::string::npos); + } + }; + + if (_selected_effect < _loaded_effects.size() && (ImGui::Button("Save & Compile (Ctrl + S)") || _input->is_key_pressed('S', true, false, false))) + { + // Hide splash bar during compile + _show_splash = false; + + const std::string text = _editor.get_text(); + + const std::filesystem::path source_file = _loaded_effects[_selected_effect].source_file; + + // Write editor text to file + std::ofstream(source_file, std::ios::trunc).write(text.c_str(), text.size()); + + // Reload effect file + _textures_loaded = false; + _reload_total_effects = 1; + _reload_remaining_effects = 1; + unload_effect(_selected_effect); + load_effect(source_file, _selected_effect); + assert(_reload_remaining_effects == 0); + + parse_errors(_loaded_effects[_selected_effect].errors); + } + + ImGui::SameLine(); + + ImGui::PushItemWidth(ImGui::GetContentRegionAvailWidth()); + + if (ImGui::BeginCombo("##file", _selected_effect < _loaded_effects.size() ? _loaded_effects[_selected_effect].source_file.u8string().c_str() : "", ImGuiComboFlags_HeightLarge)) + { + for (size_t i = 0; i < _loaded_effects.size(); ++i) + { + const auto &effect = _loaded_effects[i]; + + // Ignore unloaded effects + if (effect.source_file.empty()) + continue; + + if (ImGui::Selectable(effect.source_file.u8string().c_str(), _selected_effect == i)) + { + _selected_effect = i; + _selected_effect_changed = true; + } + } + + ImGui::EndCombo(); + } + + ImGui::PopItemWidth(); + + if (_selected_effect_changed) + { + if (_selected_effect < _loaded_effects.size()) + { + const auto &effect = _loaded_effects[_selected_effect]; + + // Load file to string and update editor text + _editor.set_text(std::string(std::istreambuf_iterator(std::ifstream(effect.source_file).rdbuf()), std::istreambuf_iterator())); + + parse_errors(effect.errors); + } + else + { + _editor.clear_text(); + } + + _selected_effect_changed = false; + } + + // Select editor font + ImGui::PushFont(_imgui_context->IO.Fonts->Fonts[1]); + + _editor.render("##editor"); + + ImGui::PopFont(); + + // Disable keyboard shortcuts when the window is focused so they don't get triggered while editing text + const bool is_focused = ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows); + _ignore_shortcuts |= is_focused; + + // Disable keyboard navigation starting with next frame when editor is focused so that the Alt key can be used without it switching focus to the menu bar + if (is_focused) + _imgui_context->IO.ConfigFlags &= ~ImGuiConfigFlags_NavEnableKeyboard; + else // Enable navigation again if focus is lost + _imgui_context->IO.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; +} + +void reshade::runtime::draw_overlay_variable_editor() +{ + const ImVec2 popup_pos = ImGui::GetCursorScreenPos() + ImVec2(std::max(0.f, ImGui::GetWindowContentRegionWidth() * 0.5f - 200.0f), ImGui::GetFrameHeightWithSpacing()); + + if (imgui_popup_button("Edit global preprocessor definitions", ImGui::GetContentRegionAvailWidth(), ImGuiWindowFlags_NoMove)) + { + ImGui::SetWindowPos(popup_pos); + + bool modified = false; + float popup_height = (std::max(_global_preprocessor_definitions.size(), _preset_preprocessor_definitions.size()) + 2) * ImGui::GetFrameHeightWithSpacing(); + popup_height = std::min(popup_height, _window_height - popup_pos.y - 20.0f); + const float button_size = ImGui::GetFrameHeight(); + const float button_spacing = _imgui_context->Style.ItemInnerSpacing.x; + + ImGui::BeginChild("##definitions", ImVec2(400.0f, popup_height), false, ImGuiWindowFlags_NoScrollWithMouse); + + if (ImGui::BeginTabBar("##definition_types", ImGuiTabBarFlags_NoTooltip)) + { + if (ImGui::BeginTabItem("Global")) + { + for (size_t i = 0; i < _global_preprocessor_definitions.size(); ++i) + { + char name[128] = ""; + char value[128] = ""; + + const size_t equals_index = _global_preprocessor_definitions[i].find('='); + _global_preprocessor_definitions[i].copy(name, std::min(equals_index, sizeof(name) - 1)); + if (equals_index != std::string::npos) + _global_preprocessor_definitions[i].copy(value, sizeof(value) - 1, equals_index + 1); + + ImGui::PushID(static_cast(i)); + + ImGui::PushItemWidth(ImGui::GetWindowContentRegionWidth() * 0.66666666f - (button_spacing)); + modified |= ImGui::InputText("##name", name, sizeof(name)); + ImGui::PopItemWidth(); + + ImGui::SameLine(0, button_spacing); + + ImGui::PushItemWidth(ImGui::GetWindowContentRegionWidth() * 0.33333333f - (button_spacing + button_size) + 1); + modified |= ImGui::InputText("##value", value, sizeof(value)); + ImGui::PopItemWidth(); + + ImGui::SameLine(0, button_spacing); + + if (ImGui::Button("-", ImVec2(button_size, 0))) + { + modified = true; + _global_preprocessor_definitions.erase(_global_preprocessor_definitions.begin() + i--); + } + else if (modified) + { + _global_preprocessor_definitions[i] = std::string(name) + '=' + std::string(value); + } + + ImGui::PopID(); + } + + ImGui::Dummy(ImVec2()); + ImGui::SameLine(0, ImGui::GetWindowContentRegionWidth() - button_size); + if (ImGui::Button("+", ImVec2(button_size, 0))) + _global_preprocessor_definitions.emplace_back(); + + ImGui::EndTabItem(); + } + + if (ImGui::BeginTabItem("Current Preset")) + { + for (size_t i = 0; i < _preset_preprocessor_definitions.size(); ++i) + { + char name[128] = ""; + char value[128] = ""; + + const size_t equals_index = _preset_preprocessor_definitions[i].find('='); + _preset_preprocessor_definitions[i].copy(name, std::min(equals_index, sizeof(name) - 1)); + if (equals_index != std::string::npos) + _preset_preprocessor_definitions[i].copy(value, sizeof(value) - 1, equals_index + 1); + + ImGui::PushID(static_cast(i)); + + ImGui::PushItemWidth(ImGui::GetWindowContentRegionWidth() * 0.66666666f - (button_spacing)); + modified |= ImGui::InputText("##name", name, sizeof(name)); + ImGui::PopItemWidth(); + + ImGui::SameLine(0, button_spacing); + + ImGui::PushItemWidth(ImGui::GetWindowContentRegionWidth() * 0.33333333f - (button_spacing + button_size) + 1); + modified |= ImGui::InputText("##value", value, sizeof(value)); + ImGui::PopItemWidth(); + + ImGui::SameLine(0, button_spacing); + + if (ImGui::Button("-", ImVec2(button_size, 0))) + { + modified = true; + _preset_preprocessor_definitions.erase(_preset_preprocessor_definitions.begin() + i--); + } + else if (modified) + { + _preset_preprocessor_definitions[i] = std::string(name) + '=' + std::string(value); + } + + ImGui::PopID(); + } + + ImGui::Dummy(ImVec2()); + ImGui::SameLine(0, ImGui::GetWindowContentRegionWidth() - button_size); + if (ImGui::Button("+", ImVec2(button_size, 0))) + _preset_preprocessor_definitions.emplace_back(); + + ImGui::EndTabItem(); + } + + ImGui::EndTabBar(); + } + + ImGui::EndChild(); + + if (modified) + save_config(), save_current_preset(), _was_preprocessor_popup_edited = true; + + ImGui::EndPopup(); + } + else if (_was_preprocessor_popup_edited) + { + _was_preprocessor_popup_edited = false; + + _show_splash = true; + _effect_filter_buffer[0] = '\0'; // Reset filter + + load_effects(); + } + + ImGui::BeginChild("##variables"); + + bool current_tree_is_open = false; + bool current_category_is_closed = false; + size_t current_effect = std::numeric_limits::max(); + std::string current_category; + + if (_variable_editor_tabs) + { + ImGui::BeginTabBar("##variables"); + } + + ImGui::PushItemWidth(ImGui::GetWindowWidth() * 0.5f); + + for (size_t index = 0; index < _uniforms.size(); ++index) + { + uniform &variable = _uniforms[index]; + + // Skip hidden and special variables + if (variable.annotation_as_int("hidden") || variable.special != special_uniform::none) + continue; + + // Hide variables that are not currently used in any of the active effects + if (!_loaded_effects[variable.effect_index].rendering) + continue; + assert(_loaded_effects[variable.effect_index].compile_sucess); + + // Create separate tab for every effect file + if (variable.effect_index != current_effect) + { + current_effect = variable.effect_index; + + const bool is_focused = _focused_effect == variable.effect_index; + + const std::string filename = _loaded_effects[current_effect].source_file.filename().u8string(); + + if (_variable_editor_tabs) + { + if (current_tree_is_open) + ImGui::EndTabItem(); + + current_tree_is_open = ImGui::BeginTabItem(filename.c_str()); + } + else + { + if (current_tree_is_open) + ImGui::TreePop(); + + if (is_focused || _effects_expanded_state & 1) + ImGui::SetNextTreeNodeOpen(is_focused || (_effects_expanded_state >> 1) != 0); + + current_tree_is_open = ImGui::TreeNodeEx(filename.c_str(), ImGuiTreeNodeFlags_DefaultOpen); + } + + if (is_focused) + { + ImGui::SetScrollHereY(0.0f); + _focused_effect = std::numeric_limits::max(); + } + + if (current_tree_is_open) + { + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(_imgui_context->Style.FramePadding.x, 0)); + if (imgui_popup_button("Reset all to default", _variable_editor_tabs ? ImGui::GetContentRegionAvailWidth() : ImGui::CalcItemWidth())) + { + ImGui::Text("Do you really want to reset all values in '%s' to their defaults?", filename.c_str()); + + if (ImGui::Button("Yes", ImVec2(ImGui::GetContentRegionAvailWidth(), 0))) + { + for (uniform &reset_variable : _uniforms) + if (reset_variable.effect_index == current_effect) + reset_uniform_value(reset_variable); + + save_current_preset(); + + ImGui::CloseCurrentPopup(); + } + + ImGui::EndPopup(); + } + ImGui::PopStyleVar(); + } + } + + // Skip rendering invisible items + if (!current_tree_is_open) + continue; + + if (const std::string_view category = variable.annotation_as_string("ui_category"); + category != current_category) + { + current_category = category; + + if (!category.empty()) + { + std::string label(category.data(), category.size()); + if (!_variable_editor_tabs) + for (float x = 0, space_x = ImGui::CalcTextSize(" ").x, width = (ImGui::CalcItemWidth() - ImGui::CalcTextSize(label.data()).x - 45) / 2; x < width; x += space_x) + label.insert(0, " "); + + current_category_is_closed = !ImGui::TreeNodeEx(label.c_str(), ImGuiTreeNodeFlags_DefaultOpen | ImGuiTreeNodeFlags_NoTreePushOnOpen); + } + else + { + current_category_is_closed = false; + } + } + + // Skip rendering invisible items + if (current_category_is_closed) + continue; + + bool modified = false; + std::string_view label = variable.annotation_as_string("ui_label"); + if (label.empty()) label = variable.name; + const std::string_view ui_type = variable.annotation_as_string("ui_type"); + + ImGui::PushID(static_cast(index)); + + switch (variable.type.base) + { + case reshadefx::type::t_bool: { + bool data; + get_uniform_value(variable, &data, 1); + + if (ui_type == "combo") + { + int current_item = data ? 1 : 0; + modified = ImGui::Combo(label.data(), ¤t_item, "Off\0On\0"); + data = current_item != 0; + } + else + { + modified = ImGui::Checkbox(label.data(), &data); + } + + if (modified) + set_uniform_value(variable, &data, 1); + break; } + case reshadefx::type::t_int: + case reshadefx::type::t_uint: { + int data[4]; + get_uniform_value(variable, data, 4); + + const auto ui_min_val = variable.annotation_as_int("ui_min"); + const auto ui_max_val = variable.annotation_as_int("ui_max"); + const auto ui_stp_val = std::max(1, variable.annotation_as_int("ui_step")); + + if (ui_type == "slider") + modified = imgui_slider_with_buttons(label.data(), variable.type.is_signed() ? ImGuiDataType_S32 : ImGuiDataType_U32, data, variable.type.rows, &ui_stp_val, &ui_min_val, &ui_max_val); + else if (ui_type == "drag") + modified = variable.annotations.find("ui_step") == variable.annotations.end() ? + ImGui::DragScalarN(label.data(), variable.type.is_signed() ? ImGuiDataType_S32 : ImGuiDataType_U32, data, variable.type.rows, 1.0f, &ui_min_val, &ui_max_val) : + imgui_drag_with_buttons(label.data(), variable.type.is_signed() ? ImGuiDataType_S32 : ImGuiDataType_U32, data, variable.type.rows, &ui_stp_val, &ui_min_val, &ui_max_val); + else if (ui_type == "list") + modified = imgui_list_with_buttons(label.data(), variable.annotation_as_string("ui_items"), data[0]); + else if (ui_type == "combo") { + const std::string_view ui_items = variable.annotation_as_string("ui_items"); + std::string items(ui_items.data(), ui_items.size()); + // Make sure list is terminated with a zero in case user forgot so no invalid memory is read accidentally + if (ui_items.empty() || ui_items.back() != '\0') + items.push_back('\0'); + + modified = ImGui::Combo(label.data(), data, items.c_str()); + } + else if (ui_type == "radio") { + const std::string_view ui_items = variable.annotation_as_string("ui_items"); + ImGui::BeginGroup(); + for (size_t offset = 0, next, i = 0; (next = ui_items.find('\0', offset)) != std::string::npos; offset = next + 1, ++i) + modified |= ImGui::RadioButton(ui_items.data() + offset, data, static_cast(i)); + ImGui::EndGroup(); + } + else + modified = ImGui::InputScalarN(label.data(), variable.type.is_signed() ? ImGuiDataType_S32 : ImGuiDataType_U32, data, variable.type.rows); + + if (modified) + set_uniform_value(variable, data, 4); + break; } + case reshadefx::type::t_float: { + float data[4]; + get_uniform_value(variable, data, 4); + + const auto ui_min_val = variable.annotation_as_float("ui_min"); + const auto ui_max_val = variable.annotation_as_float("ui_max"); + const auto ui_stp_val = std::max(0.001f, variable.annotation_as_float("ui_step")); + + // Calculate display precision based on step value + char precision_format[] = "%.0f"; + for (float x = ui_stp_val; x < 1.0f && precision_format[2] < '9'; x *= 10.0f) + ++precision_format[2]; // This changes the text to "%.1f", "%.2f", "%.3f", ... + + if (ui_type == "slider") + modified = imgui_slider_with_buttons(label.data(), ImGuiDataType_Float, data, variable.type.rows, &ui_stp_val, &ui_min_val, &ui_max_val, precision_format); + else if (ui_type == "drag") + modified = variable.annotations.find("ui_step") == variable.annotations.end() ? + ImGui::DragScalarN(label.data(), ImGuiDataType_Float, data, variable.type.rows, ui_stp_val, &ui_min_val, &ui_max_val, precision_format) : + imgui_drag_with_buttons(label.data(), ImGuiDataType_Float, data, variable.type.rows, &ui_stp_val, &ui_min_val, &ui_max_val, precision_format); + else if (ui_type == "color" && variable.type.rows == 1) + modified = imgui_slider_for_alpha(label.data(), data); + else if (ui_type == "color" && variable.type.rows == 3) + modified = ImGui::ColorEdit3(label.data(), data, ImGuiColorEditFlags_NoOptions); + else if (ui_type == "color" && variable.type.rows == 4) + modified = ImGui::ColorEdit4(label.data(), data, ImGuiColorEditFlags_NoOptions | ImGuiColorEditFlags_AlphaPreview | ImGuiColorEditFlags_AlphaBar); + else + modified = ImGui::InputScalarN(label.data(), ImGuiDataType_Float, data, variable.type.rows); + + if (modified) + set_uniform_value(variable, data, 4); + break; } + } + + // Display tooltip + if (const std::string_view tooltip = variable.annotation_as_string("ui_tooltip"); + !tooltip.empty() && ImGui::IsItemHovered()) + ImGui::SetTooltip("%s", tooltip.data()); + + // Create context menu + if (ImGui::BeginPopupContextItem("##context")) + { + if (ImGui::Button("Reset to default", ImVec2(200, 0))) + { + modified = true; + reset_uniform_value(variable); + ImGui::CloseCurrentPopup(); + } + + ImGui::EndPopup(); + } + + ImGui::PopID(); + + // A value has changed, so save the current preset + if (modified) + save_current_preset(); + } + + ImGui::PopItemWidth(); + + if (_variable_editor_tabs) + { + if (current_tree_is_open) + ImGui::EndTabItem(); + + ImGui::EndTabBar(); + } + else + { + if (current_tree_is_open) + ImGui::TreePop(); + } + + ImGui::EndChild(); +} + +void reshade::runtime::draw_overlay_technique_editor() +{ + size_t hovered_technique_index = std::numeric_limits::max(); + + for (size_t index = 0; index < _techniques.size(); ++index) + { + technique &technique = _techniques[index]; + + // Skip hidden techniques + if (technique.hidden) + continue; + + ImGui::PushID(static_cast(index)); + + // Draw border around the item if it is selected + const bool draw_border = _selected_technique == index; + if (draw_border) + ImGui::Separator(); + + const bool clicked = _imgui_context->IO.MouseClicked[0]; + const bool compile_success = _loaded_effects[technique.effect_index].compile_sucess; + assert(compile_success || !technique.enabled); + + // Prevent user from enabling the technique when the effect failed to compile + ImGui::PushItemFlag(ImGuiItemFlags_Disabled, !compile_success); + // Gray out disabled techniques and mark techniques which failed to compile red + ImGui::PushStyleColor(ImGuiCol_Text, compile_success ? _imgui_context->Style.Colors[technique.enabled ? ImGuiCol_Text : ImGuiCol_TextDisabled] : COLOR_RED); + + std::string_view ui_label = technique.annotation_as_string("ui_label"); + if (ui_label.empty() || !compile_success) ui_label = technique.name; + std::string label(ui_label.data(), ui_label.size()); + label += " [" + _loaded_effects[technique.effect_index].source_file.filename().u8string() + ']' + (!compile_success ? " (failed to compile)" : ""); + + if (bool status = technique.enabled; ImGui::Checkbox(label.data(), &status)) + { + if (status) + enable_technique(technique); + else + disable_technique(technique); + save_current_preset(); + } + + ImGui::PopStyleColor(); + ImGui::PopItemFlag(); + + if (ImGui::IsItemActive()) + _selected_technique = index; + if (ImGui::IsItemClicked()) + _focused_effect = technique.effect_index; + if (ImGui::IsItemHovered(ImGuiHoveredFlags_RectOnly)) + hovered_technique_index = index; + + // Display tooltip + if (const std::string_view tooltip = compile_success ? technique.annotation_as_string("ui_tooltip") : _loaded_effects[technique.effect_index].errors; + !tooltip.empty() && ImGui::IsItemHovered()) + { + ImGui::BeginTooltip(); + if (!compile_success) ImGui::PushStyleColor(ImGuiCol_Text, COLOR_RED); + ImGui::TextUnformatted(tooltip.data()); + if (!compile_success) ImGui::PopStyleColor(); + ImGui::EndTooltip(); + } + + // Create context menu + if (ImGui::BeginPopupContextItem("##context")) + { + if (imgui_key_input("Toggle Key", technique.toggle_key_data, *_input)) + save_current_preset(); + _ignore_shortcuts |= ImGui::IsItemActive(); + + ImGui::Separator(); + + const bool is_not_top = index > 0; + const bool is_not_bottom = index < _techniques.size() - 1; + const float button_width = ImGui::CalcItemWidth(); + + if (is_not_top && ImGui::Button("Move up", ImVec2(button_width, 0))) + { + std::swap(_techniques[index], _techniques[index - 1]); + save_current_preset(); + ImGui::CloseCurrentPopup(); + } + if (is_not_bottom && ImGui::Button("Move down", ImVec2(button_width, 0))) + { + std::swap(_techniques[index], _techniques[index + 1]); + save_current_preset(); + ImGui::CloseCurrentPopup(); + } + + if (is_not_top && ImGui::Button("Move to top", ImVec2(button_width, 0))) + { + _techniques.insert(_techniques.begin(), std::move(_techniques[index])); + _techniques.erase(_techniques.begin() + 1 + index); + save_current_preset(); + ImGui::CloseCurrentPopup(); + } + if (is_not_bottom && ImGui::Button("Move to bottom", ImVec2(button_width, 0))) + { + _techniques.push_back(std::move(_techniques[index])); + _techniques.erase(_techniques.begin() + index); + save_current_preset(); + ImGui::CloseCurrentPopup(); + } + + ImGui::EndPopup(); + } + + if (technique.toggle_key_data[0] != 0 && compile_success) + { + ImGui::SameLine(ImGui::GetWindowContentRegionWidth() * 0.75f); + ImGui::TextDisabled("%s", reshade::input::key_name(technique.toggle_key_data).c_str()); + } + + if (draw_border) + ImGui::Separator(); + + ImGui::PopID(); + } + + // Move the selected technique to the position of the mouse in the list + if (_selected_technique < _techniques.size() && ImGui::IsMouseDragging()) + { + if (hovered_technique_index < _techniques.size() && hovered_technique_index != _selected_technique) + { + std::swap(_techniques[hovered_technique_index], _techniques[_selected_technique]); + _selected_technique = hovered_technique_index; + save_current_preset(); + } + } + else + { + _selected_technique = std::numeric_limits::max(); + } +} + +void reshade::runtime::draw_preset_explorer() +{ + static const std::filesystem::path reshade_container_path = g_reshade_dll_path.parent_path(); + const ImVec2 cursor_pos = ImGui::GetCursorScreenPos(); + const float button_size = ImGui::GetFrameHeight(); + const float button_spacing = _imgui_context->Style.ItemInnerSpacing.x; + const float root_window_width = ImGui::GetWindowContentRegionWidth(); + + std::error_code ec; + + enum class condition { pass, select, popup_add, create, backward, forward, reload, cancel }; + condition condition = condition::pass; + + if (ImGui::ButtonEx("<", ImVec2(button_size, 0), ImGuiButtonFlags_NoNavFocus)) + condition = condition::backward, _current_browse_path = _current_preset_path; + + if (ImGui::SameLine(0, button_spacing); + ImGui::ButtonEx(">", ImVec2(button_size, 0), ImGuiButtonFlags_NoNavFocus)) + condition = condition::forward, _current_browse_path = _current_preset_path; + + ImGui::PushStyleVar(ImGuiStyleVar_ButtonTextAlign, ImVec2(0.0f, 0.5f)); + if (ImGui::SameLine(0, button_spacing); + ImGui::ButtonEx(_current_preset_path.stem().u8string().c_str(), ImVec2(root_window_width - (button_spacing + button_size) * 3, 0), ImGuiButtonFlags_NoNavFocus)) + if (ImGui::OpenPopup("##explore"), _imgui_context->IO.KeyCtrl) + _browse_path_is_input_mode = true; + ImGui::PopStyleVar(); + + if (ImGui::SameLine(0, button_spacing); ImGui::ButtonEx("+", ImVec2(button_size, 0), ImGuiButtonFlags_PressedOnClick | ImGuiButtonFlags_NoNavFocus)) + condition = condition::popup_add, _current_browse_path = _current_preset_path.parent_path(); + + ImGui::SetNextWindowPos(cursor_pos - _imgui_context->Style.WindowPadding); + const bool is_explore_open = ImGui::BeginPopup("##explore"); + if (is_explore_open) + { + if (ImGui::ButtonEx("<", ImVec2(button_size, 0), ImGuiButtonFlags_NoNavFocus)) + condition = condition::backward, _current_browse_path = _current_preset_path; + + if (ImGui::SameLine(0, button_spacing); + ImGui::ButtonEx(">", ImVec2(button_size, 0), ImGuiButtonFlags_NoNavFocus)) + condition = condition::forward, _current_browse_path = _current_preset_path; + + ImGui::SameLine(0, button_spacing); + if (_browse_path_is_input_mode) + { + char buf[_MAX_PATH]{}; + _current_browse_path.u8string().copy(buf, sizeof(buf) - 1); + + const bool is_edited = ImGui::InputTextEx("##path", buf, sizeof(buf), ImVec2(root_window_width - (button_spacing + button_size) * 3, 0), ImGuiInputTextFlags_None); + const bool is_returned = ImGui::IsKeyPressedMap(ImGuiKey_Enter); + + if (ImGui::IsWindowAppearing()) + ImGui::SetKeyboardFocusHere(); + else if (!ImGui::IsItemActive()) + _browse_path_is_input_mode = false; + + if (is_edited || is_returned) + { + std::filesystem::path input_preset_path = std::filesystem::u8path(buf); + std::filesystem::file_type file_type = std::filesystem::status(reshade_container_path / input_preset_path, ec).type(); + + if (is_edited && ec.value() != 0x7b) // 0x7b: ERROR_INVALID_NAME + _current_browse_path = std::move(input_preset_path); + + if (is_returned) + { + if (_current_browse_path.empty()) + condition = condition::cancel; + else if (ec.value() == 0x7b) // 0x7b: ERROR_INVALID_NAME + condition = condition::pass; + else if (file_type == std::filesystem::file_type::directory) + condition = condition::popup_add; + else + { + if (_current_browse_path.has_filename()) + if (const std::wstring extension(_current_browse_path.extension()); extension != L".ini" && extension != L".txt") + _current_browse_path += L".ini", + file_type = std::filesystem::status(reshade_container_path / _current_browse_path, ec).type(); + + if (ec.value() == 0x7b) // 0x7b: ERROR_INVALID_NAME + condition = condition::pass; + else if (file_type == std::filesystem::file_type::directory) + condition = condition::popup_add; + else if (file_type == std::filesystem::file_type::not_found) + if (_current_browse_path.has_filename()) + condition = condition::create; + else + condition = condition::popup_add; + else + condition = condition::select; + } + } + } + } + else + { + ImGui::PushStyleVar(ImGuiStyleVar_ButtonTextAlign, ImVec2(0.0f, 0.5f)); + if (ImGui::ButtonEx(_current_preset_path.stem().u8string().c_str(), ImVec2(root_window_width - (button_spacing + button_size) * 3, 0), ImGuiButtonFlags_NoNavFocus)) + if (_imgui_context->IO.KeyCtrl) + ImGui::ActivateItem(ImGui::GetID("##path")), _browse_path_is_input_mode = true; + else + condition = condition::cancel; + else if (ImGui::IsKeyPressedMap(ImGuiKey_Enter)) + condition = condition::reload, _current_browse_path = _current_preset_path; + ImGui::PopStyleVar(); + } + } + + if (is_explore_open || condition == condition::backward || condition == condition::forward) + { + std::filesystem::path preset_container_path = std::filesystem::absolute(reshade_container_path / _current_browse_path); + if (!std::filesystem::is_directory(preset_container_path, ec) && preset_container_path.has_filename()) + preset_container_path = preset_container_path.parent_path(); + + std::vector preset_container; + for (const auto &entry : std::filesystem::directory_iterator(preset_container_path, std::filesystem::directory_options::skip_permission_denied, ec)) + preset_container.push_back(entry); + + if (condition == condition::backward || condition == condition::forward) + { + std::vector preset_paths; + for (const auto &entry : preset_container) + if (!entry.is_directory()) + if (const std::wstring extension(entry.path().extension()); extension == L".ini" || extension == L".txt") + if (const reshade::ini_file preset(entry); preset.has("", "TechniqueSorting")) + preset_paths.push_back(entry); + + if (preset_paths.begin() == preset_paths.end()) + condition = condition::pass; + else + { + const std::filesystem::path ¤t_preset_path = _current_preset_path; + if (auto it = std::find_if(preset_paths.begin(), preset_paths.end(), [&ec, ¤t_preset_path](const std::filesystem::directory_entry &entry) { return std::filesystem::equivalent(entry, current_preset_path, ec); }); it == preset_paths.end()) + if (condition == condition::backward) + _current_preset_path = _current_browse_path = preset_paths.back(); + else + _current_preset_path = _current_browse_path = preset_paths.front(); + else + if (condition == condition::backward) + _current_preset_path = _current_browse_path = it == preset_paths.begin() ? preset_paths.back() : *--it; + else + _current_preset_path = _current_browse_path = it == preset_paths.end() - 1 ? preset_paths.front() : *++it; + } + } + + if (is_explore_open) + { + if (ImGui::SameLine(0, button_spacing); ImGui::ButtonEx("+", ImVec2(button_size, 0), ImGuiButtonFlags_PressedOnClick | ImGuiButtonFlags_NoNavFocus)) + if (const std::filesystem::file_type file_type = std::filesystem::status(reshade_container_path / _current_browse_path, ec).type(); ec.value() != 0x7b) // 0x7b: ERROR_INVALID_NAME + if (condition = condition::popup_add; file_type == std::filesystem::file_type::not_found || file_type != std::filesystem::file_type::directory) + _current_browse_path = _current_browse_path.parent_path(); + + if (ImGui::IsWindowAppearing() || condition == condition::backward || condition == condition::forward) + ImGui::SetNextWindowFocus(); + + if (ImGui::BeginChild("##paths", ImVec2(0, 300), true)) + { + if (ImGui::Selectable("..")) + { + for (_current_browse_path = std::filesystem::absolute(reshade_container_path / _current_browse_path); + !std::filesystem::is_directory(_current_browse_path, ec) && _current_browse_path.parent_path() != _current_browse_path;) + _current_browse_path = _current_browse_path.parent_path(); + _current_browse_path = _current_browse_path.parent_path(); + + if (std::filesystem::equivalent(reshade_container_path, _current_browse_path)) + _current_browse_path = L"."; + else if (std::equal(reshade_container_path.begin(), reshade_container_path.end(), _current_browse_path.begin())) + _current_browse_path = _current_browse_path.lexically_proximate(reshade_container_path); + } + + std::vector preset_paths; + for (const auto &entry : preset_container) + if (const std::filesystem::file_type file_type = entry.status(ec).type(); file_type == std::filesystem::file_type::directory) + if (ImGui::Selectable((" " + entry.path().filename().u8string()).c_str())) + if (std::filesystem::equivalent(reshade_container_path, entry)) + _current_browse_path = L"."; + else if (std::equal(reshade_container_path.begin(), reshade_container_path.end(), entry.path().begin())) + _current_browse_path = entry.path().lexically_proximate(reshade_container_path); + else + _current_browse_path = entry; + else {} + else if (const std::wstring extension(entry.path().extension()); extension == L".ini" || extension == L".txt") + preset_paths.push_back(entry); + + for (const auto &entry : preset_paths) + { + const bool is_current_preset = std::filesystem::equivalent(entry, _current_preset_path, ec); + + if (ImGui::Selectable(entry.path().filename().u8string().c_str(), static_cast(is_current_preset))) + condition = condition::select, _current_browse_path = entry.path().lexically_proximate(reshade_container_path); + + if (is_current_preset) + if (_current_preset_is_covered && !ImGui::IsWindowAppearing()) + _current_preset_is_covered = false, ImGui::SetScrollHereY(); + else if (condition == condition::backward || condition == condition::forward) + ImGui::SetScrollHereY(); + } + } + ImGui::EndChild(); + } + } + + if (condition == condition::select) + if (const reshade::ini_file preset(reshade_container_path / _current_browse_path); !preset.has("", "TechniqueSorting")) + condition = condition::pass; + + if (condition == condition::popup_add) + condition = condition::pass, ImGui::OpenPopup("##name"); + + ImGui::SetNextWindowPos(cursor_pos + ImVec2(root_window_width + button_size - 230, button_size)); + if (ImGui::BeginPopup("##name")) + { + if (ImGui::IsWindowAppearing()) + ImGui::SetKeyboardFocusHere(); + + char filename[_MAX_PATH]{}; + ImGui::InputText("Name", filename, sizeof(filename)); + + if (ImGui::IsKeyPressedMap(ImGuiKey_Enter)) + { + if (std::filesystem::path input_preset_path = std::filesystem::u8path(filename); input_preset_path.has_filename()) + { + if (const std::wstring extension(input_preset_path.extension()); extension != L".ini" && extension != L".txt") + input_preset_path += L".ini"; + if (const std::filesystem::file_type file_type = std::filesystem::status(reshade_container_path / _current_browse_path / input_preset_path, ec).type(); ec.value() == 0x7b) // 0x7b: ERROR_INVALID_NAME + condition = condition::pass; + else if (file_type == std::filesystem::file_type::directory) + condition = condition::pass; + else if (file_type == std::filesystem::file_type::not_found) + condition = condition::create; + else if (const reshade::ini_file preset(reshade_container_path / _current_browse_path / input_preset_path); preset.has("", "TechniqueSorting")) + condition = condition::select; + else + condition = condition::pass; + + if (condition != condition::pass) + _current_browse_path /= input_preset_path; + } + } + + if (condition != condition::pass) + ImGui::CloseCurrentPopup(); + + ImGui::EndPopup(); + } + + if (condition != condition::pass) + { + if (condition != condition::cancel) + { + _show_splash = true; + set_current_preset(); + + save_config(); + load_current_preset(); + } + if (is_explore_open && condition != condition::backward && condition != condition::forward) + ImGui::CloseCurrentPopup(); + } + + if (is_explore_open) + ImGui::EndPopup(); + else if (!_current_preset_is_covered) + _current_preset_is_covered = true; +} + +#endif diff --git a/msvc/source/gui_code_editor.cpp b/msvc/source/gui_code_editor.cpp new file mode 100644 index 0000000..bcc4903 --- /dev/null +++ b/msvc/source/gui_code_editor.cpp @@ -0,0 +1,1518 @@ +/** + * Copyright (C) 2018 Patrick Mours + * Copyright (C) 2017 BalazsJako + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "gui_code_editor.hpp" +#include "effect_lexer.hpp" +#include + +const char *imgui_code_editor::get_palette_color_name(unsigned int col) +{ + switch (col) + { + case color_default: + return "Default"; + case color_keyword: + return "Keyword"; + case color_number_literal: + return "NumberLiteral"; + case color_string_literal: + return "StringLiteral"; + case color_punctuation: + return "Punctuation"; + case color_preprocessor: + return "Preprocessor"; + case color_identifier: + return "Identifier"; + case color_known_identifier: + return "KnownIdentifier"; + case color_preprocessor_identifier: + return "PreprocessorIdentifier"; + case color_comment: + return "Comment"; + case color_multiline_comment: + return "MultilineComment"; + case color_background: + return "Background"; + case color_cursor: + return "Cursor"; + case color_selection: + return "Selection"; + case color_error_marker: + return "ErrorMarker"; + case color_warning_marker: + return "WarningMarker"; + case color_line_number: + return "LineNumber"; + case color_current_line_fill: + return "LineNumberFill"; + case color_current_line_fill_inactive: + return "LineNumberFillInactive"; + case color_current_line_edge: + return "CurrentLineEdge"; + } + return nullptr; +} + +imgui_code_editor::imgui_code_editor() +{ + _lines.emplace_back(); +} + +void imgui_code_editor::render(const char *title, bool border) +{ + assert(!_lines.empty()); + + ImGui::PushStyleColor(ImGuiCol_ChildBg, ImGui::ColorConvertU32ToFloat4(_palette[color_background])); + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0.0f, 0.0f)); + + ImGui::BeginChild(title, ImVec2(), border, ImGuiWindowFlags_HorizontalScrollbar | ImGuiWindowFlags_AlwaysHorizontalScrollbar | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoNavInputs); + ImGui::PushAllowKeyboardFocus(true); + + char buf[128] = "", *buf_end = buf; + + // 'ImGui::CalcTextSize' cancels out character spacing at the end, but we do not want that, hence the custom function + const auto calc_text_size = [](const char *text, const char *text_end = nullptr) { + return ImGui::GetFont()->CalcTextSizeA(ImGui::GetFontSize(), FLT_MAX, -1.0f, text, text_end, nullptr); + }; + + // Deduce text start offset by evaluating maximum number of lines plus two spaces as text width + snprintf(buf, 16, " %zu ", _lines.size()); + const float text_start = ImGui::CalcTextSize(buf).x + _left_margin; + // The following holds the approximate width and height of a default character for offset calculation + const ImVec2 char_advance = ImVec2(calc_text_size(" ").x, ImGui::GetTextLineHeightWithSpacing() * _line_spacing); + + ImGuiIO &io = ImGui::GetIO(); + _cursor_anim += io.DeltaTime; + + const bool ctrl = io.KeyCtrl, shift = io.KeyShift, alt = io.KeyAlt; + + // Handle keyboard input + if (ImGui::IsWindowFocused()) + { + if (ImGui::IsWindowHovered()) + ImGui::SetMouseCursor(ImGuiMouseCursor_TextInput); + + io.WantTextInput = true; + io.WantCaptureKeyboard = true; + + if (ctrl && !shift && !alt && ImGui::IsKeyPressed('Z')) + undo(); + else if (ctrl && !shift && !alt && ImGui::IsKeyPressed('Y')) + redo(); + else if (!ctrl && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_UpArrow))) + if (alt && !shift) // Alt + Up moves the current line one up + move_lines_up(); + else + move_up(1, shift); + else if (!ctrl && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_DownArrow))) + if (alt && !shift) // Alt + Down moves the current line one down + move_lines_down(); + else + move_down(1, shift); + else if (!alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_LeftArrow))) + move_left(1, shift, ctrl); + else if (!alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_RightArrow))) + move_right(1, shift, ctrl); + else if (!alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_PageUp))) + move_up(static_cast(floor((ImGui::GetWindowHeight() - 20.0f) / char_advance.y) - 4), shift); + else if (!alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_PageDown))) + move_down(static_cast(floor((ImGui::GetWindowHeight() - 20.0f) / char_advance.y) - 4), shift); + else if (!alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Home))) + ctrl ? move_top(shift) : move_home(shift); + else if (!alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_End))) + ctrl ? move_bottom(shift) : move_end(shift); + else if (!ctrl && !shift && !alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Delete))) + delete_next(); + else if (!ctrl && !shift && !alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Backspace))) + delete_previous(); + else if (!alt && ImGui::IsKeyPressed(45)) // Insert + ctrl ? clipboard_copy() : shift ? clipboard_paste() : _overwrite ^= true; + else if (ctrl && !shift && !alt && ImGui::IsKeyPressed('C')) + clipboard_copy(); + else if (ctrl && !shift && !alt && ImGui::IsKeyPressed('V')) + clipboard_paste(); + else if (ctrl && !shift && !alt && ImGui::IsKeyPressed('X')) + clipboard_cut(); + else if (ctrl && !shift && !alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_A))) + select_all(); + else if (!ctrl && !shift && !alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Enter))) + insert_character('\n', true); + else + for (ImWchar c : io.InputQueueCharacters) + if (c != 0 && (isprint(c) || isspace(c))) + insert_character(static_cast(c), true); + } + + // Handle mouse input + if (ImGui::IsWindowHovered() && !alt) + { + const auto mouse_to_text_pos = [this, text_start, &char_advance, &calc_text_size]() { + const ImVec2 pos(ImGui::GetMousePos().x - ImGui::GetCursorScreenPos().x, ImGui::GetMousePos().y - ImGui::GetCursorScreenPos().y); + + text_pos res; + res.line = std::max(0, static_cast(floor(pos.y / char_advance.y))); + res.line = std::min(res.line, _lines.size() - 1); + + float column_width = 0.0f; + std::string cumulated_string = ""; + float cumulated_string_width[2] = { 0.0f, 0.0f }; // [0] is the latest, [1] is the previous. I use that trick to check where cursor is exactly (important for tabs). + + const auto &line = _lines[res.line]; + + // First we find the hovered column + while (text_start + cumulated_string_width[0] < pos.x && res.column < line.size()) + { + cumulated_string_width[1] = cumulated_string_width[0]; + cumulated_string += line[res.column].c; + cumulated_string_width[0] = calc_text_size(cumulated_string.c_str()).x; + column_width = (cumulated_string_width[0] - cumulated_string_width[1]); + res.column++; + } + + // Then we reduce by 1 column if cursor is on the left side of the hovered column + if (text_start + cumulated_string_width[0] - column_width / 2.0f > pos.x && res.column > 0) + res.column--; + + return res; + }; + + const bool is_clicked = ImGui::IsMouseClicked(0); + const bool is_double_click = !shift && ImGui::IsMouseDoubleClicked(0); + const bool is_triple_click = !shift && is_clicked && !is_double_click && ImGui::GetTime() - _last_click_time < io.MouseDoubleClickTime; + + if (is_triple_click) + { + if (!ctrl) + { + _cursor_pos = mouse_to_text_pos(); + _interactive_beg = _cursor_pos; + _interactive_end = _cursor_pos; + + select(_interactive_beg, _interactive_end, selection_mode::line); + } + + _last_click_time = -1.0; + } + else if (is_double_click) + { + if (!ctrl) + { + _cursor_pos = mouse_to_text_pos(); + _interactive_beg = _cursor_pos; + _interactive_end = _cursor_pos; + + select(_interactive_beg, _interactive_end, selection_mode::word); + } + + _last_click_time = ImGui::GetTime(); + } + else if (is_clicked) + { + const bool flip_selection = _cursor_pos > _select_beg; + + _cursor_pos = mouse_to_text_pos(); + _interactive_beg = _cursor_pos; + _interactive_end = _cursor_pos; + + if (shift) + if (flip_selection) + _interactive_beg = _select_beg; + else + _interactive_end = _select_end; + + select(_interactive_beg, _interactive_end, ctrl ? selection_mode::word : selection_mode::normal); + + _last_click_time = ImGui::GetTime(); + } + else if (ImGui::IsMouseDragging(0) && ImGui::IsMouseDown(0)) // Update selection while left mouse is dragging + { + io.WantCaptureMouse = true; + + _cursor_pos = mouse_to_text_pos(); + _interactive_end = _cursor_pos; + + select(_interactive_beg, _interactive_end, selection_mode::normal); + } + } + + // Update glyph colors + colorize(); + + const auto draw_list = ImGui::GetWindowDrawList(); + + float longest_line = 0.0f; + const float space_size = calc_text_size(" ").x; + + size_t line_no = static_cast(floor(ImGui::GetScrollY() / char_advance.y)); + size_t line_max = std::max(0, std::min(_lines.size() - 1, line_no + static_cast(floor((ImGui::GetScrollY() + ImGui::GetWindowContentRegionMax().y) / char_advance.y)))); + + const auto calc_text_distance_to_line_begin = [this, space_size, &calc_text_size](const text_pos &from) { + float distance = 0.0f; + const auto &line = _lines[from.line]; + for (size_t i = 0u; i < line.size() && i < from.column; ++i) + if (line[i].c == '\t') + distance += _tab_size * space_size; + else + distance += calc_text_size(&line[i].c, &line[i].c + 1).x; + return distance; + }; + + for (; line_no <= line_max; ++line_no, buf_end = buf) + { + const auto &line = _lines[line_no]; + + // Position of the line number + const ImVec2 line_screen_pos = ImVec2(ImGui::GetCursorScreenPos().x, ImGui::GetCursorScreenPos().y + line_no * char_advance.y); + // Position of the text inside the editor + const ImVec2 text_screen_pos = ImVec2(line_screen_pos.x + text_start, line_screen_pos.y); + + const text_pos line_beg_pos(line_no, 0); + const text_pos line_end_pos(line_no, line.size()); + longest_line = std::max(calc_text_distance_to_line_begin(line_end_pos), longest_line); + + // Calculate selection rectangle + float selection_beg = -1.0f; + if (_select_beg <= line_end_pos) + selection_beg = _select_beg > line_beg_pos ? calc_text_distance_to_line_begin(_select_beg) : 0.0f; + float selection_end = -1.0f; + if (_select_end > line_beg_pos) + selection_end = calc_text_distance_to_line_begin(_select_end < line_end_pos ? _select_end : line_end_pos); + + // Add small overhead rectangle at the end of selected lines + if (_select_end.line > line_no) + selection_end += char_advance.x; + + // Draw selected area + if (selection_beg != -1 && selection_end != -1 && selection_beg < selection_end) + { + const ImVec2 beg = ImVec2(text_screen_pos.x + selection_beg, text_screen_pos.y); + const ImVec2 end = ImVec2(text_screen_pos.x + selection_end, text_screen_pos.y + char_advance.y); + + draw_list->AddRectFilled(beg, end, _palette[color_selection]); + } + + // Find any highlighted words and draw a selection rectangle below them + if (!_highlighted.empty()) + { + size_t begin_column = 0; + size_t highlight_index = 0; + + for (size_t i = 0; i < line.size(); ++i) + { + if (line[i].c == _highlighted[highlight_index] && line[i].col == color_identifier) + { + if (highlight_index == 0) + begin_column = i; + + ++highlight_index; + + if (highlight_index == _highlighted.size()) + { + if ((begin_column == 0 || line[begin_column - 1].col != color_identifier) && (i + 1 == line.size() || line[i + 1].col != color_identifier)) // Make sure this is a whole word and not just part of one + { + // We found a matching text block + const ImVec2 beg = ImVec2(text_screen_pos.x + calc_text_distance_to_line_begin(text_pos(line_no, begin_column)), text_screen_pos.y); + const ImVec2 end = ImVec2(text_screen_pos.x + calc_text_distance_to_line_begin(text_pos(line_no, i + 1)), text_screen_pos.y + char_advance.y); + + draw_list->AddRectFilled(beg, end, _palette[color_selection]); + } + } + else + { + // Continue search + continue; + } + } + + // Current text no longer matches, reset search + highlight_index = 0; + } + } + + // Draw error markers + if (auto it = _errors.find(line_no + 1); it != _errors.end()) + { + const ImVec2 beg = ImVec2(line_screen_pos.x + ImGui::GetScrollX(), line_screen_pos.y); + const ImVec2 end = ImVec2(line_screen_pos.x + ImGui::GetWindowContentRegionMax().x + 2.0f * ImGui::GetScrollX(), line_screen_pos.y + char_advance.y); + + draw_list->AddRectFilled(beg, end, _palette[it->second.second ? color_warning_marker : color_error_marker]); + + if (ImGui::IsMouseHoveringRect(beg, end)) + { + ImGui::BeginTooltip(); + ImGui::Text("%s", it->second.first.c_str()); + ImGui::EndTooltip(); + } + } + + // Draw line number (right aligned) + snprintf(buf, 16, "%zu ", line_no + 1); + + draw_list->AddText(ImVec2(text_screen_pos.x - ImGui::CalcTextSize(buf).x, line_screen_pos.y), _palette[color_line_number], buf); + + // Highlight the current line (where the cursor is) + if (_cursor_pos.line == line_no) + { + const bool is_focused = ImGui::IsWindowFocused(); + + if (!has_selection()) + { + const ImVec2 beg = ImVec2(line_screen_pos.x + ImGui::GetScrollX(), line_screen_pos.y); + const ImVec2 end = ImVec2(line_screen_pos.x + ImGui::GetWindowContentRegionMax().x + 2.0f * ImGui::GetScrollX(), line_screen_pos.y + char_advance.y); + + draw_list->AddRectFilled(beg, end, _palette[is_focused ? color_current_line_fill : color_current_line_fill_inactive]); + draw_list->AddRect(beg, end, _palette[color_current_line_edge], 1.0f); + } + + // Draw the cursor animation + if (is_focused && io.ConfigInputTextCursorBlink && fmodf(_cursor_anim, 1.0f) <= 0.5f) + { + const float cx = calc_text_distance_to_line_begin(_cursor_pos); + + const ImVec2 beg = ImVec2(text_screen_pos.x + cx, line_screen_pos.y); + const ImVec2 end = ImVec2(text_screen_pos.x + cx + (_overwrite ? char_advance.x : 1.0f), line_screen_pos.y + char_advance.y); // Larger cursor while overwriting + + draw_list->AddRectFilled(beg, end, _palette[color_cursor]); + } + } + + // Nothing to draw if the line is empty, so continue on + if (line.empty()) + continue; + + // Draw colorized line text + auto text_offset = 0.0f; + auto current_color = line[0].col; + + // Fill temporary buffer with glyph characters and commit it every time the color changes or a tab character is encountered + for (const glyph &glyph : line) + { + if (buf != buf_end && (glyph.col != current_color || glyph.c == '\t' || buf_end - buf >= sizeof(buf))) + { + draw_list->AddText(ImVec2(text_screen_pos.x + text_offset, text_screen_pos.y), _palette[current_color], buf, buf_end); + + text_offset += calc_text_size(buf, buf_end).x; buf_end = buf; // Reset temporary buffer + } + + if (glyph.c != '\t') + *buf_end++ = glyph.c; + else + text_offset += _tab_size * space_size; + + current_color = glyph.col; + } + + // Draw any text still in the temporary buffer that was not yet committed + if (buf != buf_end) + draw_list->AddText(ImVec2(text_screen_pos.x + text_offset, text_screen_pos.y), _palette[current_color], buf, buf_end); + } + + // Create dummy widget so a horizontal scrollbar appears + ImGui::Dummy(ImVec2(text_start + longest_line, _lines.size() * char_advance.y)); + + if (_scroll_to_cursor) + { + const float len = calc_text_distance_to_line_begin(_cursor_pos); + const float extra_space = 8.0f; + + const float max_scroll_width = ImGui::GetWindowWidth() - 16.0f; + const float max_scroll_height = ImGui::GetWindowHeight() - 32.0f; + + if (_cursor_pos.line < (ImGui::GetScrollY()) / char_advance.y) // No additional space when scrolling up + ImGui::SetScrollY(std::max(0.0f, _cursor_pos.line * char_advance.y)); + if (_cursor_pos.line > (ImGui::GetScrollY() + max_scroll_height - extra_space) / char_advance.y) + ImGui::SetScrollY(std::max(0.0f, _cursor_pos.line * char_advance.y + extra_space - max_scroll_height)); + + if (len + text_start < (ImGui::GetScrollX() + extra_space)) + ImGui::SetScrollX(std::max(0.0f, len + text_start - extra_space)); + if (len + text_start > (ImGui::GetScrollX() + max_scroll_width - extra_space)) + ImGui::SetScrollX(std::max(0.0f, len + text_start + extra_space - max_scroll_width)); + + ImGui::SetWindowFocus(); + + _scroll_to_cursor = false; + } + + ImGui::PopAllowKeyboardFocus(); + ImGui::EndChild(); + + ImGui::PopStyleVar(); + ImGui::PopStyleColor(); +} + +void imgui_code_editor::select(const text_pos &beg, const text_pos &end, selection_mode mode) +{ + assert(beg.line < _lines.size()); + assert(end.line < _lines.size()); + assert(beg.column <= _lines[beg.line].size()); // The last column is after the last character in the line + assert(end.column <= _lines[end.line].size()); + + if (end > beg) + _select_beg = beg, + _select_end = end; + else + _select_end = beg, + _select_beg = end; + + const auto select_word = [this](text_pos &beg, text_pos &end) { + const auto &beg_line = _lines[beg.line]; + const auto &end_line = _lines[end.line]; + // Empty lines cannot have any words, so abort + if (beg_line.empty() || end_line.empty()) + return; + // Whitespace has a special meaning in that if we select the space next to a word, then that word is precedence over the whitespace + if (beg.column == beg_line.size() || (beg.column > 0 && beg_line[beg.column].col == color_default)) + beg.column--; + if (end.column == end_line.size() || (end.column > 0 && end_line[end.column].col == color_default)) + end.column--; + // Search from the first position backwards until a character with a different color is found + for (auto word_color = beg_line[beg.column].col; + beg.column > 0 && beg_line[beg.column - 1].col == word_color; + --beg.column) continue; + // Search from the selection end position forwards until a character with a different color is found + for (auto word_color = end_line[end.column].col; + end.column < end_line.size() && end_line[end.column].col == word_color; + ++end.column) continue; + }; + + // Reset cursor animation (so it is always visible when clicking something) + _cursor_anim = 0; + + // Find the identifier under the cursor position + text_pos highlight_beg = _select_beg; + text_pos highlight_end = _select_end; + select_word(highlight_beg, highlight_end); + _highlighted = _lines[highlight_beg.line].size() > highlight_beg.column && _lines[highlight_beg.line][highlight_beg.column].col == color_identifier ? + get_text(highlight_beg, highlight_end) : std::string(); + + switch (mode) + { + case selection_mode::word: + select_word(_select_beg, _select_end); + break; + case selection_mode::line: + _select_beg.column = 0; + _select_end.column = _lines[end.line].size(); + break; + } +} +void imgui_code_editor::select_all() +{ + if (_lines.empty()) + return; // Cannot select anything if no text is set + + // Move cursor to end of text + _cursor_pos = text_pos(_lines.size() - 1, _lines.back().size()); + + // Update selection to contain everything + _interactive_beg = text_pos(0, 0); + _interactive_end = _cursor_pos; + + select(_interactive_beg, _interactive_end); +} + +void imgui_code_editor::set_text(const std::string &text) +{ + _undo.clear(); + _undo_index = 0; + + _lines.clear(); + _lines.emplace_back(); + + _errors.clear(); + + for (char c : text) + { + if (c == '\r') + continue; // Ignore the carriage return character + else if (c == '\n') + _lines.emplace_back(); + else + _lines.back().push_back({ c, color_default }); + } + + // Restrict cursor position to new text bounds + _select_beg = _select_end = text_pos(); + _interactive_beg = _interactive_end = text_pos(); + _cursor_pos = std::min(_cursor_pos, text_pos(_lines.size() - 1, _lines.back().size())); + + _colorize_line_beg = 0; + _colorize_line_end = _lines.size(); +} +void imgui_code_editor::clear_text() +{ + set_text(std::string()); +} +void imgui_code_editor::insert_text(const std::string &text) +{ + // Insert all characters of the text + for (const char c : text) + insert_character(c, false); + + // Move cursor to end of inserted text + select(_cursor_pos, _cursor_pos); +} +void imgui_code_editor::insert_character(char c, bool auto_indent) +{ + undo_record u; + + if (has_selection()) + { + if (c == '\t' && auto_indent) // Pressing tab with a selection indents the entire selection + { + auto &beg = _select_beg; + auto &end = _select_end; + + _colorize_line_beg = std::min(_colorize_line_beg, beg.line); + _colorize_line_end = std::max(_colorize_line_end, end.line + 1); + + beg.column = 0; + if (end.column == 0 && end.line > 0) + end.column = _lines[--end.line].size(); + + u.removed = get_text(beg, end); + u.removed_beg = beg; + u.removed_end = end; + + for (size_t i = beg.line; i <= end.line; i++) + { + auto &line = _lines[i]; + + if (ImGui::GetIO().KeyShift) + { + if (line[0].c == '\t') + { + line.erase(line.begin()); + if (i == end.line && end.column > 0) + end.column--; + } + else for (size_t j = 0; j < _tab_size && line[0].c == ' '; j++) // Do the same for spaces + { + line.erase(line.begin()); + if (i == end.line && end.column > 0) + end.column--; + } + } + else + { + line.insert(line.begin(), { '\t', color_background }); + if (i == end.line) + ++end.column; + } + } + + u.added = get_text(beg, end); + u.added_beg = beg; + u.added_end = end; + record_undo(std::move(u)); + return; + } + + // Otherwise overwrite the selection + delete_selection(); + } + + assert(!_lines.empty()); + + u.added = c; + u.added_beg = _cursor_pos; + + _colorize_line_beg = std::min(_colorize_line_beg, _cursor_pos.line); + + // New line feed requires insertion of a new line + if (c == '\n') + { + // Move all error markers after the new line one up + std::unordered_map> errors; + errors.reserve(_errors.size()); + for (auto &i : _errors) + errors.insert({ i.first >= _cursor_pos.line + 1 ? i.first + 1 : i.first, i.second }); + _errors = std::move(errors); + + auto &new_line = *_lines.emplace(_lines.begin() + _cursor_pos.line + 1); + auto &line = _lines[_cursor_pos.line]; + + // Auto indentation + if (auto_indent && _cursor_pos.column == line.size()) + for (size_t i = 0; i < line.size() && isblank(line[i].c); ++i) + new_line.push_back(line[i]), u.added.push_back(line[i].c); + const size_t indentation = new_line.size(); + + new_line.insert(new_line.end(), line.begin() + _cursor_pos.column, line.end()); + line.erase(line.begin() + _cursor_pos.column, line.begin() + line.size()); + + _cursor_pos.line++; + _cursor_pos.column = indentation; + } + else if (c != '\r') // Ignore carriage return + { + auto &line = _lines[_cursor_pos.line]; + + if (_overwrite && _cursor_pos.column < line.size()) + line[_cursor_pos.column] = { c, color_default }; + else + line.insert(line.begin() + _cursor_pos.column, { c, color_default }); + + _cursor_pos.column++; + } + + u.added_end = _cursor_pos; + record_undo(std::move(u)); + + // Reset cursor animation + _cursor_anim = 0; + + _scroll_to_cursor = true; + + _colorize_line_end = std::max(_colorize_line_end, _cursor_pos.line + 1); +} + +std::string imgui_code_editor::get_text() const +{ + return get_text(0, _lines.size()); +} +std::string imgui_code_editor::get_text(const text_pos &beg, const text_pos &end) const +{ + std::string result; + + for (auto it = beg; it < end;) + { + const auto &line = _lines[it.line]; + + if (it.column < line.size()) + { + result.push_back(line[it.column].c); + + it.column++; + } + else + { + it.line++; + it.column = 0; + + if (it.line < _lines.size()) + // Reached end of line, so append a new line feed + result.push_back('\n'); + } + } + + return result; +} +std::string imgui_code_editor::get_selected_text() const +{ + assert(has_selection()); + + return get_text(_select_beg, _select_end); +} + +void imgui_code_editor::undo(unsigned int steps) +{ + // Reset selection + _select_beg = _select_end = _cursor_pos; + _interactive_beg = _interactive_end = _cursor_pos; + + _in_undo_operation = true; + + while (can_undo() && steps-- > 0) + { + const undo_record &record = _undo[--_undo_index]; + + if (!record.added.empty()) + { + _cursor_pos = record.added_beg; + select(record.added_beg, record.added_end); + delete_selection(); + } + if (!record.removed.empty()) + { + _cursor_pos = record.removed_beg; + insert_text(record.removed); + } + } + + _scroll_to_cursor = true; + _in_undo_operation = false; +} +void imgui_code_editor::redo(unsigned int steps) +{ + // Reset selection + _select_beg = _select_end = _cursor_pos; + _interactive_beg = _interactive_end = _cursor_pos; + + _in_undo_operation = true; + + while (can_redo() && steps-- > 0) + { + const undo_record &record = _undo[_undo_index++]; + + if (!record.removed.empty()) + { + _cursor_pos = record.removed_beg; + select(record.removed_beg, record.removed_end); + delete_selection(); + } + if (!record.added.empty()) + { + _cursor_pos = record.added_beg; + insert_text(record.added); + } + } + + _scroll_to_cursor = true; + _in_undo_operation = false; +} + +void imgui_code_editor::record_undo(undo_record &&record) +{ + if (_in_undo_operation) + return; + + _undo.resize(_undo_index); // Remove all undo records after the current one + _undo.push_back(std::move(record)); // Append new record to the list + _undo_index++; +} + +void imgui_code_editor::delete_next() +{ + if (has_selection()) + { + delete_selection(); + return; + } + + assert(!_lines.empty()); + + auto &line = _lines[_cursor_pos.line]; + + undo_record u; + u.removed_beg = _cursor_pos; + u.removed_end = _cursor_pos; + + // If at end of line, move next line into the current one + if (_cursor_pos.column == line.size()) + { + if (_cursor_pos.line == _lines.size() - 1) + return; // This already is the last line + + const auto &next_line = _lines[_cursor_pos.line + 1]; + + u.removed = '\n'; + u.removed_end.line++; + u.removed_end.column = 0; + + // Copy next line into current line + line.insert(line.end(), next_line.begin(), next_line.end()); + + // Remove the line + delete_lines(_cursor_pos.line + 1, _cursor_pos.line + 1); + } + else + { + u.removed = line[_cursor_pos.column].c; + u.removed_end.column++; + + // Otherwise just remove the character at the cursor position + line.erase(line.begin() + _cursor_pos.column); + } + + record_undo(std::move(u)); + + _colorize_line_beg = std::min(_colorize_line_beg, _cursor_pos.line); + _colorize_line_end = std::max(_colorize_line_end, _cursor_pos.line + 1); +} +void imgui_code_editor::delete_previous() +{ + if (has_selection()) + { + delete_selection(); + return; + } + + assert(!_lines.empty()); + + auto &line = _lines[_cursor_pos.line]; + + undo_record u; + u.removed_end = _cursor_pos; + + // If at beginning of line, move previous line into the current one + if (_cursor_pos.column == 0) + { + if (_cursor_pos.line == 0) + return; // This already is the first line + + auto &prev_line = _lines[_cursor_pos.line - 1]; + _cursor_pos.line--; + _cursor_pos.column = prev_line.size(); + + u.removed = '\n'; + + // Copy current line into previous line + prev_line.insert(prev_line.end(), line.begin(), line.end()); + + // Remove the line + delete_lines(_cursor_pos.line + 1, _cursor_pos.line + 1); + } + else + { + _cursor_pos.column--; + + u.removed = line[_cursor_pos.column].c; + + // Otherwise remove the character next to the cursor position + line.erase(line.begin() + _cursor_pos.column); + } + + u.removed_beg = _cursor_pos; + record_undo(std::move(u)); + + _scroll_to_cursor = true; + + _colorize_line_beg = std::min(_colorize_line_beg, _cursor_pos.line); + _colorize_line_end = std::max(_colorize_line_end, _cursor_pos.line + 1); +} +void imgui_code_editor::delete_selection() +{ + if (!has_selection()) + return; + + assert(!_lines.empty()); + + undo_record u; + u.removed = get_selected_text(); + u.removed_beg = _select_beg; + u.removed_end = _select_end; + record_undo(std::move(u)); + + if (_select_beg.line == _select_end.line) + { + auto &line = _lines[_select_beg.line]; + + if (_select_end.column >= line.size()) + line.erase(line.begin() + _select_beg.column, line.end()); + else + line.erase(line.begin() + _select_beg.column, line.begin() + _select_end.column); + } + else + { + auto &beg_line = _lines[_select_beg.line]; + auto &end_line = _lines[_select_end.line]; + + beg_line.erase(beg_line.begin() + _select_beg.column, beg_line.end()); + end_line.erase(end_line.begin(), end_line.begin() + _select_end.column); + + if (_select_beg.line < _select_end.line) + { + beg_line.insert(beg_line.end(), end_line.begin(), end_line.end()); + + delete_lines(_select_beg.line + 1, _select_end.line); + } + + assert(!_lines.empty()); + } + + _colorize_line_beg = std::min(_colorize_line_beg, _select_beg.line); + _colorize_line_end = std::max(_colorize_line_end, _select_end.line + 1); + + // Reset selection + _cursor_pos = _select_beg; + select(_cursor_pos, _cursor_pos); +} +void imgui_code_editor::delete_lines(size_t first_line, size_t last_line) +{ + // Move all error markers after the deleted lines down + std::unordered_map> errors; + errors.reserve(_errors.size()); + for (auto &i : _errors) + if (i.first < first_line && i.first > last_line) + errors.insert({ i.first > last_line ? i.first - (last_line - first_line) : i.first, i.second }); + _errors = std::move(errors); + + _lines.erase(_lines.begin() + first_line, _lines.begin() + last_line + 1); +} + +void imgui_code_editor::clipboard_copy() +{ + if (has_selection()) + { + ImGui::SetClipboardText(get_selected_text().c_str()); + } + else if (!_lines.empty()) // Copy current line if there is no selection + { + std::string line_text; + line_text.reserve(_lines[_cursor_pos.line].size()); + for (const glyph &glyph : _lines[_cursor_pos.line]) + line_text.push_back(glyph.c); + + ImGui::SetClipboardText(line_text.c_str()); + } +} +void imgui_code_editor::clipboard_cut() +{ + if (!has_selection()) + return; + + clipboard_copy(); + delete_selection(); +} +void imgui_code_editor::clipboard_paste() +{ + const char *const text = ImGui::GetClipboardText(); + + if (text == nullptr || *text == '\0') + return; + + undo_record u; + + _in_undo_operation = true; + + if (has_selection()) + { + u.removed = get_selected_text(); + u.removed_beg = _select_beg; + u.removed_end = _select_end; + + delete_selection(); + } + + u.added = text; + u.added_beg = _cursor_pos; + + insert_text(text); + + u.added_end = _cursor_pos; + + _in_undo_operation = false; + + record_undo(std::move(u)); +} + +void imgui_code_editor::move_up(size_t amount, bool selection) +{ + assert(!_lines.empty()); + + const auto prev_pos = _cursor_pos; + _cursor_pos.line = std::max(0, _cursor_pos.line - amount); + + // The line before could be shorter, so adjust column + _cursor_pos.column = std::min(_cursor_pos.column, _lines[_cursor_pos.line].size()); + + if (prev_pos == _cursor_pos) + return; + + if (selection) + { + if (prev_pos == _interactive_beg) + _interactive_beg = _cursor_pos; + else if (prev_pos == _interactive_end) + _interactive_end = _cursor_pos; + else + _interactive_beg = _cursor_pos, + _interactive_end = prev_pos; + } + else + { + _interactive_beg = _cursor_pos; + _interactive_end = _cursor_pos; + } + + select(_interactive_beg, _interactive_end); + _scroll_to_cursor = true; +} +void imgui_code_editor::move_down(size_t amount, bool selection) +{ + assert(!_lines.empty()); + + const auto prev_pos = _cursor_pos; + _cursor_pos.line = std::min(_cursor_pos.line + amount, _lines.size() - 1); + + // The line after could be shorter, so adjust column + _cursor_pos.column = std::min(_cursor_pos.column, _lines[_cursor_pos.line].size()); + + if (prev_pos == _cursor_pos) + return; + + if (selection) + { + if (prev_pos == _interactive_beg) + _interactive_beg = _cursor_pos; + else if (prev_pos == _interactive_end) + _interactive_end = _cursor_pos; + else + _interactive_beg = prev_pos, + _interactive_end = _cursor_pos; + } + else + { + _interactive_beg = _cursor_pos; + _interactive_end = _cursor_pos; + } + + select(_interactive_beg, _interactive_end); + _scroll_to_cursor = true; +} +void imgui_code_editor::move_left(size_t amount, bool selection, bool word_mode) +{ + assert(!_lines.empty()); + + const auto prev_pos = _cursor_pos; + + // Move cursor to selection start when moving left and no longer selecting + if (!selection && _interactive_beg != _interactive_end) + { + _cursor_pos = _interactive_beg; + _interactive_end = _cursor_pos; + } + else + { + while (amount-- > 0) + { + if (_cursor_pos.column == 0) // At the beginning of the current line, so move on to previous + { + if (_cursor_pos.line == 0) + break; + + _cursor_pos.line--; + _cursor_pos.column = _lines[_cursor_pos.line].size(); + } + else if (word_mode) + { + for (const auto word_color = _lines[_cursor_pos.line][_cursor_pos.column - 1].col; _cursor_pos.column > 0; --_cursor_pos.column) + if (_lines[_cursor_pos.line][_cursor_pos.column - 1].col != word_color) + break; + } + else + { + _cursor_pos.column--; + } + } + + if (selection) + { + if (prev_pos == _interactive_beg) + _interactive_beg = _cursor_pos; + else if (prev_pos == _interactive_end) + _interactive_end = _cursor_pos; + else + _interactive_beg = _cursor_pos, + _interactive_end = prev_pos; + } + else + { + _interactive_beg = _cursor_pos; + _interactive_end = _cursor_pos; + } + } + + select(_interactive_beg, _interactive_end); + _scroll_to_cursor = true; +} +void imgui_code_editor::move_right(size_t amount, bool selection, bool word_mode) +{ + assert(!_lines.empty()); + + const auto prev_pos = _cursor_pos; + + while (amount-- > 0) + { + auto &line = _lines[_cursor_pos.line]; + + if (_cursor_pos.column >= line.size()) // At the end of the current line, so move on to next + { + if (_cursor_pos.line >= _lines.size() - 1) + break; // Reached end of input + + _cursor_pos.line++; + _cursor_pos.column = 0; + } + else if (word_mode) + { + for (const auto word_color = _lines[_cursor_pos.line][_cursor_pos.column].col; _cursor_pos.column < _lines[_cursor_pos.line].size(); ++_cursor_pos.column) + if (_lines[_cursor_pos.line][_cursor_pos.column].col != word_color) + break; + } + else + { + _cursor_pos.column++; + } + } + + if (selection) + { + if (prev_pos == _interactive_end) + _interactive_end = _cursor_pos; + else if (prev_pos == _interactive_beg) + _interactive_beg = _cursor_pos; + else + _interactive_beg = prev_pos, + _interactive_end = _cursor_pos; + } + else + { + _interactive_beg = _interactive_end = _cursor_pos; + } + + select(_interactive_beg, _interactive_end); + _scroll_to_cursor = true; +} +void imgui_code_editor::move_top(bool selection) +{ + assert(!_lines.empty()); + + const auto prev_pos = _cursor_pos; + _cursor_pos = text_pos(0, 0); + + if (prev_pos == _cursor_pos) + return; + + if (selection) + { + // This logic ensures the selection is updated depending on which direction it was created in (mimics behavior of popular text editors) + if (_interactive_beg > _interactive_end) + std::swap(_interactive_beg, _interactive_end); + if (prev_pos != _interactive_beg) + _interactive_end = _interactive_beg; + + _interactive_beg = _cursor_pos; + } + else + { + _interactive_beg = _cursor_pos; + _interactive_end = _cursor_pos; + } + + select(_interactive_beg, _interactive_end); + _scroll_to_cursor = true; +} +void imgui_code_editor::move_bottom(bool selection) +{ + assert(!_lines.empty()); + + const auto prev_pos = _cursor_pos; + _cursor_pos = text_pos(_lines.size() - 1, 0); + + if (prev_pos == _cursor_pos) + return; + + if (selection) + { + if (_interactive_beg > _interactive_end) + std::swap(_interactive_beg, _interactive_end); + if (prev_pos != _interactive_end) + _interactive_beg = _interactive_end; + + _interactive_end = _cursor_pos; + } + else + { + _interactive_beg = _cursor_pos; + _interactive_end = _cursor_pos; + } + + select(_interactive_beg, _interactive_end); + _scroll_to_cursor = true; +} +void imgui_code_editor::move_home(bool selection) +{ + assert(!_lines.empty()); + + const auto prev_pos = _cursor_pos; + _cursor_pos.column = 0; + + if (prev_pos == _cursor_pos && + _interactive_beg == _interactive_end) // This ensures that deselection works even when cursor is already at begin + return; + + if (selection) + { + if (prev_pos == _interactive_beg) + _interactive_beg = _cursor_pos; + else if (prev_pos == _interactive_end) + _interactive_end = _cursor_pos; + else + _interactive_beg = _cursor_pos, + _interactive_end = prev_pos; + } + else + { + _interactive_beg = _interactive_end = _cursor_pos; + } + + select(_interactive_beg, _interactive_end); + _scroll_to_cursor = true; +} +void imgui_code_editor::move_end(bool selection) +{ + assert(!_lines.empty()); + + const auto prev_pos = _cursor_pos; + _cursor_pos.column = _lines[_cursor_pos.line].size(); + + if (prev_pos == _cursor_pos && + _interactive_beg == _interactive_end) // This ensures that deselection works even when cursor is already at end + return; + + if (selection) + { + if (prev_pos == _interactive_end) + _interactive_end = _cursor_pos; + else if (prev_pos == _interactive_beg) + _interactive_beg = _cursor_pos; + else + _interactive_beg = prev_pos, + _interactive_end = _cursor_pos; + } + else + { + _interactive_beg = _interactive_end = _cursor_pos; + } + + select(_interactive_beg, _interactive_end); + _scroll_to_cursor = true; +} +void imgui_code_editor::move_lines_up() +{ + if (_select_beg.line == 0) + return; + + for (size_t line = _select_beg.line; line <= _select_end.line; ++line) + std::swap(_lines[line], _lines[line - 1]); + + _select_beg.line--; + _select_end.line--; + _cursor_pos.line--; +} +void imgui_code_editor::move_lines_down() +{ + if (_select_end.line + 1 >= _lines.size()) + return; + + for (size_t line = _select_end.line; line >= _select_beg.line && line < _lines.size(); --line) + std::swap(_lines[line], _lines[line + 1]); + + _select_beg.line++; + _select_end.line++; + _cursor_pos.line++; +} + +void imgui_code_editor::colorize() +{ + if (_colorize_line_beg >= _colorize_line_end) + return; + + // Step through code incrementally rather than coloring everything at once + const size_t from = _colorize_line_beg, to = std::min(from + 1000, _colorize_line_end); + _colorize_line_beg = to; + + // Reset coloring range if we have finished coloring it after this iteration + if (_colorize_line_beg == _colorize_line_end) + { + _colorize_line_beg = std::numeric_limits::max(); + _colorize_line_end = 0; + } + + // Copy lines into string for consumption by the lexer + std::string input_string; + for (size_t l = from; l < to && l < _lines.size(); ++l, input_string.push_back('\n')) + for (size_t k = 0; k < _lines[l].size(); ++k) + input_string.push_back(_lines[l][k].c); + + reshadefx::lexer lexer(input_string, false, true, false, true, false, true); + + for (reshadefx::token tok; (tok = lexer.lex()).id != reshadefx::tokenid::end_of_file;) + { + color col = color_default; + + switch (tok.id) + { + case reshadefx::tokenid::exclaim: + case reshadefx::tokenid::percent: + case reshadefx::tokenid::ampersand: + case reshadefx::tokenid::parenthesis_open: + case reshadefx::tokenid::parenthesis_close: + case reshadefx::tokenid::star: + case reshadefx::tokenid::plus: + case reshadefx::tokenid::comma: + case reshadefx::tokenid::minus: + case reshadefx::tokenid::dot: + case reshadefx::tokenid::slash: + case reshadefx::tokenid::colon: + case reshadefx::tokenid::semicolon: + case reshadefx::tokenid::less: + case reshadefx::tokenid::equal: + case reshadefx::tokenid::greater: + case reshadefx::tokenid::question: + case reshadefx::tokenid::bracket_open: + case reshadefx::tokenid::backslash: + case reshadefx::tokenid::bracket_close: + case reshadefx::tokenid::caret: + case reshadefx::tokenid::brace_open: + case reshadefx::tokenid::pipe: + case reshadefx::tokenid::brace_close: + case reshadefx::tokenid::tilde: + case reshadefx::tokenid::exclaim_equal: + case reshadefx::tokenid::percent_equal: + case reshadefx::tokenid::ampersand_ampersand: + case reshadefx::tokenid::ampersand_equal: + case reshadefx::tokenid::star_equal: + case reshadefx::tokenid::plus_plus: + case reshadefx::tokenid::plus_equal: + case reshadefx::tokenid::minus_minus: + case reshadefx::tokenid::minus_equal: + case reshadefx::tokenid::arrow: + case reshadefx::tokenid::ellipsis: + case reshadefx::tokenid::slash_equal: + case reshadefx::tokenid::colon_colon: + case reshadefx::tokenid::less_less_equal: + case reshadefx::tokenid::less_less: + case reshadefx::tokenid::less_equal: + case reshadefx::tokenid::equal_equal: + case reshadefx::tokenid::greater_greater_equal: + case reshadefx::tokenid::greater_greater: + case reshadefx::tokenid::greater_equal: + case reshadefx::tokenid::caret_equal: + case reshadefx::tokenid::pipe_equal: + case reshadefx::tokenid::pipe_pipe: + col = color_punctuation; + break; + case reshadefx::tokenid::identifier: + col = color_identifier; + break; + case reshadefx::tokenid::int_literal: + case reshadefx::tokenid::uint_literal: + case reshadefx::tokenid::float_literal: + case reshadefx::tokenid::double_literal: + col = color_number_literal; + break; + case reshadefx::tokenid::string_literal: + col = color_string_literal; + break; + case reshadefx::tokenid::true_literal: + case reshadefx::tokenid::false_literal: + case reshadefx::tokenid::namespace_: + case reshadefx::tokenid::struct_: + case reshadefx::tokenid::technique: + case reshadefx::tokenid::pass: + case reshadefx::tokenid::for_: + case reshadefx::tokenid::while_: + case reshadefx::tokenid::do_: + case reshadefx::tokenid::if_: + case reshadefx::tokenid::else_: + case reshadefx::tokenid::switch_: + case reshadefx::tokenid::case_: + case reshadefx::tokenid::default_: + case reshadefx::tokenid::break_: + case reshadefx::tokenid::continue_: + case reshadefx::tokenid::return_: + case reshadefx::tokenid::discard_: + case reshadefx::tokenid::extern_: + case reshadefx::tokenid::static_: + case reshadefx::tokenid::uniform_: + case reshadefx::tokenid::volatile_: + case reshadefx::tokenid::precise: + case reshadefx::tokenid::in: + case reshadefx::tokenid::out: + case reshadefx::tokenid::inout: + case reshadefx::tokenid::const_: + case reshadefx::tokenid::linear: + case reshadefx::tokenid::noperspective: + case reshadefx::tokenid::centroid: + case reshadefx::tokenid::nointerpolation: + case reshadefx::tokenid::void_: + case reshadefx::tokenid::bool_: + case reshadefx::tokenid::bool2: + case reshadefx::tokenid::bool3: + case reshadefx::tokenid::bool4: + case reshadefx::tokenid::bool2x2: + case reshadefx::tokenid::bool3x3: + case reshadefx::tokenid::bool4x4: + case reshadefx::tokenid::int_: + case reshadefx::tokenid::int2: + case reshadefx::tokenid::int3: + case reshadefx::tokenid::int4: + case reshadefx::tokenid::int2x2: + case reshadefx::tokenid::int3x3: + case reshadefx::tokenid::int4x4: + case reshadefx::tokenid::uint_: + case reshadefx::tokenid::uint2: + case reshadefx::tokenid::uint3: + case reshadefx::tokenid::uint4: + case reshadefx::tokenid::uint2x2: + case reshadefx::tokenid::uint3x3: + case reshadefx::tokenid::uint4x4: + case reshadefx::tokenid::float_: + case reshadefx::tokenid::float2: + case reshadefx::tokenid::float3: + case reshadefx::tokenid::float4: + case reshadefx::tokenid::float2x2: + case reshadefx::tokenid::float3x3: + case reshadefx::tokenid::float4x4: + case reshadefx::tokenid::vector: + case reshadefx::tokenid::matrix: + case reshadefx::tokenid::string_: + case reshadefx::tokenid::texture: + case reshadefx::tokenid::sampler: + col = color_keyword; + break; + case reshadefx::tokenid::hash_def: + case reshadefx::tokenid::hash_undef: + case reshadefx::tokenid::hash_if: + case reshadefx::tokenid::hash_ifdef: + case reshadefx::tokenid::hash_ifndef: + case reshadefx::tokenid::hash_else: + case reshadefx::tokenid::hash_elif: + case reshadefx::tokenid::hash_endif: + case reshadefx::tokenid::hash_error: + case reshadefx::tokenid::hash_warning: + case reshadefx::tokenid::hash_pragma: + case reshadefx::tokenid::hash_include: + case reshadefx::tokenid::hash_unknown: + col = color_preprocessor; + tok.offset--; // Add # to token + tok.length++; + break; + case reshadefx::tokenid::single_line_comment: + col = color_comment; + break; + case reshadefx::tokenid::multi_line_comment: + col = color_multiline_comment; + break; + } + + // Update character range matching the current the token + size_t line = from + tok.location.line - 1; + size_t column = tok.location.column - 1; + + for (size_t k = 0; k < tok.length; ++k) + { + if (column >= _lines[line].size()) + { + line++; + column = 0; + continue; + } + + _lines[line][column++].col = col; + } + } +} diff --git a/msvc/source/gui_code_editor.hpp b/msvc/source/gui_code_editor.hpp new file mode 100644 index 0000000..8160bdc --- /dev/null +++ b/msvc/source/gui_code_editor.hpp @@ -0,0 +1,165 @@ +/** + * Copyright (C) 2014 Patrick Mours. All rights reserved. + * License: https://github.com/crosire/reshade#license + */ + +#pragma once + +#include +#include +#include +#include + +class imgui_code_editor +{ +public: + struct text_pos + { + size_t line, column; + + text_pos() : line(0), column(0) {} + text_pos(size_t line, size_t column = 0) : line(line), column(column) {} + + bool operator ==(const text_pos& o) const { return line == o.line && column == o.column; } + bool operator !=(const text_pos& o) const { return line != o.line || column != o.column; } + bool operator < (const text_pos& o) const { return line != o.line ? line < o.line : column < o.column; } + bool operator <=(const text_pos& o) const { return line != o.line ? line < o.line : column <= o.column; } + bool operator > (const text_pos& o) const { return line != o.line ? line > o.line : column > o.column; } + bool operator >=(const text_pos& o) const { return line != o.line ? line > o.line : column >= o.column; } + }; + + enum color + { + color_default, + color_keyword, + color_number_literal, + color_string_literal, + color_punctuation, + color_preprocessor, + color_identifier, + color_known_identifier, + color_preprocessor_identifier, + color_comment, + color_multiline_comment, + color_background, + color_cursor, + color_selection, + color_error_marker, + color_warning_marker, + color_line_number, + color_current_line_fill, + color_current_line_fill_inactive, + color_current_line_edge, + + color_palette_max + }; + + enum class selection_mode + { + normal, + word, + line + }; + + imgui_code_editor(); + + void render(const char *title, bool border = false); + + void select(const text_pos &beg, const text_pos &end, selection_mode mode = selection_mode::normal); + void select_all(); + bool has_selection() const { return _select_end > _select_beg; } + + void set_text(const std::string &text); + void clear_text(); + void insert_text(const std::string &text); + std::string get_text() const; + std::string get_text(const text_pos &beg, const text_pos &end) const; + std::string get_selected_text() const; + + void undo(unsigned int steps = 1); + bool can_undo() const { return _undo_index > 0; } + void redo(unsigned int steps = 1); + bool can_redo() const { return _undo_index < _undo.size(); } + + void add_error(size_t line, const std::string &message, bool warning = false) { _errors.insert({ line, { message, warning } }); } + void clear_errors() { _errors.clear(); } + + void set_tab_size(unsigned int size) { _tab_size = size; } + void set_left_margin(float margin) { _left_margin = margin; } + void set_line_spacing(float spacing) { _line_spacing = spacing; } + + void set_palette(const std::array &palette) { _palette = palette; } + uint32_t &get_palette_index(unsigned int index) { return _palette[index]; } + static const char *get_palette_color_name(unsigned int index); + +private: + struct glyph + { + char c = '\0'; + color col = color_default; + }; + + struct undo_record + { + text_pos added_beg; + text_pos added_end; + std::string added; + text_pos removed_beg; + text_pos removed_end; + std::string removed; + }; + + void record_undo(undo_record &&record); + + void insert_character(char c, bool auto_indent); + + void delete_next(); + void delete_previous(); + void delete_selection(); + void delete_lines(size_t first_line, size_t last_line); + + void clipboard_copy(); + void clipboard_cut(); + void clipboard_paste(); + + void move_up(size_t amount = 1, bool select = false); + void move_down(size_t amount = 1, bool select = false); + void move_left(size_t amount = 1, bool select = false, bool word_mode = false); + void move_right(size_t amount = 1, bool select = false, bool word_mode = false); + void move_top(bool select = false); + void move_bottom(bool select = false); + void move_home(bool select = false); + void move_end(bool select = false); + void move_lines_up(); + void move_lines_down(); + + void colorize(); + + float _left_margin = 10.0f; + float _line_spacing = 1.0f; + unsigned int _tab_size = 4; + bool _overwrite = false; + bool _scroll_to_cursor = false; + float _cursor_anim = 0.0f; + double _last_click_time = -1.0; + + std::array _palette; + + std::vector> _lines; + + text_pos _cursor_pos; + text_pos _select_beg; + text_pos _select_end; + text_pos _interactive_beg; + text_pos _interactive_end; + std::string _highlighted; + + size_t _undo_index = 0; + std::vector _undo; + bool _in_undo_operation = false; + + size_t _colorize_line_beg = 0; + size_t _colorize_line_end = 0; + + std::unordered_map> _errors; +}; diff --git a/msvc/source/gui_widgets.cpp b/msvc/source/gui_widgets.cpp new file mode 100644 index 0000000..a283555 --- /dev/null +++ b/msvc/source/gui_widgets.cpp @@ -0,0 +1,512 @@ +/** + * Copyright (C) 2014 Patrick Mours. All rights reserved. + * License: https://github.com/crosire/reshade#license + */ + +#include "input.hpp" +#include "gui_widgets.hpp" +#include +#include + +bool imgui_key_input(const char *name, unsigned int key_data[4], const reshade::input &input) +{ + bool res = false; + + char buf[48] = ""; + reshade::input::key_name(key_data).copy(buf, sizeof(buf) - 1); + + ImGui::InputText(name, buf, sizeof(buf), ImGuiInputTextFlags_ReadOnly); + + if (ImGui::IsItemActive()) + { + const unsigned int last_key_pressed = input.last_key_pressed(); + + if (last_key_pressed != 0) + { + if (last_key_pressed == 0x08) // Backspace + { + key_data[0] = 0; + key_data[1] = 0; + key_data[2] = 0; + key_data[3] = 0; + } + else if (last_key_pressed < 0x10 || last_key_pressed > 0x12) + { + key_data[0] = last_key_pressed; + key_data[1] = input.is_key_down(0x11); + key_data[2] = input.is_key_down(0x10); + key_data[3] = input.is_key_down(0x12); + } + + res = true; + } + } + else if (ImGui::IsItemHovered()) + { + ImGui::SetTooltip("Click in the field and press any key to change the shortcut to that key."); + } + + return res; +} + +bool imgui_font_select(const char *name, std::filesystem::path &path, int &size) +{ + bool res = false; + const float spacing = ImGui::GetStyle().ItemInnerSpacing.x; + + ImGui::BeginGroup(); + ImGui::PushID(name); + + char buf[260] = ""; + if (path.empty()) + strcpy(buf, "ProggyClean.ttf"); + else + path.u8string().copy(buf, sizeof(buf) - 1); + + ImGui::PushItemWidth(ImGui::CalcItemWidth() - spacing - 80); + if (ImGui::InputText("##font", buf, sizeof(buf))) + { + std::error_code ec; + const std::filesystem::path new_path = std::filesystem::u8path(buf); + + if ((new_path.empty() || new_path == "ProggyClean.ttf") || (std::filesystem::is_regular_file(new_path, ec) && new_path.extension() == ".ttf")) + { + res = true; + path = new_path; + } + } + ImGui::PopItemWidth(); + + ImGui::SameLine(0, spacing); + ImGui::PushItemWidth(80); + if (ImGui::SliderInt("##size", &size, 8, 32)) + res = true; + ImGui::PopItemWidth(); + + ImGui::PopID(); + + ImGui::SameLine(0, spacing); + ImGui::TextUnformatted(name); + + ImGui::EndGroup(); + + return res; +} + +bool imgui_directory_dialog(const char *name, std::filesystem::path &path) +{ + bool ok = false, cancel = false; + + if (!ImGui::BeginPopup(name)) + return false; + + char buf[_MAX_PATH] = ""; + path.u8string().copy(buf, sizeof(buf) - 1); + + ImGui::PushItemWidth(400); + if (ImGui::InputText("##path", buf, sizeof(buf))) + path = std::filesystem::u8path(buf); + ImGui::PopItemWidth(); + + ImGui::SameLine(); + if (ImGui::Button("Ok", ImVec2(100, 0))) + ok = true; + ImGui::SameLine(); + if (ImGui::Button("Cancel", ImVec2(100, 0))) + cancel = true; + + ImGui::BeginChild("##files", ImVec2(0, 300)); + + std::error_code ec; + + if (path.is_relative()) + path = std::filesystem::absolute(path); + if (!std::filesystem::is_directory(path, ec)) + path.remove_filename(); + else if (!path.has_filename()) + path = path.parent_path(); + + if (ImGui::Selectable(".")) + ok = true; + if (path.has_parent_path() && ImGui::Selectable("..")) + path = path.parent_path(); + + for (const auto &entry : std::filesystem::directory_iterator(path, ec)) + { + if (!entry.is_directory(ec)) + continue; // Only show directories + + std::string label = entry.path().filename().u8string(); + label = " " + label; + + if (ImGui::Selectable(label.c_str(), entry.path() == path)) + { + path = entry.path(); + break; + } + } + + ImGui::EndChild(); + + if (ok || cancel) + ImGui::CloseCurrentPopup(); + + ImGui::EndPopup(); + + return ok; +} + +bool imgui_directory_input_box(const char *name, std::filesystem::path &path, std::filesystem::path &dialog_path) +{ + bool res = false; + const float button_size = ImGui::GetFrameHeight(); + const float button_spacing = ImGui::GetStyle().ItemInnerSpacing.x; + + ImGui::PushID(name); + ImGui::BeginGroup(); + + char buf[_MAX_PATH] = ""; + path.u8string().copy(buf, sizeof(buf) - 1); + + ImGui::PushItemWidth(ImGui::CalcItemWidth() - (button_spacing + button_size)); + if (ImGui::InputText("##path", buf, sizeof(buf))) + path = std::filesystem::u8path(buf), res = true; + ImGui::PopItemWidth(); + + ImGui::SameLine(0, button_spacing); + if (ImGui::Button("..", ImVec2(button_size, 0))) + dialog_path = path, ImGui::OpenPopup("##select"); + + ImGui::SameLine(0, button_spacing); + ImGui::TextUnformatted(name); + ImGui::EndGroup(); + + if (imgui_directory_dialog("##select", dialog_path)) + path = dialog_path, res = true; + + ImGui::PopID(); + + return res; +} + +bool imgui_path_list(const char *label, std::vector &paths, std::filesystem::path &dialog_path, const std::filesystem::path &default_path) +{ + bool res = false; + const float item_width = ImGui::CalcItemWidth(); + const float item_height = ImGui::GetFrameHeightWithSpacing(); + const float button_size = ImGui::GetFrameHeight(); + const float button_spacing = ImGui::GetStyle().ItemInnerSpacing.x; + + ImGui::BeginGroup(); + ImGui::PushID(label); + + char buf[_MAX_PATH]; + + if (ImGui::BeginChild("##paths", ImVec2(item_width, (paths.size() + 1) * item_height), false, ImGuiWindowFlags_NoScrollWithMouse)) + { + for (size_t i = 0; i < paths.size(); ++i) + { + ImGui::PushID(static_cast(i)); + + memset(buf, 0, sizeof(buf)); + paths[i].u8string().copy(buf, sizeof(buf) - 1); + + ImGui::PushItemWidth(ImGui::GetWindowContentRegionWidth() - (button_spacing + button_size)); + if (ImGui::InputText("##path", buf, sizeof(buf))) + { + res = true; + paths[i] = std::filesystem::u8path(buf); + } + ImGui::PopItemWidth(); + + ImGui::SameLine(0, button_spacing); + if (ImGui::Button("-", ImVec2(button_size, 0))) + { + res = true; + paths.erase(paths.begin() + i--); + } + + ImGui::PopID(); + } + + ImGui::Dummy(ImVec2(0, 0)); + ImGui::SameLine(0, ImGui::GetWindowContentRegionWidth() - button_size); + if (ImGui::Button("+", ImVec2(button_size, 0))) + { + dialog_path = default_path; + ImGui::OpenPopup("##select"); + } + + if (imgui_directory_dialog("##select", dialog_path)) + { + res = true; + paths.push_back(dialog_path); + } + } + + ImGui::EndChild(); + + ImGui::PopID(); + + ImGui::SameLine(0, button_spacing); + ImGui::TextUnformatted(label); + + ImGui::EndGroup(); + + return res; +} + +bool imgui_popup_button(const char *label, float width, ImGuiWindowFlags flags) +{ + if (ImGui::Button(label, ImVec2(width, 0))) + ImGui::OpenPopup(label); // Popups can have the same ID as other items without conflict + return ImGui::BeginPopup(label, flags); +} + +bool imgui_list_with_buttons(const char *label, const std::string_view ui_items, int &v) +{ + const auto imgui_context = ImGui::GetCurrentContext(); + + bool modified = false; + + std::vector items; + if (!ui_items.empty()) + for (std::string_view item = ui_items.data(); ui_items.data() + ui_items.size() > item.data(); + item = item.data() + item.size() + 1) + items.push_back(item); + + ImGui::BeginGroup(); + + const float button_size = ImGui::GetFrameHeight(); + const float button_spacing = imgui_context->Style.ItemInnerSpacing.x; + const ImVec2 hover_pos = ImGui::GetCursorScreenPos() + ImVec2(0, button_size); + + ImGui::PushItemWidth(ImGui::CalcItemWidth() - (button_spacing * 2 + button_size * 2)); + + if (ImGui::BeginCombo("##v", items.size() > static_cast(v) && v >= 0 ? items[v].data() : nullptr, ImGuiComboFlags_NoArrowButton)) + { + auto it = items.begin(); + for (size_t i = 0; it != items.end(); ++it, ++i) + { + bool selected = v == static_cast(i); + if (ImGui::Selectable(it->data(), &selected)) + v = static_cast(i), modified = true; + if (selected) + ImGui::SetItemDefaultFocus(); + } + + ImGui::EndCombo(); + } + + ImGui::PopItemWidth(); + + ImGui::SameLine(0, button_spacing); + if (ImGui::ButtonEx("<", ImVec2(button_size, 0), items.size() > 0 ? 0 : ImGuiButtonFlags_Disabled)) + { + modified = true; + if (v == 0) v = static_cast(items.size() - 1); + else --v; + } + + ImGui::SameLine(0, button_spacing); + if (ImGui::ButtonEx(">", ImVec2(button_size, 0), items.size() > 0 ? 0 : ImGuiButtonFlags_Disabled)) + { + modified = true; + if (v == static_cast(items.size()) - 1) v = 0; + else ++v; + } + + ImGui::EndGroup(); + const bool is_hovered = ImGui::IsItemHovered(); + + ImGui::SameLine(0, button_spacing); + ImGui::TextUnformatted(label); + + if (is_hovered) + { + ImGui::SetNextWindowPos(hover_pos); + ImGui::SetNextWindowSizeConstraints(ImVec2(ImGui::CalcItemWidth(), 0.0f), ImVec2(FLT_MAX, (imgui_context->FontSize + imgui_context->Style.ItemSpacing.y) * 8 - imgui_context->Style.ItemSpacing.y + (imgui_context->Style.WindowPadding.y * 2))); // 8 by ImGuiComboFlags_HeightRegular + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(imgui_context->Style.FramePadding.x, imgui_context->Style.WindowPadding.y)); + ImGui::Begin("##spinner_items", NULL, ImGuiWindowFlags_NoInputs | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoDocking); + ImGui::PopStyleVar(); + + auto it = items.begin(); + for (size_t i = 0; it != items.end(); ++it, ++i) + { + bool selected = v == static_cast(i); + ImGui::Selectable(it->data(), &selected); + if (selected) + ImGui::SetScrollHereY(); + } + + ImGui::End(); + } + + return modified; +} + +template +bool imgui_drag_with_buttons(const char *label, T *v, int components, T v_speed, T v_min, T v_max, const char *format) +{ + const float button_size = ImGui::GetFrameHeight(); + const float button_spacing = ImGui::GetStyle().ItemInnerSpacing.x; + + ImGui::BeginGroup(); + ImGui::PushID(label); + + ImGui::PushItemWidth(ImGui::CalcItemWidth() - (button_spacing * 2 + button_size * 2)); + bool value_changed = ImGui::DragScalarN("##v", data_type, v, components, static_cast(v_speed), &v_min, &v_max, format); + ImGui::PopItemWidth(); + + ImGui::SameLine(0, 0); + if (ImGui::ButtonEx("<", ImVec2(button_size, 0), ImGuiButtonFlags_PressedOnClick | ImGuiButtonFlags_Repeat) && v[0] > v_min) + { + for (int c = 0; c < components; ++c) + v[c] -= v_speed; + value_changed = true; + } + ImGui::SameLine(0, button_spacing); + if (ImGui::ButtonEx(">", ImVec2(button_size, 0), ImGuiButtonFlags_PressedOnClick | ImGuiButtonFlags_Repeat) && v[0] < v_max) + { + for (int c = 0; c < components; ++c) + v[c] += v_speed; + value_changed = true; + } + + ImGui::PopID(); + + ImGui::SameLine(0, button_spacing); + ImGui::TextUnformatted(label); + + ImGui::EndGroup(); + + return value_changed; + +} +bool imgui_drag_with_buttons(const char *label, ImGuiDataType data_type, void *v, int components, const void *v_speed, const void *v_min, const void *v_max, const char *format) +{ + switch (data_type) + { + default: + return false; + case ImGuiDataType_S32: + return imgui_drag_with_buttons(label, static_cast(v), components, *static_cast(v_speed), *static_cast(v_min), *static_cast(v_max), format); + case ImGuiDataType_U32: + return imgui_drag_with_buttons(label, static_cast(v), components, *static_cast(v_speed), *static_cast(v_min), *static_cast(v_max), format); + case ImGuiDataType_S64: + return imgui_drag_with_buttons(label, static_cast(v), components, *static_cast(v_speed), *static_cast(v_min), *static_cast(v_max), format); + case ImGuiDataType_U64: + return imgui_drag_with_buttons(label, static_cast(v), components, *static_cast(v_speed), *static_cast(v_min), *static_cast(v_max), format); + case ImGuiDataType_Float: + return imgui_drag_with_buttons(label, static_cast(v), components, *static_cast(v_speed), *static_cast(v_min), *static_cast(v_max), format); + case ImGuiDataType_Double: + return imgui_drag_with_buttons(label, static_cast(v), components, *static_cast(v_speed), *static_cast(v_min), *static_cast(v_max), format); + } +} + +template +bool imgui_slider_with_buttons(const char *label, T *v, int components, T v_speed, T v_min, T v_max, const char *format) +{ + const float button_size = ImGui::GetFrameHeight(); + const float button_spacing = ImGui::GetStyle().ItemInnerSpacing.x; + + ImGui::BeginGroup(); + ImGui::PushID(label); + + ImGui::PushItemWidth(ImGui::CalcItemWidth() - (button_spacing * 2 + button_size * 2)); + bool value_changed = ImGui::SliderScalarN("##v", data_type, v, components, &v_min, &v_max, format); + ImGui::PopItemWidth(); + + ImGui::SameLine(0, 0); + if (ImGui::ButtonEx("<", ImVec2(button_size, 0), ImGuiButtonFlags_PressedOnClick | ImGuiButtonFlags_Repeat) && v[0] > v_min) + { + for (int c = 0; c < components; ++c) + v[c] -= v_speed; + value_changed = true; + } + ImGui::SameLine(0, button_spacing); + if (ImGui::ButtonEx(">", ImVec2(button_size, 0), ImGuiButtonFlags_PressedOnClick | ImGuiButtonFlags_Repeat) && v[0] < v_max) + { + for (int c = 0; c < components; ++c) + v[c] += v_speed; + value_changed = true; + } + + ImGui::PopID(); + + ImGui::SameLine(0, button_spacing); + ImGui::TextUnformatted(label); + + ImGui::EndGroup(); + + return value_changed; + +} +bool imgui_slider_with_buttons(const char *label, ImGuiDataType data_type, void *v, int components, const void *v_speed, const void *v_min, const void *v_max, const char *format) +{ + switch (data_type) + { + default: + return false; + case ImGuiDataType_S32: + return imgui_slider_with_buttons(label, static_cast(v), components, *static_cast(v_speed), *static_cast(v_min), *static_cast(v_max), format); + case ImGuiDataType_U32: + return imgui_slider_with_buttons(label, static_cast(v), components, *static_cast(v_speed), *static_cast(v_min), *static_cast(v_max), format); + case ImGuiDataType_S64: + return imgui_slider_with_buttons(label, static_cast(v), components, *static_cast(v_speed), *static_cast(v_min), *static_cast(v_max), format); + case ImGuiDataType_U64: + return imgui_slider_with_buttons(label, static_cast(v), components, *static_cast(v_speed), *static_cast(v_min), *static_cast(v_max), format); + case ImGuiDataType_Float: + return imgui_slider_with_buttons(label, static_cast(v), components, *static_cast(v_speed), *static_cast(v_min), *static_cast(v_max), format); + case ImGuiDataType_Double: + return imgui_slider_with_buttons(label, static_cast(v), components, *static_cast(v_speed), *static_cast(v_min), *static_cast(v_max), format); + } +} + +bool imgui_slider_for_alpha(const char *label, float *v) +{ + const float button_size = ImGui::GetFrameHeight(); + const float button_spacing = ImGui::GetStyle().ItemInnerSpacing.x; + + ImGui::BeginGroup(); + ImGui::PushID(label); + + ImGui::PushItemWidth(ImGui::CalcItemWidth() - button_spacing - button_size); + bool value_changed = ImGui::SliderFloat("##v", v, 0.0f, 1.0f); + ImGui::PopItemWidth(); + + ImGui::SameLine(0, button_spacing); + ImGui::ColorButton("##preview", ImVec4(1.0f, 1.0f, 1.0f, *v), ImGuiColorEditFlags_NoOptions | ImGuiColorEditFlags_AlphaPreview | ImGuiColorEditFlags_NoPicker); + + ImGui::PopID(); + + ImGui::SameLine(0, button_spacing); + ImGui::TextUnformatted(label); + + ImGui::EndGroup(); + + return value_changed; + +} + +void imgui_image_with_checkerboard_background(ImTextureID user_texture_id, const ImVec2 &size) +{ + const auto draw_list = ImGui::GetWindowDrawList(); + + // Render background checkerboard pattern + const ImVec2 pos_min = ImGui::GetCursorScreenPos(); + const ImVec2 pos_max = ImVec2(pos_min.x + size.x, pos_min.y + size.y); int yi = 0; + + for (float y = pos_min.y, grid_size = 25.0f; y < pos_max.y; y += grid_size, yi++) + { + const float y1 = std::min(y, pos_max.y), y2 = std::min(y + grid_size, pos_max.y); + for (float x = pos_min.x + (yi & 1) * grid_size; x < pos_max.x; x += grid_size * 2.0f) + { + const float x1 = std::min(x, pos_max.x), x2 = std::min(x + grid_size, pos_max.x); + draw_list->AddRectFilled(ImVec2(x1, y1), ImVec2(x2, y2), IM_COL32(128, 128, 128, 255)); + } + } + + // Add image on top + ImGui::Image(user_texture_id, size); +} diff --git a/msvc/source/gui_widgets.hpp b/msvc/source/gui_widgets.hpp new file mode 100644 index 0000000..cd25bb8 --- /dev/null +++ b/msvc/source/gui_widgets.hpp @@ -0,0 +1,31 @@ +/** + * Copyright (C) 2014 Patrick Mours. All rights reserved. + * License: https://github.com/crosire/reshade#license + */ + +#pragma once + +#include +#include + +bool imgui_key_input(const char *name, unsigned int key_data[4], const reshade::input &input); + +bool imgui_font_select(const char *name, std::filesystem::path &path, int &size); + +bool imgui_directory_dialog(const char *name, std::filesystem::path &path); + +bool imgui_directory_input_box(const char *name, std::filesystem::path &path, std::filesystem::path &dialog_path); + +bool imgui_path_list(const char *label, std::vector &paths, std::filesystem::path &dialog_path, const std::filesystem::path &default_path = std::filesystem::path()); + +bool imgui_popup_button(const char *label, float width = 0.0f, ImGuiWindowFlags flags = 0); + +bool imgui_list_with_buttons(const char *label, const std::string_view ui_items, int &v); + +bool imgui_drag_with_buttons(const char *label, ImGuiDataType data_type, void *v, int components, const void *v_speed, const void *v_min, const void *v_max, const char *format = nullptr); + +bool imgui_slider_with_buttons(const char *label, ImGuiDataType data_type, void *v, int components, const void *v_speed, const void *v_min, const void *v_max, const char *format = nullptr); + +bool imgui_slider_for_alpha(const char *label, float *v); + +void imgui_image_with_checkerboard_background(ImTextureID user_texture_id, const ImVec2 &size); diff --git a/msvc/source/hook.cpp b/msvc/source/hook.cpp new file mode 100644 index 0000000..369647b --- /dev/null +++ b/msvc/source/hook.cpp @@ -0,0 +1,74 @@ +/** + * Copyright (C) 2014 Patrick Mours. All rights reserved. + * License: https://github.com/crosire/reshade#license + */ + +#include "hook.hpp" +#include +#include + +static unsigned long s_reference_count = 0; + +void reshade::hook::enable(bool enable) const +{ + if (enable) + MH_QueueEnableHook(target); + else + MH_QueueDisableHook(target); +} + +reshade::hook::status reshade::hook::install() +{ + if (!valid()) + return hook::status::unsupported_function; + + // Only leave MinHook active as long as any hooks exist + if (s_reference_count++ == 0) + MH_Initialize(); + + const MH_STATUS statuscode = MH_CreateHook(target, replacement, &trampoline); + + if (statuscode == MH_OK || statuscode == MH_ERROR_ALREADY_CREATED) + { + // Installation was successful, so enable the hook and return + enable(true); + + return hook::status::success; + } + + if (--s_reference_count == 0) + MH_Uninitialize(); + + return static_cast(statuscode); +} +reshade::hook::status reshade::hook::uninstall() +{ + if (!valid()) + return hook::status::unsupported_function; + + const MH_STATUS statuscode = MH_RemoveHook(target); + + if (statuscode == MH_ERROR_NOT_CREATED) + return hook::status::success; // If the hook was never created, then uninstallation is implicitly successful + else if (statuscode != MH_OK) + return static_cast(statuscode); + + trampoline = nullptr; + + if (--s_reference_count == 0) + MH_Uninitialize(); // If this was the last active hook, MinHook can now be uninialized, since no more are active + + return hook::status::success; +} + +reshade::hook::address reshade::hook::call() const +{ + assert(installed()); + + return trampoline; +} + +bool reshade::hook::apply_queued_actions() +{ + return MH_ApplyQueued() == MH_OK; +} diff --git a/msvc/source/hook.hpp b/msvc/source/hook.hpp new file mode 100644 index 0000000..35399aa --- /dev/null +++ b/msvc/source/hook.hpp @@ -0,0 +1,73 @@ +/** + * Copyright (C) 2014 Patrick Mours. All rights reserved. + * License: https://github.com/crosire/reshade#license + */ + +#pragma once + +namespace reshade +{ + struct hook + { + /// + /// Type which holds the address of a function. + /// + using address = void *; + + /// + /// Enumeration of status codes returned by the hook installation functions. + /// + enum class status + { + unknown = -1, + success, + not_executable = 7, + unsupported_function, + allocation_failure, + memory_protection_failure, + }; + + /// + /// Actually enable or disable any queued hooks. + /// + static bool apply_queued_actions(); + + /// + /// Returns whether this hook is valid. + /// + bool valid() const { return target != nullptr && replacement != nullptr && target != replacement; } + /// + /// Returns whether this hook is currently installed. + /// + bool installed() const { return trampoline != nullptr; } + /// + /// Returns whether this hook is not currently installed. + /// + bool uninstalled() const { return trampoline == nullptr; } + + /// + /// Enable or disable this hook. This queues the action for later execution in . + /// + /// Boolean indicating if hook should be enabled or disabled. + void enable(bool enable) const; + /// + /// Install this hook. + /// + hook::status install(); + /// + /// Uninstall this hook. + /// + hook::status uninstall(); + + /// + /// Returns the trampoline function address of the hook. + /// + address call() const; + template + T call() const { return reinterpret_cast(call()); } + + address target = nullptr; + address trampoline = nullptr; + address replacement = nullptr; + }; +} diff --git a/msvc/source/hook_manager.cpp b/msvc/source/hook_manager.cpp new file mode 100644 index 0000000..a40e935 --- /dev/null +++ b/msvc/source/hook_manager.cpp @@ -0,0 +1,463 @@ +/** + * Copyright (C) 2014 Patrick Mours. All rights reserved. + * License: https://github.com/crosire/reshade#license + */ + +#include "log.hpp" +#include "hook_manager.hpp" +#include +#include +#include +#include +#include +#include +#include + +enum class hook_method +{ + export_hook, + function_hook, + vtable_hook +}; + +struct module_export +{ + reshade::hook::address address; + const char *name; + unsigned short ordinal; +}; + +extern HMODULE g_module_handle; +extern std::filesystem::path g_reshade_dll_path; +static std::filesystem::path s_export_hook_path; +static std::vector s_delayed_hook_paths; +static std::vector> s_hooks; +static std::mutex s_mutex_hooks; +static std::mutex s_mutex_delayed_hook_paths; + +std::vector get_module_exports(HMODULE handle) +{ + const auto imagebase = reinterpret_cast(handle); + const auto imageheader = reinterpret_cast(imagebase + + reinterpret_cast(imagebase)->e_lfanew); + + if (imageheader->Signature != IMAGE_NT_SIGNATURE || + imageheader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].Size == 0) + return {}; + + const auto exportdir = reinterpret_cast(imagebase + + imageheader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress); + const auto exportbase = static_cast(exportdir->Base); + + if (exportdir->NumberOfFunctions == 0) + return {}; + + std::vector exports; + exports.reserve(exportdir->NumberOfNames); + + for (size_t i = 0; i < exports.capacity(); i++) + { + module_export symbol; + symbol.ordinal = reinterpret_cast(imagebase + + exportdir->AddressOfNameOrdinals)[i] + exportbase; + symbol.name = reinterpret_cast(imagebase + + reinterpret_cast(imagebase + exportdir->AddressOfNames)[i]); + symbol.address = const_cast(reinterpret_cast(imagebase + + reinterpret_cast(imagebase + exportdir->AddressOfFunctions)[symbol.ordinal - exportbase])); + + exports.push_back(std::move(symbol)); + } + + return exports; +} + +static bool install_internal(const char *name, reshade::hook &hook, hook_method method) +{ + // It does not make sense to install a hook which points to itself, so avoid that + if (hook.target == hook.replacement) + return false; + +#if RESHADE_VERBOSE_LOG + LOG(DEBUG) << "Installing hook for " << name << " at 0x" << hook.target << " with 0x" << hook.replacement << " using method " << static_cast(method) << " ..."; +#endif + auto status = reshade::hook::status::unknown; + + switch (method) + { + case hook_method::export_hook: + status = reshade::hook::status::success; + break; + case hook_method::function_hook: + status = hook.install(); + break; + case hook_method::vtable_hook: + if (DWORD protection = PAGE_READWRITE; + VirtualProtect(hook.target, sizeof(reshade::hook::address), protection, &protection)) + { + *reinterpret_cast(hook.target) = hook.replacement; + + VirtualProtect(hook.target, sizeof(reshade::hook::address), protection, &protection); + + status = reshade::hook::status::success; + } + else + { + status = reshade::hook::status::memory_protection_failure; + } + break; + } + + if (status != reshade::hook::status::success) + { + LOG(ERROR) << "Failed to install hook for " << name << " with status code " << static_cast(status) << '.'; + return false; + } + + { const std::lock_guard lock(s_mutex_hooks); + s_hooks.push_back(std::make_tuple(name, hook, method)); + } + +#if RESHADE_VERBOSE_LOG + LOG(DEBUG) << "> Succeeded."; +#endif + + return true; +} +static bool install_internal(HMODULE target_module, HMODULE replacement_module, hook_method method) +{ + assert(target_module != nullptr && replacement_module != nullptr); + assert(target_module != replacement_module); + + // Load export tables + const auto target_exports = get_module_exports(target_module); + const auto replacement_exports = get_module_exports(replacement_module); + + if (target_exports.empty()) + { + LOG(INFO) << "> Empty export table! Skipped."; + return false; + } + + size_t install_count = 0; + std::vector> matches; + matches.reserve(replacement_exports.size()); + +#if RESHADE_VERBOSE_LOG + LOG(DEBUG) << "> Dumping matches in export table:"; + LOG(DEBUG) << " +--------------------+---------+----------------------------------------------------+"; + LOG(DEBUG) << " | Address | Ordinal | Name |"; + LOG(DEBUG) << " +--------------------+---------+----------------------------------------------------+"; +#endif + + // Analyze export table + for (const auto &symbol : target_exports) + { + if (symbol.name == nullptr || symbol.address == nullptr) + continue; + + // Find appropriate replacement + const auto it = std::find_if(replacement_exports.cbegin(), replacement_exports.cend(), + [&symbol](const auto &moduleexport) { + return std::strcmp(moduleexport.name, symbol.name) == 0; + }); + + // Filter uninteresting functions + if (it != replacement_exports.cend() && + std::strcmp(symbol.name, "DXGIReportAdapterConfiguration") != 0 && + std::strcmp(symbol.name, "DXGIDumpJournal") != 0) + { +#if RESHADE_VERBOSE_LOG + LOG(DEBUG) << " | 0x" << std::setw(16) << symbol.address << " | " << std::setw(7) << symbol.ordinal << " | " << std::setw(50) << symbol.name << " |"; +#endif + matches.push_back(std::make_tuple(symbol.name, symbol.address, it->address)); + } + } + +#if RESHADE_VERBOSE_LOG + LOG(DEBUG) << " +--------------------+---------+----------------------------------------------------+"; +#endif + LOG(INFO) << "> Found " << matches.size() << " match(es). Installing ..."; + + // Hook matching exports + for (const auto &match : matches) + { + reshade::hook hook; + hook.target = std::get<1>(match); + hook.trampoline = hook.target; + hook.replacement = std::get<2>(match); + + if (install_internal(std::get<0>(match), hook, method)) + install_count++; + } + + return install_count != 0; +} +static bool uninstall_internal(const char *name, reshade::hook &hook, hook_method method) +{ +#if RESHADE_VERBOSE_LOG + LOG(DEBUG) << "Uninstalling hook for " << name << " ..."; +#endif + if (hook.uninstalled()) + { + LOG(WARN) << "Hook for " << name << " was already uninstalled."; + return true; + } + + auto status = reshade::hook::status::unknown; + + switch (method) + { + case hook_method::export_hook: +#if RESHADE_VERBOSE_LOG + LOG(DEBUG) << "> Skipped."; +#endif + return true; + case hook_method::function_hook: + status = hook.uninstall(); + break; + case hook_method::vtable_hook: + if (DWORD protection = PAGE_READWRITE; + VirtualProtect(hook.target, sizeof(reshade::hook::address), protection, &protection)) + { + *reinterpret_cast(hook.target) = hook.trampoline; + + VirtualProtect(hook.target, sizeof(reshade::hook::address), protection, &protection); + + status = reshade::hook::status::success; + } + else + { + status = reshade::hook::status::memory_protection_failure; + } + break; + } + + if (status != reshade::hook::status::success) + { + LOG(WARN) << "Failed to uninstall hook for " << name << " with status code " << static_cast(status) << '.'; + return false; + } + +#if RESHADE_VERBOSE_LOG + LOG(DEBUG) << "> Succeeded."; +#endif + hook.trampoline = nullptr; + + return true; +} + +static void install_delayed_hooks(const std::filesystem::path &loaded_path) +{ + // Ignore this call if unable to acquire the mutex to avoid possible deadlock + if (std::unique_lock lock(s_mutex_delayed_hook_paths, std::try_to_lock); lock.owns_lock()) + { + const auto remove = std::remove_if(s_delayed_hook_paths.begin(), s_delayed_hook_paths.end(), + [&loaded_path](const auto &path) { + // Pin the module so it cannot be unloaded by the application and cause problems when ReShade tries to call into it afterwards + HMODULE delayed_handle = nullptr; + if (!GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_PIN, path.wstring().c_str(), &delayed_handle)) + return false; + + LOG(INFO) << "Installing delayed hooks for " << path << " (Just loaded via LoadLibrary(" << loaded_path << ")) ..."; + + return install_internal(delayed_handle, g_module_handle, hook_method::function_hook) && reshade::hook::apply_queued_actions(); + }); + + s_delayed_hook_paths.erase(remove, s_delayed_hook_paths.end()); + } + else + { + LOG(WARN) << "Ignoring LoadLibrary(" << loaded_path << ") call to avoid possible deadlock."; + } +} + +static reshade::hook find_internal(reshade::hook::address target, reshade::hook::address replacement) +{ + const std::lock_guard lock(s_mutex_hooks); + + const auto it = std::find_if(s_hooks.cbegin(), s_hooks.cend(), + [target, replacement](const auto &hook) { + return std::get<1>(hook).replacement == replacement && + (target == nullptr || std::get<1>(hook).target == target); + }); + + return it != s_hooks.cend() ? std::get<1>(*it) : reshade::hook {}; +} + +template +static inline T call_unchecked(T replacement) +{ + return reinterpret_cast(find_internal(nullptr, reinterpret_cast(replacement)).call()); +} + +HMODULE WINAPI HookLoadLibraryA(LPCSTR lpFileName) +{ + static const auto trampoline = call_unchecked(&HookLoadLibraryA); + + const HMODULE handle = trampoline(lpFileName); + if (handle != nullptr && handle != g_module_handle) + install_delayed_hooks(lpFileName); + + return handle; +} +HMODULE WINAPI HookLoadLibraryExA(LPCSTR lpFileName, HANDLE hFile, DWORD dwFlags) +{ + static const auto trampoline = call_unchecked(&HookLoadLibraryExA); + + const HMODULE handle = trampoline(lpFileName, hFile, dwFlags); + if (dwFlags == 0 && handle != nullptr && handle != g_module_handle) + install_delayed_hooks(lpFileName); + + return handle; +} +HMODULE WINAPI HookLoadLibraryW(LPCWSTR lpFileName) +{ + static const auto trampoline = call_unchecked(&HookLoadLibraryW); + + const HMODULE handle = trampoline(lpFileName); + if (handle != nullptr && handle != g_module_handle) + install_delayed_hooks(lpFileName); + + return handle; +} +HMODULE WINAPI HookLoadLibraryExW(LPCWSTR lpFileName, HANDLE hFile, DWORD dwFlags) +{ + static const auto trampoline = call_unchecked(&HookLoadLibraryExW); + + const HMODULE handle = trampoline(lpFileName, hFile, dwFlags); + if (dwFlags == 0 && handle != nullptr && handle != g_module_handle) + install_delayed_hooks(lpFileName); + + return handle; +} + +bool reshade::hooks::install(const char *name, hook::address target, hook::address replacement, bool queue_enable) +{ + assert(target != nullptr); + assert(replacement != nullptr); + + hook hook = find_internal(nullptr, replacement); + // If the hook was already installed, make sure it was installed for the same target function + if (hook.installed()) + return target == hook.target; + + // Otherwise, set up the new hook and install it + hook.target = target; + hook.replacement = replacement; + + return install_internal(name, hook, hook_method::function_hook) && + (!queue_enable || hook::apply_queued_actions()); // Can optionally only queue up the hooks instead of installing them right away +} +bool reshade::hooks::install(const char *name, hook::address vtable[], unsigned int offset, hook::address replacement) +{ + assert(vtable != nullptr); + assert(replacement != nullptr); + + hook hook = find_internal(nullptr, replacement); + // Check if the hook was already installed to this vtable + if (hook.installed() && vtable[offset] == hook.replacement) + return true; + + hook.target = &vtable[offset]; // Target is the address of the vtable entry + hook.trampoline = vtable[offset]; // The current function in that entry is the original function to call + hook.replacement = replacement; + + return install_internal(name, hook, hook_method::vtable_hook); +} +void reshade::hooks::uninstall() +{ + LOG(INFO) << "Uninstalling " << s_hooks.size() << " hook(s) ..."; + + // Disable all hooks in a single batch job + for (auto &hook_info : s_hooks) + std::get<1>(hook_info).enable(false); + + hook::apply_queued_actions(); + + // Afterwards remove all hooks from the list + for (auto &hook_info : s_hooks) + uninstall_internal( + std::get<0>(hook_info), + std::get<1>(hook_info), + std::get<2>(hook_info)); + + s_hooks.clear(); +} + +void reshade::hooks::register_module(const std::filesystem::path &target_path) +{ + install("LoadLibraryA", reinterpret_cast(&LoadLibraryA), reinterpret_cast(&HookLoadLibraryA), true); + install("LoadLibraryExA", reinterpret_cast(&LoadLibraryExA), reinterpret_cast(&HookLoadLibraryExA), true); + install("LoadLibraryW", reinterpret_cast(&LoadLibraryW), reinterpret_cast(&HookLoadLibraryW), true); + install("LoadLibraryExW", reinterpret_cast(&LoadLibraryExW), reinterpret_cast(&HookLoadLibraryExW), true); + + // Install all "LoadLibrary" hooks in one go immediately + hook::apply_queued_actions(); + + LOG(INFO) << "Registering hooks for " << target_path << " ..."; + + // Compare module names and delay export hooks for later installation since we cannot call "LoadLibrary" from this function (it is called from "DLLMain", which does not allow this) + const std::filesystem::path target_name = target_path.stem(); + const std::filesystem::path replacement_name = g_reshade_dll_path.stem(); + + if (_wcsicmp(target_name.c_str(), replacement_name.c_str()) == 0) + { + assert(target_path != g_reshade_dll_path); + + LOG(INFO) << "> Delayed until first call to an exported function."; + + s_export_hook_path = target_path; + } + // Similarly, if the target module was not loaded yet, wait for it to get loaded in the "LoadLibrary" hooks and install it then + // Pin the module so it cannot be unloaded by the application and cause problems when ReShade tries to call into it afterwards + else if (HMODULE handle; !GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_PIN, target_path.wstring().c_str(), &handle)) + { + LOG(INFO) << "> Delayed."; + + s_delayed_hook_paths.push_back(target_path); + } + else // The target module is already loaded, so we can safely install hooks right away + { + LOG(INFO) << "> Libraries loaded."; + + install_internal(handle, g_module_handle, hook_method::function_hook); + + hook::apply_queued_actions(); + } +} + +reshade::hook::address reshade::hooks::call(hook::address target, hook::address replacement) +{ + const hook hook = find_internal(target, replacement); + + if (hook.valid()) + { + return hook.call(); + } + else if (!s_export_hook_path.empty()) // If the hook does not exist yet, delay-load export hooks and try again + { + // Note: Could use LoadLibraryExW with LOAD_LIBRARY_SEARCH_SYSTEM32 here, but absolute paths should to the trick as well + const HMODULE handle = LoadLibraryW(s_export_hook_path.wstring().c_str()); + + LOG(INFO) << "Installing export hooks for " << s_export_hook_path << " ..."; + + if (handle != nullptr) + { + assert(handle != g_module_handle); + + install_internal(handle, g_module_handle, hook_method::export_hook); + + s_export_hook_path.clear(); + + return call(replacement); + } + else + { + LOG(ERROR) << "Failed to load " << s_export_hook_path << '!'; + } + } + + LOG(ERROR) << "Unable to resolve hook for 0x" << replacement << '!'; + + return nullptr; +} diff --git a/msvc/source/hook_manager.hpp b/msvc/source/hook_manager.hpp new file mode 100644 index 0000000..98a59ae --- /dev/null +++ b/msvc/source/hook_manager.hpp @@ -0,0 +1,66 @@ +/** + * Copyright (C) 2014 Patrick Mours. All rights reserved. + * License: https://github.com/crosire/reshade#license + */ + +#pragma once + +#include "hook.hpp" +#include + +#define HOOK_EXPORT extern "C" + +template +inline reshade::hook::address *vtable_from_instance(T *instance) +{ + static_assert(std::is_polymorphic::value, "can only get virtual function table from polymorphic types"); + + return *reinterpret_cast(instance); +} + +namespace reshade::hooks +{ + /// + /// Install hook for the specified target function. + /// + /// The function name. This is used for debugging only. + /// The address of the target function. + /// The address of the hook function. + /// Set this to true to queue the enable action instead of immediately executing it. + /// The status of the hook installation. + bool install(const char *name, hook::address target, hook::address replacement, bool queue_enable = false); + /// + /// Install hook for the specified virtual function table entry. + /// + /// The function name. This is used for debugging only. + /// The virtual function table pointer of the object to targeted object. + /// The index of the target function in the virtual function table. + /// The address of the hook function. + /// The status of the hook installation. + bool install(const char *name, hook::address vtable[], unsigned int offset, hook::address replacement); + /// + /// Uninstall all previously installed hooks. + /// Only call this function as long as the loader-lock is active, since it is not thread-safe. + /// + void uninstall(); + + /// + /// Register the matching exports in the specified module and install or delay their hooking. + /// Only call this function as long as the loader-lock is active, since it is not thread-safe. + /// + /// The file path to the target module. + void register_module(const std::filesystem::path &path); + + /// + /// Call the original/trampoline function for the specified hook. + /// + /// The original target address the hook was installed to (optional). + /// The address of the hook function which was previously used to install a hook. + /// The address of original/trampoline function. + hook::address call(hook::address target, hook::address replacement); + template + inline T call(T replacement, hook::address target = nullptr) + { + return reinterpret_cast(call(target, reinterpret_cast(replacement))); + } +} diff --git a/msvc/source/ini_file.cpp b/msvc/source/ini_file.cpp new file mode 100644 index 0000000..f58a6e9 --- /dev/null +++ b/msvc/source/ini_file.cpp @@ -0,0 +1,145 @@ +/** + * Copyright (C) 2014 Patrick Mours. All rights reserved. + * License: https://github.com/crosire/reshade#license + */ + +#include "ini_file.hpp" +#include + +static inline void trim(std::string &str, const char *chars = " \t") +{ + str.erase(0, str.find_first_not_of(chars)); + str.erase(str.find_last_not_of(chars) + 1); +} +static inline std::string trim(const std::string &str, const char *chars = " \t") +{ + std::string res(str); + trim(res, chars); + return res; +} + +reshade::ini_file::ini_file(const std::filesystem::path &path) + : _path(path), _save_path(path) +{ + load(); +} +reshade::ini_file::ini_file(const std::filesystem::path &path, const std::filesystem::path &save_path) + : _path(path), _save_path(save_path) +{ + load(); +} +reshade::ini_file::~ini_file() +{ + save(); +} + +void reshade::ini_file::load() +{ + std::string line, section; + std::ifstream file(_path); + file.imbue(std::locale("en-us.UTF-8")); + + // Remove BOM (0xefbbbf means 0xfeff) + if (file.get() != 0xef || file.get() != 0xbb || file.get() != 0xbf) + file.seekg(0, std::ios::beg); + + while (std::getline(file, line)) + { + trim(line); + + if (line.empty() || line[0] == ';' || line[0] == '/') + continue; + + // Read section name + if (line[0] == '[') + { + section = trim(line.substr(0, line.find(']')), " \t[]"); + continue; + } + + // Read section content + const auto assign_index = line.find('='); + + if (assign_index != std::string::npos) + { + const auto key = trim(line.substr(0, assign_index)); + const auto value = trim(line.substr(assign_index + 1)); + std::vector value_splitted; + + for (size_t i = 0, len = value.size(), found; i < len; i = found + 1) + { + found = value.find_first_of(',', i); + + if (found == std::string::npos) + found = len; + + value_splitted.push_back(value.substr(i, found - i)); + } + + _sections[section][key] = value_splitted; + } + else + { + _sections[section][line] = {}; + } + } +} +void reshade::ini_file::save() const +{ + if (!_modified) + return; + + std::ofstream file(_save_path); + file.imbue(std::locale("en-us.UTF-8")); + + const auto it = _sections.find(""); + + if (it != _sections.end()) + { + for (const auto §ion_line : it->second) + { + file << section_line.first << '='; + + size_t i = 0; + + for (const auto &item : section_line.second) + { + if (i++ != 0) + file << ','; + + file << item; + } + + file << '\n'; + } + + file << '\n'; + } + + for (const auto §ion : _sections) + { + if (section.first.empty()) + continue; + + file << '[' << section.first << ']' << '\n'; + + for (const auto §ion_line : section.second) + { + file << section_line.first << '='; + + size_t i = 0; + + for (const auto &item : section_line.second) + { + if (i++ != 0) + file << ','; + + file << item; + } + + file << '\n'; + } + + file << '\n'; + } +} diff --git a/msvc/source/ini_file.hpp b/msvc/source/ini_file.hpp new file mode 100644 index 0000000..a00ccf5 --- /dev/null +++ b/msvc/source/ini_file.hpp @@ -0,0 +1,196 @@ +/** + * Copyright (C) 2014 Patrick Mours. All rights reserved. + * License: https://github.com/crosire/reshade#license + */ + +#pragma once + +#include +#include +#include +#include + +namespace reshade +{ + class ini_file + { + public: + explicit ini_file(const std::filesystem::path &path); + ini_file(const std::filesystem::path &path, const std::filesystem::path &save_path); + ~ini_file(); + + bool has(const std::string §ion, const std::string &key) const + { + const auto it1 = _sections.find(section); + if (it1 == _sections.end()) + return false; + const auto it2 = it1->second.find(key); + if (it2 == it1->second.end()) + return false; + return true; + } + + template + void get(const std::string §ion, const std::string &key, T &value) const + { + const auto it1 = _sections.find(section); + if (it1 == _sections.end()) + return; + const auto it2 = it1->second.find(key); + if (it2 == it1->second.end()) + return; + value = convert(it2->second, 0); + } + template + void get(const std::string §ion, const std::string &key, T(&values)[SIZE]) const + { + const auto it1 = _sections.find(section); + if (it1 == _sections.end()) + return; + const auto it2 = it1->second.find(key); + if (it2 == it1->second.end()) + return; + for (size_t i = 0; i < SIZE; ++i) + values[i] = convert(it2->second, i); + } + template + void get(const std::string §ion, const std::string &key, std::vector &values) const + { + const auto it1 = _sections.find(section); + if (it1 == _sections.end()) + return; + const auto it2 = it1->second.find(key); + if (it2 == it1->second.end()) + return; + values.resize(it2->second.size()); + for (size_t i = 0; i < it2->second.size(); ++i) + values[i] = convert(it2->second, i); + } + + template + void set(const std::string §ion, const std::string &key, const T &value) + { + set(section, key, std::to_string(value)); + } + template <> + void set(const std::string §ion, const std::string &key, const bool &value) + { + set(section, key, value ? "1" : "0"); + } + template <> + void set(const std::string §ion, const std::string &key, const std::string &value) + { + auto &v = _sections[section][key]; + v.assign(1, value); + _modified = true; + } + template <> + void set(const std::string §ion, const std::string &key, const std::filesystem::path &value) + { + set(section, key, value.u8string()); + } + template + void set(const std::string §ion, const std::string &key, const T(&values)[SIZE], size_t size = SIZE) + { + auto &v = _sections[section][key]; + v.resize(size); + for (size_t i = 0; i < size; ++i) + v[i] = std::to_string(values[i]); + _modified = true; + } + template + void set(const std::string §ion, const std::string &key, const std::vector &values) + { + auto &v = _sections[section][key]; + v.resize(values.size()); + for (size_t i = 0; i < values.size(); ++i) + v[i] = std::to_string(values[i]); + _modified = true; + } + template <> + void set(const std::string §ion, const std::string &key, const std::vector &values) + { + auto &v = _sections[section][key]; + v = values; + _modified = true; + } + template <> + void set(const std::string §ion, const std::string &key, const std::vector &values) + { + auto &v = _sections[section][key]; + v.resize(values.size()); + for (size_t i = 0; i < values.size(); ++i) + v[i] = values[i].u8string(); + _modified = true; + } + + private: + void load(); + void save() const; + + template + static const T convert(const std::vector &values, size_t i) = delete; + template <> + static const bool convert(const std::vector &values, size_t i) + { + return convert(values, i) != 0 || i < values.size() && (values[i] == "true" || values[i] == "True" || values[i] == "TRUE"); + } + template <> + static const int convert(const std::vector &values, size_t i) + { + return static_cast(convert(values, i)); + } + template <> + static const unsigned int convert(const std::vector &values, size_t i) + { + return static_cast(convert(values, i)); + } + template <> + static const long convert(const std::vector &values, size_t i) + { + return i < values.size() ? std::strtol(values[i].c_str(), nullptr, 10) : 0l; + } + template <> + static const unsigned long convert(const std::vector &values, size_t i) + { + return i < values.size() ? std::strtoul(values[i].c_str(), nullptr, 10) : 0ul; + } + template <> + static const long long convert(const std::vector &values, size_t i) + { + return i < values.size() ? std::strtoll(values[i].c_str(), nullptr, 10) : 0ll; + } + template <> + static const unsigned long long convert(const std::vector &values, size_t i) + { + return i < values.size() ? std::strtoull(values[i].c_str(), nullptr, 10) : 0ull; + } + template <> + static const float convert(const std::vector &values, size_t i) + { + return static_cast(convert(values, i)); + } + template <> + static const double convert(const std::vector &values, size_t i) + { + return i < values.size() ? std::strtod(values[i].c_str(), nullptr) : 0.0; + } + template <> + static const std::string convert(const std::vector &values, size_t i) + { + return i < values.size() ? values[i] : std::string(); + } + template <> + static const std::filesystem::path convert(const std::vector &values, size_t i) + { + return i < values.size() ? std::filesystem::u8path(values[i]) : std::filesystem::path(); + } + + bool _modified = false; + std::filesystem::path _path; + std::filesystem::path _save_path; + using value = std::vector; + using section = std::unordered_map; + std::unordered_map _sections; + }; +} diff --git a/msvc/source/input.cpp b/msvc/source/input.cpp new file mode 100644 index 0000000..b6ad5ec --- /dev/null +++ b/msvc/source/input.cpp @@ -0,0 +1,581 @@ +/** + * Copyright (C) 2014 Patrick Mours. All rights reserved. + * License: https://github.com/crosire/reshade#license + */ + +#include "log.hpp" +#include "input.hpp" +#include "hook_manager.hpp" +#include +#include +#include +#include +#include + +static std::mutex s_windows_mutex; +static std::unordered_map s_raw_input_windows; +static std::unordered_map> s_windows; + +reshade::input::input(window_handle window) + : _window(window) +{ + assert(window != nullptr); +} + +void reshade::input::register_window_with_raw_input(window_handle window, bool no_legacy_keyboard, bool no_legacy_mouse) +{ + const std::lock_guard lock(s_windows_mutex); + + const auto flags = (no_legacy_keyboard ? 0x1u : 0u) | (no_legacy_mouse ? 0x2u : 0u); + const auto insert = s_raw_input_windows.emplace(static_cast(window), flags); + + if (!insert.second) insert.first->second |= flags; +} +std::shared_ptr reshade::input::register_window(window_handle window) +{ + const std::lock_guard lock(s_windows_mutex); + + const auto insert = s_windows.emplace(static_cast(window), std::weak_ptr()); + + if (insert.second || insert.first->second.expired()) + { +#if RESHADE_VERBOSE_LOG + LOG(DEBUG) << "Starting input capture for window " << window << " ..."; +#endif + + const auto instance = std::make_shared(window); + + insert.first->second = instance; + + return instance; + } + else + { + return insert.first->second.lock(); + } +} + +bool reshade::input::handle_window_message(const void *message_data) +{ + assert(message_data != nullptr); + + MSG details = *static_cast(message_data); + + bool is_mouse_message = details.message >= WM_MOUSEFIRST && details.message <= WM_MOUSELAST; + bool is_keyboard_message = details.message >= WM_KEYFIRST && details.message <= WM_KEYLAST; + + // Ignore messages that are not related to mouse or keyboard input + if (details.message != WM_INPUT && !is_mouse_message && !is_keyboard_message) + return false; + + // Guard access to windows list against race conditions + std::unique_lock lock(s_windows_mutex); + + // Remove any expired entry from the list + for (auto it = s_windows.begin(); it != s_windows.end();) + it->second.expired() ? it = s_windows.erase(it) : ++it; + + // Look up the window in the list of known input windows + auto input_window = s_windows.find(details.hwnd); + const auto raw_input_window = s_raw_input_windows.find(details.hwnd); + + if (input_window == s_windows.end()) + { + // Walk through the window chain and until an known window is found + EnumChildWindows(details.hwnd, [](HWND hwnd, LPARAM lparam) -> BOOL { + auto &input_window = *reinterpret_cast(lparam); + // Return true to continue enumeration + return (input_window = s_windows.find(hwnd)) == s_windows.end(); + }, reinterpret_cast(&input_window)); + } + + if (input_window == s_windows.end() && raw_input_window != s_raw_input_windows.end()) + { + // Reroute this raw input message to the window with the most rendering + input_window = std::max_element(s_windows.begin(), s_windows.end(), + [](auto lhs, auto rhs) { return lhs.second.lock()->_frame_count < rhs.second.lock()->_frame_count; }); + } + + if (input_window == s_windows.end()) + return false; + + const std::shared_ptr input = input_window->second.lock(); + + // At this point we have a shared pointer to the input object and no longer reference any memory from the windows list, so can release the lock + lock.unlock(); + + // Prevent input threads from modifying input while it is accessed elsewhere + std::lock_guard input_lock(input->_mutex); + + // Calculate window client mouse position + ScreenToClient(static_cast(input->_window), &details.pt); + + input->_mouse_position[0] = details.pt.x; + input->_mouse_position[1] = details.pt.y; + + switch (details.message) + { + case WM_INPUT: { + RAWINPUT raw_data = {}; + if (UINT raw_data_size = sizeof(raw_data); + GET_RAWINPUT_CODE_WPARAM(details.wParam) != RIM_INPUT || + GetRawInputData(reinterpret_cast(details.lParam), RID_INPUT, &raw_data, &raw_data_size, sizeof(raw_data.header)) == UINT(-1)) + break; + + switch (raw_data.header.dwType) + { + case RIM_TYPEMOUSE: + is_mouse_message = true; + + if (raw_input_window == s_raw_input_windows.end() || (raw_input_window->second & 0x2) == 0) + break; // Input is already handled (since legacy mouse messages are enabled), so nothing to do here + + if (raw_data.data.mouse.usButtonFlags & RI_MOUSE_LEFT_BUTTON_DOWN) + input->_mouse_buttons[0] = 0x88; + else if (raw_data.data.mouse.usButtonFlags & RI_MOUSE_LEFT_BUTTON_UP) + input->_mouse_buttons[0] = 0x08; + if (raw_data.data.mouse.usButtonFlags & RI_MOUSE_RIGHT_BUTTON_DOWN) + input->_mouse_buttons[1] = 0x88; + else if (raw_data.data.mouse.usButtonFlags & RI_MOUSE_RIGHT_BUTTON_UP) + input->_mouse_buttons[1] = 0x08; + if (raw_data.data.mouse.usButtonFlags & RI_MOUSE_MIDDLE_BUTTON_DOWN) + input->_mouse_buttons[2] = 0x88; + else if (raw_data.data.mouse.usButtonFlags & RI_MOUSE_MIDDLE_BUTTON_UP) + input->_mouse_buttons[2] = 0x08; + + if (raw_data.data.mouse.usButtonFlags & RI_MOUSE_BUTTON_4_DOWN) + input->_mouse_buttons[3] = 0x88; + else if (raw_data.data.mouse.usButtonFlags & RI_MOUSE_BUTTON_4_UP) + input->_mouse_buttons[3] = 0x08; + + if (raw_data.data.mouse.usButtonFlags & RI_MOUSE_BUTTON_5_DOWN) + input->_mouse_buttons[4] = 0x88; + else if (raw_data.data.mouse.usButtonFlags & RI_MOUSE_BUTTON_5_UP) + input->_mouse_buttons[4] = 0x08; + + if (raw_data.data.mouse.usButtonFlags & RI_MOUSE_WHEEL) + input->_mouse_wheel_delta += static_cast(raw_data.data.mouse.usButtonData) / WHEEL_DELTA; + break; + case RIM_TYPEKEYBOARD: + is_keyboard_message = true; + + if (raw_input_window == s_raw_input_windows.end() || (raw_input_window->second & 0x1) == 0) + break; // Input is already handled by 'WM_KEYDOWN' and friends (since legacy keyboard messages are enabled), so nothing to do here + + if (raw_data.data.keyboard.VKey != 0xFF) + input->_keys[raw_data.data.keyboard.VKey] = (raw_data.data.keyboard.Flags & RI_KEY_BREAK) == 0 ? 0x88 : 0x08; + + // No 'WM_CHAR' messages are sent if legacy keyboard messages are disabled, so need to generate text input manually here + // Cannot use the ToAscii function always as it seems to reset dead key state and thus calling it can break subsequent application input, should be fine here though since the application is already explicitly using raw input + if (WORD ch = 0; (raw_data.data.keyboard.Flags & RI_KEY_BREAK) == 0 && ToAscii(raw_data.data.keyboard.VKey, raw_data.data.keyboard.MakeCode, input->_keys, &ch, 0)) + input->_text_input += ch; + break; + } + break; } + case WM_CHAR: + input->_text_input += static_cast(details.wParam); + break; + case WM_KEYDOWN: + case WM_SYSKEYDOWN: + assert(details.wParam < _countof(input->_keys)); + input->_keys[details.wParam] = 0x88; + break; + case WM_KEYUP: + case WM_SYSKEYUP: + assert(details.wParam < _countof(input->_keys)); + input->_keys[details.wParam] = 0x08; + break; + case WM_LBUTTONDOWN: + input->_mouse_buttons[0] = 0x88; + break; + case WM_LBUTTONUP: + input->_mouse_buttons[0] = 0x08; + break; + case WM_RBUTTONDOWN: + input->_mouse_buttons[1] = 0x88; + break; + case WM_RBUTTONUP: + input->_mouse_buttons[1] = 0x08; + break; + case WM_MBUTTONDOWN: + input->_mouse_buttons[2] = 0x88; + break; + case WM_MBUTTONUP: + input->_mouse_buttons[2] = 0x08; + break; + case WM_MOUSEWHEEL: + input->_mouse_wheel_delta += GET_WHEEL_DELTA_WPARAM(details.wParam) / WHEEL_DELTA; + break; + case WM_XBUTTONDOWN: + assert(2 + HIWORD(details.wParam) < _countof(input->_mouse_buttons)); + input->_mouse_buttons[2 + HIWORD(details.wParam)] = 0x88; + break; + case WM_XBUTTONUP: + assert(2 + HIWORD(details.wParam) < _countof(input->_mouse_buttons)); + input->_mouse_buttons[2 + HIWORD(details.wParam)] = 0x08; + break; + } + + return (is_mouse_message && input->_block_mouse) || (is_keyboard_message && input->_block_keyboard); +} + +bool reshade::input::is_key_down(unsigned int keycode) const +{ + assert(keycode < _countof(_keys)); + return keycode < _countof(_keys) && (_keys[keycode] & 0x80) == 0x80; +} +bool reshade::input::is_key_pressed(unsigned int keycode) const +{ + assert(keycode < _countof(_keys)); + return keycode < _countof(_keys) && (_keys[keycode] & 0x88) == 0x88; +} +bool reshade::input::is_key_pressed(unsigned int keycode, bool ctrl, bool shift, bool alt) const +{ + return is_key_pressed(keycode) && ctrl == is_key_down(VK_CONTROL) && shift == is_key_down(VK_SHIFT) && alt == is_key_down(VK_MENU); +} +bool reshade::input::is_key_released(unsigned int keycode) const +{ + assert(keycode < _countof(_keys)); + return keycode < _countof(_keys) && (_keys[keycode] & 0x88) == 0x08; +} + +bool reshade::input::is_any_key_down() const +{ + for (unsigned int i = 0; i < _countof(_keys); i++) + if (is_key_down(i)) + return true; + return false; +} +bool reshade::input::is_any_key_pressed() const +{ + return last_key_pressed() != 0; +} +bool reshade::input::is_any_key_released() const +{ + return last_key_released() != 0; +} + +unsigned int reshade::input::last_key_pressed() const +{ + for (unsigned int i = 0; i < _countof(_keys); i++) + if (is_key_pressed(i)) + return i; + return 0; +} +unsigned int reshade::input::last_key_released() const +{ + for (unsigned int i = 0; i < _countof(_keys); i++) + if (is_key_released(i)) + return i; + return 0; +} + +bool reshade::input::is_mouse_button_down(unsigned int button) const +{ + assert(button < _countof(_mouse_buttons)); + return button < _countof(_mouse_buttons) && (_mouse_buttons[button] & 0x80) == 0x80; +} +bool reshade::input::is_mouse_button_pressed(unsigned int button) const +{ + assert(button < _countof(_mouse_buttons)); + return button < _countof(_mouse_buttons) && (_mouse_buttons[button] & 0x88) == 0x88; +} +bool reshade::input::is_mouse_button_released(unsigned int button) const +{ + assert(button < _countof(_mouse_buttons)); + return button < _countof(_mouse_buttons) && (_mouse_buttons[button] & 0x88) == 0x08; +} + +bool reshade::input::is_any_mouse_button_down() const +{ + for (unsigned int i = 0; i < _countof(_mouse_buttons); i++) + if (is_mouse_button_down(i)) + return true; + return false; +} +bool reshade::input::is_any_mouse_button_pressed() const +{ + for (unsigned int i = 0; i < _countof(_mouse_buttons); i++) + if (is_mouse_button_pressed(i)) + return true; + return false; +} +bool reshade::input::is_any_mouse_button_released() const +{ + for (unsigned int i = 0; i < _countof(_mouse_buttons); i++) + if (is_mouse_button_released(i)) + return true; + return false; +} + +void reshade::input::block_mouse_input(bool enable) +{ + _block_mouse = enable; + + // Some applications clip the mouse cursor, so disable that while we want full control over mouse input + if (enable) + ClipCursor(nullptr); +} +void reshade::input::block_keyboard_input(bool enable) +{ + _block_keyboard = enable; +} + +void reshade::input::next_frame() +{ + _frame_count++; + + for (auto &state : _keys) + state &= ~0x8; + for (auto &state : _mouse_buttons) + state &= ~0x8; + + _text_input.clear(); + _mouse_wheel_delta = 0; + _last_mouse_position[0] = _mouse_position[0]; + _last_mouse_position[1] = _mouse_position[1]; + + // Update caps lock state + _keys[VK_CAPITAL] |= GetKeyState(VK_CAPITAL) & 0x1; + + // Update modifier key state + if ((_keys[VK_MENU] & 0x88) != 0 && + (GetKeyState(VK_MENU) & 0x8000) == 0) + _keys[VK_MENU] = 0x08; + + // Update print screen state + if ((_keys[VK_SNAPSHOT] & 0x80) == 0 && + (GetAsyncKeyState(VK_SNAPSHOT) & 0x8000) != 0) + _keys[VK_SNAPSHOT] = 0x88; +} + +std::string reshade::input::key_name(unsigned int keycode) +{ + if (keycode >= 256) + return std::string(); + + static const char *keyboard_keys_german[256] = { + "", "", "", "Cancel", "", "", "", "", "Backspace", "Tab", "", "", "Clear", "Enter", "", "", + "Shift", "Control", "Alt", "Pause", "Caps Lock", "", "", "", "", "", "", "Escape", "", "", "", "", + "Leertaste", "Bild auf", "Bild ab", "Ende", "Pos 1", "Left Arrow", "Up Arrow", "Right Arrow", "Down Arrow", "Select", "", "", "Druck", "Einfg", "Entf", "Hilfe", + "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "", "", "", "", "", "", + "", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", + "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "Left Windows", "Right Windows", "Apps", "", "Sleep", + "Numpad 0", "Numpad 1", "Numpad 2", "Numpad 3", "Numpad 4", "Numpad 5", "Numpad 6", "Numpad 7", "Numpad 8", "Numpad 9", "Numpad *", "Numpad +", "", "Numpad -", "Numpad ,", "Numpad /", + "F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9", "F10", "F11", "F12", "F13", "F14", "F15", "F16", + "F17", "F18", "F19", "F20", "F21", "F22", "F23", "F24", "", "", "", "", "", "", "", "", + "Num Lock", "Scroll Lock", "", "", "", "", "", "", "", "", "", "", "", "", "", "", + "Left Shift", "Right Shift", "Left Control", "Right Control", "Left Menu", "Right Menu", "Browser Back", "Browser Forward", "Browser Refresh", "Browser Stop", "Browser Search", "Browser Favorites", "Browser Home", "Volume Mute", "Volume Down", "Volume Up", + "Next Track", "Previous Track", "Media Stop", "Media Play/Pause", "Mail", "Media Select", "Launch App 1", "Launch App 2", "", "", u8"Ü", "OEM +", "OEM ,", "OEM -", "OEM .", "OEM #", + u8"Ö", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", + "", "", "", "", "", "", "", "", "", "", "", u8"OEM ß", "OEM ^", u8"OEM ´", u8"Ä", "OEM 8", + "", "", "OEM <", "", "", "", "", "", "", "", "", "", "", "", "", "", + "", "", "", "", "", "", "Attn", "CrSel", "ExSel", "Erase EOF", "Play", "Zoom", "", "PA1", "OEM Clear", "" + }; + static const char *keyboard_keys_international[256] = { + "", "", "", "Cancel", "", "", "", "", "Backspace", "Tab", "", "", "Clear", "Enter", "", "", + "Shift", "Control", "Alt", "Pause", "Caps Lock", "", "", "", "", "", "", "Escape", "", "", "", "", + "Space", "Page Up", "Page Down", "End", "Home", "Left Arrow", "Up Arrow", "Right Arrow", "Down Arrow", "Select", "", "", "Print Screen", "Insert", "Delete", "Help", + "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "", "", "", "", "", "", + "", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", + "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "Left Windows", "Right Windows", "Apps", "", "Sleep", + "Numpad 0", "Numpad 1", "Numpad 2", "Numpad 3", "Numpad 4", "Numpad 5", "Numpad 6", "Numpad 7", "Numpad 8", "Numpad 9", "Numpad *", "Numpad +", "", "Numpad -", "Numpad Decimal", "Numpad /", + "F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9", "F10", "F11", "F12", "F13", "F14", "F15", "F16", + "F17", "F18", "F19", "F20", "F21", "F22", "F23", "F24", "", "", "", "", "", "", "", "", + "Num Lock", "Scroll Lock", "", "", "", "", "", "", "", "", "", "", "", "", "", "", + "Left Shift", "Right Shift", "Left Control", "Right Control", "Left Menu", "Right Menu", "Browser Back", "Browser Forward", "Browser Refresh", "Browser Stop", "Browser Search", "Browser Favorites", "Browser Home", "Volume Mute", "Volume Down", "Volume Up", + "Next Track", "Previous Track", "Media Stop", "Media Play/Pause", "Mail", "Media Select", "Launch App 1", "Launch App 2", "", "", "OEM ;", "OEM +", "OEM ,", "OEM -", "OEM .", "OEM /", + "OEM ~", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", + "", "", "", "", "", "", "", "", "", "", "", "OEM [", "OEM \\", "OEM ]", "OEM '", "OEM 8", + "", "", "OEM <", "", "", "", "", "", "", "", "", "", "", "", "", "", + "", "", "", "", "", "", "Attn", "CrSel", "ExSel", "Erase EOF", "Play", "Zoom", "", "PA1", "OEM Clear", "" + }; + + const LANGID language = LOWORD(GetKeyboardLayout(0)); + + return ((language & 0xFF) == LANG_GERMAN) ? + keyboard_keys_german[keycode] : keyboard_keys_international[keycode]; +} +std::string reshade::input::key_name(const unsigned int key[4]) +{ + return (key[1] ? "Ctrl + " : std::string()) + (key[2] ? "Shift + " : std::string()) + (key[3] ? "Alt + " : std::string()) + key_name(key[0]); +} + +static inline bool is_blocking_mouse_input() +{ + const auto predicate = [](auto input_window) { + return !input_window.second.expired() && input_window.second.lock()->is_blocking_mouse_input(); + }; + return std::any_of(s_windows.cbegin(), s_windows.cend(), predicate); +} +static inline bool is_blocking_keyboard_input() +{ + const auto predicate = [](auto input_window) { + return !input_window.second.expired() && input_window.second.lock()->is_blocking_keyboard_input(); + }; + return std::any_of(s_windows.cbegin(), s_windows.cend(), predicate); +} + +HOOK_EXPORT BOOL WINAPI HookGetMessageA(LPMSG lpMsg, HWND hWnd, UINT wMsgFilterMin, UINT wMsgFilterMax) +{ + static const auto trampoline = reshade::hooks::call(HookGetMessageA); + + if (!trampoline(lpMsg, hWnd, wMsgFilterMin, wMsgFilterMax)) + return FALSE; + + assert(lpMsg != nullptr); + + if (lpMsg->hwnd != nullptr && reshade::input::handle_window_message(lpMsg)) + { + // We still want 'WM_CHAR' messages, so translate message + TranslateMessage(lpMsg); + + // Change message so it is ignored by the recipient window + lpMsg->message = WM_NULL; + } + + return TRUE; +} +HOOK_EXPORT BOOL WINAPI HookGetMessageW(LPMSG lpMsg, HWND hWnd, UINT wMsgFilterMin, UINT wMsgFilterMax) +{ + static const auto trampoline = reshade::hooks::call(HookGetMessageW); + + if (!trampoline(lpMsg, hWnd, wMsgFilterMin, wMsgFilterMax)) + return FALSE; + + assert(lpMsg != nullptr); + + if (lpMsg->hwnd != nullptr && reshade::input::handle_window_message(lpMsg)) + { + // We still want 'WM_CHAR' messages, so translate message + TranslateMessage(lpMsg); + + // Change message so it is ignored by the recipient window + lpMsg->message = WM_NULL; + } + + return TRUE; +} +HOOK_EXPORT BOOL WINAPI HookPeekMessageA(LPMSG lpMsg, HWND hWnd, UINT wMsgFilterMin, UINT wMsgFilterMax, UINT wRemoveMsg) +{ + static const auto trampoline = reshade::hooks::call(HookPeekMessageA); + + if (!trampoline(lpMsg, hWnd, wMsgFilterMin, wMsgFilterMax, wRemoveMsg)) + return FALSE; + + assert(lpMsg != nullptr); + + if (lpMsg->hwnd != nullptr && (wRemoveMsg & PM_REMOVE) != 0 && reshade::input::handle_window_message(lpMsg)) + { + // We still want 'WM_CHAR' messages, so translate message + TranslateMessage(lpMsg); + + // Change message so it is ignored by the recipient window + lpMsg->message = WM_NULL; + } + + return TRUE; +} +HOOK_EXPORT BOOL WINAPI HookPeekMessageW(LPMSG lpMsg, HWND hWnd, UINT wMsgFilterMin, UINT wMsgFilterMax, UINT wRemoveMsg) +{ + static const auto trampoline = reshade::hooks::call(HookPeekMessageW); + + if (!trampoline(lpMsg, hWnd, wMsgFilterMin, wMsgFilterMax, wRemoveMsg)) + return FALSE; + + assert(lpMsg != nullptr); + + if (lpMsg->hwnd != nullptr && (wRemoveMsg & PM_REMOVE) != 0 && reshade::input::handle_window_message(lpMsg)) + { + // We still want 'WM_CHAR' messages, so translate message + TranslateMessage(lpMsg); + + // Change message so it is ignored by the recipient window + lpMsg->message = WM_NULL; + } + + return TRUE; +} + +HOOK_EXPORT BOOL WINAPI HookPostMessageA(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam) +{ + // Do not allow mouse movement simulation while we block input + if (is_blocking_mouse_input() && Msg == WM_MOUSEMOVE) + return TRUE; + + static const auto trampoline = reshade::hooks::call(HookPostMessageA); + return trampoline(hWnd, Msg, wParam, lParam); +} +HOOK_EXPORT BOOL WINAPI HookPostMessageW(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam) +{ + if (is_blocking_mouse_input() && Msg == WM_MOUSEMOVE) + return TRUE; + + static const auto trampoline = reshade::hooks::call(HookPostMessageW); + return trampoline(hWnd, Msg, wParam, lParam); +} + +HOOK_EXPORT BOOL WINAPI HookRegisterRawInputDevices(PCRAWINPUTDEVICE pRawInputDevices, UINT uiNumDevices, UINT cbSize) +{ +#if RESHADE_VERBOSE_LOG + LOG(DEBUG) << "Redirecting RegisterRawInputDevices" << '(' << pRawInputDevices << ", " << uiNumDevices << ", " << cbSize << ')' << " ..."; +#endif + for (UINT i = 0; i < uiNumDevices; ++i) + { + const auto &device = pRawInputDevices[i]; + +#if RESHADE_VERBOSE_LOG + LOG(DEBUG) << "> Dumping device registration at index " << i << ":"; + LOG(DEBUG) << " +-----------------------------------------+-----------------------------------------+"; + LOG(DEBUG) << " | Parameter | Value |"; + LOG(DEBUG) << " +-----------------------------------------+-----------------------------------------+"; + LOG(DEBUG) << " | UsagePage | " << std::setw(39) << std::hex << device.usUsagePage << std::dec << " |"; + LOG(DEBUG) << " | Usage | " << std::setw(39) << std::hex << device.usUsage << std::dec << " |"; + LOG(DEBUG) << " | Flags | " << std::setw(39) << std::hex << device.dwFlags << std::dec << " |"; + LOG(DEBUG) << " | TargetWindow | " << std::setw(39) << device.hwndTarget << " |"; + LOG(DEBUG) << " +-----------------------------------------+-----------------------------------------+"; +#endif + + if (device.usUsagePage != 1 || device.hwndTarget == nullptr) + continue; + + reshade::input::register_window_with_raw_input(device.hwndTarget, device.usUsage == 0x06 && (device.dwFlags & RIDEV_NOLEGACY) != 0, device.usUsage == 0x02 && (device.dwFlags & RIDEV_NOLEGACY) != 0); + } + + if (!reshade::hooks::call(HookRegisterRawInputDevices)(pRawInputDevices, uiNumDevices, cbSize)) + { + LOG(WARN) << "'RegisterRawInputDevices' failed with error code " << GetLastError() << "!"; + return FALSE; + } + + return TRUE; +} + +static POINT last_cursor_position = {}; + +HOOK_EXPORT BOOL WINAPI HookSetCursorPosition(int X, int Y) +{ + last_cursor_position.x = X; + last_cursor_position.y = Y; + + if (is_blocking_mouse_input()) + return TRUE; + + static const auto trampoline = reshade::hooks::call(HookSetCursorPosition); + return trampoline(X, Y); +} +HOOK_EXPORT BOOL WINAPI HookGetCursorPosition(LPPOINT lpPoint) +{ + if (is_blocking_mouse_input()) + { + assert(lpPoint != nullptr); + + // Just return the last cursor position before we started to block mouse input, to stop it from moving + *lpPoint = last_cursor_position; + + return TRUE; + } + + static const auto trampoline = reshade::hooks::call(HookGetCursorPosition); + return trampoline(lpPoint); +} diff --git a/msvc/source/input.hpp b/msvc/source/input.hpp new file mode 100644 index 0000000..ba0e718 --- /dev/null +++ b/msvc/source/input.hpp @@ -0,0 +1,72 @@ +/** + * Copyright (C) 2014 Patrick Mours. All rights reserved. + * License: https://github.com/crosire/reshade#license + */ + +#pragma once + +#include +#include +#include + +namespace reshade +{ + class input + { + public: + using window_handle = void *; + + explicit input(window_handle window); + + static void register_window_with_raw_input(window_handle window, bool no_legacy_keyboard, bool no_legacy_mouse); + static std::shared_ptr register_window(window_handle window); + + bool is_key_down(unsigned int keycode) const; + bool is_key_pressed(unsigned int keycode) const; + bool is_key_pressed(unsigned int keycode, bool ctrl, bool shift, bool alt) const; + bool is_key_pressed(const unsigned int key[4]) const { return is_key_pressed(key[0], key[1] != 0, key[2] != 0, key[3] != 0); } + bool is_key_released(unsigned int keycode) const; + bool is_any_key_down() const; + bool is_any_key_pressed() const; + bool is_any_key_released() const; + unsigned int last_key_pressed() const; + unsigned int last_key_released() const; + bool is_mouse_button_down(unsigned int button) const; + bool is_mouse_button_pressed(unsigned int button) const; + bool is_mouse_button_released(unsigned int button) const; + bool is_any_mouse_button_down() const; + bool is_any_mouse_button_pressed() const; + bool is_any_mouse_button_released() const; + short mouse_wheel_delta() const { return _mouse_wheel_delta; } + int mouse_movement_delta_x() const { return _mouse_position[0] - _last_mouse_position[0]; } + int mouse_movement_delta_y() const { return _mouse_position[1] - _last_mouse_position[1]; } + unsigned int mouse_position_x() const { return _mouse_position[0]; } + unsigned int mouse_position_y() const { return _mouse_position[1]; } + const std::wstring &text_input() const { return _text_input; } + + void block_mouse_input(bool enable); + void block_keyboard_input(bool enable); + + bool is_blocking_mouse_input() const { return _block_mouse; } + bool is_blocking_keyboard_input() const { return _block_keyboard; } + + auto lock() { return std::lock_guard(_mutex); } + void next_frame(); + + static std::string key_name(unsigned int keycode); + static std::string key_name(const unsigned int key[4]); + + static bool handle_window_message(const void *message_data); + + private: + std::mutex _mutex; + window_handle _window; + bool _block_mouse = false, _block_keyboard = false; + uint8_t _keys[256] = {}, _mouse_buttons[5] = {}; + short _mouse_wheel_delta = 0; + unsigned int _mouse_position[2] = {}; + unsigned int _last_mouse_position[2] = {}; + uint64_t _frame_count = 0; + std::wstring _text_input; + }; +} diff --git a/msvc/source/log.cpp b/msvc/source/log.cpp new file mode 100644 index 0000000..712017d --- /dev/null +++ b/msvc/source/log.cpp @@ -0,0 +1,73 @@ +/** + * Copyright (C) 2014 Patrick Mours. All rights reserved. + * License: https://github.com/crosire/reshade#license + */ + +#include "log.hpp" +#include +#include +#include + +std::ofstream reshade::log::stream; +std::ostringstream reshade::log::linestream; +std::vector reshade::log::lines; +static std::mutex s_mutex; + +reshade::log::message::message(level level) +{ + SYSTEMTIME time; + GetLocalTime(&time); + + const char level_names[][6] = { "ERROR", "WARN ", "INFO ", "DEBUG" }; + assert(static_cast(level) - 1 < _countof(level_names)); + + // Lock the stream until the message is complete + s_mutex.lock(); + + // Start a new line + linestream.str(""); + linestream.clear(); + + stream << std::right << std::setfill('0') +#if RESHADE_VERBOSE_LOG + << std::setw(4) << time.wYear << '-' + << std::setw(2) << time.wMonth << '-' + << std::setw(2) << time.wDay << 'T' +#endif + << std::setw(2) << time.wHour << ':' + << std::setw(2) << time.wMinute << ':' + << std::setw(2) << time.wSecond << ':' + << std::setw(3) << time.wMilliseconds << ' ' + << '[' << std::setw(5) << GetCurrentThreadId() << ']' << std::setfill(' ') << " | " + << level_names[static_cast(level) - 1] << " | " << std::left; + linestream + << level_names[static_cast(level) - 1] << " | "; +} +reshade::log::message::~message() +{ + // Finish the line + stream << std::endl; + linestream << std::endl; + + lines.push_back(linestream.str()); + + // The message is finished, we can unlock the stream + s_mutex.unlock(); +} + +bool reshade::log::open(const std::filesystem::path &path) +{ + stream.open(path, std::ios::out | std::ios::trunc); + + if (!stream.is_open()) + return false; + + stream.setf(std::ios::left); + stream.setf(std::ios::showbase); + stream.flush(); + + linestream.setf(std::ios::left); + linestream.setf(std::ios::showbase); + + return true; +} diff --git a/msvc/source/log.hpp b/msvc/source/log.hpp new file mode 100644 index 0000000..0cc1269 --- /dev/null +++ b/msvc/source/log.hpp @@ -0,0 +1,82 @@ +/** + * Copyright (C) 2014 Patrick Mours. All rights reserved. + * License: https://github.com/crosire/reshade#license + */ + +#pragma once + +#include +#include +#include +#include +#include + +#define LOG(LEVEL) LOG_##LEVEL() +#define LOG_INFO() reshade::log::message(reshade::log::level::info) +#define LOG_ERROR() reshade::log::message(reshade::log::level::error) +#define LOG_WARN() reshade::log::message(reshade::log::level::warning) +#define LOG_DEBUG() reshade::log::message(reshade::log::level::debug) + +namespace reshade::log +{ + enum class level + { + info = 3, + error = 1, + warning = 2, + debug = 4, + }; + + extern std::ofstream stream; + extern std::ostringstream linestream; + extern std::vector lines; + + struct message + { + message(level level); + ~message(); + + template + inline message &operator<<(const T &value) + { + stream << value; + linestream << value; + return *this; + } + + template <> + inline message &operator<<(const std::wstring &message) + { + static_assert(sizeof(std::wstring::value_type) == sizeof(uint16_t), "expected 'std::wstring' to use UTF-16 encoding"); + std::string utf8_message; + utf8_message.reserve(message.size()); + utf8::unchecked::utf16to8(message.begin(), message.end(), std::back_inserter(utf8_message)); + return operator<<(utf8_message); + } + template <> + inline message &operator<<(const std::filesystem::path &path) + { + return operator<<('"' + path.u8string() + '"'); + } + + inline message &operator<<(const char *message) + { + stream << message; + linestream << message; + return *this; + } + inline message &operator<<(const wchar_t *message) + { + static_assert(sizeof(wchar_t) == sizeof(uint16_t), "expected 'wchar_t' to use UTF-16 encoding"); + std::string utf8_message; + utf8::unchecked::utf16to8(message, message + wcslen(message), std::back_inserter(utf8_message)); + return operator<<(utf8_message); + } + }; + + /// + /// Open a log file for writing. + /// + /// The path to the log file. + bool open(const std::filesystem::path &path); +} diff --git a/msvc/source/moving_average.hpp b/msvc/source/moving_average.hpp new file mode 100644 index 0000000..aa48821 --- /dev/null +++ b/msvc/source/moving_average.hpp @@ -0,0 +1,38 @@ +/** + * Copyright (C) 2014 Patrick Mours. All rights reserved. + * License: https://github.com/crosire/reshade#license + */ + +#pragma once + +template +class moving_average +{ +public: + moving_average() : _index(0), _average(0), _tick_sum(0), _tick_list() { } + + inline operator T() const { return _average; } + + void clear() + { + _index = 0; + _average = 0; + _tick_sum = 0; + + for (size_t i = 0; i < SAMPLES; i++) + { + _tick_list[i] = 0; + } + } + void append(T value) + { + _tick_sum -= _tick_list[_index]; + _tick_sum += _tick_list[_index] = value; + _index = ++_index % SAMPLES; + _average = _tick_sum / SAMPLES; + } + +private: + size_t _index; + T _average, _tick_sum, _tick_list[SAMPLES]; +}; diff --git a/msvc/source/opengl/opengl.hpp b/msvc/source/opengl/opengl.hpp new file mode 100644 index 0000000..ae1ad63 --- /dev/null +++ b/msvc/source/opengl/opengl.hpp @@ -0,0 +1,1367 @@ +/** + * Copyright (C) 2014 Patrick Mours. All rights reserved. + * License: https://github.com/crosire/reshade#license + */ + +#include + +#ifndef NDEBUG + +#include + +#ifndef GLCHECK +#define GLCHECK(call) \ + { \ + glGetError(); \ + call; \ + GLenum __e = glGetError(); \ + if (__e != GL_NO_ERROR) { \ + char __m[1024]; \ + sprintf_s(__m, "OpenGL error %x in %s at line %d: %s", __e, __FILE__, __LINE__, #call); \ + MessageBoxA(nullptr, __m, 0, MB_ICONERROR); \ + } \ + } +#endif + +#undef glActiveShaderProgram +#define glActiveShaderProgram(...) GLCHECK(gl3wProcs.gl.ActiveShaderProgram(__VA_ARGS__)) +#undef glActiveTexture +#define glActiveTexture(...) GLCHECK(gl3wProcs.gl.ActiveTexture(__VA_ARGS__)) +#undef glAttachShader +#define glAttachShader(...) GLCHECK(gl3wProcs.gl.AttachShader(__VA_ARGS__)) +#undef glBeginConditionalRender +#define glBeginConditionalRender(...) GLCHECK(gl3wProcs.gl.BeginConditionalRender(__VA_ARGS__)) +#undef glBeginQuery +#define glBeginQuery(...) GLCHECK(gl3wProcs.gl.BeginQuery(__VA_ARGS__)) +#undef glBeginQueryIndexed +#define glBeginQueryIndexed(...) GLCHECK(gl3wProcs.gl.BeginQueryIndexed(__VA_ARGS__)) +#undef glBeginTransformFeedback +#define glBeginTransformFeedback(...) GLCHECK(gl3wProcs.gl.BeginTransformFeedback(__VA_ARGS__)) +#undef glBindAttribLocation +#define glBindAttribLocation(...) GLCHECK(gl3wProcs.gl.BindAttribLocation(__VA_ARGS__)) +#undef glBindBuffer +#define glBindBuffer(...) GLCHECK(gl3wProcs.gl.BindBuffer(__VA_ARGS__)) +#undef glBindBufferBase +#define glBindBufferBase(...) GLCHECK(gl3wProcs.gl.BindBufferBase(__VA_ARGS__)) +#undef glBindBufferRange +#define glBindBufferRange(...) GLCHECK(gl3wProcs.gl.BindBufferRange(__VA_ARGS__)) +#undef glBindBuffersBase +#define glBindBuffersBase(...) GLCHECK(gl3wProcs.gl.BindBuffersBase(__VA_ARGS__)) +#undef glBindBuffersRange +#define glBindBuffersRange(...) GLCHECK(gl3wProcs.gl.BindBuffersRange(__VA_ARGS__)) +#undef glBindFragDataLocation +#define glBindFragDataLocation(...) GLCHECK(gl3wProcs.gl.BindFragDataLocation(__VA_ARGS__)) +#undef glBindFragDataLocationIndexed +#define glBindFragDataLocationIndexed(...) GLCHECK(gl3wProcs.gl.BindFragDataLocationIndexed(__VA_ARGS__)) +#undef glBindFramebuffer +#define glBindFramebuffer(...) GLCHECK(gl3wProcs.gl.BindFramebuffer(__VA_ARGS__)) +#undef glBindImageTexture +#define glBindImageTexture(...) GLCHECK(gl3wProcs.gl.BindImageTexture(__VA_ARGS__)) +#undef glBindImageTextures +#define glBindImageTextures(...) GLCHECK(gl3wProcs.gl.BindImageTextures(__VA_ARGS__)) +#undef glBindProgramPipeline +#define glBindProgramPipeline(...) GLCHECK(gl3wProcs.gl.BindProgramPipeline(__VA_ARGS__)) +#undef glBindRenderbuffer +#define glBindRenderbuffer(...) GLCHECK(gl3wProcs.gl.BindRenderbuffer(__VA_ARGS__)) +#undef glBindSampler +#define glBindSampler(...) GLCHECK(gl3wProcs.gl.BindSampler(__VA_ARGS__)) +#undef glBindSamplers +#define glBindSamplers(...) GLCHECK(gl3wProcs.gl.BindSamplers(__VA_ARGS__)) +#undef glBindTexture +#define glBindTexture(...) GLCHECK(gl3wProcs.gl.BindTexture(__VA_ARGS__)) +#undef glBindTextureUnit +#define glBindTextureUnit(...) GLCHECK(gl3wProcs.gl.BindTextureUnit(__VA_ARGS__)) +#undef glBindTextures +#define glBindTextures(...) GLCHECK(gl3wProcs.gl.BindTextures(__VA_ARGS__)) +#undef glBindTransformFeedback +#define glBindTransformFeedback(...) GLCHECK(gl3wProcs.gl.BindTransformFeedback(__VA_ARGS__)) +#undef glBindVertexArray +#define glBindVertexArray(...) GLCHECK(gl3wProcs.gl.BindVertexArray(__VA_ARGS__)) +#undef glBindVertexBuffer +#define glBindVertexBuffer(...) GLCHECK(gl3wProcs.gl.BindVertexBuffer(__VA_ARGS__)) +#undef glBindVertexBuffers +#define glBindVertexBuffers(...) GLCHECK(gl3wProcs.gl.BindVertexBuffers(__VA_ARGS__)) +#undef glBlendColor +#define glBlendColor(...) GLCHECK(gl3wProcs.gl.BlendColor(__VA_ARGS__)) +#undef glBlendEquation +#define glBlendEquation(...) GLCHECK(gl3wProcs.gl.BlendEquation(__VA_ARGS__)) +#undef glBlendEquationSeparate +#define glBlendEquationSeparate(...) GLCHECK(gl3wProcs.gl.BlendEquationSeparate(__VA_ARGS__)) +#undef glBlendEquationSeparatei +#define glBlendEquationSeparatei(...) GLCHECK(gl3wProcs.gl.BlendEquationSeparatei(__VA_ARGS__)) +#undef glBlendEquationSeparateiARB +#define glBlendEquationSeparateiARB(...) GLCHECK(gl3wProcs.gl.BlendEquationSeparateiARB(__VA_ARGS__)) +#undef glBlendEquationi +#define glBlendEquationi(...) GLCHECK(gl3wProcs.gl.BlendEquationi(__VA_ARGS__)) +#undef glBlendEquationiARB +#define glBlendEquationiARB(...) GLCHECK(gl3wProcs.gl.BlendEquationiARB(__VA_ARGS__)) +#undef glBlendFunc +#define glBlendFunc(...) GLCHECK(gl3wProcs.gl.BlendFunc(__VA_ARGS__)) +#undef glBlendFuncSeparate +#define glBlendFuncSeparate(...) GLCHECK(gl3wProcs.gl.BlendFuncSeparate(__VA_ARGS__)) +#undef glBlendFuncSeparatei +#define glBlendFuncSeparatei(...) GLCHECK(gl3wProcs.gl.BlendFuncSeparatei(__VA_ARGS__)) +#undef glBlendFuncSeparateiARB +#define glBlendFuncSeparateiARB(...) GLCHECK(gl3wProcs.gl.BlendFuncSeparateiARB(__VA_ARGS__)) +#undef glBlendFunci +#define glBlendFunci(...) GLCHECK(gl3wProcs.gl.BlendFunci(__VA_ARGS__)) +#undef glBlendFunciARB +#define glBlendFunciARB(...) GLCHECK(gl3wProcs.gl.BlendFunciARB(__VA_ARGS__)) +#undef glBlitFramebuffer +#define glBlitFramebuffer(...) GLCHECK(gl3wProcs.gl.BlitFramebuffer(__VA_ARGS__)) +#undef glBlitNamedFramebuffer +#define glBlitNamedFramebuffer(...) GLCHECK(gl3wProcs.gl.BlitNamedFramebuffer(__VA_ARGS__)) +#undef glBufferData +#define glBufferData(...) GLCHECK(gl3wProcs.gl.BufferData(__VA_ARGS__)) +#undef glBufferPageCommitmentARB +#define glBufferPageCommitmentARB(...) GLCHECK(gl3wProcs.gl.BufferPageCommitmentARB(__VA_ARGS__)) +#undef glBufferStorage +#define glBufferStorage(...) GLCHECK(gl3wProcs.gl.BufferStorage(__VA_ARGS__)) +#undef glBufferSubData +#define glBufferSubData(...) GLCHECK(gl3wProcs.gl.BufferSubData(__VA_ARGS__)) +#undef glClampColor +#define glClampColor(...) GLCHECK(gl3wProcs.gl.ClampColor(__VA_ARGS__)) +#undef glClear +#define glClear(...) GLCHECK(gl3wProcs.gl.Clear(__VA_ARGS__)) +#undef glClearBufferData +#define glClearBufferData(...) GLCHECK(gl3wProcs.gl.ClearBufferData(__VA_ARGS__)) +#undef glClearBufferSubData +#define glClearBufferSubData(...) GLCHECK(gl3wProcs.gl.ClearBufferSubData(__VA_ARGS__)) +#undef glClearBufferfi +#define glClearBufferfi(...) GLCHECK(gl3wProcs.gl.ClearBufferfi(__VA_ARGS__)) +#undef glClearBufferfv +#define glClearBufferfv(...) GLCHECK(gl3wProcs.gl.ClearBufferfv(__VA_ARGS__)) +#undef glClearBufferiv +#define glClearBufferiv(...) GLCHECK(gl3wProcs.gl.ClearBufferiv(__VA_ARGS__)) +#undef glClearBufferuiv +#define glClearBufferuiv(...) GLCHECK(gl3wProcs.gl.ClearBufferuiv(__VA_ARGS__)) +#undef glClearColor +#define glClearColor(...) GLCHECK(gl3wProcs.gl.ClearColor(__VA_ARGS__)) +#undef glClearDepth +#define glClearDepth(...) GLCHECK(gl3wProcs.gl.ClearDepth(__VA_ARGS__)) +#undef glClearDepthf +#define glClearDepthf(...) GLCHECK(gl3wProcs.gl.ClearDepthf(__VA_ARGS__)) +#undef glClearNamedBufferData +#define glClearNamedBufferData(...) GLCHECK(gl3wProcs.gl.ClearNamedBufferData(__VA_ARGS__)) +#undef glClearNamedBufferSubData +#define glClearNamedBufferSubData(...) GLCHECK(gl3wProcs.gl.ClearNamedBufferSubData(__VA_ARGS__)) +#undef glClearNamedFramebufferfi +#define glClearNamedFramebufferfi(...) GLCHECK(gl3wProcs.gl.ClearNamedFramebufferfi(__VA_ARGS__)) +#undef glClearNamedFramebufferfv +#define glClearNamedFramebufferfv(...) GLCHECK(gl3wProcs.gl.ClearNamedFramebufferfv(__VA_ARGS__)) +#undef glClearNamedFramebufferiv +#define glClearNamedFramebufferiv(...) GLCHECK(gl3wProcs.gl.ClearNamedFramebufferiv(__VA_ARGS__)) +#undef glClearNamedFramebufferuiv +#define glClearNamedFramebufferuiv(...) GLCHECK(gl3wProcs.gl.ClearNamedFramebufferuiv(__VA_ARGS__)) +#undef glClearStencil +#define glClearStencil(...) GLCHECK(gl3wProcs.gl.ClearStencil(__VA_ARGS__)) +#undef glClearTexImage +#define glClearTexImage(...) GLCHECK(gl3wProcs.gl.ClearTexImage(__VA_ARGS__)) +#undef glClearTexSubImage +#define glClearTexSubImage(...) GLCHECK(gl3wProcs.gl.ClearTexSubImage(__VA_ARGS__)) +#undef glClientWaitSync +#define glClientWaitSync(...) GLCHECK(gl3wProcs.gl.ClientWaitSync(__VA_ARGS__)) +#undef glClipControl +#define glClipControl(...) GLCHECK(gl3wProcs.gl.ClipControl(__VA_ARGS__)) +#undef glColorMask +#define glColorMask(...) GLCHECK(gl3wProcs.gl.ColorMask(__VA_ARGS__)) +#undef glColorMaski +#define glColorMaski(...) GLCHECK(gl3wProcs.gl.ColorMaski(__VA_ARGS__)) +#undef glCompileShader +#define glCompileShader(...) GLCHECK(gl3wProcs.gl.CompileShader(__VA_ARGS__)) +#undef glCompileShaderIncludeARB +#define glCompileShaderIncludeARB(...) GLCHECK(gl3wProcs.gl.CompileShaderIncludeARB(__VA_ARGS__)) +#undef glCompressedTexImage1D +#define glCompressedTexImage1D(...) GLCHECK(gl3wProcs.gl.CompressedTexImage1D(__VA_ARGS__)) +#undef glCompressedTexImage2D +#define glCompressedTexImage2D(...) GLCHECK(gl3wProcs.gl.CompressedTexImage2D(__VA_ARGS__)) +#undef glCompressedTexImage3D +#define glCompressedTexImage3D(...) GLCHECK(gl3wProcs.gl.CompressedTexImage3D(__VA_ARGS__)) +#undef glCompressedTexSubImage1D +#define glCompressedTexSubImage1D(...) GLCHECK(gl3wProcs.gl.CompressedTexSubImage1D(__VA_ARGS__)) +#undef glCompressedTexSubImage2D +#define glCompressedTexSubImage2D(...) GLCHECK(gl3wProcs.gl.CompressedTexSubImage2D(__VA_ARGS__)) +#undef glCompressedTexSubImage3D +#define glCompressedTexSubImage3D(...) GLCHECK(gl3wProcs.gl.CompressedTexSubImage3D(__VA_ARGS__)) +#undef glCompressedTextureSubImage1D +#define glCompressedTextureSubImage1D(...) GLCHECK(gl3wProcs.gl.CompressedTextureSubImage1D(__VA_ARGS__)) +#undef glCompressedTextureSubImage2D +#define glCompressedTextureSubImage2D(...) GLCHECK(gl3wProcs.gl.CompressedTextureSubImage2D(__VA_ARGS__)) +#undef glCompressedTextureSubImage3D +#define glCompressedTextureSubImage3D(...) GLCHECK(gl3wProcs.gl.CompressedTextureSubImage3D(__VA_ARGS__)) +#undef glCopyBufferSubData +#define glCopyBufferSubData(...) GLCHECK(gl3wProcs.gl.CopyBufferSubData(__VA_ARGS__)) +#undef glCopyImageSubData +#define glCopyImageSubData(...) GLCHECK(gl3wProcs.gl.CopyImageSubData(__VA_ARGS__)) +#undef glCopyNamedBufferSubData +#define glCopyNamedBufferSubData(...) GLCHECK(gl3wProcs.gl.CopyNamedBufferSubData(__VA_ARGS__)) +#undef glCopyTexImage1D +#define glCopyTexImage1D(...) GLCHECK(gl3wProcs.gl.CopyTexImage1D(__VA_ARGS__)) +#undef glCopyTexImage2D +#define glCopyTexImage2D(...) GLCHECK(gl3wProcs.gl.CopyTexImage2D(__VA_ARGS__)) +#undef glCopyTexSubImage1D +#define glCopyTexSubImage1D(...) GLCHECK(gl3wProcs.gl.CopyTexSubImage1D(__VA_ARGS__)) +#undef glCopyTexSubImage2D +#define glCopyTexSubImage2D(...) GLCHECK(gl3wProcs.gl.CopyTexSubImage2D(__VA_ARGS__)) +#undef glCopyTexSubImage3D +#define glCopyTexSubImage3D(...) GLCHECK(gl3wProcs.gl.CopyTexSubImage3D(__VA_ARGS__)) +#undef glCopyTextureSubImage1D +#define glCopyTextureSubImage1D(...) GLCHECK(gl3wProcs.gl.CopyTextureSubImage1D(__VA_ARGS__)) +#undef glCopyTextureSubImage2D +#define glCopyTextureSubImage2D(...) GLCHECK(gl3wProcs.gl.CopyTextureSubImage2D(__VA_ARGS__)) +#undef glCopyTextureSubImage3D +#define glCopyTextureSubImage3D(...) GLCHECK(gl3wProcs.gl.CopyTextureSubImage3D(__VA_ARGS__)) +#undef glCreateBuffers +#define glCreateBuffers(...) GLCHECK(gl3wProcs.gl.CreateBuffers(__VA_ARGS__)) +#undef glCreateFramebuffers +#define glCreateFramebuffers(...) GLCHECK(gl3wProcs.gl.CreateFramebuffers(__VA_ARGS__)) +#undef glCreateProgramPipelines +#define glCreateProgramPipelines(...) GLCHECK(gl3wProcs.gl.CreateProgramPipelines(__VA_ARGS__)) +#undef glCreateQueries +#define glCreateQueries(...) GLCHECK(gl3wProcs.gl.CreateQueries(__VA_ARGS__)) +#undef glCreateRenderbuffers +#define glCreateRenderbuffers(...) GLCHECK(gl3wProcs.gl.CreateRenderbuffers(__VA_ARGS__)) +#undef glCreateSamplers +#define glCreateSamplers(...) GLCHECK(gl3wProcs.gl.CreateSamplers(__VA_ARGS__)) +#undef glCreateShaderProgramv +#define glCreateShaderProgramv(...) GLCHECK(gl3wProcs.gl.CreateShaderProgramv(__VA_ARGS__)) +#undef glCreateSyncFromCLeventARB +#define glCreateSyncFromCLeventARB(...) GLCHECK(gl3wProcs.gl.CreateSyncFromCLeventARB(__VA_ARGS__)) +#undef glCreateTextures +#define glCreateTextures(...) GLCHECK(gl3wProcs.gl.CreateTextures(__VA_ARGS__)) +#undef glCreateTransformFeedbacks +#define glCreateTransformFeedbacks(...) GLCHECK(gl3wProcs.gl.CreateTransformFeedbacks(__VA_ARGS__)) +#undef glCreateVertexArrays +#define glCreateVertexArrays(...) GLCHECK(gl3wProcs.gl.CreateVertexArrays(__VA_ARGS__)) +#undef glCullFace +#define glCullFace(...) GLCHECK(gl3wProcs.gl.CullFace(__VA_ARGS__)) +#undef glDebugMessageCallback +#define glDebugMessageCallback(...) GLCHECK(gl3wProcs.gl.DebugMessageCallback(__VA_ARGS__)) +#undef glDebugMessageCallbackARB +#define glDebugMessageCallbackARB(...) GLCHECK(gl3wProcs.gl.DebugMessageCallbackARB(__VA_ARGS__)) +#undef glDebugMessageControl +#define glDebugMessageControl(...) GLCHECK(gl3wProcs.gl.DebugMessageControl(__VA_ARGS__)) +#undef glDebugMessageControlARB +#define glDebugMessageControlARB(...) GLCHECK(gl3wProcs.gl.DebugMessageControlARB(__VA_ARGS__)) +#undef glDebugMessageInsert +#define glDebugMessageInsert(...) GLCHECK(gl3wProcs.gl.DebugMessageInsert(__VA_ARGS__)) +#undef glDebugMessageInsertARB +#define glDebugMessageInsertARB(...) GLCHECK(gl3wProcs.gl.DebugMessageInsertARB(__VA_ARGS__)) +#undef glDeleteBuffers +#define glDeleteBuffers(...) GLCHECK(gl3wProcs.gl.DeleteBuffers(__VA_ARGS__)) +#undef glDeleteFramebuffers +#define glDeleteFramebuffers(...) GLCHECK(gl3wProcs.gl.DeleteFramebuffers(__VA_ARGS__)) +#undef glDeleteNamedStringARB +#define glDeleteNamedStringARB(...) GLCHECK(gl3wProcs.gl.DeleteNamedStringARB(__VA_ARGS__)) +#undef glDeleteProgram +#define glDeleteProgram(...) GLCHECK(gl3wProcs.gl.DeleteProgram(__VA_ARGS__)) +#undef glDeleteProgramPipelines +#define glDeleteProgramPipelines(...) GLCHECK(gl3wProcs.gl.DeleteProgramPipelines(__VA_ARGS__)) +#undef glDeleteQueries +#define glDeleteQueries(...) GLCHECK(gl3wProcs.gl.DeleteQueries(__VA_ARGS__)) +#undef glDeleteRenderbuffers +#define glDeleteRenderbuffers(...) GLCHECK(gl3wProcs.gl.DeleteRenderbuffers(__VA_ARGS__)) +#undef glDeleteSamplers +#define glDeleteSamplers(...) GLCHECK(gl3wProcs.gl.DeleteSamplers(__VA_ARGS__)) +#undef glDeleteShader +#define glDeleteShader(...) GLCHECK(gl3wProcs.gl.DeleteShader(__VA_ARGS__)) +#undef glDeleteSync +#define glDeleteSync(...) GLCHECK(gl3wProcs.gl.DeleteSync(__VA_ARGS__)) +#undef glDeleteTextures +#define glDeleteTextures(...) GLCHECK(gl3wProcs.gl.DeleteTextures(__VA_ARGS__)) +#undef glDeleteTransformFeedbacks +#define glDeleteTransformFeedbacks(...) GLCHECK(gl3wProcs.gl.DeleteTransformFeedbacks(__VA_ARGS__)) +#undef glDeleteVertexArrays +#define glDeleteVertexArrays(...) GLCHECK(gl3wProcs.gl.DeleteVertexArrays(__VA_ARGS__)) +#undef glDepthFunc +#define glDepthFunc(...) GLCHECK(gl3wProcs.gl.DepthFunc(__VA_ARGS__)) +#undef glDepthMask +#define glDepthMask(...) GLCHECK(gl3wProcs.gl.DepthMask(__VA_ARGS__)) +#undef glDepthRange +#define glDepthRange(...) GLCHECK(gl3wProcs.gl.DepthRange(__VA_ARGS__)) +#undef glDepthRangeArrayv +#define glDepthRangeArrayv(...) GLCHECK(gl3wProcs.gl.DepthRangeArrayv(__VA_ARGS__)) +#undef glDepthRangeIndexed +#define glDepthRangeIndexed(...) GLCHECK(gl3wProcs.gl.DepthRangeIndexed(__VA_ARGS__)) +#undef glDepthRangef +#define glDepthRangef(...) GLCHECK(gl3wProcs.gl.DepthRangef(__VA_ARGS__)) +#undef glDetachShader +#define glDetachShader(...) GLCHECK(gl3wProcs.gl.DetachShader(__VA_ARGS__)) +#undef glDisable +#define glDisable(...) GLCHECK(gl3wProcs.gl.Disable(__VA_ARGS__)) +#undef glDisableVertexArrayAttrib +#define glDisableVertexArrayAttrib(...) GLCHECK(gl3wProcs.gl.DisableVertexArrayAttrib(__VA_ARGS__)) +#undef glDisableVertexAttribArray +#define glDisableVertexAttribArray(...) GLCHECK(gl3wProcs.gl.DisableVertexAttribArray(__VA_ARGS__)) +#undef glDisablei +#define glDisablei(...) GLCHECK(gl3wProcs.gl.Disablei(__VA_ARGS__)) +#undef glDispatchCompute +#define glDispatchCompute(...) GLCHECK(gl3wProcs.gl.DispatchCompute(__VA_ARGS__)) +#undef glDispatchComputeGroupSizeARB +#define glDispatchComputeGroupSizeARB(...) GLCHECK(gl3wProcs.gl.DispatchComputeGroupSizeARB(__VA_ARGS__)) +#undef glDispatchComputeIndirect +#define glDispatchComputeIndirect(...) GLCHECK(gl3wProcs.gl.DispatchComputeIndirect(__VA_ARGS__)) +#undef glDrawArrays +#define glDrawArrays(...) GLCHECK(gl3wProcs.gl.DrawArrays(__VA_ARGS__)) +#undef glDrawArraysIndirect +#define glDrawArraysIndirect(...) GLCHECK(gl3wProcs.gl.DrawArraysIndirect(__VA_ARGS__)) +#undef glDrawArraysInstanced +#define glDrawArraysInstanced(...) GLCHECK(gl3wProcs.gl.DrawArraysInstanced(__VA_ARGS__)) +#undef glDrawArraysInstancedBaseInstance +#define glDrawArraysInstancedBaseInstance(...) GLCHECK(gl3wProcs.gl.DrawArraysInstancedBaseInstance(__VA_ARGS__)) +#undef glDrawBuffer +#define glDrawBuffer(...) GLCHECK(gl3wProcs.gl.DrawBuffer(__VA_ARGS__)) +#undef glDrawBuffers +#define glDrawBuffers(...) GLCHECK(gl3wProcs.gl.DrawBuffers(__VA_ARGS__)) +#undef glDrawElements +#define glDrawElements(...) GLCHECK(gl3wProcs.gl.DrawElements(__VA_ARGS__)) +#undef glDrawElementsBaseVertex +#define glDrawElementsBaseVertex(...) GLCHECK(gl3wProcs.gl.DrawElementsBaseVertex(__VA_ARGS__)) +#undef glDrawElementsIndirect +#define glDrawElementsIndirect(...) GLCHECK(gl3wProcs.gl.DrawElementsIndirect(__VA_ARGS__)) +#undef glDrawElementsInstanced +#define glDrawElementsInstanced(...) GLCHECK(gl3wProcs.gl.DrawElementsInstanced(__VA_ARGS__)) +#undef glDrawElementsInstancedBaseInstance +#define glDrawElementsInstancedBaseInstance(...) GLCHECK(gl3wProcs.gl.DrawElementsInstancedBaseInstance(__VA_ARGS__)) +#undef glDrawElementsInstancedBaseVertex +#define glDrawElementsInstancedBaseVertex(...) GLCHECK(gl3wProcs.gl.DrawElementsInstancedBaseVertex(__VA_ARGS__)) +#undef glDrawElementsInstancedBaseVertexBaseInstance +#define glDrawElementsInstancedBaseVertexBaseInstance(...) GLCHECK(gl3wProcs.gl.DrawElementsInstancedBaseVertexBaseInstance(__VA_ARGS__)) +#undef glDrawRangeElements +#define glDrawRangeElements(...) GLCHECK(gl3wProcs.gl.DrawRangeElements(__VA_ARGS__)) +#undef glDrawRangeElementsBaseVertex +#define glDrawRangeElementsBaseVertex(...) GLCHECK(gl3wProcs.gl.DrawRangeElementsBaseVertex(__VA_ARGS__)) +#undef glDrawTransformFeedback +#define glDrawTransformFeedback(...) GLCHECK(gl3wProcs.gl.DrawTransformFeedback(__VA_ARGS__)) +#undef glDrawTransformFeedbackInstanced +#define glDrawTransformFeedbackInstanced(...) GLCHECK(gl3wProcs.gl.DrawTransformFeedbackInstanced(__VA_ARGS__)) +#undef glDrawTransformFeedbackStream +#define glDrawTransformFeedbackStream(...) GLCHECK(gl3wProcs.gl.DrawTransformFeedbackStream(__VA_ARGS__)) +#undef glDrawTransformFeedbackStreamInstanced +#define glDrawTransformFeedbackStreamInstanced(...) GLCHECK(gl3wProcs.gl.DrawTransformFeedbackStreamInstanced(__VA_ARGS__)) +#undef glEnable +#define glEnable(...) GLCHECK(gl3wProcs.gl.Enable(__VA_ARGS__)) +#undef glEnableVertexArrayAttrib +#define glEnableVertexArrayAttrib(...) GLCHECK(gl3wProcs.gl.EnableVertexArrayAttrib(__VA_ARGS__)) +#undef glEnableVertexAttribArray +#define glEnableVertexAttribArray(...) GLCHECK(gl3wProcs.gl.EnableVertexAttribArray(__VA_ARGS__)) +#undef glEnablei +#define glEnablei(...) GLCHECK(gl3wProcs.gl.Enablei(__VA_ARGS__)) +#undef glEndConditionalRender +#define glEndConditionalRender(...) GLCHECK(gl3wProcs.gl.EndConditionalRender(__VA_ARGS__)) +#undef glEndQuery +#define glEndQuery(...) GLCHECK(gl3wProcs.gl.EndQuery(__VA_ARGS__)) +#undef glEndQueryIndexed +#define glEndQueryIndexed(...) GLCHECK(gl3wProcs.gl.EndQueryIndexed(__VA_ARGS__)) +#undef glEndTransformFeedback +#define glEndTransformFeedback(...) GLCHECK(gl3wProcs.gl.EndTransformFeedback(__VA_ARGS__)) +#undef glFenceSync +#define glFenceSync(...) GLCHECK(gl3wProcs.gl.FenceSync(__VA_ARGS__)) +#undef glFinish +#define glFinish(...) GLCHECK(gl3wProcs.gl.Finish(__VA_ARGS__)) +#undef glFlush +#define glFlush(...) GLCHECK(gl3wProcs.gl.Flush(__VA_ARGS__)) +#undef glFlushMappedBufferRange +#define glFlushMappedBufferRange(...) GLCHECK(gl3wProcs.gl.FlushMappedBufferRange(__VA_ARGS__)) +#undef glFlushMappedNamedBufferRange +#define glFlushMappedNamedBufferRange(...) GLCHECK(gl3wProcs.gl.FlushMappedNamedBufferRange(__VA_ARGS__)) +#undef glFramebufferParameteri +#define glFramebufferParameteri(...) GLCHECK(gl3wProcs.gl.FramebufferParameteri(__VA_ARGS__)) +#undef glFramebufferRenderbuffer +#define glFramebufferRenderbuffer(...) GLCHECK(gl3wProcs.gl.FramebufferRenderbuffer(__VA_ARGS__)) +#undef glFramebufferTexture +#define glFramebufferTexture(...) GLCHECK(gl3wProcs.gl.FramebufferTexture(__VA_ARGS__)) +#undef glFramebufferTexture1D +#define glFramebufferTexture1D(...) GLCHECK(gl3wProcs.gl.FramebufferTexture1D(__VA_ARGS__)) +#undef glFramebufferTexture2D +#define glFramebufferTexture2D(...) GLCHECK(gl3wProcs.gl.FramebufferTexture2D(__VA_ARGS__)) +#undef glFramebufferTexture3D +#define glFramebufferTexture3D(...) GLCHECK(gl3wProcs.gl.FramebufferTexture3D(__VA_ARGS__)) +#undef glFramebufferTextureLayer +#define glFramebufferTextureLayer(...) GLCHECK(gl3wProcs.gl.FramebufferTextureLayer(__VA_ARGS__)) +#undef glFrontFace +#define glFrontFace(...) GLCHECK(gl3wProcs.gl.FrontFace(__VA_ARGS__)) +#undef glGenBuffers +#define glGenBuffers(...) GLCHECK(gl3wProcs.gl.GenBuffers(__VA_ARGS__)) +#undef glGenFramebuffers +#define glGenFramebuffers(...) GLCHECK(gl3wProcs.gl.GenFramebuffers(__VA_ARGS__)) +#undef glGenProgramPipelines +#define glGenProgramPipelines(...) GLCHECK(gl3wProcs.gl.GenProgramPipelines(__VA_ARGS__)) +#undef glGenQueries +#define glGenQueries(...) GLCHECK(gl3wProcs.gl.GenQueries(__VA_ARGS__)) +#undef glGenRenderbuffers +#define glGenRenderbuffers(...) GLCHECK(gl3wProcs.gl.GenRenderbuffers(__VA_ARGS__)) +#undef glGenSamplers +#define glGenSamplers(...) GLCHECK(gl3wProcs.gl.GenSamplers(__VA_ARGS__)) +#undef glGenTextures +#define glGenTextures(...) GLCHECK(gl3wProcs.gl.GenTextures(__VA_ARGS__)) +#undef glGenTransformFeedbacks +#define glGenTransformFeedbacks(...) GLCHECK(gl3wProcs.gl.GenTransformFeedbacks(__VA_ARGS__)) +#undef glGenVertexArrays +#define glGenVertexArrays(...) GLCHECK(gl3wProcs.gl.GenVertexArrays(__VA_ARGS__)) +#undef glGenerateMipmap +#define glGenerateMipmap(...) GLCHECK(gl3wProcs.gl.GenerateMipmap(__VA_ARGS__)) +#undef glGenerateTextureMipmap +#define glGenerateTextureMipmap(...) GLCHECK(gl3wProcs.gl.GenerateTextureMipmap(__VA_ARGS__)) +#undef glGetActiveAtomicCounterBufferiv +#define glGetActiveAtomicCounterBufferiv(...) GLCHECK(gl3wProcs.gl.GetActiveAtomicCounterBufferiv(__VA_ARGS__)) +#undef glGetActiveAttrib +#define glGetActiveAttrib(...) GLCHECK(gl3wProcs.gl.GetActiveAttrib(__VA_ARGS__)) +#undef glGetActiveSubroutineName +#define glGetActiveSubroutineName(...) GLCHECK(gl3wProcs.gl.GetActiveSubroutineName(__VA_ARGS__)) +#undef glGetActiveSubroutineUniformName +#define glGetActiveSubroutineUniformName(...) GLCHECK(gl3wProcs.gl.GetActiveSubroutineUniformName(__VA_ARGS__)) +#undef glGetActiveSubroutineUniformiv +#define glGetActiveSubroutineUniformiv(...) GLCHECK(gl3wProcs.gl.GetActiveSubroutineUniformiv(__VA_ARGS__)) +#undef glGetActiveUniform +#define glGetActiveUniform(...) GLCHECK(gl3wProcs.gl.GetActiveUniform(__VA_ARGS__)) +#undef glGetActiveUniformBlockName +#define glGetActiveUniformBlockName(...) GLCHECK(gl3wProcs.gl.GetActiveUniformBlockName(__VA_ARGS__)) +#undef glGetActiveUniformBlockiv +#define glGetActiveUniformBlockiv(...) GLCHECK(gl3wProcs.gl.GetActiveUniformBlockiv(__VA_ARGS__)) +#undef glGetActiveUniformName +#define glGetActiveUniformName(...) GLCHECK(gl3wProcs.gl.GetActiveUniformName(__VA_ARGS__)) +#undef glGetActiveUniformsiv +#define glGetActiveUniformsiv(...) GLCHECK(gl3wProcs.gl.GetActiveUniformsiv(__VA_ARGS__)) +#undef glGetAttachedShaders +#define glGetAttachedShaders(...) GLCHECK(gl3wProcs.gl.GetAttachedShaders(__VA_ARGS__)) +#undef glGetBooleani_v +#define glGetBooleani_v(...) GLCHECK(gl3wProcs.gl.GetBooleani_v(__VA_ARGS__)) +#undef glGetBooleanv +#define glGetBooleanv(...) GLCHECK(gl3wProcs.gl.GetBooleanv(__VA_ARGS__)) +#undef glGetBufferParameteri64v +#define glGetBufferParameteri64v(...) GLCHECK(gl3wProcs.gl.GetBufferParameteri64v(__VA_ARGS__)) +#undef glGetBufferParameteriv +#define glGetBufferParameteriv(...) GLCHECK(gl3wProcs.gl.GetBufferParameteriv(__VA_ARGS__)) +#undef glGetBufferPointerv +#define glGetBufferPointerv(...) GLCHECK(gl3wProcs.gl.GetBufferPointerv(__VA_ARGS__)) +#undef glGetBufferSubData +#define glGetBufferSubData(...) GLCHECK(gl3wProcs.gl.GetBufferSubData(__VA_ARGS__)) +#undef glGetCompressedTexImage +#define glGetCompressedTexImage(...) GLCHECK(gl3wProcs.gl.GetCompressedTexImage(__VA_ARGS__)) +#undef glGetCompressedTextureImage +#define glGetCompressedTextureImage(...) GLCHECK(gl3wProcs.gl.GetCompressedTextureImage(__VA_ARGS__)) +#undef glGetCompressedTextureSubImage +#define glGetCompressedTextureSubImage(...) GLCHECK(gl3wProcs.gl.GetCompressedTextureSubImage(__VA_ARGS__)) +#undef glGetDebugMessageLog +#define glGetDebugMessageLog(...) GLCHECK(gl3wProcs.gl.GetDebugMessageLog(__VA_ARGS__)) +#undef glGetDebugMessageLogARB +#define glGetDebugMessageLogARB(...) GLCHECK(gl3wProcs.gl.GetDebugMessageLogARB(__VA_ARGS__)) +#undef glGetDoublei_v +#define glGetDoublei_v(...) GLCHECK(gl3wProcs.gl.GetDoublei_v(__VA_ARGS__)) +#undef glGetDoublev +#define glGetDoublev(...) GLCHECK(gl3wProcs.gl.GetDoublev(__VA_ARGS__)) +#undef glGetFloati_v +#define glGetFloati_v(...) GLCHECK(gl3wProcs.gl.GetFloati_v(__VA_ARGS__)) +#undef glGetFloatv +#define glGetFloatv(...) GLCHECK(gl3wProcs.gl.GetFloatv(__VA_ARGS__)) +#undef glGetFragDataIndex +#define glGetFragDataIndex(...) GLCHECK(gl3wProcs.gl.GetFragDataIndex(__VA_ARGS__)) +#undef glGetFragDataLocation +#define glGetFragDataLocation(...) GLCHECK(gl3wProcs.gl.GetFragDataLocation(__VA_ARGS__)) +#undef glGetFramebufferAttachmentParameteriv +#define glGetFramebufferAttachmentParameteriv(...) GLCHECK(gl3wProcs.gl.GetFramebufferAttachmentParameteriv(__VA_ARGS__)) +#undef glGetFramebufferParameteriv +#define glGetFramebufferParameteriv(...) GLCHECK(gl3wProcs.gl.GetFramebufferParameteriv(__VA_ARGS__)) +#undef glGetGraphicsResetStatus +#define glGetGraphicsResetStatus(...) GLCHECK(gl3wProcs.gl.GetGraphicsResetStatus(__VA_ARGS__)) +#undef glGetGraphicsResetStatusARB +#define glGetGraphicsResetStatusARB(...) GLCHECK(gl3wProcs.gl.GetGraphicsResetStatusARB(__VA_ARGS__)) +#undef glGetImageHandleARB +#define glGetImageHandleARB(...) GLCHECK(gl3wProcs.gl.GetImageHandleARB(__VA_ARGS__)) +#undef glGetInteger64i_v +#define glGetInteger64i_v(...) GLCHECK(gl3wProcs.gl.GetInteger64i_v(__VA_ARGS__)) +#undef glGetInteger64v +#define glGetInteger64v(...) GLCHECK(gl3wProcs.gl.GetInteger64v(__VA_ARGS__)) +#undef glGetIntegeri_v +#define glGetIntegeri_v(...) GLCHECK(gl3wProcs.gl.GetIntegeri_v(__VA_ARGS__)) +#undef glGetIntegerv +#define glGetIntegerv(...) GLCHECK(gl3wProcs.gl.GetIntegerv(__VA_ARGS__)) +#undef glGetInternalformati64v +#define glGetInternalformati64v(...) GLCHECK(gl3wProcs.gl.GetInternalformati64v(__VA_ARGS__)) +#undef glGetInternalformativ +#define glGetInternalformativ(...) GLCHECK(gl3wProcs.gl.GetInternalformativ(__VA_ARGS__)) +#undef glGetMultisamplefv +#define glGetMultisamplefv(...) GLCHECK(gl3wProcs.gl.GetMultisamplefv(__VA_ARGS__)) +#undef glGetNamedBufferParameteri64v +#define glGetNamedBufferParameteri64v(...) GLCHECK(gl3wProcs.gl.GetNamedBufferParameteri64v(__VA_ARGS__)) +#undef glGetNamedBufferParameteriv +#define glGetNamedBufferParameteriv(...) GLCHECK(gl3wProcs.gl.GetNamedBufferParameteriv(__VA_ARGS__)) +#undef glGetNamedBufferPointerv +#define glGetNamedBufferPointerv(...) GLCHECK(gl3wProcs.gl.GetNamedBufferPointerv(__VA_ARGS__)) +#undef glGetNamedBufferSubData +#define glGetNamedBufferSubData(...) GLCHECK(gl3wProcs.gl.GetNamedBufferSubData(__VA_ARGS__)) +#undef glGetNamedFramebufferAttachmentParameteriv +#define glGetNamedFramebufferAttachmentParameteriv(...) GLCHECK(gl3wProcs.gl.GetNamedFramebufferAttachmentParameteriv(__VA_ARGS__)) +#undef glGetNamedFramebufferParameteriv +#define glGetNamedFramebufferParameteriv(...) GLCHECK(gl3wProcs.gl.GetNamedFramebufferParameteriv(__VA_ARGS__)) +#undef glGetNamedRenderbufferParameteriv +#define glGetNamedRenderbufferParameteriv(...) GLCHECK(gl3wProcs.gl.GetNamedRenderbufferParameteriv(__VA_ARGS__)) +#undef glGetNamedStringARB +#define glGetNamedStringARB(...) GLCHECK(gl3wProcs.gl.GetNamedStringARB(__VA_ARGS__)) +#undef glGetNamedStringivARB +#define glGetNamedStringivARB(...) GLCHECK(gl3wProcs.gl.GetNamedStringivARB(__VA_ARGS__)) +#undef glGetObjectLabel +#define glGetObjectLabel(...) GLCHECK(gl3wProcs.gl.GetObjectLabel(__VA_ARGS__)) +#undef glGetObjectPtrLabel +#define glGetObjectPtrLabel(...) GLCHECK(gl3wProcs.gl.GetObjectPtrLabel(__VA_ARGS__)) +#undef glGetPointerv +#define glGetPointerv(...) GLCHECK(gl3wProcs.gl.GetPointerv(__VA_ARGS__)) +#undef glGetProgramBinary +#define glGetProgramBinary(...) GLCHECK(gl3wProcs.gl.GetProgramBinary(__VA_ARGS__)) +#undef glGetProgramInfoLog +#define glGetProgramInfoLog(...) GLCHECK(gl3wProcs.gl.GetProgramInfoLog(__VA_ARGS__)) +#undef glGetProgramInterfaceiv +#define glGetProgramInterfaceiv(...) GLCHECK(gl3wProcs.gl.GetProgramInterfaceiv(__VA_ARGS__)) +#undef glGetProgramPipelineInfoLog +#define glGetProgramPipelineInfoLog(...) GLCHECK(gl3wProcs.gl.GetProgramPipelineInfoLog(__VA_ARGS__)) +#undef glGetProgramPipelineiv +#define glGetProgramPipelineiv(...) GLCHECK(gl3wProcs.gl.GetProgramPipelineiv(__VA_ARGS__)) +#undef glGetProgramResourceIndex +#define glGetProgramResourceIndex(...) GLCHECK(gl3wProcs.gl.GetProgramResourceIndex(__VA_ARGS__)) +#undef glGetProgramResourceLocation +#define glGetProgramResourceLocation(...) GLCHECK(gl3wProcs.gl.GetProgramResourceLocation(__VA_ARGS__)) +#undef glGetProgramResourceLocationIndex +#define glGetProgramResourceLocationIndex(...) GLCHECK(gl3wProcs.gl.GetProgramResourceLocationIndex(__VA_ARGS__)) +#undef glGetProgramResourceName +#define glGetProgramResourceName(...) GLCHECK(gl3wProcs.gl.GetProgramResourceName(__VA_ARGS__)) +#undef glGetProgramResourceiv +#define glGetProgramResourceiv(...) GLCHECK(gl3wProcs.gl.GetProgramResourceiv(__VA_ARGS__)) +#undef glGetProgramStageiv +#define glGetProgramStageiv(...) GLCHECK(gl3wProcs.gl.GetProgramStageiv(__VA_ARGS__)) +#undef glGetProgramiv +#define glGetProgramiv(...) GLCHECK(gl3wProcs.gl.GetProgramiv(__VA_ARGS__)) +#undef glGetQueryBufferObjecti64v +#define glGetQueryBufferObjecti64v(...) GLCHECK(gl3wProcs.gl.GetQueryBufferObjecti64v(__VA_ARGS__)) +#undef glGetQueryBufferObjectiv +#define glGetQueryBufferObjectiv(...) GLCHECK(gl3wProcs.gl.GetQueryBufferObjectiv(__VA_ARGS__)) +#undef glGetQueryBufferObjectui64v +#define glGetQueryBufferObjectui64v(...) GLCHECK(gl3wProcs.gl.GetQueryBufferObjectui64v(__VA_ARGS__)) +#undef glGetQueryBufferObjectuiv +#define glGetQueryBufferObjectuiv(...) GLCHECK(gl3wProcs.gl.GetQueryBufferObjectuiv(__VA_ARGS__)) +#undef glGetQueryIndexediv +#define glGetQueryIndexediv(...) GLCHECK(gl3wProcs.gl.GetQueryIndexediv(__VA_ARGS__)) +#undef glGetQueryObjecti64v +#define glGetQueryObjecti64v(...) GLCHECK(gl3wProcs.gl.GetQueryObjecti64v(__VA_ARGS__)) +#undef glGetQueryObjectiv +#define glGetQueryObjectiv(...) GLCHECK(gl3wProcs.gl.GetQueryObjectiv(__VA_ARGS__)) +#undef glGetQueryObjectui64v +#define glGetQueryObjectui64v(...) GLCHECK(gl3wProcs.gl.GetQueryObjectui64v(__VA_ARGS__)) +#undef glGetQueryObjectuiv +#define glGetQueryObjectuiv(...) GLCHECK(gl3wProcs.gl.GetQueryObjectuiv(__VA_ARGS__)) +#undef glGetQueryiv +#define glGetQueryiv(...) GLCHECK(gl3wProcs.gl.GetQueryiv(__VA_ARGS__)) +#undef glGetRenderbufferParameteriv +#define glGetRenderbufferParameteriv(...) GLCHECK(gl3wProcs.gl.GetRenderbufferParameteriv(__VA_ARGS__)) +#undef glGetSamplerParameterIiv +#define glGetSamplerParameterIiv(...) GLCHECK(gl3wProcs.gl.GetSamplerParameterIiv(__VA_ARGS__)) +#undef glGetSamplerParameterIuiv +#define glGetSamplerParameterIuiv(...) GLCHECK(gl3wProcs.gl.GetSamplerParameterIuiv(__VA_ARGS__)) +#undef glGetSamplerParameterfv +#define glGetSamplerParameterfv(...) GLCHECK(gl3wProcs.gl.GetSamplerParameterfv(__VA_ARGS__)) +#undef glGetSamplerParameteriv +#define glGetSamplerParameteriv(...) GLCHECK(gl3wProcs.gl.GetSamplerParameteriv(__VA_ARGS__)) +#undef glGetShaderInfoLog +#define glGetShaderInfoLog(...) GLCHECK(gl3wProcs.gl.GetShaderInfoLog(__VA_ARGS__)) +#undef glGetShaderPrecisionFormat +#define glGetShaderPrecisionFormat(...) GLCHECK(gl3wProcs.gl.GetShaderPrecisionFormat(__VA_ARGS__)) +#undef glGetShaderSource +#define glGetShaderSource(...) GLCHECK(gl3wProcs.gl.GetShaderSource(__VA_ARGS__)) +#undef glGetShaderiv +#define glGetShaderiv(...) GLCHECK(gl3wProcs.gl.GetShaderiv(__VA_ARGS__)) +#undef glGetSubroutineIndex +#define glGetSubroutineIndex(...) GLCHECK(gl3wProcs.gl.GetSubroutineIndex(__VA_ARGS__)) +#undef glGetSubroutineUniformLocation +#define glGetSubroutineUniformLocation(...) GLCHECK(gl3wProcs.gl.GetSubroutineUniformLocation(__VA_ARGS__)) +#undef glGetSynciv +#define glGetSynciv(...) GLCHECK(gl3wProcs.gl.GetSynciv(__VA_ARGS__)) +#undef glGetTexImage +#define glGetTexImage(...) GLCHECK(gl3wProcs.gl.GetTexImage(__VA_ARGS__)) +#undef glGetTexLevelParameterfv +#define glGetTexLevelParameterfv(...) GLCHECK(gl3wProcs.gl.GetTexLevelParameterfv(__VA_ARGS__)) +#undef glGetTexLevelParameteriv +#define glGetTexLevelParameteriv(...) GLCHECK(gl3wProcs.gl.GetTexLevelParameteriv(__VA_ARGS__)) +#undef glGetTexParameterIiv +#define glGetTexParameterIiv(...) GLCHECK(gl3wProcs.gl.GetTexParameterIiv(__VA_ARGS__)) +#undef glGetTexParameterIuiv +#define glGetTexParameterIuiv(...) GLCHECK(gl3wProcs.gl.GetTexParameterIuiv(__VA_ARGS__)) +#undef glGetTexParameterfv +#define glGetTexParameterfv(...) GLCHECK(gl3wProcs.gl.GetTexParameterfv(__VA_ARGS__)) +#undef glGetTexParameteriv +#define glGetTexParameteriv(...) GLCHECK(gl3wProcs.gl.GetTexParameteriv(__VA_ARGS__)) +#undef glGetTextureHandleARB +#define glGetTextureHandleARB(...) GLCHECK(gl3wProcs.gl.GetTextureHandleARB(__VA_ARGS__)) +#undef glGetTextureImage +#define glGetTextureImage(...) GLCHECK(gl3wProcs.gl.GetTextureImage(__VA_ARGS__)) +#undef glGetTextureLevelParameterfv +#define glGetTextureLevelParameterfv(...) GLCHECK(gl3wProcs.gl.GetTextureLevelParameterfv(__VA_ARGS__)) +#undef glGetTextureLevelParameteriv +#define glGetTextureLevelParameteriv(...) GLCHECK(gl3wProcs.gl.GetTextureLevelParameteriv(__VA_ARGS__)) +#undef glGetTextureParameterIiv +#define glGetTextureParameterIiv(...) GLCHECK(gl3wProcs.gl.GetTextureParameterIiv(__VA_ARGS__)) +#undef glGetTextureParameterIuiv +#define glGetTextureParameterIuiv(...) GLCHECK(gl3wProcs.gl.GetTextureParameterIuiv(__VA_ARGS__)) +#undef glGetTextureParameterfv +#define glGetTextureParameterfv(...) GLCHECK(gl3wProcs.gl.GetTextureParameterfv(__VA_ARGS__)) +#undef glGetTextureParameteriv +#define glGetTextureParameteriv(...) GLCHECK(gl3wProcs.gl.GetTextureParameteriv(__VA_ARGS__)) +#undef glGetTextureSamplerHandleARB +#define glGetTextureSamplerHandleARB(...) GLCHECK(gl3wProcs.gl.GetTextureSamplerHandleARB(__VA_ARGS__)) +#undef glGetTextureSubImage +#define glGetTextureSubImage(...) GLCHECK(gl3wProcs.gl.GetTextureSubImage(__VA_ARGS__)) +#undef glGetTransformFeedbackVarying +#define glGetTransformFeedbackVarying(...) GLCHECK(gl3wProcs.gl.GetTransformFeedbackVarying(__VA_ARGS__)) +#undef glGetTransformFeedbacki64_v +#define glGetTransformFeedbacki64_v(...) GLCHECK(gl3wProcs.gl.GetTransformFeedbacki64_v(__VA_ARGS__)) +#undef glGetTransformFeedbacki_v +#define glGetTransformFeedbacki_v(...) GLCHECK(gl3wProcs.gl.GetTransformFeedbacki_v(__VA_ARGS__)) +#undef glGetTransformFeedbackiv +#define glGetTransformFeedbackiv(...) GLCHECK(gl3wProcs.gl.GetTransformFeedbackiv(__VA_ARGS__)) +#undef glGetUniformBlockIndex +#define glGetUniformBlockIndex(...) GLCHECK(gl3wProcs.gl.GetUniformBlockIndex(__VA_ARGS__)) +#undef glGetUniformIndices +#define glGetUniformIndices(...) GLCHECK(gl3wProcs.gl.GetUniformIndices(__VA_ARGS__)) +#undef glGetUniformSubroutineuiv +#define glGetUniformSubroutineuiv(...) GLCHECK(gl3wProcs.gl.GetUniformSubroutineuiv(__VA_ARGS__)) +#undef glGetUniformdv +#define glGetUniformdv(...) GLCHECK(gl3wProcs.gl.GetUniformdv(__VA_ARGS__)) +#undef glGetUniformfv +#define glGetUniformfv(...) GLCHECK(gl3wProcs.gl.GetUniformfv(__VA_ARGS__)) +#undef glGetUniformiv +#define glGetUniformiv(...) GLCHECK(gl3wProcs.gl.GetUniformiv(__VA_ARGS__)) +#undef glGetUniformuiv +#define glGetUniformuiv(...) GLCHECK(gl3wProcs.gl.GetUniformuiv(__VA_ARGS__)) +#undef glGetVertexArrayIndexed64iv +#define glGetVertexArrayIndexed64iv(...) GLCHECK(gl3wProcs.gl.GetVertexArrayIndexed64iv(__VA_ARGS__)) +#undef glGetVertexArrayIndexediv +#define glGetVertexArrayIndexediv(...) GLCHECK(gl3wProcs.gl.GetVertexArrayIndexediv(__VA_ARGS__)) +#undef glGetVertexArrayiv +#define glGetVertexArrayiv(...) GLCHECK(gl3wProcs.gl.GetVertexArrayiv(__VA_ARGS__)) +#undef glGetVertexAttribIiv +#define glGetVertexAttribIiv(...) GLCHECK(gl3wProcs.gl.GetVertexAttribIiv(__VA_ARGS__)) +#undef glGetVertexAttribIuiv +#define glGetVertexAttribIuiv(...) GLCHECK(gl3wProcs.gl.GetVertexAttribIuiv(__VA_ARGS__)) +#undef glGetVertexAttribLdv +#define glGetVertexAttribLdv(...) GLCHECK(gl3wProcs.gl.GetVertexAttribLdv(__VA_ARGS__)) +#undef glGetVertexAttribLui64vARB +#define glGetVertexAttribLui64vARB(...) GLCHECK(gl3wProcs.gl.GetVertexAttribLui64vARB(__VA_ARGS__)) +#undef glGetVertexAttribPointerv +#define glGetVertexAttribPointerv(...) GLCHECK(gl3wProcs.gl.GetVertexAttribPointerv(__VA_ARGS__)) +#undef glGetVertexAttribdv +#define glGetVertexAttribdv(...) GLCHECK(gl3wProcs.gl.GetVertexAttribdv(__VA_ARGS__)) +#undef glGetVertexAttribfv +#define glGetVertexAttribfv(...) GLCHECK(gl3wProcs.gl.GetVertexAttribfv(__VA_ARGS__)) +#undef glGetVertexAttribiv +#define glGetVertexAttribiv(...) GLCHECK(gl3wProcs.gl.GetVertexAttribiv(__VA_ARGS__)) +#undef glGetnCompressedTexImage +#define glGetnCompressedTexImage(...) GLCHECK(gl3wProcs.gl.GetnCompressedTexImage(__VA_ARGS__)) +#undef glGetnCompressedTexImageARB +#define glGetnCompressedTexImageARB(...) GLCHECK(gl3wProcs.gl.GetnCompressedTexImageARB(__VA_ARGS__)) +#undef glGetnTexImage +#define glGetnTexImage(...) GLCHECK(gl3wProcs.gl.GetnTexImage(__VA_ARGS__)) +#undef glGetnTexImageARB +#define glGetnTexImageARB(...) GLCHECK(gl3wProcs.gl.GetnTexImageARB(__VA_ARGS__)) +#undef glGetnUniformdv +#define glGetnUniformdv(...) GLCHECK(gl3wProcs.gl.GetnUniformdv(__VA_ARGS__)) +#undef glGetnUniformdvARB +#define glGetnUniformdvARB(...) GLCHECK(gl3wProcs.gl.GetnUniformdvARB(__VA_ARGS__)) +#undef glGetnUniformfv +#define glGetnUniformfv(...) GLCHECK(gl3wProcs.gl.GetnUniformfv(__VA_ARGS__)) +#undef glGetnUniformfvARB +#define glGetnUniformfvARB(...) GLCHECK(gl3wProcs.gl.GetnUniformfvARB(__VA_ARGS__)) +#undef glGetnUniformiv +#define glGetnUniformiv(...) GLCHECK(gl3wProcs.gl.GetnUniformiv(__VA_ARGS__)) +#undef glGetnUniformivARB +#define glGetnUniformivARB(...) GLCHECK(gl3wProcs.gl.GetnUniformivARB(__VA_ARGS__)) +#undef glGetnUniformuiv +#define glGetnUniformuiv(...) GLCHECK(gl3wProcs.gl.GetnUniformuiv(__VA_ARGS__)) +#undef glGetnUniformuivARB +#define glGetnUniformuivARB(...) GLCHECK(gl3wProcs.gl.GetnUniformuivARB(__VA_ARGS__)) +#undef glHint +#define glHint(...) GLCHECK(gl3wProcs.gl.Hint(__VA_ARGS__)) +#undef glInvalidateBufferData +#define glInvalidateBufferData(...) GLCHECK(gl3wProcs.gl.InvalidateBufferData(__VA_ARGS__)) +#undef glInvalidateBufferSubData +#define glInvalidateBufferSubData(...) GLCHECK(gl3wProcs.gl.InvalidateBufferSubData(__VA_ARGS__)) +#undef glInvalidateFramebuffer +#define glInvalidateFramebuffer(...) GLCHECK(gl3wProcs.gl.InvalidateFramebuffer(__VA_ARGS__)) +#undef glInvalidateNamedFramebufferData +#define glInvalidateNamedFramebufferData(...) GLCHECK(gl3wProcs.gl.InvalidateNamedFramebufferData(__VA_ARGS__)) +#undef glInvalidateNamedFramebufferSubData +#define glInvalidateNamedFramebufferSubData(...) GLCHECK(gl3wProcs.gl.InvalidateNamedFramebufferSubData(__VA_ARGS__)) +#undef glInvalidateSubFramebuffer +#define glInvalidateSubFramebuffer(...) GLCHECK(gl3wProcs.gl.InvalidateSubFramebuffer(__VA_ARGS__)) +#undef glInvalidateTexImage +#define glInvalidateTexImage(...) GLCHECK(gl3wProcs.gl.InvalidateTexImage(__VA_ARGS__)) +#undef glInvalidateTexSubImage +#define glInvalidateTexSubImage(...) GLCHECK(gl3wProcs.gl.InvalidateTexSubImage(__VA_ARGS__)) +#undef glLineWidth +#define glLineWidth(...) GLCHECK(gl3wProcs.gl.LineWidth(__VA_ARGS__)) +#undef glLinkProgram +#define glLinkProgram(...) GLCHECK(gl3wProcs.gl.LinkProgram(__VA_ARGS__)) +#undef glLogicOp +#define glLogicOp(...) GLCHECK(gl3wProcs.gl.LogicOp(__VA_ARGS__)) +#undef glMakeImageHandleNonResidentARB +#define glMakeImageHandleNonResidentARB(...) GLCHECK(gl3wProcs.gl.MakeImageHandleNonResidentARB(__VA_ARGS__)) +#undef glMakeImageHandleResidentARB +#define glMakeImageHandleResidentARB(...) GLCHECK(gl3wProcs.gl.MakeImageHandleResidentARB(__VA_ARGS__)) +#undef glMakeTextureHandleNonResidentARB +#define glMakeTextureHandleNonResidentARB(...) GLCHECK(gl3wProcs.gl.MakeTextureHandleNonResidentARB(__VA_ARGS__)) +#undef glMakeTextureHandleResidentARB +#define glMakeTextureHandleResidentARB(...) GLCHECK(gl3wProcs.gl.MakeTextureHandleResidentARB(__VA_ARGS__)) +#undef glMemoryBarrier +#define glMemoryBarrier(...) GLCHECK(gl3wProcs.gl.MemoryBarrier(__VA_ARGS__)) +#undef glMemoryBarrierByRegion +#define glMemoryBarrierByRegion(...) GLCHECK(gl3wProcs.gl.MemoryBarrierByRegion(__VA_ARGS__)) +#undef glMinSampleShading +#define glMinSampleShading(...) GLCHECK(gl3wProcs.gl.MinSampleShading(__VA_ARGS__)) +#undef glMinSampleShadingARB +#define glMinSampleShadingARB(...) GLCHECK(gl3wProcs.gl.MinSampleShadingARB(__VA_ARGS__)) +#undef glMultiDrawArrays +#define glMultiDrawArrays(...) GLCHECK(gl3wProcs.gl.MultiDrawArrays(__VA_ARGS__)) +#undef glMultiDrawArraysIndirect +#define glMultiDrawArraysIndirect(...) GLCHECK(gl3wProcs.gl.MultiDrawArraysIndirect(__VA_ARGS__)) +#undef glMultiDrawArraysIndirectCountARB +#define glMultiDrawArraysIndirectCountARB(...) GLCHECK(gl3wProcs.gl.MultiDrawArraysIndirectCountARB(__VA_ARGS__)) +#undef glMultiDrawElements +#define glMultiDrawElements(...) GLCHECK(gl3wProcs.gl.MultiDrawElements(__VA_ARGS__)) +#undef glMultiDrawElementsBaseVertex +#define glMultiDrawElementsBaseVertex(...) GLCHECK(gl3wProcs.gl.MultiDrawElementsBaseVertex(__VA_ARGS__)) +#undef glMultiDrawElementsIndirect +#define glMultiDrawElementsIndirect(...) GLCHECK(gl3wProcs.gl.MultiDrawElementsIndirect(__VA_ARGS__)) +#undef glMultiDrawElementsIndirectCountARB +#define glMultiDrawElementsIndirectCountARB(...) GLCHECK(gl3wProcs.gl.MultiDrawElementsIndirectCountARB(__VA_ARGS__)) +#undef glNamedBufferData +#define glNamedBufferData(...) GLCHECK(gl3wProcs.gl.NamedBufferData(__VA_ARGS__)) +#undef glNamedBufferPageCommitmentARB +#define glNamedBufferPageCommitmentARB(...) GLCHECK(gl3wProcs.gl.NamedBufferPageCommitmentARB(__VA_ARGS__)) +#undef glNamedBufferPageCommitmentEXT +#define glNamedBufferPageCommitmentEXT(...) GLCHECK(gl3wProcs.gl.NamedBufferPageCommitmentEXT(__VA_ARGS__)) +#undef glNamedBufferStorage +#define glNamedBufferStorage(...) GLCHECK(gl3wProcs.gl.NamedBufferStorage(__VA_ARGS__)) +#undef glNamedBufferSubData +#define glNamedBufferSubData(...) GLCHECK(gl3wProcs.gl.NamedBufferSubData(__VA_ARGS__)) +#undef glNamedFramebufferDrawBuffer +#define glNamedFramebufferDrawBuffer(...) GLCHECK(gl3wProcs.gl.NamedFramebufferDrawBuffer(__VA_ARGS__)) +#undef glNamedFramebufferDrawBuffers +#define glNamedFramebufferDrawBuffers(...) GLCHECK(gl3wProcs.gl.NamedFramebufferDrawBuffers(__VA_ARGS__)) +#undef glNamedFramebufferParameteri +#define glNamedFramebufferParameteri(...) GLCHECK(gl3wProcs.gl.NamedFramebufferParameteri(__VA_ARGS__)) +#undef glNamedFramebufferReadBuffer +#define glNamedFramebufferReadBuffer(...) GLCHECK(gl3wProcs.gl.NamedFramebufferReadBuffer(__VA_ARGS__)) +#undef glNamedFramebufferRenderbuffer +#define glNamedFramebufferRenderbuffer(...) GLCHECK(gl3wProcs.gl.NamedFramebufferRenderbuffer(__VA_ARGS__)) +#undef glNamedFramebufferTexture +#define glNamedFramebufferTexture(...) GLCHECK(gl3wProcs.gl.NamedFramebufferTexture(__VA_ARGS__)) +#undef glNamedFramebufferTextureLayer +#define glNamedFramebufferTextureLayer(...) GLCHECK(gl3wProcs.gl.NamedFramebufferTextureLayer(__VA_ARGS__)) +#undef glNamedRenderbufferStorage +#define glNamedRenderbufferStorage(...) GLCHECK(gl3wProcs.gl.NamedRenderbufferStorage(__VA_ARGS__)) +#undef glNamedRenderbufferStorageMultisample +#define glNamedRenderbufferStorageMultisample(...) GLCHECK(gl3wProcs.gl.NamedRenderbufferStorageMultisample(__VA_ARGS__)) +#undef glNamedStringARB +#define glNamedStringARB(...) GLCHECK(gl3wProcs.gl.NamedStringARB(__VA_ARGS__)) +#undef glObjectLabel +#define glObjectLabel(...) GLCHECK(gl3wProcs.gl.ObjectLabel(__VA_ARGS__)) +#undef glObjectPtrLabel +#define glObjectPtrLabel(...) GLCHECK(gl3wProcs.gl.ObjectPtrLabel(__VA_ARGS__)) +#undef glPatchParameterfv +#define glPatchParameterfv(...) GLCHECK(gl3wProcs.gl.PatchParameterfv(__VA_ARGS__)) +#undef glPatchParameteri +#define glPatchParameteri(...) GLCHECK(gl3wProcs.gl.PatchParameteri(__VA_ARGS__)) +#undef glPauseTransformFeedback +#define glPauseTransformFeedback(...) GLCHECK(gl3wProcs.gl.PauseTransformFeedback(__VA_ARGS__)) +#undef glPixelStoref +#define glPixelStoref(...) GLCHECK(gl3wProcs.gl.PixelStoref(__VA_ARGS__)) +#undef glPixelStorei +#define glPixelStorei(...) GLCHECK(gl3wProcs.gl.PixelStorei(__VA_ARGS__)) +#undef glPointParameterf +#define glPointParameterf(...) GLCHECK(gl3wProcs.gl.PointParameterf(__VA_ARGS__)) +#undef glPointParameterfv +#define glPointParameterfv(...) GLCHECK(gl3wProcs.gl.PointParameterfv(__VA_ARGS__)) +#undef glPointParameteri +#define glPointParameteri(...) GLCHECK(gl3wProcs.gl.PointParameteri(__VA_ARGS__)) +#undef glPointParameteriv +#define glPointParameteriv(...) GLCHECK(gl3wProcs.gl.PointParameteriv(__VA_ARGS__)) +#undef glPointSize +#define glPointSize(...) GLCHECK(gl3wProcs.gl.PointSize(__VA_ARGS__)) +#undef glPolygonMode +#define glPolygonMode(...) GLCHECK(gl3wProcs.gl.PolygonMode(__VA_ARGS__)) +#undef glPolygonOffset +#define glPolygonOffset(...) GLCHECK(gl3wProcs.gl.PolygonOffset(__VA_ARGS__)) +#undef glPopDebugGroup +#define glPopDebugGroup(...) GLCHECK(gl3wProcs.gl.PopDebugGroup(__VA_ARGS__)) +#undef glPrimitiveRestartIndex +#define glPrimitiveRestartIndex(...) GLCHECK(gl3wProcs.gl.PrimitiveRestartIndex(__VA_ARGS__)) +#undef glProgramBinary +#define glProgramBinary(...) GLCHECK(gl3wProcs.gl.ProgramBinary(__VA_ARGS__)) +#undef glProgramParameteri +#define glProgramParameteri(...) GLCHECK(gl3wProcs.gl.ProgramParameteri(__VA_ARGS__)) +#undef glProgramUniform1d +#define glProgramUniform1d(...) GLCHECK(gl3wProcs.gl.ProgramUniform1d(__VA_ARGS__)) +#undef glProgramUniform1dv +#define glProgramUniform1dv(...) GLCHECK(gl3wProcs.gl.ProgramUniform1dv(__VA_ARGS__)) +#undef glProgramUniform1f +#define glProgramUniform1f(...) GLCHECK(gl3wProcs.gl.ProgramUniform1f(__VA_ARGS__)) +#undef glProgramUniform1fv +#define glProgramUniform1fv(...) GLCHECK(gl3wProcs.gl.ProgramUniform1fv(__VA_ARGS__)) +#undef glProgramUniform1i +#define glProgramUniform1i(...) GLCHECK(gl3wProcs.gl.ProgramUniform1i(__VA_ARGS__)) +#undef glProgramUniform1iv +#define glProgramUniform1iv(...) GLCHECK(gl3wProcs.gl.ProgramUniform1iv(__VA_ARGS__)) +#undef glProgramUniform1ui +#define glProgramUniform1ui(...) GLCHECK(gl3wProcs.gl.ProgramUniform1ui(__VA_ARGS__)) +#undef glProgramUniform1uiv +#define glProgramUniform1uiv(...) GLCHECK(gl3wProcs.gl.ProgramUniform1uiv(__VA_ARGS__)) +#undef glProgramUniform2d +#define glProgramUniform2d(...) GLCHECK(gl3wProcs.gl.ProgramUniform2d(__VA_ARGS__)) +#undef glProgramUniform2dv +#define glProgramUniform2dv(...) GLCHECK(gl3wProcs.gl.ProgramUniform2dv(__VA_ARGS__)) +#undef glProgramUniform2f +#define glProgramUniform2f(...) GLCHECK(gl3wProcs.gl.ProgramUniform2f(__VA_ARGS__)) +#undef glProgramUniform2fv +#define glProgramUniform2fv(...) GLCHECK(gl3wProcs.gl.ProgramUniform2fv(__VA_ARGS__)) +#undef glProgramUniform2i +#define glProgramUniform2i(...) GLCHECK(gl3wProcs.gl.ProgramUniform2i(__VA_ARGS__)) +#undef glProgramUniform2iv +#define glProgramUniform2iv(...) GLCHECK(gl3wProcs.gl.ProgramUniform2iv(__VA_ARGS__)) +#undef glProgramUniform2ui +#define glProgramUniform2ui(...) GLCHECK(gl3wProcs.gl.ProgramUniform2ui(__VA_ARGS__)) +#undef glProgramUniform2uiv +#define glProgramUniform2uiv(...) GLCHECK(gl3wProcs.gl.ProgramUniform2uiv(__VA_ARGS__)) +#undef glProgramUniform3d +#define glProgramUniform3d(...) GLCHECK(gl3wProcs.gl.ProgramUniform3d(__VA_ARGS__)) +#undef glProgramUniform3dv +#define glProgramUniform3dv(...) GLCHECK(gl3wProcs.gl.ProgramUniform3dv(__VA_ARGS__)) +#undef glProgramUniform3f +#define glProgramUniform3f(...) GLCHECK(gl3wProcs.gl.ProgramUniform3f(__VA_ARGS__)) +#undef glProgramUniform3fv +#define glProgramUniform3fv(...) GLCHECK(gl3wProcs.gl.ProgramUniform3fv(__VA_ARGS__)) +#undef glProgramUniform3i +#define glProgramUniform3i(...) GLCHECK(gl3wProcs.gl.ProgramUniform3i(__VA_ARGS__)) +#undef glProgramUniform3iv +#define glProgramUniform3iv(...) GLCHECK(gl3wProcs.gl.ProgramUniform3iv(__VA_ARGS__)) +#undef glProgramUniform3ui +#define glProgramUniform3ui(...) GLCHECK(gl3wProcs.gl.ProgramUniform3ui(__VA_ARGS__)) +#undef glProgramUniform3uiv +#define glProgramUniform3uiv(...) GLCHECK(gl3wProcs.gl.ProgramUniform3uiv(__VA_ARGS__)) +#undef glProgramUniform4d +#define glProgramUniform4d(...) GLCHECK(gl3wProcs.gl.ProgramUniform4d(__VA_ARGS__)) +#undef glProgramUniform4dv +#define glProgramUniform4dv(...) GLCHECK(gl3wProcs.gl.ProgramUniform4dv(__VA_ARGS__)) +#undef glProgramUniform4f +#define glProgramUniform4f(...) GLCHECK(gl3wProcs.gl.ProgramUniform4f(__VA_ARGS__)) +#undef glProgramUniform4fv +#define glProgramUniform4fv(...) GLCHECK(gl3wProcs.gl.ProgramUniform4fv(__VA_ARGS__)) +#undef glProgramUniform4i +#define glProgramUniform4i(...) GLCHECK(gl3wProcs.gl.ProgramUniform4i(__VA_ARGS__)) +#undef glProgramUniform4iv +#define glProgramUniform4iv(...) GLCHECK(gl3wProcs.gl.ProgramUniform4iv(__VA_ARGS__)) +#undef glProgramUniform4ui +#define glProgramUniform4ui(...) GLCHECK(gl3wProcs.gl.ProgramUniform4ui(__VA_ARGS__)) +#undef glProgramUniform4uiv +#define glProgramUniform4uiv(...) GLCHECK(gl3wProcs.gl.ProgramUniform4uiv(__VA_ARGS__)) +#undef glProgramUniformHandleui64ARB +#define glProgramUniformHandleui64ARB(...) GLCHECK(gl3wProcs.gl.ProgramUniformHandleui64ARB(__VA_ARGS__)) +#undef glProgramUniformHandleui64vARB +#define glProgramUniformHandleui64vARB(...) GLCHECK(gl3wProcs.gl.ProgramUniformHandleui64vARB(__VA_ARGS__)) +#undef glProgramUniformMatrix2dv +#define glProgramUniformMatrix2dv(...) GLCHECK(gl3wProcs.gl.ProgramUniformMatrix2dv(__VA_ARGS__)) +#undef glProgramUniformMatrix2fv +#define glProgramUniformMatrix2fv(...) GLCHECK(gl3wProcs.gl.ProgramUniformMatrix2fv(__VA_ARGS__)) +#undef glProgramUniformMatrix2x3dv +#define glProgramUniformMatrix2x3dv(...) GLCHECK(gl3wProcs.gl.ProgramUniformMatrix2x3dv(__VA_ARGS__)) +#undef glProgramUniformMatrix2x3fv +#define glProgramUniformMatrix2x3fv(...) GLCHECK(gl3wProcs.gl.ProgramUniformMatrix2x3fv(__VA_ARGS__)) +#undef glProgramUniformMatrix2x4dv +#define glProgramUniformMatrix2x4dv(...) GLCHECK(gl3wProcs.gl.ProgramUniformMatrix2x4dv(__VA_ARGS__)) +#undef glProgramUniformMatrix2x4fv +#define glProgramUniformMatrix2x4fv(...) GLCHECK(gl3wProcs.gl.ProgramUniformMatrix2x4fv(__VA_ARGS__)) +#undef glProgramUniformMatrix3dv +#define glProgramUniformMatrix3dv(...) GLCHECK(gl3wProcs.gl.ProgramUniformMatrix3dv(__VA_ARGS__)) +#undef glProgramUniformMatrix3fv +#define glProgramUniformMatrix3fv(...) GLCHECK(gl3wProcs.gl.ProgramUniformMatrix3fv(__VA_ARGS__)) +#undef glProgramUniformMatrix3x2dv +#define glProgramUniformMatrix3x2dv(...) GLCHECK(gl3wProcs.gl.ProgramUniformMatrix3x2dv(__VA_ARGS__)) +#undef glProgramUniformMatrix3x2fv +#define glProgramUniformMatrix3x2fv(...) GLCHECK(gl3wProcs.gl.ProgramUniformMatrix3x2fv(__VA_ARGS__)) +#undef glProgramUniformMatrix3x4dv +#define glProgramUniformMatrix3x4dv(...) GLCHECK(gl3wProcs.gl.ProgramUniformMatrix3x4dv(__VA_ARGS__)) +#undef glProgramUniformMatrix3x4fv +#define glProgramUniformMatrix3x4fv(...) GLCHECK(gl3wProcs.gl.ProgramUniformMatrix3x4fv(__VA_ARGS__)) +#undef glProgramUniformMatrix4dv +#define glProgramUniformMatrix4dv(...) GLCHECK(gl3wProcs.gl.ProgramUniformMatrix4dv(__VA_ARGS__)) +#undef glProgramUniformMatrix4fv +#define glProgramUniformMatrix4fv(...) GLCHECK(gl3wProcs.gl.ProgramUniformMatrix4fv(__VA_ARGS__)) +#undef glProgramUniformMatrix4x2dv +#define glProgramUniformMatrix4x2dv(...) GLCHECK(gl3wProcs.gl.ProgramUniformMatrix4x2dv(__VA_ARGS__)) +#undef glProgramUniformMatrix4x2fv +#define glProgramUniformMatrix4x2fv(...) GLCHECK(gl3wProcs.gl.ProgramUniformMatrix4x2fv(__VA_ARGS__)) +#undef glProgramUniformMatrix4x3dv +#define glProgramUniformMatrix4x3dv(...) GLCHECK(gl3wProcs.gl.ProgramUniformMatrix4x3dv(__VA_ARGS__)) +#undef glProgramUniformMatrix4x3fv +#define glProgramUniformMatrix4x3fv(...) GLCHECK(gl3wProcs.gl.ProgramUniformMatrix4x3fv(__VA_ARGS__)) +#undef glProvokingVertex +#define glProvokingVertex(...) GLCHECK(gl3wProcs.gl.ProvokingVertex(__VA_ARGS__)) +#undef glPushDebugGroup +#define glPushDebugGroup(...) GLCHECK(gl3wProcs.gl.PushDebugGroup(__VA_ARGS__)) +#undef glQueryCounter +#define glQueryCounter(...) GLCHECK(gl3wProcs.gl.QueryCounter(__VA_ARGS__)) +#undef glReadBuffer +#define glReadBuffer(...) GLCHECK(gl3wProcs.gl.ReadBuffer(__VA_ARGS__)) +#undef glReadPixels +#define glReadPixels(...) GLCHECK(gl3wProcs.gl.ReadPixels(__VA_ARGS__)) +#undef glReadnPixels +#define glReadnPixels(...) GLCHECK(gl3wProcs.gl.ReadnPixels(__VA_ARGS__)) +#undef glReadnPixelsARB +#define glReadnPixelsARB(...) GLCHECK(gl3wProcs.gl.ReadnPixelsARB(__VA_ARGS__)) +#undef glReleaseShaderCompiler +#define glReleaseShaderCompiler(...) GLCHECK(gl3wProcs.gl.ReleaseShaderCompiler(__VA_ARGS__)) +#undef glRenderbufferStorage +#define glRenderbufferStorage(...) GLCHECK(gl3wProcs.gl.RenderbufferStorage(__VA_ARGS__)) +#undef glRenderbufferStorageMultisample +#define glRenderbufferStorageMultisample(...) GLCHECK(gl3wProcs.gl.RenderbufferStorageMultisample(__VA_ARGS__)) +#undef glResumeTransformFeedback +#define glResumeTransformFeedback(...) GLCHECK(gl3wProcs.gl.ResumeTransformFeedback(__VA_ARGS__)) +#undef glSampleCoverage +#define glSampleCoverage(...) GLCHECK(gl3wProcs.gl.SampleCoverage(__VA_ARGS__)) +#undef glSampleMaski +#define glSampleMaski(...) GLCHECK(gl3wProcs.gl.SampleMaski(__VA_ARGS__)) +#undef glSamplerParameterIiv +#define glSamplerParameterIiv(...) GLCHECK(gl3wProcs.gl.SamplerParameterIiv(__VA_ARGS__)) +#undef glSamplerParameterIuiv +#define glSamplerParameterIuiv(...) GLCHECK(gl3wProcs.gl.SamplerParameterIuiv(__VA_ARGS__)) +#undef glSamplerParameterf +#define glSamplerParameterf(...) GLCHECK(gl3wProcs.gl.SamplerParameterf(__VA_ARGS__)) +#undef glSamplerParameterfv +#define glSamplerParameterfv(...) GLCHECK(gl3wProcs.gl.SamplerParameterfv(__VA_ARGS__)) +#undef glSamplerParameteri +#define glSamplerParameteri(...) GLCHECK(gl3wProcs.gl.SamplerParameteri(__VA_ARGS__)) +#undef glSamplerParameteriv +#define glSamplerParameteriv(...) GLCHECK(gl3wProcs.gl.SamplerParameteriv(__VA_ARGS__)) +#undef glScissor +#define glScissor(...) GLCHECK(gl3wProcs.gl.Scissor(__VA_ARGS__)) +#undef glScissorArrayv +#define glScissorArrayv(...) GLCHECK(gl3wProcs.gl.ScissorArrayv(__VA_ARGS__)) +#undef glScissorIndexed +#define glScissorIndexed(...) GLCHECK(gl3wProcs.gl.ScissorIndexed(__VA_ARGS__)) +#undef glScissorIndexedv +#define glScissorIndexedv(...) GLCHECK(gl3wProcs.gl.ScissorIndexedv(__VA_ARGS__)) +#undef glShaderBinary +#define glShaderBinary(...) GLCHECK(gl3wProcs.gl.ShaderBinary(__VA_ARGS__)) +#undef glShaderSource +#define glShaderSource(...) GLCHECK(gl3wProcs.gl.ShaderSource(__VA_ARGS__)) +#undef glShaderStorageBlockBinding +#define glShaderStorageBlockBinding(...) GLCHECK(gl3wProcs.gl.ShaderStorageBlockBinding(__VA_ARGS__)) +#undef glStencilFunc +#define glStencilFunc(...) GLCHECK(gl3wProcs.gl.StencilFunc(__VA_ARGS__)) +#undef glStencilFuncSeparate +#define glStencilFuncSeparate(...) GLCHECK(gl3wProcs.gl.StencilFuncSeparate(__VA_ARGS__)) +#undef glStencilMask +#define glStencilMask(...) GLCHECK(gl3wProcs.gl.StencilMask(__VA_ARGS__)) +#undef glStencilMaskSeparate +#define glStencilMaskSeparate(...) GLCHECK(gl3wProcs.gl.StencilMaskSeparate(__VA_ARGS__)) +#undef glStencilOp +#define glStencilOp(...) GLCHECK(gl3wProcs.gl.StencilOp(__VA_ARGS__)) +#undef glStencilOpSeparate +#define glStencilOpSeparate(...) GLCHECK(gl3wProcs.gl.StencilOpSeparate(__VA_ARGS__)) +#undef glTexBuffer +#define glTexBuffer(...) GLCHECK(gl3wProcs.gl.TexBuffer(__VA_ARGS__)) +#undef glTexBufferRange +#define glTexBufferRange(...) GLCHECK(gl3wProcs.gl.TexBufferRange(__VA_ARGS__)) +#undef glTexImage1D +#define glTexImage1D(...) GLCHECK(gl3wProcs.gl.TexImage1D(__VA_ARGS__)) +#undef glTexImage2D +#define glTexImage2D(...) GLCHECK(gl3wProcs.gl.TexImage2D(__VA_ARGS__)) +#undef glTexImage2DMultisample +#define glTexImage2DMultisample(...) GLCHECK(gl3wProcs.gl.TexImage2DMultisample(__VA_ARGS__)) +#undef glTexImage3D +#define glTexImage3D(...) GLCHECK(gl3wProcs.gl.TexImage3D(__VA_ARGS__)) +#undef glTexImage3DMultisample +#define glTexImage3DMultisample(...) GLCHECK(gl3wProcs.gl.TexImage3DMultisample(__VA_ARGS__)) +#undef glTexPageCommitmentARB +#define glTexPageCommitmentARB(...) GLCHECK(gl3wProcs.gl.TexPageCommitmentARB(__VA_ARGS__)) +#undef glTexParameterIiv +#define glTexParameterIiv(...) GLCHECK(gl3wProcs.gl.TexParameterIiv(__VA_ARGS__)) +#undef glTexParameterIuiv +#define glTexParameterIuiv(...) GLCHECK(gl3wProcs.gl.TexParameterIuiv(__VA_ARGS__)) +#undef glTexParameterf +#define glTexParameterf(...) GLCHECK(gl3wProcs.gl.TexParameterf(__VA_ARGS__)) +#undef glTexParameterfv +#define glTexParameterfv(...) GLCHECK(gl3wProcs.gl.TexParameterfv(__VA_ARGS__)) +#undef glTexParameteri +#define glTexParameteri(...) GLCHECK(gl3wProcs.gl.TexParameteri(__VA_ARGS__)) +#undef glTexParameteriv +#define glTexParameteriv(...) GLCHECK(gl3wProcs.gl.TexParameteriv(__VA_ARGS__)) +#undef glTexStorage1D +#define glTexStorage1D(...) GLCHECK(gl3wProcs.gl.TexStorage1D(__VA_ARGS__)) +#undef glTexStorage2D +#define glTexStorage2D(...) GLCHECK(gl3wProcs.gl.TexStorage2D(__VA_ARGS__)) +#undef glTexStorage2DMultisample +#define glTexStorage2DMultisample(...) GLCHECK(gl3wProcs.gl.TexStorage2DMultisample(__VA_ARGS__)) +#undef glTexStorage3D +#define glTexStorage3D(...) GLCHECK(gl3wProcs.gl.TexStorage3D(__VA_ARGS__)) +#undef glTexStorage3DMultisample +#define glTexStorage3DMultisample(...) GLCHECK(gl3wProcs.gl.TexStorage3DMultisample(__VA_ARGS__)) +#undef glTexSubImage1D +#define glTexSubImage1D(...) GLCHECK(gl3wProcs.gl.TexSubImage1D(__VA_ARGS__)) +#undef glTexSubImage2D +#define glTexSubImage2D(...) GLCHECK(gl3wProcs.gl.TexSubImage2D(__VA_ARGS__)) +#undef glTexSubImage3D +#define glTexSubImage3D(...) GLCHECK(gl3wProcs.gl.TexSubImage3D(__VA_ARGS__)) +#undef glTextureBarrier +#define glTextureBarrier(...) GLCHECK(gl3wProcs.gl.TextureBarrier(__VA_ARGS__)) +#undef glTextureBuffer +#define glTextureBuffer(...) GLCHECK(gl3wProcs.gl.TextureBuffer(__VA_ARGS__)) +#undef glTextureBufferRange +#define glTextureBufferRange(...) GLCHECK(gl3wProcs.gl.TextureBufferRange(__VA_ARGS__)) +#undef glTextureParameterIiv +#define glTextureParameterIiv(...) GLCHECK(gl3wProcs.gl.TextureParameterIiv(__VA_ARGS__)) +#undef glTextureParameterIuiv +#define glTextureParameterIuiv(...) GLCHECK(gl3wProcs.gl.TextureParameterIuiv(__VA_ARGS__)) +#undef glTextureParameterf +#define glTextureParameterf(...) GLCHECK(gl3wProcs.gl.TextureParameterf(__VA_ARGS__)) +#undef glTextureParameterfv +#define glTextureParameterfv(...) GLCHECK(gl3wProcs.gl.TextureParameterfv(__VA_ARGS__)) +#undef glTextureParameteri +#define glTextureParameteri(...) GLCHECK(gl3wProcs.gl.TextureParameteri(__VA_ARGS__)) +#undef glTextureParameteriv +#define glTextureParameteriv(...) GLCHECK(gl3wProcs.gl.TextureParameteriv(__VA_ARGS__)) +#undef glTextureStorage1D +#define glTextureStorage1D(...) GLCHECK(gl3wProcs.gl.TextureStorage1D(__VA_ARGS__)) +#undef glTextureStorage2D +#define glTextureStorage2D(...) GLCHECK(gl3wProcs.gl.TextureStorage2D(__VA_ARGS__)) +#undef glTextureStorage2DMultisample +#define glTextureStorage2DMultisample(...) GLCHECK(gl3wProcs.gl.TextureStorage2DMultisample(__VA_ARGS__)) +#undef glTextureStorage3D +#define glTextureStorage3D(...) GLCHECK(gl3wProcs.gl.TextureStorage3D(__VA_ARGS__)) +#undef glTextureStorage3DMultisample +#define glTextureStorage3DMultisample(...) GLCHECK(gl3wProcs.gl.TextureStorage3DMultisample(__VA_ARGS__)) +#undef glTextureSubImage1D +#define glTextureSubImage1D(...) GLCHECK(gl3wProcs.gl.TextureSubImage1D(__VA_ARGS__)) +#undef glTextureSubImage2D +#define glTextureSubImage2D(...) GLCHECK(gl3wProcs.gl.TextureSubImage2D(__VA_ARGS__)) +#undef glTextureSubImage3D +#define glTextureSubImage3D(...) GLCHECK(gl3wProcs.gl.TextureSubImage3D(__VA_ARGS__)) +#undef glTextureView +#define glTextureView(...) GLCHECK(gl3wProcs.gl.TextureView(__VA_ARGS__)) +#undef glTransformFeedbackBufferBase +#define glTransformFeedbackBufferBase(...) GLCHECK(gl3wProcs.gl.TransformFeedbackBufferBase(__VA_ARGS__)) +#undef glTransformFeedbackBufferRange +#define glTransformFeedbackBufferRange(...) GLCHECK(gl3wProcs.gl.TransformFeedbackBufferRange(__VA_ARGS__)) +#undef glTransformFeedbackVaryings +#define glTransformFeedbackVaryings(...) GLCHECK(gl3wProcs.gl.TransformFeedbackVaryings(__VA_ARGS__)) +#undef glUniform1d +#define glUniform1d(...) GLCHECK(gl3wProcs.gl.Uniform1d(__VA_ARGS__)) +#undef glUniform1dv +#define glUniform1dv(...) GLCHECK(gl3wProcs.gl.Uniform1dv(__VA_ARGS__)) +#undef glUniform1f +#define glUniform1f(...) GLCHECK(gl3wProcs.gl.Uniform1f(__VA_ARGS__)) +#undef glUniform1fv +#define glUniform1fv(...) GLCHECK(gl3wProcs.gl.Uniform1fv(__VA_ARGS__)) +#undef glUniform1i +#define glUniform1i(...) GLCHECK(gl3wProcs.gl.Uniform1i(__VA_ARGS__)) +#undef glUniform1iv +#define glUniform1iv(...) GLCHECK(gl3wProcs.gl.Uniform1iv(__VA_ARGS__)) +#undef glUniform1ui +#define glUniform1ui(...) GLCHECK(gl3wProcs.gl.Uniform1ui(__VA_ARGS__)) +#undef glUniform1uiv +#define glUniform1uiv(...) GLCHECK(gl3wProcs.gl.Uniform1uiv(__VA_ARGS__)) +#undef glUniform2d +#define glUniform2d(...) GLCHECK(gl3wProcs.gl.Uniform2d(__VA_ARGS__)) +#undef glUniform2dv +#define glUniform2dv(...) GLCHECK(gl3wProcs.gl.Uniform2dv(__VA_ARGS__)) +#undef glUniform2f +#define glUniform2f(...) GLCHECK(gl3wProcs.gl.Uniform2f(__VA_ARGS__)) +#undef glUniform2fv +#define glUniform2fv(...) GLCHECK(gl3wProcs.gl.Uniform2fv(__VA_ARGS__)) +#undef glUniform2i +#define glUniform2i(...) GLCHECK(gl3wProcs.gl.Uniform2i(__VA_ARGS__)) +#undef glUniform2iv +#define glUniform2iv(...) GLCHECK(gl3wProcs.gl.Uniform2iv(__VA_ARGS__)) +#undef glUniform2ui +#define glUniform2ui(...) GLCHECK(gl3wProcs.gl.Uniform2ui(__VA_ARGS__)) +#undef glUniform2uiv +#define glUniform2uiv(...) GLCHECK(gl3wProcs.gl.Uniform2uiv(__VA_ARGS__)) +#undef glUniform3d +#define glUniform3d(...) GLCHECK(gl3wProcs.gl.Uniform3d(__VA_ARGS__)) +#undef glUniform3dv +#define glUniform3dv(...) GLCHECK(gl3wProcs.gl.Uniform3dv(__VA_ARGS__)) +#undef glUniform3f +#define glUniform3f(...) GLCHECK(gl3wProcs.gl.Uniform3f(__VA_ARGS__)) +#undef glUniform3fv +#define glUniform3fv(...) GLCHECK(gl3wProcs.gl.Uniform3fv(__VA_ARGS__)) +#undef glUniform3i +#define glUniform3i(...) GLCHECK(gl3wProcs.gl.Uniform3i(__VA_ARGS__)) +#undef glUniform3iv +#define glUniform3iv(...) GLCHECK(gl3wProcs.gl.Uniform3iv(__VA_ARGS__)) +#undef glUniform3ui +#define glUniform3ui(...) GLCHECK(gl3wProcs.gl.Uniform3ui(__VA_ARGS__)) +#undef glUniform3uiv +#define glUniform3uiv(...) GLCHECK(gl3wProcs.gl.Uniform3uiv(__VA_ARGS__)) +#undef glUniform4d +#define glUniform4d(...) GLCHECK(gl3wProcs.gl.Uniform4d(__VA_ARGS__)) +#undef glUniform4dv +#define glUniform4dv(...) GLCHECK(gl3wProcs.gl.Uniform4dv(__VA_ARGS__)) +#undef glUniform4f +#define glUniform4f(...) GLCHECK(gl3wProcs.gl.Uniform4f(__VA_ARGS__)) +#undef glUniform4fv +#define glUniform4fv(...) GLCHECK(gl3wProcs.gl.Uniform4fv(__VA_ARGS__)) +#undef glUniform4i +#define glUniform4i(...) GLCHECK(gl3wProcs.gl.Uniform4i(__VA_ARGS__)) +#undef glUniform4iv +#define glUniform4iv(...) GLCHECK(gl3wProcs.gl.Uniform4iv(__VA_ARGS__)) +#undef glUniform4ui +#define glUniform4ui(...) GLCHECK(gl3wProcs.gl.Uniform4ui(__VA_ARGS__)) +#undef glUniform4uiv +#define glUniform4uiv(...) GLCHECK(gl3wProcs.gl.Uniform4uiv(__VA_ARGS__)) +#undef glUniformBlockBinding +#define glUniformBlockBinding(...) GLCHECK(gl3wProcs.gl.UniformBlockBinding(__VA_ARGS__)) +#undef glUniformHandleui64ARB +#define glUniformHandleui64ARB(...) GLCHECK(gl3wProcs.gl.UniformHandleui64ARB(__VA_ARGS__)) +#undef glUniformHandleui64vARB +#define glUniformHandleui64vARB(...) GLCHECK(gl3wProcs.gl.UniformHandleui64vARB(__VA_ARGS__)) +#undef glUniformMatrix2dv +#define glUniformMatrix2dv(...) GLCHECK(gl3wProcs.gl.UniformMatrix2dv(__VA_ARGS__)) +#undef glUniformMatrix2fv +#define glUniformMatrix2fv(...) GLCHECK(gl3wProcs.gl.UniformMatrix2fv(__VA_ARGS__)) +#undef glUniformMatrix2x3dv +#define glUniformMatrix2x3dv(...) GLCHECK(gl3wProcs.gl.UniformMatrix2x3dv(__VA_ARGS__)) +#undef glUniformMatrix2x3fv +#define glUniformMatrix2x3fv(...) GLCHECK(gl3wProcs.gl.UniformMatrix2x3fv(__VA_ARGS__)) +#undef glUniformMatrix2x4dv +#define glUniformMatrix2x4dv(...) GLCHECK(gl3wProcs.gl.UniformMatrix2x4dv(__VA_ARGS__)) +#undef glUniformMatrix2x4fv +#define glUniformMatrix2x4fv(...) GLCHECK(gl3wProcs.gl.UniformMatrix2x4fv(__VA_ARGS__)) +#undef glUniformMatrix3dv +#define glUniformMatrix3dv(...) GLCHECK(gl3wProcs.gl.UniformMatrix3dv(__VA_ARGS__)) +#undef glUniformMatrix3fv +#define glUniformMatrix3fv(...) GLCHECK(gl3wProcs.gl.UniformMatrix3fv(__VA_ARGS__)) +#undef glUniformMatrix3x2dv +#define glUniformMatrix3x2dv(...) GLCHECK(gl3wProcs.gl.UniformMatrix3x2dv(__VA_ARGS__)) +#undef glUniformMatrix3x2fv +#define glUniformMatrix3x2fv(...) GLCHECK(gl3wProcs.gl.UniformMatrix3x2fv(__VA_ARGS__)) +#undef glUniformMatrix3x4dv +#define glUniformMatrix3x4dv(...) GLCHECK(gl3wProcs.gl.UniformMatrix3x4dv(__VA_ARGS__)) +#undef glUniformMatrix3x4fv +#define glUniformMatrix3x4fv(...) GLCHECK(gl3wProcs.gl.UniformMatrix3x4fv(__VA_ARGS__)) +#undef glUniformMatrix4dv +#define glUniformMatrix4dv(...) GLCHECK(gl3wProcs.gl.UniformMatrix4dv(__VA_ARGS__)) +#undef glUniformMatrix4fv +#define glUniformMatrix4fv(...) GLCHECK(gl3wProcs.gl.UniformMatrix4fv(__VA_ARGS__)) +#undef glUniformMatrix4x2dv +#define glUniformMatrix4x2dv(...) GLCHECK(gl3wProcs.gl.UniformMatrix4x2dv(__VA_ARGS__)) +#undef glUniformMatrix4x2fv +#define glUniformMatrix4x2fv(...) GLCHECK(gl3wProcs.gl.UniformMatrix4x2fv(__VA_ARGS__)) +#undef glUniformMatrix4x3dv +#define glUniformMatrix4x3dv(...) GLCHECK(gl3wProcs.gl.UniformMatrix4x3dv(__VA_ARGS__)) +#undef glUniformMatrix4x3fv +#define glUniformMatrix4x3fv(...) GLCHECK(gl3wProcs.gl.UniformMatrix4x3fv(__VA_ARGS__)) +#undef glUniformSubroutinesuiv +#define glUniformSubroutinesuiv(...) GLCHECK(gl3wProcs.gl.UniformSubroutinesuiv(__VA_ARGS__)) +#undef glUnmapBuffer +#define glUnmapBuffer(...) GLCHECK(gl3wProcs.gl.UnmapBuffer(__VA_ARGS__)) +#undef glUnmapNamedBuffer +#define glUnmapNamedBuffer(...) GLCHECK(gl3wProcs.gl.UnmapNamedBuffer(__VA_ARGS__)) +#undef glUseProgram +#define glUseProgram(...) GLCHECK(gl3wProcs.gl.UseProgram(__VA_ARGS__)) +#undef glUseProgramStages +#define glUseProgramStages(...) GLCHECK(gl3wProcs.gl.UseProgramStages(__VA_ARGS__)) +#undef glValidateProgram +#define glValidateProgram(...) GLCHECK(gl3wProcs.gl.ValidateProgram(__VA_ARGS__)) +#undef glValidateProgramPipeline +#define glValidateProgramPipeline(...) GLCHECK(gl3wProcs.gl.ValidateProgramPipeline(__VA_ARGS__)) +#undef glVertexArrayAttribBinding +#define glVertexArrayAttribBinding(...) GLCHECK(gl3wProcs.gl.VertexArrayAttribBinding(__VA_ARGS__)) +#undef glVertexArrayAttribFormat +#define glVertexArrayAttribFormat(...) GLCHECK(gl3wProcs.gl.VertexArrayAttribFormat(__VA_ARGS__)) +#undef glVertexArrayAttribIFormat +#define glVertexArrayAttribIFormat(...) GLCHECK(gl3wProcs.gl.VertexArrayAttribIFormat(__VA_ARGS__)) +#undef glVertexArrayAttribLFormat +#define glVertexArrayAttribLFormat(...) GLCHECK(gl3wProcs.gl.VertexArrayAttribLFormat(__VA_ARGS__)) +#undef glVertexArrayBindingDivisor +#define glVertexArrayBindingDivisor(...) GLCHECK(gl3wProcs.gl.VertexArrayBindingDivisor(__VA_ARGS__)) +#undef glVertexArrayElementBuffer +#define glVertexArrayElementBuffer(...) GLCHECK(gl3wProcs.gl.VertexArrayElementBuffer(__VA_ARGS__)) +#undef glVertexArrayVertexBuffer +#define glVertexArrayVertexBuffer(...) GLCHECK(gl3wProcs.gl.VertexArrayVertexBuffer(__VA_ARGS__)) +#undef glVertexArrayVertexBuffers +#define glVertexArrayVertexBuffers(...) GLCHECK(gl3wProcs.gl.VertexArrayVertexBuffers(__VA_ARGS__)) +#undef glVertexAttrib1d +#define glVertexAttrib1d(...) GLCHECK(gl3wProcs.gl.VertexAttrib1d(__VA_ARGS__)) +#undef glVertexAttrib1dv +#define glVertexAttrib1dv(...) GLCHECK(gl3wProcs.gl.VertexAttrib1dv(__VA_ARGS__)) +#undef glVertexAttrib1f +#define glVertexAttrib1f(...) GLCHECK(gl3wProcs.gl.VertexAttrib1f(__VA_ARGS__)) +#undef glVertexAttrib1fv +#define glVertexAttrib1fv(...) GLCHECK(gl3wProcs.gl.VertexAttrib1fv(__VA_ARGS__)) +#undef glVertexAttrib1s +#define glVertexAttrib1s(...) GLCHECK(gl3wProcs.gl.VertexAttrib1s(__VA_ARGS__)) +#undef glVertexAttrib1sv +#define glVertexAttrib1sv(...) GLCHECK(gl3wProcs.gl.VertexAttrib1sv(__VA_ARGS__)) +#undef glVertexAttrib2d +#define glVertexAttrib2d(...) GLCHECK(gl3wProcs.gl.VertexAttrib2d(__VA_ARGS__)) +#undef glVertexAttrib2dv +#define glVertexAttrib2dv(...) GLCHECK(gl3wProcs.gl.VertexAttrib2dv(__VA_ARGS__)) +#undef glVertexAttrib2f +#define glVertexAttrib2f(...) GLCHECK(gl3wProcs.gl.VertexAttrib2f(__VA_ARGS__)) +#undef glVertexAttrib2fv +#define glVertexAttrib2fv(...) GLCHECK(gl3wProcs.gl.VertexAttrib2fv(__VA_ARGS__)) +#undef glVertexAttrib2s +#define glVertexAttrib2s(...) GLCHECK(gl3wProcs.gl.VertexAttrib2s(__VA_ARGS__)) +#undef glVertexAttrib2sv +#define glVertexAttrib2sv(...) GLCHECK(gl3wProcs.gl.VertexAttrib2sv(__VA_ARGS__)) +#undef glVertexAttrib3d +#define glVertexAttrib3d(...) GLCHECK(gl3wProcs.gl.VertexAttrib3d(__VA_ARGS__)) +#undef glVertexAttrib3dv +#define glVertexAttrib3dv(...) GLCHECK(gl3wProcs.gl.VertexAttrib3dv(__VA_ARGS__)) +#undef glVertexAttrib3f +#define glVertexAttrib3f(...) GLCHECK(gl3wProcs.gl.VertexAttrib3f(__VA_ARGS__)) +#undef glVertexAttrib3fv +#define glVertexAttrib3fv(...) GLCHECK(gl3wProcs.gl.VertexAttrib3fv(__VA_ARGS__)) +#undef glVertexAttrib3s +#define glVertexAttrib3s(...) GLCHECK(gl3wProcs.gl.VertexAttrib3s(__VA_ARGS__)) +#undef glVertexAttrib3sv +#define glVertexAttrib3sv(...) GLCHECK(gl3wProcs.gl.VertexAttrib3sv(__VA_ARGS__)) +#undef glVertexAttrib4Nbv +#define glVertexAttrib4Nbv(...) GLCHECK(gl3wProcs.gl.VertexAttrib4Nbv(__VA_ARGS__)) +#undef glVertexAttrib4Niv +#define glVertexAttrib4Niv(...) GLCHECK(gl3wProcs.gl.VertexAttrib4Niv(__VA_ARGS__)) +#undef glVertexAttrib4Nsv +#define glVertexAttrib4Nsv(...) GLCHECK(gl3wProcs.gl.VertexAttrib4Nsv(__VA_ARGS__)) +#undef glVertexAttrib4Nub +#define glVertexAttrib4Nub(...) GLCHECK(gl3wProcs.gl.VertexAttrib4Nub(__VA_ARGS__)) +#undef glVertexAttrib4Nubv +#define glVertexAttrib4Nubv(...) GLCHECK(gl3wProcs.gl.VertexAttrib4Nubv(__VA_ARGS__)) +#undef glVertexAttrib4Nuiv +#define glVertexAttrib4Nuiv(...) GLCHECK(gl3wProcs.gl.VertexAttrib4Nuiv(__VA_ARGS__)) +#undef glVertexAttrib4Nusv +#define glVertexAttrib4Nusv(...) GLCHECK(gl3wProcs.gl.VertexAttrib4Nusv(__VA_ARGS__)) +#undef glVertexAttrib4bv +#define glVertexAttrib4bv(...) GLCHECK(gl3wProcs.gl.VertexAttrib4bv(__VA_ARGS__)) +#undef glVertexAttrib4d +#define glVertexAttrib4d(...) GLCHECK(gl3wProcs.gl.VertexAttrib4d(__VA_ARGS__)) +#undef glVertexAttrib4dv +#define glVertexAttrib4dv(...) GLCHECK(gl3wProcs.gl.VertexAttrib4dv(__VA_ARGS__)) +#undef glVertexAttrib4f +#define glVertexAttrib4f(...) GLCHECK(gl3wProcs.gl.VertexAttrib4f(__VA_ARGS__)) +#undef glVertexAttrib4fv +#define glVertexAttrib4fv(...) GLCHECK(gl3wProcs.gl.VertexAttrib4fv(__VA_ARGS__)) +#undef glVertexAttrib4iv +#define glVertexAttrib4iv(...) GLCHECK(gl3wProcs.gl.VertexAttrib4iv(__VA_ARGS__)) +#undef glVertexAttrib4s +#define glVertexAttrib4s(...) GLCHECK(gl3wProcs.gl.VertexAttrib4s(__VA_ARGS__)) +#undef glVertexAttrib4sv +#define glVertexAttrib4sv(...) GLCHECK(gl3wProcs.gl.VertexAttrib4sv(__VA_ARGS__)) +#undef glVertexAttrib4ubv +#define glVertexAttrib4ubv(...) GLCHECK(gl3wProcs.gl.VertexAttrib4ubv(__VA_ARGS__)) +#undef glVertexAttrib4uiv +#define glVertexAttrib4uiv(...) GLCHECK(gl3wProcs.gl.VertexAttrib4uiv(__VA_ARGS__)) +#undef glVertexAttrib4usv +#define glVertexAttrib4usv(...) GLCHECK(gl3wProcs.gl.VertexAttrib4usv(__VA_ARGS__)) +#undef glVertexAttribBinding +#define glVertexAttribBinding(...) GLCHECK(gl3wProcs.gl.VertexAttribBinding(__VA_ARGS__)) +#undef glVertexAttribDivisor +#define glVertexAttribDivisor(...) GLCHECK(gl3wProcs.gl.VertexAttribDivisor(__VA_ARGS__)) +#undef glVertexAttribFormat +#define glVertexAttribFormat(...) GLCHECK(gl3wProcs.gl.VertexAttribFormat(__VA_ARGS__)) +#undef glVertexAttribI1i +#define glVertexAttribI1i(...) GLCHECK(gl3wProcs.gl.VertexAttribI1i(__VA_ARGS__)) +#undef glVertexAttribI1iv +#define glVertexAttribI1iv(...) GLCHECK(gl3wProcs.gl.VertexAttribI1iv(__VA_ARGS__)) +#undef glVertexAttribI1ui +#define glVertexAttribI1ui(...) GLCHECK(gl3wProcs.gl.VertexAttribI1ui(__VA_ARGS__)) +#undef glVertexAttribI1uiv +#define glVertexAttribI1uiv(...) GLCHECK(gl3wProcs.gl.VertexAttribI1uiv(__VA_ARGS__)) +#undef glVertexAttribI2i +#define glVertexAttribI2i(...) GLCHECK(gl3wProcs.gl.VertexAttribI2i(__VA_ARGS__)) +#undef glVertexAttribI2iv +#define glVertexAttribI2iv(...) GLCHECK(gl3wProcs.gl.VertexAttribI2iv(__VA_ARGS__)) +#undef glVertexAttribI2ui +#define glVertexAttribI2ui(...) GLCHECK(gl3wProcs.gl.VertexAttribI2ui(__VA_ARGS__)) +#undef glVertexAttribI2uiv +#define glVertexAttribI2uiv(...) GLCHECK(gl3wProcs.gl.VertexAttribI2uiv(__VA_ARGS__)) +#undef glVertexAttribI3i +#define glVertexAttribI3i(...) GLCHECK(gl3wProcs.gl.VertexAttribI3i(__VA_ARGS__)) +#undef glVertexAttribI3iv +#define glVertexAttribI3iv(...) GLCHECK(gl3wProcs.gl.VertexAttribI3iv(__VA_ARGS__)) +#undef glVertexAttribI3ui +#define glVertexAttribI3ui(...) GLCHECK(gl3wProcs.gl.VertexAttribI3ui(__VA_ARGS__)) +#undef glVertexAttribI3uiv +#define glVertexAttribI3uiv(...) GLCHECK(gl3wProcs.gl.VertexAttribI3uiv(__VA_ARGS__)) +#undef glVertexAttribI4bv +#define glVertexAttribI4bv(...) GLCHECK(gl3wProcs.gl.VertexAttribI4bv(__VA_ARGS__)) +#undef glVertexAttribI4i +#define glVertexAttribI4i(...) GLCHECK(gl3wProcs.gl.VertexAttribI4i(__VA_ARGS__)) +#undef glVertexAttribI4iv +#define glVertexAttribI4iv(...) GLCHECK(gl3wProcs.gl.VertexAttribI4iv(__VA_ARGS__)) +#undef glVertexAttribI4sv +#define glVertexAttribI4sv(...) GLCHECK(gl3wProcs.gl.VertexAttribI4sv(__VA_ARGS__)) +#undef glVertexAttribI4ubv +#define glVertexAttribI4ubv(...) GLCHECK(gl3wProcs.gl.VertexAttribI4ubv(__VA_ARGS__)) +#undef glVertexAttribI4ui +#define glVertexAttribI4ui(...) GLCHECK(gl3wProcs.gl.VertexAttribI4ui(__VA_ARGS__)) +#undef glVertexAttribI4uiv +#define glVertexAttribI4uiv(...) GLCHECK(gl3wProcs.gl.VertexAttribI4uiv(__VA_ARGS__)) +#undef glVertexAttribI4usv +#define glVertexAttribI4usv(...) GLCHECK(gl3wProcs.gl.VertexAttribI4usv(__VA_ARGS__)) +#undef glVertexAttribIFormat +#define glVertexAttribIFormat(...) GLCHECK(gl3wProcs.gl.VertexAttribIFormat(__VA_ARGS__)) +#undef glVertexAttribIPointer +#define glVertexAttribIPointer(...) GLCHECK(gl3wProcs.gl.VertexAttribIPointer(__VA_ARGS__)) +#undef glVertexAttribL1d +#define glVertexAttribL1d(...) GLCHECK(gl3wProcs.gl.VertexAttribL1d(__VA_ARGS__)) +#undef glVertexAttribL1dv +#define glVertexAttribL1dv(...) GLCHECK(gl3wProcs.gl.VertexAttribL1dv(__VA_ARGS__)) +#undef glVertexAttribL1ui64ARB +#define glVertexAttribL1ui64ARB(...) GLCHECK(gl3wProcs.gl.VertexAttribL1ui64ARB(__VA_ARGS__)) +#undef glVertexAttribL1ui64vARB +#define glVertexAttribL1ui64vARB(...) GLCHECK(gl3wProcs.gl.VertexAttribL1ui64vARB(__VA_ARGS__)) +#undef glVertexAttribL2d +#define glVertexAttribL2d(...) GLCHECK(gl3wProcs.gl.VertexAttribL2d(__VA_ARGS__)) +#undef glVertexAttribL2dv +#define glVertexAttribL2dv(...) GLCHECK(gl3wProcs.gl.VertexAttribL2dv(__VA_ARGS__)) +#undef glVertexAttribL3d +#define glVertexAttribL3d(...) GLCHECK(gl3wProcs.gl.VertexAttribL3d(__VA_ARGS__)) +#undef glVertexAttribL3dv +#define glVertexAttribL3dv(...) GLCHECK(gl3wProcs.gl.VertexAttribL3dv(__VA_ARGS__)) +#undef glVertexAttribL4d +#define glVertexAttribL4d(...) GLCHECK(gl3wProcs.gl.VertexAttribL4d(__VA_ARGS__)) +#undef glVertexAttribL4dv +#define glVertexAttribL4dv(...) GLCHECK(gl3wProcs.gl.VertexAttribL4dv(__VA_ARGS__)) +#undef glVertexAttribLFormat +#define glVertexAttribLFormat(...) GLCHECK(gl3wProcs.gl.VertexAttribLFormat(__VA_ARGS__)) +#undef glVertexAttribLPointer +#define glVertexAttribLPointer(...) GLCHECK(gl3wProcs.gl.VertexAttribLPointer(__VA_ARGS__)) +#undef glVertexAttribP1ui +#define glVertexAttribP1ui(...) GLCHECK(gl3wProcs.gl.VertexAttribP1ui(__VA_ARGS__)) +#undef glVertexAttribP1uiv +#define glVertexAttribP1uiv(...) GLCHECK(gl3wProcs.gl.VertexAttribP1uiv(__VA_ARGS__)) +#undef glVertexAttribP2ui +#define glVertexAttribP2ui(...) GLCHECK(gl3wProcs.gl.VertexAttribP2ui(__VA_ARGS__)) +#undef glVertexAttribP2uiv +#define glVertexAttribP2uiv(...) GLCHECK(gl3wProcs.gl.VertexAttribP2uiv(__VA_ARGS__)) +#undef glVertexAttribP3ui +#define glVertexAttribP3ui(...) GLCHECK(gl3wProcs.gl.VertexAttribP3ui(__VA_ARGS__)) +#undef glVertexAttribP3uiv +#define glVertexAttribP3uiv(...) GLCHECK(gl3wProcs.gl.VertexAttribP3uiv(__VA_ARGS__)) +#undef glVertexAttribP4ui +#define glVertexAttribP4ui(...) GLCHECK(gl3wProcs.gl.VertexAttribP4ui(__VA_ARGS__)) +#undef glVertexAttribP4uiv +#define glVertexAttribP4uiv(...) GLCHECK(gl3wProcs.gl.VertexAttribP4uiv(__VA_ARGS__)) +#undef glVertexAttribPointer +#define glVertexAttribPointer(...) GLCHECK(gl3wProcs.gl.VertexAttribPointer(__VA_ARGS__)) +#undef glVertexBindingDivisor +#define glVertexBindingDivisor(...) GLCHECK(gl3wProcs.gl.VertexBindingDivisor(__VA_ARGS__)) +#undef glViewport +#define glViewport(...) GLCHECK(gl3wProcs.gl.Viewport(__VA_ARGS__)) +#undef glViewportArrayv +#define glViewportArrayv(...) GLCHECK(gl3wProcs.gl.ViewportArrayv(__VA_ARGS__)) +#undef glViewportIndexedf +#define glViewportIndexedf(...) GLCHECK(gl3wProcs.gl.ViewportIndexedf(__VA_ARGS__)) +#undef glViewportIndexedfv +#define glViewportIndexedfv(...) GLCHECK(gl3wProcs.gl.ViewportIndexedfv(__VA_ARGS__)) +#undef glWaitSync +#define glWaitSync(...) GLCHECK(gl3wProcs.gl.WaitSync(__VA_ARGS__)) + +#endif diff --git a/msvc/source/opengl/opengl_hooks.cpp b/msvc/source/opengl/opengl_hooks.cpp new file mode 100644 index 0000000..4f7ca89 --- /dev/null +++ b/msvc/source/opengl/opengl_hooks.cpp @@ -0,0 +1,2163 @@ +/** + * Copyright (C) 2014 Patrick Mours. All rights reserved. + * License: https://github.com/crosire/reshade#license + */ + +#include "log.hpp" +#include "hook_manager.hpp" +#include "runtime_opengl.hpp" +#include "opengl_hooks.hpp" +#include + +extern thread_local reshade::opengl::runtime_opengl *g_current_runtime; + +HOOK_EXPORT void WINAPI glAccum(GLenum op, GLfloat value) +{ + static const auto trampoline = reshade::hooks::call(glAccum); + trampoline(op, value); +} + +HOOK_EXPORT void WINAPI glAlphaFunc(GLenum func, GLclampf ref) +{ + static const auto trampoline = reshade::hooks::call(glAlphaFunc); + trampoline(func, ref); +} + +HOOK_EXPORT GLboolean WINAPI glAreTexturesResident(GLsizei n, const GLuint *textures, GLboolean *residences) +{ + static const auto trampoline = reshade::hooks::call(glAreTexturesResident); + return trampoline(n, textures, residences); +} + +HOOK_EXPORT void WINAPI glArrayElement(GLint i) +{ + static const auto trampoline = reshade::hooks::call(glArrayElement); + trampoline(i); +} + +HOOK_EXPORT void WINAPI glBegin(GLenum mode) +{ + if (g_current_runtime) g_current_runtime->_current_vertex_count = 0; + + static const auto trampoline = reshade::hooks::call(glBegin); + trampoline(mode); +} + +HOOK_EXPORT void WINAPI glBindTexture(GLenum target, GLuint texture) +{ + static const auto trampoline = reshade::hooks::call(glBindTexture); + trampoline(target, texture); +} + +HOOK_EXPORT void WINAPI glBitmap(GLsizei width, GLsizei height, GLfloat xorig, GLfloat yorig, GLfloat xmove, GLfloat ymove, const GLubyte *bitmap) +{ + static const auto trampoline = reshade::hooks::call(glBitmap); + trampoline(width, height, xorig, yorig, xmove, ymove, bitmap); +} + +HOOK_EXPORT void WINAPI glBlendFunc(GLenum sfactor, GLenum dfactor) +{ + static const auto trampoline = reshade::hooks::call(glBlendFunc); + trampoline(sfactor, dfactor); +} + +HOOK_EXPORT void WINAPI glCallList(GLuint list) +{ + static const auto trampoline = reshade::hooks::call(glCallList); + trampoline(list); +} +HOOK_EXPORT void WINAPI glCallLists(GLsizei n, GLenum type, const GLvoid *lists) +{ + static const auto trampoline = reshade::hooks::call(glCallLists); + trampoline(n, type, lists); +} + +HOOK_EXPORT void WINAPI glClear(GLbitfield mask) +{ + static const auto trampoline = reshade::hooks::call(glClear); + trampoline(mask); +} +HOOK_EXPORT void WINAPI glClearAccum(GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha) +{ + static const auto trampoline = reshade::hooks::call(glClearAccum); + trampoline(red, green, blue, alpha); +} +HOOK_EXPORT void WINAPI glClearColor(GLclampf red, GLclampf green, GLclampf blue, GLclampf alpha) +{ + static const auto trampoline = reshade::hooks::call(glClearColor); + trampoline(red, green, blue, alpha); +} +HOOK_EXPORT void WINAPI glClearDepth(GLclampd depth) +{ + static const auto trampoline = reshade::hooks::call(glClearDepth); + trampoline(depth); +} +HOOK_EXPORT void WINAPI glClearIndex(GLfloat c) +{ + static const auto trampoline = reshade::hooks::call(glClearIndex); + trampoline(c); +} +HOOK_EXPORT void WINAPI glClearStencil(GLint s) +{ + static const auto trampoline = reshade::hooks::call(glClearStencil); + trampoline(s); +} + +HOOK_EXPORT void WINAPI glClipPlane(GLenum plane, const GLdouble *equation) +{ + static const auto trampoline = reshade::hooks::call(glClipPlane); + trampoline(plane, equation); +} + +HOOK_EXPORT void WINAPI glColor3b(GLbyte red, GLbyte green, GLbyte blue) +{ + static const auto trampoline = reshade::hooks::call(glColor3b); + trampoline(red, green, blue); +} +HOOK_EXPORT void WINAPI glColor3bv(const GLbyte *v) +{ + static const auto trampoline = reshade::hooks::call(glColor3bv); + trampoline(v); +} +HOOK_EXPORT void WINAPI glColor3d(GLdouble red, GLdouble green, GLdouble blue) +{ + static const auto trampoline = reshade::hooks::call(glColor3d); + trampoline(red, green, blue); +} +HOOK_EXPORT void WINAPI glColor3dv(const GLdouble *v) +{ + static const auto trampoline = reshade::hooks::call(glColor3dv); + trampoline(v); +} +HOOK_EXPORT void WINAPI glColor3f(GLfloat red, GLfloat green, GLfloat blue) +{ + static const auto trampoline = reshade::hooks::call(glColor3f); + trampoline(red, green, blue); +} +HOOK_EXPORT void WINAPI glColor3fv(const GLfloat *v) +{ + static const auto trampoline = reshade::hooks::call(glColor3fv); + trampoline(v); +} +HOOK_EXPORT void WINAPI glColor3i(GLint red, GLint green, GLint blue) +{ + static const auto trampoline = reshade::hooks::call(glColor3i); + trampoline(red, green, blue); +} +HOOK_EXPORT void WINAPI glColor3iv(const GLint *v) +{ + static const auto trampoline = reshade::hooks::call(glColor3iv); + trampoline(v); +} +HOOK_EXPORT void WINAPI glColor3s(GLshort red, GLshort green, GLshort blue) +{ + static const auto trampoline = reshade::hooks::call(glColor3s); + trampoline(red, green, blue); +} +HOOK_EXPORT void WINAPI glColor3sv(const GLshort *v) +{ + static const auto trampoline = reshade::hooks::call(glColor3sv); + trampoline(v); +} +HOOK_EXPORT void WINAPI glColor3ub(GLubyte red, GLubyte green, GLubyte blue) +{ + static const auto trampoline = reshade::hooks::call(glColor3ub); + trampoline(red, green, blue); +} +HOOK_EXPORT void WINAPI glColor3ubv(const GLubyte *v) +{ + static const auto trampoline = reshade::hooks::call(glColor3ubv); + trampoline(v); +} +HOOK_EXPORT void WINAPI glColor3ui(GLuint red, GLuint green, GLuint blue) +{ + static const auto trampoline = reshade::hooks::call(glColor3ui); + trampoline(red, green, blue); +} +HOOK_EXPORT void WINAPI glColor3uiv(const GLuint *v) +{ + static const auto trampoline = reshade::hooks::call(glColor3uiv); + trampoline(v); +} +HOOK_EXPORT void WINAPI glColor3us(GLushort red, GLushort green, GLushort blue) +{ + static const auto trampoline = reshade::hooks::call(glColor3us); + trampoline(red, green, blue); +} +HOOK_EXPORT void WINAPI glColor3usv(const GLushort *v) +{ + static const auto trampoline = reshade::hooks::call(glColor3usv); + trampoline(v); +} +HOOK_EXPORT void WINAPI glColor4b(GLbyte red, GLbyte green, GLbyte blue, GLbyte alpha) +{ + static const auto trampoline = reshade::hooks::call(glColor4b); + trampoline(red, green, blue, alpha); +} +HOOK_EXPORT void WINAPI glColor4bv(const GLbyte *v) +{ + static const auto trampoline = reshade::hooks::call(glColor4bv); + trampoline(v); +} +HOOK_EXPORT void WINAPI glColor4d(GLdouble red, GLdouble green, GLdouble blue, GLdouble alpha) +{ + static const auto trampoline = reshade::hooks::call(glColor4d); + trampoline(red, green, blue, alpha); +} +HOOK_EXPORT void WINAPI glColor4dv(const GLdouble *v) +{ + static const auto trampoline = reshade::hooks::call(glColor4dv); + trampoline(v); +} +HOOK_EXPORT void WINAPI glColor4f(GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha) +{ + static const auto trampoline = reshade::hooks::call(glColor4f); + trampoline(red, green, blue, alpha); +} +HOOK_EXPORT void WINAPI glColor4fv(const GLfloat *v) +{ + static const auto trampoline = reshade::hooks::call(glColor4fv); + trampoline(v); +} +HOOK_EXPORT void WINAPI glColor4i(GLint red, GLint green, GLint blue, GLint alpha) +{ + static const auto trampoline = reshade::hooks::call(glColor4i); + trampoline(red, green, blue, alpha); +} +HOOK_EXPORT void WINAPI glColor4iv(const GLint *v) +{ + static const auto trampoline = reshade::hooks::call(glColor4iv); + trampoline(v); +} +HOOK_EXPORT void WINAPI glColor4s(GLshort red, GLshort green, GLshort blue, GLshort alpha) +{ + static const auto trampoline = reshade::hooks::call(glColor4s); + trampoline(red, green, blue, alpha); +} +HOOK_EXPORT void WINAPI glColor4sv(const GLshort *v) +{ + static const auto trampoline = reshade::hooks::call(glColor4sv); + trampoline(v); +} +HOOK_EXPORT void WINAPI glColor4ub(GLubyte red, GLubyte green, GLubyte blue, GLubyte alpha) +{ + static const auto trampoline = reshade::hooks::call(glColor4ub); + trampoline(red, green, blue, alpha); +} +HOOK_EXPORT void WINAPI glColor4ubv(const GLubyte *v) +{ + static const auto trampoline = reshade::hooks::call(glColor4ubv); + trampoline(v); +} +HOOK_EXPORT void WINAPI glColor4ui(GLuint red, GLuint green, GLuint blue, GLuint alpha) +{ + static const auto trampoline = reshade::hooks::call(glColor4ui); + trampoline(red, green, blue, alpha); +} +HOOK_EXPORT void WINAPI glColor4uiv(const GLuint *v) +{ + static const auto trampoline = reshade::hooks::call(glColor4uiv); + trampoline(v); +} +HOOK_EXPORT void WINAPI glColor4us(GLushort red, GLushort green, GLushort blue, GLushort alpha) +{ + static const auto trampoline = reshade::hooks::call(glColor4us); + trampoline(red, green, blue, alpha); +} +HOOK_EXPORT void WINAPI glColor4usv(const GLushort *v) +{ + static const auto trampoline = reshade::hooks::call(glColor4usv); + trampoline(v); +} + +HOOK_EXPORT void WINAPI glColorMask(GLboolean red, GLboolean green, GLboolean blue, GLboolean alpha) +{ + static const auto trampoline = reshade::hooks::call(glColorMask); + trampoline(red, green, blue, alpha); +} + +HOOK_EXPORT void WINAPI glColorMaterial(GLenum face, GLenum mode) +{ + static const auto trampoline = reshade::hooks::call(glColorMaterial); + trampoline(face, mode); +} + +HOOK_EXPORT void WINAPI glColorPointer(GLint size, GLenum type, GLsizei stride, const GLvoid *pointer) +{ + static const auto trampoline = reshade::hooks::call(glColorPointer); + trampoline(size, type, stride, pointer); +} + +HOOK_EXPORT void WINAPI glCopyPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum type) +{ + static const auto trampoline = reshade::hooks::call(glCopyPixels); + trampoline(x, y, width, height, type); +} + +HOOK_EXPORT void WINAPI glCopyTexImage1D(GLenum target, GLint level, GLenum internalFormat, GLint x, GLint y, GLsizei width, GLint border) +{ + static const auto trampoline = reshade::hooks::call(glCopyTexImage1D); + trampoline(target, level, internalFormat, x, y, width, border); +} +HOOK_EXPORT void WINAPI glCopyTexImage2D(GLenum target, GLint level, GLenum internalFormat, GLint x, GLint y, GLsizei width, GLsizei height, GLint border) +{ + static const auto trampoline = reshade::hooks::call(glCopyTexImage2D); + trampoline(target, level, internalFormat, x, y, width, height, border); +} +HOOK_EXPORT void WINAPI glCopyTexSubImage1D(GLenum target, GLint level, GLint xoffset, GLint x, GLint y, GLsizei width) +{ + static const auto trampoline = reshade::hooks::call(glCopyTexSubImage1D); + trampoline(target, level, xoffset, x, y, width); +} +HOOK_EXPORT void WINAPI glCopyTexSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height) +{ + static const auto trampoline = reshade::hooks::call(glCopyTexSubImage2D); + trampoline(target, level, xoffset, yoffset, x, y, width, height); +} + +HOOK_EXPORT void WINAPI glCullFace(GLenum mode) +{ + static const auto trampoline = reshade::hooks::call(glCullFace); + trampoline(mode); +} + +HOOK_EXPORT void WINAPI glDeleteLists(GLuint list, GLsizei range) +{ + static const auto trampoline = reshade::hooks::call(glDeleteLists); + trampoline(list, range); +} + +HOOK_EXPORT void WINAPI glDeleteTextures(GLsizei n, const GLuint *textures) +{ + static const auto trampoline = reshade::hooks::call(glDeleteTextures); + trampoline(n, textures); +} + +HOOK_EXPORT void WINAPI glDepthFunc(GLenum func) +{ + static const auto trampoline = reshade::hooks::call(glDepthFunc); + trampoline(func); +} +HOOK_EXPORT void WINAPI glDepthMask(GLboolean flag) +{ + static const auto trampoline = reshade::hooks::call(glDepthMask); + trampoline(flag); +} +HOOK_EXPORT void WINAPI glDepthRange(GLclampd zNear, GLclampd zFar) +{ + static const auto trampoline = reshade::hooks::call(glDepthRange); + trampoline(zNear, zFar); +} + +HOOK_EXPORT void WINAPI glDisable(GLenum cap) +{ + static const auto trampoline = reshade::hooks::call(glDisable); + trampoline(cap); +} +HOOK_EXPORT void WINAPI glDisableClientState(GLenum array) +{ + static const auto trampoline = reshade::hooks::call(glDisableClientState); + trampoline(array); +} + +HOOK_EXPORT void WINAPI glDrawArrays(GLenum mode, GLint first, GLsizei count) +{ + if (g_current_runtime) g_current_runtime->on_draw_call(count); + + static const auto trampoline = reshade::hooks::call(glDrawArrays); + trampoline(mode, first, count); +} +extern "C" void WINAPI glDrawArraysIndirect(GLenum mode, const GLvoid *indirect) +{ + static const auto trampoline = reshade::hooks::call(glDrawArraysIndirect); + trampoline(mode, indirect); +} +extern "C" void WINAPI glDrawArraysInstanced(GLenum mode, GLint first, GLsizei count, GLsizei primcount) +{ + if (g_current_runtime) g_current_runtime->on_draw_call(primcount * count); + + static const auto trampoline = reshade::hooks::call(glDrawArraysInstanced); + trampoline(mode, first, count, primcount); +} +extern "C" void WINAPI glDrawArraysInstancedARB(GLenum mode, GLint first, GLsizei count, GLsizei primcount) +{ + if (g_current_runtime) g_current_runtime->on_draw_call(primcount * count); + + static const auto trampoline = reshade::hooks::call(glDrawArraysInstancedARB); + trampoline(mode, first, count, primcount); +} +extern "C" void WINAPI glDrawArraysInstancedEXT(GLenum mode, GLint first, GLsizei count, GLsizei primcount) +{ + if (g_current_runtime) g_current_runtime->on_draw_call(primcount * count); + + static const auto trampoline = reshade::hooks::call(glDrawArraysInstancedEXT); + trampoline(mode, first, count, primcount); +} +extern "C" void WINAPI glDrawArraysInstancedBaseInstance(GLenum mode, GLint first, GLsizei count, GLsizei primcount, GLuint baseinstance) +{ + if (g_current_runtime) g_current_runtime->on_draw_call(primcount * count); + + static const auto trampoline = reshade::hooks::call(glDrawArraysInstancedBaseInstance); + trampoline(mode, first, count, primcount, baseinstance); +} + +HOOK_EXPORT void WINAPI glDrawBuffer(GLenum mode) +{ + static const auto trampoline = reshade::hooks::call(glDrawBuffer); + trampoline(mode); +} + +HOOK_EXPORT void WINAPI glDrawElements(GLenum mode, GLsizei count, GLenum type, const GLvoid *indices) +{ + if (g_current_runtime) g_current_runtime->on_draw_call(count); + + static const auto trampoline = reshade::hooks::call(glDrawElements); + trampoline(mode, count, type, indices); +} +extern "C" void WINAPI glDrawElementsBaseVertex(GLenum mode, GLsizei count, GLenum type, const GLvoid *indices, GLint basevertex) +{ + if (g_current_runtime) g_current_runtime->on_draw_call(count); + + static const auto trampoline = reshade::hooks::call(glDrawElementsBaseVertex); + trampoline(mode, count, type, indices, basevertex); +} +extern "C" void WINAPI glDrawElementsIndirect(GLenum mode, GLenum type, const GLvoid *indirect) +{ + static const auto trampoline = reshade::hooks::call(glDrawElementsIndirect); + trampoline(mode, type, indirect); +} +extern "C" void WINAPI glDrawElementsInstanced(GLenum mode, GLsizei count, GLenum type, const GLvoid *indices, GLsizei primcount) +{ + if (g_current_runtime) g_current_runtime->on_draw_call(primcount * count); + + static const auto trampoline = reshade::hooks::call(glDrawElementsInstanced); + trampoline(mode, count, type, indices, primcount); +} +extern "C" void WINAPI glDrawElementsInstancedARB(GLenum mode, GLsizei count, GLenum type, const GLvoid *indices, GLsizei primcount) +{ + if (g_current_runtime) g_current_runtime->on_draw_call(primcount * count); + + static const auto trampoline = reshade::hooks::call(glDrawElementsInstancedARB); + trampoline(mode, count, type, indices, primcount); +} +extern "C" void WINAPI glDrawElementsInstancedEXT(GLenum mode, GLsizei count, GLenum type, const GLvoid *indices, GLsizei primcount) +{ + if (g_current_runtime) g_current_runtime->on_draw_call(primcount * count); + + static const auto trampoline = reshade::hooks::call(glDrawElementsInstancedEXT); + trampoline(mode, count, type, indices, primcount); +} +extern "C" void WINAPI glDrawElementsInstancedBaseVertex(GLenum mode, GLsizei count, GLenum type, const GLvoid *indices, GLsizei primcount, GLint basevertex) +{ + if (g_current_runtime) g_current_runtime->on_draw_call(primcount * count); + + static const auto trampoline = reshade::hooks::call(glDrawElementsInstancedBaseVertex); + trampoline(mode, count, type, indices, primcount, basevertex); +} +extern "C" void WINAPI glDrawElementsInstancedBaseInstance(GLenum mode, GLsizei count, GLenum type, const GLvoid *indices, GLsizei primcount, GLuint baseinstance) +{ + if (g_current_runtime) g_current_runtime->on_draw_call(primcount * count); + + static const auto trampoline = reshade::hooks::call(glDrawElementsInstancedBaseInstance); + trampoline(mode, count, type, indices, primcount, baseinstance); +} +extern "C" void WINAPI glDrawElementsInstancedBaseVertexBaseInstance(GLenum mode, GLsizei count, GLenum type, const GLvoid *indices, GLsizei primcount, GLint basevertex, GLuint baseinstance) +{ + if (g_current_runtime) g_current_runtime->on_draw_call(primcount * count); + + static const auto trampoline = reshade::hooks::call(glDrawElementsInstancedBaseVertexBaseInstance); + trampoline(mode, count, type, indices, primcount, basevertex, baseinstance); +} + +HOOK_EXPORT void WINAPI glDrawPixels(GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *pixels) +{ + static const auto trampoline = reshade::hooks::call(glDrawPixels); + trampoline(width, height, format, type, pixels); +} + +extern "C" void WINAPI glDrawRangeElements(GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const GLvoid *indices) +{ + if (g_current_runtime) g_current_runtime->on_draw_call(count); + + static const auto trampoline = reshade::hooks::call(glDrawRangeElements); + trampoline(mode, start, end, count, type, indices); +} +extern "C" void WINAPI glDrawRangeElementsBaseVertex(GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const GLvoid *indices, GLint basevertex) +{ + if (g_current_runtime) g_current_runtime->on_draw_call(count); + + static const auto trampoline = reshade::hooks::call(glDrawRangeElementsBaseVertex); + trampoline(mode, start, end, count, type, indices, basevertex); +} + +HOOK_EXPORT void WINAPI glEdgeFlag(GLboolean flag) +{ + static const auto trampoline = reshade::hooks::call(glEdgeFlag); + trampoline(flag); +} +HOOK_EXPORT void WINAPI glEdgeFlagPointer(GLsizei stride, const GLvoid *pointer) +{ + static const auto trampoline = reshade::hooks::call(glEdgeFlagPointer); + trampoline(stride, pointer); +} +HOOK_EXPORT void WINAPI glEdgeFlagv(const GLboolean *flag) +{ + static const auto trampoline = reshade::hooks::call(glEdgeFlagv); + trampoline(flag); +} + +HOOK_EXPORT void WINAPI glEnable(GLenum cap) +{ + static const auto trampoline = reshade::hooks::call(glEnable); + trampoline(cap); +} +HOOK_EXPORT void WINAPI glEnableClientState(GLenum array) +{ + static const auto trampoline = reshade::hooks::call(glEnableClientState); + trampoline(array); +} + +HOOK_EXPORT void WINAPI glEnd() +{ + static const auto trampoline = reshade::hooks::call(glEnd); + trampoline(); + + if (g_current_runtime) g_current_runtime->on_draw_call(g_current_runtime->_current_vertex_count); +} + +HOOK_EXPORT void WINAPI glEndList() +{ + static const auto trampoline = reshade::hooks::call(glEndList); + trampoline(); +} + +HOOK_EXPORT void WINAPI glEvalCoord1d(GLdouble u) +{ + static const auto trampoline = reshade::hooks::call(glEvalCoord1d); + trampoline(u); +} +HOOK_EXPORT void WINAPI glEvalCoord1dv(const GLdouble *u) +{ + static const auto trampoline = reshade::hooks::call(glEvalCoord1dv); + trampoline(u); +} +HOOK_EXPORT void WINAPI glEvalCoord1f(GLfloat u) +{ + static const auto trampoline = reshade::hooks::call(glEvalCoord1f); + trampoline(u); +} +HOOK_EXPORT void WINAPI glEvalCoord1fv(const GLfloat *u) +{ + static const auto trampoline = reshade::hooks::call(glEvalCoord1fv); + trampoline(u); +} +HOOK_EXPORT void WINAPI glEvalCoord2d(GLdouble u, GLdouble v) +{ + static const auto trampoline = reshade::hooks::call(glEvalCoord2d); + trampoline(u, v); +} +HOOK_EXPORT void WINAPI glEvalCoord2dv(const GLdouble *u) +{ + static const auto trampoline = reshade::hooks::call(glEvalCoord2dv); + trampoline(u); +} +HOOK_EXPORT void WINAPI glEvalCoord2f(GLfloat u, GLfloat v) +{ + static const auto trampoline = reshade::hooks::call(glEvalCoord2f); + trampoline(u, v); +} +HOOK_EXPORT void WINAPI glEvalCoord2fv(const GLfloat *u) +{ + static const auto trampoline = reshade::hooks::call(glEvalCoord2fv); + trampoline(u); +} + +HOOK_EXPORT void WINAPI glEvalMesh1(GLenum mode, GLint i1, GLint i2) +{ + static const auto trampoline = reshade::hooks::call(glEvalMesh1); + trampoline(mode, i1, i2); +} +HOOK_EXPORT void WINAPI glEvalMesh2(GLenum mode, GLint i1, GLint i2, GLint j1, GLint j2) +{ + static const auto trampoline = reshade::hooks::call(glEvalMesh2); + trampoline(mode, i1, i2, j1, j2); +} + +HOOK_EXPORT void WINAPI glEvalPoint1(GLint i) +{ + static const auto trampoline = reshade::hooks::call(glEvalPoint1); + trampoline(i); +} +HOOK_EXPORT void WINAPI glEvalPoint2(GLint i, GLint j) +{ + static const auto trampoline = reshade::hooks::call(glEvalPoint2); + trampoline(i, j); +} + +HOOK_EXPORT void WINAPI glFeedbackBuffer(GLsizei size, GLenum type, GLfloat *buffer) +{ + static const auto trampoline = reshade::hooks::call(glFeedbackBuffer); + trampoline(size, type, buffer); +} + +HOOK_EXPORT void WINAPI glFinish() +{ + static const auto trampoline = reshade::hooks::call(glFinish); + trampoline(); +} +HOOK_EXPORT void WINAPI glFlush() +{ + static const auto trampoline = reshade::hooks::call(glFlush); + trampoline(); +} + +HOOK_EXPORT void WINAPI glFogf(GLenum pname, GLfloat param) +{ + static const auto trampoline = reshade::hooks::call(glFogf); + trampoline(pname, param); +} +HOOK_EXPORT void WINAPI glFogfv(GLenum pname, const GLfloat *params) +{ + static const auto trampoline = reshade::hooks::call(glFogfv); + trampoline(pname, params); +} +HOOK_EXPORT void WINAPI glFogi(GLenum pname, GLint param) +{ + static const auto trampoline = reshade::hooks::call(glFogi); + trampoline(pname, param); +} +HOOK_EXPORT void WINAPI glFogiv(GLenum pname, const GLint *params) +{ + static const auto trampoline = reshade::hooks::call(glFogiv); + trampoline(pname, params); +} + +extern "C" void WINAPI glFramebufferRenderbuffer(GLenum target, GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer) +{ + static const auto trampoline = reshade::hooks::call(glFramebufferRenderbuffer); + trampoline(target, attachment, renderbuffertarget, renderbuffer); + + if (g_current_runtime) g_current_runtime->on_fbo_attachment(attachment, renderbuffertarget, renderbuffer, 0); +} +extern "C" void WINAPI glFramebufferRenderbufferEXT(GLenum target, GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer) +{ + static const auto trampoline = reshade::hooks::call(glFramebufferRenderbufferEXT); + trampoline(target, attachment, renderbuffertarget, renderbuffer); + + if (g_current_runtime) g_current_runtime->on_fbo_attachment(attachment, renderbuffertarget, renderbuffer, 0); +} +extern "C" void WINAPI glFramebufferTexture(GLenum target, GLenum attachment, GLuint texture, GLint level) +{ + static const auto trampoline = reshade::hooks::call(glFramebufferTexture); + trampoline(target, attachment, texture, level); + + if (g_current_runtime) g_current_runtime->on_fbo_attachment(attachment, GL_TEXTURE_2D, texture, level); +} +extern "C" void WINAPI glFramebufferTextureARB(GLenum target, GLenum attachment, GLuint texture, GLint level) +{ + static const auto trampoline = reshade::hooks::call(glFramebufferTextureARB); + trampoline(target, attachment, texture, level); + + if (g_current_runtime) g_current_runtime->on_fbo_attachment(attachment, GL_TEXTURE_2D, texture, level); +} +extern "C" void WINAPI glFramebufferTextureEXT(GLenum target, GLenum attachment, GLuint texture, GLint level) +{ + static const auto trampoline = reshade::hooks::call(glFramebufferTextureEXT); + trampoline(target, attachment, texture, level); + + if (g_current_runtime) g_current_runtime->on_fbo_attachment(attachment, GL_TEXTURE_2D, texture, level); +} +extern "C" void WINAPI glFramebufferTexture1D(GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level) +{ + static const auto trampoline = reshade::hooks::call(glFramebufferTexture1D); + trampoline(target, attachment, textarget, texture, level); + + if (g_current_runtime) g_current_runtime->on_fbo_attachment(attachment, textarget, texture, level); +} +extern "C" void WINAPI glFramebufferTexture1DEXT(GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level) +{ + static const auto trampoline = reshade::hooks::call(glFramebufferTexture1DEXT); + trampoline(target, attachment, textarget, texture, level); + + if (g_current_runtime) g_current_runtime->on_fbo_attachment(attachment, textarget, texture, level); +} +extern "C" void WINAPI glFramebufferTexture2D(GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level) +{ + static const auto trampoline = reshade::hooks::call(glFramebufferTexture2D); + trampoline(target, attachment, textarget, texture, level); + + if (g_current_runtime) g_current_runtime->on_fbo_attachment(attachment, textarget, texture, level); +} +extern "C" void WINAPI glFramebufferTexture2DEXT(GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level) +{ + static const auto trampoline = reshade::hooks::call(glFramebufferTexture2DEXT); + trampoline(target, attachment, textarget, texture, level); + + if (g_current_runtime) g_current_runtime->on_fbo_attachment(attachment, textarget, texture, level); +} +extern "C" void WINAPI glFramebufferTexture3D(GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level, GLint zoffset) +{ + static const auto trampoline = reshade::hooks::call(glFramebufferTexture3D); + trampoline(target, attachment, textarget, texture, level, zoffset); + + if (g_current_runtime) g_current_runtime->on_fbo_attachment(attachment, textarget, texture, level); +} +extern "C" void WINAPI glFramebufferTexture3DEXT(GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level, GLint zoffset) +{ + static const auto trampoline = reshade::hooks::call(glFramebufferTexture3DEXT); + trampoline(target, attachment, textarget, texture, level, zoffset); + + if (g_current_runtime) g_current_runtime->on_fbo_attachment(attachment, textarget, texture, level); +} +extern "C" void WINAPI glFramebufferTextureLayer(GLenum target, GLenum attachment, GLuint texture, GLint level, GLint layer) +{ + static const auto trampoline = reshade::hooks::call(glFramebufferTextureLayer); + trampoline(target, attachment, texture, level, layer); + + if (g_current_runtime) g_current_runtime->on_fbo_attachment(attachment, GL_TEXTURE_2D, texture, level); +} +extern "C" void WINAPI glFramebufferTextureLayerARB(GLenum target, GLenum attachment, GLuint texture, GLint level, GLint layer) +{ + static const auto trampoline = reshade::hooks::call(glFramebufferTextureLayerARB); + trampoline(target, attachment, texture, level, layer); + + if (g_current_runtime) g_current_runtime->on_fbo_attachment(attachment, GL_TEXTURE_2D, texture, level); +} +extern "C" void WINAPI glFramebufferTextureLayerEXT(GLenum target, GLenum attachment, GLuint texture, GLint level, GLint layer) +{ + static const auto trampoline = reshade::hooks::call(glFramebufferTextureLayerEXT); + trampoline(target, attachment, texture, level, layer); + + if (g_current_runtime) g_current_runtime->on_fbo_attachment(attachment, GL_TEXTURE_2D, texture, level); +} + +HOOK_EXPORT void WINAPI glFrontFace(GLenum mode) +{ + static const auto trampoline = reshade::hooks::call(glFrontFace); + trampoline(mode); +} + +HOOK_EXPORT void WINAPI glFrustum(GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble zNear, GLdouble zFar) +{ + static const auto trampoline = reshade::hooks::call(glFrustum); + trampoline(left, right, bottom, top, zNear, zFar); +} + +HOOK_EXPORT GLuint WINAPI glGenLists(GLsizei range) +{ + static const auto trampoline = reshade::hooks::call(glGenLists); + return trampoline(range); +} + +HOOK_EXPORT void WINAPI glGenTextures(GLsizei n, GLuint *textures) +{ + static const auto trampoline = reshade::hooks::call(glGenTextures); + trampoline(n, textures); +} + +HOOK_EXPORT void WINAPI glGetBooleanv(GLenum pname, GLboolean *params) +{ + static const auto trampoline = reshade::hooks::call(glGetBooleanv); + trampoline(pname, params); +} +HOOK_EXPORT void WINAPI glGetDoublev(GLenum pname, GLdouble *params) +{ + static const auto trampoline = reshade::hooks::call(glGetDoublev); + trampoline(pname, params); +} +HOOK_EXPORT void WINAPI glGetFloatv(GLenum pname, GLfloat *params) +{ + static const auto trampoline = reshade::hooks::call(glGetFloatv); + trampoline(pname, params); +} +HOOK_EXPORT void WINAPI glGetIntegerv(GLenum pname, GLint *params) +{ + static const auto trampoline = reshade::hooks::call(glGetIntegerv); + trampoline(pname, params); +} + +HOOK_EXPORT void WINAPI glGetClipPlane(GLenum plane, GLdouble *equation) +{ + static const auto trampoline = reshade::hooks::call(glGetClipPlane); + trampoline(plane, equation); +} + +HOOK_EXPORT GLenum WINAPI glGetError() +{ + static const auto trampoline = reshade::hooks::call(glGetError); + return trampoline(); +} + +HOOK_EXPORT void WINAPI glGetLightfv(GLenum light, GLenum pname, GLfloat *params) +{ + static const auto trampoline = reshade::hooks::call(glGetLightfv); + trampoline(light, pname, params); +} +HOOK_EXPORT void WINAPI glGetLightiv(GLenum light, GLenum pname, GLint *params) +{ + static const auto trampoline = reshade::hooks::call(glGetLightiv); + trampoline(light, pname, params); +} + +HOOK_EXPORT void WINAPI glGetMapdv(GLenum target, GLenum query, GLdouble *v) +{ + static const auto trampoline = reshade::hooks::call(glGetMapdv); + trampoline(target, query, v); +} +HOOK_EXPORT void WINAPI glGetMapfv(GLenum target, GLenum query, GLfloat *v) +{ + static const auto trampoline = reshade::hooks::call(glGetMapfv); + trampoline(target, query, v); +} +HOOK_EXPORT void WINAPI glGetMapiv(GLenum target, GLenum query, GLint *v) +{ + static const auto trampoline = reshade::hooks::call(glGetMapiv); + trampoline(target, query, v); +} + +HOOK_EXPORT void WINAPI glGetMaterialfv(GLenum face, GLenum pname, GLfloat *params) +{ + static const auto trampoline = reshade::hooks::call(glGetMaterialfv); + trampoline(face, pname, params); +} +HOOK_EXPORT void WINAPI glGetMaterialiv(GLenum face, GLenum pname, GLint *params) +{ + static const auto trampoline = reshade::hooks::call(glGetMaterialiv); + trampoline(face, pname, params); +} + +HOOK_EXPORT void WINAPI glGetPixelMapfv(GLenum map, GLfloat *values) +{ + static const auto trampoline = reshade::hooks::call(glGetPixelMapfv); + trampoline(map, values); +} +HOOK_EXPORT void WINAPI glGetPixelMapuiv(GLenum map, GLuint *values) +{ + static const auto trampoline = reshade::hooks::call(glGetPixelMapuiv); + trampoline(map, values); +} +HOOK_EXPORT void WINAPI glGetPixelMapusv(GLenum map, GLushort *values) +{ + static const auto trampoline = reshade::hooks::call(glGetPixelMapusv); + trampoline(map, values); +} + +HOOK_EXPORT void WINAPI glGetPointerv(GLenum pname, GLvoid **params) +{ + static const auto trampoline = reshade::hooks::call(glGetPointerv); + trampoline(pname, params); +} + +HOOK_EXPORT void WINAPI glGetPolygonStipple(GLubyte *mask) +{ + static const auto trampoline = reshade::hooks::call(glGetPolygonStipple); + trampoline(mask); +} + +HOOK_EXPORT const GLubyte *WINAPI glGetString(GLenum name) +{ + static const auto trampoline = reshade::hooks::call(glGetString); + return trampoline(name); +} + +HOOK_EXPORT void WINAPI glGetTexEnvfv(GLenum target, GLenum pname, GLfloat *params) +{ + static const auto trampoline = reshade::hooks::call(glGetTexEnvfv); + trampoline(target, pname, params); +} +HOOK_EXPORT void WINAPI glGetTexEnviv(GLenum target, GLenum pname, GLint *params) +{ + static const auto trampoline = reshade::hooks::call(glGetTexEnviv); + trampoline(target, pname, params); +} + +HOOK_EXPORT void WINAPI glGetTexGendv(GLenum coord, GLenum pname, GLdouble *params) +{ + static const auto trampoline = reshade::hooks::call(glGetTexGendv); + trampoline(coord, pname, params); +} +HOOK_EXPORT void WINAPI glGetTexGenfv(GLenum coord, GLenum pname, GLfloat *params) +{ + static const auto trampoline = reshade::hooks::call(glGetTexGenfv); + trampoline(coord, pname, params); +} +HOOK_EXPORT void WINAPI glGetTexGeniv(GLenum coord, GLenum pname, GLint *params) +{ + static const auto trampoline = reshade::hooks::call(glGetTexGeniv); + trampoline(coord, pname, params); +} + +HOOK_EXPORT void WINAPI glGetTexImage(GLenum target, GLint level, GLenum format, GLenum type, GLvoid *pixels) +{ + static const auto trampoline = reshade::hooks::call(glGetTexImage); + trampoline(target, level, format, type, pixels); +} + +HOOK_EXPORT void WINAPI glGetTexLevelParameterfv(GLenum target, GLint level, GLenum pname, GLfloat *params) +{ + static const auto trampoline = reshade::hooks::call(glGetTexLevelParameterfv); + trampoline(target, level, pname, params); +} +HOOK_EXPORT void WINAPI glGetTexLevelParameteriv(GLenum target, GLint level, GLenum pname, GLint *params) +{ + static const auto trampoline = reshade::hooks::call(glGetTexLevelParameteriv); + trampoline(target, level, pname, params); +} +HOOK_EXPORT void WINAPI glGetTexParameterfv(GLenum target, GLenum pname, GLfloat *params) +{ + static const auto trampoline = reshade::hooks::call(glGetTexParameterfv); + trampoline(target, pname, params); +} +HOOK_EXPORT void WINAPI glGetTexParameteriv(GLenum target, GLenum pname, GLint *params) +{ + static const auto trampoline = reshade::hooks::call(glGetTexParameteriv); + trampoline(target, pname, params); +} + +HOOK_EXPORT void WINAPI glHint(GLenum target, GLenum mode) +{ + static const auto trampoline = reshade::hooks::call(glHint); + trampoline(target, mode); +} + +HOOK_EXPORT void WINAPI glIndexMask(GLuint mask) +{ + static const auto trampoline = reshade::hooks::call(glIndexMask); + trampoline(mask); +} + +HOOK_EXPORT void WINAPI glIndexPointer(GLenum type, GLsizei stride, const GLvoid *pointer) +{ + static const auto trampoline = reshade::hooks::call(glIndexPointer); + trampoline(type, stride, pointer); +} + +HOOK_EXPORT void WINAPI glIndexd(GLdouble c) +{ + static const auto trampoline = reshade::hooks::call(glIndexd); + trampoline(c); +} +HOOK_EXPORT void WINAPI glIndexdv(const GLdouble *c) +{ + static const auto trampoline = reshade::hooks::call(glIndexdv); + trampoline(c); +} +HOOK_EXPORT void WINAPI glIndexf(GLfloat c) +{ + static const auto trampoline = reshade::hooks::call(glIndexf); + trampoline(c); +} +HOOK_EXPORT void WINAPI glIndexfv(const GLfloat *c) +{ + static const auto trampoline = reshade::hooks::call(glIndexfv); + trampoline(c); +} +HOOK_EXPORT void WINAPI glIndexi(GLint c) +{ + static const auto trampoline = reshade::hooks::call(glIndexi); + trampoline(c); +} +HOOK_EXPORT void WINAPI glIndexiv(const GLint *c) +{ + static const auto trampoline = reshade::hooks::call(glIndexiv); + trampoline(c); +} +HOOK_EXPORT void WINAPI glIndexs(GLshort c) +{ + static const auto trampoline = reshade::hooks::call(glIndexs); + trampoline(c); +} +HOOK_EXPORT void WINAPI glIndexsv(const GLshort *c) +{ + static const auto trampoline = reshade::hooks::call(glIndexsv); + trampoline(c); +} +HOOK_EXPORT void WINAPI glIndexub(GLubyte c) +{ + static const auto trampoline = reshade::hooks::call(glIndexub); + trampoline(c); +} +HOOK_EXPORT void WINAPI glIndexubv(const GLubyte *c) +{ + static const auto trampoline = reshade::hooks::call(glIndexubv); + trampoline(c); +} + +HOOK_EXPORT void WINAPI glInitNames() +{ + static const auto trampoline = reshade::hooks::call(glInitNames); + trampoline(); +} + +HOOK_EXPORT void WINAPI glInterleavedArrays(GLenum format, GLsizei stride, const GLvoid *pointer) +{ + static const auto trampoline = reshade::hooks::call(glInterleavedArrays); + trampoline(format, stride, pointer); +} + +HOOK_EXPORT GLboolean WINAPI glIsEnabled(GLenum cap) +{ + static const auto trampoline = reshade::hooks::call(glIsEnabled); + return trampoline(cap); +} + +HOOK_EXPORT GLboolean WINAPI glIsList(GLuint list) +{ + static const auto trampoline = reshade::hooks::call(glIsList); + return trampoline(list); +} +HOOK_EXPORT GLboolean WINAPI glIsTexture(GLuint texture) +{ + static const auto trampoline = reshade::hooks::call(glIsTexture); + return trampoline(texture); +} + +HOOK_EXPORT void WINAPI glLightModelf(GLenum pname, GLfloat param) +{ + static const auto trampoline = reshade::hooks::call(glLightModelf); + trampoline(pname, param); +} +HOOK_EXPORT void WINAPI glLightModelfv(GLenum pname, const GLfloat *params) +{ + static const auto trampoline = reshade::hooks::call(glLightModelfv); + trampoline(pname, params); +} +HOOK_EXPORT void WINAPI glLightModeli(GLenum pname, GLint param) +{ + static const auto trampoline = reshade::hooks::call(glLightModeli); + trampoline(pname, param); +} +HOOK_EXPORT void WINAPI glLightModeliv(GLenum pname, const GLint *params) +{ + static const auto trampoline = reshade::hooks::call(glLightModeliv); + trampoline(pname, params); +} + +HOOK_EXPORT void WINAPI glLightf(GLenum light, GLenum pname, GLfloat param) +{ + static const auto trampoline = reshade::hooks::call(glLightf); + trampoline(light, pname, param); +} +HOOK_EXPORT void WINAPI glLightfv(GLenum light, GLenum pname, const GLfloat *params) +{ + static const auto trampoline = reshade::hooks::call(glLightfv); + trampoline(light, pname, params); +} +HOOK_EXPORT void WINAPI glLighti(GLenum light, GLenum pname, GLint param) +{ + static const auto trampoline = reshade::hooks::call(glLighti); + trampoline(light, pname, param); +} +HOOK_EXPORT void WINAPI glLightiv(GLenum light, GLenum pname, const GLint *params) +{ + static const auto trampoline = reshade::hooks::call(glLightiv); + trampoline(light, pname, params); +} + +HOOK_EXPORT void WINAPI glLineStipple(GLint factor, GLushort pattern) +{ + static const auto trampoline = reshade::hooks::call(glLineStipple); + trampoline(factor, pattern); +} +HOOK_EXPORT void WINAPI glLineWidth(GLfloat width) +{ + static const auto trampoline = reshade::hooks::call(glLineWidth); + trampoline(width); +} + +HOOK_EXPORT void WINAPI glListBase(GLuint base) +{ + static const auto trampoline = reshade::hooks::call(glListBase); + trampoline(base); +} + +HOOK_EXPORT void WINAPI glLoadIdentity() +{ + static const auto trampoline = reshade::hooks::call(glLoadIdentity); + trampoline(); +} +HOOK_EXPORT void WINAPI glLoadMatrixd(const GLdouble *m) +{ + static const auto trampoline = reshade::hooks::call(glLoadMatrixd); + trampoline(m); +} +HOOK_EXPORT void WINAPI glLoadMatrixf(const GLfloat *m) +{ + static const auto trampoline = reshade::hooks::call(glLoadMatrixf); + trampoline(m); +} + +HOOK_EXPORT void WINAPI glLoadName(GLuint name) +{ + static const auto trampoline = reshade::hooks::call(glLoadName); + trampoline(name); +} + +HOOK_EXPORT void WINAPI glLogicOp(GLenum opcode) +{ + static const auto trampoline = reshade::hooks::call(glLogicOp); + trampoline(opcode); +} + +HOOK_EXPORT void WINAPI glMap1d(GLenum target, GLdouble u1, GLdouble u2, GLint stride, GLint order, const GLdouble *points) +{ + static const auto trampoline = reshade::hooks::call(glMap1d); + trampoline(target, u1, u2, stride, order, points); +} +HOOK_EXPORT void WINAPI glMap1f(GLenum target, GLfloat u1, GLfloat u2, GLint stride, GLint order, const GLfloat *points) +{ + static const auto trampoline = reshade::hooks::call(glMap1f); + trampoline(target, u1, u2, stride, order, points); +} +HOOK_EXPORT void WINAPI glMap2d(GLenum target, GLdouble u1, GLdouble u2, GLint ustride, GLint uorder, GLdouble v1, GLdouble v2, GLint vstride, GLint vorder, const GLdouble *points) +{ + static const auto trampoline = reshade::hooks::call(glMap2d); + trampoline(target, u1, u2, ustride, uorder, v1, v2, vstride, vorder, points); +} +HOOK_EXPORT void WINAPI glMap2f(GLenum target, GLfloat u1, GLfloat u2, GLint ustride, GLint uorder, GLfloat v1, GLfloat v2, GLint vstride, GLint vorder, const GLfloat *points) +{ + static const auto trampoline = reshade::hooks::call(glMap2f); + trampoline(target, u1, u2, ustride, uorder, v1, v2, vstride, vorder, points); +} + +HOOK_EXPORT void WINAPI glMapGrid1d(GLint un, GLdouble u1, GLdouble u2) +{ + static const auto trampoline = reshade::hooks::call(glMapGrid1d); + trampoline(un, u1, u2); +} +HOOK_EXPORT void WINAPI glMapGrid1f(GLint un, GLfloat u1, GLfloat u2) +{ + static const auto trampoline = reshade::hooks::call(glMapGrid1f); + trampoline(un, u1, u2); +} +HOOK_EXPORT void WINAPI glMapGrid2d(GLint un, GLdouble u1, GLdouble u2, GLint vn, GLdouble v1, GLdouble v2) +{ + static const auto trampoline = reshade::hooks::call(glMapGrid2d); + trampoline(un, u1, u2, vn, v1, v2); +} +HOOK_EXPORT void WINAPI glMapGrid2f(GLint un, GLfloat u1, GLfloat u2, GLint vn, GLfloat v1, GLfloat v2) +{ + static const auto trampoline = reshade::hooks::call(glMapGrid2f); + trampoline(un, u1, u2, vn, v1, v2); +} + +HOOK_EXPORT void WINAPI glMaterialf(GLenum face, GLenum pname, GLfloat param) +{ + static const auto trampoline = reshade::hooks::call(glMaterialf); + trampoline(face, pname, param); +} +HOOK_EXPORT void WINAPI glMaterialfv(GLenum face, GLenum pname, const GLfloat *params) +{ + static const auto trampoline = reshade::hooks::call(glMaterialfv); + trampoline(face, pname, params); +} +HOOK_EXPORT void WINAPI glMateriali(GLenum face, GLenum pname, GLint param) +{ + static const auto trampoline = reshade::hooks::call(glMateriali); + trampoline(face, pname, param); +} +HOOK_EXPORT void WINAPI glMaterialiv(GLenum face, GLenum pname, const GLint *params) +{ + static const auto trampoline = reshade::hooks::call(glMaterialiv); + trampoline(face, pname, params); +} + +HOOK_EXPORT void WINAPI glMatrixMode(GLenum mode) +{ + static const auto trampoline = reshade::hooks::call(glMatrixMode); + trampoline(mode); +} + +HOOK_EXPORT void WINAPI glMultMatrixd(const GLdouble *m) +{ + static const auto trampoline = reshade::hooks::call(glMultMatrixd); + trampoline(m); +} +HOOK_EXPORT void WINAPI glMultMatrixf(const GLfloat *m) +{ + static const auto trampoline = reshade::hooks::call(glMultMatrixf); + trampoline(m); +} + +extern "C" void WINAPI glMultiDrawArrays(GLenum mode, const GLint *first, const GLsizei *count, GLsizei drawcount) +{ + if (g_current_runtime) g_current_runtime->on_draw_call(std::accumulate(count, count + drawcount, 0)); + + static const auto trampoline = reshade::hooks::call(glMultiDrawArrays); + trampoline(mode, first, count, drawcount); +} +extern "C" void WINAPI glMultiDrawArraysIndirect(GLenum mode, const void *indirect, GLsizei drawcount, GLsizei stride) +{ + static const auto trampoline = reshade::hooks::call(glMultiDrawArraysIndirect); + trampoline(mode, indirect, drawcount, stride); +} +extern "C" void WINAPI glMultiDrawElements(GLenum mode, const GLsizei *count, GLenum type, const GLvoid *const *indices, GLsizei drawcount) +{ + if (g_current_runtime) g_current_runtime->on_draw_call(std::accumulate(count, count + drawcount, 0)); + + static const auto trampoline = reshade::hooks::call(glMultiDrawElements); + trampoline(mode, count, type, indices, drawcount); +} +extern "C" void WINAPI glMultiDrawElementsBaseVertex(GLenum mode, const GLsizei *count, GLenum type, const GLvoid *const *indices, GLsizei drawcount, const GLint *basevertex) +{ + if (g_current_runtime) g_current_runtime->on_draw_call(std::accumulate(count, count + drawcount, 0)); + + static const auto trampoline = reshade::hooks::call(glMultiDrawElementsBaseVertex); + trampoline(mode, count, type, indices, drawcount, basevertex); +} +extern "C" void WINAPI glMultiDrawElementsIndirect(GLenum mode, GLenum type, const void *indirect, GLsizei drawcount, GLsizei stride) +{ + static const auto trampoline = reshade::hooks::call(glMultiDrawElementsIndirect); + trampoline(mode, type, indirect, drawcount, stride); +} + +HOOK_EXPORT void WINAPI glNewList(GLuint list, GLenum mode) +{ + static const auto trampoline = reshade::hooks::call(glNewList); + trampoline(list, mode); +} + +HOOK_EXPORT void WINAPI glNormal3b(GLbyte nx, GLbyte ny, GLbyte nz) +{ + static const auto trampoline = reshade::hooks::call(glNormal3b); + trampoline(nx, ny, nz); +} +HOOK_EXPORT void WINAPI glNormal3bv(const GLbyte *v) +{ + static const auto trampoline = reshade::hooks::call(glNormal3bv); + trampoline(v); +} +HOOK_EXPORT void WINAPI glNormal3d(GLdouble nx, GLdouble ny, GLdouble nz) +{ + static const auto trampoline = reshade::hooks::call(glNormal3d); + trampoline(nx, ny, nz); +} +HOOK_EXPORT void WINAPI glNormal3dv(const GLdouble *v) +{ + static const auto trampoline = reshade::hooks::call(glNormal3dv); + trampoline(v); +} +HOOK_EXPORT void WINAPI glNormal3f(GLfloat nx, GLfloat ny, GLfloat nz) +{ + static const auto trampoline = reshade::hooks::call(glNormal3f); + trampoline(nx, ny, nz); +} +HOOK_EXPORT void WINAPI glNormal3fv(const GLfloat *v) +{ + static const auto trampoline = reshade::hooks::call(glNormal3fv); + trampoline(v); +} +HOOK_EXPORT void WINAPI glNormal3i(GLint nx, GLint ny, GLint nz) +{ + static const auto trampoline = reshade::hooks::call(glNormal3i); + trampoline(nx, ny, nz); +} +HOOK_EXPORT void WINAPI glNormal3iv(const GLint *v) +{ + static const auto trampoline = reshade::hooks::call(glNormal3iv); + trampoline(v); +} +HOOK_EXPORT void WINAPI glNormal3s(GLshort nx, GLshort ny, GLshort nz) +{ + static const auto trampoline = reshade::hooks::call(glNormal3s); + trampoline(nx, ny, nz); +} +HOOK_EXPORT void WINAPI glNormal3sv(const GLshort *v) +{ + static const auto trampoline = reshade::hooks::call(glNormal3sv); + trampoline(v); +} + +HOOK_EXPORT void WINAPI glNormalPointer(GLenum type, GLsizei stride, const GLvoid *pointer) +{ + static const auto trampoline = reshade::hooks::call(glNormalPointer); + trampoline(type, stride, pointer); +} + +HOOK_EXPORT void WINAPI glOrtho(GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble zNear, GLdouble zFar) +{ + static const auto trampoline = reshade::hooks::call(glOrtho); + trampoline(left, right, bottom, top, zNear, zFar); +} + +HOOK_EXPORT void WINAPI glPassThrough(GLfloat token) +{ + static const auto trampoline = reshade::hooks::call(glPassThrough); + trampoline(token); +} + +HOOK_EXPORT void WINAPI glPixelMapfv(GLenum map, GLsizei mapsize, const GLfloat *values) +{ + static const auto trampoline = reshade::hooks::call(glPixelMapfv); + trampoline(map, mapsize, values); +} +HOOK_EXPORT void WINAPI glPixelMapuiv(GLenum map, GLsizei mapsize, const GLuint *values) +{ + static const auto trampoline = reshade::hooks::call(glPixelMapuiv); + trampoline(map, mapsize, values); +} +HOOK_EXPORT void WINAPI glPixelMapusv(GLenum map, GLsizei mapsize, const GLushort *values) +{ + static const auto trampoline = reshade::hooks::call(glPixelMapusv); + trampoline(map, mapsize, values); +} + +HOOK_EXPORT void WINAPI glPixelStoref(GLenum pname, GLfloat param) +{ + static const auto trampoline = reshade::hooks::call(glPixelStoref); + trampoline(pname, param); +} +HOOK_EXPORT void WINAPI glPixelStorei(GLenum pname, GLint param) +{ + static const auto trampoline = reshade::hooks::call(glPixelStorei); + trampoline(pname, param); +} + +HOOK_EXPORT void WINAPI glPixelTransferf(GLenum pname, GLfloat param) +{ + static const auto trampoline = reshade::hooks::call(glPixelTransferf); + trampoline(pname, param); +} +HOOK_EXPORT void WINAPI glPixelTransferi(GLenum pname, GLint param) +{ + static const auto trampoline = reshade::hooks::call(glPixelTransferi); + trampoline(pname, param); +} + +HOOK_EXPORT void WINAPI glPixelZoom(GLfloat xfactor, GLfloat yfactor) +{ + static const auto trampoline = reshade::hooks::call(glPixelZoom); + trampoline(xfactor, yfactor); +} + +HOOK_EXPORT void WINAPI glPointSize(GLfloat size) +{ + static const auto trampoline = reshade::hooks::call(glPointSize); + trampoline(size); +} + +HOOK_EXPORT void WINAPI glPolygonMode(GLenum face, GLenum mode) +{ + static const auto trampoline = reshade::hooks::call(glPolygonMode); + trampoline(face, mode); +} +HOOK_EXPORT void WINAPI glPolygonOffset(GLfloat factor, GLfloat units) +{ + static const auto trampoline = reshade::hooks::call(glPolygonOffset); + trampoline(factor, units); +} +HOOK_EXPORT void WINAPI glPolygonStipple(const GLubyte *mask) +{ + static const auto trampoline = reshade::hooks::call(glPolygonStipple); + trampoline(mask); +} + +HOOK_EXPORT void WINAPI glPopAttrib() +{ + static const auto trampoline = reshade::hooks::call(glPopAttrib); + trampoline(); +} +HOOK_EXPORT void WINAPI glPopClientAttrib() +{ + static const auto trampoline = reshade::hooks::call(glPopClientAttrib); + trampoline(); +} +HOOK_EXPORT void WINAPI glPopMatrix() +{ + static const auto trampoline = reshade::hooks::call(glPopMatrix); + trampoline(); +} +HOOK_EXPORT void WINAPI glPopName() +{ + static const auto trampoline = reshade::hooks::call(glPopName); + trampoline(); +} + +HOOK_EXPORT void WINAPI glPrioritizeTextures(GLsizei n, const GLuint *textures, const GLclampf *priorities) +{ + static const auto trampoline = reshade::hooks::call(glPrioritizeTextures); + trampoline(n, textures, priorities); +} + +HOOK_EXPORT void WINAPI glPushAttrib(GLbitfield mask) +{ + static const auto trampoline = reshade::hooks::call(glPushAttrib); + trampoline(mask); +} +HOOK_EXPORT void WINAPI glPushClientAttrib(GLbitfield mask) +{ + static const auto trampoline = reshade::hooks::call(glPushClientAttrib); + trampoline(mask); +} +HOOK_EXPORT void WINAPI glPushMatrix() +{ + static const auto trampoline = reshade::hooks::call(glPushMatrix); + trampoline(); +} +HOOK_EXPORT void WINAPI glPushName(GLuint name) +{ + static const auto trampoline = reshade::hooks::call(glPushName); + trampoline(name); +} + +HOOK_EXPORT void WINAPI glRasterPos2d(GLdouble x, GLdouble y) +{ + static const auto trampoline = reshade::hooks::call(glRasterPos2d); + trampoline(x, y); +} +HOOK_EXPORT void WINAPI glRasterPos2dv(const GLdouble *v) +{ + static const auto trampoline = reshade::hooks::call(glRasterPos2dv); + trampoline(v); +} +HOOK_EXPORT void WINAPI glRasterPos2f(GLfloat x, GLfloat y) +{ + static const auto trampoline = reshade::hooks::call(glRasterPos2f); + trampoline(x, y); +} +HOOK_EXPORT void WINAPI glRasterPos2fv(const GLfloat *v) +{ + static const auto trampoline = reshade::hooks::call(glRasterPos2fv); + trampoline(v); +} +HOOK_EXPORT void WINAPI glRasterPos2i(GLint x, GLint y) +{ + static const auto trampoline = reshade::hooks::call(glRasterPos2i); + trampoline(x, y); +} +HOOK_EXPORT void WINAPI glRasterPos2iv(const GLint *v) +{ + static const auto trampoline = reshade::hooks::call(glRasterPos2iv); + trampoline(v); +} +HOOK_EXPORT void WINAPI glRasterPos2s(GLshort x, GLshort y) +{ + static const auto trampoline = reshade::hooks::call(glRasterPos2s); + trampoline(x, y); +} +HOOK_EXPORT void WINAPI glRasterPos2sv(const GLshort *v) +{ + static const auto trampoline = reshade::hooks::call(glRasterPos2sv); + trampoline(v); +} +HOOK_EXPORT void WINAPI glRasterPos3d(GLdouble x, GLdouble y, GLdouble z) +{ + static const auto trampoline = reshade::hooks::call(glRasterPos3d); + trampoline(x, y, z); +} +HOOK_EXPORT void WINAPI glRasterPos3dv(const GLdouble *v) +{ + static const auto trampoline = reshade::hooks::call(glRasterPos3dv); + trampoline(v); +} +HOOK_EXPORT void WINAPI glRasterPos3f(GLfloat x, GLfloat y, GLfloat z) +{ + static const auto trampoline = reshade::hooks::call(glRasterPos3f); + trampoline(x, y, z); +} +HOOK_EXPORT void WINAPI glRasterPos3fv(const GLfloat *v) +{ + static const auto trampoline = reshade::hooks::call(glRasterPos3fv); + trampoline(v); +} +HOOK_EXPORT void WINAPI glRasterPos3i(GLint x, GLint y, GLint z) +{ + static const auto trampoline = reshade::hooks::call(glRasterPos3i); + trampoline(x, y, z); +} +HOOK_EXPORT void WINAPI glRasterPos3iv(const GLint *v) +{ + static const auto trampoline = reshade::hooks::call(glRasterPos3iv); + trampoline(v); +} +HOOK_EXPORT void WINAPI glRasterPos3s(GLshort x, GLshort y, GLshort z) +{ + static const auto trampoline = reshade::hooks::call(glRasterPos3s); + trampoline(x, y, z); +} +HOOK_EXPORT void WINAPI glRasterPos3sv(const GLshort *v) +{ + static const auto trampoline = reshade::hooks::call(glRasterPos3sv); + trampoline(v); +} +HOOK_EXPORT void WINAPI glRasterPos4d(GLdouble x, GLdouble y, GLdouble z, GLdouble w) +{ + static const auto trampoline = reshade::hooks::call(glRasterPos4d); + trampoline(x, y, z, w); +} +HOOK_EXPORT void WINAPI glRasterPos4dv(const GLdouble *v) +{ + static const auto trampoline = reshade::hooks::call(glRasterPos4dv); + trampoline(v); +} +HOOK_EXPORT void WINAPI glRasterPos4f(GLfloat x, GLfloat y, GLfloat z, GLfloat w) +{ + static const auto trampoline = reshade::hooks::call(glRasterPos4f); + trampoline(x, y, z, w); +} +HOOK_EXPORT void WINAPI glRasterPos4fv(const GLfloat *v) +{ + static const auto trampoline = reshade::hooks::call(glRasterPos4fv); + trampoline(v); +} +HOOK_EXPORT void WINAPI glRasterPos4i(GLint x, GLint y, GLint z, GLint w) +{ + static const auto trampoline = reshade::hooks::call(glRasterPos4i); + trampoline(x, y, z, w); +} +HOOK_EXPORT void WINAPI glRasterPos4iv(const GLint *v) +{ + static const auto trampoline = reshade::hooks::call(glRasterPos4iv); + trampoline(v); +} +HOOK_EXPORT void WINAPI glRasterPos4s(GLshort x, GLshort y, GLshort z, GLshort w) +{ + static const auto trampoline = reshade::hooks::call(glRasterPos4s); + trampoline(x, y, z, w); +} +HOOK_EXPORT void WINAPI glRasterPos4sv(const GLshort *v) +{ + static const auto trampoline = reshade::hooks::call(glRasterPos4sv); + trampoline(v); +} + +HOOK_EXPORT void WINAPI glReadBuffer(GLenum mode) +{ + static const auto trampoline = reshade::hooks::call(glReadBuffer); + trampoline(mode); +} + +HOOK_EXPORT void WINAPI glReadPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, GLvoid *pixels) +{ + static const auto trampoline = reshade::hooks::call(glReadPixels); + trampoline(x, y, width, height, format, type, pixels); +} + +HOOK_EXPORT void WINAPI glRectd(GLdouble x1, GLdouble y1, GLdouble x2, GLdouble y2) +{ + static const auto trampoline = reshade::hooks::call(glRectd); + trampoline(x1, y1, x2, y2); +} +HOOK_EXPORT void WINAPI glRectdv(const GLdouble *v1, const GLdouble *v2) +{ + static const auto trampoline = reshade::hooks::call(glRectdv); + trampoline(v1, v2); +} +HOOK_EXPORT void WINAPI glRectf(GLfloat x1, GLfloat y1, GLfloat x2, GLfloat y2) +{ + static const auto trampoline = reshade::hooks::call(glRectf); + trampoline(x1, y1, x2, y2); +} +HOOK_EXPORT void WINAPI glRectfv(const GLfloat *v1, const GLfloat *v2) +{ + static const auto trampoline = reshade::hooks::call(glRectfv); + trampoline(v1, v2); +} +HOOK_EXPORT void WINAPI glRecti(GLint x1, GLint y1, GLint x2, GLint y2) +{ + static const auto trampoline = reshade::hooks::call(glRecti); + trampoline(x1, y1, x2, y2); +} +HOOK_EXPORT void WINAPI glRectiv(const GLint *v1, const GLint *v2) +{ + static const auto trampoline = reshade::hooks::call(glRectiv); + trampoline(v1, v2); +} + +HOOK_EXPORT void WINAPI glRects(GLshort x1, GLshort y1, GLshort x2, GLshort y2) +{ + static const auto trampoline = reshade::hooks::call(glRects); + trampoline(x1, y1, x2, y2); +} +HOOK_EXPORT void WINAPI glRectsv(const GLshort *v1, const GLshort *v2) +{ + static const auto trampoline = reshade::hooks::call(glRectsv); + trampoline(v1, v2); +} + +HOOK_EXPORT GLint WINAPI glRenderMode(GLenum mode) +{ + static const auto trampoline = reshade::hooks::call(glRenderMode); + return trampoline(mode); +} + +HOOK_EXPORT void WINAPI glRotated(GLdouble angle, GLdouble x, GLdouble y, GLdouble z) +{ + static const auto trampoline = reshade::hooks::call(glRotated); + trampoline(angle, x, y, z); +} +HOOK_EXPORT void WINAPI glRotatef(GLfloat angle, GLfloat x, GLfloat y, GLfloat z) +{ + static const auto trampoline = reshade::hooks::call(glRotatef); + trampoline(angle, x, y, z); +} + +HOOK_EXPORT void WINAPI glScaled(GLdouble x, GLdouble y, GLdouble z) +{ + static const auto trampoline = reshade::hooks::call(glScaled); + trampoline(x, y, z); +} +HOOK_EXPORT void WINAPI glScalef(GLfloat x, GLfloat y, GLfloat z) +{ + static const auto trampoline = reshade::hooks::call(glScalef); + trampoline(x, y, z); +} + +HOOK_EXPORT void WINAPI glScissor(GLint x, GLint y, GLsizei width, GLsizei height) +{ + static const auto trampoline = reshade::hooks::call(glScissor); + trampoline(x, y, width, height); +} + +HOOK_EXPORT void WINAPI glSelectBuffer(GLsizei size, GLuint *buffer) +{ + static const auto trampoline = reshade::hooks::call(glSelectBuffer); + trampoline(size, buffer); +} + +HOOK_EXPORT void WINAPI glShadeModel(GLenum mode) +{ + static const auto trampoline = reshade::hooks::call(glShadeModel); + trampoline(mode); +} + +HOOK_EXPORT void WINAPI glStencilFunc(GLenum func, GLint ref, GLuint mask) +{ + static const auto trampoline = reshade::hooks::call(glStencilFunc); + trampoline(func, ref, mask); +} +HOOK_EXPORT void WINAPI glStencilMask(GLuint mask) +{ + static const auto trampoline = reshade::hooks::call(glStencilMask); + trampoline(mask); +} +HOOK_EXPORT void WINAPI glStencilOp(GLenum fail, GLenum zfail, GLenum zpass) +{ + static const auto trampoline = reshade::hooks::call(glStencilOp); + trampoline(fail, zfail, zpass); +} + +HOOK_EXPORT void WINAPI glTexCoord1d(GLdouble s) +{ + static const auto trampoline = reshade::hooks::call(glTexCoord1d); + trampoline(s); +} +HOOK_EXPORT void WINAPI glTexCoord1dv(const GLdouble *v) +{ + static const auto trampoline = reshade::hooks::call(glTexCoord1dv); + trampoline(v); +} +HOOK_EXPORT void WINAPI glTexCoord1f(GLfloat s) +{ + static const auto trampoline = reshade::hooks::call(glTexCoord1f); + trampoline(s); +} +HOOK_EXPORT void WINAPI glTexCoord1fv(const GLfloat *v) +{ + static const auto trampoline = reshade::hooks::call(glTexCoord1fv); + trampoline(v); +} +HOOK_EXPORT void WINAPI glTexCoord1i(GLint s) +{ + static const auto trampoline = reshade::hooks::call(glTexCoord1i); + trampoline(s); +} +HOOK_EXPORT void WINAPI glTexCoord1iv(const GLint *v) +{ + static const auto trampoline = reshade::hooks::call(glTexCoord1iv); + trampoline(v); +} +HOOK_EXPORT void WINAPI glTexCoord1s(GLshort s) +{ + static const auto trampoline = reshade::hooks::call(glTexCoord1s); + trampoline(s); +} +HOOK_EXPORT void WINAPI glTexCoord1sv(const GLshort *v) +{ + static const auto trampoline = reshade::hooks::call(glTexCoord1sv); + trampoline(v); +} +HOOK_EXPORT void WINAPI glTexCoord2d(GLdouble s, GLdouble t) +{ + static const auto trampoline = reshade::hooks::call(glTexCoord2d); + trampoline(s, t); +} +HOOK_EXPORT void WINAPI glTexCoord2dv(const GLdouble *v) +{ + static const auto trampoline = reshade::hooks::call(glTexCoord2dv); + trampoline(v); +} +HOOK_EXPORT void WINAPI glTexCoord2f(GLfloat s, GLfloat t) +{ + static const auto trampoline = reshade::hooks::call(glTexCoord2f); + trampoline(s, t); +} +HOOK_EXPORT void WINAPI glTexCoord2fv(const GLfloat *v) +{ + static const auto trampoline = reshade::hooks::call(glTexCoord2fv); + trampoline(v); +} +HOOK_EXPORT void WINAPI glTexCoord2i(GLint s, GLint t) +{ + static const auto trampoline = reshade::hooks::call(glTexCoord2i); + trampoline(s, t); +} +HOOK_EXPORT void WINAPI glTexCoord2iv(const GLint *v) +{ + static const auto trampoline = reshade::hooks::call(glTexCoord2iv); + trampoline(v); +} +HOOK_EXPORT void WINAPI glTexCoord2s(GLshort s, GLshort t) +{ + static const auto trampoline = reshade::hooks::call(glTexCoord2s); + trampoline(s, t); +} +HOOK_EXPORT void WINAPI glTexCoord2sv(const GLshort *v) +{ + static const auto trampoline = reshade::hooks::call(glTexCoord2sv); + trampoline(v); +} +HOOK_EXPORT void WINAPI glTexCoord3d(GLdouble s, GLdouble t, GLdouble r) +{ + static const auto trampoline = reshade::hooks::call(glTexCoord3d); + trampoline(s, t, r); +} +HOOK_EXPORT void WINAPI glTexCoord3dv(const GLdouble *v) +{ + static const auto trampoline = reshade::hooks::call(glTexCoord3dv); + trampoline(v); +} +HOOK_EXPORT void WINAPI glTexCoord3f(GLfloat s, GLfloat t, GLfloat r) +{ + static const auto trampoline = reshade::hooks::call(glTexCoord3f); + trampoline(s, t, r); +} +HOOK_EXPORT void WINAPI glTexCoord3fv(const GLfloat *v) +{ + static const auto trampoline = reshade::hooks::call(glTexCoord3fv); + trampoline(v); +} +HOOK_EXPORT void WINAPI glTexCoord3i(GLint s, GLint t, GLint r) +{ + static const auto trampoline = reshade::hooks::call(glTexCoord3i); + trampoline(s, t, r); +} +HOOK_EXPORT void WINAPI glTexCoord3iv(const GLint *v) +{ + static const auto trampoline = reshade::hooks::call(glTexCoord3iv); + trampoline(v); +} +HOOK_EXPORT void WINAPI glTexCoord3s(GLshort s, GLshort t, GLshort r) +{ + static const auto trampoline = reshade::hooks::call(glTexCoord3s); + trampoline(s, t, r); +} +HOOK_EXPORT void WINAPI glTexCoord3sv(const GLshort *v) +{ + static const auto trampoline = reshade::hooks::call(glTexCoord3sv); + trampoline(v); +} +HOOK_EXPORT void WINAPI glTexCoord4d(GLdouble s, GLdouble t, GLdouble r, GLdouble q) +{ + static const auto trampoline = reshade::hooks::call(glTexCoord4d); + trampoline(s, t, r, q); +} +HOOK_EXPORT void WINAPI glTexCoord4dv(const GLdouble *v) +{ + static const auto trampoline = reshade::hooks::call(glTexCoord4dv); + trampoline(v); +} +HOOK_EXPORT void WINAPI glTexCoord4f(GLfloat s, GLfloat t, GLfloat r, GLfloat q) +{ + static const auto trampoline = reshade::hooks::call(glTexCoord4f); + trampoline(s, t, r, q); +} +HOOK_EXPORT void WINAPI glTexCoord4fv(const GLfloat *v) +{ + static const auto trampoline = reshade::hooks::call(glTexCoord4fv); + trampoline(v); +} +HOOK_EXPORT void WINAPI glTexCoord4i(GLint s, GLint t, GLint r, GLint q) +{ + static const auto trampoline = reshade::hooks::call(glTexCoord4i); + trampoline(s, t, r, q); +} +HOOK_EXPORT void WINAPI glTexCoord4iv(const GLint *v) +{ + static const auto trampoline = reshade::hooks::call(glTexCoord4iv); + trampoline(v); +} +HOOK_EXPORT void WINAPI glTexCoord4s(GLshort s, GLshort t, GLshort r, GLshort q) +{ + static const auto trampoline = reshade::hooks::call(glTexCoord4s); + trampoline(s, t, r, q); +} +HOOK_EXPORT void WINAPI glTexCoord4sv(const GLshort *v) +{ + static const auto trampoline = reshade::hooks::call(glTexCoord4sv); + trampoline(v); +} + +HOOK_EXPORT void WINAPI glTexCoordPointer(GLint size, GLenum type, GLsizei stride, const GLvoid *pointer) +{ + static const auto trampoline = reshade::hooks::call(glTexCoordPointer); + trampoline(size, type, stride, pointer); +} + +HOOK_EXPORT void WINAPI glTexEnvf(GLenum target, GLenum pname, GLfloat param) +{ + static const auto trampoline = reshade::hooks::call(glTexEnvf); + trampoline(target, pname, param); +} +HOOK_EXPORT void WINAPI glTexEnvfv(GLenum target, GLenum pname, const GLfloat *params) +{ + static const auto trampoline = reshade::hooks::call(glTexEnvfv); + trampoline(target, pname, params); +} +HOOK_EXPORT void WINAPI glTexEnvi(GLenum target, GLenum pname, GLint param) +{ + static const auto trampoline = reshade::hooks::call(glTexEnvi); + trampoline(target, pname, param); +} +HOOK_EXPORT void WINAPI glTexEnviv(GLenum target, GLenum pname, const GLint *params) +{ + static const auto trampoline = reshade::hooks::call(glTexEnviv); + trampoline(target, pname, params); +} + +HOOK_EXPORT void WINAPI glTexGend(GLenum coord, GLenum pname, GLdouble param) +{ + static const auto trampoline = reshade::hooks::call(glTexGend); + trampoline(coord, pname, param); +} +HOOK_EXPORT void WINAPI glTexGendv(GLenum coord, GLenum pname, const GLdouble *params) +{ + static const auto trampoline = reshade::hooks::call(glTexGendv); + trampoline(coord, pname, params); +} +HOOK_EXPORT void WINAPI glTexGenf(GLenum coord, GLenum pname, GLfloat param) +{ + static const auto trampoline = reshade::hooks::call(glTexGenf); + trampoline(coord, pname, param); +} +HOOK_EXPORT void WINAPI glTexGenfv(GLenum coord, GLenum pname, const GLfloat *params) +{ + static const auto trampoline = reshade::hooks::call(glTexGenfv); + trampoline(coord, pname, params); +} +HOOK_EXPORT void WINAPI glTexGeni(GLenum coord, GLenum pname, GLint param) +{ + static const auto trampoline = reshade::hooks::call(glTexGeni); + trampoline(coord, pname, param); +} +HOOK_EXPORT void WINAPI glTexGeniv(GLenum coord, GLenum pname, const GLint *params) +{ + static const auto trampoline = reshade::hooks::call(glTexGeniv); + trampoline(coord, pname, params); +} + +HOOK_EXPORT void WINAPI glTexImage1D(GLenum target, GLint level, GLint internalformat, GLsizei width, GLint border, GLenum format, GLenum type, const GLvoid *pixels) +{ + static const auto trampoline = reshade::hooks::call(glTexImage1D); + + switch (internalformat) + { + case GL_RED: + internalformat = GL_R8; + break; + case GL_RG: + internalformat = GL_RG8; + break; + case GL_RGB: + internalformat = GL_RGB8; + break; + case GL_RGBA: + internalformat = GL_RGBA8; + break; + case GL_DEPTH_COMPONENT: + internalformat = GL_DEPTH_COMPONENT16; + break; + case GL_DEPTH_STENCIL: + internalformat = GL_DEPTH24_STENCIL8; + break; + } + trampoline(target, level, internalformat, width, border, format, type, pixels); +} +HOOK_EXPORT void WINAPI glTexImage2D(GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const GLvoid *pixels) +{ + static const auto trampoline = reshade::hooks::call(glTexImage2D); + + switch (internalformat) + { + case GL_RED: + internalformat = GL_R8; + break; + case GL_RG: + internalformat = GL_RG8; + break; + case GL_RGB: + internalformat = GL_RGB8; + break; + case GL_RGBA: + internalformat = GL_RGBA8; + break; + case GL_DEPTH_COMPONENT: + internalformat = GL_DEPTH_COMPONENT16; + break; + case GL_DEPTH_STENCIL: + internalformat = GL_DEPTH24_STENCIL8; + break; + } + trampoline(target, level, internalformat, width, height, border, format, type, pixels); +} +extern "C" void WINAPI glTexImage3D(GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLenum format, GLenum type, const GLvoid *pixels) +{ + static const auto trampoline = reshade::hooks::call(glTexImage3D); + + switch (internalformat) + { + case GL_RED: + internalformat = GL_R8; + break; + case GL_RG: + internalformat = GL_RG8; + break; + case GL_RGB: + internalformat = GL_RGB8; + break; + case GL_RGBA: + internalformat = GL_RGBA8; + break; + case GL_DEPTH_COMPONENT: + internalformat = GL_DEPTH_COMPONENT16; + break; + case GL_DEPTH_STENCIL: + internalformat = GL_DEPTH24_STENCIL8; + break; + } + trampoline(target, level, internalformat, width, height, depth, border, format, type, pixels); +} + +HOOK_EXPORT void WINAPI glTexParameterf(GLenum target, GLenum pname, GLfloat param) +{ + static const auto trampoline = reshade::hooks::call(glTexParameterf); + trampoline(target, pname, param); +} +HOOK_EXPORT void WINAPI glTexParameterfv(GLenum target, GLenum pname, const GLfloat *params) +{ + static const auto trampoline = reshade::hooks::call(glTexParameterfv); + trampoline(target, pname, params); +} +HOOK_EXPORT void WINAPI glTexParameteri(GLenum target, GLenum pname, GLint param) +{ + static const auto trampoline = reshade::hooks::call(glTexParameteri); + trampoline(target, pname, param); +} +HOOK_EXPORT void WINAPI glTexParameteriv(GLenum target, GLenum pname, const GLint *params) +{ + static const auto trampoline = reshade::hooks::call(glTexParameteriv); + trampoline(target, pname, params); +} + +HOOK_EXPORT void WINAPI glTexSubImage1D(GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLenum type, const GLvoid *pixels) +{ + static const auto trampoline = reshade::hooks::call(glTexSubImage1D); + trampoline(target, level, xoffset, width, format, type, pixels); +} +HOOK_EXPORT void WINAPI glTexSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *pixels) +{ + static const auto trampoline = reshade::hooks::call(glTexSubImage2D); + trampoline(target, level, xoffset, yoffset, width, height, format, type, pixels); +} + +HOOK_EXPORT void WINAPI glTranslated(GLdouble x, GLdouble y, GLdouble z) +{ + static const auto trampoline = reshade::hooks::call(glTranslated); + trampoline(x, y, z); +} +HOOK_EXPORT void WINAPI glTranslatef(GLfloat x, GLfloat y, GLfloat z) +{ + static const auto trampoline = reshade::hooks::call(glTranslatef); + trampoline(x, y, z); +} + +HOOK_EXPORT void WINAPI glVertex2d(GLdouble x, GLdouble y) +{ + if (g_current_runtime) g_current_runtime->_current_vertex_count += 2; + + static const auto trampoline = reshade::hooks::call(glVertex2d); + trampoline(x, y); +} +HOOK_EXPORT void WINAPI glVertex2dv(const GLdouble *v) +{ + if (g_current_runtime) g_current_runtime->_current_vertex_count += 2; + + static const auto trampoline = reshade::hooks::call(glVertex2dv); + trampoline(v); +} +HOOK_EXPORT void WINAPI glVertex2f(GLfloat x, GLfloat y) +{ + if (g_current_runtime) g_current_runtime->_current_vertex_count += 2; + + static const auto trampoline = reshade::hooks::call(glVertex2f); + trampoline(x, y); +} +HOOK_EXPORT void WINAPI glVertex2fv(const GLfloat *v) +{ + if (g_current_runtime) g_current_runtime->_current_vertex_count += 2; + + static const auto trampoline = reshade::hooks::call(glVertex2fv); + trampoline(v); +} +HOOK_EXPORT void WINAPI glVertex2i(GLint x, GLint y) +{ + if (g_current_runtime) g_current_runtime->_current_vertex_count += 2; + + static const auto trampoline = reshade::hooks::call(glVertex2i); + trampoline(x, y); +} +HOOK_EXPORT void WINAPI glVertex2iv(const GLint *v) +{ + if (g_current_runtime) g_current_runtime->_current_vertex_count += 2; + + static const auto trampoline = reshade::hooks::call(glVertex2iv); + trampoline(v); +} +HOOK_EXPORT void WINAPI glVertex2s(GLshort x, GLshort y) +{ + if (g_current_runtime) g_current_runtime->_current_vertex_count += 2; + + static const auto trampoline = reshade::hooks::call(glVertex2s); + trampoline(x, y); +} +HOOK_EXPORT void WINAPI glVertex2sv(const GLshort *v) +{ + if (g_current_runtime) g_current_runtime->_current_vertex_count += 2; + + static const auto trampoline = reshade::hooks::call(glVertex2sv); + trampoline(v); +} +HOOK_EXPORT void WINAPI glVertex3d(GLdouble x, GLdouble y, GLdouble z) +{ + if (g_current_runtime) g_current_runtime->_current_vertex_count += 3; + + static const auto trampoline = reshade::hooks::call(glVertex3d); + trampoline(x, y, z); +} +HOOK_EXPORT void WINAPI glVertex3dv(const GLdouble *v) +{ + if (g_current_runtime) g_current_runtime->_current_vertex_count += 3; + + static const auto trampoline = reshade::hooks::call(glVertex3dv); + trampoline(v); +} +HOOK_EXPORT void WINAPI glVertex3f(GLfloat x, GLfloat y, GLfloat z) +{ + if (g_current_runtime) g_current_runtime->_current_vertex_count += 3; + + static const auto trampoline = reshade::hooks::call(glVertex3f); + trampoline(x, y, z); +} +HOOK_EXPORT void WINAPI glVertex3fv(const GLfloat *v) +{ + if (g_current_runtime) g_current_runtime->_current_vertex_count += 3; + + static const auto trampoline = reshade::hooks::call(glVertex3fv); + trampoline(v); +} +HOOK_EXPORT void WINAPI glVertex3i(GLint x, GLint y, GLint z) +{ + if (g_current_runtime) g_current_runtime->_current_vertex_count += 3; + + static const auto trampoline = reshade::hooks::call(glVertex3i); + trampoline(x, y, z); +} +HOOK_EXPORT void WINAPI glVertex3iv(const GLint *v) +{ + if (g_current_runtime) g_current_runtime->_current_vertex_count += 3; + + static const auto trampoline = reshade::hooks::call(glVertex3iv); + trampoline(v); +} +HOOK_EXPORT void WINAPI glVertex3s(GLshort x, GLshort y, GLshort z) +{ + if (g_current_runtime) g_current_runtime->_current_vertex_count += 3; + + static const auto trampoline = reshade::hooks::call(glVertex3s); + trampoline(x, y, z); +} +HOOK_EXPORT void WINAPI glVertex3sv(const GLshort *v) +{ + if (g_current_runtime) g_current_runtime->_current_vertex_count += 3; + + static const auto trampoline = reshade::hooks::call(glVertex3sv); + trampoline(v); +} +HOOK_EXPORT void WINAPI glVertex4d(GLdouble x, GLdouble y, GLdouble z, GLdouble w) +{ + if (g_current_runtime) g_current_runtime->_current_vertex_count += 4; + + static const auto trampoline = reshade::hooks::call(glVertex4d); + trampoline(x, y, z, w); +} +HOOK_EXPORT void WINAPI glVertex4dv(const GLdouble *v) +{ + if (g_current_runtime) g_current_runtime->_current_vertex_count += 4; + + static const auto trampoline = reshade::hooks::call(glVertex4dv); + trampoline(v); +} +HOOK_EXPORT void WINAPI glVertex4f(GLfloat x, GLfloat y, GLfloat z, GLfloat w) +{ + if (g_current_runtime) g_current_runtime->_current_vertex_count += 4; + + static const auto trampoline = reshade::hooks::call(glVertex4f); + trampoline(x, y, z, w); +} +HOOK_EXPORT void WINAPI glVertex4fv(const GLfloat *v) +{ + if (g_current_runtime) g_current_runtime->_current_vertex_count += 4; + + static const auto trampoline = reshade::hooks::call(glVertex4fv); + trampoline(v); +} +HOOK_EXPORT void WINAPI glVertex4i(GLint x, GLint y, GLint z, GLint w) +{ + if (g_current_runtime) g_current_runtime->_current_vertex_count += 4; + + static const auto trampoline = reshade::hooks::call(glVertex4i); + trampoline(x, y, z, w); +} +HOOK_EXPORT void WINAPI glVertex4iv(const GLint *v) +{ + if (g_current_runtime) g_current_runtime->_current_vertex_count += 4; + + static const auto trampoline = reshade::hooks::call(glVertex4iv); + trampoline(v); +} +HOOK_EXPORT void WINAPI glVertex4s(GLshort x, GLshort y, GLshort z, GLshort w) +{ + if (g_current_runtime) g_current_runtime->_current_vertex_count += 4; + + static const auto trampoline = reshade::hooks::call(glVertex4s); + trampoline(x, y, z, w); +} +HOOK_EXPORT void WINAPI glVertex4sv(const GLshort *v) +{ + if (g_current_runtime) g_current_runtime->_current_vertex_count += 4; + + static const auto trampoline = reshade::hooks::call(glVertex4sv); + trampoline(v); +} + +HOOK_EXPORT void WINAPI glVertexPointer(GLint size, GLenum type, GLsizei stride, const GLvoid *pointer) +{ + static const auto trampoline = reshade::hooks::call(glVertexPointer); + trampoline(size, type, stride, pointer); +} + +HOOK_EXPORT void WINAPI glViewport(GLint x, GLint y, GLsizei width, GLsizei height) +{ + static const auto trampoline = reshade::hooks::call(glViewport); + trampoline(x, y, width, height); +} diff --git a/msvc/source/opengl/opengl_hooks.hpp b/msvc/source/opengl/opengl_hooks.hpp new file mode 100644 index 0000000..6d7ef67 --- /dev/null +++ b/msvc/source/opengl/opengl_hooks.hpp @@ -0,0 +1,195 @@ +/** + * Copyright (C) 2014 Patrick Mours. All rights reserved. + * License: https://github.com/crosire/reshade#license + */ + +#include + +#undef glBindTexture +extern "C" void WINAPI glBindTexture(GLenum target, GLuint texture); +#undef glBlendFunc +extern "C" void WINAPI glBlendFunc(GLenum sfactor, GLenum dfactor); +#undef glClear +extern "C" void WINAPI glClear(GLbitfield mask); +#undef glClearColor +extern "C" void WINAPI glClearColor(GLclampf red, GLclampf green, GLclampf blue, GLclampf alpha); +#undef glClearDepth +extern "C" void WINAPI glClearDepth(GLclampd depth); +#undef glClearStencil +extern "C" void WINAPI glClearStencil(GLint s); +#undef glColorMask +extern "C" void WINAPI glColorMask(GLboolean red, GLboolean green, GLboolean blue, GLboolean alpha); +#undef glCopyTexImage1D +extern "C" void WINAPI glCopyTexImage1D(GLenum target, GLint level, GLenum internalFormat, GLint x, GLint y, GLsizei width, GLint border); +#undef glCopyTexImage2D +extern "C" void WINAPI glCopyTexImage2D(GLenum target, GLint level, GLenum internalFormat, GLint x, GLint y, GLsizei width, GLsizei height, GLint border); +#undef glCopyTexSubImage1D +extern "C" void WINAPI glCopyTexSubImage1D(GLenum target, GLint level, GLint xoffset, GLint x, GLint y, GLsizei width); +#undef glCopyTexSubImage2D +extern "C" void WINAPI glCopyTexSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height); +#undef glCullFace +extern "C" void WINAPI glCullFace(GLenum mode); +#undef glDeleteTextures +extern "C" void WINAPI glDeleteTextures(GLsizei n, const GLuint *textures); +#undef glDepthFunc +extern "C" void WINAPI glDepthFunc(GLenum func); +#undef glDepthMask +extern "C" void WINAPI glDepthMask(GLboolean flag); +#undef glDepthRange +extern "C" void WINAPI glDepthRange(GLclampd zNear, GLclampd zFar); +#undef glDisable +extern "C" void WINAPI glDisable(GLenum cap); +#undef glDrawArrays +extern "C" void WINAPI glDrawArrays(GLenum mode, GLint first, GLsizei count); +#undef glDrawArraysIndirect +extern "C" void WINAPI glDrawArraysIndirect(GLenum mode, const GLvoid *indirect); +#undef glDrawArraysInstanced +extern "C" void WINAPI glDrawArraysInstanced(GLenum mode, GLint first, GLsizei count, GLsizei primcount); +#undef glDrawArraysInstancedBaseInstance +extern "C" void WINAPI glDrawArraysInstancedBaseInstance(GLenum mode, GLint first, GLsizei count, GLsizei primcount, GLuint baseinstance); +#undef glDrawArraysInstancedARB +extern "C" void WINAPI glDrawArraysInstancedARB(GLenum mode, GLint first, GLsizei count, GLsizei primcount); +#undef glDrawArraysInstancedEXT +extern "C" void WINAPI glDrawArraysInstancedEXT(GLenum mode, GLint first, GLsizei count, GLsizei primcount); +#undef glDrawBuffer +extern "C" void WINAPI glDrawBuffer(GLenum mode); +#undef glDrawElements +extern "C" void WINAPI glDrawElements(GLenum mode, GLsizei count, GLenum type, const GLvoid *indices); +#undef glDrawElementsBaseVertex +extern "C" void WINAPI glDrawElementsBaseVertex(GLenum mode, GLsizei count, GLenum type, const GLvoid *indices, GLint basevertex); +#undef glDrawElementsIndirect +extern "C" void WINAPI glDrawElementsIndirect(GLenum mode, GLenum type, const GLvoid *indirect); +#undef glDrawElementsInstanced +extern "C" void WINAPI glDrawElementsInstanced(GLenum mode, GLsizei count, GLenum type, const GLvoid *indices, GLsizei primcount); +#undef glDrawElementsInstancedBaseVertex +extern "C" void WINAPI glDrawElementsInstancedBaseVertex(GLenum mode, GLsizei count, GLenum type, const GLvoid *indices, GLsizei primcount, GLint basevertex); +#undef glDrawElementsInstancedBaseInstance +extern "C" void WINAPI glDrawElementsInstancedBaseInstance(GLenum mode, GLsizei count, GLenum type, const GLvoid *indices, GLsizei primcount, GLuint baseinstance); +#undef glDrawElementsInstancedBaseVertexBaseInstance +extern "C" void WINAPI glDrawElementsInstancedBaseVertexBaseInstance(GLenum mode, GLsizei count, GLenum type, const GLvoid *indices, GLsizei primcount, GLint basevertex, GLuint baseinstance); +#undef glDrawElementsInstancedARB +extern "C" void WINAPI glDrawElementsInstancedARB(GLenum mode, GLsizei count, GLenum type, const GLvoid *indices, GLsizei primcount); +#undef glDrawElementsInstancedEXT +extern "C" void WINAPI glDrawElementsInstancedEXT(GLenum mode, GLsizei count, GLenum type, const GLvoid *indices, GLsizei primcount); +#undef glDrawRangeElements +extern "C" void WINAPI glDrawRangeElements(GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const GLvoid *indices); +#undef glDrawRangeElementsBaseVertex +extern "C" void WINAPI glDrawRangeElementsBaseVertex(GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const GLvoid *indices, GLint basevertex); +#undef glEnable +extern "C" void WINAPI glEnable(GLenum cap); +#undef glFinish +extern "C" void WINAPI glFinish(); +#undef glFlush +extern "C" void WINAPI glFlush(); +#undef glFramebufferRenderbuffer +extern "C" void WINAPI glFramebufferRenderbuffer(GLenum target, GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer); +extern "C" void WINAPI glFramebufferRenderbufferEXT(GLenum target, GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer); +#undef glFramebufferTexture +extern "C" void WINAPI glFramebufferTexture(GLenum target, GLenum attachment, GLuint texture, GLint level); +#undef glFramebufferTexture1D +extern "C" void WINAPI glFramebufferTexture1D(GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level); +extern "C" void WINAPI glFramebufferTexture1DEXT(GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level); +#undef glFramebufferTexture2D +extern "C" void WINAPI glFramebufferTexture2D(GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level); +extern "C" void WINAPI glFramebufferTexture2DEXT(GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level); +#undef glFramebufferTexture3D +extern "C" void WINAPI glFramebufferTexture3D(GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level, GLint zoffset); +extern "C" void WINAPI glFramebufferTexture3DEXT(GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level, GLint zoffset); +#undef glFramebufferTextureARB +extern "C" void WINAPI glFramebufferTextureARB(GLenum target, GLenum attachment, GLuint texture, GLint level); +extern "C" void WINAPI glFramebufferTextureEXT(GLenum target, GLenum attachment, GLuint texture, GLint level); +#undef glFramebufferTextureLayer +extern "C" void WINAPI glFramebufferTextureLayer(GLenum target, GLenum attachment, GLuint texture, GLint level, GLint layer); +#undef glFramebufferTextureLayerARB +extern "C" void WINAPI glFramebufferTextureLayerARB(GLenum target, GLenum attachment, GLuint texture, GLint level, GLint layer); +extern "C" void WINAPI glFramebufferTextureLayerEXT(GLenum target, GLenum attachment, GLuint texture, GLint level, GLint layer); +#undef glFrontFace +extern "C" void WINAPI glFrontFace(GLenum mode); +#undef glGenTextures +extern "C" void WINAPI glGenTextures(GLsizei n, GLuint *textures); +#undef glGetBooleanv +extern "C" void WINAPI glGetBooleanv(GLenum pname, GLboolean *params); +#undef glGetDoublev +extern "C" void WINAPI glGetDoublev(GLenum pname, GLdouble *params); +#undef glGetFloatv +extern "C" void WINAPI glGetFloatv(GLenum pname, GLfloat *params); +#undef glGetIntegerv +extern "C" void WINAPI glGetIntegerv(GLenum pname, GLint *params); +#undef glGetError +extern "C" GLenum WINAPI glGetError(); +#undef glGetPointerv +extern "C" void WINAPI glGetPointerv(GLenum pname, GLvoid **params); +#undef glGetString +extern "C" const GLubyte *WINAPI glGetString(GLenum name); +#undef glGetTexImage +extern "C" void WINAPI glGetTexImage(GLenum target, GLint level, GLenum format, GLenum type, GLvoid *pixels); +#undef glGetTexLevelParameterfv +extern "C" void WINAPI glGetTexLevelParameterfv(GLenum target, GLint level, GLenum pname, GLfloat *params); +#undef glGetTexLevelParameteriv +extern "C" void WINAPI glGetTexLevelParameteriv(GLenum target, GLint level, GLenum pname, GLint *params); +#undef glGetTexParameterfv +extern "C" void WINAPI glGetTexParameterfv(GLenum target, GLenum pname, GLfloat *params); +#undef glGetTexParameteriv +extern "C" void WINAPI glGetTexParameteriv(GLenum target, GLenum pname, GLint *params); +#undef glHint +extern "C" void WINAPI glHint(GLenum target, GLenum mode); +#undef glIsEnabled +extern "C" GLboolean WINAPI glIsEnabled(GLenum cap); +#undef glIsTexture +extern "C" GLboolean WINAPI glIsTexture(GLuint texture); +#undef glLineWidth +extern "C" void WINAPI glLineWidth(GLfloat width); +#undef glLogicOp +extern "C" void WINAPI glLogicOp(GLenum opcode); +#undef glMultiDrawArrays +extern "C" void WINAPI glMultiDrawArrays(GLenum mode, const GLint *first, const GLsizei *count, GLsizei drawcount); +#undef glMultiDrawArraysIndirect +extern "C" void WINAPI glMultiDrawArraysIndirect(GLenum mode, const void *indirect, GLsizei drawcount, GLsizei stride); +#undef glMultiDrawElements +extern "C" void WINAPI glMultiDrawElements(GLenum mode, const GLsizei *count, GLenum type, const GLvoid *const *indices, GLsizei drawcount); +#undef glMultiDrawElementsBaseVertex +extern "C" void WINAPI glMultiDrawElementsBaseVertex(GLenum mode, const GLsizei *count, GLenum type, const GLvoid *const *indices, GLsizei drawcount, const GLint *basevertex); +#undef glMultiDrawElementsIndirect +extern "C" void WINAPI glMultiDrawElementsIndirect(GLenum mode, GLenum type, const void *indirect, GLsizei drawcount, GLsizei stride); +#undef glPixelStoref +extern "C" void WINAPI glPixelStoref(GLenum pname, GLfloat param); +#undef glPixelStorei +extern "C" void WINAPI glPixelStorei(GLenum pname, GLint param); +#undef glPointSize +extern "C" void WINAPI glPointSize(GLfloat size); +#undef glPolygonMode +extern "C" void WINAPI glPolygonMode(GLenum face, GLenum mode); +#undef glPolygonOffset +extern "C" void WINAPI glPolygonOffset(GLfloat factor, GLfloat units); +#undef glReadBuffer +extern "C" void WINAPI glReadBuffer(GLenum mode); +#undef glReadPixels +extern "C" void WINAPI glReadPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, GLvoid *pixels); +#undef glScissor +extern "C" void WINAPI glScissor(GLint x, GLint y, GLsizei width, GLsizei height); +#undef glStencilFunc +extern "C" void WINAPI glStencilFunc(GLenum func, GLint ref, GLuint mask); +#undef glStencilMask +extern "C" void WINAPI glStencilMask(GLuint mask); +#undef glStencilOp +extern "C" void WINAPI glStencilOp(GLenum fail, GLenum zfail, GLenum zpass); +#undef glTexImage1D +extern "C" void WINAPI glTexImage1D(GLenum target, GLint level, GLint internalformat, GLsizei width, GLint border, GLenum format, GLenum type, const GLvoid *pixels); +#undef glTexImage2D +extern "C" void WINAPI glTexImage2D(GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const GLvoid *pixels); +#undef glTexImage3D +extern "C" void WINAPI glTexImage3D(GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLenum format, GLenum type, const GLvoid *pixels); +#undef glTexParameterf +extern "C" void WINAPI glTexParameterf(GLenum target, GLenum pname, GLfloat param); +#undef glTexParameterfv +extern "C" void WINAPI glTexParameterfv(GLenum target, GLenum pname, const GLfloat *params); +#undef glTexParameteri +extern "C" void WINAPI glTexParameteri(GLenum target, GLenum pname, GLint param); +#undef glTexParameteriv +extern "C" void WINAPI glTexParameteriv(GLenum target, GLenum pname, const GLint *params); +#undef glTexSubImage1D +extern "C" void WINAPI glTexSubImage1D(GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLenum type, const GLvoid *pixels); +#undef glTexSubImage2D +extern "C" void WINAPI glTexSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *pixels); +#undef glViewport +extern "C" void WINAPI glViewport(GLint x, GLint y, GLsizei width, GLsizei height); diff --git a/msvc/source/opengl/opengl_hooks_wgl.cpp b/msvc/source/opengl/opengl_hooks_wgl.cpp new file mode 100644 index 0000000..1c0ed99 --- /dev/null +++ b/msvc/source/opengl/opengl_hooks_wgl.cpp @@ -0,0 +1,1088 @@ +/** + * Copyright (C) 2014 Patrick Mours. All rights reserved. + * License: https://github.com/crosire/reshade#license + */ + +#include "log.hpp" +#include "hook_manager.hpp" +#include "runtime_opengl.hpp" +#include "opengl_hooks.hpp" +#include +#include +#include +#include +#include + +DECLARE_HANDLE(HPBUFFERARB); + +static std::mutex s_mutex; +static std::unordered_set s_pbuffer_device_contexts; +static std::unordered_map s_shared_contexts; +static std::unordered_map s_opengl_runtimes; +thread_local reshade::opengl::runtime_opengl *g_current_runtime = nullptr; + +HOOK_EXPORT int WINAPI wglChoosePixelFormat(HDC hdc, const PIXELFORMATDESCRIPTOR *ppfd) +{ + LOG(INFO) << "Redirecting wglChoosePixelFormat" << '(' << hdc << ", " << ppfd << ')' << " ..."; + + assert(ppfd != nullptr); + + LOG(INFO) << "> Dumping pixel format descriptor:"; + LOG(INFO) << " +-----------------------------------------+-----------------------------------------+"; + LOG(INFO) << " | Name | Value |"; + LOG(INFO) << " +-----------------------------------------+-----------------------------------------+"; + LOG(INFO) << " | Flags | " << std::setw(39) << std::hex << ppfd->dwFlags << std::dec << " |"; + LOG(INFO) << " | ColorBits | " << std::setw(39) << static_cast(ppfd->cColorBits) << " |"; + LOG(INFO) << " | DepthBits | " << std::setw(39) << static_cast(ppfd->cDepthBits) << " |"; + LOG(INFO) << " | StencilBits | " << std::setw(39) << static_cast(ppfd->cStencilBits) << " |"; + LOG(INFO) << " +-----------------------------------------+-----------------------------------------+"; + + if (ppfd->iLayerType != PFD_MAIN_PLANE || ppfd->bReserved != 0) + { + LOG(ERROR) << "> Layered OpenGL contexts of type " << static_cast(ppfd->iLayerType) << " are not supported."; + + SetLastError(ERROR_INVALID_PARAMETER); + + return 0; + } + else if ((ppfd->dwFlags & PFD_DOUBLEBUFFER) == 0) + { + LOG(WARN) << "> Single buffered OpenGL contexts are not supported."; + } + + // Note: Windows calls into 'wglDescribePixelFormat' repeatedly from this, so make sure it reports correct results + const int format = reshade::hooks::call(wglChoosePixelFormat)(hdc, ppfd); + + if (format != 0) + LOG(INFO) << "> Returned format: " << format; + else + LOG(WARN) << "> wglChoosePixelFormat failed with error code " << (GetLastError() & 0xFFFF) << '!'; + + return format; +} + BOOL WINAPI wglChoosePixelFormatARB(HDC hdc, const int *piAttribIList, const FLOAT *pfAttribFList, UINT nMaxFormats, int *piFormats, UINT *nNumFormats) +{ + LOG(INFO) << "Redirecting wglChoosePixelFormatARB" << '(' << hdc << ", " << piAttribIList << ", " << pfAttribFList << ", " << nMaxFormats << ", " << piFormats << ", " << nNumFormats << ')' << " ..."; + + struct attribute + { + enum + { + WGL_NUMBER_PIXEL_FORMATS_ARB = 0x2000, + WGL_DRAW_TO_WINDOW_ARB = 0x2001, + WGL_DRAW_TO_BITMAP_ARB = 0x2002, + WGL_ACCELERATION_ARB = 0x2003, + WGL_NEED_PALETTE_ARB = 0x2004, + WGL_NEED_SYSTEM_PALETTE_ARB = 0x2005, + WGL_SWAP_LAYER_BUFFERS_ARB = 0x2006, + WGL_SWAP_METHOD_ARB = 0x2007, + WGL_NUMBER_OVERLAYS_ARB = 0x2008, + WGL_NUMBER_UNDERLAYS_ARB = 0x2009, + WGL_TRANSPARENT_ARB = 0x200A, + WGL_TRANSPARENT_RED_VALUE_ARB = 0x2037, + WGL_TRANSPARENT_GREEN_VALUE_ARB = 0x2038, + WGL_TRANSPARENT_BLUE_VALUE_ARB = 0x2039, + WGL_TRANSPARENT_ALPHA_VALUE_ARB = 0x203A, + WGL_TRANSPARENT_INDEX_VALUE_ARB = 0x203B, + WGL_SHARE_DEPTH_ARB = 0x200C, + WGL_SHARE_STENCIL_ARB = 0x200D, + WGL_SHARE_ACCUM_ARB = 0x200E, + WGL_SUPPORT_GDI_ARB = 0x200F, + WGL_SUPPORT_OPENGL_ARB = 0x2010, + WGL_DOUBLE_BUFFER_ARB = 0x2011, + WGL_STEREO_ARB = 0x2012, + WGL_PIXEL_TYPE_ARB = 0x2013, + WGL_COLOR_BITS_ARB = 0x2014, + WGL_RED_BITS_ARB = 0x2015, + WGL_RED_SHIFT_ARB = 0x2016, + WGL_GREEN_BITS_ARB = 0x2017, + WGL_GREEN_SHIFT_ARB = 0x2018, + WGL_BLUE_BITS_ARB = 0x2019, + WGL_BLUE_SHIFT_ARB = 0x201A, + WGL_ALPHA_BITS_ARB = 0x201B, + WGL_ALPHA_SHIFT_ARB = 0x201C, + WGL_ACCUM_BITS_ARB = 0x201D, + WGL_ACCUM_RED_BITS_ARB = 0x201E, + WGL_ACCUM_GREEN_BITS_ARB = 0x201F, + WGL_ACCUM_BLUE_BITS_ARB = 0x2020, + WGL_ACCUM_ALPHA_BITS_ARB = 0x2021, + WGL_DEPTH_BITS_ARB = 0x2022, + WGL_STENCIL_BITS_ARB = 0x2023, + WGL_AUX_BUFFERS_ARB = 0x2024, + WGL_SAMPLE_BUFFERS_ARB = 0x2041, + WGL_SAMPLES_ARB = 0x2042, + WGL_DRAW_TO_PBUFFER_ARB = 0x202D, + WGL_BIND_TO_TEXTURE_RGB_ARB = 0x2070, + WGL_BIND_TO_TEXTURE_RGBA_ARB = 0x2071, + + WGL_NO_ACCELERATION_ARB = 0x2025, + WGL_GENERIC_ACCELERATION_ARB = 0x2026, + WGL_FULL_ACCELERATION_ARB = 0x2027, + WGL_SWAP_EXCHANGE_ARB = 0x2028, + WGL_SWAP_COPY_ARB = 0x2029, + WGL_SWAP_UNDEFINED_ARB = 0x202A, + WGL_TYPE_RGBA_ARB = 0x202B, + WGL_TYPE_COLORINDEX_ARB = 0x202C, + }; + }; + + bool layerplanes = false, doublebuffered = false; + + LOG(INFO) << "> Dumping attributes:"; + LOG(INFO) << " +-----------------------------------------+-----------------------------------------+"; + LOG(INFO) << " | Attribute | Value |"; + LOG(INFO) << " +-----------------------------------------+-----------------------------------------+"; + + for (const int *attrib = piAttribIList; attrib != nullptr && *attrib != 0; attrib += 2) + { + switch (attrib[0]) + { + case attribute::WGL_DRAW_TO_WINDOW_ARB: + LOG(INFO) << " | WGL_DRAW_TO_WINDOW_ARB | " << std::setw(39) << (attrib[1] != FALSE ? "TRUE" : "FALSE") << " |"; + break; + case attribute::WGL_DRAW_TO_BITMAP_ARB: + LOG(INFO) << " | WGL_DRAW_TO_BITMAP_ARB | " << std::setw(39) << (attrib[1] != FALSE ? "TRUE" : "FALSE") << " |"; + break; + case attribute::WGL_ACCELERATION_ARB: + LOG(INFO) << " | WGL_ACCELERATION_ARB | " << std::setw(39) << std::hex << attrib[1] << std::dec << " |"; + break; + case attribute::WGL_SWAP_LAYER_BUFFERS_ARB: + layerplanes = layerplanes || attrib[1] != FALSE; + LOG(INFO) << " | WGL_SWAP_LAYER_BUFFERS_ARB | " << std::setw(39) << (attrib[1] != FALSE ? "TRUE" : "FALSE") << " |"; + break; + case attribute::WGL_SWAP_METHOD_ARB: + LOG(INFO) << " | WGL_SWAP_METHOD_ARB | " << std::setw(39) << std::hex << attrib[1] << std::dec << " |"; + break; + case attribute::WGL_NUMBER_OVERLAYS_ARB: + layerplanes = layerplanes || attrib[1] != 0; + LOG(INFO) << " | WGL_NUMBER_OVERLAYS_ARB | " << std::setw(39) << attrib[1] << " |"; + break; + case attribute::WGL_NUMBER_UNDERLAYS_ARB: + layerplanes = layerplanes || attrib[1] != 0; + LOG(INFO) << " | WGL_NUMBER_UNDERLAYS_ARB | " << std::setw(39) << attrib[1] << " |"; + break; + case attribute::WGL_SUPPORT_GDI_ARB: + LOG(INFO) << " | WGL_SUPPORT_GDI_ARB | " << std::setw(39) << (attrib[1] != FALSE ? "TRUE" : "FALSE") << " |"; + break; + case attribute::WGL_SUPPORT_OPENGL_ARB: + LOG(INFO) << " | WGL_SUPPORT_OPENGL_ARB | " << std::setw(39) << (attrib[1] != FALSE ? "TRUE" : "FALSE") << " |"; + break; + case attribute::WGL_DOUBLE_BUFFER_ARB: + doublebuffered = attrib[1] != FALSE; + LOG(INFO) << " | WGL_DOUBLE_BUFFER_ARB | " << std::setw(39) << (attrib[1] != FALSE ? "TRUE" : "FALSE") << " |"; + break; + case attribute::WGL_STEREO_ARB: + LOG(INFO) << " | WGL_STEREO_ARB | " << std::setw(39) << (attrib[1] != FALSE ? "TRUE" : "FALSE") << " |"; + break; + case attribute::WGL_RED_BITS_ARB: + LOG(INFO) << " | WGL_RED_BITS_ARB | " << std::setw(39) << attrib[1] << " |"; + break; + case attribute::WGL_GREEN_BITS_ARB: + LOG(INFO) << " | WGL_GREEN_BITS_ARB | " << std::setw(39) << attrib[1] << " |"; + break; + case attribute::WGL_BLUE_BITS_ARB: + LOG(INFO) << " | WGL_BLUE_BITS_ARB | " << std::setw(39) << attrib[1] << " |"; + break; + case attribute::WGL_ALPHA_BITS_ARB: + LOG(INFO) << " | WGL_ALPHA_BITS_ARB | " << std::setw(39) << attrib[1] << " |"; + break; + case attribute::WGL_COLOR_BITS_ARB: + LOG(INFO) << " | WGL_COLOR_BITS_ARB | " << std::setw(39) << attrib[1] << " |"; + break; + case attribute::WGL_DEPTH_BITS_ARB: + LOG(INFO) << " | WGL_DEPTH_BITS_ARB | " << std::setw(39) << attrib[1] << " |"; + break; + case attribute::WGL_STENCIL_BITS_ARB: + LOG(INFO) << " | WGL_STENCIL_BITS_ARB | " << std::setw(39) << attrib[1] << " |"; + break; + case attribute::WGL_SAMPLE_BUFFERS_ARB: + LOG(INFO) << " | WGL_SAMPLE_BUFFERS_ARB | " << std::setw(39) << attrib[1] << " |"; + break; + case attribute::WGL_SAMPLES_ARB: + LOG(INFO) << " | WGL_SAMPLES_ARB | " << std::setw(39) << attrib[1] << " |"; + break; + case attribute::WGL_DRAW_TO_PBUFFER_ARB: + LOG(INFO) << " | WGL_DRAW_TO_PBUFFER_ARB | " << std::setw(39) << (attrib[1] != FALSE ? "TRUE" : "FALSE") << " |"; + break; + default: + LOG(INFO) << " | " << std::hex << std::setw(39) << attrib[0] << " | " << std::setw(39) << attrib[1] << std::dec << " |"; + break; + } + } + + for (const FLOAT *attrib = pfAttribFList; attrib != nullptr && *attrib != 0.0f; attrib += 2) + { + LOG(INFO) << " | " << std::hex << std::setw(39) << static_cast(attrib[0]) << " | " << std::setw(39) << attrib[1] << std::dec << " |"; + } + + LOG(INFO) << " +-----------------------------------------+-----------------------------------------+"; + + if (layerplanes) + { + LOG(ERROR) << "> Layered OpenGL contexts are not supported."; + + SetLastError(ERROR_INVALID_PARAMETER); + + return FALSE; + } + else if (!doublebuffered) + { + LOG(WARN) << "> Single buffered OpenGL contexts are not supported."; + } + + if (!reshade::hooks::call(wglChoosePixelFormatARB)(hdc, piAttribIList, pfAttribFList, nMaxFormats, piFormats, nNumFormats)) + { + LOG(WARN) << "> wglChoosePixelFormatARB failed with error code " << (GetLastError() & 0xFFFF) << '!'; + + return FALSE; + } + + assert(nNumFormats != nullptr); + + if (*nNumFormats < nMaxFormats) + nMaxFormats = *nNumFormats; + + std::string formats; + + for (UINT i = 0; i < nMaxFormats; ++i) + { + assert(piFormats[i] != 0); + + formats += " " + std::to_string(piFormats[i]); + } + + LOG(INFO) << "> Returned format(s):" << formats; + + return TRUE; +} +HOOK_EXPORT int WINAPI wglGetPixelFormat(HDC hdc) +{ + return reshade::hooks::call(wglGetPixelFormat)(hdc); +} + BOOL WINAPI wglGetPixelFormatAttribivARB(HDC hdc, int iPixelFormat, int iLayerPlane, UINT nAttributes, const int *piAttributes, int *piValues) +{ + if (iLayerPlane != 0) + { + LOG(WARN) << "Access to layer plane at index " << iLayerPlane << " is unsupported."; + + SetLastError(ERROR_INVALID_PARAMETER); + + return FALSE; + } + + return reshade::hooks::call(wglGetPixelFormatAttribivARB)(hdc, iPixelFormat, 0, nAttributes, piAttributes, piValues); +} + BOOL WINAPI wglGetPixelFormatAttribfvARB(HDC hdc, int iPixelFormat, int iLayerPlane, UINT nAttributes, const int *piAttributes, FLOAT *pfValues) +{ + if (iLayerPlane != 0) + { + LOG(WARN) << "Access to layer plane at index " << iLayerPlane << " is unsupported."; + + SetLastError(ERROR_INVALID_PARAMETER); + + return FALSE; + } + + return reshade::hooks::call(wglGetPixelFormatAttribfvARB)(hdc, iPixelFormat, 0, nAttributes, piAttributes, pfValues); +} +HOOK_EXPORT BOOL WINAPI wglSetPixelFormat(HDC hdc, int iPixelFormat, const PIXELFORMATDESCRIPTOR *ppfd) +{ + LOG(INFO) << "Redirecting wglSetPixelFormat" << '(' << hdc << ", " << iPixelFormat << ", " << ppfd << ')' << " ..."; + + if (!reshade::hooks::call(wglSetPixelFormat)(hdc, iPixelFormat, ppfd)) + { + LOG(WARN) << "> wglSetPixelFormat failed with error code " << (GetLastError() & 0xFFFF) << '!'; + + return FALSE; + } + + if (GetPixelFormat(hdc) == 0) + { + LOG(WARN) << "> Application mistakenly called wglSetPixelFormat directly. Passing on to SetPixelFormat:"; + + SetPixelFormat(hdc, iPixelFormat, ppfd); + } + + return TRUE; +} +HOOK_EXPORT int WINAPI wglDescribePixelFormat(HDC hdc, int iPixelFormat, UINT nBytes, LPPIXELFORMATDESCRIPTOR ppfd) +{ + return reshade::hooks::call(wglDescribePixelFormat)(hdc, iPixelFormat, nBytes, ppfd); +} + +HOOK_EXPORT BOOL WINAPI wglDescribeLayerPlane(HDC hdc, int iPixelFormat, int iLayerPlane, UINT nBytes, LPLAYERPLANEDESCRIPTOR plpd) +{ + LOG(INFO) << "Redirecting wglDescribeLayerPlane" << '(' << hdc << ", " << iPixelFormat << ", " << iLayerPlane << ", " << nBytes << ", " << plpd << ')' << " ..."; + LOG(WARN) << "Access to layer plane at index " << iLayerPlane << " is unsupported."; + + SetLastError(ERROR_NOT_SUPPORTED); + + return FALSE; +} +HOOK_EXPORT BOOL WINAPI wglRealizeLayerPalette(HDC hdc, int iLayerPlane, BOOL b) +{ + LOG(INFO) << "Redirecting wglRealizeLayerPalette" << '(' << hdc << ", " << iLayerPlane << ", " << b << ')' << " ..."; + LOG(WARN) << "Access to layer plane at index " << iLayerPlane << " is unsupported."; + + SetLastError(ERROR_NOT_SUPPORTED); + + return FALSE; +} +HOOK_EXPORT int WINAPI wglGetLayerPaletteEntries(HDC hdc, int iLayerPlane, int iStart, int cEntries, COLORREF *pcr) +{ + LOG(INFO) << "Redirecting wglGetLayerPaletteEntries" << '(' << hdc << ", " << iLayerPlane << ", " << iStart << ", " << cEntries << ", " << pcr << ')' << " ..."; + LOG(WARN) << "Access to layer plane at index " << iLayerPlane << " is unsupported."; + + SetLastError(ERROR_NOT_SUPPORTED); + + return 0; +} +HOOK_EXPORT int WINAPI wglSetLayerPaletteEntries(HDC hdc, int iLayerPlane, int iStart, int cEntries, const COLORREF *pcr) +{ + LOG(INFO) << "Redirecting wglSetLayerPaletteEntries" << '(' << hdc << ", " << iLayerPlane << ", " << iStart << ", " << cEntries << ", " << pcr << ')' << " ..."; + LOG(WARN) << "Access to layer plane at index " << iLayerPlane << " is unsupported."; + + SetLastError(ERROR_NOT_SUPPORTED); + + return 0; +} + +HOOK_EXPORT HGLRC WINAPI wglCreateContext(HDC hdc) +{ + LOG(INFO) << "Redirecting wglCreateContext" << '(' << hdc << ')' << " ..."; + LOG(INFO) << "> Passing on to wglCreateLayerContext ..."; + + return wglCreateLayerContext(hdc, 0); +} + HGLRC WINAPI wglCreateContextAttribsARB(HDC hdc, HGLRC hShareContext, const int *piAttribList) +{ + LOG(INFO) << "Redirecting wglCreateContextAttribsARB" << '(' << hdc << ", " << hShareContext << ", " << piAttribList << ')' << " ..."; + + struct attribute + { + enum + { + WGL_CONTEXT_MAJOR_VERSION_ARB = 0x2091, + WGL_CONTEXT_MINOR_VERSION_ARB = 0x2092, + WGL_CONTEXT_LAYER_PLANE_ARB = 0x2093, + WGL_CONTEXT_FLAGS_ARB = 0x2094, + WGL_CONTEXT_PROFILE_MASK_ARB = 0x9126, + + WGL_CONTEXT_DEBUG_BIT_ARB = 0x0001, + WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB = 0x0002, + WGL_CONTEXT_CORE_PROFILE_BIT_ARB = 0x00000001, + WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB = 0x00000002, + }; + + int name, value; + }; + + int i = 0, major = 1, minor = 0, flags = 0; + bool core = true, compatibility = false; + attribute attribs[8] = { }; + + for (const int *attrib = piAttribList; attrib != nullptr && *attrib != 0 && i < 5; attrib += 2, ++i) + { + attribs[i].name = attrib[0]; + attribs[i].value = attrib[1]; + + switch (attrib[0]) + { + case attribute::WGL_CONTEXT_MAJOR_VERSION_ARB: + major = attrib[1]; + break; + case attribute::WGL_CONTEXT_MINOR_VERSION_ARB: + minor = attrib[1]; + break; + case attribute::WGL_CONTEXT_FLAGS_ARB: + flags = attrib[1]; + break; + case attribute::WGL_CONTEXT_PROFILE_MASK_ARB: + core = (attrib[1] & attribute::WGL_CONTEXT_CORE_PROFILE_BIT_ARB) != 0; + compatibility = (attrib[1] & attribute::WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB) != 0; + break; + } + } + + if (major < 3 || minor < 2) + core = compatibility = false; + +#ifdef _DEBUG + flags |= attribute::WGL_CONTEXT_DEBUG_BIT_ARB; +#endif + + // This works because the specs specifically note that "If an attribute is specified more than once, then the last value specified is used." + attribs[i].name = attribute::WGL_CONTEXT_FLAGS_ARB; + attribs[i++].value = flags; + attribs[i].name = attribute::WGL_CONTEXT_PROFILE_MASK_ARB; + attribs[i++].value = compatibility ? attribute::WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB : attribute::WGL_CONTEXT_CORE_PROFILE_BIT_ARB; + + LOG(INFO) << "> Requesting " << (core ? "core " : compatibility ? "compatibility " : "") << "OpenGL context for version " << major << '.' << minor << " ..."; + + if (major < 4 || minor < 3) + { + LOG(WARN) << "> Replacing requested version with 4.3 ..."; + + for (int k = 0; k < i; ++k) + { + switch (attribs[k].name) + { + case attribute::WGL_CONTEXT_MAJOR_VERSION_ARB: + attribs[k].value = 4; + break; + case attribute::WGL_CONTEXT_MINOR_VERSION_ARB: + attribs[k].value = 3; + break; + case attribute::WGL_CONTEXT_PROFILE_MASK_ARB: + attribs[k].value = attribute::WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB; + break; + } + } + } + + const HGLRC hglrc = reshade::hooks::call(wglCreateContextAttribsARB)(hdc, hShareContext, reinterpret_cast(attribs)); + + if (hglrc == nullptr) + { + LOG(WARN) << "> wglCreateContextAttribsARB failed with error code " << (GetLastError() & 0xFFFF) << '!'; + + return nullptr; + } + + { const std::lock_guard lock(s_mutex); + s_shared_contexts.emplace(hglrc, hShareContext); + + if (hShareContext != nullptr) + { + // Find root share context + auto it = s_shared_contexts.find(hShareContext); + + while (it != s_shared_contexts.end() && it->second != nullptr) + { + it = s_shared_contexts.find(s_shared_contexts.at(hglrc) = it->second); + } + } + } + + LOG(INFO) << "> Returning OpenGL context " << hglrc << '.'; + + return hglrc; +} +HOOK_EXPORT HGLRC WINAPI wglCreateLayerContext(HDC hdc, int iLayerPlane) +{ + LOG(INFO) << "Redirecting wglCreateLayerContext" << '(' << hdc << ", " << iLayerPlane << ')' << " ..."; + + if (iLayerPlane != 0) + { + LOG(WARN) << "Access to layer plane at index " << iLayerPlane << " is unsupported."; + + SetLastError(ERROR_INVALID_PARAMETER); + + return nullptr; + } + + const HGLRC hglrc = reshade::hooks::call(wglCreateLayerContext)(hdc, iLayerPlane); + + if (hglrc == nullptr) + { + LOG(WARN) << "> wglCreateLayerContext failed with error code " << (GetLastError() & 0xFFFF) << '!'; + + return nullptr; + } + + { const std::lock_guard lock(s_mutex); + s_shared_contexts.emplace(hglrc, nullptr); + } + + LOG(INFO) << "> Returning OpenGL context " << hglrc << '.'; + + return hglrc; +} +HOOK_EXPORT BOOL WINAPI wglCopyContext(HGLRC hglrcSrc, HGLRC hglrcDst, UINT mask) +{ + return reshade::hooks::call(wglCopyContext)(hglrcSrc, hglrcDst, mask); +} +HOOK_EXPORT BOOL WINAPI wglDeleteContext(HGLRC hglrc) +{ + LOG(INFO) << "Redirecting wglDeleteContext" << '(' << hglrc << ')' << " ..."; + + if (const auto it = s_opengl_runtimes.find(hglrc); it != s_opengl_runtimes.end()) + { + LOG(INFO) << "> Cleaning up runtime " << it->second << " ..."; + + // Choose a random device context to make current with (and hope that is still alive) + const HDC hdc = *it->second->_hdcs.begin(); + + // Set the render context current so its resources can be cleaned up + if (reshade::hooks::call(wglMakeCurrent)(hdc, hglrc)) + { + it->second->on_reset(); + + delete it->second; + } + else + { + LOG(WARN) << "> Unable to make context current, leaking resources ..."; + } + + s_opengl_runtimes.erase(it); + } + + { const std::lock_guard lock(s_mutex); + for (auto it = s_shared_contexts.begin(); it != s_shared_contexts.end();) + { + if (it->first == hglrc) + { + it = s_shared_contexts.erase(it); + continue; + } + else if (it->second == hglrc) + { + it->second = nullptr; + } + + ++it; + } + } + + if (!reshade::hooks::call(wglDeleteContext)(hglrc)) + { + LOG(WARN) << "> wglDeleteContext failed with error code " << (GetLastError() & 0xFFFF) << '!'; + + return FALSE; + } + + return TRUE; +} + +HOOK_EXPORT BOOL WINAPI wglShareLists(HGLRC hglrc1, HGLRC hglrc2) +{ + LOG(INFO) << "Redirecting wglShareLists" << '(' << hglrc1 << ", " << hglrc2 << ')' << " ..."; + + if (!reshade::hooks::call(wglShareLists)(hglrc1, hglrc2)) + { + LOG(WARN) << "> wglShareLists failed with error code " << (GetLastError() & 0xFFFF) << '!'; + + return FALSE; + } + + { const std::lock_guard lock(s_mutex); + s_shared_contexts[hglrc2] = hglrc1; + } + + return TRUE; +} + +HOOK_EXPORT BOOL WINAPI wglMakeCurrent(HDC hdc, HGLRC hglrc) +{ + static const auto trampoline = reshade::hooks::call(wglMakeCurrent); + +#if RESHADE_VERBOSE_LOG + LOG(DEBUG) << "Redirecting wglMakeCurrent" << '(' << hdc << ", " << hglrc << ')' << " ..."; +#endif + + const HGLRC prev_hglrc = wglGetCurrentContext(); + + if (!trampoline(hdc, hglrc)) + { +#if RESHADE_VERBOSE_LOG + LOG(WARN) << "> wglMakeCurrent failed with error code " << (GetLastError() & 0xFFFF) << '!'; +#endif + return FALSE; + } + + if (hglrc == prev_hglrc) + { + // Nothing has changed, so there is nothing more to do + return TRUE; + } + else if (hglrc == nullptr) + { + g_current_runtime = nullptr; + + return TRUE; + } + + const std::lock_guard lock(s_mutex); + + if (s_shared_contexts.at(hglrc) != nullptr) + { + hglrc = s_shared_contexts.at(hglrc); + +#if RESHADE_VERBOSE_LOG + LOG(DEBUG) << "> Using shared OpenGL context " << hglrc << '.'; +#endif + } + + if (const auto it = s_opengl_runtimes.find(hglrc); it != s_opengl_runtimes.end()) + { + if (it->second != g_current_runtime) + { + g_current_runtime = it->second; + +#if RESHADE_VERBOSE_LOG + LOG(DEBUG) << "> Switched to existing runtime " << it->second << '.'; +#endif + } + + // Keep track of all device contexts that were used with this render context + // Do this outside the above if statement since the game may change the device context without changing the render context and thus the current runtime + it->second->_hdcs.insert(hdc); + } + else + { + const HWND hwnd = WindowFromDC(hdc); + + if (hwnd == nullptr || s_pbuffer_device_contexts.find(hdc) != s_pbuffer_device_contexts.end()) + { + LOG(WARN) << "Skipping render context " << hglrc << " because there is no window associated with its device context " << hdc << '.'; + + return TRUE; + } + else if ((GetClassLongPtr(hwnd, GCL_STYLE) & CS_OWNDC) == 0) + { + LOG(WARN) << "Window class style of window " << hwnd << " is missing 'CS_OWNDC' flag."; + } + + // Load original OpenGL functions instead of using the hooked ones + gl3wInit2([](const char *name) { + extern std::filesystem::path get_system_path(); + // First attempt to load from the OpenGL ICD + FARPROC address = reshade::hooks::call(wglGetProcAddress)(name); + if (address == nullptr) address = GetProcAddress( // Load from the Windows OpenGL DLL if that fails + GetModuleHandleW((get_system_path() / "opengl32.dll").c_str()), name); + return reinterpret_cast(address); + }); + + if (gl3wIsSupported(4, 3)) + { + const auto runtime = new reshade::opengl::runtime_opengl(); + runtime->_hdcs.insert(hdc); + + g_current_runtime = s_opengl_runtimes[hglrc] = runtime; + +#if RESHADE_VERBOSE_LOG + LOG(DEBUG) << "> Switched to new runtime " << runtime << '.'; +#endif + } + else + { + LOG(ERROR) << "Your graphics card does not seem to support OpenGL 4.3. Initialization failed."; + + g_current_runtime = nullptr; + } + } + + return TRUE; +} + +HOOK_EXPORT HDC WINAPI wglGetCurrentDC() +{ + static const auto trampoline = reshade::hooks::call(wglGetCurrentDC); + return trampoline(); +} +HOOK_EXPORT HGLRC WINAPI wglGetCurrentContext() +{ + static const auto trampoline = reshade::hooks::call(wglGetCurrentContext); + return trampoline(); +} + + HPBUFFERARB WINAPI wglCreatePbufferARB(HDC hdc, int iPixelFormat, int iWidth, int iHeight, const int *piAttribList) +{ + LOG(INFO) << "Redirecting wglCreatePbufferARB" << '(' << hdc << ", " << iPixelFormat << ", " << iWidth << ", " << iHeight << ", " << piAttribList << ')' << " ..."; + + struct attribute + { + enum + { + WGL_PBUFFER_LARGEST_ARB = 0x2033, + WGL_TEXTURE_FORMAT_ARB = 0x2072, + WGL_TEXTURE_TARGET_ARB = 0x2073, + WGL_MIPMAP_TEXTURE_ARB = 0x2074, + + WGL_TEXTURE_RGB_ARB = 0x2075, + WGL_TEXTURE_RGBA_ARB = 0x2076, + WGL_NO_TEXTURE_ARB = 0x2077, + }; + }; + + LOG(INFO) << "> Dumping attributes:"; + LOG(INFO) << " +-----------------------------------------+-----------------------------------------+"; + LOG(INFO) << " | Attribute | Value |"; + LOG(INFO) << " +-----------------------------------------+-----------------------------------------+"; + + for (const int *attrib = piAttribList; attrib != nullptr && *attrib != 0; attrib += 2) + { + switch (attrib[0]) + { + case attribute::WGL_PBUFFER_LARGEST_ARB: + LOG(INFO) << " | WGL_PBUFFER_LARGEST_ARB | " << std::setw(39) << (attrib[1] != FALSE ? "TRUE" : "FALSE") << " |"; + break; + case attribute::WGL_TEXTURE_FORMAT_ARB: + LOG(INFO) << " | WGL_TEXTURE_FORMAT_ARB | " << std::setw(39) << std::hex << attrib[1] << std::dec << " |"; + break; + case attribute::WGL_TEXTURE_TARGET_ARB: + LOG(INFO) << " | WGL_TEXTURE_TARGET_ARB | " << std::setw(39) << std::hex << attrib[1] << std::dec << " |"; + break; + default: + LOG(INFO) << " | " << std::hex << std::setw(39) << attrib[0] << " | " << std::setw(39) << attrib[1] << std::dec << " |"; + break; + } + } + + LOG(INFO) << " +-----------------------------------------+-----------------------------------------+"; + + const HPBUFFERARB hpbuffer = reshade::hooks::call(wglCreatePbufferARB)(hdc, iPixelFormat, iWidth, iHeight, piAttribList); + + if (hpbuffer == nullptr) + { + LOG(WARN) << "> wglCreatePbufferARB failed with error code " << (GetLastError() & 0xFFFF) << '!'; + + return nullptr; + } + +#if RESHADE_VERBOSE_LOG + LOG(DEBUG) << "> Returning pixel buffer " << hpbuffer << '.'; +#endif + + return hpbuffer; +} + BOOL WINAPI wglDestroyPbufferARB(HPBUFFERARB hPbuffer) +{ + LOG(INFO) << "Redirecting wglDestroyPbufferARB" << '(' << hPbuffer << ')' << " ..."; + + if (!reshade::hooks::call(wglDestroyPbufferARB)(hPbuffer)) + { + LOG(WARN) << "> wglDestroyPbufferARB failed with error code " << (GetLastError() & 0xFFFF) << '!'; + return FALSE; + } + + return TRUE; +} + BOOL WINAPI wglQueryPbufferARB(HPBUFFERARB hPbuffer, int iAttribute, int *piValue) +{ + return reshade::hooks::call(wglQueryPbufferARB)(hPbuffer, iAttribute, piValue); +} + HDC WINAPI wglGetPbufferDCARB(HPBUFFERARB hPbuffer) +{ + LOG(INFO) << "Redirecting wglGetPbufferDCARB" << '(' << hPbuffer << ')' << " ..."; + + const HDC hdc = reshade::hooks::call(wglGetPbufferDCARB)(hPbuffer); + + if (hdc == nullptr) + { + LOG(WARN) << "> wglGetPbufferDCARB failed with error code " << (GetLastError() & 0xFFFF) << '!'; + + return nullptr; + } + + { const std::lock_guard lock(s_mutex); + s_pbuffer_device_contexts.insert(hdc); + } + +#if RESHADE_VERBOSE_LOG + LOG(DEBUG) << "> Returning pixel buffer device context " << hdc << '.'; +#endif + + return hdc; +} + int WINAPI wglReleasePbufferDCARB(HPBUFFERARB hPbuffer, HDC hdc) +{ + LOG(INFO) << "Redirecting wglReleasePbufferDCARB" << '(' << hPbuffer << ')' << " ..."; + + if (!reshade::hooks::call(wglReleasePbufferDCARB)(hPbuffer, hdc)) + { + LOG(WARN) << "> wglReleasePbufferDCARB failed with error code " << (GetLastError() & 0xFFFF) << '!'; + + return FALSE; + } + + { const std::lock_guard lock(s_mutex); + s_pbuffer_device_contexts.erase(hdc); + } + + return TRUE; +} + + BOOL WINAPI wglSwapIntervalEXT(int interval) +{ + static const auto trampoline = reshade::hooks::call(wglSwapIntervalEXT); + return trampoline(interval); +} + int WINAPI wglGetSwapIntervalEXT() +{ + static const auto trampoline = reshade::hooks::call(wglGetSwapIntervalEXT); + return trampoline(); +} + +HOOK_EXPORT BOOL WINAPI wglSwapBuffers(HDC hdc) +{ + static const auto trampoline = reshade::hooks::call(wglSwapBuffers); + + const HWND hwnd = WindowFromDC(hdc); + + // Find the runtime that is associated with this device context + const auto it = std::find_if(s_opengl_runtimes.begin(), s_opengl_runtimes.end(), + [hdc](const std::pair &it) { return it.second->_hdcs.count(hdc); }); + + // The window handle can be invalid if the window was already destroyed + if (hwnd != nullptr && it != s_opengl_runtimes.end()) + { + RECT rect = { 0, 0, 0, 0 }; + GetClientRect(hwnd, &rect); + + const auto width = static_cast(rect.right - rect.left); + const auto height = static_cast(rect.bottom - rect.top); + + if (width != it->second->frame_width() || height != it->second->frame_height()) + { + LOG(INFO) << "Resizing runtime " << it->second << " on device context " << hdc << " to " << width << "x" << height << " ..."; + + it->second->on_reset(); + + if (!(width == 0 && height == 0) && !it->second->on_init(hwnd, width, height)) + LOG(ERROR) << "Failed to recreate OpenGL runtime environment on runtime " << it->second << '.'; + } + + // Assume that the correct OpenGL context is still current here + it->second->on_present(); + } + + return trampoline(hdc); +} +HOOK_EXPORT BOOL WINAPI wglSwapLayerBuffers(HDC hdc, UINT i) +{ + if (i != WGL_SWAP_MAIN_PLANE) + { + const int index = i >= WGL_SWAP_UNDERLAY1 ? static_cast(-std::log(i >> 16) / std::log(2) - 1) : static_cast(std::log(i) / std::log(2)); + + LOG(INFO) << "Redirecting wglSwapLayerBuffers" << '(' << hdc << ", " << i << ')' << " ..."; + LOG(WARN) << "Access to layer plane at index " << index << " is unsupported."; + + SetLastError(ERROR_INVALID_PARAMETER); + + return FALSE; + } + + return wglSwapBuffers(hdc); +} +HOOK_EXPORT DWORD WINAPI wglSwapMultipleBuffers(UINT cNumBuffers, const WGLSWAP *pBuffers) +{ + for (UINT i = 0; i < cNumBuffers; ++i) + { + assert(pBuffers != nullptr); + + wglSwapBuffers(pBuffers[i].hdc); + } + + return 0; +} + +HOOK_EXPORT BOOL WINAPI wglUseFontBitmapsA(HDC hdc, DWORD dw1, DWORD dw2, DWORD dw3) +{ + static const auto trampoline = reshade::hooks::call(wglUseFontBitmapsA); + return trampoline(hdc, dw1, dw2, dw3); +} +HOOK_EXPORT BOOL WINAPI wglUseFontBitmapsW(HDC hdc, DWORD dw1, DWORD dw2, DWORD dw3) +{ + static const auto trampoline = reshade::hooks::call(wglUseFontBitmapsW); + return trampoline(hdc, dw1, dw2, dw3); +} +HOOK_EXPORT BOOL WINAPI wglUseFontOutlinesA(HDC hdc, DWORD dw1, DWORD dw2, DWORD dw3, FLOAT f1, FLOAT f2, int i, LPGLYPHMETRICSFLOAT pgmf) +{ + static const auto trampoline = reshade::hooks::call(wglUseFontOutlinesA); + return trampoline(hdc, dw1, dw2, dw3, f1, f2, i, pgmf); +} +HOOK_EXPORT BOOL WINAPI wglUseFontOutlinesW(HDC hdc, DWORD dw1, DWORD dw2, DWORD dw3, FLOAT f1, FLOAT f2, int i, LPGLYPHMETRICSFLOAT pgmf) +{ + static const auto trampoline = reshade::hooks::call(wglUseFontOutlinesW); + return trampoline(hdc, dw1, dw2, dw3, f1, f2, i, pgmf); +} + +HOOK_EXPORT PROC WINAPI wglGetProcAddress(LPCSTR lpszProc) +{ + static const auto trampoline = reshade::hooks::call(wglGetProcAddress); + const PROC address = trampoline(lpszProc); + + if (address == nullptr || lpszProc == nullptr) + return nullptr; + else if (0 == strcmp(lpszProc, "glBindTexture")) + return reinterpret_cast(glBindTexture); + else if (0 == strcmp(lpszProc, "glBlendFunc")) + return reinterpret_cast(glBlendFunc); + else if (0 == strcmp(lpszProc, "glClear")) + return reinterpret_cast(glClear); + else if (0 == strcmp(lpszProc, "glClearColor")) + return reinterpret_cast(glClearColor); + else if (0 == strcmp(lpszProc, "glClearDepth")) + return reinterpret_cast(glClearDepth); + else if (0 == strcmp(lpszProc, "glClearStencil")) + return reinterpret_cast(glClearStencil); + else if (0 == strcmp(lpszProc, "glCopyTexImage1D")) + return reinterpret_cast(glCopyTexImage1D); + else if (0 == strcmp(lpszProc, "glCopyTexImage2D")) + return reinterpret_cast(glCopyTexImage2D); + else if (0 == strcmp(lpszProc, "glCopyTexSubImage1D")) + return reinterpret_cast(glCopyTexSubImage1D); + else if (0 == strcmp(lpszProc, "glCopyTexSubImage2D")) + return reinterpret_cast(glCopyTexSubImage2D); + else if (0 == strcmp(lpszProc, "glCullFace")) + return reinterpret_cast(glCullFace); + else if (0 == strcmp(lpszProc, "glDeleteTextures")) + return reinterpret_cast(glDeleteTextures); + else if (0 == strcmp(lpszProc, "glDepthFunc")) + return reinterpret_cast(glDepthFunc); + else if (0 == strcmp(lpszProc, "glDepthMask")) + return reinterpret_cast(glDepthMask); + else if (0 == strcmp(lpszProc, "glDepthRange")) + return reinterpret_cast(glDepthRange); + else if (0 == strcmp(lpszProc, "glDisable")) + return reinterpret_cast(glDisable); + else if (0 == strcmp(lpszProc, "glDrawArrays")) + return reinterpret_cast(glDrawArrays); + else if (0 == strcmp(lpszProc, "glDrawBuffer")) + return reinterpret_cast(glDrawBuffer); + else if (0 == strcmp(lpszProc, "glDrawElements")) + return reinterpret_cast(glDrawElements); + else if (0 == strcmp(lpszProc, "glEnable")) + return reinterpret_cast(glEnable); + else if (0 == strcmp(lpszProc, "glFinish")) + return reinterpret_cast(glFinish); + else if (0 == strcmp(lpszProc, "glFlush")) + return reinterpret_cast(glFlush); + else if (0 == strcmp(lpszProc, "glFrontFace")) + return reinterpret_cast(glFrontFace); + else if (0 == strcmp(lpszProc, "glGenTextures")) + return reinterpret_cast(glGenTextures); + else if (0 == strcmp(lpszProc, "glGetBooleanv")) + return reinterpret_cast(glGetBooleanv); + else if (0 == strcmp(lpszProc, "glGetDoublev")) + return reinterpret_cast(glGetDoublev); + else if (0 == strcmp(lpszProc, "glGetFloatv")) + return reinterpret_cast(glGetFloatv); + else if (0 == strcmp(lpszProc, "glGetIntegerv")) + return reinterpret_cast(glGetIntegerv); + else if (0 == strcmp(lpszProc, "glGetError")) + return reinterpret_cast(glGetError); + else if (0 == strcmp(lpszProc, "glGetPointerv")) + return reinterpret_cast(glGetPointerv); + else if (0 == strcmp(lpszProc, "glGetString")) + return reinterpret_cast(glGetString); + else if (0 == strcmp(lpszProc, "glGetTexImage")) + return reinterpret_cast(glGetTexImage); + else if (0 == strcmp(lpszProc, "glGetTexLevelParameterfv")) + return reinterpret_cast(glGetTexLevelParameterfv); + else if (0 == strcmp(lpszProc, "glGetTexLevelParameteriv")) + return reinterpret_cast(glGetTexLevelParameteriv); + else if (0 == strcmp(lpszProc, "glGetTexParameterfv")) + return reinterpret_cast(glGetTexParameterfv); + else if (0 == strcmp(lpszProc, "glGetTexParameteriv")) + return reinterpret_cast(glGetTexParameteriv); + else if (0 == strcmp(lpszProc, "glHint")) + return reinterpret_cast(glHint); + else if (0 == strcmp(lpszProc, "glIsEnabled")) + return reinterpret_cast(glIsEnabled); + else if (0 == strcmp(lpszProc, "glIsTexture")) + return reinterpret_cast(glIsTexture); + else if (0 == strcmp(lpszProc, "glLineWidth")) + return reinterpret_cast(glLineWidth); + else if (0 == strcmp(lpszProc, "glLogicOp")) + return reinterpret_cast(glLogicOp); + else if (0 == strcmp(lpszProc, "glPixelStoref")) + return reinterpret_cast(glPixelStoref); + else if (0 == strcmp(lpszProc, "glPixelStorei")) + return reinterpret_cast(glPixelStorei); + else if (0 == strcmp(lpszProc, "glPointSize")) + return reinterpret_cast(glPointSize); + else if (0 == strcmp(lpszProc, "glPolygonMode")) + return reinterpret_cast(glPolygonMode); + else if (0 == strcmp(lpszProc, "glPolygonOffset")) + return reinterpret_cast(glPolygonOffset); + else if (0 == strcmp(lpszProc, "glReadBuffer")) + return reinterpret_cast(glReadBuffer); + else if (0 == strcmp(lpszProc, "glReadPixels")) + return reinterpret_cast(glReadPixels); + else if (0 == strcmp(lpszProc, "glScissor")) + return reinterpret_cast(glScissor); + else if (0 == strcmp(lpszProc, "glStencilFunc")) + return reinterpret_cast(glStencilFunc); + else if (0 == strcmp(lpszProc, "glStencilMask")) + return reinterpret_cast(glStencilMask); + else if (0 == strcmp(lpszProc, "glStencilOp")) + return reinterpret_cast(glStencilOp); + else if (0 == strcmp(lpszProc, "glTexImage1D")) + return reinterpret_cast(glTexImage1D); + else if (0 == strcmp(lpszProc, "glTexImage2D")) + return reinterpret_cast(glTexImage2D); + else if (0 == strcmp(lpszProc, "glTexParameterf")) + return reinterpret_cast(glTexParameterf); + else if (0 == strcmp(lpszProc, "glTexParameterfv")) + return reinterpret_cast(glTexParameterfv); + else if (0 == strcmp(lpszProc, "glTexParameteri")) + return reinterpret_cast(glTexParameteri); + else if (0 == strcmp(lpszProc, "glTexParameteriv")) + return reinterpret_cast(glTexParameteriv); + else if (0 == strcmp(lpszProc, "glTexSubImage1D")) + return reinterpret_cast(glTexSubImage1D); + else if (0 == strcmp(lpszProc, "glTexSubImage2D")) + return reinterpret_cast(glTexSubImage2D); + else if (0 == strcmp(lpszProc, "glViewport")) + return reinterpret_cast(glViewport); + else if (static bool s_hooks_not_installed = true; s_hooks_not_installed) + { + // Install all OpenGL hooks in a single batch job + reshade::hooks::install("glDrawArraysIndirect", reinterpret_cast(trampoline("glDrawArraysIndirect")), reinterpret_cast(glDrawArraysIndirect), true); + reshade::hooks::install("glDrawArraysInstanced", reinterpret_cast(trampoline("glDrawArraysInstanced")), reinterpret_cast(glDrawArraysInstanced), true); + reshade::hooks::install("glDrawArraysInstancedARB", reinterpret_cast(trampoline("glDrawArraysInstancedARB")), reinterpret_cast(glDrawArraysInstancedARB), true); + reshade::hooks::install("glDrawArraysInstancedEXT", reinterpret_cast(trampoline("glDrawArraysInstancedEXT")), reinterpret_cast(glDrawArraysInstancedEXT), true); + reshade::hooks::install("glDrawArraysInstancedBaseInstance", reinterpret_cast(trampoline("glDrawArraysInstancedBaseInstance")), reinterpret_cast(glDrawArraysInstancedBaseInstance), true); + reshade::hooks::install("glDrawElementsBaseVertex", reinterpret_cast(trampoline("glDrawElementsBaseVertex")), reinterpret_cast(glDrawElementsBaseVertex), true); + reshade::hooks::install("glDrawElementsIndirect", reinterpret_cast(trampoline("glDrawElementsIndirect")), reinterpret_cast(glDrawElementsIndirect), true); + reshade::hooks::install("glDrawElementsInstanced", reinterpret_cast(trampoline("glDrawElementsInstanced")), reinterpret_cast(glDrawElementsInstanced), true); + reshade::hooks::install("glDrawElementsInstancedARB", reinterpret_cast(trampoline("glDrawElementsInstancedARB")), reinterpret_cast(glDrawElementsInstancedARB), true); + reshade::hooks::install("glDrawElementsInstancedEXT", reinterpret_cast(trampoline("glDrawElementsInstancedEXT")), reinterpret_cast(glDrawElementsInstancedEXT), true); + reshade::hooks::install("glDrawElementsInstancedBaseVertex", reinterpret_cast(trampoline("glDrawElementsInstancedBaseVertex")), reinterpret_cast(glDrawElementsInstancedBaseVertex), true); + reshade::hooks::install("glDrawElementsInstancedBaseInstance", reinterpret_cast(trampoline("glDrawElementsInstancedBaseInstance")), reinterpret_cast(glDrawElementsInstancedBaseInstance), true); + reshade::hooks::install("glDrawElementsInstancedBaseVertexBaseInstance", reinterpret_cast(trampoline("glDrawElementsInstancedBaseVertexBaseInstance")), reinterpret_cast(glDrawElementsInstancedBaseVertexBaseInstance), true); + reshade::hooks::install("glDrawRangeElements", reinterpret_cast(trampoline("glDrawRangeElements")), reinterpret_cast(glDrawRangeElements), true); + reshade::hooks::install("glDrawRangeElementsBaseVertex", reinterpret_cast(trampoline("glDrawRangeElementsBaseVertex")), reinterpret_cast(glDrawRangeElementsBaseVertex), true); + reshade::hooks::install("glFramebufferRenderbuffer", reinterpret_cast(trampoline("glFramebufferRenderbuffer")), reinterpret_cast(glFramebufferRenderbuffer), true); + reshade::hooks::install("glFramebufferRenderbufferEXT", reinterpret_cast(trampoline("glFramebufferRenderbufferEXT")), reinterpret_cast(glFramebufferRenderbufferEXT), true); + reshade::hooks::install("glFramebufferTexture", reinterpret_cast(trampoline("glFramebufferTexture")), reinterpret_cast(glFramebufferTexture), true); + reshade::hooks::install("glFramebufferTextureARB", reinterpret_cast(trampoline("glFramebufferTextureARB")), reinterpret_cast(glFramebufferTextureARB), true); + reshade::hooks::install("glFramebufferTextureEXT", reinterpret_cast(trampoline("glFramebufferTextureEXT")), reinterpret_cast(glFramebufferTextureEXT), true); + reshade::hooks::install("glFramebufferTexture1D", reinterpret_cast(trampoline("glFramebufferTexture1D")), reinterpret_cast(glFramebufferTexture1D), true); + reshade::hooks::install("glFramebufferTexture1DEXT", reinterpret_cast(trampoline("glFramebufferTexture1DEXT")), reinterpret_cast(glFramebufferTexture1DEXT), true); + reshade::hooks::install("glFramebufferTexture2D", reinterpret_cast(trampoline("glFramebufferTexture2D")), reinterpret_cast(glFramebufferTexture2D), true); + reshade::hooks::install("glFramebufferTexture2DEXT", reinterpret_cast(trampoline("glFramebufferTexture2DEXT")), reinterpret_cast(glFramebufferTexture2DEXT), true); + reshade::hooks::install("glFramebufferTexture3D", reinterpret_cast(trampoline("glFramebufferTexture3D")), reinterpret_cast(glFramebufferTexture3D), true); + reshade::hooks::install("glFramebufferTexture3DEXT", reinterpret_cast(trampoline("glFramebufferTexture3DEXT")), reinterpret_cast(glFramebufferTexture3DEXT), true); + reshade::hooks::install("glFramebufferTextureLayer", reinterpret_cast(trampoline("glFramebufferTextureLayer")), reinterpret_cast(glFramebufferTextureLayer), true); + reshade::hooks::install("glFramebufferTextureLayerARB", reinterpret_cast(trampoline("glFramebufferTextureLayerARB")), reinterpret_cast(glFramebufferTextureLayerARB), true); + reshade::hooks::install("glFramebufferTextureLayerEXT", reinterpret_cast(trampoline("glFramebufferTextureLayerEXT")), reinterpret_cast(glFramebufferTextureLayerEXT), true); + reshade::hooks::install("glMultiDrawArrays", reinterpret_cast(trampoline("glMultiDrawArrays")), reinterpret_cast(glMultiDrawArrays), true); + reshade::hooks::install("glMultiDrawArraysIndirect", reinterpret_cast(trampoline("glMultiDrawArraysIndirect")), reinterpret_cast(glMultiDrawArraysIndirect), true); + reshade::hooks::install("glMultiDrawElements", reinterpret_cast(trampoline("glMultiDrawElements")), reinterpret_cast(glMultiDrawElements), true); + reshade::hooks::install("glMultiDrawElementsBaseVertex", reinterpret_cast(trampoline("glMultiDrawElementsBaseVertex")), reinterpret_cast(glMultiDrawElementsBaseVertex), true); + reshade::hooks::install("glMultiDrawElementsIndirect", reinterpret_cast(trampoline("glMultiDrawElementsIndirect")), reinterpret_cast(glMultiDrawElementsIndirect), true); + reshade::hooks::install("glTexImage3D", reinterpret_cast(trampoline("glTexImage3D")), reinterpret_cast(glTexImage3D), true); + + reshade::hooks::install("wglChoosePixelFormatARB", reinterpret_cast(trampoline("wglChoosePixelFormatARB")), reinterpret_cast(wglChoosePixelFormatARB), true); + reshade::hooks::install("wglCreateContextAttribsARB", reinterpret_cast(trampoline("wglCreateContextAttribsARB")), reinterpret_cast(wglCreateContextAttribsARB), true); + reshade::hooks::install("wglCreatePbufferARB", reinterpret_cast(trampoline("wglCreatePbufferARB")), reinterpret_cast(wglCreatePbufferARB), true); + reshade::hooks::install("wglDestroyPbufferARB", reinterpret_cast(trampoline("wglDestroyPbufferARB")), reinterpret_cast(wglDestroyPbufferARB), true); + reshade::hooks::install("wglGetPbufferDCARB", reinterpret_cast(trampoline("wglGetPbufferDCARB")), reinterpret_cast(wglGetPbufferDCARB), true); + reshade::hooks::install("wglGetPixelFormatAttribivARB", reinterpret_cast(trampoline("wglGetPixelFormatAttribivARB")), reinterpret_cast(wglGetPixelFormatAttribivARB), true); + reshade::hooks::install("wglGetPixelFormatAttribfvARB", reinterpret_cast(trampoline("wglGetPixelFormatAttribfvARB")), reinterpret_cast(wglGetPixelFormatAttribfvARB), true); + reshade::hooks::install("wglQueryPbufferARB", reinterpret_cast(trampoline("wglQueryPbufferARB")), reinterpret_cast(wglQueryPbufferARB), true); + reshade::hooks::install("wglReleasePbufferDCARB", reinterpret_cast(trampoline("wglReleasePbufferDCARB")), reinterpret_cast(wglReleasePbufferDCARB), true); + reshade::hooks::install("wglGetSwapIntervalEXT", reinterpret_cast(trampoline("wglGetSwapIntervalEXT")), reinterpret_cast(wglGetSwapIntervalEXT), true); + reshade::hooks::install("wglSwapIntervalEXT", reinterpret_cast(trampoline("wglSwapIntervalEXT")), reinterpret_cast(wglSwapIntervalEXT), true); + + reshade::hook::apply_queued_actions(); + + s_hooks_not_installed = false; + } + + return address; +} diff --git a/msvc/source/opengl/runtime_opengl.cpp b/msvc/source/opengl/runtime_opengl.cpp new file mode 100644 index 0000000..4cdeadd --- /dev/null +++ b/msvc/source/opengl/runtime_opengl.cpp @@ -0,0 +1,1229 @@ +/** + * Copyright (C) 2014 Patrick Mours. All rights reserved. + * License: https://github.com/crosire/reshade#license + */ + +#include "log.hpp" +#include "runtime_opengl.hpp" +#include "runtime_objects.hpp" +#include "ini_file.hpp" +#include + +namespace reshade::opengl +{ + struct opengl_tex_data : base_object + { + ~opengl_tex_data() + { + if (should_delete) + glDeleteTextures(2, id); + } + + bool should_delete = false; + GLuint id[2] = {}; + }; + + struct opengl_sampler_data + { + GLuint id; + opengl_tex_data *texture; + bool is_srgb; + bool has_mipmaps; + }; + + struct opengl_pass_data : base_object + { + ~opengl_pass_data() + { + if (program) + glDeleteProgram(program); + glDeleteFramebuffers(1, &fbo); + } + + GLuint fbo = 0; + GLuint program = 0; + GLenum blend_eq_color = GL_NONE; + GLenum blend_eq_alpha = GL_NONE; + GLenum blend_src = GL_NONE; + GLenum blend_src_alpha = GL_NONE; + GLenum blend_dest = GL_NONE; + GLenum blend_dest_alpha = GL_NONE; + GLenum stencil_func = GL_NONE; + GLenum stencil_op_fail = GL_NONE; + GLenum stencil_op_z_fail = GL_NONE; + GLenum stencil_op_z_pass = GL_NONE; + GLenum draw_targets[8] = {}; + GLuint draw_textures[8] = {}; + GLsizei viewport_width = 0; + GLsizei viewport_height = 0; + }; + + struct opengl_technique_data : base_object + { + ~opengl_technique_data() + { + glDeleteQueries(1, &query); + } + + GLuint query = 0; + bool query_in_flight = false; + std::vector samplers; + ptrdiff_t uniform_storage_index = -1; + ptrdiff_t uniform_storage_offset = 0; + }; +} + +reshade::opengl::runtime_opengl::runtime_opengl() +{ + GLint major = 0, minor = 0; + glGetIntegerv(GL_MAJOR_VERSION, &major); + glGetIntegerv(GL_MAJOR_VERSION, &minor); + _renderer_id = 0x10000 | (major << 12) | (minor << 8); + + // Query vendor and device ID from Windows assuming we are running on the primary display device + // This is done because the information reported by OpenGL is not always reflecting the actual rendering device (e.g. on NVIDIA Optimus laptops) + DISPLAY_DEVICEA dd = { sizeof(dd) }; + for (DWORD i = 0; EnumDisplayDevicesA(nullptr, i, &dd, 0) != FALSE; ++i) + { + if ((dd.StateFlags & DISPLAY_DEVICE_PRIMARY_DEVICE) != 0) + { + // Format: PCI\VEN_XXXX&DEV_XXXX... + const std::string id = dd.DeviceID; + + if (id.length() > 20) + { + _vendor_id = std::stoi(id.substr(8, 4), nullptr, 16); + _device_id = std::stoi(id.substr(17, 4), nullptr, 16); + } + break; + } + } + + subscribe_to_load_config([this](const ini_file &config) { + size_t num_reserve_texture_names = 0; + config.get("OPENGL", "ReserveTextureNames", num_reserve_texture_names); + _reserved_texture_names.resize(num_reserve_texture_names); + }); +} + +bool reshade::opengl::runtime_opengl::on_init(HWND hwnd, unsigned int width, unsigned int height) +{ + RECT window_rect = {}; + GetClientRect(hwnd, &window_rect); + + _width = width; + _height = height; + _window_width = window_rect.right - window_rect.left; + _window_height = window_rect.bottom - window_rect.top; + + // Initialize information for the default depth buffer + _depth_source_table[0] = { _width, _height, 0, GL_DEPTH24_STENCIL8 }; + + // Capture and later restore so that the resource creation code below does not affect the application state + _app_state.capture(); + + // Some games (like Hot Wheels Velocity X) use fixed texture names, which can clash with the ones ReShade generates below, since most implementations will return values linearly + // Reserve a configurable range of names for those games to work around this + glGenTextures(GLsizei(_reserved_texture_names.size()), _reserved_texture_names.data()); + + glGenBuffers(NUM_BUF, _buf); + glGenTextures(NUM_TEX, _tex); + glGenVertexArrays(NUM_VAO, _vao); + glGenFramebuffers(NUM_FBO, _fbo); + glGenRenderbuffers(NUM_RBO, _rbo); + + glBindTexture(GL_TEXTURE_2D, _tex[TEX_BACK]); + glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, _width, _height); + glTextureView(_tex[TEX_BACK_SRGB], GL_TEXTURE_2D, _tex[TEX_BACK], GL_SRGB8_ALPHA8, 0, 1, 0, 1); + + glBindTexture(GL_TEXTURE_2D, _tex[TEX_DEPTH]); + glTexStorage2D(GL_TEXTURE_2D, 1, GL_DEPTH24_STENCIL8, _width, _height); + + glBindRenderbuffer(GL_RENDERBUFFER, _rbo[RBO_COLOR]); + glRenderbufferStorage(GL_RENDERBUFFER, GL_SRGB8_ALPHA8, _width, _height); + glBindRenderbuffer(GL_RENDERBUFFER, _rbo[RBO_DEPTH]); + glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, _width, _height); + + glBindFramebuffer(GL_FRAMEBUFFER, _fbo[FBO_BACK]); + glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, _rbo[RBO_COLOR]); + glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, _rbo[RBO_DEPTH]); + assert(glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE); + + glBindFramebuffer(GL_FRAMEBUFFER, _fbo[FBO_BLIT]); + glFramebufferTexture(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, _tex[TEX_DEPTH], 0); + glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, _tex[TEX_BACK_SRGB], 0); + assert(glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE); + +#if RESHADE_GUI + init_imgui_resources(); +#endif + + _app_state.apply(); + + return runtime::on_init(hwnd); +} +void reshade::opengl::runtime_opengl::on_reset() +{ + runtime::on_reset(); + + glDeleteBuffers(NUM_BUF, _buf); + glDeleteTextures(NUM_TEX, _tex); + glDeleteTextures(GLsizei(_reserved_texture_names.size()), _reserved_texture_names.data()); + glDeleteVertexArrays(NUM_VAO, _vao); + glDeleteFramebuffers(NUM_FBO, _fbo); + glDeleteRenderbuffers(NUM_RBO, _rbo); + + memset(_buf, 0, sizeof(_vao)); + memset(_tex, 0, sizeof(_tex)); + memset(_vao, 0, sizeof(_vao)); + memset(_fbo, 0, sizeof(_fbo)); + memset(_rbo, 0, sizeof(_rbo)); + + _depth_source = 0; + +#if RESHADE_GUI + glDeleteProgram(_imgui_program); + _imgui_program = 0; +#endif +} + +void reshade::opengl::runtime_opengl::on_present() +{ + if (!_is_initialized) + return; + + _app_state.capture(); + + detect_depth_source(); + + // Copy back buffer to RBO + glDisable(GL_SCISSOR_TEST); + glDisable(GL_FRAMEBUFFER_SRGB); + glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, _fbo[FBO_BACK]); + glReadBuffer(GL_BACK); + glDrawBuffer(GL_COLOR_ATTACHMENT0); + glBlitFramebuffer(0, 0, _width, _height, 0, 0, _width, _height, GL_COLOR_BUFFER_BIT, GL_NEAREST); + + // Copy depth from FBO to depth texture + glBindFramebuffer(GL_READ_FRAMEBUFFER, _depth_source != 0 ? _fbo[FBO_DEPTH] : 0); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, _fbo[FBO_BLIT]); + glBlitFramebuffer(0, 0, _width, _height, 0, 0, _width, _height, GL_DEPTH_BUFFER_BIT, GL_NEAREST); + + // Set clip space to something consistent + if (gl3wProcs.gl.ClipControl != nullptr) + glClipControl(GL_LOWER_LEFT, GL_ZERO_TO_ONE); + + update_and_render_effects(); + + // Copy results from RBO to back buffer + glDisable(GL_SCISSOR_TEST); + glDisable(GL_FRAMEBUFFER_SRGB); + glBindFramebuffer(GL_READ_FRAMEBUFFER, _fbo[FBO_BACK]); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); + glReadBuffer(GL_COLOR_ATTACHMENT0); + glDrawBuffer(GL_BACK); + glBlitFramebuffer(0, 0, _width, _height, 0, 0, _width, _height, GL_COLOR_BUFFER_BIT, GL_NEAREST); + + runtime::on_present(); + + // Apply previous state from application + _app_state.apply(); +} + +void reshade::opengl::runtime_opengl::on_draw_call(unsigned int vertices) +{ + _vertices += vertices; + _drawcalls += 1; + + GLint object = 0; + GLint target = GL_NONE; + glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &object); + if (object != 0) { // Zero is valid too, in which case the default depth buffer is referenced, instead of a FBO + glGetFramebufferAttachmentParameteriv(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE, &target); + if (target == GL_NONE) return; + glGetFramebufferAttachmentParameteriv(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME, &object); + } + + const auto it = _depth_source_table.find(object | (target == GL_RENDERBUFFER ? 0x80000000 : 0)); + if (it != _depth_source_table.end()) + { + it->second.num_vertices += vertices; + it->second.num_drawcalls = _drawcalls; + } +} +void reshade::opengl::runtime_opengl::on_fbo_attachment(GLenum attachment, GLenum target, GLuint object, GLint level) +{ + if (object == 0 || (attachment != GL_DEPTH_ATTACHMENT && attachment != GL_DEPTH_STENCIL_ATTACHMENT)) + return; + + const GLuint id = object | (target == GL_RENDERBUFFER ? 0x80000000 : 0); + + if (_depth_source_table.find(id) != _depth_source_table.end()) + return; + + depth_source_info info = { 0, 0, level, GL_NONE }; + + if (target == GL_RENDERBUFFER) + { + GLint previous_rbo = 0; + glGetIntegerv(GL_RENDERBUFFER_BINDING, &previous_rbo); + + // Get depth stencil parameters from RBO + glBindRenderbuffer(GL_RENDERBUFFER, object); + glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_WIDTH, reinterpret_cast(&info.width)); + glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_HEIGHT, reinterpret_cast(&info.height)); + glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_INTERNAL_FORMAT, &info.format); + + glBindRenderbuffer(GL_RENDERBUFFER, previous_rbo); + } + else + { + const auto target_to_binding = [](GLenum target) -> GLenum { + switch (target) + { + default: + case GL_TEXTURE_2D: + return GL_TEXTURE_BINDING_2D; + case GL_TEXTURE_2D_ARRAY: + return GL_TEXTURE_BINDING_2D_ARRAY; + case GL_TEXTURE_2D_MULTISAMPLE: + return GL_TEXTURE_BINDING_2D_MULTISAMPLE; + case GL_TEXTURE_2D_MULTISAMPLE_ARRAY: + return GL_TEXTURE_BINDING_2D_MULTISAMPLE_ARRAY; + case GL_TEXTURE_3D: + return GL_TEXTURE_BINDING_3D; + case GL_TEXTURE_CUBE_MAP: + case GL_TEXTURE_CUBE_MAP_NEGATIVE_X: + case GL_TEXTURE_CUBE_MAP_NEGATIVE_Y: + case GL_TEXTURE_CUBE_MAP_NEGATIVE_Z: + case GL_TEXTURE_CUBE_MAP_POSITIVE_X: + case GL_TEXTURE_CUBE_MAP_POSITIVE_Y: + case GL_TEXTURE_CUBE_MAP_POSITIVE_Z: + return GL_TEXTURE_BINDING_CUBE_MAP; + case GL_TEXTURE_CUBE_MAP_ARRAY: + return GL_TEXTURE_BINDING_CUBE_MAP_ARRAY; + } + }; + + GLint previous_tex = 0; + glGetIntegerv(target_to_binding(target), &previous_tex); + + // Get depth stencil parameters from texture + glBindTexture(target, object); + glGetTexLevelParameteriv(target, level, GL_TEXTURE_WIDTH, reinterpret_cast(&info.width)); + glGetTexLevelParameteriv(target, level, GL_TEXTURE_HEIGHT, reinterpret_cast(&info.height)); + glGetTexLevelParameteriv(target, level, GL_TEXTURE_INTERNAL_FORMAT, &info.format); + + glBindTexture(target, previous_tex); + } + + _depth_source_table.emplace(id, info); +} + +void reshade::opengl::runtime_opengl::capture_screenshot(uint8_t *buffer) const +{ + glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); + glReadBuffer(GL_BACK); + glReadPixels(0, 0, GLsizei(_width), GLsizei(_height), GL_RGBA, GL_UNSIGNED_BYTE, buffer); + + // Flip image horizontally + for (unsigned int y = 0, pitch = _width * 4; y * 2 < _height; ++y) + { + const auto i1 = y * pitch; + const auto i2 = (_height - 1 - y) * pitch; + + for (unsigned int x = 0; x < pitch; x += 4) + { + buffer[i1 + x + 3] = 0xFF; // Clear alpha channel + buffer[i2 + x + 3] = 0xFF; + + std::swap(buffer[i1 + x + 0], buffer[i2 + x + 0]); + std::swap(buffer[i1 + x + 1], buffer[i2 + x + 1]); + std::swap(buffer[i1 + x + 2], buffer[i2 + x + 2]); + } + } +} + +bool reshade::opengl::runtime_opengl::init_texture(texture &texture) +{ + texture.impl = std::make_unique(); + + if (texture.impl_reference != texture_reference::none) + return update_texture_reference(texture); + + GLenum internalformat = GL_RGBA8, internalformat_srgb = GL_SRGB8_ALPHA8; + + switch (texture.format) + { + case reshadefx::texture_format::r8: + internalformat = internalformat_srgb = GL_R8; + break; + case reshadefx::texture_format::r16f: + internalformat = internalformat_srgb = GL_R16F; + break; + case reshadefx::texture_format::r32f: + internalformat = internalformat_srgb = GL_R32F; + break; + case reshadefx::texture_format::rg8: + internalformat = internalformat_srgb = GL_RG8; + break; + case reshadefx::texture_format::rg16: + internalformat = internalformat_srgb = GL_RG16; + break; + case reshadefx::texture_format::rg16f: + internalformat = internalformat_srgb = GL_RG16F; + break; + case reshadefx::texture_format::rg32f: + internalformat = internalformat_srgb = GL_RG32F; + break; + case reshadefx::texture_format::rgba8: + internalformat = GL_RGBA8; + internalformat_srgb = GL_SRGB8_ALPHA8; + break; + case reshadefx::texture_format::rgba16: + internalformat = internalformat_srgb = GL_RGBA16; + break; + case reshadefx::texture_format::rgba16f: + internalformat = internalformat_srgb = GL_RGBA16F; + break; + case reshadefx::texture_format::rgba32f: + internalformat = internalformat_srgb = GL_RGBA32F; + break; + case reshadefx::texture_format::rgb10a2: + internalformat = internalformat_srgb = GL_RGB10_A2; + break; + } + + const auto texture_data = texture.impl->as(); + texture_data->should_delete = true; + + GLint previous_tex = 0; + glGetIntegerv(GL_TEXTURE_BINDING_2D, &previous_tex); + GLint previous_fbo = 0; + glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &previous_fbo); + + glGenTextures(2, texture_data->id); + glBindTexture(GL_TEXTURE_2D, texture_data->id[0]); + glTexStorage2D(GL_TEXTURE_2D, texture.levels, internalformat, texture.width, texture.height); + glTextureView(texture_data->id[1], GL_TEXTURE_2D, texture_data->id[0], internalformat_srgb, 0, texture.levels, 0, 1); + + // Clear texture to black since by default its contents are undefined + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, _fbo[FBO_BLIT]); + glFramebufferTexture(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, texture_data->id[0], 0); + assert(glCheckFramebufferStatus(GL_DRAW_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE); + glDrawBuffer(GL_COLOR_ATTACHMENT1); + const GLuint clear_color[4] = { 0, 0, 0, 0 }; + glClearBufferuiv(GL_COLOR, 0, clear_color); + glFramebufferTexture(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, 0, 0); + + // Apply previous state from application + glBindTexture(GL_TEXTURE_2D, previous_tex); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, previous_fbo); + + return true; +} +void reshade::opengl::runtime_opengl::upload_texture(texture &texture, const uint8_t *pixels) +{ + assert(texture.impl_reference == texture_reference::none && pixels != nullptr); + + // Flip image data horizontally + const uint32_t pitch = texture.width * 4; + std::vector data_flipped(pixels, pixels + pitch * texture.height); + const auto temp = static_cast(alloca(pitch)); + + for (uint32_t y = 0; 2 * y < texture.height; y++) + { + const auto line1 = data_flipped.data() + pitch * (y); + const auto line2 = data_flipped.data() + pitch * (texture.height - 1 - y); + + std::memcpy(temp, line1, pitch); + std::memcpy(line1, line2, pitch); + std::memcpy(line2, temp, pitch); + } + + const auto texture_impl = texture.impl->as(); + assert(texture_impl != nullptr); + + // Unset any existing unpack buffer so pointer is not interpreted as offset + glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); + + // Clear pixel storage modes to defaults (texture uploads can break otherwise) + glPixelStorei(GL_UNPACK_LSB_FIRST, GL_FALSE); + glPixelStorei(GL_UNPACK_SWAP_BYTES, GL_FALSE); + glPixelStorei(GL_UNPACK_ALIGNMENT, 4); // RGBA data is 4-byte aligned + glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); + glPixelStorei(GL_UNPACK_IMAGE_HEIGHT, 0); + glPixelStorei(GL_UNPACK_SKIP_ROWS, 0); + glPixelStorei(GL_UNPACK_SKIP_PIXELS, 0); + glPixelStorei(GL_UNPACK_SKIP_IMAGES, 0); + + // Bind and update texture + GLint previous_tex = 0; + glGetIntegerv(GL_TEXTURE_BINDING_2D, &previous_tex); + + glBindTexture(GL_TEXTURE_2D, texture_impl->id[0]); + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, texture.width, texture.height, GL_RGBA, GL_UNSIGNED_BYTE, data_flipped.data()); + + if (texture.levels > 1) + glGenerateMipmap(GL_TEXTURE_2D); + + // Apply previous state from application + glBindTexture(GL_TEXTURE_2D, previous_tex); +} +bool reshade::opengl::runtime_opengl::update_texture_reference(texture &texture) +{ + GLuint new_reference[2] = {}; + + switch (texture.impl_reference) + { + case texture_reference::back_buffer: + new_reference[0] = _tex[TEX_BACK]; + new_reference[1] = _tex[TEX_BACK_SRGB]; + break; + case texture_reference::depth_buffer: + new_reference[0] = _tex[TEX_DEPTH]; + new_reference[1] = _tex[TEX_DEPTH]; + break; + default: + return false; + } + + const auto texture_impl = texture.impl->as(); + assert(texture_impl != nullptr); + + if (texture_impl->id[0] == new_reference[0] && + texture_impl->id[1] == new_reference[1]) + return true; + + if (texture_impl->should_delete) + glDeleteTextures(2, texture_impl->id); + + texture_impl->id[0] = new_reference[0]; + texture_impl->id[1] = new_reference[1]; + texture_impl->should_delete = false; + + return true; +} +void reshade::opengl::runtime_opengl::update_texture_references(texture_reference type) +{ + for (auto &tex : _textures) + if (tex.impl != nullptr && tex.impl_reference == type) + update_texture_reference(tex); +} + +bool reshade::opengl::runtime_opengl::compile_effect(effect_data &effect) +{ + assert(_app_state.has_state); // Make sure all binds below are reset later when application state is restored + + // Add specialization constant defines to source code +#if 0 + std::vector spec_constants; + std::vector spec_constant_values; + for (const auto &constant : module.spec_constants) + { + spec_constants.push_back(constant.offset); + spec_constant_values.push_back(constant.initializer_value.as_uint[0]); + } +#else + effect.preamble = "#version 430\n" + effect.preamble; +#endif + + std::unordered_map entry_points; + + // Compile all entry points + for (const auto &entry_point : effect.module.entry_points) + { + GLuint shader_id = glCreateShader(entry_point.second ? GL_FRAGMENT_SHADER : GL_VERTEX_SHADER); + entry_points[entry_point.first] = shader_id; + +#if 0 + glShaderBinary(1, &shader_id, GL_SHADER_BINARY_FORMAT_SPIR_V, module.spirv.data(), module.spirv.size() * sizeof(uint32_t)); + glSpecializeShader(shader_id, entry_point.first.c_str(), GLuint(spec_constants.size()), spec_constants.data(), spec_constant_values.data()); +#else + std::string defines = effect.preamble; + defines += "#define ENTRY_POINT_" + entry_point.first + " 1\n"; + if (!entry_point.second) // OpenGL does not allow using 'discard' in the vertex shader profile + defines += "#define discard\n" + "#define dFdx(x) x\n" // 'dFdx', 'dFdx' and 'fwidth' too are only available in fragment shaders + "#define dFdy(y) y\n" + "#define fwidth(p) p\n"; + + GLsizei lengths[] = { static_cast(defines.size()), static_cast(effect.module.hlsl.size()) }; + const GLchar *sources[] = { defines.c_str(), effect.module.hlsl.c_str() }; + glShaderSource(shader_id, 2, sources, lengths); + glCompileShader(shader_id); +#endif + + GLint status = GL_FALSE; + glGetShaderiv(shader_id, GL_COMPILE_STATUS, &status); + if (GL_FALSE == status) + { + GLint log_size = 0; + glGetShaderiv(shader_id, GL_INFO_LOG_LENGTH, &log_size); + std::string log(log_size, '\0'); + glGetShaderInfoLog(shader_id, log_size, nullptr, log.data()); + + effect.errors += log; + + for (auto &it : entry_points) + glDeleteShader(it.second); + + // No need to setup resources if any of the shaders failed to compile + return false; + } + } + + if (effect.storage_size != 0) + { + GLuint ubo = 0; + glGenBuffers(1, &ubo); + glBindBuffer(GL_UNIFORM_BUFFER, ubo); + glBufferData(GL_UNIFORM_BUFFER, effect.storage_size, _uniform_data_storage.data() + effect.storage_offset, GL_DYNAMIC_DRAW); + + _effect_ubos.emplace_back(ubo, effect.storage_size); + } + + bool success = true; + + opengl_technique_data technique_init; + technique_init.uniform_storage_index = _effect_ubos.size() - 1; + technique_init.uniform_storage_offset = effect.storage_offset; + + for (const reshadefx::sampler_info &info : effect.module.samplers) + success &= add_sampler(info, technique_init); + + for (technique &technique : _techniques) + if (technique.impl == nullptr && technique.effect_index == effect.index) + success &= init_technique(technique, technique_init, entry_points, effect.errors); + + for (auto &it : entry_points) + glDeleteShader(it.second); + + return success; +} +void reshade::opengl::runtime_opengl::unload_effects() +{ + runtime::unload_effects(); + + for (const auto &info : _effect_ubos) + glDeleteBuffers(1, &info.first); + _effect_ubos.clear(); + + for (const auto &info : _effect_sampler_states) + glDeleteSamplers(1, &info.second); + _effect_sampler_states.clear(); +} + +bool reshade::opengl::runtime_opengl::add_sampler(const reshadefx::sampler_info &info, opengl_technique_data &technique_init) +{ + const auto existing_texture = std::find_if(_textures.begin(), _textures.end(), + [&texture_name = info.texture_name](const auto &item) { + return item.unique_name == texture_name && item.impl != nullptr; + }); + if (existing_texture == _textures.end()) + return false; + + // Hash sampler state to avoid duplicated sampler objects + size_t hash = 2166136261; + hash = (hash * 16777619) ^ static_cast(info.address_u); + hash = (hash * 16777619) ^ static_cast(info.address_v); + hash = (hash * 16777619) ^ static_cast(info.address_w); + hash = (hash * 16777619) ^ static_cast(info.filter); + hash = (hash * 16777619) ^ reinterpret_cast(info.lod_bias); + hash = (hash * 16777619) ^ reinterpret_cast(info.min_lod); + hash = (hash * 16777619) ^ reinterpret_cast(info.max_lod); + + auto it = _effect_sampler_states.find(hash); + + if (it == _effect_sampler_states.end()) + { + GLenum minfilter = GL_NONE, magfilter = GL_NONE; + + switch (info.filter) + { + case reshadefx::texture_filter::min_mag_mip_point: + minfilter = GL_NEAREST_MIPMAP_NEAREST; + magfilter = GL_NEAREST; + break; + case reshadefx::texture_filter::min_mag_point_mip_linear: + minfilter = GL_NEAREST_MIPMAP_LINEAR; + magfilter = GL_NEAREST; + break; + case reshadefx::texture_filter::min_point_mag_linear_mip_point: + minfilter = GL_NEAREST_MIPMAP_NEAREST; + magfilter = GL_LINEAR; + break; + case reshadefx::texture_filter::min_point_mag_mip_linear: + minfilter = GL_NEAREST_MIPMAP_LINEAR; + magfilter = GL_LINEAR; + break; + case reshadefx::texture_filter::min_linear_mag_mip_point: + minfilter = GL_LINEAR_MIPMAP_NEAREST; + magfilter = GL_NEAREST; + break; + case reshadefx::texture_filter::min_linear_mag_point_mip_linear: + minfilter = GL_LINEAR_MIPMAP_LINEAR; + magfilter = GL_NEAREST; + break; + case reshadefx::texture_filter::min_mag_linear_mip_point: + minfilter = GL_LINEAR_MIPMAP_NEAREST; + magfilter = GL_LINEAR; + break; + case reshadefx::texture_filter::min_mag_mip_linear: + minfilter = GL_LINEAR_MIPMAP_LINEAR; + magfilter = GL_LINEAR; + break; + } + + const auto convert_address_mode = [](reshadefx::texture_address_mode value) { + switch (value) + { + case reshadefx::texture_address_mode::wrap: + return GL_REPEAT; + case reshadefx::texture_address_mode::mirror: + return GL_MIRRORED_REPEAT; + case reshadefx::texture_address_mode::clamp: + return GL_CLAMP_TO_EDGE; + case reshadefx::texture_address_mode::border: + return GL_CLAMP_TO_BORDER; + default: + return GL_NONE; + } + }; + + GLuint sampler_id = 0; + glGenSamplers(1, &sampler_id); + glSamplerParameteri(sampler_id, GL_TEXTURE_WRAP_S, convert_address_mode(info.address_u)); + glSamplerParameteri(sampler_id, GL_TEXTURE_WRAP_T, convert_address_mode(info.address_v)); + glSamplerParameteri(sampler_id, GL_TEXTURE_WRAP_R, convert_address_mode(info.address_w)); + glSamplerParameteri(sampler_id, GL_TEXTURE_MAG_FILTER, magfilter); + glSamplerParameteri(sampler_id, GL_TEXTURE_MIN_FILTER, minfilter); + glSamplerParameterf(sampler_id, GL_TEXTURE_LOD_BIAS, info.lod_bias); + glSamplerParameterf(sampler_id, GL_TEXTURE_MIN_LOD, info.min_lod); + glSamplerParameterf(sampler_id, GL_TEXTURE_MAX_LOD, info.max_lod); + + it = _effect_sampler_states.emplace(hash, sampler_id).first; + } + + opengl_sampler_data sampler; + sampler.id = it->second; + sampler.texture = existing_texture->impl->as(); + sampler.is_srgb = info.srgb; + sampler.has_mipmaps = existing_texture->levels > 1; + + technique_init.samplers.resize(std::max(technique_init.samplers.size(), size_t(info.binding + 1))); + + technique_init.samplers[info.binding] = std::move(sampler); + + return true; +} +bool reshade::opengl::runtime_opengl::init_technique(technique &technique, const opengl_technique_data &impl_init, const std::unordered_map &entry_points, std::string &errors) +{ + assert(_app_state.has_state); + + // Copy construct new technique implementation instead of move because effect may contain multiple techniques + technique.impl = std::make_unique(impl_init); + + const auto technique_data = technique.impl->as(); + + glGenQueries(1, &technique_data->query); + + for (size_t i = 0; i < technique.passes.size(); ++i) + { + technique.passes_data.push_back(std::make_unique()); + + auto &pass = *technique.passes_data.back()->as(); + const auto &pass_info = technique.passes[i]; + + const auto literal_to_blend_eq = [](unsigned int value) -> GLenum { + switch (value) + { + default: + case 1: return GL_FUNC_ADD; + case 2: return GL_FUNC_SUBTRACT; + case 3: return GL_FUNC_REVERSE_SUBTRACT; + case 4: return GL_MIN; + case 5: return GL_MAX; + } + }; + const auto literal_to_blend_func = [](unsigned int value) -> GLenum { + switch (value) + { + default: + case 0: return GL_ZERO; + case 1: return GL_ONE; + case 2: return GL_SRC_COLOR; + case 3: return GL_SRC_ALPHA; + case 4: return GL_ONE_MINUS_SRC_COLOR; + case 5: return GL_ONE_MINUS_SRC_ALPHA; + case 8: return GL_DST_COLOR; + case 6: return GL_DST_ALPHA; + case 9: return GL_ONE_MINUS_DST_COLOR; + case 7: return GL_ONE_MINUS_DST_ALPHA; + } + }; + const auto literal_to_comp_func = [](unsigned int value) -> GLenum { + switch (value) + { + default: + case 8: return GL_ALWAYS; + case 1: return GL_NEVER; + case 3: return GL_EQUAL; + case 6: return GL_NOTEQUAL; + case 2: return GL_LESS; + case 4: return GL_LEQUAL; + case 5: return GL_GREATER; + case 7: return GL_GEQUAL; + } + }; + const auto literal_to_stencil_op = [](unsigned int value) -> GLenum { + switch (value) + { + default: + case 1: return GL_KEEP; + case 0: return GL_ZERO; + case 3: return GL_REPLACE; + case 7: return GL_INCR_WRAP; + case 4: return GL_INCR; + case 8: return GL_DECR_WRAP; + case 5: return GL_DECR; + case 6: return GL_INVERT; + } + }; + + pass.blend_eq_color = literal_to_blend_eq(pass_info.blend_op); + pass.blend_eq_alpha = literal_to_blend_eq(pass_info.blend_op_alpha); + pass.blend_src = literal_to_blend_func(pass_info.src_blend); + pass.blend_dest = literal_to_blend_func(pass_info.dest_blend); + pass.blend_src_alpha = literal_to_blend_func(pass_info.src_blend_alpha); + pass.blend_dest_alpha = literal_to_blend_func(pass_info.dest_blend_alpha); + pass.stencil_func = literal_to_comp_func(pass_info.stencil_comparison_func); + pass.stencil_op_z_pass = literal_to_stencil_op(pass_info.stencil_op_pass); + pass.stencil_op_fail = literal_to_stencil_op(pass_info.stencil_op_fail); + pass.stencil_op_z_fail = literal_to_stencil_op(pass_info.stencil_op_depth_fail); + + glGenFramebuffers(1, &pass.fbo); + glBindFramebuffer(GL_FRAMEBUFFER, pass.fbo); + + bool backbuffer_fbo = true; + + for (unsigned int k = 0; k < 8; ++k) + { + if (pass_info.render_target_names[k].empty()) + continue; // Skip unbound render targets + + const auto render_target_texture = std::find_if(_textures.begin(), _textures.end(), + [&render_target = pass_info.render_target_names[k]](const auto &item) { + return item.unique_name == render_target; + }); + if (render_target_texture == _textures.end()) + return assert(false), false; + + backbuffer_fbo = false; + + const auto texture_impl = render_target_texture->impl->as(); + assert(texture_impl != nullptr); + + glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + k, texture_impl->id[pass_info.srgb_write_enable], 0); + + pass.draw_targets[k] = GL_COLOR_ATTACHMENT0 + k; + pass.draw_textures[k] = texture_impl->id[pass_info.srgb_write_enable]; + } + + if (backbuffer_fbo) + { + glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, _rbo[RBO_COLOR]); + + pass.draw_targets[0] = GL_COLOR_ATTACHMENT0; + pass.draw_textures[0] = _tex[TEX_BACK_SRGB]; + + pass.viewport_width = static_cast(_width); + pass.viewport_height = static_cast(_height); + } + else + { + // Effect compiler sets the viewport to the render target dimensions + pass.viewport_width = pass_info.viewport_width; + pass.viewport_height = pass_info.viewport_height; + } + + assert(pass.viewport_width != 0); + assert(pass.viewport_height != 0); + + if (pass.viewport_width == GLsizei(_width) && pass.viewport_height == GLsizei(_height)) + { + // Only attach depth-stencil when viewport matches back buffer or else the frame buffer will always be resized to those dimensions + glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, _rbo[RBO_DEPTH]); + } + + assert(glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE); + + // Link program from input shaders + pass.program = glCreateProgram(); + const GLuint vs_shader_id = entry_points.at(pass_info.vs_entry_point); + const GLuint fs_shader_id = entry_points.at(pass_info.ps_entry_point); + glAttachShader(pass.program, vs_shader_id); + glAttachShader(pass.program, fs_shader_id); + glLinkProgram(pass.program); + glDetachShader(pass.program, vs_shader_id); + glDetachShader(pass.program, fs_shader_id); + + GLint status = GL_FALSE; + glGetProgramiv(pass.program, GL_LINK_STATUS, &status); + if (GL_FALSE == status) + { + GLint log_size = 0; + glGetProgramiv(pass.program, GL_INFO_LOG_LENGTH, &log_size); + std::string log(log_size, '\0'); + glGetProgramInfoLog(pass.program, log_size, nullptr, log.data()); + + errors += log; + + LOG(ERROR) << "Failed to link program for pass " << i << " in technique '" << technique.name << "'."; + return false; + } + } + + return true; +} + +void reshade::opengl::runtime_opengl::render_technique(technique &technique) +{ + assert(_app_state.has_state); + + opengl_technique_data &technique_data = *technique.impl->as(); + + if (technique_data.query_in_flight) + { + GLuint64 elapsed_time = 0; + glGetQueryObjectui64v(technique_data.query, GL_QUERY_RESULT, &elapsed_time); + technique.average_gpu_duration.append(elapsed_time); + technique_data.query_in_flight = false; // Reset query status + } + + if (!technique_data.query_in_flight) + glBeginQuery(GL_TIME_ELAPSED, technique_data.query); + + // Clear depth stencil + glBindFramebuffer(GL_FRAMEBUFFER, _fbo[FBO_BACK]); + glClearBufferfi(GL_DEPTH_STENCIL, 0, 1.0f, 0); + + // Set up global states + glDisable(GL_CULL_FACE); + glDisable(GL_DEPTH_TEST); + glDisable(GL_SCISSOR_TEST); + glFrontFace(GL_CCW); + glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); + glDepthMask(GL_FALSE); // No need to write to the depth buffer + glBindVertexArray(_vao[VAO_FX]); // This is an empty vertex array object + + // Set up shader constants + if (technique_data.uniform_storage_index >= 0) + { + glBindBufferBase(GL_UNIFORM_BUFFER, 0, _effect_ubos[technique_data.uniform_storage_index].first); + glBufferSubData(GL_UNIFORM_BUFFER, 0, _effect_ubos[technique_data.uniform_storage_index].second, _uniform_data_storage.data() + technique_data.uniform_storage_offset); + } + + // Set up shader resources + for (size_t i = 0; i < technique_data.samplers.size(); i++) + { + glActiveTexture(GL_TEXTURE0 + GLenum(i)); + glBindTexture(GL_TEXTURE_2D, technique_data.samplers[i].texture->id[technique_data.samplers[i].is_srgb]); + glBindSampler(GLuint(i), technique_data.samplers[i].id); + } + + for (size_t i = 0; i < technique.passes.size(); ++i) + { + const auto &pass = *technique.passes_data[i]->as(); + const auto &pass_info = technique.passes[i]; + + // Copy back buffer of previous pass to texture + glDisable(GL_FRAMEBUFFER_SRGB); + glBindFramebuffer(GL_READ_FRAMEBUFFER, _fbo[FBO_BACK]); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, _fbo[FBO_BLIT]); + glReadBuffer(GL_COLOR_ATTACHMENT0); + glDrawBuffer(GL_COLOR_ATTACHMENT0); + glBlitFramebuffer(0, 0, _width, _height, 0, 0, _width, _height, GL_COLOR_BUFFER_BIT, GL_NEAREST); + + // Set up pass specific state + glViewport(0, 0, pass.viewport_width, pass.viewport_height); + glUseProgram(pass.program); + glBindFramebuffer(GL_FRAMEBUFFER, pass.fbo); + glDrawBuffers(8, pass.draw_targets); + + if (pass_info.blend_enable) { + glEnable(GL_BLEND); + glBlendFuncSeparate(pass.blend_src, pass.blend_dest, pass.blend_src_alpha, pass.blend_dest_alpha); + glBlendEquationSeparate(pass.blend_eq_color, pass.blend_eq_alpha); + } + else { + glDisable(GL_BLEND); + } + + if (pass_info.stencil_enable) { + glEnable(GL_STENCIL_TEST); + glStencilOp(pass.stencil_op_fail, pass.stencil_op_z_fail, pass.stencil_op_z_pass); + glStencilMask(pass_info.stencil_write_mask); + glStencilFunc(pass.stencil_func, pass_info.stencil_reference_value, pass_info.stencil_read_mask); + } + else { + glDisable(GL_STENCIL_TEST); + } + + if (pass_info.srgb_write_enable) { + glEnable(GL_FRAMEBUFFER_SRGB); + } else { + glDisable(GL_FRAMEBUFFER_SRGB); + } + + glColorMask( + (pass_info.color_write_mask & (1 << 0)) != 0, + (pass_info.color_write_mask & (1 << 1)) != 0, + (pass_info.color_write_mask & (1 << 2)) != 0, + (pass_info.color_write_mask & (1 << 3)) != 0); + + if (pass_info.clear_render_targets) + for (GLuint k = 0; k < 8; k++) + { + if (pass.draw_targets[k] == GL_NONE) + continue; // Ignore unbound render targets + const GLfloat color[4] = { 0.0f, 0.0f, 0.0f, 0.0f }; + glClearBufferfv(GL_COLOR, k, color); + } + + glDrawArrays(GL_TRIANGLES, 0, 3); + + _vertices += 3; + _drawcalls += 1; + + // Regenerate mipmaps of any textures bound as render target + for (GLuint texture_id : pass.draw_textures) + { + for (size_t k = 0; k < technique_data.samplers.size(); ++k) + { + const auto texture = technique_data.samplers[k].texture; + if (technique_data.samplers[k].has_mipmaps && + (texture->id[0] == texture_id || texture->id[1] == texture_id)) + { + glActiveTexture(GL_TEXTURE0 + GLenum(k)); + glGenerateMipmap(GL_TEXTURE_2D); + } + } + } + } + + if (!technique_data.query_in_flight) + glEndQuery(GL_TIME_ELAPSED); + technique_data.query_in_flight = true; +} + +#if RESHADE_GUI +void reshade::opengl::runtime_opengl::init_imgui_resources() +{ + assert(_app_state.has_state); + + const GLchar *vertex_shader[] = { + "#version 330\n" + "uniform mat4 ProjMtx;\n" + "in vec2 Position, UV;\n" + "in vec4 Color;\n" + "out vec2 Frag_UV;\n" + "out vec4 Frag_Color;\n" + "void main()\n" + "{\n" + " Frag_UV = UV * vec2(1.0, -1.0) + vec2(0.0, 1.0);\n" // Texture coordinates were flipped in 'update_texture' + " Frag_Color = Color;\n" + " gl_Position = ProjMtx * vec4(Position.xy, 0, 1);\n" + "}\n" + }; + const GLchar *fragment_shader[] = { + "#version 330\n" + "uniform sampler2D Texture;\n" + "in vec2 Frag_UV;\n" + "in vec4 Frag_Color;\n" + "out vec4 Out_Color;\n" + "void main()\n" + "{\n" + " Out_Color = Frag_Color * texture(Texture, Frag_UV.st);\n" + "}\n" + }; + + const GLuint imgui_vs = glCreateShader(GL_VERTEX_SHADER); + glShaderSource(imgui_vs, 1, vertex_shader, 0); + glCompileShader(imgui_vs); + const GLuint imgui_fs = glCreateShader(GL_FRAGMENT_SHADER); + glShaderSource(imgui_fs, 1, fragment_shader, 0); + glCompileShader(imgui_fs); + + _imgui_program = glCreateProgram(); + glAttachShader(_imgui_program, imgui_vs); + glAttachShader(_imgui_program, imgui_fs); + glLinkProgram(_imgui_program); + glDeleteShader(imgui_vs); + glDeleteShader(imgui_fs); + + _imgui_uniform_tex = glGetUniformLocation(_imgui_program, "Texture"); + _imgui_uniform_proj = glGetUniformLocation(_imgui_program, "ProjMtx"); + + const int attrib_pos = glGetAttribLocation(_imgui_program, "Position"); + const int attrib_uv = glGetAttribLocation(_imgui_program, "UV"); + const int attrib_col = glGetAttribLocation(_imgui_program, "Color"); + + glBindBuffer(GL_ARRAY_BUFFER, _buf[VBO_IMGUI]); + glBindVertexArray(_vao[VAO_IMGUI]); + glEnableVertexAttribArray(attrib_pos); + glEnableVertexAttribArray(attrib_uv ); + glEnableVertexAttribArray(attrib_col); + glVertexAttribPointer(attrib_pos, 2, GL_FLOAT, GL_FALSE, sizeof(ImDrawVert), reinterpret_cast(offsetof(ImDrawVert, pos))); + glVertexAttribPointer(attrib_uv , 2, GL_FLOAT, GL_FALSE, sizeof(ImDrawVert), reinterpret_cast(offsetof(ImDrawVert, uv ))); + glVertexAttribPointer(attrib_col, 4, GL_UNSIGNED_BYTE, GL_TRUE, sizeof(ImDrawVert), reinterpret_cast(offsetof(ImDrawVert, col))); +} + +void reshade::opengl::runtime_opengl::render_imgui_draw_data(ImDrawData *draw_data) +{ + assert(_app_state.has_state); + + glDisable(GL_CULL_FACE); + glDisable(GL_DEPTH_TEST); + glFrontFace(GL_CCW); + glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glBlendEquation(GL_FUNC_ADD); + glEnable(GL_SCISSOR_TEST); + glDisable(GL_STENCIL_TEST); + glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); + glDepthMask(GL_FALSE); + glActiveTexture(GL_TEXTURE0); // Bind texture at location zero below + glUseProgram(_imgui_program); + glBindSampler(0, 0); // Do not use separate sampler object, since state is already set in texture + glBindVertexArray(_vao[VAO_IMGUI]); + + glViewport(0, 0, GLsizei(draw_data->DisplaySize.x), GLsizei(draw_data->DisplaySize.y)); + + const float ortho_projection[16] = { + 2.0f / draw_data->DisplaySize.x, 0.0f, 0.0f, 0.0f, + 0.0f, -2.0f / draw_data->DisplaySize.y, 0.0f, 0.0f, + 0.0f, 0.0f, -1.0f, 0.0f, + -(2 * draw_data->DisplayPos.x + draw_data->DisplaySize.x) / draw_data->DisplaySize.x, + +(2 * draw_data->DisplayPos.y + draw_data->DisplaySize.y) / draw_data->DisplaySize.y, 0.0f, 1.0f, + }; + + glUniform1i(_imgui_uniform_tex, 0); // Set to GL_TEXTURE0 + glUniformMatrix4fv(_imgui_uniform_proj, 1, GL_FALSE, ortho_projection); + + for (int n = 0; n < draw_data->CmdListsCount; ++n) + { + const ImDrawIdx *index_offset = 0; + ImDrawList *const draw_list = draw_data->CmdLists[n]; + + glBindBuffer(GL_ARRAY_BUFFER, _buf[VBO_IMGUI]); + glBufferData(GL_ARRAY_BUFFER, draw_list->VtxBuffer.Size * sizeof(ImDrawVert), draw_list->VtxBuffer.Data, GL_STREAM_DRAW); + + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _buf[IBO_IMGUI]); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, draw_list->IdxBuffer.Size * sizeof(ImDrawIdx), draw_list->IdxBuffer.Data, GL_STREAM_DRAW); + + for (const ImDrawCmd &cmd : draw_list->CmdBuffer) + { + assert(cmd.TextureId != 0); + assert(cmd.UserCallback == nullptr); + + const ImVec4 scissor_rect( + cmd.ClipRect.x - draw_data->DisplayPos.x, + cmd.ClipRect.y - draw_data->DisplayPos.y, + cmd.ClipRect.z - draw_data->DisplayPos.x, + cmd.ClipRect.w - draw_data->DisplayPos.y); + glScissor( + static_cast(scissor_rect.x), + static_cast(_height - scissor_rect.w), + static_cast(scissor_rect.z - scissor_rect.x), + static_cast(scissor_rect.w - scissor_rect.y)); + + glBindTexture(GL_TEXTURE_2D, static_cast(cmd.TextureId)->id[0]); + + glDrawElements(GL_TRIANGLES, cmd.ElemCount, sizeof(ImDrawIdx) == 2 ? GL_UNSIGNED_SHORT : GL_UNSIGNED_INT, index_offset); + + index_offset += cmd.ElemCount; + } + } +} +#endif + +void reshade::opengl::runtime_opengl::detect_depth_source() +{ + if (_framecount % 30) + return; // Only execute detection heuristic every 30 frames to avoid too frequent changes + + if (_has_high_network_activity) + { + _depth_source = 0; + glDeleteTextures(1, &_tex[TEX_DEPTH]); + _tex[TEX_DEPTH] = 0; + update_texture_references(texture_reference::depth_buffer); + return; + } + + assert(_app_state.has_state); + + GLuint best_match = 0; + depth_source_info best_info = _depth_source_table.at(0); // Always fall back to default depth buffer if no better match is found + + for (auto &it : _depth_source_table) + { + if (it.second.num_drawcalls == 0) + continue; // Skip candidates that were not used during rendering + + // Detection heuristic based on dimensions and usage + if (((it.second.width > _width * 0.95 && it.second.width < _width * 1.05) && (it.second.height > _height * 0.95 && it.second.height < _height * 1.05)) && + ((it.second.num_vertices * (1.2f - float(it.second.num_drawcalls) / _drawcalls)) >= (best_info.num_vertices * (1.2f - float(best_info.num_drawcalls) / _drawcalls)))) + { + best_info = it.second; + best_match = it.first; + } + + // Reset statistics for next frame + it.second.num_vertices = 0; + it.second.num_drawcalls = 0; + } + + if (_depth_source != best_match || !_tex[TEX_DEPTH]) + { + const auto &previous_info = _depth_source_table.at(_depth_source); + + // Resize depth texture if it dimensions have changed + if (best_info.width != previous_info.width || best_info.height != previous_info.height || best_info.format != previous_info.format || !_tex[TEX_DEPTH]) + { + if (best_info.format == GL_DEPTH_STENCIL) + best_info.format = GL_UNSIGNED_INT_24_8; + + // Recreate depth texture name (since the storage is immutable after the first call to glTexStorage) + glDeleteTextures(1, &_tex[TEX_DEPTH]); glGenTextures(1, &_tex[TEX_DEPTH]); + + glBindTexture(GL_TEXTURE_2D, _tex[TEX_DEPTH]); + glTexStorage2D(GL_TEXTURE_2D, 1, best_info.format, best_info.width, best_info.height); + + // Update FBO attachment + glBindFramebuffer(GL_FRAMEBUFFER, _fbo[FBO_BLIT]); + glFramebufferTexture(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, _tex[TEX_DEPTH], 0); + assert(glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE); + + update_texture_references(texture_reference::depth_buffer); + } + + _depth_source = best_match; + + if (best_match != 0) + { + glBindFramebuffer(GL_FRAMEBUFFER, _fbo[FBO_DEPTH]); + + if ((best_match & 0x80000000) == 0) { + glFramebufferTexture(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, best_match, best_info.level); + } else { + glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, best_match ^ 0x80000000); + } + + assert(glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE); + } + } +} diff --git a/msvc/source/opengl/runtime_opengl.hpp b/msvc/source/opengl/runtime_opengl.hpp new file mode 100644 index 0000000..9d62268 --- /dev/null +++ b/msvc/source/opengl/runtime_opengl.hpp @@ -0,0 +1,117 @@ +/** + * Copyright (C) 2014 Patrick Mours. All rights reserved. + * License: https://github.com/crosire/reshade#license + */ + +#pragma once + +#include "runtime.hpp" +#include "state_block.hpp" +#include + +namespace reshade { enum class texture_reference; } +namespace reshadefx { struct sampler_info; } + +namespace reshade::opengl +{ + class runtime_opengl : public runtime + { + public: + runtime_opengl(); + + bool on_init(HWND hwnd, unsigned int width, unsigned int height); + void on_reset(); + void on_present(); + + void on_draw_call(unsigned int vertices); + void on_fbo_attachment(GLenum attachment, GLenum target, GLuint object, GLint level); + + void capture_screenshot(uint8_t *buffer) const override; + + GLuint _current_vertex_count = 0; // Used to calculate vertex count inside glBegin/glEnd pairs + std::unordered_set _hdcs; + + private: + struct depth_source_info + { + unsigned int width, height; + GLint level, format; + unsigned int num_drawcalls, num_vertices; + }; + + bool init_texture(texture &info) override; + void upload_texture(texture &texture, const uint8_t *data) override; + bool update_texture_reference(texture &texture); + void update_texture_references(texture_reference type); + + bool compile_effect(effect_data &effect) override; + void unload_effects() override; + + bool add_sampler(const reshadefx::sampler_info &info, struct opengl_technique_data &technique_init); + bool init_technique(technique &info, const struct opengl_technique_data &technique_init, const std::unordered_map &entry_points, std::string &errors); + + void render_technique(technique &technique) override; + +#if RESHADE_GUI + void init_imgui_resources(); + void render_imgui_draw_data(ImDrawData *data) override; +#endif + + void detect_depth_source(); + + state_block _app_state; + GLuint _depth_source = 0; + std::unordered_map _depth_source_table; + + enum BUF + { +#if RESHADE_GUI + VBO_IMGUI, + IBO_IMGUI, +#endif + NUM_BUF + }; + enum TEX + { + TEX_BACK, + TEX_BACK_SRGB, + TEX_DEPTH, + NUM_TEX + }; + enum VAO + { + VAO_FX, +#if RESHADE_GUI + VAO_IMGUI, +#endif + NUM_VAO + }; + enum FBO + { + FBO_BACK, + FBO_DEPTH, + FBO_BLIT, + NUM_FBO + }; + enum RBO + { + RBO_COLOR, + RBO_DEPTH, + NUM_RBO + }; + + GLuint _buf[NUM_BUF] = {}; + GLuint _tex[NUM_TEX] = {}; + GLuint _vao[NUM_VAO] = {}; + GLuint _fbo[NUM_FBO] = {}; + GLuint _rbo[NUM_RBO] = {}; +#if RESHADE_GUI + GLuint _imgui_program = 0; + int _imgui_uniform_tex = 0; + int _imgui_uniform_proj = 0; +#endif + std::vector _reserved_texture_names; + std::unordered_map _effect_sampler_states; + std::vector> _effect_ubos; + }; +} diff --git a/msvc/source/opengl/state_block.cpp b/msvc/source/opengl/state_block.cpp new file mode 100644 index 0000000..8aa881a --- /dev/null +++ b/msvc/source/opengl/state_block.cpp @@ -0,0 +1,137 @@ +/** + * Copyright (C) 2014 Patrick Mours. All rights reserved. + * License: https://github.com/crosire/reshade#license + */ + +#include "state_block.hpp" + +reshade::opengl::state_block::state_block() +{ + memset(this, 0, sizeof(*this)); +} + +void reshade::opengl::state_block::capture() +{ +#ifndef NDEBUG + has_state = true; +#endif + + glGetIntegerv(GL_VERTEX_ARRAY_BINDING, &_vao); + glGetIntegerv(GL_ARRAY_BUFFER_BINDING, &_vbo); + glGetIntegerv(GL_UNIFORM_BUFFER_BINDING, &_ubo); + glGetIntegerv(GL_CURRENT_PROGRAM, &_program); + + glGetIntegerv(GL_ACTIVE_TEXTURE, &_active_texture); + for (GLuint i = 0; i < 32; i++) + { + glActiveTexture(GL_TEXTURE0 + i); + glGetIntegerv(GL_TEXTURE_BINDING_2D, &_textures2d[i]); + glGetIntegerv(GL_SAMPLER_BINDING, &_samplers[i]); + } + + glGetIntegerv(GL_VIEWPORT, _viewport); + glGetIntegerv(GL_SCISSOR_BOX, _scissor_rect); + _scissor_test = glIsEnabled(GL_SCISSOR_TEST); + _blend = glIsEnabled(GL_BLEND); + glGetIntegerv(GL_BLEND_SRC, &_blend_src); + glGetIntegerv(GL_BLEND_DST, &_blend_dest); + glGetIntegerv(GL_BLEND_EQUATION_RGB, &_blend_eq_color); + glGetIntegerv(GL_BLEND_EQUATION_ALPHA, &_blend_eq_alpha); + _depth_test = glIsEnabled(GL_DEPTH_TEST); + glGetBooleanv(GL_DEPTH_WRITEMASK, &_depth_mask); + glGetIntegerv(GL_DEPTH_FUNC, &_depth_func); + _stencil_test = glIsEnabled(GL_STENCIL_TEST); + glGetIntegerv(GL_STENCIL_REF, &_stencil_ref); + glGetIntegerv(GL_STENCIL_FUNC, &_stencil_func); + glGetIntegerv(GL_STENCIL_FAIL, &_stencil_op_fail); + glGetIntegerv(GL_STENCIL_PASS_DEPTH_FAIL, &_stencil_op_zfail); + glGetIntegerv(GL_STENCIL_PASS_DEPTH_PASS, &_stencil_op_zpass); + glGetIntegerv(GL_STENCIL_VALUE_MASK, &_stencil_read_mask); + glGetIntegerv(GL_STENCIL_WRITEMASK, &_stencil_mask); + glGetIntegerv(GL_POLYGON_MODE, &_polygon_mode); + glGetIntegerv(GL_FRONT_FACE, &_frontface); + _cullface = glIsEnabled(GL_CULL_FACE); + glGetIntegerv(GL_CULL_FACE_MODE, &_cullface_mode); + glGetIntegerv(GL_FRAMEBUFFER_BINDING, &_fbo); + _srgb = glIsEnabled(GL_FRAMEBUFFER_SRGB); + glGetBooleanv(GL_COLOR_WRITEMASK, _color_mask); + + for (GLuint i = 0; i < 8; i++) + { + GLint drawbuffer = GL_NONE; + glGetIntegerv(GL_DRAW_BUFFER0 + i, &drawbuffer); + _drawbuffers[i] = static_cast(drawbuffer); + } + + if (gl3wProcs.gl.ClipControl != nullptr) + { + glGetIntegerv(GL_CLIP_ORIGIN, &clip_origin); + glGetIntegerv(GL_CLIP_DEPTH_MODE, &clip_depthmode); + } +} +void reshade::opengl::state_block::apply() const +{ +#ifndef NDEBUG + has_state = false; +#endif + + glBindVertexArray(_vao); + glBindBuffer(GL_ARRAY_BUFFER, _vbo); + glBindBuffer(GL_UNIFORM_BUFFER, _ubo); + glUseProgram(_program); + + for (GLuint i = 0; i < 32; i++) + { + glActiveTexture(GL_TEXTURE0 + i); + glBindTexture(GL_TEXTURE_2D, _textures2d[i]); + glBindSampler(i, _samplers[i]); + } + + glActiveTexture(_active_texture); + glViewport(_viewport[0], _viewport[1], _viewport[2], _viewport[3]); + glScissor(_scissor_rect[0], _scissor_rect[1], _scissor_rect[2], _scissor_rect[3]); + if (_scissor_test) { glEnable(GL_SCISSOR_TEST); } + else { glDisable(GL_SCISSOR_TEST); } + if (_blend) { glEnable(GL_BLEND); } + else { glDisable(GL_BLEND); } + glBlendFunc(_blend_src, _blend_dest); + glBlendEquationSeparate(_blend_eq_color, _blend_eq_alpha); + if (_depth_test) { glEnable(GL_DEPTH_TEST); } + else { glDisable(GL_DEPTH_TEST); } + glDepthMask(_depth_mask); + glDepthFunc(_depth_func); + if (_stencil_test) { glEnable(GL_STENCIL_TEST); } + else { glDisable(GL_STENCIL_TEST); } + glStencilFunc(_stencil_func, _stencil_ref, _stencil_read_mask); + glStencilOp(_stencil_op_fail, _stencil_op_zfail, _stencil_op_zpass); + glStencilMask(_stencil_mask); + glPolygonMode(GL_FRONT_AND_BACK, _polygon_mode); + glFrontFace(_frontface); + if (_cullface) { glEnable(GL_CULL_FACE); } + else { glDisable(GL_CULL_FACE); } + glCullFace(_cullface_mode); + glBindFramebuffer(GL_FRAMEBUFFER, _fbo); + if (_srgb) { glEnable(GL_FRAMEBUFFER_SRGB); } + else { glDisable(GL_FRAMEBUFFER_SRGB); } + glColorMask(_color_mask[0], _color_mask[1], _color_mask[2], _color_mask[3]); + + if (_drawbuffers[1] == GL_NONE && + _drawbuffers[2] == GL_NONE && + _drawbuffers[3] == GL_NONE && + _drawbuffers[4] == GL_NONE && + _drawbuffers[5] == GL_NONE && + _drawbuffers[6] == GL_NONE && + _drawbuffers[7] == GL_NONE) + { + glDrawBuffer(_drawbuffers[0]); + } + else + { + glDrawBuffers(8, _drawbuffers); + } + + if (gl3wProcs.gl.ClipControl != nullptr) + { + glClipControl(clip_origin, clip_depthmode); + } +} diff --git a/msvc/source/opengl/state_block.hpp b/msvc/source/opengl/state_block.hpp new file mode 100644 index 0000000..aa69b64 --- /dev/null +++ b/msvc/source/opengl/state_block.hpp @@ -0,0 +1,54 @@ +/** + * Copyright (C) 2014 Patrick Mours. All rights reserved. + * License: https://github.com/crosire/reshade#license + */ + +#pragma once + +#include "opengl.hpp" + +namespace reshade::opengl +{ + class state_block + { + public: + state_block(); + + void capture(); + void apply() const; + +#ifndef NDEBUG + mutable bool has_state = false; +#endif + + private: + GLint _vao; + GLint _vbo; + GLint _ubo; + GLint _program; + GLint _textures2d[32], _samplers[32]; + GLint _active_texture; + GLint _viewport[4]; + GLint _scissor_rect[4]; + GLint _scissor_test; + GLint _blend; + GLint _blend_src, _blend_dest; + GLint _blend_eq_color, _blend_eq_alpha; + GLint _depth_test; + GLboolean _depth_mask; + GLint _depth_func; + GLint _stencil_test; + GLint _stencil_ref; + GLint _stencil_func; + GLint _stencil_op_fail, _stencil_op_zfail, _stencil_op_zpass; + GLint _stencil_read_mask, _stencil_mask; + GLint _polygon_mode, _frontface; + GLint _cullface, _cullface_mode; + GLint _fbo; + GLint _srgb; + GLint clip_origin; + GLint clip_depthmode; + GLboolean _color_mask[4]; + GLenum _drawbuffers[8]; + }; +} diff --git a/msvc/source/resource_loading.cpp b/msvc/source/resource_loading.cpp new file mode 100644 index 0000000..6175a1e --- /dev/null +++ b/msvc/source/resource_loading.cpp @@ -0,0 +1,36 @@ +/** + * Copyright (C) 2014 Patrick Mours. All rights reserved. + * License: https://github.com/crosire/reshade#license + */ + +#include "resource_loading.hpp" +#include + +extern HMODULE g_module_handle; + +reshade::resources::data_resource reshade::resources::load_data_resource(unsigned int id) +{ + const HRSRC info = FindResource(g_module_handle, MAKEINTRESOURCE(id), RT_RCDATA); + const HGLOBAL handle = LoadResource(g_module_handle, info); + + data_resource result; + result.data = LockResource(handle); + result.data_size = SizeofResource(g_module_handle, info); + + return result; +} +reshade::resources::image_resource reshade::resources::load_image_resource(unsigned int id) +{ + DIBSECTION dib = {}; + const HANDLE handle = LoadImage(g_module_handle, MAKEINTRESOURCE(id), IMAGE_BITMAP, 0, 0, LR_CREATEDIBSECTION); + GetObject(handle, sizeof(dib), &dib); + + image_resource result; + result.width = dib.dsBm.bmWidth; + result.height = dib.dsBm.bmHeight; + result.bits_per_pixel = dib.dsBm.bmBitsPixel; + result.data = dib.dsBm.bmBits; + result.data_size = dib.dsBmih.biSizeImage; + + return result; +} diff --git a/msvc/source/resource_loading.hpp b/msvc/source/resource_loading.hpp new file mode 100644 index 0000000..18e7d9e --- /dev/null +++ b/msvc/source/resource_loading.hpp @@ -0,0 +1,24 @@ +/** + * Copyright (C) 2014 Patrick Mours. All rights reserved. + * License: https://github.com/crosire/reshade#license + */ + +#pragma once + +#include "resource.h" + +namespace reshade::resources +{ + struct data_resource + { + size_t data_size; + const void *data; + }; + struct image_resource : public data_resource + { + unsigned int width, height, bits_per_pixel; + }; + + data_resource load_data_resource(unsigned int id); + image_resource load_image_resource(unsigned int id); +} diff --git a/msvc/source/runtime.cpp b/msvc/source/runtime.cpp new file mode 100644 index 0000000..30419ca --- /dev/null +++ b/msvc/source/runtime.cpp @@ -0,0 +1,1283 @@ +/** + * Copyright (C) 2014 Patrick Mours. All rights reserved. + * License: https://github.com/crosire/reshade#license + */ + +#include "log.hpp" +#include "version.h" +#include "runtime.hpp" +#include "runtime_objects.hpp" +#include "effect_parser.hpp" +#include "effect_codegen.hpp" +#include "effect_preprocessor.hpp" +#include "input.hpp" +#include "ini_file.hpp" +#include +#include +#include +#include +#include +#include +#include + +extern volatile long g_network_traffic; +extern std::filesystem::path g_reshade_dll_path; +extern std::filesystem::path g_target_executable_path; + +static bool find_file(const std::vector &search_paths, std::filesystem::path &path) +{ + std::error_code ec; + if (path.is_relative()) + for (const auto &search_path : search_paths) + { + std::filesystem::path canonical_search_path = search_path; + if (search_path.is_relative()) + canonical_search_path = std::filesystem::canonical(g_reshade_dll_path.parent_path() / search_path, ec); + if (ec || canonical_search_path.empty()) + continue; + + if (std::filesystem::path result = canonical_search_path / path; std::filesystem::exists(result, ec)) + { + path = std::move(result); + return true; + } + } + return std::filesystem::exists(path, ec); +} + +static std::vector find_files(const std::vector &search_paths, std::initializer_list extensions) +{ + std::error_code ec; + std::vector files; + for (const auto &search_path : search_paths) + { + std::filesystem::path canonical_search_path = search_path; + if (search_path.is_relative()) // Ignore the working directory and instead start relative paths at the DLL location + canonical_search_path = std::filesystem::canonical(g_reshade_dll_path.parent_path() / search_path, ec); + if (ec || canonical_search_path.empty()) + continue; // Failed to find a valid directory matching the search path + + for (const auto &entry : std::filesystem::directory_iterator(canonical_search_path, ec)) + for (auto ext : extensions) + if (entry.path().extension() == ext) + files.push_back(entry.path()); + } + return files; +} + +reshade::runtime::runtime() : + _start_time(std::chrono::high_resolution_clock::now()), + _last_present_time(std::chrono::high_resolution_clock::now()), + _last_frame_duration(std::chrono::milliseconds(1)), + _effect_search_paths({ ".\\" }), + _texture_search_paths({ ".\\" }), + _global_preprocessor_definitions({ + "RESHADE_DEPTH_LINEARIZATION_FAR_PLANE=1000.0", + "RESHADE_DEPTH_INPUT_IS_UPSIDE_DOWN=0", + "RESHADE_DEPTH_INPUT_IS_REVERSED=1", + "RESHADE_DEPTH_INPUT_IS_LOGARITHMIC=0" }), + _reload_key_data(), + _effects_key_data(), + _screenshot_key_data(), + _screenshot_path(g_target_executable_path.parent_path()) +{ + // Default shortcut PrtScrn + _screenshot_key_data[0] = 0x2C; + + _configuration_path = g_reshade_dll_path; + _configuration_path.replace_extension(".ini"); + if (std::error_code ec; !std::filesystem::exists(_configuration_path, ec)) + _configuration_path = g_reshade_dll_path.parent_path() / "ReShade.ini"; + + _needs_update = check_for_update(_latest_version); + +#if RESHADE_GUI + init_ui(); +#endif + load_config(); +} +reshade::runtime::~runtime() +{ + assert(_worker_threads.empty()); + assert(!_is_initialized && _techniques.empty()); + +#if RESHADE_GUI + deinit_ui(); +#endif +} + +bool reshade::runtime::on_init(input::window_handle window) +{ + LOG(INFO) << "Recreated runtime environment on runtime " << this << '.'; + + _input = input::register_window(window); + + // Reset frame count to zero so effects are loaded in 'update_and_render_effects' + _framecount = 0; + + _is_initialized = true; + _last_reload_time = std::chrono::high_resolution_clock::now(); + +#if RESHADE_GUI + build_font_atlas(); +#endif + + return true; +} +void reshade::runtime::on_reset() +{ + unload_effects(); + + if (!_is_initialized) + return; + +#if RESHADE_GUI + destroy_font_atlas(); +#endif + + LOG(INFO) << "Destroyed runtime environment on runtime " << this << '.'; + + _width = _height = 0; + _is_initialized = false; +} +void reshade::runtime::on_present() +{ + // Get current time and date + time_t t = std::time(nullptr); tm tm; + localtime_s(&tm, &t); + _date[0] = tm.tm_year + 1900; + _date[1] = tm.tm_mon + 1; + _date[2] = tm.tm_mday; + _date[3] = tm.tm_hour * 3600 + tm.tm_min * 60 + tm.tm_sec; + + // Advance various statistics + _framecount++; + const auto current_time = std::chrono::high_resolution_clock::now(); + _last_frame_duration = current_time - _last_present_time; _last_present_time = current_time; + + // Lock input so it cannot be modified by other threads while we are reading it here + const auto input_lock = _input->lock(); + + // Handle keyboard shortcuts + if (!_ignore_shortcuts) + { + if (_input->is_key_pressed(_reload_key_data)) + load_effects(); + + if (_input->is_key_pressed(_effects_key_data)) + _effects_enabled = !_effects_enabled; + + if (_input->is_key_pressed(_screenshot_key_data)) + save_screenshot(); + } + +#if RESHADE_GUI + // Draw overlay + draw_ui(); +#endif + + // Reset input status + _input->next_frame(); + + static int cooldown = 0, traffic = 0; + + if (cooldown-- > 0) + { + traffic += g_network_traffic > 0; + } + else + { + _has_high_network_activity = traffic > 10; + traffic = 0; + cooldown = 30; + } + + g_network_traffic = 0; + _drawcalls = _vertices = 0; +} + +void reshade::runtime::load_effect(const std::filesystem::path &path, size_t &out_id) +{ + effect_data effect; + effect.source_file = path; + effect.compile_sucess = true; + + { // Load, pre-process and compile the source file + reshadefx::preprocessor pp; + if (path.is_absolute()) + pp.add_include_path(path.parent_path()); + + for (const auto &include_path : _effect_search_paths) + { + std::error_code ec; + std::filesystem::path canonical_include_path = include_path; + if (include_path.is_relative()) // Ignore the working directory and instead start relative paths at the DLL location + canonical_include_path = std::filesystem::canonical(g_reshade_dll_path.parent_path() / include_path, ec); + + if (!ec && !canonical_include_path.empty()) + pp.add_include_path(canonical_include_path); + } + + pp.add_macro_definition("__RESHADE__", std::to_string(VERSION_MAJOR * 10000 + VERSION_MINOR * 100 + VERSION_REVISION)); + pp.add_macro_definition("__RESHADE_PERFORMANCE_MODE__", _performance_mode ? "1" : "0"); + pp.add_macro_definition("__VENDOR__", std::to_string(_vendor_id)); + pp.add_macro_definition("__DEVICE__", std::to_string(_device_id)); + pp.add_macro_definition("__RENDERER__", std::to_string(_renderer_id)); + pp.add_macro_definition("__APPLICATION__", std::to_string(std::hash()(g_target_executable_path.stem().u8string()))); + pp.add_macro_definition("BUFFER_WIDTH", std::to_string(_width)); + pp.add_macro_definition("BUFFER_HEIGHT", std::to_string(_height)); + pp.add_macro_definition("BUFFER_RCP_WIDTH", "(1.0 / BUFFER_WIDTH)"); + pp.add_macro_definition("BUFFER_RCP_HEIGHT", "(1.0 / BUFFER_HEIGHT)"); + + std::vector preprocessor_definitions = _global_preprocessor_definitions; + preprocessor_definitions.insert(preprocessor_definitions.end(), _preset_preprocessor_definitions.begin(), _preset_preprocessor_definitions.end()); + + for (const auto &definition : preprocessor_definitions) + { + if (definition.empty()) + continue; // Skip invalid definitions + + const size_t equals_index = definition.find('='); + if (equals_index != std::string::npos) + pp.add_macro_definition( + definition.substr(0, equals_index), + definition.substr(equals_index + 1)); + else + pp.add_macro_definition(definition); + } + + if (!pp.append_file(path)) + { + LOG(ERROR) << "Failed to load " << path << ":\n" << pp.errors(); + effect.compile_sucess = false; + } + + unsigned shader_model; + if (_renderer_id == 0x9000) + shader_model = 30; + else if (_renderer_id < 0xa100) + shader_model = 40; + else if (_renderer_id < 0xb000) + shader_model = 41; + else if (_renderer_id < 0xc000) + shader_model = 50; + else + shader_model = 60; + + std::unique_ptr codegen; + if ((_renderer_id & 0xF0000) == 0) + codegen.reset(reshadefx::create_codegen_hlsl(shader_model, true, _performance_mode)); + else if (_renderer_id < 0x20000) + codegen.reset(reshadefx::create_codegen_glsl(true, _performance_mode)); + else // Vulkan uses SPIR-V input + codegen.reset(reshadefx::create_codegen_spirv(true, _performance_mode)); + + reshadefx::parser parser; + + // Compile the pre-processed source code (try the compile even if the preprocessor step failed to get additional error information) + if (!parser.parse(std::move(pp.output()), codegen.get())) + { + LOG(ERROR) << "Failed to compile " << path << ":\n" << parser.errors(); + effect.compile_sucess = false; + } + + // Append preprocessor and parser errors to the error list + effect.errors = std::move(pp.errors()) + std::move(parser.errors()); + + // Write result to effect module + codegen->write_result(effect.module); + } + + // Fill all specialization constants with values from the current preset + if (_performance_mode && !_current_preset_path.empty() && effect.compile_sucess) + { + const ini_file preset(_current_preset_path); + const std::string section(path.filename().u8string()); + + for (reshadefx::uniform_info &constant : effect.module.spec_constants) + { + effect.preamble += "#define SPEC_CONSTANT_" + constant.name + ' '; + + switch (constant.type.base) + { + case reshadefx::type::t_int: + preset.get(section, constant.name, constant.initializer_value.as_int); + break; + case reshadefx::type::t_bool: + case reshadefx::type::t_uint: + preset.get(section, constant.name, constant.initializer_value.as_uint); + break; + case reshadefx::type::t_float: + preset.get(section, constant.name, constant.initializer_value.as_float); + break; + } + + for (unsigned int i = 0; i < constant.type.components(); ++i) + { + switch (constant.type.base) + { + case reshadefx::type::t_bool: + effect.preamble += constant.initializer_value.as_uint[i] ? "true" : "false"; + break; + case reshadefx::type::t_int: + effect.preamble += std::to_string(constant.initializer_value.as_int[i]); + break; + case reshadefx::type::t_uint: + effect.preamble += std::to_string(constant.initializer_value.as_uint[i]); + break; + case reshadefx::type::t_float: + effect.preamble += std::to_string(constant.initializer_value.as_float[i]); + break; + } + + if (i + 1 < constant.type.components()) + effect.preamble += ", "; + } + + effect.preamble += '\n'; + } + } + + // Guard access to shared variables + const std::lock_guard lock(_reload_mutex); + + effect.index = out_id = _loaded_effects.size(); + effect.storage_offset = _uniform_data_storage.size(); + + for (const reshadefx::uniform_info &info : effect.module.uniforms) + { + uniform &variable = _uniforms.emplace_back(info); + variable.effect_index = effect.index; + + variable.storage_offset = effect.storage_offset + variable.offset; + // Create space for the new variable in the storage area and fill it with the initializer value + _uniform_data_storage.resize(variable.storage_offset + variable.size); + + // Copy initial data into uniform storage area + reset_uniform_value(variable); + + const std::string_view special = variable.annotation_as_string("source"); + if (special.empty()) /* Ignore if annotation is missing */; + else if (special == "frametime") + variable.special = special_uniform::frame_time; + else if (special == "framecount") + variable.special = special_uniform::frame_count; + else if (special == "random") + variable.special = special_uniform::random; + else if (special == "pingpong") + variable.special = special_uniform::ping_pong; + else if (special == "date") + variable.special = special_uniform::date; + else if (special == "timer") + variable.special = special_uniform::timer; + else if (special == "key") + variable.special = special_uniform::key; + else if (special == "mousepoint") + variable.special = special_uniform::mouse_point; + else if (special == "mousedelta") + variable.special = special_uniform::mouse_delta; + else if (special == "mousebutton") + variable.special = special_uniform::mouse_button; + } + + effect.storage_size = (_uniform_data_storage.size() - effect.storage_offset + 15) & ~15; + _uniform_data_storage.resize(effect.storage_offset + effect.storage_size); + + for (const reshadefx::texture_info &info : effect.module.textures) + { + // Try to share textures with the same name across effects + if (const auto existing_texture = std::find_if(_textures.begin(), _textures.end(), + [&info](const auto &item) { return item.unique_name == info.unique_name; }); + existing_texture != _textures.end()) + { + if (info.semantic.empty() && ( + existing_texture->width != info.width || + existing_texture->height != info.height || + existing_texture->levels != info.levels || + existing_texture->format != info.format)) + { + effect.errors += "warning: " + info.unique_name + ": another effect already created a texture with the same name but different dimensions; textures are shared across all effects, so either rename the variable or adjust the dimensions so they match\n"; + } + + existing_texture->shared = true; + continue; + } + + texture &texture = _textures.emplace_back(info); + texture.effect_index = effect.index; + + if (info.semantic == "COLOR") + texture.impl_reference = texture_reference::back_buffer; + else if (info.semantic == "DEPTH") + texture.impl_reference = texture_reference::depth_buffer; + else if (!info.semantic.empty()) + effect.errors += "warning: " + info.unique_name + ": unknown semantic '" + info.semantic + "'\n"; + } + + _loaded_effects.push_back(effect); // The 'enable_technique' call below needs to access this, so append the effect now + + for (const reshadefx::technique_info &info : effect.module.techniques) + { + technique &technique = _techniques.emplace_back(info); + technique.effect_index = effect.index; + + technique.hidden = technique.annotation_as_int("hidden") != 0; + technique.timeout = technique.annotation_as_int("timeout"); + technique.timeleft = technique.timeout; + technique.toggle_key_data[0] = technique.annotation_as_int("toggle"); + technique.toggle_key_data[1] = technique.annotation_as_int("togglectrl"); + technique.toggle_key_data[2] = technique.annotation_as_int("toggleshift"); + technique.toggle_key_data[3] = technique.annotation_as_int("togglealt"); + + if (technique.annotation_as_int("enabled")) + enable_technique(technique); + } + + if (effect.compile_sucess) + if (effect.errors.empty()) + LOG(INFO) << "Successfully loaded " << path << '.'; + else + LOG(WARN) << "Successfully loaded " << path << " with warnings:\n" << effect.errors; + + _reload_remaining_effects--; + _last_reload_successful &= effect.compile_sucess; +} +void reshade::runtime::load_effects() +{ + // Clear out any previous effects + unload_effects(); + + _last_reload_successful = true; + + // Reload preprocessor definitions from current preset before compiling + if (!_current_preset_path.empty()) + { + _preset_preprocessor_definitions.clear(); + + const ini_file preset(_current_preset_path); + preset.get("", "PreprocessorDefinitions", _preset_preprocessor_definitions); + } + + // Build a list of effect files by walking through the effect search paths + const std::vector effect_files = + find_files(_effect_search_paths, { ".fx" }); + + _reload_total_effects = effect_files.size(); + _reload_remaining_effects = _reload_total_effects; + + // Now that we have a list of files, load them in parallel + for (const std::filesystem::path &file : effect_files) + _worker_threads.emplace_back([this, file]() { size_t id; load_effect(file, id); }); // Keep track of the spawned threads, so the runtime cannot be destroyed while they are still running +} +void reshade::runtime::load_textures() +{ + LOG(INFO) << "Loading image files for textures ..."; + + for (texture &texture : _textures) + { + if (texture.impl == nullptr || texture.impl_reference != texture_reference::none) + continue; // Ignore textures that are not created yet and those that are handled in the runtime implementation + + std::filesystem::path source_path = std::filesystem::u8path( + texture.annotation_as_string("source")); + // Ignore textures that have no image file attached to them (e.g. plain render targets) + if (source_path.empty()) + continue; + + // Search for image file using the provided search paths unless the path provided is already absolute + if (!find_file(_texture_search_paths, source_path)) { + LOG(ERROR) << "> Source " << source_path << " for texture '" << texture.unique_name << "' could not be found in any of the texture search paths."; + continue; + } + + unsigned char *filedata = nullptr; + int width = 0, height = 0, channels = 0; + + if (FILE *file; _wfopen_s(&file, source_path.c_str(), L"rb") == 0) + { + // Read texture data into memory in one go since that is faster than reading chunk by chunk + std::vector mem(static_cast(std::filesystem::file_size(source_path))); + fread(mem.data(), 1, mem.size(), file); + fclose(file); + + if (stbi_dds_test_memory(mem.data(), static_cast(mem.size()))) + filedata = stbi_dds_load_from_memory(mem.data(), static_cast(mem.size()), &width, &height, &channels, STBI_rgb_alpha); + else + filedata = stbi_load_from_memory(mem.data(), static_cast(mem.size()), &width, &height, &channels, STBI_rgb_alpha); + } + + if (filedata == nullptr) { + LOG(ERROR) << "> Source " << source_path << " for texture '" << texture.unique_name << "' could not be loaded! Make sure it is of a compatible file format."; + continue; + } + + if (texture.width != uint32_t(width) || texture.height != uint32_t(height)) + { + LOG(INFO) << "> Resizing image data for texture '" << texture.unique_name << "' from " << width << "x" << height << " to " << texture.width << "x" << texture.height << " ..."; + + std::vector resized(texture.width * texture.height * 4); + stbir_resize_uint8(filedata, width, height, 0, resized.data(), texture.width, texture.height, 0, 4); + upload_texture(texture, resized.data()); + } + else + { + upload_texture(texture, filedata); + } + + stbi_image_free(filedata); + } + + _textures_loaded = true; +} + +void reshade::runtime::unload_effect(size_t id) +{ + _uniforms.erase(std::remove_if(_uniforms.begin(), _uniforms.end(), + [id](const auto &it) { return it.effect_index == id; }), _uniforms.end()); + _textures.erase(std::remove_if(_textures.begin(), _textures.end(), + [id](const auto &it) { return it.effect_index == id; }), _textures.end()); + _techniques.erase(std::remove_if(_techniques.begin(), _techniques.end(), + [id](const auto &it) { return it.effect_index == id; }), _techniques.end()); + + _loaded_effects[id].source_file.clear(); + +#if RESHADE_GUI + // Remove all texture preview windows since some may no longer be valid + _texture_previews.clear(); +#endif +} +void reshade::runtime::unload_effects() +{ + // Make sure no threads are still accessing effect data + for (std::thread &thread : _worker_threads) + thread.join(); + _worker_threads.clear(); + + _uniforms.clear(); + _textures.clear(); + _techniques.clear(); + + _loaded_effects.clear(); + _uniform_data_storage.clear(); + + _textures_loaded = false; + +#if RESHADE_GUI + // Remove all texture preview windows since those textures were deleted above + _texture_previews.clear(); +#endif +} + +void reshade::runtime::update_and_render_effects() +{ + // Delay first load to the first render call to avoid loading while the application is still initializing + if (_framecount == 0 && !_no_reload_on_init) + load_effects(); + + if (_reload_remaining_effects == 0) + { + // Finished loading effects, so apply preset to figure out which ones need compiling + load_current_preset(); + + _last_reload_time = std::chrono::high_resolution_clock::now(); + _reload_total_effects = 0; + _reload_remaining_effects = std::numeric_limits::max(); + } + else if (_reload_remaining_effects != std::numeric_limits::max()) + { + return; // Cannot render while effects are still being loaded + } + else + { + if (!_reload_compile_queue.empty()) + { + // Pop an effect from the queue + size_t effect_index = _reload_compile_queue.back(); + _reload_compile_queue.pop_back(); + + effect_data &effect = _loaded_effects[effect_index]; + + // Create textures now, since they are referenced when building samplers in the 'compile_effect' call below + bool success = true; + for (texture &texture : _textures) + if (texture.impl == nullptr && (texture.effect_index == effect_index || texture.shared)) + success &= init_texture(texture); + + // Compile the effect with the back-end implementation + if (success && !compile_effect(effect)) + { + success = false; + + // De-duplicate error lines (D3DCompiler sometimes repeats the same error multiple times) + for (size_t cur_line_offset = 0, next_line_offset, end_offset; + (next_line_offset = effect.errors.find('\n', cur_line_offset)) != std::string::npos && (end_offset = effect.errors.find('\n', next_line_offset + 1)) != std::string::npos; cur_line_offset = next_line_offset + 1) + { + const std::string_view cur_line(effect.errors.c_str() + cur_line_offset, next_line_offset - cur_line_offset); + const std::string_view next_line(effect.errors.c_str() + next_line_offset + 1, end_offset - next_line_offset - 1); + + if (cur_line == next_line) + { + effect.errors.erase(next_line_offset, end_offset - next_line_offset); + next_line_offset = cur_line_offset - 1; + } + } + + LOG(ERROR) << "Failed to compile " << effect.source_file << ":\n" << effect.errors; + } + + if (!success) + { + // Destroy all textures belonging to this effect + for (texture &texture : _textures) + if (texture.effect_index == effect_index && !texture.shared) + texture.impl.reset(); + // Disable all techniques belonging to this effect + for (technique &technique : _techniques) + if (technique.effect_index == effect_index) + disable_technique(technique); + + effect.compile_sucess = false; + _last_reload_successful = false; + } + + // An effect has changed, need to reload textures + _textures_loaded = false; + } + else if (!_textures_loaded) + { + load_textures(); + } + } + + // Lock input so it cannot be modified by other threads while we are reading it here + // TODO: This does not catch input happening between now and 'on_present' + const auto input_lock = _input->lock(); + + // Nothing to do here if effects are disabled globally + if (!_effects_enabled) + return; + + // Update special uniform variables + for (uniform &variable : _uniforms) + { + switch (variable.special) + { + case special_uniform::frame_time: + set_uniform_value(variable, _last_frame_duration.count() * 1e-6f, 0.0f, 0.0f, 0.0f); + break; + case special_uniform::frame_count: + if (variable.type.is_boolean()) + set_uniform_value(variable, (_framecount % 2) == 0); + else + set_uniform_value(variable, static_cast(_framecount % UINT_MAX)); + break; + case special_uniform::random: { + const int min = variable.annotation_as_int("min"); + const int max = variable.annotation_as_int("max"); + set_uniform_value(variable, min + (std::rand() % (max - min + 1))); + break; } + case special_uniform::ping_pong: { + const float min = variable.annotation_as_float("min"); + const float max = variable.annotation_as_float("max"); + const float step_min = variable.annotation_as_float("step", 0); + const float step_max = variable.annotation_as_float("step", 1); + float increment = step_max == 0 ? step_min : (step_min + std::fmodf(static_cast(std::rand()), step_max - step_min + 1)); + const float smoothing = variable.annotation_as_float("smoothing"); + + float value[2] = { 0, 0 }; + get_uniform_value(variable, value, 2); + if (value[1] >= 0) + { + increment = std::max(increment - std::max(0.0f, smoothing - (max - value[0])), 0.05f); + increment *= _last_frame_duration.count() * 1e-9f; + + if ((value[0] += increment) >= max) + value[0] = max, value[1] = -1; + } + else + { + increment = std::max(increment - std::max(0.0f, smoothing - (value[0] - min)), 0.05f); + increment *= _last_frame_duration.count() * 1e-9f; + + if ((value[0] -= increment) <= min) + value[0] = min, value[1] = +1; + } + set_uniform_value(variable, value, 2); + break; } + case special_uniform::date: + set_uniform_value(variable, _date, 4); + break; + case special_uniform::timer: + set_uniform_value(variable, static_cast(std::chrono::duration_cast(_last_present_time - _start_time).count())); + break; + case special_uniform::key: + if (const int keycode = variable.annotation_as_int("keycode"); + keycode > 7 && keycode < 256) + if (const std::string_view mode = variable.annotation_as_string("mode"); + mode == "toggle" || variable.annotation_as_int("toggle")) { + bool current_value = false; + get_uniform_value(variable, ¤t_value, 1); + if (_input->is_key_pressed(keycode)) + set_uniform_value(variable, !current_value); + } else if (mode == "press") + set_uniform_value(variable, _input->is_key_pressed(keycode)); + else + set_uniform_value(variable, _input->is_key_down(keycode)); + break; + case special_uniform::mouse_point: + set_uniform_value(variable, _input->mouse_position_x(), _input->mouse_position_y()); + break; + case special_uniform::mouse_delta: + set_uniform_value(variable, _input->mouse_movement_delta_x(), _input->mouse_movement_delta_y()); + break; + case special_uniform::mouse_button: + if (const int keycode = variable.annotation_as_int("keycode"); + keycode >= 0 && keycode < 5) + if (const std::string_view mode = variable.annotation_as_string("mode"); + mode == "toggle" || variable.annotation_as_int("toggle")) { + bool current_value = false; + get_uniform_value(variable, ¤t_value, 1); + if (_input->is_mouse_button_pressed(keycode)) + set_uniform_value(variable, !current_value); + } else if (mode == "press") + set_uniform_value(variable, _input->is_mouse_button_pressed(keycode)); + else + set_uniform_value(variable, _input->is_mouse_button_down(keycode)); + break; + } + } + + // Render all enabled techniques + for (technique &technique : _techniques) + { + if (technique.timeleft > 0) + { + technique.timeleft -= std::chrono::duration_cast(_last_frame_duration).count(); + if (technique.timeleft <= 0) + disable_technique(technique); + } + else if (!_ignore_shortcuts && (_input->is_key_pressed(technique.toggle_key_data) || + (technique.toggle_key_data[0] >= 0x01 && technique.toggle_key_data[0] <= 0x06 && _input->is_mouse_button_pressed(technique.toggle_key_data[0] - 1)))) + { + if (!technique.enabled) + enable_technique(technique); + else + disable_technique(technique); + } + + if (technique.impl == nullptr || !technique.enabled) + continue; // Ignore techniques that are not fully loaded or currently disabled + + const auto time_technique_started = std::chrono::high_resolution_clock::now(); + render_technique(technique); + const auto time_technique_finished = std::chrono::high_resolution_clock::now(); + + technique.average_cpu_duration.append(std::chrono::duration_cast(time_technique_finished - time_technique_started).count()); + } +} + +void reshade::runtime::enable_technique(technique &technique) +{ + if (!_loaded_effects[technique.effect_index].compile_sucess) + return; // Cannot enable techniques that failed to compile + + const bool status_changed = !technique.enabled; + technique.enabled = true; + technique.timeleft = technique.timeout; + + // Queue effect file for compilation if it was not fully loaded yet + if (technique.impl == nullptr && // Avoid adding the same effect multiple times to the queue if it contains multiple techniques that were enabled simultaneously + std::find(_reload_compile_queue.begin(), _reload_compile_queue.end(), technique.effect_index) == _reload_compile_queue.end()) + { + _reload_total_effects++; + _reload_compile_queue.push_back(technique.effect_index); + } + + if (status_changed) // Increase rendering reference count + _loaded_effects[technique.effect_index].rendering++; +} +void reshade::runtime::disable_technique(technique &technique) +{ + const bool status_changed = technique.enabled; + technique.enabled = false; + technique.timeleft = 0; + technique.average_cpu_duration.clear(); + technique.average_gpu_duration.clear(); + + if (status_changed) // Decrease rendering reference count + _loaded_effects[technique.effect_index].rendering--; +} + +void reshade::runtime::subscribe_to_load_config(std::function function) +{ + _load_config_callables.push_back(function); + + const ini_file config(_configuration_path); + function(config); +} +void reshade::runtime::subscribe_to_save_config(std::function function) +{ + _save_config_callables.push_back(function); + + ini_file config(_configuration_path); + function(config); +} + +void reshade::runtime::load_config() +{ + const ini_file config(_configuration_path); + + std::filesystem::path current_preset_path; + + config.get("INPUT", "KeyReload", _reload_key_data); + config.get("INPUT", "KeyEffects", _effects_key_data); + config.get("INPUT", "KeyScreenshot", _screenshot_key_data); + + config.get("GENERAL", "PerformanceMode", _performance_mode); + config.get("GENERAL", "EffectSearchPaths", _effect_search_paths); + config.get("GENERAL", "TextureSearchPaths", _texture_search_paths); + config.get("GENERAL", "PreprocessorDefinitions", _global_preprocessor_definitions); + config.get("GENERAL", "CurrentPresetPath", current_preset_path); + config.get("GENERAL", "ScreenshotPath", _screenshot_path); + config.get("GENERAL", "ScreenshotFormat", _screenshot_format); + config.get("GENERAL", "ScreenshotIncludePreset", _screenshot_include_preset); + config.get("GENERAL", "NoReloadOnInit", _no_reload_on_init); + + if (current_preset_path.empty()) + { + size_t preset_index = 0; + std::vector preset_files; + config.get("GENERAL", "PresetFiles", preset_files); + config.get("GENERAL", "CurrentPreset", preset_index); + + if (preset_index < preset_files.size()) + current_preset_path = preset_files[preset_index]; + } + + set_current_preset(current_preset_path); + + for (const auto &callback : _load_config_callables) + callback(config); +} +void reshade::runtime::save_config() const +{ + save_config(_configuration_path); +} +void reshade::runtime::save_config(const std::filesystem::path &path) const +{ + ini_file config(_configuration_path, path); + + config.set("INPUT", "KeyReload", _reload_key_data); + config.set("INPUT", "KeyEffects", _effects_key_data); + config.set("INPUT", "KeyScreenshot", _screenshot_key_data); + + config.set("GENERAL", "PerformanceMode", _performance_mode); + config.set("GENERAL", "EffectSearchPaths", _effect_search_paths); + config.set("GENERAL", "TextureSearchPaths", _texture_search_paths); + config.set("GENERAL", "PreprocessorDefinitions", _global_preprocessor_definitions); + config.set("GENERAL", "CurrentPresetPath", _current_preset_path); + config.set("GENERAL", "ScreenshotPath", _screenshot_path); + config.set("GENERAL", "ScreenshotFormat", _screenshot_format); + config.set("GENERAL", "ScreenshotIncludePreset", _screenshot_include_preset); + config.set("GENERAL", "NoReloadOnInit", _no_reload_on_init); + + for (const auto &callback : _save_config_callables) + callback(config); +} + +void reshade::runtime::load_preset(const std::filesystem::path &path) +{ + const ini_file preset(path); + + std::vector technique_list; + preset.get("", "Techniques", technique_list); + std::vector sorted_technique_list; + preset.get("", "TechniqueSorting", sorted_technique_list); + std::vector preset_preprocessor_definitions; + preset.get("", "PreprocessorDefinitions", preset_preprocessor_definitions); + + // Recompile effects if preprocessor definitions have changed or running in performance mode (in which case all preset values are compile-time constants) + if (_reload_remaining_effects != 0 && // ... unless this is the 'load_current_preset' call in 'update_and_render_effects' + (_performance_mode || preset_preprocessor_definitions != _preset_preprocessor_definitions)) + { + assert(path == _current_preset_path); + _preset_preprocessor_definitions = std::move(preset_preprocessor_definitions); + load_effects(); + return; // Preset values are loaded in 'update_and_render_effects' during effect loading + } + + // Reorder techniques + if (sorted_technique_list.empty()) + sorted_technique_list = technique_list; + + std::sort(_techniques.begin(), _techniques.end(), + [&sorted_technique_list](const auto &lhs, const auto &rhs) { + return (std::find(sorted_technique_list.begin(), sorted_technique_list.end(), lhs.name) - sorted_technique_list.begin()) < + (std::find(sorted_technique_list.begin(), sorted_technique_list.end(), rhs.name) - sorted_technique_list.begin()); + }); + + for (uniform &variable : _uniforms) + { + reshadefx::constant values; + + switch (variable.type.base) + { + case reshadefx::type::t_int: + get_uniform_value(variable, values.as_int, 16); + preset.get(_loaded_effects[variable.effect_index].source_file.filename().u8string(), variable.name, values.as_int); + set_uniform_value(variable, values.as_int, 16); + break; + case reshadefx::type::t_bool: + case reshadefx::type::t_uint: + get_uniform_value(variable, values.as_uint, 16); + preset.get(_loaded_effects[variable.effect_index].source_file.filename().u8string(), variable.name, values.as_uint); + set_uniform_value(variable, values.as_uint, 16); + break; + case reshadefx::type::t_float: + get_uniform_value(variable, values.as_float, 16); + preset.get(_loaded_effects[variable.effect_index].source_file.filename().u8string(), variable.name, values.as_float); + set_uniform_value(variable, values.as_float, 16); + break; + } + } + + for (technique &technique : _techniques) + { + // Ignore preset if "enabled" annotation is set + if (technique.annotation_as_int("enabled") + || std::find(technique_list.begin(), technique_list.end(), technique.name) != technique_list.end()) + enable_technique(technique); + else + disable_technique(technique); + + // Reset toggle key first, since it may not exist in the preset + std::fill_n(technique.toggle_key_data, _countof(technique.toggle_key_data), 0); + preset.get("", "Key" + technique.name, technique.toggle_key_data); + } +} +void reshade::runtime::load_current_preset() +{ + load_preset(_current_preset_path); +} +void reshade::runtime::save_preset(const std::filesystem::path &path) const +{ + ini_file preset(path); + + std::vector effect_list; + std::vector technique_list; + std::vector sorted_technique_list; + + for (const technique &technique : _techniques) + { + if (technique.enabled) + technique_list.push_back(technique.name); + if (technique.enabled || technique.toggle_key_data[0] != 0) + effect_list.push_back(technique.effect_index); + + // Keep track of the order of all techniques and not just the enabled ones + sorted_technique_list.push_back(technique.name); + + if (technique.toggle_key_data[0] != 0) + preset.set("", "Key" + technique.name, technique.toggle_key_data); + else if (int value = 0; preset.get("", "Key" + technique.name, value), value != 0) + preset.set("", "Key" + technique.name, 0); // Clear toggle key data + } + + preset.set("", "Techniques", std::move(technique_list)); + preset.set("", "TechniqueSorting", std::move(sorted_technique_list)); + preset.set("", "PreprocessorDefinitions", _preset_preprocessor_definitions); + + // TODO: Do we want to save spec constants here too? The preset will be rather empty in performance mode otherwise. + for (const uniform &variable : _uniforms) + { + if (variable.special != special_uniform::none + || std::find(effect_list.begin(), effect_list.end(), variable.effect_index) == effect_list.end()) + continue; + + const std::string section = + _loaded_effects[variable.effect_index].source_file.filename().u8string(); + reshadefx::constant values; + + assert(variable.type.components() <= 16); + + switch (variable.type.base) + { + case reshadefx::type::t_int: + get_uniform_value(variable, values.as_int, 16); + preset.set(section, variable.name, values.as_int, variable.type.components()); + break; + case reshadefx::type::t_bool: + case reshadefx::type::t_uint: + get_uniform_value(variable, values.as_uint, 16); + preset.set(section, variable.name, values.as_uint, variable.type.components()); + break; + case reshadefx::type::t_float: + get_uniform_value(variable, values.as_float, 16); + preset.set(section, variable.name, values.as_float, variable.type.components()); + break; + } + } +} +void reshade::runtime::save_current_preset() const +{ + save_preset(_current_preset_path); +} + +void reshade::runtime::save_screenshot() +{ + std::vector data(_width * _height * 4); + capture_screenshot(data.data()); + + const int hour = _date[3] / 3600; + const int minute = (_date[3] - hour * 3600) / 60; + const int seconds = _date[3] - hour * 3600 - minute * 60; + + char filename[21]; + sprintf_s(filename, " %.4d-%.2d-%.2d %.2d-%.2d-%.2d", _date[0], _date[1], _date[2], hour, minute, seconds); + + const std::wstring least = (_screenshot_path.is_relative() ? g_target_executable_path.parent_path() / _screenshot_path : _screenshot_path) / g_target_executable_path.stem().concat(filename); + const std::wstring screenshot_path = least + (_screenshot_format == 0 ? L".bmp" : L".png"); + + LOG(INFO) << "Saving screenshot to " << screenshot_path << " ..."; + + _screenshot_save_success = false; // Default to a save failure unless it is reported to succeed below + + if (FILE *file; _wfopen_s(&file, screenshot_path.c_str(), L"wb") == 0) + { + const auto write_callback = [](void *context, void *data, int size) { + fwrite(data, 1, size, static_cast(context)); + }; + + switch (_screenshot_format) + { + case 0: + _screenshot_save_success = stbi_write_bmp_to_func(write_callback, file, _width, _height, 4, data.data()) != 0; + break; + case 1: + _screenshot_save_success = stbi_write_png_to_func(write_callback, file, _width, _height, 4, data.data(), 0) != 0; + break; + } + + fclose(file); + } + + _last_screenshot_file = screenshot_path; + _last_screenshot_time = std::chrono::high_resolution_clock::now(); + + if (!_screenshot_save_success) + { + LOG(ERROR) << "Failed to write screenshot to " << screenshot_path << '!'; + } + else if (_screenshot_include_preset) + { + save_preset(least + L".ini"); + } +} + +void reshade::runtime::get_uniform_value(const uniform &variable, uint8_t *data, size_t size) const +{ + assert(data != nullptr); + + size = std::min(size, size_t(variable.size)); + + assert(variable.storage_offset + size <= _uniform_data_storage.size()); + + std::memcpy(data, &_uniform_data_storage[variable.storage_offset], size); +} +void reshade::runtime::get_uniform_value(const uniform &variable, bool *values, size_t count) const +{ + count = std::min(count, size_t(variable.size / 4)); + + assert(values != nullptr); + + const auto data = static_cast(alloca(variable.size)); + get_uniform_value(variable, data, variable.size); + + for (size_t i = 0; i < count; i++) + values[i] = reinterpret_cast(data)[i] != 0; +} +void reshade::runtime::get_uniform_value(const uniform &variable, int32_t *values, size_t count) const +{ + if (!variable.type.is_floating_point() && _renderer_id != 0x9000) + { + get_uniform_value(variable, reinterpret_cast(values), count * sizeof(int32_t)); + return; + } + + count = std::min(count, variable.size / sizeof(float)); + + assert(values != nullptr); + + const auto data = static_cast(alloca(variable.size)); + get_uniform_value(variable, data, variable.size); + + for (size_t i = 0; i < count; i++) + values[i] = static_cast(reinterpret_cast(data)[i]); +} +void reshade::runtime::get_uniform_value(const uniform &variable, uint32_t *values, size_t count) const +{ + get_uniform_value(variable, reinterpret_cast(values), count); +} +void reshade::runtime::get_uniform_value(const uniform &variable, float *values, size_t count) const +{ + if (variable.type.is_floating_point() || _renderer_id == 0x9000) + { + get_uniform_value(variable, reinterpret_cast(values), count * sizeof(float)); + return; + } + + count = std::min(count, variable.size / sizeof(int32_t)); + + assert(values != nullptr); + + const auto data = static_cast(alloca(variable.size)); + get_uniform_value(variable, data, variable.size); + + for (size_t i = 0; i < count; ++i) + if (variable.type.is_signed()) + values[i] = static_cast(reinterpret_cast(data)[i]); + else + values[i] = static_cast(reinterpret_cast(data)[i]); +} +void reshade::runtime::set_uniform_value(uniform &variable, const uint8_t *data, size_t size) +{ + assert(data != nullptr); + + size = std::min(size, size_t(variable.size)); + + assert(variable.storage_offset + size <= _uniform_data_storage.size()); + + std::memcpy(&_uniform_data_storage[variable.storage_offset], data, size); +} +void reshade::runtime::set_uniform_value(uniform &variable, const bool *values, size_t count) +{ + const auto data = static_cast(alloca(count * 4)); + switch (_renderer_id != 0x9000 ? variable.type.base : reshadefx::type::t_float) + { + case reshadefx::type::t_bool: + for (size_t i = 0; i < count; ++i) + reinterpret_cast(data)[i] = values[i] ? -1 : 0; + break; + case reshadefx::type::t_int: + case reshadefx::type::t_uint: + for (size_t i = 0; i < count; ++i) + reinterpret_cast(data)[i] = values[i] ? 1 : 0; + break; + case reshadefx::type::t_float: + for (size_t i = 0; i < count; ++i) + reinterpret_cast(data)[i] = values[i] ? 1.0f : 0.0f; + break; + } + + set_uniform_value(variable, data, count * 4); +} +void reshade::runtime::set_uniform_value(uniform &variable, const int32_t *values, size_t count) +{ + if (!variable.type.is_floating_point() && _renderer_id != 0x9000) + { + set_uniform_value(variable, reinterpret_cast(values), count * sizeof(int)); + return; + } + + const auto data = static_cast(alloca(count * sizeof(float))); + for (size_t i = 0; i < count; ++i) + data[i] = static_cast(values[i]); + + set_uniform_value(variable, reinterpret_cast(data), count * sizeof(float)); +} +void reshade::runtime::set_uniform_value(uniform &variable, const uint32_t *values, size_t count) +{ + if (!variable.type.is_floating_point() && _renderer_id != 0x9000) + { + set_uniform_value(variable, reinterpret_cast(values), count * sizeof(int)); + return; + } + + const auto data = static_cast(alloca(count * sizeof(float))); + for (size_t i = 0; i < count; ++i) + data[i] = static_cast(values[i]); + + set_uniform_value(variable, reinterpret_cast(data), count * sizeof(float)); +} +void reshade::runtime::set_uniform_value(uniform &variable, const float *values, size_t count) +{ + if (variable.type.is_floating_point() || _renderer_id == 0x9000) + { + set_uniform_value(variable, reinterpret_cast(values), count * sizeof(float)); + return; + } + + const auto data = static_cast(alloca(count * sizeof(int32_t))); + for (size_t i = 0; i < count; ++i) + data[i] = static_cast(values[i]); + + set_uniform_value(variable, reinterpret_cast(data), count * sizeof(int32_t)); +} + +void reshade::runtime::reset_uniform_value(uniform &variable) +{ + if (!variable.has_initializer_value) + { + memset(_uniform_data_storage.data() + variable.storage_offset, 0, variable.size); + return; + } + + if (_renderer_id != 0x9000) + { + memcpy(_uniform_data_storage.data() + variable.storage_offset, variable.initializer_value.as_uint, variable.size); + return; + } + + // Force all uniforms to floating-point in D3D9 + for (size_t i = 0; i < variable.size / sizeof(float); i++) + { + switch (variable.type.base) + { + case reshadefx::type::t_int: + reinterpret_cast(_uniform_data_storage.data() + variable.storage_offset)[i] = static_cast(variable.initializer_value.as_int[i]); + break; + case reshadefx::type::t_bool: + case reshadefx::type::t_uint: + reinterpret_cast(_uniform_data_storage.data() + variable.storage_offset)[i] = static_cast(variable.initializer_value.as_uint[i]); + break; + case reshadefx::type::t_float: + reinterpret_cast(_uniform_data_storage.data() + variable.storage_offset)[i] = variable.initializer_value.as_float[i]; + break; + } + } +} + +void reshade::runtime::set_current_preset() +{ + set_current_preset(_current_browse_path); +} +void reshade::runtime::set_current_preset(std::filesystem::path path) +{ + std::error_code ec; + std::filesystem::path reshade_container_path = g_reshade_dll_path.parent_path(); + + enum class path_state { invalid, valid }; + path_state path_state = path_state::invalid; + + if (path.has_filename()) + if (const std::wstring extension(path.extension()); extension == L".ini" || extension == L".txt") + if (!std::filesystem::exists(reshade_container_path / path, ec)) + path_state = path_state::valid; + else if (const reshade::ini_file preset(reshade_container_path / path); preset.has("", "TechniqueSorting")) + path_state = path_state::valid; + + // Select a default preset file if none exists yet or not own + if (path_state == path_state::invalid) + path = "DefaultPreset.ini"; + else if (const std::filesystem::path preset_canonical_path = std::filesystem::weakly_canonical(reshade_container_path / path, ec); + std::equal(reshade_container_path.begin(), reshade_container_path.end(), preset_canonical_path.begin())) + path = preset_canonical_path.lexically_proximate(reshade_container_path); + else if (const std::filesystem::path preset_absolute_path = std::filesystem::absolute(reshade_container_path / path, ec); + std::equal(reshade_container_path.begin(), reshade_container_path.end(), preset_absolute_path.begin())) + path = preset_absolute_path.lexically_proximate(reshade_container_path); + else + path = preset_canonical_path; + + _current_browse_path = path; + _current_preset_path = reshade_container_path / path; +} diff --git a/msvc/source/runtime.hpp b/msvc/source/runtime.hpp new file mode 100644 index 0000000..74f0ecd --- /dev/null +++ b/msvc/source/runtime.hpp @@ -0,0 +1,368 @@ +/** + * Copyright (C) 2014 Patrick Mours. All rights reserved. + * License: https://github.com/crosire/reshade#license + */ + +#pragma once + +#include +#include +#include +#include +#include +#include + +#if RESHADE_GUI +#include "gui_code_editor.hpp" + +struct ImDrawData; +struct ImGuiContext; +#endif + +namespace reshade +{ + class ini_file; // Some forward declarations to keep number of includes small + struct uniform; + struct texture; + struct technique; + struct effect_data; + + /// + /// Platform independent base class for the main ReShade runtime. + /// This class needs to be implemented for all supported rendering APIs. + /// + class runtime abstract + { + public: + /// + /// Return the frame width in pixels. + /// + unsigned int frame_width() const { return _width; } + /// + /// Return the frame height in pixels. + /// + unsigned int frame_height() const { return _height; } + + /// + /// Create a copy of the current frame image in system memory. + /// + /// The 32bpp RGBA buffer to save the screenshot to. + virtual void capture_screenshot(uint8_t *buffer) const = 0; + + /// + /// Save user configuration to disk. + /// + void save_config() const; + + /// + /// Create a new texture with the specified dimensions. + /// + /// The texture description. + virtual bool init_texture(texture &texture) = 0; + /// + /// Upload the image data of a texture. + /// + /// The texture to update. + /// The 32bpp RGBA image data to update the texture with. + virtual void upload_texture(texture &texture, const uint8_t *pixels) = 0; + + /// + /// Get the value of a uniform variable. + /// + /// The variable to retrieve the value from. + /// The buffer to store the value data in. + /// The number of components the value. + void get_uniform_value(const uniform &variable, bool *values, size_t count) const; + void get_uniform_value(const uniform &variable, int32_t *values, size_t count) const; + void get_uniform_value(const uniform &variable, uint32_t *values, size_t count) const; + void get_uniform_value(const uniform &variable, float *values, size_t count) const; + /// + /// Update the value of a uniform variable. + /// + /// The variable to update. + /// The value data to update the variable to. + /// The number of components the value. + void set_uniform_value(uniform &variable, const bool *values, size_t count); + void set_uniform_value(uniform &variable, bool x, bool y = false, bool z = false, bool w = false) { const bool data[4] = { x, y, z, w }; set_uniform_value(variable, data, 4); } + void set_uniform_value(uniform &variable, const int32_t *values, size_t count); + void set_uniform_value(uniform &variable, int32_t x, int32_t y = 0, int32_t z = 0, int32_t w = 0) { const int32_t data[4] = { x, y, z, w }; set_uniform_value(variable, data, 4); } + void set_uniform_value(uniform &variable, const uint32_t *values, size_t count); + void set_uniform_value(uniform &variable, uint32_t x, uint32_t y = 0u, uint32_t z = 0u, uint32_t w = 0u) { const uint32_t data[4] = { x, y, z, w }; set_uniform_value(variable, data, 4); } + void set_uniform_value(uniform &variable, const float *values, size_t count); + void set_uniform_value(uniform &variable, float x, float y = 0.0f, float z = 0.0f, float w = 0.0f) { const float data[4] = { x, y, z, w }; set_uniform_value(variable, data, 4); } + + /// + /// Reset a uniform variable to its initial value. + /// + /// The variable to update. + void reset_uniform_value(uniform &variable); + +#if RESHADE_GUI + /// + /// Register a function to be called when the UI is drawn. + /// + /// Name of the widget. + /// The callback function. + void subscribe_to_ui(std::string label, std::function function) { _menu_callables.push_back({ label, function }); } +#endif + /// + /// Register a function to be called when user configuration is loaded. + /// + /// The callback function. + void subscribe_to_load_config(std::function function); + /// + /// Register a function to be called when user configuration is stored. + /// + /// The callback function. + void subscribe_to_save_config(std::function function); + + protected: + runtime(); + virtual ~runtime(); + + /// + /// Callback function called when the runtime is initialized. + /// + /// Returns if the initialization succeeded. + bool on_init(void *window); + /// + /// Callback function called when the runtime is uninitialized. + /// + void on_reset(); + /// + /// Callback function called every frame. + /// + void on_present(); + + /// + /// Load all effects found in the effect search paths. + /// + virtual void load_effects(); + /// + /// Unload all effects currently loaded. + /// + virtual void unload_effects(); + /// + /// Load image files and update textures with image data. + /// + void load_textures(); + + /// + /// Compile effect from the specified effect module. + /// + /// The effect module to compile. + virtual bool compile_effect(effect_data &effect) = 0; + + /// + /// Apply post-processing effects to the frame. + /// + void update_and_render_effects(); + /// + /// Render all passes in a technique. + /// + /// The technique to render. + virtual void render_technique(technique &technique) = 0; +#if RESHADE_GUI + /// + /// Render command lists obtained from ImGui. + /// + /// The draw data to render. + virtual void render_imgui_draw_data(ImDrawData *draw_data) = 0; +#endif + + bool _is_initialized = false; + bool _has_high_network_activity = false; + unsigned int _width = 0; + unsigned int _height = 0; + unsigned int _window_width = 0; + unsigned int _window_height = 0; + unsigned int _vendor_id = 0; + unsigned int _device_id = 0; + unsigned int _renderer_id = 0; + uint64_t _framecount = 0; + unsigned int _vertices = 0; + unsigned int _drawcalls = 0; + std::vector _textures; + std::vector _uniforms; + std::vector _techniques; + std::vector _uniform_data_storage; + + private: + /// + /// Compare current version against the latest published one. + /// + /// Contains the latest version after this function returned. + /// true if an update is available, false otherwise + static bool check_for_update(unsigned long latest_version[3]); + + /// + /// Compile effect from the specified source file and initialize textures, uniforms and techniques. + /// + /// The path to an effect source code file. + /// The ID of the effect. + void load_effect(const std::filesystem::path &path, size_t &out_id); + /// + /// Unload the specified effect. + /// + /// The ID of the effect. + void unload_effect(size_t id); + + /// + /// Enable a technique so it is rendered. + /// + /// + void enable_technique(technique &technique); + /// + /// Disable a technique so that it is no longer rendered. + /// + /// + void disable_technique(technique &technique); + + /// + /// Load user configuration from disk. + /// + void load_config(); + /// + /// Save user configuration to the specified file on disk. + /// + /// Output configuration path. + void save_config(const std::filesystem::path &path) const; + + /// + /// Load a preset from the specified file and apply it. + /// + /// The preset file to load. + void load_preset(const std::filesystem::path &path); + /// + /// Load the selected preset and apply it. + /// + void load_current_preset(); + /// + /// Save the current value configuration as a preset to the specified file. + /// + /// The preset file to save to. + void save_preset(const std::filesystem::path &path) const; + /// + /// Save the current value configuration to the currently selected preset. + /// + void save_current_preset() const; + + /// + /// Create a copy of the current frame and write it to an image file on disk. + /// + void save_screenshot(); + + void get_uniform_value(const uniform &variable, uint8_t *data, size_t size) const; + void set_uniform_value(uniform &variable, const uint8_t *data, size_t size); + + void set_current_preset(); + void set_current_preset(std::filesystem::path path); + + bool _needs_update = false; + unsigned long _latest_version[3] = {}; + std::shared_ptr _input; + + bool _effects_enabled = true; + bool _ignore_shortcuts = false; + unsigned int _reload_key_data[4]; + unsigned int _effects_key_data[4]; + unsigned int _screenshot_key_data[4]; + int _screenshot_format = 1; + std::filesystem::path _screenshot_path; + std::filesystem::path _configuration_path; + std::filesystem::path _last_screenshot_file; + bool _screenshot_save_success = false; + bool _screenshot_include_preset = false; + + std::filesystem::path _current_preset_path; + + std::vector _global_preprocessor_definitions; + std::vector _preset_preprocessor_definitions; + std::vector _effect_search_paths; + std::vector _texture_search_paths; + + bool _textures_loaded = false; + bool _performance_mode = false; + bool _no_reload_on_init = false; + bool _last_reload_successful = true; + std::mutex _reload_mutex; + size_t _reload_total_effects = 1; + std::vector _reload_compile_queue; + std::atomic _reload_remaining_effects = 0; + std::vector _loaded_effects; + std::vector _worker_threads; + + int _date[4] = {}; + std::chrono::high_resolution_clock::duration _last_frame_duration; + std::chrono::high_resolution_clock::time_point _start_time; + std::chrono::high_resolution_clock::time_point _last_reload_time; + std::chrono::high_resolution_clock::time_point _last_present_time; + std::chrono::high_resolution_clock::time_point _last_screenshot_time; + + std::vector> _save_config_callables; + std::vector> _load_config_callables; + +#if RESHADE_GUI + void init_ui(); + void deinit_ui(); + void build_font_atlas(); + void destroy_font_atlas(); + + void draw_ui(); + + void draw_overlay_menu_home(); + void draw_overlay_menu_settings(); + void draw_overlay_menu_statistics(); + void draw_overlay_menu_log(); + void draw_overlay_menu_about(); + void draw_code_editor(); + void draw_overlay_variable_editor(); + void draw_overlay_technique_editor(); + void draw_preset_explorer(); + + std::vector _texture_previews; + std::vector>> _menu_callables; + std::unique_ptr _imgui_font_atlas; + ImGuiContext *_imgui_context = nullptr; + size_t _focused_effect = std::numeric_limits::max(); + size_t _selected_effect = std::numeric_limits::max(); + size_t _selected_technique = std::numeric_limits::max(); + int _input_processing_mode = 2; + int _style_index = 2; + int _editor_style_index = 0; + int _font_size = 13; + int _editor_font_size = 13; + int _clock_format = 0; + std::filesystem::path _font; + std::filesystem::path _editor_font; + unsigned int _menu_key_data[4]; + bool _show_menu = false; + bool _show_clock = false; + bool _show_fps = false; + bool _show_frametime = false; + bool _show_splash = true; + bool _show_code_editor = false; + bool _show_screenshot_message = true; + bool _no_font_scaling = false; + bool _log_wordwrap = false; + bool _variable_editor_tabs = false; + bool _selected_effect_changed = false; + bool _rebuild_font_atlas = false; + bool _was_preprocessor_popup_edited = false; + bool _statistics_effects_show_enabled = false; + float _fps_col[4] = { 1.0f, 1.0f, 0.784314f, 1.0f }; + float _fps_scale = 1.0f; + float _variable_editor_height = 0.0f; + unsigned int _tutorial_index = 0; + unsigned int _effects_expanded_state = 2; + char _effect_filter_buffer[64] = {}; + std::filesystem::path _file_selection_path; + imgui_code_editor _editor; + + // used by preset explorer + bool _browse_path_is_input_mode = false; + bool _current_preset_is_covered = true; + std::filesystem::path _current_browse_path; +#endif + }; +} diff --git a/msvc/source/runtime_objects.hpp b/msvc/source/runtime_objects.hpp new file mode 100644 index 0000000..5d42109 --- /dev/null +++ b/msvc/source/runtime_objects.hpp @@ -0,0 +1,151 @@ +/** + * Copyright (C) 2014 Patrick Mours. All rights reserved. + * License: https://github.com/crosire/reshade#license + */ + +#pragma once + +#include "effect_expression.hpp" +#include "moving_average.hpp" +#include + +namespace reshade +{ + enum class special_uniform + { + none, + frame_time, + frame_count, + random, + ping_pong, + date, + timer, + key, + mouse_point, + mouse_delta, + mouse_button, + }; + + enum class texture_reference + { + none, + back_buffer, + depth_buffer + }; + + class base_object abstract + { + public: + virtual ~base_object() {} + + template + T *as() { return dynamic_cast(this); } + template + const T *as() const { return dynamic_cast(this); } + }; + + struct effect_data + { + size_t index = std::numeric_limits::max(); + unsigned int rendering = 0; + bool compile_sucess = false; + std::string errors; + std::string preamble; + reshadefx::module module; + std::filesystem::path source_file; + size_t storage_offset = 0, storage_size = 0; + }; + + struct texture final : reshadefx::texture_info + { + texture() {} + texture(const reshadefx::texture_info &init) : texture_info(init) { } + + int annotation_as_int(const char *ann_name, size_t i = 0) const + { + const auto it = annotations.find(ann_name); + if (it == annotations.end()) return 0; + return it->second.first.is_integral() ? it->second.second.as_int[i] : static_cast(it->second.second.as_float[i]); + } + float annotation_as_float(const char *ann_name, size_t i = 0) const + { + const auto it = annotations.find(ann_name); + if (it == annotations.end()) return 0.0f; + return it->second.first.is_floating_point() ? it->second.second.as_float[i] : static_cast(it->second.second.as_int[i]); + } + std::string_view annotation_as_string(const char *ann_name) const + { + const auto it = annotations.find(ann_name); + if (it == annotations.end()) return std::string_view(); + return it->second.second.string_data; + } + + size_t effect_index = std::numeric_limits::max(); + texture_reference impl_reference = texture_reference::none; + std::unique_ptr impl; + bool shared = false; + }; + + struct uniform final : reshadefx::uniform_info + { + uniform(const reshadefx::uniform_info &init) : uniform_info(init) {} + + int annotation_as_int(const char *ann_name, size_t i = 0) const + { + const auto it = annotations.find(ann_name); + if (it == annotations.end()) return 0; + return it->second.first.is_integral() ? it->second.second.as_int[i] : static_cast(it->second.second.as_float[i]); + } + float annotation_as_float(const char *ann_name, size_t i = 0) const + { + const auto it = annotations.find(ann_name); + if (it == annotations.end()) return 0.0f; + return it->second.first.is_floating_point() ? it->second.second.as_float[i] : static_cast(it->second.second.as_int[i]); + } + std::string_view annotation_as_string(const char *ann_name) const + { + const auto it = annotations.find(ann_name); + if (it == annotations.end()) return std::string_view(); + return it->second.second.string_data; + } + + size_t effect_index = std::numeric_limits::max(); + size_t storage_offset = 0; + special_uniform special = special_uniform::none; + }; + + struct technique final : reshadefx::technique_info + { + technique(const reshadefx::technique_info &init) : technique_info(init) {} + + int annotation_as_int(const char *ann_name, size_t i = 0) const + { + const auto it = annotations.find(ann_name); + if (it == annotations.end()) return 0; + return it->second.first.is_integral() ? it->second.second.as_int[i] : static_cast(it->second.second.as_float[i]); + } + float annotation_as_float(const char *ann_name, size_t i = 0) const + { + const auto it = annotations.find(ann_name); + if (it == annotations.end()) return 0.0f; + return it->second.first.is_floating_point() ? it->second.second.as_float[i] : static_cast(it->second.second.as_int[i]); + } + std::string_view annotation_as_string(const char *ann_name) const + { + const auto it = annotations.find(ann_name); + if (it == annotations.end()) return std::string_view(); + return it->second.second.string_data; + } + + size_t effect_index = std::numeric_limits::max(); + std::vector> passes_data; + bool hidden = false; + bool enabled = false; + int32_t timeout = 0; + int64_t timeleft = 0; + uint32_t toggle_key_data[4]; + moving_average average_cpu_duration; + moving_average average_gpu_duration; + std::unique_ptr impl; + }; +} diff --git a/msvc/source/update_check.cpp b/msvc/source/update_check.cpp new file mode 100644 index 0000000..323f078 --- /dev/null +++ b/msvc/source/update_check.cpp @@ -0,0 +1,59 @@ +#include "version.h" +#include "runtime.hpp" +#include +#include + +struct scoped_handle +{ + scoped_handle(HINTERNET handle) : handle(handle) {} + ~scoped_handle() { InternetCloseHandle(handle); } + + inline operator HINTERNET() const { return handle; } + +private: + HINTERNET handle; +}; + +bool reshade::runtime::check_for_update(unsigned long latest_version[3]) +{ + memset(latest_version, 0, 3 * sizeof(unsigned long)); + +#if !defined(_DEBUG) + const scoped_handle handle = InternetOpen(L"reshade", INTERNET_OPEN_TYPE_PRECONFIG, nullptr, nullptr, 0); + + if (handle == nullptr) + return false; + + constexpr auto api_url = TEXT("https://api.github.com/repos/crosire/reshade/tags"); + + const scoped_handle request = InternetOpenUrl(handle, api_url, nullptr, 0, INTERNET_FLAG_RELOAD | INTERNET_FLAG_PRAGMA_NOCACHE | INTERNET_FLAG_NO_CACHE_WRITE, 0); + + if (request == nullptr) + return false; + + CHAR response_data[32]; + DWORD response_length = 0; + + if (InternetReadFile(request, response_data, sizeof(response_data) - 1, &response_length) && response_length > 0) + { + response_data[response_length] = '\0'; + + const char *version_major_offset = std::strchr(response_data, 'v'); + if (version_major_offset == nullptr) return false; else version_major_offset++; + const char *version_minor_offset = std::strchr(version_major_offset, '.'); + if (version_minor_offset == nullptr) return false; else version_minor_offset++; + const char *version_revision_offset = std::strchr(version_minor_offset, '.'); + if (version_revision_offset == nullptr) return false; else version_revision_offset++; + + latest_version[0] = std::strtoul(version_major_offset, nullptr, 10); + latest_version[1] = std::strtoul(version_minor_offset, nullptr, 10); + latest_version[2] = std::strtoul(version_revision_offset, nullptr, 10); + + return (latest_version[0] > VERSION_MAJOR) || + (latest_version[0] == VERSION_MAJOR && latest_version[1] > VERSION_MINOR) || + (latest_version[0] == VERSION_MAJOR && latest_version[1] == VERSION_MINOR && latest_version[2] > VERSION_REVISION); + } +#endif + + return false; +} diff --git a/msvc/source/windows/user32.cpp b/msvc/source/windows/user32.cpp new file mode 100644 index 0000000..61a4c34 --- /dev/null +++ b/msvc/source/windows/user32.cpp @@ -0,0 +1,91 @@ +/** + * Copyright (C) 2014 Patrick Mours. All rights reserved. + * License: https://github.com/crosire/reshade#license + */ + +#include "log.hpp" +#include "hook_manager.hpp" +#include "input.hpp" +#include +#include + +HOOK_EXPORT ATOM WINAPI HookRegisterClassA(const WNDCLASSA *lpWndClass) +{ + assert(lpWndClass != nullptr); + + auto wndclass = *lpWndClass; + + if (wndclass.hInstance == GetModuleHandle(nullptr)) + { + LOG(INFO) << "Redirecting RegisterClassA" << '(' << lpWndClass << " { " << wndclass.lpszClassName << " }" << ')' << " ..."; + + if ((wndclass.style & CS_OWNDC) == 0) + { + LOG(INFO) << "> Adding 'CS_OWNDC' window class style flag to '" << wndclass.lpszClassName << "'."; + + wndclass.style |= CS_OWNDC; + } + } + + return reshade::hooks::call(HookRegisterClassA)(&wndclass); +} +HOOK_EXPORT ATOM WINAPI HookRegisterClassW(const WNDCLASSW *lpWndClass) +{ + assert(lpWndClass != nullptr); + + auto wndclass = *lpWndClass; + + if (wndclass.hInstance == GetModuleHandle(nullptr)) + { + LOG(INFO) << "Redirecting RegisterClassW" << '(' << lpWndClass << " { " << wndclass.lpszClassName << " }" << ')' << " ..."; + + if ((wndclass.style & CS_OWNDC) == 0) + { + LOG(INFO) << "> Adding 'CS_OWNDC' window class style flag to '" << wndclass.lpszClassName << "'."; + + wndclass.style |= CS_OWNDC; + } + } + + return reshade::hooks::call(HookRegisterClassW)(&wndclass); +} +HOOK_EXPORT ATOM WINAPI HookRegisterClassExA(const WNDCLASSEXA *lpWndClassEx) +{ + assert(lpWndClassEx != nullptr); + + auto wndclass = *lpWndClassEx; + + if (wndclass.hInstance == GetModuleHandle(nullptr)) + { + LOG(INFO) << "Redirecting RegisterClassExA" << '(' << lpWndClassEx << " { " << wndclass.lpszClassName << " }" << ')' << " ..."; + + if ((wndclass.style & CS_OWNDC) == 0) + { + LOG(INFO) << "> Adding 'CS_OWNDC' window class style flag to '" << wndclass.lpszClassName << "'."; + + wndclass.style |= CS_OWNDC; + } + } + + return reshade::hooks::call(HookRegisterClassExA)(&wndclass); +} +HOOK_EXPORT ATOM WINAPI HookRegisterClassExW(const WNDCLASSEXW *lpWndClassEx) +{ + assert(lpWndClassEx != nullptr); + + auto wndclass = *lpWndClassEx; + + if (wndclass.hInstance == GetModuleHandle(nullptr)) + { + LOG(INFO) << "Redirecting RegisterClassExW" << '(' << lpWndClassEx << " { " << wndclass.lpszClassName << " }" << ')' << " ..."; + + if ((wndclass.style & CS_OWNDC) == 0) + { + LOG(INFO) << "> Adding 'CS_OWNDC' window class style flag to '" << wndclass.lpszClassName << "'."; + + wndclass.style |= CS_OWNDC; + } + } + + return reshade::hooks::call(HookRegisterClassExW)(&wndclass); +} diff --git a/msvc/source/windows/ws2_32.cpp b/msvc/source/windows/ws2_32.cpp new file mode 100644 index 0000000..9d58029 --- /dev/null +++ b/msvc/source/windows/ws2_32.cpp @@ -0,0 +1,88 @@ +/** + * Copyright (C) 2014 Patrick Mours. All rights reserved. + * License: https://github.com/crosire/reshade#license + */ + +#include "hook_manager.hpp" +#include +#include + +volatile long g_network_traffic = 0; + +HOOK_EXPORT int WSAAPI HookWSASend(SOCKET s, LPWSABUF lpBuffers, DWORD dwBufferCount, LPDWORD lpNumberOfBytesSent, DWORD dwFlags, LPWSAOVERLAPPED lpOverlapped, LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine) +{ + for (DWORD i = 0; i < dwBufferCount; ++i) + InterlockedAdd(&g_network_traffic, lpBuffers[i].len); + + static const auto trampoline = reshade::hooks::call(HookWSASend); + return trampoline(s, lpBuffers, dwBufferCount, lpNumberOfBytesSent, dwFlags, lpOverlapped, lpCompletionRoutine); +} +HOOK_EXPORT int WSAAPI HookWSASendTo(SOCKET s, LPWSABUF lpBuffers, DWORD dwBufferCount, LPDWORD lpNumberOfBytesSent, DWORD dwFlags, const struct sockaddr *lpTo, int iToLen, LPWSAOVERLAPPED lpOverlapped, LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine) +{ + for (DWORD i = 0; i < dwBufferCount; ++i) + InterlockedAdd(&g_network_traffic, lpBuffers[i].len); + + static const auto trampoline = reshade::hooks::call(HookWSASendTo); + return trampoline(s, lpBuffers, dwBufferCount, lpNumberOfBytesSent, dwFlags, lpTo, iToLen, lpOverlapped, lpCompletionRoutine); +} +HOOK_EXPORT int WSAAPI HookWSARecv(SOCKET s, LPWSABUF lpBuffers, DWORD dwBufferCount, LPDWORD lpNumberOfBytesRecvd, LPDWORD lpFlags, LPWSAOVERLAPPED lpOverlapped, LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine) +{ + static const auto trampoline = reshade::hooks::call(HookWSARecv); + const auto status = trampoline(s, lpBuffers, dwBufferCount, lpNumberOfBytesRecvd, lpFlags, lpOverlapped, lpCompletionRoutine); + + if (status == 0 && lpNumberOfBytesRecvd != nullptr) + InterlockedAdd(&g_network_traffic, *lpNumberOfBytesRecvd); + + return status; +} +HOOK_EXPORT int WSAAPI HookWSARecvFrom(SOCKET s, LPWSABUF lpBuffers, DWORD dwBufferCount, LPDWORD lpNumberOfBytesRecvd, LPDWORD lpFlags, struct sockaddr *lpFrom, LPINT lpFromlen, LPWSAOVERLAPPED lpOverlapped, LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine) +{ + static const auto trampoline = reshade::hooks::call(HookWSARecvFrom); + const auto status = trampoline(s, lpBuffers, dwBufferCount, lpNumberOfBytesRecvd, lpFlags, lpFrom, lpFromlen, lpOverlapped, lpCompletionRoutine); + + if (status == 0 && lpNumberOfBytesRecvd != nullptr) + InterlockedAdd(&g_network_traffic, *lpNumberOfBytesRecvd); + + return status; +} + +HOOK_EXPORT int WSAAPI HookSend(SOCKET s, const char *buf, int len, int flags) +{ + static const auto trampoline = reshade::hooks::call(HookSend); + const auto num_bytes_send = trampoline(s, buf, len, flags); + + if (num_bytes_send != SOCKET_ERROR) + InterlockedAdd(&g_network_traffic, num_bytes_send); + + return num_bytes_send; +} +HOOK_EXPORT int WSAAPI HookSendTo(SOCKET s, const char *buf, int len, int flags, const struct sockaddr *to, int tolen) +{ + static const auto trampoline = reshade::hooks::call(HookSendTo); + const auto num_bytes_send = trampoline(s, buf, len, flags, to, tolen); + + if (num_bytes_send != SOCKET_ERROR) + InterlockedAdd(&g_network_traffic, num_bytes_send); + + return num_bytes_send; +} +HOOK_EXPORT int WSAAPI HookRecv(SOCKET s, char *buf, int len, int flags) +{ + static const auto trampoline = reshade::hooks::call(HookRecv); + const auto num_bytes_recieved = trampoline(s, buf, len, flags); + + if (num_bytes_recieved != SOCKET_ERROR) + InterlockedAdd(&g_network_traffic, num_bytes_recieved); + + return num_bytes_recieved; +} +HOOK_EXPORT int WSAAPI HookRecvFrom(SOCKET s, char *buf, int len, int flags, struct sockaddr *from, int *fromlen) +{ + static const auto trampoline = reshade::hooks::call(HookRecvFrom); + const auto num_bytes_recieved = trampoline(s, buf, len, flags, from, fromlen); + + if (num_bytes_recieved != SOCKET_ERROR) + InterlockedAdd(&g_network_traffic, num_bytes_recieved); + + return num_bytes_recieved; +} diff --git a/msvc/tools/7za.exe b/msvc/tools/7za.exe new file mode 100644 index 0000000000000000000000000000000000000000..d3055c8f4effc5a84d3bb2b48e85d44dbf8d8b71 GIT binary patch literal 1144320 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

  • Eft#FE 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

    HrpF!#ypDah8 zB4$EOm?!o9IY)9eD%)~CCsA<9URLSi=GJuc&iq8q_nd3d13c$^q8dEs;*lNyH7OQc zm_~Uqd+50Nln_gZ-EwTR^$ThHQFwM!RV& z>3>@n?_Ad(O*Cy4od+^*Hqb!qy4+NkCx-USUIWIMMV} ziX>P4!~qj>I8(-KClY^|Fyec!hLO=hEwHA6Pp7n1D=c_d!tg8<-qou#oEDE3EiAP{ z?>G}M@ezFCbV0WA2~V^8X9f+$tS)nntAVxSo6;rn)z|pfcUDQc%WPWC0g^K4XL;ym zu4#aN#y%$OEa%8)rNC)}b`YNl2C=nad=Q)MAgVzA?fVJB&#Z1JfwL_&YP!cENaQ5; zno~rUb*0Prw;|bhQQcL?BrBfV#8(JuG^hB()s$<|2z+gM&T3jd@>z35B#M5WJWyF!t_>xmjXU|suE zacj?iA!idNfFSs=ZPRCCltdJE^Dli8=TwzZjVS8v;l7@zR(LEF*H@IJPW0Q#g9?z^ z*uFC_0mU8o$rLx&Gg}mPy58I~Ti@r00k5xTwyHq8y9sC9`scAsi5taS9{_<>;vC8& z+OA}hrFz#XG4yajGMGRO^y zNXu5U81%l}9%&)gM}aU5RqyjjFsG^vj@yw7^^5^PsbZDjyQdK~@)eJY?w*%Fpv-IG z$Lu$hTRJ+$JX0qPZfI7JOQe#J>;KBdjJrusf-ySq0^~ac-gs8RbwF3-LVhaFS&ula zk%7uRz`zCr6nW5HnmLs*K3Lm{BJX9cYf&gfS`iMm_M! z=OM{+m@RaJQquDx+SkHuZS@z zx~V+>hKXTFS4$dON_bB>BL@L6#;NmZK$OQ!mw;hsr6{e6Ru!gIZLar3Ys{6A7c{(Y zFjsUlbG<#W!jg~?j@1)dsY=GgN<1&hoN^6puIV zGHNu8Zw$Yyq~yCvdR6~&V4n~C|ImuifvlgWBJ$95cBFq|PRw?{_c8Trym zc?e^!q`a?5Lf3q+#_WuCMbp~KKp87fdjF=EVgYqVEabr*Jos*mfB3DXjLUsoa8TT> zi}iy|5D=*IgXE{H}@#`07&6OvUs&cN+1Ov1s|_G(v4MNZ}NX5w|+}lAG26cJ`W?9=hogX2T_3X7z1YK;xB6~OU?RR&&5z$3DIl*ei}v;jp_z{SS05M= z&fV-|U$|$A$d_nMqBONXM!qxu#kkRY*fu)^`JZt}KPn{vAQF7d&M7HjB8&a$@@#AA z$PZ0pJ)^ePbw+Q`_f#3%o%tZUs4}r8FJ_g%qapjvzBx3T{fZuF!&~qdy#+WrrVI95 z)vr2HL~!Y{vNXI8i4cK6Of4I?dwhgO1(7v8?kI{ADg&to>mR%{W)>XH@H*`V+64vT z^@sA0gHm)F%u?q|F+axjDhQE08~RJW$@rcPktN?moqND|C26sKNYJuKTL>SsTN%9H zX&|(px(FL`ty_8aftbcZb585(E6f!E>)$zGkm|3g zGfd%-RkmS#QYUH(m1uH-EoG21k|ObKaFsHDVDuCI#Rtlh{#W%`f>lqbKJYwyVwsv$>M|cuOqsRInY|MNgst3}B|QW(r?*D!aJh?1!v8{-oB1vC?NB_s zG?e;4BJ+4R&kL!!?S8L*v7-6g5`MpX;5SJh=S`_5!>I^SK%>XFk5fSZ#axzHR{Gui zmP!^AB&5C;sYy0T`X6JxuIT_Z`DgFe^!`2)Z+6{meURT|p4=*UvK7ebYYmUD-I$2n z`;-LoPrBK8$0TH#`o0rz$=#8jzte>B*Xk?T?$}(uRrJDu<$qGEtG&g7hb4NuQ&a?o|WWm)e*5Q$BV!`GZ7}UTNgPDIef&nYW;fqc!`I(W_;Dl5lX9i6!leN3Y6% z`*hWPdsG^EK*>)!4^>bhQ(;x%1e@z5bFjjBjAyhGe@fAzd+EB9`SS8mfno-Mn@&Pznz^K%e0;j?MN65v75c8T;u9CjYMuZP0t(e6(=I;0hy^i@p8{R z0Nzy4TW_ta_I$(5skno?{0C1p?BNti*FrAiBT3)Rie39OD;~4?)!+b@sH~iBEX@X> z$Bck3A=C<^_ZSIGqh$zk%_hx|K`S@?C(+EXzbQGm60$P6sEz#HM_1c@rq-R&ZqsY4 zE$RLa?sXN#71OXe{(nb$q}(`-?bk-WHK<6=UT)>hDf-VQOFNQHoawbL+RfvYxG8ZN z`7mpt+xS&6oJ96J;uz#ZX|i~$7v9b@+c9&X_yij?1>}x?@TBsQp4*WG$MfQuk6ho> z<*pe}T{u^og@Fu+!kMMi^@g`6BJuCF`_CcmU-~LK!S|BcZ}kI^QddD62=Cm;$wCw4 z=625dqQBy{sp(8LH7_j#KLXL3StUq_X;uj&d-Exhvet$UjR$W!#%b|<>3COYAM-K{ zmEyT7zyg8tPuf@dned;^-^Tu*w2Fv4lVJVB)av*z85?pU{1PCgKCLA}KrzJ)2K^oF z963@r*-X7e(~vr04b+j|E<+gW4L)4|g@nH&QL8+l#o|Syv9V_pX-jd#tSm3X)Ruqm zx5cm4|G(_L4S1C0nKt}DG8!c~QNhNF^|1y`%a4K3HUjDlnZQI6n3900Vo69QCTo7i z%tV5XZ8UM4bjUXQmUgpSwwv9uUA9ZRv`Z~*dAA8rjEWkoZDU1^mbQanjg{7D(R}B5 z-`73QlL4&ne(xUNaeT)urUfatKU~l^6Sc33A@Eb@#ovMm-PY-q2Q!qS79d^}qDu60M&aA^wfW>7cfM#bD zs!Gu*N>hGGj9p9XI#o)h{h0m>PLo|_lb;_@bTBJ z$qa{>pv$~EEh4!GUHRi9sGP8{PyrPj09wb_=sjCbZwu{}x(c=;?m39VahMXvAmv%? zWf(@i`#qZUt{$$%{U@AU&z}VKMDMNBL5yk?PaK|R{L3Mb&P zB7(jfbCaw7^#-0~>)D36p37N>qlrf^I`MgRi2|1fI&rZhR};*;zJD4`?g9S~^hIdT z;y~@6ME!8$@j0An!8R6Dq)6A9?-Wk6;#tcsZoQ~B9!jrW_Q~|tbRMs~Vz6@*g0UI; zIAR96*_#Y4o3h6u~P_ZaWT|W#TYjXW$3{Ke%WvB983##j-4(}8!TQn*m5bZ>4yk9 zbs(Q=c>(Eh0lSU!G&1#~v5r4`H4uk}8-mJ9?L?dO_loEPaplq5X=tg4i*Mc}eK zSQ#H>MTEtY-AVwLTCeU6EI`wC$ou)}6yj~m*pbCqaCj&;QT=rc242Oe-rL2z&o&u# zPES(??odou=S+(+>Tk{KEyA7{HFpkM%vhD|$-yCB(1z}uPX^8G&UIqF2^xd(cVe4V z4A%CQ#y^Wig=d1jnfWDY?*!*PUD~@UBe8NPy&v0*D2$`-MhPDUi$aNcnzahOGgLmE zpUbdf%jj4HAML}(`m&0bz0>l|{8#^Y5Eq%l4V+KUM__TK+-w-Nj;m|tikBVr27Yop zfF#k^&MiI1o9a*Js31$r>f=Xw@%i8H)bLqj!S zl!3NXIzd#&SW;jfR!0uo;K>3mX1iBfOR4&mQr^`1lJ~)1pzgw*VVT|s9#?O#VX*%= zoK9pZzqMYB!lWu3B=6&iuPQ_DgEy(1h0&983bXCN{6BWIV|9`pg*uR}GeG9w=IweG zF^1!}nf@PJFjnjMdP$$<=f&b>EULUoI*99{;coxss`AK?m$X0DdI}SCxY7_;-ZLzZz<>9@%l_p(UsaXTi7m(g-Uq)!e{)L! zg?5tYOa}NW?eNq9e^VRYpn~RoP?bm(tOsjhJO7W~hfvS5Vp?Pl(s#b)t@Zb0=G;@N zrtVnDFm}|61(3}Rqle8+MB(n7HihVa0o59YLM&?#v4#QQjs{siPF#iqCO4q!$7Ow4 zh>#OIFc*i-q;npG9D1dH!u@rON|lPYWHUCs54IwYm&WnAO2j8Gr%YUpJVwSwFi2mF z-nY6X=ijE@{kQ{5@tMJ8Z?$|W`0x;BrLjeQ82he8{fQ4~^_P(WA7TsFeO6-j68^0&Dy)WU-w8@y?Q2VK|SnAJWeSGHpAWvR%aW;6vqI(vp zLYzP8s_zT45PztO&QJKh2LDXrYsC-S!Daa^$E2n!g8*V4mt~@aCnvNXF3dpMV7?w} z7KB139MYOIf36}NEaTwwS?<4D(zkUZt+vc(ct9b8xw2s z)t0_sT4<~!4Vie*htFrWj+ZWbIy#fG#X~RSyQ(GAGm3lDF&rO~DIL~jm~@Ecputsv zv-ves%*@0#m*A_J`2J1Sl=4{`FPl(b@+d`z?7Cu{j*d?+>CK#mZJR9@7xU6Ye7iIL zNa?ZzEx$f-J~9Zh0TLEeizC<@%fL()(*NgZC_TvyH{Px%V2QUM;w(AG?#LvVw&Gos@!`a^=+=>j zMa&BGb_UKvV})C7ncyx_=nD3vV@ANoQOJeZnBhY*1};S4eVCmLT$ml4#2sDhF?juH ztPwGiST{=*vcKS-XzZQS!+~Pm5kYY;p0^$s9?r@^ZwloQVNO(il_@tR^xwv!PHO18 zE^LAdx#Ak1&q$ek*w0GkJS=`UjJNNwC-@pjHAt$@IxBUTp1f=XV zHVwsx-hAW1pQ0tM#hMbXgn4wO_o3}q`*39!_GSc!j%WLaUe4e(Udk*mV+0+$V$PKe zC9=r!J~Zs+Lc<{FZ8kNKG1vDZZvTQ{{%~N%QzxoB|8(9xpXP4rPhkvLtj5k+(1EUa z(qX91rd6c_*BZl7J;=^Di-Y*^Oj4F?2*=OIXH59%W_+g;wNyG{24j!Y-Upb3CGngb zrW0Cq#01mMW)h)$dnf}HqbYw6Z*9Mv(Ey{2qIlPrM!ubeJ7`pNDf6QIuFjEfauW<~G05ApDUUcD z0xv%D-7FP_%1{Y^1HzM!26|SVZtaz7SI-i*sK*c#roQ(f zBndudx&LoQ{th>xB*W~_-~YvtuPKvkynlY=pRyPne99H?Ur|BLzfa_;p(wudZ$zFs z3FWIOf9DrPCq5Cp(`Y!L-nE86epr1S2cCche|-B?PaG&7dIjOy?s$Zn^S3KiNC%}} z`3XkQ2>)RQ=LB5q`aJ!Tzn=GTF$MmC-dDyD3*;AN`a2I}vTQl~izu`{r#!}gkGl^2 z7`ep{6>;eM6I;jB3FHNY6JJT);r1kMKJ(}Aa6Fj5BN2Lt(~r|1!Q0u~T2%!gAw0&&EMNivjw6<#J=95IN*^?4sU zY~p}P865f@k`_@1JBX7B@E->KeHB;nQG5wVaaB?s2w)(-{xrQ1h(D@)@jf%nx$;c0 z&Vwf3u%tn`$ux@jKC;b!HP`5@>|~qbGqcUAb7h+cN73IOpZ}H^kZ)ztAEo4mHLtu zqaHx&oqlK5hzHA5jkuyW{SO>;#5dlb8HmriAIlc^qbfz^niVh2@aG4S-%D|i@vB(; z4&t-z&+3MuC_bd>1Jrvc&1eo7=ghPb)P<=A}oJs*-1xFR?sAA2|>Qhv~7rOeSy7*pY&PZ3q zoN9+oWH8Z`(caHf5nc=P_x=qVFn>ObAeG-bN7tT9Rqcsky{4lfsyrpKRE4QSijQHV@`PjP)#Trys$i`}MYj_`6b%*K$+$v= zsWs@zQ)xJa=*~$-_rt-V*RoaR>8N3?%*xY=CtZ1}c+&C5NKuui#1ksaT$%;q&#CBx zGL&3Y?js<7Oiv(-BqKI{Ab?}FfsI=*+#jEAi>S}!6})B7MWR0-cgML zTRyb8$;d!v(UI{U8AmEmcU-U$NNPbC&z5KK{d82GDlT4;JOd-0XIO(F)BMBPGR=cW zRo#h96NsbBiZ+(DAZpA+7VDmZk%v?PVdz{n4NXyB9cFBuVGJL#ht{R+f=j`Cj3aae zvA#s)Q27m1L)RSXvsakfIq20DCP}3i-8|&N47Q9Vfw7 z;8=D#P6%SAA-*CLUkjevpT_V{rW83lz*#~yh4?JKt`)mY%`>|CQ|Igb-Uk<@#aChW z_vuB2=~#(cRG1N8mAR-eGjRp>#{}Y61bVKD^Fxde<4V^jdRCpn-iDr46Y;`Kc9DN5 zo`*{kmgKC1uJ5r0aL_$Lh^{WC21|Kq`vDGH(Fu092%M;w`CEUy5tU3U8o&E+km8O^ zy~iJuGKEX+mqE~qGZWAevbD92=Y{0ZGom^OpoqM1C>Kj3qO#MJtfg@yF4^-t-Huc%<{CD=-2@p~nZ^ar392HSSaK1L#!vXAHT1 z2Yb4{$KErl;TgkZy_CU@$D{rkgS3WsCHzQW#(^0JdbWL!-C&y18hXn`Glob3;MTT< z^d7tsAQye=vwCMxOfB)G_aXR;X1(c`B#m$y=4mt<`M)g6ABjI!l#dU?qM!YUKYtWo z+k}G>hJ$>?he{5^!6U5yb35&M|PWXFitvf<2z+SKxxqUeC8R^`fQ*SXcLW9s!jIT!Wo>?2B<@ zlT!|v@SFm~h48(eJgk#L{;##|?_#15)R#21Q}ga4W%_E&z_^0-Qy+%e&>G@1G zbNCcEH-qy76pqQs*-OBEb7YEDmH+XHp6LO6dv4j&u^i?ZB-V6){88koqQwV>j&lGw zScHCEAfGa=W2v7s)%(dxdS^dx+|Z$5kfOLphJSM!JC zLr)#=9Gmv$=v+k)#fO#9f2Y9xZ?o9=dwiJls9`P9>$x8O^#5QG#wVU zUU{ER#=o+WWIdjL@DZ+T1C@B=Dr^Oe&)fzhWD#!w%SSUmM}DV!J08=!*iz|z34rN8 z`v$yV{^|z|D&oKRM=QLa|HYSYZBU;j{=%b}F+u<{f-17PHu>hW#n(I*%pd;3P*5dE z@XgWUYYqnUhrTc@LjSE2h?wFY--P!;^`WBP*$+&nS^j;ju9^R^CvE~0dU>dyFz1G8 z&h!KD8B9llJr4}yQj#+mm^OT11yjOMM z7vrWEz?qJ}Te9%8&c@GedH89cf!Eg_#^00p`vv}9RHl$Bcmc)*FTlLu1uzO;AZPiE zb1>RxjGv9udBScTHuhem!WXIVMJjv|3ST=6=3;~cetYc+ytlyR&(V{w{hSnh_c{9Z zHTu6L57cc~$2fp?Es^f+yq3{~!hhms6}}506HoyfqF(|tpr+u3bmis1jB)zqcXXE~ zbeN7dzs{uvsGAo35lrqL`}7^R!&F6XGa~n3t@o+`^31v88w>M8b`YNoeDeh!06uYp z_bMN{1n}@{3{+so7~DJx_X)}~AmHLbyY*EVJx`<0O;7sbC!his4W8i*SDW zD*c1;lfj`^F<1R~aOl_R59`bE9xmb#8dv41C8CG%DONm=&yPO`(tA}0O%Vn6;pdq* z@q(CAh~!a-gi-i{3M8@+0(h1zq{7P-Kgtw8$`n7E%tA-c!f03Nbp}Vp6vTDjd!81b zr^e^0@paM}WytdpDf!z}(DO^aXXS5u zP=|U5QKI7F5BI;M#Ibdd;(?_Z`3NRrim{&G)l2dJVtBC>R~pB(-|>?8qgb}|cKt2< zRs7}<+G2bb3_Y4fSUpvV%mHyQKkx@GCbT|Z-1&A!OQbaIDYl}WuV7K><~d`#bo7?VBfCGR0};{YEuMFj@)2$^`T2?8oIsJIZ46N=*hR%~3AWMwkdc!1 zLvTGtEVkOSRf;i7DOQh)@Q2C5(mcH8-Cs~0Z5)ebK3rtO5i4F~pYSBgR9YUg?>LA} zrX?6HX;15q{=usoIqZtTF?iZXPZ#jRP1O_F0)=BpN)h&nU?27`gK4;a2fjowa44w_ z>v=EeFmuP~Gba)hM>j|XCB&epahH!l;hB<(gGv8)-e+65ZX;fbuUQr$4zMvcURs&h zt(LZwS5<)ZrwLX3VyXonZ`Q{i%zbK=W0)BoEsYQ=U*f`~%{8@3@X@;+I@*o0`bK*DeeaUc}t-JN!y|Ve#va2c|QUkX)E=z{Q<7>h2H3{S)X*@}iC(BHN-Hia|-@(+Iuq zkB6sWXKwH6iNx2duuj@rh;(p9Ear0lL>H_o*$iZ$L>omB-#)d*Z-gf2mM4AU_$SzT zn>+V)A?_~PVuTmwKX*g_1dP02W-3>nxU#b? z=CHkFCmgo=^w;F7&2h=UAlaGdD{Jvn<3)%DJP8L7pCr&xi57Rbzle3@UnV{a;y;{jdP?`8=DB&4pj95MY8|)HZz(8x*7VC?{R$|8P z34BWoYL{RiRtApG&3+Jale5pq?}^Lf6FA3SX|-NJM*Bjf*W~Q`0DWjsp%!J3SI{ui z>!ee^s=T6R!G;_J|EYLJLgiNMCcWd}2OJW&fF}7nwx4atM%XivNc}X`&A3uwQ)Hb zO1A!sFae^nd!WefF38*ZVj4Z)K`XP)}Q1{4?b}ZS5uTXl`lRDIB`0!<=5h7gkSK z-zk6kx2QDRD!wq|ha4!z-+ua)j-hEi!O$00PkZ{e^WN@hD-XnUEAbyuGfZG-PPmPtt@rm=og7{s_{XbN#^>h9ikH;=TwRbw z?}M-KU-S6TOP8TxHT+Kc{c zo(-nG4!fb=Iky+zf3O2Kp8i!y+H?N++wm~O6GMp^-hUsOHxd8Cyo9&#VF-TaJa6GIevFf0mUqSP z{N5FBt1mBiJ~uh>Zt+M_0>E6O8jn4QR)JlbKQ{mfC zxI7;UXT7JwDSlK6lb&=GCVi+B{*MpeE1lm*9KOE-rKgZsxJ84>?D}WoQw-oip(2{r>ign0MbJZ!JrBA2^8_#Ca3*68O^G+e5E-=Dl6p zvtQZset(1g-gZ0lX7JpIQ&u+EC`A@Fu=zU&v;F%$2mtaUFV;CnfjK}Qu&tQt1Aosl z?hEwqM@M7weQ4NqRt@(b97nzLBmB`9t1({V`2GKS0f|4_`6pk?F8}^l;m~n5&uYBl z8lfxNjMrW9i4KvrqlwgSdx{%l!_%;52%Xe|870-U&+J%D~_I# zfl(!Admq3&mi1%ikIp}Nyes~M)b1Gi?fE5XF9zbjQi;V_d5;L0Qj7ghP5V2i>Q3f? z|Aw|8{@8yd7j!;%vpW~y#ICBzI^Vj^`@mme#M*g$Zs%jFX`PPyO|a!S{lxq;i;yWl zPbK2-potB><}H3<@{w3x+H<|pELFpJJI7F6xSQn@KTXl&y^7`kg7>J9+|8{jXlT4u z#ms_M+#5Zw#9R1;6P(XX=QtZWFFv=5w>bV{4s^Vtcg#4^bH_U6_M z)CValGu+VcvrmWR%!Yyg+0qxqu0FgK~&3ucb2{A z?ffDpvL+A0EP}yU49!y;I<^rU*~IWChVdUS-E2ppzt+DGjgs|a%rIF2^pI31zIfix zyioTJr~I9X^VnNu)RfLURWk4OsHY?iXZu%S zJ#`6=IkPuA^wRXA_^G06UN6K4i1MeyXm#&)g+=+lDN1|f&n9k>x9~Sb@z>#o>1?R> z14&|aKKG7E^hNgmcA}`RMbr=uyfIgkOKQbyuQx@>s9+#N^;6sVO-Qi#rL1EaB!SM!p`C8DfP{~x1*k58up%G(s$1C<2!J|DWwUT+SBd9 zwRb4>esz-Qhd5-#y72&e(QpDi@Pcy}Y4n|=Ew0{V&xQgO1Mu;CTJk<{Ihx57Z=lN$ zd&h(LN%3zA*_UDwm;ACG!_XwgZ@|ak=g@mPAN{}-KZn>_93R2fmQ=+dDrRHS2Ay^9 ztrPnwU89gX`XqAvGiOm+2amRs-LBllCi~Rzb{@al`{u7 zDqA&wfbmR8{JG+^_h?C--gwX!WT_$XKjm$)agGg!x5@)sm$)5_bf6NoGPC8 z+*2ox{|&ERi@#YMKdl?RH-Cu?^$Bb*Vf)2J^<}TOzRHGg-rL7L@NEE-*fnabbK+9m z+t>OUW_xRR5jApocK+EW7^pX+WFBtF#(nWPu(B0j^eE`5U+#Y@5y174fu4FFTJ@Tq ztsB|6_uxWbHt1Wq@2Rb4YY5QZv$Y)izHzL>zsdh;{~i8~DQgFun>xPw1$7t>GlSDG z8FGBzan9R%daqEEh4`2)W`aGu~)9_LIatcWd4n@;V3ez~4mvV#EKW<}cO! zO7Pc`e~IEBzx_qy-u)kjdqKL_JM(9JU#{Ojat+?|_|CH?y)z$~%Q?e0E~k%X?*xGp zQj@czik`WfbdP5yT$r5wq?U;onYE;QJc)njfjE5&EA(x}GywKiuW%4{;iwv-iCL4V(F2?p4tDJX5j>v=Y?Hxp~v$gq1ThMRyq4D zsQkh1nZMV}pK5qo%bZ@LWFFVlUj%Xco97V_IEUyN)`mZW$YIZ;ATV9;`4N!X_LJG; zQPAu8mXg^8Gg|z2C3D#GZ6NKChJQvEP|@Fs&_9V#+(kp^Kju?t8w_dl%OZ522$6=+ zQsrE|7T+l+VKl}@0MCt_sn7vJsw)mA3W??3Z$<|QwA5- z9QG^&L0ua~sL(}2=sQq!*mFG$X>+Xz*&;+5LfuN}YB5|PLYDw(iS%KQhup)SSp-=5 zq_T28MGkwWXs)W9OMGRjT zp>ZHJlZMa_mC#WUI;IUjwURc6#qdRKxJ^ku0)lz)r$A-kTJ9QH&(P*1y(IqazcGNxLAJ)SN2h5Io;w! zzKKMS=MDg!4*;oVy*A!J==GFA6ow%YS}Q`uz#h+P{6cpS(5>vl!iSWfZWg&bVE!N! z@TJGI7{9P^J)qk&55KT*{1yh{S`f%Yp99eESwMQ~^FXG^1wdpN{K9$$pxcvB7t=OhPJj2B-I=T4S0pn>)8TPP{%~*b`d(Ng`U?!YqZcWL}(3!7%NX|p%E=K zR|`F=gt}*{ko0&Cf$Z@d#xFd15YX-UF@E9l>&5h>M|*O&Hha8~;XY41@h{r&5Cjja z(9-*V2SXKme@m{Wq3x>>0=3<6`O@S03Vz}4mjKnP)bjaHeRQVJB=}4 zdOQ!}7YZoc?fEQzp&&;~-VI7A;7gAuieE@Z0NtJj{6g}NsCVJ1O`w$Ir)jKdaGz1N zauv)Nu60W2u!q4Sy&Bl#Da9|e-U8_Mti&(u^(%Wn0Kr693{=V&k*jG4HEXBm8}2;K zB@J$==3Z;KeChGz;unt11$29^#xEqV(2}!2DOWC~agT>IxNoge0ldg?XKF5KaDSn> z8HUT39?ymNg)8R+x;<(5ek1w&G@qNCl>Ld9lJu7LoFp6cPY!mD zt$*qj>`-s1}2kU9f=Abn^oJ3gE?ICT6f?zsK_l|$*+lKT|4gb%%P)tkS(|7B%@ za6&&sm$4V;Qh5EA-fec}mw0^Z>BQfm13QVGB^SFY7d2P zG4kKX{_zCXuDuUkv=5%Qo*u)Q{>zJdx6QBIj^m*5L7(@bU*M?pM%WIfo$$T;Fa`_0 zj>$_}9_Ikoc5kULo4`$-V7d zxM+Cyce}o?*G=#@F0N|_V#C8TlBP3jTeaR}fmFkA;D3>-w3`dc$xU`TEUMCmI!VXY!Upd zV4vWC;9fzK@31JrhaWLhc%dL0NIV@_Ay9}ughCCL^&!c>^HcKiVu_zz!4C>%2~HP$ zC8Fc`=YroA91#49;5NYr1lt6g1ZxDBir$dWD+O;7{J7vk!D|KQ2+k6GSHg2j@D;%q z1RoU~6by*F6@n##b%HU$Zo%z>UlY7d+%@rPaQqh-CgPb8Odr*>E$9=h6s!}B33do> z6C4vfDL5gR{;bxUB{)YgS1@}_^9uwk1aW(Z@?XE;Zoz$mhXh9i#{|a(LoaFjeS#x` z121dyJ%WRRBZ31Q`{UUoI4C$GI3_qQXdTn?wqTy1PcS5?l)lH$=pVG61q-y0?fKH> zEF&Yhe8xO1BYmK4Ao-*_#vDEyrg0aa_9t9?59k5VHn6<8*=}fVy3>wssgG6HVd_jj z#ZA@CEiK{dm@w=}b2L_4-)018FIv~!V)(|qw6!5tA882N^-a-ORa131YS-elNmBdz zrn{P&w=^XUO!z8aa{N#KWq{5Dl8?m*YdD1;0d2!P_X;0=^)k9K1 zlOb}aeuxnw z)vm2ajS{Jf)meeNe9D|U`DFb`m-h@!vb=vBM=PwczwjxO^x1xe9n~5K#C*5l5y4Tx zU7~*)1BoXh`nk`7Pg&59E!F4N-!AsM1-AkTJ<8I%O#n{F)Js2wtLffTuH&_0M1wX~qxz9v~-x?OTC4<#aJ z^c+gz_kp${XZY4B$3L`x#Kouo>Rf#0w+zsxekA{B3cpSGg)QN#SU6bKQd3gjbl0uc zT~^p?v6}GPVC7qjk&|lh)&LFqG5)dtFx5XNq0feh;crag=Yn?g_oVRKg}*S?7@A3SD&?g}m5Wp%3_GX###M2Ic9_R@8CjF;@?sM@~I6&L92m9m?r||nz_|`j) ze%c>#@kz&Ae3kz|+b}o!gYQnYp8?tje)puVe>S2OyxEe%CH9y;kx9oM%lw4cqh8iw z?bb#3)lW8{1M=O#Q=&Iv^l!X*BidNyz(%}_6)RM?HCkU?)evZ@zb6bs>SaFIGu_d9 z9q3upE=q=n;e95};a@^NXdAdH91GM(?^@Xs4wpr$s>4NXx5Cu2*2NmY+7NDu);BkS zZn9!hvZj`>eydc;H@C*@=32Wk+}PZ*6(%Mg^E{5bj2{cMEqv0aTzuyLohkgv^Bnu+ zXMwhXM*o0|&u~UueCj{#;xqpDx%iYneZHfg`CvO}8))+1q zX9++3CCv{AW{KQBk;@RdlfTw->B8SF{L{y^dAsoU3rp{1k2YJZP1#JUAVcm-wz>#~#CFg;8 zx5)MVeIY2zGX1b^2mOlPKkid#8dU8P9N>cx@ugK?z=jnRJ(EC#8y(#pb z6nb|Gy(@*@nL-bw&^v_A(B857Q~29c=xr%(%Aa)cDOm#A@HrkD^*Z)12AvMt@RhzShwlYH1GM2!1HI&8 zhfn#FDg20wPyZ|jP5X`b<72`Zy2Q~#xopt3i@!aEUm^VYm-r#&l&e0WH(spGjsEcz z`-5U%S?15Bmu6&S%$Pmiv^x!gHCJi+ zqxMJbg|Vs@^#eVd?_F$fzPCQI*pBfPKQsfaT(hhvhID`O&!LE`e8loFXZA(bG~g+O zkNuiGKpR+cllF&+UsM0?{f#zfyfFXf^0=EktYdu8XUg08p!+|d^+$jo1Ra9B4@mp0 zQ)({P{N=z8gC2ogQ230%Ss!%ttMUaoTln;UgNsl9&j(F@hQz1oS5++1{yrk{d-{4! zn|@V^@W-yv=Aph-(1Q6;ku&`(pUCa9wVV>dPp-(Be%2vze^lHX7kQ)C67$OIw4T6+ zG4g)my2AkV3y$Mr*ypL-6ruX={cpmnL>y7xbt57P82gchL>36Ub`xC7tHF>I-wmZ zyS_qjNggtNc~w&lnkZ{Ucx@TYOE;8R8&>+u`R>QGcoVR!wCws%1~h+7`AQ{6e)-x^ z*@DG+R$=IR6@c3olx>a1s7*hmRn_%PvF0dimtaWS^P_&K2fd;6`odCZTHjR9rWJIV z#Sn_~s=Tp$j3d3L0f&Jse-*%7;KDmmq2OuwxQ-VypPlE|bk=0C;-412nGa9D zRr3em)_f&|ALc*0mvN2MATeME3nuw$?0?+|*I z;9kMXA87s_!8S4P66_PSgePODYZV{EBwG(9X?ZUrq;*1L~d}omV;C3Nv)5&Ma~kru^XrA^)1!(Mv1S1 zC7Q0t*7b3&(58N_5!$%N@H3z8afP4xw*j;bB!3!k+{I^p8%*Kn<~s35{)F()&$!IG zg$GmQ@h4}3wL$O_Rc%-XjokLlIvj>Rdb_5lS!TqbIXXn_1nod+{-2Q2erd}!#ex~sA#GfXg z^i^v4idv0bVU69N*H|O*VA69y=#tw?{Y&Am6@|Ch0BerS|C@f0Sq~Ult;_M4Aj7ZH z^CM3BvHw#6nu^FLJw8wBUm*K#W8m)*{xo34e8)cZ<$|_>X|R0VTD&BmSCVghNuQI&uHc^(&42V=4A`i+$hS+8?`L)#a&Q z=*rzX9+wEcUHIFC9y_G_wYfsK3%^6?(6_YxIYQS6KPL2u=(j~KFQWZ1OZX*5?weYV zjtw0jtPlGZ>h!7uehBpZ1y28z<=wj8NiXL2LCD#_V4HTw@j@(KpVK^Z?%3?E)JRS{a%}I?9&KQ^_ct-fIjmuf6oGK1IefU z32|=($o9S*$o8J~-(|O4yYzaz^O>f>s>#;*p1SyK6q>sDR0z$whEKWBOj|xBLbGi0 z2?|ZO`S^ssK$BL1&{qqcCv>i$*+(GkB$)Naq`{wgG3$GOMl8vmW@V}%<-MTe!xsN! z3uX!S{ku+=grHHNN1K?NK{vMzT|xfwS<_soW^!0Bvc4({`PUT!{p(6~zjj@zRqR(w zr&cIb$a>QXgd1X2lwDz2%Ju>$eUE)i=coNZ#&HnH^j*==Tz%L2CM+G?RaSp*Skoax zY5Pp$u8(W`8-WaPI?x81{N?+ElfT%)4ZHZv&l4_w7U&@8h}b7TR-pBl0GWp}fVPW& z6tsEB-y!_e@(}!nj^}qgB_EWgNN*%*ghxA0Y&xJmOzAxC{}KwJ3A|KOX4`~uK6knYod&dm;=W$1|TNB&CJ zoMU+B>qN(Z|e0r;rIWI)(bDG=j6X> z+SKblkt-0naY5s+k!@N&_p2K1FK9IVgYD8!7!f=wXxwA`F#QG*KhuEyK*nbY(4-&D zEuRyAjL#A9ZJ^1o1u6Ut&~APth3`w@?@Qr#r|`4^S-OGW3NWjz75L!{YkNJ!awG6pYgLx+&B5px5Dv1^WV7e zO@GAb8F9%`PakL-X!uJC9X;e*pxylKDg2F~eGoJD6Dj;XVt?b$bT}!;_}mz9+@XIn zLEA1pqb@$%lm-`{{u(ZF?0*3C82CZ4&-$`&mBari`H&wFKHU!nb$+ygEYBm5w_W@_ zFgFkVR{`4OSK7}hp2~j)d>iJ8hjchIf1+{LevNbfQ)9oBx4?^GHjsS!bNCj=UvoeYfbaHqQZLM!i5=`Q&V#c8`;1#8pzljZMHUV24VoW83Ep0g?On8bJ@&dkh!&cYJhAtbk$o;(Dh#S*TG&Cw&_y%9FiKhqtV)UCFJ7A`Ek+qs@eDj)a@+#;QmJDXul^UsF$o${hc(Y-fYEfo}il zP0?m&HS$cX@xA!mN4+45oWXMQUV50H%wzns)8!w=U*%c+BRQGVCdtR7$3%+#-D017 zhOeXC39ssZfVN@o_Ge4Di3L&$_fl&?OCJ7`PN^5;-)CIevnsm4A- zJGM0vM$)%nYaHBbp1KbnR;E1E&#H0O2FJf_ciN%95?G(yXWtq@UPX1K_TigIN|uLq zmwoD60@??3{C7`tLo2I09thFu;nWXSw3r<8n)WaO<}vLrMeDV&g^Owh|K`C53|OG@9Rt)JGHoFMPoJG64uyU z1FJ}saAQ?{gC?PYC5lR#8#mWCg<0<}sH!(np%N4443g!V$r!7HvC9FeU$J+=u0oyK z(2A(!hGC|=K7ugShF`p|bENoSE4+p6#tf(EQ}wk8DrL#zr>L*3SK(!SAB{Cw(VDPoc%n@)E80|zzZU#O@mG&O@SEx^IEO#f z#gQ5-S_OMi$YIk)G`hu#Ho#sr=xW$OU4iDsiq_+gofEndZq)i~+u&X!YI2+%#9u4^ zs+#bJ{!10)@!#5lzi6yY?enREo+=~^BWH6of*IuE=8|6S%Ujc1# zhk8#5|A_Fn0omRf`wgGc_5(oLPY2q`(1q6uMvL#M^}J?e`H^YnfGrObL0P6w`>2OggyFyH_)^{BE1`Yc*E)fvY$R5XanhQhHHM4=BERh&kv>WcM88<_{)LH{*yjP zqn!Mi{mCQuIPpw>9D{xH(0_Z4KKO@pd$aaWIgtLHaPisC%)DFkk43aUwnNT_xp{K& zaxwnGaO|&vvxHIiaorAuKxgCc5CZGwSNs`%Q21PnXZ~o%AKfycDG)y0BfkyqyZN~( z_LstaH$U6Or~ki^Vn4&h_kzC$iB|;VAD@Yj>G0A0OYwI-BJ*A#|M(;}o6DTa zk9=Uxi_~M*iKyo!B6#8C)&(>->p?WnL7dLJbgr84iO4xH#z#9g;9YvWYYeYeo#?L= z+$dNsSYl{d7YYh(*1ycUm`}`?2<8deg4u#uf|lU;5}l6{f@6Y51&0Oq3GNl#Ex1!~ zK(Jr1PjH)HmtdPt&lZ6l_W!D^Aj%3YD)#dTAQxWdiH`YXX{HS?VujTJj;ksG_`R-5P(%rb2&V zPzo%~KfYu|arvf_wf;a^`Py|woBZW#ON+%O`GMjM#Q~9D=`Sl^=`U7!hkDlqZe6#s zY*pC`f0>flP`s{u)5_v?rMLRm(V%3pznpT^v!-b68ou)(yYRv>tmza$j&Z(8xGa&ZuAj+;u?ZYU}(l6ZhzX=#OV$ATA&O4nl_9B|z_oZ9;2^3MPCkR8$xz& zN$4X+n6$Xxmm`uWEYteHzbYzeM&$e~Xy$=7=perG7_|(7aGY+5gUp8n|r%C(PXB|E-C*BACKH<|} zZ7x2;?*nZMpX1{tpL6U}e}{|j0X@IN;m-u!4*5zT{htXexZmOPtsL_1_&?&})BS$X zhEIKmx*Yqgl#ha+0gqEZ-9P%E!>9aw&?fx2f7e>R&Ef9?o&J{yKO(NYS!Hv}e~+53 zv{7;`%7CsJ1p-O}u`+wGOj&oRMCK_w`%|fq7XZtmc>k^*p_U`Yjx* zYp&rN>jBJCsWlKikzMGbT4|q!49^NIZ|Et(SRJPNYNAU~pkYp(m>#TZ*@{^|9t*Z;ThXZZV0r9o{5fqVh}FcqclKPbejtbM3whEig$(b!xn8r0ufp0IM zl;-1b>fKaoFR-m!v7pktC2G~ejQNI0xmdlPt5Xv#RIWb0qtk==v7y(AZ>G;2&^EAQ zSt)+uj%&R}=KqvA(-X;R4ZG~C{2=zpr~V~><>+Vm7;*7wzs<#`{QzhiNc*JiFFN*_ zpZi^W>hE^(**6Y=wt>cfM_hc$_q+J)d**$~(a$zHv+p8nRP0keeY?YF{Op0eEqwYv z=;G7Avp^d@>x&T=pWzR=_|zZz@>Km6Xdle!e-07)zvA#&A69}t2wbXeh;ZbB{f=A? z_#?3A15(c=z=|CXpJkx~@;(H>#8d9yJA8&C0eK&ge8yAcYYsmfv=8!?z(6ZDT~>3# z-pmejoW4}qWB9WE!Li5q8-yNHAJU!eDf|Fv8|JC?w%M=Ikh20@$KS_X_Ni~T*f-(w z4LI(Tp8?ty`^+!9U3^vFyZH2P;2$0POphZjeirERoesYkbOYoWuPO7KGLKqQ&#Q!R z5?sw*V(K~>zlZ+C(Zl-i8R#8{p6V*D^4274C(M+nso0o%eW$g$&O|?~s;B&BU4BD? zC4xc0fS}LtL*-WC+EQ!H`Vz~(zQPKvTLXGcGJjO;a{R}1%>!)%O+5Fd@GD(>#%9eYg23Fs+6I+h_Jn>Z0J zOQ^;ld-pi<%wIv!HjsSAv;7^1&-{2q_~ABW9`Ydv_14Z5xs9N0;Zx7Ji?8y%i_iFI z|E}W>)5ijB1E`|Fv&Pzclg^hVD>McK zeS*sc3k1!+&m}^ebBE?URGyg67t9sR5wr!{s#>gj>T0Z(NF(-MG@@K>wpw8JiF?sT zA}SkOH(QNW)pgDAaJZpXgm=r0}yq8~fDP^^jwq z^T%DnU$1r{G+~dy7QET`t-|_6liHndC*)H3yzQk$?MMIqXLKS$u6EqsP= z(8Z^J8bBNVG|-v*9Q%|Xb@ACQjJo*D7qdXyK;!=QAL#yc1CaS77fAlO_Tz0F_koM@ zJj<`ptXG)&zx;KdGB@#`fIjok|2sv$;Rk-`@R>g2Dg2!&{1VVMq{5ha)ph?frf9mwi1v0$Hgr9o8Ate5-{I2$2>iLF2@#m7i)%==W8v6wY z1nqy*=H)#a(*?5xkG@j`U!cDuv?B0B|I&ZyKi2*|3jZ=ZbwFGAq)Uc0KL^NmDhKjy z!e{!P68;e&^=$*%!k-2#IpD-6%TGG!0^sdd0CVUi_$$X>z`DbArw`c5f3&~LWj_sc z474qL>gzw~=*M)fwMX~~xnD4KzimkJdr0`H`)y5rHyDxpZ)k%iy#t3F_vxPr_{Rp4 zPycUo@#+6^(1uU>@}D^NxxTO+{BEGJf6~QgyVnjnSNM$oC5IjRjQ>;M+rno*I_Fu3 z?}LQ^V!;g!E24p-l`vAz1kPdb85c|l9u*uB92OiD+#|S4uwQVS zV4Gl_V1=MhFjp`~Fk3J~aN?8NUkSl6L7RzC7kZZXKhL2% zsBgZ|Xgd_0Ei~3r6+KI6^qm!*CG@RpS6YFUrPj*glA=wa5`SS)aBWGTXdP5qYjE9w zKNPAcE7yfOP!w3tv07kdnc`EXxVX%{EM=8_s9 zGMEG>5N^VTxOHm-g(2J~w57SFW?fj%v|yHT6*fRbly%GB(9pc)eF(1emtnhMvA?V= z+;R_YuhX`1&mC2TxJ7JhA-66zx2)aLBfi{Qh{b{Y-}oQZi=-uGhD`^F>l1SHkxXBebh*3+R?ju-HP&6h^kes;b;t= zRvhdp!){D&Tej#f_6~9eiXp474hJxe;jhMp%gV{3d%{hzlIAu}LvEF1_&?}{}?9OtZ1OIWdViGm z3E~_#l8c;#AL|WO4Xgw5V4jB#f7yaX3n;RFP1*WTXzjZ4BJ|(aty{|qtpy1bPwX|c&?(t?-vbkRy6QkJm4#BhRn|3W|FdXbc?0?yb5B6`jfwqC9g#izcFDg6B@{Js?acnbee3g36k@gLo{K-++Ee}I%Vyi}u^ zS26o15B=V!_~~zGtTZV5D~^yFuGPmfMu>iH(NlDs}#--rSsA zih0ZkW-(Lmq!#(cW*ntJjcT@k8a=Yz+~MeR6e^yStX?)@W6m@GpCZcz-JGzz%0DAOWKXLPXafyDGEMV?K=LzyZhk`egTn7f;nz*q_I*Iw zw}5W@W5VAj{I(Q+MVhvs2c-S+6#j_tcL~1^=+CIy!sIrpY?VDX!3V`FMwb8>n9pZex$KNaK~XyC!{`IBIZ89 zKH)#}buBkX%yR|1gx@9nbTQ8oY!iOLKZ*O_(0KBP8YcuBMDCQx9TT|>(Q6Ct7x`hq zO0oBh&|`ubd$s9d2jpzw zPXms^+&q-;18oDdKCSz8<9BG>BbeO(uPQrz`V0fbdS0t;=PE9TMPV>uku3m3*(8iE+3W3X)3qHWb9gW;#O+`wiX zfBk~V^Xs3`=51nLDHs&Y6U-L01jnniy+Oe}f*pc&f+4|d!3@EPN^L(Om}8zni~&**naKf!R6dYo^>ncCX=mPQC*>IUC`aykK&Sl?824-&BoL+rEi zgL*zfhlR)Kv9F9%6Hq4(EwpcK;RmUBn>6Qtcmm$CrkY{DfLjmv#d=hf^sEzJrz3Pm zpxFhARBctAHAV~coEd)2aED7o=`EbgiB+8~Ff5&C3yCAItgCcgi3n5uTvueA%Rcjg zme=;(`M|xeZ%wnRKGZb`xE#Su3t5;KYT1gTu!vyvwAS9GwV-@sd{0A(Kr_s>irO&c zgyNjaDpdPHFm;D{2yBAFuDe$3@u`syK3t8}GySNsIJ9ve2u)1=GvcaW*sk`2w#i2$ zy?JXa%vxYR461kyc@v$`_c+j;abxBW^tK@E|C5uNRhW}lkhFv&_2|qtLRgu*EGB8_ zzH#!Val)VTDq-;x2E{#9pCSq>OCc5O>@V=@b{d9-eAZf&Tsw>G!p+}T|i zqBJ9^BMta4h#pF~kGU?T1>@c>mwSw#2668u$}Gpd7B}m1ZR96h@@z-Pi9IWG(9&mjdH{&Me1$`v0WZN4+KOepKBtVXUIOso5M% z@HDvxijd#7&4vIWQGy!S+{HXjw-C#X+8>e6ZRLI8GURBX|g@F(ZLSkbzf$z1N@>&&i7 zfKv*beP>!5*7Z3hkJ++2#K6SE>6Cal1lryIube$KopM0i(80+9s`97SPwE1OvzSA6 zrK{drDOF9<pg z76|4F&JoNKJavN(k0tbYlY~!jL~u}Wzu+Fhe!*_R4#9|EjbMdfP_RI7zMw6bC1?pI z8pS_?qkYVQH^-EG23_{ueAexJOu5vQ){zauoVVaCzCRY@(w=%y9iXci*0n;p z9A*H=bZAQCeu>onT48f!tIbd0iJrB%MZ?rpG~#-oncE{>rmA4On4_)wHEL;x#umS* zXY{z#;d_jm!!g`G>1cc1WLRp+TAY_AFwLOCY3MH?y7)efo-0$HyD$TVkz{G(m9cbsa-d;hbTvw3&E}TDMlUaJ#cX)t6M`HuZ+~QhirZ zc~#lO_aqnagO9{TFe%4}*T}#*K}1On*4b1rTeiWt+U-z zFL#!ZR_lb414^nk>&s&O~;0N+*xKWE09cq{;2XV zJb|^`q&loOt3`F&A4#s>E4@11bdF@EGx6@Pso_OzxM#MC)p zCbHTG<_mc2XWLj@#wU>#Bh(zN=cI?0Qsr9ZOm!bF49(UBE|pS-eF59`X^0>`uWaLU z5`K(d$Twbfov%_sR(E~deop37OyB)&uLz^Nq|%|t45t-0=?_*fRvETC5c!yKgr-pU zsGS2!Oc|;4WBx;(q_zi0{taQhoTVSb4oml8v<8e5u|q&d0ON+Hi}A-i?S4$ZEBi91 zU&VU%8R=KCzii)#A9;-YMwdM6)cK%o;C=Y}4F1exZzsnpBq3ZI~OL;^-LLmFx1Hz z7YO2+5ZpVbb%B1T0G7&K~t6|hD@%{vJsuqFW_M2#4gDz?#5 zjY>6E+M=ZzEw)stMnuJmHd?II(l%P!Vx=0DDk|3dJoEjY-Ps{g+uyzSbN{&Klg%^F z`M#X*IdkUBnKQGq^iO%GPreb{QTY)-Yed}tTF`xvA4EgTIH`?*yKX!#n5N15#q_9= zc4ym5>*IIO&_B4S!v4-=ZtEAhSAU5A8kyHL)(M*R%tGZ^iYbcTbs&u!|!ME zq91RphcO+F{B%v8rILaX3>;I@RjfTiXT6+;yJ zDk^v>xST3g+q;4Rc^Txogw>6Llf_GAd`Hb$R?D3c`@S zrq)(C%kczCnY>eu^{9nbCA4s6TY&{l48+y$(@=O19X%&JegO&9tDw#a9NOQ#D8+Sq z>`p4gHA^6$Ba8cP30xMMRn$=1P}i`Gei`a)EQHESWbp_=nd_O&jyo^z+O@aGBJQ%PQmPae&kv8C zbeX!)FE>^#U5HoLEibEbHP|DK6ZfsOnAKoUmGVicr8TZf?W&~>Wew4*SA`oUG)y!B zsW+M6yUQPvGro*d#_=&<6{%bofqO$ujqj3%~WuW%oLnJyGo&H!^olY~NI` zieMgw8why*&IE|VTfQicTRXDU4F4#hy1H)uEYl`SVsDYex70DT@L2-xZR*v>)x5sk zwmXgT3Ah7RglTDQ-Dn2`2eVFfvhWa8O%ad9u}fFwnlF-?pk~X90%J!m)uISq?j0B{ z!zq;}Agqyaw# zW(qSe1m+4BKj{9v&xKBa=UB$ePGul?MWZ^Pn+a3$XEeyh^cluw!>Y>CtGn~d156HI z@l;bX-+d!V1O88$+`wO{=RaIoG@N*uc2{@sG7qk}k=2iJ%En-b(}CMPD)|CRc42uB zq6|NBhxZuUHg|i@LHSUI}ur)*_;iZy(HJ$=Q4VPqi^Q?PG=8ZzU z)y^O5Hkn%{e>e^g@Zccx-r;Z+m%^Rr9oHCB<;fpc81)V^X28TNX)$Q?H0E}^grOEs z-oRg3RV80+jq_NketVsFX>sbQS&B!hpMa$t~84eeoDXwy^#Kp3E zBSHpsIdia6*Oq7vF~*`}@T$mtODrs7`wQD{V?|@Vri7P^h<%JxS&sE{T^MjDo9_L) z_%mQ`1Jtg71JfU|=W$GZ+wbQIOkl2z)&<_Vtff^8R(k0y@-y6 zwEzsu?rhVtQe`5WRW#a}kUcJZvXg~3I*n$Pu<%t=xv-+n$tr>D)KgEz&oEBNcFtRf zgFjNVylUxkXfbPSc=Fgu;Z*ThIbg`hXL4>n0q$)s{3me7$8jN-AjgHHhbLL>KHlu( zxNu0&Z}jmTA3w#%@f>%8J>CVAz$g1SF2EDyxEN31iS6+UVPDG0uLqdWGm4=*}{dOM?cwW_3`U{e6^1k z`S_JS9`*4;ACLHW;{H$e^~Ypugik-Q{gU~jNl$%(T-;9JiTyXo#q9(+E}Rp1V*3Vt z?G@xgc!EAIr2pIJ9{*Q;eVvcxxC?t{;5}pbA_K-Dz5|G}O@N!s`k>+GRxK^YC>>jO zH2L^`g8PgF*XL;N%sl(5lK5i=)&)36V`5}oh~eleRdzL4KAfCeR;RfV$a$E4(02sC zm>^I7j>76Ex;4#G)N22~);~;pWb5;0o$pCJQN>)% zH_xMWDi3vup2?s0k??hzzeaJ?Cz79cP|&O+%{w-}PK4pb8V>FNW$h>a-?WEW-)i&N zVVJTOs9h?)6=L3Sahm>Cs5|9S2j>1hmnCBl#zSWA)5j)*`b8K%hvOI_7M~J$T-m+r zE;B+gWSmLxTQM?TKd!SSfjdCcUMZeI~6zV7WtSz2=0GV_4f$o|3xtSEy0~yUfw^2&-=UJZpAc}oBgxb zQ|~0i->UUC`+d}?c)M`FA5gjJe+^el+!?C{^KTT~u4vlN;P4w%|J#Bcw+Xf?-=w%l zaqrC{Z@WdXaIIjM;^bR}Pu4g}uN6MLQE-i7+I7NrYMi-E!c99=dFsh_7z6GADaYx= za*m6)1MjV_Rk{B?M=AQ^Q{%VYW7i9IQE-R;wBO|)ANR|C*XfaSCKo;-F6VvOD(E{v z+7AO(oEVq$8Kg$&^F32?Z_cd3y7x=mMGo_X6{pVT5fhjlb{|ge3&!mayFaL+bGTB7 zZGG+|X5ii@@C2dj^06mjbat;xvwmrOomrM{vJwy0jEjFkBb6@{bbkiP^=78+xS#BP zK{j?PgZFbw;0EtkZEzf{hr9RP&af_+JL{a8=g!8kke|mm&T*GhZL*tjdWqjMAvJ>9;xKEW=Ki7df9(h_g-Y%5y^2zgj@(ggV z{d%9gLgm{3-5WQon|qvMU7!f-349omPvWDvSe(0kem1C|i{0NN!?&2Uf%v`Je@{^4%)m`&~IMTNRCutV>2?En~csHEZ-z!nDy_z?c7|F}JaP+duy0aV$f3fzd^OVoyAO{>`D|H5L2GwA7Mi#z#XA6!w zBP_<38u5O&vQqc^BVz_mgEr=8`g}Z=a_ew!?ZY$A2E*b+_30-)k1S zNAjz_|JCw&cZi(r%acXN$?^8>4{m`wL`@Zcllb>3u6aiIUd0WH)(^z*_BTb|wN23f z`<3gr#>;1WZl4k_pYx1Ta0kfv@U9JO>gjPgF88fHkmuPLw|(u}zD@k*KPBjYubX*~ z^iD0`FE{&Tz1nZtd=5&(yi(r})xKT)*4-!hSt>X0jXI>|=cv3V|0zSIU06RIeJ&)&xoVY#Dp9ehh4DhzI~<%4;z8s0?&gy3k^g69RD_+ANSAs zL>{;U96Pw*@qkzynHR+M7{^}KqaU{8e#qh0+786Bk(CduAoBOaF*PvtS&*OTXE-?Z zPFaaJ;REqI;(QFqI<2~_n#lLw6HUB(pwDtP16ghz&`~+#&z~jrJv4KG#pCY)@pxSe?f^p>a$K1A_*D!NUN~0Jynil9_00SH7N}m%iK2J#2to5czlh4s_a(zg ziT=#@B~46WwKwni%ToKM{$khJR}fbQu1Q`W!JK!cUyafH#(N}xt>T)`MZQka?2m%k z@%G?;b-*1W_AA?Yt4GfEt?BVee!u1#^odF7y4PiEAgaDbzQl|Cs^ySr}<^7 zx)S&=!>6PiA1EQ2{l3xThx@St+)+8>oIEF94(oBqBPU<)k@Gmq19yPa=ega@Er)t( zbK`!fw^{WV2g_~s$l1?FfjdCTX+L~k+&&q+-6Ln5O&&S@PX>2@yd43E`DwXG`gs(H zcQ{#Vp=ZX$Bf$&ii#!ax9lQ;Ce7Bg{Z@U)6<6t>W;119~E}Qd8+mLCH#_hLW7Pn`; zn!p{8+_^kazSkqCev3!Wev<_r_1JfLY>{)^4Hw1b zL&2@W0oFc%*$>&paXIblp>N9PxM!8Q`2)R~*yda+2OpmEyou+K9DLko+B^c*nfdhPO^k!OxD}I-jsl}@>;hk<)a56n z+yHf1Mv=)-WUDa0*q6^^gmo+N@3O8 zQ(k*jt+lkI-YTs!EA%i%!jo-I{Ys~5Wi`HuRN_90gzL0LPVq{o4xi$c6NwXX!@3yH zOcXl{k&jPBkyFLb$_jk&Jk-j1OwAWNxK--lP0YC3taDZ_UFzTsm(C*OmEp4jpX%C5 zXVOAk6fCdAE$QV>b+l|D{_#`htf*e@6cv>?McBrVz3_wIw=9Bel2bjII2kwd@!OdT z`Q_OX6z0@aJ2i`oaDyCQ#$Hk6)YTO^E0)6%plCU=%JJ*f)lNlyB|f#zLfm^_#1a>w z#6{7GPL$WBmF4Al?hd!kD@&F-(OUf0b}a&2fih~HGJHxF;wrhm+^Jo@&{2QCM|Ry)&tluQFQd;0IrwB{isFDQ^5%L$9>b!S{;s zJ1><>o%(2{v%I|2!5gfd<;(D?E^(IEE(ez4)--OtFIkSKltA&*+{>#G8~!cfzgqmO zTL68!sva||&4IrV7H zdQ_{vmWan(D(V;GQ{q&ha`+(E{CBEx>l+{BRn)COAlON@QB)FcOBPl-i;z+4ii=iY z0SnJdRG>F3s>R>ZB~G*=%FiVJo6LVF^B@mKBOOC`~(aD7_r)Rj~@b!-hhSBU3C zoH~BW>T$z>PsEfswfIVS4f+tCk8nztmhgl2Ex}3!u0?C=%AE>4%TtertgouUrw*SL z=p1-<5FMZvRjaMXH_OpUYU>x`vjWe(Ag>M_wZbpfI!jBju33jq=|UhrrC9K&!v`H| zDI!}6KSjl8fpY9yRJCeF)LDTiW>%mBu0S~}YRdU!4RoMaUhNb^zZ^|ay|5hie6CoNo~2a1bhXy@mG$|LVWP>9r{9*!)h)su6M8$ieH~a<5U&n zC_(#U5ou}hB0N!4%wLst(8+KZ!7z#+FQ^1ADS;r0B{?^l@hqz%e!k#%)}`Z_k?-`$ z*Md9H!*QM9$Eo!BQN77e%6zh3F|tkg{)Yuw4$IvKdv|;Vay*Uzn}PmyfYPmEw_}5# z|9Z9Q&(qwvte=V8f4$oNtjIG`1^xE98ei@wGh9D@`#oCzn$JYe_yf?~x+q?M#y<}{ z4D{E3s@flXBwl~ohZo1~Igi-~dk1Lt!&;A=?X>{h$Z5adBd2|m|{*cPU zkH+I*+&R^8J)SRHVCMi$J@dIkdGQ8tV^2JEP27Hu z4G#HkI8L#8Cqrzt+!P^L=Dl_X-Aw!&Gw57*vq{n9n|2)2828V34_zBCAH`XvKKW#D z2l@VfxM7HtQ=o|7U+6O!lZM%Yk3S(Y0oNH1$a%j|u|P4Rn15*jyjr-=k2e4;tHy7| z;XPpBv+$ee%=eeqnknVAE3!U}fB1Fr`mll8dx&?-abiDjw4iB^W{*AhXFj+CH2%AM z@)nPr`*XqSc=@zX0XO#KS=Y;PZ2>ultAczDa2zHw0Jj{*?KFwrLFjSr7y(wOocpB_ za+6r^2)F|@_1Nx{ukpwkXZ{W1e-x1YZa3sSK50+SRkw&d7x*A}DfIZeB9zm9>e_hu zv_Akn2WZM~@W>foKDd$7ewRnic4_j+Q4D?;D(;{5dwuc+KKUqc2YM`@bMnp268~D@ zZ17UZqj2oGKPOM&>3xHlbI*1U{Q2&kp|Sb1XPrAXGHqTS@0&2uFXPyKdps`Atq~wNsaTg7 zj|0wlg2}y8(oPx2!i<@g`xLi2pgndEe!nVi&-QLnd(-~8-;c|CL%v(( zB^)e}{J)bi=6b2e)N2I~H44%n`|W;@KOT2Y;11A~xA_k3M?fC0X+R_A4&8p2$kzjB zf$xO85oqi;+%573ApK_o&2h&19t1au_1zBc0BKMCg7tAZul<~R$e8z04Zm@Z9jQxk?q2EirgD-7q)wswu@=kT^@OF$m_u!ppl2~i`%>X$|L8zVfYVa zzZ|%K0Pf6UJc52ZF#3?57ZvgC;G!D*3PVml-o=EQ+l!&YG;aL3@!mW>G|YQ*ET8=* z_kno%jBgjpi>RFA!sH*tcX2;Qc0dK&(-xW8Ay z4?!NLp(d$^VVFmyz1Pcz-1({0w_Y)-Xf*e|BKs}pw}OTD3i8r)>GC41Z%Usxac=j- z!FE^&?f{MakWao-<(pp>zvjM8)@#bQ=ewbp=h!xD92-^N{H{*tae1!ZK9#Rmx%pk4 zT`FIze$!Om8W6jd-hvxDq}*>BPoDZ?{dm0ZeK@`!*w0GA9iXZIhWwP6#J>heIqOsP z^SGSHZ5p`ak?-}$*^k$G-%KP z@1y0ZJV!BGF|3%S=qP3?h7?U4DayGonGUu|y|^!#_9d;2!1pDra5s=U=j@4AbTRJpxo@x# zL*Wxn<=D=od?MH4nI`2zeZmAIRzAfQ!(S0~pu*?uI4?71d`}5ZMDi;Ju1;BPNlhhw z(m;k`OW%87og?2Lbdh>9i|Q+@aUC~{&yiti_hP*-iTjYHlwVx5oUb&n&d0hZx%)fY zz{myX;RWmNlWfe!b2Iw;+AfQ6OSH=HJPUvPcCz9m#fgd&thp8V!2p~@@l9IyMGcep zpX+)u`3j~nlo@h3~=W*l!Tvx9r0tv^A6<^)oWDTqVt^LzH*&jGX6)F zi{Fs1+zgfHsJ!mo#PY&j!o$~yeXEvZ=5GWw6dkOqV4*O9n@p_ zqb3kwM8WZ9j_+9Uu8G-y`CH=qhhz3|a0mEx^Mz*Le4$H^t3!$@K{*~8ACvyJQ}cT| zPB*Ij>yFbgs^>pWJ9VCCj?)Y+|Lf|}^@zl0=7nzK;GgNg?Vk2w|E<#YG3}S~tHk!} z@W|Q!OFeSVM;vg6i2a)kw4R9DbAQ))IrZB;avOYsM^61La0h7WzsVzK{G}c_`P%2?xss_smcJ8no@{7OzGsK@-_5{z z;9H^Bih?O;d8IEV#+L!^&~A$KAJbnBo+8{jL$E{TroZf0`GIL7H`j%5<)%@pZ~D!4 z)lWK6^pj2!H0vkUCo+zdYJT(s;qCtv+^LwN<57m9DUbDI`)>EtkM&)v^)vF!-z3(5 zmrq{hk<&gK+yR>QJLr+KKdtk~+5hTZO7!o5)4pUGJ~)r_omre^Ou)>d2ycUxr;Eft z?YDdExxbs#p5=4Bto1e7zoo!mg71R74Th9cf9I}v`NxBo{!Yrj^gLe8;_(E1e$uRK znddGx*UJ7d?X>sxxF5E|CU6I6`1x0d_ea`A(0Vew)D^m6NY|Q{pHDrh@N*JWJ(_GwqKe z-wQe0uNCO1+y+J8Ruh{UvX|G+Vk4bEw7H|jXzdw=xN9m8|zM7-= z*Qyl#`lcVJgr>RfO+Op{cDy|H)17a{_aFP^c8{F`*x2#$LA_=Hof28)OV}Lp7BNY#>;1X9a=v9vww7Y9rL$%dE7sT)SrCbm&f+m zs{TxQyF7BnvmV?58hOS);`Ws9@yYXj^5Njdp6%D+k#jz<06Yt5{O9dU^q&E4?5SV) zZd`s5cm(o7TkN_2viHa3Dd3x-?*Qq4J}`19F25Ic70_?Pj-;IJ(dc<1iFR3E%6ud# zazQW&|9U0kUvhGClK6z~c(;vk`a}q8<;@@V(JdJYy8aPxVBj^{zAgGwrv} z6F>LQ7I4QSckB?DB+9!~-hP_IQ+S!o7dy`r-aJXLRk2O=a#XKF_1cVF>(zOk`pFaf z#%Y2k&aHuX9PF11zNRRRhK>Fr z@!O{I&}5C{Y{3qdcWHY&DmU#hFDbD-hJ!n3kI)5TA5|P?2iaU!>pJP5Y+EMdXRXiA z0`+6$+kNtNrks3@S23vN6l6*{+?V{A)Tw&TOtI?~khtdg^rI?oQ?!#rFW;wU%@RL3 zigqv2%k$|qtGvVL^%lJxpPn;Y>!a8!Mf9?LdN~o*nZ!dxYl~hejpeQjt#zJ zO$mHr{UNw3@Ur#1eMRtH`)`2{t;d7ExBe8EADnG(4^#)v44h|uC-7!qxBY;<*&b|n z*(Jf(tnbXftP_H>1AhuE z4cuY9XPs`XvNr{)gI5L~w5|_SS`S%Q1X2PUf^P)&*pCMuv>&!^LRl-V+4jJ|6ZS>H zUj{z4er*Q>w+2T9o(|j@ToioMt_dEn=LAXv*9Ard_gFu)CIxo~X9n!R`}Q%x`!U)) z6&w`!lhtgmMwA7C{=sYPd4YTFpIS+Q74}1czuDgnX4u8n=)eP3v3+;oN5PHuMfRXT zdGN2+n}I*tPguR}n*u`v4+jbYo!0Ait`!L08GOoGW*=`)31$Uf!MOWKa9FU>zSe%( zUT1$CI5)T=_)_3(Yf&J>-f7(ttPJ)EE(nAJzYMlo(*v&sW?Q3zh1Om6i-AqSwSgZ8 ze-nJ#KGQC;=LcH@Q7b)oL*RGRV!One99$bL3M2*Zwg%d{fi2c-t0u5K z@b|#2fg|nD(2g4e7h3lPYXXCUS6lbkeF6^!$Ji$aXIU59kKi2l8QL^CxHEW!{g2?a zXp>nuhhAe{65JXrw-*O;0}a+&fjNO+qy2t>(r>hW5m;<@21f_~5!h(G5^M{uu=fWh zTLpo41FHkSur3c?XcgGU1@{F18u-%wZeUz+mi?&}3|tp{GT0J$J^1awm9TorzB;(q zdeoi~934or?+tcY<>)Ujp>I4K_%LvM@L=Fy_V?`j?3?XJ0$T%*1@8?s2Z{r42iDjJ z1IM8CZVkK^oMz_)Dg!lvkL_!MKM8zfZ9(r^8r)`04KxOJ1?~-ITRW|-foB7ES})qq z*hdAQwT1-81s;X&X!~^QVkpbl7UBS*kRq)UD!}bLGAu9mO&4Dw5bF7PlzY1;$ zd~985|0a06{a)aN;OW6TtieIYz9%>;xYB+DXQxPDsC{zaq`>omIaXuvtl-E%FFP&p zihZ2@s{OH@ZGAs*a-~@2#|thL7I`GlS0r&%o8^ z&|p*G&EWU!Mb;Ag+t{wNPYJwcKW5($c+I-OJ|0KFO;%d4S8!F}2K(7SDSF0J!5>)% z?UrDh^^Emm;F4e;`z5?}cYUDBx*_mq^pfYS|JY@AxjiR%X|T%P7`!IH@!maA^p2lb zc%I&vEa#O*J)bozcCM1~t@9Ec4>dok*rD>)r6SMK{D@+^%DdFw(fqJtn~^UO`-u9n zR31`nRlRog(`n9|ip?r-Q@K?s{>=HctyXy3Rf4Fpo7f(lmux|M@I2NGQsA@P{`ne~wH&T9mtDmU}8 zt_L;$YQg63tG>?5+BPcxiJ&Pz+)v8yObfZ!&)GmTKWBf~rSeTc)-MO>t$)%0vEST3 zWO4pI8EERq`Zhss66f0);0`dP^@%D50x~l(_x0FzOpKG;dpv#^*CzF2fc21-)+SuCO`6UDR3OU6l*+>DTAW{-i zFC-%)b|*Ixu}yi5GA_0@`zQM#*MX>$iI0O%7@r(`3h`;gryU;)#*P0+4j3EbX3WXS zy?dvm^y!nD+P807TEBk%`-jp8S}DEzB&YW3o0Jyp7wB)tuR~cp+m~^N2g&hsAR}Z^ zBMX?La@NfmEb_h3W8D@2S)Z?J-~Ye;`}Yq7IohBf^spb;{%qed!=!z?j+F7O8OZS~ zM~}x&#S}fB&2tItI!xv`X@(z@xL*sw9iWl#@X1?L-j*ceKvr!l1 zeaF^uMDsfo8&%$Jsa*BiRBi<&KdSO(#V*aa)V|fA+B+)mR1B#+qVlNG)ArqTl(cUY z$m25vWcym_QhrFW(O_@M?+gicDMnT9^pgB`&F@ewRJmp314OS)<&Mf*`%8Y~P{C%! zR>d~GpBzzMs2DYRDtDBJ6>}8ZRo;~;{w>W9DYh9sm3Jt1Dn^eKyLQE9&2Lp~Q*1W% zQ7km}ij783;|VLzQH&^dn08YhQgjsCRGu?P;_4V8*lAGZZNs#_lLbSXA5z|al-f-a zH2bwndH86_Z&z;iYlrgA;UaGxCusI-`*`7nYVRB)JnRTID~@2FLkfG2D{^yu?ms5+ z_}vQbc;us;MEL=g=ZuzeB8r8IU5X(^M=`8u9V>R7S%O`Pmdc}w?TT$GZ&&Ovc3MAc zg!pOH{AR^g#X>DFVz^>dF`{xuc~~(=F{J)GkJETh5VVZF=685Xo8zS5_{92WfwO&N8k7>sBteSkdU5Rf&j8p56>5QRQ-h%u}70va6vsmo9%GHmaXWLJd^K28) zJ@(!&(<+)?TJ_ShojnH?`)7WP6&r9U$2G9TNcxoo?W<8fa zn9RDZ%*OEjc-7}zmdUKc_7Q5C^;YX9sh?^0kn;8$B){`ULDTMC%EKzRu2Vh5F2%6w zIqL75J|`ExS^Sy)vSnPnJ=r#O;0};-w&&#W(w=ERwpF`F&c4}+e3NLu9^3)ap1dtA z_G^HAmUN#-PWzMziS|37=OEvdU+R%_--N&^FT7Lg5mj{V5guA2n4|d-#gNK#R30{V zdVOlCyyH9S=MF*BU#+`^7v8S&wSp$j4qu!N8fVvCqUVnz^li!SRJl2B{Bd-)h~77e zgYB6$F|j@OX&hm#U$dg!`vbRYw|K@G+Os~y^*%pU;EqSW%O~HU^7bEUeIF3K&hB!{ z+5N}(Is^UV88|%Ra`yDIvCU-4&6%7Se+b<1$m@ObJe9X?)pkVCsP+KANS(6>uwlRbNl`Kp8nwl!Q%$XEqE?sXFE8{1tg8JifJU^3T(TpL+g7yJyY* z&HDKtEge_!-su0-u_{jPVHSNFr;(NzWIqQ?1lYjKL z53bxjVNlWwBcIK@qu1TN2R6R_?g@`A>vR2>BZn=yv~|wuzyJB@zm9L3`>ORX=g_0` z7rpeXhmGWEWV7e0{l-P?bV{qL>6cymPP%a2}rIqiu3 zSKOI9WcyF9IivKlp|yqA&E9kDiOp-yd2dU`pD*wG$IgkTrJk2OYQeVOZ7RO)`+KkY z$Bt8~Z+(5%gH_);@XxZ952rj)f84m?Ki>TOzCYC+bHT4~xjA~~t*EZSi_CUh89tcY*q9P~Yo; zjAbiuH$L=#J0H43+5luczf%4Ju)FfpkM3g)TAkiCptD~`-}XMyl)~PTWVAQSb+p{P z!LCcI0@#EP{amIGY~2JT=i(sC%K(!5>-SaVv}-w~YTE>C)$LyaS;x15^uHg-^s&nQ zQQqp|3yKNw$?V9eQ3oR_$tV>x=o|2k{gO z>tBAf8>V#?(AysA=}A_~oTO;5FyQvZ#!s{_La$AAf3LRxRQ|s5Abz)jzEXfpM<~x! zJ`%|KoC9RvyiWOAmEQ@Z{d$!@r1HmA-mdcJRQ`p^Qye%$I>HfqUX#%OWFX5s3&?c7 zBl6im%8P-NuLLsP;E4QYAoCvsa^LP${yLDU(-FPD0V(eSQvXvRQ_={L_W@E~4y1f3 zkZC25^}H5H`C~vE_#}|&c_8I40V&@Fw1NKsa&FmjtndebOb-L8-v*@oDV4vd@?9!7 zeedfqeMqv^|HJs`#$&Bj`+@lpd%i!JG36*0DmD^LAMA~vmY5!ly5nOC`qf0~SJ6=t zZ!3`L9pxsj^bqV;iyg~uH)S6O-{v?lWhh?*8Qb7VAk(YV1-_;Hb6^0x|M8Ss)(JrB zn=<|A9tXF_kAqH)Ay4fq6xRU1uD%^7NLl{^GWE)m*fN1kBZ1w;l0E?Eh}-2jv<}K~ zn4>m@^ufCG*!JQa5AU07^_>{skN$Ir?U#G@k?q~~?nl$^Ub){-zus2A>Av$LV>Gc% zM2uda-fjQ5_I2DFhu4oO&u{P5@!EQ~y*l01*Q@*A*1e}T@cQ&_dt>qD`|I_8%GqAr zFWxeGD%-TJvGMA8x4V<42CdZT$cE9o4vCNuynT2YTmdi4OmW)!^_O_M3Uz#>H*blKxkH^yE@#x(b zru_$`SOX>|&VXZMpnuh>o2=rePPNp1^D%(=1@OV`n$>t7dJt z1!~&>T&MQ4=7_(=K(D{_42+?3;$tY!M{Vk79dtLT{#$B4Xs*O}0g$N(==B%tM@ROy z(u)tD({`xu2DM)g+^jxE>URz*fNXyUy@p*OP_{@5BY1v5^ zF>=cqrI-sW#D_lrrg`UnE3piBKI`k-p-<(U)d}5$sz3Q+@qHf98>4$&;W;K5Pgw2O zK(|%(UsL<{{r27Lr-<5z3$PA}56gQ7$n=Twgf=NYyiKC&BkK~(b?~8&N+8pn%40r! zbLVEYZG)cO%`sDm3vl{dt{39BE06i}TobmbuU6=6RlVP--5wzO!$IW%JP1#}#*e}D zjG)!GxOdcCKXs_j;e2o&X)3TlZLie479f3ZR{o;OjZcH!UC(r>|9xtkvH*PwAI3HV z$W#KP|0{v)!`A|Xz*~Xc)xnf!&_5oBK9fGT0rpJWf%Lx@NdHNfS(bwjeV+$pY64Q{ zK_GR031ke<0h55gQ~4es)91i%5?d_BVZE$@#r>oG3ezHeF{ZhFTjLB}EtU`Qm z3m^L424wnOq3C=F?5@o8Oq?@bOFU<=9im#+)*`Xn13akPcNNn!u5W-g@MU1PzPTpx zcKm!!zs>45vqWsNf%A0x2_VzJQt|s~nbfOyxwPM4Aou$i<);CAfu9RZ1};^8HIQ+A z4CMWel!fv<(r_U8i9pJi0C~UT3FYS93uzVI*T?jm{}t26rdp%yVb+mdnVo|>GTH}5 z(+fk90q*sDo3=|S%4TW;GR6&x+krvwy~>j+rS7AE)XN33Zl%hbi2k{Te|}}o>)yG5 znTHseAHBML@DqmiITq&w!>gsuCIXpE8+q5yLWg7wSPz};s^96?9}=|sZ;OwG*=loO z3C5_UQpSBiroStXmBqgN*x`M-P<>2};yxHY?5oW{rYDrgeDGXR6TiM}RNFn!OSw{P z&jm6qRUWhTT_3lqZ430;RCjQV*p2{lzg?!h0?4?QE5A;;@omtZ_dJ$3?`hX^TGh54 zxLfVb>i&vaDevb%rl)``Z>RD%l$&x4rgKiR?eOz&rqw2E}dUoGlR$uh( z*c_hg%f;QSFMHK_AS z{%FlVMe}Qc91m|&euwf0lwZ9HLZmf7w&h*QHz@zP@~4z{DF37Kca$Gcj;|-UsUOh0 zZ%ljkgz0!=MhN4cw-@nT(4y^~t$quDtZzM#?bD)s3vd@cEHm|LiD4*^e3wv7& zeL(KZXMx;zGp-dL0aC9FNI$ije~spUPxJ5B{7stwjOP36)e}!2lx(HhM_8uD+z-38 zzK7J;@J5Mcs^S75V{Rb#^!M%HjP+w6Q?KiU_XaW@31ponD9-^>f4cG+%KfqW*Z2-< ze5GpF1XP=_wOV`@0-0`6ZhT68=O)xQ^FK1~Sl7dkdk&lBp2LjKgUDx`%}wI79Y~(& z^P&Wwjq39tbj4>xe0thz*A3z)bffq^8JOVLH$Jwi&r;}_*u3qZu&w9m(woHR8Ed4B zSwNN%QGU7d9|CQtv;o;K8@?_3crB3q@=hSMaIR?C3O@#z z4Etgr=R+B{3O@!&{ZT;X&({3;%5PQP0;JyE${$v4_Wl2g(ht)U`b@j--(6b90U+z1 z@f~T4$%^xUY@<3LV_OGg9XA7aYTlUdA_FNONF5Vr`oMtI=P_BU?$q+Cpwpy!d(|$f zxo11;OF3RfL1(J!H36Bv=eOh9xR&Rr-6rU4SG|jF6T7Q{tm|eV_xo>@|6S!D0KN4v z^)cwqOS)Mr$uY6sF6HC`Sxza@KezQ9n+eB~SC8w${`D{;H^^hJJ3Y@_x#{~-pCNZh zTo(bo^-Dhz^NVeV&o4MW7iyiFV54*T4l{n+1Cp%)_K;Y=a@QL5KA~IRVy*XAp(oD6 zt{J>-WUd9gc|F{B-*}(Y?`1;#+sZ#u{+aSYKXlz$S-=1` zrz$@e$nx@)UjgK~bG7nYRsLPge@63nYW^$A|DfE|w#b zAaRaT%vCG}s?YenYwvzFefz&f|Ge4Tx73&OQ)#0uK&F?Jzp4BkppE=blm|A6{t-at zpABT1rTO0ja(sLm$aBie%6F^$9nC-KXQDR+$bD1+^Hws$rzeI{h}bIb2Qc}!heZm3P-4Fefp1jzP|DsKj|er-UuO9wFFo`-MzT>q%} z`3T5lKPG%2kZFSQnLzIErOF$X|5*9s%0E-y=jWn71Q>u_ner%*abKhPZJPh2=J(Wo z-91wi);8LswujW$@XaXOd;DaU zvCU)Ld(N4Tw$O88!Bf)q*8-VLTc;08wg%S3pB&?7M>2-@odnu5@WyZ662{r_PdDk+kospCU%3}-5c4Vb=ah~JL$_4XZ~~I zs~X637m#JODgQv_rVNAKm9dhc-kYG~pW}>r zLHwK#WLgPioVRGc@n_JzX7j9@WvTBCs=uA~-g5!wiMUtU^YaFtHuJV$f)CT*&2eJ5 zw;Zn@)!VaE>Xz}cTQ%z#AnW!WAjge&2rmwn?C^ zZdE`#wHKeWXpgNc4#IPk#fLvf*Gz^>8n~QOi#0^0k|{PJMAdce>oeI4nMIyXkmT|B=MIzrVEwFV(aFf`VNh&0=lt&V)lhMwy%1=#H;7s_Qq^{_Hf;o(3iY+-SpW6T{DJz zZN9;F(&y4vxj?2`<*~N%-OuSr)^i^84r?cWpVhi%e<5Wo1~NSc%Qmld9+)oT^UE9@v zC(tdqm!Jb=$^kO2LSQ{UjNw-*+YY4GKb8Mm<(0|O2CoBI_XEoN^cMTRK(>*i`~>Aw zfo@sKt5p7Vbu{vxt_dSr*ER6Rv{C);&~o-Erli=`D12DQg+Qh@Q&$h1y*%-(lwH>>R?=q1b(IgXh5j2XXrI8VfmMeq1$Yz+1^ zE}L4hA_Jsu*8`beQy!~ZH)H%3tzQ>(hlj*|CXnet$*JHXu{G@>n@s@5S0D_g!X+i~*g{iTN^Z<{w+xzUG>- zySDBo&l)VT)B%}(tvnVB`%`gZf9lkjI-u)q|5$wfxV-zer`SC@Q}Jy>_R$}^-#WCc zHSjM?^^tbk58YTC-un7uGI1F6_ucN!twz`D*W?jva2;*XQ=i?(NPXS|GSxc5 z>w!!|Q}KPMiOCV}Z!5K*HPm;{ig_l{f}fN@{jD6630wnT*+8}%vkfN92Q=1#5mNs5 zflQAme^L2vASL^iTgS@ycodN1=LF?5fE*vol`jJ_H33=vy()iE<-b$;+baK5<-JFW z|8yXe1Ejs#-(QFBx%2k;n80?-L7UM>p8Bj%Tm$4-zfo}quoEBZ9s)9zkCOT~0-0`8 z{-E-Afb6l9LRRNS^2#{w%g-Co@@6j|3c*hj+gu~KlMy%WfEKag8L1#+K#sB%*$ zKYFiSTTw5Tdq{l^A1A)20vG7^3qYoqfZTdr^S{b(yZYU)wrS&G>#IjbSjw6NWXe%) z{HFIq8x_kup}nufIS)Ejs&|XpwS0wLr`m0T&UV$?t9D=b?L2b_D^1E9H34y(^AyiZ zX5N;B*ruZ&sH}Ce)a7v?)AK-X{p@7fC$9j>{|;n20A#y=s@!`X>*{y-xHHsq-<)zL z?rZiXdfYQCu`lLKNj#@P#j=n`T?fdrndRRnhuv@Wp0EA$fN$^&TFz-w)*K*H36NU_ zr%St(1Ie!eGOYzNw%e3@+oiC7H|^rNzfU=nw_OelPi&V?`1PLaso#n`mfo&qGtZhT zaSR4BH7ozVa&KIXDA%2LXy0(h3yp=g^uSN!QHim%!k0G|vrpQ!tltA^xAF{$p%F-( z8-UDd*8Cp;S?3=s_r})B*gSJ2m;2^O%%eYk=!ZD(=>GVh*p@lS%W-q%UpvZU{C)6g zZIt&9GA3`GJnHVIsiQe=ymPGPGo^n20J`Zc;j!_+vkq+ykmEB1ox}RDIW|4>NK@W| zY2x!bAk%Ijw?0)Knl5}ekZGjyY9QO@c_5FuKPvwa=$@0$7WsG}El&k9%>`0kq4E_f zzfR?Msr*YI&znc*il4DSdYB4ing^tOC6MQ!8-P3yJ*oK}K*skMAT8blG9}Ltz3D*O z&sW}{{QJu9Q@%y{+2@FU0g!$d0_kx%km)->mj61Ce&17Ga<1@tAnk4f(&7#v)5Ad5 z4oJIqmG{mQejJeUla-$jWcwWe(qHgA@i!32bOMn1Q#5~u=3lD$QO$4C{JVkdgZC-_ z_W8DZZ~Xf}ChxJ*g+AuzrZ0X|9C}P2>j-2J$#5SH29$bS;qfcd7iRK*rLgJZYxHay*dsCj)7j3uKxF zqSt@ss=j7Kb!iFL$kircp)AD=k(?ETi^ z*EmfY{{|rIyIpZFko_zPHtbKMfRyJdmMS(WZUAyWYzMMkuxW)ZmhH7braOS#x*tei zn}9q{eg$M%F9BKBF$KbB0_p3&ZA1TeAR~Ib7OKBSAmeBSGLCj2dgi+E%*w%X0?k# zC$|2agnCWKc)LK#`!SH7wgQ=UYW`t$^32&H1EoHlYGYl7bz^+kMi&E_mMd>meur}7 z*S#;$%{p>aeK%Z=xO~@{c3LkhZCiXD*7e2pM(TOiXT5XLgl(;RtS!9PL@HZfBK7z! zkf~Go_)=-789>(aG9dfQ0p+G%estI6=JakiQS+K2a)h+sm@@H|uUG{%bt)(q+0{U% zH-X&xkMcnag^vI-Wht)(GOlNU?8m=X{x2Z=bErb(#{g+L8pw1ukn&4ZUZL_DmEWxL zyMR0{f2DjokmdajNKbzPGW{D!z44Xe_cS2&&I3}T0LWAaq~60omj5dt^>zTM@dl9T zBOvwGE*8BzfzgW_CMoq#q{Mi|1#Syyv9EaXPd5@awR8jkN{H*taO|2C_~Efs7rS zRyORpT>xaNP;TON-#MfAS(>$9u7Qrq!}FGi{ii^tUQ30??0xsjLK)JxM?ufk!ST6K zZJq%#ec|`f&AS^S>L)V_KmKvRtmAZ-Z^nb^=7__ZbgjgE7LaKjkXu`n|5o`QmA|e0 zfbza|qJJ%r?Y$PrW9xq9j|186`;~tVr2o`qQr@vZ%JYDDnDQ47pc5P<*QZR z24vsZ4rIIS0eKS0_IYZU!SK+&2RY2DBRv^o30kXU{Anl(7(*AiMEnfvP z?E%vMV<7EQuM_)0K-!N5(moqV`?GCC8GQJ9 zGxJQM2Y$LHC62i{@a0{bWjoWRP|Lbj%UY}4w0*)g1jlxd>GMx^jj~wORx;4YUDc9go-jlmMaRrU@Vd^rm4DJVd-#a<6&MhdzTW6?P z1<0dry~e>jQTnv6DG$i4dvB3C?*o$0T`Te;AnRPN+*{{p zW;b=tNW%9u;_rQ+4wJX89VaK&wGF=gb&VpA5j1Q0^wFW)Zv&Y=Qk|k(g)ap%);i_h zSlc|Y4&}WU%$v9`n9oEHjFA|fk{Dkr{Q2W+)Uu$CXOZ1!IpW{{GV`?~Qmi8$OMLHI zOS8oIB#`N4mrZWDQLAXC2bS<0izYn0!h-1Is3`wgq(b3xXz19fD(Y=myR>YwsG z@jo9(S()-511Wi1d6&xnt@-J9h}}pa?ZyI`Gez@rHGh`oKdbq#YQBlnVER$1R)&3w z<^RSeuu$qD(|&k z$MxmII(GE-Y46+K&DrL+}f}F3+01bMIHgNtrshAP~N1xMR}X@ zKLJ_hhsyihFKv7okf~PrHsv2G|3vwKABz6vK&Cq7%|N!rPn2&_`OBKW2gvJ!50v+M zK;|0RK-!%PWP26>nXU(N4)Ub(*OY&*eBcI=j{`EzP(BaH>xD|?R|7e3zfbwYK&EX# zw)qRny?vwo8}yBy`bx2=BRn)Uu|H+~`0)O;Ph(H{ zk<=+mF%QVLtN^lY*C=jO`3@l4cAsJje4GBK@rR?D_iZ3k$xoyo9s)A;-Y7O{Kql|_ z+v#aP_c^oTSI~uxIK7x?2wkcYyvF*CUT*y$fUvsXrB)qkv2& z0>3Jj?%qY{XdDgdW0U&aN%XB19M~lOhx|;+i##m+A|R7@ALb0hIb}$1tN&{6lV#Fh zDKm+d{A@ZaasQRVr+5FcUbJb_GT+iNOCL$Bv-ccXh;nFG2c2fsdj!a|)o<4ieZE-Y zYgD@q={CMsgv>QLrG4|ebyY2aY zQ}{9QlMiI7S00Pc^Nd%g`fY~JM%8;??S}na{7eKgU8a03kU5_zH+~Ivw{C9@mv+zI zEItc>{=VerYabWg9|4)(QXVU(o3+nIE$0w)RWCgC7vg6DkjF?dkRGdmOv_bn{QA+` ze_GZ5TD9GzzIGBlb1i>e4&$CVU&XH^zRV{ip3%UB{>1l?toF}prC;vQc=BKqyKeIx zqyDl-Z5971zn1bY116N`o9FJ+GMb?09~1q&>S^({v0Z#$@{G?nzYp-k#A}L_qoi+C zK{ufd_1Y)tS@Cfckm*w8W(`WX@U2O<4xJ z`|fu{%Uqx~4ZwA}y#>g0^b6wuI3Uwi%CA@cGv(Wrx9<@BSh+oXha{?HAAo+^i(+#! zkm+va_bdOm@-LN_{zmkd0GWOP`3jYPN9DJv z{85#^uJV5>@AYzG9Zg*grt_V!KTLclY_ryP3~ZV5f!wMBHmSdyS41`&$gSsqjCbg7 zh35g8<^oxt0?oe)$n*Hi%0CA(C3lEkUm$ai2Xam{4ahlB(yKBbjR48ZfL!}}Qu#+f z>K_8qzSnEwHw0w)gMhR<9!S0MKx$+Ina&1MFAqq)TYo2dcL1qzACT!GAoU&rQttqe z{=NWGBYBtTr30xq2uS(SKuS(f`9zhQ{qVnH`sh^aD7+78a92j>z>ZM6*^`{NwdsB= z)iRqjh7F3_754%eW76wVr%{Tzzyk9ExIVO>Kzhja{TKO#D? zFg+3qOFUiLK5O0-pBsVQy{ErrkNAER$g~^Ctsou>I0DqpDF-yS`2_x)o%ocA|syRA{*8-Xl!hw^gki%J0s8Bq z@rI|qD}Eb*Oiu&3^?T*kevzjEnZ_wUO?e)Weg9JB6)HFF;YatGp>2nM_cTjm-lTqZ zD((j+{YzpP3uHPI$gO;!Tejw34(zUfm>B%;xyaKp>(qW7kbXA)d^0mruQ{JNdKIMN_{#WIPl=pgH{EY>2>r&-qK-OiE=0`PumFC}|`OQGK!TriN z0hxXYr2SUS|DERlS@ZX4eoB`dpGN>Wri=jcTpb3|Qz4LPF_8XDTYgjQ?wzs~+V0Kj zyG_gLQ0xM7%m{rTF@%Z!W7Yrt7jx_yOvk(LrYHVZhwXd6Md<;lQzMY+W+1oL0okVa z0vX3fAlv2_K(RY2-B0U6hKfYkT_km+wg>V2gA z^e=^f<70h9s+DP{TmCl|x%a(vKa|2JQxlL|8-R>uJCL#M1s=kOal9L#1kZg00}1=w zJ;(a4-J3_s@w67Y<~Z4^c7Y`Ea}1Dv$1A^6xv8K3-O|n|@wo|l+g0~pYWp7`<3FyK z_#O?UZ{y3L|D02(K6BNs6zK1dA@oQ3nhs=&09jV4@(Ptd2BgN6-rbiSi-GSMxV?A6 z^Q(;-M+CM^QT5Rb+^E}sRoM}J68-md4)0K(70_=~n^%EMANqapH$u1hdx~CbsGq{V z0jnM#w#9-p@zbZD@biI87b=g{mDezDcYh6&bDZqYjj(A~yLtW957r{t##aEDQwwyD zN#z|trYRvQzYfTpYk^FE(|i+0Pnhl=Bgb`njI?TeIqBlFP_YrnoL1l#eApgm;5(>H zc|gW}2hJ_b_m?Z@-w~Es>2mK;XgMhvVv_~T(d`?6OwB<0Jp^RVfI-q8M+3=^1yVi^ z*j>4Y#nDY4bdHz(5mg_}>az{VGCIilz1Fu6UvtbHA>|eWnXXd4O8Fm^4>(rh91CQe zrz`)y@^0g-Njz3MMoXMq)Mht*;Mn7D@68)2erteC#-IN;jt;7iz0mW|L)`0t)$&fg zJ;#Z^cYsWTju(C;kjcAdkaHr|4Eo_5P%Os{xk=nN{8SVqt{IGhpYGNS^0k~xv>fLI zDfc)alegSLQ|@Xhm)s;%Zl}Lo`02J>E$0#~r$TwPa&Nhfrd+Krxk;wnf=d$X+W{4cBK8X)7kRk^#L^qGwu`0zfDL~fGVPuBdz{j?r_yw4QSH*LHz>>nd#_8KcOOdK!# z6d;o~hC;@`_kgTUyg$o)lT0jYeX-=jUw5%oX?fRbc@Hc9x$=a*%JRA2z28IcrtFjj ziS4izzPjtL3BMWHKgsG}5sbg52jh@`4sjUo=G~jklcfD#1~OedS@io@96C^zwa9j0@w#P|Cq&KQZkOMQk;kr=~3MiT)tMVarv=JedZ@wfNZY^lR3 zr;7btAnRme>~75}M`LODzopxdPqx?QTF+ofQ)v|Wm!+ocrRUIE=MBcR)55_G#%Lbppbbh{LkrtMM; z-7clj?NR~VE+e4ZWfII^=d*TC+vas>jzv9E&Mo&yYoioe(?QU+Qvt2*ROlE+K*w|) z%#0B_=6j)Y@E7Qu{1v)(--gO(@c+e{$Q(oRuD6zNnT{caJyUI+ptY7kTLwVKG8#Iz zDbTUbg^qm*bS?__Omou-I#*@Txf=l8&qhPH=Mx6v zm%!TQE%Tl(@40G^?So=$!}m?`Ccy0d{rUWFtcLrgzU_LYT)m-kTMo@^6w3ux6?x(5&HDd4?G~v=c&;7JQq5jbD;D2M(729NOtQ&|37Z z`6Q@51S)mPeFQWaM&^sKG>^6k@?H77^=)*!k zKlCNgeW}f%X@A`es$T%zZ?6gc2WY(Zebat?7WDW!1-c(U7Wy9#Oa0@Z`}K3s{d#`r zpB|p}?|MhX@fdns=nsuQ7%HQpe>(K|Tpjv9p!PpN?He7L?At@_c7knSIcx{ti1un| zJ^z3if0*$vQ` zA@qMl{|e}M8ud?gw1&#&P`~Zq`tZik?|{ag4)uEsTK@}Bc@Jv8_3_DnC#ZcH)czo- z{jpFv8)|8DI)V@2^t{1efL!ohxhsGTQje9;+u7mo&28-e6Q2*sn z|9U4T|01Y=d#L~BP}vO@!^5F|$3y)FLG!)~^;-h<`wl8=3`qU&L;F92_AiIZnkS|9 zw$S!Xpt3W}%q!doz3$0LUke(i1Jr&CsFX&3dGsF<{U=5L`Or8GPf2mshQ`?d8fR;$ z^n}_CfF0pIp}!BU=L@Lck5IqDfyu7})NgO7-6c@Fu~54iPva)DUi;CtY>6(@Ernvn!=C2Na z<4$Q-PztkBe{kJ`z9`Opwrz53gE_&)!TP7AbBvC`p3rlV{!keSJ;#_F`kdfm=(ewa zdSXXtWj%xaqkSZFtxSf##?J|TF?21}AC%ZJ*b{n8>kobH9T|F-lS7{aJti)OnK);p zeYRt;Cv;!#4~vZxoD8*}16|XLVRoK6GH?Fh2P!9?ne^=T&%F1|yT)3$RXT?riEZvR zgzzb8J0!)}94b3Qx7kmjH$E%%H-k#NqMaJ6pQC^MbJBaG?V#~SL*rZrmD{0neRt@&bGftNx1b z%{&&Gcln^2+oSKft8R~3QR4z=mCJ)|v2_gHpf&R^_fiU1`J82)y4+LdIR8(t*VRw= zO=3JXe{0I?d=LJ152(hq*Su#)>Udo>?>pskuUW5*pZV>>Rvf46Rb*;wQoo={osA2; z>$6Io=I@=8-{%$jW$)l|%(*O$e=y9hF$KlYFzj|A8}r^pE?d3m)7Q$P<;Ya?uO=hD*8W&{$oE8K zwC+p#+>-JPg37qi=ZC%sx{W>!J@*(_+Kt!bb`3Tz+Nf!7zBQ+$VR_t3r9CV9`Zx2e zn#V5V=f0LWR()2`?*BVXO!W+b`p*h|9<+a9wEr4p?Ob%$^}sS>~{XW%sgjY%e{V&Wfg1>V@`-1vm@t1Xx0_bn%du)YU~dC@TboM zQ28^o&biTE9eUfzDaKwvQN^#eLN=K;wPEh;Zp#Hs~ z^L|U{4?sPhfY$XIR2D-0zk=HT2z~wB?e3%>1dVqh)Z=WZ|K(5_2lc-f>h}Qjdg#*7 ze}={_`csOtG1RjwG)@^*dPCz3fcl>bjdu^!|8Zy?uR=Ylq5j`MUSj6@)W4w1yH#R>NgRZZ;yME-=R>yQ=pdTK>eXG-#fuq4sY=EvjLrKB)Wx zwQqc1vhM)3-v(;2Gt~YMP&o{0e>61yV5t42P>Zop`^iw52DP6KwVwmEUjVgO1hxMT zW@KuzZv!3ou~5HLp?(*_OdgoY1C?peJim88TbB-wY~e zgnoADkA?np=x5HTeV&SnoACMdbyqo$^tdxA#yuHdWe%*4IK3WBZKpuze|YHkg#J+I zYdw_e-UjM%Z0Hw5=WG`AI5A*m(uYCEc{fyMK;u6W?Q^32m1utp8t1dRqAn_Jm3~w7!F(+xi%oS+`KT^PzGX z)b2W{-2~`y<-O3qfcpIm^=tO$|DxrF|zS^EKsE)i#^m#1Vw}D=p+8#Q;dq8uPLvtPnU7u$|Yq|lN zaT;{qo`A~BFgJg;j{lVLy#y5vc`xU_Sg>O{2Ur&Qif5<%rO*m0p!JP~)>{Rwe;IT< zEgnzfD}m+wS?f%wJPjR76|`juG*-PQQmhV8eG0Vy5vc!Lp??zkr=foz`tKck`p%%* zk3q#T?&2p?t)sM&YMwQ97FLaKMfzHvX&^a0lT?4m4 z*Thulz38W)_o82fUax!uTEp)h-+yYCd3ItjXXfP%Gv94h8gsFP{7T!W()hYTTlzxh zVl;GobD`r~0v%uB(`kI2pyMlpj&A^Te52uH{v6vIP^te+&9UY`GglEcwZbmOSyB-` zw?gFs``}a1md~UA`_O+5z0sW1|0n1;zJ!kBz-Lq4CqdhnLF+%_xwJjbgtpItj`xkw zKZfR8^Z7J}wV?B`In;i8X#Y{se| zH7V1hwyLOg8FWrtyqZ`NEDsI}jtx!^Rt1*@TfA1cpc8-2$0VpsgYHkWp)D^$b1jDM zFF%ET*XyZ&I@JH=(BBIEozOoH{rApKI$pP_{hTr8rS1GwYd2`_zR;GT&|1gC>Ct!d z8_9MGbX<=>TV8AVXChsw7&Mx@ofODf9vSqCHnV@{&ML29SyY` z2p#7+&^Q-Ep?U6uUin%W z`uEUh20Oi<`fsmJb=(WB;{m8&;Ros2q}EVXq@@czxNmES*wGg&rq%iy~(oVzZtZ> z7xbB|!=wF=U#9!FS3>XK-U-{nH=)lM{T=%3PJ^$K-X7Zj1hlRjze#o71+8luw62Gu z{i$gGYqY--?e9kW$I<@xX#XMF>wcT!)Q66HEol68qkThY9VO5@c7@vYg4!Jnt>duh zKQa2xivIJX|C(r@80}M{{n5~$4*mBYum9WZ(sz|ySnGF{RL1MeGUh;O@m-qJ5@<^~ zbj}Ask0E1&)1k+fs^GFYFRd5+}8@E`V_RSKgULG6-UDIPj zpALN{p(^xc(6!RyhqQ)EU~H$7f&A|}c?Bx(h2HGP^!nHV+MmB&Yy19%lAY80#)088 z#uyEm3qLoJdq2T@f<3su_*07A7b<@ZJ)0}@dkcfpJ*PntdjU4f!fv}2$!{;{Yk8m0 zkAc>FO0@<-epDrN5?_XF|tuKD1>*w9kO%dOF${MEf?ZKHGQ8tU}(Sgq|H&J~P|w z>EG^eI@YSFb7;gEADjhq?@@NIlX6`QmB*m97S&DdtK@23zaVp`hpP(aSr&QPW3P0F zeZ%+SXv@YZTEDPh?cO!N``8#Qx~KKkw;s7&SG`$RS>}F5BzLXlUF)3NAl18d!&Jvk z&~Y3Ooy+q>za;eALZ1fRCbL6-KKiSp{qN9-ze0}%Mbx0aA=Gb6Xr6A+JO@VqvC#LC zgQ3Ta;h~R;{%fKBw?h4=K>eSB+Ao6oe-6#J9BN;$FxfYQ+HVTA?+Lx`a9rpoLH&n7 z{V#y}Uk=T8J=A|1)bCMfoado_uR;CZh59Xl`h5-cYuY&ZwS)R?3iay(_1gvNR|fSv z2+ zXBKpP3!vj&4p+I)oPXarZyz1eERDGeD*HE2b8#G0?uNC^Ns-?*TBlXPZ%=fj>zU;- zrt0u7;CSDjKVuvTm4TsW=QscR1O`QnG1yHG+h@b~73j8l3%cz-ght(=C>;y-gwD}X zu(rChIakNr`}bp`jv>TS#zAw>gyxzy@Ia%nfF+zjEDL>ANu>zpPxVT zda&5iQ|4sO|YFV$KGk?ZE1}cN0@y~ug+3oz+hXXvti|`5sCR(c9}D## z1ns{j`X@wxb@YD`{cT&PwRalSemFGVC}_MFq4BCiZ?|@uqb;EQJ3{+=LdUUJ=m&Dvv2)o>xyZY)&hL+utu`x@(} z_CG+|4~5zv745f2``yt#Big@>_8+5tpY|!vmC!iXLgPFNJ=Ro(-o8V!tAN`5-tCib zSG0-WTiCMJ_X5jfdyS4ur+k3!!n>hRO!fz6rFht~=s!RDFOB}!qyK~GUmE>ihra!$={5U6=rx@t zos!-bDqBF?dqjJAv>zAk6Qlk9Xs?X+x1jI$T5gv7kA|Kb41~&*&>so?Dd_w>6YZ}? z`@(2{FWQ$z`}fiQ(B}0r*PIqXuQ7GnqF(0O(kQ5YJ@lH?tk9ne{Z(kbqRy#*9jJak z^z(3!LEm>Sfj%35;g(6i52}9<`WH~YFGFv(Rnj{{{dWqzZ|En4p1(i-zt)SkY*Mg6 zoh=I1S=P3C?W$IlYYi%@Xi?rg^M0o7F6q6s-W@?bgUh=O?oG2p6#LS=RwDOCDh`M(5FZLe5kbTnvP#vLg!|G z=<%n2=!2tw9CWU}haP8|ZSR|E5s;?V$1Z zfaW^@D%U~hYdkd0-Oyvk6VT)AJm_ok_t4|)PJ1T1GHCuoppp7R;}3?$KMyL;LF2y! zjsFhR?^CG7_fWqEdnLaXP&o|hR{_mC7V3W+)PE|}@-I;TxzN0?L#5r`$-fxtw-5C7 z;keM>hQ?U}_4p1Nr+!(A(;O-XL+dya8s}%I-x~WQzjjc+O`-X_KxHV@?<%O@J<$7C zOQ7cpXYQNy*P;C%K;wP^jqo!x?i%~0xNAe@NNC*bdi;OIbYEbLTHgx{iv3|s)I2rD zFb_JWrO?qd?UmMaXQpXuJ=gc1!ytyC0!;I~|US|T-!)LbTc9!->NgeY_ZO(&GN|7#P`{>qli%7?+U2b?=i0Mke=Ca`1_Vb#$2KK67dqx8&^ahPJk4Dv=vrpQp>4$NclJI(!%DdW-VkO>v!~f6DnORJJ<4=D2D*Cs`78mtxyL?8idocIZ4l z03E~Q(f$ZZUK{Pdh2;k3!?V4)thza`M{% z>URRPe=PL%<`(Gd%|hro(0ZpN{b1-h$+6ILic>;AJ^J5;`Y(aT+i+mgw}k4uLo*MB z%Ei$9bD;TFK>Zt?n(WtwTK0y@QBeEapmz5_<30|x_y#J!LhTMXEwx_)m9fzF)$I@e zr`pqV=2IYT6PieW3k^g+3_sKZbrSwD!A0 zpAr3!M0?#cQ=B!San^#`Zy)WuNBdsTv7ZqAr$_%e(LXZ!Cr1C2=zl2sXGQ-)=zjGH zbneQBr2XZ1sQwsqeZ3I+x6rxlc2?@&1L}VOw4URmebCvd{ak4KL(q6nMEeWTz5qJD z51_IXI_|HbaesmOuldJhw>DHZfZBC}+HDK9>j90oA5;#3+8qV88vwQYBh+pLRIY&9 zT?@6l6>2vXYWFZyDxr3Bpmwi7?cRgheFBxQpmyIyf1{zPzXep*h4%j++s}&q-QPWG zeI|57>}O?B%K+$W!RXMZ1m{A>v;;cF!eMF5ouJ#YEI1%I8dmb>xW0wTX6K~XJ)rwj zdFXwhax(m9^H8)7zn##p=5HtXnlnAdFe2hiinx_%Z(6kHuc`ds)NH$ZdcB?<{tMyq@ICT^6tf?6j?M`EUTBV|LN6JSw&$KuIS|%1 zr`cLo_nm&-qLu*>XLN81oD(sBh_(h7rWy`|uBHCa>`y}1>1)t+`a$R)MgN!3+B;m7 zYCRnOvs$w?=QHy;^woSOxNnSOa^#o;&AB*O|Ke0@M`*1*Vg4E_9GU8R6)FucNikYM z$9qKRCqZSyQ7Pt*P}vjyo^kmZ!&dzESpL&i?pH%&9HS%V6zDkShQ0*WAD!}agvwS> zn{LoLE22Mt{<3=RdxY{HX)O+kSmPqrOqspD+KA)LMe%(OIsXs1Jk@bCRPGD?{?O-! z{z~WtSJa%J{Ljt|idx!Y*FS8ph04s(9|`@{&=-f^bj&}GJvL%@#V&ulXWug{ju=C) zOgYCwx99DlKN$K;(E0u(^yQ&9x+>Xk2;C0dL*GC2Gef@$nqf-lkB0t!=sR4U*64xI z{6~d8EcDAme=_v!cKuiUXW!#6J?5w9*p#z>aAa_Da1N}K`M#0{bu#a_a@Irc`_(zu zrZ~%?vi@~x&bEX~1$1l!Lmw9UC81v(`YoZ~8hZX1Gw+A)sQLUyi#^hs9yc!KnhA67 zA9TMyjUyd?6ihth^wq^Gk63fD zTb#AMG5I!t*4Y?Z=bF%-Qs^;lU+D4oJm|U6DCl{@IOuz@+o9)@A3k%X*E98-xXSaA85~z;5cagGlTP?^?w7cztv4VC&Zt%?+cZ)LO&<;-0$-k zOaA=tg|aOx&ZU1N9&J`onr(z5iPa67$L1_2N3M1^r`+2?`*sNZywJ1rnSXp+7V|L~ zyV+s;S@<@;CB-U&O6Smb3;o2Kuzs1n?`|j;2PIi9(Q|2Fc zTI`v&`4ZwQg?FU-Izj6zgZ2!7);Ahj-xO$lbKw&HT*EuvnPTh@9pmlL_WW8`?&I;y zbV<}OEBqJepFhuWO>Wub6mz4yQm(ErH%I1Jp11C*BYqXOOS1NNr*X7`j%x#G#I2y? z9tIu5#Zb8t=H|&C)9UovuIUx*mF9d(#GV_umOyhC{wdYc30hkjjQAy!`QJL8g34#1 ze-V1_ep5`I{9~7GS;_7@-Jhtr@6?~NntdnEhJ3M!+H>d0?fF?QB+1XR9; z#%nY+>FYxCoCIxo5xNcbx<9R#Ay9oJwEsG&{T)!51>HX$hwdM3rX{@-)UO-Va&M^L zAy63zwZ8`He^cnQLw^bC|1Q*HDb)Wbs1!Yr{KrE5Z-?5?fLc^S?O%b)N6>s5PEYpR zLhW~jTI>(CKN2c~q4p0z<5xoMUxZq`4YmIiDl4FN8_Y;{+dz9tp>}_O%CXSxaYE>s zwGqGjvaFW1k-t{*^>$qv7i?B%t%CKJb*Nsqs$FH9L2FmEj%PxQr#KFesc!dfPt{!e zr4QDt@mnpJ6pV=4CP8bjgjQb-or3~=9BVOjEH+id|EEG_Oz78yp1TjU*t_QIn|fBP z(ZA}vIwujQw%51H$TRVwl=m@cO)rGLBJ}LM6}4_q(DdoN*J<+WQ#zAeK@qzbcKyTlYN$*I{R`-r z8~-`QS_i6c9eO!*JDeQ)xuK5@{XS^KXG4EJ^m?;WytSeITZUc+-H!c2KQr{pL(h)o zU$M4pK4W9NeI8A@hXlt3X9nlPRqi9@zXs;(+2iqJDfY`ySq`1A=ChOD4!S+_>o02Q zcSg3!-1BnVv?xp4eMI<8g0sW-muM?`yk@N0?j@B(obK563Hy7X(&mX&+u2b06uK>! zLtC0Zne+}&ISRUMhJ?OvWy;?dD#wL>R_G%_&mZgR^mHAiWv$my%3~apBleu&Vrb3v zpGq}!gx&dbj$eH`wS58AzX`qFGf6Lo%F&_s3;lx7vvvARed{}F|2@M&QP+w&^$J=& zo8oqbZu6c{=@aeQcv)sXYslxx+}>j&&XDjK2d9VMdC#RdqoHwMfsXlSXwQ%rl71ev z|5B)210DA*(3UEw-&;_-MNs)1YWEeirNLj*c3um5ES(wpY-s^<>dDdsNeTcsryRuYXog6hQ{9vYS#rSyFlajfVP|ieZ9J*D!mW*3siqD^!Bf& z_qDyDuW3(1``-%v^4F4nJGB4g*OPuBRKF(lyF$M=^d~}J7W(${Q$4#u>n?-VeIQhh zgw}m5wB=-|-I-9k;ZPX`wHpJia}u=Y9;p3;Q2WQA@*LFuMX3E7(4P08_MbrQmqFzx zsC|Psl6`Y%Pg|({22lIWp|Ty+ekW-Bp3t8Cq4tMB?T>-V$x!afQ=$69(EduO{j1POZ$smM2(9NIQ27BG{}<>z{Vm=~ak@g|>;{cf z4)r(;8mAvL&Z$rt3XO9fG|m`kJ=a6yOoGOF0O~Od8s{l!oOw|B8#K-b&^T+po$gP6 z_-=Z(VD0ykzAp6au7Bu5LccronV~-oeFkpB>STW?^nQ0gX#0)PJ}=sveUS3DgPHk- z*53tM|L)LA_l4Gf5VZcIp>h(m{?nlK4}-=T1&wnpG|uf%k9(nU9)iYs0xB;+!9&&gUUV7c+;TqWV)8uuz_gd3r8Cqd)h2bD*laUX+j?_Z$tw_Tj#?*ffq2919RH2$&B_=BL4 zhC$%$tU$P-?MVjlB8b&?Y{wdmab*u%gqZnGpR?s?jf!1*VG~=Pr zIx3)boC1}zq2oUnns=YC>t((pWBqTFehBnCGlqnIU+B+5zZd4a?^6F3-zWQi(Dsvm zOzjh)aVvgG;~5Mc&&AO3jDe2lM(Dgwf{tezbUd@6<9QJ}p0}Zue+V7VQs{WThf3WQ zX*`Xf^Vt@fZ!>7VF3^0tK=bVd&DRH-uOBqu8PI$eK_g!Q%{LC3Zz5Fgh0fmt(0q?V z^UZ_idjp!U8k+A@Xuj{E`5OG3^0k8ITMrs}Q)s>tXuh4HvKKVpe$aWn7@Bt+H1D0z zyi=ih{|wDr3C;U5H19&_IR6gK`z)E`a%l=GzAv=TK;z{D8s|yqe7ym!|6^$V-$U!?#|$&|uK}&U6*TXL(7Zfcn#sExG;cXH?_toq{h)bI zh00K9-t(aOu7c*92+cPII$sY#^Ua3l`ztix0_ZqDf#zEd%~z*x%GU&%uQgPPq4_q4 zjRiH2*cw{1c)1r$O^S4$c1@H2*8m{BJ|$BWV6FpyU1l znzu>)ly_}t-u0n*H;3lk7Mgc=Xx=|S^BxV&dnz>V+0eWfK=WP>mFuB-Z-z>xJ7PO8Hpml5stz&y=9lJy8*cVzyUuYdCK{MqY7 z|E>II-*YxSu6I?&n5$zB*vT_)svlReC~m_LuB`nO_*Fs9Ul;V(-s&bLCE`-Q%IZ0L}dawAKS!CA|VV z&KsbfPeAAXYv{V#gYOV?U7Z2duZ2FVaw~Lwjb1yguREanG-&^9sQvTMcyB=Ce+a6Mu?~ffeSB~hbWRpP zx50AQqJ0|w-cUIr^uf^eITE@auZGUc*P;Is{f#>$|7OtsGH9g3p*5Tq`nl16H#FW1 zX#CmG?e*`@UuIv-`OPnn8Kp5#)e*B`y_BOEnzIyIV+FLv5%xFYT4|5;Z0THdYdLZK zG#48e*Q~9mZRYpYmgRpRSViO=fPIXkq%wS~p|UJLR`c}TeAYc8{99~*fA;fZImeFN z8Z+@9+7NKt&`n6lsoU{Dx8gp2kTe)*ka!@*sR)&8yw7!DQ^$XhaXAOUV%4wmW z75XTs?H$muydL^H(Cxj(mT7ypgRaTXq5DO>t&-jm8hLA|eShdNb!zAjLuDznj`~|C zyEUPCyF%r7X#dcXq+bf%-)4v2ZVj$y_51C6CWbQR{Q3F4db@QC3)ZSrRItXf#?_6g z8dTQfHO6@~rem;vb6a2a2Q{Blu)e;uTkH6!eO8QRL6GiDoaN|_rY%0sT{md_zA&?W z_c1d;IpSz$$CHOMFiyY`Sfl#|@#?%4DVyVJt&0%(mh zLZ2P|**IBd;tfvU!C4XU7KG1o*uQJ4r{Q*~o*vM-Tj^iR^NJ%PPJe8Nhy4xVztXQ& zW<3nbe(%Vn@L7Rf$L&*&U7@l%pUUu=jNQEO`S@S>REN*-9hjeZ4xozv9n;=BrhEs% z8e`nBaY4I_S}$u^o%;j_#}C$A!NGCwADCb;2A={sGJf5_3s^JBw zwCkQ%6X#U9&xce*9VOVU@(f^pzMR^o?2_U>3zdI_{&ncN&)bY3NB(nhwq?cj=3k$W zYVLD|yVkGunVU|uJI}jAW$(~eZtq$?A3Z6?IUd_p#+m;bpg#DwbL+Cwl1 z!*?upQ^NL}@ck7!&rSAB^IHu)u6`H#HhZQ1?$Gg$hi->S(SBdFXLGNPna^D<%6+v> z`)p~{Hf8S=e=fA1CD1&DW$6E8pXt9(@@u$nnv3-?yO`^h$bNX#bj_H;4MI6YU#E`=-&}CE9n1_SMbD>e|!!ckRDpT=Su|egm~=wSWDB&ipy9W1%uU^h-m(9U9}^(0`8pRtF^eL!kbrhCU(mM?#+) z`l8UkgwFdf&|2D-r&>lqU$-ZQUhlx99|6tOw0Ale+6bzTf%?A*jo1F5RL`c+dUl2O z?*~1{crNr^4^H+Yp>j3U{%)xKEa>^jyPph~w8-y_g*JPY-!g8Hr9KlyC{_3H-p+vxa|zYElVcc_0k)c**m|3s+V3-x~*>i;dY z{`w~*|CUhy4Wa&9LjBKx%5bRv4N(8x1|{+ zy^if%>vg2*@jBM@q%;?ugT0`0G!QyxW1tpOgY%+&X|Uyx>yknr}KV?L(bm#vcyg&+U5UscCGt zKwn?yLdW(VbZpB)|2FzJJT3WkhT8XmMi>CSMsRbqzXpxJ>FMcpuOCz&82ZJ~*SFiD zbMipwM-57I^EkBsMd<71n^6Di=x={U>fapNza6xHH|X4sgO2sS&|ihddmn258PxuZ z=x;nYZG-io{`)}f4~5$IgW3;_{)?cmx35CC)%(!b*MrYY`fhZG`W*!I zI~MA9I@Iq$Xue9Q--}Sc*P(vgT+R43gZj0H`W*xHI~D3T6zX>o)bD+0zR#h4KSBL= z{bTa$1@$`=>US*E?@p-S45;6eP`|oElV1y{-?~u0jiL72LhW~l+TRSV?`7zD&j+D@ z4fX#8>R&i4*=-1I-xAusL$vP;y^r(+%-lyhC+&m7pywmkg+3YDf8e>vei$_WC}`Yk zp>c19+BF)U?6!v5?E4=cuYnuEiO{(BL*qUMjr$=q?w8PUt$@a@b7_j(92&PRH13JexM#tR z@M37(YoKu_LgPLUjr#^PZZ$OSN6>oL9F^j(4UKy|H13&jV|XDn?$yw^w?gAS3yu3a zH12!QxL-lz9zVK%!6xv0xG9_w`jgOc%!TG#`?8d;BkY9U1)8rEn(qK;zO$kEMndyl z4b3+h8h;7g4E`GWCYPuDU7`7Qf#$mcI=-9W=I~F@d=EqOJp;}6cWAyJp!w=wk@B^L zj%yR>ICqAf;h4~GhUU8yn(r%UzJf9J3$_H!p!wQE^KA~z*BhFz0-Em>==cUfSEl&8!mZGILE|3|jXwYye=IcqZP57lK;ut?#(x2B4ZnjWu>Mu`3$}q> z;kNLA(2s%kp9;0VFWTos`)ko&4UN0m)v4|spmmo*>%Iy)FB4!FcrUc>STU|nzYgpQOW<~}0&Wk_hC9G9up7Js?g-moQ@>y*I2x+o0PUX)wSN%m zUkQ!(6*N!5wP`#(q1)jcxHB9JyTcdZE^xc+>KE(^XF>Jnp#25olHLrew}<+7gvRRv zou8ed^K%(=er|-j!MmXI^AL1?o`TNLr_lNN9y&jDuTS&S2%2Y4SPDml{ya4AD=@R3 zpm{%l=H2>+ly?`nJNkamyoW*ao(Ro*H8k(7(7aQi>*;=I-nq~`RnR92);wX#AI<@&5*mzw!7Ke_OZ*`tH#9e}Kk6 z3L5_sX#DG-@o$61Uvg_YPyQBeLHn{tzo(MC5jLI&+o7Ox6i}>bp7o* zH7;mh=eB|xcYruhX6M=03}0_tOPTjkztp_{;d&eGIz#4W*C5ok@TT-0trJu)gPHeW zp$~xijfR=Z16o-+@Cox3MZugPSEzU&<8;G-OyD(m$xl(YRj9M@4Lt=D7P4r?&hfBhNLFXHw{Qg`S(YEHAHmRj48oZ9lvk31hlo@Jqb8+vZu0eN}Vvy#o*vtG^d)}LH^-j1}p*0zDl;h`TBdT!p) zd3n{dlFd6iFYgfI)HdGy`>ez7N_E@zHC4EgKXx>DRC_eX~kB?%SS~ zY(2f|*PMsW#H+O)@Avm(-}D}1zu%qlk{@v{M`a=zZ=$tJKmf0 zo=~|GYPY)A!J>AJ3R*n9>V46!F%)_)?o!wg&Vs(4Js0|`(f>Zw{zvFF%LezS8rFfz7NL)TzMfqb`rXn0=g{Yc z{%+|1n3nv1h0a~|1L<1jKcITU>8ZaJbe!!%&s_`4n4i_n>A&0FZvDmuE$ggZ&~#ao z>cXm;Ys)-}*HV3Lx4tP2Yp$)nGit6aObWVDv$lPML!omx9y+>N&~2~)x(${??b{Md zc7u+ce+!1j{}Z6{P}qD3-M(LjzTbnX|3ql6S3<82z41e-zYVni#L&-z<~k?z+<9(s z#Omj{UAu<-E@@FgcE&sh(_S&Y*&NG)Mm6Va33+n&MNA4t)28i|s9`S5j=ykb`hRn% z>aC;xq+vHuWy?s&W9jJKV?QMzc=HKC1S4VnE5&MvGu z#?i#iALD??tM8Qf|NW?8gGW+L*|l5ScaroauXXpuHgmkX=(pol(?2KwaZtGnTHnLa z8s|c{$s16MMbP!vU{+dxr$A$8^Z&cd|4z1{QR{HxDifgf%!byp5L(X)s73onQ$5{b z{_`IFA4|2J2d#N5)aO=cjC-Lm{|wEs0xAt>|7T;W?fcH!9+l?4BQcep&|LkYxko~4 znGCg<0~hn>oVI*C`AmcQEQk6ud?M-FLZxTumqBCQ7wuJ0i_b&9;K?)(*Foi8=rQC& z=y9C=F>^d_57lpl_D_MztY~ljRBA7VN=fKDhrS=w|C4C{D%!t;j_aq;Gy6|`-&Hr} z>EB&D`S$HPHZE9~-v(~AY^~}RRn01!4qBt4Nxa81zT)eR$7JWTThp5RUaM#7uk!s@ zt)5PE-8I+;Dno+ff-|9WH6J?mZ=hqa`WZt6d~>Tzju)H4TK z_u{CBe+xQdZ*6^`vM}^dLa*~&>d$V&+CG~yl)8-33fr(NS^0dq=U+Won-OOoR941o z!E^C<{5Hmtqf@L-&l9VVb>1bu>pb`WyywNn%}q6a1eI@~bFSr&&iy~5`lsEi5yn$TNR=f(6H z^_=%v#;c5Yow3bbv)w*OzNbLt6X=>hWKq%w!Q46hZR`zL>)Er?H16uiF*p|D175 zi!t@>LPCDh=24f%*MHRZXC-tpU;Is01fKDglH z6u;poDgLM>X)bPr$_ud8`S(3~-uw@W+{?qa?WbJ(<4^yypt8b#C%f%)#~y2`q_#D((!L4b=g_0!payxzEk1icRMlFQ-y7fV`ch$RrPOsAjkf%Q~bleN%60Mx!WQ0*`~p( z?zep-N2_niQQWwoxXy+J?UuEvYF*iK&{`Em|Y?g#w>CegUI9Ox;^76MhBjq28-Po|56~2q0`|{_|{raq5l0E_|SHRrZ*)@=5 zZJ$#piF}hHMrFjThRY+~mcOR>dqCsw8G7H)YsAlapXgkaXUBtWjrHv|&Yn-j=VQL% zOvw0K)$xpob8YB&dqKy02=u(80$S0@P|F*k=h3%A&!eA!#>v+8pECb_=A;6=g-*3K;<-O?4h9# zkN#C+x8n0oIiEYs9D^c$`&KDdci5Ld<6jtUW1#Wd@twxb_eiKd0cw|@C#z?UBa599 z(EcMrKOcJSU=sBD!AsEVx?8kwka@0kVF$*Iz7+bLYvFpSe{HDWHqbl=L;Kg+fOAdQ z1$u9{6gG#az!vZv=)K)XH)@c%mpdP-SBJhv$D|M3B-u}g#(e@>*F0$6PoR0X-Za_w zg!cD__MZyvKNmV*Bca>lGN|2+P`d}Ac9l@O7oc`kP`me_c0WMv8g)u`EueO7pmrNW z?Mk6`2SDQ;3bi{LYBvySHwtQZJ=E?tsNG%AdHMo6?w_H4YiySMHi7zeh5GFR_3Huk z8w2&b8R|C$n(rB?->Xo+x1oNEpnjbi0C%uTkgZ*Ba`#5!7#2s9!Ir z-@#D7qo96wK=Vz9`aJ>ldkgCK3Dj>H)NeV|Z?`Rz-~Ld)BcOg~L;XfV{jP-iT@Uqp z8=7wk)bBf}U(r^{Z+)m=C#YWu)bAXq-)N}c4N$)s(0orq{pLdbs-pe2tyBA-pzTvb ze>n8)aqRy$rr%D!v({@o71`q}W5`HJIwlrFkBOzwu~tCmWCYZ567(2X8G1Ez4hyzP zEQZc)DRizYf+L{Eu1U~iS7o$UL$^&qmvpQthBaP)Jx`4DiB-;(a?c@~asPzhG01)I zvWk6kt}%33qsI3ztYQ=AqMde3}aHRiRb$8WFQ{dY@sJPwu5 zps$Tvl%{pi4f@)646Lo*Y~5MrpED1*G|kD7@E-?_KNGsn=Bww`lpWWRJyOo|p>iM8 zx85GL*OH&RVpPiA9($!bJTiQ*iMHoMUs-?4x&=*or|(4=5&jFYSsr#9_Dr#Mgw}E_ ztZiO$zpr7y=v2qh@Exyj-k6K`OmS|4%AL^oGobVIB6K@^4&5J{@0Iq)_R#(DW>{Mt z*>U|V=Fi>esChbZl`3dG%b@kN*gMrz0n5@_s(9?{+FnHSP|T0nnI(L%%fo z^J}f`9$977H$Hr487ps2OZG|m_J_{hQ~Rdc>+P5Ft_hVk@b}E!>VB8CI%*jd@y15{ z>Cm~Vf=l@`$7a1!&TXM%-vw&3H?+o6q4RkT)bdK`_HDL*ItM)ndc3_5dQLe9{+`Gx~=n)Mvp9{JK`fTJf=;v`BIiw$^JS#j)Q^jk%Z?Tne3| zrl+Pk>kOU4UeH+^2wf9nf>VR@p!@w&=r(D3T4Lv5FX$Q=82XstROq&x2i>+yq0jp^ zJw4^=9P9;M!vmpfd`z@Yg|5|k(6zi2=3XB?a!}gFH$r71bYH$V^arB9?2Odk3%VvI zgnkcnJ{}4Et~PI3Z5%H$jb+-4}jW-uyc9^p&w%*DYxJRl4S#$-%hY=FYdrt^Qj!&#cvg z5t%dm0=KpIoLs*PE=_Ioa@xAlR!ZNBoVKC(meAIV?N$bT?HGz(C)l2CWSjBY(pC;9 z(3Z_Hi#GpX8g|y!mw5X0V!KJ(CSlhXPNhwIr4x1?;fk;;wJ~HM+f+N(@9=y75Wbe>;^=34+A%Oa>O zh1QqdR{t(@&tK=VevH*(Sc+W&%~cM~JqTLMSZHn2p_Wy!PEq56qKoo>b(?w5or|3R z=bxAA-sb$2>nxZ%j@sULRgOvZ&B1nI*spm(^6w05_^-wBv)^y;e=^rf!@n1{{lk7* z_Db_HkToZbYSiOI{zgB)# zny;zY%?aBt!*~5rDc0(I2Ze7}?0SXmrBJCJo#(r{=LsUl3hYE$Wr zG`Pyz$-7QHH{#91Hgnw~#yb6q4Ta8Kjwou7^Jt(}o_Gis)&x-q-f3v#OypLZ> zyzH?^TkDk-k?-ut_jKsbhn}0illgtFrQdJQKG~L)Z0@ex*34Z+tlDy~oY&Ue6YsI= zJ@MkPX)X?eN&WmbE?aRYskTxiWmK;qvg`;+hnHCusaXgnlm6CmT1* z+Tst0_~XNOma*b^zA)l!b#2P?7|e~E|E$oGh}Hf&V&vb?u2(nnyMj?m$-wJVth=D{ zLg+7s-u8yPTgJ}X zZ9|`rs%$){up)b2S9VR>w#y?%r`uAFGFZW1W*ksyI5FwHpi%{`XL;y*PfGn)LffB% zO6%KG`w`Ig&F@I{?+(@Pfy#1dzMbz(?H54j`&;O|HlCc?yFq0X)cz&t_V^(Bzm5K$ zccuPOP>;Ky`DVb3+@1ROf^NUw(Ebyk@^JM36z%n=q&2%Y)b3E|+Po$7`=IZq>fDq1 zi=gMQM?>Fp&x0Nj*St6BYeW0DfyV0zGoQB%{SoMU?}gAa-BweR-T`Vq6e`a{=b`ES zsl5YK2146sMEk60Z!|5n_kzYb3Mx~h{mW=y9_@QSknG1m>%9f)Hx(*BMStn^G_HN1 zc736CCqZQvw4R@%z1fV^eiXF*4(NFAgZj;k{(29l{xWF$h0yn(&q3dRHh(DT>p}ZF zL*-m(+)Ak37BkZ|iiVFQeRpV_a;V?YP`MxK_Z8G{{XeH`HxI_~=(ZmLS01}_?;Wk~|J?KWT${#u$NM~e(35G~Uj~&Y zL(gvW+OB7pU7L>ORoK?J|CPPRQ_K6s!lzT7y`XYN=qvO1T*%-?RoQntZoAQuX9~92 zxmXc??Vm|;I>X$xoBJEMQ^LO&HmjV&)N+5V|FbF9&roUgT+%Or?q5GckKYZRPp=bu zLiN7Tr$qaM(ASS=qWy2sda9xR-$4C;fxeD3|7*&Z-B$m% zS+oxC%kNnAeYtC7ZfxV8QDgt$$l&DQoZw>U*y_KS#@Z1&mpw!8ANt79Cqw6EPP8u$ z)}O~c0si!z43+tzXXht#f3)AK_eYmR%`334Q_DKcuJ!pZrTD{MPVpx}YkxNMZ=m_l zcrC4yE1~BxGeUn2+Mlgwb^K?)M_qVb+SiA?o@y8ejX4v#Hs{0KbI5V?Q-6aul6?zk z%^N}OyFhC!gRb2(pq3Yg-r>!(UCxE}xBXk%*EWW(<%6O7+TGAK{5o{|Y_uTlTL(b( z3TXcj=svXqdK}*Lt)w3U?LQeBZvxcs-e`X;+UG&@&WF~s7+U{V(D8J7JMAO=q2oLi zIzPjpc`t_M83)aC7c}1e(0GqS$4-d@mn2f)m@-%b8Aq35d4K;yg) zjq?FCPRsY=oC6x?FsKZG#ycMxulf5aUI%Er&d_+>q4D;G#v2Yjhj2Mpktp2os0R7aR={ZnyR{*uHlt9hx45)SK8K`x24b-yOYVB>mbn9FWRC|H3 z#5lwF3|z@e^Sl{`rOx>zLebn9HQokgc^}j<;wh*atDtJk{p$bO<<{hRht2B< za!JNPjdP0W3!rLV303n(D9fa;UCjfaY90Z{@zVIWK}qU2PVWmP%T4#!H`|xj?l?nn z_bet`O&hTf?g!1!{~egxh-ZBQ=W2Q1xxD{^l8a4m%o}*dqug>W!DfxwZ8g7`4UzL7 zJP)cizh2nnnq3K$+zT~_@0$L#>2GXw{U1T~Z-81ayiHE;05#VKK+W|~D366u>qoQA zPEUjC?+sseh5_iiBKLzQ1Qk?#k&bA-d#}gIFx@qRNSAT z{1dl2{|qSq-J$#sgz`TUN(!O;E`;)12G#Q}l;2lSem_F_#r@>``aszafoeGp%Kj`U z`%9thZ-$Zwptie5Oke%8+o$~k!}5#M6QLxd6~AH4vvRS#VW#uFde-HP6uzr9)W(ai zbnic^&EH=BQ@ZcZI-tq#&r*ENjpkSTUyWz!5jvZ2y7t9tE2k|N`jW{Q%29It23W~B zmq5*d-&|>PRBPl1IKy+72{o6wP_t18HP@w3>#{_3l?8M^?oQV`c5PO<&CP#{U)}sp zh4NW$`dd(AiTTa-r$Y5d&udf9vB%GF=Zg8}TMVs^#_PIJvyj(}H%Xh?R}TNf!9%YA>v z*#afQlAN9gCBb=ELY$109lx0--4~5juj&zzwm-g*I9q_ROjCo%D*?1e-4x!4mBSmti8(G z--TLZzcM{MFZR6;Yc(%=CLy5v%O#o_9b>(;P){$h(tZ9^>y7+XgZ2Z;q5lUR(zcKw+(;M?deda#Pa35hu!}NDe|G@O^ zriWNZC3`^S+sE|brXOMYrKXQJ{W{ZcGJUb>OHJQo`WDlBc5?BvpyU+OPc!{`({C{S zQPUqceVOUIn!B+!cWbZ)gFive4T7cs_&g>s46HD)XsR4F8^J)cP8| z{_dm))|IVYT}>(7TrK0F=Dx88mVfm7Z8cWQ3~YnrYcSvJOc(1wsBsFHv1fr{<*a=F?-Nf#I#ho8e|Q`>C%EVs=TSd0>j zIRjQ$ysxY;qqobqH&p&ysQuD0)_y#cjD>$!M`Jx5n4}`Kg)Y^}Pe^*QR94PSY!$`|L&ito9#a#faEmq6EF6X)ZTZSZ1GsAVHg zIx+J2noI29xeF#HVc8M2SF8wKTe4wT(xPY(hh za-7}1P;K03_a5l_FNDf70m}bYD0$!dzlJ)mX}+(sPlmGZ3}wF? zlw1a7cN}dyLMXo%q5R%}lD7LhyM3Vi4uY~90cCe8l$1lodlt&>x&vav z*L-uK^!ZSJ&qDdV3MI)0I=l8zcHN=u_Jpz<0wuRWT|+(!W%mq}-Et_q8Yt;H*!3R( zbsbz_`WPtt2cWKvpNG00{vPW6(6}M7?m2&``{5@-oxP5S>Mw)xuZGI^0hIJO$k`2n zvO5dPZYq@BJy7xuRJ?Wv^PP+ET&VX~Z#8`mRR2P#IPXKn`2#9W%Ul=dWGKHoq2iPb zjSb)Lo(OfH`c|m-XG6oB-VW-1cu%PJ==Xzq-}Wdd|5KpyUI3N%E~vcApz^*2B^`#l zxOq@{MnT0r52nLwp}w1WI#k>;sJPES#a#y#H|9_mw>6ZU2DL9LG(EfzobK+IR0j5e z{=V@4Ys}a^f$xmCDe^lbvfVYU#wXGI>G^-n_wdsq_ur*?vEEqP{rxrndZo;jY|EhL zv=VBYs5N?rIX%;uYb-RDLiv?JZ3~sA*Fr5T?{K#*WJ2k=P|LCqYT1?=%b?bQN~m?B z*4jO4);g96WuFVRZWKa)9RBlujdai8hkwI(M;qJd-aGC{H@5rFe*26%XH6R6@_Y&< z&5v<R2Tmps5xqkujeM4_`Ged<>+-pMPT7UWs1s(6A=oCoHpZmRamd%T)`N+q=;AH*hd7 z<$WAVwwpfuBBx&tRp+BnzNZ&E-%Fs*-zGtwFTD-r@vn>BdBqi%IQ{lZ-Fd*%Q2nh- zoPEjV&i@*yd6^6qe+E>%MNsD_HBf$=t-s9`uAV)hWH9{i%-cWCYp+BvJ=Cqq@6D;S zd7W#yms&0BplaU^HI~lf-Pi_0jb#*6wG)hU;Sye|d*an@9J8SGxu$;!HSehtT;KXE|Fr&uYg~UiRBdlV`Nv%A{8OO(cQqereG%*Q8b^ z7q==*%5Uku+so!*ki{Aa6?;6KW^o_0zHO$*T<7w3fwIjq{bCK6}#~Ie! z@|m%TapB{R_V;S@MR7IOwPRAk=brgD>uAOt5?l8tbv~MIZMoK_IgpLd#$#60{vqPH z5ZHG|?C*Eg?%%7?y%+o5Oq=)YFm=ye`|vSM?8AdG1NUxrItE488?EbD6!^?fMBPn1 zS2y7nw|?Axt6Oe^Z;M=C(t0L%9UqKdJKT9vCd)+gIS!vm<~Lx9i<1wvoYtAX3CecR zRJWdf0<{fog4(}??r?e{)Us^@WxuQSHoE=qz6UGU>WulftF0GQ?BP(kilAzl1f!1^ z(R#sj=iC2I7wbS6tTX&R|HPf2*($UeO0W;EW9!WSkQpxCk#{u_Z~4ygO8vRVev1&~uMFZ4$`^|FW9St@2#ZddN>!2dM4Yf~BnCPV}<ZFNw!#@yp-?*-*C9BQmZFnS#cE?=uX;yZxH-|NQtF_a|VA35&uJ<>~d z+#~hwaobrIb`i1t^KAd~G=Y7{$_HJ{{D)kQPV=I2v}QjM_4%%v`(2*F*f!R!eOhbw zTiSO-t=AFzmO$=^_9pf@b&tAQj(gnIcGrTa+PWujp4s$ye}mOnhfhQve=ckvJnab= z^ARX{&UAlW3f^~5FLUFr!6rP;p^@8LQ_omtKjq>d3ninW)}M?;PVWn~E|;1<4Qkta z!1Ugo z=0LM|-*rBb`3M{@OJ8(3%b?_W)0acd+iKJOoIZo=!$`~7V78e{13CTq3*=l!zs9*8 zO8PEydSmRA9q((8x7Z`F3F$eMv97Pye13zH#Ft!*ZcugaX}TZNXY_lYvn_t1*-wKD z?Eg=p3&t~J!Y?m?*W_&NTG(ch=D#A;YT9LY+kd>f(iB~`k71KtK#L(=V2)M2x`0uuR6UOY-&8a`hFhoLASkR zn}0qWYccM%wo9vAjxs2D32Gc0OpkfZkuI{!nv#z3H={ z;y-2WOQGgt3)FH=e#@xb_+{i1hW|BF!eRZwx?hLSI!;%ByfQom( z8W(RERJ;*TQV13AYN$BRLfwZP|AEuzLgjf3D(-5ixL-iU-Tgxs_duw)!=Yp(RNPCT z;@$^!kN*v*`{7b@PL(_mN+Ox@W%9+CPDM&ttEzW5f5+_lNpUs6O8~eK1u2i{H6CZ$jO-99;bUW1Y znFY0c%An*KsO7T?D$hEoJTbqxxb2|g_JmsQM?u9s5i0IHsJIKE;ywo@Z$ibbgNnNa zDsIPZE^dFQxcfuJ9Ss%te5kmKq2g9T#eD-xK81?A9V%|(uP*N1Q1dVhD(-Pmaj%4m zdjnM5)lhLifr?uPCEK9lw*Jk{dnQ!;J)r772`c_MQ1LH=ihnOu{P|Gve};V1)Oq1LzSpqBeosQmXp<$o0_|7xiGwNUxvx4ZnUq4JN0 z%6}eI{!5|cW~lrRK;?ZBDsLrJ-ZfBp*F)uPfXbWxhs)a+D(^t3yr)Br`y8mePebKh z3YGUYDESB~@6S;ATKwtqb%M&*4=Ue&Q2EY)8s~*j`6fW+n+KKe38;L3K;?`5%jHXf zlAcichCt;z9BO>0L*+XkD&Lh*`5u7ER{@ppC8&H~L*;9L$~Tx+m$sJx#+<^2LG?*^#6e?jGK)xzcN1eJFfRNmvE z@)klxoD7wBI#k{=sJxFtdz36*aXRKDi1E?*kd{C0!NcQ{nOlc4eyK}DJZ zm2W0gzIjmj7DDBF4ocpH%2x+9&aa{J{sNV^Wt_{K0hPBeRNiBu@)knny$~wmOsKqN zP&q4Hf073n^xeC1I2o`cHw zGE}~5C|L)U?`NodaS1M8C#dDv4=Ue&Q2EY)%2y1P?^>uxk3i+CfXeq0RKC}t^3^~| z9aO$hVqEw*p9qyF9V$;Bs659&ZQ2Ba8<$D1t->XpaAymFCQ2DmQ&ag$2 z%hv`fUw5c{M?mE}6)N9Ys7TYG^4$%UuN*4hb5QwShLZQ7@-;x65B&+1H=cWR%9{o? z&Tde7cZbS*B2?b9q4JhMMVt$j_YtVP&p_o}4wd&UDES;J?{=tsv3!5E@})rK>jIUp zKUBUGpz;+#<+}tb(%n$`=0W9K1eLE6D&HGW@+nllZBX%&Qe3=FQ1SXfSsn)!?=+}* z=R(E11S;OuP;v)UyhowpeFatDR;YN*QeC{=pyK5~#TyD0?IRA!< zb3c^D>riptg^KezRGfOKI6pv1i!>K!SJ&nLe`$B${eExL-~I024`Hq(`A~CM3^k|I zpys#&YR+qn4aU^gZkc35Eu(y>WmasQ2DMI97;B6TP}f|kZCrmg)HSR)V6WqYpZOi{(Pu&*OjiW?j&(f2B+J>>3}jWxf?7N=Yc)AErhm$q@$()NbP z_fOJ$#O?Gv_7d{TcCB%n)kTvx2z%9703~NZ*%tM3WBfN%zWYsI3YGg!)8B{TIf0Vj zq2hVHogN(X$iEr0KL;7@xhKVET4O7Byl*H!iSp`xwDKiQiX8WB@xJKHR*#GOHqlAWK~SD!B$dwBdeM$T0ZF@tlZy5(cF zXD_wfTP*jOJ={EA0VTIX&1dqSZrvCFwQLTDT0Uc-uBab_@>p#83Mjw#p=6WwH_LX% zr2bGn`$G923Kg#aDt-~vx>y1g=LRU519eO*hiZ8m%6~bO|GQBBUqJc)0OjxP<^0=0 z`By+46W=qv4$9(tsCa)u#cQ>aUYu>sGfaccz$4beqeZhV0eCDcz&Q{4pbdIbK=7H!4HGdPlmD> z0~POLsCcDN@uoq=D}##nB$T`cbwB+xs5sw4_521ECt;wAlMWRp3o6bas5pm1$>~sW zra{Gd0IFvJRGb%~>Uk3?&c{%3zJrSME0m<{>-_f|%c0^-H+`n*4?|g2K#k*NsJN@3 z;(i7dcQaJnKcJ-TU>Em*AujF_P(1}uan6K_a|u+OiBNH-L&bRjN|r#?ck@9m&P=GD z2cY6qK*f0(D$YAlaXyENvl&WS9PHv;2sO@Ypn4`l#hC>a=Mktl&q2jm0Tt(cDA@>A z&!$6Mob6COaYJ34_E2$pL&X^g73WZ>I0aC0K2)5Cq3U@Cs^?{>dR9Tj`4lS7Ca5@n zK*dQK=Hm2(igPGboC2txGoa#J2o>iVs5n!h;@k@rX91K{Ld97P6=yTlzVt`y4i*o? zqvx}!&r5D&+={=^Vk8|N=VkFy+d)uroauhN;Ctuk58L@QHl=2_!2Fg$)$^h0pIE;i z$7fUD6*0)-HkfZ}9(D0jT}4oGh3S76XQag`$F|b!x0-+45iVY%e_QTfNB#EXc=ON4 zcC6XYf|BJ><9iEg+5Zf+>=TZ3%lb??U-~4(0b9l;59Fekn&gzg|%P^@BRD41w}H*4l4?TIXh2`@>N2 zDxl(3LB)F?O1_2qul5*s{AhNpJFawrdcQXds(&A-ZRv2Rd?!N1Erg0&0u}d0D47Gb z{XGR0=M$)&?@W)+cX|h?{?4ZNh5DS#flzslfXY(y$2;A`D-*{qNL`uhJCGk9jWLnzxzZAh+7s`0;#!d;80K8fa2_b)W=vT2d~(Y%7l z^K4AKJez~DP;)jJY7Wbx=Cm4WTdFrb{sh;aWy~{RGzLK4Yh6MO(%1^nB`0HoN-V&ws(U?hIG|$%Ss+ zIRk3WuZJ4fO{PCI+VwAlYF`9px6$-JOkXy}^;bdl-+Pwxn-Ar;0II$9*={-YgwiXZ z%{ zO?A>7u2)=3yV`!RTGGe5TKhxE!BG9tbJo=HyOi2wJJNi|E0(|B2A=E8I@iTK0!nU# z8p|D~&#?ZTa;EHfwlQ_S%T;bZ)rt|gRuB75XS)gH9XrnD{Wp|+Y`UK(eC&^UPC4K5 zY{NG70_T4|lso}d_iLsnUg*}t?oe_BY-;Wr^XW5kO+0U3Y&pv;PNlIHHhz}GpV#1+ z^}l`JLUpmr{T-C-aj~m)Fw{D8B-B1_3H-YnchtJ`^KX?_XEiY-^-%K>e~HVH1xN8x z{HvhkHYl5iOkZsJa?{^4eM*V5zZ=T`e$#{7N-g>Q`oiC1b;*vqaoT3pFHt^?SuC3w zx&L2D+~9o{O!&Pbms9_5wEuUz%+<3UDo)zvPS1jhJppPyKZn|vx4XjWy`cK{fs!Mj z`j3Y?hLoGW6v}@kl>Is=`2ouACn&$z@y@OtlwB_b`L_y5-7U^ zuXJ`JpzKbAvO5n-N}=p#K*f6m%I-NRyA@Ef&ic1Q*>}0h+4q3bk2d{ZQ2rM{`A>k7 z=}>mtq0UQ3UhVwPg|Z(HWq&i2+z(|}4dwR{RDIt;+5ZYGOnp|?rej9et*T&I${EaU5ZBQ}?s@C~1yskjaZ_k@; zzM$kPD7(o}b_=2GYM^p=p6v8KQ2LWl%jX%>mzw^C>ED|E8`Sbkxy9Lcgz_H<)jq`9 z^Q`?$YoBZF^R0cU>93pa&((jGX?zx^U8t+q)OY_C+Wbzw)y+jY)Lc|U)mm?izs-#y z3+D1t->FbC%k;UX2iLD@#Lv)geg&4P+I&$iy=v!1u1^)j3LZa{Pd+y0?7}IohGkIl z3RIlepqAl>P|NTOsAaVQYR==Py8TKrl;?|3vHz=cxhuQq&u~=Oyw(t3(g0Oo>g}$+ zY^eJ3q3SEP_GwW2kqWqumulMrW!vHo=X)?ztYe|%4ESFeL-3wX^`maPnqV>JTFj+T z^S4gAoePbc=HlN3C9_PQWBM~t_55sl+`pTyC;T4jgFAk+tKMoDfW2e{^m9(L7~fgn zHuYf>JAD_i^;zRFJ3eb1|Ck$JD)y3W*yQKJqOR?h-09+f2_+dbT;5So^Ku^4vKSAw z+^(_yhoR(oYp=KV)Vo}JcPRU_pw`1u({F2MNsi(K}oswe{B6wn4m-?084tiQ!< z=ie48-eFMw7edK(*6z)5?O9N=Ka~AwYrnie66>E1b${(q(-%Y8?{<%iHwbE*8e)3z{2_~VYghY$|89Fmw*;?EsG}G8 zZl*i#Dvv~U>YkqJi+?b3KRJHx&gT^)sadw;q1M^irZ0h-o3+Mm#&lw+eUNb^)YxhA z$LT)%>VFe_r2%2+J`~aeyr)ILdjWB zwe4!2)A&w^PNAcl{7#6;woe#7-^D6|nx{!{E-%HcwZ0A3_cK%-`#}frwU=4@q?@(xYWYUD|J`@fl-u$iVX?fjrR;oR4KO@vy z-<3Rkr}L$V`yh!iUSjA_Pe-#*OMZ(1d-v1^V|m(*?PMs~Wcn|rANGvv_s5dfneQMB zWq83)EbH8Nt<^sR`vvB+8A>`m>*g&7YAo|jUjij>KrPo=>p%H9H`a5Yw)F|7-wAa* z`2y-VP_WqTzsErBPm7`UKX*dygRWZQ-bd*9yu0UmCDdN3`3p|p1BO4RWcub8o&8VC zoWJ*y%X1M_-fN+j!DOg$%z|1U%Aw?WsBzRl<^Ke#&WIFq2_+zA!uUZ^+=pyDip zl2uT3dUke3&gW2ZHbcev3rbR7b#eMa#Tf(@ zXBbqRiBNI=4Hf4es5lQp#d!`&UWcmZ3#d3-pyK=i6=!gji*poIoD-qqoBs#q50(F2DF1a({+-@%{<}f>=Ro-ngYqwclCz-vuYvNP4CVhiRQ`9N z{6B&6{|?IkcPL4E)A{#;^4}B6|3WDL@lgH~q5S_1<^Ld*JPnoqEhzsFp#0mt<@~!r z`S*kJ-v`QnIFy_S<$n>B|5Z@_&qL*Z70Ul@DE|+k{Oh6QS1A9~mCmmZl;1!ozk{Lt zj)C$!14@da{3b!=n+D}~Ka}62P<~6G{N8|)kD>hhW7hv}{@w38ZnNWL(%Y`q0Z_G% zfEvpIN~k&52<4fy${q6tK(&v6S~laLwzVlx`|btCm9Ul7oZ{-pO^{*$2O zOw;}N!FwEK7Hu2{XJ{{(%QFL`>)oX@P*6U5h`ytsQKH| z+H~DgSSy1+mTK^L3ueSb=tp5vZ|H;}zUpl{*P;om# z#oY}`4z&JZ)_#Ju7g_t+)_%FQ-)!x-So<7nf6Uq!K;?PC`d3;1$JXy}5C5yE>+JMU zyC$!*3m3ZWIKIw}Jqv0s@}O!SYn*H>H&z?#jqzVO`z)wsmIt*A$HG!xT28B>q#i2A z&rmsHzIN@2P}u=?CH}IA{v1#C({8=V$8Nm&yktIaL(S_KrpJE2i*ZNY2hO*c>DWsK!SFJC(El_O z_4s4=>5s=){DaFi6-w@bs^urBYw1h3I6ePIw+@VkI(A%S`c+W&^P$cws!e})tJ}wS z`pNbG0VVN2yL`1!5_~_UhVl6G_g`g3hZrw;%Z|^KD27C}D1&M%d@l07qH+h{f5F7d zq)oQDP}^9c>7~XpVh_e4#n^Ee(*G`Wm{paff{pz>8bdrpNDV4bCVDKd7ftfe+(txnC{mb zJTGdXKKZ3KBVO=)FtCjmHFvS^fs%Kia(9h!dJa?!KfX`h%W89cN^S5LQgkmX^*NWb z!2C<#6pQh&wXHY3F;)|wi_5k++p&$_7gw}!HLZh^uc2yAjNL`8(fbOEor%397mhOD zOQF{J>!8ld?toek=Rhr^hpc}ARL? z6b#?rgBCk`CI732uJNv>ET|eUglfOc^tGmcZu$WUE_N|g>_t$q-!lCZ>)!|!?-!_e ze?r-1COW@tsJS}?%5DUd-3d^3=R?K64$AI!D7!gOc4bg@^-yzP*wWdb4`n|A%Km03 z``e-Ho`BkiEH}MRl8ZACDqk*?|8Y?MqoDlHgz}#XwGG#rK8I&DHG@w>^;bdFwF)ZU zM^N#;gNpYPRJ`p_ezD2UJ{8Ko6O{b`DEs}O><@vmzZ&Yi=MK~Ffy%o8D(`bpabAIn zvl=SSr%-X~p!~N$oi`=$JL9_M=mx{*!chH1Q2C0X;$IIH|2C-jGoj+ohl;-lD*p3O z{;xy%zX#>N$@Je%k4ts&(xBpXhKjd4RJ=h@@eYRap9FQCJv)u(YGFB){teW<#Tg6bcL7wKiBO+^UkCO1_AREDcX06*L&aMT74JQ$c%Q+r@Oe?ijf0AF8B`sU zp#1KD@+*V#dmKvMhVokj<+lgFD4+0!;hfi)fd^ z{)a%xnNa>$Lit|{e@d(f_Bnr{yGiS)ne?IyGd}cBpAr*`~O4VOoA__;&%O`R5Zh*6lX-&OO{* z42GJUQBZR=0c!5&8kZW^LCw~7sAECrp6=K(7|MPW)IM;6ajtQx^{+E-H+Jsj{02iU zvr)zgQ0wAcxP+IM%POea)I~LZV|7ikSo17a6|A>7M`XD?#V{B* z`dPIGi!~X$d1kx9d|UQ&H5S2O41e8k>OMp2Vt1}zf{$c|VnoeF%x*4TKd3pFY5F~; zKLaJNK+VnjrU$RzvdOcPp27FjU9#g7OS<-uopR`93l;9;)VFOi$k3t#7$d z_QyiWnbtlQYMWRNwf!Bvhtp4k8s|7DzpJ6-4k)|Fq5PhQvU?NC?jtDK1ZCG@PiNN~ z%5GmMyF;Pm6zf07`b(kwwpn{@w)5)%<<|#F4uG<|5Qg_RQ1Pci+0BKL3hRH#`agv7 zTW9UxS^Mu$eu;ZIzphYrheO3b5i0)KP{fD0u|R{|_ktmixH4ouTadL&?EVcGp7LO@XTKUMRZ- zP*Q3AtE_*|9M^vklpG7St`?Xc-j3|I;qtY;xO-pu|3vp({Ju>-7q2)H)vN0;)mL2| zxsA0Q81Idx-QRBb*DJAFXBeM>8si$MF>i&weGdNm{|+db1C{3ysO{!S>)+Klg8S5B z>Q$^Ni&bZ_V)i9w)c&iUHra;ub9oD4Shsm=YQLDj#KkYi*6PWQ8RYziK*=$t=bL_> z>3;p;XK3v?Jnh?xE#?gD{Fu?T4%*-4xED$`ncf&P>bJM2Szfzxf%YTXa zu7z98Z?B;)$0(?HlcDCX8fx9IHT_5H{}W119On8bLD}CA<^LFz{AvB0hP!Pi^-yQu z3Cez7DEmX8{+P1l~8t{K-ql_B|{E#c6m^CqoKC*iKb75@+*h(dm2jO4tI9l zp!{}&vO5&YE+0zfL&aMRWmf~W5Bb6L?NELl@|<4}D7hTUZYGr915kELq2j*|C0&nj zcKxC3hC|)!Im`57D8JjF{ANPQcTjf8M>@a#pzMx^vO670p0fTL>)&AgKU@EiM>+p< zq5LO7`QHI0Us`|n5w3qQRR7^navxOP-&=p;(XPK8l#GMwPdUb&pJzhp>BqWw`#{Az z2ukjP>YoR79{-x@HBj|!fU^G?N{-C8dq+@y7eU$G3T1Z}lx&2G_ZQSXr&hP<~6H{C3yN%426nw zG?Y95Ww#K@ZwZv$dr)?tLCL@ZXLk&gT?y2^sM}3n4s}NP7u3C)%T9FnTJD0<=Rx%^ zg7RMhmFEknJR6}T_ax`{ER^3;D8DzM{62&7`xVMB=49u08q~d=i=pn#Ooh6By3h0l z*8e$F+^?bHZiR{)KhnkR2^Dt_D7g_T?o=qhNvAl!=}>-;K-KdKl;27y>3OQN-wP_w z!BF-mK-pgiWq&1<)Ii1m70N#DG-uxl%6?xc`$M2)Hk5q@l>JLk@mE9HZ-TPl1|`Ri za`x+>?7xGu{}akS<8)`=3ra47+Rxu$dRn2ox0MMc;qxW?ew%Fe>(RQu|NlRY8CeNl z=g_HMyM{KkX*DU8t%{QhTV}gst;QkIdZYVznwRmb-Dlv(m!^74Xoz9_BWcg&`s7^^PtwF8%)35`lng@Y-@kW+Ml!b71qAq z+J7`X@hmqVyFHoBThd=ut^}90pb{HKzA-_xOWWx4z8 zT2sFz79FSZQa4}ULCKyaPWR`lsqaIYZS#_k?M`(zv95;N@wv{SJO9R7Q=fCoz1rpf z21@q7#_1!VWQ^&J_31YwmpA>JkxQ)3GJL|n1GIeS-@+Vuor^yoO5QL%tJLWOp|-1o zOb_or?YlYF5`QN>mHF3a2MgW4N;bYIj@FxAAMV&!WuYsVo?*!4<;zHG)4HrSpW$(= z-+3I9u6N@|nCR-h6Dr@uH@J261t|S9DA_W}*~i`J+E0O!BB)#wO`mG|d?^1#*1pWz zd*9^z_JQ&{()1HeA7}a%rr&P*9Mk>f7|pbf{B~&*%S+cg+iba3S*$v!+G1{w_d4@Z z9fv^47}L)+eS+yTO)ocnx#{;zcK**oNrUOVZgF})sQAN7A7T30rk9((-1Lu3|Ize= zZ*~5sK*^=1Pc?mo>7Sb3<2GmS&rez>juTB>17^P9uGvOlAH7YMPH{1YPjxx(gc?I* z%yjl$$4BnFG|#yfvl4r=&Ca{s`A&zDQ|@qcKLJXnn!dpF7ff$G&GmPI%H7BG;Pz8U zKEI}nbiPN6&l^U6#$PrPEpKg0V#&vm_i9EGJNlWRL6#SDZ@m3~kJa#@>9wW@YbqsQ zhPLT3J3bGrHeZxquWeB^)DtUMgT^eM_u-Vfv+}k2ig;=?|Fxl<7~K{;KKko1SsMy9e+gRK8zL?^NdWeo*5&)AWl? zf70|TA9VeTp#0x9y~RULKN!mY3e#^jeYxq+&3FBqq5M0QyL$^W9&`GGQ2w7m-E-*n zxNFZ^;Mx~J$%|0ubg!Ad0qP#kPp13pL^Ly!ntMrO*c0Ttb;Em+ZL*0r-_Kn;3~ln- zLB3iq^-?Wb57vE_5@z_k-`c8aQ`P>uwvHM{q6gbFF12awQ)~>V;aEZHe~Ijp7P`3^ zU>pI*@>1Wc6>gqBgwoeO<@B$h>fCI4`$bOg1tq(ievs*Bn|`C|87=wFfuU?$Hh-C# zzoyy(-(MZJ=@IW`ToQQ&rsIP8d{JGRw{4&AIH%DzsoF(d8~Jk>Pd$0)xx9QCX>A&> z+I)tWMC2pyzlD#J@|a$uKrg|6Ink3H8oy81lw9Q@!fvAI^PfG+Lg_&+JnBq{{yuQf6dL= zsZiTYA=ENI4@xexet!<58N9DtxYW(#B=enTv8u#C&cYQg=L9Gzg~~Yzs)lJ$>)dRp z{l{FW{ZbXwa*D3upY-Tw7)!0r8sbSBpz^1_?(%0t<Jl&0?)pjKF#lj5qRK7jGJrykL4`yw=UU1oo5KFIV{Su!$b?u6_qM z+HT$&m#+$HKHr3z-;bf@`#Y#P+X^Lr!vD@P4$h_bl3TWI-*>Tcpkfz5u*oOQB?o>5cQ*#IxL)R%g=3@m?=p8t)J&8EyLC`RAJdGuYOc zeS`TYeiAuDi`VVf}u+K6kaR zDYY8P%)e6c{ACe+EDWy0QGN41cXhoDB^_#`#vS$DTV<9v2fGnwI|E7{H+?zO*nWUo zU;czzUq*i6*4=SX`>O%#-T8A7lzti1x_twb{aw~y0d@Ym()1Z$y7#26s&oFgLHW1& z%K3MP@;?JA|3gsqJO#B3o`cF$1!cDy%C7y_&aM}f-5yYJ4~DWo8p{4ODEs@N;x2@; zUj$|UDwO@ZQ1+ig+4ua$+3y8qzdw}S;ZSxbLfOrMiaQ_5?kT9a%c1ORpzJ?`vhPvv z?6aZl2SL4uc?6W-sZf4nq5Lj~@_PX)Pc@WZ4V2#(P<~sW{Jd|SUow>6@lbw+P=4n@ z`CSd=HwDV?Zm4|aP<|Vs{CjY)j56bQWD7&kn>~6RIdDdTV{Xbj(@7CXU zgY!Qh%Ku6ze}7y0pQZbau``?ejj>AGF6LU!rB=f_s4;Da8e`{;Zp?$B=4uqw+)Xgf zg~8*@giUS?bD?A&)Yuk4#a#sz_dBRL{}U?iwVVI5u}7bC)!MkG5KpoID*s9tUZzm_ zlfHNP2f#dDn)e%_WX}fYGZ{+Wf$IO(^npLPSfin0oxj!TS3=3nQ1n5nN z9`=))!&{;D6N{icUxB*LSqJqV&@WK$0d@M>={=$P`$O693*|o?>b=?Hp{}S3q26o0 z2x?hf1C{rAs5)MSs;kv6uI^4y(hsWcL!k0L4D}wNi*S2~c&P29@b-sJcs_o^hQB_5Ry5sQ2RThpKxaRNdb~)%_z>-MPQJx{ra9 zQBZZ4K+W3(sC<*4Ebf5HcP~tbk3;?T_w!KCeOE)}`v59myX`JtH>iB)LajSjK*^0z z`R;|v_b^nxg-{ktpz^JNo!}a%-w-};>O|z2RY0FFhZszX&S+B~X5g zpzNB5oZU@uAo^3Lk7?%oE{F1)1m)MQd4jhu90>P=6)_3kf$%3NJt;Q9I~Y!Z(re?L z{bnftJ_)Y<1E@H4Q0)T~U7lf3`f*Up;Z&&Qa3<8uT?n-tCcsR1E9?emL;cR+e5mE{ zEYxz?3bmg73AG%CwM+;vhvT5+45;OBCDb@?hRS~zRQ~&*EayYzUj&uE6840vU=O$s zc843F^8X5ze^8RkKNKqe%~0#XT~P86RQ_d9%jNpr^9xixUMp8mGE_Yo zQ1$eMS#Tii3x~lz@OY?t3Zd$m3pF2)K-IGWs-9n=Bre(2(+#Sgeo*xcfvV>ysCrI; zs%H$8XE9VgS3}iv3+xYP!QJ4)upfK|s-ERg_52A{Pkf52=V+*UPJ@zjq2}j$sCsUL zs^@;FdKN;}^CDC|RZyO*q3Zb*s-6vS5BMwG9mb_5cmrU2sCs%q)l&#n&v{Vu^CVO~ z%b?^fsCvGFs^@#CdYW^cp?cDw>gfhmPZpHtzEJfX0#(nka4&c|v zsCufP>RAO!^;JRmkX4<1(p9RsQf=d<=6Ym%AW{j*#;_qSE&30 z;6d;JI0WXw!SH0L{AWSsp9huy38?%(Lgf#&cllGG^7n<>?+t>g<1nZ?3ZUvZ9m?Zu zs5&l&s^dC16i$VQz z%5pwb{zXvvE8(GV6&w!N!C`O%RQ_L~@()aR`45K5KM86crb9^?RQ?yB@>fIU{{Slg z=TMfLpz?2n${(AN;N`)#@Nn1z9tQV<%6~9a{%fJeKN%|j+feyGfs${b@`rejSou?+ z@^^vC-v`QaPpJF{K;_SaN5PZek+29J0WXEhe?3(GWl;HFhsvMQ$>r|?CH<51ZDXrRQ`mnE`JADfZiLP0QZ5%!=X^)&xguC1uFkcsQjNm<^L8+ zeu2uL!gtatZ#Gok{h=&|LghUgYTT#6li|7WBzOfp5#9uqcLr468mPPGJLl zB?m&~Jpn53qfmJlL0P;Am3IYH-Zk)4xE`JY8({eTBB;Ery1TqbK;=CFD(`Hl@je12 z&p_q}(L9vd(6zR6O~=ag;tqxF^V@~*X?rib zd&Kp&EyVY5+e8-BHj)Rm&5VWGh9*O8%jHnpST)qPSP!*r#`Cc^`^)7;<+UBEGWM`sB7P`P}jnft-Tz!N3Vt*V7;}+_f803OJ+e` zTjoLK84GnzHyJ8!In=dIHPp3Fy|u^pan~SOum|mVQ0IhWVJ|othW%k5^lI1_)?0gg zUw4k41$U!856XTl)VcCxsB`CXYp;enXRU`ihmGgCT-j$qokQk9ol}m5;rvkJDTg{X zR72U_?~mWz9SgEx9xv_JXG2LD)IG1CO;6mz^`}9#?*=6YS^J^Z zKGWJCv-aiIzRubkti8paE>2r0IS^_aIu>dhI@8)OgsSTX>wg7m+j`&nYpwq$>rc;i z+uQ&s*$>Kp1XTPos5ySd`d_yG|8zg=ey6QdlfToJ>F+NrSNvYCrYvKgajbE&u^d+N zQr(|h+eXv-yN`{->l%H4W_Gcrp2_?7il1tbhm~3Hc5#kM|$EHRPyb&sD-!R>U5wpjDJD=TZ0_IWWPi3j4AI z@QrMXvT4!GYY}bJKgsJ)jt=BZC1(;jNC|P0pTc(GfXS2^$l;fAtif20>bkB=O_eI;SUg^zKy3P?yzRVFV~C-3iDedS3-SD9Dz&JFh9lf~(vNiG8wfS_#_@G%;dNkL zXs!5T-1yeM;Heihh+;~vj* zNdEIgK4Zh@NISRt{r@SWmQmyK+*Q12d%v!v7%!y zBKF7yExgRPbu#wUB@u`9;r9Qx0oza2iUuy2tQ6JZ;kj~+X(Q_<^Kw666f z^|S?=C4 z-)POgp#}SpictyPgJalki5W_3;ia*^NYZ|lbs;o4!JAx?;$2;o>~&-R)rM_H+iLMS zZe98WHlwb>vF`YI0hC+?gX^Zhj`>UrZ;R|BSy%nv=gh8l>s*<|sI-{1qW`Q~)H16a z=kjD;;PRe$q0=j&n9NO^Q_rm@P_XLugjZo~696kl7n$}V#Ct%s72FLwIpP}1cRS8o=S1nVtSz1gk2 zY`#0S|C)H}=6tp!k-9aH?9-OJx@G5!U-zi^$huRB-(=nXw&HIK#@b6=oo6s6$;vC7 z-seiE?*@a*J1uBw$}}I{nS-i0_UpXARm5+4Xy5msn+Ns#;?F}-LgYLY6St{(=&rLi z)(emI!kfUoKVGScY;y`2q3_C$eT46?bBzd}s2Dl1UJf>i*tdwv7wQ)0b>rC4n&U<* zjg56Pw0D}f_trSCXI(sfY|ETqbYL58!#0}2HrkSPA&xod9^cH1-}sEUm*SzPouUbRS5BdF*_rqhp?Dhoj zDqc$zL&quh9XqwLZ}FxigxgmtUVVaB&wiu>`;*q}XIin}3GY)lW*)|Rv_EbA7*`kS zO=jKI+~!YD@Xq8lqNST_{XdCUj`Ws1Z>JdK-l?7aSqyoC{-HJ;bEzqXJT0jsZp@DD zFqGcHOQ(jW&L5}EOz@uKwKZHrGnQjaSYJk4t<|OfKjBp^UGq^C??trfw`1^YQ4kmD z8$QOzdcho_wB}wKwZsf{b1-dIf;Ve6*MvzHn{^`C7Wy{M`<8hd$U5V%6S;HTb`p2D z+fJ51ZBxrkuQR>$9@jq=YMYp8`ZCj3n7+yMt)>UJyUaJ)?t;%dH>Edmt|dQTlv~$L zTN$G&((f#edsJHuZTi2$+H(2dms)Gn7}e%8bFLfr94J`{m8-?QPVWR|yVmsZ zIF5I}7oAIe{_!cJPfM>4``GSP92ZJBF0hXc?^`C?vBAco*sLMp9F?si_br+CxqJ40 zZJ6-}gUhdfyYtFLzlK-mWR`VXo(b5>XBk-+5?NxN<{%?TnZM~nN@-%H$a@OB zrlqp(b&g>@=Q@&OOt3wCt~!QgiJj(U*b{E7qoMT1v1)r^ooqahV%-gHFY#~LF&@7P z^Bwu5i!~7{)*YrlWO`%l{+zQCyANaO&UVq6*unFiEQ?)(U*olgoiAoT;|(>TV&&g5#tW{XRdT(EXJnH?3z98{5ru1zb4LqbIm^= zTkRW);jg>~w6@~_$C6-MsAHVhkvT~&(!PLe{Glv+|NJelg_p-MuMPd-_OlYavshO~ zax6K4WvuJaxQW>Bs(n#*D$H;bG+k!S;a!zZ`1=8I0K@;m#=!6#D34wOiU-m+R zw~SYV>eum*`dd(Euszheh1VLtR*WH{{$Xj}u&puP?rUPbemN;#@46P@^Smx4JWs@V zUb})sFYQ6M56ygu>z9|A^ETumul{dQncz(^UH^A`CBZ9^u5+S-Gs69iZG3lnhpnf0 z@oP?t^p$PZC@&a4)HTuTT6Ze*KH6(vRLDC1FXrX+#`Q({Q(xu&C9l-Bt{(kApL6wX z%Fna9X?2|Et#LMu?I|%{3fpuGw(p>Sh(fvuIR=Jc z-*=oo1WNa6oSq1^{-r>*UuEr6t$nWPt=@I}x%N;p#Ps2&2ao$@Z%3X7NcXp^i~*eU zZ%y*D>RNfd)+BqGRc+Y6wD!`A(!91@m&P#$ZFBOGsHSH2if`wRANoED9VpUP= z|C?l6o5tj~)!JC3hq}gjUAcyDUDSeeB)k8GkL)j9?Z$R1l%DyX(|3oeV_(xxH@(R8 z;Mgmp#vaxOw(!stDicbq{Bl?nKy=3AgC=T%=;^^Eusx8!` zh1Y}eC9@0?9(4N)?+f0?;5A9Pd~WrH8LInnkv0Kg@Q~qNuqS^bO$~>d7;E zCEPK zs`u+4XRs}#{U9|Y6jS4_AlX`$D;OL;g$sN zLS9}+SC{^uz^l&c)c?=&s*$ea#hSEme`8w=>rPecaEwO3B<`;fKc4t81??hogp&Ck zkDRs~Q`+o&tPO9kJm1RkuLH-w)*KgGX{=qM2d*8Y`=S`lc*S&b`+i*~B(pC2=SR7Jx$%sKlBZ1fk6XcOfw9!5*p=AS`gY-WzIjdts;?=($>x`jP2>5P zzwh`>IF_Y&ZMkQi#5Pct?B;nP zl&muSBhxoQtvf%P9$f!QYa-XbupU_dr2FEpf0MfGIQPhtAY$Yt! zEm>x9%h|W=^4gMEof5sbdDZ!`*am}b;rFz3Y+*fX+&((Bdh{1>kBPM7zhD2;o(X0toG@E2eKuK6(6Vak>l`5^M*rLUX zN)=nG)KbfRmnv4YTZ@&p)UK7bv{Jjaw9>m&S4GA8ygug#IZKFl@8@~`_+Hod=DOZ_ zy?4%>IdkUB%$YOiN8*`-wuakePRq7HD{t4b1a%H-HMhq;Si@rW!PriwORuZ>Q2s7A zevkJNmiuezzcQ{BiBoUHYctk+`cu}+GuG=Pj(%RY%wZaRgY2(=MS^NW9WJRa%sUpV#)|f(IF5=kj*T~Az0D4_nYgQoyMXoQvc8@5<0q_VTgx>X?S2Jqe4(7TwDS@3 ztBfP&ay(|5uhdL>=04RW5agvHd6iTl2YXP9VDb1B|oOnOeJ&blqn# zw}aAF80oTq*0BGsuB#0RDgl+q`dVF|OI_y)D@(Xu*XQYVoqak7HJICzx^4-J*$<ld%(ckAInUuZo=bgBlYQkn zDk^Q4r{fqm#x9!>_p8&?SBY~g@nxK8jccoSsQQSLaqt2)KRlc9!kqCjr@8;_=W(vz zLH{S)HTKsUW=oo)ml^%+@>$cEyO>71lKBh9!mJm{OHh5RYkP3IdXRFFd(BCdwRZhb zIh8p~o~@vs#rD&B*}DtaUzEK{_IZ*pBR|%7G5Mj~ctJ{qP`#E0-5&lIs9JtY9pQeF z+{20q*V3h*w5)4e#d~1v+g$c-Ci|w{FWN8lMDFh)+NM0KYsNTobWe z%EP+M#<#gr*zVkb^o#nvO55`3T!)+Uc*eE5*kZ-4hf`3o7p$J?J}Ub5iV zdixtB&iL3QJxQg9r*O_??u%{DCBL)is}nhQPUo0f+gs)poJY{WYB?S}i-r%Q9{D|o zIhGLfS@fqd;Zl~AnRQ*;l{3_p^s!ohD*J2PzcA0!wv|(oJ9o8&o}bYp!tam_N)t^*@7XOXB}`x4YqRFp1d zAm>xA`(;^}>xdBfBaZewSxh|Ilwqz=EJ=`xa=M&62wsE%F7@fkLL%do=-CRt)m3~p` z{45X8q;foDUPzAp$iV~^I7I%}>UBYU1Bx2^C;svh+JXF*JcR7p@|dv5G%bzs^^Ce= zOTSo%&{+5 zYkVuzO3zBQEGv!YQ&wqn)b@V8Zy7Ptblyb2hGcu^K>54M_+4uJz7a}3ZZm#cuS?TO z?}Xo$>rz>6{z)2g3{o>b5j3ta1=v{Q{fggDl)$;Q93Ugm8HY7_FxdfK>m zHsjtoG2!~Scdpi#xo&6Ni|WknkRNN>! z+lqE?mp+4Io62!q!10|U{V&H-?%@>vMsFYOtk-9(`!G0MsMyl%lg`o7!^<J+HCu4N(5-pzP!0#_#8i-!B`#Up0Qe zW&Hlc_`T>W`ugjktbdO2`w}Saf4A}5TJD|X!#uVNCm9RLa`}_=sI?lKGGoiWo^-8c zFY*1xHpRCa{x`<Z>K~Q6ud>WBn0H&*=ApU)9qZg7UYdQQy{OQ2wgn|72Uu zzt!7Cv$1}+q-ShPeN9j63@Crwjo-)fFZbq*XHq?}&r+oSPEWU+b4-{#0ejB!!-B-^wu zGq-75lAxB*56`C`o}eR14ex!j?|+;nBKztA@i@x)2FF)&p9e3w2AZ5=9p!+ zMG0zAB%3*evzbH5VIR+BAGavmQ9I9yJ&h;+lIoQC7;|beI!u7EX zZwUrga$a_+k;fC%zfjAW>~n=(RS@Q2j^57lc*ca*<=Q&i1DR% z?B@}dOxRq)X0r|IWpp0b&M6!N-oo_o)u60JYUHT|wc>jTYWOU+t6gUkC(OOrPmM?4 z7qgzy(zu&+`n7cU9uocq6f%B`{~KyIev3b|gFZrj%NUGp#)K_qyh?uUaN#XMVLZqMx3%-<$Y7B0`XlfM28 zn=$ugtRrZqa(DC*n)FU#h04NjT~q$h35nx>8I+fJROPGw$X zHS;1XxW19&vxxGZ&vB43u*{2?=XA+$-%pu0ewk|~$?x2Fp52f+z^O6e+MFVD4=Kz& z%nmSbO#6|xYRj>!9P%=ke9WYM&M?|y_s^J%{dt1Q+C-Yt2XLOWhUx1isZ+TQSwf#Z zPitTFNsI?nm~$p`6-wT__!a4(Rx_PwqW0VQD(6KBBVLGki&WXV z{HLVMx1#LLbem3fC#_D!+e|;Uw=2vR-UBFLk`lJ zt@bkBoEzqxNSo1~X?cNd{CCSI+n)9=NfP~(JR8G(49V-i8>Zds)33X&*OU7C6Z$?a z&Db!h3&HqAbr%YrOI_f%_-0gQ{1)Gf3gx%fM@-k2$AocAV%C#*)B$VSwwd%j6UJ0B zhMCPV8o#eTSMFcfh{rj9g)zpM#`9Ct6IH*teIMfsOPFmj^AD^u*CXlE-<_wg=VzS< zkaLTXZrPY?NoN$8u}f_8*}5VWt){ZyW2N(BqI9`kCf9BHv=*P`?*75S$>T=f2XB{_b zWsz0}>8v81g`_c$G~|7Ka}LrvZZYpwHZwPv!nMlKR*tJY*Uf&)`(f5_Th(;l<(RIr z$%oS@7j6A;l4@t&mr&t(tS{G~5&b+aVP9C2sB$HI%QX62-WepmJZ}}VES`25O^iv$ z#`no4d$z{r_=I>)%7ZvlBgD1Dvu#dPn>oL%<@_S&78wU?&tg2n zvDh(Hd!Nuem&vx>>l4*ArzNU@Z2J_uIwi_nBkehv_H5^R+`8Pxb$8!z8|UyY zj-fuugIreoSwX#BOq-Rt`nkq+ieBfqk8p9K8Y`f` z*YZYNvHq4aV?Aq_EpL{}i%wM;wEuM4e;Vyyt_hQA?@rpg;|1EF^qbO7*zS108Rppu z`q7wv)2w6KGv&vhUDNx{L^ZOTciQ$Os?78C;~Vo^@<#gBFzx*z@|%=p&Ua8Z+2_o{ zM0Gc+GSZ3pt+kifIFmgKq>Z6v-^@IllzK_H)V~pLqDt6D8<6d2`Qv>jOPKY!loOtJ z)5eARwYAf8{2682nk2P`X^yq6GfvU~OBb?b7L zFCp$i(mC;dK4lf2q6QBps`pWI9{cbIyZQs;bVo$yKp1b)rmf5M^N8MdW1bhei0?Df zFQ3nT=QH@Ia(!WPiNPD5OhTBe?sZNV7;JukeYE59VTFIQqGUnJ8 zGY>bHxmeq;XVWJosFhLX3nF}Dnz}XVJMSD5|LuwD+*+RRyTCXmlz}zO)|04uD3enu zlU0yw(nGfI{G!5OP2SRr3{$+AnmCW(zc~eQK?z8Rg%$W+8^Xw z1`j2wtevDQ|KE&~2m)FHp6E<|~@FfuJRT+$3r zk6G8oIcO{Wc$Q&3s06d=9{T$CC3v{CSrC?1~xc3i7s;`n`~LU>&=;zsdLy zwVL^%ep|zA%XuG@ck8ryoc*MkGh5|E?aUYO&Ac$}G{n8P0M8o9`+goL=ZRVRd16Ap zKi;>VY)mkIy<+73+1ct@%H|0Bd5Ch@PyWZhtwdRm@4L}g%Q5tKB&si=z=cNHvcJ|a zZT#wseGb9wtIhcs^Ico*9lI=}-J9nFo-xRZF6Fx{%Tz|##rbzR=ie1-sc#L}J*zqY zu3~((QrXfISEbW--eKIZxvDnPvCknQGFQ|%DPg2IM&uM+Y;XMqaRG6-_zgm;dpy2=S*5%q99{G~`q|M!tw)PAh^+>(MyTxc!(n)i0Eyny5+f5GW-{MGMyOKX^Jgu%u zJu=2p2rfWOH_5pcQM=CXjdBvn;qgiu`HkJi;r2Z=bdv~9NU}F zHnedF>$-lKsCJ_^Nl)I%qW(+YZVlI-)u7LpI_7pJ6Yb-doPZ=5sev{W#hsGfS2v3>z*u&tQiGRI6DJ8`Yywq<-9 zhI7q)&NXv52kGY<%X5qLllprAa=nqn{A{B7R)3=EmTi2&p}r7h&Xza<`qFax(tX5{ zXAEQFNj;korpAbJH#x`-T zM(Z<%64j-Dr!7cYa_=UTuKboTTRQJHhiTt|Q)$Z?jK|k;-Es=$y!L-oht0VXef-OJ zz1~h#7ofCD*{;m_NOCoxltY`)L-u3zRNh`y+llNy|+-Lc>!Y3S0>pC{-tCXj6l`N9Z5S z=fsbX z5>>6NC-s=T1TA6ObK3M**86SRwi&xB-qBJ#L+Ad4I)dUkziG#hW%7GJib%ThoB?6+ zjCEteojjY$IM+cscG8LWEQv|WCeKu7$vwfvdb*($<(}qH=T3L1GHqL2v&H!J^>;D#cM;oMs2#6vyF)!a!=YMbds_MM44x&-cKUR6I{lb@e?sPl zrEO~8u#6hdMbl?Uzv54DD6fNY>}66$oQti$Q+Upb__HD$FXGF$53I{=y#M|%XgtRZ(o7_9H zE{nZ?thcj??JLN)Olwe=q?O9M$CSI=_f6uM%Uq9jx!JzP$^4<*H)Z zSeA|NpS5nu^DE{&^!U6L?UJ zHQZdsthW<6!^lV93l8m>xwI{OmljE%e45aj|1&K4x6bX4?~9ZZ*Nt4qxXyE^HK<4O zrk!8(?*>`JwY<@9FIT*07@g!@3C8NuKPawi3${6w50!kD^t8D$J$`IBT`qB+LT-sC zeJktBb#Y9%e0!Je$sB?78}dz+MDiWa_N?jI9L#aN!FffyFJ;V6a-8Go$ISOCt@FwL z^Bw8|)G6E6#@Eb|Tf+2p=#PAiaURjnd9FO#AsUl)HYBJG(Yais@=cu(?_&jc|KGDv zS>xE&*||45kMT|l?RgRH+35eNL*4lf^)r-nIcZtykTu+_57N(OUg%JtLk$w2-{POT z$f1JrTh7-}`h*C50_763%-A>U`ug(<`g5_f13ZuAG46l9G+Vty8eJi~`aXGligV~{ z(p?hfo)vNATcI;wkg|}mgnqs*xXhtE$Z-Yvkg*u+NqaQU*Vg6c{*2dC?O08?2fKBS z+Df@@pj_8bb}K2{Wt8n=%5VYYJC|~tMLC*nKX!hEX@LC>^$co|{8+|H)^Kfn=&|%s zvOkPlaj!C9%*A{3Z@Nw#yV`ansU2ai!KwEyw!N6+EAJng-*b@d zsUr?`3+k8c$#WN^tv@3|Ta8&ZF|N|q`(n96b<4U^SICp3H6AAIlJw%4x3Z>XJCk{k zDC61)@40hMUqt@rbKNZ8xsW{dRXWr=%on6xNxHl2Y8T%PIxjqvvAW!^nG>^IJ7-26 zoHO~pE%OWWI0wkLL~V>#Rq~>p`)kR=?Y#4GJY2tys8MqQUu50e*pDwL8~2>n(-u|-PQ2GI z`{)lk)PEs$73peicsA$InK9wEWqg|?%6-rXbN6A|P>5&s$z!5tmVQrUHTR`yUn|(o zQns^*<2&EObv6C+^jqk=CR`8LuAQN-rG07lTd2e4y?x1#tHz-&M)i^(+0Ov^VVl-) z?Oks2u#h}h-&3~ze5U$&$f;iBKFEb(?hnX4FplRe?uSh`?a;U1i85^+vr1!KY zo=eYmsqJTULyvP`5%h;Yd?ffL(<>0;weTCewIbojv@j2@8$O`ok zW5SH+a>`*P<&Z`>tfCxN)5or%9M)>(>Al~fZbyY)_D}BrQn#&P+BZh2Yw}%Ktv{9V zip(iee;4{{$=B`U^;oN`4>;7HQ0hK8UX&$uPUdI*G2!&-^!M5_>fnjXsq-=MdGCwg zGn<$jkaVTLWqo;fG$ve|=aung?Dn*8)NsB@rS8i+tMcBqeDheVPY*iO6{t|sm41ok zE=!nRkLB6A*m*u#w~u(|Jj8fL+gJL-=$)Le8rYvNPgph~?lRu1A+D3S_6c!qs~l=o zf?)PZO$UZbb;!wlL zE9oVhb8YgSgQS>c+Wnh|Wo#sM;tjnFgDnpAN7N!|ESaX3cz8}P#B;xasmi)cf1itI zf|(D~=Fld5lUwpL_zj2J+Uih4=KfLc)-YS{EZVnCofC;uXLFzFOxoe;9J95w!xgm4 z>F4Qra<)0t&EIyY%mc^E$r@%`&G#utXBp`%CY=SOBgfc!Z%Xpx|E@#bgR0DQ*p4;K z93RA98?fS297ohG>m@T^K_BU)FLcl!+bO&FDAx{_I9fXN-E-(iXVP!VeQepj`W|y0 zXmou0tY-<+w{JcpU|x$U6(mj#a zjUf3OUyo;DR)zKVO~=38$hxyF>+XJ@@d#>0@~71wyq&bui;o}oK}(ok2QB|UgK?dr z?KiLF8hu>B46SY&&;D7qdE<{9>KcwuGZJclLB=fn$vZwmoBT83Pr|Cm(`0|mlY~qB z^a<(Go1X9|@%koNUm}i95-#!G6VjzOKH*Q|75*FX;)qvpkoSAh$^NP)StjY5f0Hej zW%3%8f=$j-jl7{@b4o$uf^8T{?eOf*w{X*9NwDf}~Nk8Sb z*!1OBzWhf`6aKO%iPthkwtW0gehspn&PlfAOVi`pOHSNRIUW<&G2^>W62EFfekMCU zlCFP}^(DuRlY~qBla*o5B=IGkl-py=&sw)+x%^4k=tV6p zgY%92tv0yPV7|d3gB1pEGFWf0*KaZ^ndre1=gz=>``WbQ+8^_&3^={QcVCQwEz2-e<7R;4KEL3?4D)Gq}k}x4`(l z!{E6FHyT`TaJ9iygUJT}W$aJX;NJ}X!Qf8~b{ot!wp(a0-(ZQsfWcaW4F(@Fc)77% zvs^FE-@f%S&xSMB`^w7r-|^J8l6@7I`TfNed-$w^_N$M*e?;w4-{8cYAZ2A z9=EGFu!9wM6(2ZSQ5v|OE&fxTe?r*S+}&FXE-<(JaNObI!w3A=^FQjUy?eJ+Rqd$Q zceprj-w|I~afPa~?e!n6C_b={oq0%$dDzeYH6C}Efbs*(+A3MaQ&LfW-S(;j#r{BP z`4KKf;|^977ju4!J9wb1yb|Y$J9wx#a6rAQ_Li3KX9h9up#Nx~Qtj1vK<$b5+YbVz>LVTMG*>=YIm-vgP!^_5R|$rAH2yyWN*<-@4UJ_(i10!7e{iTvZxS zM{Iiyw^8jaJm8ljLQ?8xwfDeYEyj10&RYo40#tsS&Pz$D^hjx-_{cH-@8-RGDYC=+ z$V0`UN)=^~SR}bp`&q`WAFbrazCcNF1?6!Usg+Z3K1%yVwfCUzyn4*N`~3dBl6Ahf zY@Hdm+ z71ygv;_}P)`HWxGwVO9~77~l@r6{{eTQV}Mzu{U zh+f^)>M2GI_LLqj=6_M%#vu!6iS1F#&1G8yfr`@oM+3!`tp9x6rNvTNa!aYCf$|Ee z^M~UuBt){XM?E5Wl4i2i=c{0={Qu_1bE$8YH7+r>WKHFEm0Mg^Y;JvyY<=6&ii+YR z0c)&WNo|twehK#!ANCh;eAvQzS>ICXJ1pxNt@r|;`lGg1>49sul^;Dq&Az}Ai#A66 zC6fB3+EIGMr4J)PQP4*YQOtKz7RO2t6kkxjt^7!!qP$EUG*j4BdPA{VVIG@;avBo9SFYlai`I|8d^+Ms-lTWNB_`^-}JN?R+Rc?7@E)`ba zteF8?qiH?mmKdt{c)GI2)=D#)dPZ9k@R{ZCfZ8s_x{YF#gG2wW_p=wq8ecR{6*M0v#yS~({f&Wn|7LA@*IF)CVR zd0Fu`dUm}OD%2)P#R%VZfEsb+yrTyX7FTEog?VLd>EXk4=lP{as42_LTDjF<$~p4l z?HBU3ZV5BepixjeH(FBFLv|ZI)_4UPKQ0f)U2?Rz;`)N(3azppIZ&)MR4V-KQ+AhD z1di@2+p3=zzi9r}`n~(i;b3R9mvf_P}ob8Q3zAYAa0oAs2Um1;Z;xX8{{nCrJ=bLAJbF}jRo!-|eR?FY| zEn9bQyQuj1aKP=>mYxtVPhaVT@Ph4I3t2Q-c&@gJTl@EfWOfA{T<6nFk9&?@7XY zP7)q6!j0<#Gdrr=2=Um^!)AKCc}~Q7>8eTJa-qFBNe%hVS{Jg(Dj`)sgtL_?Sdm)D-M(}w6?5& z)(!59jL=E*E9cY8j#M7?`^zf=#Xe)ObkYR859sBSee!%p|BV{uDT+xG2` z$qeN_Svifb&)V*bE-&>@T#5Bu=>N^1d7RC4C;m3W|Cj%VlmKI(AEJ`illT^dQm4;m zCIFuY??K)8M%aNm@trV?+VR4*bGT;3^9c)8G?#P+lQiSmOUuYnJvOnfukvXH#t-LMF? z;=QmMHRFS@2{qyeAdl;*L3|Fp3Ju}?@MAQBckr38RY<+T``YjZWXIRSkB}2T2J7fE zQt%~wTI^5Ag&&1=eDW<1-w4~08!vo&g;M!EbEfzlTnfs?yI?lT!MouVC=2g}RVV`= zgpDW-FMI*j;d|k~Py=42aqkQ@C_>-8y2EoycbrX5MKBg zisD;gKN`aer>*5G@Xtwn51efx2fhzpbqe1q#E-4x8^{?H0lpTlIF&NQr@=c=F1`-F zjtcODFmXL~MdH8$RDu^ihWz+e_%^D>N8v@MQRWf{ejYXBYv41e72gFvM(uc}SXCD4 z!VB+1J@`gAg8K2oYftA`-~;eEG>Y$rAEWrckj@!Oor0YB40sDl!MDP{qjU-1z}zm% z!Z*Pva^r<7vZ!zP99V)1@xmV?FTMxPJX5JMyc6z2Rd^o^p<2A~UDSjhg^5BIDo7rEy18ziR_-t5;0{B6gmBX>d2jGuUExreS zf*SDZ9O?jS!WY0>P%FL${s?v8L$Dup;e`v%r5y1oa4YJ?=fYc2KfVV31V!*YaM>26 zhVd@wLt}X1v&ivR>IU5IrVilq;Iggs4|o^6`8?V!J_vt?O7MN~J>`&Dj-4DYyl_8q<9+a#C?6k&GxMpRct8A4J|o zQ5Y}mLcMsQ^9r{2UwkhZW}#Gk4lG2;c;RnQ5I+P}5&6g4VH)z|g^g$w-vYbP2%i4} zR}G>ed<3d1`HhclrzHq!cLTh?}h^?13w7IP#Ru&*>4eE`1C%?3*QC9s1ZK^M^Qaqm~?>k@hLD9 zMe*6tgNE_KJtef0w`mu!8pY#lU=teqCuIe@P!u16185i@fnz9wk1r)(XaJuE-6)LD zg+-_r?}Im^5WWUBp)R~I@oKisb{+6RREKYaFQX>>Al!Nl?F*j^uSf0pR`?gxiBB%0 ze?Z-M54;KW;Dhk{s1M%>7ak^`_*D26q~0M9@C7tVcnJRG2<1h16s|1ic;nOH8b8Mv zp9S-g4=)@*?Ra4nwc_p9avabwz7e*g2wvD&L0gV;++iom!*|1eQ%)!-^ZJ z&!iK8b*K^F0NYSKUikLS_)*4PFgZv+j8BDGD2UI29#n-df&t{mSHlKWf)|ERA6|In z=UM*)>L;v58TcC5gwpU~_z5b+t6QieC@B6`&WET9FT4Xaw(z6(yhowCN;;n}DNp9>G561)%o>`u-p zW26a(k-|rz<1X^_5$y$Lpb>l)%tu3b4=h82_yDX!{rCp>9n^>KfW4>(FTCn*%7paG zU=6Cm3+LBUfADEA2espckD(EK8w{Z#yl~!^Dd&%g54WHKd@d|Pd3YbZ8M*O6_ylUe zw?X??IA(a^G1Q8$kucPO7ru(R@WXI-1KY+I!fR0&AAt9w0emC;1B&1W;koxxj(9h` z1&!fr;C1)WKl}?1n~)1H?EWhG#|sm_M)!?(z&*&1FNC+CYJ3fBK{a?`530ip|A89t zBhd8#+rSI2M=khj7;K_!@xs5MZu~IZ`5?y$?|~1YFuoan^I?uJz7-BNQ|6!0Z$R~R z@{G5`l_(FN21g%}(+|&sKg#$D72?xi4l2Os!XlK1_rYr9#tWZ7P51%0wuL-P9GLJJ zZA9Y0-=j`^@EiOeyr>)B4qrn(_G!)%KqT1;VWoF!eRX5 z?3-<-DuYX(pbg+%@a6ANwsA969~?q;_z`Gt=UCvKFdbFnGhrSI;0s_0D#QC>E%M>( zVJj-ax5FOf!S}%-l#ds#{w`&TPlxxT_$f106CC&+^%F0Qq7M8ROzxnp@To8hwcv9l z3=QLj<^MsNct4!>pX@{YOl60aC>bAsKSVBkH+&Ce;78&5?~^V*6V{;|ys!o3;)M%4 z$v-|7UXBX!MesJ{!`H%}p)!0Q97X}WaLNxTLwr2+q8hyLX;hEzf^(jxp5T+=cGQZ` zgV&-Cd;mU(y7A5MWz>uBg&(4R{209CS=ub#0~i00I)G1wyU{4V5GFoH-#vAvN`bk^ ziTA-fP&U2}zK(M7gD}0DeaCCWd+_WsegC}Ohayb2Fyj-c;QW`9$y2SP%U1# z;wO|RJ`FyKhVU)03k~8!Z~*n=g_rcuMyAsiVD3wl6}|}m2Xf&%;X5b;pY>C=i?Z=S z7(uys`^%IQD!>anQ4wDFJLJPhVRA2Z1~0q<1@J|%83pme=TRMg0A~N3ZQygD2MtJk z7(iisHEck=c%k|Qb=ywegD#YT7ru$|@k4NWA9=&uVH5JcffV1 z7M}rkq98sWUW2OeW$+H<$L9=iPJEp_B#<{4|3}IfFWic<@wu=9<>CXd_6^D$FWmPh z$`|j0--@sd-vN735MTYKQa?Z~_%8T2)Q%s9FaL%8mGHkZ|A~6=?SJF^Ai@tp?_1Po zyzm|r!8gDkq7i&IbPv-f;Dz@fdm?e*pHVV?2s-}Ge&L1rC=V|zL2i5y#{U=F!wZih zKfVfn0|oJ|@L&HR&v^R?>7jOf5qugA;JcvbpR`T9@V6+6AAq5^smBiL7>s|1GRF(O zC>37?uZprhz61tQKE4hNTg?~iN_z2wa9_JW*77U_J zyzm7S!iV60p+5WwJoG+gjxT{tXaq0pM`L*5wWE}C66Z?zE=t9ZLiY#svv}bVl!^Dl z=THv58>W3o`QwGQkJw*)JiG-3@injsRpEv2%WsJ@M*oaj@MZ8_)P^60*&ovn;)OZ? zl3_CE9q2_-dS1vjH6yc-sx2D}$mp*pV|OVNcX#9 z^K2>&?|>eZiTA?iQ8qpVKSsHDHQ%OQL}mCsID~w7;m!rr4SX$ZM~!%)GljZ=7y3~< zUib*=!neR_3n_oR9d1Hld=7jQsbuln@~DlXkTnoS$N?&C>QUB z*P?uU0EUqVFMKVP{NwxKtR)-^yc6Dpg7|v)5~{=JF14wDp$5EKW>fo76W#}3LM`}y z7<6%5@U`$U)Q4|{e@7YfIKFVta>^E82;D0wqxtj+@Bp&oOJEQwd@XE7qr`88-Dm{g z0|(I%Uig*OwABS1Q`m_L@ZGQ<<>3e6D01V~8je58#=C?l6YqvsA{X8ZUtEV@$gze) zC1Z+i6?CIy@f#>Bl!f=gS5XeWA5P7pEbw-ijtcP2kOw?fCtjF> z+VL)!jau<;cqMAYm%$H^GnI7CvZ-s33tt9bK$-Xu+?~yK@rAJeZ1xu~bZ??=VZ87PG>R{R-@AZ*WI5v* z7)B-d0r)=h;>X~E&yat7Dm)7n;BzDl<>8B90J-ti@G+E$Z-Xx(7rqyM{vx)ul5r0F zCMv;)h}U#cT`jfN3ZXp8<1`8!tR|8Sz$gzJSeWY!!VV>_Sm|2o9iO zd<2f62tIx{@9Cofd>Y(>!uVWRgnIElSdBvX8u&2k!neTZPzOE)2T&V60&RQf$M8_83pPWT$C z!}r4(m(wTUQ(z|Q#|w|3lyv$KSc{x^;Wtnw-dki-ccW~4Jsd=Syl@ni;MJA1UF5|J zE3cx?ck6|@1>manec8e#{ypu6Zdhv)^beYo5+D5f~W2$U3@0|3QET}z+a;* zd_P=#fVPQGg*T#Hd=TD`3h+&EX)$ddp8<1GGd>^sP$Rw!)}VU4aQ8vV2VV%UE@Rzw zjB{W;YQ{Ih@1aI~C+tJ@_P2&=V8t<_!M|1^5e7N?IC>r)Fb4(k!pBexz7;0l1Z?=kwf^^^&$LAm%k*n)EKZ7_tg@VzjCGVsH2#&x8FcfccP1YZps(Gb1~ zcA`PNaC;SHbQ=2$ueb%jf%XM&Ms|D+Y(fg(0=v-Y8MH4rfJX2UIEIGs@wc*lG>A`w zIjA3>3yV-6-Un|+J@^{fgu3x9@Hy0p55WP{j*q}kP%EC7iPUD)Lw?-wO4Nz>!h29w z7VTHUPzGN3F3QJ`!b@(Wec}sYC2GT0!Ftq!Z-oDW8t@_b50r8y;{iDBcAIkH?eI2~ zj!&tjZhwjG;$5)3j(p<%uogAr>m>{|;@jcRP%XX>e(p~4wvqM%8&NL489s}$@ZInO z)PzsIi}4<6!MovJ)P`?>MfL0#z7c-y9`be;^%Hg@7hd@OeH;tC`YLS}_2FGG8};CY z$K-c5WdiR->G($YcT|HPf%i31UJ?$6Q7?WJCf`rJ#HYY4)P)z`i-sjWobmu^o=q9T zRVW3Y4zGTQzIYSch4m{W0)wa-Un@k7_*U4B>hV2r5Y^&`p!x>u;O)?bs_^M>J1W8F z!E)q3hxz~)w2~Iy1+!5n-VLurt#~hd3Z|s_^P@ z+8y#se3*ku@WT617rqI8?g{$#E%XWSF%*w)gD;~o;`G9|P!vB5``Sp0I8o?(l4FDy zejb(KYvAGUa6Iq7r zVbXt4=J-^22WrIE!GEG2{1{CBPx62l=Aa?Gum)wGM|{|d((!Gu2f6UQa0sQ~g}c8` z9`J?m1LVWUcQW2a0en9^_yf|#m%trQQ-|?+(2H8}!jDitehhAZhWaXT;M^{b5k3uG z_bhqX#<7D=7ycRf z@k6lY&#a3Veizl_JK+1M5kCqOhB)`(9q?Hc#`nSq>ctC}{*}H9?}C>gl~3ObSN@H1 zz-Pidl!GsTB`6Co{0%C@55TkEqE6s*;1k27iEo1;G>q?s5fOeE#{ZrAffxEk7gGOW z9V)>$z&7N?cfdYWh!@^ELOK`GN5NJ!fNzIAD2(reL#P)&0`31KA9yFsKwbDun1?#> z!jDkK#qI~dEJx-P2 z3xvpv_rqFLh_8pOr~og#YDS#u#FxMzYR3zAC&sBUycbrXC|>wLQXJp!WnI{f^6|n= zvx$Ru!$LHS_rfX^!3SX@8o)QhP87xqZOL(5_tVcn7b?Q1LpSo^h5PwGdWP|SSc@X~ zdf18v@a^zr6vp?#w@@#B7^WDfu8dP%cn^F7 z4d5d%A&vC$W$+a=iVwrbR#Eh0llU){v%z!wXQGgu_ad^;wQ1 zydS0En_zA_aq#)jhg$H$pP(Up7!IRByzqgwaeNz^bzuj}!gs+i%D@XRKZSk7`(U*Q z-v~cI&G<3cu#Wx3H^CpEUVIn)YX*6{f^EU`PNQDo^I<6}z?Z>i&>+4G{s#5o2jGX9 z&~Dq;WOYDQ4zil-nAi4mEr5*nlq`N_)M6GTJgf0P#7PC zZ*GiJ{wq0-(0&$e0Plq9$cxW}`KSc$6Q6Y9h_!{<;tz8k)dTJeMM6V!}XTd4o25$}RIs2=ZzMW`08>f@i0CYzmGlvrlB@`I?P2a_&n%CP52U6gBtL)um#oOTVV**;CtX-P&IxCPTwA< z0(d)IgUayf@Ol4ABOiHraZ6Vya2mUAwC2LPys#y$50+V{s?sjx$$Xm8_LG#!o4UH z?}ODS9bW^RkPF`eyHE-~1P724AAz4BJ3hXg^pV1+!5lPtHR;2>Xaw(rH=`kZ4SX04 z;#*)B>c@xR>!=SOfn%r#AMYo9)QwMrIj9q#3-_XSybo5RR(uU?Le2OV*o7MLAvl2Q z@e%k5s>R1&OPxePd>YI_Rrp+3g#36PtVSjH8u%#k;#=Tzs1P55ucHEd1dgFRe0)V5 z|0gbGj!%Qtm6T5z>A)s5f)~DwQVvre;5#S{pAz8znmbC}6Av>{89p0chKlfoumwf& zt?W$)2%il*Q5Y}mL%n$6C>q8K8?K|yl+)+Kc9f0pgs-A>d_Vl| z4Xo=Y9QL6Qz8{XDF8nB*`#IVsJ_T+-ZTM`s3$@@2;Zf9puYx;oq>Wt5`2s$H3h`}l z^-YvHJ{?|#{CF??3982T!1$YKJ9y!CREN)lD}&Sl3I9BGM1&uM7u-VL@B{GqTggAZ z=nLc#_2Y-&mfI*#yc<4_hVgB12#rY`=%}SFR4}%LZ=w|Z5G?#6WrY_yzeL@@r@(Vj z9^MV#L1lP#N1XZ+s>avBo9n2b_#k{9HQ+oGHr}-CwvIy;+tV7%E1dy{fg9GjvXvO0lcsQHR6SX zU!_h5sQ>U2)PRp~qY(>YVdim6@~G_-ywCBaueQ9-Ut`Y`Wod)cqY6DrQ;jm z>IXPi;?vko530q7;RmP+KL#Iogkx|W9OyxVc;PSF=ud87TW}cF;-k>^q^5Z?Q43*#m@NVSAH^A?p zLVO48MFn`_>?fsNktVzqsn5}ez;}=nABFdSmptHGU>6GFL-2J}g&%~kc9Q=a@o*IN z2cxCdjtnW)zQi!@X!swgo?jqWBRyJ~)K(@gvaw3VjaV3DZ#yUYHc79)E$d zg&R-@J{#^qZTLc1iCXYgupTwx8(}+Yzzc`oCpf0>0)}ju4 zJ$wSS;oITMs0H5#-$G6J5tum2e&L;P9je1;!aP)iFM!veYP=uTq5!@gK7q>c?eJyf z!}q~|pd$PTO#Fajgm=PqC?B5*ccNT;KD-7c-^q0x-1i~z#lsqukFSF*C>Jk$8CBzZ zVf;tL!wb(u_4sV~8tTON!x7Yu7q*VkSKLLOVGl~d_rW3L#0xin%)a3BpcfV4h41}~ zc7h*;0X0S0@1~x>dZh4;umg<}rxW&}5xj7-ZHme!P7X|so1y}EVKyq0a9D_ZcwzpO zDXLvOtU;}K;d3aA?}i_t0sI(THg$@M;9c-aG=lfSAEPmR5A02xqLRNtnZPJ=;76g; z!T(2!Pl1_e?8}rPEI?6wA*@8h_$pYBBKStwjt20Z@K-2|?}zW8Ui>JWn>0m*@X4?a z1=*Hx-b~Vx?ZO*TGd>7k;s1vm!VkhxG>BKuDQYq5$EU)ZP*wx!z!sE&7ap8FMRnr? zunu+N8(_N#-vRqjD?SWIP%}OXopYw}UJ?0(8&Ewy3r11)y~Kfzxs(w;8D^k#yzq1L zr>Ls?xaNWl$d7M=9jFA~1;faTAAs+pLi`v^UO-vnQ(+d$!{@*~$c-_VCN5F9}1 z_z3(2x$yC)QQuGsJ`Ls|Cq5SzAv@j&tC7Ojz=zT31EdeT&D1&Ti|o39Up?PqgMPN{LAU&?Lqnh;Thx&FT4X~ z;_Kj%jU0=IIF7IZ<>8xP2Xf=PU>IfN2jKfC6F&x%&!VjGsW1z<@Hub~O2HSw0CM81 z;a8Cz-vqyp6uuLVoK0EbN1<~Qzws$B6Aj^oCCK$K=Wh6Slz|_C&dqEaFWi7~@NW1f zs*yO*p2M+`I4}bR@R=|lmEnc|M7{Vin0yZH7caaI4dWZ(2pYu;FFu!jZ{~afOOOjM zd?PvrsKQ8+uR>UkC%J3NJj%P5!>lafd}H4ex_Nl!~u`%_te)0=tm| zAA$oY9xuFkC);b`_`+|Z41605p)`CijG$EfFr1!Gns^6XgB`~5B z@OBi%*THY1VSF3>35wu*;ag|`KMWHuqz>U7a1H9kXTbAO2%irRp)PzGyd8DmYhm7J z>F>Tt`-KNk6TSrAiW=~>@KIEUZ-qZXHTWL*Csd6ef>SQ1tnqfZ5|!c8p&R+|dGG)# z!k55Xkq2J`9cMDEJt~GKfDhW;2UAue)?FvumY9f1Mo+v z3Lk<$=l>b5#rMH;i>X6+H!MV*cpv;VN`IU_3XUNcKK>x}1*PEApc^^y!oMD2TWz!p zXfLPF$2(y}Zv7tK?6BU&b^Gb4$pXb=zSR=QgUUzUWlDeNWsL@%TMwOo7MI>~AV;#n$<9swqbrshmt{eFR z#B>MuBGUbw!OHE{3BRSwGxi6rJ8#F7ZsVQE>0aKAah(axfOb8|`_Qcy_|$&m zxyQM4OF*}BH=1-051~Pi@C54gB+sK(2Vd~DHU8|3&tiV<*Ii>>&Xa+#Klcr|2k&+6 zJc=qk#?wgX;5UC`O_b8L{5(eWDwkgGS*^>s218*VZpMHPp2CU_UO+(~@D0W=?t4xy z9&`@6gsU*7tGN-Qx`{h5qC2@CLwbO-7|>%pgFZdW1@!1;e&vnkPM7cr=+xDGIhyY? zpZrbK>k%Hg$(jkx6PVV)U)g1RKk}UC5hQeQ^N{z|t=x?s9XyJ89sCfc^)zq1*?qCc z=Ob64QU{-nYTd|RMXm1V*e$NF4!#pjdYa#St97Dl`FrTpqg*p=3_5uKH@(+{_0Q3_ z++#Y%6)4dOu0ye|=T;QyHtxZyv;!SbsK*Z^Lm7zN6C*p6EdESDAKL`4W#rikAKfP);XR-PUm?A<2t(C{e_G!=P%sp zI6v`h=RvII9EZoTtaChvC7tILEa>Qmehr9uUCuR_)k!`VlRCvWV8Zc-`F@P*9G^U9 z{ruGa+>A;cd^ei(INx};^U_0n#y#d%H*hQZba2ysp6`3zcifIH-N}9E&;vY*Ha*7E zNa7yX1$qAR!-#X9)8N#~(5i=c z3@v(sXVIkRc^M5lxWd2tTk(Wz%C#udb=-my-OAl4*1^xCNw4ttUsDvNfA1dQN71F{ zxPT75!tq0kqBdR1Cm^M3xDm~|nLEPQx{I$vy&mK&l6stHP^0I#fGWMh@v>s((9>vFEapiXi#`gMxC(5utD3u!&XQ&`c#JIc+~lb(m1MwuSu z2T-qbyy0-?qvKqOHr>J}zTWY54L^#0JoZk^|YPH<1@ zVm=9@x|W+UqJtm8s-EKlmh}q9t34}pE5GkVW7wy;8`V1aS#;`Ee*H=A9bLgUAfre4 z7Z}#l{K=EeukPk=VK(f;4`W5oaOazfqVfgnhX+xrNBH13TN}ERzlhxtLY z=_yX0Vor4Mc}VLN-;Ca{Kfkfoy{)Ub9-}&V7iRPjzx33iD6iw3K|u$)_`xu2=Zor@0?=6K_ivMRU5Izxr0=c-m_@i=-as8Pw=GE}%*$ z-{#t(OHcE*(~F{vrx%RtG{3zbILB@0?i_eFPP{m3K6F&7XaiaUWJ}AK)wsdW>hVsAqWz zdA-cV?=&7=!j+iO!A0jA&$HG!S0JY=c^k%c1Ggfp+qnlB-Nz#s)}uU$K|Rg;(XW@d z=o98q7xPx6btP{@mu}!4=+NzaCE9c!k07N-c@oWfnitThm$>Kx*IgHL1(Ldwx1mNi z@D5bzcD@=3-N(11T#xc3O7%4FM_ey)QF~Do)5W|Mk*?%Ato+UKc?Sx*jdO#J`9Ig1 z_n}lT@|qhRQ^$AD8FEc^jJKd(CwLo@x}LYAMz`^msM5W>8ws7^y(rgHybq;% zk=NgB{&kGEBBm3(4Uw+r9avd)eBOzI?&aICs5875c|FDZF{c-K-7V%^$9M~JI>Fm8 zuIqURvbv3TBBOhGH->eF_hL{_@qYB{MP75OIoC1Xg0xQX>FCn+yd537jd!9=_wsI} zbcXk$Sx@mkH0niOGi=UvjJF`E6MQ;qbUklJm2TrJkk9rD26ZQY4ZV7jKmToOKzH-j z?|5y|=BSd-L|ixUK6K~>er?7z(&e20o^_>zSFou4LHDQ>`LGYyU`_`gll8g2PIDvL zb#vH;R^7$@Xwie5MUx)qN710?_@AiLD;&Sm=aeqxDpczlZbYSS=Fgx^cX97sKHt}G zKHymNnBf{TCd8tW3S#JCQAFY-FWhdyXMy=-%o;3Q%?_$R2;lUy?e9ef?? z^Z*|@V;*!hpN(bR#1~;nck;DZ2>bIW=Jgm)V^+`dv*Bx9;Lkl`PB&Y({Kx#<}Yj?nG7xk7G^;fA+V=sk=CXf)0Mi zW6n`G@N4GGPux1>FQQua@dzq)@TT9n2XqNnqD=?y!l)kNF^uTo>Bl`o^eitSuY-Rx z?|J@m_W~~?se_Mu(%RD1+<+b(d}Q8hbtTtfP6wa-l>6}&t{q?TNB>Rt@@~}X4Chd- zgE#Fn&$^i3fG%ChJ29pEcmz2;$`2u{r+N2+HTg>Oz@PY&_tKrb8?|9OM^Bp*9sF)| z>L&gT(t3`cS#&Sx6^{Sey4R&#g)tp`%wMeU63z7}(!po_&AjMl?m(Rm&ZAogpZu(K zqU-o!^oQ-dx!`%I<9r52bUl9=qk4e_FrHU=M)7<9=^S(u zzw$Zb(Is4oS{;1&^X5cX@Q;z!Ij&zZP92>3w`YNF=bJF1hj`6@j7=AD2PSlIKc;nX z(+kFP@a8DRU0Ag}_TAVpZTvfrn|Tw-8%S||G7u>FppzV=Xefz zo#&PCwGO`MMW0Wvb{y_Uy$;?Kt&KW#G5;FfdWQdrUcJmyFIgLvm%2x|fKt88#RskR z^Q+7!S0SdWxe<|W;ts4hPAB)Hpa(dMMLoukBClt;YR%fH(`$ooLXRHeB@F1`sf}x+ zc|FYwm<`*xXw%wgS_gl-cx_a9h~x7#Ds=F+m#&SPbUptxzBZcEQ=G?)UgYTI-b2T@ z966ofB*t~{!zg`?x#GrGIKB>k6zw|CD`?fxE7!U&*ZNsZo?Tp8;`pJt8aZ9d%^26g z_q}SZ-(xd2UO<&z-BKY?ML=NB-jqvISO{koh_L9b5o zIY{diUxF^3=3VH}!~6i+bdH}yO6U0nH0$VibBjh@&NZml!F?D!-1C$FiVL{wJ#SG7rDiwa|Hf>)VV^xA03(cRsp|t1+c(`5ffJK0Jex3S;IK6zkwm z)LHYoiwDr82l-w!=y5*m?VdThoJY>^+9Rw_oJzdh5h7V|*mqbb_x(Mh|ip!+M-&FsOr%In&p=irdd}KOX7$+=qnj z=TVgFEKj3U&+roBI{4S`^eotFu6P9{dX-Dx<=k``*Puw(ax+%FwuLXjvhL7xPwRbS0mKVcozxFsR%4O7!bKz74&4lpjP|PxF3s=_Ov@ z;kl$^{2?Tdc8$2}Q~DU!h;KlR9^#)Pp{My@DAlWc#;47dZs2FJTxmO(T}-`Zo{>N@U0K?na2rN?>SE4(k_I`|!5HXhx; zBWTs5Jc$+^eEgNJsjlV*42J#rKfTUbuX4#(TqhlT3!>v4ho3-9&+~&ny*e&vnvCJ{R3O#Sf$S1lNfFidEYSTz!palkLIf{nnBWz7JWQ<2ht>o>wre zg9pB59;!WWIg6Mc=NUwLo?msHW9l-lMzan+cEH$ll8^j`G3x|hfC1gk$6xQ-=vr>V zqz=xbpo4F_!E@t8=gz;wxX$wmvN{@cy^zu6d>V#zlF!4SPVuGa*J&O?uO8+Jq;-zx z(WUdef({*g*-hr|B?8_%Ic=lKN`>*z;5FHoe*`4p_a*}3yMSk@`N1WP*2yRe{# zc@O4wj^{9|^Spv-9qn;XU{aTJ4JLGw&%u~Z@g*45X&%Ig9_Dcj=^Q_S0iEX;(5Isb z$48GY=Tp$FlY9<3b&4-RyH4{iwCW*#2+PiWn%{W8bJtZoj99HP@P;3oH(ktUqFgud zO{mmE{6Ey_=mFo8pk8RR4`5#7dXe`+3djK71t&TtNMI`{=d zr+OyvS$o~%x`EqJu7iI(X)Wn#{>abV`?{4+`h|0Ri}l9mVOh6wHSTn9k*aixALVJ)jd3f5k0~Wgs=4^&tpI@@GAOr(X{o8 z9v%E2jGgA(x#U-lse`|ZIX%j=Skl24KWZFF-zRe)n)Cqw01bMKr%|V8`B~KJ0)K7R z*uwt&-lyFAr+c>WMd;O?d@a&?fJf1#$9NhYdX}F>n_lM4e{_H95psq4PLJ{wX7n^KhOhM!Z+hA~*2R1@#&sp1g{*Gi3y{(6 z{M3GP`*yG8qD6D7gU`i?9^|Ky)r-9TFOI2W{B}(1dLG4!9_LvUbnupE%;6cgZpdnW)$0d*R(r}z?7=``;`LJ#vEl@`gpRj8Fbu~YW8C~GF z{@4Ac>-Yf_o#h@3+puc;B7g3G#%X&OZ+y{u(#3o`YV;_-Hd+_e=}KNXXq{gdxi`6F z%{o7i%RS507}B-;LG!Mbj=d+6) zUpMk6F`zs6jW1p2|K)qmylh?cq4+xgo}}jwcVk}n@DOJ82v1;IPx5o&YhCp6b)bko%hfs z+=ILh{s#(rnXfu*T~ziS&lw&?i4Oi7YIT9nJKTHe6n7!5(>xTu*26rG4qddx+Nf9; zE$b$3$CB>kek|w#&SG8%cO7AT?{!QbM7JL1adhe&&!JuCc@?cX_`D<6`59TBOWXs0 zpfBp<5v*Y0r?~U&*2y`>$%BaL zVcvsC=lBV%e8@P1QP9yD>!MOD>T<3@UMIO3b2`Ocn9*t8g(*GEdyvyPeiGw4&o3aW zqiyS=Ly^(td>V#zlFz}QPVpt^*J<8`UOmivkk&bV0$n=KFQ7yF$CskR(5B1zG^BKr z&qK3L@ug_gX}%dry~^)7b6u2Zu^#wZROx%9bLxNSk$%LguHIy&tXn?^9`8MLwql$^f({)CHLM(J)gM| zaox-vi0LlwN2G&`FLjPO&NUd;Np3^G4*ojg+dap)A?^4&_>V~F1&&`9=G}4lElBDl z--!l2#)n<*-qC&hC|dO_Z{KO1=vKZBT{^>mKw9Vdy;rz@brXLV!+MmT!l+*0?O%57 zbSwWAQ@X%+UTNO+7$4qiZgmAuU_}Ri;VafytGVTmTx~9NJNKYU2fyTNu7xh*GR)}; zuEmV5;}%TmR_;bl_wW$Lb@1~@e9W(%`MqEFUb=}puQTsD_*$fNKhL922Y>tIL30lg(=DO%tnAbD> z(6^k6p6265thqM#2sa_7r?`MB9en$@&8;5gzVCQH9sJtcyhfMvok;01eqYA;bu%w~ z*SK}?!l*Ha<`*%d8}3*aJ%U+1%WwL=wV`XdAB#Hpmx!Nl{Jeyi4u1Xz)_`8&syofK z4sQOT_tGi81S2}lW9Zkx(HK7A`NKydrW1TO;(C&gzk6L&uB-XqsMM?cg?qf0?&eR8 zd*)`E3 z>Q=rM^}3&bhekcmXWZ}p(GA>&P96Lt26Uc3@MHI&PVrYTqx*RjQ#yF`0qe6}^CX(| zG%un-FY%_F&oW)iN269(@>!_X4g42WebWA1^b^-k7YCzUSMpgX)eU?B;<}x$Mojl{ z29X}+DXcjDG%uo{gX@0kez8v@UxbEDY#Iz5so?gRe%9?&sUlt+V_~bm|#? z2JO1Qn_js-YSnQ*8ZEkt&q9-K3+T)wK~hcM75sbXHcmNys2cpzvJup zd@Raz6`zd~-N+Z9SaluCqOS-_DUbQ}2&~ZK*^SYAHMOoS! z;~tdg;Gd#NPx3q(bnwQ5&57>f_q}?3)TNtwH+uC5{|*CTe|`Z&dX*0?b&d2eKY&r4 z<9}gHukg8ttdC-sS%;iPq^u)StNBAzx`0xRM&F~ow|)n z%GO77x|FLhqieVcQ@WWukki3$f30I)ZvMFqHM)a)QKf_LINaRoah}1vp5p>$^$N#d zZ*A*RJ`oeThF^BXdf%Ja&Q+MxHQb09-OLwbN_TNTa(a;OMplpW>yI?<9``BNp-l(h znphuY^axL2SO>rRDC5yhd~BsTzru5v-;I^9o%f){_6c5JXMJAndBs~%rYm_HN^}G7 zK(TJ;D^aBT_%^Iw<$1*qVp&h~ek|!FE_%B;)WuwZd0okMnAHuu1Jk;ldoZc{_%=-F zQGN)cdYT)~aLh0^eg+kN-iwR28NV*(1WI)9htaOvIE_{vT)>15j@4VMI?f4<>fl`{ zy2kqC99C@)e&?Ca!S+UOL#+-@zQ=p@Tc5lgt-6iVXwkjA8%;XHIW*`g-iJE9$m`$h zm^#K=P^}YuIx2NNZ%2i0R{>>nYBoNH6l5Cg*akja;UC0)-S z!@O?epQ7$-o*VoxH0Twse4lgC!C!vAbJX+P_5tUvga3psy~wA2&^@M;eD{ae`+siM zIp1}TIoD(SjdNWWJ;-OAXPxMJehQ0vfuH&KdOzRMuh+PEhxn{tl8l%fCUDp5wFItSjBj9hlc$+>cou{1X%pmlyxdyU#D(#(%}QUg7vBotF;&+J)BTH#}o`9OXL4b12n$UO`+3 zw_fC0=-_XnRgdrz+I8?$hwNs*F$_STJ;1!h88`~%V^T8Tym+kr_1wA_{5_+CjP_Bay>v#X?7T)qT_k*tFI*jWEZbeoHUxNkR&+)H&j@{~9 zxDqA0iW^X@gXfUcHP?BDph=JOyLVZ`-*o?S2c~rw4`5Oc@)#!cIM0Ny^&A&4s#iEZ zF`M^y#+pdGZiC#UzTW|49(FsmrPzS%{ zR_jR@@eLT&gM2S0^f(_s>|As;pL&~na<}gXxCLdpmAg@*dw2-NI{0m)wtw3kax02; zJNKYS_wfi;N8HOiiDf;_3s}<0JJv_vM~nTleD|H6^?IB;f9P{g2k%9np5#~DH6ByH#T#r%Rz^xe3?c9SQ-50iDK#%f+=+o1@A3b`J4|&)z?{G~yiBTQ=B`oPQ z4`M+F--V*@`@G{np;)hQ&x~uNGraK;bD@j*CRB(0`H25BFFL{7(5UPAD`?hzJn^V` z(805q)bqTI2_3xQH@?=zd>JZ!;I*7VnGRmv=X`(Y*|p$aMvqQ#65YC3^dd)pa&PMxm!nQ6IEh+a&nZ;vHcq2b2Os>jS2BW6FSFTi{5w4K0JsDJqN6)X1E{|eS-@QHyID;OYw55{{Zp(oC}w%i$#sPoNG|8liY%&4t@+v zdY+fDpjWwceJq;SWn6<O1^bp^Rm7lm5_%RgpJTGHWuX4$jSd`agd@|;AEjM9C zw{Rz>bT{9CoF3wPF|H^0F=X{TFC(K@xunAN(`8(ZL0!vD=+`a$IrQpo9za?T@xAEM z<9y5!*3I7VYj&?iN;h*SnspZspivL<80z&n&myUp_#H)WOeXmTg}^~&uAXP zitQtuLqSh+9*cUBFG)DQ{d;)?NgdpAlyT@z?hEJv&Z0*LAM^%mKo@Za6+iPi!t*H6 z3;dF!WB$INefXmo)verC?f6rU&)1?)5AY~@^cX*kZavFO=+w)+`9$}LF5%!O-i^ko$N!Z>*ptF{ME zqulo36W?Ub>l$7`vkoqKvumn@zk^Pl;i6NVuMV!LHFr9=7PGpJTQIF#IgLpj{37DN z^n5$joV?Y0={P6QsH?a>po71UF+I*R7}ayUj1e7t!@1__|EvL?K&_tSc~t8Kei4CV9Ys1&Nj$2ToTe%y>x`%H?ksjd*tUluS{3Mq30>6kQU36Y7It&ZCf@?9a z>-ap(>Q=rK)4GRm#iSnL2QZ;0`ALlF1%45ux+vvYg&|$d??#_);zK@a-X8TF;M{ie zse_Mgb#A(fKe)p&XMHwt7e;lO2Qi|Dc^pGJ$8#9a!Pm7pM?Ju=`Gj-VWn6NBXW(zF z6aE-VbQ}KyWqO)dP^p7YX?I<8E#HbdJIGg!wJy5S zoS{+&pK-O<{?0R#cc4kPak>vWc+WMSaXQCy$mrm=f7N=^^?Xvl_kG+w&W)(m&HNcu z>MmZyj1FG=HET%6_*1BT!am%OY8^a%oi(Iqco7{scrpNBJ?d>Umy&hjHs--imr%$=i_B4ZH(2x{ZHxr_aqN%>(}r z<2v@kSadcN@^B zdUQ9xh&~IFXT$JUar=9|&3hxt!GG5>mzUp;AlbSYnpj2`6QVocBR zx}UkOx{fbL@gJ>o9zu~G;T%@&Gs$@@>)>;LZfy2R@vo89GrWk5E^zUK){>5MB?fgB zH=tiPayxo;2lpYZgU^`qUb>!}QMAu>=SNViXL;ZuYe@%>BB^Kis9*Y8SMgaG(G7eB zrgSg=8_5OhYTDeRMklxqRl1&Akk9ayobr1s(iLl>Nyy3foYkgLlrDbKS!qc*OX1Ge3m(us;{j8usTe{hx8` zG@m_dzH}q+#;6|Q;@^0$r;UfJP@=235yiTRJ5Z!MxgV=uJHS~i>)^-GWS=?y>~F1I z-Nk3ldA)AnGk@n=hvtJG_gSfnxdLN4_#miW^X^gTII-o#q>V z?>Ksx_h3}#cn%{v_(kM(^atw=i#qt_Px_qGCHyTEEt(%bD(}5?H8-GA2k&~u{6FLS zZ5~67p5Vt&rRVv1B=jnmELlIgj88_XuH_FPu3NYhG2P7ri1ZMTVdXF8gg^gR_gC15 zd;ezcmW-cAP^L$D3MG1)7g4O2IQFdb(#4#>s^e60J(hLw6Nhi`vvfQ|IC}jCKTF3w z!dp%^>v##eIXw+@I6ZN{6cO$7Yycackiua>RFY@{$ z&A*QE7L@A*Z$qiB=N*XaHr|Pt?&aNxbcXk0CLyiV{o%;|dG zff?P#J29nu`8MQqh9AVZp5lGT>P23kF#kHnTQRH?d^!eoJ?}ujZsRM_t9yAj(mKO? z(WR$&A3F3RuQ|&6>lklEN+)<5nsq&IN26}zov7End>fKF!w;fHPw_rf=|x`i2J^3D zycOj-!P`)(>v=okx{a?yO!x9_h;)YcV&&hC&-+o(i@g45^RHvP1$mv|ZJ5*byaO}3 zjdx;7_wsGX=?p)JaXrQRkkyO4zS8{b7;nX}PVng%)b+dr{kn~>M6d4U-AL;UKZq_p z#rx5r7kSMw=3mEnD^fba+t94*d0&<7Q9N4Y=s4STjJKdsCpd|EUC*iTwQl1yYIH9T zqe^EuhlCFPJLYtByla9P9sGX8UlNa+xdSmBJc5MIa{mdAugj~A1xq^k9Vc#xCUx-Q zN#;`r{{zE1`0rMg|rJ`}r^E(FH!? z3~M0l&s~_+!MS>CNeBN98J*`<#Eaqw{I)ZFZF_J7mUMyJ-?1U8wND3s8`U~~mi2~i z-N-VN~aNbHj#cTF3d_nAIsB!hG1De~X2%KbODL7_S5Tt+c?M-V_>i+VL>0Q6Q>fIz-$k`fHkv0i=p6qJO*+roIG&@J#!FPPndFcXgJjc1L z^IrTnBz16mi!tlq-6+#p{?u3ynvK0@aY%1-nx;m$DAJKHJ|p{nDKB8;yQV; z`w3|s{7v-gEI)>Ro#(4Q@0hxuGsuN~xb+hEzy{~V*PvWS-5a8}Bfc?yz`xeZwg>02 zpo1&E=$JbA1Y~vanHbi==c8A5@a&ho#xXlC-4I=ycCB@A@#W542mj}4Yeq+X8=}vA z)jHR~U%?*`J2uEOU>sk)*#ZloKHoE zPVyH~_Okc^KX{vYust}BB^`W3#`x`1#qY;}PVtw~qx<;|bm}bEeOGUGzT6(r9egcX zbwB?AEjr5&qe;*3vuM!q@3|K+tsA)wle&Xns;s<=&r1Q5u_|LyGhdTJ5n9;$-zxHelujNN(jNfbXyn>vL9@!8b ziL9>TCJgHoPoe%*&XHg7=mtNN%{lTJ7}bs3i6NclgJ+HTVEgc&QLhWZzqLMfBlkaM z9qQnT-?{cW$Mui9@4`O3592!cz9-zLI`|n3=mMWR@7~cV-iyXp#}9bJ?>%31@QHtL zZFKMl^X@Ml{5dRp?E&j)9X(}DqQ^eLzek4-ehw)e{n7IbIUPKVQ5`&nA)Vvb?XxyY z;|Dyu;2Cav@INrCqd#r%a|PYk_UYh@(WQfLKuQNcjs_jPh*}-|UsUSo=?&3&=+-Ho z#emN9k$*OCx{BY4MIC$(W_9p2e=&zT{>+BxSkxU7Kj6>nDjj@8!81Vz*P%!U{|d`q zJHzk)hwE#5iZ4X;n)m^C>jm3`@5YP{KI)&Yy$)`~s1B}LHitU+C+N{LyyrRV*fDdw z^t^e~1)lhi&p|!IQ!kjS(EPzw^QnW+{h#%wgFlNVo#qFS*ExO~GrGVpebM#RaXtyP zhr0Lq$4KiO&!J1_`Rr(;f0M!ZxeZy}!9AGK{d^DRboom*MrR=(_UBeChW+_^6m*Wi zd(cMzo`dVVW@B_1d;s{lz#F4S5YzFZjnT1)>ncv8RA>1)l!xtWH%6aXzcEUL?fekB zbnt1hjZsPm--||_fkRRqk}KUpbj2FuO8+nk$SDU z;&q$7myYu>Na`xS4--1aqw$T=q%ME?#;6I?I?r!=g*nl|?=IOGE$HCOFspMsiI&&J z5BMQnuY+Iys*O>#4lYN94n6@TI>`^d+V#+Rei7|Dd&tJuCs@3jDCd$9ehri^`L`0kki4xMM39z&ug8d&hb;IIV^s_uX&v@>)D=Tniyb?&o95H%4Q+inn84cW^Ie!#=zhCFSll zUPQ5uAHFeq0hKy>{YJlMxG}2L9XyIL9sKtS@27*;9bwEmxDHJ^_^j}?ZsaF0sq_5T zt;VCHg!=*uy5lHo2TMA~f5WOSaO=?wpIEhj^lryTzwN;fqgw|b{2s^B!N;IU2VaOf-N6qdANJ=TzSpsI@IA=r zc$4QMmUW8%`9ACWNay~3*ZBj+ql4dwa$Uv0N4?JTDw4Xf+4cOOdnD|`3m-D?y1*6Z zct(WnJcfB4Jc}tEyy;x)TL;H6tb>n2zwY42u&nd^*AIK|u+Mp(C#c+N4BUna-NDI} z`O(46XwfNtXeMIC=i-$9-PtLhug9KPrB}1A5u^;19LAjym}C^BrFY*JDBl zzZ0W6_~RJT!+iTETsz0i@~bXzopd>W0W~_!2eq3oo#Zcl(md+m|6XWa>FA=3(VLLd z!7HEkT+qSsi(OwG{4UJsMt()7XY3o|2ONFI^Ue0)YRu_m*oKsS3Vg_CJ-c){pNgbT z@t?nFJV)DyV_&krj`PuI)>V8FR&)nnk3~Jq_hU}y_@=aTskEJE(5a)#%qzNefj3`n zZRiv)?6gL8@DV+pOFFpt3UjN26X?@bd?yxkj-NusF+QXDHD5MII=Bjv4t_III`{$9 z>)?6R=<+Mg1NwAuJGyl6WoXm=9Pf24bns~y)=A#@758}9&JBp(7(d`Hy<~gvHJH=E z6<2va=qheU%>Es`{%Y4k2mc-AI(S2$_txdye2r_TQ~bTJdQR#rPoYoGaOzs$&xC#W zi^%IVe;;kf#t-;!x=9Cb?Drhgac=yYYovou{<<}KR_dgf8$)hOEC6;s>181>1xF ziFqB}?4Cn|eS+VEDjobOlFhZbDk8_=@kCL*36Kw|TwJ^3zz>1^#Hp=ZNm$mweaUo@fkw2u&9HFF{OhaKvw5?6$KsL;qwJ6VITfEs!oa@ z@KN749v%EP6zfL*9cp!+i+=x0oE$&kYxSHC{wXGP@L!PC!6%M+4;_5Y-Hxw=FF>abz8I}K_!2bf;GL+` z!MjkcgU3;fp^^N*B~{nDL;=&?Kjk;DkODq1L}1nPoYuIaPxhx zolfz7bn61Q|HvBCY4(q0Mk6}Mn^&r zTy&i8LqX>_kD@nu?OyjC`gHK$e&)W_!8?C$-gNLysL{b=DAU3BqFCqnlqu&Dn*Z-1 z`{|iqc;5cf=a&xt@U(ka2Y(q8x}Wd-m3h`V-ui3T<;}h~=GFi6xuAm&e$?lJF6XVt zgnh!c-4VN#W_^fnos`Rg6Fdi?ng`q---IL4?lxOUEsA( zTdz9KP5aGf*q_@MJqL6L-~VTyD>}zlJ!5XeKK!pGW7g4MH%5OzbgHrO)ZfgNp5bp6 zJZE*5m;Yf-bo5WZzJAU1Q@LN%#gRe!2?&r33 zoBUi;bHYzxMCW&s@#{F(px!>gH=tSv z-;N5M<)3fd6t(CXUU})Js7FUH+vMNv+2rS`S`TsOjVT@6k2xKDC-OSSuYCEYs5luv z;JfsS?ZJ;=QRn%)uhQ#aClM2j7dd4&Gj}DeBN^ z&R|$)`N&uKT32!D!RAVr^Ocy?{rtXHyJkAYx0P;+7Ic;quQ7+ZiuWGs8okx^E!z}r ze(k2HQpfpHRO^}7ZHjI_+&IHN+_Gg;)T&c_Z-sH{0uLQwJYgR$J<>er;E!NN2j7kf zo#ltn{5HP_$X9H&mUQq{3FFhjzd>9FpMR7wdu<1QuojCHPqKZJsPg3rgC4(`B|4o<$&JlH?@f@3#DojUjr$8U<7{=dDm z4|A)k^Y|%N4K%YGut>z}UTb|Em7$r=Oq+qW1}T^-LXc9Tyd^V}AtUWfoS9;ZfLZCT zfh`cQ`#3H|Z{BZSCJ9j7(Pf{_YH`N}+^D$Dqo@(;64&k;1=px3`}w8wk`7Z~dHio@ zc)tC7=Jw>~+;h%7=iKQqg9~5RMg4ILoakYkOZknQZxiDRufo0dRq6>m1pkvxwNyq7yLN6i83KLxP`He2VjX*aG_-@ zV;vXXP7-(oUj1Ii2JV5cad3WIgFA@%0*=F@WE>aXdmiH%7go<_JmbQjlFhjA%x#P% zT=)U98h624E})O_5Y#TD?{MK6@2AX#e9a2pNuKAp@N48KE$_mTiEe9h&wKW>57@1#xe06ajdxDKBoHM|aYT(L^o-p{|= z!js&q)EZp)Mqq&$1Hh32aa$LCSBlIOMbdepn@W;e~2d-SDzDoAv6&Syodf+8^ z>$Tihcm#geLx16Qc#4-k#f9gRhjHNrB#*n`Nqwu-leh&wLXP4U_)z~UwbIEv1`iA{ z#&8{eYmjk>SK)7njtdX_X+vDNWr(!^cfb`Nqup=|e8crzJ1z{~N*m(B5~<+A>p#Oi zxSi{TTf?*oE-VulF1+S8+5|7b9}oi|2jXM}Wcm7;llTjL%4999K?kYvL6>dMsC6@@QMWY=|y}E7VafhTv#P5apAh;D)k)Yg&Ts=gcNf>v) z+sFhSfsc|rUV*)bX4LZKN-M<_HR-jT=>AhF<0YdcfFJ%Z`WhGR{RQob3o~RNUV`yD z?IsStN1ni|aOjt`Egpb9zv8~Y9dJ8Y_hG&-0xCUGpC5Lcfl^n!{D;v}q7n;a8Zh>d~mO5O;m{^=7q;*9vbVZd`aTapJ<;OlH+B$Km~C z9WH!`Snx9Z%E@MB;KJXL1GsSL6tmig2cXAnR_4oT2YAb$o7GW{3qMDm#Dxj+FkXUV zr}18t55Y^{U{-c{Ei|vDZny>BM_jlLUw1n7m*em<;*s+3apK1-@V##|tDA5Ky!=eF z%HzTZ*HC9%_$b*Y<>BdXp+2|}y+(I711MvIn%xcX}zBdN`;$3EC z!s~GFyJ=@!XnzlN#)ad=frsEe@-Qyc&Z3WS;Rp%g0r)j?7_UH22k(mu&p6wxYU1!? z>&~fulLjvQ2C3r0qvSEX4xb}ux%ql8eEU}Fj|(p(3U|S~ zNjI*;C&^~K3a#%ot8KUjd&xz32&Ty{Tsh3@gJc|c!S9fp@GA74$2H@1XnmhqMQ{zi zlT72nv(7iG5-xO-3ETw-i2ozZ|L_6gz=ii;U{>w8@SDVn3+qJTLi2@YRpC4q_-@Us`0)eJ6tkWAxc_&C}8QNG^;uJ{o5 z6fSHh7Tg9uNeTPwuDhaN&()7aoFlk}58= z_!x({@LH0cDZe%dCF2n?4mEHmgsQ8C-bV zh*_0!;mQEljtfsE2XUdD?8k+l8KZx3;eEt|SKzbb^u^VDKP3FhCpZ@_{4sfsGF5ng zkiO%18Sc4}zQBbYw=m9e8@!7cxbTl8f(vi@6#a;Ypg%;L-~ssGL|wzzZ{ZLAl6iyU z!WEz98gUDJifELn!z*s39dHjkZ69riYw!{h!G&#q#W=u)?;?IY1fTh9>W0^$7G_N1 z!o$RLEzef4Z$H<|abb)c!G&KU6}$>RbO-ka)kO5qH^_`4K+yjGm(eJqM z%OrxA;nQRauS08ue#C9?k-MoIUV-_07`J!{Uix{)vxjrS&k#2*{65)+3tu4JxcWP@ z8YTmH0KWBJ#-})Znym5iy`%8Y4${}S@M3b5GD0_b5*L1)JcbKTxsN*G!qwyeE_^H5 zhimXk;^v&fNwN(Wew}pV6}aUKTq7?0Z5dqz# zjthfid^P_sHk=^#ZvG$epHN@2`L&E6SRuRc%2V{q(~Ofh^Ik_7d&IVZdmjeK(Qd96 zT7JrYhU3CZi4zxIPwe>r&ySOoT)C!H$O`Qcw0x{kEBMTHg5&-rj-Sl0l@GO?;h3&a zay-H(di_*&_@spb{%22BcfMxf^TQ`~^T|$inYxxgFHj%Gw(}jz7xVdc{=5*EkAGbG zi$5xA_gZyY^Yb~rxA~b;EBR0A-ukhQ_g%sH#?`1AQp4&x&NifWDIaGZR)cCp3OCEx zRJXFJUec}l_*2@3HBX&SncXU&yp-QdZM=NuYre&H&Njk%HCoH7Zs4pV{HfGO)th+T z-1}^!^qA^zzJGvv3{h&h`T9+&gR(=MTWi)(-eG_<$$5r(mDYUkVO~Abtg*aEey7&L zxpz`(l)qm%XD78jo7(qKw!PUNa-HMNa>LDYFI>SUbt6aE%BXdm^CHSz*DN7d9N=1| z#jfMK!^bJ};$s@;?^o}lmYuYk{MVs2@Xk^Ljn-YdZS0DleMNsBJGbtH$ofj;ho2i>x+f9J{%jlqW z7|3b|e=9vSMt!}<_2bDaOHx2>_KuDDcK7Yts|9!O8XnuScF*YWIb;2P-)`?%$L^v2 z(UGx{!SRm%k=^Hb$9A86L+4s;w|97G&^I=|(>FRcG%~zpZRgpxwOiXxnLpL`0pIZW z(D+{MhL#d%+bE?4Mo0R6V`C$u=Z*IJhsJ&V<9kMZTh_jNtv2l4jcmGctv0?lfPG?U zxNF1Ot((`+RdDXC@=oKUd&b7M4-byK!di18UNz?H-!n?Rwr(EveS8mB;2Us_4&5-c z%Xgh`tmWVZK?;s@X&3o!@a@ue$)8)+ddId8-!O8$Z*;A;XXw0sxr{Aq2fe$-e9e=u zpFijEFXsy8|EBfb)-Tj%{ru%fEoth2cYLUCXqPnM%l67~#if6?ZuSNOyN3EX$H=gp z@NFF(+RmY|aj&Ln9UWV>&0~84fss+J{*ueI?E_oZe$uzW*4f$Jv#F!Ar+Z^ZcUON; zhqq_Y*I~2S2YfzX*Fe{x?dG-X=T3IT(6F}YgeU9j>Fl!i^?Eyc26{Q!VBbK8-M3+& z!@Hro+qPjt&tPwtchSl0Cp=lVcf;TYyRWOGr>n2Gqr1Div!l=3+ut#;v3H=`+wI-x z>#{FC+1Z^ZxXjMMjhi<1Z`#;lxA{6dx;J$7cJ%i2^>x_025f^H2W$i0O^Z*~X**Gy z*?qRbP2SE89esm+U0kNm-oZupbadMX2YPJY&K_^?+-0tB?U2pukLxjM)A=F3Rqn{; zk7f(h*_$DbgfrnvxD%d)KM_a-6QM*n5lKuW^n{TpC8iSP#B`#Pm`PLTCzQ9OWKo;q%-MCx|5!yKN&~{lc8id8A(ng^`w!kBvsmyZcp3N_Ov7IOuN$V zv?uLP2hzcGJ*#rooR(|P*>d)rBj?Pya-Li;7s`cmk=#U1&l$N=uAH06RdcmmJ=e&o zyeS_l&J?RfrP|by73?GQa5NH~i0V-zT8d6Z%hBm*B{~zWMr+Y}v=LP?Q_K>x#~d+d z%oTITJTZSP5DUgav2ZLBn~3Q#BUXw{#mceiSS27Msg}Sot#P5l8vM(WlgoG>?voekyeJuuo${w7$sxM zC>zs8#h5Wv#+0#StQjri&jd2TOeho1L^2bZN@gZg&D1jWjFxTB+Oqa+HCxNpvkltW zl(QVy)~=kJw)W=&FKTZs-=4SS?RiJunRn&gc~9P-59EXSP(GZG{)( z=qdV(fnu;2Du#=Z;zUs|8pTp^s#q>g7c0#kt`(J~jh_#zR6q~vAw8@|^a)+p4ZWmK z>1BOdujp01rq}g`uA-)>C2Eao(e|hS-_G5$;RY?;2T(7V;7gI;x|+$m3LIyIB3r5Y(y+REKwpS?2zX+1rYuBL10 zdRltlm2qc0&0cn7omp4bo%Lk>*+4d!4Q0dGNOmHtXN_zrJC!YGE7_S7^!JH+w48TN zF7%kY;3@bEfkLnlD$Mo9Tu;=C4emIl(H}B#=qBB!J9L-s(F62M$Osz|V`6@sYO{B= z-n@UOxPxo+cdsRHogYEv{B*vuXdGE4t&`eh`=o8sKIxcrPKFDS!bCwY7==>7&bSFO zR;Cyq&HC8d)Du+4s@rupV`PGHF~exEG6Gyte>B9rP+~r)9yb?QV;UpE_9vYaJhL-e zC>>5m(i8NekuIgDX769Cp&9Ll&2Z%^lQWam$=al)Xf4j&5v^mMi*d9m4`be>TXd_g z>BdXUYS!4|ndg;_Ro4mT>ejKkFjD=gKq{CDrNXI5Y9ghlj8rK#l`5y?`pW6)bmh1a zYceYHvrr?WvZkyhYt3r2^U>l_9A*R?jNWO+ZjE`$#2nSmTHs`M@-s7qSp|&TRBoCz zpvL@fV)V2#H#wP?{LD#V<|Bi-XquU*k!KcS+Eg91&fMDKbu86#&CIKM#>kX1Q<-vRVs=fEF+H8DELxdlY@g^}TReL%J@zLi zb!r@#9q%*qv!^PWimhYD@d{^AF4WVej)K`xfi|@ow64PZKn=A<_6a9k>t(jDF1czSoBv%)?u<&WZeO(M9-2;7SO==C1a;N| zl{T?gvZl2~`=z;%>9zRxzh=L^j4_2y<;drD?TcgsAuxDOX*f7z?O z^!?&lw1Zo?TV%DjH}}NH_G$mu`DW?)<@mML#XMPMW~?(es)DItDOeZn;@mBJg4UI| z+}zz&n>)M3bCZ)*+Rr*2W~DY*tEXA5Ysc2>QX4-fK-M;^-p-htI>Bzeb=){#^{#!c z=OT-C>6Tacrjh`cf;L@eR2=?TL4xb9;?4`_RggPf^FlSE;!ZZC~_s>?!g; zKJZm?89(i;qw*x8v)agbIQ}_Adc4jvsfqi_N_)t@T4ygkld^K%5uO^VX*tdE$8rlS mx4?1>EVsaN3oN(5atkcCz;X*Lx4?1>EVsaN3;Zct;C}(pS9+QN literal 0 HcmV?d00001 diff --git a/msvc/tools/fxc.cpp b/msvc/tools/fxc.cpp new file mode 100644 index 0000000..4136971 --- /dev/null +++ b/msvc/tools/fxc.cpp @@ -0,0 +1,179 @@ +/** + * Copyright (C) 2014 Patrick Mours. All rights reserved. + * License: https://github.com/crosire/reshade#license + */ + +#include "effect_parser.hpp" +#include "effect_codegen.hpp" +#include "effect_preprocessor.hpp" +#include "version.h" +#include +#include +#include + +void print_usage(const char *path) +{ + printf(R"(usage: %s [options] + +Options: + -h, --help Print this help. + --version Print ReShade version. + + -D = Define a preprocessor macro. + -I Add directory to include search path. + -P Pre-process to file. If is "-", then result is written to standard output instead. + + -Fo Output SPIR-V binary to the given file. + -Fe Output warnings and errors to the given file. + + --glsl Print GLSL code for the previously specified entry point. + --hlsl Print HLSL code for the previously specified entry point. + --shader-model HLSL shader model version. Can be 30, 40, 41, 50, ... + + -Zi Enable debug information. + )", path); +} + +int main(int argc, char *argv[]) +{ + const char *filename = nullptr; + const char *preprocess = nullptr; + const char *errorfile = nullptr; + const char *objectfile = nullptr; + bool print_glsl = false; + bool print_hlsl = false; + bool debug_info = false; + unsigned int shader_model = 50; + + reshadefx::parser parser; + reshadefx::preprocessor pp; + pp.add_macro_definition("__RESHADE__", std::to_string(VERSION_MAJOR * 10000 + VERSION_MINOR * 100 + VERSION_REVISION)); + pp.add_macro_definition("__RESHADE_PERFORMANCE_MODE__", "0"); + pp.add_macro_definition("BUFFER_WIDTH", "800"); + pp.add_macro_definition("BUFFER_HEIGHT", "600"); + pp.add_macro_definition("BUFFER_RCP_WIDTH", "(1.0 / BUFFER_WIDTH)"); + pp.add_macro_definition("BUFFER_RCP_HEIGHT", "(1.0 / BUFFER_HEIGHT)"); + + // Parse command-line arguments + for (int i = 1; i < argc; ++i) + { + if (const char *arg = argv[i]; arg[0] == '-') + { + if (0 == strcmp(arg, "-h") || 0 == strcmp(arg, "--help")) + { + print_usage(argv[0]); + return 0; + } + else if (0 == strcmp(arg, "--version")) + { + printf("%s\n", VERSION_STRING_PRODUCT); + return 0; + } + else if (0 == strcmp(arg, "-D")) + { + char *macro = argv[++i]; + char *value = strchr(macro, '='); + if (value) *value++ = '\0'; + pp.add_macro_definition(macro, value ? value : "1"); + } + else if (0 == strcmp(arg, "-I")) + { + pp.add_include_path(argv[++i]); + } + else if (0 == strcmp(arg, "-P")) + { + preprocess = argv[++i]; + } + else if (0 == strcmp(arg, "-Fe")) + { + errorfile = argv[++i]; + } + else if (0 == strcmp(arg, "-Fo")) + { + objectfile = argv[++i]; + } + else if (0 == strcmp(arg, "-Zi")) + { + debug_info = true; + } + else if (0 == strcmp(arg, "--glsl")) + { + print_glsl = true; + } + else if (0 == strcmp(arg, "--hlsl")) + { + print_hlsl = true; + } + else if (0 == strcmp(arg, "--shader-model")) + { + shader_model = std::strtol(argv[++i], nullptr, 10); + } + } + else + { + if (filename != nullptr) + { + std::cout << "error: More than one input file specified" << std::endl; + return 1; + } + + filename = arg; + } + } + + if (filename == nullptr) + { + print_usage(argv[0]); + return 1; + } + + if (!pp.append_file(filename)) + { + if (errorfile == nullptr) + std::cout << pp.errors() << std::endl; + else + std::ofstream(errorfile) << pp.errors(); + return 1; + } + + if (preprocess != nullptr) + { + if (strcmp(preprocess, "-") == 0) + std::cout << pp.output() << std::endl; + else + std::ofstream(preprocess) << pp.output(); + return 0; + } + + std::unique_ptr backend; + if (print_glsl) + backend.reset(reshadefx::create_codegen_glsl(debug_info, false)); + else if (print_hlsl) + backend.reset(reshadefx::create_codegen_hlsl(shader_model, debug_info, false)); + else + backend.reset(reshadefx::create_codegen_spirv(debug_info, false)); + + if (!parser.parse(pp.output(), backend.get())) + { + if (errorfile == nullptr) + std::cout << pp.errors() << parser.errors() << std::endl; + else + std::ofstream(errorfile) << pp.errors() << parser.errors(); + return 1; + } + + reshadefx::module module; + backend->write_result(module); + + if (print_glsl || print_hlsl) + { + std::cout << module.hlsl << std::endl; + } + else if (objectfile != nullptr) + { + std::ofstream(objectfile, std::ios::binary).write( + reinterpret_cast(module.spirv.data()), module.spirv.size() * sizeof(uint32_t)); + } + + return 0; +} diff --git a/msvc/tools/verbuild.exe b/msvc/tools/verbuild.exe new file mode 100644 index 0000000000000000000000000000000000000000..e9a6d8aa2a91c5570dfc5e49c926e624c70d2a2d GIT binary patch literal 34816 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 Date: Mon, 10 Jun 2019 16:34:25 -0600 Subject: [PATCH 38/55] move .gitmodules to top level --- msvc/.gitmodules => .gitmodules | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename msvc/.gitmodules => .gitmodules (100%) diff --git a/msvc/.gitmodules b/.gitmodules similarity index 100% rename from msvc/.gitmodules rename to .gitmodules From ec22e41fc8fc84014c4691dff4b97b26c647b0b1 Mon Sep 17 00:00:00 2001 From: Aubrey Buchanan Date: Mon, 10 Jun 2019 17:28:29 -0600 Subject: [PATCH 39/55] Revert "move .gitmodules to top level" This reverts commit 7d41476e20232317e1937bdd49b43d74b702dea8. --- .gitmodules => msvc/.gitmodules | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .gitmodules => msvc/.gitmodules (100%) diff --git a/.gitmodules b/msvc/.gitmodules similarity index 100% rename from .gitmodules rename to msvc/.gitmodules From c1b93f26800251af562cad1f369067f7443e0255 Mon Sep 17 00:00:00 2001 From: Aubrey Buchanan Date: Mon, 10 Jun 2019 17:28:32 -0600 Subject: [PATCH 40/55] Revert "merge" This reverts commit fcf50361a61c0b28c18a4568980a0180c60f6b4c. --- msvc/.editorconfig | 7 - msvc/.gitmodules | 18 - msvc/Common.props | 8 - msvc/LICENSE.md | 9 - msvc/README.md | 22 - msvc/ReShade.vcxproj | 471 --- msvc/ReShade.vcxproj.filters | 336 -- msvc/ReShadeFX.vcxproj | 160 - msvc/ReShadeFX.vcxproj.filters | 24 - msvc/ReShadeFXC.vcxproj | 167 - msvc/ReShadeFXC.vcxproj.filters | 9 - msvc/UTF8.cpp | 265 -- msvc/angrylion-plus-x.sln | 170 - msvc/deps/ImGui.props | 10 - msvc/deps/ImGui.vcxproj | 73 - msvc/deps/MinHook.props | 9 - msvc/deps/MinHook.vcxproj | 76 - msvc/deps/MinHook.vcxproj.filters | 44 - msvc/deps/SPIRV.props | 9 - msvc/deps/Windows.props | 9 - msvc/deps/gl3w.props | 9 - msvc/deps/gl3w.vcxproj | 91 - msvc/deps/stb.props | 10 - msvc/deps/stb.vcxproj | 75 - msvc/deps/stb_image_dds/stb_image_dds.h | 586 ---- msvc/deps/stb_impl.c | 9 - msvc/deps/utfcpp.props | 9 - msvc/res/exports.def | 433 --- msvc/res/resource.h | Bin 1354 -> 0 bytes msvc/res/resource.rc | Bin 3738 -> 0 bytes msvc/res/resource.rc2 | Bin 2846 -> 0 bytes msvc/res/shader_copy_ps.hlsl | 8 - msvc/res/shader_fullscreen_vs.hlsl | 6 - msvc/res/shader_imgui_ps.hlsl | 8 - msvc/res/shader_imgui_vs.hlsl | 24 - msvc/res/shader_mipmap_cs.hlsl | 17 - msvc/res/version.h | 14 - msvc/setup/FodyWeavers.xml | 4 - msvc/setup/Glass.cs | 113 - msvc/setup/IniFile.cs | 22 - msvc/setup/PEInfo.cs | 115 - msvc/setup/Packages.config | 7 - msvc/setup/Properties/App.xaml | 5 - msvc/setup/Properties/Assembly.manifest | 24 - msvc/setup/Properties/AssemblyInfo.cs | 9 - msvc/setup/Properties/Icon.ico | Bin 68582 -> 0 bytes msvc/setup/ReShade Setup.csproj | 163 - msvc/setup/Select.xaml | 28 - msvc/setup/Select.xaml.cs | 73 - msvc/setup/Settings.xaml | 55 - msvc/setup/Settings.xaml.cs | 158 - msvc/setup/Wizard.xaml | 87 - msvc/setup/Wizard.xaml.cs | 496 --- msvc/source/com_ptr.hpp | 121 - msvc/source/d3d10/d3d10.cpp | 100 - msvc/source/d3d10/d3d10_device.cpp | 560 ---- msvc/source/d3d10/d3d10_device.hpp | 144 - msvc/source/d3d10/draw_call_tracker.cpp | 285 -- msvc/source/d3d10/draw_call_tracker.hpp | 86 - msvc/source/d3d10/runtime_d3d10.cpp | 1586 --------- msvc/source/d3d10/runtime_d3d10.hpp | 116 - msvc/source/d3d10/state_block.cpp | 117 - msvc/source/d3d10/state_block.hpp | 56 - msvc/source/d3d11/d3d11.cpp | 99 - msvc/source/d3d11/d3d11_device.cpp | 493 --- msvc/source/d3d11/d3d11_device.hpp | 122 - msvc/source/d3d11/d3d11_device_context.cpp | 857 ----- msvc/source/d3d11/d3d11_device_context.hpp | 200 -- msvc/source/d3d11/draw_call_tracker.cpp | 285 -- msvc/source/d3d11/draw_call_tracker.hpp | 86 - msvc/source/d3d11/runtime_d3d11.cpp | 1596 --------- msvc/source/d3d11/runtime_d3d11.hpp | 119 - msvc/source/d3d11/state_block.cpp | 166 - msvc/source/d3d11/state_block.hpp | 70 - msvc/source/d3d12/d3d12.cpp | 92 - msvc/source/d3d12/d3d12_command_queue.cpp | 126 - msvc/source/d3d12/d3d12_command_queue.hpp | 47 - msvc/source/d3d12/d3d12_device.cpp | 396 --- msvc/source/d3d12/d3d12_device.hpp | 103 - msvc/source/d3d12/runtime_d3d12.cpp | 1403 -------- msvc/source/d3d12/runtime_d3d12.hpp | 96 - msvc/source/d3d9/d3d9.cpp | 207 -- msvc/source/d3d9/d3d9_device.cpp | 912 ------ msvc/source/d3d9/d3d9_device.hpp | 171 - msvc/source/d3d9/d3d9_profiling.cpp | 48 - msvc/source/d3d9/d3d9_swapchain.cpp | 149 - msvc/source/d3d9/d3d9_swapchain.hpp | 48 - msvc/source/d3d9/runtime_d3d9.cpp | 1711 ---------- msvc/source/d3d9/runtime_d3d9.hpp | 127 - msvc/source/d3d9/state_block.cpp | 61 - msvc/source/d3d9/state_block.hpp | 35 - msvc/source/dllmain.cpp | 406 --- msvc/source/dxgi/dxgi.cpp | 341 -- msvc/source/dxgi/dxgi_d3d10.cpp | 38 - msvc/source/dxgi/dxgi_device.cpp | 178 - msvc/source/dxgi/dxgi_device.hpp | 57 - msvc/source/dxgi/dxgi_swapchain.cpp | 479 --- msvc/source/dxgi/dxgi_swapchain.hpp | 95 - msvc/source/dxgi/format_utils.hpp | 108 - msvc/source/effect_codegen.hpp | 323 -- msvc/source/effect_codegen_glsl.cpp | 1365 -------- msvc/source/effect_codegen_hlsl.cpp | 1355 -------- msvc/source/effect_expression.cpp | 451 --- msvc/source/effect_expression.hpp | 381 --- msvc/source/effect_lexer.cpp | 1004 ------ msvc/source/effect_lexer.hpp | 281 -- msvc/source/effect_parser.cpp | 2872 ----------------- msvc/source/effect_parser.hpp | 89 - msvc/source/effect_preprocessor.cpp | 1068 ------ msvc/source/effect_preprocessor.hpp | 135 - msvc/source/effect_symbol_table.cpp | 399 --- msvc/source/effect_symbol_table.hpp | 102 - .../source/effect_symbol_table_intrinsics.inl | 1705 ---------- msvc/source/gui.cpp | 2313 ------------- msvc/source/gui_code_editor.cpp | 1518 --------- msvc/source/gui_code_editor.hpp | 165 - msvc/source/gui_widgets.cpp | 512 --- msvc/source/gui_widgets.hpp | 31 - msvc/source/hook.cpp | 74 - msvc/source/hook.hpp | 73 - msvc/source/hook_manager.cpp | 463 --- msvc/source/hook_manager.hpp | 66 - msvc/source/ini_file.cpp | 145 - msvc/source/ini_file.hpp | 196 -- msvc/source/input.cpp | 581 ---- msvc/source/input.hpp | 72 - msvc/source/log.cpp | 73 - msvc/source/log.hpp | 82 - msvc/source/moving_average.hpp | 38 - msvc/source/opengl/opengl.hpp | 1367 -------- msvc/source/opengl/opengl_hooks.cpp | 2163 ------------- msvc/source/opengl/opengl_hooks.hpp | 195 -- msvc/source/opengl/opengl_hooks_wgl.cpp | 1088 ------- msvc/source/opengl/runtime_opengl.cpp | 1229 ------- msvc/source/opengl/runtime_opengl.hpp | 117 - msvc/source/opengl/state_block.cpp | 137 - msvc/source/opengl/state_block.hpp | 54 - msvc/source/resource_loading.cpp | 36 - msvc/source/resource_loading.hpp | 24 - msvc/source/runtime.cpp | 1283 -------- msvc/source/runtime.hpp | 368 --- msvc/source/runtime_objects.hpp | 151 - msvc/source/update_check.cpp | 59 - msvc/source/windows/user32.cpp | 91 - msvc/source/windows/ws2_32.cpp | 88 - msvc/tools/7za.exe | Bin 1144320 -> 0 bytes msvc/tools/fxc.cpp | 179 - msvc/tools/verbuild.exe | Bin 34816 -> 0 bytes 148 files changed, 45813 deletions(-) delete mode 100644 msvc/.editorconfig delete mode 100644 msvc/.gitmodules delete mode 100644 msvc/Common.props delete mode 100644 msvc/LICENSE.md delete mode 100644 msvc/README.md delete mode 100644 msvc/ReShade.vcxproj delete mode 100644 msvc/ReShade.vcxproj.filters delete mode 100644 msvc/ReShadeFX.vcxproj delete mode 100644 msvc/ReShadeFX.vcxproj.filters delete mode 100644 msvc/ReShadeFXC.vcxproj delete mode 100644 msvc/ReShadeFXC.vcxproj.filters delete mode 100644 msvc/UTF8.cpp delete mode 100644 msvc/deps/ImGui.props delete mode 100644 msvc/deps/ImGui.vcxproj delete mode 100644 msvc/deps/MinHook.props delete mode 100644 msvc/deps/MinHook.vcxproj delete mode 100644 msvc/deps/MinHook.vcxproj.filters delete mode 100644 msvc/deps/SPIRV.props delete mode 100644 msvc/deps/Windows.props delete mode 100644 msvc/deps/gl3w.props delete mode 100644 msvc/deps/gl3w.vcxproj delete mode 100644 msvc/deps/stb.props delete mode 100644 msvc/deps/stb.vcxproj delete mode 100644 msvc/deps/stb_image_dds/stb_image_dds.h delete mode 100644 msvc/deps/stb_impl.c delete mode 100644 msvc/deps/utfcpp.props delete mode 100644 msvc/res/exports.def delete mode 100644 msvc/res/resource.h delete mode 100644 msvc/res/resource.rc delete mode 100644 msvc/res/resource.rc2 delete mode 100644 msvc/res/shader_copy_ps.hlsl delete mode 100644 msvc/res/shader_fullscreen_vs.hlsl delete mode 100644 msvc/res/shader_imgui_ps.hlsl delete mode 100644 msvc/res/shader_imgui_vs.hlsl delete mode 100644 msvc/res/shader_mipmap_cs.hlsl delete mode 100644 msvc/res/version.h delete mode 100644 msvc/setup/FodyWeavers.xml delete mode 100644 msvc/setup/Glass.cs delete mode 100644 msvc/setup/IniFile.cs delete mode 100644 msvc/setup/PEInfo.cs delete mode 100644 msvc/setup/Packages.config delete mode 100644 msvc/setup/Properties/App.xaml delete mode 100644 msvc/setup/Properties/Assembly.manifest delete mode 100644 msvc/setup/Properties/AssemblyInfo.cs delete mode 100644 msvc/setup/Properties/Icon.ico delete mode 100644 msvc/setup/ReShade Setup.csproj delete mode 100644 msvc/setup/Select.xaml delete mode 100644 msvc/setup/Select.xaml.cs delete mode 100644 msvc/setup/Settings.xaml delete mode 100644 msvc/setup/Settings.xaml.cs delete mode 100644 msvc/setup/Wizard.xaml delete mode 100644 msvc/setup/Wizard.xaml.cs delete mode 100644 msvc/source/com_ptr.hpp delete mode 100644 msvc/source/d3d10/d3d10.cpp delete mode 100644 msvc/source/d3d10/d3d10_device.cpp delete mode 100644 msvc/source/d3d10/d3d10_device.hpp delete mode 100644 msvc/source/d3d10/draw_call_tracker.cpp delete mode 100644 msvc/source/d3d10/draw_call_tracker.hpp delete mode 100644 msvc/source/d3d10/runtime_d3d10.cpp delete mode 100644 msvc/source/d3d10/runtime_d3d10.hpp delete mode 100644 msvc/source/d3d10/state_block.cpp delete mode 100644 msvc/source/d3d10/state_block.hpp delete mode 100644 msvc/source/d3d11/d3d11.cpp delete mode 100644 msvc/source/d3d11/d3d11_device.cpp delete mode 100644 msvc/source/d3d11/d3d11_device.hpp delete mode 100644 msvc/source/d3d11/d3d11_device_context.cpp delete mode 100644 msvc/source/d3d11/d3d11_device_context.hpp delete mode 100644 msvc/source/d3d11/draw_call_tracker.cpp delete mode 100644 msvc/source/d3d11/draw_call_tracker.hpp delete mode 100644 msvc/source/d3d11/runtime_d3d11.cpp delete mode 100644 msvc/source/d3d11/runtime_d3d11.hpp delete mode 100644 msvc/source/d3d11/state_block.cpp delete mode 100644 msvc/source/d3d11/state_block.hpp delete mode 100644 msvc/source/d3d12/d3d12.cpp delete mode 100644 msvc/source/d3d12/d3d12_command_queue.cpp delete mode 100644 msvc/source/d3d12/d3d12_command_queue.hpp delete mode 100644 msvc/source/d3d12/d3d12_device.cpp delete mode 100644 msvc/source/d3d12/d3d12_device.hpp delete mode 100644 msvc/source/d3d12/runtime_d3d12.cpp delete mode 100644 msvc/source/d3d12/runtime_d3d12.hpp delete mode 100644 msvc/source/d3d9/d3d9.cpp delete mode 100644 msvc/source/d3d9/d3d9_device.cpp delete mode 100644 msvc/source/d3d9/d3d9_device.hpp delete mode 100644 msvc/source/d3d9/d3d9_profiling.cpp delete mode 100644 msvc/source/d3d9/d3d9_swapchain.cpp delete mode 100644 msvc/source/d3d9/d3d9_swapchain.hpp delete mode 100644 msvc/source/d3d9/runtime_d3d9.cpp delete mode 100644 msvc/source/d3d9/runtime_d3d9.hpp delete mode 100644 msvc/source/d3d9/state_block.cpp delete mode 100644 msvc/source/d3d9/state_block.hpp delete mode 100644 msvc/source/dllmain.cpp delete mode 100644 msvc/source/dxgi/dxgi.cpp delete mode 100644 msvc/source/dxgi/dxgi_d3d10.cpp delete mode 100644 msvc/source/dxgi/dxgi_device.cpp delete mode 100644 msvc/source/dxgi/dxgi_device.hpp delete mode 100644 msvc/source/dxgi/dxgi_swapchain.cpp delete mode 100644 msvc/source/dxgi/dxgi_swapchain.hpp delete mode 100644 msvc/source/dxgi/format_utils.hpp delete mode 100644 msvc/source/effect_codegen.hpp delete mode 100644 msvc/source/effect_codegen_glsl.cpp delete mode 100644 msvc/source/effect_codegen_hlsl.cpp delete mode 100644 msvc/source/effect_expression.cpp delete mode 100644 msvc/source/effect_expression.hpp delete mode 100644 msvc/source/effect_lexer.cpp delete mode 100644 msvc/source/effect_lexer.hpp delete mode 100644 msvc/source/effect_parser.cpp delete mode 100644 msvc/source/effect_parser.hpp delete mode 100644 msvc/source/effect_preprocessor.cpp delete mode 100644 msvc/source/effect_preprocessor.hpp delete mode 100644 msvc/source/effect_symbol_table.cpp delete mode 100644 msvc/source/effect_symbol_table.hpp delete mode 100644 msvc/source/effect_symbol_table_intrinsics.inl delete mode 100644 msvc/source/gui.cpp delete mode 100644 msvc/source/gui_code_editor.cpp delete mode 100644 msvc/source/gui_code_editor.hpp delete mode 100644 msvc/source/gui_widgets.cpp delete mode 100644 msvc/source/gui_widgets.hpp delete mode 100644 msvc/source/hook.cpp delete mode 100644 msvc/source/hook.hpp delete mode 100644 msvc/source/hook_manager.cpp delete mode 100644 msvc/source/hook_manager.hpp delete mode 100644 msvc/source/ini_file.cpp delete mode 100644 msvc/source/ini_file.hpp delete mode 100644 msvc/source/input.cpp delete mode 100644 msvc/source/input.hpp delete mode 100644 msvc/source/log.cpp delete mode 100644 msvc/source/log.hpp delete mode 100644 msvc/source/moving_average.hpp delete mode 100644 msvc/source/opengl/opengl.hpp delete mode 100644 msvc/source/opengl/opengl_hooks.cpp delete mode 100644 msvc/source/opengl/opengl_hooks.hpp delete mode 100644 msvc/source/opengl/opengl_hooks_wgl.cpp delete mode 100644 msvc/source/opengl/runtime_opengl.cpp delete mode 100644 msvc/source/opengl/runtime_opengl.hpp delete mode 100644 msvc/source/opengl/state_block.cpp delete mode 100644 msvc/source/opengl/state_block.hpp delete mode 100644 msvc/source/resource_loading.cpp delete mode 100644 msvc/source/resource_loading.hpp delete mode 100644 msvc/source/runtime.cpp delete mode 100644 msvc/source/runtime.hpp delete mode 100644 msvc/source/runtime_objects.hpp delete mode 100644 msvc/source/update_check.cpp delete mode 100644 msvc/source/windows/user32.cpp delete mode 100644 msvc/source/windows/ws2_32.cpp delete mode 100644 msvc/tools/7za.exe delete mode 100644 msvc/tools/fxc.cpp delete mode 100644 msvc/tools/verbuild.exe diff --git a/msvc/.editorconfig b/msvc/.editorconfig deleted file mode 100644 index 8c8cbd5..0000000 --- a/msvc/.editorconfig +++ /dev/null @@ -1,7 +0,0 @@ -root = true - -[*] -indent_size = 4 -indent_style = tab -insert_final_newline = true -trim_trailing_whitespace = true diff --git a/msvc/.gitmodules b/msvc/.gitmodules deleted file mode 100644 index a2bbc98..0000000 --- a/msvc/.gitmodules +++ /dev/null @@ -1,18 +0,0 @@ -[submodule "imgui"] - path = deps/imgui - url = https://github.com/ocornut/imgui.git -[submodule "minhook"] - path = deps/minhook - url = https://github.com/TsudaKageyu/minhook.git -[submodule "stb"] - path = deps/stb - url = https://github.com/nothings/stb.git -[submodule "gl3w"] - path = deps/gl3w - url = https://github.com/skaslev/gl3w.git -[submodule "utfcpp"] - path = deps/utfcpp - url = https://github.com/nemtrif/utfcpp -[submodule "spirv"] - path = deps/spirv - url = https://github.com/KhronosGroup/SPIRV-Headers/ diff --git a/msvc/Common.props b/msvc/Common.props deleted file mode 100644 index 78fd2f3..0000000 --- a/msvc/Common.props +++ /dev/null @@ -1,8 +0,0 @@ - - - - - $(SolutionDir)bin\$(Platform)\$(Configuration)\ - $(SolutionDir)intermediate\$(ProjectName)\$(Platform)\$(Configuration)\ - - diff --git a/msvc/LICENSE.md b/msvc/LICENSE.md deleted file mode 100644 index 52d2965..0000000 --- a/msvc/LICENSE.md +++ /dev/null @@ -1,9 +0,0 @@ -Copyright 2014 Patrick Mours. All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - - * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/msvc/README.md b/msvc/README.md deleted file mode 100644 index 6f72542..0000000 --- a/msvc/README.md +++ /dev/null @@ -1,22 +0,0 @@ -ReShade -======= - -This is a generic post-processing injector for games and video software. It exposes an automated way to access both frame color and depth information and a custom shader language called ReShade FX to write effects like ambient occlusion, depth of field, color correction and more which work everywhere. - -## Building - -You'll need both Git and Visual Studio 2017 or higher to build ReShade. Latter is required since the project makes use of several C++14 and C++17 features. Additionally a Python 2.7.9 or later (Python 3 is supported as well) installation is necessary for the `gl3w` dependency to build. - -1. Clone this repository including all Git submodules -2. Open the Visual Studio solution -3. Select either the "32-bit" or "64-bit" target platform and build the solution (this will build ReShade and all dependencies). - -After the first build, a `version.h` file will show up in the [res](/res) directory. Change the `VERSION_FULL` definition inside to something matching the current release version and rebuild so that shaders from the official repository at https://github.com/crosire/reshade-shaders won't cause a version mismatch error during compilation. - -## Contributing - -Any contributions to the project are welcomed, it's recommended to use GitHub [pull requests](https://help.github.com/articles/using-pull-requests/). - -## License - -All source code in this repository is licensed under a [BSD 3-clause license](LICENSE.md). diff --git a/msvc/ReShade.vcxproj b/msvc/ReShade.vcxproj deleted file mode 100644 index 51cee10..0000000 --- a/msvc/ReShade.vcxproj +++ /dev/null @@ -1,471 +0,0 @@ - - - - - Debug - Win32 - - - Debug - x64 - - - Debug App - Win32 - - - Debug App - x64 - - - Release - Win32 - - - Release - x64 - - - Release App - Win32 - - - Release App - x64 - - - - {0401ADF5-D085-4A3D-95B2-D9B7896BB338} - Win32Proj - 10.0.17763.0 - reshade - - - - Unicode - v141 - - - ReShade32 - - - ReShade64 - - - DynamicLibrary - true - true - - - Application - true - true - - - DynamicLibrary - false - false - true - - - Application - false - false - true - - - - - - - - - - - - - - - - Level4 - true - Disabled - $(SolutionDir)res;$(SolutionDir)source;%(AdditionalIncludeDirectories) - RESHADE_GUI;RESHADE_VERBOSE_LOG;WIN32_LEAN_AND_MEAN;NOMINMAX;_SILENCE_ALL_CXX17_DEPRECATION_WARNINGS;_GDI32_;WINSOCK_API_LINKAGE=;WIN32;_DEBUG;%(PreprocessorDefinitions) - 4351;%(DisableSpecificWarnings) - $(IntDir)%(RelativeDir) - MultiThreadedDebugDLL - stdcpplatest - - - Windows - true - $(SolutionDir)bin\$(Platform)\$(Configuration);%(AdditionalLibraryDirectories) - res\exports.def - - - 4.0 - $(SolutionDir)res\%(Filename).cso - - - WIN32;_DEBUG;%(PreprocessorDefinitions) - - - tools\verbuild res\version.h -c - - - - - Level4 - true - Disabled - $(SolutionDir)res;$(SolutionDir)source;%(AdditionalIncludeDirectories) - RESHADE_GUI;RESHADE_VERBOSE_LOG;WIN32_LEAN_AND_MEAN;NOMINMAX;_SILENCE_ALL_CXX17_DEPRECATION_WARNINGS;_GDI32_;WINSOCK_API_LINKAGE=;WIN64;_DEBUG;%(PreprocessorDefinitions) - 4351;%(DisableSpecificWarnings) - $(IntDir)%(RelativeDir) - MultiThreadedDebugDLL - stdcpplatest - - - Windows - true - $(SolutionDir)bin\$(Platform)\$(Configuration);%(AdditionalLibraryDirectories) - res\exports.def - - - 4.0 - $(SolutionDir)res\%(Filename).cso - - - WIN64;_DEBUG;%(PreprocessorDefinitions) - - - tools\verbuild res\version.h -c - - - - - Level4 - true - Disabled - $(SolutionDir)res;$(SolutionDir)source;%(AdditionalIncludeDirectories) - RESHADE_GUI;RESHADE_TEST_APPLICATION;RESHADE_VERBOSE_LOG;D3D_DEBUG_INFO;WIN32_LEAN_AND_MEAN;NOMINMAX;_SILENCE_ALL_CXX17_DEPRECATION_WARNINGS;_GDI32_;WINSOCK_API_LINKAGE=;WIN32;_DEBUG;%(PreprocessorDefinitions) - 4351;%(DisableSpecificWarnings) - $(IntDir)%(RelativeDir) - MultiThreadedDebugDLL - stdcpplatest - - - Windows - true - $(SolutionDir)bin\$(Platform)\Debug;%(AdditionalLibraryDirectories) - res\exports.def - - - 4.0 - $(SolutionDir)res\%(Filename).cso - - - WIN32;_DEBUG;%(PreprocessorDefinitions) - - - tools\verbuild res\version.h -c - - - - - Level4 - true - Disabled - $(SolutionDir)res;$(SolutionDir)source;%(AdditionalIncludeDirectories) - RESHADE_GUI;RESHADE_TEST_APPLICATION;RESHADE_VERBOSE_LOG;D3D_DEBUG_INFO;WIN32_LEAN_AND_MEAN;NOMINMAX;_SILENCE_ALL_CXX17_DEPRECATION_WARNINGS;_GDI32_;WINSOCK_API_LINKAGE=;WIN64;_DEBUG;%(PreprocessorDefinitions) - 4351;%(DisableSpecificWarnings) - $(IntDir)%(RelativeDir) - MultiThreadedDebugDLL - stdcpplatest - - - Windows - true - $(SolutionDir)bin\$(Platform)\Debug;%(AdditionalLibraryDirectories) - res\exports.def - - - 4.0 - $(SolutionDir)res\%(Filename).cso - - - WIN64;_DEBUG;%(PreprocessorDefinitions) - - - tools\verbuild res\version.h -c - - - - - Level3 - true - MaxSpeed - true - true - $(SolutionDir)res;$(SolutionDir)source;%(AdditionalIncludeDirectories) - RESHADE_GUI;WIN32_LEAN_AND_MEAN;NOMINMAX;_SILENCE_ALL_CXX17_DEPRECATION_WARNINGS;_GDI32_;WINSOCK_API_LINKAGE=;WIN32;NDEBUG;%(PreprocessorDefinitions) - 4351;%(DisableSpecificWarnings) - $(IntDir)%(RelativeDir) - MultiThreaded - stdcpplatest - - - Windows - false - false - true - $(SolutionDir)bin\$(Platform)\$(Configuration);%(AdditionalLibraryDirectories) - res\exports.def - - - 4.0 - $(SolutionDir)res\%(Filename).cso - - - WIN32;NDEBUG;%(PreprocessorDefinitions) - - - tools\verbuild res\version.h *.*.*.+ -xFP -b0.0.0.0 -s - - - if exist "res\sign.pfx" signtool sign /f "res\sign.pfx" /t http://timestamp.verisign.com/scripts/timstamp.dll "$(TargetPath)" - - - - - Level3 - true - MaxSpeed - true - true - $(SolutionDir)res;$(SolutionDir)source;%(AdditionalIncludeDirectories) - RESHADE_GUI;WIN32_LEAN_AND_MEAN;NOMINMAX;_SILENCE_ALL_CXX17_DEPRECATION_WARNINGS;_GDI32_;WINSOCK_API_LINKAGE=;WIN64;NDEBUG;%(PreprocessorDefinitions) - 4351;%(DisableSpecificWarnings) - $(IntDir)%(RelativeDir) - MultiThreaded - stdcpplatest - - - Windows - false - false - true - $(SolutionDir)bin\$(Platform)\$(Configuration);%(AdditionalLibraryDirectories) - res\exports.def - - - 4.0 - $(SolutionDir)res\%(Filename).cso - - - WIN64;NDEBUG;%(PreprocessorDefinitions) - - - tools\verbuild res\version.h *.*.*.+ -xFP -b0.0.0.0 -s - - - if exist "res\sign.pfx" signtool sign /f "res\sign.pfx" /t http://timestamp.verisign.com/scripts/timstamp.dll "$(TargetPath)" - - - - - Level3 - true - MaxSpeed - true - true - $(SolutionDir)res;$(SolutionDir)source;%(AdditionalIncludeDirectories) - RESHADE_TEST_APPLICATION;RESHADE_VERBOSE_LOG;WIN32_LEAN_AND_MEAN;NOMINMAX;_SILENCE_ALL_CXX17_DEPRECATION_WARNINGS;_GDI32_;WINSOCK_API_LINKAGE=;WIN32;NDEBUG;%(PreprocessorDefinitions) - 4351;%(DisableSpecificWarnings) - $(IntDir)%(RelativeDir) - MultiThreaded - stdcpplatest - - - Windows - false - false - true - $(SolutionDir)bin\$(Platform)\$(Configuration);%(AdditionalLibraryDirectories) - res\exports.def - true - - - 4.0 - $(SolutionDir)res\%(Filename).cso - - - WIN32;NDEBUG;%(PreprocessorDefinitions) - - - - - Level3 - true - MaxSpeed - true - true - $(SolutionDir)res;$(SolutionDir)source;%(AdditionalIncludeDirectories) - RESHADE_TEST_APPLICATION;RESHADE_VERBOSE_LOG;WIN32_LEAN_AND_MEAN;NOMINMAX;_SILENCE_ALL_CXX17_DEPRECATION_WARNINGS;_GDI32_;WINSOCK_API_LINKAGE=;WIN64;NDEBUG;%(PreprocessorDefinitions) - 4351;%(DisableSpecificWarnings) - $(IntDir)%(RelativeDir) - MultiThreaded - stdcpplatest - - - Windows - false - false - true - $(SolutionDir)bin\$(Platform)\$(Configuration);%(AdditionalLibraryDirectories) - res\exports.def - true - - - 4.0 - $(SolutionDir)res\%(Filename).cso - - - WIN64;NDEBUG;%(PreprocessorDefinitions) - - - - - {09c0d610-9b82-40d8-b37e-0d26e3bff77f} - - - {9a62233b-0b70-4b48-91e8-35aa666bc32e} - - - {783fedfb-5124-4f8c-87bc-70aa8490266b} - - - {723bdef8-4a39-4961-bdab-54074012ff47} - - - {d1c2099b-bec7-4993-8947-01d4a1f7eae2} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Pixel - - - Vertex - - - Pixel - - - Vertex - - - Compute - 5.0 - - - - \ No newline at end of file diff --git a/msvc/ReShade.vcxproj.filters b/msvc/ReShade.vcxproj.filters deleted file mode 100644 index 3cc78a0..0000000 --- a/msvc/ReShade.vcxproj.filters +++ /dev/null @@ -1,336 +0,0 @@ - - - - - {7dea67a5-a2de-4e39-94a8-e8f01b6f070f} - - - {ed3eb34b-86ef-4bb0-b071-44f1163953bf} - - - {ecdb23a8-21dc-4334-b5cf-2ea57d27001d} - - - {d3347d57-7868-467f-a688-fe9c6adc8e5c} - - - {3c3eb495-1eeb-4c7c-b58f-3bbcf24758f6} - - - {63356f14-45b5-4047-9be1-e7e6e8a3f3e5} - - - {d61375df-0b47-4eb2-b2b9-ed8034d7d180} - - - {2ed46921-48e6-4996-a11c-0a246bd56a86} - - - {e905e357-66ed-4246-a0a9-d3b4c84c2d7f} - - - {4d42777e-6ba3-4965-b0dc-88186095f1a9} - - - {78832e2a-8fda-4ae5-aecb-a4e0f5a0df02} - - - {ab5a23b3-e97e-4a41-99a1-89c6f1d54b16} - - - {7f3f7a66-aeb1-4c44-abb4-d7eff03a1247} - - - - - core - - - core\hook - - - core\hook - - - core\runtime - - - core\runtime - - - core\runtime - - - core\runtime - - - core\runtime - - - core\utility - - - core\utility - - - core\utility - - - core\utility - - - hooks\d3d9 - - - hooks\d3d9 - - - hooks\d3d9 - - - hooks\d3d9 - - - hooks\d3d9 - - - hooks\d3d9 - - - hooks\d3d10 - - - hooks\d3d10 - - - hooks\d3d10 - - - hooks\d3d10 - - - hooks\d3d10 - - - hooks\d3d11 - - - hooks\d3d11 - - - hooks\d3d11 - - - hooks\d3d11 - - - hooks\d3d11 - - - hooks\d3d11 - - - hooks\d3d12 - - - hooks\d3d12 - - - hooks\d3d12 - - - hooks\d3d12 - - - hooks\dxgi - - - hooks\dxgi - - - hooks\dxgi - - - hooks\dxgi - - - hooks\opengl - - - hooks\opengl - - - hooks\opengl - - - hooks\opengl - - - hooks\windows - - - hooks\windows - - - core - - - - - core\hook - - - core\hook - - - core\runtime - - - core\runtime - - - core\runtime - - - core\runtime - - - core\runtime - - - core\utility - - - core\utility - - - core\utility - - - core\utility - - - core\utility - - - hooks\d3d9 - - - hooks\d3d9 - - - hooks\d3d9 - - - hooks\d3d9 - - - hooks\d3d10 - - - hooks\d3d10 - - - hooks\d3d10 - - - hooks\d3d10 - - - hooks\d3d11 - - - hooks\d3d11 - - - hooks\d3d11 - - - hooks\d3d11 - - - hooks\d3d11 - - - hooks\d3d12 - - - hooks\d3d12 - - - hooks\d3d12 - - - hooks\dxgi - - - hooks\dxgi - - - hooks\dxgi - - - hooks\opengl - - - hooks\opengl - - - hooks\opengl - - - hooks\opengl - - - resources - - - resources - - - - - resources - - - resources - - - resources - - - resources - - - resources - - - resources - - - resources - - - - - resources - - - resources - - - resources - - - resources - - - resources - - - - - resources - - - \ No newline at end of file diff --git a/msvc/ReShadeFX.vcxproj b/msvc/ReShadeFX.vcxproj deleted file mode 100644 index 3d38355..0000000 --- a/msvc/ReShadeFX.vcxproj +++ /dev/null @@ -1,160 +0,0 @@ - - - - - Debug - Win32 - - - Debug - x64 - - - Release - Win32 - - - Release - x64 - - - - {D1C2099B-BEC7-4993-8947-01D4A1F7EAE2} - Win32Proj - 10.0.17763.0 - reshade-fx - - - - StaticLibrary - Unicode - v141 - - - ReShadeFX32 - - - ReShadeFX64 - - - true - true - - - false - false - true - - - - - - - - - - Level4 - true - Disabled - $(SolutionDir)res;$(SolutionDir)source;%(AdditionalIncludeDirectories) - WIN32;WIN32_LEAN_AND_MEAN;NOMINMAX;_DEBUG;%(PreprocessorDefinitions); - 4351;%(DisableSpecificWarnings) - $(IntDir)%(RelativeDir) - MultiThreadedDebugDLL - /std:c++latest %(AdditionalOptions) - - - Windows - true - $(SolutionDir)\bin\$(Platform)\$(Configuration);%(AdditionalLibraryDirectories) - res\exports.def - - - - - Level4 - true - Disabled - $(SolutionDir)res;$(SolutionDir)source;%(AdditionalIncludeDirectories) - WIN64;WIN32_LEAN_AND_MEAN;NOMINMAX;_DEBUG;%(PreprocessorDefinitions); - 4351;%(DisableSpecificWarnings) - $(IntDir)%(RelativeDir) - MultiThreadedDebugDLL - /std:c++latest %(AdditionalOptions) - - - Windows - true - $(SolutionDir)\bin\$(Platform)\$(Configuration);%(AdditionalLibraryDirectories) - res\exports.def - - - - - Level3 - true - MaxSpeed - true - true - $(SolutionDir)res;$(SolutionDir)source;%(AdditionalIncludeDirectories) - WIN32;WIN32_LEAN_AND_MEAN;NOMINMAX;NDEBUG;%(PreprocessorDefinitions); - 4351;%(DisableSpecificWarnings) - $(IntDir)%(RelativeDir) - MultiThreaded - /std:c++latest %(AdditionalOptions) - - - Windows - false - true - true - $(SolutionDir)\bin\$(Platform)\$(Configuration);%(AdditionalLibraryDirectories) - res\exports.def - - - - - Level3 - true - MaxSpeed - true - true - $(SolutionDir)res;$(SolutionDir)source;%(AdditionalIncludeDirectories) - WIN64;WIN32_LEAN_AND_MEAN;NOMINMAX;NDEBUG;%(PreprocessorDefinitions); - 4351;%(DisableSpecificWarnings) - $(IntDir)%(RelativeDir) - MultiThreaded - /std:c++latest %(AdditionalOptions) - - - Windows - false - true - true - $(SolutionDir)\bin\$(Platform)\$(Configuration);%(AdditionalLibraryDirectories) - res\exports.def - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/msvc/ReShadeFX.vcxproj.filters b/msvc/ReShadeFX.vcxproj.filters deleted file mode 100644 index 0ae9ca7..0000000 --- a/msvc/ReShadeFX.vcxproj.filters +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/msvc/ReShadeFXC.vcxproj b/msvc/ReShadeFXC.vcxproj deleted file mode 100644 index 1b5c95c..0000000 --- a/msvc/ReShadeFXC.vcxproj +++ /dev/null @@ -1,167 +0,0 @@ - - - - - Debug - Win32 - - - Debug - x64 - - - Release - Win32 - - - Release - x64 - - - - {65640687-0740-4681-B018-17DBF33E061C} - Win32Proj - 10.0.17763.0 - reshade-fxc - - - - Application - true - v141 - Unicode - - - Application - true - v141 - Unicode - - - Application - false - v141 - true - Unicode - - - Application - false - v141 - true - Unicode - - - - - - - - - - fxc - - - fxc - - - fxc - - - fxc - - - - Level3 - Disabled - true - _DEBUG;_CONSOLE;%(PreprocessorDefinitions) - true - $(SolutionDir)res;$(SolutionDir)source;%(AdditionalIncludeDirectories) - stdcpplatest - - - Console - true - - - RESHADE_FXC;WIN64;_DEBUG;%(PreprocessorDefinitions) - - - - - Level3 - Disabled - true - _DEBUG;_CONSOLE;%(PreprocessorDefinitions) - true - $(SolutionDir)res;$(SolutionDir)source;%(AdditionalIncludeDirectories) - stdcpplatest - - - Console - true - - - RESHADE_FXC;WIN32;_DEBUG;%(PreprocessorDefinitions) - - - - - Level3 - MaxSpeed - true - true - true - NDEBUG;_CONSOLE;%(PreprocessorDefinitions) - true - $(SolutionDir)res;$(SolutionDir)source;%(AdditionalIncludeDirectories) - stdcpplatest - MultiThreaded - - - Console - true - true - false - - - RESHADE_FXC;WIN64;NDEBUG;%(PreprocessorDefinitions) - - - - - Level3 - MaxSpeed - true - true - true - NDEBUG;_CONSOLE;%(PreprocessorDefinitions) - true - $(SolutionDir)res;$(SolutionDir)source;%(AdditionalIncludeDirectories) - stdcpplatest - MultiThreaded - - - Console - true - true - false - - - RESHADE_FXC;WIN32;NDEBUG;%(PreprocessorDefinitions) - - - - - {d1c2099b-bec7-4993-8947-01d4a1f7eae2} - - - - - - - - - - \ No newline at end of file diff --git a/msvc/ReShadeFXC.vcxproj.filters b/msvc/ReShadeFXC.vcxproj.filters deleted file mode 100644 index 4b67128..0000000 --- a/msvc/ReShadeFXC.vcxproj.filters +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/msvc/UTF8.cpp b/msvc/UTF8.cpp deleted file mode 100644 index 57a362f..0000000 --- a/msvc/UTF8.cpp +++ /dev/null @@ -1,265 +0,0 @@ -// Copyright 2006 Nemanja Trifunovic - -/* -Permission is hereby granted, free of charge, to any person or organization -obtaining a copy of the software and accompanying documentation covered by -this license (the "Software") to use, reproduce, display, distribute, -execute, and transmit the Software, and to prepare derivative works of the -Software, and to permit third-parties to whom the Software is furnished to -do so, all subject to the following: -The copyright notices in the Software and this entire statement, including -the above license grant, this restriction and the following disclaimer, -must be included in all copies of the Software, in whole or in part, and -all derivative works of the Software, unless such copies or derivative -works are solely in the form of machine-executable object code generated by -a source language processor. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT -SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE -FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, -ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -DEALINGS IN THE SOFTWARE. -*/ - - -#ifndef UTF8_FOR_CPP_UNCHECKED_H_2675DCD0_9480_4c0c_B92A_CC14C027B731 -#define UTF8_FOR_CPP_UNCHECKED_H_2675DCD0_9480_4c0c_B92A_CC14C027B731 -#endif - -#include "core.h" - -namespace utf8 -{ - namespace unchecked - { - template - octet_iterator append(uint32_t cp, octet_iterator result) - { - if (cp < 0x80) // one octet - *(result++) = static_cast(cp); - else if (cp < 0x800) { // two octets - *(result++) = static_cast((cp >> 6) | 0xc0); - *(result++) = static_cast((cp & 0x3f) | 0x80); - } - else if (cp < 0x10000) { // three octets - *(result++) = static_cast((cp >> 12) | 0xe0); - *(result++) = static_cast(((cp >> 6) & 0x3f) | 0x80); - *(result++) = static_cast((cp & 0x3f) | 0x80); - } - else { // four octets - *(result++) = static_cast((cp >> 18) | 0xf0); - *(result++) = static_cast(((cp >> 12) & 0x3f) | 0x80); - *(result++) = static_cast(((cp >> 6) & 0x3f) | 0x80); - *(result++) = static_cast((cp & 0x3f) | 0x80); - } - return result; - } - - template - output_iterator replace_invalid(octet_iterator start, octet_iterator end, output_iterator out, uint32_t replacement) - { - while (start != end) { - octet_iterator sequence_start = start; - internal::utf_error err_code = utf8::internal::validate_next(start, end); - switch (err_code) { - case internal::UTF8_OK: - for (octet_iterator it = sequence_start; it != start; ++it) - *out++ = *it; - break; - case internal::NOT_ENOUGH_ROOM: - out = utf8::unchecked::append(replacement, out); - start = end; - break; - case internal::INVALID_LEAD: - out = utf8::unchecked::append(replacement, out); - ++start; - break; - case internal::INCOMPLETE_SEQUENCE: - case internal::OVERLONG_SEQUENCE: - case internal::INVALID_CODE_POINT: - out = utf8::unchecked::append(replacement, out); - ++start; - // just one replacement mark for the sequence - while (start != end && utf8::internal::is_trail(*start)) - ++start; - break; - } - } - return out; - } - - template - inline output_iterator replace_invalid(octet_iterator start, octet_iterator end, output_iterator out) - { - static const uint32_t replacement_marker = utf8::internal::mask16(0xfffd); - return utf8::unchecked::replace_invalid(start, end, out, replacement_marker); - } - - template - uint32_t next(octet_iterator& it) - { - uint32_t cp = utf8::internal::mask8(*it); - typename std::iterator_traits::difference_type length = utf8::internal::sequence_length(it); - switch (length) { - case 1: - break; - case 2: - it++; - cp = ((cp << 6) & 0x7ff) + ((*it) & 0x3f); - break; - case 3: - ++it; - cp = ((cp << 12) & 0xffff) + ((utf8::internal::mask8(*it) << 6) & 0xfff); - ++it; - cp += (*it) & 0x3f; - break; - case 4: - ++it; - cp = ((cp << 18) & 0x1fffff) + ((utf8::internal::mask8(*it) << 12) & 0x3ffff); - ++it; - cp += (utf8::internal::mask8(*it) << 6) & 0xfff; - ++it; - cp += (*it) & 0x3f; - break; - } - ++it; - return cp; - } - - template - uint32_t peek_next(octet_iterator it) - { - return utf8::unchecked::next(it); - } - - template - uint32_t prior(octet_iterator& it) - { - while (utf8::internal::is_trail(*(--it))); - octet_iterator temp = it; - return utf8::unchecked::next(temp); - } - - template - void advance(octet_iterator& it, distance_type n) - { - const distance_type zero(0); - if (n < zero) { - // backward - for (distance_type i = n; i < zero; ++i) - utf8::unchecked::prior(it); - } - else { - // forward - for (distance_type i = zero; i < n; ++i) - utf8::unchecked::next(it); - } - } - - template - typename std::iterator_traits::difference_type - distance(octet_iterator first, octet_iterator last) - { - typename std::iterator_traits::difference_type dist; - for (dist = 0; first < last; ++dist) - utf8::unchecked::next(first); - return dist; - } - - template - octet_iterator utf16to8(u16bit_iterator start, u16bit_iterator end, octet_iterator result) - { - while (start != end) { - uint32_t cp = utf8::internal::mask16(*start++); - // Take care of surrogate pairs first - if (utf8::internal::is_lead_surrogate(cp)) { - uint32_t trail_surrogate = utf8::internal::mask16(*start++); - cp = (cp << 10) + trail_surrogate + internal::SURROGATE_OFFSET; - } - result = utf8::unchecked::append(cp, result); - } - return result; - } - - template - u16bit_iterator utf8to16(octet_iterator start, octet_iterator end, u16bit_iterator result) - { - while (start < end) { - uint32_t cp = utf8::unchecked::next(start); - if (cp > 0xffff) { //make a surrogate pair - *result++ = static_cast((cp >> 10) + internal::LEAD_OFFSET); - *result++ = static_cast((cp & 0x3ff) + internal::TRAIL_SURROGATE_MIN); - } - else - *result++ = static_cast(cp); - } - return result; - } - - template - octet_iterator utf32to8(u32bit_iterator start, u32bit_iterator end, octet_iterator result) - { - while (start != end) - result = utf8::unchecked::append(*(start++), result); - - return result; - } - - template - u32bit_iterator utf8to32(octet_iterator start, octet_iterator end, u32bit_iterator result) - { - while (start < end) - (*result++) = utf8::unchecked::next(start); - - return result; - } - - // The iterator class - template - class iterator : public std::iterator { - octet_iterator it; - public: - iterator() {} - explicit iterator(const octet_iterator& octet_it) : it(octet_it) {} - // the default "big three" are OK - octet_iterator base() const { return it; } - uint32_t operator * () const - { - octet_iterator temp = it; - return utf8::unchecked::next(temp); - } - bool operator == (const iterator& rhs) const - { - return (it == rhs.it); - } - bool operator != (const iterator& rhs) const - { - return !(operator == (rhs)); - } - iterator& operator ++ () - { - ::std::advance(it, utf8::internal::sequence_length(it)); - return *this; - } - iterator operator ++ (int) - { - iterator temp = *this; - ::std::advance(it, utf8::internal::sequence_length(it)); - return temp; - } - iterator& operator -- () - { - utf8::unchecked::prior(it); - return *this; - } - iterator operator -- (int) - { - iterator temp = *this; - utf8::unchecked::prior(it); - return temp; - } - }; // class iterator - - } // namespace utf8::unchecked -} // namespace utf8 diff --git a/msvc/angrylion-plus-x.sln b/msvc/angrylion-plus-x.sln index c36dbd2..4e9d4fc 100644 --- a/msvc/angrylion-plus-x.sln +++ b/msvc/angrylion-plus-x.sln @@ -21,220 +21,50 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "plugin-zilmar", "plugin-zil {13F7DDD7-2282-408A-AEF9-72266E0236DC} = {13F7DDD7-2282-408A-AEF9-72266E0236DC} EndProjectSection EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "reshade", "ReShade.vcxproj", "{0401ADF5-D085-4A3D-95B2-D9B7896BB338}" -EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "reshade-fx", "ReShadeFX.vcxproj", "{D1C2099B-BEC7-4993-8947-01D4A1F7EAE2}" -EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "reshade-fxc", "ReShadeFXC.vcxproj", "{65640687-0740-4681-B018-17DBF33E061C}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "deps", "deps", "{02176CFA-E87C-41C3-AAF5-A8353C33FF11}" -EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "gl3w", "deps\gl3w.vcxproj", "{09C0D610-9B82-40D8-B37E-0D26E3BFF77F}" -EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ImGui", "deps\ImGui.vcxproj", "{9A62233B-0B70-4B48-91E8-35AA666BC32E}" -EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "MinHook", "deps\MinHook.vcxproj", "{783FEDFB-5124-4F8C-87BC-70AA8490266B}" -EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "stb", "deps\stb.vcxproj", "{723BDEF8-4A39-4961-BDAB-54074012FF47}" -EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug App|x64 = Debug App|x64 - Debug App|x86 = Debug App|x86 Debug|x64 = Debug|x64 Debug|x86 = Debug|x86 - Release App|x64 = Release App|x64 - Release App|x86 = Release App|x86 Release|x64 = Release|x64 Release|x86 = Release|x86 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {D86C6E84-3371-4B20-8620-A703FF3F2CC5}.Debug App|x64.ActiveCfg = Debug|x64 - {D86C6E84-3371-4B20-8620-A703FF3F2CC5}.Debug App|x64.Build.0 = Debug|x64 - {D86C6E84-3371-4B20-8620-A703FF3F2CC5}.Debug App|x86.ActiveCfg = Debug|Win32 - {D86C6E84-3371-4B20-8620-A703FF3F2CC5}.Debug App|x86.Build.0 = Debug|Win32 {D86C6E84-3371-4B20-8620-A703FF3F2CC5}.Debug|x64.ActiveCfg = Debug|x64 {D86C6E84-3371-4B20-8620-A703FF3F2CC5}.Debug|x64.Build.0 = Debug|x64 {D86C6E84-3371-4B20-8620-A703FF3F2CC5}.Debug|x86.ActiveCfg = Debug|Win32 {D86C6E84-3371-4B20-8620-A703FF3F2CC5}.Debug|x86.Build.0 = Debug|Win32 - {D86C6E84-3371-4B20-8620-A703FF3F2CC5}.Release App|x64.ActiveCfg = Release|x64 - {D86C6E84-3371-4B20-8620-A703FF3F2CC5}.Release App|x64.Build.0 = Release|x64 - {D86C6E84-3371-4B20-8620-A703FF3F2CC5}.Release App|x86.ActiveCfg = Release|Win32 - {D86C6E84-3371-4B20-8620-A703FF3F2CC5}.Release App|x86.Build.0 = Release|Win32 {D86C6E84-3371-4B20-8620-A703FF3F2CC5}.Release|x64.ActiveCfg = Release|x64 {D86C6E84-3371-4B20-8620-A703FF3F2CC5}.Release|x64.Build.0 = Release|x64 {D86C6E84-3371-4B20-8620-A703FF3F2CC5}.Release|x86.ActiveCfg = Release|Win32 {D86C6E84-3371-4B20-8620-A703FF3F2CC5}.Release|x86.Build.0 = Release|Win32 - {13F7DDD7-2282-408A-AEF9-72266E0236DC}.Debug App|x64.ActiveCfg = Debug|x64 - {13F7DDD7-2282-408A-AEF9-72266E0236DC}.Debug App|x64.Build.0 = Debug|x64 - {13F7DDD7-2282-408A-AEF9-72266E0236DC}.Debug App|x86.ActiveCfg = Debug|Win32 - {13F7DDD7-2282-408A-AEF9-72266E0236DC}.Debug App|x86.Build.0 = Debug|Win32 {13F7DDD7-2282-408A-AEF9-72266E0236DC}.Debug|x64.ActiveCfg = Debug|x64 {13F7DDD7-2282-408A-AEF9-72266E0236DC}.Debug|x64.Build.0 = Debug|x64 {13F7DDD7-2282-408A-AEF9-72266E0236DC}.Debug|x86.ActiveCfg = Debug|Win32 {13F7DDD7-2282-408A-AEF9-72266E0236DC}.Debug|x86.Build.0 = Debug|Win32 - {13F7DDD7-2282-408A-AEF9-72266E0236DC}.Release App|x64.ActiveCfg = Release|x64 - {13F7DDD7-2282-408A-AEF9-72266E0236DC}.Release App|x64.Build.0 = Release|x64 - {13F7DDD7-2282-408A-AEF9-72266E0236DC}.Release App|x86.ActiveCfg = Release|Win32 - {13F7DDD7-2282-408A-AEF9-72266E0236DC}.Release App|x86.Build.0 = Release|Win32 {13F7DDD7-2282-408A-AEF9-72266E0236DC}.Release|x64.ActiveCfg = Release|x64 {13F7DDD7-2282-408A-AEF9-72266E0236DC}.Release|x64.Build.0 = Release|x64 {13F7DDD7-2282-408A-AEF9-72266E0236DC}.Release|x86.ActiveCfg = Release|Win32 {13F7DDD7-2282-408A-AEF9-72266E0236DC}.Release|x86.Build.0 = Release|Win32 - {54BDC4EF-2526-47F7-A73E-C1C0FBC0237C}.Debug App|x64.ActiveCfg = Debug|x64 - {54BDC4EF-2526-47F7-A73E-C1C0FBC0237C}.Debug App|x64.Build.0 = Debug|x64 - {54BDC4EF-2526-47F7-A73E-C1C0FBC0237C}.Debug App|x86.ActiveCfg = Debug|Win32 - {54BDC4EF-2526-47F7-A73E-C1C0FBC0237C}.Debug App|x86.Build.0 = Debug|Win32 {54BDC4EF-2526-47F7-A73E-C1C0FBC0237C}.Debug|x64.ActiveCfg = Debug|x64 {54BDC4EF-2526-47F7-A73E-C1C0FBC0237C}.Debug|x64.Build.0 = Debug|x64 {54BDC4EF-2526-47F7-A73E-C1C0FBC0237C}.Debug|x86.ActiveCfg = Debug|Win32 {54BDC4EF-2526-47F7-A73E-C1C0FBC0237C}.Debug|x86.Build.0 = Debug|Win32 - {54BDC4EF-2526-47F7-A73E-C1C0FBC0237C}.Release App|x64.ActiveCfg = Release|x64 - {54BDC4EF-2526-47F7-A73E-C1C0FBC0237C}.Release App|x64.Build.0 = Release|x64 - {54BDC4EF-2526-47F7-A73E-C1C0FBC0237C}.Release App|x86.ActiveCfg = Release|Win32 - {54BDC4EF-2526-47F7-A73E-C1C0FBC0237C}.Release App|x86.Build.0 = Release|Win32 {54BDC4EF-2526-47F7-A73E-C1C0FBC0237C}.Release|x64.ActiveCfg = Release|x64 {54BDC4EF-2526-47F7-A73E-C1C0FBC0237C}.Release|x64.Build.0 = Release|x64 {54BDC4EF-2526-47F7-A73E-C1C0FBC0237C}.Release|x86.ActiveCfg = Release|Win32 {54BDC4EF-2526-47F7-A73E-C1C0FBC0237C}.Release|x86.Build.0 = Release|Win32 - {722AD25D-C281-48FB-9A58-7699E50F8E6D}.Debug App|x64.ActiveCfg = Debug|x64 - {722AD25D-C281-48FB-9A58-7699E50F8E6D}.Debug App|x64.Build.0 = Debug|x64 - {722AD25D-C281-48FB-9A58-7699E50F8E6D}.Debug App|x86.ActiveCfg = Debug|Win32 - {722AD25D-C281-48FB-9A58-7699E50F8E6D}.Debug App|x86.Build.0 = Debug|Win32 {722AD25D-C281-48FB-9A58-7699E50F8E6D}.Debug|x64.ActiveCfg = Debug|x64 {722AD25D-C281-48FB-9A58-7699E50F8E6D}.Debug|x64.Build.0 = Debug|x64 {722AD25D-C281-48FB-9A58-7699E50F8E6D}.Debug|x86.ActiveCfg = Debug|Win32 {722AD25D-C281-48FB-9A58-7699E50F8E6D}.Debug|x86.Build.0 = Debug|Win32 - {722AD25D-C281-48FB-9A58-7699E50F8E6D}.Release App|x64.ActiveCfg = Release|x64 - {722AD25D-C281-48FB-9A58-7699E50F8E6D}.Release App|x64.Build.0 = Release|x64 - {722AD25D-C281-48FB-9A58-7699E50F8E6D}.Release App|x86.ActiveCfg = Release|Win32 - {722AD25D-C281-48FB-9A58-7699E50F8E6D}.Release App|x86.Build.0 = Release|Win32 {722AD25D-C281-48FB-9A58-7699E50F8E6D}.Release|x64.ActiveCfg = Release|x64 {722AD25D-C281-48FB-9A58-7699E50F8E6D}.Release|x64.Build.0 = Release|x64 {722AD25D-C281-48FB-9A58-7699E50F8E6D}.Release|x86.ActiveCfg = Release|Win32 {722AD25D-C281-48FB-9A58-7699E50F8E6D}.Release|x86.Build.0 = Release|Win32 - {0401ADF5-D085-4A3D-95B2-D9B7896BB338}.Debug App|x64.ActiveCfg = Debug App|x64 - {0401ADF5-D085-4A3D-95B2-D9B7896BB338}.Debug App|x64.Build.0 = Debug App|x64 - {0401ADF5-D085-4A3D-95B2-D9B7896BB338}.Debug App|x86.ActiveCfg = Debug App|Win32 - {0401ADF5-D085-4A3D-95B2-D9B7896BB338}.Debug App|x86.Build.0 = Debug App|Win32 - {0401ADF5-D085-4A3D-95B2-D9B7896BB338}.Debug|x64.ActiveCfg = Debug|x64 - {0401ADF5-D085-4A3D-95B2-D9B7896BB338}.Debug|x64.Build.0 = Debug|x64 - {0401ADF5-D085-4A3D-95B2-D9B7896BB338}.Debug|x86.ActiveCfg = Debug|Win32 - {0401ADF5-D085-4A3D-95B2-D9B7896BB338}.Debug|x86.Build.0 = Debug|Win32 - {0401ADF5-D085-4A3D-95B2-D9B7896BB338}.Release App|x64.ActiveCfg = Release App|x64 - {0401ADF5-D085-4A3D-95B2-D9B7896BB338}.Release App|x64.Build.0 = Release App|x64 - {0401ADF5-D085-4A3D-95B2-D9B7896BB338}.Release App|x86.ActiveCfg = Release App|Win32 - {0401ADF5-D085-4A3D-95B2-D9B7896BB338}.Release App|x86.Build.0 = Release App|Win32 - {0401ADF5-D085-4A3D-95B2-D9B7896BB338}.Release|x64.ActiveCfg = Release|x64 - {0401ADF5-D085-4A3D-95B2-D9B7896BB338}.Release|x64.Build.0 = Release|x64 - {0401ADF5-D085-4A3D-95B2-D9B7896BB338}.Release|x86.ActiveCfg = Release|Win32 - {0401ADF5-D085-4A3D-95B2-D9B7896BB338}.Release|x86.Build.0 = Release|Win32 - {D1C2099B-BEC7-4993-8947-01D4A1F7EAE2}.Debug App|x64.ActiveCfg = Debug|x64 - {D1C2099B-BEC7-4993-8947-01D4A1F7EAE2}.Debug App|x64.Build.0 = Debug|x64 - {D1C2099B-BEC7-4993-8947-01D4A1F7EAE2}.Debug App|x86.ActiveCfg = Debug|Win32 - {D1C2099B-BEC7-4993-8947-01D4A1F7EAE2}.Debug App|x86.Build.0 = Debug|Win32 - {D1C2099B-BEC7-4993-8947-01D4A1F7EAE2}.Debug|x64.ActiveCfg = Debug|x64 - {D1C2099B-BEC7-4993-8947-01D4A1F7EAE2}.Debug|x64.Build.0 = Debug|x64 - {D1C2099B-BEC7-4993-8947-01D4A1F7EAE2}.Debug|x86.ActiveCfg = Debug|Win32 - {D1C2099B-BEC7-4993-8947-01D4A1F7EAE2}.Debug|x86.Build.0 = Debug|Win32 - {D1C2099B-BEC7-4993-8947-01D4A1F7EAE2}.Release App|x64.ActiveCfg = Release|x64 - {D1C2099B-BEC7-4993-8947-01D4A1F7EAE2}.Release App|x64.Build.0 = Release|x64 - {D1C2099B-BEC7-4993-8947-01D4A1F7EAE2}.Release App|x86.ActiveCfg = Release|Win32 - {D1C2099B-BEC7-4993-8947-01D4A1F7EAE2}.Release App|x86.Build.0 = Release|Win32 - {D1C2099B-BEC7-4993-8947-01D4A1F7EAE2}.Release|x64.ActiveCfg = Release|x64 - {D1C2099B-BEC7-4993-8947-01D4A1F7EAE2}.Release|x64.Build.0 = Release|x64 - {D1C2099B-BEC7-4993-8947-01D4A1F7EAE2}.Release|x86.ActiveCfg = Release|Win32 - {D1C2099B-BEC7-4993-8947-01D4A1F7EAE2}.Release|x86.Build.0 = Release|Win32 - {65640687-0740-4681-B018-17DBF33E061C}.Debug App|x64.ActiveCfg = Debug|x64 - {65640687-0740-4681-B018-17DBF33E061C}.Debug App|x64.Build.0 = Debug|x64 - {65640687-0740-4681-B018-17DBF33E061C}.Debug App|x86.ActiveCfg = Debug|Win32 - {65640687-0740-4681-B018-17DBF33E061C}.Debug App|x86.Build.0 = Debug|Win32 - {65640687-0740-4681-B018-17DBF33E061C}.Debug|x64.ActiveCfg = Debug|x64 - {65640687-0740-4681-B018-17DBF33E061C}.Debug|x64.Build.0 = Debug|x64 - {65640687-0740-4681-B018-17DBF33E061C}.Debug|x86.ActiveCfg = Debug|Win32 - {65640687-0740-4681-B018-17DBF33E061C}.Debug|x86.Build.0 = Debug|Win32 - {65640687-0740-4681-B018-17DBF33E061C}.Release App|x64.ActiveCfg = Release|x64 - {65640687-0740-4681-B018-17DBF33E061C}.Release App|x64.Build.0 = Release|x64 - {65640687-0740-4681-B018-17DBF33E061C}.Release App|x86.ActiveCfg = Release|Win32 - {65640687-0740-4681-B018-17DBF33E061C}.Release App|x86.Build.0 = Release|Win32 - {65640687-0740-4681-B018-17DBF33E061C}.Release|x64.ActiveCfg = Release|x64 - {65640687-0740-4681-B018-17DBF33E061C}.Release|x64.Build.0 = Release|x64 - {65640687-0740-4681-B018-17DBF33E061C}.Release|x86.ActiveCfg = Release|Win32 - {65640687-0740-4681-B018-17DBF33E061C}.Release|x86.Build.0 = Release|Win32 - {09C0D610-9B82-40D8-B37E-0D26E3BFF77F}.Debug App|x64.ActiveCfg = Debug|x64 - {09C0D610-9B82-40D8-B37E-0D26E3BFF77F}.Debug App|x64.Build.0 = Debug|x64 - {09C0D610-9B82-40D8-B37E-0D26E3BFF77F}.Debug App|x86.ActiveCfg = Debug|Win32 - {09C0D610-9B82-40D8-B37E-0D26E3BFF77F}.Debug App|x86.Build.0 = Debug|Win32 - {09C0D610-9B82-40D8-B37E-0D26E3BFF77F}.Debug|x64.ActiveCfg = Debug|x64 - {09C0D610-9B82-40D8-B37E-0D26E3BFF77F}.Debug|x64.Build.0 = Debug|x64 - {09C0D610-9B82-40D8-B37E-0D26E3BFF77F}.Debug|x86.ActiveCfg = Debug|Win32 - {09C0D610-9B82-40D8-B37E-0D26E3BFF77F}.Debug|x86.Build.0 = Debug|Win32 - {09C0D610-9B82-40D8-B37E-0D26E3BFF77F}.Release App|x64.ActiveCfg = Release|x64 - {09C0D610-9B82-40D8-B37E-0D26E3BFF77F}.Release App|x64.Build.0 = Release|x64 - {09C0D610-9B82-40D8-B37E-0D26E3BFF77F}.Release App|x86.ActiveCfg = Release|Win32 - {09C0D610-9B82-40D8-B37E-0D26E3BFF77F}.Release App|x86.Build.0 = Release|Win32 - {09C0D610-9B82-40D8-B37E-0D26E3BFF77F}.Release|x64.ActiveCfg = Release|x64 - {09C0D610-9B82-40D8-B37E-0D26E3BFF77F}.Release|x64.Build.0 = Release|x64 - {09C0D610-9B82-40D8-B37E-0D26E3BFF77F}.Release|x86.ActiveCfg = Release|Win32 - {09C0D610-9B82-40D8-B37E-0D26E3BFF77F}.Release|x86.Build.0 = Release|Win32 - {9A62233B-0B70-4B48-91E8-35AA666BC32E}.Debug App|x64.ActiveCfg = Debug|x64 - {9A62233B-0B70-4B48-91E8-35AA666BC32E}.Debug App|x64.Build.0 = Debug|x64 - {9A62233B-0B70-4B48-91E8-35AA666BC32E}.Debug App|x86.ActiveCfg = Debug|Win32 - {9A62233B-0B70-4B48-91E8-35AA666BC32E}.Debug App|x86.Build.0 = Debug|Win32 - {9A62233B-0B70-4B48-91E8-35AA666BC32E}.Debug|x64.ActiveCfg = Debug|x64 - {9A62233B-0B70-4B48-91E8-35AA666BC32E}.Debug|x64.Build.0 = Debug|x64 - {9A62233B-0B70-4B48-91E8-35AA666BC32E}.Debug|x86.ActiveCfg = Debug|Win32 - {9A62233B-0B70-4B48-91E8-35AA666BC32E}.Debug|x86.Build.0 = Debug|Win32 - {9A62233B-0B70-4B48-91E8-35AA666BC32E}.Release App|x64.ActiveCfg = Release|x64 - {9A62233B-0B70-4B48-91E8-35AA666BC32E}.Release App|x64.Build.0 = Release|x64 - {9A62233B-0B70-4B48-91E8-35AA666BC32E}.Release App|x86.ActiveCfg = Release|Win32 - {9A62233B-0B70-4B48-91E8-35AA666BC32E}.Release App|x86.Build.0 = Release|Win32 - {9A62233B-0B70-4B48-91E8-35AA666BC32E}.Release|x64.ActiveCfg = Release|x64 - {9A62233B-0B70-4B48-91E8-35AA666BC32E}.Release|x64.Build.0 = Release|x64 - {9A62233B-0B70-4B48-91E8-35AA666BC32E}.Release|x86.ActiveCfg = Release|Win32 - {9A62233B-0B70-4B48-91E8-35AA666BC32E}.Release|x86.Build.0 = Release|Win32 - {783FEDFB-5124-4F8C-87BC-70AA8490266B}.Debug App|x64.ActiveCfg = Debug|x64 - {783FEDFB-5124-4F8C-87BC-70AA8490266B}.Debug App|x64.Build.0 = Debug|x64 - {783FEDFB-5124-4F8C-87BC-70AA8490266B}.Debug App|x86.ActiveCfg = Debug|Win32 - {783FEDFB-5124-4F8C-87BC-70AA8490266B}.Debug App|x86.Build.0 = Debug|Win32 - {783FEDFB-5124-4F8C-87BC-70AA8490266B}.Debug|x64.ActiveCfg = Debug|x64 - {783FEDFB-5124-4F8C-87BC-70AA8490266B}.Debug|x64.Build.0 = Debug|x64 - {783FEDFB-5124-4F8C-87BC-70AA8490266B}.Debug|x86.ActiveCfg = Debug|Win32 - {783FEDFB-5124-4F8C-87BC-70AA8490266B}.Debug|x86.Build.0 = Debug|Win32 - {783FEDFB-5124-4F8C-87BC-70AA8490266B}.Release App|x64.ActiveCfg = Release|x64 - {783FEDFB-5124-4F8C-87BC-70AA8490266B}.Release App|x64.Build.0 = Release|x64 - {783FEDFB-5124-4F8C-87BC-70AA8490266B}.Release App|x86.ActiveCfg = Release|Win32 - {783FEDFB-5124-4F8C-87BC-70AA8490266B}.Release App|x86.Build.0 = Release|Win32 - {783FEDFB-5124-4F8C-87BC-70AA8490266B}.Release|x64.ActiveCfg = Release|x64 - {783FEDFB-5124-4F8C-87BC-70AA8490266B}.Release|x64.Build.0 = Release|x64 - {783FEDFB-5124-4F8C-87BC-70AA8490266B}.Release|x86.ActiveCfg = Release|Win32 - {783FEDFB-5124-4F8C-87BC-70AA8490266B}.Release|x86.Build.0 = Release|Win32 - {723BDEF8-4A39-4961-BDAB-54074012FF47}.Debug App|x64.ActiveCfg = Debug|x64 - {723BDEF8-4A39-4961-BDAB-54074012FF47}.Debug App|x64.Build.0 = Debug|x64 - {723BDEF8-4A39-4961-BDAB-54074012FF47}.Debug App|x86.ActiveCfg = Debug|Win32 - {723BDEF8-4A39-4961-BDAB-54074012FF47}.Debug App|x86.Build.0 = Debug|Win32 - {723BDEF8-4A39-4961-BDAB-54074012FF47}.Debug|x64.ActiveCfg = Debug|x64 - {723BDEF8-4A39-4961-BDAB-54074012FF47}.Debug|x64.Build.0 = Debug|x64 - {723BDEF8-4A39-4961-BDAB-54074012FF47}.Debug|x86.ActiveCfg = Debug|Win32 - {723BDEF8-4A39-4961-BDAB-54074012FF47}.Debug|x86.Build.0 = Debug|Win32 - {723BDEF8-4A39-4961-BDAB-54074012FF47}.Release App|x64.ActiveCfg = Release|x64 - {723BDEF8-4A39-4961-BDAB-54074012FF47}.Release App|x64.Build.0 = Release|x64 - {723BDEF8-4A39-4961-BDAB-54074012FF47}.Release App|x86.ActiveCfg = Release|Win32 - {723BDEF8-4A39-4961-BDAB-54074012FF47}.Release App|x86.Build.0 = Release|Win32 - {723BDEF8-4A39-4961-BDAB-54074012FF47}.Release|x64.ActiveCfg = Release|x64 - {723BDEF8-4A39-4961-BDAB-54074012FF47}.Release|x64.Build.0 = Release|x64 - {723BDEF8-4A39-4961-BDAB-54074012FF47}.Release|x86.ActiveCfg = Release|Win32 - {723BDEF8-4A39-4961-BDAB-54074012FF47}.Release|x86.Build.0 = Release|Win32 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection - GlobalSection(NestedProjects) = preSolution - {09C0D610-9B82-40D8-B37E-0D26E3BFF77F} = {02176CFA-E87C-41C3-AAF5-A8353C33FF11} - {9A62233B-0B70-4B48-91E8-35AA666BC32E} = {02176CFA-E87C-41C3-AAF5-A8353C33FF11} - {783FEDFB-5124-4F8C-87BC-70AA8490266B} = {02176CFA-E87C-41C3-AAF5-A8353C33FF11} - {723BDEF8-4A39-4961-BDAB-54074012FF47} = {02176CFA-E87C-41C3-AAF5-A8353C33FF11} - EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {2896B5DF-D1A8-4A2B-BB7D-835C07F05863} EndGlobalSection diff --git a/msvc/deps/ImGui.props b/msvc/deps/ImGui.props deleted file mode 100644 index a064474..0000000 --- a/msvc/deps/ImGui.props +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - $(SolutionDir)deps\imgui;%(AdditionalIncludeDirectories) - IMGUI_DEFINE_MATH_OPERATORS;IMGUI_DISABLE_OBSOLETE_FUNCTIONS;%(PreprocessorDefinitions) - - - \ No newline at end of file diff --git a/msvc/deps/ImGui.vcxproj b/msvc/deps/ImGui.vcxproj deleted file mode 100644 index 2301e1c..0000000 --- a/msvc/deps/ImGui.vcxproj +++ /dev/null @@ -1,73 +0,0 @@ - - - - - Debug - Win32 - - - Debug - x64 - - - Release - Win32 - - - Release - x64 - - - - {9A62233B-0B70-4B48-91E8-35AA666BC32E} - 10.0.17763.0 - - - - StaticLibrary - Unicode - v141 - - - - - - - - - MultiThreadedDebugDLL - Disabled - IMGUI_DISABLE_OBSOLETE_FUNCTIONS;%(PreprocessorDefinitions) - - - - - MultiThreadedDebugDLL - Disabled - IMGUI_DISABLE_OBSOLETE_FUNCTIONS;%(PreprocessorDefinitions) - - - - - MultiThreaded - IMGUI_DISABLE_OBSOLETE_FUNCTIONS;NDEBUG;%(PreprocessorDefinitions) - - - - - MultiThreaded - IMGUI_DISABLE_OBSOLETE_FUNCTIONS;NDEBUG;%(PreprocessorDefinitions) - - - - - - - - - - - - - - \ No newline at end of file diff --git a/msvc/deps/MinHook.props b/msvc/deps/MinHook.props deleted file mode 100644 index 04ae54a..0000000 --- a/msvc/deps/MinHook.props +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - $(SolutionDir)deps\minhook\include;%(AdditionalIncludeDirectories) - - - \ No newline at end of file diff --git a/msvc/deps/MinHook.vcxproj b/msvc/deps/MinHook.vcxproj deleted file mode 100644 index df09cf5..0000000 --- a/msvc/deps/MinHook.vcxproj +++ /dev/null @@ -1,76 +0,0 @@ - - - - - Debug - Win32 - - - Debug - x64 - - - Release - Win32 - - - Release - x64 - - - - {783FEDFB-5124-4F8C-87BC-70AA8490266B} - 10.0.17763.0 - - - - StaticLibrary - Unicode - v141 - - - - - - - - - MultiThreadedDebugDLL - Disabled - - - - - MultiThreadedDebugDLL - Disabled - - - - - MultiThreaded - - - - - MultiThreaded - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/msvc/deps/MinHook.vcxproj.filters b/msvc/deps/MinHook.vcxproj.filters deleted file mode 100644 index 6717bfb..0000000 --- a/msvc/deps/MinHook.vcxproj.filters +++ /dev/null @@ -1,44 +0,0 @@ - - - - - {4f094667-e67f-4105-b94d-5ffd7bc80831} - - - {6d8d397a-3296-469c-bfc1-0138956f951d} - - - - - HDE - - - HDE - - - - - - - - HDE - - - HDE - - - HDE - - - HDE - - - HDE - - - API - - - - - \ No newline at end of file diff --git a/msvc/deps/SPIRV.props b/msvc/deps/SPIRV.props deleted file mode 100644 index fe8ba74..0000000 --- a/msvc/deps/SPIRV.props +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - $(SolutionDir)deps\spirv\include\spirv\unified1;%(AdditionalIncludeDirectories) - - - \ No newline at end of file diff --git a/msvc/deps/Windows.props b/msvc/deps/Windows.props deleted file mode 100644 index 061e297..0000000 --- a/msvc/deps/Windows.props +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - Shlwapi.lib;WinInet.lib;%(AdditionalDependencies) - - - \ No newline at end of file diff --git a/msvc/deps/gl3w.props b/msvc/deps/gl3w.props deleted file mode 100644 index f823818..0000000 --- a/msvc/deps/gl3w.props +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - $(SolutionDir)deps\gl3w\include;%(AdditionalIncludeDirectories) - - - \ No newline at end of file diff --git a/msvc/deps/gl3w.vcxproj b/msvc/deps/gl3w.vcxproj deleted file mode 100644 index 553aa04..0000000 --- a/msvc/deps/gl3w.vcxproj +++ /dev/null @@ -1,91 +0,0 @@ - - - - - Debug - Win32 - - - Debug - x64 - - - Release - Win32 - - - Release - x64 - - - - {09C0D610-9B82-40D8-B37E-0D26E3BFF77F} - 10.0.17763.0 - - - - StaticLibrary - Unicode - v141 - - - - - - - - - gl3w\include;%(AdditionalIncludeDirectories) - - - - - MultiThreadedDebugDLL - _GDI32_;%(PreprocessorDefinitions) - Disabled - - - - - MultiThreadedDebugDLL - _GDI32_;%(PreprocessorDefinitions) - Disabled - - - - - MultiThreaded - _GDI32_;%(PreprocessorDefinitions) - - - - - MultiThreaded - _GDI32_;%(PreprocessorDefinitions) - - - - - - - - - - - - - $(CleanDependsOn); - CleanGL3WSources; - - - GenerateGL3WSources; - $(BuildDependsOn); - - - - - - - - - \ No newline at end of file diff --git a/msvc/deps/stb.props b/msvc/deps/stb.props deleted file mode 100644 index 4a5733a..0000000 --- a/msvc/deps/stb.props +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - $(SolutionDir)deps\stb;$(SolutionDir)deps\stb_image_dds;%(AdditionalIncludeDirectories) - _CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) - - - \ No newline at end of file diff --git a/msvc/deps/stb.vcxproj b/msvc/deps/stb.vcxproj deleted file mode 100644 index 1043de7..0000000 --- a/msvc/deps/stb.vcxproj +++ /dev/null @@ -1,75 +0,0 @@ - - - - - Debug - Win32 - - - Debug - x64 - - - Release - Win32 - - - Release - x64 - - - - {723BDEF8-4A39-4961-BDAB-54074012FF47} - 10.0.17763.0 - - - - StaticLibrary - Unicode - v141 - - - - - - - - - stb;stb_image_dds;%(AdditionalIncludeDirectories) - - - - - MultiThreadedDebugDLL - Disabled - - - - - MultiThreadedDebugDLL - Disabled - - - - - MultiThreaded - NDEBUG;%(PreprocessorDefinitions) - - - - - MultiThreaded - NDEBUG;%(PreprocessorDefinitions) - - - - - - - - - - - - - \ No newline at end of file diff --git a/msvc/deps/stb_image_dds/stb_image_dds.h b/msvc/deps/stb_image_dds/stb_image_dds.h deleted file mode 100644 index dc757db..0000000 --- a/msvc/deps/stb_image_dds/stb_image_dds.h +++ /dev/null @@ -1,586 +0,0 @@ -// DDS loader -// http://lonesock.net/soil.html - -#ifndef HEADER_STB_IMAGE_DDS_AUGMENTATION -#define HEADER_STB_IMAGE_DDS_AUGMENTATION - -#ifdef __cplusplus -extern "C" { -#endif - -extern int stbi_dds_test_memory(stbi_uc const *buffer, int len); -extern stbi_uc *stbi_dds_load(const char *filename, int *x, int *y, int *comp, int req_comp); -extern stbi_uc *stbi_dds_load_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp); -#ifndef STBI_NO_STDIO -extern int stbi_dds_test_file(FILE *f); -extern stbi_uc *stbi_dds_load_from_file(FILE *f, int *x, int *y, int *comp, int req_comp); -#endif - -#ifdef STB_IMAGE_DDS_IMPLEMENTATION - -typedef struct DDS_HEADER -{ - unsigned int dwSize; - unsigned int dwFlags; - unsigned int dwHeight; - unsigned int dwWidth; - unsigned int dwPitchOrLinearSize; - unsigned int dwDepth; - unsigned int dwMipMapCount; - unsigned int dwReserved1[11]; - - struct DDS_PIXELFORMAT - { - unsigned int dwSize; - unsigned int dwFlags; - unsigned int dwFourCC; - unsigned int dwRGBBitCount; - unsigned int dwRBitMask; - unsigned int dwGBitMask; - unsigned int dwBBitMask; - unsigned int dwAlphaBitMask; - } sPixelFormat; - - struct // DDCAPS2 - { - unsigned int dwCaps1; - unsigned int dwCaps2; - unsigned int dwDDSX; - unsigned int dwReserved; - } sCaps; - - unsigned int dwReserved2; -} DDS_HEADER; -typedef struct DDS_HEADER_DXT10 -{ - unsigned int dxgiFormat; - unsigned int resourceDimension; - unsigned int miscFlag; - unsigned int arraySize; - unsigned int miscFlags2; -} DDS_HEADER_DXT10; - -#define DDSD_CAPS 0x00000001 -#define DDSD_HEIGHT 0x00000002 -#define DDSD_WIDTH 0x00000004 -#define DDSD_PITCH 0x00000008 -#define DDSD_PIXELFORMAT 0x00001000 -#define DDSD_MIPMAPCOUNT 0x00020000 -#define DDSD_LINEARSIZE 0x00080000 -#define DDSD_DEPTH 0x00800000 - -#define DDPF_ALPHAPIXELS 0x00000001 -#define DDPF_ALPHA 0x00000002 -#define DDPF_FOURCC 0x00000004 -#define DDPF_INDEXED 0x00000020 -#define DDPF_RGB 0x00000040 -#define DDPF_YUV 0x00000200 -#define DDPF_LUMINANCE 0x00020000 - -#define DDSCAPS_COMPLEX 0x00000008 -#define DDSCAPS_TEXTURE 0x00001000 -#define DDSCAPS_MIPMAP 0x00400000 - -#define DDSCAPS2_CUBEMAP 0x00000200 -#define DDSCAPS2_CUBEMAP_POSITIVEX 0x00000400 -#define DDSCAPS2_CUBEMAP_NEGATIVEX 0x00000800 -#define DDSCAPS2_CUBEMAP_POSITIVEY 0x00001000 -#define DDSCAPS2_CUBEMAP_NEGATIVEY 0x00002000 -#define DDSCAPS2_CUBEMAP_POSITIVEZ 0x00004000 -#define DDSCAPS2_CUBEMAP_NEGATIVEZ 0x00008000 -#define DDSCAPS2_VOLUME 0x00200000 - -#define MAKEFOURCC(ch0, ch1, ch2, ch3) ((stbi__uint32)(stbi_uc)(ch0) | ((stbi__uint32)(stbi_uc)(ch1) << 8) | ((stbi__uint32)(stbi_uc)(ch2) << 16) | ((stbi__uint32)(stbi_uc)(ch3) << 24 )) - -stbi_inline static int stbi__convert_bit_range(int c, int from_bits, int to_bits) -{ - int b = (1 << (from_bits - 1)) + c * ((1 << to_bits) - 1); - - return (b + (b >> from_bits)) >> from_bits; -} -stbi_inline static void stbi__rgb_888_from_565(unsigned int c, int *r, int *g, int *b) -{ - *r = stbi__convert_bit_range((c >> 11) & 31, 5, 8); - *g = stbi__convert_bit_range((c >> 05) & 63, 6, 8); - *b = stbi__convert_bit_range((c >> 00) & 31, 5, 8); -} -void stbi__dxt_decode_DXT1_block(unsigned char uncompressed[16 * 4], unsigned char compressed[8]) -{ - int next_bit = 4 * 8; - int i, r, g, b; - int c0, c1; - unsigned char decode_colors[4 * 4]; - // find the 2 primary colors - c0 = compressed[0] + (compressed[1] << 8); - c1 = compressed[2] + (compressed[3] << 8); - stbi__rgb_888_from_565(c0, &r, &g, &b); - decode_colors[0] = r; - decode_colors[1] = g; - decode_colors[2] = b; - decode_colors[3] = 255; - stbi__rgb_888_from_565(c1, &r, &g, &b); - decode_colors[4] = r; - decode_colors[5] = g; - decode_colors[6] = b; - decode_colors[7] = 255; - if (c0 > c1) - { - // no alpha, 2 interpolated colors - decode_colors[8] = (2 * decode_colors[0] + decode_colors[4]) / 3; - decode_colors[9] = (2 * decode_colors[1] + decode_colors[5]) / 3; - decode_colors[10] = (2 * decode_colors[2] + decode_colors[6]) / 3; - decode_colors[11] = 255; - decode_colors[12] = (decode_colors[0] + 2 * decode_colors[4]) / 3; - decode_colors[13] = (decode_colors[1] + 2 * decode_colors[5]) / 3; - decode_colors[14] = (decode_colors[2] + 2 * decode_colors[6]) / 3; - decode_colors[15] = 255; - } - else - { - // 1 interpolated color, alpha - decode_colors[8] = (decode_colors[0] + decode_colors[4]) / 2; - decode_colors[9] = (decode_colors[1] + decode_colors[5]) / 2; - decode_colors[10] = (decode_colors[2] + decode_colors[6]) / 2; - decode_colors[11] = 255; - decode_colors[12] = 0; - decode_colors[13] = 0; - decode_colors[14] = 0; - decode_colors[15] = 0; - } - // decode the block - for (i = 0; i < 16 * 4; i += 4) - { - int idx = ((compressed[next_bit >> 3] >> (next_bit & 7)) & 3) * 4; - next_bit += 2; - uncompressed[i + 0] = decode_colors[idx + 0]; - uncompressed[i + 1] = decode_colors[idx + 1]; - uncompressed[i + 2] = decode_colors[idx + 2]; - uncompressed[i + 3] = decode_colors[idx + 3]; - } - // done -} -void stbi__dxt_decode_DXT23_alpha_block(unsigned char uncompressed[16 * 4], unsigned char compressed[8]) -{ - int i, next_bit = 0; - - for (i = 3; i < 16 * 4; i += 4) - { - uncompressed[i] = stbi__convert_bit_range((compressed[next_bit >> 3] >> (next_bit & 7)) & 15, 4, 8); - next_bit += 4; - } -} -void stbi__dxt_decode_DXT45_alpha_block(unsigned char uncompressed[16 * 4], unsigned char compressed[8]) -{ - int i, next_bit = 8 * 2; - unsigned char decode_alpha[8]; - // each alpha value gets 3 bits, and the 1st 2 bytes are the range - decode_alpha[0] = compressed[0]; - decode_alpha[1] = compressed[1]; - if (decode_alpha[0] > decode_alpha[1]) - { - // 6 step intermediate - decode_alpha[2] = (6 * decode_alpha[0] + 1 * decode_alpha[1]) / 7; - decode_alpha[3] = (5 * decode_alpha[0] + 2 * decode_alpha[1]) / 7; - decode_alpha[4] = (4 * decode_alpha[0] + 3 * decode_alpha[1]) / 7; - decode_alpha[5] = (3 * decode_alpha[0] + 4 * decode_alpha[1]) / 7; - decode_alpha[6] = (2 * decode_alpha[0] + 5 * decode_alpha[1]) / 7; - decode_alpha[7] = (1 * decode_alpha[0] + 6 * decode_alpha[1]) / 7; - } - else - { - // 4 step intermediate, pluss full and none - decode_alpha[2] = (4 * decode_alpha[0] + 1 * decode_alpha[1]) / 5; - decode_alpha[3] = (3 * decode_alpha[0] + 2 * decode_alpha[1]) / 5; - decode_alpha[4] = (2 * decode_alpha[0] + 3 * decode_alpha[1]) / 5; - decode_alpha[5] = (1 * decode_alpha[0] + 4 * decode_alpha[1]) / 5; - decode_alpha[6] = 0; - decode_alpha[7] = 255; - } - for (i = 3; i < 16 * 4; i += 4) - { - int idx = 0, bit; - bit = (compressed[next_bit >> 3] >> (next_bit & 7)) & 1; - idx += bit << 0; - ++next_bit; - bit = (compressed[next_bit >> 3] >> (next_bit & 7)) & 1; - idx += bit << 1; - ++next_bit; - bit = (compressed[next_bit >> 3] >> (next_bit & 7)) & 1; - idx += bit << 2; - ++next_bit; - uncompressed[i] = decode_alpha[idx & 7]; - } - // done -} -void stbi__dxt_decode_DXT_color_block(unsigned char uncompressed[16 * 4], unsigned char compressed[8]) -{ - int next_bit = 4 * 8; - int i, r, g, b; - int c0, c1; - unsigned char decode_colors[4 * 3]; - // find the 2 primary colors - c0 = compressed[0] + (compressed[1] << 8); - c1 = compressed[2] + (compressed[3] << 8); - stbi__rgb_888_from_565(c0, &r, &g, &b); - decode_colors[0] = r; - decode_colors[1] = g; - decode_colors[2] = b; - stbi__rgb_888_from_565(c1, &r, &g, &b); - decode_colors[3] = r; - decode_colors[4] = g; - decode_colors[5] = b; - // Like DXT1, but no choicees: - // no alpha, 2 interpolated colors - decode_colors[6] = (2 * decode_colors[0] + decode_colors[3]) / 3; - decode_colors[7] = (2 * decode_colors[1] + decode_colors[4]) / 3; - decode_colors[8] = (2 * decode_colors[2] + decode_colors[5]) / 3; - decode_colors[9] = (decode_colors[0] + 2 * decode_colors[3]) / 3; - decode_colors[10] = (decode_colors[1] + 2 * decode_colors[4]) / 3; - decode_colors[11] = (decode_colors[2] + 2 * decode_colors[5]) / 3; - // decode the block - for (i = 0; i < 16 * 4; i += 4) - { - int idx = ((compressed[next_bit >> 3] >> (next_bit & 7)) & 3) * 3; - next_bit += 2; - uncompressed[i + 0] = decode_colors[idx + 0]; - uncompressed[i + 1] = decode_colors[idx + 1]; - uncompressed[i + 2] = decode_colors[idx + 2]; - } - // done -} - -static int stbi__dds_test(stbi__context *s) -{ - // Check the magic number - if (stbi__get8(s) != 'D') - { - return 0; - } - if (stbi__get8(s) != 'D') - { - return 0; - } - if (stbi__get8(s) != 'S') - { - return 0; - } - if (stbi__get8(s) != ' ') - { - return 0; - } - - return 1; -} - -static stbi_uc *stbi__dds_load(stbi__context *s, int *x, int *y, int *comp, int req_comp) -{ - // All variables go up front - stbi_uc *dds_data = NULL; - stbi_uc block[16 * 4]; - stbi_uc compressed[8]; - int flags, DXT_family; - int has_alpha, has_mipmap; - int is_compressed, cubemap_faces; - int block_pitch, num_blocks; - int i, sz, cf; - DDS_HEADER header; - DDS_HEADER_DXT10 header2; - - // Check the magic number - if (!stbi__dds_test(s)) - { - return NULL; - } - - // Load the header - stbi__getn(s, (stbi_uc *)(&header), sizeof(DDS_HEADER)); - - if (header.dwSize != 124) - { - return NULL; - } - - // Checks - flags = DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH | DDSD_PIXELFORMAT; - - if ((header.dwFlags & flags) != flags) - { - return NULL; - } - - if (header.sPixelFormat.dwSize != 32) - { - return NULL; - } - - flags = DDPF_FOURCC | DDPF_RGB | DDPF_LUMINANCE | DDPF_YUV; - - if ((header.sPixelFormat.dwFlags & flags) == 0) - { - return NULL; - } - - if ((header.sCaps.dwCaps1 & DDSCAPS_TEXTURE) == 0) - { - return NULL; - } - - // Get the image information - *x = s->img_x = header.dwWidth; - *y = s->img_y = header.dwHeight; - *comp = s->img_n = 4; - - is_compressed = (header.sPixelFormat.dwFlags & DDPF_FOURCC) / DDPF_FOURCC; - has_alpha = (header.sPixelFormat.dwFlags & DDPF_ALPHAPIXELS) / DDPF_ALPHAPIXELS; - has_mipmap = (header.sCaps.dwCaps1 & DDSCAPS_MIPMAP) && (header.dwMipMapCount > 1); - cubemap_faces = (header.sCaps.dwCaps2 & DDSCAPS2_CUBEMAP) / DDSCAPS2_CUBEMAP; - - // Cubemaps need to have square faces - cubemap_faces &= (s->img_x == s->img_y); - cubemap_faces *= 5; - cubemap_faces += 1; - - block_pitch = (s->img_x + 3) >> 2; - num_blocks = block_pitch * ((s->img_y + 3) >> 2); - - if (is_compressed) - { -#pragma region Compressed - if (header.sPixelFormat.dwFourCC & MAKEFOURCC('D', 'X', '1', '0')) - { - stbi__getn(s, (stbi_uc *)(&header2), sizeof(DDS_HEADER_DXT10)); - } - - /* compressed */ - // note: header.sPixelFormat.dwFourCC is something like (('D'<<0)|('X'<<8)|('T'<<16)|('1'<<24)) - DXT_family = 1 + (header.sPixelFormat.dwFourCC >> 24) - '1'; - if ((DXT_family < 1) || (DXT_family > 5)) return NULL; - /* check the expected size...oops, nevermind... - those non-compliant writers leave - dwPitchOrLinearSize == 0 */ - // passed all the tests, get the RAM for decoding - sz = (s->img_x)*(s->img_y) * 4 * cubemap_faces; - dds_data = (unsigned char*)malloc(sz); - /* do this once for each face */ - for (cf = 0; cf < cubemap_faces; ++cf) - { - // now read and decode all the blocks - for (i = 0; i < num_blocks; ++i) - { - // where are we? - int bx, by, bw = 4, bh = 4; - int ref_x = 4 * (i % block_pitch); - int ref_y = 4 * (i / block_pitch); - // get the next block's worth of compressed data, and decompress it - if (DXT_family == 1) - { - // DXT1 - stbi__getn(s, compressed, 8); - stbi__dxt_decode_DXT1_block(block, compressed); - } - else if (DXT_family < 4) - { - // DXT2/3 - stbi__getn(s, compressed, 8); - stbi__dxt_decode_DXT23_alpha_block(block, compressed); - stbi__getn(s, compressed, 8); - stbi__dxt_decode_DXT_color_block(block, compressed); - } - else - { - // DXT4/5 - stbi__getn(s, compressed, 8); - stbi__dxt_decode_DXT45_alpha_block(block, compressed); - stbi__getn(s, compressed, 8); - stbi__dxt_decode_DXT_color_block(block, compressed); - } - // is this a partial block? - if (ref_x + 4 > (int)s->img_x) - { - bw = s->img_x - ref_x; - } - if (ref_y + 4 > (int)s->img_y) - { - bh = s->img_y - ref_y; - } - // now drop our decompressed data into the buffer - for (by = 0; by < bh; ++by) - { - int idx = 4 * ((ref_y + by + cf*s->img_x)*s->img_x + ref_x); - for (bx = 0; bx < bw * 4; ++bx) - { - - dds_data[idx + bx] = block[by * 16 + bx]; - } - } - } - /* done reading and decoding the main image... - skip MIPmaps if present */ - if (has_mipmap) - { - int block_size = 16; - if (DXT_family == 1) - { - block_size = 8; - } - for (i = 1; i < (int)header.dwMipMapCount; ++i) - { - int mx = s->img_x >> (i + 2); - int my = s->img_y >> (i + 2); - if (mx < 1) - { - mx = 1; - } - if (my < 1) - { - my = 1; - } - stbi__skip(s, mx*my*block_size); - } - } - }/* per cubemap face */ -#pragma endregion - } - else - { -#pragma region Uncompressed - DXT_family = 0; - - if (header.sPixelFormat.dwRGBBitCount != 0) - { - s->img_n = header.sPixelFormat.dwRGBBitCount / 8; - } - else - { - s->img_n = 3; - if (has_alpha) - { - s->img_n = 4; - } - } - *comp = s->img_n; - sz = s->img_x*s->img_y*s->img_n*cubemap_faces; - dds_data = (unsigned char*)malloc(sz); - /* do this once for each face */ - for (cf = 0; cf < cubemap_faces; ++cf) - { - /* read the main image for this face */ - stbi__getn(s, &dds_data[cf*s->img_x*s->img_y*s->img_n], s->img_x*s->img_y*s->img_n); - /* done reading and decoding the main image... - skip MIPmaps if present */ - if (has_mipmap) - { - for (i = 1; i < (int)header.dwMipMapCount; ++i) - { - int mx = s->img_x >> i; - int my = s->img_y >> i; - if (mx < 1) - { - mx = 1; - } - if (my < 1) - { - my = 1; - } - stbi__skip(s, mx*my*s->img_n); - } - } - } - if (s->img_n >= 3) - { - /* data was BGR, I need it RGB */ - for (i = 0; i < sz; i += s->img_n) - { - unsigned char temp = dds_data[i]; - dds_data[i] = dds_data[i + 2]; - dds_data[i + 2] = temp; - } - } -#pragma endregion - } - - // Finished decompressing into RGBA, adjust the y size if we have a cubemap - s->img_y *= cubemap_faces; - *y = s->img_y; - - // Test if all alpha values are 255 (i.e. no transparency) - has_alpha = 0; - - if (s->img_n == 4) - { - for (i = 3; (i < sz) && (has_alpha == 0); i += 4) - { - has_alpha |= (dds_data[i] < 255); - } - } - - if ((req_comp <= 4) && (req_comp >= 1)) - { - if (req_comp != s->img_n) - { - dds_data = stbi__convert_format(dds_data, s->img_n, req_comp, s->img_x, s->img_y); - *comp = s->img_n; - } - } - else - { - if ((has_alpha == 0) && (s->img_n == 4)) - { - dds_data = stbi__convert_format(dds_data, 4, 3, s->img_x, s->img_y); - *comp = 3; - } - } - - return dds_data; -} - -#ifndef STBI_NO_STDIO -int stbi_dds_test_file(FILE *f) -{ - stbi__context s; - int r, n = ftell(f); - stbi__start_file(&s, f); - r = stbi__dds_test(&s); - fseek(f, n, SEEK_SET); - return r; -} -#endif -int stbi_dds_test_memory(stbi_uc const *buffer, int len) -{ - stbi__context s; - stbi__start_mem(&s, buffer, len); - return stbi__dds_test(&s); -} - -#ifndef STBI_NO_STDIO -stbi_uc *stbi_dds_load_from_file(FILE *f, int *x, int *y, int *comp, int req_comp) -{ - stbi__context s; - stbi__start_file(&s, f); - return stbi__dds_load(&s, x, y, comp, req_comp); -} -stbi_uc *stbi_dds_load(const char *filename, int *x, int *y, int *comp, int req_comp) -{ - stbi_uc *data; - FILE *f = stbi__fopen(filename, "rb"); - if (!f) return NULL; - data = stbi_dds_load_from_file(f, x, y, comp, req_comp); - fclose(f); - return data; -} -#endif - -stbi_uc *stbi_dds_load_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp) -{ - stbi__context s; - stbi__start_mem(&s, buffer, len); - return stbi__dds_load(&s, x, y, comp, req_comp); -} - -#endif - -#ifdef __cplusplus -} -#endif - -#endif // HEADER_STB_IMAGE_DDS_AUGMENTATION diff --git a/msvc/deps/stb_impl.c b/msvc/deps/stb_impl.c deleted file mode 100644 index e8053f9..0000000 --- a/msvc/deps/stb_impl.c +++ /dev/null @@ -1,9 +0,0 @@ -#define STB_IMAGE_IMPLEMENTATION -#define STB_IMAGE_DDS_IMPLEMENTATION -#define STB_IMAGE_WRITE_IMPLEMENTATION -#define STB_IMAGE_RESIZE_IMPLEMENTATION - -#include "stb_image.h" -#include "stb_image_dds.h" -#include "stb_image_write.h" -#include "stb_image_resize.h" diff --git a/msvc/deps/utfcpp.props b/msvc/deps/utfcpp.props deleted file mode 100644 index ef8b303..0000000 --- a/msvc/deps/utfcpp.props +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - $(SolutionDir)deps\utfcpp\source;%(AdditionalIncludeDirectories) - - - \ No newline at end of file diff --git a/msvc/res/exports.def b/msvc/res/exports.def deleted file mode 100644 index 117adc9..0000000 --- a/msvc/res/exports.def +++ /dev/null @@ -1,433 +0,0 @@ -EXPORTS - ; d3d9.dll - Direct3DCreate9 @37 - Direct3DCreate9Ex @38 - - D3DPERF_BeginEvent @27 - D3DPERF_EndEvent @28 - D3DPERF_GetStatus @29 - D3DPERF_QueryRepeatFrame @30 - D3DPERF_SetMarker @31 - D3DPERF_SetOptions @32 - D3DPERF_SetRegion @33 - - ; d3d10.dll - D3D10CreateDevice @4 - D3D10CreateDeviceAndSwapChain @5 - - ; d3d10_1.dll - D3D10CreateDevice1 - D3D10CreateDeviceAndSwapChain1 - - ; d3d11.dll - D3D11CreateDevice @22 - D3D11CreateDeviceAndSwapChain @23 - - ; d3d12.dll - D3D12CreateDevice @101 - D3D12GetDebugInterface @102 - D3D12CreateRootSignatureDeserializer @107 - D3D12CreateVersionedRootSignatureDeserializer @108 - D3D12EnableExperimentalFeatures @110 - D3D12SerializeRootSignature @115 - D3D12SerializeVersionedRootSignature @116 - - ; dxgi.dll - CreateDXGIFactory @10 - CreateDXGIFactory1 @11 - CreateDXGIFactory2 @12 - - DXGIDumpJournal - DXGIReportAdapterConfiguration - DXGID3D10CreateDevice @13 - DXGID3D10CreateLayeredDevice @14 - DXGID3D10GetLayeredDeviceSize @15 - DXGID3D10RegisterLayers - - ; opengl32.dll - glAccum - glAlphaFunc - glAreTexturesResident - glArrayElement - glBegin - glBindTexture - glBitmap - glBlendFunc - glCallList - glCallLists - glClear - glClearAccum - glClearColor - glClearDepth - glClearIndex - glClearStencil - glClipPlane - glColor3b - glColor3bv - glColor3d - glColor3dv - glColor3f - glColor3fv - glColor3i - glColor3iv - glColor3s - glColor3sv - glColor3ub - glColor3ubv - glColor3ui - glColor3uiv - glColor3us - glColor3usv - glColor4b - glColor4bv - glColor4d - glColor4dv - glColor4f - glColor4fv - glColor4i - glColor4iv - glColor4s - glColor4sv - glColor4ub - glColor4ubv - glColor4ui - glColor4uiv - glColor4us - glColor4usv - glColorMask - glColorMaterial - glColorPointer - glCopyPixels - glCopyTexImage1D - glCopyTexImage2D - glCopyTexSubImage1D - glCopyTexSubImage2D - glCullFace - glDeleteLists - glDeleteTextures - glDepthFunc - glDepthMask - glDepthRange - glDisable - glDisableClientState - glDrawArrays - glDrawBuffer - glDrawElements - glDrawPixels - glEdgeFlag - glEdgeFlagPointer - glEdgeFlagv - glEnable - glEnableClientState - glEnd - glEndList - glEvalCoord1d - glEvalCoord1dv - glEvalCoord1f - glEvalCoord1fv - glEvalCoord2d - glEvalCoord2dv - glEvalCoord2f - glEvalCoord2fv - glEvalMesh1 - glEvalMesh2 - glEvalPoint1 - glEvalPoint2 - glFeedbackBuffer - glFinish - glFlush - glFogf - glFogfv - glFogi - glFogiv - glFrontFace - glFrustum - glGenLists - glGenTextures - glGetBooleanv - glGetClipPlane - glGetDoublev - glGetError - glGetFloatv - glGetIntegerv - glGetLightfv - glGetLightiv - glGetMapdv - glGetMapfv - glGetMapiv - glGetMaterialfv - glGetMaterialiv - glGetPixelMapfv - glGetPixelMapuiv - glGetPixelMapusv - glGetPointerv - glGetPolygonStipple - glGetString - glGetTexEnvfv - glGetTexEnviv - glGetTexGendv - glGetTexGenfv - glGetTexGeniv - glGetTexImage - glGetTexLevelParameterfv - glGetTexLevelParameteriv - glGetTexParameterfv - glGetTexParameteriv - glHint - glIndexMask - glIndexPointer - glIndexd - glIndexdv - glIndexf - glIndexfv - glIndexi - glIndexiv - glIndexs - glIndexsv - glIndexub - glIndexubv - glInitNames - glInterleavedArrays - glIsEnabled - glIsList - glIsTexture - glLightModelf - glLightModelfv - glLightModeli - glLightModeliv - glLightf - glLightfv - glLighti - glLightiv - glLineStipple - glLineWidth - glListBase - glLoadIdentity - glLoadMatrixd - glLoadMatrixf - glLoadName - glLogicOp - glMap1d - glMap1f - glMap2d - glMap2f - glMapGrid1d - glMapGrid1f - glMapGrid2d - glMapGrid2f - glMaterialf - glMaterialfv - glMateriali - glMaterialiv - glMatrixMode - glMultMatrixd - glMultMatrixf - glNewList - glNormal3b - glNormal3bv - glNormal3d - glNormal3dv - glNormal3f - glNormal3fv - glNormal3i - glNormal3iv - glNormal3s - glNormal3sv - glNormalPointer - glOrtho - glPassThrough - glPixelMapfv - glPixelMapuiv - glPixelMapusv - glPixelStoref - glPixelStorei - glPixelTransferf - glPixelTransferi - glPixelZoom - glPointSize - glPolygonMode - glPolygonOffset - glPolygonStipple - glPopAttrib - glPopClientAttrib - glPopMatrix - glPopName - glPrioritizeTextures - glPushAttrib - glPushClientAttrib - glPushMatrix - glPushName - glRasterPos2d - glRasterPos2dv - glRasterPos2f - glRasterPos2fv - glRasterPos2i - glRasterPos2iv - glRasterPos2s - glRasterPos2sv - glRasterPos3d - glRasterPos3dv - glRasterPos3f - glRasterPos3fv - glRasterPos3i - glRasterPos3iv - glRasterPos3s - glRasterPos3sv - glRasterPos4d - glRasterPos4dv - glRasterPos4f - glRasterPos4fv - glRasterPos4i - glRasterPos4iv - glRasterPos4s - glRasterPos4sv - glReadBuffer - glReadPixels - glRectd - glRectdv - glRectf - glRectfv - glRecti - glRectiv - glRects - glRectsv - glRenderMode - glRotated - glRotatef - glScaled - glScalef - glScissor - glSelectBuffer - glShadeModel - glStencilFunc - glStencilMask - glStencilOp - glTexCoord1d - glTexCoord1dv - glTexCoord1f - glTexCoord1fv - glTexCoord1i - glTexCoord1iv - glTexCoord1s - glTexCoord1sv - glTexCoord2d - glTexCoord2dv - glTexCoord2f - glTexCoord2fv - glTexCoord2i - glTexCoord2iv - glTexCoord2s - glTexCoord2sv - glTexCoord3d - glTexCoord3dv - glTexCoord3f - glTexCoord3fv - glTexCoord3i - glTexCoord3iv - glTexCoord3s - glTexCoord3sv - glTexCoord4d - glTexCoord4dv - glTexCoord4f - glTexCoord4fv - glTexCoord4i - glTexCoord4iv - glTexCoord4s - glTexCoord4sv - glTexCoordPointer - glTexEnvf - glTexEnvfv - glTexEnvi - glTexEnviv - glTexGend - glTexGendv - glTexGenf - glTexGenfv - glTexGeni - glTexGeniv - glTexImage1D - glTexImage2D - glTexParameterf - glTexParameterfv - glTexParameteri - glTexParameteriv - glTexSubImage1D - glTexSubImage2D - glTranslated - glTranslatef - glVertex2d - glVertex2dv - glVertex2f - glVertex2fv - glVertex2i - glVertex2iv - glVertex2s - glVertex2sv - glVertex3d - glVertex3dv - glVertex3f - glVertex3fv - glVertex3i - glVertex3iv - glVertex3s - glVertex3sv - glVertex4d - glVertex4dv - glVertex4f - glVertex4fv - glVertex4i - glVertex4iv - glVertex4s - glVertex4sv - glVertexPointer - glViewport - - wglChoosePixelFormat - wglCopyContext - wglCreateContext - wglCreateLayerContext - wglDeleteContext - wglDescribeLayerPlane - wglDescribePixelFormat - wglGetCurrentContext - wglGetCurrentDC - wglGetLayerPaletteEntries - wglGetPixelFormat - wglGetProcAddress - wglMakeCurrent - wglRealizeLayerPalette - wglSetLayerPaletteEntries - wglSetPixelFormat - wglShareLists - wglSwapBuffers - wglSwapLayerBuffers - wglSwapMultipleBuffers - wglUseFontBitmapsA - wglUseFontBitmapsW - wglUseFontOutlinesA - wglUseFontOutlinesW - - ; user32.dll - RegisterClassA = HookRegisterClassA - RegisterClassW = HookRegisterClassW - RegisterClassExA = HookRegisterClassExA - RegisterClassExW = HookRegisterClassExW - RegisterRawInputDevices = HookRegisterRawInputDevices - GetMessageA = HookGetMessageA - GetMessageW = HookGetMessageW - PeekMessageA = HookPeekMessageA - PeekMessageW = HookPeekMessageW - PostMessageA = HookPostMessageA - PostMessageW = HookPostMessageW - GetCursorPos = HookGetCursorPosition - SetCursorPos = HookSetCursorPosition - - ; ws2_32.dll - send = HookSend @19 - sendto = HookSendTo @20 - recv = HookRecv @16 - recvfrom = HookRecvFrom @17 - WSASend = HookWSASend @96 - WSASendTo = HookWSASendTo @99 - WSARecv = HookWSARecv @91 - WSARecvFrom = HookWSARecvFrom @93 diff --git a/msvc/res/resource.h b/msvc/res/resource.h deleted file mode 100644 index 80c901b981fe1618e33a426398017623bc0c405a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1354 zcmb7^+e*Vg5QhJ2!FLFFDcIKIb#1EzdkBpYy$aekQW1r;@qqa1>NlGP72373Y<72; z{bqLd-+X@5RMQ)+uYppv)lo$6yIEf?g{)$|(>Aq%Z%u)Enrft6kDAdl&9Plzo2k&X z?pd|8O$&6Zi5}=V_BrDi+Z4+PuY%Qtb%y1h+0Ks$39QolMz8qftSnZ+Y~*}xtTM83 z%h8Nw@dEO0YsiUCl&DLK$!W-0>})C7#`ySJLv=M=gjM7g6^nEQE&7x8Gm?>az2uMz zpOSaDR;J%++;e)vduJ819F2VF z$dyh4?tY>dr&rTnhAIoRXZn3|$}OD}XEZH6Gd}7@cg#ol&K$FR38nI;_?NuE zoj)emgq%b7rb41#Fgrtg?ZruL$Af2&DYc3BfW2L(v6yd9$5>3u%M_n3VyEos=#m^# zr}&sX39}YfQ+^Xd!u)r&0%}{=(b{BFf6v{w{VROj0OizOTmGJw^jkAJ%(x? z4u|9Hncdm_?|&}4{&~nsRwAj%REDyYds*>LWR0$iZY3KT$c(Royyc1HM#eJdU7}wj zN9dMFwcF4d(vJAj*`~5!Oh#VID^|%mnMOOOcgED)PK zNK4w%k)l-OtMsKQCA3A_b*ajCG%bE>l1EdeZR;zB(d8`>CA0Zp~(rB8n_tWCT z*tdC;B@JDKug7H08V~!tyuj^|S^oS!c-UxR7787W8EJ)*H$07GZd>Uv zo;o(&0Nc5ECaxaOwl|@%i;cegIEt;poki@?s>PkD{5xR^Q@(H-n8;Az0GM%SU3iqH7dPw8ak{5~4XY?J;jaqQj|R=NH8 zlGWB-DI$~S-oo{q3QrfCU1fMi9y^cJ!mN|UoT2f1iaP`6RAc>D8Q#vrDAAez0){WR Ay8r+H diff --git a/msvc/res/resource.rc2 b/msvc/res/resource.rc2 deleted file mode 100644 index a025b78b91481e7f8289da611796462e2bc9f545..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2846 zcmd6pU2hUm5QgX4#Q$(YFVv((ijDVBKEz^afI_|6AW+0$A%Kl0{vrRQ`phf`_AH3# zg*BvmcF)JmJMX+Rr$4?Q$yn~BFBADB0~v~wjC2_1vS4M(T2|gO_gEg;-*fq2e~M7C z`O&4~@bf;Bj3sDadQAHRU+9 zSJ%|$?zN`buB~xxpDxO^VJKO=POk9LkP@@!(oXq$NL-$&3Vao$N<_*@OD>FI%|ON2 z)G_9Cd#7@)I=wZ8-f}V`(y}NA)m8l4r=2{72k#*=hBTvP%ba2;W9e&vBWd@USw5l` zCe*@kkDr`;mK;`NoI`46U}!_FH1MdI&@m$OZJu4zK#937vo9o{^3s;K;8vU+_|X$3 z{KTF%Dr~_+k=1Z-p552@xiFP=c`BYF)~e<;M(42Yn5~21+_I7Z$&&ilzA(b;nD~dB zxP$-csuRSg^xQ6Mo7JS%oSeau{?6nb&$y@fy?)$p(eLV@p*qniGZ}DC<)h4`2gWbN z+2d=-{IRX}iDhctY;Lp3xV=3;hIW1IVLy!RhLs<|SsQI>MOWe7tMPDOSB7#x7w*G; zI)*05TlC?cRlsiMcE=Ff{qsn|zH)dxbr6rhJF)D{$crj!L=4MyW;Ac%ij!~TERQ-) zQAw8f0v0BWo6`&X8d^=xaX288{UCgbXvukj_jNr*P?1)Cn=^BIo26}lf`SOHIn#i7-tq8xE)jV$Ga zH?5bx#jO7WXcurceqc`3Ic0~lZlI$gKB1z=mf6GEZLD&t9#kiuZT(8tZ-`XAWy!dz z!sk5fxVu<=iJA^a=8-%ktBUyFv8vvF$7;xOs7gnDT~cL9j<;vRTc$&v=j*Kh&HWPA fLK|y(11wNRFIA;_L&vv|-U+AHVRFAfyTSbvRl|CL diff --git a/msvc/res/shader_copy_ps.hlsl b/msvc/res/shader_copy_ps.hlsl deleted file mode 100644 index 6f5a644..0000000 --- a/msvc/res/shader_copy_ps.hlsl +++ /dev/null @@ -1,8 +0,0 @@ -Texture2D t0 : register(t0); -SamplerState s0 : register(s0); - -void main(float4 vpos : SV_POSITION, float2 uv : TEXCOORD0, out float4 col : SV_TARGET) -{ - col = t0.Sample(s0, uv); - col.a = 1.0; // Clear alpha channel -} diff --git a/msvc/res/shader_fullscreen_vs.hlsl b/msvc/res/shader_fullscreen_vs.hlsl deleted file mode 100644 index 48f6162..0000000 --- a/msvc/res/shader_fullscreen_vs.hlsl +++ /dev/null @@ -1,6 +0,0 @@ -void main(uint id : SV_VERTEXID, out float4 pos : SV_POSITION, out float2 uv : TEXCOORD0) -{ - uv.x = (id == 2) ? 2.0 : 0.0; - uv.y = (id == 1) ? 2.0 : 0.0; - pos = float4(uv * float2(2.0, -2.0) + float2(-1.0, 1.0), 0.0, 1.0); -} diff --git a/msvc/res/shader_imgui_ps.hlsl b/msvc/res/shader_imgui_ps.hlsl deleted file mode 100644 index 26d7e30..0000000 --- a/msvc/res/shader_imgui_ps.hlsl +++ /dev/null @@ -1,8 +0,0 @@ -Texture2D t0 : register(t0); -SamplerState s0 : register(s0); - -void main(float4 vpos : SV_POSITION, float4 vcol : COLOR0, float2 uv : TEXCOORD0, out float4 col : SV_TARGET) -{ - col = t0.Sample(s0, uv); - col *= vcol; // Blend vertex color and texture -} diff --git a/msvc/res/shader_imgui_vs.hlsl b/msvc/res/shader_imgui_vs.hlsl deleted file mode 100644 index c47cfe6..0000000 --- a/msvc/res/shader_imgui_vs.hlsl +++ /dev/null @@ -1,24 +0,0 @@ -struct VS_INPUT -{ - float2 pos : POSITION; - float4 col : COLOR0; - float2 tex : TEXCOORD0; -}; -struct PS_INPUT -{ - float4 pos : SV_POSITION; - float4 col : COLOR0; - float2 tex : TEXCOORD0; -}; - -cbuffer cb0 : register(b0) -{ - float4x4 ProjectionMatrix; -}; - -void main(VS_INPUT input, out PS_INPUT output) -{ - output.pos = mul(ProjectionMatrix, float4(input.pos.xy, 0.0, 1.0)); - output.col = input.col; - output.tex = input.tex; -} diff --git a/msvc/res/shader_mipmap_cs.hlsl b/msvc/res/shader_mipmap_cs.hlsl deleted file mode 100644 index 43eae2c..0000000 --- a/msvc/res/shader_mipmap_cs.hlsl +++ /dev/null @@ -1,17 +0,0 @@ -SamplerState s0 : register(s0); -Texture2D t0 : register(t0); -RWTexture2D dest : register(u0); - -cbuffer cb0 : register(b0) -{ - float2 texel; // 1.0 / dimension -} - -[numthreads(8, 8, 1)] -void main(uint3 tid : SV_DispatchThreadID) -{ - float2 uv = texel * (tid.xy + 0.5); - - // Sample input texture and write result to the destination mipmap level - dest[tid.xy] = t0.SampleLevel(s0, uv, 0); -} diff --git a/msvc/res/version.h b/msvc/res/version.h deleted file mode 100644 index 1dcab77..0000000 --- a/msvc/res/version.h +++ /dev/null @@ -1,14 +0,0 @@ -#pragma once - -#define VERSION_DATE "2019-06-10" -#define VERSION_TIME "16:24:17" -#define VERSION_BASEYEAR 0 - -#define VERSION_FULL 0.0.0.1 -#define VERSION_MAJOR 0 -#define VERSION_MINOR 0 -#define VERSION_REVISION 0 -#define VERSION_BUILD 1 - -#define VERSION_STRING_FILE "0.0.0.1" -#define VERSION_STRING_PRODUCT "0.0.0" diff --git a/msvc/setup/FodyWeavers.xml b/msvc/setup/FodyWeavers.xml deleted file mode 100644 index d769be1..0000000 --- a/msvc/setup/FodyWeavers.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/msvc/setup/Glass.cs b/msvc/setup/Glass.cs deleted file mode 100644 index 813fe58..0000000 --- a/msvc/setup/Glass.cs +++ /dev/null @@ -1,113 +0,0 @@ -using System; -using System.Runtime.InteropServices; -using System.Windows; -using System.Windows.Interop; -using System.Windows.Media; - -public static class Glass -{ - [StructLayout(LayoutKind.Sequential)] - private struct MARGINS - { - public int cxLeftWidth; - public int cxRightWidth; - public int cyTopHeight; - public int cyBottomHeight; - } - - [DllImport("dwmapi.dll")] - private static extern int DwmIsCompositionEnabled(out bool enabled); - [DllImport("dwmapi.dll")] - private static extern int DwmExtendFrameIntoClientArea(IntPtr hwnd, ref MARGINS pMarInset); - [DllImport("user32.dll")] - private static extern int GetWindowLong(IntPtr hwnd, int index); - [DllImport("user32.dll")] - private static extern int SetWindowLong(IntPtr hwnd, int index, int newStyle); - [DllImport("user32.dll")] - private static extern bool SetWindowPos(IntPtr hwnd, IntPtr hwndInsertAfter, int x, int y, int width, int height, uint flags); - [DllImport("user32.dll")] - private static extern IntPtr SendMessage(IntPtr hWnd, UInt32 Msg, IntPtr wParam, IntPtr lParam); - - public static bool IsEnabled - { - get - { - bool enabled = false; - DwmIsCompositionEnabled(out enabled); - - return enabled; - } - } - - public static void HideIcon(Window window) - { - IntPtr hwnd = new WindowInteropHelper(window).Handle; - - const int WM_SETICON = 0x0080; - - SendMessage(hwnd, WM_SETICON, new IntPtr(1), IntPtr.Zero); - SendMessage(hwnd, WM_SETICON, IntPtr.Zero, IntPtr.Zero); - - const int GWL_EXSTYLE = -20; - const int WS_EX_DLGMODALFRAME = 0x0001; - - SetWindowLong(hwnd, GWL_EXSTYLE, GetWindowLong(hwnd, GWL_EXSTYLE) | WS_EX_DLGMODALFRAME); - - const int SWP_NOSIZE = 0x0001; - const int SWP_NOMOVE = 0x0002; - const int SWP_NOZORDER = 0x0004; - const int SWP_FRAMECHANGED = 0x0020; - - SetWindowPos(hwnd, IntPtr.Zero, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE | SWP_NOZORDER | SWP_FRAMECHANGED); - } - public static void HideSystemMenu(Window window, bool hidden = true) - { - IntPtr hwnd = new WindowInteropHelper(window).Handle; - - const int GWL_STYLE = -16; - const int WS_SYSMENU = 0x80000; - - if (hidden) - { - SetWindowLong(hwnd, GWL_STYLE, GetWindowLong(hwnd, GWL_STYLE) & ~WS_SYSMENU); - } - else - { - SetWindowLong(hwnd, GWL_STYLE, GetWindowLong(hwnd, GWL_STYLE) | WS_SYSMENU); - } - } - - public static bool ExtendFrame(Window window) - { - return ExtendFrame(window, new Thickness(-1, -1, -1, -1)); - } - public static bool ExtendFrame(Window window, Thickness margin) - { - if (!IsEnabled) - { - return false; - } - - IntPtr hwnd = new WindowInteropHelper(window).Handle; - - if (hwnd == IntPtr.Zero) - { - throw new InvalidOperationException("The window must be shown before extending glass effect."); - } - - // Adapted from http://blogs.msdn.com/b/adam_nathan/archive/2006/05/04/589686.aspx - window.Background = Brushes.Transparent; - HwndSource.FromHwnd(hwnd).CompositionTarget.BackgroundColor = Colors.Transparent; - - var margins = new MARGINS { - cxLeftWidth = (int)margin.Left, - cxRightWidth = (int)margin.Right, - cyTopHeight = (int)margin.Top, - cyBottomHeight = (int)margin.Bottom - }; - - DwmExtendFrameIntoClientArea(hwnd, ref margins); - - return true; - } -} diff --git a/msvc/setup/IniFile.cs b/msvc/setup/IniFile.cs deleted file mode 100644 index 1c1d16d..0000000 --- a/msvc/setup/IniFile.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System.Text; -using System.Runtime.InteropServices; - -public static class IniFile -{ - [DllImport("kernel32", CharSet = CharSet.Unicode)] - private static extern int GetPrivateProfileString(string section, string key, string defaultValue, StringBuilder value, int size, string filePath); - [DllImport("kernel32", CharSet = CharSet.Unicode, SetLastError = true)] - [return: MarshalAs(UnmanagedType.Bool)] - private static extern bool WritePrivateProfileString(string section, string key, string value, string filePath); - - public static string ReadValue(string file, string section, string key, string defaultValue = "") - { - var value = new StringBuilder(512); - GetPrivateProfileString(section, key, defaultValue, value, value.Capacity, file); - return value.ToString(); - } - public static bool WriteValue(string file, string section, string key, string value) - { - return WritePrivateProfileString(section, key, value, file); - } -} diff --git a/msvc/setup/PEInfo.cs b/msvc/setup/PEInfo.cs deleted file mode 100644 index 11b15d0..0000000 --- a/msvc/setup/PEInfo.cs +++ /dev/null @@ -1,115 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Runtime.InteropServices; -using System.Security; - -public unsafe class PEInfo -{ - public enum BinaryType : ushort - { - IMAGE_FILE_MACHINE_UNKNOWN = 0x0, - IMAGE_FILE_MACHINE_I386 = 0x14c, - IMAGE_FILE_MACHINE_AMD64 = 0x8664, - } - - [StructLayout(LayoutKind.Sequential)] - private struct LOADED_IMAGE - { - public IntPtr ModuleName; - public IntPtr hFile; - public IntPtr MappedAddress; - public IntPtr FileHeader; - public IntPtr LastRvaSection; - public uint NumberOfSections; - public IntPtr Sections; - public uint Characteristics; - public ushort fSystemImage; - public ushort fDOSImage; - public ushort fReadOnly; - public ushort Version; - public IntPtr Flink; - public IntPtr BLink; - public uint SizeOfImage; - } - [StructLayout(LayoutKind.Explicit)] - private struct IMAGE_NT_HEADERS - { - [FieldOffset(0)] - public uint Signature; - [FieldOffset(4)] - public IMAGE_FILE_HEADER FileHeader; - } - [StructLayout(LayoutKind.Sequential)] - private struct IMAGE_FILE_HEADER - { - public BinaryType Machine; - public ushort NumberOfSections; - public uint TimeDateStamp; - public uint PointerToSymbolTable; - public uint NumberOfSymbols; - public ushort SizeOfOptionalHeader; - public ushort Characteristics; - } - [StructLayout(LayoutKind.Explicit)] - private struct IMAGE_IMPORT_DESCRIPTOR - { - #region union - [FieldOffset(0)] - public UInt32 Characteristics; - [FieldOffset(0)] - public UInt32 OriginalFirstThunk; - #endregion - - [FieldOffset(4)] - public uint TimeDateStamp; - [FieldOffset(8)] - public uint ForwarderChain; - [FieldOffset(12)] - public uint Name; - [FieldOffset(16)] - public uint FirstThunk; - } - - [DllImport("dbghelp.dll"), SuppressUnmanagedCodeSecurity] - private static extern void* ImageDirectoryEntryToData(void* pBase, bool mappedAsImage, ushort directoryEntry, out uint size); - [DllImport("dbghelp.dll"), SuppressUnmanagedCodeSecurity] - private static extern IntPtr ImageRvaToVa(IntPtr pNtHeaders, IntPtr pBase, uint rva, IntPtr pLastRvaSection); - [DllImport("imagehlp.dll"), SuppressUnmanagedCodeSecurity] - private static extern bool MapAndLoad(string imageName, string dllPath, out LOADED_IMAGE loadedImage, bool dotDll, bool readOnly); - - private readonly BinaryType _binaryType = BinaryType.IMAGE_FILE_MACHINE_UNKNOWN; - private readonly List _modules = new List(); - - // Adapted from http://stackoverflow.com/a/4696857/2055880 - public PEInfo(string path) - { - uint size; - LOADED_IMAGE image; - - if (MapAndLoad(path, null, out image, true, true) && image.MappedAddress != IntPtr.Zero) - { - var imports = (IMAGE_IMPORT_DESCRIPTOR*)ImageDirectoryEntryToData((void*)image.MappedAddress, false, 1, out size); - - if (imports != null) - { - while (imports->OriginalFirstThunk != 0) - { - _modules.Add(Marshal.PtrToStringAnsi(ImageRvaToVa(image.FileHeader, image.MappedAddress, imports->Name, IntPtr.Zero))); - - ++imports; - } - } - - _binaryType = ((IMAGE_NT_HEADERS*)image.FileHeader)->FileHeader.Machine; - } - } - - public BinaryType Type - { - get { return _binaryType; } - } - public IEnumerable Modules - { - get { return _modules; } - } -} diff --git a/msvc/setup/Packages.config b/msvc/setup/Packages.config deleted file mode 100644 index 28acfc3..0000000 --- a/msvc/setup/Packages.config +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/msvc/setup/Properties/App.xaml b/msvc/setup/Properties/App.xaml deleted file mode 100644 index 4d09c7d..0000000 --- a/msvc/setup/Properties/App.xaml +++ /dev/null @@ -1,5 +0,0 @@ - diff --git a/msvc/setup/Properties/Assembly.manifest b/msvc/setup/Properties/Assembly.manifest deleted file mode 100644 index 75b8067..0000000 --- a/msvc/setup/Properties/Assembly.manifest +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/msvc/setup/Properties/AssemblyInfo.cs b/msvc/setup/Properties/AssemblyInfo.cs deleted file mode 100644 index 6285be9..0000000 --- a/msvc/setup/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System.Reflection; - -[assembly: AssemblyTitle("ReShade Setup")] -[assembly: AssemblyVersion("2.0.0.0")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("crosire")] -[assembly: AssemblyProduct("ReShade")] -[assembly: AssemblyCopyright("Copyright © 2014. All rights reserved.")] diff --git a/msvc/setup/Properties/Icon.ico b/msvc/setup/Properties/Icon.ico deleted file mode 100644 index 62caf48d28651e11f97baeb81a2e8388656d491e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 68582 zcmafab95z7wDpZ|?BvF_ZQHhOXC}67n=_ecV%x^Vb|$ta`tp14-*2tgtGjERRbAa@ zRiCQ8&#rC&01N;HKtTa~mBfGnZ~#CP000mZ|F0bj0|4Ma007L)|7(jQ0RUxi06<{i z|JtHL0Kf_8tHIayU;B;*0Dw*#0EkjnltP5V{kjr>C?hSd`c-~C0e}L8{yG!geVPCO z;L$STBI?|6L&Mj_=0iQlr^z(=OYBAh^h9DhB+x-kzpz9S7b$hI7RwF9 zMJyU=tmkBdiEWc`)%S{(M`0*zkECgfkXU$`PbayjHu#>lG^53v;Qm!l^WvzQ|CNpKJ}q43B#4A$aA&XXw7y56-SfbPoojLG z6J28x@)&eKF?ic=gXmwiEL}M?g;&ibCwISIb{JN=$#Fk@SeS*yiW#!+-JoqI~>=S{fDgBx#G&c+H7!vBy-e zWdnSSeb2W#o!XO+#69f^j&RB0(TS<@!rzf`j8H}rY)rfzHoq3ciK%79lF}jqVTsLk zlI`f~g|_*wD!m2rp}ce3bAb&f7mz<9c$`lK9krm#VQ1DaS$TL15#{A9vT<|35-Ec8 zllR-}ojU%IHG;^@)LIEBW8fP~*+T>(ri&|N+y$m;+9k)I@RQ}$s&jzyoltvdqM$oD zNo^2rJ_4OY4g-dzjQxYmIU}>ztaska3sk2elP&E~<@9${5HdUXcAx^50axscMS~qH z=;`>FcJKkgyIXv9uWp_N#q9db$CAn$$bcJ~&SD{xJ%_lD&x{~$0jQkJxROhUk4r1> z;@+Ovi=N!)GI8JBk*@BaHicQFP-NXi5>**lRX>&DO4Ri!`DH^_S9fKmG|gtfyiGJIn7H3&w8js=hF*cuu-$~yd7Q`27k@5q-z~ z7Xjr$rC%^{+#VE!43W68)?o3l(PrQQ*+gcZ{jc}FzZ(MKipY3p6y;@uxQSR?CJZGz z)XB4H#`(%pfnvv5o6fHZO}(WDJJ2uM>ZVD^=yf07%Fx}8QjaoARTv@?%lclr)i{Er zOf=rRG*K3vMywi1fu;|e39VDmdVZwx!*rQ-eSrEjU>mS%zx468xV*8lw$}Y`FGfn* z3UL;ioO{J1aD-@k%q%D3`Qgg@Bo{QBjtRotT_eT}oiF6j7c}_oG&&eQ@qsC#yah;A zgxSgMtfQBcF5@M&+4NJqa~$T72h5K;3+eg%%VhnAwEZ!i22ii$C|43HXqFeZ?Uz6x z*chspyOp<7QvDSKx7(qmxA!QuI1*~z_CtBp0Qs*PSgcp?H-W2-c%`>`KT*q*a7>!F zB}GF4O3$OTn+@0XYVKwZPH8(aP&h^m#ch~LFtO?3c$#^%0DZ?PJsc*=@bz%w?j>S5 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

    6Hc_o~ts}AJX5WkV3eU(g{yN3tLJG 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 diff --git a/msvc/setup/ReShade Setup.csproj b/msvc/setup/ReShade Setup.csproj deleted file mode 100644 index 812f282..0000000 --- a/msvc/setup/ReShade Setup.csproj +++ /dev/null @@ -1,163 +0,0 @@ - - - - - {3B7009FA-0B09-4F27-8126-0885E66A5679} - Debug - AnyCPU - WinExe - ReShade Setup - Properties\Icon.ico - $(SolutionDir)\bin\$(Platform)\$(Configuration)\ - $(SolutionDir)\intermediate\$(AssemblyName)\$(Platform)\$(Configuration)\ - Properties - ReShade.Setup - v4.6.1 - 512 - Properties\Assembly.manifest - 4 - - - - - - AnyCPU - true - full - false - DEBUG;TRACE - true - true - true - - - AnyCPU - none - true - true - false - false - - - - ..\packages\Costura.Fody.1.6.2\lib\dotnet\Costura.dll - False - - - ..\packages\WindowsAPICodePack-Core.1.1.2\lib\Microsoft.WindowsAPICodePack.dll - - - ..\packages\WindowsAPICodePack-Shell.1.1.1\lib\Microsoft.WindowsAPICodePack.Shell.dll - - - - - - - - - - - - MSBuild:Compile - Designer - - - Settings.xaml - - - - - - Designer - MSBuild:Compile - - - Designer - MSBuild:Compile - - - Designer - MSBuild:Compile - - - Select.xaml - - - Wizard.xaml - - - - - Code - - - - - - Designer - - - - - - - - - - - - - - - - - - (); -var attribute = config.Attribute("ExcludeAssemblies"); -if (attribute != null) - foreach (var item in attribute.Value.Split('|').Select(x => x.Trim()).Where(x => x != string.Empty)) - excludedAssemblies.Add(item); -var element = config.Element("ExcludeAssemblies"); -if (element != null) - foreach (var item in element.Value.Split(new[] { "\r\n", "\n" }, StringSplitOptions.RemoveEmptyEntries).Select(x => x.Trim()).Where(x => x != string.Empty)) - excludedAssemblies.Add(item); - -var filesToCleanup = Files.Select(f => f.ItemSpec).Where(f => !excludedAssemblies.Contains(Path.GetFileNameWithoutExtension(f), StringComparer.InvariantCultureIgnoreCase)); - -foreach (var item in filesToCleanup) - File.Delete(item); -]]> - - - - - - - - This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - - - - - - -echo Compressing ReShade DLLs ... -"$(SolutionDir)tools\7za" a -y "$(TargetDir)ReShade.zip" "$(SolutionDir)bin\Win32\Release\ReShade32.dll" "$(SolutionDir)bin\x64\Release\ReShade64.dll" - -echo Appending archive to setup executable ... -copy /b "$(TargetPath)" + "$(TargetDir)ReShade.zip" "$(TargetPath)" - -echo Cleaning up and deleting temporary archive ... -del "$(TargetDir)ReShade.zip" - - OnOutputUpdated - - \ No newline at end of file diff --git a/msvc/setup/Select.xaml b/msvc/setup/Select.xaml deleted file mode 100644 index b59d938..0000000 --- a/msvc/setup/Select.xaml +++ /dev/null @@ -1,28 +0,0 @@ - - - diff --git a/msvc/setup/Wizard.xaml.cs b/msvc/setup/Wizard.xaml.cs deleted file mode 100644 index 27b28c4..0000000 --- a/msvc/setup/Wizard.xaml.cs +++ /dev/null @@ -1,496 +0,0 @@ -/** - * Copyright (C) 2014 Patrick Mours. All rights reserved. - * License: https://github.com/crosire/reshade#license - */ - -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.IO.Compression; -using System.Linq; -using System.Net; -using System.Reflection; -using System.Windows; -using System.Windows.Input; -using Microsoft.Win32; - -namespace ReShade.Setup -{ - public partial class WizardWindow - { - bool _isHeadless = false; - bool _isElevated = false; - string _configPath = null; - string _targetPath = null; - string _targetModulePath = null; - PEInfo _targetPEInfo = null; - string _tempDownloadPath = null; - - public WizardWindow() - { - InitializeComponent(); - } - - static void MoveFiles(string sourcePath, string targetPath) - { - if (!Directory.Exists(targetPath)) - { - Directory.CreateDirectory(targetPath); - } - - foreach (string source in Directory.GetFiles(sourcePath)) - { - string target = targetPath + source.Replace(sourcePath, string.Empty); - - File.Copy(source, target, true); - } - } - static bool IsWritable(string targetPath) - { - try - { - File.Create(Path.Combine(targetPath, Path.GetRandomFileName()), 1, FileOptions.DeleteOnClose); - return true; - } - catch - { - return false; - } - } - - static ZipArchive ExtractArchive() - { - var output = new FileStream(Path.GetTempFileName(), FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.ReadWrite | FileShare.Delete, 4096, FileOptions.DeleteOnClose); - - using (var input = File.OpenRead(Assembly.GetExecutingAssembly().Location)) - { - byte[] block = new byte[512]; - byte[] signature = { 0x50, 0x4B, 0x03, 0x04, 0x14, 0x00, 0x00, 0x00 }; // PK.. - - // Look for archive at the end of this executable and copy it to a file - while (input.Read(block, 0, block.Length) >= signature.Length) - { - if (block.Take(signature.Length).SequenceEqual(signature)) - { - output.Write(block, 0, block.Length); - input.CopyTo(output); - break; - } - } - } - - return new ZipArchive(output, ZipArchiveMode.Read, false); - } - - void ShowMessage(string title, string message, string description = null, bool done = false, int exitCode = -1) - { - if (done && _isHeadless) - { - Environment.Exit(exitCode); - } - else if (exitCode == 0) - { - message = "Edit ReShade settings"; - SetupButton.IsEnabled = true; - SetupButton.Click -= OnSetupButtonClick; - SetupButton.Click += (object s, RoutedEventArgs e) => new SettingsWindow(_configPath) { Owner = this }.ShowDialog(); - } - - Glass.HideSystemMenu(this, !done); - - Title = title; - Message.Text = message == null ? string.Empty : message; - MessageDescription.Visibility = string.IsNullOrEmpty(description) ? Visibility.Collapsed : Visibility.Visible; - MessageDescription.Text = description; - - } - void RestartAsAdmin() - { - Process.Start(new ProcessStartInfo { Verb = "runas", FileName = Assembly.GetExecutingAssembly().Location, Arguments = $"\"{_targetPath}\" --elevated --left {Left} --top {Top}" }); - - Close(); - } - - void AddSearchPath(List searchPaths, string newPath) - { - string basePath = Path.GetDirectoryName(_targetPath); - Directory.SetCurrentDirectory(basePath); - - bool pathExists = false; - - foreach (var searchPath in searchPaths) - { - if (Path.GetFullPath(searchPath) == Path.GetFullPath(newPath)) - { - pathExists = true; - break; - } - } - - if (!pathExists) - searchPaths.Add(newPath); - } - void WriteSearchPaths(string targetPathShaders, string targetPathTextures) - { - var effectSearchPaths = IniFile.ReadValue(_configPath, "GENERAL", "EffectSearchPaths").Split(',').Where(x => x.Length != 0).ToList(); - var textureSearchPaths = IniFile.ReadValue(_configPath, "GENERAL", "TextureSearchPaths").Split(',').Where(x => x.Length != 0).ToList(); - - AddSearchPath(effectSearchPaths, targetPathShaders); - AddSearchPath(textureSearchPaths, targetPathTextures); - - IniFile.WriteValue(_configPath, "GENERAL", "EffectSearchPaths", string.Join(",", effectSearchPaths)); - IniFile.WriteValue(_configPath, "GENERAL", "TextureSearchPaths", string.Join(",", textureSearchPaths)); - } - - void InstallationStep0() - { - if (!_isElevated && !IsWritable(Path.GetDirectoryName(_targetPath))) - RestartAsAdmin(); - else - InstallationStep1(); - } - void InstallationStep1() - { - SetupButton.IsEnabled = false; - Glass.HideSystemMenu(this); - - var info = FileVersionInfo.GetVersionInfo(_targetPath); - string name = !string.IsNullOrEmpty(info.ProductName) ? info.ProductName : Path.GetFileNameWithoutExtension(_targetPath); - _targetPEInfo = new PEInfo(_targetPath); - - ShowMessage("Working on " + name + " ...", "Analyzing " + name + " ..."); - - string nameModule = _targetPEInfo.Modules.FirstOrDefault(s => - s.StartsWith("d3d8", StringComparison.OrdinalIgnoreCase) || - s.StartsWith("d3d9", StringComparison.OrdinalIgnoreCase) || - s.StartsWith("dxgi", StringComparison.OrdinalIgnoreCase) || - s.StartsWith("opengl32", StringComparison.OrdinalIgnoreCase)); - - if (nameModule == null) - { - nameModule = string.Empty; - } - - bool isApiD3D8 = nameModule.StartsWith("d3d8", StringComparison.OrdinalIgnoreCase); - bool isApiD3D9 = isApiD3D8 || nameModule.StartsWith("d3d9", StringComparison.OrdinalIgnoreCase); - bool isApiDXGI = nameModule.StartsWith("dxgi", StringComparison.OrdinalIgnoreCase); - bool isApiOpenGL = nameModule.StartsWith("opengl32", StringComparison.OrdinalIgnoreCase); - - if (isApiD3D8 && !_isHeadless) - { - MessageBox.Show(this, "It looks like the target application uses Direct3D 8. You'll have to download an additional wrapper from 'http://reshade.me/d3d8to9' which converts all API calls to Direct3D 9 in order to use ReShade.", "Warning", MessageBoxButton.OK, MessageBoxImage.Warning); - } - - Message.Text = "Select the rendering API the game uses:"; - ApiGroup.IsEnabled = true; - ApiDirect3D9.IsChecked = isApiD3D9; - ApiDirectXGI.IsChecked = isApiDXGI; - ApiOpenGL.IsChecked = isApiOpenGL; - } - void InstallationStep2() - { - string nameModule = null; - ApiGroup.IsEnabled = false; - if (ApiDirect3D9.IsChecked == true) - nameModule = "d3d9.dll"; - if (ApiDirectXGI.IsChecked == true) - nameModule = "dxgi.dll"; - if (ApiOpenGL.IsChecked == true) - nameModule = "opengl32.dll"; - - string targetDir = Path.GetDirectoryName(_targetPath); - string pathModule = _targetModulePath = Path.Combine(targetDir, nameModule); - - _configPath = Path.ChangeExtension(pathModule, ".ini"); - if (!File.Exists(_configPath)) - _configPath = Path.Combine(targetDir, "ReShade.ini"); - - if (File.Exists(pathModule) && !_isHeadless) - { - var result = MessageBox.Show(this, "Do you want to overwrite the existing installation or uninstall ReShade?\n\nPress 'Yes' to overwrite or 'No' to uninstall.", string.Empty, MessageBoxButton.YesNoCancel); - - if (result == MessageBoxResult.No) - { - try - { - File.Delete(pathModule); - if (File.Exists(_configPath)) - File.Delete(_configPath); - if (File.Exists(Path.ChangeExtension(pathModule, ".log"))) - File.Delete(Path.ChangeExtension(pathModule, ".log")); - if (Directory.Exists(Path.Combine(targetDir, "reshade-shaders"))) - Directory.Delete(Path.Combine(targetDir, "reshade-shaders"), true); - } - catch (Exception ex) - { - ShowMessage("Failed!", "Unable to delete some files.", ex.Message, true, 1); - return; - } - - ShowMessage("Succeeded!", "Successfully uninstalled.", null, true); - return; - } - else if (result != MessageBoxResult.Yes) - { - ShowMessage("Failed", "Existing installation found.", null, true, 1); - return; - } - } - - try - { - using (ZipArchive zip = ExtractArchive()) - { - if (zip.Entries.Count != 2) - throw new FileFormatException("Expected ReShade archive to contain ReShade DLLs"); - - using (Stream input = zip.Entries[_targetPEInfo.Type == PEInfo.BinaryType.IMAGE_FILE_MACHINE_AMD64 ? 1 : 0].Open()) - using (FileStream output = File.Create(pathModule)) - input.CopyTo(output); - } - } - catch (Exception ex) - { - ShowMessage("Failed!", "Unable to write file \"" + pathModule + "\".", ex.Message, true, 1); - return; - } - - if (_isHeadless || MessageBox.Show(this, "Do you wish to download a collection of standard effects from https://github.com/crosire/reshade-shaders?", string.Empty, MessageBoxButton.YesNo) == MessageBoxResult.Yes) - { - InstallationStep3(); - } - else - { - if (!File.Exists(_configPath)) - { - string targetDirectory = Path.GetDirectoryName(_targetPath); - - WriteSearchPaths(".\\", ".\\"); - } - - ShowMessage("Succeeded!", "Successfully installed.", null, true, 0); - } - } - void InstallationStep3() - { - ShowMessage("Downloading ...", "Downloading ..."); - - _tempDownloadPath = Path.GetTempFileName(); - - var client = new WebClient(); - - client.DownloadFileCompleted += (object sender, System.ComponentModel.AsyncCompletedEventArgs e) => { - if (e.Error != null) - ShowMessage("Failed!", "Unable to download archive.", e.Error.Message, true, 1); - else - InstallationStep4(); - }; - - client.DownloadProgressChanged += (object sender, DownloadProgressChangedEventArgs e) => { - Message.Text = "Downloading ... (" + ((100 * e.BytesReceived) / e.TotalBytesToReceive) + "%)"; - }; - - try - { - client.DownloadFileAsync(new Uri("https://github.com/crosire/reshade-shaders/archive/master.zip"), _tempDownloadPath); - } - catch (Exception ex) - { - ShowMessage("Failed!", "Unable to download archive.", ex.Message); - } - } - void InstallationStep4() - { - Message.Text = "Extracting ..."; - - string tempPath = Path.Combine(Path.GetTempPath(), "reshade-shaders-master"); - string tempPathShaders = Path.Combine(tempPath, "Shaders"); - string tempPathTextures = Path.Combine(tempPath, "Textures"); - string targetPath = Path.Combine(Path.GetDirectoryName(_targetPath), "reshade-shaders"); - string targetPathShaders = Path.Combine(targetPath, "Shaders"); - string targetPathTextures = Path.Combine(targetPath, "Textures"); - - string[] installedEffects = null; - - if (Directory.Exists(targetPath)) - { - installedEffects = Directory.GetFiles(targetPathShaders).ToArray(); - } - - try - { - if (Directory.Exists(tempPath)) // Delete existing directories since extraction fails if the target is not empty - Directory.Delete(tempPath, true); - - ZipFile.ExtractToDirectory(_tempDownloadPath, Path.GetTempPath()); - - MoveFiles(tempPathShaders, targetPathShaders); - MoveFiles(tempPathTextures, targetPathTextures); - - File.Delete(_tempDownloadPath); - Directory.Delete(tempPath, true); - } - catch (Exception ex) - { - ShowMessage("Failed!", "Unable to extract downloaded archive.", ex.Message, true, 1); - return; - } - - if (!_isHeadless) - { - var wnd = new SelectWindow(Directory.GetFiles(targetPathShaders)); - wnd.Owner = this; - - // If there was an existing installation, select the same effects as previously - if (installedEffects != null) - { - foreach (var item in wnd.GetSelection()) - { - item.IsChecked = installedEffects.Contains(item.Path); - } - } - - wnd.ShowDialog(); - - foreach (var item in wnd.GetSelection()) - { - if (!item.IsChecked) - { - try - { - File.Delete(item.Path); - } - catch - { - continue; - } - } - } - } - - WriteSearchPaths(".\\reshade-shaders\\Shaders", ".\\reshade-shaders\\Textures"); - - ShowMessage("Succeeded!", null, null, true, 0); - } - - void OnWindowInit(object sender, EventArgs e) - { - Glass.HideIcon(this); - Glass.ExtendFrame(this); - } - void OnWindowLoaded(object sender, RoutedEventArgs e) - { - var args = Environment.GetCommandLineArgs().Skip(1).ToArray(); - - bool hasApi = false; - bool hasTargetPath = false; - - // Parse command line arguments - for (int i = 0; i < args.Length; i++) - { - if (args[i] == "--headless") - { - _isHeadless = true; - continue; - } - if (args[i] == "--elevated") - { - _isElevated = true; - continue; - } - - if (i + 1 < args.Length) - { - if (args[i] == "--api") - { - hasApi = true; - - string api = args[++i]; - ApiDirect3D9.IsChecked = api == "d3d9"; - ApiDirectXGI.IsChecked = api == "dxgi" || api == "d3d10" || api == "d3d11"; - ApiOpenGL.IsChecked = api == "opengl"; - continue; - } - - if (args[i] == "--top") - { - Top = double.Parse(args[++i]); - continue; - } - if (args[i] == "--left") - { - Left = double.Parse(args[++i]); - continue; - } - } - - if (File.Exists(args[i])) - { - hasTargetPath = true; - - _targetPath = args[i]; - } - } - - if (hasTargetPath) - { - InstallationStep1(); - - if (hasApi) - InstallationStep2(); - } - } - - void OnApiChecked(object sender, RoutedEventArgs e) - { - InstallationStep2(); - } - void OnSetupButtonClick(object sender, RoutedEventArgs e) - { - if (Keyboard.Modifiers == ModifierKeys.Control) - { - try - { - using (ZipArchive zip = ExtractArchive()) - zip.ExtractToDirectory("."); - } - catch (Exception ex) - { - ShowMessage("Failed!", "Unable to extract files.", ex.Message, true); - SetupButton.IsEnabled = false; - return; - } - - Close(); - return; - } - - var dlg = new OpenFileDialog { Filter = "Applications|*.exe", DefaultExt = ".exe", Multiselect = false, ValidateNames = true, CheckFileExists = true }; - - // Open Steam game installation directory by default if it exists - string steamPath = Path.Combine( - Environment.GetFolderPath(Environment.Is64BitOperatingSystem ? Environment.SpecialFolder.ProgramFilesX86 : Environment.SpecialFolder.ProgramFiles), - "Steam", "steamapps", "common"); - - if (Directory.Exists(steamPath)) - dlg.InitialDirectory = steamPath; - - if (dlg.ShowDialog(this) == true) - { - _targetPath = dlg.FileName; - - InstallationStep0(); - } - } - void OnSetupButtonDragDrop(object sender, DragEventArgs e) - { - if (e.Data.GetData(DataFormats.FileDrop, true) is string[] files && files.Length >= 1) - { - _targetPath = files[0]; - - InstallationStep0(); - } - } - } -} diff --git a/msvc/source/com_ptr.hpp b/msvc/source/com_ptr.hpp deleted file mode 100644 index 6b5c257..0000000 --- a/msvc/source/com_ptr.hpp +++ /dev/null @@ -1,121 +0,0 @@ -/** - * Copyright (C) 2014 Patrick Mours. All rights reserved. - * License: https://github.com/crosire/reshade#license - */ - -#pragma once - -#include - -template -class com_ptr -{ -public: - com_ptr() - : _object(nullptr) {} - com_ptr(std::nullptr_t) - : _object(nullptr) {} - com_ptr(T *object, bool own = false) - : _object(object) - { - if (!own && _object != nullptr) - _object->AddRef(); - } - com_ptr(const com_ptr &ptr) - : _object(nullptr) { reset(ptr._object); } - com_ptr(com_ptr &&ptr) - : _object(nullptr) { operator=(ptr); } - ~com_ptr() { reset(); } - - ///

    - /// Returns the stored pointer to the managed object. - /// - T *get() const { return _object; } - - /// - /// Returns the current COM reference count of the managed object. - /// - unsigned long ref_count() const - { - return _object->AddRef(), _object->Release(); - } - - /// - /// Returns the stored pointer and releases ownership without decreasing the reference count. - /// - T *release() - { - T *const object = _object; - _object = nullptr; - return object; - } - - /// - /// Replaces the managed object. - /// - /// The new object to manage and take ownership and add a reference to. - void reset(T *object = nullptr) - { - if (_object != nullptr) - _object->Release(); - _object = object; - if (_object != nullptr) - _object->AddRef(); - } - - // Overloaded pointer operators which operate on the managed object. - T &operator*() const { assert(_object != nullptr); return *_object; } - T *operator->() const { assert(_object != nullptr); return _object; } - - // This should only be called on uninitialized objects, e.g. when passed into 'QueryInterface' or creation functions. - T **operator&() { assert(_object == nullptr); return &_object; } - - com_ptr &operator=(T *object) - { - reset(object); - return *this; - } - com_ptr &operator=(const com_ptr ©) - { - reset(copy._object); - return *this; - } - com_ptr &operator=(com_ptr &&move) - { - // Clear the current object first - if (_object != nullptr) - _object->Release(); - - _object = move._object; - move._object = nullptr; - - return *this; - } - - bool operator==(const T *rhs) const { return _object == rhs; } - bool operator==(const com_ptr &rhs) const { return _object == rhs._object; } - friend bool operator==(const T *lhs, const com_ptr &rhs) { return rhs.operator==(lhs); } - bool operator!=(const T *rhs) const { return _object != rhs; } - bool operator!=(const com_ptr &rhs) const { return _object != rhs._object; } - friend bool operator!=(const T *lhs, const com_ptr &rhs) { return rhs.operator!=(lhs); } - - // Default operator used for sorting - friend bool operator< (const com_ptr &lhs, const com_ptr &rhs) { return lhs._object < rhs._object; } - -private: - T *_object; -}; - -#include // std::hash - -namespace std -{ - template - struct hash> - { - size_t operator()(const com_ptr &ptr) const - { - return std::hash()(ptr.get()); - } - }; -} diff --git a/msvc/source/d3d10/d3d10.cpp b/msvc/source/d3d10/d3d10.cpp deleted file mode 100644 index 56843a2..0000000 --- a/msvc/source/d3d10/d3d10.cpp +++ /dev/null @@ -1,100 +0,0 @@ -/** - * Copyright (C) 2014 Patrick Mours. All rights reserved. - * License: https://github.com/crosire/reshade#license - */ - -#include "log.hpp" -#include "hook_manager.hpp" -#include "d3d10_device.hpp" - -HOOK_EXPORT HRESULT WINAPI D3D10CreateDevice(IDXGIAdapter *pAdapter, D3D10_DRIVER_TYPE DriverType, HMODULE Software, UINT Flags, UINT SDKVersion, ID3D10Device **ppDevice) -{ - LOG(INFO) << "Redirecting D3D10CreateDevice" << '(' << pAdapter << ", " << DriverType << ", " << Software << ", " << std::hex << Flags << std::dec << ", " << SDKVersion << ", " << ppDevice << ')' << " ..."; - LOG(INFO) << "> Passing on to D3D10CreateDeviceAndSwapChain1:"; - - // Only 'd3d10.dll' is guaranteed to be loaded at this point, but the 'D3D10CreateDeviceAndSwapChain1' entry point is in 'd3d10_1.dll', so load that now to make sure hooks can be resolved - LoadLibraryW(L"d3d10_1.dll"); - - return D3D10CreateDeviceAndSwapChain1(pAdapter, DriverType, Software, Flags, D3D10_FEATURE_LEVEL_10_0, D3D10_1_SDK_VERSION, nullptr, nullptr, reinterpret_cast(ppDevice)); -} - -HOOK_EXPORT HRESULT WINAPI D3D10CreateDevice1(IDXGIAdapter *pAdapter, D3D10_DRIVER_TYPE DriverType, HMODULE Software, UINT Flags, D3D10_FEATURE_LEVEL1 HardwareLevel, UINT SDKVersion, ID3D10Device1 **ppDevice) -{ - LOG(INFO) << "Redirecting D3D10CreateDevice1" << '(' << pAdapter << ", " << DriverType << ", " << Software << ", " << std::hex << Flags << std::dec << ", " << HardwareLevel << ", " << SDKVersion << ", " << ppDevice << ')' << " ..."; - LOG(INFO) << "> Passing on to D3D10CreateDeviceAndSwapChain1:"; - - return D3D10CreateDeviceAndSwapChain1(pAdapter, DriverType, Software, Flags, HardwareLevel, SDKVersion, nullptr, nullptr, ppDevice); -} - -HOOK_EXPORT HRESULT WINAPI D3D10CreateDeviceAndSwapChain(IDXGIAdapter *pAdapter, D3D10_DRIVER_TYPE DriverType, HMODULE Software, UINT Flags, UINT SDKVersion, DXGI_SWAP_CHAIN_DESC *pSwapChainDesc, IDXGISwapChain **ppSwapChain, ID3D10Device **ppDevice) -{ - LOG(INFO) << "Redirecting D3D10CreateDeviceAndSwapChain" << '(' << pAdapter << ", " << DriverType << ", " << Software << ", " << std::hex << Flags << std::dec << ", " << SDKVersion << ", " << pSwapChainDesc << ", " << ppSwapChain << ", " << ppDevice << ')' << " ..."; - LOG(INFO) << "> Passing on to D3D10CreateDeviceAndSwapChain1:"; - - LoadLibraryW(L"d3d10_1.dll"); - - return D3D10CreateDeviceAndSwapChain1(pAdapter, DriverType, Software, Flags, D3D10_FEATURE_LEVEL_10_0, D3D10_1_SDK_VERSION, pSwapChainDesc, ppSwapChain, reinterpret_cast(ppDevice)); -} - -HOOK_EXPORT HRESULT WINAPI D3D10CreateDeviceAndSwapChain1(IDXGIAdapter *pAdapter, D3D10_DRIVER_TYPE DriverType, HMODULE Software, UINT Flags, D3D10_FEATURE_LEVEL1 HardwareLevel, UINT SDKVersion, DXGI_SWAP_CHAIN_DESC *pSwapChainDesc, IDXGISwapChain **ppSwapChain, ID3D10Device1 **ppDevice) -{ - LOG(INFO) << "Redirecting D3D10CreateDeviceAndSwapChain1" << '(' << pAdapter << ", " << DriverType << ", " << Software << ", " << std::hex << Flags << ", " << HardwareLevel << std::dec << ", " << SDKVersion << ", " << pSwapChainDesc << ", " << ppSwapChain << ", " << ppDevice << ')' << " ..."; - - HRESULT hr = reshade::hooks::call(D3D10CreateDeviceAndSwapChain1)(pAdapter, DriverType, Software, Flags, HardwareLevel, SDKVersion, nullptr, nullptr, ppDevice); - - if (FAILED(hr)) - { - LOG(WARN) << "> D3D10CreateDeviceAndSwapChain1 failed with error code " << std::hex << hr << std::dec << '!'; - return hr; - } - - // It is valid for the device out parameter to be NULL if the application wants to check feature level support, so just return early in that case - if (ppDevice == nullptr) - return hr; - - const auto device = *ppDevice; - - // Query for the DXGI device since we need to reference it in the hooked device - IDXGIDevice1 *dxgi_device = nullptr; - device->QueryInterface(&dxgi_device); - - const auto device_proxy = new D3D10Device(dxgi_device, device); - - // Swap chain creation is piped through the 'IDXGIFactory::CreateSwapChain' function hook - if (pSwapChainDesc != nullptr) - { - assert(ppSwapChain != nullptr); - - com_ptr adapter(pAdapter, false); - // Fall back to the same adapter as the device if it was not explicitly specified in the argument list - if (adapter == nullptr) - { - hr = dxgi_device->GetAdapter(&adapter); - assert(SUCCEEDED(hr)); - } - - // Time to find a factory associated with the target adapter and create a swap chain with it - com_ptr factory; - hr = adapter->GetParent(IID_PPV_ARGS(&factory)); - assert(SUCCEEDED(hr)); - - LOG(INFO) << "> Calling IDXGIFactory::CreateSwapChain:"; - - hr = factory->CreateSwapChain(device_proxy, pSwapChainDesc, ppSwapChain); - } - - if (SUCCEEDED(hr)) - { -#if RESHADE_VERBOSE_LOG - LOG(DEBUG) << "Returning IDXGIDevice1 object " << device_proxy->_dxgi_device << " and ID3D10Device1 object " << device_proxy << '.'; -#endif - *ppDevice = device_proxy; - } - else - { - // Swap chain creation failed, so do clean up - device_proxy->Release(); - } - - return hr; -} diff --git a/msvc/source/d3d10/d3d10_device.cpp b/msvc/source/d3d10/d3d10_device.cpp deleted file mode 100644 index fe11335..0000000 --- a/msvc/source/d3d10/d3d10_device.cpp +++ /dev/null @@ -1,560 +0,0 @@ -/** - * Copyright (C) 2014 Patrick Mours. All rights reserved. - * License: https://github.com/crosire/reshade#license - */ - -#include "log.hpp" -#include "d3d10_device.hpp" -#include "../dxgi/dxgi_device.hpp" -#include "runtime_d3d10.hpp" - -D3D10Device::D3D10Device(IDXGIDevice1 *dxgi_device, ID3D10Device1 *original) : - _orig(original), - _dxgi_device(new DXGIDevice(dxgi_device, this)) { - assert(original != nullptr); -} - -void D3D10Device::clear_drawcall_stats() -{ - _draw_call_tracker.reset(); - _active_depthstencil.reset(); - _clear_DSV_iter = 1; -} - -#if RESHADE_DX10_CAPTURE_DEPTH_BUFFERS -bool D3D10Device::save_depth_texture(ID3D10DepthStencilView *pDepthStencilView, bool cleared) -{ - if (_runtimes.empty()) - return false; - - const auto runtime = _runtimes.front(); - - if (!runtime->depth_buffer_before_clear) - return false; - if (!cleared && !runtime->extended_depth_buffer_detection) - return false; - - assert(pDepthStencilView != nullptr); - - // Retrieve texture from depth stencil - com_ptr resource; - pDepthStencilView->GetResource(&resource); - - com_ptr texture; - if (FAILED(resource->QueryInterface(&texture))) - return false; - - D3D10_TEXTURE2D_DESC desc; - texture->GetDesc(&desc); - - // Check if aspect ratio is similar to the back buffer one - const float screen_aspect_ratio = float(runtime->frame_width()) / float(runtime->frame_height()); - const float texture_aspect_ratio = float(desc.Width) / float(desc.Height); - - if (fabs(texture_aspect_ratio - screen_aspect_ratio) > 0.1f || desc.Width > runtime->frame_width()) - return false; - - // In case the depth texture is retrieved, we make a copy of it and store it in an ordered map to use it later in the final rendering stage. - if ((runtime->cleared_depth_buffer_index == 0 && cleared) || (_clear_DSV_iter <= runtime->cleared_depth_buffer_index)) - { - // Select an appropriate destination texture - com_ptr depth_texture_save = runtime->select_depth_texture_save(desc); - if (depth_texture_save == nullptr) - return false; - - // Copy the depth texture. This is necessary because the content of the depth texture is cleared. - // This way, we can retrieve this content in the final rendering stage - CopyResource(depth_texture_save.get(), texture.get()); - - // Store the saved texture in the ordered map. - _draw_call_tracker.track_depth_texture(runtime->depth_buffer_texture_format, _clear_DSV_iter, texture.get(), pDepthStencilView, depth_texture_save, cleared); - } - else - { - // Store a null depth texture in the ordered map in order to display it even if the user chose a previous cleared texture. - // This way the texture will still be visible in the depth buffer selection window and the user can choose it. - _draw_call_tracker.track_depth_texture(runtime->depth_buffer_texture_format, _clear_DSV_iter, texture.get(), pDepthStencilView, nullptr, cleared); - } - - _clear_DSV_iter++; - - return true; -} - -void D3D10Device::track_active_rendertargets(UINT NumViews, ID3D10RenderTargetView *const *ppRenderTargetViews, ID3D10DepthStencilView *pDepthStencilView) -{ - if (pDepthStencilView == nullptr || _runtimes.empty()) - return; - - const auto runtime = _runtimes.front(); - - _draw_call_tracker.track_rendertargets(runtime->depth_buffer_texture_format, pDepthStencilView, NumViews, ppRenderTargetViews); - - save_depth_texture(pDepthStencilView, false); -} -void D3D10Device::track_cleared_depthstencil(ID3D10DepthStencilView *pDepthStencilView) -{ - if (pDepthStencilView == nullptr) - return; - - save_depth_texture(pDepthStencilView, true); -} -#endif - -HRESULT STDMETHODCALLTYPE D3D10Device::QueryInterface(REFIID riid, void **ppvObj) -{ - if (ppvObj == nullptr) - return E_POINTER; - - if (riid == __uuidof(this) || - riid == __uuidof(ID3D10Device) || - riid == __uuidof(ID3D10Device1)) - { - AddRef(); - *ppvObj = this; - return S_OK; - } - - // Note: Objects must have an identity, so use DXGIDevice for IID_IUnknown - // See https://docs.microsoft.com/en-us/windows/desktop/com/rules-for-implementing-queryinterface - if (riid == __uuidof(IUnknown) || - riid == __uuidof(DXGIDevice) || - riid == __uuidof(IDXGIObject) || - riid == __uuidof(IDXGIDevice) || - riid == __uuidof(IDXGIDevice1) || - riid == __uuidof(IDXGIDevice2) || - riid == __uuidof(IDXGIDevice3)) - return _dxgi_device->QueryInterface(riid, ppvObj); - - return _orig->QueryInterface(riid, ppvObj); -} -ULONG STDMETHODCALLTYPE D3D10Device::AddRef() -{ - ++_ref; - - return _orig->AddRef(); -} -ULONG STDMETHODCALLTYPE D3D10Device::Release() -{ - --_ref; - - const ULONG ref = _orig->Release(); - - if (ref != 0 || _ref != 0) - return ref; - else if (ref != 0) - LOG(WARN) << "Reference count for ID3D10Device1 object " << this << " is inconsistent: " << ref << ", but expected 0."; - - assert(_ref <= 0); -#if RESHADE_VERBOSE_LOG - LOG(DEBUG) << "Destroyed ID3D10Device1 object " << this << '.'; -#endif - delete this; - return 0; -} - -void STDMETHODCALLTYPE D3D10Device::VSSetConstantBuffers(UINT StartSlot, UINT NumBuffers, ID3D10Buffer *const *ppConstantBuffers) -{ - _orig->VSSetConstantBuffers(StartSlot, NumBuffers, ppConstantBuffers); -} -void STDMETHODCALLTYPE D3D10Device::PSSetShaderResources(UINT StartSlot, UINT NumViews, ID3D10ShaderResourceView *const *ppShaderResourceViews) -{ - _orig->PSSetShaderResources(StartSlot, NumViews, ppShaderResourceViews); -} -void STDMETHODCALLTYPE D3D10Device::PSSetShader(ID3D10PixelShader *pPixelShader) -{ - _orig->PSSetShader(pPixelShader); -} -void STDMETHODCALLTYPE D3D10Device::PSSetSamplers(UINT StartSlot, UINT NumSamplers, ID3D10SamplerState *const *ppSamplers) -{ - _orig->PSSetSamplers(StartSlot, NumSamplers, ppSamplers); -} -void STDMETHODCALLTYPE D3D10Device::VSSetShader(ID3D10VertexShader *pVertexShader) -{ - _orig->VSSetShader(pVertexShader); -} -void STDMETHODCALLTYPE D3D10Device::DrawIndexed(UINT IndexCount, UINT StartIndexLocation, INT BaseVertexLocation) -{ - _orig->DrawIndexed(IndexCount, StartIndexLocation, BaseVertexLocation); - _draw_call_tracker.on_draw(this, IndexCount); -} -void STDMETHODCALLTYPE D3D10Device::Draw(UINT VertexCount, UINT StartVertexLocation) -{ - _orig->Draw(VertexCount, StartVertexLocation); - _draw_call_tracker.on_draw(this, VertexCount); -} -void STDMETHODCALLTYPE D3D10Device::PSSetConstantBuffers(UINT StartSlot, UINT NumBuffers, ID3D10Buffer *const *ppConstantBuffers) -{ - _orig->PSSetConstantBuffers(StartSlot, NumBuffers, ppConstantBuffers); -} -void STDMETHODCALLTYPE D3D10Device::IASetInputLayout(ID3D10InputLayout *pInputLayout) -{ - _orig->IASetInputLayout(pInputLayout); -} -void STDMETHODCALLTYPE D3D10Device::IASetVertexBuffers(UINT StartSlot, UINT NumBuffers, ID3D10Buffer *const *ppVertexBuffers, const UINT *pStrides, const UINT *pOffsets) -{ - _orig->IASetVertexBuffers(StartSlot, NumBuffers, ppVertexBuffers, pStrides, pOffsets); -} -void STDMETHODCALLTYPE D3D10Device::IASetIndexBuffer(ID3D10Buffer *pIndexBuffer, DXGI_FORMAT Format, UINT Offset) -{ - _orig->IASetIndexBuffer(pIndexBuffer, Format, Offset); -} -void STDMETHODCALLTYPE D3D10Device::DrawIndexedInstanced(UINT IndexCountPerInstance, UINT InstanceCount, UINT StartIndexLocation, INT BaseVertexLocation, UINT StartInstanceLocation) -{ - _orig->DrawIndexedInstanced(IndexCountPerInstance, InstanceCount, StartIndexLocation, BaseVertexLocation, StartInstanceLocation); - _draw_call_tracker.on_draw(this, IndexCountPerInstance * InstanceCount); -} -void STDMETHODCALLTYPE D3D10Device::DrawInstanced(UINT VertexCountPerInstance, UINT InstanceCount, UINT StartVertexLocation, UINT StartInstanceLocation) -{ - _orig->DrawInstanced(VertexCountPerInstance, InstanceCount, StartVertexLocation, StartInstanceLocation); - _draw_call_tracker.on_draw(this, VertexCountPerInstance * InstanceCount); -} -void STDMETHODCALLTYPE D3D10Device::GSSetConstantBuffers(UINT StartSlot, UINT NumBuffers, ID3D10Buffer *const *ppConstantBuffers) -{ - _orig->GSSetConstantBuffers(StartSlot, NumBuffers, ppConstantBuffers); -} -void STDMETHODCALLTYPE D3D10Device::GSSetShader(ID3D10GeometryShader *pShader) -{ - _orig->GSSetShader(pShader); -} -void STDMETHODCALLTYPE D3D10Device::IASetPrimitiveTopology(D3D10_PRIMITIVE_TOPOLOGY Topology) -{ - _orig->IASetPrimitiveTopology(Topology); -} -void STDMETHODCALLTYPE D3D10Device::VSSetShaderResources(UINT StartSlot, UINT NumViews, ID3D10ShaderResourceView *const *ppShaderResourceViews) -{ - _orig->VSSetShaderResources(StartSlot, NumViews, ppShaderResourceViews); -} -void STDMETHODCALLTYPE D3D10Device::VSSetSamplers(UINT StartSlot, UINT NumSamplers, ID3D10SamplerState *const *ppSamplers) -{ - _orig->VSSetSamplers(StartSlot, NumSamplers, ppSamplers); -} -void STDMETHODCALLTYPE D3D10Device::SetPredication(ID3D10Predicate *pPredicate, BOOL PredicateValue) -{ - _orig->SetPredication(pPredicate, PredicateValue); -} -void STDMETHODCALLTYPE D3D10Device::GSSetShaderResources(UINT StartSlot, UINT NumViews, ID3D10ShaderResourceView *const *ppShaderResourceViews) -{ - _orig->GSSetShaderResources(StartSlot, NumViews, ppShaderResourceViews); -} -void STDMETHODCALLTYPE D3D10Device::GSSetSamplers(UINT StartSlot, UINT NumSamplers, ID3D10SamplerState *const *ppSamplers) -{ - _orig->GSSetSamplers(StartSlot, NumSamplers, ppSamplers); -} -void STDMETHODCALLTYPE D3D10Device::OMSetRenderTargets(UINT NumViews, ID3D10RenderTargetView *const *ppRenderTargetViews, ID3D10DepthStencilView *pDepthStencilView) -{ -#if RESHADE_DX10_CAPTURE_DEPTH_BUFFERS - track_active_rendertargets(NumViews, ppRenderTargetViews, pDepthStencilView); -#endif - _orig->OMSetRenderTargets(NumViews, ppRenderTargetViews, pDepthStencilView); -} -void STDMETHODCALLTYPE D3D10Device::OMSetBlendState(ID3D10BlendState *pBlendState, const FLOAT BlendFactor[4], UINT SampleMask) -{ - _orig->OMSetBlendState(pBlendState, BlendFactor, SampleMask); -} -void STDMETHODCALLTYPE D3D10Device::OMSetDepthStencilState(ID3D10DepthStencilState *pDepthStencilState, UINT StencilRef) -{ - _orig->OMSetDepthStencilState(pDepthStencilState, StencilRef); -} -void STDMETHODCALLTYPE D3D10Device::SOSetTargets(UINT NumBuffers, ID3D10Buffer *const *ppSOTargets, const UINT *pOffsets) -{ - _orig->SOSetTargets(NumBuffers, ppSOTargets, pOffsets); -} -void STDMETHODCALLTYPE D3D10Device::DrawAuto() -{ - _orig->DrawAuto(); - _draw_call_tracker.on_draw(this, 0); -} -void STDMETHODCALLTYPE D3D10Device::RSSetState(ID3D10RasterizerState *pRasterizerState) -{ - _orig->RSSetState(pRasterizerState); -} -void STDMETHODCALLTYPE D3D10Device::RSSetViewports(UINT NumViewports, const D3D10_VIEWPORT *pViewports) -{ - _orig->RSSetViewports(NumViewports, pViewports); -} -void STDMETHODCALLTYPE D3D10Device::RSSetScissorRects(UINT NumRects, const D3D10_RECT *pRects) -{ - _orig->RSSetScissorRects(NumRects, pRects); -} -void STDMETHODCALLTYPE D3D10Device::CopySubresourceRegion(ID3D10Resource *pDstResource, UINT DstSubresource, UINT DstX, UINT DstY, UINT DstZ, ID3D10Resource *pSrcResource, UINT SrcSubresource, const D3D10_BOX *pSrcBox) -{ - _orig->CopySubresourceRegion(pDstResource, DstSubresource, DstX, DstY, DstZ, pSrcResource, SrcSubresource, pSrcBox); -} -void STDMETHODCALLTYPE D3D10Device::CopyResource(ID3D10Resource *pDstResource, ID3D10Resource *pSrcResource) -{ - _orig->CopyResource(pDstResource, pSrcResource); -} -void STDMETHODCALLTYPE D3D10Device::UpdateSubresource(ID3D10Resource *pDstResource, UINT DstSubresource, const D3D10_BOX *pDstBox, const void *pSrcData, UINT SrcRowPitch, UINT SrcDepthPitch) -{ - _orig->UpdateSubresource(pDstResource, DstSubresource, pDstBox, pSrcData, SrcRowPitch, SrcDepthPitch); -} -void STDMETHODCALLTYPE D3D10Device::ClearRenderTargetView(ID3D10RenderTargetView *pRenderTargetView, const FLOAT ColorRGBA[4]) -{ - _orig->ClearRenderTargetView(pRenderTargetView, ColorRGBA); -} -void STDMETHODCALLTYPE D3D10Device::ClearDepthStencilView(ID3D10DepthStencilView *pDepthStencilView, UINT ClearFlags, FLOAT Depth, UINT8 Stencil) -{ -#if RESHADE_DX10_CAPTURE_DEPTH_BUFFERS - if (ClearFlags & D3D10_CLEAR_DEPTH) - track_cleared_depthstencil(pDepthStencilView); -#endif - _orig->ClearDepthStencilView(pDepthStencilView, ClearFlags, Depth, Stencil); -} -void STDMETHODCALLTYPE D3D10Device::GenerateMips(ID3D10ShaderResourceView *pShaderResourceView) -{ - _orig->GenerateMips(pShaderResourceView); -} -void STDMETHODCALLTYPE D3D10Device::ResolveSubresource(ID3D10Resource *pDstResource, UINT DstSubresource, ID3D10Resource *pSrcResource, UINT SrcSubresource, DXGI_FORMAT Format) -{ - _orig->ResolveSubresource(pDstResource, DstSubresource, pSrcResource, SrcSubresource, Format); -} -void STDMETHODCALLTYPE D3D10Device::VSGetConstantBuffers(UINT StartSlot, UINT NumBuffers, ID3D10Buffer **ppConstantBuffers) -{ - _orig->VSGetConstantBuffers(StartSlot, NumBuffers, ppConstantBuffers); -} -void STDMETHODCALLTYPE D3D10Device::PSGetShaderResources(UINT StartSlot, UINT NumViews, ID3D10ShaderResourceView **ppShaderResourceViews) -{ - _orig->PSGetShaderResources(StartSlot, NumViews, ppShaderResourceViews); -} -void STDMETHODCALLTYPE D3D10Device::PSGetShader(ID3D10PixelShader **ppPixelShader) -{ - _orig->PSGetShader(ppPixelShader); -} -void STDMETHODCALLTYPE D3D10Device::PSGetSamplers(UINT StartSlot, UINT NumSamplers, ID3D10SamplerState **ppSamplers) -{ - _orig->PSGetSamplers(StartSlot, NumSamplers, ppSamplers); -} -void STDMETHODCALLTYPE D3D10Device::VSGetShader(ID3D10VertexShader **ppVertexShader) -{ - _orig->VSGetShader(ppVertexShader); -} -void STDMETHODCALLTYPE D3D10Device::PSGetConstantBuffers(UINT StartSlot, UINT NumBuffers, ID3D10Buffer **ppConstantBuffers) -{ - _orig->PSGetConstantBuffers(StartSlot, NumBuffers, ppConstantBuffers); -} -void STDMETHODCALLTYPE D3D10Device::IAGetInputLayout(ID3D10InputLayout **ppInputLayout) -{ - _orig->IAGetInputLayout(ppInputLayout); -} -void STDMETHODCALLTYPE D3D10Device::IAGetVertexBuffers(UINT StartSlot, UINT NumBuffers, ID3D10Buffer **ppVertexBuffers, UINT *pStrides, UINT *pOffsets) -{ - _orig->IAGetVertexBuffers(StartSlot, NumBuffers, ppVertexBuffers, pStrides, pOffsets); -} -void STDMETHODCALLTYPE D3D10Device::IAGetIndexBuffer(ID3D10Buffer **pIndexBuffer, DXGI_FORMAT *Format, UINT *Offset) -{ - _orig->IAGetIndexBuffer(pIndexBuffer, Format, Offset); -} -void STDMETHODCALLTYPE D3D10Device::GSGetConstantBuffers(UINT StartSlot, UINT NumBuffers, ID3D10Buffer **ppConstantBuffers) -{ - _orig->GSGetConstantBuffers(StartSlot, NumBuffers, ppConstantBuffers); -} -void STDMETHODCALLTYPE D3D10Device::GSGetShader(ID3D10GeometryShader **ppGeometryShader) -{ - _orig->GSGetShader(ppGeometryShader); -} -void STDMETHODCALLTYPE D3D10Device::IAGetPrimitiveTopology(D3D10_PRIMITIVE_TOPOLOGY *pTopology) -{ - _orig->IAGetPrimitiveTopology(pTopology); -} -void STDMETHODCALLTYPE D3D10Device::VSGetShaderResources(UINT StartSlot, UINT NumViews, ID3D10ShaderResourceView **ppShaderResourceViews) -{ - _orig->VSGetShaderResources(StartSlot, NumViews, ppShaderResourceViews); -} -void STDMETHODCALLTYPE D3D10Device::VSGetSamplers(UINT StartSlot, UINT NumSamplers, ID3D10SamplerState **ppSamplers) -{ - _orig->VSGetSamplers(StartSlot, NumSamplers, ppSamplers); -} -void STDMETHODCALLTYPE D3D10Device::GetPredication(ID3D10Predicate **ppPredicate, BOOL *pPredicateValue) -{ - _orig->GetPredication(ppPredicate, pPredicateValue); -} -void STDMETHODCALLTYPE D3D10Device::GSGetShaderResources(UINT StartSlot, UINT NumViews, ID3D10ShaderResourceView **ppShaderResourceViews) -{ - _orig->GSGetShaderResources(StartSlot, NumViews, ppShaderResourceViews); -} -void STDMETHODCALLTYPE D3D10Device::GSGetSamplers(UINT StartSlot, UINT NumSamplers, ID3D10SamplerState **ppSamplers) -{ - _orig->GSGetSamplers(StartSlot, NumSamplers, ppSamplers); -} -void STDMETHODCALLTYPE D3D10Device::OMGetRenderTargets(UINT NumViews, ID3D10RenderTargetView **ppRenderTargetViews, ID3D10DepthStencilView **ppDepthStencilView) -{ - _orig->OMGetRenderTargets(NumViews, ppRenderTargetViews, ppDepthStencilView); -} -void STDMETHODCALLTYPE D3D10Device::OMGetBlendState(ID3D10BlendState **ppBlendState, FLOAT BlendFactor[4], UINT *pSampleMask) -{ - _orig->OMGetBlendState(ppBlendState, BlendFactor, pSampleMask); -} -void STDMETHODCALLTYPE D3D10Device::OMGetDepthStencilState(ID3D10DepthStencilState **ppDepthStencilState, UINT *pStencilRef) -{ - _orig->OMGetDepthStencilState(ppDepthStencilState, pStencilRef); -} -void STDMETHODCALLTYPE D3D10Device::SOGetTargets(UINT NumBuffers, ID3D10Buffer **ppSOTargets, UINT *pOffsets) -{ - _orig->SOGetTargets(NumBuffers, ppSOTargets, pOffsets); -} -void STDMETHODCALLTYPE D3D10Device::RSGetState(ID3D10RasterizerState **ppRasterizerState) -{ - _orig->RSGetState(ppRasterizerState); -} -void STDMETHODCALLTYPE D3D10Device::RSGetViewports(UINT *NumViewports, D3D10_VIEWPORT *pViewports) -{ - _orig->RSGetViewports(NumViewports, pViewports); -} -void STDMETHODCALLTYPE D3D10Device::RSGetScissorRects(UINT *NumRects, D3D10_RECT *pRects) -{ - _orig->RSGetScissorRects(NumRects, pRects); -} -HRESULT STDMETHODCALLTYPE D3D10Device::GetDeviceRemovedReason() -{ - return _orig->GetDeviceRemovedReason(); -} -HRESULT STDMETHODCALLTYPE D3D10Device::SetExceptionMode(UINT RaiseFlags) -{ - return _orig->SetExceptionMode(RaiseFlags); -} -UINT STDMETHODCALLTYPE D3D10Device::GetExceptionMode() -{ - return _orig->GetExceptionMode(); -} -HRESULT STDMETHODCALLTYPE D3D10Device::GetPrivateData(REFGUID guid, UINT *pDataSize, void *pData) -{ - return _orig->GetPrivateData(guid, pDataSize, pData); -} -HRESULT STDMETHODCALLTYPE D3D10Device::SetPrivateData(REFGUID guid, UINT DataSize, const void *pData) -{ - return _orig->SetPrivateData(guid, DataSize, pData); -} -HRESULT STDMETHODCALLTYPE D3D10Device::SetPrivateDataInterface(REFGUID guid, const IUnknown *pData) -{ - return _orig->SetPrivateDataInterface(guid, pData); -} -void STDMETHODCALLTYPE D3D10Device::ClearState() -{ - _orig->ClearState(); -} -void STDMETHODCALLTYPE D3D10Device::Flush() -{ - _orig->Flush(); -} -HRESULT STDMETHODCALLTYPE D3D10Device::CreateBuffer(const D3D10_BUFFER_DESC *pDesc, const D3D10_SUBRESOURCE_DATA *pInitialData, ID3D10Buffer **ppBuffer) -{ - return _orig->CreateBuffer(pDesc, pInitialData, ppBuffer); -} -HRESULT STDMETHODCALLTYPE D3D10Device::CreateTexture1D(const D3D10_TEXTURE1D_DESC *pDesc, const D3D10_SUBRESOURCE_DATA *pInitialData, ID3D10Texture1D **ppTexture1D) -{ - return _orig->CreateTexture1D(pDesc, pInitialData, ppTexture1D); -} -HRESULT STDMETHODCALLTYPE D3D10Device::CreateTexture2D(const D3D10_TEXTURE2D_DESC *pDesc, const D3D10_SUBRESOURCE_DATA *pInitialData, ID3D10Texture2D **ppTexture2D) -{ - return _orig->CreateTexture2D(pDesc, pInitialData, ppTexture2D); -} -HRESULT STDMETHODCALLTYPE D3D10Device::CreateTexture3D(const D3D10_TEXTURE3D_DESC *pDesc, const D3D10_SUBRESOURCE_DATA *pInitialData, ID3D10Texture3D **ppTexture3D) -{ - return _orig->CreateTexture3D(pDesc, pInitialData, ppTexture3D); -} -HRESULT STDMETHODCALLTYPE D3D10Device::CreateShaderResourceView(ID3D10Resource *pResource, const D3D10_SHADER_RESOURCE_VIEW_DESC *pDesc, ID3D10ShaderResourceView **ppSRView) -{ - return _orig->CreateShaderResourceView(pResource, pDesc, ppSRView); -} -HRESULT STDMETHODCALLTYPE D3D10Device::CreateRenderTargetView(ID3D10Resource *pResource, const D3D10_RENDER_TARGET_VIEW_DESC *pDesc, ID3D10RenderTargetView **ppRTView) -{ - return _orig->CreateRenderTargetView(pResource, pDesc, ppRTView); -} -HRESULT STDMETHODCALLTYPE D3D10Device::CreateDepthStencilView(ID3D10Resource *pResource, const D3D10_DEPTH_STENCIL_VIEW_DESC *pDesc, ID3D10DepthStencilView **ppDepthStencilView) -{ - return _orig->CreateDepthStencilView(pResource, pDesc, ppDepthStencilView); -} -HRESULT STDMETHODCALLTYPE D3D10Device::CreateInputLayout(const D3D10_INPUT_ELEMENT_DESC *pInputElementDescs, UINT NumElements, const void *pShaderBytecodeWithInputSignature, SIZE_T BytecodeLength, ID3D10InputLayout **ppInputLayout) -{ - return _orig->CreateInputLayout(pInputElementDescs, NumElements, pShaderBytecodeWithInputSignature, BytecodeLength, ppInputLayout); -} -HRESULT STDMETHODCALLTYPE D3D10Device::CreateVertexShader(const void *pShaderBytecode, SIZE_T BytecodeLength, ID3D10VertexShader **ppVertexShader) -{ - return _orig->CreateVertexShader(pShaderBytecode, BytecodeLength, ppVertexShader); -} -HRESULT STDMETHODCALLTYPE D3D10Device::CreateGeometryShader(const void *pShaderBytecode, SIZE_T BytecodeLength, ID3D10GeometryShader **ppGeometryShader) -{ - return _orig->CreateGeometryShader(pShaderBytecode, BytecodeLength, ppGeometryShader); -} -HRESULT STDMETHODCALLTYPE D3D10Device::CreateGeometryShaderWithStreamOutput(const void *pShaderBytecode, SIZE_T BytecodeLength, const D3D10_SO_DECLARATION_ENTRY *pSODeclaration, UINT NumEntries, UINT OutputStreamStride, ID3D10GeometryShader **ppGeometryShader) -{ - return _orig->CreateGeometryShaderWithStreamOutput(pShaderBytecode, BytecodeLength, pSODeclaration, NumEntries, OutputStreamStride, ppGeometryShader); -} -HRESULT STDMETHODCALLTYPE D3D10Device::CreatePixelShader(const void *pShaderBytecode, SIZE_T BytecodeLength, ID3D10PixelShader **ppPixelShader) -{ - return _orig->CreatePixelShader(pShaderBytecode, BytecodeLength, ppPixelShader); -} -HRESULT STDMETHODCALLTYPE D3D10Device::CreateBlendState(const D3D10_BLEND_DESC *pBlendStateDesc, ID3D10BlendState **ppBlendState) -{ - return _orig->CreateBlendState(pBlendStateDesc, ppBlendState); -} -HRESULT STDMETHODCALLTYPE D3D10Device::CreateDepthStencilState(const D3D10_DEPTH_STENCIL_DESC *pDepthStencilDesc, ID3D10DepthStencilState **ppDepthStencilState) -{ - return _orig->CreateDepthStencilState(pDepthStencilDesc, ppDepthStencilState); -} -HRESULT STDMETHODCALLTYPE D3D10Device::CreateRasterizerState(const D3D10_RASTERIZER_DESC *pRasterizerDesc, ID3D10RasterizerState **ppRasterizerState) -{ - return _orig->CreateRasterizerState(pRasterizerDesc, ppRasterizerState); -} -HRESULT STDMETHODCALLTYPE D3D10Device::CreateSamplerState(const D3D10_SAMPLER_DESC *pSamplerDesc, ID3D10SamplerState **ppSamplerState) -{ - return _orig->CreateSamplerState(pSamplerDesc, ppSamplerState); -} -HRESULT STDMETHODCALLTYPE D3D10Device::CreateQuery(const D3D10_QUERY_DESC *pQueryDesc, ID3D10Query **ppQuery) -{ - return _orig->CreateQuery(pQueryDesc, ppQuery); -} -HRESULT STDMETHODCALLTYPE D3D10Device::CreatePredicate(const D3D10_QUERY_DESC *pPredicateDesc, ID3D10Predicate **ppPredicate) -{ - return _orig->CreatePredicate(pPredicateDesc, ppPredicate); -} -HRESULT STDMETHODCALLTYPE D3D10Device::CreateCounter(const D3D10_COUNTER_DESC *pCounterDesc, ID3D10Counter **ppCounter) -{ - return _orig->CreateCounter(pCounterDesc, ppCounter); -} -HRESULT STDMETHODCALLTYPE D3D10Device::CheckFormatSupport(DXGI_FORMAT Format, UINT *pFormatSupport) -{ - return _orig->CheckFormatSupport(Format, pFormatSupport); -} -HRESULT STDMETHODCALLTYPE D3D10Device::CheckMultisampleQualityLevels(DXGI_FORMAT Format, UINT SampleCount, UINT *pNumQualityLevels) -{ - return _orig->CheckMultisampleQualityLevels(Format, SampleCount, pNumQualityLevels); -} -void STDMETHODCALLTYPE D3D10Device::CheckCounterInfo(D3D10_COUNTER_INFO *pCounterInfo) -{ - _orig->CheckCounterInfo(pCounterInfo); -} -HRESULT STDMETHODCALLTYPE D3D10Device::CheckCounter(const D3D10_COUNTER_DESC *pDesc, D3D10_COUNTER_TYPE *pType, UINT *pActiveCounters, LPSTR szName, UINT *pNameLength, LPSTR szUnits, UINT *pUnitsLength, LPSTR szDescription, UINT *pDescriptionLength) -{ - return _orig->CheckCounter(pDesc, pType, pActiveCounters, szName, pNameLength, szUnits, pUnitsLength, szDescription, pDescriptionLength); -} -UINT STDMETHODCALLTYPE D3D10Device::GetCreationFlags() -{ - return _orig->GetCreationFlags(); -} -HRESULT STDMETHODCALLTYPE D3D10Device::OpenSharedResource(HANDLE hResource, REFIID ReturnedInterface, void **ppResource) -{ - return _orig->OpenSharedResource(hResource, ReturnedInterface, ppResource); -} -void STDMETHODCALLTYPE D3D10Device::SetTextFilterSize(UINT Width, UINT Height) -{ - _orig->SetTextFilterSize(Width, Height); -} -void STDMETHODCALLTYPE D3D10Device::GetTextFilterSize(UINT *pWidth, UINT *pHeight) -{ - _orig->GetTextFilterSize(pWidth, pHeight); -} - -HRESULT STDMETHODCALLTYPE D3D10Device::CreateShaderResourceView1(ID3D10Resource *pResource, const D3D10_SHADER_RESOURCE_VIEW_DESC1 *pDesc, ID3D10ShaderResourceView1 **ppSRView) -{ - return _orig->CreateShaderResourceView1(pResource, pDesc, ppSRView); -} -HRESULT STDMETHODCALLTYPE D3D10Device::CreateBlendState1(const D3D10_BLEND_DESC1 *pBlendStateDesc, ID3D10BlendState1 **ppBlendState) -{ - return _orig->CreateBlendState1(pBlendStateDesc, ppBlendState); -} -D3D10_FEATURE_LEVEL1 STDMETHODCALLTYPE D3D10Device::GetFeatureLevel() -{ - return _orig->GetFeatureLevel(); -} diff --git a/msvc/source/d3d10/d3d10_device.hpp b/msvc/source/d3d10/d3d10_device.hpp deleted file mode 100644 index 8260617..0000000 --- a/msvc/source/d3d10/d3d10_device.hpp +++ /dev/null @@ -1,144 +0,0 @@ -/** - * Copyright (C) 2014 Patrick Mours. All rights reserved. - * License: https://github.com/crosire/reshade#license - */ - -#pragma once - -#include -#include "draw_call_tracker.hpp" - -struct DXGIDevice; -namespace reshade::d3d10 { class runtime_d3d10; } - -struct __declspec(uuid("88399375-734F-4892-A95F-70DD42CE7CDD")) D3D10Device : ID3D10Device1 -{ - D3D10Device(IDXGIDevice1 *dxgi_device, ID3D10Device1 *original); - - D3D10Device(const D3D10Device &) = delete; - D3D10Device &operator=(const D3D10Device &) = delete; - - HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void **ppvObj) override; - ULONG STDMETHODCALLTYPE AddRef() override; - ULONG STDMETHODCALLTYPE Release() override; - - #pragma region ID3D10Device - void STDMETHODCALLTYPE VSSetConstantBuffers(UINT StartSlot, UINT NumBuffers, ID3D10Buffer *const *ppConstantBuffers) override; - void STDMETHODCALLTYPE PSSetShaderResources(UINT StartSlot, UINT NumViews, ID3D10ShaderResourceView *const *ppShaderResourceViews) override; - void STDMETHODCALLTYPE PSSetShader(ID3D10PixelShader *pPixelShader) override; - void STDMETHODCALLTYPE PSSetSamplers(UINT StartSlot, UINT NumSamplers, ID3D10SamplerState *const *ppSamplers) override; - void STDMETHODCALLTYPE VSSetShader(ID3D10VertexShader *pVertexShader) override; - void STDMETHODCALLTYPE DrawIndexed(UINT IndexCount, UINT StartIndexLocation, INT BaseVertexLocation) override; - void STDMETHODCALLTYPE Draw(UINT VertexCount, UINT StartVertexLocation) override; - void STDMETHODCALLTYPE PSSetConstantBuffers(UINT StartSlot, UINT NumBuffers, ID3D10Buffer *const *ppConstantBuffers) override; - void STDMETHODCALLTYPE IASetInputLayout(ID3D10InputLayout *pInputLayout) override; - void STDMETHODCALLTYPE IASetVertexBuffers(UINT StartSlot, UINT NumBuffers, ID3D10Buffer *const *ppVertexBuffers, const UINT *pStrides, const UINT *pOffsets) override; - void STDMETHODCALLTYPE IASetIndexBuffer(ID3D10Buffer *pIndexBuffer, DXGI_FORMAT Format, UINT Offset) override; - void STDMETHODCALLTYPE DrawIndexedInstanced(UINT IndexCountPerInstance, UINT InstanceCount, UINT StartIndexLocation, INT BaseVertexLocation, UINT StartInstanceLocation) override; - void STDMETHODCALLTYPE DrawInstanced(UINT VertexCountPerInstance, UINT InstanceCount, UINT StartVertexLocation, UINT StartInstanceLocation) override; - void STDMETHODCALLTYPE GSSetConstantBuffers(UINT StartSlot, UINT NumBuffers, ID3D10Buffer *const *ppConstantBuffers) override; - void STDMETHODCALLTYPE GSSetShader(ID3D10GeometryShader *pShader) override; - void STDMETHODCALLTYPE IASetPrimitiveTopology(D3D10_PRIMITIVE_TOPOLOGY Topology) override; - void STDMETHODCALLTYPE VSSetShaderResources(UINT StartSlot, UINT NumViews, ID3D10ShaderResourceView *const *ppShaderResourceViews) override; - void STDMETHODCALLTYPE VSSetSamplers(UINT StartSlot, UINT NumSamplers, ID3D10SamplerState *const *ppSamplers) override; - void STDMETHODCALLTYPE SetPredication(ID3D10Predicate *pPredicate, BOOL PredicateValue) override; - void STDMETHODCALLTYPE GSSetShaderResources(UINT StartSlot, UINT NumViews, ID3D10ShaderResourceView *const *ppShaderResourceViews) override; - void STDMETHODCALLTYPE GSSetSamplers(UINT StartSlot, UINT NumSamplers, ID3D10SamplerState *const *ppSamplers) override; - void STDMETHODCALLTYPE OMSetRenderTargets(UINT NumViews, ID3D10RenderTargetView *const *ppRenderTargetViews, ID3D10DepthStencilView *pDepthStencilView) override; - void STDMETHODCALLTYPE OMSetBlendState(ID3D10BlendState *pBlendState, const FLOAT BlendFactor[4], UINT SampleMask) override; - void STDMETHODCALLTYPE OMSetDepthStencilState(ID3D10DepthStencilState *pDepthStencilState, UINT StencilRef) override; - void STDMETHODCALLTYPE SOSetTargets(UINT NumBuffers, ID3D10Buffer *const *ppSOTargets, const UINT *pOffsets) override; - void STDMETHODCALLTYPE DrawAuto() override; - void STDMETHODCALLTYPE RSSetState(ID3D10RasterizerState *pRasterizerState) override; - void STDMETHODCALLTYPE RSSetViewports(UINT NumViewports, const D3D10_VIEWPORT *pViewports) override; - void STDMETHODCALLTYPE RSSetScissorRects(UINT NumRects, const D3D10_RECT *pRects) override; - void STDMETHODCALLTYPE CopySubresourceRegion(ID3D10Resource *pDstResource, UINT DstSubresource, UINT DstX, UINT DstY, UINT DstZ, ID3D10Resource *pSrcResource, UINT SrcSubresource, const D3D10_BOX *pSrcBox) override; - void STDMETHODCALLTYPE CopyResource(ID3D10Resource *pDstResource, ID3D10Resource *pSrcResource) override; - void STDMETHODCALLTYPE UpdateSubresource(ID3D10Resource *pDstResource, UINT DstSubresource, const D3D10_BOX *pDstBox, const void *pSrcData, UINT SrcRowPitch, UINT SrcDepthPitch) override; - void STDMETHODCALLTYPE ClearRenderTargetView(ID3D10RenderTargetView *pRenderTargetView, const FLOAT ColorRGBA[4]) override; - void STDMETHODCALLTYPE ClearDepthStencilView(ID3D10DepthStencilView *pDepthStencilView, UINT ClearFlags, FLOAT Depth, UINT8 Stencil) override; - void STDMETHODCALLTYPE GenerateMips(ID3D10ShaderResourceView *pShaderResourceView) override; - void STDMETHODCALLTYPE ResolveSubresource(ID3D10Resource *pDstResource, UINT DstSubresource, ID3D10Resource *pSrcResource, UINT SrcSubresource, DXGI_FORMAT Format) override; - void STDMETHODCALLTYPE VSGetConstantBuffers(UINT StartSlot, UINT NumBuffers, ID3D10Buffer **ppConstantBuffers) override; - void STDMETHODCALLTYPE PSGetShaderResources(UINT StartSlot, UINT NumViews, ID3D10ShaderResourceView **ppShaderResourceViews) override; - void STDMETHODCALLTYPE PSGetShader(ID3D10PixelShader **ppPixelShader) override; - void STDMETHODCALLTYPE PSGetSamplers(UINT StartSlot, UINT NumSamplers, ID3D10SamplerState **ppSamplers) override; - void STDMETHODCALLTYPE VSGetShader(ID3D10VertexShader **ppVertexShader) override; - void STDMETHODCALLTYPE PSGetConstantBuffers(UINT StartSlot, UINT NumBuffers, ID3D10Buffer **ppConstantBuffers) override; - void STDMETHODCALLTYPE IAGetInputLayout(ID3D10InputLayout **ppInputLayout) override; - void STDMETHODCALLTYPE IAGetVertexBuffers(UINT StartSlot, UINT NumBuffers, ID3D10Buffer **ppVertexBuffers, UINT *pStrides, UINT *pOffsets) override; - void STDMETHODCALLTYPE IAGetIndexBuffer(ID3D10Buffer **pIndexBuffer, DXGI_FORMAT *Format, UINT *Offset) override; - void STDMETHODCALLTYPE GSGetConstantBuffers(UINT StartSlot, UINT NumBuffers, ID3D10Buffer **ppConstantBuffers) override; - void STDMETHODCALLTYPE GSGetShader(ID3D10GeometryShader **ppGeometryShader) override; - void STDMETHODCALLTYPE IAGetPrimitiveTopology(D3D10_PRIMITIVE_TOPOLOGY *pTopology) override; - void STDMETHODCALLTYPE VSGetShaderResources(UINT StartSlot, UINT NumViews, ID3D10ShaderResourceView **ppShaderResourceViews) override; - void STDMETHODCALLTYPE VSGetSamplers(UINT StartSlot, UINT NumSamplers, ID3D10SamplerState **ppSamplers) override; - void STDMETHODCALLTYPE GetPredication(ID3D10Predicate **ppPredicate, BOOL *pPredicateValue) override; - void STDMETHODCALLTYPE GSGetShaderResources(UINT StartSlot, UINT NumViews, ID3D10ShaderResourceView **ppShaderResourceViews) override; - void STDMETHODCALLTYPE GSGetSamplers(UINT StartSlot, UINT NumSamplers, ID3D10SamplerState **ppSamplers) override; - void STDMETHODCALLTYPE OMGetRenderTargets(UINT NumViews, ID3D10RenderTargetView **ppRenderTargetViews, ID3D10DepthStencilView **ppDepthStencilView) override; - void STDMETHODCALLTYPE OMGetBlendState(ID3D10BlendState **ppBlendState, FLOAT BlendFactor[4], UINT *pSampleMask) override; - void STDMETHODCALLTYPE OMGetDepthStencilState(ID3D10DepthStencilState **ppDepthStencilState, UINT *pStencilRef) override; - void STDMETHODCALLTYPE SOGetTargets(UINT NumBuffers, ID3D10Buffer **ppSOTargets, UINT *pOffsets) override; - void STDMETHODCALLTYPE RSGetState(ID3D10RasterizerState **ppRasterizerState) override; - void STDMETHODCALLTYPE RSGetViewports(UINT *NumViewports, D3D10_VIEWPORT *pViewports) override; - void STDMETHODCALLTYPE RSGetScissorRects(UINT *NumRects, D3D10_RECT *pRects) override; - HRESULT STDMETHODCALLTYPE GetDeviceRemovedReason() override; - HRESULT STDMETHODCALLTYPE SetExceptionMode(UINT RaiseFlags) override; - UINT STDMETHODCALLTYPE GetExceptionMode() override; - HRESULT STDMETHODCALLTYPE GetPrivateData(REFGUID guid, UINT *pDataSize, void *pData) override; - HRESULT STDMETHODCALLTYPE SetPrivateData(REFGUID guid, UINT DataSize, const void *pData) override; - HRESULT STDMETHODCALLTYPE SetPrivateDataInterface(REFGUID guid, const IUnknown *pData) override; - void STDMETHODCALLTYPE ClearState() override; - void STDMETHODCALLTYPE Flush() override; - HRESULT STDMETHODCALLTYPE CreateBuffer(const D3D10_BUFFER_DESC *pDesc, const D3D10_SUBRESOURCE_DATA *pInitialData, ID3D10Buffer **ppBuffer) override; - HRESULT STDMETHODCALLTYPE CreateTexture1D(const D3D10_TEXTURE1D_DESC *pDesc, const D3D10_SUBRESOURCE_DATA *pInitialData, ID3D10Texture1D **ppTexture1D) override; - HRESULT STDMETHODCALLTYPE CreateTexture2D(const D3D10_TEXTURE2D_DESC *pDesc, const D3D10_SUBRESOURCE_DATA *pInitialData, ID3D10Texture2D **ppTexture2D) override; - HRESULT STDMETHODCALLTYPE CreateTexture3D(const D3D10_TEXTURE3D_DESC *pDesc, const D3D10_SUBRESOURCE_DATA *pInitialData, ID3D10Texture3D **ppTexture3D) override; - HRESULT STDMETHODCALLTYPE CreateShaderResourceView(ID3D10Resource *pResource, const D3D10_SHADER_RESOURCE_VIEW_DESC *pDesc, ID3D10ShaderResourceView **ppSRView) override; - HRESULT STDMETHODCALLTYPE CreateRenderTargetView(ID3D10Resource *pResource, const D3D10_RENDER_TARGET_VIEW_DESC *pDesc, ID3D10RenderTargetView **ppRTView) override; - HRESULT STDMETHODCALLTYPE CreateDepthStencilView(ID3D10Resource *pResource, const D3D10_DEPTH_STENCIL_VIEW_DESC *pDesc, ID3D10DepthStencilView **ppDepthStencilView) override; - HRESULT STDMETHODCALLTYPE CreateInputLayout(const D3D10_INPUT_ELEMENT_DESC *pInputElementDescs, UINT NumElements, const void *pShaderBytecodeWithInputSignature, SIZE_T BytecodeLength, ID3D10InputLayout **ppInputLayout) override; - HRESULT STDMETHODCALLTYPE CreateVertexShader(const void *pShaderBytecode, SIZE_T BytecodeLength, ID3D10VertexShader **ppVertexShader) override; - HRESULT STDMETHODCALLTYPE CreateGeometryShader(const void *pShaderBytecode, SIZE_T BytecodeLength, ID3D10GeometryShader **ppGeometryShader) override; - HRESULT STDMETHODCALLTYPE CreateGeometryShaderWithStreamOutput(const void *pShaderBytecode, SIZE_T BytecodeLength, const D3D10_SO_DECLARATION_ENTRY *pSODeclaration, UINT NumEntries, UINT OutputStreamStride, ID3D10GeometryShader **ppGeometryShader) override; - HRESULT STDMETHODCALLTYPE CreatePixelShader(const void *pShaderBytecode, SIZE_T BytecodeLength, ID3D10PixelShader **ppPixelShader) override; - HRESULT STDMETHODCALLTYPE CreateBlendState(const D3D10_BLEND_DESC *pBlendStateDesc, ID3D10BlendState **ppBlendState) override; - HRESULT STDMETHODCALLTYPE CreateDepthStencilState(const D3D10_DEPTH_STENCIL_DESC *pDepthStencilDesc, ID3D10DepthStencilState **ppDepthStencilState) override; - HRESULT STDMETHODCALLTYPE CreateRasterizerState(const D3D10_RASTERIZER_DESC *pRasterizerDesc, ID3D10RasterizerState **ppRasterizerState) override; - HRESULT STDMETHODCALLTYPE CreateSamplerState(const D3D10_SAMPLER_DESC *pSamplerDesc, ID3D10SamplerState **ppSamplerState) override; - HRESULT STDMETHODCALLTYPE CreateQuery(const D3D10_QUERY_DESC *pQueryDesc, ID3D10Query **ppQuery) override; - HRESULT STDMETHODCALLTYPE CreatePredicate(const D3D10_QUERY_DESC *pPredicateDesc, ID3D10Predicate **ppPredicate) override; - HRESULT STDMETHODCALLTYPE CreateCounter(const D3D10_COUNTER_DESC *pCounterDesc, ID3D10Counter **ppCounter) override; - HRESULT STDMETHODCALLTYPE CheckFormatSupport(DXGI_FORMAT Format, UINT *pFormatSupport) override; - HRESULT STDMETHODCALLTYPE CheckMultisampleQualityLevels(DXGI_FORMAT Format, UINT SampleCount, UINT *pNumQualityLevels) override; - void STDMETHODCALLTYPE CheckCounterInfo(D3D10_COUNTER_INFO *pCounterInfo) override; - HRESULT STDMETHODCALLTYPE CheckCounter(const D3D10_COUNTER_DESC *pDesc, D3D10_COUNTER_TYPE *pType, UINT *pActiveCounters, LPSTR szName, UINT *pNameLength, LPSTR szUnits, UINT *pUnitsLength, LPSTR szDescription, UINT *pDescriptionLength) override; - UINT STDMETHODCALLTYPE GetCreationFlags() override; - HRESULT STDMETHODCALLTYPE OpenSharedResource(HANDLE hResource, REFIID ReturnedInterface, void **ppResource) override; - void STDMETHODCALLTYPE SetTextFilterSize(UINT Width, UINT Height) override; - void STDMETHODCALLTYPE GetTextFilterSize(UINT *pWidth, UINT *pHeight) override; - #pragma endregion - #pragma region ID3D10Device1 - HRESULT STDMETHODCALLTYPE CreateShaderResourceView1(ID3D10Resource *pResource, const D3D10_SHADER_RESOURCE_VIEW_DESC1 *pDesc, ID3D10ShaderResourceView1 **ppSRView) override; - HRESULT STDMETHODCALLTYPE CreateBlendState1(const D3D10_BLEND_DESC1 *pBlendStateDesc, ID3D10BlendState1 **ppBlendState) override; - D3D10_FEATURE_LEVEL1 STDMETHODCALLTYPE GetFeatureLevel() override; - #pragma endregion - - void clear_drawcall_stats(); - -#if RESHADE_DX10_CAPTURE_DEPTH_BUFFERS - bool save_depth_texture(ID3D10DepthStencilView *pDepthStencilView, bool cleared); - - void track_active_rendertargets(UINT NumViews, ID3D10RenderTargetView *const *ppRenderTargetViews, ID3D10DepthStencilView *pDepthStencilView); - void track_cleared_depthstencil(ID3D10DepthStencilView* pDepthStencilView); -#endif - - LONG _ref = 1; - ID3D10Device1 *_orig; - DXGIDevice *const _dxgi_device; - std::vector> _runtimes; - com_ptr _active_depthstencil; - reshade::d3d10::draw_call_tracker _draw_call_tracker; - unsigned int _clear_DSV_iter = 1; -}; diff --git a/msvc/source/d3d10/draw_call_tracker.cpp b/msvc/source/d3d10/draw_call_tracker.cpp deleted file mode 100644 index bc99d79..0000000 --- a/msvc/source/d3d10/draw_call_tracker.cpp +++ /dev/null @@ -1,285 +0,0 @@ -#include "draw_call_tracker.hpp" -#include "log.hpp" -#include "dxgi/format_utils.hpp" -#include - -namespace reshade::d3d10 -{ - void draw_call_tracker::merge(const draw_call_tracker& source) - { - _global_counter.vertices += source.total_vertices(); - _global_counter.drawcalls += source.total_drawcalls(); - -#if RESHADE_DX10_CAPTURE_DEPTH_BUFFERS - for (const auto &[depthstencil, snapshot] : source._counters_per_used_depthstencil) - { - _counters_per_used_depthstencil[depthstencil].stats.vertices += snapshot.stats.vertices; - _counters_per_used_depthstencil[depthstencil].stats.drawcalls += snapshot.stats.drawcalls; - } - - for (auto source_entry : source._cleared_depth_textures) - { - const auto destination_entry = _cleared_depth_textures.find(source_entry.first); - - if (destination_entry == _cleared_depth_textures.end()) - _cleared_depth_textures.emplace(source_entry.first, source_entry.second); - } -#endif -#if RESHADE_DX10_CAPTURE_CONSTANT_BUFFERS - for (const auto &[buffer, snapshot] : source._counters_per_constant_buffer) - { - _counters_per_constant_buffer[buffer].vertices += snapshot.vertices; - _counters_per_constant_buffer[buffer].drawcalls += snapshot.drawcalls; - _counters_per_constant_buffer[buffer].ps_uses += snapshot.ps_uses; - _counters_per_constant_buffer[buffer].vs_uses += snapshot.vs_uses; - } -#endif - } - - void draw_call_tracker::reset() - { - _global_counter.vertices = 0; - _global_counter.drawcalls = 0; -#if RESHADE_DX10_CAPTURE_DEPTH_BUFFERS - _counters_per_used_depthstencil.clear(); - _cleared_depth_textures.clear(); -#endif -#if RESHADE_DX10_CAPTURE_CONSTANT_BUFFERS - _counters_per_constant_buffer.clear(); -#endif - } - - void draw_call_tracker::on_map(ID3D10Resource *resource) - { - UNREFERENCED_PARAMETER(resource); - -#if RESHADE_DX10_CAPTURE_CONSTANT_BUFFERS - D3D10_RESOURCE_DIMENSION dim; - resource->GetType(&dim); - - if (dim == D3D10_RESOURCE_DIMENSION_BUFFER) - _counters_per_constant_buffer[static_cast(resource)].mapped += 1; -#endif - } - - void draw_call_tracker::on_draw(ID3D10Device *device, UINT vertices) - { - UNREFERENCED_PARAMETER(device); - - _global_counter.vertices += vertices; - _global_counter.drawcalls += 1; - -#if RESHADE_DX10_CAPTURE_DEPTH_BUFFERS - com_ptr targets[D3D10_SIMULTANEOUS_RENDER_TARGET_COUNT]; - com_ptr depthstencil; - - device->OMGetRenderTargets(D3D10_SIMULTANEOUS_RENDER_TARGET_COUNT, reinterpret_cast(targets), &depthstencil); - - if (depthstencil == nullptr) - // This is a draw call with no depth stencil - return; - - if (const auto intermediate_snapshot = _counters_per_used_depthstencil.find(depthstencil); intermediate_snapshot != _counters_per_used_depthstencil.end()) - { - intermediate_snapshot->second.stats.vertices += vertices; - intermediate_snapshot->second.stats.drawcalls += 1; - - // Find the render targets, if they exist, and update their counts - for (UINT i = 0; i < D3D10_SIMULTANEOUS_RENDER_TARGET_COUNT; i++) - { - // Ignore empty slots - if (targets[i] == nullptr) - continue; - - if (const auto it = intermediate_snapshot->second.additional_views.find(targets[i].get()); it != intermediate_snapshot->second.additional_views.end()) - { - it->second.vertices += vertices; - it->second.drawcalls += 1; - } - else - { - // This shouldn't happen - it means somehow someone has called 'on_draw' with a render target without calling 'track_rendertargets' first - assert(false); - } - } - } -#endif -#if RESHADE_DX10_CAPTURE_CONSTANT_BUFFERS - // Capture constant buffers that are used when depth stencils are drawn - com_ptr vscbuffers[D3D10_COMMONSHADER_CONSTANT_BUFFER_API_SLOT_COUNT]; - device->VSGetConstantBuffers(0, ARRAYSIZE(vscbuffers), reinterpret_cast(vscbuffers)); - - for (UINT i = 0; i < ARRAYSIZE(vscbuffers); i++) - // Uses the default drawcalls = 0 the first time around. - if (vscbuffers[i] != nullptr) - _counters_per_constant_buffer[vscbuffers[i]].vs_uses += 1; - - com_ptr pscbuffers[D3D10_COMMONSHADER_CONSTANT_BUFFER_API_SLOT_COUNT]; - device->PSGetConstantBuffers(0, ARRAYSIZE(pscbuffers), reinterpret_cast(pscbuffers)); - - for (UINT i = 0; i < ARRAYSIZE(pscbuffers); i++) - // Uses the default drawcalls = 0 the first time around. - if (pscbuffers[i] != nullptr) - _counters_per_constant_buffer[pscbuffers[i]].ps_uses += 1; -#endif - } - -#if RESHADE_DX10_CAPTURE_DEPTH_BUFFERS - bool draw_call_tracker::check_depthstencil(ID3D10DepthStencilView *depthstencil) const - { - return _counters_per_used_depthstencil.find(depthstencil) != _counters_per_used_depthstencil.end(); - } - bool draw_call_tracker::check_depth_texture_format(int format_index, ID3D10DepthStencilView *depthstencil) - { - assert(depthstencil != nullptr); - - // Do not check format if all formats are allowed (index zero is DXGI_FORMAT_UNKNOWN) - if (format_index == 0) - return true; - - // Retrieve texture from depth stencil - com_ptr resource; - com_ptr texture; - depthstencil->GetResource(&resource); - if (FAILED(resource->QueryInterface(&texture))) - return false; - - D3D10_TEXTURE2D_DESC desc; - texture->GetDesc(&desc); - - const DXGI_FORMAT depth_texture_formats[] = { - DXGI_FORMAT_UNKNOWN, - DXGI_FORMAT_R16_TYPELESS, - DXGI_FORMAT_R32_TYPELESS, - DXGI_FORMAT_R24G8_TYPELESS, - DXGI_FORMAT_R32G8X24_TYPELESS - }; - - assert(format_index > 0 && format_index < ARRAYSIZE(depth_texture_formats)); - - return make_dxgi_format_typeless(desc.Format) == depth_texture_formats[format_index]; - } - - void draw_call_tracker::track_rendertargets(int format_index, ID3D10DepthStencilView *depthstencil, UINT num_views, ID3D10RenderTargetView *const *views) - { - assert(depthstencil != nullptr); - - if (!check_depth_texture_format(format_index, depthstencil)) - return; - - if (_counters_per_used_depthstencil[depthstencil].depthstencil == nullptr) - _counters_per_used_depthstencil[depthstencil].depthstencil = depthstencil; - - for (UINT i = 0; i < num_views; i++) - // If the render target isn't being tracked, this will create it - _counters_per_used_depthstencil[depthstencil].additional_views[views[i]].drawcalls += 1; - } - void draw_call_tracker::track_depth_texture(int format_index, UINT index, com_ptr src_texture, com_ptr src_depthstencil, com_ptr dest_texture, bool cleared) - { - // Function that keeps track of a cleared depth texture in an ordered map in order to retrieve it at the final rendering stage - assert(src_texture != nullptr); - - if (!check_depth_texture_format(format_index, src_depthstencil.get())) - return; - - // Gather some extra info for later display - D3D10_TEXTURE2D_DESC src_texture_desc; - src_texture->GetDesc(&src_texture_desc); - - // check if it is really a depth texture - assert((src_texture_desc.BindFlags & D3D10_BIND_DEPTH_STENCIL) != 0); - - // fill the ordered map with the saved depth texture - if (const auto it = _cleared_depth_textures.find(index); it == _cleared_depth_textures.end()) - _cleared_depth_textures.emplace(index, depth_texture_save_info { src_texture, src_depthstencil, src_texture_desc, dest_texture, cleared }); - else - it->second = depth_texture_save_info { src_texture, src_depthstencil, src_texture_desc, dest_texture, cleared }; - } - - draw_call_tracker::intermediate_snapshot_info draw_call_tracker::find_best_snapshot(UINT width, UINT height) - { - const float aspect_ratio = float(width) / float(height); - intermediate_snapshot_info best_snapshot; - - for (auto &[depthstencil, snapshot] : _counters_per_used_depthstencil) - { - if (snapshot.stats.drawcalls == 0 || snapshot.stats.vertices == 0) - continue; - - if (snapshot.texture == nullptr) - { - com_ptr resource; - depthstencil->GetResource(&resource); - if (FAILED(resource->QueryInterface(&snapshot.texture))) - continue; - } - - D3D10_TEXTURE2D_DESC desc; - snapshot.texture->GetDesc(&desc); - - assert((desc.BindFlags & D3D10_BIND_DEPTH_STENCIL) != 0); - - // Check aspect ratio - const float width_factor = desc.Width != width ? float(width) / desc.Width : 1.0f; - const float height_factor = desc.Height != height ? float(height) / desc.Height : 1.0f; - const float texture_aspect_ratio = float(desc.Width) / float(desc.Height); - - if (fabs(texture_aspect_ratio - aspect_ratio) > 0.1f || width_factor > 2.0f || height_factor > 2.0f || width_factor < 0.5f || height_factor < 0.5f) - continue; // No match, not a good fit - - if (snapshot.stats.drawcalls >= best_snapshot.stats.drawcalls) - best_snapshot = snapshot; - } - - return best_snapshot; - } - - void draw_call_tracker::keep_cleared_depth_textures() - { - // Function that keeps only the depth textures that has been retrieved before the last depth stencil clearance - std::map::reverse_iterator it = _cleared_depth_textures.rbegin(); - - // Reverse loop on the cleared depth textures map - while (it != _cleared_depth_textures.rend()) - { - // Exit if the last cleared depth stencil is found - if (it->second.cleared) - return; - - // Remove the depth texture if it was retrieved after the last clearance of the depth stencil - it = std::map::reverse_iterator(_cleared_depth_textures.erase(std::next(it).base())); - } - } - - ID3D10Texture2D *draw_call_tracker::find_best_cleared_depth_buffer_texture(UINT clear_index) - { - // Function that selects the best cleared depth texture according to the clearing number defined in the configuration settings - ID3D10Texture2D *best_match = nullptr; - - // Ensure to work only on the depth textures retrieved before the last depth stencil clearance - keep_cleared_depth_textures(); - - for (const auto &it : _cleared_depth_textures) - { - UINT i = it.first; - auto &texture_counter_info = it.second; - - com_ptr texture; - if (texture_counter_info.dest_texture == nullptr) - continue; - texture = texture_counter_info.dest_texture; - - if (clear_index != 0 && i > clear_index) - continue; - - // The _cleared_dept_textures ordered map stores the depth textures, according to the order of clearing - // if clear_index == 0, the auto select mode is defined, so the last cleared depth texture is retrieved - // if the user selects a clearing number and the number of cleared depth textures is greater or equal than it, the texture corresponding to this number is retrieved - // if the user selects a clearing number and the number of cleared depth textures is lower than it, the last cleared depth texture is retrieved - best_match = texture.get(); - } - - return best_match; - } -#endif -} diff --git a/msvc/source/d3d10/draw_call_tracker.hpp b/msvc/source/d3d10/draw_call_tracker.hpp deleted file mode 100644 index 1bb2ff1..0000000 --- a/msvc/source/d3d10/draw_call_tracker.hpp +++ /dev/null @@ -1,86 +0,0 @@ -#pragma once - -#include -#include -#include "com_ptr.hpp" - -#define RESHADE_DX10_CAPTURE_DEPTH_BUFFERS 1 -#define RESHADE_DX10_CAPTURE_CONSTANT_BUFFERS 0 - -namespace reshade::d3d10 -{ - class draw_call_tracker - { - public: - struct draw_stats - { - UINT vertices = 0; - UINT drawcalls = 0; - UINT mapped = 0; - UINT vs_uses = 0; - UINT ps_uses = 0; - }; - -#if RESHADE_DX10_CAPTURE_DEPTH_BUFFERS - struct intermediate_snapshot_info - { - ID3D10DepthStencilView *depthstencil = nullptr; // No need to use a 'com_ptr' here since '_counters_per_used_depthstencil' already keeps a reference - draw_stats stats; - com_ptr texture; - std::map additional_views; - }; -#endif - - UINT total_vertices() const { return _global_counter.vertices; } - UINT total_drawcalls() const { return _global_counter.drawcalls; } - -#if RESHADE_DX10_CAPTURE_DEPTH_BUFFERS - const auto &depth_buffer_counters() const { return _counters_per_used_depthstencil; } - const auto &cleared_depth_textures() const { return _cleared_depth_textures; } -#endif -#if RESHADE_DX10_CAPTURE_CONSTANT_BUFFERS - const auto &constant_buffer_counters() const { return _counters_per_constant_buffer; } -#endif - - void merge(const draw_call_tracker &source); - void reset(); - - void on_map(ID3D10Resource *pResource); - void on_draw(ID3D10Device *device, UINT vertices); - -#if RESHADE_DX10_CAPTURE_DEPTH_BUFFERS - void track_rendertargets(int format_index, ID3D10DepthStencilView *depthstencil, UINT num_views, ID3D10RenderTargetView *const *views); - void track_depth_texture(int format_index, UINT index, com_ptr src_texture, com_ptr src_depthstencil, com_ptr dest_texture, bool cleared); - - void keep_cleared_depth_textures(); - - intermediate_snapshot_info find_best_snapshot(UINT width, UINT height); - ID3D10Texture2D *find_best_cleared_depth_buffer_texture(UINT clear_index); -#endif - - private: - struct depth_texture_save_info - { - com_ptr src_texture; - com_ptr src_depthstencil; - D3D10_TEXTURE2D_DESC src_texture_desc; - com_ptr dest_texture; - bool cleared = false; - }; - -#if RESHADE_DX10_CAPTURE_DEPTH_BUFFERS - bool check_depthstencil(ID3D10DepthStencilView *depthstencil) const; - bool check_depth_texture_format(int format_index, ID3D10DepthStencilView *depthstencil); -#endif - - draw_stats _global_counter; -#if RESHADE_DX10_CAPTURE_DEPTH_BUFFERS - // Use "std::map" instead of "std::unordered_map" so that the iteration order is guaranteed - std::map, intermediate_snapshot_info> _counters_per_used_depthstencil; - std::map _cleared_depth_textures; -#endif -#if RESHADE_DX10_CAPTURE_CONSTANT_BUFFERS - std::map, draw_stats> _counters_per_constant_buffer; -#endif - }; -} diff --git a/msvc/source/d3d10/runtime_d3d10.cpp b/msvc/source/d3d10/runtime_d3d10.cpp deleted file mode 100644 index fb4432e..0000000 --- a/msvc/source/d3d10/runtime_d3d10.cpp +++ /dev/null @@ -1,1586 +0,0 @@ -/** - * Copyright (C) 2014 Patrick Mours. All rights reserved. - * License: https://github.com/crosire/reshade#license - */ - -#include "log.hpp" -#include "ini_file.hpp" -#include "runtime_d3d10.hpp" -#include "runtime_objects.hpp" -#include "resource_loading.hpp" -#include "dxgi/format_utils.hpp" -#include -#include - -namespace reshade::d3d10 -{ - struct d3d10_tex_data : base_object - { - com_ptr texture; - com_ptr srv[2]; - com_ptr rtv[2]; - }; - struct d3d10_pass_data : base_object - { - com_ptr vertex_shader; - com_ptr pixel_shader; - com_ptr blend_state; - com_ptr depth_stencil_state; - UINT stencil_reference; - bool clear_render_targets; - com_ptr render_targets[D3D10_SIMULTANEOUS_RENDER_TARGET_COUNT]; - com_ptr render_target_resources[D3D10_SIMULTANEOUS_RENDER_TARGET_COUNT]; - D3D10_VIEWPORT viewport; - std::vector> shader_resources; - }; - struct d3d10_technique_data : base_object - { - bool query_in_flight = false; - com_ptr timestamp_disjoint; - com_ptr timestamp_query_beg; - com_ptr timestamp_query_end; - std::vector> sampler_states; - std::vector> texture_bindings; - ptrdiff_t uniform_storage_offset = 0; - ptrdiff_t uniform_storage_index = -1; - }; -} - -reshade::d3d10::runtime_d3d10::runtime_d3d10(ID3D10Device1 *device, IDXGISwapChain *swapchain) : - _device(device), _swapchain(swapchain), - _app_state(device) -{ - assert(device != nullptr); - assert(swapchain != nullptr); - - com_ptr dxgi_device; - _device->QueryInterface(&dxgi_device); - com_ptr dxgi_adapter; - dxgi_device->GetAdapter(&dxgi_adapter); - - _renderer_id = device->GetFeatureLevel(); - if (DXGI_ADAPTER_DESC desc; SUCCEEDED(dxgi_adapter->GetDesc(&desc))) - _vendor_id = desc.VendorId, _device_id = desc.DeviceId; - -#if RESHADE_GUI - subscribe_to_ui("DX10", [this]() { draw_debug_menu(); }); -#endif - subscribe_to_load_config([this](const ini_file &config) { - config.get("DX10_BUFFER_DETECTION", "DepthBufferRetrievalMode", depth_buffer_before_clear); - config.get("DX10_BUFFER_DETECTION", "DepthBufferTextureFormat", depth_buffer_texture_format); - config.get("DX10_BUFFER_DETECTION", "ExtendedDepthBufferDetection", extended_depth_buffer_detection); - config.get("DX10_BUFFER_DETECTION", "DepthBufferClearingNumber", cleared_depth_buffer_index); - }); - subscribe_to_save_config([this](ini_file &config) { - config.set("DX10_BUFFER_DETECTION", "DepthBufferRetrievalMode", depth_buffer_before_clear); - config.set("DX10_BUFFER_DETECTION", "DepthBufferTextureFormat", depth_buffer_texture_format); - config.set("DX10_BUFFER_DETECTION", "ExtendedDepthBufferDetection", extended_depth_buffer_detection); - config.set("DX10_BUFFER_DETECTION", "DepthBufferClearingNumber", cleared_depth_buffer_index); - }); -} -reshade::d3d10::runtime_d3d10::~runtime_d3d10() -{ - if (_d3d_compiler != nullptr) - FreeLibrary(_d3d_compiler); -} - -bool reshade::d3d10::runtime_d3d10::init_backbuffer_texture() -{ - HRESULT hr = _swapchain->GetBuffer(0, IID_PPV_ARGS(&_backbuffer)); - assert(SUCCEEDED(hr)); - - D3D10_TEXTURE2D_DESC tex_desc = {}; - tex_desc.Width = _width; - tex_desc.Height = _height; - tex_desc.MipLevels = 1; - tex_desc.ArraySize = 1; - tex_desc.Format = make_dxgi_format_typeless(_backbuffer_format); - tex_desc.SampleDesc = { 1, 0 }; - tex_desc.Usage = D3D10_USAGE_DEFAULT; - tex_desc.BindFlags = D3D10_BIND_RENDER_TARGET; - - // Creating a render target view for the back buffer fails on Windows 8+, so use a intermediate texture there - OSVERSIONINFOEX verinfo_windows7 = { sizeof(OSVERSIONINFOEX), 6, 1 }; - const bool is_windows7 = VerifyVersionInfo(&verinfo_windows7, VER_MAJORVERSION | VER_MINORVERSION, - VerSetConditionMask(VerSetConditionMask(0, VER_MAJORVERSION, VER_EQUAL), VER_MINORVERSION, VER_EQUAL)) != FALSE; - - if (_is_multisampling_enabled || - make_dxgi_format_normal(_backbuffer_format) != _backbuffer_format || - !is_windows7) - { - if (hr = _device->CreateTexture2D(&tex_desc, nullptr, &_backbuffer_resolved); FAILED(hr)) - { - LOG(ERROR) << "Failed to create back buffer resolve texture (" - "Width = " << tex_desc.Width << ", " - "Height = " << tex_desc.Height << ", " - "Format = " << tex_desc.Format << ")! HRESULT is '" << std::hex << hr << std::dec << "'."; - return false; - } - - hr = _device->CreateRenderTargetView(_backbuffer.get(), nullptr, &_backbuffer_rtv[2]); - assert(SUCCEEDED(hr)); - } - else - { - _backbuffer_resolved = _backbuffer; - } - - // Create back buffer shader texture - tex_desc.BindFlags = D3D10_BIND_SHADER_RESOURCE; - - if (hr = _device->CreateTexture2D(&tex_desc, nullptr, &_backbuffer_texture); SUCCEEDED(hr)) - { - D3D10_SHADER_RESOURCE_VIEW_DESC srv_desc = {}; - srv_desc.Format = make_dxgi_format_normal(tex_desc.Format); - srv_desc.ViewDimension = D3D10_SRV_DIMENSION_TEXTURE2D; - srv_desc.Texture2D.MipLevels = tex_desc.MipLevels; - - if (SUCCEEDED(hr)) - hr = _device->CreateShaderResourceView(_backbuffer_texture.get(), &srv_desc, &_backbuffer_texture_srv[0]); - else - LOG(ERROR) << "Failed to create back buffer texture resource view (" - "Format = " << srv_desc.Format << ")! HRESULT is '" << std::hex << hr << std::dec << "'."; - - srv_desc.Format = make_dxgi_format_srgb(tex_desc.Format); - - if (SUCCEEDED(hr)) - hr = _device->CreateShaderResourceView(_backbuffer_texture.get(), &srv_desc, &_backbuffer_texture_srv[1]); - else - LOG(ERROR) << "Failed to create back buffer SRGB texture resource view (" - "Format = " << srv_desc.Format << ")! HRESULT is '" << std::hex << hr << std::dec << "'."; - } - else - { - LOG(ERROR) << "Failed to create back buffer texture (" - "Width = " << tex_desc.Width << ", " - "Height = " << tex_desc.Height << ", " - "Format = " << tex_desc.Format << ")! HRESULT is '" << std::hex << hr << std::dec << "'."; - } - - if (FAILED(hr)) - return false; - - D3D10_RENDER_TARGET_VIEW_DESC rtv_desc = {}; - rtv_desc.Format = make_dxgi_format_normal(tex_desc.Format); - rtv_desc.ViewDimension = D3D10_RTV_DIMENSION_TEXTURE2D; - - if (hr = _device->CreateRenderTargetView(_backbuffer_resolved.get(), &rtv_desc, &_backbuffer_rtv[0]); FAILED(hr)) - { - LOG(ERROR) << "Failed to create back buffer render target (" - "Format = " << rtv_desc.Format << ")! HRESULT is '" << std::hex << hr << std::dec << "'."; - return false; - } - - rtv_desc.Format = make_dxgi_format_srgb(tex_desc.Format); - - if (hr = _device->CreateRenderTargetView(_backbuffer_resolved.get(), &rtv_desc, &_backbuffer_rtv[1]); FAILED(hr)) - { - LOG(ERROR) << "Failed to create back buffer SRGB render target (" - "Format = " << rtv_desc.Format << ")! HRESULT is '" << std::hex << hr << std::dec << "'."; - return false; - } - - const resources::data_resource vs = resources::load_data_resource(IDR_FULLSCREEN_VS); - if (hr = _device->CreateVertexShader(vs.data, vs.data_size, &_copy_vertex_shader); FAILED(hr)) - return false; - const resources::data_resource ps = resources::load_data_resource(IDR_COPY_PS); - if (hr = _device->CreatePixelShader(ps.data, ps.data_size, &_copy_pixel_shader); FAILED(hr)) - return false; - - { D3D10_SAMPLER_DESC desc = {}; - desc.Filter = D3D10_FILTER_MIN_MAG_MIP_POINT; - desc.AddressU = D3D10_TEXTURE_ADDRESS_CLAMP; - desc.AddressV = D3D10_TEXTURE_ADDRESS_CLAMP; - desc.AddressW = D3D10_TEXTURE_ADDRESS_CLAMP; - if (hr = _device->CreateSamplerState(&desc, &_copy_sampler); FAILED(hr)) - return false; - } - - { D3D10_RASTERIZER_DESC desc = {}; - desc.FillMode = D3D10_FILL_SOLID; - desc.CullMode = D3D10_CULL_NONE; - desc.DepthClipEnable = TRUE; - if (hr = _device->CreateRasterizerState(&desc, &_effect_rasterizer_state); FAILED(hr)) - return false; - } - - return true; -} -bool reshade::d3d10::runtime_d3d10::init_default_depth_stencil() -{ - const D3D10_TEXTURE2D_DESC tex_desc = { - _width, - _height, - 1, 1, - DXGI_FORMAT_D24_UNORM_S8_UINT, - { 1, 0 }, - D3D10_USAGE_DEFAULT, - D3D10_BIND_DEPTH_STENCIL - }; - - com_ptr depth_stencil_texture; - - if (HRESULT hr = _device->CreateTexture2D(&tex_desc, nullptr, &depth_stencil_texture); FAILED(hr)) - { - LOG(ERROR) << "Failed to create depth stencil texture (" - "Width = " << tex_desc.Width << ", " - "Height = " << tex_desc.Height << ", " - "Format = " << tex_desc.Format << ")! HRESULT is '" << std::hex << hr << std::dec << "'."; - return false; - } - - return SUCCEEDED(_device->CreateDepthStencilView(depth_stencil_texture.get(), nullptr, &_default_depthstencil)); -} - -bool reshade::d3d10::runtime_d3d10::on_init(const DXGI_SWAP_CHAIN_DESC &desc) -{ - RECT window_rect = {}; - GetClientRect(desc.OutputWindow, &window_rect); - - _width = desc.BufferDesc.Width; - _height = desc.BufferDesc.Height; - _window_width = window_rect.right - window_rect.left; - _window_height = window_rect.bottom - window_rect.top; - _backbuffer_format = desc.BufferDesc.Format; - _is_multisampling_enabled = desc.SampleDesc.Count > 1; - - if (!init_backbuffer_texture() || - !init_default_depth_stencil() -#if RESHADE_GUI - || !init_imgui_resources() -#endif - ) - return false; - - return runtime::on_init(desc.OutputWindow); -} -void reshade::d3d10::runtime_d3d10::on_reset() -{ - runtime::on_reset(); - - _backbuffer.reset(); - _backbuffer_resolved.reset(); - _backbuffer_texture.reset(); - _backbuffer_texture_srv[0].reset(); - _backbuffer_texture_srv[1].reset(); - _backbuffer_rtv[0].reset(); - _backbuffer_rtv[1].reset(); - _backbuffer_rtv[2].reset(); - - _depthstencil.reset(); - _depthstencil_replacement.reset(); - _depthstencil_texture.reset(); - _depthstencil_texture_srv.reset(); - - _depth_texture_saves.clear(); - - _default_depthstencil.reset(); - _copy_vertex_shader.reset(); - _copy_pixel_shader.reset(); - _copy_sampler.reset(); - - _effect_rasterizer_state.reset(); - - _imgui_index_buffer_size = 0; - _imgui_index_buffer.reset(); - _imgui_vertex_buffer_size = 0; - _imgui_vertex_buffer.reset(); - _imgui_vertex_shader.reset(); - _imgui_pixel_shader.reset(); - _imgui_input_layout.reset(); - _imgui_constant_buffer.reset(); - _imgui_texture_sampler.reset(); - _imgui_rasterizer_state.reset(); - _imgui_blend_state.reset(); - _imgui_depthstencil_state.reset(); -} - -void reshade::d3d10::runtime_d3d10::on_present(draw_call_tracker &tracker) -{ - if (!_is_initialized) - return; - - _vertices = tracker.total_vertices(); - _drawcalls = tracker.total_drawcalls(); - _current_tracker = &tracker; - -#if RESHADE_DX10_CAPTURE_DEPTH_BUFFERS - detect_depth_source(tracker); -#endif - _app_state.capture(); - - // Resolve MSAA back buffer if MSAA is active - if (_backbuffer_resolved != _backbuffer) - _device->ResolveSubresource(_backbuffer_resolved.get(), 0, _backbuffer.get(), 0, _backbuffer_format); - - update_and_render_effects(); - runtime::on_present(); - - // Stretch main render target back into MSAA back buffer if MSAA is active - if (_backbuffer_resolved != _backbuffer) - { - _device->CopyResource(_backbuffer_texture.get(), _backbuffer_resolved.get()); - - _device->IASetInputLayout(nullptr); - const uintptr_t null = 0; - _device->IASetVertexBuffers(0, 1, reinterpret_cast(&null), reinterpret_cast(&null), reinterpret_cast(&null)); - _device->IASetPrimitiveTopology(D3D10_PRIMITIVE_TOPOLOGY_TRIANGLELIST); - _device->VSSetShader(_copy_vertex_shader.get()); - _device->GSSetShader(nullptr); - _device->PSSetShader(_copy_pixel_shader.get()); - ID3D10SamplerState *const samplers[] = { _copy_sampler.get() }; - _device->PSSetSamplers(0, ARRAYSIZE(samplers), samplers); - ID3D10ShaderResourceView *const srvs[] = { _backbuffer_texture_srv[make_dxgi_format_srgb(_backbuffer_format) == _backbuffer_format].get() }; - _device->PSSetShaderResources(0, ARRAYSIZE(srvs), srvs); - _device->RSSetState(_effect_rasterizer_state.get()); - const D3D10_VIEWPORT viewport = { 0, 0, _width, _height, 0.0f, 1.0f }; - _device->RSSetViewports(1, &viewport); - _device->OMSetBlendState(nullptr, nullptr, D3D10_DEFAULT_SAMPLE_MASK); - _device->OMSetDepthStencilState(nullptr, D3D10_DEFAULT_STENCIL_REFERENCE); - ID3D10RenderTargetView *const render_targets[] = { _backbuffer_rtv[2].get() }; - _device->OMSetRenderTargets(ARRAYSIZE(render_targets), render_targets, nullptr); - - _device->Draw(3, 0); - } - - // Apply previous state from application - _app_state.apply_and_release(); -} - -void reshade::d3d10::runtime_d3d10::capture_screenshot(uint8_t *buffer) const -{ - if (_backbuffer_format != DXGI_FORMAT_R8G8B8A8_UNORM && - _backbuffer_format != DXGI_FORMAT_R8G8B8A8_UNORM_SRGB && - _backbuffer_format != DXGI_FORMAT_B8G8R8A8_UNORM && - _backbuffer_format != DXGI_FORMAT_B8G8R8A8_UNORM_SRGB) - { - LOG(WARN) << "Screenshots are not supported for back buffer format " << _backbuffer_format << '.'; - return; - } - - // Create a texture in system memory, copy back buffer data into it and map it for reading - D3D10_TEXTURE2D_DESC desc = {}; - desc.Width = _width; - desc.Height = _height; - desc.MipLevels = 1; - desc.ArraySize = 1; - desc.Format = _backbuffer_format; - desc.SampleDesc = { 1, 0 }; - desc.Usage = D3D10_USAGE_STAGING; - desc.CPUAccessFlags = D3D10_CPU_ACCESS_READ; - - com_ptr intermediate; - if (FAILED(_device->CreateTexture2D(&desc, nullptr, &intermediate))) - { - LOG(ERROR) << "Failed to create system memory texture for screenshot capture!"; - return; - } - - _device->CopyResource(intermediate.get(), _backbuffer_resolved.get()); - - D3D10_MAPPED_TEXTURE2D mapped; - if (FAILED(intermediate->Map(0, D3D10_MAP_READ, 0, &mapped))) - return; - auto mapped_data = static_cast(mapped.pData); - - for (uint32_t y = 0, pitch = _width * 4; y < _height; y++, buffer += pitch, mapped_data += mapped.RowPitch) - { - memcpy(buffer, mapped_data, pitch); - - for (uint32_t x = 0; x < pitch; x += 4) - { - buffer[x + 3] = 0xFF; // Clear alpha channel - if (_backbuffer_format == DXGI_FORMAT_B8G8R8A8_UNORM || _backbuffer_format == DXGI_FORMAT_B8G8R8A8_UNORM_SRGB) - std::swap(buffer[x + 0], buffer[x + 2]); // Format is BGRA, but output should be RGBA, so flip channels - } - } - - intermediate->Unmap(0); -} - -bool reshade::d3d10::runtime_d3d10::init_texture(texture &info) -{ - info.impl = std::make_unique(); - - if (info.impl_reference != texture_reference::none) - return update_texture_reference(info); - - D3D10_TEXTURE2D_DESC desc = {}; - desc.Width = info.width; - desc.Height = info.height; - desc.MipLevels = info.levels; - desc.ArraySize = 1; - desc.SampleDesc = { 1, 0 }; - desc.Usage = D3D10_USAGE_DEFAULT; - desc.BindFlags = D3D10_BIND_SHADER_RESOURCE | D3D10_BIND_RENDER_TARGET; - desc.MiscFlags = D3D10_RESOURCE_MISC_GENERATE_MIPS; - - switch (info.format) - { - case reshadefx::texture_format::r8: - desc.Format = DXGI_FORMAT_R8_UNORM; - break; - case reshadefx::texture_format::r16f: - desc.Format = DXGI_FORMAT_R16_FLOAT; - break; - case reshadefx::texture_format::r32f: - desc.Format = DXGI_FORMAT_R32_FLOAT; - break; - case reshadefx::texture_format::rg8: - desc.Format = DXGI_FORMAT_R8G8_UNORM; - break; - case reshadefx::texture_format::rg16: - desc.Format = DXGI_FORMAT_R16G16_UNORM; - break; - case reshadefx::texture_format::rg16f: - desc.Format = DXGI_FORMAT_R16G16_FLOAT; - break; - case reshadefx::texture_format::rg32f: - desc.Format = DXGI_FORMAT_R32G32_FLOAT; - break; - case reshadefx::texture_format::rgba8: - desc.Format = DXGI_FORMAT_R8G8B8A8_TYPELESS; - break; - case reshadefx::texture_format::rgba16: - desc.Format = DXGI_FORMAT_R16G16B16A16_UNORM; - break; - case reshadefx::texture_format::rgba16f: - desc.Format = DXGI_FORMAT_R16G16B16A16_FLOAT; - break; - case reshadefx::texture_format::rgba32f: - desc.Format = DXGI_FORMAT_R32G32B32A32_FLOAT; - break; - case reshadefx::texture_format::rgb10a2: - desc.Format = DXGI_FORMAT_R10G10B10A2_UNORM; - break; - } - - const auto texture_data = info.impl->as(); - - if (HRESULT hr = _device->CreateTexture2D(&desc, nullptr, &texture_data->texture); FAILED(hr)) - { - LOG(ERROR) << "Failed to create texture '" << info.unique_name << "' (" - "Width = " << desc.Width << ", " - "Height = " << desc.Height << ", " - "Format = " << desc.Format << ")! " - "HRESULT is '" << std::hex << hr << std::dec << "'."; - return false; - } - - D3D10_SHADER_RESOURCE_VIEW_DESC srv_desc = {}; - srv_desc.Format = make_dxgi_format_normal(desc.Format); - srv_desc.ViewDimension = D3D10_SRV_DIMENSION_TEXTURE2D; - srv_desc.Texture2D.MipLevels = desc.MipLevels; - - if (HRESULT hr = _device->CreateShaderResourceView(texture_data->texture.get(), &srv_desc, &texture_data->srv[0]); FAILED(hr)) - { - LOG(ERROR) << "Failed to create shader resource view for texture '" << info.unique_name << "' (" - "Format = " << srv_desc.Format << ")! " - "HRESULT is '" << std::hex << hr << std::dec << "'."; - return false; - } - - srv_desc.Format = make_dxgi_format_srgb(desc.Format); - - if (srv_desc.Format != desc.Format) - { - if (HRESULT hr = _device->CreateShaderResourceView(texture_data->texture.get(), &srv_desc, &texture_data->srv[1]); FAILED(hr)) - { - LOG(ERROR) << "Failed to create shader resource view for texture '" << info.unique_name << "' (" - "Format = " << srv_desc.Format << ")! " - "HRESULT is '" << std::hex << hr << std::dec << "'."; - return false; - } - } - else - { - texture_data->srv[1] = texture_data->srv[0]; - } - - return true; -} -void reshade::d3d10::runtime_d3d10::upload_texture(texture &texture, const uint8_t *pixels) -{ - assert(texture.impl_reference == texture_reference::none && pixels != nullptr); - - unsigned int upload_pitch; - std::vector upload_data; - - switch (texture.format) - { - case reshadefx::texture_format::r8: - upload_pitch = texture.width; - upload_data.resize(upload_pitch * texture.height); - for (uint32_t i = 0, k = 0; i < texture.width * texture.height * 4; i += 4, k += 1) - upload_data[k] = pixels[i]; - pixels = upload_data.data(); - break; - case reshadefx::texture_format::rg8: - upload_pitch = texture.width * 2; - upload_data.resize(upload_pitch * texture.height); - for (uint32_t i = 0, k = 0; i < texture.width * texture.height * 4; i += 4, k += 2) - upload_data[k + 0] = pixels[i + 0], - upload_data[k + 1] = pixels[i + 1]; - pixels = upload_data.data(); - break; - case reshadefx::texture_format::rgba8: - upload_pitch = texture.width * 4; - break; - default: - LOG(ERROR) << "Texture upload is not supported for format " << static_cast(texture.format) << '!'; - return; - } - - const auto texture_impl = texture.impl->as(); - assert(texture_impl != nullptr); - - _device->UpdateSubresource(texture_impl->texture.get(), 0, nullptr, pixels, upload_pitch, upload_pitch * texture.height); - - if (texture.levels > 1) - _device->GenerateMips(texture_impl->srv[0].get()); -} -bool reshade::d3d10::runtime_d3d10::update_texture_reference(texture &texture) -{ - com_ptr new_reference[2]; - - switch (texture.impl_reference) - { - case texture_reference::back_buffer: - new_reference[0] = _backbuffer_texture_srv[0]; - new_reference[1] = _backbuffer_texture_srv[1]; - break; - case texture_reference::depth_buffer: - new_reference[0] = _depthstencil_texture_srv; - new_reference[1] = _depthstencil_texture_srv; - break; - default: - return false; - } - - const auto texture_impl = texture.impl->as(); - assert(texture_impl != nullptr); - - if (new_reference[0] == texture_impl->srv[0] && - new_reference[1] == texture_impl->srv[1]) - return true; - - // Update references in technique list - for (const auto &technique : _techniques) - for (const auto &pass : technique.passes_data) - for (auto &srv : pass->as()->shader_resources) - if (srv == texture_impl->srv[0]) srv = new_reference[0]; - else if (srv == texture_impl->srv[1]) srv = new_reference[1]; - - texture.width = frame_width(); - texture.height = frame_height(); - - texture_impl->srv[0] = new_reference[0]; - texture_impl->srv[1] = new_reference[1]; - - return true; -} -void reshade::d3d10::runtime_d3d10::update_texture_references(texture_reference type) -{ - for (auto &tex : _textures) - if (tex.impl != nullptr && tex.impl_reference == type) - update_texture_reference(tex); -} - -bool reshade::d3d10::runtime_d3d10::compile_effect(effect_data &effect) -{ - if (_d3d_compiler == nullptr) - _d3d_compiler = LoadLibraryW(L"d3dcompiler_47.dll"); - if (_d3d_compiler == nullptr) - _d3d_compiler = LoadLibraryW(L"d3dcompiler_43.dll"); - - if (_d3d_compiler == nullptr) - { - LOG(ERROR) << "Unable to load HLSL compiler (\"d3dcompiler_47.dll\"). Make sure you have the DirectX end-user runtime (June 2010) installed or a newer version of the library in the application directory."; - return false; - } - - const auto D3DCompile = reinterpret_cast(GetProcAddress(_d3d_compiler, "D3DCompile")); - - const std::string hlsl = effect.preamble + effect.module.hlsl; - std::unordered_map> entry_points; - - // Compile the generated HLSL source code to DX byte code - for (const auto &entry_point : effect.module.entry_points) - { - std::string profile = entry_point.second ? "ps" : "vs"; - - switch (_renderer_id) - { - case D3D10_FEATURE_LEVEL_10_1: - profile += "_4_1"; - break; - default: - case D3D10_FEATURE_LEVEL_10_0: - profile += "_4_0"; - break; - case D3D10_FEATURE_LEVEL_9_1: - case D3D10_FEATURE_LEVEL_9_2: - profile += "_4_0_level_9_1"; - break; - case D3D10_FEATURE_LEVEL_9_3: - profile += "_4_0_level_9_3"; - break; - } - - com_ptr d3d_compiled, d3d_errors; - - HRESULT hr = D3DCompile( - hlsl.c_str(), hlsl.size(), - nullptr, nullptr, nullptr, - entry_point.first.c_str(), - profile.c_str(), - D3DCOMPILE_ENABLE_STRICTNESS | D3DCOMPILE_OPTIMIZATION_LEVEL3, 0, - &d3d_compiled, &d3d_errors); - - if (d3d_errors != nullptr) // Append warnings to the output error string as well - effect.errors.append(static_cast(d3d_errors->GetBufferPointer()), d3d_errors->GetBufferSize() - 1); // Subtracting one to not append the null-terminator as well - - // No need to setup resources if any of the shaders failed to compile - if (FAILED(hr)) - return false; - - // Create runtime shader objects from the compiled DX byte code - if (entry_point.second) - hr = _device->CreatePixelShader(d3d_compiled->GetBufferPointer(), d3d_compiled->GetBufferSize(), reinterpret_cast(&entry_points[entry_point.first])); - else - hr = _device->CreateVertexShader(d3d_compiled->GetBufferPointer(), d3d_compiled->GetBufferSize(), reinterpret_cast(&entry_points[entry_point.first])); - - if (FAILED(hr)) - { - LOG(ERROR) << "Failed to create shader for entry point '" << entry_point.first << "'. " - "HRESULT is '" << std::hex << hr << std::dec << "'."; - return false; - } - } - - if (effect.storage_size != 0) - { - com_ptr cbuffer; - - const D3D10_BUFFER_DESC desc = { static_cast(effect.storage_size), D3D10_USAGE_DYNAMIC, D3D10_BIND_CONSTANT_BUFFER, D3D10_CPU_ACCESS_WRITE }; - const D3D10_SUBRESOURCE_DATA init_data = { _uniform_data_storage.data() + effect.storage_offset, static_cast(effect.storage_size) }; - - if (HRESULT hr = _device->CreateBuffer(&desc, &init_data, &cbuffer); FAILED(hr)) - { - LOG(ERROR) << "Failed to create constant buffer for effect file " << effect.source_file << ". " - "HRESULT is '" << std::hex << hr << std::dec << "'."; - return false; - } - - _constant_buffers.push_back(std::move(cbuffer)); - } - - bool success = true; - - d3d10_technique_data technique_init; - technique_init.uniform_storage_index = _constant_buffers.size() - 1; - technique_init.uniform_storage_offset = effect.storage_offset; - - for (const reshadefx::sampler_info &info : effect.module.samplers) - success &= add_sampler(info, technique_init); - - for (technique &technique : _techniques) - if (technique.impl == nullptr && technique.effect_index == effect.index) - success &= init_technique(technique, technique_init, entry_points); - - return success; -} -void reshade::d3d10::runtime_d3d10::unload_effects() -{ - runtime::unload_effects(); - - _effect_sampler_states.clear(); - _constant_buffers.clear(); -} - -bool reshade::d3d10::runtime_d3d10::add_sampler(const reshadefx::sampler_info &info, d3d10_technique_data &technique_init) -{ - if (info.binding >= D3D10_COMMONSHADER_SAMPLER_SLOT_COUNT) - { - LOG(ERROR) << "Cannot bind sampler '" << info.unique_name << "' since it exceeds the maximum number of allowed sampler slots in D3D10 (" << info.binding << ", allowed are up to " << D3D10_COMMONSHADER_SAMPLER_SLOT_COUNT << ")."; - return false; - } - if (info.texture_binding >= D3D10_COMMONSHADER_INPUT_RESOURCE_SLOT_COUNT) - { - LOG(ERROR) << "Cannot bind texture '" << info.texture_name << "' since it exceeds the maximum number of allowed resource slots in D3D10 (" << info.texture_binding << ", allowed are up to " << D3D10_COMMONSHADER_INPUT_RESOURCE_SLOT_COUNT << ")."; - return false; - } - - const auto existing_texture = std::find_if(_textures.begin(), _textures.end(), - [&texture_name = info.texture_name](const auto &item) { - return item.unique_name == texture_name && item.impl != nullptr; - }); - - if (existing_texture == _textures.end()) - return false; - - D3D10_SAMPLER_DESC desc = {}; - desc.Filter = static_cast(info.filter); - desc.AddressU = static_cast(info.address_u); - desc.AddressV = static_cast(info.address_v); - desc.AddressW = static_cast(info.address_w); - desc.MipLODBias = info.lod_bias; - desc.MaxAnisotropy = 1; - desc.ComparisonFunc = D3D10_COMPARISON_NEVER; - desc.MinLOD = info.min_lod; - desc.MaxLOD = info.max_lod; - - // Generate hash for sampler description - size_t desc_hash = 2166136261; - for (size_t i = 0; i < sizeof(desc); ++i) - desc_hash = (desc_hash * 16777619) ^ reinterpret_cast(&desc)[i]; - - auto it = _effect_sampler_states.find(desc_hash); - if (it == _effect_sampler_states.end()) - { - com_ptr sampler; - - HRESULT hr = _device->CreateSamplerState(&desc, &sampler); - - if (FAILED(hr)) - { - LOG(ERROR) << "Failed to create sampler state for sampler '" << info.unique_name << "' (" - "Filter = " << desc.Filter << ", " - "AddressU = " << desc.AddressU << ", " - "AddressV = " << desc.AddressV << ", " - "AddressW = " << desc.AddressW << ", " - "MipLODBias = " << desc.MipLODBias << ", " - "MinLOD = " << desc.MinLOD << ", " - "MaxLOD = " << desc.MaxLOD << ")! " - "HRESULT is '" << std::hex << hr << std::dec << "'."; - return false; - } - - it = _effect_sampler_states.emplace(desc_hash, std::move(sampler)).first; - } - - technique_init.sampler_states.resize(std::max(technique_init.sampler_states.size(), size_t(info.binding + 1))); - technique_init.texture_bindings.resize(std::max(technique_init.texture_bindings.size(), size_t(info.texture_binding + 1))); - - technique_init.sampler_states[info.binding] = it->second; - technique_init.texture_bindings[info.texture_binding] = existing_texture->impl->as()->srv[info.srgb ? 1 : 0]; - - return true; -} -bool reshade::d3d10::runtime_d3d10::init_technique(technique &technique, const d3d10_technique_data &impl_init, const std::unordered_map> &entry_points) -{ - // Copy construct new technique implementation instead of move because effect may contain multiple techniques - technique.impl = std::make_unique(impl_init); - - const auto technique_data = technique.impl->as(); - - D3D10_QUERY_DESC query_desc = {}; - query_desc.Query = D3D10_QUERY_TIMESTAMP; - _device->CreateQuery(&query_desc, &technique_data->timestamp_query_beg); - _device->CreateQuery(&query_desc, &technique_data->timestamp_query_end); - query_desc.Query = D3D10_QUERY_TIMESTAMP_DISJOINT; - _device->CreateQuery(&query_desc, &technique_data->timestamp_disjoint); - - for (size_t pass_index = 0; pass_index < technique.passes.size(); ++pass_index) - { - technique.passes_data.push_back(std::make_unique()); - - auto &pass = *technique.passes_data.back()->as(); - const auto &pass_info = technique.passes[pass_index]; - - entry_points.at(pass_info.ps_entry_point)->QueryInterface(&pass.pixel_shader); - entry_points.at(pass_info.vs_entry_point)->QueryInterface(&pass.vertex_shader); - - pass.viewport.MaxDepth = 1.0f; - pass.viewport.Width = pass_info.viewport_width; - pass.viewport.Height = pass_info.viewport_height; - - pass.shader_resources = technique_data->texture_bindings; - pass.clear_render_targets = pass_info.clear_render_targets; - - const int target_index = pass_info.srgb_write_enable ? 1 : 0; - pass.render_targets[0] = _backbuffer_rtv[target_index]; - pass.render_target_resources[0] = _backbuffer_texture_srv[target_index]; - - for (unsigned int k = 0; k < 8; k++) - { - if (pass_info.render_target_names[k].empty()) - continue; // Skip unbound render targets - - const auto render_target_texture = std::find_if(_textures.begin(), _textures.end(), - [&render_target = pass_info.render_target_names[k]](const auto &item) { - return item.unique_name == render_target; - }); - - if (render_target_texture == _textures.end()) - return assert(false), false; - - const auto texture_impl = render_target_texture->impl->as(); - assert(texture_impl != nullptr); - - D3D10_TEXTURE2D_DESC desc; - texture_impl->texture->GetDesc(&desc); - - D3D10_RENDER_TARGET_VIEW_DESC rtv_desc = {}; - rtv_desc.Format = pass_info.srgb_write_enable ? make_dxgi_format_srgb(desc.Format) : make_dxgi_format_normal(desc.Format); - rtv_desc.ViewDimension = desc.SampleDesc.Count > 1 ? D3D10_RTV_DIMENSION_TEXTURE2DMS : D3D10_RTV_DIMENSION_TEXTURE2D; - - if (texture_impl->rtv[target_index] == nullptr) - if (HRESULT hr = _device->CreateRenderTargetView(texture_impl->texture.get(), &rtv_desc, &texture_impl->rtv[target_index]); FAILED(hr)) - { - LOG(ERROR) << "Failed to create render target view for texture '" << render_target_texture->unique_name << "' (" - "Format = " << rtv_desc.Format << ")! " - "HRESULT is '" << std::hex << hr << std::dec << "'."; - return false; - } - - pass.render_targets[k] = texture_impl->rtv[target_index]; - pass.render_target_resources[k] = texture_impl->srv[target_index]; - } - - if (pass.viewport.Width == 0) - { - pass.viewport.Width = frame_width(); - pass.viewport.Height = frame_height(); - } - - { D3D10_BLEND_DESC desc = {}; - desc.BlendEnable[0] = pass_info.blend_enable; - - const auto literal_to_blend_func = [](unsigned int value) { - switch (value) { - case 0: - return D3D10_BLEND_ZERO; - default: - case 1: - return D3D10_BLEND_ONE; - case 2: - return D3D10_BLEND_SRC_COLOR; - case 4: - return D3D10_BLEND_INV_SRC_COLOR; - case 3: - return D3D10_BLEND_SRC_ALPHA; - case 5: - return D3D10_BLEND_INV_SRC_ALPHA; - case 6: - return D3D10_BLEND_DEST_ALPHA; - case 7: - return D3D10_BLEND_INV_DEST_ALPHA; - case 8: - return D3D10_BLEND_DEST_COLOR; - case 9: - return D3D10_BLEND_INV_DEST_COLOR; - } - }; - - desc.SrcBlend = literal_to_blend_func(pass_info.src_blend); - desc.DestBlend = literal_to_blend_func(pass_info.dest_blend); - desc.BlendOp = static_cast(pass_info.blend_op); - desc.SrcBlendAlpha = literal_to_blend_func(pass_info.src_blend_alpha); - desc.DestBlendAlpha = literal_to_blend_func(pass_info.dest_blend_alpha); - desc.BlendOpAlpha = static_cast(pass_info.blend_op_alpha); - desc.RenderTargetWriteMask[0] = pass_info.color_write_mask; - - for (UINT i = 1; i < 8; ++i) - { - desc.BlendEnable[i] = desc.BlendEnable[0]; - desc.RenderTargetWriteMask[i] = desc.RenderTargetWriteMask[0]; - } - - if (HRESULT hr = _device->CreateBlendState(&desc, &pass.blend_state); FAILED(hr)) - { - LOG(ERROR) << "Failed to create blend state for pass " << pass_index << " in technique '" << technique.name << "'! " - "HRESULT is '" << std::hex << hr << std::dec << "'."; - return false; - } - } - - // Rasterizer state is the same for all passes - assert(_effect_rasterizer_state != nullptr); - - { D3D10_DEPTH_STENCIL_DESC desc = {}; - desc.DepthEnable = FALSE; - desc.DepthWriteMask = D3D10_DEPTH_WRITE_MASK_ZERO; - desc.DepthFunc = D3D10_COMPARISON_ALWAYS; - - const auto literal_to_stencil_op = [](unsigned int value) { - switch (value) { - default: - case 1: - return D3D10_STENCIL_OP_KEEP; - case 0: - return D3D10_STENCIL_OP_ZERO; - case 3: - return D3D10_STENCIL_OP_REPLACE; - case 4: - return D3D10_STENCIL_OP_INCR_SAT; - case 5: - return D3D10_STENCIL_OP_DECR_SAT; - case 6: - return D3D10_STENCIL_OP_INVERT; - case 7: - return D3D10_STENCIL_OP_INCR; - case 8: - return D3D10_STENCIL_OP_DECR; - } - }; - - desc.StencilEnable = pass_info.stencil_enable; - desc.StencilReadMask = pass_info.stencil_read_mask; - desc.StencilWriteMask = pass_info.stencil_write_mask; - desc.FrontFace.StencilFailOp = literal_to_stencil_op(pass_info.stencil_op_fail); - desc.FrontFace.StencilDepthFailOp = literal_to_stencil_op(pass_info.stencil_op_depth_fail); - desc.FrontFace.StencilPassOp = literal_to_stencil_op(pass_info.stencil_op_pass); - desc.FrontFace.StencilFunc = static_cast(pass_info.stencil_comparison_func); - desc.BackFace = desc.FrontFace; - if (HRESULT hr = _device->CreateDepthStencilState(&desc, &pass.depth_stencil_state); FAILED(hr)) - { - LOG(ERROR) << "Failed to create depth stencil state for pass " << pass_index << " in technique '" << technique.name << "'! " - "HRESULT is '" << std::hex << hr << std::dec << "'."; - return false; - } - - pass.stencil_reference = pass_info.stencil_reference_value; - } - - for (auto &srv : pass.shader_resources) - { - if (srv == nullptr) - continue; - - com_ptr res1; - srv->GetResource(&res1); - - for (const auto &rtv : pass.render_targets) - { - if (rtv == nullptr) - continue; - - com_ptr res2; - rtv->GetResource(&res2); - - if (res1 == res2) - { - srv.reset(); - break; - } - } - } - } - - return true; -} - -void reshade::d3d10::runtime_d3d10::render_technique(technique &technique) -{ - d3d10_technique_data &technique_data = *technique.impl->as(); - - // Evaluate queries - if (technique_data.query_in_flight) - { - UINT64 timestamp0, timestamp1; - D3D10_QUERY_DATA_TIMESTAMP_DISJOINT disjoint; - - if (technique_data.timestamp_disjoint->GetData(&disjoint, sizeof(disjoint), D3D10_ASYNC_GETDATA_DONOTFLUSH) == S_OK && - technique_data.timestamp_query_beg->GetData(×tamp0, sizeof(timestamp0), D3D10_ASYNC_GETDATA_DONOTFLUSH) == S_OK && - technique_data.timestamp_query_end->GetData(×tamp1, sizeof(timestamp1), D3D10_ASYNC_GETDATA_DONOTFLUSH) == S_OK) - { - if (!disjoint.Disjoint) - technique.average_gpu_duration.append((timestamp1 - timestamp0) * 1'000'000'000 / disjoint.Frequency); - technique_data.query_in_flight = false; - } - } - - if (!technique_data.query_in_flight) - { - technique_data.timestamp_disjoint->Begin(); - technique_data.timestamp_query_beg->End(); - } - - bool is_default_depthstencil_cleared = false; - - // Setup vertex input - const uintptr_t null = 0; - _device->IASetPrimitiveTopology(D3D10_PRIMITIVE_TOPOLOGY_TRIANGLELIST); - _device->IASetInputLayout(nullptr); - _device->IASetVertexBuffers(0, 1, reinterpret_cast(&null), reinterpret_cast(&null), reinterpret_cast(&null)); - - _device->RSSetState(_effect_rasterizer_state.get()); - - // Setup samplers - _device->VSSetSamplers(0, static_cast(technique_data.sampler_states.size()), reinterpret_cast(technique_data.sampler_states.data())); - _device->PSSetSamplers(0, static_cast(technique_data.sampler_states.size()), reinterpret_cast(technique_data.sampler_states.data())); - - // Setup shader constants - if (technique_data.uniform_storage_index >= 0) - { - void *data = nullptr; - D3D10_BUFFER_DESC desc = {}; - const auto constant_buffer = _constant_buffers[technique_data.uniform_storage_index].get(); - - constant_buffer->GetDesc(&desc); - const HRESULT hr = constant_buffer->Map(D3D10_MAP_WRITE_DISCARD, 0, &data); - - if (SUCCEEDED(hr)) - { - memcpy(data, _uniform_data_storage.data() + technique_data.uniform_storage_offset, desc.ByteWidth); - - constant_buffer->Unmap(); - } - else - { - LOG(ERROR) << "Failed to map constant buffer! HRESULT is '" << std::hex << hr << std::dec << "'!"; - } - - _device->VSSetConstantBuffers(0, 1, &constant_buffer); - _device->PSSetConstantBuffers(0, 1, &constant_buffer); - } - - // Disable unused shader stages - _device->GSSetShader(nullptr); - - for (const auto &pass_object : technique.passes_data) - { - const d3d10_pass_data &pass = *pass_object->as(); - - // Setup states - _device->VSSetShader(pass.vertex_shader.get()); - _device->PSSetShader(pass.pixel_shader.get()); - - _device->OMSetBlendState(pass.blend_state.get(), nullptr, D3D10_DEFAULT_SAMPLE_MASK); - _device->OMSetDepthStencilState(pass.depth_stencil_state.get(), pass.stencil_reference); - - // Save back buffer of previous pass - _device->CopyResource(_backbuffer_texture.get(), _backbuffer_resolved.get()); - - // Setup shader resources - _device->VSSetShaderResources(0, static_cast(pass.shader_resources.size()), reinterpret_cast(pass.shader_resources.data())); - _device->PSSetShaderResources(0, static_cast(pass.shader_resources.size()), reinterpret_cast(pass.shader_resources.data())); - - // Setup render targets - if (pass.viewport.Width == _width && pass.viewport.Height == _height) - { - _device->OMSetRenderTargets(D3D10_SIMULTANEOUS_RENDER_TARGET_COUNT, reinterpret_cast(pass.render_targets), _default_depthstencil.get()); - - if (!is_default_depthstencil_cleared) - { - is_default_depthstencil_cleared = true; - - _device->ClearDepthStencilView(_default_depthstencil.get(), D3D10_CLEAR_DEPTH | D3D10_CLEAR_STENCIL, 1.0f, 0); - } - } - else - { - _device->OMSetRenderTargets(D3D10_SIMULTANEOUS_RENDER_TARGET_COUNT, reinterpret_cast(pass.render_targets), nullptr); - } - - _device->RSSetViewports(1, &pass.viewport); - - if (pass.clear_render_targets) - { - for (const auto &target : pass.render_targets) - { - if (target != nullptr) - { - const float color[4] = { 0.0f, 0.0f, 0.0f, 0.0f }; - _device->ClearRenderTargetView(target.get(), color); - } - } - } - - // Draw triangle - _device->Draw(3, 0); - - _vertices += 3; - _drawcalls += 1; - - // Reset render targets - _device->OMSetRenderTargets(0, nullptr, nullptr); - - // Reset shader resources - ID3D10ShaderResourceView *null_srv[D3D10_COMMONSHADER_INPUT_RESOURCE_SLOT_COUNT] = { nullptr }; - _device->VSSetShaderResources(0, static_cast(pass.shader_resources.size()), null_srv); - _device->PSSetShaderResources(0, static_cast(pass.shader_resources.size()), null_srv); - - // Update shader resources - for (const auto &resource : pass.render_target_resources) - { - if (resource == nullptr) - continue; - - D3D10_SHADER_RESOURCE_VIEW_DESC resource_desc; - resource->GetDesc(&resource_desc); - - if (resource_desc.Texture2D.MipLevels > 1) - _device->GenerateMips(resource.get()); - } - } - - if (!technique_data.query_in_flight) - { - technique_data.timestamp_query_end->End(); - technique_data.timestamp_disjoint->End(); - technique_data.query_in_flight = true; - } -} - -#if RESHADE_GUI -bool reshade::d3d10::runtime_d3d10::init_imgui_resources() -{ - { const resources::data_resource vs = resources::load_data_resource(IDR_IMGUI_VS); - if (FAILED(_device->CreateVertexShader(vs.data, vs.data_size, &_imgui_vertex_shader))) - return false; - - const D3D10_INPUT_ELEMENT_DESC input_layout[] = { - { "POSITION", 0, DXGI_FORMAT_R32G32_FLOAT, 0, offsetof(ImDrawVert, pos), D3D10_INPUT_PER_VERTEX_DATA, 0 }, - { "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, offsetof(ImDrawVert, uv ), D3D10_INPUT_PER_VERTEX_DATA, 0 }, - { "COLOR", 0, DXGI_FORMAT_R8G8B8A8_UNORM, 0, offsetof(ImDrawVert, col), D3D10_INPUT_PER_VERTEX_DATA, 0 }, - }; - if (FAILED(_device->CreateInputLayout(input_layout, _countof(input_layout), vs.data, vs.data_size, &_imgui_input_layout))) - return false; - } - - { const resources::data_resource ps = resources::load_data_resource(IDR_IMGUI_PS); - if (FAILED(_device->CreatePixelShader(ps.data, ps.data_size, &_imgui_pixel_shader))) - return false; - } - - { D3D10_BUFFER_DESC desc = {}; - desc.ByteWidth = 16 * sizeof(float); - desc.Usage = D3D10_USAGE_IMMUTABLE; - desc.BindFlags = D3D10_BIND_CONSTANT_BUFFER; - - // Setup orthographic projection matrix - const float ortho_projection[16] = { - 2.0f / _width, 0.0f, 0.0f, 0.0f, - 0.0f, -2.0f / _height, 0.0f, 0.0f, - 0.0f, 0.0f, 0.5f, 0.0f, - -1.f, 1.0f, 0.5f, 1.0f - }; - - D3D10_SUBRESOURCE_DATA initial_data = {}; - initial_data.pSysMem = ortho_projection; - initial_data.SysMemPitch = sizeof(ortho_projection); - if (FAILED(_device->CreateBuffer(&desc, &initial_data, &_imgui_constant_buffer))) - return false; - } - - { D3D10_BLEND_DESC desc = {}; - desc.BlendEnable[0] = true; - desc.SrcBlend = D3D10_BLEND_SRC_ALPHA; - desc.DestBlend = D3D10_BLEND_INV_SRC_ALPHA; - desc.BlendOp = D3D10_BLEND_OP_ADD; - desc.SrcBlendAlpha = D3D10_BLEND_INV_SRC_ALPHA; - desc.DestBlendAlpha = D3D10_BLEND_ZERO; - desc.BlendOpAlpha = D3D10_BLEND_OP_ADD; - desc.RenderTargetWriteMask[0] = D3D10_COLOR_WRITE_ENABLE_ALL; - if (FAILED(_device->CreateBlendState(&desc, &_imgui_blend_state))) - return false; - } - - { D3D10_RASTERIZER_DESC desc = {}; - desc.FillMode = D3D10_FILL_SOLID; - desc.CullMode = D3D10_CULL_NONE; - desc.ScissorEnable = true; - desc.DepthClipEnable = true; - if (FAILED(_device->CreateRasterizerState(&desc, &_imgui_rasterizer_state))) - return false; - } - - { D3D10_DEPTH_STENCIL_DESC desc = {}; - desc.DepthEnable = false; - desc.StencilEnable = false; - if (FAILED(_device->CreateDepthStencilState(&desc, &_imgui_depthstencil_state))) - return false; - } - - { D3D10_SAMPLER_DESC desc = {}; - desc.Filter = D3D10_FILTER_MIN_MAG_MIP_LINEAR; - desc.AddressU = D3D10_TEXTURE_ADDRESS_WRAP; - desc.AddressV = D3D10_TEXTURE_ADDRESS_WRAP; - desc.AddressW = D3D10_TEXTURE_ADDRESS_WRAP; - desc.ComparisonFunc = D3D10_COMPARISON_ALWAYS; - if (FAILED(_device->CreateSamplerState(&desc, &_imgui_texture_sampler))) - return false; - } - - return true; -} - -void reshade::d3d10::runtime_d3d10::render_imgui_draw_data(ImDrawData *draw_data) -{ - assert(draw_data->DisplayPos.x == 0 && draw_data->DisplaySize.x == _width); - assert(draw_data->DisplayPos.y == 0 && draw_data->DisplaySize.y == _height); - - // Create and grow vertex/index buffers if needed - if (_imgui_index_buffer_size < draw_data->TotalIdxCount) - { - _imgui_index_buffer.reset(); - _imgui_index_buffer_size = draw_data->TotalIdxCount + 10000; - - D3D10_BUFFER_DESC desc = {}; - desc.Usage = D3D10_USAGE_DYNAMIC; - desc.ByteWidth = _imgui_index_buffer_size * sizeof(ImDrawIdx); - desc.BindFlags = D3D10_BIND_INDEX_BUFFER; - desc.CPUAccessFlags = D3D10_CPU_ACCESS_WRITE; - - if (FAILED(_device->CreateBuffer(&desc, nullptr, &_imgui_index_buffer))) - return; - } - if (_imgui_vertex_buffer_size < draw_data->TotalVtxCount) - { - _imgui_vertex_buffer.reset(); - _imgui_vertex_buffer_size = draw_data->TotalVtxCount + 5000; - - D3D10_BUFFER_DESC desc = {}; - desc.Usage = D3D10_USAGE_DYNAMIC; - desc.ByteWidth = _imgui_vertex_buffer_size * sizeof(ImDrawVert); - desc.BindFlags = D3D10_BIND_VERTEX_BUFFER; - desc.CPUAccessFlags = D3D10_CPU_ACCESS_WRITE; - desc.MiscFlags = 0; - - if (FAILED(_device->CreateBuffer(&desc, nullptr, &_imgui_vertex_buffer))) - return; - } - - ImDrawIdx *idx_dst; ImDrawVert *vtx_dst; - if (FAILED(_imgui_index_buffer->Map(D3D10_MAP_WRITE_DISCARD, 0, reinterpret_cast(&idx_dst))) || - FAILED(_imgui_vertex_buffer->Map(D3D10_MAP_WRITE_DISCARD, 0, reinterpret_cast(&vtx_dst)))) - return; - - for (int n = 0; n < draw_data->CmdListsCount; n++) - { - const ImDrawList *const draw_list = draw_data->CmdLists[n]; - memcpy(idx_dst, draw_list->IdxBuffer.Data, draw_list->IdxBuffer.Size * sizeof(ImDrawIdx)); - memcpy(vtx_dst, draw_list->VtxBuffer.Data, draw_list->VtxBuffer.Size * sizeof(ImDrawVert)); - idx_dst += draw_list->IdxBuffer.Size; - vtx_dst += draw_list->VtxBuffer.Size; - } - - _imgui_index_buffer->Unmap(); - _imgui_vertex_buffer->Unmap(); - - // Setup render state and render draw lists - _device->IASetInputLayout(_imgui_input_layout.get()); - _device->IASetIndexBuffer(_imgui_index_buffer.get(), sizeof(ImDrawIdx) == 2 ? DXGI_FORMAT_R16_UINT : DXGI_FORMAT_R32_UINT, 0); - const UINT stride = sizeof(ImDrawVert), offset = 0; - ID3D10Buffer *const vertex_buffers[] = { _imgui_vertex_buffer.get() }; - _device->IASetVertexBuffers(0, ARRAYSIZE(vertex_buffers), vertex_buffers, &stride, &offset); - _device->IASetPrimitiveTopology(D3D10_PRIMITIVE_TOPOLOGY_TRIANGLELIST); - _device->VSSetShader(_imgui_vertex_shader.get()); - ID3D10Buffer *const constant_buffers[] = { _imgui_constant_buffer.get() }; - _device->VSSetConstantBuffers(0, ARRAYSIZE(constant_buffers), constant_buffers); - _device->GSSetShader(nullptr); - _device->PSSetShader(_imgui_pixel_shader.get()); - ID3D10SamplerState *const samplers[] = { _imgui_texture_sampler.get() }; - _device->PSSetSamplers(0, ARRAYSIZE(samplers), samplers); - _device->RSSetState(_imgui_rasterizer_state.get()); - const D3D10_VIEWPORT viewport = { 0, 0, _width, _height, 0.0f, 1.0f }; - _device->RSSetViewports(1, &viewport); - const FLOAT blend_factor[4] = { 0.f, 0.f, 0.f, 0.f }; - _device->OMSetBlendState(_imgui_blend_state.get(), blend_factor, D3D10_DEFAULT_SAMPLE_MASK); - _device->OMSetDepthStencilState(_imgui_depthstencil_state.get(), 0); - ID3D10RenderTargetView *const render_targets[] = { _backbuffer_rtv[0].get() }; - _device->OMSetRenderTargets(ARRAYSIZE(render_targets), render_targets, nullptr); - - UINT vtx_offset = 0, idx_offset = 0; - for (int n = 0; n < draw_data->CmdListsCount; n++) - { - const ImDrawList *const draw_list = draw_data->CmdLists[n]; - - for (const ImDrawCmd &cmd : draw_list->CmdBuffer) - { - assert(cmd.TextureId != 0); - assert(cmd.UserCallback == nullptr); - - const D3D10_RECT scissor_rect = { - static_cast(cmd.ClipRect.x), - static_cast(cmd.ClipRect.y), - static_cast(cmd.ClipRect.z), - static_cast(cmd.ClipRect.w) - }; - _device->RSSetScissorRects(1, &scissor_rect); - - ID3D10ShaderResourceView *const texture_view = - static_cast(cmd.TextureId)->srv[0].get(); - _device->PSSetShaderResources(0, 1, &texture_view); - - _device->DrawIndexed(cmd.ElemCount, idx_offset, vtx_offset); - - idx_offset += cmd.ElemCount; - } - - vtx_offset += draw_list->VtxBuffer.size(); - } -} - -void reshade::d3d10::runtime_d3d10::draw_debug_menu() -{ - ImGui::Text("MSAA is %s", _is_multisampling_enabled ? "active" : "inactive"); - ImGui::Spacing(); - - assert(_current_tracker != nullptr); - -#if RESHADE_DX10_CAPTURE_DEPTH_BUFFERS - if (ImGui::CollapsingHeader("Depth and Intermediate Buffers", ImGuiTreeNodeFlags_DefaultOpen)) - { - bool modified = false; - modified |= ImGui::Combo("Depth Texture Format", &depth_buffer_texture_format, "All\0D16\0D32F\0D24S8\0D32FS8\0"); - - if (modified) - { - runtime::save_config(); - _current_tracker->reset(); - create_depthstencil_replacement(nullptr, nullptr); - return; - } - - modified |= ImGui::Checkbox("Copy depth before clearing", &depth_buffer_before_clear); - - if (depth_buffer_before_clear) - { - if (ImGui::Checkbox("Extended depth buffer detection", &extended_depth_buffer_detection)) - { - cleared_depth_buffer_index = 0; - modified = true; - } - - _current_tracker->keep_cleared_depth_textures(); - - ImGui::Spacing(); - ImGui::TextUnformatted("Depth Buffers:"); - - unsigned int current_index = 1; - - for (const auto &it : _current_tracker->cleared_depth_textures()) - { - char label[512] = ""; - sprintf_s(label, "%s%2u", (current_index == cleared_depth_buffer_index ? "> " : " "), current_index); - - if (bool value = cleared_depth_buffer_index == current_index; ImGui::Checkbox(label, &value)) - { - cleared_depth_buffer_index = value ? current_index : 0; - modified = true; - } - - ImGui::SameLine(); - - ImGui::Text("=> 0x%p | 0x%p | %ux%u", it.second.src_depthstencil.get(), it.second.src_texture.get(), it.second.src_texture_desc.Width, it.second.src_texture_desc.Height); - - if (it.second.dest_texture != nullptr) - { - ImGui::SameLine(); - - ImGui::Text("=> %p", it.second.dest_texture.get()); - } - - current_index++; - } - } - else if (!_current_tracker->depth_buffer_counters().empty()) - { - ImGui::Spacing(); - ImGui::TextUnformatted("Depth Buffers: (intermediate buffer draw calls in parentheses)"); - - for (const auto &[depthstencil, snapshot] : _current_tracker->depth_buffer_counters()) - { - char label[512] = ""; - sprintf_s(label, "%s0x%p", (depthstencil == _depthstencil ? "> " : " "), depthstencil.get()); - - if (bool value = _best_depth_stencil_overwrite == depthstencil; ImGui::Checkbox(label, &value)) - { - _best_depth_stencil_overwrite = value ? depthstencil.get() : nullptr; - - com_ptr texture = snapshot.texture; - - if (texture == nullptr && _best_depth_stencil_overwrite != nullptr) - { - com_ptr resource; - _best_depth_stencil_overwrite->GetResource(&resource); - - resource->QueryInterface(&texture); - } - - create_depthstencil_replacement(_best_depth_stencil_overwrite, texture.get()); - } - - ImGui::SameLine(); - - std::string additional_view_label; - - if (!snapshot.additional_views.empty()) - { - additional_view_label += '('; - - for (auto const &[view, stats] : snapshot.additional_views) - additional_view_label += std::to_string(stats.drawcalls) + ", "; - - // Remove last ", " from string - additional_view_label.pop_back(); - additional_view_label.pop_back(); - - additional_view_label += ')'; - } - - ImGui::Text("| %5u draw calls ==> %8u vertices, %2u additional render target%c %s", snapshot.stats.drawcalls, snapshot.stats.vertices, snapshot.additional_views.size(), snapshot.additional_views.size() != 1 ? 's' : ' ', additional_view_label.c_str()); - } - } - - if (modified) - { - runtime::save_config(); - } - } -#endif -#if RESHADE_DX10_CAPTURE_CONSTANT_BUFFERS - if (ImGui::CollapsingHeader("Constant Buffers", ImGuiTreeNodeFlags_DefaultOpen)) - { - for (const auto &[buffer, counter] : _current_tracker.constant_buffer_counters()) - { - bool likely_camera_transform_buffer = false; - - D3D10_BUFFER_DESC desc; - buffer->GetDesc(&desc); - - if (counter.ps_uses > 0 && counter.vs_uses > 0 && counter.mapped < .10 * counter.vs_uses && desc.ByteWidth > 1000) - likely_camera_transform_buffer = true; - - ImGui::Text("%s 0x%p | used in %4u vertex shaders and %4u pixel shaders, mapped %3u times, %8u bytes", likely_camera_transform_buffer ? ">" : " ", buffer.get(), counter.vs_uses, counter.ps_uses, counter.mapped, desc.ByteWidth); - } - } -#endif -} -#endif - -#if RESHADE_DX10_CAPTURE_DEPTH_BUFFERS -void reshade::d3d10::runtime_d3d10::detect_depth_source(draw_call_tracker &tracker) -{ - if (depth_buffer_before_clear) - _best_depth_stencil_overwrite = nullptr; - - if (_is_multisampling_enabled || _best_depth_stencil_overwrite != nullptr || (_framecount % 30 && !depth_buffer_before_clear)) - return; - - if (_has_high_network_activity) - { - create_depthstencil_replacement(nullptr, nullptr); - return; - } - - if (depth_buffer_before_clear) - { - // At the final rendering stage, it is fine to rely on the depth stencil to select the best depth texture - // But when we retrieve the depth textures before the final rendering stage, there is chance that one or many different depth textures are associated to the same depth stencil (for instance, in Bioshock 2) - // In this case, we cannot use the depth stencil to determine which depth texture is the good one, so we can use the default depth stencil - // For the moment, the best we can do is retrieve all the depth textures that has been cleared in the rendering pipeline, then select one of them (by default, the last one) - // In the future, maybe we could find a way to retrieve depth texture statistics (number of draw calls and number of vertices), so ReShade could automatically select the best one - ID3D10Texture2D *const best_match_texture = tracker.find_best_cleared_depth_buffer_texture(cleared_depth_buffer_index); - if (best_match_texture != nullptr) - create_depthstencil_replacement(_default_depthstencil.get(), best_match_texture); - return; - } - - const auto best_snapshot = tracker.find_best_snapshot(_width, _height); - if (best_snapshot.depthstencil != nullptr) - create_depthstencil_replacement(best_snapshot.depthstencil, best_snapshot.texture.get()); -} - -bool reshade::d3d10::runtime_d3d10::create_depthstencil_replacement(ID3D10DepthStencilView *depthstencil, ID3D10Texture2D *texture) -{ - _depthstencil.reset(); - _depthstencil_replacement.reset(); - _depthstencil_texture.reset(); - _depthstencil_texture_srv.reset(); - - if (depthstencil != nullptr) - { - assert(texture != nullptr); - _depthstencil = depthstencil; - _depthstencil_texture = texture; - - D3D10_TEXTURE2D_DESC tex_desc; - _depthstencil_texture->GetDesc(&tex_desc); - - HRESULT hr = S_OK; - - if ((tex_desc.BindFlags & D3D10_BIND_SHADER_RESOURCE) == 0) - { - _depthstencil_texture.reset(); - - tex_desc.Format = make_dxgi_format_typeless(tex_desc.Format); - tex_desc.BindFlags = D3D10_BIND_DEPTH_STENCIL | D3D10_BIND_SHADER_RESOURCE; - - hr = _device->CreateTexture2D(&tex_desc, nullptr, &_depthstencil_texture); - - if (SUCCEEDED(hr)) - { - D3D10_DEPTH_STENCIL_VIEW_DESC dsv_desc = {}; - dsv_desc.ViewDimension = D3D10_DSV_DIMENSION_TEXTURE2D; - dsv_desc.Format = make_dxgi_format_dsv(tex_desc.Format); - - hr = _device->CreateDepthStencilView(_depthstencil_texture.get(), &dsv_desc, &_depthstencil_replacement); - } - } - - if (FAILED(hr)) - { - LOG(ERROR) << "Failed to create depth stencil replacement texture! HRESULT is '" << std::hex << hr << std::dec << "'."; - return false; - } - - D3D10_SHADER_RESOURCE_VIEW_DESC srv_desc = {}; - srv_desc.ViewDimension = D3D10_SRV_DIMENSION_TEXTURE2D; - srv_desc.Texture2D.MipLevels = 1; - srv_desc.Format = make_dxgi_format_normal(tex_desc.Format); - - if (hr = _device->CreateShaderResourceView(_depthstencil_texture.get(), &srv_desc, &_depthstencil_texture_srv); FAILED(hr)) - { - LOG(ERROR) << "Failed to create depth stencil replacement resource view! HRESULT is '" << std::hex << hr << std::dec << "'."; - return false; - } - - // Update auto depth stencil - com_ptr current_depthstencil; - com_ptr targets[D3D10_SIMULTANEOUS_RENDER_TARGET_COUNT]; - _device->OMGetRenderTargets(D3D10_SIMULTANEOUS_RENDER_TARGET_COUNT, reinterpret_cast(targets), ¤t_depthstencil); - if (current_depthstencil != nullptr && current_depthstencil == _depthstencil) - _device->OMSetRenderTargets(D3D10_SIMULTANEOUS_RENDER_TARGET_COUNT, reinterpret_cast(targets), _depthstencil_replacement.get()); - } - - update_texture_references(texture_reference::depth_buffer); - - return true; -} - -com_ptr reshade::d3d10::runtime_d3d10::select_depth_texture_save(D3D10_TEXTURE2D_DESC &texture_desc) -{ - // Function that selects the appropriate texture where we want to save the depth texture before it is cleared - // If this texture is null, create it according to the dimensions and the format of the depth texture - // Doing so, we avoid to create a new texture each time the depth texture is saved - - texture_desc.Format = make_dxgi_format_typeless(texture_desc.Format); - - // Create an unique index based on the texture format and dimensions - UINT idx = texture_desc.Format * texture_desc.Width * texture_desc.Height; - - if (const auto it = _depth_texture_saves.find(idx); it != _depth_texture_saves.end()) - return it->second; - - texture_desc.BindFlags = D3D10_BIND_DEPTH_STENCIL | D3D10_BIND_SHADER_RESOURCE; - - // Create the saved texture pointed by the index if it does not already exist - com_ptr depth_texture_save; - - HRESULT hr = _device->CreateTexture2D(&texture_desc, nullptr, &depth_texture_save); - - if (FAILED(hr)) - { - LOG(ERROR) << "Failed to create depth texture copy! HRESULT is '" << std::hex << hr << std::dec << "'."; - return nullptr; - } - - _depth_texture_saves.emplace(idx, depth_texture_save); - - return depth_texture_save; -} -#endif diff --git a/msvc/source/d3d10/runtime_d3d10.hpp b/msvc/source/d3d10/runtime_d3d10.hpp deleted file mode 100644 index 16704f2..0000000 --- a/msvc/source/d3d10/runtime_d3d10.hpp +++ /dev/null @@ -1,116 +0,0 @@ -/** - * Copyright (C) 2014 Patrick Mours. All rights reserved. - * License: https://github.com/crosire/reshade#license - */ - -#pragma once - -#include "runtime.hpp" -#include "state_block.hpp" -#include "draw_call_tracker.hpp" - -namespace reshade { enum class texture_reference; } -namespace reshadefx { struct sampler_info; } - -namespace reshade::d3d10 -{ - class runtime_d3d10 : public runtime - { - public: - runtime_d3d10(ID3D10Device1 *device, IDXGISwapChain *swapchain); - ~runtime_d3d10(); - - bool on_init(const DXGI_SWAP_CHAIN_DESC &desc); - void on_reset(); - void on_present(draw_call_tracker& tracker); - - void capture_screenshot(uint8_t *buffer) const override; - -#if RESHADE_DX10_CAPTURE_DEPTH_BUFFERS - com_ptr select_depth_texture_save(D3D10_TEXTURE2D_DESC &texture_desc); -#endif - - bool depth_buffer_before_clear = false; - bool extended_depth_buffer_detection = false; - unsigned int cleared_depth_buffer_index = 0; - int depth_buffer_texture_format = 0; // No depth buffer texture format filter by default - - private: - bool init_backbuffer_texture(); - bool init_default_depth_stencil(); - - bool init_texture(texture &info) override; - void upload_texture(texture &texture, const uint8_t *pixels) override; - bool update_texture_reference(texture &texture); - void update_texture_references(texture_reference type); - - bool compile_effect(effect_data &effect) override; - void unload_effects() override; - - bool add_sampler(const reshadefx::sampler_info &info, struct d3d10_technique_data &technique_init); - bool init_technique(technique &info, const struct d3d10_technique_data &technique_init, const std::unordered_map> &entry_points); - - void render_technique(technique &technique) override; - -#if RESHADE_GUI - bool init_imgui_resources(); - void render_imgui_draw_data(ImDrawData *data) override; - void draw_debug_menu(); -#endif - -#if RESHADE_DX10_CAPTURE_DEPTH_BUFFERS - void detect_depth_source(draw_call_tracker& tracker); - bool create_depthstencil_replacement(ID3D10DepthStencilView *depthstencil, ID3D10Texture2D *texture); -#endif - - struct depth_texture_save_info - { - com_ptr src_texture; - D3D10_TEXTURE2D_DESC src_texture_desc; - com_ptr dest_texture; - bool cleared = false; - }; - - com_ptr _device; - com_ptr _swapchain; - - com_ptr _backbuffer_texture; - com_ptr _backbuffer_rtv[3]; - com_ptr _backbuffer_texture_srv[2]; - com_ptr _depthstencil_texture_srv; - std::unordered_map> _effect_sampler_states; - std::vector> _constant_buffers; - - std::map _displayed_depth_textures; - std::unordered_map> _depth_texture_saves; - - bool _is_multisampling_enabled = false; - DXGI_FORMAT _backbuffer_format = DXGI_FORMAT_UNKNOWN; - state_block _app_state; - com_ptr _backbuffer, _backbuffer_resolved; - com_ptr _depthstencil, _depthstencil_replacement; - ID3D10DepthStencilView *_best_depth_stencil_overwrite = nullptr; - com_ptr _depthstencil_texture; - com_ptr _default_depthstencil; - com_ptr _copy_vertex_shader; - com_ptr _copy_pixel_shader; - com_ptr _copy_sampler; - com_ptr _effect_rasterizer_state; - - int _imgui_index_buffer_size = 0; - com_ptr _imgui_index_buffer; - int _imgui_vertex_buffer_size = 0; - com_ptr _imgui_vertex_buffer; - com_ptr _imgui_vertex_shader; - com_ptr _imgui_pixel_shader; - com_ptr _imgui_input_layout; - com_ptr _imgui_constant_buffer; - com_ptr _imgui_texture_sampler; - com_ptr _imgui_rasterizer_state; - com_ptr _imgui_blend_state; - com_ptr _imgui_depthstencil_state; - draw_call_tracker *_current_tracker = nullptr; - - HMODULE _d3d_compiler = nullptr; - }; -} diff --git a/msvc/source/d3d10/state_block.cpp b/msvc/source/d3d10/state_block.cpp deleted file mode 100644 index 1b23ee2..0000000 --- a/msvc/source/d3d10/state_block.cpp +++ /dev/null @@ -1,117 +0,0 @@ -/** - * Copyright (C) 2014 Patrick Mours. All rights reserved. - * License: https://github.com/crosire/reshade#license - */ - -#include "state_block.hpp" - -template -static inline void safe_release(T *&object) -{ - if (object != nullptr) - { - object->Release(); - object = nullptr; - } -} - -reshade::d3d10::state_block::state_block(ID3D10Device *device) -{ - ZeroMemory(this, sizeof(*this)); - - _device = device; -} -reshade::d3d10::state_block::~state_block() -{ - release_all_device_objects(); -} - -void reshade::d3d10::state_block::capture() -{ - _device->IAGetPrimitiveTopology(&_ia_primitive_topology); - _device->IAGetInputLayout(&_ia_input_layout); - - _device->IAGetVertexBuffers(0, ARRAYSIZE(_ia_vertex_buffers), _ia_vertex_buffers, _ia_vertex_strides, _ia_vertex_offsets); - _device->IAGetIndexBuffer(&_ia_index_buffer, &_ia_index_format, &_ia_index_offset); - - _device->RSGetState(&_rs_state); - _device->RSGetViewports(&_rs_num_viewports, nullptr); - _device->RSGetViewports(&_rs_num_viewports, _rs_viewports); - _device->RSGetScissorRects(&_rs_num_scissor_rects, nullptr); - _device->RSGetScissorRects(&_rs_num_scissor_rects, _rs_scissor_rects); - - _device->VSGetShader(&_vs); - _device->VSGetConstantBuffers(0, ARRAYSIZE(_vs_constant_buffers), _vs_constant_buffers); - _device->VSGetSamplers(0, ARRAYSIZE(_vs_sampler_states), _vs_sampler_states); - _device->VSGetShaderResources(0, ARRAYSIZE(_vs_shader_resources), _vs_shader_resources); - - _device->GSGetShader(&_gs); - - _device->PSGetShader(&_ps); - _device->PSGetConstantBuffers(0, ARRAYSIZE(_ps_constant_buffers), _ps_constant_buffers); - _device->PSGetSamplers(0, ARRAYSIZE(_ps_sampler_states), _ps_sampler_states); - _device->PSGetShaderResources(0, ARRAYSIZE(_ps_shader_resources), _ps_shader_resources); - - _device->OMGetBlendState(&_om_blend_state, _om_blend_factor, &_om_sample_mask); - _device->OMGetDepthStencilState(&_om_depth_stencil_state, &_om_stencil_ref); - _device->OMGetRenderTargets(ARRAYSIZE(_om_render_targets), _om_render_targets, &_om_depth_stencil); -} -void reshade::d3d10::state_block::apply_and_release() -{ - _device->IASetPrimitiveTopology(_ia_primitive_topology); - _device->IASetInputLayout(_ia_input_layout); - - _device->IASetVertexBuffers(0, ARRAYSIZE(_ia_vertex_buffers), _ia_vertex_buffers, _ia_vertex_strides, _ia_vertex_offsets); - _device->IASetIndexBuffer(_ia_index_buffer, _ia_index_format, _ia_index_offset); - - _device->RSSetState(_rs_state); - _device->RSSetViewports(_rs_num_viewports, _rs_viewports); - _device->RSSetScissorRects(_rs_num_scissor_rects, _rs_scissor_rects); - - _device->VSSetShader(_vs); - _device->VSSetConstantBuffers(0, ARRAYSIZE(_vs_constant_buffers), _vs_constant_buffers); - _device->VSSetSamplers(0, ARRAYSIZE(_vs_sampler_states), _vs_sampler_states); - _device->VSSetShaderResources(0, ARRAYSIZE(_vs_shader_resources), _vs_shader_resources); - - _device->GSSetShader(_gs); - - _device->PSSetShader(_ps); - _device->PSSetConstantBuffers(0, ARRAYSIZE(_ps_constant_buffers), _ps_constant_buffers); - _device->PSSetSamplers(0, ARRAYSIZE(_ps_sampler_states), _ps_sampler_states); - _device->PSSetShaderResources(0, ARRAYSIZE(_ps_shader_resources), _ps_shader_resources); - - _device->OMSetBlendState(_om_blend_state, _om_blend_factor, _om_sample_mask); - _device->OMSetDepthStencilState(_om_depth_stencil_state, _om_stencil_ref); - _device->OMSetRenderTargets(ARRAYSIZE(_om_render_targets), _om_render_targets, _om_depth_stencil); - - release_all_device_objects(); -} - -void reshade::d3d10::state_block::release_all_device_objects() -{ - safe_release(_ia_input_layout); - for (auto &vertex_buffer : _ia_vertex_buffers) - safe_release(vertex_buffer); - safe_release(_ia_index_buffer); - safe_release(_vs); - for (auto &constant_buffer : _vs_constant_buffers) - safe_release(constant_buffer); - for (auto &sampler_state : _vs_sampler_states) - safe_release(sampler_state); - for (auto &shader_resource : _vs_shader_resources) - safe_release(shader_resource); - safe_release(_gs); - safe_release(_rs_state); - safe_release(_ps); - for (auto &constant_buffer : _ps_constant_buffers) - safe_release(constant_buffer); - for (auto &sampler_state : _ps_sampler_states) - safe_release(sampler_state); - for (auto &shader_resource : _ps_shader_resources) - safe_release(shader_resource); - safe_release(_om_blend_state); - safe_release(_om_depth_stencil_state); - for (auto &render_target : _om_render_targets) - safe_release(render_target); - safe_release(_om_depth_stencil); -} diff --git a/msvc/source/d3d10/state_block.hpp b/msvc/source/d3d10/state_block.hpp deleted file mode 100644 index 399cd5e..0000000 --- a/msvc/source/d3d10/state_block.hpp +++ /dev/null @@ -1,56 +0,0 @@ -/** - * Copyright (C) 2014 Patrick Mours. All rights reserved. - * License: https://github.com/crosire/reshade#license - */ - -#pragma once - -#include "com_ptr.hpp" -#include - -namespace reshade::d3d10 -{ - class state_block - { - public: - explicit state_block(ID3D10Device *device); - ~state_block(); - - void capture(); - void apply_and_release(); - - private: - void release_all_device_objects(); - - com_ptr _device; - ID3D10InputLayout *_ia_input_layout; - D3D10_PRIMITIVE_TOPOLOGY _ia_primitive_topology; - ID3D10Buffer *_ia_vertex_buffers[D3D10_IA_VERTEX_INPUT_RESOURCE_SLOT_COUNT]; - UINT _ia_vertex_strides[D3D10_IA_VERTEX_INPUT_RESOURCE_SLOT_COUNT]; - UINT _ia_vertex_offsets[D3D10_IA_VERTEX_INPUT_RESOURCE_SLOT_COUNT]; - ID3D10Buffer *_ia_index_buffer; - DXGI_FORMAT _ia_index_format; - UINT _ia_index_offset; - ID3D10VertexShader *_vs; - ID3D10Buffer *_vs_constant_buffers[D3D10_COMMONSHADER_CONSTANT_BUFFER_API_SLOT_COUNT]; - ID3D10SamplerState *_vs_sampler_states[D3D10_COMMONSHADER_SAMPLER_SLOT_COUNT]; - ID3D10ShaderResourceView *_vs_shader_resources[D3D10_COMMONSHADER_INPUT_RESOURCE_SLOT_COUNT]; - ID3D10GeometryShader *_gs; - ID3D10RasterizerState *_rs_state; - UINT _rs_num_viewports; - D3D10_VIEWPORT _rs_viewports[D3D10_VIEWPORT_AND_SCISSORRECT_OBJECT_COUNT_PER_PIPELINE]; - UINT _rs_num_scissor_rects; - D3D10_RECT _rs_scissor_rects[D3D10_VIEWPORT_AND_SCISSORRECT_OBJECT_COUNT_PER_PIPELINE]; - ID3D10PixelShader *_ps; - ID3D10Buffer *_ps_constant_buffers[D3D10_COMMONSHADER_CONSTANT_BUFFER_API_SLOT_COUNT]; - ID3D10SamplerState *_ps_sampler_states[D3D10_COMMONSHADER_SAMPLER_SLOT_COUNT]; - ID3D10ShaderResourceView *_ps_shader_resources[D3D10_COMMONSHADER_INPUT_RESOURCE_SLOT_COUNT]; - ID3D10BlendState *_om_blend_state; - FLOAT _om_blend_factor[4]; - UINT _om_sample_mask; - ID3D10DepthStencilState *_om_depth_stencil_state; - UINT _om_stencil_ref; - ID3D10RenderTargetView *_om_render_targets[D3D10_SIMULTANEOUS_RENDER_TARGET_COUNT]; - ID3D10DepthStencilView *_om_depth_stencil; - }; -} diff --git a/msvc/source/d3d11/d3d11.cpp b/msvc/source/d3d11/d3d11.cpp deleted file mode 100644 index 582cd79..0000000 --- a/msvc/source/d3d11/d3d11.cpp +++ /dev/null @@ -1,99 +0,0 @@ -/** - * Copyright (C) 2014 Patrick Mours. All rights reserved. - * License: https://github.com/crosire/reshade#license - */ - -#include "log.hpp" -#include "hook_manager.hpp" -#include "d3d11_device.hpp" -#include "d3d11_device_context.hpp" - -HOOK_EXPORT HRESULT WINAPI D3D11CreateDevice(IDXGIAdapter *pAdapter, D3D_DRIVER_TYPE DriverType, HMODULE Software, UINT Flags, const D3D_FEATURE_LEVEL *pFeatureLevels, UINT FeatureLevels, UINT SDKVersion, ID3D11Device **ppDevice, D3D_FEATURE_LEVEL *pFeatureLevel, ID3D11DeviceContext **ppImmediateContext) -{ - LOG(INFO) << "Redirecting D3D11CreateDevice" << '(' << pAdapter << ", " << DriverType << ", " << Software << ", " << std::hex << Flags << std::dec << ", " << pFeatureLevels << ", " << FeatureLevels << ", " << SDKVersion << ", " << ppDevice << ", " << pFeatureLevel << ", " << ppImmediateContext << ')' << " ..."; - LOG(INFO) << "> Passing on to D3D11CreateDeviceAndSwapChain:"; - - return D3D11CreateDeviceAndSwapChain(pAdapter, DriverType, Software, Flags, pFeatureLevels, FeatureLevels, SDKVersion, nullptr, nullptr, ppDevice, pFeatureLevel, ppImmediateContext); -} - -HOOK_EXPORT HRESULT WINAPI D3D11CreateDeviceAndSwapChain(IDXGIAdapter *pAdapter, D3D_DRIVER_TYPE DriverType, HMODULE Software, UINT Flags, const D3D_FEATURE_LEVEL *pFeatureLevels, UINT FeatureLevels, UINT SDKVersion, const DXGI_SWAP_CHAIN_DESC *pSwapChainDesc, IDXGISwapChain **ppSwapChain, ID3D11Device **ppDevice, D3D_FEATURE_LEVEL *pFeatureLevel, ID3D11DeviceContext **ppImmediateContext) -{ - LOG(INFO) << "Redirecting D3D11CreateDeviceAndSwapChain" << '(' << pAdapter << ", " << DriverType << ", " << Software << ", " << std::hex << Flags << std::dec << ", " << pFeatureLevels << ", " << FeatureLevels << ", " << SDKVersion << ", " << pSwapChainDesc << ", " << ppSwapChain << ", " << ppDevice << ", " << pFeatureLevel << ", " << ppImmediateContext << ')' << " ..."; - - // Use local feature level variable in case the application did not pass one in - D3D_FEATURE_LEVEL FeatureLevel = D3D_FEATURE_LEVEL_11_0; - - HRESULT hr = reshade::hooks::call(D3D11CreateDeviceAndSwapChain)(pAdapter, DriverType, Software, Flags, pFeatureLevels, FeatureLevels, SDKVersion, nullptr, nullptr, ppDevice, &FeatureLevel, nullptr); - - if (FAILED(hr)) - { - LOG(WARN) << "> D3D11CreateDeviceAndSwapChain failed with error code " << std::hex << hr << std::dec << '!'; - return hr; - } - - if (pFeatureLevel != nullptr) // Copy feature level value to application variable if the argument exists - *pFeatureLevel = FeatureLevel; - - LOG(INFO) << "> Using feature level " << std::hex << FeatureLevel << std::dec << '.'; - - // It is valid for the device out parameter to be NULL if the application wants to check feature level support, so just return early in that case - if (ppDevice == nullptr) - return hr; - - const auto device = *ppDevice; - - // Query for the DXGI device and immediate device context since we need to reference them in the hooked device - IDXGIDevice1 *dxgi_device = nullptr; - device->QueryInterface(&dxgi_device); - ID3D11DeviceContext *device_context = nullptr; - device->GetImmediateContext(&device_context); - - const auto device_proxy = new D3D11Device(dxgi_device, device, device_context); - - // Swap chain creation is piped through the 'IDXGIFactory::CreateSwapChain' function hook - if (pSwapChainDesc != nullptr) - { - assert(ppSwapChain != nullptr); - - com_ptr adapter(pAdapter, false); - // Fall back to the same adapter as the device if it was not explicitly specified in the argument list - if (adapter == nullptr) - { - hr = dxgi_device->GetAdapter(&adapter); - assert(SUCCEEDED(hr)); // Lets just assume this works =) - } - - // Time to find a factory associated with the target adapter and create a swap chain with it - com_ptr factory; - hr = adapter->GetParent(IID_PPV_ARGS(&factory)); - assert(SUCCEEDED(hr)); - - LOG(INFO) << "> Calling IDXGIFactory::CreateSwapChain:"; - - hr = factory->CreateSwapChain(device_proxy, const_cast(pSwapChainDesc), ppSwapChain); - } - - if (SUCCEEDED(hr)) - { -#if RESHADE_VERBOSE_LOG - LOG(DEBUG) << "Returning IDXGIDevice1 object " << device_proxy->_dxgi_device << " and ID3D11Device object " << device_proxy << '.'; -#endif - *ppDevice = device_proxy; - - if (ppImmediateContext != nullptr) - { -#if RESHADE_VERBOSE_LOG - LOG(DEBUG) << "Returning ID3D11DeviceContext object " << device_proxy->_immediate_context << '.'; -#endif - device_proxy->_immediate_context->AddRef(); // D3D11CreateDeviceAndSwapChain increases the reference count on the returned object - *ppImmediateContext = device_proxy->_immediate_context; - } - } - else - { - // Swap chain creation failed, so do clean up - device_proxy->Release(); - } - - return hr; -} diff --git a/msvc/source/d3d11/d3d11_device.cpp b/msvc/source/d3d11/d3d11_device.cpp deleted file mode 100644 index 375e9e9..0000000 --- a/msvc/source/d3d11/d3d11_device.cpp +++ /dev/null @@ -1,493 +0,0 @@ -/** - * Copyright (C) 2014 Patrick Mours. All rights reserved. - * License: https://github.com/crosire/reshade#license - */ - -#include "log.hpp" -#include "d3d11_device.hpp" -#include "d3d11_device_context.hpp" -#include "../dxgi/dxgi_device.hpp" -#include "draw_call_tracker.hpp" - -D3D11Device::D3D11Device(IDXGIDevice1 *dxgi_device, ID3D11Device *original, ID3D11DeviceContext *immediate_context) : - _orig(original), - _interface_version(0), - _dxgi_device(new DXGIDevice(dxgi_device, this)), - _immediate_context(new D3D11DeviceContext(this, immediate_context)) { - assert(original != nullptr); -} - -void D3D11Device::add_commandlist_trackers(ID3D11CommandList* command_list, const reshade::d3d11::draw_call_tracker &tracker_source) -{ - assert(command_list != nullptr); - - const std::lock_guard lock(_trackers_per_commandlist_mutex); - - // Merges the counters in counters_source in the counters_per_commandlist for the command list specified - const auto it = _trackers_per_commandlist.find(command_list); - if (it == _trackers_per_commandlist.end()) - _trackers_per_commandlist.emplace(command_list, tracker_source); - else - it->second.merge(tracker_source); -} -void D3D11Device::merge_commandlist_trackers(ID3D11CommandList* command_list, reshade::d3d11::draw_call_tracker &tracker_destination) -{ - assert(command_list != nullptr); - - const std::lock_guard lock(_trackers_per_commandlist_mutex); - - // Merges the counters logged for the specified command list in the counters destination tracker specified - const auto it = _trackers_per_commandlist.find(command_list); - if (it != _trackers_per_commandlist.end()) - tracker_destination.merge(it->second); -} - -void D3D11Device::clear_drawcall_stats() -{ - _immediate_context->clear_drawcall_stats(); - - const std::lock_guard lock(_trackers_per_commandlist_mutex); - - _trackers_per_commandlist.clear(); - _clear_DSV_iter = 1; -} - -bool D3D11Device::check_and_upgrade_interface(REFIID riid) -{ - static const IID iid_lookup[] = { - __uuidof(ID3D11Device), - __uuidof(ID3D11Device1), - __uuidof(ID3D11Device2), - __uuidof(ID3D11Device3), - __uuidof(ID3D11Device4), - __uuidof(ID3D11Device5), - }; - - for (unsigned int new_version = _interface_version + 1; new_version < ARRAYSIZE(iid_lookup); ++new_version) - { - if (riid == iid_lookup[new_version]) - { - IUnknown *new_interface = nullptr; - if (FAILED(_orig->QueryInterface(riid, reinterpret_cast(&new_interface)))) - return false; -#if RESHADE_VERBOSE_LOG - LOG(DEBUG) << "Upgraded ID3D11Device" << _interface_version << " object " << this << " to ID3D11Device" << new_version << '.'; -#endif - _orig->Release(); - _orig = static_cast(new_interface); - _interface_version = new_version; - break; - } - } - - return true; -} - -HRESULT STDMETHODCALLTYPE D3D11Device::QueryInterface(REFIID riid, void **ppvObj) -{ - if (ppvObj == nullptr) - return E_POINTER; - - if (riid == __uuidof(this) || - riid == __uuidof(ID3D11Device) || - riid == __uuidof(ID3D11Device1) || - riid == __uuidof(ID3D11Device2) || - riid == __uuidof(ID3D11Device3) || - riid == __uuidof(ID3D11Device4) || - riid == __uuidof(ID3D11Device5)) - { - if (!check_and_upgrade_interface(riid)) - return E_NOINTERFACE; - - AddRef(); - *ppvObj = this; - return S_OK; - } - - // Note: Objects must have an identity, so use DXGIDevice for IID_IUnknown - // See https://docs.microsoft.com/en-us/windows/desktop/com/rules-for-implementing-queryinterface - if (riid == __uuidof(IUnknown) || - riid == __uuidof(DXGIDevice) || - riid == __uuidof(IDXGIObject) || - riid == __uuidof(IDXGIDevice) || - riid == __uuidof(IDXGIDevice1) || - riid == __uuidof(IDXGIDevice2) || - riid == __uuidof(IDXGIDevice3)) - return _dxgi_device->QueryInterface(riid, ppvObj); - - return _orig->QueryInterface(riid, ppvObj); -} -ULONG STDMETHODCALLTYPE D3D11Device::AddRef() -{ - ++_ref; - - _dxgi_device->AddRef(); - _immediate_context->AddRef(); - - return _orig->AddRef(); -} -ULONG STDMETHODCALLTYPE D3D11Device::Release() -{ - --_ref; - - _dxgi_device->Release(); - _immediate_context->Release(); - - const ULONG ref = _orig->Release(); - - if (ref != 0 && _ref != 0) - return ref; - else if (ref != 0) - LOG(WARN) << "Reference count for ID3D11Device" << _interface_version << " object " << this << " is inconsistent: " << ref << ", but expected 0."; - - assert(_ref <= 0); -#if RESHADE_VERBOSE_LOG - LOG(DEBUG) << "Destroyed ID3D11Device" << _interface_version << " object " << this << '.'; -#endif - delete this; - return 0; -} - -HRESULT STDMETHODCALLTYPE D3D11Device::CreateBuffer(const D3D11_BUFFER_DESC *pDesc, const D3D11_SUBRESOURCE_DATA *pInitialData, ID3D11Buffer **ppBuffer) -{ - return _orig->CreateBuffer(pDesc, pInitialData, ppBuffer); -} -HRESULT STDMETHODCALLTYPE D3D11Device::CreateTexture1D(const D3D11_TEXTURE1D_DESC *pDesc, const D3D11_SUBRESOURCE_DATA *pInitialData, ID3D11Texture1D **ppTexture1D) -{ - return _orig->CreateTexture1D(pDesc, pInitialData, ppTexture1D); -} -HRESULT STDMETHODCALLTYPE D3D11Device::CreateTexture2D(const D3D11_TEXTURE2D_DESC *pDesc, const D3D11_SUBRESOURCE_DATA *pInitialData, ID3D11Texture2D **ppTexture2D) -{ - return _orig->CreateTexture2D(pDesc, pInitialData, ppTexture2D); -} -HRESULT STDMETHODCALLTYPE D3D11Device::CreateTexture3D(const D3D11_TEXTURE3D_DESC *pDesc, const D3D11_SUBRESOURCE_DATA *pInitialData, ID3D11Texture3D **ppTexture3D) -{ - return _orig->CreateTexture3D(pDesc, pInitialData, ppTexture3D); -} -HRESULT STDMETHODCALLTYPE D3D11Device::CreateShaderResourceView(ID3D11Resource *pResource, const D3D11_SHADER_RESOURCE_VIEW_DESC *pDesc, ID3D11ShaderResourceView **ppSRView) -{ - return _orig->CreateShaderResourceView(pResource, pDesc, ppSRView); -} -HRESULT STDMETHODCALLTYPE D3D11Device::CreateUnorderedAccessView(ID3D11Resource *pResource, const D3D11_UNORDERED_ACCESS_VIEW_DESC *pDesc, ID3D11UnorderedAccessView **ppUAView) -{ - return _orig->CreateUnorderedAccessView(pResource, pDesc, ppUAView); -} -HRESULT STDMETHODCALLTYPE D3D11Device::CreateRenderTargetView(ID3D11Resource *pResource, const D3D11_RENDER_TARGET_VIEW_DESC *pDesc, ID3D11RenderTargetView **ppRTView) -{ - return _orig->CreateRenderTargetView(pResource, pDesc, ppRTView); -} -HRESULT STDMETHODCALLTYPE D3D11Device::CreateDepthStencilView(ID3D11Resource *pResource, const D3D11_DEPTH_STENCIL_VIEW_DESC *pDesc, ID3D11DepthStencilView **ppDepthStencilView) -{ - return _orig->CreateDepthStencilView(pResource, pDesc, ppDepthStencilView); -} -HRESULT STDMETHODCALLTYPE D3D11Device::CreateInputLayout(const D3D11_INPUT_ELEMENT_DESC *pInputElementDescs, UINT NumElements, const void *pShaderBytecodeWithInputSignature, SIZE_T BytecodeLength, ID3D11InputLayout **ppInputLayout) -{ - return _orig->CreateInputLayout(pInputElementDescs, NumElements, pShaderBytecodeWithInputSignature, BytecodeLength, ppInputLayout); -} -HRESULT STDMETHODCALLTYPE D3D11Device::CreateVertexShader(const void *pShaderBytecode, SIZE_T BytecodeLength, ID3D11ClassLinkage *pClassLinkage, ID3D11VertexShader **ppVertexShader) -{ - return _orig->CreateVertexShader(pShaderBytecode, BytecodeLength, pClassLinkage, ppVertexShader); -} -HRESULT STDMETHODCALLTYPE D3D11Device::CreateGeometryShader(const void *pShaderBytecode, SIZE_T BytecodeLength, ID3D11ClassLinkage *pClassLinkage, ID3D11GeometryShader **ppGeometryShader) -{ - return _orig->CreateGeometryShader(pShaderBytecode, BytecodeLength, pClassLinkage, ppGeometryShader); -} -HRESULT STDMETHODCALLTYPE D3D11Device::CreateGeometryShaderWithStreamOutput(const void *pShaderBytecode, SIZE_T BytecodeLength, const D3D11_SO_DECLARATION_ENTRY *pSODeclaration, UINT NumEntries, const UINT *pBufferStrides, UINT NumStrides, UINT RasterizedStream, ID3D11ClassLinkage *pClassLinkage, ID3D11GeometryShader **ppGeometryShader) -{ - return _orig->CreateGeometryShaderWithStreamOutput(pShaderBytecode, BytecodeLength, pSODeclaration, NumEntries, pBufferStrides, NumStrides, RasterizedStream, pClassLinkage, ppGeometryShader); -} -HRESULT STDMETHODCALLTYPE D3D11Device::CreatePixelShader(const void *pShaderBytecode, SIZE_T BytecodeLength, ID3D11ClassLinkage *pClassLinkage, ID3D11PixelShader **ppPixelShader) -{ - return _orig->CreatePixelShader(pShaderBytecode, BytecodeLength, pClassLinkage, ppPixelShader); -} -HRESULT STDMETHODCALLTYPE D3D11Device::CreateHullShader(const void *pShaderBytecode, SIZE_T BytecodeLength, ID3D11ClassLinkage *pClassLinkage, ID3D11HullShader **ppHullShader) -{ - return _orig->CreateHullShader(pShaderBytecode, BytecodeLength, pClassLinkage, ppHullShader); -} -HRESULT STDMETHODCALLTYPE D3D11Device::CreateDomainShader(const void *pShaderBytecode, SIZE_T BytecodeLength, ID3D11ClassLinkage *pClassLinkage, ID3D11DomainShader **ppDomainShader) -{ - return _orig->CreateDomainShader(pShaderBytecode, BytecodeLength, pClassLinkage, ppDomainShader); -} -HRESULT STDMETHODCALLTYPE D3D11Device::CreateComputeShader(const void *pShaderBytecode, SIZE_T BytecodeLength, ID3D11ClassLinkage *pClassLinkage, ID3D11ComputeShader **ppComputeShader) -{ - return _orig->CreateComputeShader(pShaderBytecode, BytecodeLength, pClassLinkage, ppComputeShader); -} -HRESULT STDMETHODCALLTYPE D3D11Device::CreateClassLinkage(ID3D11ClassLinkage **ppLinkage) -{ - return _orig->CreateClassLinkage(ppLinkage); -} -HRESULT STDMETHODCALLTYPE D3D11Device::CreateBlendState(const D3D11_BLEND_DESC *pBlendStateDesc, ID3D11BlendState **ppBlendState) -{ - return _orig->CreateBlendState(pBlendStateDesc, ppBlendState); -} -HRESULT STDMETHODCALLTYPE D3D11Device::CreateDepthStencilState(const D3D11_DEPTH_STENCIL_DESC *pDepthStencilDesc, ID3D11DepthStencilState **ppDepthStencilState) -{ - return _orig->CreateDepthStencilState(pDepthStencilDesc, ppDepthStencilState); -} -HRESULT STDMETHODCALLTYPE D3D11Device::CreateRasterizerState(const D3D11_RASTERIZER_DESC *pRasterizerDesc, ID3D11RasterizerState **ppRasterizerState) -{ - return _orig->CreateRasterizerState(pRasterizerDesc, ppRasterizerState); -} -HRESULT STDMETHODCALLTYPE D3D11Device::CreateSamplerState(const D3D11_SAMPLER_DESC *pSamplerDesc, ID3D11SamplerState **ppSamplerState) -{ - return _orig->CreateSamplerState(pSamplerDesc, ppSamplerState); -} -HRESULT STDMETHODCALLTYPE D3D11Device::CreateQuery(const D3D11_QUERY_DESC *pQueryDesc, ID3D11Query **ppQuery) -{ - return _orig->CreateQuery(pQueryDesc, ppQuery); -} -HRESULT STDMETHODCALLTYPE D3D11Device::CreatePredicate(const D3D11_QUERY_DESC *pPredicateDesc, ID3D11Predicate **ppPredicate) -{ - return _orig->CreatePredicate(pPredicateDesc, ppPredicate); -} -HRESULT STDMETHODCALLTYPE D3D11Device::CreateCounter(const D3D11_COUNTER_DESC *pCounterDesc, ID3D11Counter **ppCounter) -{ - return _orig->CreateCounter(pCounterDesc, ppCounter); -} -HRESULT STDMETHODCALLTYPE D3D11Device::CreateDeferredContext(UINT ContextFlags, ID3D11DeviceContext **ppDeferredContext) -{ - LOG(INFO) << "Redirecting ID3D11Device::CreateDeferredContext" << '(' << this << ", " << ContextFlags << ", " << ppDeferredContext << ')' << " ..."; - - if (ppDeferredContext == nullptr) - return E_INVALIDARG; - - const HRESULT hr = _orig->CreateDeferredContext(ContextFlags, ppDeferredContext); - - if (FAILED(hr)) - { - LOG(WARN) << "> ID3D11Device::CreateDeferredContext failed with error code " << std::hex << hr << std::dec << '!'; - return hr; - } - - *ppDeferredContext = new D3D11DeviceContext(this, *ppDeferredContext); - -#if RESHADE_VERBOSE_LOG - LOG(DEBUG) << "Returning ID3D11DeviceContext object " << *ppDeferredContext << '.'; -#endif - return hr; -} -HRESULT STDMETHODCALLTYPE D3D11Device::OpenSharedResource(HANDLE hResource, REFIID ReturnedInterface, void **ppResource) -{ - return _orig->OpenSharedResource(hResource, ReturnedInterface, ppResource); -} -HRESULT STDMETHODCALLTYPE D3D11Device::CheckFormatSupport(DXGI_FORMAT Format, UINT *pFormatSupport) -{ - return _orig->CheckFormatSupport(Format, pFormatSupport); -} -HRESULT STDMETHODCALLTYPE D3D11Device::CheckMultisampleQualityLevels(DXGI_FORMAT Format, UINT SampleCount, UINT *pNumQualityLevels) -{ - return _orig->CheckMultisampleQualityLevels(Format, SampleCount, pNumQualityLevels); -} -void STDMETHODCALLTYPE D3D11Device::CheckCounterInfo(D3D11_COUNTER_INFO *pCounterInfo) -{ - _orig->CheckCounterInfo(pCounterInfo); -} -HRESULT STDMETHODCALLTYPE D3D11Device::CheckCounter(const D3D11_COUNTER_DESC *pDesc, D3D11_COUNTER_TYPE *pType, UINT *pActiveCounters, LPSTR szName, UINT *pNameLength, LPSTR szUnits, UINT *pUnitsLength, LPSTR szDescription, UINT *pDescriptionLength) -{ - return _orig->CheckCounter(pDesc, pType, pActiveCounters, szName, pNameLength, szUnits, pUnitsLength, szDescription, pDescriptionLength); -} -HRESULT STDMETHODCALLTYPE D3D11Device::CheckFeatureSupport(D3D11_FEATURE Feature, void *pFeatureSupportData, UINT FeatureSupportDataSize) -{ - return _orig->CheckFeatureSupport(Feature, pFeatureSupportData, FeatureSupportDataSize); -} -HRESULT STDMETHODCALLTYPE D3D11Device::GetPrivateData(REFGUID guid, UINT *pDataSize, void *pData) -{ - return _orig->GetPrivateData(guid, pDataSize, pData); -} -HRESULT STDMETHODCALLTYPE D3D11Device::SetPrivateData(REFGUID guid, UINT DataSize, const void *pData) -{ - return _orig->SetPrivateData(guid, DataSize, pData); -} -HRESULT STDMETHODCALLTYPE D3D11Device::SetPrivateDataInterface(REFGUID guid, const IUnknown *pData) -{ - return _orig->SetPrivateDataInterface(guid, pData); -} -UINT STDMETHODCALLTYPE D3D11Device::GetCreationFlags() -{ - return _orig->GetCreationFlags(); -} -HRESULT STDMETHODCALLTYPE D3D11Device::GetDeviceRemovedReason() -{ - return _orig->GetDeviceRemovedReason(); -} -void STDMETHODCALLTYPE D3D11Device::GetImmediateContext(ID3D11DeviceContext **ppImmediateContext) -{ - if (ppImmediateContext == nullptr) - return; - - _immediate_context->AddRef(); - *ppImmediateContext = _immediate_context; -} -HRESULT STDMETHODCALLTYPE D3D11Device::SetExceptionMode(UINT RaiseFlags) -{ - return _orig->SetExceptionMode(RaiseFlags); -} -UINT STDMETHODCALLTYPE D3D11Device::GetExceptionMode() -{ - return _orig->GetExceptionMode(); -} -D3D_FEATURE_LEVEL STDMETHODCALLTYPE D3D11Device::GetFeatureLevel() -{ - return _orig->GetFeatureLevel(); -} - -void STDMETHODCALLTYPE D3D11Device::GetImmediateContext1(ID3D11DeviceContext1 **ppImmediateContext) -{ - if (ppImmediateContext == nullptr) - return; - - assert(_interface_version >= 1); - assert(_immediate_context->_interface_version >= 1); - - _immediate_context->AddRef(); - *ppImmediateContext = _immediate_context; -} -HRESULT STDMETHODCALLTYPE D3D11Device::CreateDeferredContext1(UINT ContextFlags, ID3D11DeviceContext1 **ppDeferredContext) -{ - LOG(INFO) << "Redirecting ID3D11Device1::CreateDeferredContext1" << '(' << this << ", " << ContextFlags << ", " << ppDeferredContext << ')' << " ..."; - - if (ppDeferredContext == nullptr) - return E_INVALIDARG; - - assert(_interface_version >= 1); - - const HRESULT hr = static_cast(_orig)->CreateDeferredContext1(ContextFlags, ppDeferredContext); - - if (FAILED(hr)) - { - LOG(WARN) << "> ID3D11Device1::CreateDeferredContext1 failed with error code " << std::hex << hr << std::dec << '!'; - return hr; - } - - *ppDeferredContext = new D3D11DeviceContext(this, *ppDeferredContext); - -#if RESHADE_VERBOSE_LOG - LOG(DEBUG) << "Returning ID3D11DeviceContext1 object " << *ppDeferredContext << '.'; -#endif - return hr; -} -HRESULT STDMETHODCALLTYPE D3D11Device::CreateBlendState1(const D3D11_BLEND_DESC1 *pBlendStateDesc, ID3D11BlendState1 **ppBlendState) -{ - assert(_interface_version >= 1); - return static_cast(_orig)->CreateBlendState1(pBlendStateDesc, ppBlendState); -} -HRESULT STDMETHODCALLTYPE D3D11Device::CreateRasterizerState1(const D3D11_RASTERIZER_DESC1 *pRasterizerDesc, ID3D11RasterizerState1 **ppRasterizerState) -{ - assert(_interface_version >= 1); - return static_cast(_orig)->CreateRasterizerState1(pRasterizerDesc, ppRasterizerState); -} -HRESULT STDMETHODCALLTYPE D3D11Device::CreateDeviceContextState(UINT Flags, const D3D_FEATURE_LEVEL *pFeatureLevels, UINT FeatureLevels, UINT SDKVersion, REFIID EmulatedInterface, D3D_FEATURE_LEVEL *pChosenFeatureLevel, ID3DDeviceContextState **ppContextState) -{ - assert(_interface_version >= 1); - return static_cast(_orig)->CreateDeviceContextState(Flags, pFeatureLevels, FeatureLevels, SDKVersion, EmulatedInterface, pChosenFeatureLevel, ppContextState); -} -HRESULT STDMETHODCALLTYPE D3D11Device::OpenSharedResource1(HANDLE hResource, REFIID returnedInterface, void **ppResource) -{ - assert(_interface_version >= 1); - return static_cast(_orig)->OpenSharedResource1(hResource, returnedInterface, ppResource); -} -HRESULT STDMETHODCALLTYPE D3D11Device::OpenSharedResourceByName(LPCWSTR lpName, DWORD dwDesiredAccess, REFIID returnedInterface, void **ppResource) -{ - assert(_interface_version >= 1); - return static_cast(_orig)->OpenSharedResourceByName(lpName, dwDesiredAccess, returnedInterface, ppResource); -} - - void STDMETHODCALLTYPE D3D11Device::GetImmediateContext2(ID3D11DeviceContext2 **ppImmediateContext) -{ - assert(_interface_version >= 2); - static_cast(_orig)->GetImmediateContext2(ppImmediateContext); -} -HRESULT STDMETHODCALLTYPE D3D11Device::CreateDeferredContext2(UINT ContextFlags, ID3D11DeviceContext2 **ppDeferredContext) -{ - assert(_interface_version >= 2); - return static_cast(_orig)->CreateDeferredContext2(ContextFlags, ppDeferredContext); -} -void STDMETHODCALLTYPE D3D11Device::GetResourceTiling(ID3D11Resource *pTiledResource, UINT *pNumTilesForEntireResource, D3D11_PACKED_MIP_DESC *pPackedMipDesc, D3D11_TILE_SHAPE *pStandardTileShapeForNonPackedMips, UINT *pNumSubresourceTilings, UINT FirstSubresourceTilingToGet, D3D11_SUBRESOURCE_TILING *pSubresourceTilingsForNonPackedMips) -{ - assert(_interface_version >= 2); - static_cast(_orig)->GetResourceTiling(pTiledResource, pNumTilesForEntireResource, pPackedMipDesc, pStandardTileShapeForNonPackedMips, pNumSubresourceTilings, FirstSubresourceTilingToGet, pSubresourceTilingsForNonPackedMips); -} -HRESULT STDMETHODCALLTYPE D3D11Device::CheckMultisampleQualityLevels1(DXGI_FORMAT Format, UINT SampleCount, UINT Flags, UINT *pNumQualityLevels) -{ - assert(_interface_version >= 2); - return static_cast(_orig)->CheckMultisampleQualityLevels1(Format, SampleCount, Flags, pNumQualityLevels); -} - -HRESULT STDMETHODCALLTYPE D3D11Device::CreateTexture2D1(const D3D11_TEXTURE2D_DESC1 *pDesc1, const D3D11_SUBRESOURCE_DATA *pInitialData, ID3D11Texture2D1 **ppTexture2D) -{ - assert(_interface_version >= 3); - return static_cast(_orig)->CreateTexture2D1(pDesc1, pInitialData, ppTexture2D); -} -HRESULT STDMETHODCALLTYPE D3D11Device::CreateTexture3D1(const D3D11_TEXTURE3D_DESC1 *pDesc1, const D3D11_SUBRESOURCE_DATA *pInitialData, ID3D11Texture3D1 **ppTexture3D) -{ - assert(_interface_version >= 3); - return static_cast(_orig)->CreateTexture3D1(pDesc1, pInitialData, ppTexture3D); -} -HRESULT STDMETHODCALLTYPE D3D11Device::CreateRasterizerState2(const D3D11_RASTERIZER_DESC2 *pRasterizerDesc, ID3D11RasterizerState2 **ppRasterizerState) -{ - assert(_interface_version >= 3); - return static_cast(_orig)->CreateRasterizerState2(pRasterizerDesc, ppRasterizerState); -} -HRESULT STDMETHODCALLTYPE D3D11Device::CreateShaderResourceView1(ID3D11Resource *pResource, const D3D11_SHADER_RESOURCE_VIEW_DESC1 *pDesc1, ID3D11ShaderResourceView1 **ppSRView1) -{ - assert(_interface_version >= 3); - return static_cast(_orig)->CreateShaderResourceView1(pResource, pDesc1, ppSRView1); -} -HRESULT STDMETHODCALLTYPE D3D11Device::CreateUnorderedAccessView1(ID3D11Resource *pResource, const D3D11_UNORDERED_ACCESS_VIEW_DESC1 *pDesc1, ID3D11UnorderedAccessView1 **ppUAView1) -{ - assert(_interface_version >= 3); - return static_cast(_orig)->CreateUnorderedAccessView1(pResource, pDesc1, ppUAView1); -} -HRESULT STDMETHODCALLTYPE D3D11Device::CreateRenderTargetView1(ID3D11Resource *pResource, const D3D11_RENDER_TARGET_VIEW_DESC1 *pDesc1, ID3D11RenderTargetView1 **ppRTView1) -{ - assert(_interface_version >= 3); - return static_cast(_orig)->CreateRenderTargetView1(pResource, pDesc1, ppRTView1); -} -HRESULT STDMETHODCALLTYPE D3D11Device::CreateQuery1(const D3D11_QUERY_DESC1 *pQueryDesc1, ID3D11Query1 **ppQuery1) -{ - assert(_interface_version >= 3); - return static_cast(_orig)->CreateQuery1(pQueryDesc1, ppQuery1); -} -void STDMETHODCALLTYPE D3D11Device::GetImmediateContext3(ID3D11DeviceContext3 **ppImmediateContext) -{ - assert(_interface_version >= 3); - static_cast(_orig)->GetImmediateContext3(ppImmediateContext); -} -HRESULT STDMETHODCALLTYPE D3D11Device::CreateDeferredContext3(UINT ContextFlags, ID3D11DeviceContext3 **ppDeferredContext) -{ - assert(_interface_version >= 3); - return static_cast(_orig)->CreateDeferredContext3(ContextFlags, ppDeferredContext); -} -void STDMETHODCALLTYPE D3D11Device::WriteToSubresource(ID3D11Resource *pDstResource, UINT DstSubresource, const D3D11_BOX *pDstBox, const void *pSrcData, UINT SrcRowPitch, UINT SrcDepthPitch) -{ - assert(_interface_version >= 3); - static_cast(_orig)->WriteToSubresource(pDstResource, DstSubresource, pDstBox, pSrcData, SrcRowPitch, SrcDepthPitch); -} -void STDMETHODCALLTYPE D3D11Device::ReadFromSubresource(void *pDstData, UINT DstRowPitch, UINT DstDepthPitch, ID3D11Resource *pSrcResource, UINT SrcSubresource, const D3D11_BOX *pSrcBox) -{ - assert(_interface_version >= 3); - - static_cast(_orig)->ReadFromSubresource(pDstData, DstRowPitch, DstDepthPitch, pSrcResource, SrcSubresource, pSrcBox); -} - -HRESULT STDMETHODCALLTYPE D3D11Device::RegisterDeviceRemovedEvent(HANDLE hEvent, DWORD *pdwCookie) -{ - assert(_interface_version >= 4); - return static_cast(_orig)->RegisterDeviceRemovedEvent(hEvent, pdwCookie); -} -void STDMETHODCALLTYPE D3D11Device::UnregisterDeviceRemoved(DWORD dwCookie) -{ - assert(_interface_version >= 4); - static_cast(_orig)->UnregisterDeviceRemoved(dwCookie); -} - -HRESULT STDMETHODCALLTYPE D3D11Device::OpenSharedFence(HANDLE hFence, REFIID ReturnedInterface, void **ppFence) -{ - assert(_interface_version >= 5); - return static_cast(_orig)->OpenSharedFence(hFence, ReturnedInterface, ppFence); -} -HRESULT STDMETHODCALLTYPE D3D11Device::CreateFence(UINT64 InitialValue, D3D11_FENCE_FLAG Flags, REFIID ReturnedInterface, void **ppFence) -{ - assert(_interface_version >= 5); - return static_cast(_orig)->CreateFence(InitialValue, Flags, ReturnedInterface, ppFence); -} diff --git a/msvc/source/d3d11/d3d11_device.hpp b/msvc/source/d3d11/d3d11_device.hpp deleted file mode 100644 index 3285648..0000000 --- a/msvc/source/d3d11/d3d11_device.hpp +++ /dev/null @@ -1,122 +0,0 @@ -/** - * Copyright (C) 2014 Patrick Mours. All rights reserved. - * License: https://github.com/crosire/reshade#license - */ - -#pragma once - -#include -#include "draw_call_tracker.hpp" -#include - -struct DXGIDevice; -struct D3D11DeviceContext; -namespace reshade::d3d11 { class runtime_d3d11; } - -struct __declspec(uuid("72299288-2C68-4AD8-945D-2BFB5AA9C609")) D3D11Device : ID3D11Device5 -{ - D3D11Device(IDXGIDevice1 *dxgi_device, ID3D11Device *original, ID3D11DeviceContext *immediate_context); - - D3D11Device(const D3D11Device &) = delete; - D3D11Device &operator=(const D3D11Device &) = delete; - - HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void **ppvObj) override; - ULONG STDMETHODCALLTYPE AddRef() override; - ULONG STDMETHODCALLTYPE Release() override; - - #pragma region ID3D11Device - HRESULT STDMETHODCALLTYPE CreateBuffer(const D3D11_BUFFER_DESC *pDesc, const D3D11_SUBRESOURCE_DATA *pInitialData, ID3D11Buffer **ppBuffer) override; - HRESULT STDMETHODCALLTYPE CreateTexture1D(const D3D11_TEXTURE1D_DESC *pDesc, const D3D11_SUBRESOURCE_DATA *pInitialData, ID3D11Texture1D **ppTexture1D) override; - HRESULT STDMETHODCALLTYPE CreateTexture2D(const D3D11_TEXTURE2D_DESC *pDesc, const D3D11_SUBRESOURCE_DATA *pInitialData, ID3D11Texture2D **ppTexture2D) override; - HRESULT STDMETHODCALLTYPE CreateTexture3D(const D3D11_TEXTURE3D_DESC *pDesc, const D3D11_SUBRESOURCE_DATA *pInitialData, ID3D11Texture3D **ppTexture3D) override; - HRESULT STDMETHODCALLTYPE CreateShaderResourceView(ID3D11Resource *pResource, const D3D11_SHADER_RESOURCE_VIEW_DESC *pDesc, ID3D11ShaderResourceView **ppSRView) override; - HRESULT STDMETHODCALLTYPE CreateUnorderedAccessView(ID3D11Resource *pResource, const D3D11_UNORDERED_ACCESS_VIEW_DESC *pDesc, ID3D11UnorderedAccessView **ppUAView) override; - HRESULT STDMETHODCALLTYPE CreateRenderTargetView(ID3D11Resource *pResource, const D3D11_RENDER_TARGET_VIEW_DESC *pDesc, ID3D11RenderTargetView **ppRTView) override; - HRESULT STDMETHODCALLTYPE CreateDepthStencilView(ID3D11Resource *pResource, const D3D11_DEPTH_STENCIL_VIEW_DESC *pDesc, ID3D11DepthStencilView **ppDepthStencilView) override; - HRESULT STDMETHODCALLTYPE CreateInputLayout(const D3D11_INPUT_ELEMENT_DESC *pInputElementDescs, UINT NumElements, const void *pShaderBytecodeWithInputSignature, SIZE_T BytecodeLength, ID3D11InputLayout **ppInputLayout) override; - HRESULT STDMETHODCALLTYPE CreateVertexShader(const void *pShaderBytecode, SIZE_T BytecodeLength, ID3D11ClassLinkage *pClassLinkage, ID3D11VertexShader **ppVertexShader) override; - HRESULT STDMETHODCALLTYPE CreateGeometryShader(const void *pShaderBytecode, SIZE_T BytecodeLength, ID3D11ClassLinkage *pClassLinkage, ID3D11GeometryShader **ppGeometryShader) override; - HRESULT STDMETHODCALLTYPE CreateGeometryShaderWithStreamOutput(const void *pShaderBytecode, SIZE_T BytecodeLength, const D3D11_SO_DECLARATION_ENTRY *pSODeclaration, UINT NumEntries, const UINT *pBufferStrides, UINT NumStrides, UINT RasterizedStream, ID3D11ClassLinkage *pClassLinkage, ID3D11GeometryShader **ppGeometryShader) override; - HRESULT STDMETHODCALLTYPE CreatePixelShader(const void *pShaderBytecode, SIZE_T BytecodeLength, ID3D11ClassLinkage *pClassLinkage, ID3D11PixelShader **ppPixelShader) override; - HRESULT STDMETHODCALLTYPE CreateHullShader(const void *pShaderBytecode, SIZE_T BytecodeLength, ID3D11ClassLinkage *pClassLinkage, ID3D11HullShader **ppHullShader) override; - HRESULT STDMETHODCALLTYPE CreateDomainShader(const void *pShaderBytecode, SIZE_T BytecodeLength, ID3D11ClassLinkage *pClassLinkage, ID3D11DomainShader **ppDomainShader) override; - HRESULT STDMETHODCALLTYPE CreateComputeShader(const void *pShaderBytecode, SIZE_T BytecodeLength, ID3D11ClassLinkage *pClassLinkage, ID3D11ComputeShader **ppComputeShader) override; - HRESULT STDMETHODCALLTYPE CreateClassLinkage(ID3D11ClassLinkage **ppLinkage) override; - HRESULT STDMETHODCALLTYPE CreateBlendState(const D3D11_BLEND_DESC *pBlendStateDesc, ID3D11BlendState **ppBlendState) override; - HRESULT STDMETHODCALLTYPE CreateDepthStencilState(const D3D11_DEPTH_STENCIL_DESC *pDepthStencilDesc, ID3D11DepthStencilState **ppDepthStencilState) override; - HRESULT STDMETHODCALLTYPE CreateRasterizerState(const D3D11_RASTERIZER_DESC *pRasterizerDesc, ID3D11RasterizerState **ppRasterizerState) override; - HRESULT STDMETHODCALLTYPE CreateSamplerState(const D3D11_SAMPLER_DESC *pSamplerDesc, ID3D11SamplerState **ppSamplerState) override; - HRESULT STDMETHODCALLTYPE CreateQuery(const D3D11_QUERY_DESC *pQueryDesc, ID3D11Query **ppQuery) override; - HRESULT STDMETHODCALLTYPE CreatePredicate(const D3D11_QUERY_DESC *pPredicateDesc, ID3D11Predicate **ppPredicate) override; - HRESULT STDMETHODCALLTYPE CreateCounter(const D3D11_COUNTER_DESC *pCounterDesc, ID3D11Counter **ppCounter) override; - HRESULT STDMETHODCALLTYPE CreateDeferredContext(UINT ContextFlags, ID3D11DeviceContext **ppDeferredContext) override; - HRESULT STDMETHODCALLTYPE OpenSharedResource(HANDLE hResource, REFIID ReturnedInterface, void **ppResource) override; - HRESULT STDMETHODCALLTYPE CheckFormatSupport(DXGI_FORMAT Format, UINT *pFormatSupport) override; - HRESULT STDMETHODCALLTYPE CheckMultisampleQualityLevels(DXGI_FORMAT Format, UINT SampleCount, UINT *pNumQualityLevels) override; - void STDMETHODCALLTYPE CheckCounterInfo(D3D11_COUNTER_INFO *pCounterInfo) override; - HRESULT STDMETHODCALLTYPE CheckCounter(const D3D11_COUNTER_DESC *pDesc, D3D11_COUNTER_TYPE *pType, UINT *pActiveCounters, LPSTR szName, UINT *pNameLength, LPSTR szUnits, UINT *pUnitsLength, LPSTR szDescription, UINT *pDescriptionLength) override; - HRESULT STDMETHODCALLTYPE CheckFeatureSupport(D3D11_FEATURE Feature, void *pFeatureSupportData, UINT FeatureSupportDataSize) override; - HRESULT STDMETHODCALLTYPE GetPrivateData(REFGUID guid, UINT *pDataSize, void *pData) override; - HRESULT STDMETHODCALLTYPE SetPrivateData(REFGUID guid, UINT DataSize, const void *pData) override; - HRESULT STDMETHODCALLTYPE SetPrivateDataInterface(REFGUID guid, const IUnknown *pData) override; - UINT STDMETHODCALLTYPE GetCreationFlags() override; - HRESULT STDMETHODCALLTYPE GetDeviceRemovedReason() override; - void STDMETHODCALLTYPE GetImmediateContext(ID3D11DeviceContext **ppImmediateContext) override; - HRESULT STDMETHODCALLTYPE SetExceptionMode(UINT RaiseFlags) override; - UINT STDMETHODCALLTYPE GetExceptionMode() override; - D3D_FEATURE_LEVEL STDMETHODCALLTYPE GetFeatureLevel() override; - #pragma endregion - #pragma region ID3D11Device1 - void STDMETHODCALLTYPE GetImmediateContext1(ID3D11DeviceContext1 **ppImmediateContext) override; - HRESULT STDMETHODCALLTYPE CreateDeferredContext1(UINT ContextFlags, ID3D11DeviceContext1 **ppDeferredContext) override; - HRESULT STDMETHODCALLTYPE CreateBlendState1(const D3D11_BLEND_DESC1 *pBlendStateDesc, ID3D11BlendState1 **ppBlendState); - HRESULT STDMETHODCALLTYPE CreateRasterizerState1(const D3D11_RASTERIZER_DESC1 *pRasterizerDesc, ID3D11RasterizerState1 **ppRasterizerState) override; - HRESULT STDMETHODCALLTYPE CreateDeviceContextState(UINT Flags, const D3D_FEATURE_LEVEL *pFeatureLevels, UINT FeatureLevels, UINT SDKVersion, REFIID EmulatedInterface, D3D_FEATURE_LEVEL *pChosenFeatureLevel, ID3DDeviceContextState **ppContextState) override; - HRESULT STDMETHODCALLTYPE OpenSharedResource1(HANDLE hResource, REFIID returnedInterface, void **ppResource) override; - HRESULT STDMETHODCALLTYPE OpenSharedResourceByName(LPCWSTR lpName, DWORD dwDesiredAccess, REFIID returnedInterface, void **ppResource) override; - #pragma endregion - #pragma region ID3D11Device2 - void STDMETHODCALLTYPE GetImmediateContext2(ID3D11DeviceContext2 **ppImmediateContext) override; - HRESULT STDMETHODCALLTYPE CreateDeferredContext2(UINT ContextFlags, ID3D11DeviceContext2 **ppDeferredContext) override; - void STDMETHODCALLTYPE GetResourceTiling(ID3D11Resource *pTiledResource, UINT *pNumTilesForEntireResource, D3D11_PACKED_MIP_DESC *pPackedMipDesc, D3D11_TILE_SHAPE *pStandardTileShapeForNonPackedMips, UINT *pNumSubresourceTilings, UINT FirstSubresourceTilingToGet, D3D11_SUBRESOURCE_TILING *pSubresourceTilingsForNonPackedMips) override; - HRESULT STDMETHODCALLTYPE CheckMultisampleQualityLevels1(DXGI_FORMAT Format, UINT SampleCount, UINT Flags, UINT *pNumQualityLevels) override; - #pragma endregion - #pragma region ID3D11Device3 - HRESULT STDMETHODCALLTYPE CreateTexture2D1(const D3D11_TEXTURE2D_DESC1 *pDesc1, const D3D11_SUBRESOURCE_DATA *pInitialData, ID3D11Texture2D1 **ppTexture2D) override; - HRESULT STDMETHODCALLTYPE CreateTexture3D1(const D3D11_TEXTURE3D_DESC1 *pDesc1, const D3D11_SUBRESOURCE_DATA *pInitialData, ID3D11Texture3D1 **ppTexture3D) override; - HRESULT STDMETHODCALLTYPE CreateRasterizerState2(const D3D11_RASTERIZER_DESC2 *pRasterizerDesc, ID3D11RasterizerState2 **ppRasterizerState) override; - HRESULT STDMETHODCALLTYPE CreateShaderResourceView1(ID3D11Resource *pResource, const D3D11_SHADER_RESOURCE_VIEW_DESC1 *pDesc1, ID3D11ShaderResourceView1 **ppSRView1) override; - HRESULT STDMETHODCALLTYPE CreateUnorderedAccessView1(ID3D11Resource *pResource, const D3D11_UNORDERED_ACCESS_VIEW_DESC1 *pDesc1, ID3D11UnorderedAccessView1 **ppUAView1) override; - HRESULT STDMETHODCALLTYPE CreateRenderTargetView1(ID3D11Resource *pResource, const D3D11_RENDER_TARGET_VIEW_DESC1 *pDesc1, ID3D11RenderTargetView1 **ppRTView1) override; - HRESULT STDMETHODCALLTYPE CreateQuery1(const D3D11_QUERY_DESC1 *pQueryDesc1, ID3D11Query1 **ppQuery1) override; - void STDMETHODCALLTYPE GetImmediateContext3(ID3D11DeviceContext3 **ppImmediateContext) override; - HRESULT STDMETHODCALLTYPE CreateDeferredContext3(UINT ContextFlags, ID3D11DeviceContext3 **ppDeferredContext) override; - void STDMETHODCALLTYPE WriteToSubresource(ID3D11Resource *pDstResource, UINT DstSubresource, const D3D11_BOX *pDstBox, const void *pSrcData, UINT SrcRowPitch, UINT SrcDepthPitch) override; - void STDMETHODCALLTYPE ReadFromSubresource(void *pDstData, UINT DstRowPitch, UINT DstDepthPitch, ID3D11Resource *pSrcResource, UINT SrcSubresource, const D3D11_BOX *pSrcBox) override; - #pragma endregion - #pragma region ID3D11Device4 - HRESULT STDMETHODCALLTYPE RegisterDeviceRemovedEvent( HANDLE hEvent, DWORD *pdwCookie) override; - void STDMETHODCALLTYPE UnregisterDeviceRemoved(DWORD dwCookie) override; - #pragma endregion - #pragma region ID3D11Device5 - HRESULT STDMETHODCALLTYPE OpenSharedFence(HANDLE hFence, REFIID ReturnedInterface, void **ppFence) override; - HRESULT STDMETHODCALLTYPE CreateFence(UINT64 InitialValue, D3D11_FENCE_FLAG Flags, REFIID ReturnedInterface, void **ppFence) override; - #pragma endregion - - void add_commandlist_trackers(ID3D11CommandList* command_list, const reshade::d3d11::draw_call_tracker &tracker_source); - void merge_commandlist_trackers(ID3D11CommandList* command_list, reshade::d3d11::draw_call_tracker &tracker_destination); - - void clear_drawcall_stats(); - - bool check_and_upgrade_interface(REFIID riid); - - LONG _ref = 1; - ID3D11Device *_orig; - unsigned int _interface_version; - DXGIDevice *const _dxgi_device; - D3D11DeviceContext *const _immediate_context; - std::vector> _runtimes; - std::unordered_map _trackers_per_commandlist; - std::mutex _trackers_per_commandlist_mutex; - unsigned int _clear_DSV_iter = 1; -}; diff --git a/msvc/source/d3d11/d3d11_device_context.cpp b/msvc/source/d3d11/d3d11_device_context.cpp deleted file mode 100644 index a02b341..0000000 --- a/msvc/source/d3d11/d3d11_device_context.cpp +++ /dev/null @@ -1,857 +0,0 @@ -/** - * Copyright (C) 2014 Patrick Mours. All rights reserved. - * License: https://github.com/crosire/reshade#license - */ - -#include "log.hpp" -#include "d3d11_device.hpp" -#include "d3d11_device_context.hpp" -#include "runtime_d3d11.hpp" - -D3D11DeviceContext::D3D11DeviceContext(D3D11Device *device, ID3D11DeviceContext *original) : - _orig(original), - _interface_version(0), - _device(device) { - assert(original != nullptr); -} -D3D11DeviceContext::D3D11DeviceContext(D3D11Device *device, ID3D11DeviceContext1 *original) : - _orig(original), - _interface_version(1), - _device(device) { - assert(original != nullptr); -} -D3D11DeviceContext::D3D11DeviceContext(D3D11Device *device, ID3D11DeviceContext2 *original) : - _orig(original), - _interface_version(2), - _device(device) { - assert(original != nullptr); -} -D3D11DeviceContext::D3D11DeviceContext(D3D11Device *device, ID3D11DeviceContext3 *original) : - _orig(original), - _interface_version(3), - _device(device) { - assert(original != nullptr); -} - -void D3D11DeviceContext::clear_drawcall_stats() -{ - _draw_call_tracker.reset(); -} - -#if RESHADE_DX11_CAPTURE_DEPTH_BUFFERS -bool D3D11DeviceContext::save_depth_texture(ID3D11DepthStencilView *pDepthStencilView, bool cleared) -{ - if (_device->_runtimes.empty()) - return false; - - const auto runtime = _device->_runtimes.front(); - - if (!runtime->depth_buffer_before_clear) - return false; - if (!cleared && !runtime->extended_depth_buffer_detection) - return false; - - assert(pDepthStencilView != nullptr); - - // Retrieve texture from depth stencil - com_ptr resource; - pDepthStencilView->GetResource(&resource); - - com_ptr texture; - if (FAILED(resource->QueryInterface(&texture))) - return false; - - D3D11_TEXTURE2D_DESC desc; - texture->GetDesc(&desc); - - // Check if aspect ratio is similar to the back buffer one - const float screen_aspect_ratio = float(runtime->frame_width()) / float(runtime->frame_height()); - const float texture_aspect_ratio = float(desc.Width) / float(desc.Height); - - if (fabs(texture_aspect_ratio - screen_aspect_ratio) > 0.1f || desc.Width > runtime->frame_width()) - return false; - - // In case the depth texture is retrieved, we make a copy of it and store it in an ordered map to use it later in the final rendering stage. - if ((runtime->cleared_depth_buffer_index == 0 && cleared) || (_device->_clear_DSV_iter <= runtime->cleared_depth_buffer_index)) - { - // Select an appropriate destination texture - com_ptr depth_texture_save = runtime->select_depth_texture_save(desc); - if (depth_texture_save == nullptr) - return false; - - // Copy the depth texture. This is necessary because the content of the depth texture is cleared. - // This way, we can retrieve this content in the final rendering stage - this->CopyResource(depth_texture_save.get(), texture.get()); - - // Store the saved texture in the ordered map. - _draw_call_tracker.track_depth_texture(runtime->depth_buffer_texture_format, _device->_clear_DSV_iter, texture.get(), pDepthStencilView, depth_texture_save, cleared); - } - else - { - // Store a null depth texture in the ordered map in order to display it even if the user chose a previous cleared texture. - // This way the texture will still be visible in the depth buffer selection window and the user can choose it. - _draw_call_tracker.track_depth_texture(runtime->depth_buffer_texture_format, _device->_clear_DSV_iter, texture.get(), pDepthStencilView, nullptr, cleared); - } - - // TODO: This is unsafe if multiple device contexts are used on multiple threads - _device->_clear_DSV_iter++; - - return true; -} - -void D3D11DeviceContext::track_active_rendertargets(UINT NumViews, ID3D11RenderTargetView *const *ppRenderTargetViews, ID3D11DepthStencilView *pDepthStencilView) -{ - if (pDepthStencilView == nullptr || _device->_runtimes.empty()) - return; - - const auto runtime = _device->_runtimes.front(); - - _draw_call_tracker.track_rendertargets(runtime->depth_buffer_texture_format, pDepthStencilView, NumViews, ppRenderTargetViews); - - save_depth_texture(pDepthStencilView, false); -} -void D3D11DeviceContext::track_cleared_depthstencil(ID3D11DepthStencilView *pDepthStencilView) -{ - if (pDepthStencilView == nullptr) - return; - - save_depth_texture(pDepthStencilView, true); -} -#endif - -bool D3D11DeviceContext::check_and_upgrade_interface(REFIID riid) -{ - static const IID iid_lookup[] = { - __uuidof(ID3D11DeviceContext), - __uuidof(ID3D11DeviceContext1), - __uuidof(ID3D11DeviceContext2), - __uuidof(ID3D11DeviceContext3), - __uuidof(ID3D11DeviceContext4), - }; - - for (unsigned int new_version = _interface_version + 1; new_version < ARRAYSIZE(iid_lookup); ++new_version) - { - if (riid == iid_lookup[new_version]) - { - IUnknown *new_interface = nullptr; - if (FAILED(_orig->QueryInterface(riid, reinterpret_cast(&new_interface)))) - return false; -#if RESHADE_VERBOSE_LOG - LOG(DEBUG) << "Upgraded ID3D11DeviceContext" << _interface_version << " object " << this << " to ID3D11DeviceContext" << new_version << '.'; -#endif - _orig->Release(); - _orig = static_cast(new_interface); - _interface_version = new_version; - break; - } - } - - return true; -} - -HRESULT STDMETHODCALLTYPE D3D11DeviceContext::QueryInterface(REFIID riid, void **ppvObj) -{ - if (ppvObj == nullptr) - return E_POINTER; - - if (riid == __uuidof(this) || - riid == __uuidof(IUnknown) || - riid == __uuidof(ID3D11DeviceChild) || - riid == __uuidof(ID3D11DeviceContext) || - riid == __uuidof(ID3D11DeviceContext1) || - riid == __uuidof(ID3D11DeviceContext2) || - riid == __uuidof(ID3D11DeviceContext3) || - riid == __uuidof(ID3D11DeviceContext4)) - { - if (!check_and_upgrade_interface(riid)) - return E_NOINTERFACE; - - AddRef(); - *ppvObj = this; - return S_OK; - } - - return _orig->QueryInterface(riid, ppvObj); -} -ULONG STDMETHODCALLTYPE D3D11DeviceContext::AddRef() -{ - ++_ref; - - return _orig->AddRef(); -} -ULONG STDMETHODCALLTYPE D3D11DeviceContext::Release() -{ - --_ref; - - const ULONG ref = _orig->Release(); - - if (ref != 0 && _ref != 0) - return ref; - else if (ref != 0) - LOG(WARN) << "Reference count for ID3D11DeviceContext" << _interface_version << " object " << this << " is inconsistent: " << ref << ", but expected 0."; - - assert(_ref <= 0); -#if RESHADE_VERBOSE_LOG - LOG(DEBUG) << "Destroyed ID3D11DeviceContext" << _interface_version << " object " << this << '.'; -#endif - delete this; - return 0; -} - -void STDMETHODCALLTYPE D3D11DeviceContext::GetDevice(ID3D11Device **ppDevice) -{ - if (ppDevice == nullptr) - return; - - _device->AddRef(); - *ppDevice = _device; -} -HRESULT STDMETHODCALLTYPE D3D11DeviceContext::GetPrivateData(REFGUID guid, UINT *pDataSize, void *pData) -{ - return _orig->GetPrivateData(guid, pDataSize, pData); -} -HRESULT STDMETHODCALLTYPE D3D11DeviceContext::SetPrivateData(REFGUID guid, UINT DataSize, const void *pData) -{ - return _orig->SetPrivateData(guid, DataSize, pData); -} -HRESULT STDMETHODCALLTYPE D3D11DeviceContext::SetPrivateDataInterface(REFGUID guid, const IUnknown *pData) -{ - return _orig->SetPrivateDataInterface(guid, pData); -} - -void STDMETHODCALLTYPE D3D11DeviceContext::VSSetConstantBuffers(UINT StartSlot, UINT NumBuffers, ID3D11Buffer *const *ppConstantBuffers) -{ - _orig->VSSetConstantBuffers(StartSlot, NumBuffers, ppConstantBuffers); -} -void STDMETHODCALLTYPE D3D11DeviceContext::PSSetShaderResources(UINT StartSlot, UINT NumViews, ID3D11ShaderResourceView *const *ppShaderResourceViews) -{ - _orig->PSSetShaderResources(StartSlot, NumViews, ppShaderResourceViews); -} -void STDMETHODCALLTYPE D3D11DeviceContext::PSSetShader(ID3D11PixelShader *pPixelShader, ID3D11ClassInstance *const *ppClassInstances, UINT NumClassInstances) -{ - _orig->PSSetShader(pPixelShader, ppClassInstances, NumClassInstances); -} -void STDMETHODCALLTYPE D3D11DeviceContext::PSSetSamplers(UINT StartSlot, UINT NumSamplers, ID3D11SamplerState *const *ppSamplers) -{ - _orig->PSSetSamplers(StartSlot, NumSamplers, ppSamplers); -} -void STDMETHODCALLTYPE D3D11DeviceContext::VSSetShader(ID3D11VertexShader *pVertexShader, ID3D11ClassInstance *const *ppClassInstances, UINT NumClassInstances) -{ - _orig->VSSetShader(pVertexShader, ppClassInstances, NumClassInstances); -} -void STDMETHODCALLTYPE D3D11DeviceContext::DrawIndexed(UINT IndexCount, UINT StartIndexLocation, INT BaseVertexLocation) -{ - _orig->DrawIndexed(IndexCount, StartIndexLocation, BaseVertexLocation); - _draw_call_tracker.on_draw(this, IndexCount); -} -void STDMETHODCALLTYPE D3D11DeviceContext::Draw(UINT VertexCount, UINT StartVertexLocation) -{ - _orig->Draw(VertexCount, StartVertexLocation); - _draw_call_tracker.on_draw(this, VertexCount); -} -HRESULT STDMETHODCALLTYPE D3D11DeviceContext::Map(ID3D11Resource *pResource, UINT Subresource, D3D11_MAP MapType, UINT MapFlags, D3D11_MAPPED_SUBRESOURCE *pMappedResource) -{ -#if RESHADE_DX11_CAPTURE_CONSTANT_BUFFERS - _draw_call_tracker.on_map(pResource); -#endif - return _orig->Map(pResource, Subresource, MapType, MapFlags, pMappedResource); -} -void STDMETHODCALLTYPE D3D11DeviceContext::Unmap(ID3D11Resource *pResource, UINT Subresource) -{ - _orig->Unmap(pResource, Subresource); -} -void STDMETHODCALLTYPE D3D11DeviceContext::PSSetConstantBuffers(UINT StartSlot, UINT NumBuffers, ID3D11Buffer *const *ppConstantBuffers) -{ - _orig->PSSetConstantBuffers(StartSlot, NumBuffers, ppConstantBuffers); -} -void STDMETHODCALLTYPE D3D11DeviceContext::IASetInputLayout(ID3D11InputLayout *pInputLayout) -{ - _orig->IASetInputLayout(pInputLayout); -} -void STDMETHODCALLTYPE D3D11DeviceContext::IASetVertexBuffers(UINT StartSlot, UINT NumBuffers, ID3D11Buffer *const *ppVertexBuffers, const UINT *pStrides, const UINT *pOffsets) -{ - _orig->IASetVertexBuffers(StartSlot, NumBuffers, ppVertexBuffers, pStrides, pOffsets); -} -void STDMETHODCALLTYPE D3D11DeviceContext::IASetIndexBuffer(ID3D11Buffer *pIndexBuffer, DXGI_FORMAT Format, UINT Offset) -{ - _orig->IASetIndexBuffer(pIndexBuffer, Format, Offset); -} -void STDMETHODCALLTYPE D3D11DeviceContext::DrawIndexedInstanced(UINT IndexCountPerInstance, UINT InstanceCount, UINT StartIndexLocation, INT BaseVertexLocation, UINT StartInstanceLocation) -{ - _orig->DrawIndexedInstanced(IndexCountPerInstance, InstanceCount, StartIndexLocation, BaseVertexLocation, StartInstanceLocation); - _draw_call_tracker.on_draw(this, IndexCountPerInstance * InstanceCount); -} -void STDMETHODCALLTYPE D3D11DeviceContext::DrawInstanced(UINT VertexCountPerInstance, UINT InstanceCount, UINT StartVertexLocation, UINT StartInstanceLocation) -{ - _orig->DrawInstanced(VertexCountPerInstance, InstanceCount, StartVertexLocation, StartInstanceLocation); - _draw_call_tracker.on_draw(this, VertexCountPerInstance * InstanceCount); -} -void STDMETHODCALLTYPE D3D11DeviceContext::GSSetConstantBuffers(UINT StartSlot, UINT NumBuffers, ID3D11Buffer *const *ppConstantBuffers) -{ - _orig->GSSetConstantBuffers(StartSlot, NumBuffers, ppConstantBuffers); -} -void STDMETHODCALLTYPE D3D11DeviceContext::GSSetShader(ID3D11GeometryShader *pShader, ID3D11ClassInstance *const *ppClassInstances, UINT NumClassInstances) -{ - _orig->GSSetShader(pShader, ppClassInstances, NumClassInstances); -} -void STDMETHODCALLTYPE D3D11DeviceContext::IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY Topology) -{ - _orig->IASetPrimitiveTopology(Topology); -} -void STDMETHODCALLTYPE D3D11DeviceContext::VSSetShaderResources(UINT StartSlot, UINT NumViews, ID3D11ShaderResourceView *const *ppShaderResourceViews) -{ - _orig->VSSetShaderResources(StartSlot, NumViews, ppShaderResourceViews); -} -void STDMETHODCALLTYPE D3D11DeviceContext::VSSetSamplers(UINT StartSlot, UINT NumSamplers, ID3D11SamplerState *const *ppSamplers) -{ - _orig->VSSetSamplers(StartSlot, NumSamplers, ppSamplers); -} -void STDMETHODCALLTYPE D3D11DeviceContext::Begin(ID3D11Asynchronous *pAsync) -{ - _orig->Begin(pAsync); -} -void STDMETHODCALLTYPE D3D11DeviceContext::End(ID3D11Asynchronous *pAsync) -{ - _orig->End(pAsync); -} -HRESULT STDMETHODCALLTYPE D3D11DeviceContext::GetData(ID3D11Asynchronous *pAsync, void *pData, UINT DataSize, UINT GetDataFlags) -{ - return _orig->GetData(pAsync, pData, DataSize, GetDataFlags); -} -void STDMETHODCALLTYPE D3D11DeviceContext::SetPredication(ID3D11Predicate *pPredicate, BOOL PredicateValue) -{ - _orig->SetPredication(pPredicate, PredicateValue); -} -void STDMETHODCALLTYPE D3D11DeviceContext::GSSetShaderResources(UINT StartSlot, UINT NumViews, ID3D11ShaderResourceView *const *ppShaderResourceViews) -{ - _orig->GSSetShaderResources(StartSlot, NumViews, ppShaderResourceViews); -} -void STDMETHODCALLTYPE D3D11DeviceContext::GSSetSamplers(UINT StartSlot, UINT NumSamplers, ID3D11SamplerState *const *ppSamplers) -{ - _orig->GSSetSamplers(StartSlot, NumSamplers, ppSamplers); -} -void STDMETHODCALLTYPE D3D11DeviceContext::OMSetRenderTargets(UINT NumViews, ID3D11RenderTargetView *const *ppRenderTargetViews, ID3D11DepthStencilView *pDepthStencilView) -{ -#if RESHADE_DX11_CAPTURE_DEPTH_BUFFERS - track_active_rendertargets(NumViews, ppRenderTargetViews, pDepthStencilView); -#endif - _orig->OMSetRenderTargets(NumViews, ppRenderTargetViews, pDepthStencilView); -} -void STDMETHODCALLTYPE D3D11DeviceContext::OMSetRenderTargetsAndUnorderedAccessViews(UINT NumRTVs, ID3D11RenderTargetView *const *ppRenderTargetViews, ID3D11DepthStencilView *pDepthStencilView, UINT UAVStartSlot, UINT NumUAVs, ID3D11UnorderedAccessView *const *ppUnorderedAccessViews, const UINT *pUAVInitialCounts) -{ -#if RESHADE_DX11_CAPTURE_DEPTH_BUFFERS - track_active_rendertargets(NumRTVs, ppRenderTargetViews, pDepthStencilView); -#endif - _orig->OMSetRenderTargetsAndUnorderedAccessViews(NumRTVs, ppRenderTargetViews, pDepthStencilView, UAVStartSlot, NumUAVs, ppUnorderedAccessViews, pUAVInitialCounts); -} -void STDMETHODCALLTYPE D3D11DeviceContext::OMSetBlendState(ID3D11BlendState *pBlendState, const FLOAT BlendFactor[4], UINT SampleMask) -{ - _orig->OMSetBlendState(pBlendState, BlendFactor, SampleMask); -} -void STDMETHODCALLTYPE D3D11DeviceContext::OMSetDepthStencilState(ID3D11DepthStencilState *pDepthStencilState, UINT StencilRef) -{ - _orig->OMSetDepthStencilState(pDepthStencilState, StencilRef); -} -void STDMETHODCALLTYPE D3D11DeviceContext::SOSetTargets(UINT NumBuffers, ID3D11Buffer *const *ppSOTargets, const UINT *pOffsets) -{ - _orig->SOSetTargets(NumBuffers, ppSOTargets, pOffsets); -} -void STDMETHODCALLTYPE D3D11DeviceContext::DrawAuto() -{ - _orig->DrawAuto(); - _draw_call_tracker.on_draw(this, 0); -} -void STDMETHODCALLTYPE D3D11DeviceContext::DrawIndexedInstancedIndirect(ID3D11Buffer *pBufferForArgs, UINT AlignedByteOffsetForArgs) -{ - _orig->DrawIndexedInstancedIndirect(pBufferForArgs, AlignedByteOffsetForArgs); - _draw_call_tracker.on_draw(this, 0); -} -void STDMETHODCALLTYPE D3D11DeviceContext::DrawInstancedIndirect(ID3D11Buffer *pBufferForArgs, UINT AlignedByteOffsetForArgs) -{ - _orig->DrawInstancedIndirect(pBufferForArgs, AlignedByteOffsetForArgs); - _draw_call_tracker.on_draw(this, 0); -} -void STDMETHODCALLTYPE D3D11DeviceContext::Dispatch(UINT ThreadGroupCountX, UINT ThreadGroupCountY, UINT ThreadGroupCountZ) -{ - _orig->Dispatch(ThreadGroupCountX, ThreadGroupCountY, ThreadGroupCountZ); -} -void STDMETHODCALLTYPE D3D11DeviceContext::DispatchIndirect(ID3D11Buffer *pBufferForArgs, UINT AlignedByteOffsetForArgs) -{ - _orig->DispatchIndirect(pBufferForArgs, AlignedByteOffsetForArgs); -} -void STDMETHODCALLTYPE D3D11DeviceContext::RSSetState(ID3D11RasterizerState *pRasterizerState) -{ - _orig->RSSetState(pRasterizerState); -} -void STDMETHODCALLTYPE D3D11DeviceContext::RSSetViewports(UINT NumViewports, const D3D11_VIEWPORT *pViewports) -{ - _orig->RSSetViewports(NumViewports, pViewports); -} -void STDMETHODCALLTYPE D3D11DeviceContext::RSSetScissorRects(UINT NumRects, const D3D11_RECT *pRects) -{ - _orig->RSSetScissorRects(NumRects, pRects); -} -void STDMETHODCALLTYPE D3D11DeviceContext::CopySubresourceRegion(ID3D11Resource *pDstResource, UINT DstSubresource, UINT DstX, UINT DstY, UINT DstZ, ID3D11Resource *pSrcResource, UINT SrcSubresource, const D3D11_BOX *pSrcBox) -{ - _orig->CopySubresourceRegion(pDstResource, DstSubresource, DstX, DstY, DstZ, pSrcResource, SrcSubresource, pSrcBox); -} -void STDMETHODCALLTYPE D3D11DeviceContext::CopyResource(ID3D11Resource *pDstResource, ID3D11Resource *pSrcResource) -{ - _orig->CopyResource(pDstResource, pSrcResource); -} -void STDMETHODCALLTYPE D3D11DeviceContext::UpdateSubresource(ID3D11Resource *pDstResource, UINT DstSubresource, const D3D11_BOX *pDstBox, const void *pSrcData, UINT SrcRowPitch, UINT SrcDepthPitch) -{ - _orig->UpdateSubresource(pDstResource, DstSubresource, pDstBox, pSrcData, SrcRowPitch, SrcDepthPitch); -} -void STDMETHODCALLTYPE D3D11DeviceContext::CopyStructureCount(ID3D11Buffer *pDstBuffer, UINT DstAlignedByteOffset, ID3D11UnorderedAccessView *pSrcView) -{ - _orig->CopyStructureCount(pDstBuffer, DstAlignedByteOffset, pSrcView); -} -void STDMETHODCALLTYPE D3D11DeviceContext::ClearRenderTargetView(ID3D11RenderTargetView *pRenderTargetView, const FLOAT ColorRGBA[4]) -{ - _orig->ClearRenderTargetView(pRenderTargetView, ColorRGBA); -} -void STDMETHODCALLTYPE D3D11DeviceContext::ClearUnorderedAccessViewUint(ID3D11UnorderedAccessView *pUnorderedAccessView, const UINT Values[4]) -{ - _orig->ClearUnorderedAccessViewUint(pUnorderedAccessView, Values); -} -void STDMETHODCALLTYPE D3D11DeviceContext::ClearUnorderedAccessViewFloat(ID3D11UnorderedAccessView *pUnorderedAccessView, const FLOAT Values[4]) -{ - _orig->ClearUnorderedAccessViewFloat(pUnorderedAccessView, Values); -} -void STDMETHODCALLTYPE D3D11DeviceContext::ClearDepthStencilView(ID3D11DepthStencilView *pDepthStencilView, UINT ClearFlags, FLOAT Depth, UINT8 Stencil) -{ -#if RESHADE_DX11_CAPTURE_DEPTH_BUFFERS - if (ClearFlags & D3D11_CLEAR_DEPTH) - track_cleared_depthstencil(pDepthStencilView); -#endif - _orig->ClearDepthStencilView(pDepthStencilView, ClearFlags, Depth, Stencil); -} -void STDMETHODCALLTYPE D3D11DeviceContext::GenerateMips(ID3D11ShaderResourceView *pShaderResourceView) -{ - _orig->GenerateMips(pShaderResourceView); -} -void STDMETHODCALLTYPE D3D11DeviceContext::SetResourceMinLOD(ID3D11Resource *pResource, FLOAT MinLOD) -{ - _orig->SetResourceMinLOD(pResource, MinLOD); -} -FLOAT STDMETHODCALLTYPE D3D11DeviceContext::GetResourceMinLOD(ID3D11Resource *pResource) -{ - return _orig->GetResourceMinLOD(pResource); -} -void STDMETHODCALLTYPE D3D11DeviceContext::ResolveSubresource(ID3D11Resource *pDstResource, UINT DstSubresource, ID3D11Resource *pSrcResource, UINT SrcSubresource, DXGI_FORMAT Format) -{ - _orig->ResolveSubresource(pDstResource, DstSubresource, pSrcResource, SrcSubresource, Format); -} -void STDMETHODCALLTYPE D3D11DeviceContext::ExecuteCommandList(ID3D11CommandList *pCommandList, BOOL RestoreContextState) -{ - if (pCommandList != nullptr) - _device->merge_commandlist_trackers(pCommandList, _draw_call_tracker); - - _orig->ExecuteCommandList(pCommandList, RestoreContextState); -} -void STDMETHODCALLTYPE D3D11DeviceContext::HSSetShaderResources(UINT StartSlot, UINT NumViews, ID3D11ShaderResourceView *const *ppShaderResourceViews) -{ - _orig->HSSetShaderResources(StartSlot, NumViews, ppShaderResourceViews); -} -void STDMETHODCALLTYPE D3D11DeviceContext::HSSetShader(ID3D11HullShader *pHullShader, ID3D11ClassInstance *const *ppClassInstances, UINT NumClassInstances) -{ - _orig->HSSetShader(pHullShader, ppClassInstances, NumClassInstances); -} -void STDMETHODCALLTYPE D3D11DeviceContext::HSSetSamplers(UINT StartSlot, UINT NumSamplers, ID3D11SamplerState *const *ppSamplers) -{ - _orig->HSSetSamplers(StartSlot, NumSamplers, ppSamplers); -} -void STDMETHODCALLTYPE D3D11DeviceContext::HSSetConstantBuffers(UINT StartSlot, UINT NumBuffers, ID3D11Buffer *const *ppConstantBuffers) -{ - _orig->HSSetConstantBuffers(StartSlot, NumBuffers, ppConstantBuffers); -} -void STDMETHODCALLTYPE D3D11DeviceContext::DSSetShaderResources(UINT StartSlot, UINT NumViews, ID3D11ShaderResourceView *const *ppShaderResourceViews) -{ - _orig->DSSetShaderResources(StartSlot, NumViews, ppShaderResourceViews); -} -void STDMETHODCALLTYPE D3D11DeviceContext::DSSetShader(ID3D11DomainShader *pDomainShader, ID3D11ClassInstance *const *ppClassInstances, UINT NumClassInstances) -{ - _orig->DSSetShader(pDomainShader, ppClassInstances, NumClassInstances); -} -void STDMETHODCALLTYPE D3D11DeviceContext::DSSetSamplers(UINT StartSlot, UINT NumSamplers, ID3D11SamplerState *const *ppSamplers) -{ - _orig->DSSetSamplers(StartSlot, NumSamplers, ppSamplers); -} -void STDMETHODCALLTYPE D3D11DeviceContext::DSSetConstantBuffers(UINT StartSlot, UINT NumBuffers, ID3D11Buffer *const *ppConstantBuffers) -{ - _orig->DSSetConstantBuffers(StartSlot, NumBuffers, ppConstantBuffers); -} -void STDMETHODCALLTYPE D3D11DeviceContext::CSSetShaderResources(UINT StartSlot, UINT NumViews, ID3D11ShaderResourceView *const *ppShaderResourceViews) -{ - _orig->CSSetShaderResources(StartSlot, NumViews, ppShaderResourceViews); -} -void STDMETHODCALLTYPE D3D11DeviceContext::CSSetUnorderedAccessViews(UINT StartSlot, UINT NumUAVs, ID3D11UnorderedAccessView *const *ppUnorderedAccessViews, const UINT *pUAVInitialCounts) -{ - _orig->CSSetUnorderedAccessViews(StartSlot, NumUAVs, ppUnorderedAccessViews, pUAVInitialCounts); -} -void STDMETHODCALLTYPE D3D11DeviceContext::CSSetShader(ID3D11ComputeShader *pComputeShader, ID3D11ClassInstance *const *ppClassInstances, UINT NumClassInstances) -{ - _orig->CSSetShader(pComputeShader, ppClassInstances, NumClassInstances); -} -void STDMETHODCALLTYPE D3D11DeviceContext::CSSetSamplers(UINT StartSlot, UINT NumSamplers, ID3D11SamplerState *const *ppSamplers) -{ - _orig->CSSetSamplers(StartSlot, NumSamplers, ppSamplers); -} -void STDMETHODCALLTYPE D3D11DeviceContext::CSSetConstantBuffers(UINT StartSlot, UINT NumBuffers, ID3D11Buffer *const *ppConstantBuffers) -{ - _orig->CSSetConstantBuffers(StartSlot, NumBuffers, ppConstantBuffers); -} -void STDMETHODCALLTYPE D3D11DeviceContext::VSGetConstantBuffers(UINT StartSlot, UINT NumBuffers, ID3D11Buffer **ppConstantBuffers) -{ - _orig->VSGetConstantBuffers(StartSlot, NumBuffers, ppConstantBuffers); -} -void STDMETHODCALLTYPE D3D11DeviceContext::PSGetShaderResources(UINT StartSlot, UINT NumViews, ID3D11ShaderResourceView **ppShaderResourceViews) -{ - _orig->PSGetShaderResources(StartSlot, NumViews, ppShaderResourceViews); -} -void STDMETHODCALLTYPE D3D11DeviceContext::PSGetShader(ID3D11PixelShader **ppPixelShader, ID3D11ClassInstance **ppClassInstances, UINT *pNumClassInstances) -{ - _orig->PSGetShader(ppPixelShader, ppClassInstances, pNumClassInstances); -} -void STDMETHODCALLTYPE D3D11DeviceContext::PSGetSamplers(UINT StartSlot, UINT NumSamplers, ID3D11SamplerState **ppSamplers) -{ - _orig->PSGetSamplers(StartSlot, NumSamplers, ppSamplers); -} -void STDMETHODCALLTYPE D3D11DeviceContext::VSGetShader(ID3D11VertexShader **ppVertexShader, ID3D11ClassInstance **ppClassInstances, UINT *pNumClassInstances) -{ - _orig->VSGetShader(ppVertexShader, ppClassInstances, pNumClassInstances); -} -void STDMETHODCALLTYPE D3D11DeviceContext::PSGetConstantBuffers(UINT StartSlot, UINT NumBuffers, ID3D11Buffer **ppConstantBuffers) -{ - _orig->PSGetConstantBuffers(StartSlot, NumBuffers, ppConstantBuffers); -} -void STDMETHODCALLTYPE D3D11DeviceContext::IAGetInputLayout(ID3D11InputLayout **ppInputLayout) -{ - _orig->IAGetInputLayout(ppInputLayout); -} -void STDMETHODCALLTYPE D3D11DeviceContext::IAGetVertexBuffers(UINT StartSlot, UINT NumBuffers, ID3D11Buffer **ppVertexBuffers, UINT *pStrides, UINT *pOffsets) -{ - _orig->IAGetVertexBuffers(StartSlot, NumBuffers, ppVertexBuffers, pStrides, pOffsets); -} -void STDMETHODCALLTYPE D3D11DeviceContext::IAGetIndexBuffer(ID3D11Buffer **pIndexBuffer, DXGI_FORMAT *Format, UINT *Offset) -{ - _orig->IAGetIndexBuffer(pIndexBuffer, Format, Offset); -} -void STDMETHODCALLTYPE D3D11DeviceContext::GSGetConstantBuffers(UINT StartSlot, UINT NumBuffers, ID3D11Buffer **ppConstantBuffers) -{ - _orig->GSGetConstantBuffers(StartSlot, NumBuffers, ppConstantBuffers); -} -void STDMETHODCALLTYPE D3D11DeviceContext::GSGetShader(ID3D11GeometryShader **ppGeometryShader, ID3D11ClassInstance **ppClassInstances, UINT *pNumClassInstances) -{ - _orig->GSGetShader(ppGeometryShader, ppClassInstances, pNumClassInstances); -} -void STDMETHODCALLTYPE D3D11DeviceContext::IAGetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY *pTopology) -{ - _orig->IAGetPrimitiveTopology(pTopology); -} -void STDMETHODCALLTYPE D3D11DeviceContext::VSGetShaderResources(UINT StartSlot, UINT NumViews, ID3D11ShaderResourceView **ppShaderResourceViews) -{ - _orig->VSGetShaderResources(StartSlot, NumViews, ppShaderResourceViews); -} -void STDMETHODCALLTYPE D3D11DeviceContext::VSGetSamplers(UINT StartSlot, UINT NumSamplers, ID3D11SamplerState **ppSamplers) -{ - _orig->VSGetSamplers(StartSlot, NumSamplers, ppSamplers); -} -void STDMETHODCALLTYPE D3D11DeviceContext::GetPredication(ID3D11Predicate **ppPredicate, BOOL *pPredicateValue) -{ - _orig->GetPredication(ppPredicate, pPredicateValue); -} -void STDMETHODCALLTYPE D3D11DeviceContext::GSGetShaderResources(UINT StartSlot, UINT NumViews, ID3D11ShaderResourceView **ppShaderResourceViews) -{ - _orig->GSGetShaderResources(StartSlot, NumViews, ppShaderResourceViews); -} -void STDMETHODCALLTYPE D3D11DeviceContext::GSGetSamplers(UINT StartSlot, UINT NumSamplers, ID3D11SamplerState **ppSamplers) -{ - _orig->GSGetSamplers(StartSlot, NumSamplers, ppSamplers); -} -void STDMETHODCALLTYPE D3D11DeviceContext::OMGetRenderTargets(UINT NumViews, ID3D11RenderTargetView **ppRenderTargetViews, ID3D11DepthStencilView **ppDepthStencilView) -{ - _orig->OMGetRenderTargets(NumViews, ppRenderTargetViews, ppDepthStencilView); -} -void STDMETHODCALLTYPE D3D11DeviceContext::OMGetRenderTargetsAndUnorderedAccessViews(UINT NumRTVs, ID3D11RenderTargetView **ppRenderTargetViews, ID3D11DepthStencilView **ppDepthStencilView, UINT UAVStartSlot, UINT NumUAVs, ID3D11UnorderedAccessView **ppUnorderedAccessViews) -{ - _orig->OMGetRenderTargetsAndUnorderedAccessViews(NumRTVs, ppRenderTargetViews, ppDepthStencilView, UAVStartSlot, NumUAVs, ppUnorderedAccessViews); -} -void STDMETHODCALLTYPE D3D11DeviceContext::OMGetBlendState(ID3D11BlendState **ppBlendState, FLOAT BlendFactor[4], UINT *pSampleMask) -{ - _orig->OMGetBlendState(ppBlendState, BlendFactor, pSampleMask); -} -void STDMETHODCALLTYPE D3D11DeviceContext::OMGetDepthStencilState(ID3D11DepthStencilState **ppDepthStencilState, UINT *pStencilRef) -{ - _orig->OMGetDepthStencilState(ppDepthStencilState, pStencilRef); -} -void STDMETHODCALLTYPE D3D11DeviceContext::SOGetTargets(UINT NumBuffers, ID3D11Buffer **ppSOTargets) -{ - _orig->SOGetTargets(NumBuffers, ppSOTargets); -} -void STDMETHODCALLTYPE D3D11DeviceContext::RSGetState(ID3D11RasterizerState **ppRasterizerState) -{ - _orig->RSGetState(ppRasterizerState); -} -void STDMETHODCALLTYPE D3D11DeviceContext::RSGetViewports(UINT *pNumViewports, D3D11_VIEWPORT *pViewports) -{ - _orig->RSGetViewports(pNumViewports, pViewports); -} -void STDMETHODCALLTYPE D3D11DeviceContext::RSGetScissorRects(UINT *pNumRects, D3D11_RECT *pRects) -{ - _orig->RSGetScissorRects(pNumRects, pRects); -} -void STDMETHODCALLTYPE D3D11DeviceContext::HSGetShaderResources(UINT StartSlot, UINT NumViews, ID3D11ShaderResourceView **ppShaderResourceViews) -{ - _orig->HSGetShaderResources(StartSlot, NumViews, ppShaderResourceViews); -} -void STDMETHODCALLTYPE D3D11DeviceContext::HSGetShader(ID3D11HullShader **ppHullShader, ID3D11ClassInstance **ppClassInstances, UINT *pNumClassInstances) -{ - _orig->HSGetShader(ppHullShader, ppClassInstances, pNumClassInstances); -} -void STDMETHODCALLTYPE D3D11DeviceContext::HSGetSamplers(UINT StartSlot, UINT NumSamplers, ID3D11SamplerState **ppSamplers) -{ - _orig->HSGetSamplers(StartSlot, NumSamplers, ppSamplers); -} -void STDMETHODCALLTYPE D3D11DeviceContext::HSGetConstantBuffers(UINT StartSlot, UINT NumBuffers, ID3D11Buffer **ppConstantBuffers) -{ - _orig->HSGetConstantBuffers(StartSlot, NumBuffers, ppConstantBuffers); -} -void STDMETHODCALLTYPE D3D11DeviceContext::DSGetShaderResources(UINT StartSlot, UINT NumViews, ID3D11ShaderResourceView **ppShaderResourceViews) -{ - _orig->DSGetShaderResources(StartSlot, NumViews, ppShaderResourceViews); -} -void STDMETHODCALLTYPE D3D11DeviceContext::DSGetShader(ID3D11DomainShader **ppDomainShader, ID3D11ClassInstance **ppClassInstances, UINT *pNumClassInstances) -{ - _orig->DSGetShader(ppDomainShader, ppClassInstances, pNumClassInstances); -} -void STDMETHODCALLTYPE D3D11DeviceContext::DSGetSamplers(UINT StartSlot, UINT NumSamplers, ID3D11SamplerState **ppSamplers) -{ - _orig->DSGetSamplers(StartSlot, NumSamplers, ppSamplers); -} -void STDMETHODCALLTYPE D3D11DeviceContext::DSGetConstantBuffers(UINT StartSlot, UINT NumBuffers, ID3D11Buffer **ppConstantBuffers) -{ - _orig->DSGetConstantBuffers(StartSlot, NumBuffers, ppConstantBuffers); -} -void STDMETHODCALLTYPE D3D11DeviceContext::CSGetShaderResources(UINT StartSlot, UINT NumViews, ID3D11ShaderResourceView **ppShaderResourceViews) -{ - _orig->CSGetShaderResources(StartSlot, NumViews, ppShaderResourceViews); -} -void STDMETHODCALLTYPE D3D11DeviceContext::CSGetUnorderedAccessViews(UINT StartSlot, UINT NumUAVs, ID3D11UnorderedAccessView **ppUnorderedAccessViews) -{ - _orig->CSGetUnorderedAccessViews(StartSlot, NumUAVs, ppUnorderedAccessViews); -} -void STDMETHODCALLTYPE D3D11DeviceContext::CSGetShader(ID3D11ComputeShader **ppComputeShader, ID3D11ClassInstance **ppClassInstances, UINT *pNumClassInstances) -{ - _orig->CSGetShader(ppComputeShader, ppClassInstances, pNumClassInstances); -} -void STDMETHODCALLTYPE D3D11DeviceContext::CSGetSamplers(UINT StartSlot, UINT NumSamplers, ID3D11SamplerState **ppSamplers) -{ - _orig->CSGetSamplers(StartSlot, NumSamplers, ppSamplers); -} -void STDMETHODCALLTYPE D3D11DeviceContext::CSGetConstantBuffers(UINT StartSlot, UINT NumBuffers, ID3D11Buffer **ppConstantBuffers) -{ - _orig->CSGetConstantBuffers(StartSlot, NumBuffers, ppConstantBuffers); -} -void STDMETHODCALLTYPE D3D11DeviceContext::ClearState() -{ - _orig->ClearState(); -} -void STDMETHODCALLTYPE D3D11DeviceContext::Flush() -{ - _orig->Flush(); -} -UINT STDMETHODCALLTYPE D3D11DeviceContext::GetContextFlags() -{ - return _orig->GetContextFlags(); -} -HRESULT STDMETHODCALLTYPE D3D11DeviceContext::FinishCommandList(BOOL RestoreDeferredContextState, ID3D11CommandList **ppCommandList) -{ - const HRESULT hr = _orig->FinishCommandList(RestoreDeferredContextState, ppCommandList); - - if (SUCCEEDED(hr) && ppCommandList != nullptr) - _device->add_commandlist_trackers(*ppCommandList, _draw_call_tracker); - - _draw_call_tracker.reset(); - - return hr; -} -D3D11_DEVICE_CONTEXT_TYPE STDMETHODCALLTYPE D3D11DeviceContext::GetType() -{ - return _orig->GetType(); -} - -void STDMETHODCALLTYPE D3D11DeviceContext::CopySubresourceRegion1(ID3D11Resource *pDstResource, UINT DstSubresource, UINT DstX, UINT DstY, UINT DstZ, ID3D11Resource *pSrcResource, UINT SrcSubresource, const D3D11_BOX *pSrcBox, UINT CopyFlags) -{ - assert(_interface_version >= 1); - static_cast(_orig)->CopySubresourceRegion1(pDstResource, DstSubresource, DstX, DstY, DstZ, pSrcResource, SrcSubresource, pSrcBox, CopyFlags); -} -void STDMETHODCALLTYPE D3D11DeviceContext::UpdateSubresource1(ID3D11Resource *pDstResource, UINT DstSubresource, const D3D11_BOX *pDstBox, const void *pSrcData, UINT SrcRowPitch, UINT SrcDepthPitch, UINT CopyFlags) -{ - assert(_interface_version >= 1); - static_cast(_orig)->UpdateSubresource1(pDstResource, DstSubresource, pDstBox, pSrcData, SrcRowPitch, SrcDepthPitch, CopyFlags); -} -void STDMETHODCALLTYPE D3D11DeviceContext::DiscardResource(ID3D11Resource *pResource) -{ - assert(_interface_version >= 1); - static_cast(_orig)->DiscardResource(pResource); -} -void STDMETHODCALLTYPE D3D11DeviceContext::DiscardView(ID3D11View *pResourceView) -{ - assert(_interface_version >= 1); - static_cast(_orig)->DiscardView(pResourceView); -} -void STDMETHODCALLTYPE D3D11DeviceContext::VSSetConstantBuffers1(UINT StartSlot, UINT NumBuffers, ID3D11Buffer *const *ppConstantBuffers, const UINT *pFirstConstant, const UINT *pNumConstants) -{ - assert(_interface_version >= 1); - static_cast(_orig)->VSSetConstantBuffers1(StartSlot, NumBuffers, ppConstantBuffers, pFirstConstant, pNumConstants); -} -void STDMETHODCALLTYPE D3D11DeviceContext::HSSetConstantBuffers1(UINT StartSlot, UINT NumBuffers, ID3D11Buffer *const *ppConstantBuffers, const UINT *pFirstConstant, const UINT *pNumConstants) -{ - assert(_interface_version >= 1); - static_cast(_orig)->HSSetConstantBuffers1(StartSlot, NumBuffers, ppConstantBuffers, pFirstConstant, pNumConstants); -} -void STDMETHODCALLTYPE D3D11DeviceContext::DSSetConstantBuffers1(UINT StartSlot, UINT NumBuffers, ID3D11Buffer *const *ppConstantBuffers, const UINT *pFirstConstant, const UINT *pNumConstants) -{ - assert(_interface_version >= 1); - static_cast(_orig)->DSSetConstantBuffers1(StartSlot, NumBuffers, ppConstantBuffers, pFirstConstant, pNumConstants); -} -void STDMETHODCALLTYPE D3D11DeviceContext::GSSetConstantBuffers1(UINT StartSlot, UINT NumBuffers, ID3D11Buffer *const *ppConstantBuffers, const UINT *pFirstConstant, const UINT *pNumConstants) -{ - assert(_interface_version >= 1); - static_cast(_orig)->GSSetConstantBuffers1(StartSlot, NumBuffers, ppConstantBuffers, pFirstConstant, pNumConstants); -} -void STDMETHODCALLTYPE D3D11DeviceContext::PSSetConstantBuffers1(UINT StartSlot, UINT NumBuffers, ID3D11Buffer *const *ppConstantBuffers, const UINT *pFirstConstant, const UINT *pNumConstants) -{ - assert(_interface_version >= 1); - static_cast(_orig)->PSSetConstantBuffers1(StartSlot, NumBuffers, ppConstantBuffers, pFirstConstant, pNumConstants); -} -void STDMETHODCALLTYPE D3D11DeviceContext::CSSetConstantBuffers1(UINT StartSlot, UINT NumBuffers, ID3D11Buffer *const *ppConstantBuffers, const UINT *pFirstConstant, const UINT *pNumConstants) -{ - assert(_interface_version >= 1); - static_cast(_orig)->CSSetConstantBuffers1(StartSlot, NumBuffers, ppConstantBuffers, pFirstConstant, pNumConstants); -} -void STDMETHODCALLTYPE D3D11DeviceContext::VSGetConstantBuffers1(UINT StartSlot, UINT NumBuffers, ID3D11Buffer **ppConstantBuffers, UINT *pFirstConstant, UINT *pNumConstants) -{ - assert(_interface_version >= 1); - static_cast(_orig)->VSGetConstantBuffers1(StartSlot, NumBuffers, ppConstantBuffers, pFirstConstant, pNumConstants); -} -void STDMETHODCALLTYPE D3D11DeviceContext::HSGetConstantBuffers1(UINT StartSlot, UINT NumBuffers, ID3D11Buffer **ppConstantBuffers, UINT *pFirstConstant, UINT *pNumConstants) -{ - assert(_interface_version >= 1); - static_cast(_orig)->HSGetConstantBuffers1(StartSlot, NumBuffers, ppConstantBuffers, pFirstConstant, pNumConstants); -} -void STDMETHODCALLTYPE D3D11DeviceContext::DSGetConstantBuffers1(UINT StartSlot, UINT NumBuffers, ID3D11Buffer **ppConstantBuffers, UINT *pFirstConstant, UINT *pNumConstants) -{ - assert(_interface_version >= 1); - static_cast(_orig)->DSGetConstantBuffers1(StartSlot, NumBuffers, ppConstantBuffers, pFirstConstant, pNumConstants); -} -void STDMETHODCALLTYPE D3D11DeviceContext::GSGetConstantBuffers1(UINT StartSlot, UINT NumBuffers, ID3D11Buffer **ppConstantBuffers, UINT *pFirstConstant, UINT *pNumConstants) -{ - assert(_interface_version >= 1); - static_cast(_orig)->GSGetConstantBuffers1(StartSlot, NumBuffers, ppConstantBuffers, pFirstConstant, pNumConstants); -} -void STDMETHODCALLTYPE D3D11DeviceContext::PSGetConstantBuffers1(UINT StartSlot, UINT NumBuffers, ID3D11Buffer **ppConstantBuffers, UINT *pFirstConstant, UINT *pNumConstants) -{ - assert(_interface_version >= 1); - static_cast(_orig)->PSGetConstantBuffers1(StartSlot, NumBuffers, ppConstantBuffers, pFirstConstant, pNumConstants); -} -void STDMETHODCALLTYPE D3D11DeviceContext::CSGetConstantBuffers1(UINT StartSlot, UINT NumBuffers, ID3D11Buffer **ppConstantBuffers, UINT *pFirstConstant, UINT *pNumConstants) -{ - assert(_interface_version >= 1); - static_cast(_orig)->CSGetConstantBuffers1(StartSlot, NumBuffers, ppConstantBuffers, pFirstConstant, pNumConstants); -} -void STDMETHODCALLTYPE D3D11DeviceContext::SwapDeviceContextState(ID3DDeviceContextState *pState, ID3DDeviceContextState **ppPreviousState) -{ - assert(_interface_version >= 1); - static_cast(_orig)->SwapDeviceContextState(pState, ppPreviousState); -} -void STDMETHODCALLTYPE D3D11DeviceContext::ClearView(ID3D11View *pView, const FLOAT Color[4], const D3D11_RECT *pRect, UINT NumRects) -{ - assert(_interface_version >= 1); - static_cast(_orig)->ClearView(pView, Color, pRect, NumRects); -} -void STDMETHODCALLTYPE D3D11DeviceContext::DiscardView1(ID3D11View *pResourceView, const D3D11_RECT *pRects, UINT NumRects) -{ - assert(_interface_version >= 1); - static_cast(_orig)->DiscardView1(pResourceView, pRects, NumRects); -} - -HRESULT STDMETHODCALLTYPE D3D11DeviceContext::UpdateTileMappings(ID3D11Resource *pTiledResource, UINT NumTiledResourceRegions, const D3D11_TILED_RESOURCE_COORDINATE *pTiledResourceRegionStartCoordinates, const D3D11_TILE_REGION_SIZE *pTiledResourceRegionSizes, ID3D11Buffer *pTilePool, UINT NumRanges, const UINT *pRangeFlags, const UINT *pTilePoolStartOffsets, const UINT *pRangeTileCounts, UINT Flags) -{ - assert(_interface_version >= 2); - return static_cast(_orig)->UpdateTileMappings(pTiledResource, NumTiledResourceRegions, pTiledResourceRegionStartCoordinates, pTiledResourceRegionSizes, pTilePool, NumRanges, pRangeFlags, pTilePoolStartOffsets, pRangeTileCounts, Flags); -} -HRESULT STDMETHODCALLTYPE D3D11DeviceContext::CopyTileMappings(ID3D11Resource *pDestTiledResource, const D3D11_TILED_RESOURCE_COORDINATE *pDestRegionStartCoordinate, ID3D11Resource *pSourceTiledResource, const D3D11_TILED_RESOURCE_COORDINATE *pSourceRegionStartCoordinate, const D3D11_TILE_REGION_SIZE *pTileRegionSize, UINT Flags) -{ - assert(_interface_version >= 2); - return static_cast(_orig)->CopyTileMappings(pDestTiledResource, pDestRegionStartCoordinate, pSourceTiledResource, pSourceRegionStartCoordinate, pTileRegionSize, Flags); -} -void STDMETHODCALLTYPE D3D11DeviceContext::CopyTiles(ID3D11Resource *pTiledResource, const D3D11_TILED_RESOURCE_COORDINATE *pTileRegionStartCoordinate, const D3D11_TILE_REGION_SIZE *pTileRegionSize, ID3D11Buffer *pBuffer, UINT64 BufferStartOffsetInBytes, UINT Flags) -{ - assert(_interface_version >= 2); - static_cast(_orig)->CopyTiles(pTiledResource, pTileRegionStartCoordinate, pTileRegionSize, pBuffer, BufferStartOffsetInBytes, Flags); -} -void STDMETHODCALLTYPE D3D11DeviceContext::UpdateTiles(ID3D11Resource *pDestTiledResource, const D3D11_TILED_RESOURCE_COORDINATE *pDestTileRegionStartCoordinate, const D3D11_TILE_REGION_SIZE *pDestTileRegionSize, const void *pSourceTileData, UINT Flags) -{ - assert(_interface_version >= 2); - static_cast(_orig)->UpdateTiles(pDestTiledResource, pDestTileRegionStartCoordinate, pDestTileRegionSize, pSourceTileData, Flags); -} -HRESULT STDMETHODCALLTYPE D3D11DeviceContext::ResizeTilePool(ID3D11Buffer *pTilePool, UINT64 NewSizeInBytes) -{ - assert(_interface_version >= 2); - return static_cast(_orig)->ResizeTilePool(pTilePool, NewSizeInBytes); -} -void STDMETHODCALLTYPE D3D11DeviceContext::TiledResourceBarrier(ID3D11DeviceChild *pTiledResourceOrViewAccessBeforeBarrier, ID3D11DeviceChild *pTiledResourceOrViewAccessAfterBarrier) -{ - assert(_interface_version >= 2); - static_cast(_orig)->TiledResourceBarrier(pTiledResourceOrViewAccessBeforeBarrier, pTiledResourceOrViewAccessAfterBarrier); -} -BOOL STDMETHODCALLTYPE D3D11DeviceContext::IsAnnotationEnabled() -{ - assert(_interface_version >= 2); - return static_cast(_orig)->IsAnnotationEnabled(); -} -void STDMETHODCALLTYPE D3D11DeviceContext::SetMarkerInt(LPCWSTR pLabel, INT Data) -{ - assert(_interface_version >= 2); - static_cast(_orig)->SetMarkerInt(pLabel, Data); -} -void STDMETHODCALLTYPE D3D11DeviceContext::BeginEventInt(LPCWSTR pLabel, INT Data) -{ - assert(_interface_version >= 2); - static_cast(_orig)->BeginEventInt(pLabel, Data); -} -void STDMETHODCALLTYPE D3D11DeviceContext::EndEvent() -{ - assert(_interface_version >= 2); - static_cast(_orig)->EndEvent(); -} - -void STDMETHODCALLTYPE D3D11DeviceContext::Flush1(D3D11_CONTEXT_TYPE ContextType, HANDLE hEvent) -{ - assert(_interface_version >= 3); - static_cast(_orig)->Flush1(ContextType, hEvent); -} -void STDMETHODCALLTYPE D3D11DeviceContext::SetHardwareProtectionState(BOOL HwProtectionEnable) -{ - assert(_interface_version >= 3); - static_cast(_orig)->SetHardwareProtectionState(HwProtectionEnable); -} -void STDMETHODCALLTYPE D3D11DeviceContext::GetHardwareProtectionState(BOOL *pHwProtectionEnable) -{ - assert(_interface_version >= 3); - static_cast(_orig)->GetHardwareProtectionState(pHwProtectionEnable); -} - -HRESULT STDMETHODCALLTYPE D3D11DeviceContext::Signal(ID3D11Fence *pFence, UINT64 Value) -{ - assert(_interface_version >= 4); - return static_cast(_orig)->Signal(pFence, Value); -} -HRESULT STDMETHODCALLTYPE D3D11DeviceContext::Wait(ID3D11Fence *pFence, UINT64 Value) -{ - assert(_interface_version >= 4); - return static_cast(_orig)->Wait(pFence, Value); -} diff --git a/msvc/source/d3d11/d3d11_device_context.hpp b/msvc/source/d3d11/d3d11_device_context.hpp deleted file mode 100644 index ebabe79..0000000 --- a/msvc/source/d3d11/d3d11_device_context.hpp +++ /dev/null @@ -1,200 +0,0 @@ -/** - * Copyright (C) 2014 Patrick Mours. All rights reserved. - * License: https://github.com/crosire/reshade#license - */ - -#pragma once - -#include -#include "draw_call_tracker.hpp" - -struct __declspec(uuid("27B0246B-2152-4D42-AD11-32489472238F")) D3D11DeviceContext : ID3D11DeviceContext4 -{ - D3D11DeviceContext(D3D11Device *device, ID3D11DeviceContext *original); - D3D11DeviceContext(D3D11Device *device, ID3D11DeviceContext1 *original); - D3D11DeviceContext(D3D11Device *device, ID3D11DeviceContext2 *original); - D3D11DeviceContext(D3D11Device *device, ID3D11DeviceContext3 *original); - - D3D11DeviceContext(const D3D11DeviceContext &) = delete; - D3D11DeviceContext &operator=(const D3D11DeviceContext &) = delete; - - HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void **ppvObj) override; - ULONG STDMETHODCALLTYPE AddRef() override; - ULONG STDMETHODCALLTYPE Release() override; - - #pragma region ID3D11DeviceChild - void STDMETHODCALLTYPE GetDevice(ID3D11Device **ppDevice) override; - HRESULT STDMETHODCALLTYPE GetPrivateData(REFGUID guid, UINT *pDataSize, void *pData) override; - HRESULT STDMETHODCALLTYPE SetPrivateData(REFGUID guid, UINT DataSize, const void *pData) override; - HRESULT STDMETHODCALLTYPE SetPrivateDataInterface(REFGUID guid, const IUnknown *pData) override; - #pragma endregion - #pragma region ID3D11DeviceContext - void STDMETHODCALLTYPE VSSetConstantBuffers(UINT StartSlot, UINT NumBuffers, ID3D11Buffer *const *ppConstantBuffers) override; - void STDMETHODCALLTYPE PSSetShaderResources(UINT StartSlot, UINT NumViews, ID3D11ShaderResourceView *const *ppShaderResourceViews) override; - void STDMETHODCALLTYPE PSSetShader(ID3D11PixelShader *pPixelShader, ID3D11ClassInstance *const *ppClassInstances, UINT NumClassInstances) override; - void STDMETHODCALLTYPE PSSetSamplers(UINT StartSlot, UINT NumSamplers, ID3D11SamplerState *const *ppSamplers) override; - void STDMETHODCALLTYPE VSSetShader(ID3D11VertexShader *pVertexShader, ID3D11ClassInstance *const *ppClassInstances, UINT NumClassInstances) override; - void STDMETHODCALLTYPE DrawIndexed(UINT IndexCount, UINT StartIndexLocation, INT BaseVertexLocation) override; - void STDMETHODCALLTYPE Draw(UINT VertexCount, UINT StartVertexLocation) override; - HRESULT STDMETHODCALLTYPE Map(ID3D11Resource *pResource, UINT Subresource, D3D11_MAP MapType, UINT MapFlags, D3D11_MAPPED_SUBRESOURCE *pMappedResource) override; - void STDMETHODCALLTYPE Unmap(ID3D11Resource *pResource, UINT Subresource) override; - void STDMETHODCALLTYPE PSSetConstantBuffers(UINT StartSlot, UINT NumBuffers, ID3D11Buffer *const *ppConstantBuffers) override; - void STDMETHODCALLTYPE IASetInputLayout(ID3D11InputLayout *pInputLayout) override; - void STDMETHODCALLTYPE IASetVertexBuffers(UINT StartSlot, UINT NumBuffers, ID3D11Buffer *const *ppVertexBuffers, const UINT *pStrides, const UINT *pOffsets) override; - void STDMETHODCALLTYPE IASetIndexBuffer(ID3D11Buffer *pIndexBuffer, DXGI_FORMAT Format, UINT Offset) override; - void STDMETHODCALLTYPE DrawIndexedInstanced(UINT IndexCountPerInstance, UINT InstanceCount, UINT StartIndexLocation, INT BaseVertexLocation, UINT StartInstanceLocation) override; - void STDMETHODCALLTYPE DrawInstanced(UINT VertexCountPerInstance, UINT InstanceCount, UINT StartVertexLocation, UINT StartInstanceLocation) override; - void STDMETHODCALLTYPE GSSetConstantBuffers(UINT StartSlot, UINT NumBuffers, ID3D11Buffer *const *ppConstantBuffers) override; - void STDMETHODCALLTYPE GSSetShader(ID3D11GeometryShader *pShader, ID3D11ClassInstance *const *ppClassInstances, UINT NumClassInstances) override; - void STDMETHODCALLTYPE IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY Topology) override; - void STDMETHODCALLTYPE VSSetShaderResources(UINT StartSlot, UINT NumViews, ID3D11ShaderResourceView *const *ppShaderResourceViews) override; - void STDMETHODCALLTYPE VSSetSamplers(UINT StartSlot, UINT NumSamplers, ID3D11SamplerState *const *ppSamplers) override; - void STDMETHODCALLTYPE Begin(ID3D11Asynchronous *pAsync) override; - void STDMETHODCALLTYPE End(ID3D11Asynchronous *pAsync) override; - HRESULT STDMETHODCALLTYPE GetData(ID3D11Asynchronous *pAsync, void *pData, UINT DataSize, UINT GetDataFlags) override; - void STDMETHODCALLTYPE SetPredication(ID3D11Predicate *pPredicate, BOOL PredicateValue) override; - void STDMETHODCALLTYPE GSSetShaderResources(UINT StartSlot, UINT NumViews, ID3D11ShaderResourceView *const *ppShaderResourceViews) override; - void STDMETHODCALLTYPE GSSetSamplers(UINT StartSlot, UINT NumSamplers, ID3D11SamplerState *const *ppSamplers) override; - void STDMETHODCALLTYPE OMSetRenderTargets(UINT NumViews, ID3D11RenderTargetView *const *ppRenderTargetViews, ID3D11DepthStencilView *pDepthStencilView) override; - void STDMETHODCALLTYPE OMSetRenderTargetsAndUnorderedAccessViews(UINT NumRTVs, ID3D11RenderTargetView *const *ppRenderTargetViews, ID3D11DepthStencilView *pDepthStencilView, UINT UAVStartSlot, UINT NumUAVs, ID3D11UnorderedAccessView *const *ppUnorderedAccessViews, const UINT *pUAVInitialCounts) override; - void STDMETHODCALLTYPE OMSetBlendState(ID3D11BlendState *pBlendState, const FLOAT BlendFactor[4], UINT SampleMask) override; - void STDMETHODCALLTYPE OMSetDepthStencilState(ID3D11DepthStencilState *pDepthStencilState, UINT StencilRef) override; - void STDMETHODCALLTYPE SOSetTargets(UINT NumBuffers, ID3D11Buffer *const *ppSOTargets, const UINT *pOffsets) override; - void STDMETHODCALLTYPE DrawAuto() override; - void STDMETHODCALLTYPE DrawIndexedInstancedIndirect(ID3D11Buffer *pBufferForArgs, UINT AlignedByteOffsetForArgs) override; - void STDMETHODCALLTYPE DrawInstancedIndirect(ID3D11Buffer *pBufferForArgs, UINT AlignedByteOffsetForArgs) override; - void STDMETHODCALLTYPE Dispatch(UINT ThreadGroupCountX, UINT ThreadGroupCountY, UINT ThreadGroupCountZ) override; - void STDMETHODCALLTYPE DispatchIndirect(ID3D11Buffer *pBufferForArgs, UINT AlignedByteOffsetForArgs) override; - void STDMETHODCALLTYPE RSSetState(ID3D11RasterizerState *pRasterizerState) override; - void STDMETHODCALLTYPE RSSetViewports(UINT NumViewports, const D3D11_VIEWPORT *pViewports) override; - void STDMETHODCALLTYPE RSSetScissorRects(UINT NumRects, const D3D11_RECT *pRects) override; - void STDMETHODCALLTYPE CopySubresourceRegion(ID3D11Resource *pDstResource, UINT DstSubresource, UINT DstX, UINT DstY, UINT DstZ, ID3D11Resource *pSrcResource, UINT SrcSubresource, const D3D11_BOX *pSrcBox) override; - void STDMETHODCALLTYPE CopyResource(ID3D11Resource *pDstResource, ID3D11Resource *pSrcResource) override; - void STDMETHODCALLTYPE UpdateSubresource(ID3D11Resource *pDstResource, UINT DstSubresource, const D3D11_BOX *pDstBox, const void *pSrcData, UINT SrcRowPitch, UINT SrcDepthPitch) override; - void STDMETHODCALLTYPE CopyStructureCount(ID3D11Buffer *pDstBuffer, UINT DstAlignedByteOffset, ID3D11UnorderedAccessView *pSrcView) override; - void STDMETHODCALLTYPE ClearRenderTargetView(ID3D11RenderTargetView *pRenderTargetView, const FLOAT ColorRGBA[4]) override; - void STDMETHODCALLTYPE ClearUnorderedAccessViewUint(ID3D11UnorderedAccessView *pUnorderedAccessView, const UINT Values[4]) override; - void STDMETHODCALLTYPE ClearUnorderedAccessViewFloat(ID3D11UnorderedAccessView *pUnorderedAccessView, const FLOAT Values[4]) override; - void STDMETHODCALLTYPE ClearDepthStencilView(ID3D11DepthStencilView *pDepthStencilView, UINT ClearFlags, FLOAT Depth, UINT8 Stencil) override; - void STDMETHODCALLTYPE GenerateMips(ID3D11ShaderResourceView *pShaderResourceView) override; - void STDMETHODCALLTYPE SetResourceMinLOD(ID3D11Resource *pResource, FLOAT MinLOD) override; - FLOAT STDMETHODCALLTYPE GetResourceMinLOD(ID3D11Resource *pResource) override; - void STDMETHODCALLTYPE ResolveSubresource(ID3D11Resource *pDstResource, UINT DstSubresource, ID3D11Resource *pSrcResource, UINT SrcSubresource, DXGI_FORMAT Format) override; - void STDMETHODCALLTYPE ExecuteCommandList(ID3D11CommandList *pCommandList, BOOL RestoreContextState) override; - void STDMETHODCALLTYPE HSSetShaderResources(UINT StartSlot, UINT NumViews, ID3D11ShaderResourceView *const *ppShaderResourceViews) override; - void STDMETHODCALLTYPE HSSetShader(ID3D11HullShader *pHullShader, ID3D11ClassInstance *const *ppClassInstances, UINT NumClassInstances) override; - void STDMETHODCALLTYPE HSSetSamplers(UINT StartSlot, UINT NumSamplers, ID3D11SamplerState *const *ppSamplers) override; - void STDMETHODCALLTYPE HSSetConstantBuffers(UINT StartSlot, UINT NumBuffers, ID3D11Buffer *const *ppConstantBuffers) override; - void STDMETHODCALLTYPE DSSetShaderResources(UINT StartSlot, UINT NumViews, ID3D11ShaderResourceView *const *ppShaderResourceViews) override; - void STDMETHODCALLTYPE DSSetShader(ID3D11DomainShader *pDomainShader, ID3D11ClassInstance *const *ppClassInstances, UINT NumClassInstances) override; - void STDMETHODCALLTYPE DSSetSamplers(UINT StartSlot, UINT NumSamplers, ID3D11SamplerState *const *ppSamplers) override; - void STDMETHODCALLTYPE DSSetConstantBuffers(UINT StartSlot, UINT NumBuffers, ID3D11Buffer *const *ppConstantBuffers) override; - void STDMETHODCALLTYPE CSSetShaderResources(UINT StartSlot, UINT NumViews, ID3D11ShaderResourceView *const *ppShaderResourceViews) override; - void STDMETHODCALLTYPE CSSetUnorderedAccessViews(UINT StartSlot, UINT NumUAVs, ID3D11UnorderedAccessView *const *ppUnorderedAccessViews, const UINT *pUAVInitialCounts) override; - void STDMETHODCALLTYPE CSSetShader(ID3D11ComputeShader *pComputeShader, ID3D11ClassInstance *const *ppClassInstances, UINT NumClassInstances) override; - void STDMETHODCALLTYPE CSSetSamplers(UINT StartSlot, UINT NumSamplers, ID3D11SamplerState *const *ppSamplers) override; - void STDMETHODCALLTYPE CSSetConstantBuffers(UINT StartSlot, UINT NumBuffers, ID3D11Buffer *const *ppConstantBuffers) override; - void STDMETHODCALLTYPE VSGetConstantBuffers(UINT StartSlot, UINT NumBuffers, ID3D11Buffer **ppConstantBuffers) override; - void STDMETHODCALLTYPE PSGetShaderResources(UINT StartSlot, UINT NumViews, ID3D11ShaderResourceView **ppShaderResourceViews) override; - void STDMETHODCALLTYPE PSGetShader(ID3D11PixelShader **ppPixelShader, ID3D11ClassInstance **ppClassInstances, UINT *pNumClassInstances) override; - void STDMETHODCALLTYPE PSGetSamplers(UINT StartSlot, UINT NumSamplers, ID3D11SamplerState **ppSamplers) override; - void STDMETHODCALLTYPE VSGetShader(ID3D11VertexShader **ppVertexShader, ID3D11ClassInstance **ppClassInstances, UINT *pNumClassInstances) override; - void STDMETHODCALLTYPE PSGetConstantBuffers(UINT StartSlot, UINT NumBuffers, ID3D11Buffer **ppConstantBuffers) override; - void STDMETHODCALLTYPE IAGetInputLayout(ID3D11InputLayout **ppInputLayout) override; - void STDMETHODCALLTYPE IAGetVertexBuffers(UINT StartSlot, UINT NumBuffers, ID3D11Buffer **ppVertexBuffers, UINT *pStrides, UINT *pOffsets) override; - void STDMETHODCALLTYPE IAGetIndexBuffer(ID3D11Buffer **pIndexBuffer, DXGI_FORMAT *Format, UINT *Offset) override; - void STDMETHODCALLTYPE GSGetConstantBuffers(UINT StartSlot, UINT NumBuffers, ID3D11Buffer **ppConstantBuffers) override; - void STDMETHODCALLTYPE GSGetShader(ID3D11GeometryShader **ppGeometryShader, ID3D11ClassInstance **ppClassInstances, UINT *pNumClassInstances) override; - void STDMETHODCALLTYPE IAGetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY *pTopology) override; - void STDMETHODCALLTYPE VSGetShaderResources(UINT StartSlot, UINT NumViews, ID3D11ShaderResourceView **ppShaderResourceViews) override; - void STDMETHODCALLTYPE VSGetSamplers(UINT StartSlot, UINT NumSamplers, ID3D11SamplerState **ppSamplers) override; - void STDMETHODCALLTYPE GetPredication(ID3D11Predicate **ppPredicate, BOOL *pPredicateValue) override; - void STDMETHODCALLTYPE GSGetShaderResources(UINT StartSlot, UINT NumViews, ID3D11ShaderResourceView **ppShaderResourceViews) override; - void STDMETHODCALLTYPE GSGetSamplers(UINT StartSlot, UINT NumSamplers, ID3D11SamplerState **ppSamplers) override; - void STDMETHODCALLTYPE OMGetRenderTargets(UINT NumViews, ID3D11RenderTargetView **ppRenderTargetViews, ID3D11DepthStencilView **ppDepthStencilView) override; - void STDMETHODCALLTYPE OMGetRenderTargetsAndUnorderedAccessViews(UINT NumRTVs, ID3D11RenderTargetView **ppRenderTargetViews, ID3D11DepthStencilView **ppDepthStencilView, UINT UAVStartSlot, UINT NumUAVs, ID3D11UnorderedAccessView **ppUnorderedAccessViews) override; - void STDMETHODCALLTYPE OMGetBlendState(ID3D11BlendState **ppBlendState, FLOAT BlendFactor[4], UINT *pSampleMask) override; - void STDMETHODCALLTYPE OMGetDepthStencilState(ID3D11DepthStencilState **ppDepthStencilState, UINT *pStencilRef) override; - void STDMETHODCALLTYPE SOGetTargets(UINT NumBuffers, ID3D11Buffer **ppSOTargets) override; - void STDMETHODCALLTYPE RSGetState(ID3D11RasterizerState **ppRasterizerState) override; - void STDMETHODCALLTYPE RSGetViewports(UINT *pNumViewports, D3D11_VIEWPORT *pViewports) override; - void STDMETHODCALLTYPE RSGetScissorRects(UINT *pNumRects, D3D11_RECT *pRects) override; - void STDMETHODCALLTYPE HSGetShaderResources(UINT StartSlot, UINT NumViews, ID3D11ShaderResourceView **ppShaderResourceViews) override; - void STDMETHODCALLTYPE HSGetShader(ID3D11HullShader **ppHullShader, ID3D11ClassInstance **ppClassInstances, UINT *pNumClassInstances) override; - void STDMETHODCALLTYPE HSGetSamplers(UINT StartSlot, UINT NumSamplers, ID3D11SamplerState **ppSamplers) override; - void STDMETHODCALLTYPE HSGetConstantBuffers(UINT StartSlot, UINT NumBuffers, ID3D11Buffer **ppConstantBuffers) override; - void STDMETHODCALLTYPE DSGetShaderResources(UINT StartSlot, UINT NumViews, ID3D11ShaderResourceView **ppShaderResourceViews) override; - void STDMETHODCALLTYPE DSGetShader(ID3D11DomainShader **ppDomainShader, ID3D11ClassInstance **ppClassInstances, UINT *pNumClassInstances) override; - void STDMETHODCALLTYPE DSGetSamplers(UINT StartSlot, UINT NumSamplers, ID3D11SamplerState **ppSamplers) override; - void STDMETHODCALLTYPE DSGetConstantBuffers(UINT StartSlot, UINT NumBuffers, ID3D11Buffer **ppConstantBuffers) override; - void STDMETHODCALLTYPE CSGetShaderResources(UINT StartSlot, UINT NumViews, ID3D11ShaderResourceView **ppShaderResourceViews) override; - void STDMETHODCALLTYPE CSGetUnorderedAccessViews(UINT StartSlot, UINT NumUAVs, ID3D11UnorderedAccessView **ppUnorderedAccessViews) override; - void STDMETHODCALLTYPE CSGetShader(ID3D11ComputeShader **ppComputeShader, ID3D11ClassInstance **ppClassInstances, UINT *pNumClassInstances) override; - void STDMETHODCALLTYPE CSGetSamplers(UINT StartSlot, UINT NumSamplers, ID3D11SamplerState **ppSamplers) override; - void STDMETHODCALLTYPE CSGetConstantBuffers(UINT StartSlot, UINT NumBuffers, ID3D11Buffer **ppConstantBuffers) override; - void STDMETHODCALLTYPE ClearState() override; - void STDMETHODCALLTYPE Flush() override; - UINT STDMETHODCALLTYPE GetContextFlags() override; - HRESULT STDMETHODCALLTYPE FinishCommandList(BOOL RestoreDeferredContextState, ID3D11CommandList **ppCommandList) override; - D3D11_DEVICE_CONTEXT_TYPE STDMETHODCALLTYPE GetType() override; - #pragma endregion - #pragma region ID3D11DeviceContext1 - void STDMETHODCALLTYPE CopySubresourceRegion1(ID3D11Resource *pDstResource, UINT DstSubresource, UINT DstX, UINT DstY, UINT DstZ, ID3D11Resource *pSrcResource, UINT SrcSubresource, const D3D11_BOX *pSrcBox, UINT CopyFlags) override; - void STDMETHODCALLTYPE UpdateSubresource1(ID3D11Resource *pDstResource, UINT DstSubresource, const D3D11_BOX *pDstBox, const void *pSrcData, UINT SrcRowPitch, UINT SrcDepthPitch, UINT CopyFlags) override; - void STDMETHODCALLTYPE DiscardResource(ID3D11Resource *pResource) override; - void STDMETHODCALLTYPE DiscardView(ID3D11View *pResourceView) override; - void STDMETHODCALLTYPE VSSetConstantBuffers1(UINT StartSlot, UINT NumBuffers, ID3D11Buffer *const *ppConstantBuffers, const UINT *pFirstConstant, const UINT *pNumConstants) override; - void STDMETHODCALLTYPE HSSetConstantBuffers1(UINT StartSlot, UINT NumBuffers, ID3D11Buffer *const *ppConstantBuffers, const UINT *pFirstConstant, const UINT *pNumConstants) override; - void STDMETHODCALLTYPE DSSetConstantBuffers1(UINT StartSlot, UINT NumBuffers, ID3D11Buffer *const *ppConstantBuffers, const UINT *pFirstConstant, const UINT *pNumConstants) override; - void STDMETHODCALLTYPE GSSetConstantBuffers1(UINT StartSlot, UINT NumBuffers, ID3D11Buffer *const *ppConstantBuffers, const UINT *pFirstConstant, const UINT *pNumConstants) override; - void STDMETHODCALLTYPE PSSetConstantBuffers1(UINT StartSlot, UINT NumBuffers, ID3D11Buffer *const *ppConstantBuffers, const UINT *pFirstConstant, const UINT *pNumConstants) override; - void STDMETHODCALLTYPE CSSetConstantBuffers1(UINT StartSlot, UINT NumBuffers, ID3D11Buffer *const *ppConstantBuffers, const UINT *pFirstConstant, const UINT *pNumConstants) override; - void STDMETHODCALLTYPE VSGetConstantBuffers1(UINT StartSlot, UINT NumBuffers, ID3D11Buffer **ppConstantBuffers, UINT *pFirstConstant, UINT *pNumConstants) override; - void STDMETHODCALLTYPE HSGetConstantBuffers1(UINT StartSlot, UINT NumBuffers, ID3D11Buffer **ppConstantBuffers, UINT *pFirstConstant, UINT *pNumConstants) override; - void STDMETHODCALLTYPE DSGetConstantBuffers1(UINT StartSlot, UINT NumBuffers, ID3D11Buffer **ppConstantBuffers, UINT *pFirstConstant, UINT *pNumConstants) override; - void STDMETHODCALLTYPE GSGetConstantBuffers1(UINT StartSlot, UINT NumBuffers, ID3D11Buffer **ppConstantBuffers, UINT *pFirstConstant, UINT *pNumConstants) override; - void STDMETHODCALLTYPE PSGetConstantBuffers1(UINT StartSlot, UINT NumBuffers, ID3D11Buffer **ppConstantBuffers, UINT *pFirstConstant, UINT *pNumConstants) override; - void STDMETHODCALLTYPE CSGetConstantBuffers1(UINT StartSlot, UINT NumBuffers, ID3D11Buffer **ppConstantBuffers, UINT *pFirstConstant, UINT *pNumConstants) override; - void STDMETHODCALLTYPE SwapDeviceContextState(ID3DDeviceContextState *pState, ID3DDeviceContextState **ppPreviousState) override; - void STDMETHODCALLTYPE ClearView(ID3D11View *pView, const FLOAT Color[4], const D3D11_RECT *pRect, UINT NumRects) override; - void STDMETHODCALLTYPE DiscardView1(ID3D11View *pResourceView, const D3D11_RECT *pRects, UINT NumRects) override; - #pragma endregion - #pragma region ID3D11DeviceContext2 - HRESULT STDMETHODCALLTYPE UpdateTileMappings(ID3D11Resource *pTiledResource, UINT NumTiledResourceRegions, const D3D11_TILED_RESOURCE_COORDINATE *pTiledResourceRegionStartCoordinates, const D3D11_TILE_REGION_SIZE *pTiledResourceRegionSizes, ID3D11Buffer *pTilePool, UINT NumRanges, const UINT *pRangeFlags, const UINT *pTilePoolStartOffsets, const UINT *pRangeTileCounts, UINT Flags) override; - HRESULT STDMETHODCALLTYPE CopyTileMappings(ID3D11Resource *pDestTiledResource, const D3D11_TILED_RESOURCE_COORDINATE *pDestRegionStartCoordinate, ID3D11Resource *pSourceTiledResource, const D3D11_TILED_RESOURCE_COORDINATE *pSourceRegionStartCoordinate, const D3D11_TILE_REGION_SIZE *pTileRegionSize, UINT Flags) override; - void STDMETHODCALLTYPE CopyTiles(ID3D11Resource *pTiledResource, const D3D11_TILED_RESOURCE_COORDINATE *pTileRegionStartCoordinate, const D3D11_TILE_REGION_SIZE *pTileRegionSize, ID3D11Buffer *pBuffer, UINT64 BufferStartOffsetInBytes, UINT Flags) override; - void STDMETHODCALLTYPE UpdateTiles(ID3D11Resource *pDestTiledResource, const D3D11_TILED_RESOURCE_COORDINATE *pDestTileRegionStartCoordinate, const D3D11_TILE_REGION_SIZE *pDestTileRegionSize, const void *pSourceTileData, UINT Flags) override; - HRESULT STDMETHODCALLTYPE ResizeTilePool(ID3D11Buffer *pTilePool, UINT64 NewSizeInBytes) override; - void STDMETHODCALLTYPE TiledResourceBarrier(ID3D11DeviceChild *pTiledResourceOrViewAccessBeforeBarrier, ID3D11DeviceChild *pTiledResourceOrViewAccessAfterBarrier) override; - BOOL STDMETHODCALLTYPE IsAnnotationEnabled() override; - void STDMETHODCALLTYPE SetMarkerInt(LPCWSTR pLabel, INT Data) override; - void STDMETHODCALLTYPE BeginEventInt(LPCWSTR pLabel, INT Data) override; - void STDMETHODCALLTYPE EndEvent() override; - #pragma endregion - #pragma region ID3D11DeviceContext3 - void STDMETHODCALLTYPE Flush1(D3D11_CONTEXT_TYPE ContextType, HANDLE hEvent) override; - void STDMETHODCALLTYPE SetHardwareProtectionState(BOOL HwProtectionEnable) override; - void STDMETHODCALLTYPE GetHardwareProtectionState(BOOL *pHwProtectionEnable) override; - #pragma endregion - #pragma region ID3D11DeviceContext4 - HRESULT STDMETHODCALLTYPE Signal(ID3D11Fence *pFence, UINT64 Value) override; - HRESULT STDMETHODCALLTYPE Wait(ID3D11Fence *pFence, UINT64 Value) override; - #pragma endregion - - void clear_drawcall_stats(); - -#if RESHADE_DX11_CAPTURE_DEPTH_BUFFERS - bool save_depth_texture(ID3D11DepthStencilView *pDepthStencilView, bool cleared); - - void track_active_rendertargets(UINT NumViews, ID3D11RenderTargetView *const *ppRenderTargetViews, ID3D11DepthStencilView *pDepthStencilView); - void track_cleared_depthstencil(ID3D11DepthStencilView* pDepthStencilView); -#endif - - bool check_and_upgrade_interface(REFIID riid); - - LONG _ref = 1; - ID3D11DeviceContext *_orig; - unsigned int _interface_version; - D3D11Device *const _device; - reshade::d3d11::draw_call_tracker _draw_call_tracker; -}; diff --git a/msvc/source/d3d11/draw_call_tracker.cpp b/msvc/source/d3d11/draw_call_tracker.cpp deleted file mode 100644 index f6622a2..0000000 --- a/msvc/source/d3d11/draw_call_tracker.cpp +++ /dev/null @@ -1,285 +0,0 @@ -#include "draw_call_tracker.hpp" -#include "log.hpp" -#include "dxgi/format_utils.hpp" -#include - -namespace reshade::d3d11 -{ - void draw_call_tracker::merge(const draw_call_tracker& source) - { - _global_counter.vertices += source.total_vertices(); - _global_counter.drawcalls += source.total_drawcalls(); - -#if RESHADE_DX11_CAPTURE_DEPTH_BUFFERS - for (const auto &[depthstencil, snapshot] : source._counters_per_used_depthstencil) - { - _counters_per_used_depthstencil[depthstencil].stats.vertices += snapshot.stats.vertices; - _counters_per_used_depthstencil[depthstencil].stats.drawcalls += snapshot.stats.drawcalls; - } - - for (auto source_entry : source._cleared_depth_textures) - { - const auto destination_entry = _cleared_depth_textures.find(source_entry.first); - - if (destination_entry == _cleared_depth_textures.end()) - _cleared_depth_textures.emplace(source_entry.first, source_entry.second); - } -#endif -#if RESHADE_DX11_CAPTURE_CONSTANT_BUFFERS - for (const auto &[buffer, snapshot] : source._counters_per_constant_buffer) - { - _counters_per_constant_buffer[buffer].vertices += snapshot.vertices; - _counters_per_constant_buffer[buffer].drawcalls += snapshot.drawcalls; - _counters_per_constant_buffer[buffer].ps_uses += snapshot.ps_uses; - _counters_per_constant_buffer[buffer].vs_uses += snapshot.vs_uses; - } -#endif - } - - void draw_call_tracker::reset() - { - _global_counter.vertices = 0; - _global_counter.drawcalls = 0; -#if RESHADE_DX11_CAPTURE_DEPTH_BUFFERS - _counters_per_used_depthstencil.clear(); - _cleared_depth_textures.clear(); -#endif -#if RESHADE_DX11_CAPTURE_CONSTANT_BUFFERS - _counters_per_constant_buffer.clear(); -#endif - } - - void draw_call_tracker::on_map(ID3D11Resource *resource) - { - UNREFERENCED_PARAMETER(resource); - -#if RESHADE_DX11_CAPTURE_CONSTANT_BUFFERS - D3D11_RESOURCE_DIMENSION dim; - resource->GetType(&dim); - - if (dim == D3D11_RESOURCE_DIMENSION_BUFFER) - _counters_per_constant_buffer[static_cast(resource)].mapped += 1; -#endif - } - - void draw_call_tracker::on_draw(ID3D11DeviceContext *context, UINT vertices) - { - UNREFERENCED_PARAMETER(context); - - _global_counter.vertices += vertices; - _global_counter.drawcalls += 1; - -#if RESHADE_DX11_CAPTURE_DEPTH_BUFFERS - com_ptr targets[D3D11_SIMULTANEOUS_RENDER_TARGET_COUNT]; - com_ptr depthstencil; - - context->OMGetRenderTargets(D3D11_SIMULTANEOUS_RENDER_TARGET_COUNT, reinterpret_cast(targets), &depthstencil); - - if (depthstencil == nullptr) - // This is a draw call with no depth stencil - return; - - if (const auto intermediate_snapshot = _counters_per_used_depthstencil.find(depthstencil); intermediate_snapshot != _counters_per_used_depthstencil.end()) - { - intermediate_snapshot->second.stats.vertices += vertices; - intermediate_snapshot->second.stats.drawcalls += 1; - - // Find the render targets, if they exist, and update their counts - for (UINT i = 0; i < D3D11_SIMULTANEOUS_RENDER_TARGET_COUNT; i++) - { - // Ignore empty slots - if (targets[i] == nullptr) - continue; - - if (const auto it = intermediate_snapshot->second.additional_views.find(targets[i].get()); it != intermediate_snapshot->second.additional_views.end()) - { - it->second.vertices += vertices; - it->second.drawcalls += 1; - } - else - { - // This shouldn't happen - it means somehow someone has called 'on_draw' with a render target without calling 'track_rendertargets' first - assert(false); - } - } - } -#endif -#if RESHADE_DX11_CAPTURE_CONSTANT_BUFFERS - // Capture constant buffers that are used when depth stencils are drawn - com_ptr vscbuffers[D3D11_COMMONSHADER_CONSTANT_BUFFER_API_SLOT_COUNT]; - context->VSGetConstantBuffers(0, ARRAYSIZE(vscbuffers), reinterpret_cast(vscbuffers)); - - for (UINT i = 0; i < ARRAYSIZE(vscbuffers); i++) - // Uses the default drawcalls = 0 the first time around. - if (vscbuffers[i] != nullptr) - _counters_per_constant_buffer[vscbuffers[i]].vs_uses += 1; - - com_ptr pscbuffers[D3D11_COMMONSHADER_CONSTANT_BUFFER_API_SLOT_COUNT]; - context->PSGetConstantBuffers(0, ARRAYSIZE(pscbuffers), reinterpret_cast(pscbuffers)); - - for (UINT i = 0; i < ARRAYSIZE(pscbuffers); i++) - // Uses the default drawcalls = 0 the first time around. - if (pscbuffers[i] != nullptr) - _counters_per_constant_buffer[pscbuffers[i]].ps_uses += 1; -#endif - } - -#if RESHADE_DX11_CAPTURE_DEPTH_BUFFERS - bool draw_call_tracker::check_depthstencil(ID3D11DepthStencilView *depthstencil) const - { - return _counters_per_used_depthstencil.find(depthstencil) != _counters_per_used_depthstencil.end(); - } - bool draw_call_tracker::check_depth_texture_format(int format_index, ID3D11DepthStencilView *depthstencil) - { - assert(depthstencil != nullptr); - - // Do not check format if all formats are allowed (index zero is DXGI_FORMAT_UNKNOWN) - if (format_index == 0) - return true; - - // Retrieve texture from depth stencil - com_ptr resource; - com_ptr texture; - depthstencil->GetResource(&resource); - if (FAILED(resource->QueryInterface(&texture))) - return false; - - D3D11_TEXTURE2D_DESC desc; - texture->GetDesc(&desc); - - const DXGI_FORMAT depth_texture_formats[] = { - DXGI_FORMAT_UNKNOWN, - DXGI_FORMAT_R16_TYPELESS, - DXGI_FORMAT_R32_TYPELESS, - DXGI_FORMAT_R24G8_TYPELESS, - DXGI_FORMAT_R32G8X24_TYPELESS - }; - - assert(format_index > 0 && format_index < ARRAYSIZE(depth_texture_formats)); - - return make_dxgi_format_typeless(desc.Format) == depth_texture_formats[format_index]; - } - - void draw_call_tracker::track_rendertargets(int format_index, ID3D11DepthStencilView *depthstencil, UINT num_views, ID3D11RenderTargetView *const *views) - { - assert(depthstencil != nullptr); - - if (!check_depth_texture_format(format_index, depthstencil)) - return; - - if (_counters_per_used_depthstencil[depthstencil].depthstencil == nullptr) - _counters_per_used_depthstencil[depthstencil].depthstencil = depthstencil; - - for (UINT i = 0; i < num_views; i++) - // If the render target isn't being tracked, this will create it - _counters_per_used_depthstencil[depthstencil].additional_views[views[i]].drawcalls += 1; - } - void draw_call_tracker::track_depth_texture(int format_index, UINT index, com_ptr src_texture, com_ptr src_depthstencil, com_ptr dest_texture, bool cleared) - { - // Function that keeps track of a cleared depth texture in an ordered map in order to retrieve it at the final rendering stage - assert(src_texture != nullptr); - - if (!check_depth_texture_format(format_index, src_depthstencil.get())) - return; - - // Gather some extra info for later display - D3D11_TEXTURE2D_DESC src_texture_desc; - src_texture->GetDesc(&src_texture_desc); - - // check if it is really a depth texture - assert((src_texture_desc.BindFlags & D3D11_BIND_DEPTH_STENCIL) != 0); - - // fill the ordered map with the saved depth texture - if (const auto it = _cleared_depth_textures.find(index); it == _cleared_depth_textures.end()) - _cleared_depth_textures.emplace(index, depth_texture_save_info { src_texture, src_depthstencil, src_texture_desc, dest_texture, cleared }); - else - it->second = depth_texture_save_info { src_texture, src_depthstencil, src_texture_desc, dest_texture, cleared }; - } - - draw_call_tracker::intermediate_snapshot_info draw_call_tracker::find_best_snapshot(UINT width, UINT height) - { - const float aspect_ratio = float(width) / float(height); - intermediate_snapshot_info best_snapshot; - - for (auto &[depthstencil, snapshot] : _counters_per_used_depthstencil) - { - if (snapshot.stats.drawcalls == 0 || snapshot.stats.vertices == 0) - continue; - - if (snapshot.texture == nullptr) - { - com_ptr resource; - depthstencil->GetResource(&resource); - if (FAILED(resource->QueryInterface(&snapshot.texture))) - continue; - } - - D3D11_TEXTURE2D_DESC desc; - snapshot.texture->GetDesc(&desc); - - assert((desc.BindFlags & D3D11_BIND_DEPTH_STENCIL) != 0); - - // Check aspect ratio - const float width_factor = desc.Width != width ? float(width) / desc.Width : 1.0f; - const float height_factor = desc.Height != height ? float(height) / desc.Height : 1.0f; - const float texture_aspect_ratio = float(desc.Width) / float(desc.Height); - - if (fabs(texture_aspect_ratio - aspect_ratio) > 0.1f || width_factor > 2.0f || height_factor > 2.0f || width_factor < 0.5f || height_factor < 0.5f) - continue; // No match, not a good fit - - if (snapshot.stats.drawcalls >= best_snapshot.stats.drawcalls) - best_snapshot = snapshot; - } - - return best_snapshot; - } - - void draw_call_tracker::keep_cleared_depth_textures() - { - // Function that keeps only the depth textures that has been retrieved before the last depth stencil clearance - std::map::reverse_iterator it = _cleared_depth_textures.rbegin(); - - // Reverse loop on the cleared depth textures map - while (it != _cleared_depth_textures.rend()) - { - // Exit if the last cleared depth stencil is found - if (it->second.cleared) - return; - - // Remove the depth texture if it was retrieved after the last clearance of the depth stencil - it = std::map::reverse_iterator(_cleared_depth_textures.erase(std::next(it).base())); - } - } - - ID3D11Texture2D *draw_call_tracker::find_best_cleared_depth_buffer_texture(UINT clear_index) - { - // Function that selects the best cleared depth texture according to the clearing number defined in the configuration settings - ID3D11Texture2D *best_match = nullptr; - - // Ensure to work only on the depth textures retrieved before the last depth stencil clearance - keep_cleared_depth_textures(); - - for (const auto &it : _cleared_depth_textures) - { - UINT i = it.first; - auto &texture_counter_info = it.second; - - com_ptr texture; - if (texture_counter_info.dest_texture == nullptr) - continue; - texture = texture_counter_info.dest_texture; - - if (clear_index != 0 && i > clear_index) - continue; - - // The _cleared_dept_textures ordered map stores the depth textures, according to the order of clearing - // if clear_index == 0, the auto select mode is defined, so the last cleared depth texture is retrieved - // if the user selects a clearing number and the number of cleared depth textures is greater or equal than it, the texture corresponding to this number is retrieved - // if the user selects a clearing number and the number of cleared depth textures is lower than it, the last cleared depth texture is retrieved - best_match = texture.get(); - } - - return best_match; - } -#endif -} diff --git a/msvc/source/d3d11/draw_call_tracker.hpp b/msvc/source/d3d11/draw_call_tracker.hpp deleted file mode 100644 index fa97511..0000000 --- a/msvc/source/d3d11/draw_call_tracker.hpp +++ /dev/null @@ -1,86 +0,0 @@ -#pragma once - -#include -#include -#include "com_ptr.hpp" - -#define RESHADE_DX11_CAPTURE_DEPTH_BUFFERS 1 -#define RESHADE_DX11_CAPTURE_CONSTANT_BUFFERS 0 - -namespace reshade::d3d11 -{ - class draw_call_tracker - { - public: - struct draw_stats - { - UINT vertices = 0; - UINT drawcalls = 0; - UINT mapped = 0; - UINT vs_uses = 0; - UINT ps_uses = 0; - }; - -#if RESHADE_DX11_CAPTURE_DEPTH_BUFFERS - struct intermediate_snapshot_info - { - ID3D11DepthStencilView *depthstencil = nullptr; // No need to use a 'com_ptr' here since '_counters_per_used_depthstencil' already keeps a reference - draw_stats stats; - com_ptr texture; - std::map additional_views; - }; -#endif - - UINT total_vertices() const { return _global_counter.vertices; } - UINT total_drawcalls() const { return _global_counter.drawcalls; } - -#if RESHADE_DX11_CAPTURE_DEPTH_BUFFERS - const auto &depth_buffer_counters() const { return _counters_per_used_depthstencil; } - const auto &cleared_depth_textures() const { return _cleared_depth_textures; } -#endif -#if RESHADE_DX11_CAPTURE_CONSTANT_BUFFERS - const auto &constant_buffer_counters() const { return _counters_per_constant_buffer; } -#endif - - void merge(const draw_call_tracker &source); - void reset(); - - void on_map(ID3D11Resource *pResource); - void on_draw(ID3D11DeviceContext *context, UINT vertices); - -#if RESHADE_DX11_CAPTURE_DEPTH_BUFFERS - void track_rendertargets(int format_index, ID3D11DepthStencilView *depthstencil, UINT num_views, ID3D11RenderTargetView *const *views); - void track_depth_texture(int format_index, UINT index, com_ptr src_texture, com_ptr src_depthstencil, com_ptr dest_texture, bool cleared); - - void keep_cleared_depth_textures(); - - intermediate_snapshot_info find_best_snapshot(UINT width, UINT height); - ID3D11Texture2D *find_best_cleared_depth_buffer_texture(UINT clear_index); -#endif - - private: - struct depth_texture_save_info - { - com_ptr src_texture; - com_ptr src_depthstencil; - D3D11_TEXTURE2D_DESC src_texture_desc; - com_ptr dest_texture; - bool cleared = false; - }; - -#if RESHADE_DX11_CAPTURE_DEPTH_BUFFERS - bool check_depthstencil(ID3D11DepthStencilView *depthstencil) const; - bool check_depth_texture_format(int format_index, ID3D11DepthStencilView *depthstencil); -#endif - - draw_stats _global_counter; -#if RESHADE_DX11_CAPTURE_DEPTH_BUFFERS - // Use "std::map" instead of "std::unordered_map" so that the iteration order is guaranteed - std::map, intermediate_snapshot_info> _counters_per_used_depthstencil; - std::map _cleared_depth_textures; -#endif -#if RESHADE_DX11_CAPTURE_CONSTANT_BUFFERS - std::map, draw_stats> _counters_per_constant_buffer; -#endif - }; -} diff --git a/msvc/source/d3d11/runtime_d3d11.cpp b/msvc/source/d3d11/runtime_d3d11.cpp deleted file mode 100644 index d2b12c3..0000000 --- a/msvc/source/d3d11/runtime_d3d11.cpp +++ /dev/null @@ -1,1596 +0,0 @@ -/** - * Copyright (C) 2014 Patrick Mours. All rights reserved. - * License: https://github.com/crosire/reshade#license - */ - -#include "log.hpp" -#include "ini_file.hpp" -#include "runtime_d3d11.hpp" -#include "runtime_objects.hpp" -#include "resource_loading.hpp" -#include "dxgi/format_utils.hpp" -#include -#include - -namespace reshade::d3d11 -{ - struct d3d11_tex_data : base_object - { - com_ptr texture; - com_ptr srv[2]; - com_ptr rtv[2]; - }; - struct d3d11_pass_data : base_object - { - com_ptr vertex_shader; - com_ptr pixel_shader; - com_ptr blend_state; - com_ptr depth_stencil_state; - UINT stencil_reference; - bool clear_render_targets; - com_ptr render_targets[D3D11_SIMULTANEOUS_RENDER_TARGET_COUNT]; - com_ptr render_target_resources[D3D11_SIMULTANEOUS_RENDER_TARGET_COUNT]; - D3D11_VIEWPORT viewport; - std::vector> shader_resources; - }; - struct d3d11_technique_data : base_object - { - bool query_in_flight = false; - com_ptr timestamp_disjoint; - com_ptr timestamp_query_beg; - com_ptr timestamp_query_end; - std::vector> sampler_states; - std::vector> texture_bindings; - ptrdiff_t uniform_storage_offset = 0; - ptrdiff_t uniform_storage_index = -1; - }; -} - -reshade::d3d11::runtime_d3d11::runtime_d3d11(ID3D11Device *device, IDXGISwapChain *swapchain) : - _device(device), _swapchain(swapchain), - _app_state(device) -{ - assert(device != nullptr); - assert(swapchain != nullptr); - - _device->GetImmediateContext(&_immediate_context); - - com_ptr dxgi_device; - _device->QueryInterface(&dxgi_device); - com_ptr dxgi_adapter; - dxgi_device->GetAdapter(&dxgi_adapter); - - _renderer_id = device->GetFeatureLevel(); - if (DXGI_ADAPTER_DESC desc; SUCCEEDED(dxgi_adapter->GetDesc(&desc))) - _vendor_id = desc.VendorId, _device_id = desc.DeviceId; - -#if RESHADE_GUI - subscribe_to_ui("DX11", [this]() { draw_debug_menu(); }); -#endif - subscribe_to_load_config([this](const ini_file &config) { - config.get("DX11_BUFFER_DETECTION", "DepthBufferRetrievalMode", depth_buffer_before_clear); - config.get("DX11_BUFFER_DETECTION", "DepthBufferTextureFormat", depth_buffer_texture_format); - config.get("DX11_BUFFER_DETECTION", "ExtendedDepthBufferDetection", extended_depth_buffer_detection); - config.get("DX11_BUFFER_DETECTION", "DepthBufferClearingNumber", cleared_depth_buffer_index); - }); - subscribe_to_save_config([this](ini_file &config) { - config.set("DX11_BUFFER_DETECTION", "DepthBufferRetrievalMode", depth_buffer_before_clear); - config.set("DX11_BUFFER_DETECTION", "DepthBufferTextureFormat", depth_buffer_texture_format); - config.set("DX11_BUFFER_DETECTION", "ExtendedDepthBufferDetection", extended_depth_buffer_detection); - config.set("DX11_BUFFER_DETECTION", "DepthBufferClearingNumber", cleared_depth_buffer_index); - }); -} -reshade::d3d11::runtime_d3d11::~runtime_d3d11() -{ - if (_d3d_compiler != nullptr) - FreeLibrary(_d3d_compiler); -} - -bool reshade::d3d11::runtime_d3d11::init_backbuffer_texture() -{ - HRESULT hr = _swapchain->GetBuffer(0, IID_PPV_ARGS(&_backbuffer)); - assert(SUCCEEDED(hr)); - - D3D11_TEXTURE2D_DESC tex_desc = {}; - tex_desc.Width = _width; - tex_desc.Height = _height; - tex_desc.MipLevels = 1; - tex_desc.ArraySize = 1; - tex_desc.Format = make_dxgi_format_typeless(_backbuffer_format); - tex_desc.SampleDesc = { 1, 0 }; - tex_desc.Usage = D3D11_USAGE_DEFAULT; - tex_desc.BindFlags = D3D11_BIND_RENDER_TARGET; - - // Creating a render target view for the back buffer fails on Windows 8+, so use a intermediate texture there - OSVERSIONINFOEX verinfo_windows7 = { sizeof(OSVERSIONINFOEX), 6, 1 }; - const bool is_windows7 = VerifyVersionInfo(&verinfo_windows7, VER_MAJORVERSION | VER_MINORVERSION, - VerSetConditionMask(VerSetConditionMask(0, VER_MAJORVERSION, VER_EQUAL), VER_MINORVERSION, VER_EQUAL)) != FALSE; - - if (_is_multisampling_enabled || - make_dxgi_format_normal(_backbuffer_format) != _backbuffer_format || - !is_windows7) - { - if (hr = _device->CreateTexture2D(&tex_desc, nullptr, &_backbuffer_resolved); FAILED(hr)) - { - LOG(ERROR) << "Failed to create back buffer resolve texture (" - "Width = " << tex_desc.Width << ", " - "Height = " << tex_desc.Height << ", " - "Format = " << tex_desc.Format << ")! HRESULT is '" << std::hex << hr << std::dec << "'."; - return false; - } - - hr = _device->CreateRenderTargetView(_backbuffer.get(), nullptr, &_backbuffer_rtv[2]); - assert(SUCCEEDED(hr)); - } - else - { - _backbuffer_resolved = _backbuffer; - } - - // Create back buffer shader texture - tex_desc.BindFlags = D3D11_BIND_SHADER_RESOURCE; - - if (hr = _device->CreateTexture2D(&tex_desc, nullptr, &_backbuffer_texture); SUCCEEDED(hr)) - { - D3D11_SHADER_RESOURCE_VIEW_DESC srv_desc = {}; - srv_desc.Format = make_dxgi_format_normal(tex_desc.Format); - srv_desc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D; - srv_desc.Texture2D.MipLevels = tex_desc.MipLevels; - - if (SUCCEEDED(hr)) - hr = _device->CreateShaderResourceView(_backbuffer_texture.get(), &srv_desc, &_backbuffer_texture_srv[0]); - else - LOG(ERROR) << "Failed to create back buffer texture resource view (" - "Format = " << srv_desc.Format << ")! HRESULT is '" << std::hex << hr << std::dec << "'."; - - srv_desc.Format = make_dxgi_format_srgb(tex_desc.Format); - - if (SUCCEEDED(hr)) - hr = _device->CreateShaderResourceView(_backbuffer_texture.get(), &srv_desc, &_backbuffer_texture_srv[1]); - else - LOG(ERROR) << "Failed to create back buffer SRGB texture resource view (" - "Format = " << srv_desc.Format << ")! HRESULT is '" << std::hex << hr << std::dec << "'."; - } - else - { - LOG(ERROR) << "Failed to create back buffer texture (" - "Width = " << tex_desc.Width << ", " - "Height = " << tex_desc.Height << ", " - "Format = " << tex_desc.Format << ")! HRESULT is '" << std::hex << hr << std::dec << "'."; - } - - if (FAILED(hr)) - return false; - - D3D11_RENDER_TARGET_VIEW_DESC rtv_desc = {}; - rtv_desc.Format = make_dxgi_format_normal(tex_desc.Format); - rtv_desc.ViewDimension = D3D11_RTV_DIMENSION_TEXTURE2D; - - if (hr = _device->CreateRenderTargetView(_backbuffer_resolved.get(), &rtv_desc, &_backbuffer_rtv[0]); FAILED(hr)) - { - LOG(ERROR) << "Failed to create back buffer render target (" - "Format = " << rtv_desc.Format << ")! HRESULT is '" << std::hex << hr << std::dec << "'."; - return false; - } - - rtv_desc.Format = make_dxgi_format_srgb(tex_desc.Format); - - if (hr = _device->CreateRenderTargetView(_backbuffer_resolved.get(), &rtv_desc, &_backbuffer_rtv[1]); FAILED(hr)) - { - LOG(ERROR) << "Failed to create back buffer SRGB render target (" - "Format = " << rtv_desc.Format << ")! HRESULT is '" << std::hex << hr << std::dec << "'."; - return false; - } - - const resources::data_resource vs = resources::load_data_resource(IDR_FULLSCREEN_VS); - if (hr = _device->CreateVertexShader(vs.data, vs.data_size, nullptr, &_copy_vertex_shader); FAILED(hr)) - return false; - const resources::data_resource ps = resources::load_data_resource(IDR_COPY_PS); - if (hr = _device->CreatePixelShader(ps.data, ps.data_size, nullptr, &_copy_pixel_shader); FAILED(hr)) - return false; - - { D3D11_SAMPLER_DESC desc = {}; - desc.Filter = D3D11_FILTER_MIN_MAG_MIP_POINT; - desc.AddressU = D3D11_TEXTURE_ADDRESS_CLAMP; - desc.AddressV = D3D11_TEXTURE_ADDRESS_CLAMP; - desc.AddressW = D3D11_TEXTURE_ADDRESS_CLAMP; - if (hr = _device->CreateSamplerState(&desc, &_copy_sampler); FAILED(hr)) - return false; - } - - { D3D11_RASTERIZER_DESC desc = {}; - desc.FillMode = D3D11_FILL_SOLID; - desc.CullMode = D3D11_CULL_NONE; - desc.DepthClipEnable = TRUE; - if (hr = _device->CreateRasterizerState(&desc, &_effect_rasterizer_state); FAILED(hr)) - return false; - } - - return true; -} -bool reshade::d3d11::runtime_d3d11::init_default_depth_stencil() -{ - const D3D11_TEXTURE2D_DESC tex_desc = { - _width, - _height, - 1, 1, - DXGI_FORMAT_D24_UNORM_S8_UINT, - { 1, 0 }, - D3D11_USAGE_DEFAULT, - D3D11_BIND_DEPTH_STENCIL - }; - - com_ptr depth_stencil_texture; - - if (HRESULT hr = _device->CreateTexture2D(&tex_desc, nullptr, &depth_stencil_texture); FAILED(hr)) - { - LOG(ERROR) << "Failed to create depth stencil texture (" - "Width = " << tex_desc.Width << ", " - "Height = " << tex_desc.Height << ", " - "Format = " << tex_desc.Format << ")! HRESULT is '" << std::hex << hr << std::dec << "'."; - return false; - } - - return SUCCEEDED(_device->CreateDepthStencilView(depth_stencil_texture.get(), nullptr, &_default_depthstencil)); -} - -bool reshade::d3d11::runtime_d3d11::on_init(const DXGI_SWAP_CHAIN_DESC &desc) -{ - RECT window_rect = {}; - GetClientRect(desc.OutputWindow, &window_rect); - - _width = desc.BufferDesc.Width; - _height = desc.BufferDesc.Height; - _window_width = window_rect.right - window_rect.left; - _window_height = window_rect.bottom - window_rect.top; - _backbuffer_format = desc.BufferDesc.Format; - _is_multisampling_enabled = desc.SampleDesc.Count > 1; - - if (!init_backbuffer_texture() || - !init_default_depth_stencil() -#if RESHADE_GUI - || !init_imgui_resources() -#endif - ) - return false; - - // Clear reference count to make UnrealEngine happy - _backbuffer->Release(); - - return runtime::on_init(desc.OutputWindow); -} -void reshade::d3d11::runtime_d3d11::on_reset() -{ - runtime::on_reset(); - - // Reset reference count to make UnrealEngine happy - _backbuffer->AddRef(); - - _backbuffer.reset(); - _backbuffer_resolved.reset(); - _backbuffer_texture.reset(); - _backbuffer_texture_srv[0].reset(); - _backbuffer_texture_srv[1].reset(); - _backbuffer_rtv[0].reset(); - _backbuffer_rtv[1].reset(); - _backbuffer_rtv[2].reset(); - - _depthstencil.reset(); - _depthstencil_replacement.reset(); - _depthstencil_texture.reset(); - _depthstencil_texture_srv.reset(); - - _depth_texture_saves.clear(); - - _default_depthstencil.reset(); - _copy_vertex_shader.reset(); - _copy_pixel_shader.reset(); - _copy_sampler.reset(); - - _effect_rasterizer_state.reset(); - - _imgui_index_buffer_size = 0; - _imgui_index_buffer.reset(); - _imgui_vertex_buffer_size = 0; - _imgui_vertex_buffer.reset(); - _imgui_vertex_shader.reset(); - _imgui_pixel_shader.reset(); - _imgui_input_layout.reset(); - _imgui_constant_buffer.reset(); - _imgui_texture_sampler.reset(); - _imgui_rasterizer_state.reset(); - _imgui_blend_state.reset(); - _imgui_depthstencil_state.reset(); -} - -void reshade::d3d11::runtime_d3d11::on_present(draw_call_tracker &tracker) -{ - if (!_is_initialized) - return; - - _vertices = tracker.total_vertices(); - _drawcalls = tracker.total_drawcalls(); - _current_tracker = &tracker; - -#if RESHADE_DX11_CAPTURE_DEPTH_BUFFERS - detect_depth_source(tracker); -#endif - _app_state.capture(_immediate_context.get()); - - // Resolve MSAA back buffer if MSAA is active - if (_backbuffer_resolved != _backbuffer) - _immediate_context->ResolveSubresource(_backbuffer_resolved.get(), 0, _backbuffer.get(), 0, _backbuffer_format); - - update_and_render_effects(); - runtime::on_present(); - - // Stretch main render target back into MSAA back buffer if MSAA is active - if (_backbuffer_resolved != _backbuffer) - { - _immediate_context->CopyResource(_backbuffer_texture.get(), _backbuffer_resolved.get()); - - _immediate_context->IASetInputLayout(nullptr); - const uintptr_t null = 0; - _immediate_context->IASetVertexBuffers(0, 1, reinterpret_cast(&null), reinterpret_cast(&null), reinterpret_cast(&null)); - _immediate_context->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST); - _immediate_context->VSSetShader(_copy_vertex_shader.get(), nullptr, 0); - _immediate_context->HSSetShader(nullptr, nullptr, 0); - _immediate_context->DSSetShader(nullptr, nullptr, 0); - _immediate_context->GSSetShader(nullptr, nullptr, 0); - _immediate_context->PSSetShader(_copy_pixel_shader.get(), nullptr, 0); - ID3D11SamplerState *const samplers[] = { _copy_sampler.get() }; - _immediate_context->PSSetSamplers(0, ARRAYSIZE(samplers), samplers); - ID3D11ShaderResourceView *const srvs[] = { _backbuffer_texture_srv[make_dxgi_format_srgb(_backbuffer_format) == _backbuffer_format].get() }; - _immediate_context->PSSetShaderResources(0, ARRAYSIZE(srvs), srvs); - _immediate_context->RSSetState(_effect_rasterizer_state.get()); - const D3D11_VIEWPORT viewport = { 0, 0, FLOAT(_width), FLOAT(_height), 0.0f, 1.0f }; - _immediate_context->RSSetViewports(1, &viewport); - _immediate_context->OMSetBlendState(nullptr, nullptr, D3D11_DEFAULT_SAMPLE_MASK); - _immediate_context->OMSetDepthStencilState(nullptr, D3D11_DEFAULT_STENCIL_REFERENCE); - ID3D11RenderTargetView *const render_targets[] = { _backbuffer_rtv[2].get() }; - _immediate_context->OMSetRenderTargets(ARRAYSIZE(render_targets), render_targets, nullptr); - - _immediate_context->Draw(3, 0); - } - - // Apply previous state from application - _app_state.apply_and_release(); -} - -void reshade::d3d11::runtime_d3d11::capture_screenshot(uint8_t *buffer) const -{ - if (_backbuffer_format != DXGI_FORMAT_R8G8B8A8_UNORM && - _backbuffer_format != DXGI_FORMAT_R8G8B8A8_UNORM_SRGB && - _backbuffer_format != DXGI_FORMAT_B8G8R8A8_UNORM && - _backbuffer_format != DXGI_FORMAT_B8G8R8A8_UNORM_SRGB) - { - LOG(WARN) << "Screenshots are not supported for back buffer format " << _backbuffer_format << '.'; - return; - } - - // Create a texture in system memory, copy back buffer data into it and map it for reading - D3D11_TEXTURE2D_DESC desc = {}; - desc.Width = _width; - desc.Height = _height; - desc.MipLevels = 1; - desc.ArraySize = 1; - desc.Format = _backbuffer_format; - desc.SampleDesc = { 1, 0 }; - desc.Usage = D3D11_USAGE_STAGING; - desc.CPUAccessFlags = D3D11_CPU_ACCESS_READ; - - com_ptr intermediate; - if (FAILED(_device->CreateTexture2D(&desc, nullptr, &intermediate))) - { - LOG(ERROR) << "Failed to create system memory texture for screenshot capture!"; - return; - } - - _immediate_context->CopyResource(intermediate.get(), _backbuffer_resolved.get()); - - D3D11_MAPPED_SUBRESOURCE mapped; - if (FAILED(_immediate_context->Map(intermediate.get(), 0, D3D11_MAP_READ, 0, &mapped))) - return; - auto mapped_data = static_cast(mapped.pData); - - for (uint32_t y = 0, pitch = _width * 4; y < _height; y++, buffer += pitch, mapped_data += mapped.RowPitch) - { - memcpy(buffer, mapped_data, pitch); - - for (uint32_t x = 0; x < pitch; x += 4) - { - buffer[x + 3] = 0xFF; // Clear alpha channel - if (_backbuffer_format == DXGI_FORMAT_B8G8R8A8_UNORM || _backbuffer_format == DXGI_FORMAT_B8G8R8A8_UNORM_SRGB) - std::swap(buffer[x + 0], buffer[x + 2]); // Format is BGRA, but output should be RGBA, so flip channels - } - } - - _immediate_context->Unmap(intermediate.get(), 0); -} - -bool reshade::d3d11::runtime_d3d11::init_texture(texture &info) -{ - info.impl = std::make_unique(); - - if (info.impl_reference != texture_reference::none) - return update_texture_reference(info); - - D3D11_TEXTURE2D_DESC desc = {}; - desc.Width = info.width; - desc.Height = info.height; - desc.MipLevels = info.levels; - desc.ArraySize = 1; - desc.SampleDesc = { 1, 0 }; - desc.Usage = D3D11_USAGE_DEFAULT; - desc.BindFlags = D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_RENDER_TARGET; - desc.MiscFlags = D3D11_RESOURCE_MISC_GENERATE_MIPS; - - switch (info.format) - { - case reshadefx::texture_format::r8: - desc.Format = DXGI_FORMAT_R8_UNORM; - break; - case reshadefx::texture_format::r16f: - desc.Format = DXGI_FORMAT_R16_FLOAT; - break; - case reshadefx::texture_format::r32f: - desc.Format = DXGI_FORMAT_R32_FLOAT; - break; - case reshadefx::texture_format::rg8: - desc.Format = DXGI_FORMAT_R8G8_UNORM; - break; - case reshadefx::texture_format::rg16: - desc.Format = DXGI_FORMAT_R16G16_UNORM; - break; - case reshadefx::texture_format::rg16f: - desc.Format = DXGI_FORMAT_R16G16_FLOAT; - break; - case reshadefx::texture_format::rg32f: - desc.Format = DXGI_FORMAT_R32G32_FLOAT; - break; - case reshadefx::texture_format::rgba8: - desc.Format = DXGI_FORMAT_R8G8B8A8_TYPELESS; - break; - case reshadefx::texture_format::rgba16: - desc.Format = DXGI_FORMAT_R16G16B16A16_UNORM; - break; - case reshadefx::texture_format::rgba16f: - desc.Format = DXGI_FORMAT_R16G16B16A16_FLOAT; - break; - case reshadefx::texture_format::rgba32f: - desc.Format = DXGI_FORMAT_R32G32B32A32_FLOAT; - break; - case reshadefx::texture_format::rgb10a2: - desc.Format = DXGI_FORMAT_R10G10B10A2_UNORM; - break; - } - - const auto texture_data = info.impl->as(); - - if (HRESULT hr = _device->CreateTexture2D(&desc, nullptr, &texture_data->texture); FAILED(hr)) - { - LOG(ERROR) << "Failed to create texture '" << info.unique_name << "' (" - "Width = " << desc.Width << ", " - "Height = " << desc.Height << ", " - "Format = " << desc.Format << ")! " - "HRESULT is '" << std::hex << hr << std::dec << "'."; - return false; - } - - D3D11_SHADER_RESOURCE_VIEW_DESC srv_desc = {}; - srv_desc.Format = make_dxgi_format_normal(desc.Format); - srv_desc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D; - srv_desc.Texture2D.MipLevels = desc.MipLevels; - - if (HRESULT hr = _device->CreateShaderResourceView(texture_data->texture.get(), &srv_desc, &texture_data->srv[0]); FAILED(hr)) - { - LOG(ERROR) << "Failed to create shader resource view for texture '" << info.unique_name << "' (" - "Format = " << srv_desc.Format << ")! " - "HRESULT is '" << std::hex << hr << std::dec << "'."; - return false; - } - - srv_desc.Format = make_dxgi_format_srgb(desc.Format); - - if (srv_desc.Format != desc.Format) - { - if (HRESULT hr = _device->CreateShaderResourceView(texture_data->texture.get(), &srv_desc, &texture_data->srv[1]); FAILED(hr)) - { - LOG(ERROR) << "Failed to create shader resource view for texture '" << info.unique_name << "' (" - "Format = " << srv_desc.Format << ")! " - "HRESULT is '" << std::hex << hr << std::dec << "'."; - return false; - } - } - else - { - texture_data->srv[1] = texture_data->srv[0]; - } - - return true; -} -void reshade::d3d11::runtime_d3d11::upload_texture(texture &texture, const uint8_t *pixels) -{ - assert(texture.impl_reference == texture_reference::none && pixels != nullptr); - - unsigned int upload_pitch; - std::vector upload_data; - - switch (texture.format) - { - case reshadefx::texture_format::r8: - upload_pitch = texture.width; - upload_data.resize(upload_pitch * texture.height); - for (uint32_t i = 0, k = 0; i < texture.width * texture.height * 4; i += 4, k += 1) - upload_data[k] = pixels[i]; - pixels = upload_data.data(); - break; - case reshadefx::texture_format::rg8: - upload_pitch = texture.width * 2; - upload_data.resize(upload_pitch * texture.height); - for (uint32_t i = 0, k = 0; i < texture.width * texture.height * 4; i += 4, k += 2) - upload_data[k + 0] = pixels[i + 0], - upload_data[k + 1] = pixels[i + 1]; - pixels = upload_data.data(); - break; - case reshadefx::texture_format::rgba8: - upload_pitch = texture.width * 4; - break; - default: - LOG(ERROR) << "Texture upload is not supported for format " << static_cast(texture.format) << '!'; - return; - } - - const auto texture_impl = texture.impl->as(); - assert(texture_impl != nullptr); - - _immediate_context->UpdateSubresource(texture_impl->texture.get(), 0, nullptr, pixels, upload_pitch, upload_pitch * texture.height); - - if (texture.levels > 1) - _immediate_context->GenerateMips(texture_impl->srv[0].get()); -} -bool reshade::d3d11::runtime_d3d11::update_texture_reference(texture &texture) -{ - com_ptr new_reference[2]; - - switch (texture.impl_reference) - { - case texture_reference::back_buffer: - new_reference[0] = _backbuffer_texture_srv[0]; - new_reference[1] = _backbuffer_texture_srv[1]; - break; - case texture_reference::depth_buffer: - new_reference[0] = _depthstencil_texture_srv; - new_reference[1] = _depthstencil_texture_srv; - break; - default: - return false; - } - - const auto texture_impl = texture.impl->as(); - assert(texture_impl != nullptr); - - if (new_reference[0] == texture_impl->srv[0] && - new_reference[1] == texture_impl->srv[1]) - return true; - - // Update references in technique list - for (const auto &technique : _techniques) - for (const auto &pass : technique.passes_data) - for (auto &srv : pass->as()->shader_resources) - if (srv == texture_impl->srv[0]) srv = new_reference[0]; - else if (srv == texture_impl->srv[1]) srv = new_reference[1]; - - texture.width = frame_width(); - texture.height = frame_height(); - - texture_impl->srv[0] = new_reference[0]; - texture_impl->srv[1] = new_reference[1]; - - return true; -} -void reshade::d3d11::runtime_d3d11::update_texture_references(texture_reference type) -{ - for (auto &tex : _textures) - if (tex.impl != nullptr && tex.impl_reference == type) - update_texture_reference(tex); -} - -bool reshade::d3d11::runtime_d3d11::compile_effect(effect_data &effect) -{ - if (_d3d_compiler == nullptr) - _d3d_compiler = LoadLibraryW(L"d3dcompiler_47.dll"); - if (_d3d_compiler == nullptr) - _d3d_compiler = LoadLibraryW(L"d3dcompiler_43.dll"); - - if (_d3d_compiler == nullptr) - { - LOG(ERROR) << "Unable to load HLSL compiler (\"d3dcompiler_47.dll\"). Make sure you have the DirectX end-user runtime (June 2010) installed or a newer version of the library in the application directory."; - return false; - } - - const auto D3DCompile = reinterpret_cast(GetProcAddress(_d3d_compiler, "D3DCompile")); - - const std::string hlsl = effect.preamble + effect.module.hlsl; - std::unordered_map> entry_points; - - // Compile the generated HLSL source code to DX byte code - for (const auto &entry_point : effect.module.entry_points) - { - std::string profile = entry_point.second ? "ps" : "vs"; - - switch (_renderer_id) - { - default: - case D3D_FEATURE_LEVEL_11_0: - profile += "_5_0"; - break; - case D3D_FEATURE_LEVEL_10_1: - profile += "_4_1"; - break; - case D3D_FEATURE_LEVEL_10_0: - profile += "_4_0"; - break; - case D3D_FEATURE_LEVEL_9_1: - case D3D_FEATURE_LEVEL_9_2: - profile += "_4_0_level_9_1"; - break; - case D3D_FEATURE_LEVEL_9_3: - profile += "_4_0_level_9_3"; - break; - } - - com_ptr d3d_compiled, d3d_errors; - - HRESULT hr = D3DCompile( - hlsl.c_str(), hlsl.size(), - nullptr, nullptr, nullptr, - entry_point.first.c_str(), - profile.c_str(), - D3DCOMPILE_ENABLE_STRICTNESS | D3DCOMPILE_OPTIMIZATION_LEVEL3, 0, - &d3d_compiled, &d3d_errors); - - if (d3d_errors != nullptr) // Append warnings to the output error string as well - effect.errors.append(static_cast(d3d_errors->GetBufferPointer()), d3d_errors->GetBufferSize() - 1); // Subtracting one to not append the null-terminator as well - - // No need to setup resources if any of the shaders failed to compile - if (FAILED(hr)) - return false; - - // Create runtime shader objects from the compiled DX byte code - if (entry_point.second) - hr = _device->CreatePixelShader(d3d_compiled->GetBufferPointer(), d3d_compiled->GetBufferSize(), nullptr, reinterpret_cast(&entry_points[entry_point.first])); - else - hr = _device->CreateVertexShader(d3d_compiled->GetBufferPointer(), d3d_compiled->GetBufferSize(), nullptr, reinterpret_cast(&entry_points[entry_point.first])); - - if (FAILED(hr)) - { - LOG(ERROR) << "Failed to create shader for entry point '" << entry_point.first << "'. " - "HRESULT is '" << std::hex << hr << std::dec << "'."; - return false; - } - } - - if (effect.storage_size != 0) - { - com_ptr cbuffer; - - const D3D11_BUFFER_DESC desc = { static_cast(effect.storage_size), D3D11_USAGE_DYNAMIC, D3D11_BIND_CONSTANT_BUFFER, D3D11_CPU_ACCESS_WRITE }; - const D3D11_SUBRESOURCE_DATA init_data = { _uniform_data_storage.data() + effect.storage_offset, static_cast(effect.storage_size) }; - - if (const HRESULT hr = _device->CreateBuffer(&desc, &init_data, &cbuffer); FAILED(hr)) - { - LOG(ERROR) << "Failed to create constant buffer for effect file " << effect.source_file << ". " - "HRESULT is '" << std::hex << hr << std::dec << "'."; - return false; - } - - _constant_buffers.push_back(std::move(cbuffer)); - } - - bool success = true; - - d3d11_technique_data technique_init; - technique_init.uniform_storage_index = _constant_buffers.size() - 1; - technique_init.uniform_storage_offset = effect.storage_offset; - - for (const reshadefx::sampler_info &info : effect.module.samplers) - success &= add_sampler(info, technique_init); - - for (technique &technique : _techniques) - if (technique.impl == nullptr && technique.effect_index == effect.index) - success &= init_technique(technique, technique_init, entry_points); - - return success; -} -void reshade::d3d11::runtime_d3d11::unload_effects() -{ - runtime::unload_effects(); - - _effect_sampler_states.clear(); - _constant_buffers.clear(); -} - -bool reshade::d3d11::runtime_d3d11::add_sampler(const reshadefx::sampler_info &info, d3d11_technique_data &technique_init) -{ - if (info.binding >= D3D11_COMMONSHADER_SAMPLER_SLOT_COUNT) - { - LOG(ERROR) << "Cannot bind sampler '" << info.unique_name << "' since it exceeds the maximum number of allowed sampler slots in D3D11 (" << info.binding << ", allowed are up to " << D3D11_COMMONSHADER_SAMPLER_SLOT_COUNT << ")."; - return false; - } - if (info.texture_binding >= D3D11_COMMONSHADER_INPUT_RESOURCE_SLOT_COUNT) - { - LOG(ERROR) << "Cannot bind texture '" << info.texture_name << "' since it exceeds the maximum number of allowed resource slots in D3D11 (" << info.texture_binding << ", allowed are up to " << D3D11_COMMONSHADER_INPUT_RESOURCE_SLOT_COUNT << ")."; - return false; - } - - const auto existing_texture = std::find_if(_textures.begin(), _textures.end(), - [&texture_name = info.texture_name](const auto &item) { - return item.unique_name == texture_name && item.impl != nullptr; - }); - - if (existing_texture == _textures.end()) - return false; - - D3D11_SAMPLER_DESC desc = {}; - desc.Filter = static_cast(info.filter); - desc.AddressU = static_cast(info.address_u); - desc.AddressV = static_cast(info.address_v); - desc.AddressW = static_cast(info.address_w); - desc.MipLODBias = info.lod_bias; - desc.MaxAnisotropy = 1; - desc.ComparisonFunc = D3D11_COMPARISON_NEVER; - desc.MinLOD = info.min_lod; - desc.MaxLOD = info.max_lod; - - // Generate hash for sampler description - size_t desc_hash = 2166136261; - for (size_t i = 0; i < sizeof(desc); ++i) - desc_hash = (desc_hash * 16777619) ^ reinterpret_cast(&desc)[i]; - - auto it = _effect_sampler_states.find(desc_hash); - if (it == _effect_sampler_states.end()) - { - com_ptr sampler; - - HRESULT hr = _device->CreateSamplerState(&desc, &sampler); - - if (FAILED(hr)) - { - LOG(ERROR) << "Failed to create sampler state for sampler '" << info.unique_name << "' (" - "Filter = " << desc.Filter << ", " - "AddressU = " << desc.AddressU << ", " - "AddressV = " << desc.AddressV << ", " - "AddressW = " << desc.AddressW << ", " - "MipLODBias = " << desc.MipLODBias << ", " - "MinLOD = " << desc.MinLOD << ", " - "MaxLOD = " << desc.MaxLOD << ")! " - "HRESULT is '" << std::hex << hr << std::dec << "'."; - return false; - } - - it = _effect_sampler_states.emplace(desc_hash, std::move(sampler)).first; - } - - technique_init.sampler_states.resize(std::max(technique_init.sampler_states.size(), size_t(info.binding + 1))); - technique_init.texture_bindings.resize(std::max(technique_init.texture_bindings.size(), size_t(info.texture_binding + 1))); - - technique_init.sampler_states[info.binding] = it->second; - technique_init.texture_bindings[info.texture_binding] = existing_texture->impl->as()->srv[info.srgb ? 1 : 0]; - - return true; -} -bool reshade::d3d11::runtime_d3d11::init_technique(technique &technique, const d3d11_technique_data &impl_init, const std::unordered_map> &entry_points) -{ - // Copy construct new technique implementation instead of move because effect may contain multiple techniques - technique.impl = std::make_unique(impl_init); - - const auto technique_data = technique.impl->as(); - - D3D11_QUERY_DESC query_desc = {}; - query_desc.Query = D3D11_QUERY_TIMESTAMP; - _device->CreateQuery(&query_desc, &technique_data->timestamp_query_beg); - _device->CreateQuery(&query_desc, &technique_data->timestamp_query_end); - query_desc.Query = D3D11_QUERY_TIMESTAMP_DISJOINT; - _device->CreateQuery(&query_desc, &technique_data->timestamp_disjoint); - - for (size_t pass_index = 0; pass_index < technique.passes.size(); ++pass_index) - { - technique.passes_data.push_back(std::make_unique()); - - auto &pass = *technique.passes_data.back()->as(); - const auto &pass_info = technique.passes[pass_index]; - - entry_points.at(pass_info.ps_entry_point)->QueryInterface(&pass.pixel_shader); - entry_points.at(pass_info.vs_entry_point)->QueryInterface(&pass.vertex_shader); - - pass.viewport.MaxDepth = 1.0f; - pass.viewport.Width = static_cast(pass_info.viewport_width); - pass.viewport.Height = static_cast(pass_info.viewport_height); - - pass.shader_resources = technique_data->texture_bindings; - pass.clear_render_targets = pass_info.clear_render_targets; - - const int target_index = pass_info.srgb_write_enable ? 1 : 0; - pass.render_targets[0] = _backbuffer_rtv[target_index]; - pass.render_target_resources[0] = _backbuffer_texture_srv[target_index]; - - for (unsigned int k = 0; k < 8; k++) - { - if (pass_info.render_target_names[k].empty()) - continue; // Skip unbound render targets - - const auto render_target_texture = std::find_if(_textures.begin(), _textures.end(), - [&render_target = pass_info.render_target_names[k]](const auto &item) { - return item.unique_name == render_target; - }); - - if (render_target_texture == _textures.end()) - return assert(false), false; - - const auto texture_impl = render_target_texture->impl->as(); - assert(texture_impl != nullptr); - - D3D11_TEXTURE2D_DESC desc; - texture_impl->texture->GetDesc(&desc); - - D3D11_RENDER_TARGET_VIEW_DESC rtv_desc = {}; - rtv_desc.Format = pass_info.srgb_write_enable ? make_dxgi_format_srgb(desc.Format) : make_dxgi_format_normal(desc.Format); - rtv_desc.ViewDimension = desc.SampleDesc.Count > 1 ? D3D11_RTV_DIMENSION_TEXTURE2DMS : D3D11_RTV_DIMENSION_TEXTURE2D; - - if (texture_impl->rtv[target_index] == nullptr) - if (HRESULT hr = _device->CreateRenderTargetView(texture_impl->texture.get(), &rtv_desc, &texture_impl->rtv[target_index]); FAILED(hr)) - { - LOG(ERROR) << "Failed to create render target view for texture '" << render_target_texture->unique_name << "' (" - "Format = " << rtv_desc.Format << ")! " - "HRESULT is '" << std::hex << hr << std::dec << "'."; - return false; - } - - pass.render_targets[k] = texture_impl->rtv[target_index]; - pass.render_target_resources[k] = texture_impl->srv[target_index]; - } - - if (pass.viewport.Width == 0) - { - pass.viewport.Width = static_cast(frame_width()); - pass.viewport.Height = static_cast(frame_height()); - } - - { D3D11_BLEND_DESC desc = {}; - desc.RenderTarget[0].BlendEnable = pass_info.blend_enable; - - const auto literal_to_blend_func = [](unsigned int value) { - switch (value) { - case 0: - return D3D11_BLEND_ZERO; - default: - case 1: - return D3D11_BLEND_ONE; - case 2: - return D3D11_BLEND_SRC_COLOR; - case 4: - return D3D11_BLEND_INV_SRC_COLOR; - case 3: - return D3D11_BLEND_SRC_ALPHA; - case 5: - return D3D11_BLEND_INV_SRC_ALPHA; - case 6: - return D3D11_BLEND_DEST_ALPHA; - case 7: - return D3D11_BLEND_INV_DEST_ALPHA; - case 8: - return D3D11_BLEND_DEST_COLOR; - case 9: - return D3D11_BLEND_INV_DEST_COLOR; - } - }; - - desc.RenderTarget[0].SrcBlend = literal_to_blend_func(pass_info.src_blend); - desc.RenderTarget[0].DestBlend = literal_to_blend_func(pass_info.dest_blend); - desc.RenderTarget[0].BlendOp = static_cast(pass_info.blend_op); - desc.RenderTarget[0].SrcBlendAlpha = literal_to_blend_func(pass_info.src_blend_alpha); - desc.RenderTarget[0].DestBlendAlpha = literal_to_blend_func(pass_info.dest_blend_alpha); - desc.RenderTarget[0].BlendOpAlpha = static_cast(pass_info.blend_op_alpha); - desc.RenderTarget[0].RenderTargetWriteMask = pass_info.color_write_mask; - if (HRESULT hr = _device->CreateBlendState(&desc, &pass.blend_state); FAILED(hr)) - { - LOG(ERROR) << "Failed to create blend state for pass " << pass_index << " in technique '" << technique.name << "'! " - "HRESULT is '" << std::hex << hr << std::dec << "'."; - return false; - } - } - - // Rasterizer state is the same for all passes - assert(_effect_rasterizer_state != nullptr); - - { D3D11_DEPTH_STENCIL_DESC desc = {}; - desc.DepthEnable = FALSE; - desc.DepthWriteMask = D3D11_DEPTH_WRITE_MASK_ZERO; - desc.DepthFunc = D3D11_COMPARISON_ALWAYS; - - const auto literal_to_stencil_op = [](unsigned int value) { - switch (value) { - default: - case 1: - return D3D11_STENCIL_OP_KEEP; - case 0: - return D3D11_STENCIL_OP_ZERO; - case 3: - return D3D11_STENCIL_OP_REPLACE; - case 4: - return D3D11_STENCIL_OP_INCR_SAT; - case 5: - return D3D11_STENCIL_OP_DECR_SAT; - case 6: - return D3D11_STENCIL_OP_INVERT; - case 7: - return D3D11_STENCIL_OP_INCR; - case 8: - return D3D11_STENCIL_OP_DECR; - } - }; - - desc.StencilEnable = pass_info.stencil_enable; - desc.StencilReadMask = pass_info.stencil_read_mask; - desc.StencilWriteMask = pass_info.stencil_write_mask; - desc.FrontFace.StencilFailOp = literal_to_stencil_op(pass_info.stencil_op_fail); - desc.FrontFace.StencilDepthFailOp = literal_to_stencil_op(pass_info.stencil_op_depth_fail); - desc.FrontFace.StencilPassOp = literal_to_stencil_op(pass_info.stencil_op_pass); - desc.FrontFace.StencilFunc = static_cast(pass_info.stencil_comparison_func); - desc.BackFace = desc.FrontFace; - if (HRESULT hr = _device->CreateDepthStencilState(&desc, &pass.depth_stencil_state); FAILED(hr)) - { - LOG(ERROR) << "Failed to create depth stencil state for pass " << pass_index << " in technique '" << technique.name << "'! " - "HRESULT is '" << std::hex << hr << std::dec << "'."; - return false; - } - - pass.stencil_reference = pass_info.stencil_reference_value; - } - - for (auto &srv : pass.shader_resources) - { - if (srv == nullptr) - continue; - - com_ptr res1; - srv->GetResource(&res1); - - for (const auto &rtv : pass.render_targets) - { - if (rtv == nullptr) - continue; - - com_ptr res2; - rtv->GetResource(&res2); - - if (res1 == res2) - { - srv.reset(); - break; - } - } - } - } - - return true; -} - -void reshade::d3d11::runtime_d3d11::render_technique(technique &technique) -{ - d3d11_technique_data &technique_data = *technique.impl->as(); - - // Evaluate queries - if (technique_data.query_in_flight) - { - UINT64 timestamp0, timestamp1; - D3D11_QUERY_DATA_TIMESTAMP_DISJOINT disjoint; - - if (_immediate_context->GetData(technique_data.timestamp_disjoint.get(), &disjoint, sizeof(disjoint), D3D11_ASYNC_GETDATA_DONOTFLUSH) == S_OK && - _immediate_context->GetData(technique_data.timestamp_query_beg.get(), ×tamp0, sizeof(timestamp0), D3D11_ASYNC_GETDATA_DONOTFLUSH) == S_OK && - _immediate_context->GetData(technique_data.timestamp_query_end.get(), ×tamp1, sizeof(timestamp1), D3D11_ASYNC_GETDATA_DONOTFLUSH) == S_OK) - { - if (!disjoint.Disjoint) - technique.average_gpu_duration.append((timestamp1 - timestamp0) * 1'000'000'000 / disjoint.Frequency); - technique_data.query_in_flight = false; - } - } - - if (!technique_data.query_in_flight) - { - _immediate_context->Begin(technique_data.timestamp_disjoint.get()); - _immediate_context->End(technique_data.timestamp_query_beg.get()); - } - - bool is_default_depthstencil_cleared = false; - - // Setup vertex input - const uintptr_t null = 0; - _immediate_context->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST); - _immediate_context->IASetInputLayout(nullptr); - _immediate_context->IASetVertexBuffers(0, 1, reinterpret_cast(&null), reinterpret_cast(&null), reinterpret_cast(&null)); - - _immediate_context->RSSetState(_effect_rasterizer_state.get()); - - // Setup samplers - _immediate_context->VSSetSamplers(0, static_cast(technique_data.sampler_states.size()), reinterpret_cast(technique_data.sampler_states.data())); - _immediate_context->PSSetSamplers(0, static_cast(technique_data.sampler_states.size()), reinterpret_cast(technique_data.sampler_states.data())); - - // Setup shader constants - if (technique_data.uniform_storage_index >= 0) - { - const auto constant_buffer = _constant_buffers[technique_data.uniform_storage_index].get(); - D3D11_MAPPED_SUBRESOURCE mapped; - - const HRESULT hr = _immediate_context->Map(constant_buffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mapped); - - if (SUCCEEDED(hr)) - { - memcpy(mapped.pData, _uniform_data_storage.data() + technique_data.uniform_storage_offset, mapped.RowPitch); - - _immediate_context->Unmap(constant_buffer, 0); - } - else - { - LOG(ERROR) << "Failed to map constant buffer! HRESULT is '" << std::hex << hr << std::dec << "'!"; - } - - _immediate_context->VSSetConstantBuffers(0, 1, &constant_buffer); - _immediate_context->PSSetConstantBuffers(0, 1, &constant_buffer); - } - - // Disable unused pipeline stages - _immediate_context->HSSetShader(nullptr, nullptr, 0); - _immediate_context->DSSetShader(nullptr, nullptr, 0); - _immediate_context->GSSetShader(nullptr, nullptr, 0); - - for (const auto &pass_object : technique.passes_data) - { - const d3d11_pass_data &pass = *pass_object->as(); - - // Setup states - _immediate_context->VSSetShader(pass.vertex_shader.get(), nullptr, 0); - _immediate_context->PSSetShader(pass.pixel_shader.get(), nullptr, 0); - - _immediate_context->OMSetBlendState(pass.blend_state.get(), nullptr, D3D11_DEFAULT_SAMPLE_MASK); - _immediate_context->OMSetDepthStencilState(pass.depth_stencil_state.get(), pass.stencil_reference); - - // Save back buffer of previous pass - _immediate_context->CopyResource(_backbuffer_texture.get(), _backbuffer_resolved.get()); - - // Setup shader resources - _immediate_context->VSSetShaderResources(0, static_cast(pass.shader_resources.size()), reinterpret_cast(pass.shader_resources.data())); - _immediate_context->PSSetShaderResources(0, static_cast(pass.shader_resources.size()), reinterpret_cast(pass.shader_resources.data())); - - // Setup render targets - if (static_cast(pass.viewport.Width) == _width && static_cast(pass.viewport.Height) == _height) - { - _immediate_context->OMSetRenderTargets(D3D11_SIMULTANEOUS_RENDER_TARGET_COUNT, reinterpret_cast(pass.render_targets), _default_depthstencil.get()); - - if (!is_default_depthstencil_cleared) - { - is_default_depthstencil_cleared = true; - - _immediate_context->ClearDepthStencilView(_default_depthstencil.get(), D3D11_CLEAR_DEPTH | D3D11_CLEAR_STENCIL, 1.0f, 0); - } - } - else - { - _immediate_context->OMSetRenderTargets(D3D11_SIMULTANEOUS_RENDER_TARGET_COUNT, reinterpret_cast(pass.render_targets), nullptr); - } - - _immediate_context->RSSetViewports(1, &pass.viewport); - - if (pass.clear_render_targets) - { - for (const auto &target : pass.render_targets) - { - if (target != nullptr) - { - const float color[4] = { 0.0f, 0.0f, 0.0f, 0.0f }; - _immediate_context->ClearRenderTargetView(target.get(), color); - } - } - } - - // Draw triangle - _immediate_context->Draw(3, 0); - - _vertices += 3; - _drawcalls += 1; - - // Reset render targets - _immediate_context->OMSetRenderTargets(0, nullptr, nullptr); - - // Reset shader resources - ID3D11ShaderResourceView *null_srv[D3D11_COMMONSHADER_INPUT_RESOURCE_SLOT_COUNT] = { nullptr }; - _immediate_context->VSSetShaderResources(0, static_cast(pass.shader_resources.size()), null_srv); - _immediate_context->PSSetShaderResources(0, static_cast(pass.shader_resources.size()), null_srv); - - // Update shader resources - for (const auto &resource : pass.render_target_resources) - { - if (resource == nullptr) - continue; - - D3D11_SHADER_RESOURCE_VIEW_DESC resource_desc; - resource->GetDesc(&resource_desc); - - if (resource_desc.Texture2D.MipLevels > 1) - _immediate_context->GenerateMips(resource.get()); - } - } - - if (!technique_data.query_in_flight) - { - _immediate_context->End(technique_data.timestamp_query_end.get()); - _immediate_context->End(technique_data.timestamp_disjoint.get()); - technique_data.query_in_flight = true; - } -} - -#if RESHADE_GUI -bool reshade::d3d11::runtime_d3d11::init_imgui_resources() -{ - { const resources::data_resource vs = resources::load_data_resource(IDR_IMGUI_VS); - if (FAILED(_device->CreateVertexShader(vs.data, vs.data_size, nullptr, &_imgui_vertex_shader))) - return false; - - const D3D11_INPUT_ELEMENT_DESC input_layout[] = { - { "POSITION", 0, DXGI_FORMAT_R32G32_FLOAT, 0, offsetof(ImDrawVert, pos), D3D11_INPUT_PER_VERTEX_DATA, 0 }, - { "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, offsetof(ImDrawVert, uv ), D3D11_INPUT_PER_VERTEX_DATA, 0 }, - { "COLOR", 0, DXGI_FORMAT_R8G8B8A8_UNORM, 0, offsetof(ImDrawVert, col), D3D11_INPUT_PER_VERTEX_DATA, 0 }, - }; - if (FAILED(_device->CreateInputLayout(input_layout, _countof(input_layout), vs.data, vs.data_size, &_imgui_input_layout))) - return false; - } - - { const resources::data_resource ps = resources::load_data_resource(IDR_IMGUI_PS); - if (FAILED(_device->CreatePixelShader(ps.data, ps.data_size, nullptr, &_imgui_pixel_shader))) - return false; - } - - { D3D11_BUFFER_DESC desc = {}; - desc.ByteWidth = 16 * sizeof(float); - desc.Usage = D3D11_USAGE_IMMUTABLE; - desc.BindFlags = D3D11_BIND_CONSTANT_BUFFER; - - // Setup orthographic projection matrix - const float ortho_projection[16] = { - 2.0f / _width, 0.0f, 0.0f, 0.0f, - 0.0f, -2.0f / _height, 0.0f, 0.0f, - 0.0f, 0.0f, 0.5f, 0.0f, - -1.f, 1.0f, 0.5f, 1.0f - }; - - D3D11_SUBRESOURCE_DATA initial_data = {}; - initial_data.pSysMem = ortho_projection; - initial_data.SysMemPitch = sizeof(ortho_projection); - if (FAILED(_device->CreateBuffer(&desc, &initial_data, &_imgui_constant_buffer))) - return false; - } - - { D3D11_BLEND_DESC desc = {}; - desc.RenderTarget[0].BlendEnable = true; - desc.RenderTarget[0].SrcBlend = D3D11_BLEND_SRC_ALPHA; - desc.RenderTarget[0].DestBlend = D3D11_BLEND_INV_SRC_ALPHA; - desc.RenderTarget[0].BlendOp = D3D11_BLEND_OP_ADD; - desc.RenderTarget[0].SrcBlendAlpha = D3D11_BLEND_INV_SRC_ALPHA; - desc.RenderTarget[0].DestBlendAlpha = D3D11_BLEND_ZERO; - desc.RenderTarget[0].BlendOpAlpha = D3D11_BLEND_OP_ADD; - desc.RenderTarget[0].RenderTargetWriteMask = D3D11_COLOR_WRITE_ENABLE_ALL; - if (FAILED(_device->CreateBlendState(&desc, &_imgui_blend_state))) - return false; - } - - { D3D11_RASTERIZER_DESC desc = {}; - desc.FillMode = D3D11_FILL_SOLID; - desc.CullMode = D3D11_CULL_NONE; - desc.ScissorEnable = true; - desc.DepthClipEnable = true; - if (FAILED(_device->CreateRasterizerState(&desc, &_imgui_rasterizer_state))) - return false; - } - - { D3D11_DEPTH_STENCIL_DESC desc = {}; - desc.DepthEnable = false; - desc.StencilEnable = false; - if (FAILED(_device->CreateDepthStencilState(&desc, &_imgui_depthstencil_state))) - return false; - } - - { D3D11_SAMPLER_DESC desc = {}; - desc.Filter = D3D11_FILTER_MIN_MAG_MIP_LINEAR; - desc.AddressU = D3D11_TEXTURE_ADDRESS_WRAP; - desc.AddressV = D3D11_TEXTURE_ADDRESS_WRAP; - desc.AddressW = D3D11_TEXTURE_ADDRESS_WRAP; - desc.ComparisonFunc = D3D11_COMPARISON_ALWAYS; - if (FAILED(_device->CreateSamplerState(&desc, &_imgui_texture_sampler))) - return false; - } - - return true; -} - -void reshade::d3d11::runtime_d3d11::render_imgui_draw_data(ImDrawData *draw_data) -{ - assert(draw_data->DisplayPos.x == 0 && draw_data->DisplaySize.x == _width); - assert(draw_data->DisplayPos.y == 0 && draw_data->DisplaySize.y == _height); - - // Create and grow vertex/index buffers if needed - if (_imgui_index_buffer_size < draw_data->TotalIdxCount) - { - _imgui_index_buffer.reset(); - _imgui_index_buffer_size = draw_data->TotalIdxCount + 10000; - - D3D11_BUFFER_DESC desc = {}; - desc.Usage = D3D11_USAGE_DYNAMIC; - desc.ByteWidth = _imgui_index_buffer_size * sizeof(ImDrawIdx); - desc.BindFlags = D3D11_BIND_INDEX_BUFFER; - desc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE; - - if (FAILED(_device->CreateBuffer(&desc, nullptr, &_imgui_index_buffer))) - return; - } - if (_imgui_vertex_buffer_size < draw_data->TotalVtxCount) - { - _imgui_vertex_buffer.reset(); - _imgui_vertex_buffer_size = draw_data->TotalVtxCount + 5000; - - D3D11_BUFFER_DESC desc = {}; - desc.Usage = D3D11_USAGE_DYNAMIC; - desc.ByteWidth = _imgui_vertex_buffer_size * sizeof(ImDrawVert); - desc.BindFlags = D3D11_BIND_VERTEX_BUFFER; - desc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE; - desc.MiscFlags = 0; - - if (FAILED(_device->CreateBuffer(&desc, nullptr, &_imgui_vertex_buffer))) - return; - } - - D3D11_MAPPED_SUBRESOURCE vtx_resource, idx_resource; - if (FAILED(_immediate_context->Map(_imgui_index_buffer.get(), 0, D3D11_MAP_WRITE_DISCARD, 0, &idx_resource)) || - FAILED(_immediate_context->Map(_imgui_vertex_buffer.get(), 0, D3D11_MAP_WRITE_DISCARD, 0, &vtx_resource))) - return; - - auto idx_dst = static_cast(idx_resource.pData); - auto vtx_dst = static_cast(vtx_resource.pData); - for (int n = 0; n < draw_data->CmdListsCount; n++) - { - const ImDrawList *const draw_list = draw_data->CmdLists[n]; - memcpy(idx_dst, draw_list->IdxBuffer.Data, draw_list->IdxBuffer.Size * sizeof(ImDrawIdx)); - memcpy(vtx_dst, draw_list->VtxBuffer.Data, draw_list->VtxBuffer.Size * sizeof(ImDrawVert)); - idx_dst += draw_list->IdxBuffer.Size; - vtx_dst += draw_list->VtxBuffer.Size; - } - - _immediate_context->Unmap(_imgui_index_buffer.get(), 0); - _immediate_context->Unmap(_imgui_vertex_buffer.get(), 0); - - // Setup render state and render draw lists - _immediate_context->IASetInputLayout(_imgui_input_layout.get()); - _immediate_context->IASetIndexBuffer(_imgui_index_buffer.get(), sizeof(ImDrawIdx) == 2 ? DXGI_FORMAT_R16_UINT : DXGI_FORMAT_R32_UINT, 0); - const UINT stride = sizeof(ImDrawVert), offset = 0; - ID3D11Buffer *const vertex_buffers[] = { _imgui_vertex_buffer.get() }; - _immediate_context->IASetVertexBuffers(0, ARRAYSIZE(vertex_buffers), vertex_buffers, &stride, &offset); - _immediate_context->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST); - _immediate_context->VSSetShader(_imgui_vertex_shader.get(), nullptr, 0); - ID3D11Buffer *const constant_buffers[] = { _imgui_constant_buffer.get() }; - _immediate_context->VSSetConstantBuffers(0, ARRAYSIZE(constant_buffers), constant_buffers); - _immediate_context->HSSetShader(nullptr, nullptr, 0); - _immediate_context->DSSetShader(nullptr, nullptr, 0); - _immediate_context->GSSetShader(nullptr, nullptr, 0); - _immediate_context->PSSetShader(_imgui_pixel_shader.get(), nullptr, 0); - ID3D11SamplerState *const samplers[] = { _imgui_texture_sampler.get() }; - _immediate_context->PSSetSamplers(0, ARRAYSIZE(samplers), samplers); - _immediate_context->RSSetState(_imgui_rasterizer_state.get()); - const D3D11_VIEWPORT viewport = { 0, 0, FLOAT(_width), FLOAT(_height), 0.0f, 1.0f }; - _immediate_context->RSSetViewports(1, &viewport); - const FLOAT blend_factor[4] = { 0.f, 0.f, 0.f, 0.f }; - _immediate_context->OMSetBlendState(_imgui_blend_state.get(), blend_factor, D3D11_DEFAULT_SAMPLE_MASK); - _immediate_context->OMSetDepthStencilState(_imgui_depthstencil_state.get(), 0); - ID3D11RenderTargetView *const render_targets[] = { _backbuffer_rtv[0].get() }; - _immediate_context->OMSetRenderTargets(ARRAYSIZE(render_targets), render_targets, nullptr); - - UINT vtx_offset = 0, idx_offset = 0; - for (int n = 0; n < draw_data->CmdListsCount; ++n) - { - const ImDrawList *const draw_list = draw_data->CmdLists[n]; - - for (const ImDrawCmd &cmd : draw_list->CmdBuffer) - { - assert(cmd.TextureId != 0); - assert(cmd.UserCallback == nullptr); - - const D3D11_RECT scissor_rect = { - static_cast(cmd.ClipRect.x), - static_cast(cmd.ClipRect.y), - static_cast(cmd.ClipRect.z), - static_cast(cmd.ClipRect.w) - }; - _immediate_context->RSSetScissorRects(1, &scissor_rect); - - ID3D11ShaderResourceView *const texture_view = - static_cast(cmd.TextureId)->srv[0].get(); - _immediate_context->PSSetShaderResources(0, 1, &texture_view); - - _immediate_context->DrawIndexed(cmd.ElemCount, idx_offset, vtx_offset); - - idx_offset += cmd.ElemCount; - } - - vtx_offset += draw_list->VtxBuffer.size(); - } -} - -void reshade::d3d11::runtime_d3d11::draw_debug_menu() -{ - ImGui::Text("MSAA is %s", _is_multisampling_enabled ? "active" : "inactive"); - ImGui::Spacing(); - - assert(_current_tracker != nullptr); - -#if RESHADE_DX11_CAPTURE_DEPTH_BUFFERS - if (ImGui::CollapsingHeader("Depth and Intermediate Buffers", ImGuiTreeNodeFlags_DefaultOpen)) - { - bool modified = false; - modified |= ImGui::Combo("Depth Texture Format", &depth_buffer_texture_format, "All\0D16\0D32F\0D24S8\0D32FS8\0"); - - if (modified) - { - runtime::save_config(); - _current_tracker->reset(); - create_depthstencil_replacement(nullptr, nullptr); - return; - } - - modified |= ImGui::Checkbox("Copy depth before clearing", &depth_buffer_before_clear); - - if (depth_buffer_before_clear) - { - if (ImGui::Checkbox("Extended depth buffer detection", &extended_depth_buffer_detection)) - { - cleared_depth_buffer_index = 0; - modified = true; - } - - _current_tracker->keep_cleared_depth_textures(); - - ImGui::Spacing(); - ImGui::TextUnformatted("Depth Buffers:"); - - unsigned int current_index = 1; - - for (const auto &it : _current_tracker->cleared_depth_textures()) - { - char label[512] = ""; - sprintf_s(label, "%s%2u", (current_index == cleared_depth_buffer_index ? "> " : " "), current_index); - - if (bool value = cleared_depth_buffer_index == current_index; ImGui::Checkbox(label, &value)) - { - cleared_depth_buffer_index = value ? current_index : 0; - modified = true; - } - - ImGui::SameLine(); - - ImGui::Text("=> 0x%p | 0x%p | %ux%u", it.second.src_depthstencil.get(), it.second.src_texture.get(), it.second.src_texture_desc.Width, it.second.src_texture_desc.Height); - - if (it.second.dest_texture != nullptr) - { - ImGui::SameLine(); - - ImGui::Text("=> %p", it.second.dest_texture.get()); - } - - current_index++; - } - } - else if (!_current_tracker->depth_buffer_counters().empty()) - { - ImGui::Spacing(); - ImGui::TextUnformatted("Depth Buffers: (intermediate buffer draw calls in parentheses)"); - - for (const auto &[depthstencil, snapshot] : _current_tracker->depth_buffer_counters()) - { - char label[512] = ""; - sprintf_s(label, "%s0x%p", (depthstencil == _depthstencil ? "> " : " "), depthstencil.get()); - - if (bool value = _best_depth_stencil_overwrite == depthstencil; ImGui::Checkbox(label, &value)) - { - _best_depth_stencil_overwrite = value ? depthstencil.get() : nullptr; - - com_ptr texture = snapshot.texture; - - if (texture == nullptr && _best_depth_stencil_overwrite != nullptr) - { - com_ptr resource; - _best_depth_stencil_overwrite->GetResource(&resource); - - resource->QueryInterface(&texture); - } - - create_depthstencil_replacement(_best_depth_stencil_overwrite, texture.get()); - } - - ImGui::SameLine(); - - std::string additional_view_label; - - if (!snapshot.additional_views.empty()) - { - additional_view_label += '('; - - for (auto const &[view, stats] : snapshot.additional_views) - additional_view_label += std::to_string(stats.drawcalls) + ", "; - - // Remove last ", " from string - additional_view_label.pop_back(); - additional_view_label.pop_back(); - - additional_view_label += ')'; - } - - ImGui::Text("| %5u draw calls ==> %8u vertices, %2u additional render target%c %s", snapshot.stats.drawcalls, snapshot.stats.vertices, snapshot.additional_views.size(), snapshot.additional_views.size() != 1 ? 's' : ' ', additional_view_label.c_str()); - } - } - - if (modified) - { - runtime::save_config(); - } - } -#endif -#if RESHADE_DX11_CAPTURE_CONSTANT_BUFFERS - if (ImGui::CollapsingHeader("Constant Buffers", ImGuiTreeNodeFlags_DefaultOpen)) - { - for (const auto &[buffer, counter] : _current_tracker->constant_buffer_counters()) - { - bool likely_camera_transform_buffer = false; - - D3D11_BUFFER_DESC desc; - buffer->GetDesc(&desc); - - if (counter.ps_uses > 0 && counter.vs_uses > 0 && counter.mapped < .10 * counter.vs_uses && desc.ByteWidth > 1000) - likely_camera_transform_buffer = true; - - ImGui::Text("%s 0x%p | used in %4u vertex shaders and %4u pixel shaders, mapped %3u times, %8u bytes", likely_camera_transform_buffer ? ">" : " ", buffer.get(), counter.vs_uses, counter.ps_uses, counter.mapped, desc.ByteWidth); - } - } -#endif -} -#endif - -#if RESHADE_DX11_CAPTURE_DEPTH_BUFFERS -void reshade::d3d11::runtime_d3d11::detect_depth_source(draw_call_tracker &tracker) -{ - if (depth_buffer_before_clear) - _best_depth_stencil_overwrite = nullptr; - - if (_is_multisampling_enabled || _best_depth_stencil_overwrite != nullptr || (_framecount % 30 && !depth_buffer_before_clear)) - return; - - if (_has_high_network_activity) - { - create_depthstencil_replacement(nullptr, nullptr); - return; - } - - if (depth_buffer_before_clear) - { - // At the final rendering stage, it is fine to rely on the depth stencil to select the best depth texture - // But when we retrieve the depth textures before the final rendering stage, there is chance that one or many different depth textures are associated to the same depth stencil (for instance, in Bioshock 2) - // In this case, we cannot use the depth stencil to determine which depth texture is the good one, so we can use the default depth stencil - // For the moment, the best we can do is retrieve all the depth textures that has been cleared in the rendering pipeline, then select one of them (by default, the last one) - // In the future, maybe we could find a way to retrieve depth texture statistics (number of draw calls and number of vertices), so ReShade could automatically select the best one - ID3D11Texture2D *const best_match_texture = tracker.find_best_cleared_depth_buffer_texture(cleared_depth_buffer_index); - if (best_match_texture != nullptr) - create_depthstencil_replacement(_default_depthstencil.get(), best_match_texture); - return; - } - - const auto best_snapshot = tracker.find_best_snapshot(_width, _height); - if (best_snapshot.depthstencil != nullptr) - create_depthstencil_replacement(best_snapshot.depthstencil, best_snapshot.texture.get()); -} - -bool reshade::d3d11::runtime_d3d11::create_depthstencil_replacement(ID3D11DepthStencilView *depthstencil, ID3D11Texture2D *texture) -{ - _depthstencil.reset(); - _depthstencil_replacement.reset(); - _depthstencil_texture.reset(); - _depthstencil_texture_srv.reset(); - - if (depthstencil != nullptr) - { - assert(texture != nullptr); - _depthstencil = depthstencil; - _depthstencil_texture = texture; - - D3D11_TEXTURE2D_DESC tex_desc; - _depthstencil_texture->GetDesc(&tex_desc); - - HRESULT hr = S_OK; - - if ((tex_desc.BindFlags & D3D11_BIND_SHADER_RESOURCE) == 0) - { - _depthstencil_texture.reset(); - - tex_desc.Format = make_dxgi_format_typeless(tex_desc.Format); - tex_desc.BindFlags = D3D11_BIND_DEPTH_STENCIL | D3D11_BIND_SHADER_RESOURCE; - - hr = _device->CreateTexture2D(&tex_desc, nullptr, &_depthstencil_texture); - - if (SUCCEEDED(hr)) - { - D3D11_DEPTH_STENCIL_VIEW_DESC dsv_desc = {}; - dsv_desc.ViewDimension = D3D11_DSV_DIMENSION_TEXTURE2D; - dsv_desc.Format = make_dxgi_format_dsv(tex_desc.Format); - - hr = _device->CreateDepthStencilView(_depthstencil_texture.get(), &dsv_desc, &_depthstencil_replacement); - } - } - - if (FAILED(hr)) - { - LOG(ERROR) << "Failed to create depth stencil replacement texture! HRESULT is '" << std::hex << hr << std::dec << "'."; - return false; - } - - D3D11_SHADER_RESOURCE_VIEW_DESC srv_desc = {}; - srv_desc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D; - srv_desc.Texture2D.MipLevels = 1; - srv_desc.Format = make_dxgi_format_normal(tex_desc.Format); - - if (hr = _device->CreateShaderResourceView(_depthstencil_texture.get(), &srv_desc, &_depthstencil_texture_srv); FAILED(hr)) - { - LOG(ERROR) << "Failed to create depth stencil replacement resource view! HRESULT is '" << std::hex << hr << std::dec << "'."; - return false; - } - - // Update auto depth stencil - com_ptr current_depthstencil; - com_ptr targets[D3D11_SIMULTANEOUS_RENDER_TARGET_COUNT]; - _immediate_context->OMGetRenderTargets(D3D11_SIMULTANEOUS_RENDER_TARGET_COUNT, reinterpret_cast(targets), ¤t_depthstencil); - if (current_depthstencil != nullptr && current_depthstencil == _depthstencil) - _immediate_context->OMSetRenderTargets(D3D11_SIMULTANEOUS_RENDER_TARGET_COUNT, reinterpret_cast(targets), _depthstencil_replacement.get()); - } - - update_texture_references(texture_reference::depth_buffer); - - return true; -} - -com_ptr reshade::d3d11::runtime_d3d11::select_depth_texture_save(D3D11_TEXTURE2D_DESC &texture_desc) -{ - // Function that selects the appropriate texture where we want to save the depth texture before it is cleared - // If this texture is null, create it according to the dimensions and the format of the depth texture - // Doing so, we avoid to create a new texture each time the depth texture is saved - - texture_desc.Format = make_dxgi_format_typeless(texture_desc.Format); - - // Create an unique index based on the texture format and dimensions - UINT idx = texture_desc.Format * texture_desc.Width * texture_desc.Height; - - if (const auto it = _depth_texture_saves.find(idx); it != _depth_texture_saves.end()) - return it->second; - - texture_desc.BindFlags = D3D11_BIND_DEPTH_STENCIL | D3D11_BIND_SHADER_RESOURCE; - - // Create the saved texture pointed by the index if it does not already exist - com_ptr depth_texture_save; - - HRESULT hr = _device->CreateTexture2D(&texture_desc, nullptr, &depth_texture_save); - - if (FAILED(hr)) - { - LOG(ERROR) << "Failed to create depth texture copy! HRESULT is '" << std::hex << hr << std::dec << "'."; - return nullptr; - } - - _depth_texture_saves.emplace(idx, depth_texture_save); - - return depth_texture_save; -} -#endif diff --git a/msvc/source/d3d11/runtime_d3d11.hpp b/msvc/source/d3d11/runtime_d3d11.hpp deleted file mode 100644 index 8c4113c..0000000 --- a/msvc/source/d3d11/runtime_d3d11.hpp +++ /dev/null @@ -1,119 +0,0 @@ -/** - * Copyright (C) 2014 Patrick Mours. All rights reserved. - * License: https://github.com/crosire/reshade#license - */ - -#pragma once - -#include "runtime.hpp" -#include "state_block.hpp" -#include "draw_call_tracker.hpp" -#include - -namespace reshade { enum class texture_reference; } -namespace reshadefx { struct sampler_info; } - -namespace reshade::d3d11 -{ - class runtime_d3d11 : public runtime - { - public: - runtime_d3d11(ID3D11Device *device, IDXGISwapChain *swapchain); - ~runtime_d3d11(); - - bool on_init(const DXGI_SWAP_CHAIN_DESC &desc); - void on_reset(); - void on_present(draw_call_tracker& tracker); - - void capture_screenshot(uint8_t *buffer) const override; - -#if RESHADE_DX11_CAPTURE_DEPTH_BUFFERS - com_ptr select_depth_texture_save(D3D11_TEXTURE2D_DESC &texture_desc); -#endif - - bool depth_buffer_before_clear = false; - bool extended_depth_buffer_detection = false; - unsigned int cleared_depth_buffer_index = 0; - int depth_buffer_texture_format = 0; // No depth buffer texture format filter by default - - private: - bool init_backbuffer_texture(); - bool init_default_depth_stencil(); - - bool init_texture(texture &info) override; - void upload_texture(texture &texture, const uint8_t *pixels) override; - bool update_texture_reference(texture &texture); - void update_texture_references(texture_reference type); - - bool compile_effect(effect_data &effect) override; - void unload_effects() override; - - bool add_sampler(const reshadefx::sampler_info &info, struct d3d11_technique_data &technique_init); - bool init_technique(technique &info, const struct d3d11_technique_data &technique_init, const std::unordered_map> &entry_points); - - void render_technique(technique &technique) override; - -#if RESHADE_GUI - bool init_imgui_resources(); - void render_imgui_draw_data(ImDrawData *data) override; - void draw_debug_menu(); -#endif - -#if RESHADE_DX11_CAPTURE_DEPTH_BUFFERS - void detect_depth_source(draw_call_tracker& tracker); - bool create_depthstencil_replacement(ID3D11DepthStencilView *depthstencil, ID3D11Texture2D *texture); -#endif - - struct depth_texture_save_info - { - com_ptr src_texture; - D3D11_TEXTURE2D_DESC src_texture_desc; - com_ptr dest_texture; - bool cleared = false; - }; - - com_ptr _device; - com_ptr _immediate_context; - com_ptr _swapchain; - - com_ptr _backbuffer_texture; - com_ptr _backbuffer_rtv[3]; - com_ptr _backbuffer_texture_srv[2]; - com_ptr _depthstencil_texture_srv; - std::unordered_map> _effect_sampler_states; - std::vector> _constant_buffers; - - std::map _displayed_depth_textures; - std::unordered_map> _depth_texture_saves; - - bool _is_multisampling_enabled = false; - DXGI_FORMAT _backbuffer_format = DXGI_FORMAT_UNKNOWN; - state_block _app_state; - com_ptr _backbuffer, _backbuffer_resolved; - com_ptr _depthstencil, _depthstencil_replacement; - ID3D11DepthStencilView *_best_depth_stencil_overwrite = nullptr; - com_ptr _depthstencil_texture; - com_ptr _default_depthstencil; - com_ptr _copy_vertex_shader; - com_ptr _copy_pixel_shader; - com_ptr _copy_sampler; - std::mutex _mutex; - com_ptr _effect_rasterizer_state; - - int _imgui_index_buffer_size = 0; - com_ptr _imgui_index_buffer; - int _imgui_vertex_buffer_size = 0; - com_ptr _imgui_vertex_buffer; - com_ptr _imgui_vertex_shader; - com_ptr _imgui_pixel_shader; - com_ptr _imgui_input_layout; - com_ptr _imgui_constant_buffer; - com_ptr _imgui_texture_sampler; - com_ptr _imgui_rasterizer_state; - com_ptr _imgui_blend_state; - com_ptr _imgui_depthstencil_state; - draw_call_tracker *_current_tracker = nullptr; - - HMODULE _d3d_compiler = nullptr; - }; -} diff --git a/msvc/source/d3d11/state_block.cpp b/msvc/source/d3d11/state_block.cpp deleted file mode 100644 index e437748..0000000 --- a/msvc/source/d3d11/state_block.cpp +++ /dev/null @@ -1,166 +0,0 @@ -/** - * Copyright (C) 2014 Patrick Mours. All rights reserved. - * License: https://github.com/crosire/reshade#license - */ - -#include "state_block.hpp" - -template -static inline void safe_release(T *&object) -{ - if (object != nullptr) - { - object->Release(); - object = nullptr; - } -} - -reshade::d3d11::state_block::state_block(ID3D11Device *device) -{ - ZeroMemory(this, sizeof(*this)); - - _device = device; - _device_feature_level = device->GetFeatureLevel(); -} -reshade::d3d11::state_block::~state_block() -{ - release_all_device_objects(); -} - -void reshade::d3d11::state_block::capture(ID3D11DeviceContext *devicecontext) -{ - _device_context = devicecontext; - - _device_context->IAGetPrimitiveTopology(&_ia_primitive_topology); - _device_context->IAGetInputLayout(&_ia_input_layout); - - if (_device_feature_level > D3D_FEATURE_LEVEL_10_0) - _device_context->IAGetVertexBuffers(0, D3D11_IA_VERTEX_INPUT_RESOURCE_SLOT_COUNT, _ia_vertex_buffers, _ia_vertex_strides, _ia_vertex_offsets); - else - _device_context->IAGetVertexBuffers(0, D3D10_IA_VERTEX_INPUT_RESOURCE_SLOT_COUNT, _ia_vertex_buffers, _ia_vertex_strides, _ia_vertex_offsets); - - _device_context->IAGetIndexBuffer(&_ia_index_buffer, &_ia_index_format, &_ia_index_offset); - - _device_context->RSGetState(&_rs_state); - _device_context->RSGetViewports(&_rs_num_viewports, nullptr); - _device_context->RSGetViewports(&_rs_num_viewports, _rs_viewports); - _device_context->RSGetScissorRects(&_rs_num_scissor_rects, nullptr); - _device_context->RSGetScissorRects(&_rs_num_scissor_rects, _rs_scissor_rects); - - _vs_num_class_instances = ARRAYSIZE(_vs_class_instances); - _device_context->VSGetShader(&_vs, _vs_class_instances, &_vs_num_class_instances); - _device_context->VSGetConstantBuffers(0, ARRAYSIZE(_vs_constant_buffers), _vs_constant_buffers); - _device_context->VSGetSamplers(0, ARRAYSIZE(_vs_sampler_states), _vs_sampler_states); - _device_context->VSGetShaderResources(0, ARRAYSIZE(_vs_shader_resources), _vs_shader_resources); - - if (_device_feature_level >= D3D_FEATURE_LEVEL_10_0) - { - if (_device_feature_level >= D3D_FEATURE_LEVEL_11_0) - { - _hs_num_class_instances = ARRAYSIZE(_hs_class_instances); - _device_context->HSGetShader(&_hs, _hs_class_instances, &_hs_num_class_instances); - - _ds_num_class_instances = ARRAYSIZE(_ds_class_instances); - _device_context->DSGetShader(&_ds, _ds_class_instances, &_ds_num_class_instances); - } - - _gs_num_class_instances = ARRAYSIZE(_gs_class_instances); - _device_context->GSGetShader(&_gs, _gs_class_instances, &_gs_num_class_instances); - } - - _ps_num_class_instances = ARRAYSIZE(_ps_class_instances); - _device_context->PSGetShader(&_ps, _ps_class_instances, &_ps_num_class_instances); - _device_context->PSGetConstantBuffers(0, ARRAYSIZE(_ps_constant_buffers), _ps_constant_buffers); - _device_context->PSGetSamplers(0, ARRAYSIZE(_ps_sampler_states), _ps_sampler_states); - _device_context->PSGetShaderResources(0, ARRAYSIZE(_ps_shader_resources), _ps_shader_resources); - - _device_context->OMGetBlendState(&_om_blend_state, _om_blend_factor, &_om_sample_mask); - _device_context->OMGetDepthStencilState(&_om_depth_stencil_state, &_om_stencil_ref); - _device_context->OMGetRenderTargets(ARRAYSIZE(_om_render_targets), _om_render_targets, &_om_depth_stencil); -} -void reshade::d3d11::state_block::apply_and_release() -{ - _device_context->IASetPrimitiveTopology(_ia_primitive_topology); - _device_context->IASetInputLayout(_ia_input_layout); - - if (_device_feature_level > D3D_FEATURE_LEVEL_10_0) - _device_context->IASetVertexBuffers(0, D3D11_IA_VERTEX_INPUT_RESOURCE_SLOT_COUNT, _ia_vertex_buffers, _ia_vertex_strides, _ia_vertex_offsets); - else - _device_context->IASetVertexBuffers(0, D3D10_IA_VERTEX_INPUT_RESOURCE_SLOT_COUNT, _ia_vertex_buffers, _ia_vertex_strides, _ia_vertex_offsets); - - _device_context->IASetIndexBuffer(_ia_index_buffer, _ia_index_format, _ia_index_offset); - - _device_context->RSSetState(_rs_state); - _device_context->RSSetViewports(_rs_num_viewports, _rs_viewports); - _device_context->RSSetScissorRects(_rs_num_scissor_rects, _rs_scissor_rects); - - _device_context->VSSetShader(_vs, _vs_class_instances, _vs_num_class_instances); - _device_context->VSSetConstantBuffers(0, ARRAYSIZE(_vs_constant_buffers), _vs_constant_buffers); - _device_context->VSSetSamplers(0, ARRAYSIZE(_vs_sampler_states), _vs_sampler_states); - _device_context->VSSetShaderResources(0, ARRAYSIZE(_vs_shader_resources), _vs_shader_resources); - - if (_device_feature_level >= D3D_FEATURE_LEVEL_10_0) - { - if (_device_feature_level >= D3D_FEATURE_LEVEL_11_0) - { - _device_context->HSSetShader(_hs, _hs_class_instances, _hs_num_class_instances); - _device_context->DSSetShader(_ds, _ds_class_instances, _ds_num_class_instances); - } - - _device_context->GSSetShader(_gs, _gs_class_instances, _gs_num_class_instances); - } - - _device_context->PSSetShader(_ps, _ps_class_instances, _ps_num_class_instances); - _device_context->PSSetConstantBuffers(0, ARRAYSIZE(_ps_constant_buffers), _ps_constant_buffers); - _device_context->PSSetSamplers(0, ARRAYSIZE(_ps_sampler_states), _ps_sampler_states); - _device_context->PSSetShaderResources(0, ARRAYSIZE(_ps_shader_resources), _ps_shader_resources); - - _device_context->OMSetBlendState(_om_blend_state, _om_blend_factor, _om_sample_mask); - _device_context->OMSetDepthStencilState(_om_depth_stencil_state, _om_stencil_ref); - _device_context->OMSetRenderTargets(ARRAYSIZE(_om_render_targets), _om_render_targets, _om_depth_stencil); - - release_all_device_objects(); - - _device_context.reset(); -} - -void reshade::d3d11::state_block::release_all_device_objects() -{ - safe_release(_ia_input_layout); - for (auto &vertex_buffer : _ia_vertex_buffers) - safe_release(vertex_buffer); - safe_release(_ia_index_buffer); - safe_release(_vs); - for (UINT i = 0; i < _vs_num_class_instances; i++) - safe_release(_vs_class_instances[i]); - for (auto &constant_buffer : _vs_constant_buffers) - safe_release(constant_buffer); - for (auto &sampler_state : _vs_sampler_states) - safe_release(sampler_state); - for (auto &shader_resource : _vs_shader_resources) - safe_release(shader_resource); - safe_release(_hs); - for (UINT i = 0; i < _hs_num_class_instances; i++) - safe_release(_hs_class_instances[i]); - safe_release(_ds); - for (UINT i = 0; i < _ds_num_class_instances; i++) - safe_release(_ds_class_instances[i]); - safe_release(_gs); - for (UINT i = 0; i < _gs_num_class_instances; i++) - safe_release(_gs_class_instances[i]); - safe_release(_rs_state); - safe_release(_ps); - for (UINT i = 0; i < _ps_num_class_instances; i++) - safe_release(_ps_class_instances[i]); - for (auto &constant_buffer : _ps_constant_buffers) - safe_release(constant_buffer); - for (auto &sampler_state : _ps_sampler_states) - safe_release(sampler_state); - for (auto &shader_resource : _ps_shader_resources) - safe_release(shader_resource); - safe_release(_om_blend_state); - safe_release(_om_depth_stencil_state); - for (auto &render_target : _om_render_targets) - safe_release(render_target); - safe_release(_om_depth_stencil); -} diff --git a/msvc/source/d3d11/state_block.hpp b/msvc/source/d3d11/state_block.hpp deleted file mode 100644 index 304b31b..0000000 --- a/msvc/source/d3d11/state_block.hpp +++ /dev/null @@ -1,70 +0,0 @@ -/** - * Copyright (C) 2014 Patrick Mours. All rights reserved. - * License: https://github.com/crosire/reshade#license - */ - -#pragma once - -#include "com_ptr.hpp" -#include - -namespace reshade::d3d11 -{ - class state_block - { - public: - explicit state_block(ID3D11Device *device); - ~state_block(); - - void capture(ID3D11DeviceContext *devicecontext); - void apply_and_release(); - - private: - void release_all_device_objects(); - - D3D_FEATURE_LEVEL _device_feature_level; - com_ptr _device; - com_ptr _device_context; - ID3D11InputLayout *_ia_input_layout; - D3D11_PRIMITIVE_TOPOLOGY _ia_primitive_topology; - ID3D11Buffer *_ia_vertex_buffers[D3D11_IA_VERTEX_INPUT_RESOURCE_SLOT_COUNT]; - UINT _ia_vertex_strides[D3D11_IA_VERTEX_INPUT_RESOURCE_SLOT_COUNT]; - UINT _ia_vertex_offsets[D3D11_IA_VERTEX_INPUT_RESOURCE_SLOT_COUNT]; - ID3D11Buffer *_ia_index_buffer; - DXGI_FORMAT _ia_index_format; - UINT _ia_index_offset; - ID3D11VertexShader *_vs; - UINT _vs_num_class_instances; - ID3D11ClassInstance *_vs_class_instances[256]; - ID3D11Buffer *_vs_constant_buffers[D3D11_COMMONSHADER_CONSTANT_BUFFER_API_SLOT_COUNT]; - ID3D11SamplerState *_vs_sampler_states[D3D11_COMMONSHADER_SAMPLER_SLOT_COUNT]; - ID3D11ShaderResourceView *_vs_shader_resources[D3D11_COMMONSHADER_INPUT_RESOURCE_SLOT_COUNT]; - ID3D11HullShader *_hs; - UINT _hs_num_class_instances; - ID3D11ClassInstance *_hs_class_instances[256]; - ID3D11DomainShader *_ds; - UINT _ds_num_class_instances; - ID3D11ClassInstance *_ds_class_instances[256]; - ID3D11GeometryShader *_gs; - UINT _gs_num_class_instances; - ID3D11ClassInstance *_gs_class_instances[256]; - ID3D11RasterizerState *_rs_state; - UINT _rs_num_viewports; - D3D11_VIEWPORT _rs_viewports[D3D11_VIEWPORT_AND_SCISSORRECT_OBJECT_COUNT_PER_PIPELINE]; - UINT _rs_num_scissor_rects; - D3D11_RECT _rs_scissor_rects[D3D11_VIEWPORT_AND_SCISSORRECT_OBJECT_COUNT_PER_PIPELINE]; - ID3D11PixelShader *_ps; - UINT _ps_num_class_instances; - ID3D11ClassInstance *_ps_class_instances[256]; - ID3D11Buffer *_ps_constant_buffers[D3D11_COMMONSHADER_CONSTANT_BUFFER_API_SLOT_COUNT]; - ID3D11SamplerState *_ps_sampler_states[D3D11_COMMONSHADER_SAMPLER_SLOT_COUNT]; - ID3D11ShaderResourceView *_ps_shader_resources[D3D11_COMMONSHADER_INPUT_RESOURCE_SLOT_COUNT]; - ID3D11BlendState *_om_blend_state; - FLOAT _om_blend_factor[4]; - UINT _om_sample_mask; - ID3D11DepthStencilState *_om_depth_stencil_state; - UINT _om_stencil_ref; - ID3D11RenderTargetView *_om_render_targets[D3D11_SIMULTANEOUS_RENDER_TARGET_COUNT]; - ID3D11DepthStencilView *_om_depth_stencil; - }; -} diff --git a/msvc/source/d3d12/d3d12.cpp b/msvc/source/d3d12/d3d12.cpp deleted file mode 100644 index cbe08f9..0000000 --- a/msvc/source/d3d12/d3d12.cpp +++ /dev/null @@ -1,92 +0,0 @@ -/** - * Copyright (C) 2014 Patrick Mours. All rights reserved. - * License: https://github.com/crosire/reshade#license - */ - -#include "log.hpp" -#include "hook_manager.hpp" -#include "d3d12_device.hpp" - -extern reshade::log::message &operator<<(reshade::log::message &m, REFIID riid); - -HOOK_EXPORT HRESULT WINAPI D3D12CreateDevice( - IUnknown *pAdapter, - D3D_FEATURE_LEVEL MinimumFeatureLevel, - REFIID riid, - void **ppDevice) -{ - LOG(INFO) << "Redirecting D3D12CreateDevice" << '(' << pAdapter << ", " << std::hex << MinimumFeatureLevel << std::dec << ", " << riid << ", " << ppDevice << ')' << " ..."; - - const HRESULT hr = reshade::hooks::call(D3D12CreateDevice)(pAdapter, MinimumFeatureLevel, riid, ppDevice); - - if (FAILED(hr)) - { - LOG(WARN) << "> D3D12CreateDevice failed with error code " << std::hex << hr << std::dec << '!'; - return hr; - } - - if (ppDevice == nullptr) - return hr; - - // The returned device should alway implement the 'ID3D12Device' base interface - const auto device_proxy = new D3D12Device(static_cast(*ppDevice)); - device_proxy->check_and_upgrade_interface(riid); // Upgrade to the actual interface version requested here - - *ppDevice = device_proxy; - -#if RESHADE_VERBOSE_LOG - LOG(DEBUG) << "Returning ID3D12Device" << device_proxy->_interface_version << " object " << device_proxy << '.'; -#endif - return hr; -} - -HOOK_EXPORT HRESULT WINAPI D3D12GetDebugInterface( - REFIID riid, - void **ppvDebug) -{ - return reshade::hooks::call(D3D12GetDebugInterface)(riid, ppvDebug); -} - -HOOK_EXPORT HRESULT WINAPI D3D12CreateRootSignatureDeserializer( - LPCVOID pSrcData, - SIZE_T SrcDataSizeInBytes, - REFIID pRootSignatureDeserializerInterface, - void **ppRootSignatureDeserializer) -{ - return reshade::hooks::call(D3D12CreateRootSignatureDeserializer)(pSrcData, SrcDataSizeInBytes, pRootSignatureDeserializerInterface, ppRootSignatureDeserializer); -} - -HOOK_EXPORT HRESULT WINAPI D3D12CreateVersionedRootSignatureDeserializer( - LPCVOID pSrcData, - SIZE_T SrcDataSizeInBytes, - REFIID pRootSignatureDeserializerInterface, - void **ppRootSignatureDeserializer) -{ - return reshade::hooks::call(D3D12CreateVersionedRootSignatureDeserializer)(pSrcData, SrcDataSizeInBytes, pRootSignatureDeserializerInterface, ppRootSignatureDeserializer); -} - -HOOK_EXPORT HRESULT WINAPI D3D12EnableExperimentalFeatures( - UINT NumFeatures, - const IID *pIIDs, - void *pConfigurationStructs, - UINT *pConfigurationStructSizes) -{ - return reshade::hooks::call(D3D12EnableExperimentalFeatures)(NumFeatures, pIIDs, pConfigurationStructs, pConfigurationStructSizes); -} - -HOOK_EXPORT HRESULT WINAPI D3D12SerializeRootSignature( - const D3D12_ROOT_SIGNATURE_DESC *pRootSignature, - D3D_ROOT_SIGNATURE_VERSION Version, - ID3DBlob **ppBlob, - ID3DBlob **ppErrorBlob) -{ - return reshade::hooks::call(D3D12SerializeRootSignature)(pRootSignature, Version, ppBlob, ppErrorBlob); -} - -HOOK_EXPORT HRESULT WINAPI D3D12SerializeVersionedRootSignature( - const D3D12_VERSIONED_ROOT_SIGNATURE_DESC *pRootSignature, - ID3DBlob **ppBlob, - ID3DBlob **ppErrorBlob) -{ - return reshade::hooks::call(D3D12SerializeVersionedRootSignature)(pRootSignature, ppBlob, ppErrorBlob); -} diff --git a/msvc/source/d3d12/d3d12_command_queue.cpp b/msvc/source/d3d12/d3d12_command_queue.cpp deleted file mode 100644 index ff5785a..0000000 --- a/msvc/source/d3d12/d3d12_command_queue.cpp +++ /dev/null @@ -1,126 +0,0 @@ -/** - * Copyright (C) 2014 Patrick Mours. All rights reserved. - * License: https://github.com/crosire/reshade#license - */ - -#include "log.hpp" -#include "d3d12_device.hpp" -#include "d3d12_command_queue.hpp" -#include - -D3D12CommandQueue::D3D12CommandQueue(D3D12Device *device, ID3D12CommandQueue *original) : - _orig(original), - _device(device) { - assert(original != nullptr); -} - -HRESULT STDMETHODCALLTYPE D3D12CommandQueue::QueryInterface(REFIID riid, void **ppvObj) -{ - if (ppvObj == nullptr) - return E_POINTER; - - if (riid == __uuidof(this) || - riid == __uuidof(IUnknown) || - riid == __uuidof(ID3D12Object) || - riid == __uuidof(ID3D12DeviceChild) || - riid == __uuidof(ID3D12Pageable) || - riid == __uuidof(ID3D12CommandQueue)) - { - AddRef(); - *ppvObj = this; - return S_OK; - } - - return _orig->QueryInterface(riid, ppvObj); -} -ULONG STDMETHODCALLTYPE D3D12CommandQueue::AddRef() -{ - ++_ref; - - return _orig->AddRef(); -} -ULONG STDMETHODCALLTYPE D3D12CommandQueue::Release() -{ - --_ref; - - const ULONG ref = _orig->Release(); - - if (ref != 0 && _ref != 0) - return ref; - else if (ref != 0) - LOG(WARN) << "Reference count for ID3D12CommandQueue object " << this << " is inconsistent: " << ref << ", but expected 0."; - - assert(_ref <= 0); -#if RESHADE_VERBOSE_LOG - LOG(DEBUG) << "Destroyed ID3D12CommandQueue object " << this << "."; -#endif - delete this; - return 0; -} - -HRESULT STDMETHODCALLTYPE D3D12CommandQueue::GetPrivateData(REFGUID guid, UINT *pDataSize, void *pData) -{ - return _orig->GetPrivateData(guid, pDataSize, pData); -} -HRESULT STDMETHODCALLTYPE D3D12CommandQueue::SetPrivateData(REFGUID guid, UINT DataSize, const void *pData) -{ - return _orig->SetPrivateData(guid, DataSize, pData); -} -HRESULT STDMETHODCALLTYPE D3D12CommandQueue::SetPrivateDataInterface(REFGUID guid, const IUnknown *pData) -{ - return _orig->SetPrivateDataInterface(guid, pData); -} -HRESULT STDMETHODCALLTYPE D3D12CommandQueue::SetName(LPCWSTR Name) -{ - return _orig->SetName(Name); -} - -HRESULT STDMETHODCALLTYPE D3D12CommandQueue::GetDevice(REFIID riid, void **ppvDevice) -{ - return _device->QueryInterface(riid, ppvDevice); -} - -void STDMETHODCALLTYPE D3D12CommandQueue::UpdateTileMappings(ID3D12Resource *pResource, UINT NumResourceRegions, const D3D12_TILED_RESOURCE_COORDINATE *pResourceRegionStartCoordinates, const D3D12_TILE_REGION_SIZE *pResourceRegionSizes, ID3D12Heap *pHeap, UINT NumRanges, const D3D12_TILE_RANGE_FLAGS *pRangeFlags, const UINT *pHeapRangeStartOffsets, const UINT *pRangeTileCounts, D3D12_TILE_MAPPING_FLAGS Flags) -{ - _orig->UpdateTileMappings(pResource, NumResourceRegions, pResourceRegionStartCoordinates, pResourceRegionSizes, pHeap, NumRanges, pRangeFlags, pHeapRangeStartOffsets, pRangeTileCounts, Flags); -} -void STDMETHODCALLTYPE D3D12CommandQueue::CopyTileMappings(ID3D12Resource *pDstResource, const D3D12_TILED_RESOURCE_COORDINATE *pDstRegionStartCoordinate, ID3D12Resource *pSrcResource, const D3D12_TILED_RESOURCE_COORDINATE *pSrcRegionStartCoordinate, const D3D12_TILE_REGION_SIZE *pRegionSize, D3D12_TILE_MAPPING_FLAGS Flags) -{ - _orig->CopyTileMappings(pDstResource, pDstRegionStartCoordinate, pSrcResource, pSrcRegionStartCoordinate, pRegionSize, Flags); -} -void STDMETHODCALLTYPE D3D12CommandQueue::ExecuteCommandLists(UINT NumCommandLists, ID3D12CommandList *const *ppCommandLists) -{ - _orig->ExecuteCommandLists(NumCommandLists, ppCommandLists); -} -void STDMETHODCALLTYPE D3D12CommandQueue::SetMarker(UINT Metadata, const void *pData, UINT Size) -{ - _orig->SetMarker(Metadata, pData, Size); -} -void STDMETHODCALLTYPE D3D12CommandQueue::BeginEvent(UINT Metadata, const void *pData, UINT Size) -{ - _orig->BeginEvent(Metadata, pData, Size); -} -void STDMETHODCALLTYPE D3D12CommandQueue::EndEvent() -{ - _orig->EndEvent(); -} -HRESULT STDMETHODCALLTYPE D3D12CommandQueue::Signal(ID3D12Fence *pFence, UINT64 Value) -{ - return _orig->Signal(pFence, Value); -} -HRESULT STDMETHODCALLTYPE D3D12CommandQueue::Wait(ID3D12Fence *pFence, UINT64 Value) -{ - return _orig->Wait(pFence, Value); -} -HRESULT STDMETHODCALLTYPE D3D12CommandQueue::GetTimestampFrequency(UINT64 *pFrequency) -{ - return _orig->GetTimestampFrequency(pFrequency); -} -HRESULT STDMETHODCALLTYPE D3D12CommandQueue::GetClockCalibration(UINT64 *pGpuTimestamp, UINT64 *pCpuTimestamp) -{ - return _orig->GetClockCalibration(pGpuTimestamp, pCpuTimestamp); -} -D3D12_COMMAND_QUEUE_DESC STDMETHODCALLTYPE D3D12CommandQueue::GetDesc() -{ - return _orig->GetDesc(); -} diff --git a/msvc/source/d3d12/d3d12_command_queue.hpp b/msvc/source/d3d12/d3d12_command_queue.hpp deleted file mode 100644 index b97a9ba..0000000 --- a/msvc/source/d3d12/d3d12_command_queue.hpp +++ /dev/null @@ -1,47 +0,0 @@ -/** - * Copyright (C) 2014 Patrick Mours. All rights reserved. - * License: https://github.com/crosire/reshade#license - */ - -#pragma once - -#include - -struct __declspec(uuid("2C576D2A-0C1C-4D1D-AD7C-BC4FAEC15ABC")) D3D12CommandQueue : ID3D12CommandQueue -{ - D3D12CommandQueue(D3D12Device *device, ID3D12CommandQueue *original); - - D3D12CommandQueue(const D3D12CommandQueue &) = delete; - D3D12CommandQueue &operator=(const D3D12CommandQueue &) = delete; - - HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void **ppvObj) override; - ULONG STDMETHODCALLTYPE AddRef() override; - ULONG STDMETHODCALLTYPE Release() override; - - #pragma region ID3D12Object - HRESULT STDMETHODCALLTYPE GetPrivateData(REFGUID guid, UINT *pDataSize, void *pData) override; - HRESULT STDMETHODCALLTYPE SetPrivateData(REFGUID guid, UINT DataSize, const void *pData) override; - HRESULT STDMETHODCALLTYPE SetPrivateDataInterface(REFGUID guid, const IUnknown *pData) override; - HRESULT STDMETHODCALLTYPE SetName(LPCWSTR Name) override; - #pragma endregion - #pragma region ID3D12DeviceChild - HRESULT STDMETHODCALLTYPE GetDevice(REFIID riid, void **ppvDevice) override; - #pragma endregion - #pragma region ID3D12CommandQueue - void STDMETHODCALLTYPE UpdateTileMappings(ID3D12Resource *pResource, UINT NumResourceRegions, const D3D12_TILED_RESOURCE_COORDINATE *pResourceRegionStartCoordinates, const D3D12_TILE_REGION_SIZE *pResourceRegionSizes, ID3D12Heap *pHeap, UINT NumRanges, const D3D12_TILE_RANGE_FLAGS *pRangeFlags, const UINT *pHeapRangeStartOffsets, const UINT *pRangeTileCounts, D3D12_TILE_MAPPING_FLAGS Flags) override; - void STDMETHODCALLTYPE CopyTileMappings(ID3D12Resource *pDstResource, const D3D12_TILED_RESOURCE_COORDINATE *pDstRegionStartCoordinate, ID3D12Resource *pSrcResource, const D3D12_TILED_RESOURCE_COORDINATE *pSrcRegionStartCoordinate, const D3D12_TILE_REGION_SIZE *pRegionSize, D3D12_TILE_MAPPING_FLAGS Flags) override; - void STDMETHODCALLTYPE ExecuteCommandLists(UINT NumCommandLists, ID3D12CommandList *const *ppCommandLists) override; - void STDMETHODCALLTYPE SetMarker(UINT Metadata, const void *pData, UINT Size) override; - void STDMETHODCALLTYPE BeginEvent(UINT Metadata, const void *pData, UINT Size) override; - void STDMETHODCALLTYPE EndEvent() override; - HRESULT STDMETHODCALLTYPE Signal(ID3D12Fence *pFence, UINT64 Value) override; - HRESULT STDMETHODCALLTYPE Wait(ID3D12Fence *pFence, UINT64 Value) override; - HRESULT STDMETHODCALLTYPE GetTimestampFrequency(UINT64 *pFrequency) override; - HRESULT STDMETHODCALLTYPE GetClockCalibration(UINT64 *pGpuTimestamp, UINT64 *pCpuTimestamp) override; - D3D12_COMMAND_QUEUE_DESC STDMETHODCALLTYPE GetDesc() override; - #pragma endregion - - ULONG _ref = 1; - ID3D12CommandQueue *const _orig; - D3D12Device *const _device; -}; diff --git a/msvc/source/d3d12/d3d12_device.cpp b/msvc/source/d3d12/d3d12_device.cpp deleted file mode 100644 index 6fb30fc..0000000 --- a/msvc/source/d3d12/d3d12_device.cpp +++ /dev/null @@ -1,396 +0,0 @@ -/** - * Copyright (C) 2014 Patrick Mours. All rights reserved. - * License: https://github.com/crosire/reshade#license - */ - -#include "log.hpp" -#include "d3d12_device.hpp" -#include "d3d12_command_queue.hpp" -#include "../dxgi/dxgi_device.hpp" -#include - -extern reshade::log::message &operator<<(reshade::log::message &m, REFIID riid); - -D3D12Device::D3D12Device(ID3D12Device *original) : - _orig(original), - _interface_version(0) { - assert(original != nullptr); -} - -bool D3D12Device::check_and_upgrade_interface(REFIID riid) -{ - static const IID iid_lookup[] = { - __uuidof(ID3D12Device), - __uuidof(ID3D12Device1), - __uuidof(ID3D12Device2), - __uuidof(ID3D12Device3), - __uuidof(ID3D12Device4), - __uuidof(ID3D12Device5), - }; - - for (unsigned int new_version = _interface_version + 1; new_version < ARRAYSIZE(iid_lookup); ++new_version) - { - if (riid == iid_lookup[new_version]) - { - IUnknown *new_interface = nullptr; - if (FAILED(_orig->QueryInterface(riid, reinterpret_cast(&new_interface)))) - return false; -#if RESHADE_VERBOSE_LOG - LOG(DEBUG) << "Upgraded ID3D12Device" << _interface_version << " object " << this << " to ID3D12Device" << new_version << '.'; -#endif - _orig->Release(); - _orig = static_cast(new_interface); - _interface_version = new_version; - break; - } - } - - return true; -} - -HRESULT STDMETHODCALLTYPE D3D12Device::QueryInterface(REFIID riid, void **ppvObj) -{ - if (ppvObj == nullptr) - return E_POINTER; - - if (riid == __uuidof(this) || - riid == __uuidof(IUnknown) || - riid == __uuidof(ID3D12Object) || - riid == __uuidof(ID3D12Device) || - riid == __uuidof(ID3D12Device1) || - riid == __uuidof(ID3D12Device2) || - riid == __uuidof(ID3D12Device3) || - riid == __uuidof(ID3D12Device4) || - riid == __uuidof(ID3D12Device5)) - { - if (!check_and_upgrade_interface(riid)) - return E_NOINTERFACE; - - AddRef(); - *ppvObj = this; - return S_OK; - } - - return _orig->QueryInterface(riid, ppvObj); -} -ULONG STDMETHODCALLTYPE D3D12Device::AddRef() -{ - ++_ref; - - return _orig->AddRef(); -} -ULONG STDMETHODCALLTYPE D3D12Device::Release() -{ - --_ref; - - const ULONG ref = _orig->Release(); - - if (ref != 0 && _ref != 0) - return ref; - else if (ref != 0) - LOG(WARN) << "Reference count for ID3D12Device" << _interface_version << " object " << this << " is inconsistent: " << ref << ", but expected 0."; - - assert(_ref <= 0); -#if RESHADE_VERBOSE_LOG - LOG(DEBUG) << "Destroyed ID3D12Device" << _interface_version << " object " << this << "."; -#endif - delete this; - return 0; -} - -HRESULT STDMETHODCALLTYPE D3D12Device::GetPrivateData(REFGUID guid, UINT *pDataSize, void *pData) -{ - return _orig->GetPrivateData(guid, pDataSize, pData); -} -HRESULT STDMETHODCALLTYPE D3D12Device::SetPrivateData(REFGUID guid, UINT DataSize, const void *pData) -{ - return _orig->SetPrivateData(guid, DataSize, pData); -} -HRESULT STDMETHODCALLTYPE D3D12Device::SetPrivateDataInterface(REFGUID guid, const IUnknown *pData) -{ - return _orig->SetPrivateDataInterface(guid, pData); -} -HRESULT STDMETHODCALLTYPE D3D12Device::SetName(LPCWSTR Name) -{ - return _orig->SetName(Name); -} - -UINT STDMETHODCALLTYPE D3D12Device::GetNodeCount() -{ - return _orig->GetNodeCount(); -} -HRESULT STDMETHODCALLTYPE D3D12Device::CreateCommandQueue(const D3D12_COMMAND_QUEUE_DESC *pDesc, REFIID riid, void **ppCommandQueue) -{ - LOG(INFO) << "Redirecting ID3D12Device::CreateCommandQueue" << '(' << this << ", " << pDesc << ", " << riid << ", " << ppCommandQueue << ')' << " ..."; - - if (ppCommandQueue == nullptr) - return E_INVALIDARG; - - const HRESULT hr = _orig->CreateCommandQueue(pDesc, riid, ppCommandQueue); - - if (FAILED(hr)) - { - LOG(WARN) << "> 'ID3D12Device::CreateCommandQueue' failed with error code " << std::hex << hr << std::dec << "!"; - return hr; - } - - if (riid == __uuidof(ID3D12CommandQueue)) - { - *ppCommandQueue = new D3D12CommandQueue(this, static_cast(*ppCommandQueue)); - } - -#if RESHADE_VERBOSE_LOG - LOG(DEBUG) << "Returning ID3D12CommandQueue object " << *ppCommandQueue << '.'; -#endif - return hr; -} -HRESULT STDMETHODCALLTYPE D3D12Device::CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE type, REFIID riid, void **ppCommandAllocator) -{ - return _orig->CreateCommandAllocator(type, riid, ppCommandAllocator); -} -HRESULT STDMETHODCALLTYPE D3D12Device::CreateGraphicsPipelineState(const D3D12_GRAPHICS_PIPELINE_STATE_DESC *pDesc, REFIID riid, void **ppPipelineState) -{ - return _orig->CreateGraphicsPipelineState(pDesc, riid, ppPipelineState); -} -HRESULT STDMETHODCALLTYPE D3D12Device::CreateComputePipelineState(const D3D12_COMPUTE_PIPELINE_STATE_DESC *pDesc, REFIID riid, void **ppPipelineState) -{ - return _orig->CreateComputePipelineState(pDesc, riid, ppPipelineState); -} -HRESULT STDMETHODCALLTYPE D3D12Device::CreateCommandList(UINT nodeMask, D3D12_COMMAND_LIST_TYPE type, ID3D12CommandAllocator *pCommandAllocator, ID3D12PipelineState *pInitialState, REFIID riid, void **ppCommandList) -{ - return _orig->CreateCommandList(nodeMask, type, pCommandAllocator, pInitialState, riid, ppCommandList); -} -HRESULT STDMETHODCALLTYPE D3D12Device::CheckFeatureSupport(D3D12_FEATURE Feature, void *pFeatureSupportData, UINT FeatureSupportDataSize) -{ - return _orig->CheckFeatureSupport(Feature, pFeatureSupportData, FeatureSupportDataSize); -} -HRESULT STDMETHODCALLTYPE D3D12Device::CreateDescriptorHeap(const D3D12_DESCRIPTOR_HEAP_DESC *pDescriptorHeapDesc, REFIID riid, void **ppvHeap) -{ - return _orig->CreateDescriptorHeap(pDescriptorHeapDesc, riid, ppvHeap); -} -UINT STDMETHODCALLTYPE D3D12Device::GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE DescriptorHeapType) -{ - return _orig->GetDescriptorHandleIncrementSize(DescriptorHeapType); -} -HRESULT STDMETHODCALLTYPE D3D12Device::CreateRootSignature(UINT nodeMask, const void *pBlobWithRootSignature, SIZE_T blobLengthInBytes, REFIID riid, void **ppvRootSignature) -{ - return _orig->CreateRootSignature(nodeMask, pBlobWithRootSignature, blobLengthInBytes, riid, ppvRootSignature); -} -void STDMETHODCALLTYPE D3D12Device::CreateConstantBufferView(const D3D12_CONSTANT_BUFFER_VIEW_DESC *pDesc, D3D12_CPU_DESCRIPTOR_HANDLE DestDescriptor) -{ - _orig->CreateConstantBufferView(pDesc, DestDescriptor); -} -void STDMETHODCALLTYPE D3D12Device::CreateShaderResourceView(ID3D12Resource *pResource, const D3D12_SHADER_RESOURCE_VIEW_DESC *pDesc, D3D12_CPU_DESCRIPTOR_HANDLE DestDescriptor) -{ - _orig->CreateShaderResourceView(pResource, pDesc, DestDescriptor); -} -void STDMETHODCALLTYPE D3D12Device::CreateUnorderedAccessView(ID3D12Resource *pResource, ID3D12Resource *pCounterResource, const D3D12_UNORDERED_ACCESS_VIEW_DESC *pDesc, D3D12_CPU_DESCRIPTOR_HANDLE DestDescriptor) -{ - _orig->CreateUnorderedAccessView(pResource, pCounterResource, pDesc, DestDescriptor); -} -void STDMETHODCALLTYPE D3D12Device::CreateRenderTargetView(ID3D12Resource *pResource, const D3D12_RENDER_TARGET_VIEW_DESC *pDesc, D3D12_CPU_DESCRIPTOR_HANDLE DestDescriptor) -{ - _orig->CreateRenderTargetView(pResource, pDesc, DestDescriptor); -} -void STDMETHODCALLTYPE D3D12Device::CreateDepthStencilView(ID3D12Resource *pResource, const D3D12_DEPTH_STENCIL_VIEW_DESC *pDesc, D3D12_CPU_DESCRIPTOR_HANDLE DestDescriptor) -{ - _orig->CreateDepthStencilView(pResource, pDesc, DestDescriptor); -} -void STDMETHODCALLTYPE D3D12Device::CreateSampler(const D3D12_SAMPLER_DESC *pDesc, D3D12_CPU_DESCRIPTOR_HANDLE DestDescriptor) -{ - _orig->CreateSampler(pDesc, DestDescriptor); -} -void STDMETHODCALLTYPE D3D12Device::CopyDescriptors(UINT NumDestDescriptorRanges, const D3D12_CPU_DESCRIPTOR_HANDLE *pDestDescriptorRangeStarts, const UINT *pDestDescriptorRangeSizes, UINT NumSrcDescriptorRanges, const D3D12_CPU_DESCRIPTOR_HANDLE *pSrcDescriptorRangeStarts, const UINT *pSrcDescriptorRangeSizes, D3D12_DESCRIPTOR_HEAP_TYPE DescriptorHeapsType) -{ - _orig->CopyDescriptors(NumDestDescriptorRanges, pDestDescriptorRangeStarts, pDestDescriptorRangeSizes, NumSrcDescriptorRanges, pSrcDescriptorRangeStarts, pSrcDescriptorRangeSizes, DescriptorHeapsType); -} -void STDMETHODCALLTYPE D3D12Device::CopyDescriptorsSimple(UINT NumDescriptors, D3D12_CPU_DESCRIPTOR_HANDLE DestDescriptorRangeStart, D3D12_CPU_DESCRIPTOR_HANDLE SrcDescriptorRangeStart, D3D12_DESCRIPTOR_HEAP_TYPE DescriptorHeapsType) -{ - _orig->CopyDescriptorsSimple(NumDescriptors, DestDescriptorRangeStart, SrcDescriptorRangeStart, DescriptorHeapsType); -} -D3D12_RESOURCE_ALLOCATION_INFO STDMETHODCALLTYPE D3D12Device::GetResourceAllocationInfo(UINT visibleMask, UINT numResourceDescs, const D3D12_RESOURCE_DESC *pResourceDescs) -{ - return _orig->GetResourceAllocationInfo(visibleMask, numResourceDescs, pResourceDescs); -} -D3D12_HEAP_PROPERTIES STDMETHODCALLTYPE D3D12Device::GetCustomHeapProperties(UINT nodeMask, D3D12_HEAP_TYPE heapType) -{ - return _orig->GetCustomHeapProperties(nodeMask, heapType); -} -HRESULT STDMETHODCALLTYPE D3D12Device::CreateCommittedResource(const D3D12_HEAP_PROPERTIES *pHeapProperties, D3D12_HEAP_FLAGS HeapFlags, const D3D12_RESOURCE_DESC *pResourceDesc, D3D12_RESOURCE_STATES InitialResourceState, const D3D12_CLEAR_VALUE *pOptimizedClearValue, REFIID riidResource, void **ppvResource) -{ - return _orig->CreateCommittedResource(pHeapProperties, HeapFlags, pResourceDesc, InitialResourceState, pOptimizedClearValue, riidResource, ppvResource); -} -HRESULT STDMETHODCALLTYPE D3D12Device::CreateHeap(const D3D12_HEAP_DESC *pDesc, REFIID riid, void **ppvHeap) -{ - return _orig->CreateHeap(pDesc, riid, ppvHeap); -} -HRESULT STDMETHODCALLTYPE D3D12Device::CreatePlacedResource(ID3D12Heap *pHeap, UINT64 HeapOffset, const D3D12_RESOURCE_DESC *pDesc, D3D12_RESOURCE_STATES InitialState, const D3D12_CLEAR_VALUE *pOptimizedClearValue, REFIID riid, void **ppvResource) -{ - return _orig->CreatePlacedResource(pHeap, HeapOffset, pDesc, InitialState, pOptimizedClearValue, riid, ppvResource); -} -HRESULT STDMETHODCALLTYPE D3D12Device::CreateReservedResource(const D3D12_RESOURCE_DESC *pDesc, D3D12_RESOURCE_STATES InitialState, const D3D12_CLEAR_VALUE *pOptimizedClearValue, REFIID riid, void **ppvResource) -{ - return _orig->CreateReservedResource(pDesc, InitialState, pOptimizedClearValue, riid, ppvResource); -} -HRESULT STDMETHODCALLTYPE D3D12Device::CreateSharedHandle(ID3D12DeviceChild *pObject, const SECURITY_ATTRIBUTES *pAttributes, DWORD Access, LPCWSTR Name, HANDLE *pHandle) -{ - return _orig->CreateSharedHandle(pObject, pAttributes, Access, Name, pHandle); -} -HRESULT STDMETHODCALLTYPE D3D12Device::OpenSharedHandle(HANDLE NTHandle, REFIID riid, void **ppvObj) -{ - return _orig->OpenSharedHandle(NTHandle, riid, ppvObj); -} -HRESULT STDMETHODCALLTYPE D3D12Device::OpenSharedHandleByName(LPCWSTR Name, DWORD Access, HANDLE *pNTHandle) -{ - return _orig->OpenSharedHandleByName(Name, Access, pNTHandle); -} -HRESULT STDMETHODCALLTYPE D3D12Device::MakeResident(UINT NumObjects, ID3D12Pageable *const *ppObjects) -{ - return _orig->MakeResident(NumObjects, ppObjects); -} -HRESULT STDMETHODCALLTYPE D3D12Device::Evict(UINT NumObjects, ID3D12Pageable *const *ppObjects) -{ - return _orig->Evict(NumObjects, ppObjects); -} -HRESULT STDMETHODCALLTYPE D3D12Device::CreateFence(UINT64 InitialValue, D3D12_FENCE_FLAGS Flags, REFIID riid, void **ppFence) -{ - return _orig->CreateFence(InitialValue, Flags, riid, ppFence); -} -HRESULT STDMETHODCALLTYPE D3D12Device::GetDeviceRemovedReason() -{ - return _orig->GetDeviceRemovedReason(); -} -void STDMETHODCALLTYPE D3D12Device::GetCopyableFootprints(const D3D12_RESOURCE_DESC *pResourceDesc, UINT FirstSubresource, UINT NumSubresources, UINT64 BaseOffset, D3D12_PLACED_SUBRESOURCE_FOOTPRINT *pLayouts, UINT *pNumRows, UINT64 *pRowSizeInBytes, UINT64 *pTotalBytes) -{ - _orig->GetCopyableFootprints(pResourceDesc, FirstSubresource, NumSubresources, BaseOffset, pLayouts, pNumRows, pRowSizeInBytes, pTotalBytes); -} -HRESULT STDMETHODCALLTYPE D3D12Device::CreateQueryHeap(const D3D12_QUERY_HEAP_DESC *pDesc, REFIID riid, void **ppvHeap) -{ - return _orig->CreateQueryHeap(pDesc, riid, ppvHeap); -} -HRESULT STDMETHODCALLTYPE D3D12Device::SetStablePowerState(BOOL Enable) -{ - return _orig->SetStablePowerState(Enable); -} -HRESULT STDMETHODCALLTYPE D3D12Device::CreateCommandSignature(const D3D12_COMMAND_SIGNATURE_DESC *pDesc, ID3D12RootSignature *pRootSignature, REFIID riid, void **ppvCommandSignature) -{ - return _orig->CreateCommandSignature(pDesc, pRootSignature, riid, ppvCommandSignature); -} -void STDMETHODCALLTYPE D3D12Device::GetResourceTiling(ID3D12Resource *pTiledResource, UINT *pNumTilesForEntireResource, D3D12_PACKED_MIP_INFO *pPackedMipDesc, D3D12_TILE_SHAPE *pStandardTileShapeForNonPackedMips, UINT *pNumSubresourceTilings, UINT FirstSubresourceTilingToGet, D3D12_SUBRESOURCE_TILING *pSubresourceTilingsForNonPackedMips) -{ - _orig->GetResourceTiling(pTiledResource, pNumTilesForEntireResource, pPackedMipDesc, pStandardTileShapeForNonPackedMips, pNumSubresourceTilings, FirstSubresourceTilingToGet, pSubresourceTilingsForNonPackedMips); -} -LUID STDMETHODCALLTYPE D3D12Device::GetAdapterLuid() -{ - return _orig->GetAdapterLuid(); -} - -HRESULT STDMETHODCALLTYPE D3D12Device::CreatePipelineLibrary(const void *pLibraryBlob, SIZE_T BlobLength, REFIID riid, void **ppPipelineLibrary) -{ - assert(_interface_version >= 1); - return static_cast(_orig)->CreatePipelineLibrary(pLibraryBlob, BlobLength, riid, ppPipelineLibrary); -} -HRESULT STDMETHODCALLTYPE D3D12Device::SetEventOnMultipleFenceCompletion(ID3D12Fence *const *ppFences, const UINT64 *pFenceValues, UINT NumFences, D3D12_MULTIPLE_FENCE_WAIT_FLAGS Flags, HANDLE hEvent) -{ - assert(_interface_version >= 1); - return static_cast(_orig)->SetEventOnMultipleFenceCompletion(ppFences, pFenceValues, NumFences, Flags, hEvent); -} -HRESULT STDMETHODCALLTYPE D3D12Device::SetResidencyPriority(UINT NumObjects, ID3D12Pageable *const *ppObjects, const D3D12_RESIDENCY_PRIORITY *pPriorities) -{ - assert(_interface_version >= 1); - return static_cast(_orig)->SetResidencyPriority(NumObjects, ppObjects, pPriorities); -} - -HRESULT STDMETHODCALLTYPE D3D12Device::CreatePipelineState(const D3D12_PIPELINE_STATE_STREAM_DESC *pDesc, REFIID riid, void **ppPipelineState) -{ - assert(_interface_version >= 2); - return static_cast(_orig)->CreatePipelineState(pDesc, riid, ppPipelineState); -} - -HRESULT STDMETHODCALLTYPE D3D12Device::OpenExistingHeapFromAddress(const void *pAddress, REFIID riid, void **ppvHeap) -{ - assert(_interface_version >= 3); - return static_cast(_orig)->OpenExistingHeapFromAddress(pAddress, riid, ppvHeap); -} -HRESULT STDMETHODCALLTYPE D3D12Device::OpenExistingHeapFromFileMapping(HANDLE hFileMapping, REFIID riid, void **ppvHeap) -{ - assert(_interface_version >= 3); - return static_cast(_orig)->OpenExistingHeapFromFileMapping(hFileMapping, riid, ppvHeap); -} -HRESULT STDMETHODCALLTYPE D3D12Device::EnqueueMakeResident(D3D12_RESIDENCY_FLAGS Flags, UINT NumObjects, ID3D12Pageable *const *ppObjects, ID3D12Fence *pFenceToSignal, UINT64 FenceValueToSignal) -{ - assert(_interface_version >= 3); - return static_cast(_orig)->EnqueueMakeResident(Flags, NumObjects, ppObjects, pFenceToSignal, FenceValueToSignal); -} - -HRESULT STDMETHODCALLTYPE D3D12Device::CreateCommandList1(UINT NodeMask, D3D12_COMMAND_LIST_TYPE Type, D3D12_COMMAND_LIST_FLAGS Flags, REFIID riid, void **ppCommandList) -{ - assert(_interface_version >= 4); - return static_cast(_orig)->CreateCommandList1(NodeMask, Type, Flags, riid, ppCommandList); -} -HRESULT STDMETHODCALLTYPE D3D12Device::CreateProtectedResourceSession(const D3D12_PROTECTED_RESOURCE_SESSION_DESC *pDesc, REFIID riid, void **ppSession) -{ - assert(_interface_version >= 4); - return static_cast(_orig)->CreateProtectedResourceSession(pDesc, riid, ppSession); -} -HRESULT STDMETHODCALLTYPE D3D12Device::CreateCommittedResource1(const D3D12_HEAP_PROPERTIES *pHeapProperties, D3D12_HEAP_FLAGS HeapFlags, const D3D12_RESOURCE_DESC *pDesc, D3D12_RESOURCE_STATES InitialResourceState, const D3D12_CLEAR_VALUE *pOptimizedClearValue, ID3D12ProtectedResourceSession *pProtectedSession, REFIID riidResource, void **ppvResource) -{ - assert(_interface_version >= 4); - return static_cast(_orig)->CreateCommittedResource1(pHeapProperties, HeapFlags, pDesc, InitialResourceState, pOptimizedClearValue, pProtectedSession, riidResource, ppvResource); -} -HRESULT STDMETHODCALLTYPE D3D12Device::CreateHeap1(const D3D12_HEAP_DESC *pDesc, ID3D12ProtectedResourceSession *pProtectedSession, REFIID riid, void **ppvHeap) -{ - assert(_interface_version >= 4); - return static_cast(_orig)->CreateHeap1(pDesc, pProtectedSession, riid, ppvHeap); -} -HRESULT STDMETHODCALLTYPE D3D12Device::CreateReservedResource1(const D3D12_RESOURCE_DESC *pDesc, D3D12_RESOURCE_STATES InitialState, const D3D12_CLEAR_VALUE *pOptimizedClearValue, ID3D12ProtectedResourceSession *pProtectedSession, REFIID riid, void **ppvResource) -{ - assert(_interface_version >= 4); - return static_cast(_orig)->CreateReservedResource1(pDesc, InitialState, pOptimizedClearValue, pProtectedSession, riid, ppvResource); -} -D3D12_RESOURCE_ALLOCATION_INFO STDMETHODCALLTYPE D3D12Device::GetResourceAllocationInfo1(UINT VisibleMask, UINT NumResourceDescs, const D3D12_RESOURCE_DESC *pResourceDescs, D3D12_RESOURCE_ALLOCATION_INFO1 *pResourceAllocationInfo1) -{ - assert(_interface_version >= 4); - return static_cast(_orig)->GetResourceAllocationInfo1(VisibleMask, NumResourceDescs, pResourceDescs, pResourceAllocationInfo1); -} - -HRESULT STDMETHODCALLTYPE D3D12Device::CreateLifetimeTracker(ID3D12LifetimeOwner *pOwner, REFIID riid, void **ppvTracker) -{ - assert(_interface_version >= 5); - return static_cast(_orig)->CreateLifetimeTracker(pOwner, riid, ppvTracker); -} -void STDMETHODCALLTYPE D3D12Device::RemoveDevice() -{ - assert(_interface_version >= 5); - static_cast(_orig)->RemoveDevice(); -} -HRESULT STDMETHODCALLTYPE D3D12Device::EnumerateMetaCommands(UINT *pNumMetaCommands, D3D12_META_COMMAND_DESC *pDescs) -{ - assert(_interface_version >= 5); - return static_cast(_orig)->EnumerateMetaCommands(pNumMetaCommands, pDescs); -} -HRESULT STDMETHODCALLTYPE D3D12Device::EnumerateMetaCommandParameters(REFGUID CommandId, D3D12_META_COMMAND_PARAMETER_STAGE Stage, UINT *pTotalStructureSizeInBytes, UINT *pParameterCount, D3D12_META_COMMAND_PARAMETER_DESC *pParameterDescs) -{ - assert(_interface_version >= 5); - return static_cast(_orig)->EnumerateMetaCommandParameters(CommandId, Stage, pTotalStructureSizeInBytes, pParameterCount, pParameterDescs); -} -HRESULT STDMETHODCALLTYPE D3D12Device::CreateMetaCommand(REFGUID CommandId, UINT NodeMask, const void *pCreationParametersData, SIZE_T CreationParametersDataSizeInBytes, REFIID riid, void **ppMetaCommand) -{ - assert(_interface_version >= 5); - return static_cast(_orig)->CreateMetaCommand(CommandId, NodeMask, pCreationParametersData, CreationParametersDataSizeInBytes, riid, ppMetaCommand); -} -HRESULT STDMETHODCALLTYPE D3D12Device::CreateStateObject(const D3D12_STATE_OBJECT_DESC *pDesc, REFIID riid, void **ppStateObject) -{ - assert(_interface_version >= 5); - return static_cast(_orig)->CreateStateObject(pDesc, riid, ppStateObject); -} -void STDMETHODCALLTYPE D3D12Device::GetRaytracingAccelerationStructurePrebuildInfo(const D3D12_BUILD_RAYTRACING_ACCELERATION_STRUCTURE_INPUTS *pDesc, D3D12_RAYTRACING_ACCELERATION_STRUCTURE_PREBUILD_INFO *pInfo) -{ - assert(_interface_version >= 5); - static_cast(_orig)->GetRaytracingAccelerationStructurePrebuildInfo(pDesc, pInfo); -} -D3D12_DRIVER_MATCHING_IDENTIFIER_STATUS STDMETHODCALLTYPE D3D12Device::CheckDriverMatchingIdentifier(D3D12_SERIALIZED_DATA_TYPE SerializedDataType, const D3D12_SERIALIZED_DATA_DRIVER_MATCHING_IDENTIFIER *pIdentifierToCheck) -{ - assert(_interface_version >= 5); - return static_cast(_orig)->CheckDriverMatchingIdentifier(SerializedDataType, pIdentifierToCheck); -} diff --git a/msvc/source/d3d12/d3d12_device.hpp b/msvc/source/d3d12/d3d12_device.hpp deleted file mode 100644 index cd1a364..0000000 --- a/msvc/source/d3d12/d3d12_device.hpp +++ /dev/null @@ -1,103 +0,0 @@ -/** - * Copyright (C) 2014 Patrick Mours. All rights reserved. - * License: https://github.com/crosire/reshade#license - */ - -#pragma once - -#include - -struct __declspec(uuid("2523AFF4-978B-4939-BA16-8EE876A4CB2A")) D3D12Device : ID3D12Device5 -{ - D3D12Device(ID3D12Device *original); - - D3D12Device(const D3D12Device &) = delete; - D3D12Device &operator=(const D3D12Device &) = delete; - - HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void **ppvObj) override; - ULONG STDMETHODCALLTYPE AddRef() override; - ULONG STDMETHODCALLTYPE Release() override; - - #pragma region ID3D12Object - HRESULT STDMETHODCALLTYPE GetPrivateData(REFGUID guid, UINT *pDataSize, void *pData) override; - HRESULT STDMETHODCALLTYPE SetPrivateData(REFGUID guid, UINT DataSize, const void *pData) override; - HRESULT STDMETHODCALLTYPE SetPrivateDataInterface(REFGUID guid, const IUnknown *pData) override; - HRESULT STDMETHODCALLTYPE SetName(LPCWSTR Name) override; - #pragma endregion - #pragma region ID3D12Device - UINT STDMETHODCALLTYPE GetNodeCount() override; - HRESULT STDMETHODCALLTYPE CreateCommandQueue(const D3D12_COMMAND_QUEUE_DESC *pDesc, REFIID riid, void **ppCommandQueue) override; - HRESULT STDMETHODCALLTYPE CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE type, REFIID riid, void **ppCommandAllocator) override; - HRESULT STDMETHODCALLTYPE CreateGraphicsPipelineState(const D3D12_GRAPHICS_PIPELINE_STATE_DESC *pDesc, REFIID riid, void **ppPipelineState) override; - HRESULT STDMETHODCALLTYPE CreateComputePipelineState(const D3D12_COMPUTE_PIPELINE_STATE_DESC *pDesc, REFIID riid, void **ppPipelineState) override; - HRESULT STDMETHODCALLTYPE CreateCommandList(UINT nodeMask, D3D12_COMMAND_LIST_TYPE type, ID3D12CommandAllocator *pCommandAllocator, ID3D12PipelineState *pInitialState, REFIID riid, void **ppCommandList) override; - HRESULT STDMETHODCALLTYPE CheckFeatureSupport(D3D12_FEATURE Feature, void *pFeatureSupportData, UINT FeatureSupportDataSize) override; - HRESULT STDMETHODCALLTYPE CreateDescriptorHeap(const D3D12_DESCRIPTOR_HEAP_DESC *pDescriptorHeapDesc, REFIID riid, void **ppvHeap) override; - UINT STDMETHODCALLTYPE GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE DescriptorHeapType) override; - HRESULT STDMETHODCALLTYPE CreateRootSignature(UINT nodeMask, const void *pBlobWithRootSignature, SIZE_T blobLengthInBytes, REFIID riid, void **ppvRootSignature) override; - void STDMETHODCALLTYPE CreateConstantBufferView(const D3D12_CONSTANT_BUFFER_VIEW_DESC *pDesc, D3D12_CPU_DESCRIPTOR_HANDLE DestDescriptor) override; - void STDMETHODCALLTYPE CreateShaderResourceView(ID3D12Resource *pResource, const D3D12_SHADER_RESOURCE_VIEW_DESC *pDesc, D3D12_CPU_DESCRIPTOR_HANDLE DestDescriptor) override; - void STDMETHODCALLTYPE CreateUnorderedAccessView(ID3D12Resource *pResource, ID3D12Resource *pCounterResource, const D3D12_UNORDERED_ACCESS_VIEW_DESC *pDesc, D3D12_CPU_DESCRIPTOR_HANDLE DestDescriptor) override; - void STDMETHODCALLTYPE CreateRenderTargetView(ID3D12Resource *pResource, const D3D12_RENDER_TARGET_VIEW_DESC *pDesc, D3D12_CPU_DESCRIPTOR_HANDLE DestDescriptor) override; - void STDMETHODCALLTYPE CreateDepthStencilView(ID3D12Resource *pResource, const D3D12_DEPTH_STENCIL_VIEW_DESC *pDesc, D3D12_CPU_DESCRIPTOR_HANDLE DestDescriptor) override; - void STDMETHODCALLTYPE CreateSampler(const D3D12_SAMPLER_DESC *pDesc, D3D12_CPU_DESCRIPTOR_HANDLE DestDescriptor) override; - void STDMETHODCALLTYPE CopyDescriptors(UINT NumDestDescriptorRanges, const D3D12_CPU_DESCRIPTOR_HANDLE *pDestDescriptorRangeStarts, const UINT *pDestDescriptorRangeSizes, UINT NumSrcDescriptorRanges, const D3D12_CPU_DESCRIPTOR_HANDLE *pSrcDescriptorRangeStarts, const UINT *pSrcDescriptorRangeSizes, D3D12_DESCRIPTOR_HEAP_TYPE DescriptorHeapsType) override; - void STDMETHODCALLTYPE CopyDescriptorsSimple(UINT NumDescriptors, D3D12_CPU_DESCRIPTOR_HANDLE DestDescriptorRangeStart, D3D12_CPU_DESCRIPTOR_HANDLE SrcDescriptorRangeStart, D3D12_DESCRIPTOR_HEAP_TYPE DescriptorHeapsType) override; - D3D12_RESOURCE_ALLOCATION_INFO STDMETHODCALLTYPE GetResourceAllocationInfo(UINT visibleMask, UINT numResourceDescs, const D3D12_RESOURCE_DESC *pResourceDescs) override; - D3D12_HEAP_PROPERTIES STDMETHODCALLTYPE GetCustomHeapProperties(UINT nodeMask, D3D12_HEAP_TYPE heapType) override; - HRESULT STDMETHODCALLTYPE CreateCommittedResource(const D3D12_HEAP_PROPERTIES *pHeapProperties, D3D12_HEAP_FLAGS HeapFlags, const D3D12_RESOURCE_DESC *pResourceDesc, D3D12_RESOURCE_STATES InitialResourceState, const D3D12_CLEAR_VALUE *pOptimizedClearValue, REFIID riidResource, void **ppvResource) override; - HRESULT STDMETHODCALLTYPE CreateHeap(const D3D12_HEAP_DESC *pDesc, REFIID riid, void **ppvHeap) override; - HRESULT STDMETHODCALLTYPE CreatePlacedResource(ID3D12Heap *pHeap, UINT64 HeapOffset, const D3D12_RESOURCE_DESC *pDesc, D3D12_RESOURCE_STATES InitialState, const D3D12_CLEAR_VALUE *pOptimizedClearValue, REFIID riid, void **ppvResource) override; - HRESULT STDMETHODCALLTYPE CreateReservedResource(const D3D12_RESOURCE_DESC *pDesc, D3D12_RESOURCE_STATES InitialState, const D3D12_CLEAR_VALUE *pOptimizedClearValue, REFIID riid, void **ppvResource) override; - HRESULT STDMETHODCALLTYPE CreateSharedHandle(ID3D12DeviceChild *pObject, const SECURITY_ATTRIBUTES *pAttributes, DWORD Access, LPCWSTR Name, HANDLE *pHandle) override; - HRESULT STDMETHODCALLTYPE OpenSharedHandle(HANDLE NTHandle, REFIID riid, void **ppvObj) override; - HRESULT STDMETHODCALLTYPE OpenSharedHandleByName(LPCWSTR Name, DWORD Access, HANDLE *pNTHandle) override; - HRESULT STDMETHODCALLTYPE MakeResident(UINT NumObjects, ID3D12Pageable *const *ppObjects) override; - HRESULT STDMETHODCALLTYPE Evict(UINT NumObjects, ID3D12Pageable *const *ppObjects) override; - HRESULT STDMETHODCALLTYPE CreateFence(UINT64 InitialValue, D3D12_FENCE_FLAGS Flags, REFIID riid, void **ppFence) override; - HRESULT STDMETHODCALLTYPE GetDeviceRemovedReason() override; - void STDMETHODCALLTYPE GetCopyableFootprints(const D3D12_RESOURCE_DESC *pResourceDesc, UINT FirstSubresource, UINT NumSubresources, UINT64 BaseOffset, D3D12_PLACED_SUBRESOURCE_FOOTPRINT *pLayouts, UINT *pNumRows, UINT64 *pRowSizeInBytes, UINT64 *pTotalBytes) override; - HRESULT STDMETHODCALLTYPE CreateQueryHeap(const D3D12_QUERY_HEAP_DESC *pDesc, REFIID riid, void **ppvHeap) override; - HRESULT STDMETHODCALLTYPE SetStablePowerState(BOOL Enable) override; - HRESULT STDMETHODCALLTYPE CreateCommandSignature(const D3D12_COMMAND_SIGNATURE_DESC *pDesc, ID3D12RootSignature *pRootSignature, REFIID riid, void **ppvCommandSignature) override; - void STDMETHODCALLTYPE GetResourceTiling(ID3D12Resource *pTiledResource, UINT *pNumTilesForEntireResource, D3D12_PACKED_MIP_INFO *pPackedMipDesc, D3D12_TILE_SHAPE *pStandardTileShapeForNonPackedMips, UINT *pNumSubresourceTilings, UINT FirstSubresourceTilingToGet, D3D12_SUBRESOURCE_TILING *pSubresourceTilingsForNonPackedMips) override; - LUID STDMETHODCALLTYPE GetAdapterLuid() override; - #pragma endregion - #pragma region ID3D12Device1 - HRESULT STDMETHODCALLTYPE CreatePipelineLibrary(const void *pLibraryBlob, SIZE_T BlobLength, REFIID riid, void **ppPipelineLibrary) override; - HRESULT STDMETHODCALLTYPE SetEventOnMultipleFenceCompletion(ID3D12Fence *const *ppFences, const UINT64 *pFenceValues, UINT NumFences, D3D12_MULTIPLE_FENCE_WAIT_FLAGS Flags, HANDLE hEvent) override; - HRESULT STDMETHODCALLTYPE SetResidencyPriority(UINT NumObjects, ID3D12Pageable *const *ppObjects, const D3D12_RESIDENCY_PRIORITY *pPriorities) override; - #pragma endregion - #pragma region ID3D12Device2 - HRESULT STDMETHODCALLTYPE CreatePipelineState(const D3D12_PIPELINE_STATE_STREAM_DESC *pDesc, REFIID riid, void **ppPipelineState) override; - #pragma endregion - #pragma region ID3D12Device3 - HRESULT STDMETHODCALLTYPE OpenExistingHeapFromAddress(const void *pAddress, REFIID riid, void **ppvHeap) override; - HRESULT STDMETHODCALLTYPE OpenExistingHeapFromFileMapping(HANDLE hFileMapping, REFIID riid, void **ppvHeap) override; - HRESULT STDMETHODCALLTYPE EnqueueMakeResident(D3D12_RESIDENCY_FLAGS Flags, UINT NumObjects, ID3D12Pageable *const *ppObjects, ID3D12Fence *pFenceToSignal, UINT64 FenceValueToSignal) override; - #pragma endregion - #pragma region ID3D12Device4 - HRESULT STDMETHODCALLTYPE CreateCommandList1(UINT NodeMask, D3D12_COMMAND_LIST_TYPE Type, D3D12_COMMAND_LIST_FLAGS Flags, REFIID riid, void **ppCommandList) override; - HRESULT STDMETHODCALLTYPE CreateProtectedResourceSession(const D3D12_PROTECTED_RESOURCE_SESSION_DESC *pDesc, REFIID riid, void **ppSession) override; - HRESULT STDMETHODCALLTYPE CreateCommittedResource1(const D3D12_HEAP_PROPERTIES *pHeapProperties, D3D12_HEAP_FLAGS HeapFlags, const D3D12_RESOURCE_DESC *pDesc, D3D12_RESOURCE_STATES InitialResourceState, const D3D12_CLEAR_VALUE *pOptimizedClearValue, ID3D12ProtectedResourceSession *pProtectedSession, REFIID riidResource, void **ppvResource) override; - HRESULT STDMETHODCALLTYPE CreateHeap1(const D3D12_HEAP_DESC *pDesc, ID3D12ProtectedResourceSession *pProtectedSession, REFIID riid, void **ppvHeap) override; - HRESULT STDMETHODCALLTYPE CreateReservedResource1(const D3D12_RESOURCE_DESC *pDesc, D3D12_RESOURCE_STATES InitialState, const D3D12_CLEAR_VALUE *pOptimizedClearValue, ID3D12ProtectedResourceSession *pProtectedSession, REFIID riid, void **ppvResource) override; - D3D12_RESOURCE_ALLOCATION_INFO STDMETHODCALLTYPE GetResourceAllocationInfo1(UINT VisibleMask, UINT NumResourceDescs, const D3D12_RESOURCE_DESC *pResourceDescs, D3D12_RESOURCE_ALLOCATION_INFO1 *pResourceAllocationInfo1) override; - #pragma endregion - #pragma region ID3D12Device5 - HRESULT STDMETHODCALLTYPE CreateLifetimeTracker(ID3D12LifetimeOwner *pOwner, REFIID riid, void **ppvTracker) override; - void STDMETHODCALLTYPE RemoveDevice() override; - HRESULT STDMETHODCALLTYPE EnumerateMetaCommands(UINT *pNumMetaCommands, D3D12_META_COMMAND_DESC *pDescs) override; - HRESULT STDMETHODCALLTYPE EnumerateMetaCommandParameters(REFGUID CommandId, D3D12_META_COMMAND_PARAMETER_STAGE Stage, UINT *pTotalStructureSizeInBytes, UINT *pParameterCount, D3D12_META_COMMAND_PARAMETER_DESC *pParameterDescs) override; - HRESULT STDMETHODCALLTYPE CreateMetaCommand(REFGUID CommandId, UINT NodeMask, const void *pCreationParametersData, SIZE_T CreationParametersDataSizeInBytes, REFIID riid, void **ppMetaCommand) override; - HRESULT STDMETHODCALLTYPE CreateStateObject(const D3D12_STATE_OBJECT_DESC *pDesc, REFIID riid, void **ppStateObject) override; - void STDMETHODCALLTYPE GetRaytracingAccelerationStructurePrebuildInfo(const D3D12_BUILD_RAYTRACING_ACCELERATION_STRUCTURE_INPUTS *pDesc, D3D12_RAYTRACING_ACCELERATION_STRUCTURE_PREBUILD_INFO *pInfo) override; - D3D12_DRIVER_MATCHING_IDENTIFIER_STATUS STDMETHODCALLTYPE CheckDriverMatchingIdentifier(D3D12_SERIALIZED_DATA_TYPE SerializedDataType, const D3D12_SERIALIZED_DATA_DRIVER_MATCHING_IDENTIFIER *pIdentifierToCheck) override; - #pragma endregion - - bool check_and_upgrade_interface(REFIID riid); - - LONG _ref = 1; - ID3D12Device *_orig; - unsigned int _interface_version; -}; diff --git a/msvc/source/d3d12/runtime_d3d12.cpp b/msvc/source/d3d12/runtime_d3d12.cpp deleted file mode 100644 index 108652f..0000000 --- a/msvc/source/d3d12/runtime_d3d12.cpp +++ /dev/null @@ -1,1403 +0,0 @@ -/** - * Copyright (C) 2014 Patrick Mours. All rights reserved. - * License: https://github.com/crosire/reshade#license - */ - -#include "log.hpp" -#include "hook_manager.hpp" -#include "runtime_d3d12.hpp" -#include "runtime_objects.hpp" -#include "resource_loading.hpp" -#include "dxgi/format_utils.hpp" -#include -#include - -namespace reshade::d3d12 -{ - struct d3d12_tex_data : base_object - { - D3D12_RESOURCE_STATES state; - com_ptr resource; - com_ptr descriptors; - }; - struct d3d12_pass_data : base_object - { - com_ptr pipeline; - D3D12_VIEWPORT viewport; - UINT num_render_targets; - D3D12_CPU_DESCRIPTOR_HANDLE render_targets; - }; - struct d3d12_technique_data : base_object - { - }; - struct d3d12_effect_data - { - com_ptr cb; - com_ptr signature; - com_ptr srv_heap; - com_ptr rtv_heap; - com_ptr sampler_heap; - - UINT16 sampler_list = 0; - SIZE_T storage_offset, storage_size; - D3D12_GPU_VIRTUAL_ADDRESS cbv_gpu_address; - D3D12_CPU_DESCRIPTOR_HANDLE srv_cpu_base; - D3D12_GPU_DESCRIPTOR_HANDLE srv_gpu_base; - D3D12_CPU_DESCRIPTOR_HANDLE rtv_cpu_base; - D3D12_CPU_DESCRIPTOR_HANDLE sampler_cpu_base; - D3D12_GPU_DESCRIPTOR_HANDLE sampler_gpu_base; - }; - - static uint32_t float_as_uint(float value) - { - return *reinterpret_cast(&value); - } - - static void transition_state( - const com_ptr &list, - const com_ptr &res, - D3D12_RESOURCE_STATES from, D3D12_RESOURCE_STATES to, - UINT subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES) - { - D3D12_RESOURCE_BARRIER transition = { D3D12_RESOURCE_BARRIER_TYPE_TRANSITION }; - transition.Transition.pResource = res.get(); - transition.Transition.Subresource = subresource; - transition.Transition.StateBefore = from; - transition.Transition.StateAfter = to; - list->ResourceBarrier(1, &transition); - } -} - -reshade::d3d12::runtime_d3d12::runtime_d3d12(ID3D12Device *device, ID3D12CommandQueue *queue, IDXGISwapChain3 *swapchain) : - _device(device), _commandqueue(queue), _swapchain(swapchain) -{ - assert(queue != nullptr); - assert(device != nullptr); - assert(swapchain != nullptr); - - _renderer_id = D3D_FEATURE_LEVEL_12_0; - - if (com_ptr factory; - SUCCEEDED(swapchain->GetParent(IID_PPV_ARGS(&factory)))) - { - const LUID luid = device->GetAdapterLuid(); - - if (com_ptr adapter; - factory->EnumAdapterByLuid(luid, IID_PPV_ARGS(&adapter))) - { - DXGI_ADAPTER_DESC desc; - adapter->GetDesc(&desc); - _vendor_id = desc.VendorId; - _device_id = desc.DeviceId; - } - } -} -reshade::d3d12::runtime_d3d12::~runtime_d3d12() -{ - if (_d3d_compiler != nullptr) - FreeLibrary(_d3d_compiler); -} - -bool reshade::d3d12::runtime_d3d12::init_backbuffer_textures(UINT num_buffers) -{ - _srv_handle_size = _device->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV); - _rtv_handle_size = _device->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_RTV); - _sampler_handle_size = _device->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER); - - { D3D12_DESCRIPTOR_HEAP_DESC desc = { D3D12_DESCRIPTOR_HEAP_TYPE_RTV }; - desc.NumDescriptors = num_buffers * 2; - - if (FAILED(_device->CreateDescriptorHeap(&desc, IID_PPV_ARGS(&_backbuffer_rtvs)))) - return false; - } - { D3D12_DESCRIPTOR_HEAP_DESC desc = { D3D12_DESCRIPTOR_HEAP_TYPE_DSV }; - desc.NumDescriptors = 1; - - if (FAILED(_device->CreateDescriptorHeap(&desc, IID_PPV_ARGS(&_depthstencil_dsvs)))) - return false; - } - - _backbuffers.resize(num_buffers); - - D3D12_CPU_DESCRIPTOR_HANDLE rtv_handle = _backbuffer_rtvs->GetCPUDescriptorHandleForHeapStart(); - - for (unsigned int i = 0; i < num_buffers; ++i) - { - if (FAILED(_swapchain->GetBuffer(i, IID_PPV_ARGS(&_backbuffers[i])))) - return false; -#ifdef _DEBUG - _backbuffers[i]->SetName(L"Backbuffer"); -#endif - - for (unsigned int srgb_write_enable = 0; srgb_write_enable < 2; ++srgb_write_enable, rtv_handle.ptr += _rtv_handle_size) - { - D3D12_RENDER_TARGET_VIEW_DESC rtv_desc = {}; - rtv_desc.Format = srgb_write_enable ? - make_dxgi_format_srgb(_backbuffer_format) : - make_dxgi_format_normal(_backbuffer_format); - rtv_desc.ViewDimension = D3D12_RTV_DIMENSION_TEXTURE2D; - - _device->CreateRenderTargetView(_backbuffers[i].get(), &rtv_desc, rtv_handle); - } - } - - { D3D12_RESOURCE_DESC desc = { D3D12_RESOURCE_DIMENSION_TEXTURE2D }; - desc.Width = _width; - desc.Height = _height; - desc.DepthOrArraySize = 1; - desc.MipLevels = 1; - desc.Format = make_dxgi_format_typeless(_backbuffer_format); - desc.SampleDesc = { 1, 0 }; - D3D12_HEAP_PROPERTIES props = { D3D12_HEAP_TYPE_DEFAULT }; - - if (FAILED(_device->CreateCommittedResource(&props, D3D12_HEAP_FLAG_NONE, &desc, D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE, nullptr, IID_PPV_ARGS(&_backbuffer_texture)))) - return false; -#ifdef _DEBUG - _backbuffer_texture->SetName(L"ReShade Backbuffer Texture"); -#endif - } - - return true; -} -bool reshade::d3d12::runtime_d3d12::init_default_depth_stencil() -{ - { D3D12_RESOURCE_DESC desc = { D3D12_RESOURCE_DIMENSION_TEXTURE2D }; - desc.Width = _width; - desc.Height = _height; - desc.DepthOrArraySize = 1; - desc.MipLevels = 1; - desc.Format = DXGI_FORMAT_D24_UNORM_S8_UINT; - desc.SampleDesc = { 1, 0 }; - desc.Flags = D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL; - D3D12_HEAP_PROPERTIES props = { D3D12_HEAP_TYPE_DEFAULT }; - - D3D12_CLEAR_VALUE clear_value = {}; - clear_value.Format = desc.Format; - clear_value.DepthStencil = { 1.0f, 0x0 }; - - if (FAILED(_device->CreateCommittedResource(&props, D3D12_HEAP_FLAG_NONE, &desc, D3D12_RESOURCE_STATE_DEPTH_WRITE, &clear_value, IID_PPV_ARGS(&_default_depthstencil)))) - return false; -#ifdef _DEBUG - _default_depthstencil->SetName(L"ReShade Default Depth-Stencil"); -#endif - _device->CreateDepthStencilView(_default_depthstencil.get(), nullptr, _depthstencil_dsvs->GetCPUDescriptorHandleForHeapStart()); - } - - return true; -} -bool reshade::d3d12::runtime_d3d12::init_mipmap_pipeline() -{ - { D3D12_DESCRIPTOR_RANGE srv_range = {}; - srv_range.RangeType = D3D12_DESCRIPTOR_RANGE_TYPE_SRV; - srv_range.NumDescriptors = 1; - srv_range.BaseShaderRegister = 0; // t0 - D3D12_DESCRIPTOR_RANGE uav_range = {}; - uav_range.RangeType = D3D12_DESCRIPTOR_RANGE_TYPE_UAV; - uav_range.NumDescriptors = 1; - uav_range.BaseShaderRegister = 0; // u0 - - D3D12_ROOT_PARAMETER params[3] = {}; - params[0].ParameterType = D3D12_ROOT_PARAMETER_TYPE_32BIT_CONSTANTS; - params[0].Constants.ShaderRegister = 0; // b0 - params[0].Constants.Num32BitValues = 2; - params[0].ShaderVisibility = D3D12_SHADER_VISIBILITY_ALL; - params[1].ParameterType = D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE; - params[1].DescriptorTable.NumDescriptorRanges = 1; - params[1].DescriptorTable.pDescriptorRanges = &srv_range; - params[1].ShaderVisibility = D3D12_SHADER_VISIBILITY_ALL; - params[2].ParameterType = D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE; - params[2].DescriptorTable.NumDescriptorRanges = 1; - params[2].DescriptorTable.pDescriptorRanges = &uav_range; - params[2].ShaderVisibility = D3D12_SHADER_VISIBILITY_ALL; - - D3D12_STATIC_SAMPLER_DESC samplers[1] = {}; - samplers[0].Filter = D3D12_FILTER_MIN_MAG_LINEAR_MIP_POINT; - samplers[0].AddressU = D3D12_TEXTURE_ADDRESS_MODE_CLAMP; - samplers[0].AddressV = D3D12_TEXTURE_ADDRESS_MODE_CLAMP; - samplers[0].AddressW = D3D12_TEXTURE_ADDRESS_MODE_CLAMP; - samplers[0].ComparisonFunc = D3D12_COMPARISON_FUNC_ALWAYS; - samplers[0].ShaderRegister = 0; // s0 - samplers[0].ShaderVisibility = D3D12_SHADER_VISIBILITY_ALL; - - D3D12_ROOT_SIGNATURE_DESC desc = {}; - desc.NumParameters = ARRAYSIZE(params); - desc.pParameters = params; - desc.NumStaticSamplers = ARRAYSIZE(samplers); - desc.pStaticSamplers = samplers; - - _mipmap_signature = create_root_signature(desc); - } - - { D3D12_COMPUTE_PIPELINE_STATE_DESC pso_desc = {}; - pso_desc.pRootSignature = _mipmap_signature.get(); - - const resources::data_resource cs = resources::load_data_resource(IDR_MIPMAP_CS); - pso_desc.CS = { cs.data, cs.data_size }; - - if (FAILED(_device->CreateComputePipelineState(&pso_desc, IID_PPV_ARGS(&_mipmap_pipeline)))) - return false; - } - - return true; -} - -bool reshade::d3d12::runtime_d3d12::on_init(const DXGI_SWAP_CHAIN_DESC &desc) -{ - RECT window_rect = {}; - GetClientRect(desc.OutputWindow, &window_rect); - - _width = desc.BufferDesc.Width; - _height = desc.BufferDesc.Height; - _window_width = window_rect.right - window_rect.left; - _window_height = window_rect.bottom - window_rect.top; - _backbuffer_format = desc.BufferDesc.Format; - - // Create multiple command allocators to buffer for multiple frames - _cmd_alloc.resize(desc.BufferCount); - for (UINT i = 0; i < desc.BufferCount; ++i) - if (FAILED(_device->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(&_cmd_alloc[i])))) - return false; - if (FAILED(_device->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, _cmd_alloc[0].get(), nullptr, IID_PPV_ARGS(&_cmd_list)))) - return false; - _cmd_list->Close(); // Immediately close since it will be reset on first use - - // Create fences for synchronization - _fence_event = CreateEvent(nullptr, FALSE, FALSE, nullptr); - if (_fence_event == nullptr) - return false; - _fence.resize(desc.BufferCount); - _fence_value.resize(desc.BufferCount); - for (UINT i = 0; i < desc.BufferCount; ++i) - if (FAILED(_device->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&_fence[i])))) - return false; - - if (!init_backbuffer_textures(desc.BufferCount) || - !init_default_depth_stencil() || - !init_mipmap_pipeline() -#if RESHADE_GUI - || !init_imgui_resources() -#endif - ) - return false; - - return runtime::on_init(desc.OutputWindow); -} -void reshade::d3d12::runtime_d3d12::on_reset() -{ - runtime::on_reset(); - - _cmd_list.reset(); - _cmd_alloc.clear(); - - CloseHandle(_fence_event); - _fence.clear(); - _fence_value.clear(); - - _backbuffers.clear(); - _backbuffer_rtvs.reset(); - _backbuffer_texture.reset(); - _depthstencil_dsvs.reset(); - _default_depthstencil.reset(); - - _mipmap_pipeline.reset(); - _mipmap_signature.reset(); - -#if RESHADE_GUI - for (unsigned int resource_index = 0; resource_index < 3; ++resource_index) - { - _imgui_index_buffer[resource_index].reset(); - _imgui_index_buffer_size[resource_index] = 0; - _imgui_vertex_buffer[resource_index].reset(); - _imgui_vertex_buffer_size[resource_index] = 0; - } - - _imgui_pipeline.reset(); - _imgui_signature.reset(); -#endif -} - -void reshade::d3d12::runtime_d3d12::on_present() -{ - if (!_is_initialized) - return; - - _swap_index = _swapchain->GetCurrentBackBufferIndex(); - - // Make sure all commands for this command allocator have finished executing before reseting it - if (_fence[_swap_index]->GetCompletedValue() < _fence_value[_swap_index]) - { - _fence[_swap_index]->SetEventOnCompletion(_fence_value[_swap_index], _fence_event); - WaitForSingleObject(_fence_event, INFINITE); - } - - // Reset command allocator before using it this frame again - _cmd_alloc[_swap_index]->Reset(); - - update_and_render_effects(); - runtime::on_present(); - - _commandqueue->Signal(_fence[_swap_index].get(), ++_fence_value[_swap_index]); -} - -void reshade::d3d12::runtime_d3d12::capture_screenshot(uint8_t *buffer) const -{ - if (_backbuffer_format != DXGI_FORMAT_R8G8B8A8_UNORM && - _backbuffer_format != DXGI_FORMAT_R8G8B8A8_UNORM_SRGB && - _backbuffer_format != DXGI_FORMAT_B8G8R8A8_UNORM && - _backbuffer_format != DXGI_FORMAT_B8G8R8A8_UNORM_SRGB) - { - LOG(WARN) << "Screenshots are not supported for back buffer format " << _backbuffer_format << '.'; - return; - } - - const uint32_t data_pitch = _width * 4; - const uint32_t download_pitch = (data_pitch + D3D12_TEXTURE_DATA_PITCH_ALIGNMENT - 1u) & ~(D3D12_TEXTURE_DATA_PITCH_ALIGNMENT - 1u); - - D3D12_RESOURCE_DESC desc = { D3D12_RESOURCE_DIMENSION_BUFFER }; - desc.Width = _height * download_pitch; - desc.Height = 1; - desc.DepthOrArraySize = 1; - desc.MipLevels = 1; - desc.SampleDesc = { 1, 0 }; - desc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR; - D3D12_HEAP_PROPERTIES props = { D3D12_HEAP_TYPE_READBACK }; - - com_ptr intermediate; - if (FAILED(_device->CreateCommittedResource(&props, D3D12_HEAP_FLAG_NONE, &desc, D3D12_RESOURCE_STATE_COPY_DEST, nullptr, IID_PPV_ARGS(&intermediate)))) - { - LOG(ERROR) << "Failed to create system memory texture for screenshot capture!"; - return; - } - -#ifdef _DEBUG - intermediate->SetName(L"ReShade screenshot texture"); -#endif - - const com_ptr cmd_list = create_command_list(); - if (cmd_list == nullptr) - return; - - transition_state(cmd_list, _backbuffers[_swap_index], D3D12_RESOURCE_STATE_PRESENT, D3D12_RESOURCE_STATE_COPY_SOURCE, 0); - { // Copy data from upload buffer into target texture - D3D12_TEXTURE_COPY_LOCATION src_location = { _backbuffers[_swap_index].get() }; - src_location.Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX; - src_location.SubresourceIndex = 0; - - D3D12_TEXTURE_COPY_LOCATION dst_location = { intermediate.get() }; - dst_location.Type = D3D12_TEXTURE_COPY_TYPE_PLACED_FOOTPRINT; - dst_location.PlacedFootprint.Footprint.Width = _width; - dst_location.PlacedFootprint.Footprint.Height = _height; - dst_location.PlacedFootprint.Footprint.Depth = 1; - dst_location.PlacedFootprint.Footprint.Format = DXGI_FORMAT_R8G8B8A8_UNORM; - dst_location.PlacedFootprint.Footprint.RowPitch = download_pitch; - - cmd_list->CopyTextureRegion(&dst_location, 0, 0, 0, &src_location, nullptr); - } - transition_state(cmd_list, _backbuffers[_swap_index], D3D12_RESOURCE_STATE_COPY_SOURCE, D3D12_RESOURCE_STATE_PRESENT, 0); - - // Execute and wait for completion - execute_command_list(cmd_list); - wait_for_command_queue(); - - // Copy data from system memory texture into output buffer - uint8_t *mapped_data; - if (FAILED(intermediate->Map(0, nullptr, reinterpret_cast(&mapped_data)))) - return; - - for (uint32_t y = 0; y < _height; y++, buffer += data_pitch, mapped_data += download_pitch) - { - memcpy(buffer, mapped_data, data_pitch); - for (uint32_t x = 0; x < data_pitch; x += 4) - buffer[x + 3] = 0xFF; // Clear alpha channel - } - - intermediate->Unmap(0, nullptr); -} - -bool reshade::d3d12::runtime_d3d12::init_texture(texture &info) -{ - info.impl = std::make_unique(); - - // Do not create resource if it is a reference, it is set in 'render_technique' - if (info.impl_reference != texture_reference::none) - return true; - - D3D12_RESOURCE_DESC desc = { D3D12_RESOURCE_DIMENSION_TEXTURE2D }; - desc.Width = info.width; - desc.Height = info.height; - desc.DepthOrArraySize = 1; - desc.MipLevels = static_cast(info.levels); - desc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; - desc.SampleDesc = { 1, 0 }; - desc.Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN; - desc.Flags = D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET; // Textures may be bound as render target - - if (info.levels > 1) // Need UAV for mipmap generation - desc.Flags |= D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS; - - switch (info.format) - { - case reshadefx::texture_format::r8: - desc.Format = DXGI_FORMAT_R8_UNORM; - break; - case reshadefx::texture_format::r16f: - desc.Format = DXGI_FORMAT_R16_FLOAT; - break; - case reshadefx::texture_format::r32f: - desc.Format = DXGI_FORMAT_R32_FLOAT; - break; - case reshadefx::texture_format::rg8: - desc.Format = DXGI_FORMAT_R8G8_UNORM; - break; - case reshadefx::texture_format::rg16: - desc.Format = DXGI_FORMAT_R16G16_UNORM; - break; - case reshadefx::texture_format::rg16f: - desc.Format = DXGI_FORMAT_R16G16_FLOAT; - break; - case reshadefx::texture_format::rg32f: - desc.Format = DXGI_FORMAT_R32G32_FLOAT; - break; - case reshadefx::texture_format::rgba8: - desc.Format = DXGI_FORMAT_R8G8B8A8_TYPELESS; - break; - case reshadefx::texture_format::rgba16: - desc.Format = DXGI_FORMAT_R16G16B16A16_UNORM; - break; - case reshadefx::texture_format::rgba16f: - desc.Format = DXGI_FORMAT_R16G16B16A16_FLOAT; - break; - case reshadefx::texture_format::rgba32f: - desc.Format = DXGI_FORMAT_R32G32B32A32_FLOAT; - break; - case reshadefx::texture_format::rgb10a2: - desc.Format = DXGI_FORMAT_R10G10B10A2_UNORM; - break; - } - - D3D12_HEAP_PROPERTIES props = { D3D12_HEAP_TYPE_DEFAULT }; - - // Render targets are always either cleared to zero or not cleared at all (see 'ClearRenderTargets' pass state), so can set the optimized clear value here to zero - D3D12_CLEAR_VALUE clear_value = {}; - clear_value.Format = make_dxgi_format_normal(desc.Format); - - const auto texture_data = info.impl->as(); - texture_data->state = D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE; - - if (HRESULT hr = _device->CreateCommittedResource(&props, D3D12_HEAP_FLAG_NONE, &desc, texture_data->state, &clear_value, IID_PPV_ARGS(&texture_data->resource)); FAILED(hr)) - { - LOG(ERROR) << "Failed to create texture '" << info.unique_name << "' (" - "Width = " << desc.Width << ", " - "Height = " << desc.Height << ", " - "Format = " << desc.Format << ")! " - "HRESULT is '" << std::hex << hr << std::dec << "'."; - return false; - } - -#ifdef _DEBUG - std::wstring debug_name; - debug_name.reserve(info.unique_name.size()); - utf8::unchecked::utf8to16(info.unique_name.begin(), info.unique_name.end(), std::back_inserter(debug_name)); - texture_data->resource->SetName(debug_name.c_str()); -#endif - - { D3D12_DESCRIPTOR_HEAP_DESC heap_desc = { D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV }; - heap_desc.NumDescriptors = info.levels /* SRV */ + info.levels - 1 /* UAV */; - heap_desc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE; - - if (FAILED(_device->CreateDescriptorHeap(&heap_desc, IID_PPV_ARGS(&texture_data->descriptors)))) - return false; - } - - D3D12_CPU_DESCRIPTOR_HANDLE srv_cpu_handle = texture_data->descriptors->GetCPUDescriptorHandleForHeapStart(); - - for (uint32_t level = 0; level < info.levels; ++level, srv_cpu_handle.ptr += _srv_handle_size) - { - D3D12_SHADER_RESOURCE_VIEW_DESC srv_desc = {}; - srv_desc.Format = make_dxgi_format_normal(desc.Format); - srv_desc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D; - srv_desc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING; - srv_desc.Texture2D.MipLevels = 1; - srv_desc.Texture2D.MostDetailedMip = level; - - _device->CreateShaderResourceView(texture_data->resource.get(), &srv_desc, srv_cpu_handle); - } - - // Generate UAVs for mipmap generation - for (uint32_t level = 1; level < info.levels; ++level, srv_cpu_handle.ptr += _srv_handle_size) - { - D3D12_UNORDERED_ACCESS_VIEW_DESC uav_desc = {}; - uav_desc.Format = make_dxgi_format_normal(desc.Format); - uav_desc.ViewDimension = D3D12_UAV_DIMENSION_TEXTURE2D; - uav_desc.Texture2D.MipSlice = level; - - _device->CreateUnorderedAccessView(texture_data->resource.get(), nullptr, &uav_desc, srv_cpu_handle); - } - - return true; -} -void reshade::d3d12::runtime_d3d12::upload_texture(texture &texture, const uint8_t *pixels) -{ - assert(texture.impl_reference == texture_reference::none); - - const uint32_t data_pitch = texture.width * 4; - const uint32_t upload_pitch = (data_pitch + D3D12_TEXTURE_DATA_PITCH_ALIGNMENT - 1u) & ~(D3D12_TEXTURE_DATA_PITCH_ALIGNMENT - 1u); - - D3D12_RESOURCE_DESC desc = { D3D12_RESOURCE_DIMENSION_BUFFER }; - desc.Width = texture.height * upload_pitch; - desc.Height = 1; - desc.DepthOrArraySize = 1; - desc.MipLevels = 1; - desc.SampleDesc = { 1, 0 }; - desc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR; - D3D12_HEAP_PROPERTIES props = { D3D12_HEAP_TYPE_UPLOAD }; - - com_ptr intermediate; - if (FAILED(_device->CreateCommittedResource(&props, D3D12_HEAP_FLAG_NONE, &desc, D3D12_RESOURCE_STATE_GENERIC_READ, nullptr, IID_PPV_ARGS(&intermediate)))) - { - LOG(ERROR) << "Failed to create system memory texture for texture updating!"; - return; - } - -#ifdef _DEBUG - intermediate->SetName(L"ReShade upload texture"); -#endif - - // Fill upload buffer with pixel data - uint8_t *mapped_data; - if (FAILED(intermediate->Map(0, nullptr, reinterpret_cast(&mapped_data)))) - return; - - switch (texture.format) - { - case reshadefx::texture_format::r8: - for (uint32_t y = 0; y < texture.height; ++y, mapped_data += upload_pitch, pixels += data_pitch) - for (uint32_t x = 0; x < texture.width; ++x) - mapped_data[x] = pixels[x * 4]; - break; - case reshadefx::texture_format::rg8: - for (uint32_t y = 0; y < texture.height; ++y, mapped_data += upload_pitch, pixels += data_pitch) - for (uint32_t x = 0; x < texture.width; ++x) - mapped_data[x * 2 + 0] = pixels[x * 4 + 0], - mapped_data[x * 2 + 1] = pixels[x * 4 + 1]; - break; - case reshadefx::texture_format::rgba8: - for (uint32_t y = 0; y < texture.height; ++y, mapped_data += upload_pitch, pixels += data_pitch) - memcpy(mapped_data, pixels, data_pitch); - break; - default: - LOG(ERROR) << "Texture upload is not supported for format " << static_cast(texture.format) << '!'; - break; - } - - intermediate->Unmap(0, nullptr); - - const auto texture_impl = texture.impl->as(); - - assert(pixels != nullptr); - assert(texture_impl != nullptr); - - const com_ptr cmd_list = create_command_list(); - if (cmd_list == nullptr) - return; - - transition_state(cmd_list, texture_impl->resource, texture_impl->state, D3D12_RESOURCE_STATE_COPY_DEST, 0); - { // Copy data from upload buffer into target texture - D3D12_TEXTURE_COPY_LOCATION src_location = { intermediate.get() }; - src_location.Type = D3D12_TEXTURE_COPY_TYPE_PLACED_FOOTPRINT; - src_location.PlacedFootprint.Footprint.Width = texture.width; - src_location.PlacedFootprint.Footprint.Height = texture.height; - src_location.PlacedFootprint.Footprint.Depth = 1; - src_location.PlacedFootprint.Footprint.Format = texture_impl->resource->GetDesc().Format; - src_location.PlacedFootprint.Footprint.RowPitch = upload_pitch; - - D3D12_TEXTURE_COPY_LOCATION dst_location = { texture_impl->resource.get() }; - dst_location.Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX; - dst_location.SubresourceIndex = 0; - - cmd_list->CopyTextureRegion(&dst_location, 0, 0, 0, &src_location, nullptr); - } - transition_state(cmd_list, texture_impl->resource, D3D12_RESOURCE_STATE_COPY_DEST, texture_impl->state, 0); - - generate_mipmaps(cmd_list, texture); - - // Execute and wait for completion - execute_command_list(cmd_list); - wait_for_command_queue(); -} - -void reshade::d3d12::runtime_d3d12::generate_mipmaps(const com_ptr &cmd_list, texture &texture) -{ - if (texture.levels <= 1) - return; // No need to generate mipmaps when texture does not have any - - const auto texture_impl = texture.impl->as(); - assert(texture_impl != nullptr); - - cmd_list->SetComputeRootSignature(_mipmap_signature.get()); - cmd_list->SetPipelineState(_mipmap_pipeline.get()); - ID3D12DescriptorHeap *const descriptor_heap = texture_impl->descriptors.get(); - cmd_list->SetDescriptorHeaps(1, &descriptor_heap); - - transition_state(cmd_list, texture_impl->resource, texture_impl->state, D3D12_RESOURCE_STATE_UNORDERED_ACCESS); - for (uint32_t level = 1; level < texture.levels; ++level) - { - const uint32_t width = std::max(1u, texture.width >> level); - const uint32_t height = std::max(1u, texture.height >> level); - - cmd_list->SetComputeRoot32BitConstant(0, float_as_uint(1.0f / width), 0); - cmd_list->SetComputeRoot32BitConstant(0, float_as_uint(1.0f / height), 1); - // Bind next higher mipmap level as input - cmd_list->SetComputeRootDescriptorTable(1, { texture_impl->descriptors->GetGPUDescriptorHandleForHeapStart().ptr + _srv_handle_size * (level - 1) }); - // There is no UAV for level 0, so substract one - cmd_list->SetComputeRootDescriptorTable(2, { texture_impl->descriptors->GetGPUDescriptorHandleForHeapStart().ptr + _srv_handle_size * (texture.levels + level - 1) }); - - cmd_list->Dispatch(std::max(1u, (width + 7) / 8), std::max(1u, (height + 7) / 8), 1); - - // Wait for all accesses to be finished, since the result will be the input for the next mipmap - D3D12_RESOURCE_BARRIER barrier = { D3D12_RESOURCE_BARRIER_TYPE_UAV }; - barrier.UAV.pResource = texture_impl->resource.get(); - cmd_list->ResourceBarrier(1, &barrier); - } - transition_state(cmd_list, texture_impl->resource, D3D12_RESOURCE_STATE_UNORDERED_ACCESS, texture_impl->state); -} - -com_ptr reshade::d3d12::runtime_d3d12::create_root_signature(const D3D12_ROOT_SIGNATURE_DESC &desc) const -{ - com_ptr signature; - if (com_ptr blob; SUCCEEDED(hooks::call(D3D12SerializeRootSignature)(&desc, D3D_ROOT_SIGNATURE_VERSION_1, &blob, nullptr))) - _device->CreateRootSignature(0, blob->GetBufferPointer(), blob->GetBufferSize(), IID_PPV_ARGS(&signature)); - return signature; -} -com_ptr reshade::d3d12::runtime_d3d12::create_command_list(const com_ptr &state) const -{ - // Reset command list using current command allocator and put it into the recording state - return SUCCEEDED(_cmd_list->Reset(_cmd_alloc[_swap_index].get(), state.get())) ? _cmd_list : nullptr; -} -void reshade::d3d12::runtime_d3d12::execute_command_list(const com_ptr &list) const -{ - if (FAILED(list->Close())) - return; - - ID3D12CommandList *const cmd_lists[] = { list.get() }; - _commandqueue->ExecuteCommandLists(ARRAYSIZE(cmd_lists), cmd_lists); -} -void reshade::d3d12::runtime_d3d12::wait_for_command_queue() const -{ - const UINT64 sync_value = ++_fence_value[_swap_index]; - _commandqueue->Signal(_fence[_swap_index].get(), sync_value); - _fence[_swap_index]->SetEventOnCompletion(sync_value, _fence_event); - WaitForSingleObject(_fence_event, INFINITE); -} - -bool reshade::d3d12::runtime_d3d12::compile_effect(effect_data &effect) -{ - if (_d3d_compiler == nullptr) - _d3d_compiler = LoadLibraryW(L"d3dcompiler_47.dll"); - - if (_d3d_compiler == nullptr) - { - LOG(ERROR) << "Unable to load HLSL compiler (\"d3dcompiler_47.dll\")."; - return false; - } - - const auto D3DCompile = reinterpret_cast(GetProcAddress(_d3d_compiler, "D3DCompile")); - - const std::string hlsl = effect.preamble + effect.module.hlsl; - std::unordered_map> entry_points; - - // Compile the generated HLSL source code to DX byte code - for (const auto &entry_point : effect.module.entry_points) - { - com_ptr d3d_errors; - - const HRESULT hr = D3DCompile( - hlsl.c_str(), hlsl.size(), - nullptr, nullptr, nullptr, - entry_point.first.c_str(), - entry_point.second ? "ps_5_0" : "vs_5_0", - D3DCOMPILE_ENABLE_STRICTNESS | D3DCOMPILE_OPTIMIZATION_LEVEL3, 0, - &entry_points[entry_point.first], &d3d_errors); - - if (d3d_errors != nullptr) // Append warnings to the output error string as well - effect.errors.append(static_cast(d3d_errors->GetBufferPointer()), d3d_errors->GetBufferSize() - 1); // Subtracting one to not append the null-terminator as well - - // No need to setup resources if any of the shaders failed to compile - if (FAILED(hr)) - return false; - } - - if (_effect_data.size() <= effect.index) - _effect_data.resize(effect.index + 1); - - d3d12_effect_data &effect_data = _effect_data[effect.index]; - effect_data.storage_size = effect.storage_size; - effect_data.storage_offset = effect.storage_offset; - - { D3D12_DESCRIPTOR_RANGE srv_range = {}; - srv_range.RangeType = D3D12_DESCRIPTOR_RANGE_TYPE_SRV; - srv_range.NumDescriptors = effect.module.num_texture_bindings; - srv_range.BaseShaderRegister = 0; - D3D12_DESCRIPTOR_RANGE sampler_range = {}; - sampler_range.RangeType = D3D12_DESCRIPTOR_RANGE_TYPE_SAMPLER; - sampler_range.NumDescriptors = effect.module.num_sampler_bindings; - sampler_range.BaseShaderRegister = 0; - - D3D12_ROOT_PARAMETER params[3] = {}; - params[0].ParameterType = D3D12_ROOT_PARAMETER_TYPE_CBV; - params[0].Descriptor.ShaderRegister = 0; // b0 (global constant buffer) - params[0].ShaderVisibility = D3D12_SHADER_VISIBILITY_ALL; - params[1].ParameterType = D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE; - params[1].DescriptorTable.NumDescriptorRanges = 1; - params[1].DescriptorTable.pDescriptorRanges = &srv_range; - params[1].ShaderVisibility = D3D12_SHADER_VISIBILITY_ALL; - params[2].ParameterType = D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE; - params[2].DescriptorTable.NumDescriptorRanges = 1; - params[2].DescriptorTable.pDescriptorRanges = &sampler_range; - params[2].ShaderVisibility = D3D12_SHADER_VISIBILITY_ALL; - - D3D12_ROOT_SIGNATURE_DESC desc = {}; - desc.NumParameters = ARRAYSIZE(params); - desc.pParameters = params; - - effect_data.signature = create_root_signature(desc); - } - - if (effect.storage_size != 0) - { - D3D12_RESOURCE_DESC desc = { D3D12_RESOURCE_DIMENSION_BUFFER }; - desc.Width = effect.storage_size; - desc.Height = 1; - desc.DepthOrArraySize = 1; - desc.MipLevels = 1; - desc.SampleDesc = { 1, 0 }; - desc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR; - D3D12_HEAP_PROPERTIES props = { D3D12_HEAP_TYPE_UPLOAD }; - - if (FAILED(_device->CreateCommittedResource(&props, D3D12_HEAP_FLAG_NONE, &desc, D3D12_RESOURCE_STATE_GENERIC_READ, nullptr, IID_PPV_ARGS(&effect_data.cb)))) - return false; -#ifdef _DEBUG - effect_data.cb->SetName(L"ReShade Global CB"); -#endif - effect_data.cbv_gpu_address = effect_data.cb->GetGPUVirtualAddress(); - } - - { D3D12_DESCRIPTOR_HEAP_DESC desc = { D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV }; - desc.NumDescriptors = effect.module.num_texture_bindings; - desc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE; - - if (FAILED(_device->CreateDescriptorHeap(&desc, IID_PPV_ARGS(&effect_data.srv_heap)))) - return false; - - effect_data.srv_cpu_base = effect_data.srv_heap->GetCPUDescriptorHandleForHeapStart(); - effect_data.srv_gpu_base = effect_data.srv_heap->GetGPUDescriptorHandleForHeapStart(); - } - - { D3D12_DESCRIPTOR_HEAP_DESC desc = { D3D12_DESCRIPTOR_HEAP_TYPE_RTV }; - for (auto &info : effect.module.techniques) - desc.NumDescriptors += static_cast(8 * info.passes.size()); - - if (FAILED(_device->CreateDescriptorHeap(&desc, IID_PPV_ARGS(&effect_data.rtv_heap)))) - return false; - - effect_data.rtv_cpu_base = effect_data.rtv_heap->GetCPUDescriptorHandleForHeapStart(); - } - - { D3D12_DESCRIPTOR_HEAP_DESC desc = { D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER }; - desc.NumDescriptors = effect.module.num_sampler_bindings; - desc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE; - - if (FAILED(_device->CreateDescriptorHeap(&desc, IID_PPV_ARGS(&effect_data.sampler_heap)))) - return false; - - effect_data.sampler_cpu_base = effect_data.sampler_heap->GetCPUDescriptorHandleForHeapStart(); - effect_data.sampler_gpu_base = effect_data.sampler_heap->GetGPUDescriptorHandleForHeapStart(); - } - - bool success = true; - - for (const reshadefx::sampler_info &info : effect.module.samplers) - { - if (info.binding >= D3D12_COMMONSHADER_SAMPLER_SLOT_COUNT) - { - LOG(ERROR) << "Cannot bind sampler '" << info.unique_name << "' since it exceeds the maximum number of allowed sampler slots in D3D12 (" << info.binding << ", allowed are up to " << D3D12_COMMONSHADER_SAMPLER_SLOT_COUNT << ")."; - return false; - } - if (info.texture_binding >= D3D12_COMMONSHADER_INPUT_RESOURCE_SLOT_COUNT) - { - LOG(ERROR) << "Cannot bind texture '" << info.texture_name << "' since it exceeds the maximum number of allowed resource slots in D3D12 (" << info.texture_binding << ", allowed are up to " << D3D12_COMMONSHADER_INPUT_RESOURCE_SLOT_COUNT << ")."; - return false; - } - - const auto existing_texture = std::find_if(_textures.begin(), _textures.end(), - [&texture_name = info.texture_name](const auto &item) { - return item.unique_name == texture_name && item.impl != nullptr; - }); - if (existing_texture == _textures.end()) - return false; - - com_ptr resource; - switch (existing_texture->impl_reference) - { - case texture_reference::back_buffer: - resource = _backbuffer_texture; - break; - case texture_reference::depth_buffer: - break; // TODO - default: - resource = existing_texture->impl->as()->resource; - break; - } - - if (resource == nullptr) - continue; - - { D3D12_SHADER_RESOURCE_VIEW_DESC desc = {}; - desc.Format = info.srgb ? - make_dxgi_format_srgb(resource->GetDesc().Format) : - make_dxgi_format_normal(resource->GetDesc().Format); - desc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D; - desc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING; - desc.Texture2D.MipLevels = existing_texture->levels; - - D3D12_CPU_DESCRIPTOR_HANDLE srv_handle = effect_data.srv_cpu_base; - srv_handle.ptr += info.texture_binding * _srv_handle_size; - - _device->CreateShaderResourceView(resource.get(), &desc, srv_handle); - } - - // Only initialize sampler if it has not been created before - if (0 == (effect_data.sampler_list & (1 << info.binding))) - { - effect_data.sampler_list |= (1 << info.binding); // D3D12_COMMONSHADER_SAMPLER_SLOT_COUNT is 16, so a 16-bit integer is enough to hold all bindings - - D3D12_SAMPLER_DESC desc = {}; - desc.Filter = static_cast(info.filter); - desc.AddressU = static_cast(info.address_u); - desc.AddressV = static_cast(info.address_v); - desc.AddressW = static_cast(info.address_w); - desc.MipLODBias = info.lod_bias; - desc.MaxAnisotropy = 1; - desc.ComparisonFunc = D3D12_COMPARISON_FUNC_NEVER; - desc.MinLOD = info.min_lod; - desc.MaxLOD = info.max_lod; - - D3D12_CPU_DESCRIPTOR_HANDLE sampler_handle = effect_data.sampler_cpu_base; - sampler_handle.ptr += info.binding * _sampler_handle_size; - - _device->CreateSampler(&desc, sampler_handle); - } - } - - for (technique &technique : _techniques) - if (technique.impl == nullptr && technique.effect_index == effect.index) - success &= init_technique(technique, effect_data, entry_points); - - return success; -} -void reshade::d3d12::runtime_d3d12::unload_effects() -{ - // Wait for all GPU operations to finish so resources are no longer referenced - wait_for_command_queue(); - - runtime::unload_effects(); - - _effect_data.clear(); -} - -bool reshade::d3d12::runtime_d3d12::init_technique(technique &technique, const d3d12_effect_data &effect_data, const std::unordered_map> &entry_points) -{ - technique.impl = std::make_unique(); - - for (size_t pass_index = 0; pass_index < technique.passes.size(); ++pass_index) - { - technique.passes_data.push_back(std::make_unique()); - - auto &pass_data = *technique.passes_data.back()->as(); - const auto &pass_info = technique.passes[pass_index]; - - D3D12_GRAPHICS_PIPELINE_STATE_DESC pso_desc = {}; - pso_desc.pRootSignature = _effect_data[technique.effect_index].signature.get(); - - const auto &VS = entry_points.at(pass_info.vs_entry_point); - pso_desc.VS = { VS->GetBufferPointer(), VS->GetBufferSize() }; - const auto &PS = entry_points.at(pass_info.ps_entry_point); - pso_desc.PS = { PS->GetBufferPointer(), PS->GetBufferSize() }; - - pass_data.viewport.Width = pass_info.viewport_width ? FLOAT(pass_info.viewport_width) : FLOAT(frame_width()); - pass_data.viewport.Height = pass_info.viewport_height ? FLOAT(pass_info.viewport_height) : FLOAT(frame_height()); - pass_data.viewport.MaxDepth = 1.0f; - - D3D12_CPU_DESCRIPTOR_HANDLE rtv_handle = effect_data.rtv_cpu_base; - rtv_handle.ptr += pass_index * 8 * _rtv_handle_size; - pass_data.render_targets = rtv_handle; - - pso_desc.DSVFormat = DXGI_FORMAT_D24_UNORM_S8_UINT; - - for (unsigned int k = 0; k < 8; k++) - { - if (pass_info.render_target_names[k].empty()) - continue; // Skip unbound render targets - - const auto render_target_texture = std::find_if(_textures.begin(), _textures.end(), - [&render_target = pass_info.render_target_names[k]](const auto &item) { - return item.unique_name == render_target; - }); - if (render_target_texture == _textures.end()) - return assert(false), false; - - const auto texture_impl = render_target_texture->impl->as(); - assert(texture_impl != nullptr); - - rtv_handle.ptr += k * _rtv_handle_size; - - D3D12_RENDER_TARGET_VIEW_DESC desc = {}; - desc.Format = pass_info.srgb_write_enable ? - make_dxgi_format_srgb(texture_impl->resource->GetDesc().Format) : - make_dxgi_format_normal(texture_impl->resource->GetDesc().Format); - desc.ViewDimension = D3D12_RTV_DIMENSION_TEXTURE2D; - - _device->CreateRenderTargetView(texture_impl->resource.get(), &desc, rtv_handle); - - pso_desc.RTVFormats[k] = desc.Format; - pso_desc.NumRenderTargets = k + 1; - } - - pass_data.num_render_targets = pso_desc.NumRenderTargets; - - if (pso_desc.NumRenderTargets == 0) - { - pso_desc.NumRenderTargets = 1; - pso_desc.RTVFormats[0] = pass_info.srgb_write_enable ? - make_dxgi_format_srgb(_backbuffer_format) : - make_dxgi_format_normal(_backbuffer_format); - } - - pso_desc.SampleMask = UINT_MAX; - pso_desc.SampleDesc = { 1, 0 }; - pso_desc.PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE; - pso_desc.NodeMask = 1; - - { D3D12_BLEND_DESC &desc = pso_desc.BlendState; - desc.RenderTarget[0].BlendEnable = pass_info.blend_enable; - - const auto literal_to_blend_func = [](unsigned int value) { - switch (value) { - default: - case 1: return D3D12_BLEND_ONE; - case 0: return D3D12_BLEND_ZERO; - case 2: return D3D12_BLEND_SRC_COLOR; - case 4: return D3D12_BLEND_INV_SRC_COLOR; - case 3: return D3D12_BLEND_SRC_ALPHA; - case 5: return D3D12_BLEND_INV_SRC_ALPHA; - case 6: return D3D12_BLEND_DEST_ALPHA; - case 7: return D3D12_BLEND_INV_DEST_ALPHA; - case 8: return D3D12_BLEND_DEST_COLOR; - case 9: return D3D12_BLEND_INV_DEST_COLOR; - } - }; - - desc.RenderTarget[0].SrcBlend = literal_to_blend_func(pass_info.src_blend); - desc.RenderTarget[0].DestBlend = literal_to_blend_func(pass_info.dest_blend); - desc.RenderTarget[0].BlendOp = static_cast(pass_info.blend_op); - desc.RenderTarget[0].SrcBlendAlpha = literal_to_blend_func(pass_info.src_blend_alpha); - desc.RenderTarget[0].DestBlendAlpha = literal_to_blend_func(pass_info.dest_blend_alpha); - desc.RenderTarget[0].BlendOpAlpha = static_cast(pass_info.blend_op_alpha); - desc.RenderTarget[0].RenderTargetWriteMask = pass_info.color_write_mask; - } - - { D3D12_RASTERIZER_DESC &desc = pso_desc.RasterizerState; - desc.FillMode = D3D12_FILL_MODE_SOLID; - desc.CullMode = D3D12_CULL_MODE_NONE; - desc.DepthClipEnable = true; - } - - { D3D12_DEPTH_STENCIL_DESC &desc = pso_desc.DepthStencilState; - desc.DepthEnable = FALSE; - desc.DepthWriteMask = D3D12_DEPTH_WRITE_MASK_ZERO; - desc.DepthFunc = D3D12_COMPARISON_FUNC_ALWAYS; - - const auto literal_to_stencil_op = [](unsigned int value) { - switch (value) { - default: - case 1: return D3D12_STENCIL_OP_KEEP; - case 0: return D3D12_STENCIL_OP_ZERO; - case 3: return D3D12_STENCIL_OP_REPLACE; - case 4: return D3D12_STENCIL_OP_INCR_SAT; - case 5: return D3D12_STENCIL_OP_DECR_SAT; - case 6: return D3D12_STENCIL_OP_INVERT; - case 7: return D3D12_STENCIL_OP_INCR; - case 8: return D3D12_STENCIL_OP_DECR; - } - }; - - desc.StencilEnable = pass_info.stencil_enable; - desc.StencilReadMask = pass_info.stencil_read_mask; - desc.StencilWriteMask = pass_info.stencil_write_mask; - desc.FrontFace.StencilFailOp = literal_to_stencil_op(pass_info.stencil_op_fail); - desc.FrontFace.StencilDepthFailOp = literal_to_stencil_op(pass_info.stencil_op_depth_fail); - desc.FrontFace.StencilPassOp = literal_to_stencil_op(pass_info.stencil_op_pass); - desc.FrontFace.StencilFunc = static_cast(pass_info.stencil_comparison_func); - desc.BackFace = desc.FrontFace; - } - - if (HRESULT hr = _device->CreateGraphicsPipelineState(&pso_desc, IID_PPV_ARGS(&pass_data.pipeline)); FAILED(hr)) - { - LOG(ERROR) << "Failed to create pipeline for pass " << pass_index << " in technique '" << technique.name << "'! " - "HRESULT is '" << std::hex << hr << std::dec << "'."; - return false; - } - } - - return true; -} - -void reshade::d3d12::runtime_d3d12::render_technique(technique &technique) -{ - d3d12_effect_data &effect_data = _effect_data[technique.effect_index]; - - const com_ptr cmd_list = create_command_list(); - if (cmd_list == nullptr) - return; - - ID3D12DescriptorHeap *const descriptor_heaps[] = { effect_data.srv_heap.get(), effect_data.sampler_heap.get() }; - cmd_list->SetDescriptorHeaps(ARRAYSIZE(descriptor_heaps), descriptor_heaps); - cmd_list->SetGraphicsRootSignature(effect_data.signature.get()); - - // Setup vertex input - cmd_list->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST); - - // Setup shader constants - if (effect_data.storage_size != 0) - { - void *mapped; - effect_data.cb->Map(0, nullptr, &mapped); - memcpy(mapped, _uniform_data_storage.data() + effect_data.storage_offset, effect_data.storage_size); - effect_data.cb->Unmap(0, nullptr); - - cmd_list->SetGraphicsRootConstantBufferView(0, effect_data.cbv_gpu_address); - } - - // Setup shader resources - cmd_list->SetGraphicsRootDescriptorTable(1, effect_data.srv_gpu_base); - - // Setup samplers - cmd_list->SetGraphicsRootDescriptorTable(2, effect_data.sampler_gpu_base); - - // Clear default depth stencil - const D3D12_CPU_DESCRIPTOR_HANDLE default_depth_stencil = _depthstencil_dsvs->GetCPUDescriptorHandleForHeapStart(); - cmd_list->ClearDepthStencilView(default_depth_stencil, D3D12_CLEAR_FLAG_DEPTH | D3D12_CLEAR_FLAG_STENCIL, 1.0f, 0, 0, nullptr); - - transition_state(cmd_list, _backbuffers[_swap_index], D3D12_RESOURCE_STATE_PRESENT, D3D12_RESOURCE_STATE_RENDER_TARGET); - - for (size_t i = 0; i < technique.passes.size(); ++i) - { - const auto &pass_info = technique.passes[i]; - const auto &pass_data = *technique.passes_data[i]->as(); - - // Transition render targets - for (unsigned int k = 0; k < pass_data.num_render_targets; ++k) - { - const auto texture_impl = std::find_if(_textures.begin(), _textures.end(), - [&render_target = pass_info.render_target_names[k]](const auto &item) { - return item.unique_name == render_target; - })->impl->as(); - - if (texture_impl->state != D3D12_RESOURCE_STATE_RENDER_TARGET) - transition_state(cmd_list, texture_impl->resource, texture_impl->state, D3D12_RESOURCE_STATE_RENDER_TARGET); - texture_impl->state = D3D12_RESOURCE_STATE_RENDER_TARGET; - } - - // Save back buffer of previous pass - transition_state(cmd_list, _backbuffer_texture, D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE, D3D12_RESOURCE_STATE_COPY_DEST); - transition_state(cmd_list, _backbuffers[_swap_index], D3D12_RESOURCE_STATE_RENDER_TARGET, D3D12_RESOURCE_STATE_COPY_SOURCE); - cmd_list->CopyResource(_backbuffer_texture.get(), _backbuffers[_swap_index].get()); - transition_state(cmd_list, _backbuffer_texture, D3D12_RESOURCE_STATE_COPY_DEST, D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE); - transition_state(cmd_list, _backbuffers[_swap_index], D3D12_RESOURCE_STATE_COPY_SOURCE, D3D12_RESOURCE_STATE_RENDER_TARGET); - - // Setup states - cmd_list->SetPipelineState(pass_data.pipeline.get()); - cmd_list->OMSetStencilRef(pass_info.stencil_reference_value); - - // Setup render targets - const float clear_color[4] = { 0.0f, 0.0f, 0.0f, 0.0f }; - - if (pass_data.num_render_targets == 0) - { - D3D12_CPU_DESCRIPTOR_HANDLE render_target = { _backbuffer_rtvs->GetCPUDescriptorHandleForHeapStart().ptr + (_swap_index * 2 + pass_info.srgb_write_enable) * _rtv_handle_size }; - cmd_list->OMSetRenderTargets(1, &render_target, false, pass_info.stencil_enable ? &default_depth_stencil : nullptr); - - if (pass_info.clear_render_targets) - cmd_list->ClearRenderTargetView(render_target, clear_color, 0, nullptr); - } - else if (_width == UINT(pass_data.viewport.Width) && _height == UINT(pass_data.viewport.Height)) - { - cmd_list->OMSetRenderTargets(pass_data.num_render_targets, &pass_data.render_targets, true, pass_info.stencil_enable ? &default_depth_stencil : nullptr); - - if (pass_info.clear_render_targets) - for (UINT k = 0; k < pass_data.num_render_targets; ++k) - cmd_list->ClearRenderTargetView({ pass_data.render_targets.ptr + k * _rtv_handle_size }, clear_color, 0, nullptr); - } - else - { - assert(!pass_info.stencil_enable); - - cmd_list->OMSetRenderTargets(pass_data.num_render_targets, &pass_data.render_targets, true, nullptr); - - if (pass_info.clear_render_targets) - for (UINT k = 0; k < pass_data.num_render_targets; ++k) - cmd_list->ClearRenderTargetView({ pass_data.render_targets.ptr + k * _rtv_handle_size }, clear_color, 0, nullptr); - } - - cmd_list->RSSetViewports(1, &pass_data.viewport); - - D3D12_RECT scissor_rect = { 0, 0, LONG(pass_data.viewport.Width), LONG(pass_data.viewport.Height) }; - cmd_list->RSSetScissorRects(1, &scissor_rect); - - // Draw triangle - cmd_list->DrawInstanced(3, 1, 0, 0); - - _vertices += 3; - _drawcalls += 1; - - // Generate mipmaps - for (unsigned int k = 0; k < pass_data.num_render_targets; ++k) - { - const auto render_target_texture = std::find_if(_textures.begin(), _textures.end(), - [&render_target = pass_info.render_target_names[k]](const auto &item) { - return item.unique_name == render_target; - }); - - generate_mipmaps(cmd_list, *render_target_texture); - } - } - - transition_state(cmd_list, _backbuffers[_swap_index], D3D12_RESOURCE_STATE_RENDER_TARGET, D3D12_RESOURCE_STATE_PRESENT); - - execute_command_list(cmd_list); -} - -#if RESHADE_GUI -bool reshade::d3d12::runtime_d3d12::init_imgui_resources() -{ - { D3D12_DESCRIPTOR_RANGE srv_range = {}; - srv_range.RangeType = D3D12_DESCRIPTOR_RANGE_TYPE_SRV; - srv_range.NumDescriptors = 1; - srv_range.BaseShaderRegister = 0; // t0 - - D3D12_ROOT_PARAMETER params[2] = {}; - params[0].ParameterType = D3D12_ROOT_PARAMETER_TYPE_32BIT_CONSTANTS; - params[0].Constants.ShaderRegister = 0; // b0 - params[0].Constants.Num32BitValues = 16; - params[0].ShaderVisibility = D3D12_SHADER_VISIBILITY_VERTEX; - params[1].ParameterType = D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE; - params[1].DescriptorTable.NumDescriptorRanges = 1; - params[1].DescriptorTable.pDescriptorRanges = &srv_range; - params[1].ShaderVisibility = D3D12_SHADER_VISIBILITY_PIXEL; - - D3D12_STATIC_SAMPLER_DESC samplers[1] = {}; - samplers[0].Filter = D3D12_FILTER_MIN_MAG_MIP_LINEAR; - samplers[0].AddressU = D3D12_TEXTURE_ADDRESS_MODE_WRAP; - samplers[0].AddressV = D3D12_TEXTURE_ADDRESS_MODE_WRAP; - samplers[0].AddressW = D3D12_TEXTURE_ADDRESS_MODE_WRAP; - samplers[0].ComparisonFunc = D3D12_COMPARISON_FUNC_ALWAYS; - samplers[0].ShaderRegister = 0; // s0 - samplers[0].ShaderVisibility = D3D12_SHADER_VISIBILITY_PIXEL; - - D3D12_ROOT_SIGNATURE_DESC desc = {}; - desc.NumParameters = ARRAYSIZE(params); - desc.pParameters = params; - desc.NumStaticSamplers = ARRAYSIZE(samplers); - desc.pStaticSamplers = samplers; - desc.Flags = D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT; - - _imgui_signature = create_root_signature(desc); - } - - D3D12_GRAPHICS_PIPELINE_STATE_DESC pso_desc = {}; - pso_desc.pRootSignature = _imgui_signature.get(); - pso_desc.SampleMask = UINT_MAX; - pso_desc.NumRenderTargets = 1; - pso_desc.RTVFormats[0] = _backbuffer_format; - pso_desc.SampleDesc = { 1, 0 }; - pso_desc.PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE; - pso_desc.NodeMask = 1; - - { const resources::data_resource vs = resources::load_data_resource(IDR_IMGUI_VS); - pso_desc.VS = { vs.data, vs.data_size }; - - static const D3D12_INPUT_ELEMENT_DESC input_layout[] = { - { "POSITION", 0, DXGI_FORMAT_R32G32_FLOAT, 0, offsetof(ImDrawVert, pos), D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 }, - { "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, offsetof(ImDrawVert, uv ), D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 }, - { "COLOR", 0, DXGI_FORMAT_R8G8B8A8_UNORM, 0, offsetof(ImDrawVert, col), D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 }, - }; - pso_desc.InputLayout = { input_layout, ARRAYSIZE(input_layout) }; - } - { const resources::data_resource ps = resources::load_data_resource(IDR_IMGUI_PS); - pso_desc.PS = { ps.data, ps.data_size }; - } - - { D3D12_BLEND_DESC &desc = pso_desc.BlendState; - desc.RenderTarget[0].BlendEnable = true; - desc.RenderTarget[0].SrcBlend = D3D12_BLEND_SRC_ALPHA; - desc.RenderTarget[0].DestBlend = D3D12_BLEND_INV_SRC_ALPHA; - desc.RenderTarget[0].BlendOp = D3D12_BLEND_OP_ADD; - desc.RenderTarget[0].SrcBlendAlpha = D3D12_BLEND_INV_SRC_ALPHA; - desc.RenderTarget[0].DestBlendAlpha = D3D12_BLEND_ZERO; - desc.RenderTarget[0].BlendOpAlpha = D3D12_BLEND_OP_ADD; - desc.RenderTarget[0].RenderTargetWriteMask = D3D12_COLOR_WRITE_ENABLE_ALL; - } - - { D3D12_RASTERIZER_DESC &desc = pso_desc.RasterizerState; - desc.FillMode = D3D12_FILL_MODE_SOLID; - desc.CullMode = D3D12_CULL_MODE_NONE; - desc.DepthClipEnable = true; - } - - { D3D12_DEPTH_STENCIL_DESC &desc = pso_desc.DepthStencilState; - desc.DepthEnable = false; - desc.StencilEnable = false; - } - - return SUCCEEDED(_device->CreateGraphicsPipelineState(&pso_desc, IID_PPV_ARGS(&_imgui_pipeline))); -} - -void reshade::d3d12::runtime_d3d12::render_imgui_draw_data(ImDrawData *draw_data) -{ - const unsigned int resource_index = _framecount % 3; - - // Create and grow vertex/index buffers if needed - if (_imgui_index_buffer_size[resource_index] < UINT(draw_data->TotalIdxCount)) - { - _imgui_index_buffer[resource_index].reset(); - - const UINT new_size = draw_data->TotalIdxCount + 10000; - D3D12_RESOURCE_DESC desc = { D3D12_RESOURCE_DIMENSION_BUFFER }; - desc.Width = new_size * sizeof(ImDrawIdx); - desc.Height = 1; - desc.DepthOrArraySize = 1; - desc.MipLevels = 1; - desc.SampleDesc = { 1, 0 }; - desc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR; - D3D12_HEAP_PROPERTIES props = { D3D12_HEAP_TYPE_UPLOAD }; - - if (FAILED(_device->CreateCommittedResource(&props, D3D12_HEAP_FLAG_NONE, &desc, D3D12_RESOURCE_STATE_GENERIC_READ, NULL, IID_PPV_ARGS(&_imgui_index_buffer[resource_index])))) - return; -#ifdef _DEBUG - _imgui_index_buffer[resource_index]->SetName(L"ImGui Index Buffer"); -#endif - _imgui_index_buffer_size[resource_index] = new_size; - } - if (_imgui_vertex_buffer_size[resource_index] < UINT(draw_data->TotalVtxCount)) - { - _imgui_vertex_buffer[resource_index].reset(); - - const UINT new_size = draw_data->TotalVtxCount + 5000; - D3D12_RESOURCE_DESC desc = { D3D12_RESOURCE_DIMENSION_BUFFER }; - desc.Width = new_size * sizeof(ImDrawVert); - desc.Height = 1; - desc.DepthOrArraySize = 1; - desc.MipLevels = 1; - desc.SampleDesc = { 1, 0 }; - desc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR; - D3D12_HEAP_PROPERTIES props = { D3D12_HEAP_TYPE_UPLOAD }; - - if (FAILED(_device->CreateCommittedResource(&props, D3D12_HEAP_FLAG_NONE, &desc, D3D12_RESOURCE_STATE_GENERIC_READ, NULL, IID_PPV_ARGS(&_imgui_vertex_buffer[resource_index])))) - return; -#ifdef _DEBUG - _imgui_index_buffer[resource_index]->SetName(L"ImGui Vertex Buffer"); -#endif - _imgui_vertex_buffer_size[resource_index] = new_size; - } - - ImDrawIdx *idx_dst; ImDrawVert *vtx_dst; - if (FAILED(_imgui_index_buffer[resource_index]->Map(0, nullptr, reinterpret_cast(&idx_dst))) || - FAILED(_imgui_vertex_buffer[resource_index]->Map(0, nullptr, reinterpret_cast(&vtx_dst)))) - return; - - for (int n = 0; n < draw_data->CmdListsCount; n++) - { - const ImDrawList *draw_list = draw_data->CmdLists[n]; - CopyMemory(idx_dst, draw_list->IdxBuffer.Data, draw_list->IdxBuffer.Size * sizeof(ImDrawIdx)); - CopyMemory(vtx_dst, draw_list->VtxBuffer.Data, draw_list->VtxBuffer.Size * sizeof(ImDrawVert)); - idx_dst += draw_list->IdxBuffer.Size; - vtx_dst += draw_list->VtxBuffer.Size; - } - - _imgui_index_buffer[resource_index]->Unmap(0, nullptr); - _imgui_vertex_buffer[resource_index]->Unmap(0, nullptr); - - const com_ptr cmd_list = create_command_list(_imgui_pipeline); - if (cmd_list == nullptr) - return; - - // Transition render target - transition_state(cmd_list, _backbuffers[_swap_index], D3D12_RESOURCE_STATE_PRESENT, D3D12_RESOURCE_STATE_RENDER_TARGET); - - // Setup orthographic projection matrix - const float ortho_projection[16] = { - 2.0f / draw_data->DisplaySize.x, 0.0f, 0.0f, 0.0f, - 0.0f, -2.0f / draw_data->DisplaySize.y, 0.0f, 0.0f, - 0.0f, 0.0f, 0.5f, 0.0f, - -(2 * draw_data->DisplayPos.x + draw_data->DisplaySize.x) / draw_data->DisplaySize.x, - +(2 * draw_data->DisplayPos.y + draw_data->DisplaySize.y) / draw_data->DisplaySize.y, 0.5f, 1.0f, - }; - - // Setup render state and render draw lists - const D3D12_INDEX_BUFFER_VIEW index_buffer_view = { - _imgui_index_buffer[resource_index]->GetGPUVirtualAddress(), _imgui_index_buffer_size[resource_index] * sizeof(ImDrawIdx), sizeof(ImDrawIdx) == 2 ? DXGI_FORMAT_R16_UINT : DXGI_FORMAT_R32_UINT }; - cmd_list->IASetIndexBuffer(&index_buffer_view); - const D3D12_VERTEX_BUFFER_VIEW vertex_buffer_view = { - _imgui_vertex_buffer[resource_index]->GetGPUVirtualAddress(), _imgui_vertex_buffer_size[resource_index] * sizeof(ImDrawVert), sizeof(ImDrawVert) }; - cmd_list->IASetVertexBuffers(0, 1, &vertex_buffer_view); - cmd_list->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST); - cmd_list->SetGraphicsRootSignature(_imgui_signature.get()); - cmd_list->SetGraphicsRoot32BitConstants(0, sizeof(ortho_projection) / 4, ortho_projection, 0); - const D3D12_VIEWPORT viewport = { 0, 0, draw_data->DisplaySize.x, draw_data->DisplaySize.y, 0.0f, 1.0f }; - cmd_list->RSSetViewports(1, &viewport); - const FLOAT blend_factor[4] = { 0.f, 0.f, 0.f, 0.f }; - cmd_list->OMSetBlendFactor(blend_factor); - D3D12_CPU_DESCRIPTOR_HANDLE render_target = { _backbuffer_rtvs->GetCPUDescriptorHandleForHeapStart().ptr + _swap_index * 2 * _rtv_handle_size }; - cmd_list->OMSetRenderTargets(1, &render_target, false, nullptr); - - UINT vtx_offset = 0, idx_offset = 0; - for (int n = 0; n < draw_data->CmdListsCount; ++n) - { - const ImDrawList *const draw_list = draw_data->CmdLists[n]; - - for (const ImDrawCmd &cmd : draw_list->CmdBuffer) - { - assert(cmd.TextureId != 0); - assert(cmd.UserCallback == nullptr); - - const D3D12_RECT scissor_rect = { - static_cast(cmd.ClipRect.x - draw_data->DisplayPos.x), - static_cast(cmd.ClipRect.y - draw_data->DisplayPos.y), - static_cast(cmd.ClipRect.z - draw_data->DisplayPos.x), - static_cast(cmd.ClipRect.w - draw_data->DisplayPos.y) - }; - cmd_list->RSSetScissorRects(1, &scissor_rect); - - const auto tex_data = static_cast(cmd.TextureId); - // TODO: Transition resource state of the user texture? - assert(tex_data->state == D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE); - - // First descriptor in resource-specific descriptor heap is SRV to top-most mipmap level - ID3D12DescriptorHeap *const descriptor_heap = { tex_data->descriptors.get() }; - cmd_list->SetDescriptorHeaps(1, &descriptor_heap); - cmd_list->SetGraphicsRootDescriptorTable(1, descriptor_heap->GetGPUDescriptorHandleForHeapStart()); - - cmd_list->DrawIndexedInstanced(cmd.ElemCount, 1, idx_offset, vtx_offset, 0); - - idx_offset += cmd.ElemCount; - } - - vtx_offset += draw_list->VtxBuffer.Size; - } - - // Transition render target back to previous state - transition_state(cmd_list, _backbuffers[_swap_index], D3D12_RESOURCE_STATE_RENDER_TARGET, D3D12_RESOURCE_STATE_PRESENT); - - execute_command_list(cmd_list); -} -#endif diff --git a/msvc/source/d3d12/runtime_d3d12.hpp b/msvc/source/d3d12/runtime_d3d12.hpp deleted file mode 100644 index 5c01e8e..0000000 --- a/msvc/source/d3d12/runtime_d3d12.hpp +++ /dev/null @@ -1,96 +0,0 @@ -/** - * Copyright (C) 2014 Patrick Mours. All rights reserved. - * License: https://github.com/crosire/reshade#license - */ - -#pragma once - -#include "runtime.hpp" -#include "com_ptr.hpp" -#include -#include - -namespace reshadefx { struct sampler_info; } - -namespace reshade::d3d12 -{ - class runtime_d3d12 : public runtime - { - public: - runtime_d3d12(ID3D12Device *device, ID3D12CommandQueue *queue, IDXGISwapChain3 *swapchain); - ~runtime_d3d12(); - - bool on_init(const DXGI_SWAP_CHAIN_DESC &desc); - void on_reset(); - void on_present(); - - void capture_screenshot(uint8_t *buffer) const override; - - private: - bool init_backbuffer_textures(UINT num_buffers); - bool init_default_depth_stencil(); - bool init_mipmap_pipeline(); - - bool init_texture(texture &info) override; - void upload_texture(texture &texture, const uint8_t *pixels) override; - - bool compile_effect(effect_data &effect) override; - void unload_effects() override; - - bool init_technique(technique &technique, const struct d3d12_effect_data &effect_data, const std::unordered_map> &entry_points); - - void render_technique(technique &technique) override; - -#if RESHADE_GUI - bool init_imgui_resources(); - void render_imgui_draw_data(ImDrawData *data) override; -#endif - - void generate_mipmaps(const com_ptr &list, texture &texture); - - com_ptr create_root_signature(const D3D12_ROOT_SIGNATURE_DESC &desc) const; - com_ptr create_command_list(const com_ptr &state = nullptr) const; - void execute_command_list(const com_ptr &list) const; - void wait_for_command_queue() const; - - com_ptr _device; - com_ptr _commandqueue; - com_ptr _swapchain; - - UINT _swap_index = 0; - HANDLE _fence_event = nullptr; - mutable std::vector _fence_value; - std::vector> _fence; - - com_ptr _cmd_list; - std::vector> _cmd_alloc; - - DXGI_FORMAT _backbuffer_format = DXGI_FORMAT_UNKNOWN; - com_ptr _backbuffer_rtvs; - com_ptr _depthstencil_dsvs; - std::vector> _backbuffers; - - com_ptr _backbuffer_texture; - com_ptr _default_depthstencil; - - com_ptr _mipmap_pipeline; - com_ptr _mipmap_signature; - - UINT _srv_handle_size = 0; - UINT _rtv_handle_size = 0; - UINT _sampler_handle_size = 0; - - std::vector _effect_data; - - HMODULE _d3d_compiler = nullptr; - -#if RESHADE_GUI - unsigned int _imgui_index_buffer_size[3] = {}; - com_ptr _imgui_index_buffer[3]; - unsigned int _imgui_vertex_buffer_size[3] = {}; - com_ptr _imgui_vertex_buffer[3]; - com_ptr _imgui_pipeline; - com_ptr _imgui_signature; -#endif - }; -} diff --git a/msvc/source/d3d9/d3d9.cpp b/msvc/source/d3d9/d3d9.cpp deleted file mode 100644 index a53f6d1..0000000 --- a/msvc/source/d3d9/d3d9.cpp +++ /dev/null @@ -1,207 +0,0 @@ -/** - * Copyright (C) 2014 Patrick Mours. All rights reserved. - * License: https://github.com/crosire/reshade#license - */ - -#include "log.hpp" -#include "hook_manager.hpp" -#include "d3d9_device.hpp" -#include "d3d9_swapchain.hpp" -#include "runtime_d3d9.hpp" - -// These are defined in d3d9.h, but we want to use them as function names below -#undef IDirect3D9_CreateDevice -#undef IDirect3D9Ex_CreateDeviceEx - -void dump_present_parameters(const D3DPRESENT_PARAMETERS &pp) -{ - LOG(INFO) << "> Dumping presentation parameters:"; - LOG(INFO) << " +-----------------------------------------+-----------------------------------------+"; - LOG(INFO) << " | Parameter | Value |"; - LOG(INFO) << " +-----------------------------------------+-----------------------------------------+"; - LOG(INFO) << " | BackBufferWidth | " << std::setw(39) << pp.BackBufferWidth << " |"; - LOG(INFO) << " | BackBufferHeight | " << std::setw(39) << pp.BackBufferHeight << " |"; - LOG(INFO) << " | BackBufferFormat | " << std::setw(39) << pp.BackBufferFormat << " |"; - LOG(INFO) << " | BackBufferCount | " << std::setw(39) << pp.BackBufferCount << " |"; - LOG(INFO) << " | MultiSampleType | " << std::setw(39) << pp.MultiSampleType << " |"; - LOG(INFO) << " | MultiSampleQuality | " << std::setw(39) << pp.MultiSampleQuality << " |"; - LOG(INFO) << " | SwapEffect | " << std::setw(39) << pp.SwapEffect << " |"; - LOG(INFO) << " | DeviceWindow | " << std::setw(39) << pp.hDeviceWindow << " |"; - LOG(INFO) << " | Windowed | " << std::setw(39) << (pp.Windowed != FALSE ? "TRUE" : "FALSE") << " |"; - LOG(INFO) << " | EnableAutoDepthStencil | " << std::setw(39) << (pp.EnableAutoDepthStencil ? "TRUE" : "FALSE") << " |"; - LOG(INFO) << " | AutoDepthStencilFormat | " << std::setw(39) << pp.AutoDepthStencilFormat << " |"; - LOG(INFO) << " | Flags | " << std::setw(39) << std::hex << pp.Flags << std::dec << " |"; - LOG(INFO) << " | FullScreen_RefreshRateInHz | " << std::setw(39) << pp.FullScreen_RefreshRateInHz << " |"; - LOG(INFO) << " | PresentationInterval | " << std::setw(39) << std::hex << pp.PresentationInterval << std::dec << " |"; - LOG(INFO) << " +-----------------------------------------+-----------------------------------------+"; - - if (pp.MultiSampleType != D3DMULTISAMPLE_NONE) - LOG(WARN) << "> Multisampling is enabled. This is not compatible with depth buffer access, which was therefore disabled."; -} - -template -static void init_runtime_d3d(T *&device, D3DDEVTYPE device_type, D3DPRESENT_PARAMETERS pp, bool use_software_rendering) -{ - // Enable software vertex processing if the application requested a software device - if (use_software_rendering) - device->SetSoftwareVertexProcessing(TRUE); - - // TODO: Make this configurable, since it prevents ReShade from being applied to video players. - if (pp.Flags & D3DPRESENTFLAG_VIDEO) - { - LOG(WARN) << "> Skipping device due to video swapchain."; - return; - } - - if (device_type == D3DDEVTYPE_NULLREF) - { - LOG(WARN) << "> Skipping device due to device type being 'D3DDEVTYPE_NULLREF'."; - return; - } - - IDirect3DSwapChain9 *swapchain = nullptr; - device->GetSwapChain(0, &swapchain); - assert(swapchain != nullptr); - - swapchain->GetPresentParameters(&pp); - - const auto runtime = std::make_shared(device, swapchain); - - if (!runtime->on_init(pp)) - LOG(ERROR) << "Failed to initialize Direct3D 9 runtime environment on runtime " << runtime.get() << '.'; - - const auto device_proxy = new Direct3DDevice9(device); - const auto swapchain_proxy = new Direct3DSwapChain9(device_proxy, swapchain, runtime); - - device_proxy->_implicit_swapchain = swapchain_proxy; - device_proxy->_use_software_rendering = use_software_rendering; - - // Get and set depth stencil surface so that the depth detection callbacks are called with the auto depth stencil surface - if (pp.EnableAutoDepthStencil) - { - device->GetDepthStencilSurface(&device_proxy->_auto_depthstencil); - device_proxy->SetDepthStencilSurface(device_proxy->_auto_depthstencil.get()); - } - - // Overwrite returned device with hooked one - device = device_proxy; - - // Upgrade to extended interface if available to prevent compatibility issues with some games - com_ptr deviceex; - device_proxy->QueryInterface(IID_PPV_ARGS(&deviceex)); - -#if RESHADE_VERBOSE_LOG - LOG(DEBUG) << "Returning IDirect3DDevice9" << (device_proxy->_extended_interface ? "Ex" : "") << " object " << device << '.'; -#endif -} - -HRESULT STDMETHODCALLTYPE IDirect3D9_CreateDevice(IDirect3D9 *pD3D, UINT Adapter, D3DDEVTYPE DeviceType, HWND hFocusWindow, DWORD BehaviorFlags, D3DPRESENT_PARAMETERS *pPresentationParameters, IDirect3DDevice9 **ppReturnedDeviceInterface) -{ - LOG(INFO) << "Redirecting IDirect3D9::CreateDevice" << '(' << pD3D << ", " << Adapter << ", " << DeviceType << ", " << hFocusWindow << ", " << std::hex << BehaviorFlags << std::dec << ", " << pPresentationParameters << ", " << ppReturnedDeviceInterface << ')' << " ..."; - - if (pPresentationParameters == nullptr) - return D3DERR_INVALIDCALL; - - if ((BehaviorFlags & D3DCREATE_ADAPTERGROUP_DEVICE) != 0) - { - LOG(WARN) << "Adapter group devices are unsupported."; - return D3DERR_NOTAVAILABLE; - } - - dump_present_parameters(*pPresentationParameters); - - const bool use_software_rendering = (BehaviorFlags & D3DCREATE_SOFTWARE_VERTEXPROCESSING) != 0; - if (use_software_rendering) - { - LOG(WARN) << "> Replacing 'D3DCREATE_SOFTWARE_VERTEXPROCESSING' flag with 'D3DCREATE_MIXED_VERTEXPROCESSING' to allow for hardware rendering ..."; - BehaviorFlags = (BehaviorFlags & ~D3DCREATE_SOFTWARE_VERTEXPROCESSING) | D3DCREATE_MIXED_VERTEXPROCESSING; - } - - const HRESULT hr = reshade::hooks::call(IDirect3D9_CreateDevice, vtable_from_instance(pD3D) + 16)(pD3D, Adapter, DeviceType, hFocusWindow, BehaviorFlags, pPresentationParameters, ppReturnedDeviceInterface); - - if (FAILED(hr)) - { - LOG(WARN) << "> IDirect3D9::CreateDevice failed with error code " << std::hex << hr << std::dec << '!'; - return hr; - } - - init_runtime_d3d(*ppReturnedDeviceInterface, DeviceType, *pPresentationParameters, use_software_rendering); - - return hr; -} - -HRESULT STDMETHODCALLTYPE IDirect3D9Ex_CreateDeviceEx(IDirect3D9Ex *pD3D, UINT Adapter, D3DDEVTYPE DeviceType, HWND hFocusWindow, DWORD BehaviorFlags, D3DPRESENT_PARAMETERS *pPresentationParameters, D3DDISPLAYMODEEX *pFullscreenDisplayMode, IDirect3DDevice9Ex **ppReturnedDeviceInterface) -{ - LOG(INFO) << "Redirecting IDirect3D9Ex::CreateDeviceEx" << '(' << pD3D << ", " << Adapter << ", " << DeviceType << ", " << hFocusWindow << ", " << std::hex << BehaviorFlags << std::dec << ", " << pPresentationParameters << ", " << pFullscreenDisplayMode << ", " << ppReturnedDeviceInterface << ')' << " ..."; - - if (pPresentationParameters == nullptr) - return D3DERR_INVALIDCALL; - - if ((BehaviorFlags & D3DCREATE_ADAPTERGROUP_DEVICE) != 0) - { - LOG(WARN) << "Adapter group devices are unsupported."; - return D3DERR_NOTAVAILABLE; - } - - dump_present_parameters(*pPresentationParameters); - - const bool use_software_rendering = (BehaviorFlags & D3DCREATE_SOFTWARE_VERTEXPROCESSING) != 0; - if (use_software_rendering) - { - LOG(WARN) << "> Replacing 'D3DCREATE_SOFTWARE_VERTEXPROCESSING' flag with 'D3DCREATE_MIXED_VERTEXPROCESSING' to allow for hardware rendering ..."; - BehaviorFlags = (BehaviorFlags & ~D3DCREATE_SOFTWARE_VERTEXPROCESSING) | D3DCREATE_MIXED_VERTEXPROCESSING; - } - - const HRESULT hr = reshade::hooks::call(IDirect3D9Ex_CreateDeviceEx, vtable_from_instance(pD3D) + 20)(pD3D, Adapter, DeviceType, hFocusWindow, BehaviorFlags, pPresentationParameters, pFullscreenDisplayMode, ppReturnedDeviceInterface); - - if (FAILED(hr)) - { - LOG(WARN) << "> IDirect3D9Ex::CreateDeviceEx failed with error code " << std::hex << hr << std::dec << '!'; - return hr; - } - - init_runtime_d3d(*ppReturnedDeviceInterface, DeviceType, *pPresentationParameters, use_software_rendering); - - return hr; -} - -HOOK_EXPORT IDirect3D9 *WINAPI Direct3DCreate9(UINT SDKVersion) -{ - LOG(INFO) << "Redirecting Direct3DCreate9" << '(' << SDKVersion << ')' << " ..."; - - IDirect3D9 *const res = reshade::hooks::call(Direct3DCreate9)(SDKVersion); - - if (res == nullptr) - { - LOG(WARN) << "> Direct3DCreate9 failed!"; - return nullptr; - } - - reshade::hooks::install("IDirect3D9::CreateDevice", vtable_from_instance(res), 16, reinterpret_cast(&IDirect3D9_CreateDevice)); - -#if RESHADE_VERBOSE_LOG - LOG(DEBUG) << "Returning IDirect3D9 object " << res << '.'; -#endif - return res; -} - -HOOK_EXPORT HRESULT WINAPI Direct3DCreate9Ex(UINT SDKVersion, IDirect3D9Ex **ppD3D) -{ - LOG(INFO) << "Redirecting Direct3DCreate9Ex" << '(' << SDKVersion << ", " << ppD3D << ')' << " ..."; - - const HRESULT hr = reshade::hooks::call(Direct3DCreate9Ex)(SDKVersion, ppD3D); - - if (FAILED(hr)) - { - LOG(WARN) << "> Direct3DCreate9Ex failed with error code " << std::hex << hr << std::dec << '!'; - return hr; - } - - reshade::hooks::install("IDirect3D9::CreateDevice", vtable_from_instance(*ppD3D), 16, reinterpret_cast(&IDirect3D9_CreateDevice)); - reshade::hooks::install("IDirect3D9Ex::CreateDeviceEx", vtable_from_instance(*ppD3D), 20, reinterpret_cast(&IDirect3D9Ex_CreateDeviceEx)); - -#if RESHADE_VERBOSE_LOG - LOG(DEBUG) << "Returning IDirect3D9Ex object " << *ppD3D << '.'; -#endif - return hr; -} diff --git a/msvc/source/d3d9/d3d9_device.cpp b/msvc/source/d3d9/d3d9_device.cpp deleted file mode 100644 index 1e8ea50..0000000 --- a/msvc/source/d3d9/d3d9_device.cpp +++ /dev/null @@ -1,912 +0,0 @@ -/** - * Copyright (C) 2014 Patrick Mours. All rights reserved. - * License: https://github.com/crosire/reshade#license - */ - -#include "log.hpp" -#include "d3d9_device.hpp" -#include "d3d9_swapchain.hpp" -#include "runtime_d3d9.hpp" - -extern void dump_present_parameters(const D3DPRESENT_PARAMETERS &pp); - -Direct3DDevice9::Direct3DDevice9(IDirect3DDevice9 *original) : - _orig(original), - _extended_interface(0) {} -Direct3DDevice9::Direct3DDevice9(IDirect3DDevice9Ex *original) : - _orig(original), - _extended_interface(1) {} - -bool Direct3DDevice9::check_and_upgrade_interface(REFIID riid) -{ - if (_extended_interface || riid != __uuidof(IDirect3DDevice9Ex)) - return true; - - IDirect3DDevice9Ex *new_interface = nullptr; - if (FAILED(_orig->QueryInterface(IID_PPV_ARGS(&new_interface)))) - return false; -#if RESHADE_VERBOSE_LOG - LOG(DEBUG) << "Upgraded IDirect3DDevice9 object " << this << " to IDirect3DDevice9Ex."; -#endif - _orig = new_interface; - _extended_interface = true; - - return true; -} - -HRESULT STDMETHODCALLTYPE Direct3DDevice9::QueryInterface(REFIID riid, void **ppvObj) -{ - if (ppvObj == nullptr) - return E_POINTER; - - if (riid == __uuidof(this) || - riid == __uuidof(IUnknown) || - riid == __uuidof(IDirect3DDevice9) || - riid == __uuidof(IDirect3DDevice9Ex)) - { - if (!check_and_upgrade_interface(riid)) - return E_NOINTERFACE; - - AddRef(); - *ppvObj = this; - return S_OK; - } - - return _orig->QueryInterface(riid, ppvObj); -} -ULONG STDMETHODCALLTYPE Direct3DDevice9::AddRef() -{ - ++_ref; - - return _orig->AddRef(); -} -ULONG STDMETHODCALLTYPE Direct3DDevice9::Release() -{ - if (--_ref == 0) - { - _auto_depthstencil.reset(); - assert(_implicit_swapchain != nullptr); - _implicit_swapchain->Release(); - } - - const ULONG ref = _orig->Release(); - - if (ref != 0 && _ref != 0) - return ref; - else if (ref != 0) - LOG(WARN) << "Reference count for IDirect3DDevice9" << (_extended_interface ? "Ex" : "") << " object " << this << " is inconsistent: " << ref << ", but expected 0."; - - assert(_ref <= 0); -#if RESHADE_VERBOSE_LOG - LOG(DEBUG) << "Destroyed IDirect3DDevice9" << (_extended_interface ? "Ex" : "") << " object " << this << '.'; -#endif - delete this; - return 0; -} - -HRESULT STDMETHODCALLTYPE Direct3DDevice9::TestCooperativeLevel() -{ - return _orig->TestCooperativeLevel(); -} -UINT STDMETHODCALLTYPE Direct3DDevice9::GetAvailableTextureMem() -{ - return _orig->GetAvailableTextureMem(); -} -HRESULT STDMETHODCALLTYPE Direct3DDevice9::EvictManagedResources() -{ - return _orig->EvictManagedResources(); -} -HRESULT STDMETHODCALLTYPE Direct3DDevice9::GetDirect3D(IDirect3D9 **ppD3D9) -{ - return _orig->GetDirect3D(ppD3D9); -} -HRESULT STDMETHODCALLTYPE Direct3DDevice9::GetDeviceCaps(D3DCAPS9 *pCaps) -{ - return _orig->GetDeviceCaps(pCaps); -} -HRESULT STDMETHODCALLTYPE Direct3DDevice9::GetDisplayMode(UINT iSwapChain, D3DDISPLAYMODE *pMode) -{ - if (iSwapChain != 0) - { - LOG(WARN) << "Access to multi-head swap chain at index " << iSwapChain << " is unsupported."; - return D3DERR_INVALIDCALL; - } - - assert(_implicit_swapchain != nullptr); - return _implicit_swapchain->GetDisplayMode(pMode); -} -HRESULT STDMETHODCALLTYPE Direct3DDevice9::GetCreationParameters(D3DDEVICE_CREATION_PARAMETERS *pParameters) -{ - return _orig->GetCreationParameters(pParameters); -} -HRESULT STDMETHODCALLTYPE Direct3DDevice9::SetCursorProperties(UINT XHotSpot, UINT YHotSpot, IDirect3DSurface9 *pCursorBitmap) -{ - return _orig->SetCursorProperties(XHotSpot, YHotSpot, pCursorBitmap); -} -void STDMETHODCALLTYPE Direct3DDevice9::SetCursorPosition(int X, int Y, DWORD Flags) -{ - return _orig->SetCursorPosition(X, Y, Flags); -} -BOOL STDMETHODCALLTYPE Direct3DDevice9::ShowCursor(BOOL bShow) -{ - return _orig->ShowCursor(bShow); -} -HRESULT STDMETHODCALLTYPE Direct3DDevice9::CreateAdditionalSwapChain(D3DPRESENT_PARAMETERS *pPresentationParameters, IDirect3DSwapChain9 **ppSwapChain) -{ - LOG(INFO) << "Redirecting IDirect3DDevice9::CreateAdditionalSwapChain" << '(' << this << ", " << pPresentationParameters << ", " << ppSwapChain << ')' << " ..."; - - if (pPresentationParameters == nullptr) - return D3DERR_INVALIDCALL; - - dump_present_parameters(*pPresentationParameters); - - const HRESULT hr = _orig->CreateAdditionalSwapChain(pPresentationParameters, ppSwapChain); - - if (FAILED(hr)) - { - LOG(WARN) << "> IDirect3DDevice9::CreateAdditionalSwapChain failed with error code " << std::hex << hr << std::dec << '!'; - return hr; - } - - IDirect3DDevice9 *const device = _orig; - IDirect3DSwapChain9 *const swapchain = *ppSwapChain; - assert(swapchain != nullptr); - - D3DPRESENT_PARAMETERS pp; - swapchain->GetPresentParameters(&pp); - - const auto runtime = std::make_shared(device, swapchain); - - if (!runtime->on_init(pp)) - LOG(ERROR) << "Failed to initialize Direct3D 9 runtime environment on runtime " << runtime.get() << '.'; - - AddRef(); // Add reference which is released when the swap chain is destroyed (see 'Direct3DSwapChain9::Release') - - const auto swapchain_proxy = new Direct3DSwapChain9(this, swapchain, runtime); - - _additional_swapchains.push_back(swapchain_proxy); - *ppSwapChain = swapchain_proxy; - -#if RESHADE_VERBOSE_LOG - LOG(DEBUG) << "Returning IDirect3DSwapChain9 object: " << *ppSwapChain << '.'; -#endif - return D3D_OK; -} -HRESULT STDMETHODCALLTYPE Direct3DDevice9::GetSwapChain(UINT iSwapChain, IDirect3DSwapChain9 **ppSwapChain) -{ - if (iSwapChain != 0) - { - LOG(WARN) << "Access to multi-head swap chain at index " << iSwapChain << " is unsupported."; - return D3DERR_INVALIDCALL; - } - - if (ppSwapChain == nullptr) - return D3DERR_INVALIDCALL; - - assert(_implicit_swapchain != nullptr); - _implicit_swapchain->AddRef(); - *ppSwapChain = _implicit_swapchain; - - return D3D_OK; -} -UINT STDMETHODCALLTYPE Direct3DDevice9::GetNumberOfSwapChains() -{ - return 1; -} -HRESULT STDMETHODCALLTYPE Direct3DDevice9::Reset(D3DPRESENT_PARAMETERS *pPresentationParameters) -{ - LOG(INFO) << "Redirecting IDirect3DDevice9::Reset" << '(' << this << ", " << pPresentationParameters << ')' << " ..."; - - if (pPresentationParameters == nullptr) - return D3DERR_INVALIDCALL; - - dump_present_parameters(*pPresentationParameters); - - assert(_implicit_swapchain != nullptr); - assert(_implicit_swapchain->_runtime != nullptr); - const auto runtime = _implicit_swapchain->_runtime; - - runtime->on_reset(); - - _auto_depthstencil.reset(); - - const HRESULT hr = _orig->Reset(pPresentationParameters); - - if (FAILED(hr)) - { - LOG(ERROR) << "> IDirect3DDevice9::Reset failed with error code " << std::hex << hr << std::dec << '!'; - return hr; - } - - D3DPRESENT_PARAMETERS pp; - _implicit_swapchain->GetPresentParameters(&pp); - - if (!runtime->on_init(pp)) - LOG(ERROR) << "Failed to recreate Direct3D 9 runtime environment on runtime " << runtime.get() << '.'; - - if (pp.EnableAutoDepthStencil) - { - _orig->GetDepthStencilSurface(&_auto_depthstencil); - SetDepthStencilSurface(_auto_depthstencil.get()); - } - - return hr; -} -HRESULT STDMETHODCALLTYPE Direct3DDevice9::Present(const RECT *pSourceRect, const RECT *pDestRect, HWND hDestWindowOverride, const RGNDATA *pDirtyRegion) -{ - assert(_implicit_swapchain != nullptr); - assert(_implicit_swapchain->_runtime != nullptr); - _implicit_swapchain->_runtime->on_present(); - - return _orig->Present(pSourceRect, pDestRect, hDestWindowOverride, pDirtyRegion); -} -HRESULT STDMETHODCALLTYPE Direct3DDevice9::GetBackBuffer(UINT iSwapChain, UINT iBackBuffer, D3DBACKBUFFER_TYPE Type, IDirect3DSurface9 **ppBackBuffer) -{ - if (iSwapChain != 0) - { - LOG(WARN) << "Access to multi-head swap chain at index " << iSwapChain << " is unsupported."; - return D3DERR_INVALIDCALL; - } - - assert(_implicit_swapchain != nullptr); - return _implicit_swapchain->GetBackBuffer(iBackBuffer, Type, ppBackBuffer); -} -HRESULT STDMETHODCALLTYPE Direct3DDevice9::GetRasterStatus(UINT iSwapChain, D3DRASTER_STATUS *pRasterStatus) -{ - if (iSwapChain != 0) - { - LOG(WARN) << "Access to multi-head swap chain at index " << iSwapChain << " is unsupported."; - return D3DERR_INVALIDCALL; - } - - assert(_implicit_swapchain != nullptr); - return _implicit_swapchain->GetRasterStatus(pRasterStatus); -} -HRESULT STDMETHODCALLTYPE Direct3DDevice9::SetDialogBoxMode(BOOL bEnableDialogs) -{ - return _orig->SetDialogBoxMode(bEnableDialogs); -} -void STDMETHODCALLTYPE Direct3DDevice9::SetGammaRamp(UINT iSwapChain, DWORD Flags, const D3DGAMMARAMP *pRamp) -{ - if (iSwapChain != 0) - { - LOG(WARN) << "Access to multi-head swap chain at index " << iSwapChain << " is unsupported."; - return; - } - - return _orig->SetGammaRamp(0, Flags, pRamp); -} -void STDMETHODCALLTYPE Direct3DDevice9::GetGammaRamp(UINT iSwapChain, D3DGAMMARAMP *pRamp) -{ - if (iSwapChain != 0) - { - LOG(WARN) << "Access to multi-head swap chain at index " << iSwapChain << " is unsupported."; - return; - } - - return _orig->GetGammaRamp(0, pRamp); -} -HRESULT STDMETHODCALLTYPE Direct3DDevice9::CreateTexture(UINT Width, UINT Height, UINT Levels, DWORD Usage, D3DFORMAT Format, D3DPOOL Pool, IDirect3DTexture9 **ppTexture, HANDLE *pSharedHandle) -{ - return _orig->CreateTexture(Width, Height, Levels, Usage, Format, Pool, ppTexture, pSharedHandle); -} -HRESULT STDMETHODCALLTYPE Direct3DDevice9::CreateVolumeTexture(UINT Width, UINT Height, UINT Depth, UINT Levels, DWORD Usage, D3DFORMAT Format, D3DPOOL Pool, IDirect3DVolumeTexture9 **ppVolumeTexture, HANDLE *pSharedHandle) -{ - return _orig->CreateVolumeTexture(Width, Height, Depth, Levels, Usage, Format, Pool, ppVolumeTexture, pSharedHandle); -} -HRESULT STDMETHODCALLTYPE Direct3DDevice9::CreateCubeTexture(UINT EdgeLength, UINT Levels, DWORD Usage, D3DFORMAT Format, D3DPOOL Pool, IDirect3DCubeTexture9 **ppCubeTexture, HANDLE *pSharedHandle) -{ - return _orig->CreateCubeTexture(EdgeLength, Levels, Usage, Format, Pool, ppCubeTexture, pSharedHandle); -} -HRESULT STDMETHODCALLTYPE Direct3DDevice9::CreateVertexBuffer(UINT Length, DWORD Usage, DWORD FVF, D3DPOOL Pool, IDirect3DVertexBuffer9 **ppVertexBuffer, HANDLE *pSharedHandle) -{ - if (_use_software_rendering) - Usage |= D3DUSAGE_SOFTWAREPROCESSING; - - return _orig->CreateVertexBuffer(Length, Usage, FVF, Pool, ppVertexBuffer, pSharedHandle); -} -HRESULT STDMETHODCALLTYPE Direct3DDevice9::CreateIndexBuffer(UINT Length, DWORD Usage, D3DFORMAT Format, D3DPOOL Pool, IDirect3DIndexBuffer9 **ppIndexBuffer, HANDLE *pSharedHandle) -{ - if (_use_software_rendering) - Usage |= D3DUSAGE_SOFTWAREPROCESSING; - - return _orig->CreateIndexBuffer(Length, Usage, Format, Pool, ppIndexBuffer, pSharedHandle); -} -HRESULT STDMETHODCALLTYPE Direct3DDevice9::CreateRenderTarget(UINT Width, UINT Height, D3DFORMAT Format, D3DMULTISAMPLE_TYPE MultiSample, DWORD MultisampleQuality, BOOL Lockable, IDirect3DSurface9 **ppSurface, HANDLE *pSharedHandle) -{ - return _orig->CreateRenderTarget(Width, Height, Format, MultiSample, MultisampleQuality, Lockable, ppSurface, pSharedHandle); -} -HRESULT STDMETHODCALLTYPE Direct3DDevice9::CreateDepthStencilSurface(UINT Width, UINT Height, D3DFORMAT Format, D3DMULTISAMPLE_TYPE MultiSample, DWORD MultisampleQuality, BOOL Discard, IDirect3DSurface9 **ppSurface, HANDLE *pSharedHandle) -{ - return _orig->CreateDepthStencilSurface(Width, Height, Format, MultiSample, MultisampleQuality, Discard, ppSurface, pSharedHandle); -} -HRESULT STDMETHODCALLTYPE Direct3DDevice9::UpdateSurface(IDirect3DSurface9 *pSourceSurface, const RECT *pSourceRect, IDirect3DSurface9 *pDestinationSurface, const POINT *pDestPoint) -{ - return _orig->UpdateSurface(pSourceSurface, pSourceRect, pDestinationSurface, pDestPoint); -} -HRESULT STDMETHODCALLTYPE Direct3DDevice9::UpdateTexture(IDirect3DBaseTexture9 *pSourceTexture, IDirect3DBaseTexture9 *pDestinationTexture) -{ - return _orig->UpdateTexture(pSourceTexture, pDestinationTexture); -} -HRESULT STDMETHODCALLTYPE Direct3DDevice9::GetRenderTargetData(IDirect3DSurface9 *pRenderTarget, IDirect3DSurface9 *pDestSurface) -{ - return _orig->GetRenderTargetData(pRenderTarget, pDestSurface); -} -HRESULT STDMETHODCALLTYPE Direct3DDevice9::GetFrontBufferData(UINT iSwapChain, IDirect3DSurface9 *pDestSurface) -{ - if (iSwapChain != 0) - { - LOG(WARN) << "Access to multi-head swap chain at index " << iSwapChain << " is unsupported."; - return D3DERR_INVALIDCALL; - } - - assert(_implicit_swapchain != nullptr); - return _implicit_swapchain->GetFrontBufferData(pDestSurface); -} -HRESULT STDMETHODCALLTYPE Direct3DDevice9::StretchRect(IDirect3DSurface9 *pSourceSurface, const RECT *pSourceRect, IDirect3DSurface9 *pDestSurface, const RECT *pDestRect, D3DTEXTUREFILTERTYPE Filter) -{ - return _orig->StretchRect(pSourceSurface, pSourceRect, pDestSurface, pDestRect, Filter); -} -HRESULT STDMETHODCALLTYPE Direct3DDevice9::ColorFill(IDirect3DSurface9 *pSurface, const RECT *pRect, D3DCOLOR color) -{ - return _orig->ColorFill(pSurface, pRect, color); -} -HRESULT STDMETHODCALLTYPE Direct3DDevice9::CreateOffscreenPlainSurface(UINT Width, UINT Height, D3DFORMAT Format, D3DPOOL Pool, IDirect3DSurface9 **ppSurface, HANDLE *pSharedHandle) -{ - return _orig->CreateOffscreenPlainSurface(Width, Height, Format, Pool, ppSurface, pSharedHandle); -} -HRESULT STDMETHODCALLTYPE Direct3DDevice9::SetRenderTarget(DWORD RenderTargetIndex, IDirect3DSurface9 *pRenderTarget) -{ - return _orig->SetRenderTarget(RenderTargetIndex, pRenderTarget); -} -HRESULT STDMETHODCALLTYPE Direct3DDevice9::GetRenderTarget(DWORD RenderTargetIndex, IDirect3DSurface9 **ppRenderTarget) -{ - return _orig->GetRenderTarget(RenderTargetIndex, ppRenderTarget); -} -HRESULT STDMETHODCALLTYPE Direct3DDevice9::SetDepthStencilSurface(IDirect3DSurface9 *pNewZStencil) -{ - if (pNewZStencil != nullptr) - { - assert(_implicit_swapchain != nullptr); - assert(_implicit_swapchain->_runtime != nullptr); - _implicit_swapchain->_runtime->on_set_depthstencil_surface(pNewZStencil); - - for (auto swapchain : _additional_swapchains) - { - assert(swapchain->_runtime != nullptr); - swapchain->_runtime->on_set_depthstencil_surface(pNewZStencil); - } - } - - return _orig->SetDepthStencilSurface(pNewZStencil); -} -HRESULT STDMETHODCALLTYPE Direct3DDevice9::GetDepthStencilSurface(IDirect3DSurface9 **ppZStencilSurface) -{ - const HRESULT hr = _orig->GetDepthStencilSurface(ppZStencilSurface); - - if (FAILED(hr)) - { - return hr; - } - else if (*ppZStencilSurface != nullptr) - { - assert(_implicit_swapchain != nullptr); - assert(_implicit_swapchain->_runtime != nullptr); - _implicit_swapchain->_runtime->on_get_depthstencil_surface(*ppZStencilSurface); - - for (auto swapchain : _additional_swapchains) - { - assert(swapchain->_runtime); - swapchain->_runtime->on_get_depthstencil_surface(*ppZStencilSurface); - } - } - - return D3D_OK; -} -HRESULT STDMETHODCALLTYPE Direct3DDevice9::BeginScene() -{ - return _orig->BeginScene(); -} -HRESULT STDMETHODCALLTYPE Direct3DDevice9::EndScene() -{ - return _orig->EndScene(); -} -HRESULT STDMETHODCALLTYPE Direct3DDevice9::Clear(DWORD Count, const D3DRECT *pRects, DWORD Flags, D3DCOLOR Color, float Z, DWORD Stencil) -{ - if (Flags & D3DCLEAR_ZBUFFER) - { - com_ptr depthstencil; - _orig->GetDepthStencilSurface(&depthstencil); - - assert(_implicit_swapchain != nullptr); - assert(_implicit_swapchain->_runtime != nullptr); - _implicit_swapchain->_runtime->on_clear_depthstencil_surface(depthstencil.get()); - } - - return _orig->Clear(Count, pRects, Flags, Color, Z, Stencil); -} -HRESULT STDMETHODCALLTYPE Direct3DDevice9::SetTransform(D3DTRANSFORMSTATETYPE State, const D3DMATRIX *pMatrix) -{ - return _orig->SetTransform(State, pMatrix); -} -HRESULT STDMETHODCALLTYPE Direct3DDevice9::GetTransform(D3DTRANSFORMSTATETYPE State, D3DMATRIX *pMatrix) -{ - return _orig->GetTransform(State, pMatrix); -} -HRESULT STDMETHODCALLTYPE Direct3DDevice9::MultiplyTransform(D3DTRANSFORMSTATETYPE State, const D3DMATRIX *pMatrix) -{ - return _orig->MultiplyTransform(State, pMatrix); -} -HRESULT STDMETHODCALLTYPE Direct3DDevice9::SetViewport(const D3DVIEWPORT9 *pViewport) -{ - return _orig->SetViewport(pViewport); -} -HRESULT STDMETHODCALLTYPE Direct3DDevice9::GetViewport(D3DVIEWPORT9 *pViewport) -{ - return _orig->GetViewport(pViewport); -} -HRESULT STDMETHODCALLTYPE Direct3DDevice9::SetMaterial(const D3DMATERIAL9 *pMaterial) -{ - return _orig->SetMaterial(pMaterial); -} -HRESULT STDMETHODCALLTYPE Direct3DDevice9::GetMaterial(D3DMATERIAL9 *pMaterial) -{ - return _orig->GetMaterial(pMaterial); -} -HRESULT STDMETHODCALLTYPE Direct3DDevice9::SetLight(DWORD Index, const D3DLIGHT9 *pLight) -{ - return _orig->SetLight(Index, pLight); -} -HRESULT STDMETHODCALLTYPE Direct3DDevice9::GetLight(DWORD Index, D3DLIGHT9 *pLight) -{ - return _orig->GetLight(Index, pLight); -} -HRESULT STDMETHODCALLTYPE Direct3DDevice9::LightEnable(DWORD Index, BOOL Enable) -{ - return _orig->LightEnable(Index, Enable); -} -HRESULT STDMETHODCALLTYPE Direct3DDevice9::GetLightEnable(DWORD Index, BOOL *pEnable) -{ - return _orig->GetLightEnable(Index, pEnable); -} -HRESULT STDMETHODCALLTYPE Direct3DDevice9::SetClipPlane(DWORD Index, const float *pPlane) -{ - return _orig->SetClipPlane(Index, pPlane); -} -HRESULT STDMETHODCALLTYPE Direct3DDevice9::GetClipPlane(DWORD Index, float *pPlane) -{ - return _orig->GetClipPlane(Index, pPlane); -} -HRESULT STDMETHODCALLTYPE Direct3DDevice9::SetRenderState(D3DRENDERSTATETYPE State, DWORD Value) -{ - return _orig->SetRenderState(State, Value); -} -HRESULT STDMETHODCALLTYPE Direct3DDevice9::GetRenderState(D3DRENDERSTATETYPE State, DWORD *pValue) -{ - return _orig->GetRenderState(State, pValue); -} -HRESULT STDMETHODCALLTYPE Direct3DDevice9::CreateStateBlock(D3DSTATEBLOCKTYPE Type, IDirect3DStateBlock9 **ppSB) -{ - return _orig->CreateStateBlock(Type, ppSB); -} -HRESULT STDMETHODCALLTYPE Direct3DDevice9::BeginStateBlock() -{ - return _orig->BeginStateBlock(); -} -HRESULT STDMETHODCALLTYPE Direct3DDevice9::EndStateBlock(IDirect3DStateBlock9 **ppSB) -{ - return _orig->EndStateBlock(ppSB); -} -HRESULT STDMETHODCALLTYPE Direct3DDevice9::SetClipStatus(const D3DCLIPSTATUS9 *pClipStatus) -{ - return _orig->SetClipStatus(pClipStatus); -} -HRESULT STDMETHODCALLTYPE Direct3DDevice9::GetClipStatus(D3DCLIPSTATUS9 *pClipStatus) -{ - return _orig->GetClipStatus(pClipStatus); -} -HRESULT STDMETHODCALLTYPE Direct3DDevice9::GetTexture(DWORD Stage, IDirect3DBaseTexture9 **ppTexture) -{ - return _orig->GetTexture(Stage, ppTexture); -} -HRESULT STDMETHODCALLTYPE Direct3DDevice9::SetTexture(DWORD Stage, IDirect3DBaseTexture9 *pTexture) -{ - return _orig->SetTexture(Stage, pTexture); -} -HRESULT STDMETHODCALLTYPE Direct3DDevice9::GetTextureStageState(DWORD Stage, D3DTEXTURESTAGESTATETYPE Type, DWORD *pValue) -{ - return _orig->GetTextureStageState(Stage, Type, pValue); -} -HRESULT STDMETHODCALLTYPE Direct3DDevice9::SetTextureStageState(DWORD Stage, D3DTEXTURESTAGESTATETYPE Type, DWORD Value) -{ - return _orig->SetTextureStageState(Stage, Type, Value); -} -HRESULT STDMETHODCALLTYPE Direct3DDevice9::GetSamplerState(DWORD Sampler, D3DSAMPLERSTATETYPE Type, DWORD *pValue) -{ - return _orig->GetSamplerState(Sampler, Type, pValue); -} -HRESULT STDMETHODCALLTYPE Direct3DDevice9::SetSamplerState(DWORD Sampler, D3DSAMPLERSTATETYPE Type, DWORD Value) -{ - return _orig->SetSamplerState(Sampler, Type, Value); -} -HRESULT STDMETHODCALLTYPE Direct3DDevice9::ValidateDevice(DWORD *pNumPasses) -{ - return _orig->ValidateDevice(pNumPasses); -} -HRESULT STDMETHODCALLTYPE Direct3DDevice9::SetPaletteEntries(UINT PaletteNumber, const PALETTEENTRY *pEntries) -{ - return _orig->SetPaletteEntries(PaletteNumber, pEntries); -} -HRESULT STDMETHODCALLTYPE Direct3DDevice9::GetPaletteEntries(UINT PaletteNumber, PALETTEENTRY *pEntries) -{ - return _orig->GetPaletteEntries(PaletteNumber, pEntries); -} -HRESULT STDMETHODCALLTYPE Direct3DDevice9::SetCurrentTexturePalette(UINT PaletteNumber) -{ - return _orig->SetCurrentTexturePalette(PaletteNumber); -} -HRESULT STDMETHODCALLTYPE Direct3DDevice9::GetCurrentTexturePalette(UINT *PaletteNumber) -{ - return _orig->GetCurrentTexturePalette(PaletteNumber); -} -HRESULT STDMETHODCALLTYPE Direct3DDevice9::SetScissorRect(const RECT *pRect) -{ - return _orig->SetScissorRect(pRect); -} -HRESULT STDMETHODCALLTYPE Direct3DDevice9::GetScissorRect(RECT *pRect) -{ - return _orig->GetScissorRect(pRect); -} -HRESULT STDMETHODCALLTYPE Direct3DDevice9::SetSoftwareVertexProcessing(BOOL bSoftware) -{ - return _orig->SetSoftwareVertexProcessing(bSoftware); -} -BOOL STDMETHODCALLTYPE Direct3DDevice9::GetSoftwareVertexProcessing() -{ - return _orig->GetSoftwareVertexProcessing(); -} -HRESULT STDMETHODCALLTYPE Direct3DDevice9::SetNPatchMode(float nSegments) -{ - return _orig->SetNPatchMode(nSegments); -} -float STDMETHODCALLTYPE Direct3DDevice9::GetNPatchMode() -{ - return _orig->GetNPatchMode(); -} -HRESULT STDMETHODCALLTYPE Direct3DDevice9::DrawPrimitive(D3DPRIMITIVETYPE PrimitiveType, UINT StartVertex, UINT PrimitiveCount) -{ - assert(_implicit_swapchain != nullptr); - assert(_implicit_swapchain->_runtime != nullptr); - _implicit_swapchain->_runtime->on_draw_primitive(PrimitiveType, StartVertex, PrimitiveCount); - - for (auto swapchain : _additional_swapchains) - { - assert(swapchain->_runtime != nullptr); - swapchain->_runtime->on_draw_primitive(PrimitiveType, StartVertex, PrimitiveCount); - } - - return _orig->DrawPrimitive(PrimitiveType, StartVertex, PrimitiveCount); -} -HRESULT STDMETHODCALLTYPE Direct3DDevice9::DrawIndexedPrimitive(D3DPRIMITIVETYPE PrimitiveType, INT BaseVertexIndex, UINT MinVertexIndex, UINT NumVertices, UINT StartIndex, UINT PrimitiveCount) -{ - assert(_implicit_swapchain != nullptr); - assert(_implicit_swapchain->_runtime != nullptr); - _implicit_swapchain->_runtime->on_draw_indexed_primitive(PrimitiveType, BaseVertexIndex, MinVertexIndex, NumVertices, StartIndex, PrimitiveCount); - - for (auto swapchain : _additional_swapchains) - { - assert(swapchain->_runtime != nullptr); - swapchain->_runtime->on_draw_indexed_primitive(PrimitiveType, BaseVertexIndex, MinVertexIndex, NumVertices, StartIndex, PrimitiveCount); - } - - return _orig->DrawIndexedPrimitive(PrimitiveType, BaseVertexIndex, MinVertexIndex, NumVertices, StartIndex, PrimitiveCount); -} -HRESULT STDMETHODCALLTYPE Direct3DDevice9::DrawPrimitiveUP(D3DPRIMITIVETYPE PrimitiveType, UINT PrimitiveCount, const void *pVertexStreamZeroData, UINT VertexStreamZeroStride) -{ - assert(_implicit_swapchain != nullptr); - assert(_implicit_swapchain->_runtime != nullptr); - - _implicit_swapchain->_runtime->on_draw_primitive_up(PrimitiveType, PrimitiveCount, pVertexStreamZeroData, VertexStreamZeroStride); - - for (auto swapchain : _additional_swapchains) - { - assert(swapchain->_runtime != nullptr); - swapchain->_runtime->on_draw_primitive_up(PrimitiveType, PrimitiveCount, pVertexStreamZeroData, VertexStreamZeroStride); - } - - return _orig->DrawPrimitiveUP(PrimitiveType, PrimitiveCount, pVertexStreamZeroData, VertexStreamZeroStride); -} -HRESULT STDMETHODCALLTYPE Direct3DDevice9::DrawIndexedPrimitiveUP(D3DPRIMITIVETYPE PrimitiveType, UINT MinVertexIndex, UINT NumVertices, UINT PrimitiveCount, const void *pIndexData, D3DFORMAT IndexDataFormat, const void *pVertexStreamZeroData, UINT VertexStreamZeroStride) -{ - assert(_implicit_swapchain != nullptr); - assert(_implicit_swapchain->_runtime != nullptr); - - _implicit_swapchain->_runtime->on_draw_indexed_primitive_up(PrimitiveType, MinVertexIndex, NumVertices, PrimitiveCount, pIndexData, IndexDataFormat, pVertexStreamZeroData, VertexStreamZeroStride); - - for (auto swapchain : _additional_swapchains) - { - assert(swapchain->_runtime != nullptr); - swapchain->_runtime->on_draw_indexed_primitive_up(PrimitiveType, MinVertexIndex, NumVertices, PrimitiveCount, pIndexData, IndexDataFormat, pVertexStreamZeroData, VertexStreamZeroStride); - } - - return _orig->DrawIndexedPrimitiveUP(PrimitiveType, MinVertexIndex, NumVertices, PrimitiveCount, pIndexData, IndexDataFormat, pVertexStreamZeroData, VertexStreamZeroStride); -} -HRESULT STDMETHODCALLTYPE Direct3DDevice9::ProcessVertices(UINT SrcStartIndex, UINT DestIndex, UINT VertexCount, IDirect3DVertexBuffer9 *pDestBuffer, IDirect3DVertexDeclaration9 *pVertexDecl, DWORD Flags) -{ - return _orig->ProcessVertices(SrcStartIndex, DestIndex, VertexCount, pDestBuffer, pVertexDecl, Flags); -} -HRESULT STDMETHODCALLTYPE Direct3DDevice9::CreateVertexDeclaration(const D3DVERTEXELEMENT9 *pVertexElements, IDirect3DVertexDeclaration9 **ppDecl) -{ - return _orig->CreateVertexDeclaration(pVertexElements, ppDecl); -} -HRESULT STDMETHODCALLTYPE Direct3DDevice9::SetVertexDeclaration(IDirect3DVertexDeclaration9 *pDecl) -{ - return _orig->SetVertexDeclaration(pDecl); -} -HRESULT STDMETHODCALLTYPE Direct3DDevice9::GetVertexDeclaration(IDirect3DVertexDeclaration9 **ppDecl) -{ - return _orig->GetVertexDeclaration(ppDecl); -} -HRESULT STDMETHODCALLTYPE Direct3DDevice9::SetFVF(DWORD FVF) -{ - return _orig->SetFVF(FVF); -} -HRESULT STDMETHODCALLTYPE Direct3DDevice9::GetFVF(DWORD *pFVF) -{ - return _orig->GetFVF(pFVF); -} -HRESULT STDMETHODCALLTYPE Direct3DDevice9::CreateVertexShader(const DWORD *pFunction, IDirect3DVertexShader9 **ppShader) -{ - return _orig->CreateVertexShader(pFunction, ppShader); -} -HRESULT STDMETHODCALLTYPE Direct3DDevice9::SetVertexShader(IDirect3DVertexShader9 *pShader) -{ - return _orig->SetVertexShader(pShader); -} -HRESULT STDMETHODCALLTYPE Direct3DDevice9::GetVertexShader(IDirect3DVertexShader9 **ppShader) -{ - return _orig->GetVertexShader(ppShader); -} -HRESULT STDMETHODCALLTYPE Direct3DDevice9::SetVertexShaderConstantF(UINT StartRegister, const float *pConstantData, UINT Vector4fCount) -{ - return _orig->SetVertexShaderConstantF(StartRegister, pConstantData, Vector4fCount); -} -HRESULT STDMETHODCALLTYPE Direct3DDevice9::GetVertexShaderConstantF(UINT StartRegister, float *pConstantData, UINT Vector4fCount) -{ - return _orig->GetVertexShaderConstantF(StartRegister, pConstantData, Vector4fCount); -} -HRESULT STDMETHODCALLTYPE Direct3DDevice9::SetVertexShaderConstantI(UINT StartRegister, const int *pConstantData, UINT Vector4iCount) -{ - return _orig->SetVertexShaderConstantI(StartRegister, pConstantData, Vector4iCount); -} -HRESULT STDMETHODCALLTYPE Direct3DDevice9::GetVertexShaderConstantI(UINT StartRegister, int *pConstantData, UINT Vector4iCount) -{ - return _orig->GetVertexShaderConstantI(StartRegister, pConstantData, Vector4iCount); -} -HRESULT STDMETHODCALLTYPE Direct3DDevice9::SetVertexShaderConstantB(UINT StartRegister, const BOOL *pConstantData, UINT BoolCount) -{ - return _orig->SetVertexShaderConstantB(StartRegister, pConstantData, BoolCount); -} -HRESULT STDMETHODCALLTYPE Direct3DDevice9::GetVertexShaderConstantB(UINT StartRegister, BOOL *pConstantData, UINT BoolCount) -{ - return _orig->GetVertexShaderConstantB(StartRegister, pConstantData, BoolCount); -} -HRESULT STDMETHODCALLTYPE Direct3DDevice9::SetStreamSource(UINT StreamNumber, IDirect3DVertexBuffer9 *pStreamData, UINT OffsetInBytes, UINT Stride) -{ - return _orig->SetStreamSource(StreamNumber, pStreamData, OffsetInBytes, Stride); -} -HRESULT STDMETHODCALLTYPE Direct3DDevice9::GetStreamSource(UINT StreamNumber, IDirect3DVertexBuffer9 **ppStreamData, UINT *OffsetInBytes, UINT *pStride) -{ - return _orig->GetStreamSource(StreamNumber, ppStreamData, OffsetInBytes, pStride); -} -HRESULT STDMETHODCALLTYPE Direct3DDevice9::SetStreamSourceFreq(UINT StreamNumber, UINT Divider) -{ - return _orig->SetStreamSourceFreq(StreamNumber, Divider); -} -HRESULT STDMETHODCALLTYPE Direct3DDevice9::GetStreamSourceFreq(UINT StreamNumber, UINT *Divider) -{ - return _orig->GetStreamSourceFreq(StreamNumber, Divider); -} -HRESULT STDMETHODCALLTYPE Direct3DDevice9::SetIndices(IDirect3DIndexBuffer9 *pIndexData) -{ - return _orig->SetIndices(pIndexData); -} -HRESULT STDMETHODCALLTYPE Direct3DDevice9::GetIndices(IDirect3DIndexBuffer9 **ppIndexData) -{ - return _orig->GetIndices(ppIndexData); -} -HRESULT STDMETHODCALLTYPE Direct3DDevice9::CreatePixelShader(const DWORD *pFunction, IDirect3DPixelShader9 **ppShader) -{ - return _orig->CreatePixelShader(pFunction, ppShader); -} -HRESULT STDMETHODCALLTYPE Direct3DDevice9::SetPixelShader(IDirect3DPixelShader9 *pShader) -{ - return _orig->SetPixelShader(pShader); -} -HRESULT STDMETHODCALLTYPE Direct3DDevice9::GetPixelShader(IDirect3DPixelShader9 **ppShader) -{ - return _orig->GetPixelShader(ppShader); -} -HRESULT STDMETHODCALLTYPE Direct3DDevice9::SetPixelShaderConstantF(UINT StartRegister, const float *pConstantData, UINT Vector4fCount) -{ - return _orig->SetPixelShaderConstantF(StartRegister, pConstantData, Vector4fCount); -} -HRESULT STDMETHODCALLTYPE Direct3DDevice9::GetPixelShaderConstantF(UINT StartRegister, float *pConstantData, UINT Vector4fCount) -{ - return _orig->GetPixelShaderConstantF(StartRegister, pConstantData, Vector4fCount); -} -HRESULT STDMETHODCALLTYPE Direct3DDevice9::SetPixelShaderConstantI(UINT StartRegister, const int *pConstantData, UINT Vector4iCount) -{ - return _orig->SetPixelShaderConstantI(StartRegister, pConstantData, Vector4iCount); -} -HRESULT STDMETHODCALLTYPE Direct3DDevice9::GetPixelShaderConstantI(UINT StartRegister, int *pConstantData, UINT Vector4iCount) -{ - return _orig->GetPixelShaderConstantI(StartRegister, pConstantData, Vector4iCount); -} -HRESULT STDMETHODCALLTYPE Direct3DDevice9::SetPixelShaderConstantB(UINT StartRegister, const BOOL *pConstantData, UINT BoolCount) -{ - return _orig->SetPixelShaderConstantB(StartRegister, pConstantData, BoolCount); -} -HRESULT STDMETHODCALLTYPE Direct3DDevice9::GetPixelShaderConstantB(UINT StartRegister, BOOL *pConstantData, UINT BoolCount) -{ - return _orig->GetPixelShaderConstantB(StartRegister, pConstantData, BoolCount); -} -HRESULT STDMETHODCALLTYPE Direct3DDevice9::DrawRectPatch(UINT Handle, const float *pNumSegs, const D3DRECTPATCH_INFO *pRectPatchInfo) -{ - return _orig->DrawRectPatch(Handle, pNumSegs, pRectPatchInfo); -} -HRESULT STDMETHODCALLTYPE Direct3DDevice9::DrawTriPatch(UINT Handle, const float *pNumSegs, const D3DTRIPATCH_INFO *pTriPatchInfo) -{ - return _orig->DrawTriPatch(Handle, pNumSegs, pTriPatchInfo); -} -HRESULT STDMETHODCALLTYPE Direct3DDevice9::DeletePatch(UINT Handle) -{ - return _orig->DeletePatch(Handle); -} -HRESULT STDMETHODCALLTYPE Direct3DDevice9::CreateQuery(D3DQUERYTYPE Type, IDirect3DQuery9 **ppQuery) -{ - return _orig->CreateQuery(Type, ppQuery); -} - -HRESULT STDMETHODCALLTYPE Direct3DDevice9::SetConvolutionMonoKernel(UINT width, UINT height, float *rows, float *columns) -{ - assert(_extended_interface); - - return static_cast(_orig)->SetConvolutionMonoKernel(width, height, rows, columns); -} -HRESULT STDMETHODCALLTYPE Direct3DDevice9::ComposeRects(IDirect3DSurface9 *pSrc, IDirect3DSurface9 *pDst, IDirect3DVertexBuffer9 *pSrcRectDescs, UINT NumRects, IDirect3DVertexBuffer9 *pDstRectDescs, D3DCOMPOSERECTSOP Operation, int Xoffset, int Yoffset) -{ - assert(_extended_interface); - - return static_cast(_orig)->ComposeRects(pSrc, pDst, pSrcRectDescs, NumRects, pDstRectDescs, Operation, Xoffset, Yoffset); -} -HRESULT STDMETHODCALLTYPE Direct3DDevice9::PresentEx(const RECT *pSourceRect, const RECT *pDestRect, HWND hDestWindowOverride, const RGNDATA *pDirtyRegion, DWORD dwFlags) -{ - assert(_extended_interface); - assert(_implicit_swapchain != nullptr); - assert(_implicit_swapchain->_runtime != nullptr); - _implicit_swapchain->_runtime->on_present(); - - return static_cast(_orig)->PresentEx(pSourceRect, pDestRect, hDestWindowOverride, pDirtyRegion, dwFlags); -} -HRESULT STDMETHODCALLTYPE Direct3DDevice9::GetGPUThreadPriority(INT *pPriority) -{ - assert(_extended_interface); - - return static_cast(_orig)->GetGPUThreadPriority(pPriority); -} -HRESULT STDMETHODCALLTYPE Direct3DDevice9::SetGPUThreadPriority(INT Priority) -{ - assert(_extended_interface); - - return static_cast(_orig)->SetGPUThreadPriority(Priority); -} -HRESULT STDMETHODCALLTYPE Direct3DDevice9::WaitForVBlank(UINT iSwapChain) -{ - assert(_extended_interface); - - if (iSwapChain != 0) - { - LOG(WARN) << "Access to multi-head swap chain at index " << iSwapChain << " is unsupported."; - return D3DERR_INVALIDCALL; - } - - return static_cast(_orig)->WaitForVBlank(0); -} -HRESULT STDMETHODCALLTYPE Direct3DDevice9::CheckResourceResidency(IDirect3DResource9 **pResourceArray, UINT32 NumResources) -{ - assert(_extended_interface); - - return static_cast(_orig)->CheckResourceResidency(pResourceArray, NumResources); -} -HRESULT STDMETHODCALLTYPE Direct3DDevice9::SetMaximumFrameLatency(UINT MaxLatency) -{ - assert(_extended_interface); - - return static_cast(_orig)->SetMaximumFrameLatency(MaxLatency); -} -HRESULT STDMETHODCALLTYPE Direct3DDevice9::GetMaximumFrameLatency(UINT *pMaxLatency) -{ - assert(_extended_interface); - - return static_cast(_orig)->GetMaximumFrameLatency(pMaxLatency); -} -HRESULT STDMETHODCALLTYPE Direct3DDevice9::CheckDeviceState(HWND hDestinationWindow) -{ - assert(_extended_interface); - - return static_cast(_orig)->CheckDeviceState(hDestinationWindow); -} -HRESULT STDMETHODCALLTYPE Direct3DDevice9::CreateRenderTargetEx(UINT Width, UINT Height, D3DFORMAT Format, D3DMULTISAMPLE_TYPE MultiSample, DWORD MultisampleQuality, BOOL Lockable, IDirect3DSurface9 **ppSurface, HANDLE *pSharedHandle, DWORD Usage) -{ - assert(_extended_interface); - - return static_cast(_orig)->CreateRenderTargetEx(Width, Height, Format, MultiSample, MultisampleQuality, Lockable, ppSurface, pSharedHandle, Usage); -} -HRESULT STDMETHODCALLTYPE Direct3DDevice9::CreateOffscreenPlainSurfaceEx(UINT Width, UINT Height, D3DFORMAT Format, D3DPOOL Pool, IDirect3DSurface9 **ppSurface, HANDLE *pSharedHandle, DWORD Usage) -{ - assert(_extended_interface); - - return static_cast(_orig)->CreateOffscreenPlainSurfaceEx(Width, Height, Format, Pool, ppSurface, pSharedHandle, Usage); -} -HRESULT STDMETHODCALLTYPE Direct3DDevice9::CreateDepthStencilSurfaceEx(UINT Width, UINT Height, D3DFORMAT Format, D3DMULTISAMPLE_TYPE MultiSample, DWORD MultisampleQuality, BOOL Discard, IDirect3DSurface9 **ppSurface, HANDLE *pSharedHandle, DWORD Usage) -{ - assert(_extended_interface); - - return static_cast(_orig)->CreateDepthStencilSurfaceEx(Width, Height, Format, MultiSample, MultisampleQuality, Discard, ppSurface, pSharedHandle, Usage); -} -HRESULT STDMETHODCALLTYPE Direct3DDevice9::ResetEx(D3DPRESENT_PARAMETERS *pPresentationParameters, D3DDISPLAYMODEEX *pFullscreenDisplayMode) -{ - assert(_extended_interface); - - LOG(INFO) << "Redirecting IDirect3DDevice9Ex::ResetEx" << '(' << this << ", " << pPresentationParameters << ", " << pFullscreenDisplayMode << ')' << " ..."; - - if (pPresentationParameters == nullptr) - return D3DERR_INVALIDCALL; - - dump_present_parameters(*pPresentationParameters); - - assert(_implicit_swapchain != nullptr); - assert(_implicit_swapchain->_runtime != nullptr); - const auto runtime = _implicit_swapchain->_runtime; - - runtime->on_reset(); - - _auto_depthstencil.reset(); - - const HRESULT hr = static_cast(_orig)->ResetEx(pPresentationParameters, pFullscreenDisplayMode); - - if (FAILED(hr)) - { - LOG(ERROR) << "> IDirect3DDevice9Ex::ResetEx failed with error code " << std::hex << hr << std::dec << '!'; - return hr; - } - - D3DPRESENT_PARAMETERS pp; - _implicit_swapchain->GetPresentParameters(&pp); - - if (!runtime->on_init(pp)) - LOG(ERROR) << "Failed to recreate Direct3D 9 runtime environment on runtime " << runtime.get() << '.'; - - if (pp.EnableAutoDepthStencil) - { - _orig->GetDepthStencilSurface(&_auto_depthstencil); - SetDepthStencilSurface(_auto_depthstencil.get()); - } - - return hr; -} -HRESULT STDMETHODCALLTYPE Direct3DDevice9::GetDisplayModeEx(UINT iSwapChain, D3DDISPLAYMODEEX *pMode, D3DDISPLAYROTATION *pRotation) -{ - assert(_extended_interface); - - if (iSwapChain != 0) - { - LOG(WARN) << "Access to multi-head swap chain at index " << iSwapChain << " is unsupported."; - return D3DERR_INVALIDCALL; - } - - assert(_implicit_swapchain != nullptr); - assert(_implicit_swapchain->_extended_interface); - return static_cast(_implicit_swapchain)->GetDisplayModeEx(pMode, pRotation); -} diff --git a/msvc/source/d3d9/d3d9_device.hpp b/msvc/source/d3d9/d3d9_device.hpp deleted file mode 100644 index d4aeec0..0000000 --- a/msvc/source/d3d9/d3d9_device.hpp +++ /dev/null @@ -1,171 +0,0 @@ -/** - * Copyright (C) 2014 Patrick Mours. All rights reserved. - * License: https://github.com/crosire/reshade#license - */ - -#pragma once - -#include -#include "com_ptr.hpp" -#include - -struct Direct3DSwapChain9; - -struct __declspec(uuid("F1006E9A-1C51-4AF4-ACEF-3605D2D4C8EE")) Direct3DDevice9 : IDirect3DDevice9Ex -{ - explicit Direct3DDevice9(IDirect3DDevice9 *original); - explicit Direct3DDevice9(IDirect3DDevice9Ex *original); - - Direct3DDevice9(const Direct3DDevice9 &) = delete; - Direct3DDevice9 &operator=(const Direct3DDevice9 &) = delete; - - HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void **ppvObj) override; - ULONG STDMETHODCALLTYPE AddRef() override; - ULONG STDMETHODCALLTYPE Release() override; - - #pragma region IDirect3DDevice9 - HRESULT STDMETHODCALLTYPE TestCooperativeLevel() override; - UINT STDMETHODCALLTYPE GetAvailableTextureMem() override; - HRESULT STDMETHODCALLTYPE EvictManagedResources() override; - HRESULT STDMETHODCALLTYPE GetDirect3D(IDirect3D9 **ppD3D9) override; - HRESULT STDMETHODCALLTYPE GetDeviceCaps(D3DCAPS9 *pCaps) override; - HRESULT STDMETHODCALLTYPE GetDisplayMode(UINT iSwapChain, D3DDISPLAYMODE *pMode) override; - HRESULT STDMETHODCALLTYPE GetCreationParameters(D3DDEVICE_CREATION_PARAMETERS *pParameters) override; - HRESULT STDMETHODCALLTYPE SetCursorProperties(UINT XHotSpot, UINT YHotSpot, IDirect3DSurface9 *pCursorBitmap) override; - void STDMETHODCALLTYPE SetCursorPosition(int X, int Y, DWORD Flags) override; - BOOL STDMETHODCALLTYPE ShowCursor(BOOL bShow) override; - HRESULT STDMETHODCALLTYPE CreateAdditionalSwapChain(D3DPRESENT_PARAMETERS *pPresentationParameters, IDirect3DSwapChain9 **ppSwapChain) override; - HRESULT STDMETHODCALLTYPE GetSwapChain(UINT iSwapChain, IDirect3DSwapChain9 **ppSwapChain) override; - UINT STDMETHODCALLTYPE GetNumberOfSwapChains() override; - HRESULT STDMETHODCALLTYPE Reset(D3DPRESENT_PARAMETERS *pPresentationParameters) override; - HRESULT STDMETHODCALLTYPE Present(const RECT *pSourceRect, const RECT *pDestRect, HWND hDestWindowOverride, const RGNDATA *pDirtyRegion) override; - HRESULT STDMETHODCALLTYPE GetBackBuffer(UINT iSwapChain, UINT iBackBuffer, D3DBACKBUFFER_TYPE Type, IDirect3DSurface9 **ppBackBuffer) override; - HRESULT STDMETHODCALLTYPE GetRasterStatus(UINT iSwapChain, D3DRASTER_STATUS *pRasterStatus) override; - HRESULT STDMETHODCALLTYPE SetDialogBoxMode(BOOL bEnableDialogs) override; - void STDMETHODCALLTYPE SetGammaRamp(UINT iSwapChain, DWORD Flags, const D3DGAMMARAMP *pRamp) override; - void STDMETHODCALLTYPE GetGammaRamp(UINT iSwapChain, D3DGAMMARAMP *pRamp) override; - HRESULT STDMETHODCALLTYPE CreateTexture(UINT Width, UINT Height, UINT Levels, DWORD Usage, D3DFORMAT Format, D3DPOOL Pool, IDirect3DTexture9 **ppTexture, HANDLE *pSharedHandle) override; - HRESULT STDMETHODCALLTYPE CreateVolumeTexture(UINT Width, UINT Height, UINT Depth, UINT Levels, DWORD Usage, D3DFORMAT Format, D3DPOOL Pool, IDirect3DVolumeTexture9 **ppVolumeTexture, HANDLE *pSharedHandle) override; - HRESULT STDMETHODCALLTYPE CreateCubeTexture(UINT EdgeLength, UINT Levels, DWORD Usage, D3DFORMAT Format, D3DPOOL Pool, IDirect3DCubeTexture9 **ppCubeTexture, HANDLE *pSharedHandle) override; - HRESULT STDMETHODCALLTYPE CreateVertexBuffer(UINT Length, DWORD Usage, DWORD FVF, D3DPOOL Pool, IDirect3DVertexBuffer9 **ppVertexBuffer, HANDLE *pSharedHandle) override; - HRESULT STDMETHODCALLTYPE CreateIndexBuffer(UINT Length, DWORD Usage, D3DFORMAT Format, D3DPOOL Pool, IDirect3DIndexBuffer9 **ppIndexBuffer, HANDLE *pSharedHandle) override; - HRESULT STDMETHODCALLTYPE CreateRenderTarget(UINT Width, UINT Height, D3DFORMAT Format, D3DMULTISAMPLE_TYPE MultiSample, DWORD MultisampleQuality, BOOL Lockable, IDirect3DSurface9 **ppSurface, HANDLE *pSharedHandle) override; - HRESULT STDMETHODCALLTYPE CreateDepthStencilSurface(UINT Width, UINT Height, D3DFORMAT Format, D3DMULTISAMPLE_TYPE MultiSample, DWORD MultisampleQuality, BOOL Discard, IDirect3DSurface9 **ppSurface, HANDLE *pSharedHandle) override; - HRESULT STDMETHODCALLTYPE UpdateSurface(IDirect3DSurface9 *pSourceSurface, const RECT *pSourceRect, IDirect3DSurface9 *pDestinationSurface, const POINT *pDestPoint) override; - HRESULT STDMETHODCALLTYPE UpdateTexture(IDirect3DBaseTexture9 *pSourceTexture, IDirect3DBaseTexture9 *pDestinationTexture) override; - HRESULT STDMETHODCALLTYPE GetRenderTargetData(IDirect3DSurface9 *pRenderTarget, IDirect3DSurface9 *pDestSurface) override; - HRESULT STDMETHODCALLTYPE GetFrontBufferData(UINT iSwapChain, IDirect3DSurface9 *pDestSurface) override; - HRESULT STDMETHODCALLTYPE StretchRect(IDirect3DSurface9 *pSourceSurface, const RECT *pSourceRect, IDirect3DSurface9 *pDestSurface, const RECT *pDestRect, D3DTEXTUREFILTERTYPE Filter) override; - HRESULT STDMETHODCALLTYPE ColorFill(IDirect3DSurface9 *pSurface, const RECT *pRect, D3DCOLOR color) override; - HRESULT STDMETHODCALLTYPE CreateOffscreenPlainSurface(UINT Width, UINT Height, D3DFORMAT Format, D3DPOOL Pool, IDirect3DSurface9 **ppSurface, HANDLE *pSharedHandle) override; - HRESULT STDMETHODCALLTYPE SetRenderTarget(DWORD RenderTargetIndex, IDirect3DSurface9 *pRenderTarget) override; - HRESULT STDMETHODCALLTYPE GetRenderTarget(DWORD RenderTargetIndex, IDirect3DSurface9 **ppRenderTarget) override; - HRESULT STDMETHODCALLTYPE SetDepthStencilSurface(IDirect3DSurface9 *pNewZStencil) override; - HRESULT STDMETHODCALLTYPE GetDepthStencilSurface(IDirect3DSurface9 **ppZStencilSurface) override; - HRESULT STDMETHODCALLTYPE BeginScene() override; - HRESULT STDMETHODCALLTYPE EndScene() override; - HRESULT STDMETHODCALLTYPE Clear(DWORD Count, const D3DRECT *pRects, DWORD Flags, D3DCOLOR Color, float Z, DWORD Stencil) override; - HRESULT STDMETHODCALLTYPE SetTransform(D3DTRANSFORMSTATETYPE State, const D3DMATRIX *pMatrix) override; - HRESULT STDMETHODCALLTYPE GetTransform(D3DTRANSFORMSTATETYPE State, D3DMATRIX *pMatrix) override; - HRESULT STDMETHODCALLTYPE MultiplyTransform(D3DTRANSFORMSTATETYPE State, const D3DMATRIX *pMatrix) override; - HRESULT STDMETHODCALLTYPE SetViewport(const D3DVIEWPORT9 *pViewport) override; - HRESULT STDMETHODCALLTYPE GetViewport(D3DVIEWPORT9 *pViewport) override; - HRESULT STDMETHODCALLTYPE SetMaterial(const D3DMATERIAL9 *pMaterial) override; - HRESULT STDMETHODCALLTYPE GetMaterial(D3DMATERIAL9 *pMaterial) override; - HRESULT STDMETHODCALLTYPE SetLight(DWORD Index, const D3DLIGHT9 *pLight) override; - HRESULT STDMETHODCALLTYPE GetLight(DWORD Index, D3DLIGHT9 *pLight) override; - HRESULT STDMETHODCALLTYPE LightEnable(DWORD Index, BOOL Enable) override; - HRESULT STDMETHODCALLTYPE GetLightEnable(DWORD Index, BOOL *pEnable) override; - HRESULT STDMETHODCALLTYPE SetClipPlane(DWORD Index, const float *pPlane) override; - HRESULT STDMETHODCALLTYPE GetClipPlane(DWORD Index, float *pPlane) override; - HRESULT STDMETHODCALLTYPE SetRenderState(D3DRENDERSTATETYPE State, DWORD Value) override; - HRESULT STDMETHODCALLTYPE GetRenderState(D3DRENDERSTATETYPE State, DWORD *pValue) override; - HRESULT STDMETHODCALLTYPE CreateStateBlock(D3DSTATEBLOCKTYPE Type, IDirect3DStateBlock9 **ppSB) override; - HRESULT STDMETHODCALLTYPE BeginStateBlock() override; - HRESULT STDMETHODCALLTYPE EndStateBlock(IDirect3DStateBlock9 **ppSB) override; - HRESULT STDMETHODCALLTYPE SetClipStatus(const D3DCLIPSTATUS9 *pClipStatus) override; - HRESULT STDMETHODCALLTYPE GetClipStatus(D3DCLIPSTATUS9 *pClipStatus) override; - HRESULT STDMETHODCALLTYPE GetTexture(DWORD Stage, IDirect3DBaseTexture9 **ppTexture) override; - HRESULT STDMETHODCALLTYPE SetTexture(DWORD Stage, IDirect3DBaseTexture9 *pTexture) override; - HRESULT STDMETHODCALLTYPE GetTextureStageState(DWORD Stage, D3DTEXTURESTAGESTATETYPE Type, DWORD *pValue) override; - HRESULT STDMETHODCALLTYPE SetTextureStageState(DWORD Stage, D3DTEXTURESTAGESTATETYPE Type, DWORD Value) override; - HRESULT STDMETHODCALLTYPE GetSamplerState(DWORD Sampler, D3DSAMPLERSTATETYPE Type, DWORD *pValue) override; - HRESULT STDMETHODCALLTYPE SetSamplerState(DWORD Sampler, D3DSAMPLERSTATETYPE Type, DWORD Value) override; - HRESULT STDMETHODCALLTYPE ValidateDevice(DWORD *pNumPasses) override; - HRESULT STDMETHODCALLTYPE SetPaletteEntries(UINT PaletteNumber, const PALETTEENTRY *pEntries) override; - HRESULT STDMETHODCALLTYPE GetPaletteEntries(UINT PaletteNumber, PALETTEENTRY *pEntries) override; - HRESULT STDMETHODCALLTYPE SetCurrentTexturePalette(UINT PaletteNumber) override; - HRESULT STDMETHODCALLTYPE GetCurrentTexturePalette(UINT *PaletteNumber) override; - HRESULT STDMETHODCALLTYPE SetScissorRect(const RECT *pRect) override; - HRESULT STDMETHODCALLTYPE GetScissorRect(RECT *pRect) override; - HRESULT STDMETHODCALLTYPE SetSoftwareVertexProcessing(BOOL bSoftware) override; - BOOL STDMETHODCALLTYPE GetSoftwareVertexProcessing() override; - HRESULT STDMETHODCALLTYPE SetNPatchMode(float nSegments) override; - float STDMETHODCALLTYPE GetNPatchMode() override; - HRESULT STDMETHODCALLTYPE DrawPrimitive(D3DPRIMITIVETYPE PrimitiveType, UINT StartVertex, UINT PrimitiveCount) override; - HRESULT STDMETHODCALLTYPE DrawIndexedPrimitive(D3DPRIMITIVETYPE PrimitiveType, INT BaseVertexIndex, UINT MinVertexIndex, UINT NumVertices, UINT startIndex, UINT primCount) override; - HRESULT STDMETHODCALLTYPE DrawPrimitiveUP(D3DPRIMITIVETYPE PrimitiveType, UINT PrimitiveCount, const void *pVertexStreamZeroData, UINT VertexStreamZeroStride) override; - HRESULT STDMETHODCALLTYPE DrawIndexedPrimitiveUP(D3DPRIMITIVETYPE PrimitiveType, UINT MinVertexIndex, UINT NumVertices, UINT PrimitiveCount, const void *pIndexData, D3DFORMAT IndexDataFormat, const void *pVertexStreamZeroData, UINT VertexStreamZeroStride) override; - HRESULT STDMETHODCALLTYPE ProcessVertices(UINT SrcStartIndex, UINT DestIndex, UINT VertexCount, IDirect3DVertexBuffer9 *pDestBuffer, IDirect3DVertexDeclaration9 *pVertexDecl, DWORD Flags) override; - HRESULT STDMETHODCALLTYPE CreateVertexDeclaration(const D3DVERTEXELEMENT9 *pVertexElements, IDirect3DVertexDeclaration9 **ppDecl) override; - HRESULT STDMETHODCALLTYPE SetVertexDeclaration(IDirect3DVertexDeclaration9 *pDecl) override; - HRESULT STDMETHODCALLTYPE GetVertexDeclaration(IDirect3DVertexDeclaration9 **ppDecl) override; - HRESULT STDMETHODCALLTYPE SetFVF(DWORD FVF) override; - HRESULT STDMETHODCALLTYPE GetFVF(DWORD *pFVF) override; - HRESULT STDMETHODCALLTYPE CreateVertexShader(const DWORD *pFunction, IDirect3DVertexShader9 **ppShader) override; - HRESULT STDMETHODCALLTYPE SetVertexShader(IDirect3DVertexShader9 *pShader) override; - HRESULT STDMETHODCALLTYPE GetVertexShader(IDirect3DVertexShader9 **ppShader) override; - HRESULT STDMETHODCALLTYPE SetVertexShaderConstantF(UINT StartRegister, const float *pConstantData, UINT Vector4fCount) override; - HRESULT STDMETHODCALLTYPE GetVertexShaderConstantF(UINT StartRegister, float *pConstantData, UINT Vector4fCount) override; - HRESULT STDMETHODCALLTYPE SetVertexShaderConstantI(UINT StartRegister, const int *pConstantData, UINT Vector4iCount) override; - HRESULT STDMETHODCALLTYPE GetVertexShaderConstantI(UINT StartRegister, int *pConstantData, UINT Vector4iCount) override; - HRESULT STDMETHODCALLTYPE SetVertexShaderConstantB(UINT StartRegister, const BOOL *pConstantData, UINT BoolCount) override; - HRESULT STDMETHODCALLTYPE GetVertexShaderConstantB(UINT StartRegister, BOOL *pConstantData, UINT BoolCount) override; - HRESULT STDMETHODCALLTYPE SetStreamSource(UINT StreamNumber, IDirect3DVertexBuffer9 *pStreamData, UINT OffsetInBytes, UINT Stride) override; - HRESULT STDMETHODCALLTYPE GetStreamSource(UINT StreamNumber, IDirect3DVertexBuffer9 **ppStreamData, UINT *OffsetInBytes, UINT *pStride) override; - HRESULT STDMETHODCALLTYPE SetStreamSourceFreq(UINT StreamNumber, UINT Divider) override; - HRESULT STDMETHODCALLTYPE GetStreamSourceFreq(UINT StreamNumber, UINT *Divider) override; - HRESULT STDMETHODCALLTYPE SetIndices(IDirect3DIndexBuffer9 *pIndexData) override; - HRESULT STDMETHODCALLTYPE GetIndices(IDirect3DIndexBuffer9 **ppIndexData) override; - HRESULT STDMETHODCALLTYPE CreatePixelShader(const DWORD *pFunction, IDirect3DPixelShader9 **ppShader) override; - HRESULT STDMETHODCALLTYPE SetPixelShader(IDirect3DPixelShader9 *pShader) override; - HRESULT STDMETHODCALLTYPE GetPixelShader(IDirect3DPixelShader9 **ppShader) override; - HRESULT STDMETHODCALLTYPE SetPixelShaderConstantF(UINT StartRegister, const float *pConstantData, UINT Vector4fCount) override; - HRESULT STDMETHODCALLTYPE GetPixelShaderConstantF(UINT StartRegister, float *pConstantData, UINT Vector4fCount) override; - HRESULT STDMETHODCALLTYPE SetPixelShaderConstantI(UINT StartRegister, const int *pConstantData, UINT Vector4iCount) override; - HRESULT STDMETHODCALLTYPE GetPixelShaderConstantI(UINT StartRegister, int *pConstantData, UINT Vector4iCount) override; - HRESULT STDMETHODCALLTYPE SetPixelShaderConstantB(UINT StartRegister, const BOOL *pConstantData, UINT BoolCount) override; - HRESULT STDMETHODCALLTYPE GetPixelShaderConstantB(UINT StartRegister, BOOL *pConstantData, UINT BoolCount) override; - HRESULT STDMETHODCALLTYPE DrawRectPatch(UINT Handle, const float *pNumSegs, const D3DRECTPATCH_INFO *pRectPatchInfo) override; - HRESULT STDMETHODCALLTYPE DrawTriPatch(UINT Handle, const float *pNumSegs, const D3DTRIPATCH_INFO *pTriPatchInfo) override; - HRESULT STDMETHODCALLTYPE DeletePatch(UINT Handle) override; - HRESULT STDMETHODCALLTYPE CreateQuery(D3DQUERYTYPE Type, IDirect3DQuery9 **ppQuery) override; - #pragma endregion - #pragma region IDirect3DDevice9Ex - HRESULT STDMETHODCALLTYPE SetConvolutionMonoKernel(UINT width, UINT height, float *rows, float *columns) override; - HRESULT STDMETHODCALLTYPE ComposeRects(IDirect3DSurface9 *pSrc, IDirect3DSurface9 *pDst, IDirect3DVertexBuffer9 *pSrcRectDescs, UINT NumRects, IDirect3DVertexBuffer9 *pDstRectDescs, D3DCOMPOSERECTSOP Operation, int Xoffset, int Yoffset) override; - HRESULT STDMETHODCALLTYPE PresentEx(const RECT *pSourceRect, const RECT *pDestRect, HWND hDestWindowOverride, const RGNDATA *pDirtyRegion, DWORD dwFlags) override; - HRESULT STDMETHODCALLTYPE GetGPUThreadPriority(INT *pPriority) override; - HRESULT STDMETHODCALLTYPE SetGPUThreadPriority(INT Priority) override; - HRESULT STDMETHODCALLTYPE WaitForVBlank(UINT iSwapChain) override; - HRESULT STDMETHODCALLTYPE CheckResourceResidency(IDirect3DResource9 **pResourceArray, UINT32 NumResources) override; - HRESULT STDMETHODCALLTYPE SetMaximumFrameLatency(UINT MaxLatency) override; - HRESULT STDMETHODCALLTYPE GetMaximumFrameLatency(UINT *pMaxLatency) override; - HRESULT STDMETHODCALLTYPE CheckDeviceState(HWND hDestinationWindow) override; - HRESULT STDMETHODCALLTYPE CreateRenderTargetEx(UINT Width, UINT Height, D3DFORMAT Format, D3DMULTISAMPLE_TYPE MultiSample, DWORD MultisampleQuality, BOOL Lockable, IDirect3DSurface9 **ppSurface, HANDLE *pSharedHandle, DWORD Usage) override; - HRESULT STDMETHODCALLTYPE CreateOffscreenPlainSurfaceEx(UINT Width, UINT Height, D3DFORMAT Format, D3DPOOL Pool, IDirect3DSurface9 **ppSurface, HANDLE *pSharedHandle, DWORD Usage) override; - HRESULT STDMETHODCALLTYPE CreateDepthStencilSurfaceEx(UINT Width, UINT Height, D3DFORMAT Format, D3DMULTISAMPLE_TYPE MultiSample, DWORD MultisampleQuality, BOOL Discard, IDirect3DSurface9 **ppSurface, HANDLE *pSharedHandle, DWORD Usage) override; - HRESULT STDMETHODCALLTYPE ResetEx(D3DPRESENT_PARAMETERS *pPresentationParameters, D3DDISPLAYMODEEX *pFullscreenDisplayMode) override; - HRESULT STDMETHODCALLTYPE GetDisplayModeEx(UINT iSwapChain, D3DDISPLAYMODEEX *pMode, D3DDISPLAYROTATION *pRotation) override; - #pragma endregion - - bool check_and_upgrade_interface(REFIID riid); - - LONG _ref = 1; - IDirect3DDevice9 *_orig; - bool _extended_interface; - Direct3DSwapChain9 *_implicit_swapchain = nullptr; - std::vector _additional_swapchains; - com_ptr _auto_depthstencil; - bool _use_software_rendering = false; -}; diff --git a/msvc/source/d3d9/d3d9_profiling.cpp b/msvc/source/d3d9/d3d9_profiling.cpp deleted file mode 100644 index 59db1b1..0000000 --- a/msvc/source/d3d9/d3d9_profiling.cpp +++ /dev/null @@ -1,48 +0,0 @@ -/** - * Copyright (C) 2014 Patrick Mours. All rights reserved. - * License: https://github.com/crosire/reshade#license - */ - -#include "hook_manager.hpp" -#include - -HOOK_EXPORT int WINAPI D3DPERF_BeginEvent(D3DCOLOR col, LPCWSTR wszName) -{ - UNREFERENCED_PARAMETER(col); - UNREFERENCED_PARAMETER(wszName); - - return 0; -} -HOOK_EXPORT int WINAPI D3DPERF_EndEvent() -{ - return 0; -} - -HOOK_EXPORT void WINAPI D3DPERF_SetMarker(D3DCOLOR col, LPCWSTR wszName) -{ - UNREFERENCED_PARAMETER(col); - UNREFERENCED_PARAMETER(wszName); -} -HOOK_EXPORT void WINAPI D3DPERF_SetRegion(D3DCOLOR col, LPCWSTR wszName) -{ - UNREFERENCED_PARAMETER(col); - UNREFERENCED_PARAMETER(wszName); -} -HOOK_EXPORT void WINAPI D3DPERF_SetOptions(DWORD dwOptions) -{ - UNREFERENCED_PARAMETER(dwOptions); - -#ifdef _DEBUG // Enable PIX in debug builds (calling 'D3DPERF_SetOptions(1)' disables profiling/analysis tools, so revert that) - reshade::hooks::call(D3DPERF_SetOptions)(0); -#endif -} - -HOOK_EXPORT BOOL WINAPI D3DPERF_QueryRepeatFrame() -{ - return FALSE; -} - -HOOK_EXPORT DWORD WINAPI D3DPERF_GetStatus() -{ - return 0; -} diff --git a/msvc/source/d3d9/d3d9_swapchain.cpp b/msvc/source/d3d9/d3d9_swapchain.cpp deleted file mode 100644 index e9a2627..0000000 --- a/msvc/source/d3d9/d3d9_swapchain.cpp +++ /dev/null @@ -1,149 +0,0 @@ -/** - * Copyright (C) 2014 Patrick Mours. All rights reserved. - * License: https://github.com/crosire/reshade#license - */ - -#include "log.hpp" -#include "d3d9_device.hpp" -#include "d3d9_swapchain.hpp" -#include "runtime_d3d9.hpp" - -Direct3DSwapChain9::Direct3DSwapChain9(Direct3DDevice9 *device, IDirect3DSwapChain9 *original, const std::shared_ptr &runtime) : - _orig(original), - _extended_interface(0), - _device(device), - _runtime(runtime) {} -Direct3DSwapChain9::Direct3DSwapChain9(Direct3DDevice9 *device, IDirect3DSwapChain9Ex *original, const std::shared_ptr &runtime) : - _orig(original), - _extended_interface(1), - _device(device), - _runtime(runtime) {} - -bool Direct3DSwapChain9::check_and_upgrade_interface(REFIID riid) -{ - if (_extended_interface || riid != __uuidof(IDirect3DSwapChain9Ex)) - return true; - - IDirect3DSwapChain9Ex *new_interface = nullptr; - if (FAILED(_orig->QueryInterface(IID_PPV_ARGS(&new_interface)))) - return false; -#if RESHADE_VERBOSE_LOG - LOG(DEBUG) << "Upgraded IDirect3DSwapChain9 object " << this << " to IDirect3DSwapChain9Ex."; -#endif - _orig->Release(); - _orig = new_interface; - _extended_interface = true; - - return true; -} - -HRESULT STDMETHODCALLTYPE Direct3DSwapChain9::QueryInterface(REFIID riid, void **ppvObj) -{ - if (ppvObj == nullptr) - return E_POINTER; - - if (riid == __uuidof(this) || - riid == __uuidof(IUnknown) || - riid == __uuidof(IDirect3DSwapChain9) || - riid == __uuidof(IDirect3DSwapChain9Ex)) - { - if (!check_and_upgrade_interface(riid)) - return E_NOINTERFACE; - - AddRef(); - *ppvObj = this; - return S_OK; - } - - return _orig->QueryInterface(riid, ppvObj); -} -ULONG STDMETHODCALLTYPE Direct3DSwapChain9::AddRef() -{ - ++_ref; - - return _orig->AddRef(); -} -ULONG STDMETHODCALLTYPE Direct3DSwapChain9::Release() -{ - if (--_ref == 0) - { - assert(_runtime != nullptr); - _runtime->on_reset(); - _runtime.reset(); - - const auto it = std::find(_device->_additional_swapchains.begin(), _device->_additional_swapchains.end(), this); - if (it != _device->_additional_swapchains.end()) - { - _device->_additional_swapchains.erase(it); - _device->Release(); // Remove the reference that was added in 'Direct3DDevice9::CreateAdditionalSwapChain' - } - } - - const ULONG ref = _orig->Release(); - - if (ref != 0 && _ref != 0) - return ref; - else if (ref != 0) - LOG(WARN) << "Reference count for IDirect3DSwapChain9" << (_extended_interface ? "Ex" : "") << " object " << this << " is inconsistent: " << ref << ", but expected 0."; - - assert(_ref <= 0); -#if RESHADE_VERBOSE_LOG - LOG(DEBUG) << "Destroyed IDirect3DSwapChain9" << (_extended_interface ? "Ex" : "") << " object " << this << '.'; -#endif - delete this; - return 0; -} - -HRESULT STDMETHODCALLTYPE Direct3DSwapChain9::Present(const RECT *pSourceRect, const RECT *pDestRect, HWND hDestWindowOverride, const RGNDATA *pDirtyRegion, DWORD dwFlags) -{ - assert(_runtime != nullptr); - _runtime->on_present(); - - return _orig->Present(pSourceRect, pDestRect, hDestWindowOverride, pDirtyRegion, dwFlags); -} -HRESULT STDMETHODCALLTYPE Direct3DSwapChain9::GetFrontBufferData(IDirect3DSurface9 *pDestSurface) -{ - return _orig->GetFrontBufferData(pDestSurface); -} -HRESULT STDMETHODCALLTYPE Direct3DSwapChain9::GetBackBuffer(UINT iBackBuffer, D3DBACKBUFFER_TYPE Type, IDirect3DSurface9 **ppBackBuffer) -{ - return _orig->GetBackBuffer(iBackBuffer, Type, ppBackBuffer); -} -HRESULT STDMETHODCALLTYPE Direct3DSwapChain9::GetRasterStatus(D3DRASTER_STATUS *pRasterStatus) -{ - return _orig->GetRasterStatus(pRasterStatus); -} -HRESULT STDMETHODCALLTYPE Direct3DSwapChain9::GetDisplayMode(D3DDISPLAYMODE *pMode) -{ - return _orig->GetDisplayMode(pMode); -} -HRESULT STDMETHODCALLTYPE Direct3DSwapChain9::GetDevice(IDirect3DDevice9 **ppDevice) -{ - if (ppDevice == nullptr) - return D3DERR_INVALIDCALL; - - _device->AddRef(); - *ppDevice = _device; - - return D3D_OK; -} -HRESULT STDMETHODCALLTYPE Direct3DSwapChain9::GetPresentParameters(D3DPRESENT_PARAMETERS *pPresentationParameters) -{ - return _orig->GetPresentParameters(pPresentationParameters); -} - -HRESULT STDMETHODCALLTYPE Direct3DSwapChain9::GetLastPresentCount(UINT *pLastPresentCount) -{ - assert(_extended_interface); - return static_cast(_orig)->GetLastPresentCount(pLastPresentCount); -} -HRESULT STDMETHODCALLTYPE Direct3DSwapChain9::GetPresentStats(D3DPRESENTSTATS *pPresentationStatistics) -{ - assert(_extended_interface); - return static_cast(_orig)->GetPresentStats(pPresentationStatistics); -} -HRESULT STDMETHODCALLTYPE Direct3DSwapChain9::GetDisplayModeEx(D3DDISPLAYMODEEX *pMode, D3DDISPLAYROTATION *pRotation) -{ - assert(_extended_interface); - return static_cast(_orig)->GetDisplayModeEx(pMode, pRotation); -} diff --git a/msvc/source/d3d9/d3d9_swapchain.hpp b/msvc/source/d3d9/d3d9_swapchain.hpp deleted file mode 100644 index a1b2146..0000000 --- a/msvc/source/d3d9/d3d9_swapchain.hpp +++ /dev/null @@ -1,48 +0,0 @@ -/** - * Copyright (C) 2014 Patrick Mours. All rights reserved. - * License: https://github.com/crosire/reshade#license - */ - -#pragma once - -#include -#include - -struct Direct3DDevice9; -namespace reshade::d3d9 { class runtime_d3d9; } - -struct __declspec(uuid("BC52FCE4-1EAC-40C8-84CF-863600BBAA01")) Direct3DSwapChain9 : IDirect3DSwapChain9Ex -{ - Direct3DSwapChain9(Direct3DDevice9 *device, IDirect3DSwapChain9 *original, const std::shared_ptr &runtime); - Direct3DSwapChain9(Direct3DDevice9 *device, IDirect3DSwapChain9Ex *original, const std::shared_ptr &runtime); - - Direct3DSwapChain9(const Direct3DSwapChain9 &) = delete; - Direct3DSwapChain9 &operator=(const Direct3DSwapChain9 &) = delete; - - HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void **ppvObj) override; - ULONG STDMETHODCALLTYPE AddRef() override; - ULONG STDMETHODCALLTYPE Release() override; - - #pragma region IDirect3DSwapChain9 - HRESULT STDMETHODCALLTYPE Present(const RECT *pSourceRect, const RECT *pDestRect, HWND hDestWindowOverride, const RGNDATA *pDirtyRegion, DWORD dwFlags) override; - HRESULT STDMETHODCALLTYPE GetFrontBufferData(IDirect3DSurface9 *pDestSurface) override; - HRESULT STDMETHODCALLTYPE GetBackBuffer(UINT iBackBuffer, D3DBACKBUFFER_TYPE Type, IDirect3DSurface9 **ppBackBuffer) override; - HRESULT STDMETHODCALLTYPE GetRasterStatus(D3DRASTER_STATUS *pRasterStatus) override; - HRESULT STDMETHODCALLTYPE GetDisplayMode(D3DDISPLAYMODE *pMode) override; - HRESULT STDMETHODCALLTYPE GetDevice(IDirect3DDevice9 **ppDevice) override; - HRESULT STDMETHODCALLTYPE GetPresentParameters(D3DPRESENT_PARAMETERS *pPresentationParameters) override; - #pragma endregion - #pragma region IDirect3DSwapChain9Ex - HRESULT STDMETHODCALLTYPE GetLastPresentCount(UINT *pLastPresentCount) override; - HRESULT STDMETHODCALLTYPE GetPresentStats(D3DPRESENTSTATS *pPresentationStatistics) override; - HRESULT STDMETHODCALLTYPE GetDisplayModeEx(D3DDISPLAYMODEEX *pMode, D3DDISPLAYROTATION *pRotation) override; - #pragma endregion - - bool check_and_upgrade_interface(REFIID riid); - - LONG _ref = 1; - IDirect3DSwapChain9 *_orig; - bool _extended_interface; - Direct3DDevice9 *const _device; - std::shared_ptr _runtime; -}; diff --git a/msvc/source/d3d9/runtime_d3d9.cpp b/msvc/source/d3d9/runtime_d3d9.cpp deleted file mode 100644 index 6f1cd31..0000000 --- a/msvc/source/d3d9/runtime_d3d9.cpp +++ /dev/null @@ -1,1711 +0,0 @@ -/** - * Copyright (C) 2014 Patrick Mours. All rights reserved. - * License: https://github.com/crosire/reshade#license - */ - -#include "log.hpp" -#include "ini_file.hpp" -#include "runtime_d3d9.hpp" -#include "runtime_objects.hpp" -#include -#include - -constexpr auto D3DFMT_INTZ = static_cast(MAKEFOURCC('I', 'N', 'T', 'Z')); -constexpr auto D3DFMT_DF16 = static_cast(MAKEFOURCC('D', 'F', '1', '6')); -constexpr auto D3DFMT_DF24 = static_cast(MAKEFOURCC('D', 'F', '2', '4')); -constexpr auto D3DFMT_ATI1 = static_cast(MAKEFOURCC('A', 'T', 'I', '1')); -constexpr auto D3DFMT_ATI2 = static_cast(MAKEFOURCC('A', 'T', 'I', '2')); - -namespace reshade::d3d9 -{ - struct d3d9_tex_data : base_object - { - com_ptr texture; - com_ptr surface; - }; - struct d3d9_pass_data : base_object - { - com_ptr vertex_shader; - com_ptr pixel_shader; - com_ptr stateblock; - bool clear_render_targets = false; - IDirect3DSurface9 *render_targets[8] = {}; - IDirect3DTexture9 *sampler_textures[16] = {}; - }; - struct d3d9_technique_data : base_object - { - DWORD num_samplers = 0; - DWORD sampler_states[16][12] = {}; - IDirect3DTexture9 *sampler_textures[16] = {}; - DWORD constant_register_count = 0; - size_t uniform_storage_offset = 0; - }; -} - -reshade::d3d9::runtime_d3d9::runtime_d3d9(IDirect3DDevice9 *device, IDirect3DSwapChain9 *swapchain) : - _device(device), _swapchain(swapchain), - _app_state(device) -{ - assert(device != nullptr); - assert(swapchain != nullptr); - - _device->GetDirect3D(&_d3d); - assert(_d3d != nullptr); - - D3DCAPS9 caps = {}; - _device->GetDeviceCaps(&caps); - D3DDEVICE_CREATION_PARAMETERS creation_params = {}; - _device->GetCreationParameters(&creation_params); - D3DADAPTER_IDENTIFIER9 adapter_desc = {}; - _d3d->GetAdapterIdentifier(creation_params.AdapterOrdinal, 0, &adapter_desc); - - _vendor_id = adapter_desc.VendorId; - _device_id = adapter_desc.DeviceId; - _renderer_id = 0x9000; - - _num_samplers = caps.MaxSimultaneousTextures; - _num_simultaneous_rendertargets = std::min(caps.NumSimultaneousRTs, DWORD(8)); - _behavior_flags = creation_params.BehaviorFlags; - -#if RESHADE_GUI - subscribe_to_ui("DX9", [this]() { draw_debug_menu(); }); -#endif - subscribe_to_load_config([this](const ini_file &config) { - config.get("DX9_BUFFER_DETECTION", "DisableINTZ", _disable_intz); - config.get("DX9_BUFFER_DETECTION", "PreserveDepthBuffer", _preserve_depth_buffer); - config.get("DX9_BUFFER_DETECTION", "PreserveDepthBufferIndex", _preserve_starting_index); - config.get("DX9_BUFFER_DETECTION", "AutoPreserve", _auto_preserve); - config.get("DX9_BUFFER_DETECTION", "SourceEngineFix", _source_engine_fix); - config.get("DX9_BUFFER_DETECTION", "FocusOnBestOriginalDepthstencilSource", _focus_on_best_original_depthstencil_source); - config.get("DX9_BUFFER_DETECTION", "BruteForceFix", _brute_force_fix); - }); - subscribe_to_save_config([this](ini_file &config) { - config.set("DX9_BUFFER_DETECTION", "DisableINTZ", _disable_intz); - config.set("DX9_BUFFER_DETECTION", "PreserveDepthBuffer", _preserve_depth_buffer); - config.set("DX9_BUFFER_DETECTION", "PreserveDepthBufferIndex", _preserve_starting_index); - config.set("DX9_BUFFER_DETECTION", "AutoPreserve", _auto_preserve); - config.set("DX9_BUFFER_DETECTION", "SourceEngineFix", _source_engine_fix); - config.set("DX9_BUFFER_DETECTION", "FocusOnBestOriginalDepthstencilSource", _focus_on_best_original_depthstencil_source); - config.set("DX9_BUFFER_DETECTION", "BruteForceFix", _brute_force_fix); - }); -} -reshade::d3d9::runtime_d3d9::~runtime_d3d9() -{ - if (_d3d_compiler != nullptr) - FreeLibrary(_d3d_compiler); -} - -bool reshade::d3d9::runtime_d3d9::init_backbuffer_texture() -{ - // Get back buffer surface - HRESULT hr = _swapchain->GetBackBuffer(0, D3DBACKBUFFER_TYPE_MONO, &_backbuffer); - assert(SUCCEEDED(hr)); - - if (_is_multisampling_enabled || - (_backbuffer_format == D3DFMT_X8R8G8B8 || _backbuffer_format == D3DFMT_X8B8G8R8)) - { - switch (_backbuffer_format) - { - case D3DFMT_X8R8G8B8: - _backbuffer_format = D3DFMT_A8R8G8B8; - break; - case D3DFMT_X8B8G8R8: - _backbuffer_format = D3DFMT_A8B8G8R8; - break; - } - - if (hr = _device->CreateRenderTarget(_width, _height, _backbuffer_format, D3DMULTISAMPLE_NONE, 0, FALSE, &_backbuffer_resolved, nullptr); FAILED(hr)) - { - LOG(ERROR) << "Failed to create back buffer resolve texture! HRESULT is '" << std::hex << hr << std::dec << "'."; - return false; - } - } - else - { - _backbuffer_resolved = _backbuffer; - } - - // Create back buffer shader texture - if (hr = _device->CreateTexture(_width, _height, 1, D3DUSAGE_RENDERTARGET, _backbuffer_format, D3DPOOL_DEFAULT, &_backbuffer_texture, nullptr); FAILED(hr)) - { - LOG(ERROR) << "Failed to create back buffer texture! HRESULT is '" << std::hex << hr << std::dec << "'."; - return false; - } - - _backbuffer_texture->GetSurfaceLevel(0, &_backbuffer_texture_surface); - - return true; -} -bool reshade::d3d9::runtime_d3d9::init_default_depth_stencil() -{ - if (HRESULT hr = _device->CreateDepthStencilSurface(_width, _height, D3DFMT_D24S8, D3DMULTISAMPLE_NONE, 0, FALSE, &_default_depthstencil, nullptr); FAILED(hr)) - { - LOG(ERROR) << "Failed to create default depth stencil! HRESULT is '" << std::hex << hr << std::dec << "'."; - return false; - } - - return true; -} -bool reshade::d3d9::runtime_d3d9::init_fullscreen_triangle_resources() -{ - if (HRESULT hr = _device->CreateVertexBuffer(3 * sizeof(float), D3DUSAGE_WRITEONLY, 0, D3DPOOL_DEFAULT, &_effect_triangle_buffer, nullptr); FAILED(hr)) - { - LOG(ERROR) << "Failed to create effect vertex buffer! HRESULT is '" << std::hex << hr << std::dec << "'."; - return false; - } - - float *data = nullptr; - _effect_triangle_buffer->Lock(0, 3 * sizeof(float), reinterpret_cast(&data), 0); - for (unsigned int i = 0; i < 3; i++) - data[i] = static_cast(i); - _effect_triangle_buffer->Unlock(); - - const D3DVERTEXELEMENT9 declaration[] = { - { 0, 0, D3DDECLTYPE_FLOAT1, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 0 }, - D3DDECL_END() - }; - - if (HRESULT hr = _device->CreateVertexDeclaration(declaration, &_effect_triangle_layout); FAILED(hr)) - { - LOG(ERROR) << "Failed to create effect vertex declaration! HRESULT is '" << std::hex << hr << std::dec << "'."; - return false; - } - - return true; -} - -bool reshade::d3d9::runtime_d3d9::on_init(const D3DPRESENT_PARAMETERS &pp) -{ - RECT window_rect = {}; - GetClientRect(pp.hDeviceWindow, &window_rect); - - _width = pp.BackBufferWidth; - _height = pp.BackBufferHeight; - _window_width = window_rect.right - window_rect.left; - _window_height = window_rect.bottom - window_rect.top; - _backbuffer_format = pp.BackBufferFormat; - _is_multisampling_enabled = pp.MultiSampleType != D3DMULTISAMPLE_NONE; - - if (!_app_state.init_state_block() || - !init_backbuffer_texture() || - !init_default_depth_stencil() || - !init_fullscreen_triangle_resources() -#if RESHADE_GUI - || !init_imgui_resources() -#endif - ) - return false; - - return runtime::on_init(pp.hDeviceWindow); -} -void reshade::d3d9::runtime_d3d9::on_reset() -{ - runtime::on_reset(); - - _app_state.release_state_block(); - - _backbuffer.reset(); - _backbuffer_resolved.reset(); - _backbuffer_texture.reset(); - _backbuffer_texture_surface.reset(); - - _depthstencil.reset(); - _depthstencil_replacement.reset(); - _depthstencil_texture.reset(); - - _default_depthstencil.reset(); - - _effect_triangle_buffer.reset(); - _effect_triangle_layout.reset(); - - _imgui_vertex_buffer.reset(); - _imgui_index_buffer.reset(); - _imgui_vertex_buffer_size = 0; - _imgui_index_buffer_size = 0; - - _imgui_state.reset(); - - _depth_source_table.clear(); - _depth_buffer_table.clear(); - - _db_vertices = 0; - _db_drawcalls = 0; - _current_db_vertices = 0; - _current_db_drawcalls = 0; - - _disable_depth_buffer_size_restriction = false; - _init_depthbuffer_detection = true; -} - -void reshade::d3d9::runtime_d3d9::on_present() -{ - if (!_is_initialized || FAILED(_device->BeginScene())) - return; - - detect_depth_source(); - - /** Vanquish fix (and other games using bigger depth buffer surface than the viewport) **/ - // if the depthstencil_replacement surface detection fails on the first attempt, try to detect it - // in some bigger resolutions - if (_depthstencil_replacement == nullptr) - _disable_depth_buffer_size_restriction = true; - // if the depthstencil_replacement surface detection succeeds by retrieving bigger resolutions candidates - // the texture is cropped to the actual viewport, so we can go back to the standard resolution filter - // and recheck for a depthstencil replacement candidate using the good resolution - else if (_disable_depth_buffer_size_restriction) - { - _depth_source_table.clear(); - _disable_depth_buffer_size_restriction = false; - } - - _init_depthbuffer_detection = false; - - _app_state.capture(); - BOOL software_rendering_enabled = FALSE; - if ((_behavior_flags & D3DCREATE_MIXED_VERTEXPROCESSING) != 0) - software_rendering_enabled = _device->GetSoftwareVertexProcessing(), - _device->SetSoftwareVertexProcessing(FALSE); // Disable software vertex processing since it is incompatible with programmable shaders - - // Resolve MSAA back buffer if MSAA is active - if (_backbuffer_resolved != _backbuffer) - _device->StretchRect(_backbuffer.get(), nullptr, _backbuffer_resolved.get(), nullptr, D3DTEXF_NONE); - - update_and_render_effects(); - runtime::on_present(); - - // Stretch main render target back into MSAA back buffer if MSAA is active - if (_backbuffer_resolved != _backbuffer) - _device->StretchRect(_backbuffer_resolved.get(), nullptr, _backbuffer.get(), nullptr, D3DTEXF_NONE); - - // Apply previous state from application - _app_state.apply_and_release(); - if ((_behavior_flags & D3DCREATE_MIXED_VERTEXPROCESSING) != 0) - _device->SetSoftwareVertexProcessing(software_rendering_enabled); - - _device->EndScene(); - - if (_preserve_depth_buffer && !_depth_buffer_table.empty()) - { - _depth_buffer_table.clear(); - - com_ptr depthstencil; - _device->GetDepthStencilSurface(&depthstencil); - - // Ensure that the main depth buffer replacement surface (and texture) is cleared before the next frame - _device->SetDepthStencilSurface(_depthstencil_replacement.get()); - _device->Clear(0, nullptr, D3DCLEAR_ZBUFFER, 0, 1.0f, 0); - - _device->SetDepthStencilSurface(depthstencil.get()); - } -} - -void reshade::d3d9::runtime_d3d9::on_draw_primitive(D3DPRIMITIVETYPE PrimitiveType, UINT StartVertex, UINT PrimitiveCount) -{ - com_ptr depthstencil; - _device->GetDepthStencilSurface(&depthstencil); - - if (depthstencil != nullptr) - { - // Resolve pointer to original depth stencil - if (_depthstencil_replacement == depthstencil) - depthstencil = _depthstencil; - } - - // fix to display user weapon and cockpit in some games - weapon_or_cockpit_fix(depthstencil, PrimitiveType, StartVertex, PrimitiveCount); - - on_draw_call(depthstencil, PrimitiveType, PrimitiveCount); -} -void reshade::d3d9::runtime_d3d9::on_draw_indexed_primitive(D3DPRIMITIVETYPE PrimitiveType, INT BaseVertexIndex, UINT MinVertexIndex, UINT NumVertices, UINT StartIndex, UINT PrimitiveCount) -{ - com_ptr depthstencil; - _device->GetDepthStencilSurface(&depthstencil); - - if (depthstencil != nullptr) - { - // Resolve pointer to original depth stencil - if (_depthstencil_replacement == depthstencil) - depthstencil = _depthstencil; - } - - // fix to display user weapon and cockpit in some games - weapon_or_cockpit_fix(depthstencil, PrimitiveType, BaseVertexIndex, MinVertexIndex, NumVertices, StartIndex, PrimitiveCount); - - on_draw_call(depthstencil, PrimitiveType, PrimitiveCount); -} -void reshade::d3d9::runtime_d3d9::on_draw_primitive_up(D3DPRIMITIVETYPE PrimitiveType, UINT PrimitiveCount, const void *pVertexStreamZeroData, UINT VertexStreamZeroStride) -{ - com_ptr depthstencil; - _device->GetDepthStencilSurface(&depthstencil); - - if (depthstencil != nullptr) - { - // Resolve pointer to original depth stencil - if (_depthstencil_replacement == depthstencil) - depthstencil = _depthstencil; - } - - on_draw_call(depthstencil, PrimitiveType, PrimitiveCount); -} -void reshade::d3d9::runtime_d3d9::on_draw_indexed_primitive_up(D3DPRIMITIVETYPE PrimitiveType, UINT MinVertexIndex, UINT NumVertices, UINT PrimitiveCount, const void *pIndexData, D3DFORMAT IndexDataFormat, const void *pVertexStreamZeroData, UINT VertexStreamZeroStride) -{ - com_ptr depthstencil; - _device->GetDepthStencilSurface(&depthstencil); - - if (depthstencil != nullptr) - { - // Resolve pointer to original depth stencil - if (_depthstencil_replacement == depthstencil) - depthstencil = _depthstencil; - } - - on_draw_call(depthstencil, PrimitiveType, PrimitiveCount); -} -void reshade::d3d9::runtime_d3d9::on_draw_call(com_ptr depthstencil, D3DPRIMITIVETYPE type, unsigned int vertices) -{ - switch (type) - { - case D3DPT_LINELIST: - vertices *= 2; - break; - case D3DPT_LINESTRIP: - vertices += 1; - break; - case D3DPT_TRIANGLELIST: - vertices *= 3; - break; - case D3DPT_TRIANGLESTRIP: - case D3DPT_TRIANGLEFAN: - vertices += 2; - break; - } - - _vertices += vertices; - _drawcalls += 1; - - if (depthstencil != nullptr) - { - // Resolve pointer to original depth stencil - if (_depthstencil_replacement == depthstencil) - depthstencil = _depthstencil; - - // Update draw statistics for tracked depth stencil surfaces - const auto it = _depth_source_table.find(depthstencil.get()); - if (it != _depth_source_table.end()) - { - it->second.drawcall_count = _drawcalls; - it->second.vertices_count += vertices; - } - else - { - D3DSURFACE_DESC desc; - depthstencil->GetDesc(&desc); - - if (!check_depthstencil_size(desc)) // Ignore unlikely candidates - return; - - _depth_source_table.emplace(depthstencil, depth_source_info{ nullptr, desc.Width, desc.Height, _drawcalls, _vertices }); - } - } - - if (_preserve_depth_buffer && _depthstencil_replacement != nullptr) - { - _device->SetDepthStencilSurface(depthstencil.get()); - - D3DSURFACE_DESC desc, depthstencil_desc; - D3DVIEWPORT9 pViewport; - - _device->GetViewport(&pViewport); - - desc.Width = pViewport.Width; - desc.Height = pViewport.Height; - desc.MultiSampleType = D3DMULTISAMPLE_NONE; - _is_good_viewport = true; - - if (_depthstencil_replacement == nullptr) - _is_good_viewport = check_depthstencil_size(desc); - else - { - _depthstencil_replacement->GetDesc(&depthstencil_desc); - _is_good_viewport = check_depthstencil_size(desc, depthstencil_desc); - } - - // remove parasite items - if (!_is_good_viewport) - return; - - // check that the drawcall is done on the best original depthstencil source (the one from which the depthstencil_replaceent was created) - if (_focus_on_best_original_depthstencil_source && !_is_best_original_depthstencil_source) - return; - - _current_db_vertices += vertices, - _current_db_drawcalls += 1; - - if (_depthstencil_replacement != depthstencil && _depth_buffer_table.size() <= _adjusted_preserve_starting_index) - _device->SetDepthStencilSurface(_depthstencil_replacement.get()); - } -} -void reshade::d3d9::runtime_d3d9::on_set_depthstencil_surface(IDirect3DSurface9 *&depthstencil) -{ - _is_best_original_depthstencil_source = (depthstencil == _depthstencil); - - // Keep track of all used depth stencil surfaces - if (_depth_source_table.find(depthstencil) == _depth_source_table.end()) - { - D3DSURFACE_DESC desc; - depthstencil->GetDesc(&desc); - if (!check_depthstencil_size(desc)) // Ignore unlikely candidates - return; - - _depth_source_table.emplace(depthstencil, depth_source_info { nullptr, desc.Width, desc.Height }); - } - - if (_depthstencil_replacement != nullptr && depthstencil == _depthstencil) - depthstencil = _depthstencil_replacement.get(); // Replace application depth stencil surface with our custom one -} -void reshade::d3d9::runtime_d3d9::on_get_depthstencil_surface(IDirect3DSurface9 *&depthstencil) -{ - if (_depthstencil_replacement != nullptr && depthstencil == _depthstencil_replacement) - depthstencil->Release(), depthstencil = _depthstencil.get(), depthstencil->AddRef(); // Return original application depth stencil surface -} -void reshade::d3d9::runtime_d3d9::on_clear_depthstencil_surface(IDirect3DSurface9 *depthstencil) -{ - if (!_preserve_depth_buffer || depthstencil != _depthstencil_replacement) - return; - - const unsigned int min_db_drawcalls = 4; - const unsigned int min_db_vertices = 20; - - D3DSURFACE_DESC desc; - depthstencil->GetDesc(&desc); - if (!check_depthstencil_size(desc)) // Ignore unlikely candidates - return; - - // Check if any draw calls have been registered since the last clear operation - if (_current_db_drawcalls > min_db_drawcalls && _current_db_vertices > min_db_vertices) - { - _depth_buffer_table.push_back({ - _depthstencil_replacement, - desc.Width, - desc.Height, - _current_db_drawcalls, - _current_db_vertices }); - } - - _current_db_vertices = 0; - _current_db_drawcalls = 0; - - if (_depth_buffer_table.empty() || _depth_buffer_table.size() <= _adjusted_preserve_starting_index) - return; - - // If the current depth buffer replacement texture has to be preserved, replace the set surface with the original one, so that the replacement texture will not be cleared - _device->SetDepthStencilSurface(_depthstencil.get()); -} - -void reshade::d3d9::runtime_d3d9::capture_screenshot(uint8_t *buffer) const -{ - if (_backbuffer_format != D3DFMT_X8R8G8B8 && - _backbuffer_format != D3DFMT_X8B8G8R8 && - _backbuffer_format != D3DFMT_A8R8G8B8 && - _backbuffer_format != D3DFMT_A8B8G8R8) - { - LOG(WARN) << "Screenshots are not supported for back buffer format " << _backbuffer_format << '.'; - return; - } - - // Create a surface in system memory, copy back buffer data into it and lock it for reading - com_ptr intermediate; - if (FAILED(_device->CreateOffscreenPlainSurface(_width, _height, _backbuffer_format, D3DPOOL_SYSTEMMEM, &intermediate, nullptr))) - { - LOG(ERROR) << "Failed to create system memory texture for screenshot capture!"; - return; - } - - _device->GetRenderTargetData(_backbuffer_resolved.get(), intermediate.get()); - - D3DLOCKED_RECT mapped; - if (FAILED(intermediate->LockRect(&mapped, nullptr, D3DLOCK_READONLY))) - return; - auto mapped_data = static_cast(mapped.pBits); - - for (uint32_t y = 0, pitch = _width * 4; y < _height; y++, buffer += pitch, mapped_data += mapped.Pitch) - { - std::memcpy(buffer, mapped_data, pitch); - - for (uint32_t x = 0; x < pitch; x += 4) - { - buffer[x + 3] = 0xFF; // Clear alpha channel - if (_backbuffer_format == D3DFMT_A8R8G8B8 || _backbuffer_format == D3DFMT_X8R8G8B8) - std::swap(buffer[x + 0], buffer[x + 2]); // Format is BGRA, but output should be RGBA, so flip channels - } - } - - intermediate->UnlockRect(); -} - -bool reshade::d3d9::runtime_d3d9::init_texture(texture &texture) -{ - texture.impl = std::make_unique(); - - const auto texture_data = texture.impl->as(); - - if (texture.impl_reference != texture_reference::none) - return update_texture_reference(texture); - - UINT levels = texture.levels; - DWORD usage = 0; - D3DFORMAT format = D3DFMT_UNKNOWN; - D3DDEVICE_CREATION_PARAMETERS cp; - _device->GetCreationParameters(&cp); - - switch (texture.format) - { - case reshadefx::texture_format::r8: - format = D3DFMT_A8R8G8B8; - break; - case reshadefx::texture_format::r16f: - format = D3DFMT_R16F; - break; - case reshadefx::texture_format::r32f: - format = D3DFMT_R32F; - break; - case reshadefx::texture_format::rg8: - format = D3DFMT_A8R8G8B8; - break; - case reshadefx::texture_format::rg16: - format = D3DFMT_G16R16; - break; - case reshadefx::texture_format::rg16f: - format = D3DFMT_G16R16F; - break; - case reshadefx::texture_format::rg32f: - format = D3DFMT_G32R32F; - break; - case reshadefx::texture_format::rgba8: - format = D3DFMT_A8R8G8B8; - break; - case reshadefx::texture_format::rgba16: - format = D3DFMT_A16B16G16R16; - break; - case reshadefx::texture_format::rgba16f: - format = D3DFMT_A16B16G16R16F; - break; - case reshadefx::texture_format::rgba32f: - format = D3DFMT_A32B32G32R32F; - break; - case reshadefx::texture_format::rgb10a2: - format = D3DFMT_A2B10G10R10; - break; - } - - if (levels > 1) - { - // Enable auto-generated mipmaps if the format supports it - if (_d3d->CheckDeviceFormat(cp.AdapterOrdinal, cp.DeviceType, D3DFMT_X8R8G8B8, D3DUSAGE_AUTOGENMIPMAP, D3DRTYPE_TEXTURE, format) == D3D_OK) - { - usage |= D3DUSAGE_AUTOGENMIPMAP; - levels = 0; - } - else - { - LOG(WARN) << "Auto-generated mipmap levels are not supported for the format of texture '" << texture.unique_name << "'."; - } - } - - // Make texture a render target if format allows it - HRESULT hr = _d3d->CheckDeviceFormat(cp.AdapterOrdinal, cp.DeviceType, D3DFMT_X8R8G8B8, D3DUSAGE_RENDERTARGET, D3DRTYPE_TEXTURE, format); - if (SUCCEEDED(hr)) - usage |= D3DUSAGE_RENDERTARGET; - - hr = _device->CreateTexture(texture.width, texture.height, levels, usage, format, D3DPOOL_DEFAULT, &texture_data->texture, nullptr); - if (FAILED(hr)) - { - LOG(ERROR) << "Failed to create texture '" << texture.unique_name << "' (" - "Width = " << texture.width << ", " - "Height = " << texture.height << ", " - "Levels = " << levels << ", " - "Usage = " << usage << ", " - "Format = " << format << ")! " - "HRESULT is '" << std::hex << hr << std::dec << "'."; - return false; - } - - hr = texture_data->texture->GetSurfaceLevel(0, &texture_data->surface); - assert(SUCCEEDED(hr)); - - return true; -} -void reshade::d3d9::runtime_d3d9::upload_texture(texture &texture, const uint8_t *pixels) -{ - assert(texture.impl_reference == texture_reference::none && pixels != nullptr); - - const auto texture_impl = texture.impl->as(); - assert(texture_impl != nullptr); - - D3DSURFACE_DESC desc; texture_impl->texture->GetLevelDesc(0, &desc); // Get D3D texture format - com_ptr intermediate; - if (FAILED(_device->CreateTexture(texture.width, texture.height, 1, 0, desc.Format, D3DPOOL_SYSTEMMEM, &intermediate, nullptr))) - { - LOG(ERROR) << "Failed to create system memory texture for texture updating!"; - return; - } - - D3DLOCKED_RECT mapped; - if (FAILED(intermediate->LockRect(0, &mapped, nullptr, 0))) - return; - auto mapped_data = static_cast(mapped.pBits); - - switch (texture.format) - { - case reshadefx::texture_format::r8: // These are actually D3DFMT_A8R8G8B8, see 'init_texture' - for (uint32_t y = 0, pitch = texture.width * 4; y < texture.height; ++y, mapped_data += mapped.Pitch, pixels += pitch) - for (uint32_t x = 0; x < pitch; x += 4) - mapped_data[x + 0] = 0, // Set green and blue channel to zero - mapped_data[x + 1] = 0, - mapped_data[x + 2] = pixels[x + 0], - mapped_data[x + 3] = 0xFF; - break; - case reshadefx::texture_format::rg8: - for (uint32_t y = 0, pitch = texture.width * 4; y < texture.height; ++y, mapped_data += mapped.Pitch, pixels += pitch) - for (uint32_t x = 0; x < pitch; x += 4) - mapped_data[x + 0] = 0, // Set blue channel to zero - mapped_data[x + 1] = pixels[x + 1], - mapped_data[x + 2] = pixels[x + 0], - mapped_data[x + 3] = 0xFF; - break; - case reshadefx::texture_format::rgba8: - for (uint32_t y = 0, pitch = texture.width * 4; y < texture.height; ++y, mapped_data += mapped.Pitch, pixels += pitch) - for (uint32_t x = 0; x < pitch; x += 4) - mapped_data[x + 0] = pixels[x + 2], // Flip RGBA input to BGRA - mapped_data[x + 1] = pixels[x + 1], - mapped_data[x + 2] = pixels[x + 0], - mapped_data[x + 3] = pixels[x + 3]; - break; - default: - LOG(ERROR) << "Texture upload is not supported for format " << static_cast(texture.format) << '!'; - break; - } - - intermediate->UnlockRect(0); - - if (HRESULT hr = _device->UpdateTexture(intermediate.get(), texture_impl->texture.get()); FAILED(hr)) - { - LOG(ERROR) << "Failed to update texture from system memory texture! HRESULT is '" << std::hex << hr << std::dec << "'."; - return; - } -} -bool reshade::d3d9::runtime_d3d9::update_texture_reference(texture &texture) -{ - com_ptr new_reference; - - switch (texture.impl_reference) - { - case texture_reference::back_buffer: - new_reference = _backbuffer_texture; - break; - case texture_reference::depth_buffer: - new_reference = _depthstencil_texture; - break; - default: - return false; - } - - const auto texture_impl = texture.impl->as(); - assert(texture_impl != nullptr); - - if (new_reference == texture_impl->texture) - return true; - - // Update references in technique list - for (const auto &technique : _techniques) - for (const auto &pass : technique.passes_data) - for (auto &tex : pass->as()->sampler_textures) - if (tex == texture_impl->texture) tex = new_reference.get(); - - texture_impl->surface.reset(); - - if (new_reference == nullptr) - { - texture_impl->texture.reset(); - - texture.width = texture.height = texture.levels = 0; - texture.format = reshadefx::texture_format::unknown; - } - else - { - texture_impl->texture = new_reference; - new_reference->GetSurfaceLevel(0, &texture_impl->surface); - - D3DSURFACE_DESC desc; - texture_impl->surface->GetDesc(&desc); - - texture.width = desc.Width; - texture.height = desc.Height; - texture.format = reshadefx::texture_format::unknown; - texture.levels = static_cast(new_reference->GetLevelCount()); - } - - return true; -} -void reshade::d3d9::runtime_d3d9::update_texture_references(texture_reference type) -{ - for (auto &tex : _textures) - if (tex.impl != nullptr && tex.impl_reference == type) - update_texture_reference(tex); -} - -bool reshade::d3d9::runtime_d3d9::compile_effect(effect_data &effect) -{ - if (_d3d_compiler == nullptr) - _d3d_compiler = LoadLibraryW(L"d3dcompiler_47.dll"); - if (_d3d_compiler == nullptr) - _d3d_compiler = LoadLibraryW(L"d3dcompiler_43.dll"); - - if (_d3d_compiler == nullptr) - { - LOG(ERROR) << "Unable to load HLSL compiler (\"d3dcompiler_47.dll\"). Make sure you have the DirectX end-user runtime (June 2010) installed or a newer version of the library in the application directory."; - return false; - } - - const auto D3DCompile = reinterpret_cast(GetProcAddress(_d3d_compiler, "D3DCompile")); - - // Add specialization constant defines to source code - effect.preamble += "#define COLOR_PIXEL_SIZE " + std::to_string(1.0f / _width) + ", " + std::to_string(1.0f / _height) + "\n" - "#define DEPTH_PIXEL_SIZE COLOR_PIXEL_SIZE\n" - "#define SV_TARGET_PIXEL_SIZE COLOR_PIXEL_SIZE\n" - "#define SV_DEPTH_PIXEL_SIZE COLOR_PIXEL_SIZE\n"; - - const std::string hlsl_vs = effect.preamble + effect.module.hlsl; - const std::string hlsl_ps = effect.preamble + "#define POSITION VPOS\n" + effect.module.hlsl; - - std::unordered_map> entry_points; - - // Compile the generated HLSL source code to DX byte code - for (const auto &entry_point : effect.module.entry_points) - { - const std::string &hlsl = entry_point.second ? hlsl_ps : hlsl_vs; - - com_ptr compiled, d3d_errors; - - HRESULT hr = D3DCompile( - hlsl.c_str(), hlsl.size(), - nullptr, nullptr, nullptr, - entry_point.first.c_str(), - entry_point.second ? "ps_3_0" : "vs_3_0", - D3DCOMPILE_OPTIMIZATION_LEVEL3, 0, - &compiled, &d3d_errors); - - if (d3d_errors != nullptr) // Append warnings to the output error string as well - effect.errors.append(static_cast(d3d_errors->GetBufferPointer()), d3d_errors->GetBufferSize() - 1); // Subtracting one to not append the null-terminator as well - - // No need to setup resources if any of the shaders failed to compile - if (FAILED(hr)) - return false; - - // Create runtime shader objects from the compiled DX byte code - if (entry_point.second) - hr = _device->CreatePixelShader(static_cast(compiled->GetBufferPointer()), reinterpret_cast(&entry_points[entry_point.first])); - else - hr = _device->CreateVertexShader(static_cast(compiled->GetBufferPointer()), reinterpret_cast(&entry_points[entry_point.first])); - - if (FAILED(hr)) - { - LOG(ERROR) << "Failed to create shader for entry point '" << entry_point.first << "'. " - "HRESULT is '" << std::hex << hr << std::dec << "'."; - return false; - } - } - - bool success = true; - - d3d9_technique_data technique_init; - technique_init.constant_register_count = static_cast((effect.storage_size + 15) / 16); - technique_init.uniform_storage_offset = effect.storage_offset; - - for (const reshadefx::sampler_info &info : effect.module.samplers) - success &= add_sampler(info, technique_init); - - for (technique &technique : _techniques) - if (technique.impl == nullptr && technique.effect_index == effect.index) - success &= init_technique(technique, technique_init, entry_points); - - return success; -} - -bool reshade::d3d9::runtime_d3d9::add_sampler(const reshadefx::sampler_info &info, d3d9_technique_data &technique_init) -{ - const auto existing_texture = std::find_if(_textures.begin(), _textures.end(), - [&texture_name = info.texture_name](const auto &item) { - return item.unique_name == texture_name && item.impl != nullptr; - }); - - if (existing_texture == _textures.end() || info.binding > ARRAYSIZE(technique_init.sampler_states)) - return false; - - // Since textures with auto-generated mipmap levels do not have a mipmap maximum, limit the bias here so this is not as obvious - assert(existing_texture->levels > 0); - const float lod_bias = std::min(existing_texture->levels - 1.0f, info.lod_bias); - - technique_init.num_samplers = std::max(technique_init.num_samplers, DWORD(info.binding + 1)); - - technique_init.sampler_states[info.binding][D3DSAMP_ADDRESSU] = static_cast(info.address_u); - technique_init.sampler_states[info.binding][D3DSAMP_ADDRESSV] = static_cast(info.address_v); - technique_init.sampler_states[info.binding][D3DSAMP_ADDRESSW] = static_cast(info.address_w); - technique_init.sampler_states[info.binding][D3DSAMP_BORDERCOLOR] = 0; - technique_init.sampler_states[info.binding][D3DSAMP_MAGFILTER] = 1 + ((static_cast(info.filter) & 0x0C) >> 2); - technique_init.sampler_states[info.binding][D3DSAMP_MINFILTER] = 1 + ((static_cast(info.filter) & 0x30) >> 4); - technique_init.sampler_states[info.binding][D3DSAMP_MIPFILTER] = 1 + ((static_cast(info.filter) & 0x03)); - technique_init.sampler_states[info.binding][D3DSAMP_MIPMAPLODBIAS] = *reinterpret_cast(&lod_bias); - technique_init.sampler_states[info.binding][D3DSAMP_MAXMIPLEVEL] = static_cast(std::max(0.0f, info.min_lod)); - technique_init.sampler_states[info.binding][D3DSAMP_MAXANISOTROPY] = 1; - technique_init.sampler_states[info.binding][D3DSAMP_SRGBTEXTURE] = info.srgb; - - technique_init.sampler_textures[info.binding] = existing_texture->impl->as()->texture.get(); - - return true; -} -bool reshade::d3d9::runtime_d3d9::init_technique(technique &technique, const d3d9_technique_data &impl_init, const std::unordered_map> &entry_points) -{ - // Copy construct new technique implementation instead of move because effect may contain multiple techniques - technique.impl = std::make_unique(impl_init); - - const auto technique_data = technique.impl->as(); - - for (size_t pass_index = 0; pass_index < technique.passes.size(); ++pass_index) - { - technique.passes_data.push_back(std::make_unique()); - - auto &pass = *technique.passes_data.back()->as(); - const auto &pass_info = technique.passes[pass_index]; - - entry_points.at(pass_info.ps_entry_point)->QueryInterface(&pass.pixel_shader); - entry_points.at(pass_info.vs_entry_point)->QueryInterface(&pass.vertex_shader); - - pass.render_targets[0] = _backbuffer_resolved.get(); - pass.clear_render_targets = pass_info.clear_render_targets; - - for (size_t k = 0; k < ARRAYSIZE(pass.sampler_textures); ++k) - pass.sampler_textures[k] = technique_data->sampler_textures[k]; - - HRESULT hr = _device->BeginStateBlock(); - - if (SUCCEEDED(hr)) - { - _device->SetVertexShader(pass.vertex_shader.get()); - _device->SetPixelShader(pass.pixel_shader.get()); - - const auto literal_to_blend_func = [](unsigned int value) { - switch (value) - { - case 0: - return D3DBLEND_ZERO; - default: - case 1: - return D3DBLEND_ONE; - case 2: - return D3DBLEND_SRCCOLOR; - case 4: - return D3DBLEND_INVSRCCOLOR; - case 3: - return D3DBLEND_SRCALPHA; - case 5: - return D3DBLEND_INVSRCALPHA; - case 6: - return D3DBLEND_DESTALPHA; - case 7: - return D3DBLEND_INVDESTALPHA; - case 8: - return D3DBLEND_DESTCOLOR; - case 9: - return D3DBLEND_INVDESTCOLOR; - } - }; - const auto literal_to_stencil_op = [](unsigned int value) { - switch (value) - { - default: - case 1: - return D3DSTENCILOP_KEEP; - case 0: - return D3DSTENCILOP_ZERO; - case 3: - return D3DSTENCILOP_REPLACE; - case 4: - return D3DSTENCILOP_INCRSAT; - case 5: - return D3DSTENCILOP_DECRSAT; - case 6: - return D3DSTENCILOP_INVERT; - case 7: - return D3DSTENCILOP_INCR; - case 8: - return D3DSTENCILOP_DECR; - } - }; - - _device->SetRenderState(D3DRS_ZENABLE, false); - _device->SetRenderState(D3DRS_SPECULARENABLE, false); - _device->SetRenderState(D3DRS_FILLMODE, D3DFILL_SOLID); - _device->SetRenderState(D3DRS_SHADEMODE, D3DSHADE_GOURAUD); - _device->SetRenderState(D3DRS_ZWRITEENABLE, true); - _device->SetRenderState(D3DRS_ALPHATESTENABLE, false); - _device->SetRenderState(D3DRS_LASTPIXEL, true); - _device->SetRenderState(D3DRS_SRCBLEND, literal_to_blend_func(pass_info.src_blend)); - _device->SetRenderState(D3DRS_DESTBLEND, literal_to_blend_func(pass_info.dest_blend)); - _device->SetRenderState(D3DRS_ALPHAREF, 0); - _device->SetRenderState(D3DRS_ALPHAFUNC, D3DCMP_ALWAYS); - _device->SetRenderState(D3DRS_DITHERENABLE, false); - _device->SetRenderState(D3DRS_FOGSTART, 0); - _device->SetRenderState(D3DRS_FOGEND, 1); - _device->SetRenderState(D3DRS_FOGDENSITY, 1); - _device->SetRenderState(D3DRS_ALPHABLENDENABLE, pass_info.blend_enable); - _device->SetRenderState(D3DRS_DEPTHBIAS, 0); - _device->SetRenderState(D3DRS_STENCILENABLE, pass_info.stencil_enable); - _device->SetRenderState(D3DRS_STENCILPASS, literal_to_stencil_op(pass_info.stencil_op_pass)); - _device->SetRenderState(D3DRS_STENCILFAIL, literal_to_stencil_op(pass_info.stencil_op_fail)); - _device->SetRenderState(D3DRS_STENCILZFAIL, literal_to_stencil_op(pass_info.stencil_op_depth_fail)); - _device->SetRenderState(D3DRS_STENCILFUNC, static_cast(pass_info.stencil_comparison_func)); - _device->SetRenderState(D3DRS_STENCILREF, pass_info.stencil_reference_value); - _device->SetRenderState(D3DRS_STENCILMASK, pass_info.stencil_read_mask); - _device->SetRenderState(D3DRS_STENCILWRITEMASK, pass_info.stencil_write_mask); - _device->SetRenderState(D3DRS_TEXTUREFACTOR, 0xFFFFFFFF); - _device->SetRenderState(D3DRS_LOCALVIEWER, true); - _device->SetRenderState(D3DRS_EMISSIVEMATERIALSOURCE, D3DMCS_MATERIAL); - _device->SetRenderState(D3DRS_AMBIENTMATERIALSOURCE, D3DMCS_MATERIAL); - _device->SetRenderState(D3DRS_DIFFUSEMATERIALSOURCE, D3DMCS_COLOR1); - _device->SetRenderState(D3DRS_SPECULARMATERIALSOURCE, D3DMCS_COLOR2); - _device->SetRenderState(D3DRS_COLORWRITEENABLE, pass_info.color_write_mask); - _device->SetRenderState(D3DRS_BLENDOP, static_cast(pass_info.blend_op)); - _device->SetRenderState(D3DRS_SCISSORTESTENABLE, false); - _device->SetRenderState(D3DRS_SLOPESCALEDEPTHBIAS, 0); - _device->SetRenderState(D3DRS_ANTIALIASEDLINEENABLE, false); - _device->SetRenderState(D3DRS_TWOSIDEDSTENCILMODE, false); - _device->SetRenderState(D3DRS_CCW_STENCILFAIL, D3DSTENCILOP_KEEP); - _device->SetRenderState(D3DRS_CCW_STENCILZFAIL, D3DSTENCILOP_KEEP); - _device->SetRenderState(D3DRS_CCW_STENCILPASS, D3DSTENCILOP_KEEP); - _device->SetRenderState(D3DRS_CCW_STENCILFUNC, D3DCMP_ALWAYS); - _device->SetRenderState(D3DRS_COLORWRITEENABLE1, 0x0000000F); - _device->SetRenderState(D3DRS_COLORWRITEENABLE2, 0x0000000F); - _device->SetRenderState(D3DRS_COLORWRITEENABLE3, 0x0000000F); - _device->SetRenderState(D3DRS_BLENDFACTOR, 0xFFFFFFFF); - _device->SetRenderState(D3DRS_SRGBWRITEENABLE, pass_info.srgb_write_enable); - _device->SetRenderState(D3DRS_SEPARATEALPHABLENDENABLE, false); - _device->SetRenderState(D3DRS_SRCBLENDALPHA, literal_to_blend_func(pass_info.src_blend_alpha)); - _device->SetRenderState(D3DRS_DESTBLENDALPHA, literal_to_blend_func(pass_info.dest_blend_alpha)); - _device->SetRenderState(D3DRS_BLENDOPALPHA, static_cast(pass_info.blend_op_alpha)); - _device->SetRenderState(D3DRS_FOGENABLE, false); - _device->SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE); - _device->SetRenderState(D3DRS_LIGHTING, false); - - hr = _device->EndStateBlock(&pass.stateblock); - } - - if (FAILED(hr)) - { - LOG(ERROR) << "Failed to create state block for pass " << pass_index << " in technique '" << technique.name << "'. " - "HRESULT is '" << std::hex << hr << std::dec << "'."; - return false; - } - - for (unsigned int k = 0; k < 8; ++k) - { - if (pass_info.render_target_names[k].empty()) - continue; // Skip unbound render targets - - if (k > _num_simultaneous_rendertargets) - { - LOG(WARN) << "Device only supports " << _num_simultaneous_rendertargets << " simultaneous render targets, but pass " << pass_index << " in technique '" << technique.name << "' uses more, which are ignored"; - break; - } - - const auto render_target_texture = std::find_if(_textures.begin(), _textures.end(), - [&render_target = pass_info.render_target_names[k]](const auto &item) { - return item.unique_name == render_target; - }); - - if (render_target_texture == _textures.end()) - return assert(false), false; - - const auto texture_impl = render_target_texture->impl->as(); - - assert(texture_impl != nullptr); - - // Unset textures that are used as render target - for (DWORD s = 0; s < technique_data->num_samplers; ++s) - if (pass.sampler_textures[s] == texture_impl->texture) - pass.sampler_textures[s] = nullptr; - - pass.render_targets[k] = texture_impl->surface.get(); - } - } - - return true; -} - -void reshade::d3d9::runtime_d3d9::render_technique(technique &technique) -{ - auto &technique_data = *technique.impl->as(); - - bool is_default_depthstencil_cleared = false; - - // Setup vertex input - _device->SetStreamSource(0, _effect_triangle_buffer.get(), 0, sizeof(float)); - _device->SetVertexDeclaration(_effect_triangle_layout.get()); - - // Setup shader constants - if (technique_data.constant_register_count > 0) - { - const auto uniform_storage_data = reinterpret_cast(_uniform_data_storage.data() + technique_data.uniform_storage_offset); - _device->SetPixelShaderConstantF(0, uniform_storage_data, technique_data.constant_register_count); - _device->SetVertexShaderConstantF(0, uniform_storage_data, technique_data.constant_register_count); - } - - for (const auto &pass_object : technique.passes_data) - { - const d3d9_pass_data &pass = *pass_object->as(); - - // Setup states - pass.stateblock->Apply(); - - // Save back buffer of previous pass - _device->StretchRect(_backbuffer_resolved.get(), nullptr, _backbuffer_texture_surface.get(), nullptr, D3DTEXF_NONE); - - // Setup shader resources - for (DWORD s = 0; s < technique_data.num_samplers; s++) - { - _device->SetTexture(s, pass.sampler_textures[s]); - - for (DWORD state = D3DSAMP_ADDRESSU; state <= D3DSAMP_SRGBTEXTURE; state++) - _device->SetSamplerState(s, static_cast(state), technique_data.sampler_states[s][state]); - } - - // Setup render targets - for (DWORD target = 0; target < _num_simultaneous_rendertargets; target++) - _device->SetRenderTarget(target, pass.render_targets[target]); - - D3DVIEWPORT9 viewport; - _device->GetViewport(&viewport); - - const float texelsize[4] = { -1.0f / viewport.Width, 1.0f / viewport.Height }; - _device->SetVertexShaderConstantF(255, texelsize, 1); - - const bool is_viewport_sized = viewport.Width == _width && viewport.Height == _height; - - _device->SetDepthStencilSurface(is_viewport_sized ? _default_depthstencil.get() : nullptr); - - if (is_viewport_sized && !is_default_depthstencil_cleared) - { - is_default_depthstencil_cleared = true; - - _device->Clear(0, nullptr, (pass.clear_render_targets ? D3DCLEAR_TARGET : 0) | D3DCLEAR_ZBUFFER | D3DCLEAR_STENCIL, 0, 1.0f, 0); - } - else if (pass.clear_render_targets) - { - _device->Clear(0, nullptr, D3DCLEAR_TARGET, 0, 0.0f, 0); - } - - // Draw triangle - _device->DrawPrimitive(D3DPT_TRIANGLELIST, 0, 1); - - _vertices += 3; - _drawcalls += 1; - - // Update shader resources - for (const auto target : pass.render_targets) - { - if (target == nullptr || target == _backbuffer_resolved) - continue; - - com_ptr texture; - - if (SUCCEEDED(target->GetContainer(IID_PPV_ARGS(&texture))) && texture->GetLevelCount() > 1) - { - texture->SetAutoGenFilterType(D3DTEXF_LINEAR); - texture->GenerateMipSubLevels(); - } - } - } -} - -#if RESHADE_GUI -bool reshade::d3d9::runtime_d3d9::init_imgui_resources() -{ - HRESULT hr = _device->BeginStateBlock(); - - if (SUCCEEDED(hr)) - { - _device->SetFVF(D3DFVF_XYZ | D3DFVF_DIFFUSE | D3DFVF_TEX1); - _device->SetPixelShader(nullptr); - _device->SetVertexShader(nullptr); - _device->SetRenderState(D3DRS_ZENABLE, false); - _device->SetRenderState(D3DRS_FILLMODE, D3DFILL_SOLID); - _device->SetRenderState(D3DRS_ALPHATESTENABLE, false); - _device->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA); - _device->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA); - _device->SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE); - _device->SetRenderState(D3DRS_ALPHABLENDENABLE, true); - _device->SetRenderState(D3DRS_FOGENABLE, false); - _device->SetRenderState(D3DRS_STENCILENABLE, false); - _device->SetRenderState(D3DRS_CLIPPING, false); - _device->SetRenderState(D3DRS_LIGHTING, false); - _device->SetRenderState(D3DRS_COLORWRITEENABLE, D3DCOLORWRITEENABLE_RED | D3DCOLORWRITEENABLE_GREEN | D3DCOLORWRITEENABLE_BLUE | D3DCOLORWRITEENABLE_ALPHA); - _device->SetRenderState(D3DRS_SCISSORTESTENABLE, true); - _device->SetRenderState(D3DRS_BLENDOP, D3DBLENDOP_ADD); - _device->SetRenderState(D3DRS_SRGBWRITEENABLE, false); - _device->SetRenderState(D3DRS_BLENDOPALPHA, D3DBLENDOP_ADD); - _device->SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_MODULATE); - _device->SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_TEXTURE); - _device->SetTextureStageState(0, D3DTSS_COLORARG2, D3DTA_DIFFUSE); - _device->SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_MODULATE); - _device->SetTextureStageState(0, D3DTSS_ALPHAARG1, D3DTA_TEXTURE); - _device->SetTextureStageState(0, D3DTSS_ALPHAARG2, D3DTA_DIFFUSE); - _device->SetTextureStageState(0, D3DTSS_TEXCOORDINDEX, 0); - _device->SetTextureStageState(0, D3DTSS_TEXTURETRANSFORMFLAGS, D3DTTFF_DISABLE); - _device->SetSamplerState(0, D3DSAMP_ADDRESSU, D3DTADDRESS_WRAP); - _device->SetSamplerState(0, D3DSAMP_ADDRESSV, D3DTADDRESS_WRAP); - _device->SetSamplerState(0, D3DSAMP_ADDRESSW, D3DTADDRESS_WRAP); - _device->SetSamplerState(0, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR); - _device->SetSamplerState(0, D3DSAMP_MINFILTER, D3DTEXF_LINEAR); - _device->SetSamplerState(0, D3DSAMP_MIPFILTER, D3DTEXF_NONE); - _device->SetSamplerState(0, D3DSAMP_MIPMAPLODBIAS, 0); - _device->SetSamplerState(0, D3DSAMP_MAXMIPLEVEL, 0); - _device->SetSamplerState(0, D3DSAMP_SRGBTEXTURE, 0); - - hr = _device->EndStateBlock(&_imgui_state); - } - - if (FAILED(hr)) - { - LOG(ERROR) << "Failed to create state block! HRESULT is '" << std::hex << hr << std::dec << "'."; - return false; - } - - return true; -} - -void reshade::d3d9::runtime_d3d9::render_imgui_draw_data(ImDrawData *draw_data) -{ - // Fixed-function vertex layout - struct ImDrawVert9 - { - float x, y, z; - D3DCOLOR col; - float u, v; - }; - - // Create and grow vertex/index buffers if needed - if (_imgui_index_buffer_size < draw_data->TotalIdxCount) - { - _imgui_index_buffer.reset(); - _imgui_index_buffer_size = draw_data->TotalIdxCount + 10000; - - if (FAILED(_device->CreateIndexBuffer(_imgui_index_buffer_size * sizeof(ImDrawIdx), D3DUSAGE_DYNAMIC | D3DUSAGE_WRITEONLY, sizeof(ImDrawIdx) == 2 ? D3DFMT_INDEX16 : D3DFMT_INDEX32, D3DPOOL_DEFAULT, &_imgui_index_buffer, nullptr))) - return; - } - if (_imgui_vertex_buffer_size < draw_data->TotalVtxCount) - { - _imgui_vertex_buffer.reset(); - _imgui_vertex_buffer_size = draw_data->TotalVtxCount + 5000; - - if (FAILED(_device->CreateVertexBuffer(_imgui_vertex_buffer_size * sizeof(ImDrawVert9), D3DUSAGE_DYNAMIC | D3DUSAGE_WRITEONLY, D3DFVF_XYZ | D3DFVF_DIFFUSE | D3DFVF_TEX1, D3DPOOL_DEFAULT, &_imgui_vertex_buffer, nullptr))) - return; - } - - ImDrawVert9 *vtx_dst; ImDrawIdx *idx_dst; - if (FAILED(_imgui_index_buffer->Lock(0, draw_data->TotalIdxCount * sizeof(ImDrawIdx), reinterpret_cast(&idx_dst), D3DLOCK_DISCARD)) || - FAILED(_imgui_vertex_buffer->Lock(0, draw_data->TotalVtxCount * sizeof(ImDrawVert9), reinterpret_cast(&vtx_dst), D3DLOCK_DISCARD))) - return; - - for (int n = 0; n < draw_data->CmdListsCount; n++) - { - const ImDrawList *const draw_list = draw_data->CmdLists[n]; - - for (const ImDrawVert &vtx : draw_list->VtxBuffer) - { - vtx_dst->x = vtx.pos.x; - vtx_dst->y = vtx.pos.y; - vtx_dst->z = 0.0f; - - // RGBA --> ARGB for Direct3D 9 - vtx_dst->col = (vtx.col & 0xFF00FF00) | ((vtx.col & 0xFF0000) >> 16) | ((vtx.col & 0xFF) << 16); - - vtx_dst->u = vtx.uv.x; - vtx_dst->v = vtx.uv.y; - - ++vtx_dst; // Next vertex - } - - memcpy(idx_dst, draw_list->IdxBuffer.Data, draw_list->IdxBuffer.size() * sizeof(ImDrawIdx)); - idx_dst += draw_list->IdxBuffer.size(); - } - - _imgui_index_buffer->Unlock(); - _imgui_vertex_buffer->Unlock(); - - // Setup render state: fixed-pipeline, alpha-blending, no face culling, no depth testing - _imgui_state->Apply(); - _device->SetIndices(_imgui_index_buffer.get()); - _device->SetStreamSource(0, _imgui_vertex_buffer.get(), 0, sizeof(ImDrawVert9)); - for (unsigned int i = 0; i < _num_samplers; i++) - _device->SetTexture(i, nullptr); - _device->SetRenderTarget(0, _backbuffer_resolved.get()); - for (unsigned int i = 1; i < _num_simultaneous_rendertargets; i++) - _device->SetRenderTarget(i, nullptr); - _device->SetDepthStencilSurface(nullptr); - - const D3DMATRIX identity_mat = { - 1.0f, 0.0f, 0.0f, 0.0f, - 0.0f, 1.0f, 0.0f, 0.0f, - 0.0f, 0.0f, 1.0f, 0.0f, - 0.0f, 0.0f, 0.0f, 1.0f - }; - const D3DMATRIX ortho_projection = { - 2.0f / draw_data->DisplaySize.x, 0.0f, 0.0f, 0.0f, - 0.0f, -2.0f / draw_data->DisplaySize.y, 0.0f, 0.0f, - 0.0f, 0.0f, 0.5f, 0.0f, - -(2 * draw_data->DisplayPos.x + draw_data->DisplaySize.x + 1.0f) / draw_data->DisplaySize.x, // Bake half-pixel offset into projection matrix - +(2 * draw_data->DisplayPos.y + draw_data->DisplaySize.y + 1.0f) / draw_data->DisplaySize.y, 0.5f, 1.0f - }; - - _device->SetTransform(D3DTS_VIEW, &identity_mat); - _device->SetTransform(D3DTS_WORLD, &identity_mat); - _device->SetTransform(D3DTS_PROJECTION, &ortho_projection); - - UINT vtx_offset = 0, idx_offset = 0; - for (int n = 0; n < draw_data->CmdListsCount; n++) - { - const ImDrawList *const draw_list = draw_data->CmdLists[n]; - - for (const ImDrawCmd &cmd : draw_list->CmdBuffer) - { - assert(cmd.TextureId != 0); - assert(cmd.UserCallback == nullptr); - - const RECT scissor_rect = { - static_cast(cmd.ClipRect.x - draw_data->DisplayPos.x), - static_cast(cmd.ClipRect.y - draw_data->DisplayPos.y), - static_cast(cmd.ClipRect.z - draw_data->DisplayPos.x), - static_cast(cmd.ClipRect.w - draw_data->DisplayPos.y) - }; - _device->SetScissorRect(&scissor_rect); - - _device->SetTexture(0, static_cast(cmd.TextureId)->texture.get()); - - _device->DrawIndexedPrimitive(D3DPT_TRIANGLELIST, vtx_offset, 0, draw_list->VtxBuffer.size(), idx_offset, cmd.ElemCount / 3); - - idx_offset += cmd.ElemCount; - } - - vtx_offset += draw_list->VtxBuffer.size(); - } -} - -void reshade::d3d9::runtime_d3d9::draw_debug_menu() -{ - ImGui::Text("MSAA is %s", _is_multisampling_enabled ? "active" : "inactive"); - ImGui::Spacing(); - - if (ImGui::CollapsingHeader("Buffer Detection", ImGuiTreeNodeFlags_DefaultOpen)) - { - if (ImGui::Checkbox("Preserve depth buffer from being cleared", &_preserve_depth_buffer)) - { - runtime::save_config(); - - // Force depth-stencil replacement recreation - _depthstencil = _default_depthstencil; - // Force depth source table recreation - _depth_buffer_table.clear(); - _depth_source_table.clear(); - _depthstencil_replacement.reset(); - _init_depthbuffer_detection = true; - - if (_preserve_depth_buffer) - _disable_intz = false; - } - - ImGui::Spacing(); - ImGui::Spacing(); - - if (!_preserve_depth_buffer) - { - if (ImGui::Checkbox("Disable replacement with INTZ format", &_disable_intz)) - { - runtime::save_config(); - - // Force depth-stencil replacement recreation - _depthstencil = _default_depthstencil; - // Force depth source table recreation - _depth_source_table.clear(); - } - } - - if (_preserve_depth_buffer) - { - ImGui::Spacing(); - - bool modified = false; - - if (ImGui::Checkbox("Auto preserve", &_auto_preserve)) - { - modified = true; - _preserve_starting_index = std::numeric_limits::max(); - _adjusted_preserve_starting_index = _depth_buffer_table.size() - 1; - } - - ImGui::Spacing(); - - for (size_t i = 0; i < _depth_buffer_table.size(); ++i) - { - char label[512] = ""; - sprintf_s(label, "%s%2zu", (i == _adjusted_preserve_starting_index ? "> " : " "), i); - - if (!_auto_preserve) - { - if (bool value = (_preserve_starting_index == i && !_auto_preserve); ImGui::Checkbox(label, &value)) - { - _adjusted_preserve_starting_index = _preserve_starting_index = value ? i : std::numeric_limits::max(); - if(_preserve_starting_index == std::numeric_limits::max()) - _adjusted_preserve_starting_index = _depth_buffer_table.size() - 1; - modified = true; - } - } - else - { - ImGui::Text("%s", label); - } - - ImGui::SameLine(); - ImGui::Text("0x%p | %ux%u : %u draw calls ==> %u vertices", - _depth_buffer_table[i].depthstencil.get(), - _depth_buffer_table[i].width, - _depth_buffer_table[i].height, - _depth_buffer_table[i].drawcall_count, - _depth_buffer_table[i].vertices_count); - ImGui::Spacing(); - } - - ImGui::Spacing(); - ImGui::Spacing(); - - // this feature can help resolving source_engine wrong depth buffer detection - if (ImGui::Checkbox("Fix for source engine games", &_source_engine_fix)) - { - modified = true; - } - ImGui::Text("Tip: in source engine games, the background can have more vertices than the main scene, so it leads to select the wrong depth buffer in auto preserve mode"); - - // this feature can help resolving user weapon or cockpit not appearing in the depth buffer - if (ImGui::Checkbox("Fix for user weapon or cockpit", &_brute_force_fix)) - { - modified = true; - } - ImGui::Text("Tip: in some games, can help display user weapon or cockpit correctly in the depth buffer"); - - ImGui::Spacing(); - ImGui::Spacing(); - - // this feature can help resolving weapons or cockpits not appearing in the depth buffer - if (ImGui::Checkbox("Focus on the best original depthstencil source", &_focus_on_best_original_depthstencil_source)) - { - modified = true; - } - ImGui::Text("Tip: in some games, can help remove undesired UI elements from the depth buffer"); - - if (modified) - { - runtime::save_config(); - - // Force depth-stencil replacement recreation - _depthstencil = _default_depthstencil; - // Force depth source table recreation - _depth_buffer_table.clear(); - _depth_source_table.clear(); - _depthstencil_replacement.reset(); - _init_depthbuffer_detection = true; - } - } - else - { - for (const auto &it : _depth_source_table) - { - ImGui::Text("%s0x%p | %u draw calls ==> %u vertices", (it.first == _depthstencil ? "> " : " "), it.first.get(), it.second.drawcall_count, it.second.vertices_count); - } - } - } -} -#endif - -void reshade::d3d9::runtime_d3d9::detect_depth_source() -{ - if (_preserve_depth_buffer) - { - // check if we draw calls have been registered since the last cleaning - if (_depthstencil_replacement != nullptr && _current_db_drawcalls > 0 && _current_db_vertices > 0) - { - D3DSURFACE_DESC desc; - _depthstencil_replacement->GetDesc(&desc); - - _depth_buffer_table.push_back({ _depthstencil_replacement, desc.Width, desc.Height, _current_db_drawcalls, _current_db_vertices }); - } - - unsigned int compared_vertices = 0; - _db_vertices = 0; - _db_drawcalls = 0; - _current_db_vertices = 0; - _current_db_drawcalls = 0; - - if (_auto_preserve) - { - // if auto preserve mode is enabled, try to detect the best depth buffer clearing instance from which the depth buffer texture could be preserved - _adjusted_preserve_starting_index = _preserve_starting_index = 0; - - for (size_t i = 0; i != _depth_buffer_table.size(); i++) - { - const auto &it = _depth_buffer_table[i]; - // fix for source engine games: add a weight in order not to select the first db instance if it is related to the backgroud scene - int mult = (_source_engine_fix && i > 0) ? 10 : 1; - if (mult*it.vertices_count >= compared_vertices) - { - _db_vertices = it.vertices_count; - _db_drawcalls = it.drawcall_count; - _adjusted_preserve_starting_index = _preserve_starting_index = i; - compared_vertices = mult * it.vertices_count; - } - } - } - else - { - _adjusted_preserve_starting_index = _preserve_starting_index; - if (_preserve_starting_index == std::numeric_limits::max()) - _adjusted_preserve_starting_index = _depth_buffer_table.size() - 1; - } - } - - if (!_init_depthbuffer_detection && (_framecount % 30 || _is_multisampling_enabled || _depth_source_table.empty())) - return; - - if (_has_high_network_activity) - { - // Force depth source table recreation - _depth_buffer_table.clear(); - _depth_source_table.clear(); - create_depthstencil_replacement(nullptr); - return; - } - - depth_source_info best_info = {}; - com_ptr best_match; - - for (auto it = _depth_source_table.begin(); it != _depth_source_table.end();) - { - auto &depthstencil_info = it->second; - const auto &depthstencil = it->first; - - // Remove unreferenced depth stencil surfaces from the list (application is no longer using it if we are the only ones who still hold a reference) - if (!_preserve_depth_buffer && depthstencil.ref_count() == 1) - { - it = _depth_source_table.erase(it); - continue; - } - else - { - ++it; - } - - if (depthstencil_info.drawcall_count == 0 && !_preserve_depth_buffer) - continue; - - if ((depthstencil_info.vertices_count * (1.2f - float(depthstencil_info.drawcall_count) / _drawcalls)) >= (best_info.vertices_count * (1.2f - float(best_info.drawcall_count) / _drawcalls))) - { - best_info = depthstencil_info; - best_match = depthstencil; - } - - // Reset statistics to zero for next frame - depthstencil_info.drawcall_count = depthstencil_info.vertices_count = 0; - } - - if (best_match != nullptr && _depthstencil != best_match) - create_depthstencil_replacement(best_match.get()); -} - -bool reshade::d3d9::runtime_d3d9::check_depthstencil_size(const D3DSURFACE_DESC &desc) -{ - if (desc.MultiSampleType != D3DMULTISAMPLE_NONE) - return false; // MSAA depth buffers are not supported since they would have to be moved into a plain surface before attaching to a shader slot - - if (_disable_depth_buffer_size_restriction) - { - // Allow depth buffers with greater dimensions than the viewport (e.g. in games like Vanquish) - return desc.Width >= floor(_width * 0.95) && desc.Height >= ceil(_height * 0.95); - } - else - { - return (desc.Width >= floor(_width * 0.95) && desc.Width <= ceil(_width * 1.05)) - && (desc.Height >= floor(_height * 0.95) && desc.Height <= ceil(_height * 1.05)); - } -} - -bool reshade::d3d9::runtime_d3d9::check_depthstencil_size(const D3DSURFACE_DESC &desc, const D3DSURFACE_DESC &compared_desc) -{ - if (desc.MultiSampleType != D3DMULTISAMPLE_NONE) - return false; // MSAA depth buffers are not supported since they would have to be moved into a plain surface before attaching to a shader slot - - if (_disable_depth_buffer_size_restriction) - { - // Allow depth buffers with greater dimensions than the viewport (e.g. in games like Vanquish) - return desc.Width >= floor(compared_desc.Width * 0.95) && desc.Height >= ceil(compared_desc.Height * 0.95); - } - else - { - return (desc.Width >= floor(compared_desc.Width * 0.95) && desc.Width <= ceil(compared_desc.Width * 1.05)) - && (desc.Height >= floor(compared_desc.Height * 0.95) && desc.Height <= ceil(compared_desc.Height * 1.05)); - } -} - -bool reshade::d3d9::runtime_d3d9::create_depthstencil_replacement(const com_ptr &depthstencil) -{ - _depthstencil.reset(); - _depthstencil_replacement.reset(); - _depthstencil_texture.reset(); - - if (depthstencil != nullptr) - { - D3DSURFACE_DESC desc; - depthstencil->GetDesc(&desc); - - _depthstencil = depthstencil; - - if (_preserve_depth_buffer || - (!_disable_intz && - desc.Format != D3DFMT_INTZ && - desc.Format != D3DFMT_DF16 && - desc.Format != D3DFMT_DF24)) - { - D3DDISPLAYMODE displaymode; - _swapchain->GetDisplayMode(&displaymode); - D3DDEVICE_CREATION_PARAMETERS creation_params; - _device->GetCreationParameters(&creation_params); - - desc.Format = D3DFMT_UNKNOWN; - const D3DFORMAT formats[] = { D3DFMT_INTZ, D3DFMT_DF24, D3DFMT_DF16 }; - - for (const auto format : formats) - { - if (SUCCEEDED(_d3d->CheckDeviceFormat(creation_params.AdapterOrdinal, creation_params.DeviceType, displaymode.Format, D3DUSAGE_DEPTHSTENCIL, D3DRTYPE_TEXTURE, format))) - { - desc.Format = format; - break; - } - } - - if (desc.Format == D3DFMT_UNKNOWN) - { - LOG(ERROR) << "Your graphics card is missing support for at least one of the 'INTZ', 'DF24' or 'DF16' texture formats. Cannot create depth replacement texture."; - return false; - } - - const unsigned int width = _disable_depth_buffer_size_restriction ? _width : desc.Width; - const unsigned int height = _disable_depth_buffer_size_restriction ? _height : desc.Height; - - if (HRESULT hr = _device->CreateTexture(width, height, 1, D3DUSAGE_DEPTHSTENCIL, desc.Format, D3DPOOL_DEFAULT, &_depthstencil_texture, nullptr); FAILED(hr)) - { - LOG(ERROR) << "Failed to create depth replacement texture! HRESULT is '" << std::hex << hr << std::dec << "'."; - return false; - } - - _depthstencil_texture->GetSurfaceLevel(0, &_depthstencil_replacement); - - // Update auto depth stencil - com_ptr current_depthstencil; - _device->GetDepthStencilSurface(¤t_depthstencil); - - if (!_preserve_depth_buffer && current_depthstencil != nullptr && current_depthstencil == _depthstencil) - _device->SetDepthStencilSurface(_depthstencil_replacement.get()); - } - else - { - _depthstencil_replacement = _depthstencil; - - if (HRESULT hr = _depthstencil_replacement->GetContainer(IID_PPV_ARGS(&_depthstencil_texture)); FAILED(hr)) - { - LOG(ERROR) << "Failed to retrieve texture from depth surface! HRESULT is '" << std::hex << hr << std::dec << "'."; - return false; - } - } - } - - update_texture_references(texture_reference::depth_buffer); - - return true; -} - -void reshade::d3d9::runtime_d3d9::weapon_or_cockpit_fix(const com_ptr depthstencil, D3DPRIMITIVETYPE PrimitiveType, UINT StartVertex, UINT PrimitiveCount) -{ - if (_brute_force_fix && - depthstencil != _depthstencil_replacement && - _is_good_viewport && - _is_best_original_depthstencil_source && - _depth_buffer_table.size() > _adjusted_preserve_starting_index) - { - D3DVIEWPORT9 mViewport; // Holds viewport data - _device->GetViewport(&mViewport); // retrieve current viewport - - // Viewport work around (help resolving z-fighting issues) - create_fixed_viewport(mViewport); - _device->SetDepthStencilSurface(_depthstencil_replacement.get()); - - if (FAILED(_device->DrawPrimitive(PrimitiveType, StartVertex, PrimitiveCount))) - { - // Original viewport is reloaded - _device->SetViewport(&mViewport); - return; - } - - // Original viewport is reloaded - _device->SetViewport(&mViewport); - } -} - -void reshade::d3d9::runtime_d3d9::weapon_or_cockpit_fix(const com_ptr depthstencil, D3DPRIMITIVETYPE PrimitiveType, INT BaseVertexIndex, UINT MinVertexIndex, UINT NumVertices, UINT StartIndex, UINT PrimitiveCount) -{ - if (_brute_force_fix && - depthstencil != _depthstencil_replacement && - _is_good_viewport && - _is_best_original_depthstencil_source && - _depth_buffer_table.size() > _adjusted_preserve_starting_index) - { - D3DVIEWPORT9 mViewport; // Holds viewport data - _device->GetViewport(&mViewport); // retrieve current viewport - - // Viewport work around (help resolving z-fighting issues) - create_fixed_viewport(mViewport); - _device->SetDepthStencilSurface(_depthstencil_replacement.get()); - - if (FAILED(_device->DrawIndexedPrimitive(PrimitiveType, BaseVertexIndex, MinVertexIndex, NumVertices, StartIndex, PrimitiveCount))) - { - // Original viewport is reloaded - _device->SetViewport(&mViewport); - return; - } - - // Original viewport is reloaded - _device->SetViewport(&mViewport); - } -} - -void reshade::d3d9::runtime_d3d9::create_fixed_viewport(const D3DVIEWPORT9 mViewport) -{ - D3DVIEWPORT9 mNewViewport; // Holds new viewport data - float g_fViewportBias = 0.5f; - - // Copy old Viewport to new - mNewViewport = mViewport; - - // Change by the bias - mNewViewport.MinZ -= g_fViewportBias; - mNewViewport.MaxZ -= g_fViewportBias; - - // The new viewport is loaded - _device->SetViewport(&mNewViewport); -} diff --git a/msvc/source/d3d9/runtime_d3d9.hpp b/msvc/source/d3d9/runtime_d3d9.hpp deleted file mode 100644 index 2fc5d22..0000000 --- a/msvc/source/d3d9/runtime_d3d9.hpp +++ /dev/null @@ -1,127 +0,0 @@ -/** - * Copyright (C) 2014 Patrick Mours. All rights reserved. - * License: https://github.com/crosire/reshade#license - */ - -#pragma once - -#include "runtime.hpp" -#include "effect_expression.hpp" -#include "state_block.hpp" - -namespace reshade { enum class texture_reference; } -namespace reshadefx { struct sampler_info; } - -namespace reshade::d3d9 -{ - class runtime_d3d9 : public runtime - { - public: - runtime_d3d9(IDirect3DDevice9 *device, IDirect3DSwapChain9 *swapchain); - ~runtime_d3d9(); - - bool on_init(const D3DPRESENT_PARAMETERS &pp); - void on_reset(); - void on_present(); - - void on_draw_primitive(D3DPRIMITIVETYPE PrimitiveType, UINT StartVertex, UINT PrimitiveCount); - void on_draw_indexed_primitive(D3DPRIMITIVETYPE PrimitiveType, INT BaseVertexIndex, UINT MinVertexIndex, UINT NumVertices, UINT StartIndex, UINT PrimitiveCount); - void on_draw_primitive_up(D3DPRIMITIVETYPE PrimitiveType, UINT PrimitiveCount, const void *pVertexStreamZeroData, UINT VertexStreamZeroStride); - void on_draw_indexed_primitive_up(D3DPRIMITIVETYPE PrimitiveType, UINT MinVertexIndex, UINT NumVertices, UINT PrimitiveCount, const void *pIndexData, D3DFORMAT IndexDataFormat, const void *pVertexStreamZeroData, UINT VertexStreamZeroStride); - void on_draw_call(com_ptr depthstencil, D3DPRIMITIVETYPE type, unsigned int count); - - void on_set_depthstencil_surface(IDirect3DSurface9 *&depthstencil); - void on_get_depthstencil_surface(IDirect3DSurface9 *&depthstencil); - void on_clear_depthstencil_surface(IDirect3DSurface9 *depthstencil); - - void capture_screenshot(uint8_t *buffer) const override; - - private: - struct depth_source_info - { - com_ptr depthstencil; - UINT width, height; - UINT drawcall_count, vertices_count; - }; - - bool init_backbuffer_texture(); - bool init_default_depth_stencil(); - bool init_fullscreen_triangle_resources(); - - bool init_texture(texture &info) override; - void upload_texture(texture &texture, const uint8_t *pixels) override; - bool update_texture_reference(texture &texture); - void update_texture_references(texture_reference type); - - bool compile_effect(effect_data &effect) override; - - bool add_sampler(const reshadefx::sampler_info &info, struct d3d9_technique_data &technique_init); - bool init_technique(technique &info, const struct d3d9_technique_data &technique_init, const std::unordered_map> &entry_points); - - void render_technique(technique &technique) override; - - bool check_depthstencil_size(const D3DSURFACE_DESC &desc); - bool check_depthstencil_size(const D3DSURFACE_DESC &desc, const D3DSURFACE_DESC &compared_desc); - -#if RESHADE_GUI - bool init_imgui_resources(); - void render_imgui_draw_data(ImDrawData *data) override; - void draw_debug_menu(); -#endif - - void detect_depth_source(); - bool create_depthstencil_replacement(const com_ptr &depthstencil); - - void weapon_or_cockpit_fix(const com_ptr depthstencil, D3DPRIMITIVETYPE PrimitiveType, UINT StartVertex, UINT PrimitiveCount); - void weapon_or_cockpit_fix(const com_ptr depthstencil, D3DPRIMITIVETYPE PrimitiveType, INT BaseVertexIndex, UINT MinVertexIndex, UINT NumVertices, UINT StartIndex, UINT PrimitiveCount); - void create_fixed_viewport(const D3DVIEWPORT9 mViewport); - - com_ptr _d3d; - com_ptr _device; - com_ptr _swapchain; - - com_ptr _backbuffer; - com_ptr _backbuffer_resolved; - com_ptr _backbuffer_texture; - com_ptr _backbuffer_texture_surface; - com_ptr _depthstencil_texture; - - unsigned int _num_samplers; - unsigned int _num_simultaneous_rendertargets; - unsigned int _behavior_flags; - bool _disable_intz = false; - bool _preserve_depth_buffer = false; - bool _auto_preserve = true; - size_t _preserve_starting_index = 0; - size_t _adjusted_preserve_starting_index = 0; - bool _source_engine_fix = false; - bool _brute_force_fix = false; - bool _focus_on_best_original_depthstencil_source = false; - bool _disable_depth_buffer_size_restriction = false; - bool _init_depthbuffer_detection = true; - bool _is_good_viewport = true; - bool _is_best_original_depthstencil_source = true; - unsigned int _db_vertices = 0; - unsigned int _db_drawcalls = 0; - unsigned int _current_db_vertices = 0; - unsigned int _current_db_drawcalls = 0; - bool _is_multisampling_enabled = false; - D3DFORMAT _backbuffer_format = D3DFMT_UNKNOWN; - state_block _app_state; - com_ptr _depthstencil; - com_ptr _depthstencil_replacement; - com_ptr _default_depthstencil; - std::unordered_map, depth_source_info> _depth_source_table; - std::vector _depth_buffer_table; - - com_ptr _effect_triangle_buffer; - com_ptr _effect_triangle_layout; - - com_ptr _imgui_state; - com_ptr _imgui_vertex_buffer; - com_ptr _imgui_index_buffer; - int _imgui_vertex_buffer_size = 0, _imgui_index_buffer_size = 0; - - HMODULE _d3d_compiler = nullptr; - }; -} diff --git a/msvc/source/d3d9/state_block.cpp b/msvc/source/d3d9/state_block.cpp deleted file mode 100644 index 2ef9466..0000000 --- a/msvc/source/d3d9/state_block.cpp +++ /dev/null @@ -1,61 +0,0 @@ -/** - * Copyright (C) 2014 Patrick Mours. All rights reserved. - * License: https://github.com/crosire/reshade#license - */ - -#include "state_block.hpp" -#include - -reshade::d3d9::state_block::state_block(IDirect3DDevice9 *device) -{ - ZeroMemory(this, sizeof(*this)); - - _device = device; - - D3DCAPS9 caps; - device->GetDeviceCaps(&caps); - _num_simultaneous_rendertargets = std::min(caps.NumSimultaneousRTs, DWORD(8)); -} -reshade::d3d9::state_block::~state_block() -{ - release_all_device_objects(); -} - -void reshade::d3d9::state_block::capture() -{ - _state_block->Capture(); - - _device->GetViewport(&_viewport); - - for (DWORD target = 0; target < _num_simultaneous_rendertargets; target++) - _device->GetRenderTarget(target, &_render_targets[target]); - _device->GetDepthStencilSurface(&_depth_stencil); -} -void reshade::d3d9::state_block::apply_and_release() -{ - _state_block->Apply(); - - for (DWORD target = 0; target < _num_simultaneous_rendertargets; target++) - _device->SetRenderTarget(target, _render_targets[target].get()); - _device->SetDepthStencilSurface(_depth_stencil.get()); - - // Set viewport after render targets have been set, since 'SetRenderTarget' causes the viewport to be set to the full size of the render target - _device->SetViewport(&_viewport); - - release_all_device_objects(); -} - -bool reshade::d3d9::state_block::init_state_block() -{ - return SUCCEEDED(_device->CreateStateBlock(D3DSBT_ALL, &_state_block)); -} -void reshade::d3d9::state_block::release_state_block() -{ - _state_block.reset(); -} -void reshade::d3d9::state_block::release_all_device_objects() -{ - _depth_stencil.reset(); - for (auto &render_target : _render_targets) - render_target.reset(); -} diff --git a/msvc/source/d3d9/state_block.hpp b/msvc/source/d3d9/state_block.hpp deleted file mode 100644 index 49bdb9c..0000000 --- a/msvc/source/d3d9/state_block.hpp +++ /dev/null @@ -1,35 +0,0 @@ -/** - * Copyright (C) 2014 Patrick Mours. All rights reserved. - * License: https://github.com/crosire/reshade#license - */ - -#pragma once - -#include "com_ptr.hpp" -#include - -namespace reshade::d3d9 -{ - class state_block - { - public: - explicit state_block(IDirect3DDevice9 *device); - ~state_block(); - - bool init_state_block(); - void release_state_block(); - - void capture(); - void apply_and_release(); - - private: - void release_all_device_objects(); - - com_ptr _device; - com_ptr _state_block; - UINT _num_simultaneous_rendertargets; - D3DVIEWPORT9 _viewport; - com_ptr _depth_stencil; - com_ptr _render_targets[8]; - }; -} diff --git a/msvc/source/dllmain.cpp b/msvc/source/dllmain.cpp deleted file mode 100644 index e8d7a11..0000000 --- a/msvc/source/dllmain.cpp +++ /dev/null @@ -1,406 +0,0 @@ -/** - * Copyright (C) 2014 Patrick Mours. All rights reserved. - * License: https://github.com/crosire/reshade#license - */ - -#include "log.hpp" -#include "hook_manager.hpp" -#include "version.h" -#include - -HMODULE g_module_handle = nullptr; - -std::filesystem::path g_reshade_dll_path; -std::filesystem::path g_target_executable_path; - -extern std::filesystem::path get_system_path() -{ - static std::filesystem::path system_path; - if (!system_path.empty()) - return system_path; - TCHAR buf[MAX_PATH] = {}; - GetSystemDirectory(buf, ARRAYSIZE(buf)); - return system_path = buf; -} -static inline std::filesystem::path get_module_path(HMODULE module) -{ - TCHAR buf[MAX_PATH] = {}; - GetModuleFileName(module, buf, ARRAYSIZE(buf)); - return buf; -} - -#if defined(RESHADE_TEST_APPLICATION) - -#include "d3d9/runtime_d3d9.hpp" -#include "d3d11/runtime_d3d11.hpp" -#include "d3d12/runtime_d3d12.hpp" -#include "opengl/runtime_opengl.hpp" - -#define HCHECK(exp) assert(SUCCEEDED(exp)) - -int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE, LPSTR lpCmdLine, int nCmdShow) -{ - using namespace reshade; - - g_module_handle = hInstance; - g_reshade_dll_path = get_module_path(nullptr); - g_target_executable_path = get_module_path(nullptr); - - log::open(std::filesystem::path(g_reshade_dll_path).replace_extension(".log")); - - hooks::register_module("user32.dll"); - - static UINT s_resize_w = 0, s_resize_h = 0; - - // Register window class - WNDCLASS wc = { sizeof(wc) }; - wc.hInstance = hInstance; - wc.style = CS_HREDRAW | CS_VREDRAW; - wc.lpszClassName = TEXT("Test"); - wc.lpfnWndProc = - [](HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam) -> LRESULT { - switch (Msg) - { - case WM_DESTROY: - PostQuitMessage(EXIT_SUCCESS); - break; - case WM_SIZE: - s_resize_w = LOWORD(lParam); - s_resize_h = HIWORD(lParam); - break; - } - - return DefWindowProc(hWnd, Msg, wParam, lParam); - }; - - RegisterClass(&wc); - - // Create and show window instance - const HWND window_handle = CreateWindow( - wc.lpszClassName, TEXT("ReShade ") TEXT(VERSION_STRING_FILE) TEXT(" by crosire"), WS_OVERLAPPEDWINDOW, - 0, 0, 1024, 800, nullptr, nullptr, hInstance, nullptr); - - if (window_handle == nullptr) - return 0; - - ShowWindow(window_handle, nCmdShow); - - // Avoid resize caused by 'ShowWindow' call - s_resize_w = 0; - s_resize_h = 0; - - MSG msg = {}; - - #pragma region D3D9 Implementation - if (strstr(lpCmdLine, "-d3d9")) - { - hooks::register_module("d3d9.dll"); - const auto d3d9_module = LoadLibrary(TEXT("d3d9.dll")); - - D3DPRESENT_PARAMETERS pp = {}; - pp.SwapEffect = D3DSWAPEFFECT_DISCARD; - pp.hDeviceWindow = window_handle; - pp.Windowed = true; - pp.PresentationInterval = D3DPRESENT_INTERVAL_ONE; - - // Initialize Direct3D 9 - com_ptr d3d(Direct3DCreate9(D3D_SDK_VERSION), true); - com_ptr device; - - HCHECK(d3d->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, window_handle, D3DCREATE_HARDWARE_VERTEXPROCESSING, &pp, &device)); - - while (msg.message != WM_QUIT) - { - while (msg.message != WM_QUIT && - PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE)) - DispatchMessage(&msg); - - if (s_resize_w != 0) - { - pp.BackBufferWidth = s_resize_w; - pp.BackBufferHeight = s_resize_h; - - HCHECK(device->Reset(&pp)); - - s_resize_w = s_resize_h = 0; - } - - HCHECK(device->Clear(0, nullptr, D3DCLEAR_TARGET, 0xFF7F7F7F, 0, 0)); - HCHECK(device->Present(nullptr, nullptr, nullptr, nullptr)); - } - - reshade::hooks::uninstall(); - - FreeLibrary(d3d9_module); - - return static_cast(msg.wParam); - } - #pragma endregion - - #pragma region D3D11 Implementation - if (strstr(lpCmdLine, "-d3d11")) - { - hooks::register_module("dxgi.dll"); - hooks::register_module("d3d11.dll"); - const auto d3d11_module = LoadLibrary(TEXT("d3d11.dll")); - - // Initialize Direct3D 11 - com_ptr device; - com_ptr immediate_context; - com_ptr swapchain; - - { DXGI_SWAP_CHAIN_DESC desc = {}; - desc.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; - desc.SampleDesc = { 1, 0 }; - desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; - desc.BufferCount = 1; - desc.OutputWindow = window_handle; - desc.Windowed = true; - desc.SwapEffect = DXGI_SWAP_EFFECT_DISCARD; - - HCHECK(D3D11CreateDeviceAndSwapChain(nullptr, D3D_DRIVER_TYPE_HARDWARE, nullptr, D3D11_CREATE_DEVICE_DEBUG, nullptr, 0, D3D11_SDK_VERSION, &desc, &swapchain, &device, nullptr, &immediate_context)); - } - - com_ptr backbuffer; - HCHECK(swapchain->GetBuffer(0, IID_PPV_ARGS(&backbuffer))); - com_ptr target; - HCHECK(device->CreateRenderTargetView(backbuffer.get(), nullptr, &target)); - - while (msg.message != WM_QUIT) - { - while (msg.message != WM_QUIT && - PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE)) - DispatchMessage(&msg); - - if (s_resize_w != 0) - { - target.reset(); - backbuffer.reset(); - - HCHECK(swapchain->ResizeBuffers(1, s_resize_w, s_resize_h, DXGI_FORMAT_UNKNOWN, 0)); - - HCHECK(swapchain->GetBuffer(0, IID_PPV_ARGS(&backbuffer))); - HCHECK(device->CreateRenderTargetView(backbuffer.get(), nullptr, &target)); - - s_resize_w = s_resize_h = 0; - } - - const float color[4] = { 0.5f, 0.5f, 0.5f, 1.0f }; - immediate_context->ClearRenderTargetView(target.get(), color); - - HCHECK(swapchain->Present(1, 0)); - } - - reshade::hooks::uninstall(); - - FreeLibrary(d3d11_module); - - return static_cast(msg.wParam); - } - #pragma endregion - - #pragma region D3D12 Implementation - if (strstr(lpCmdLine, "-d3d12")) - { - hooks::register_module("dxgi.dll"); - hooks::register_module("d3d12.dll"); - const auto d3d12_module = LoadLibrary(TEXT("d3d12.dll")); - - // Enable D3D debug layer - { com_ptr debug_iface; - HCHECK(D3D12GetDebugInterface(IID_PPV_ARGS(&debug_iface))); - debug_iface->EnableDebugLayer(); - } - - // Initialize Direct3D 12 - com_ptr device; - com_ptr command_queue; - com_ptr swapchain; - com_ptr backbuffers[3]; - com_ptr rtv_heap; - com_ptr cmd_alloc; - com_ptr cmd_lists[3]; - - HCHECK(D3D12CreateDevice(nullptr, D3D_FEATURE_LEVEL_12_0, IID_PPV_ARGS(&device))); - - { D3D12_COMMAND_QUEUE_DESC desc = { D3D12_COMMAND_LIST_TYPE_DIRECT }; - HCHECK(device->CreateCommandQueue(&desc, IID_PPV_ARGS(&command_queue))); - } - - { DXGI_SWAP_CHAIN_DESC1 desc = {}; - desc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; - desc.SampleDesc = { 1, 0 }; - desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; - desc.BufferCount = ARRAYSIZE(backbuffers); - desc.Scaling = DXGI_SCALING_STRETCH; - desc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD; - - com_ptr dxgi_factory; - com_ptr dxgi_swapchain; - HCHECK(CreateDXGIFactory2(DXGI_CREATE_FACTORY_DEBUG, IID_PPV_ARGS(&dxgi_factory))); - HCHECK(dxgi_factory->CreateSwapChainForHwnd(command_queue.get(), window_handle, &desc, nullptr, nullptr, &dxgi_swapchain)); - HCHECK(dxgi_swapchain->QueryInterface(&swapchain)); - } - - { D3D12_DESCRIPTOR_HEAP_DESC desc = { D3D12_DESCRIPTOR_HEAP_TYPE_RTV }; - desc.NumDescriptors = ARRAYSIZE(backbuffers); - HCHECK(device->CreateDescriptorHeap(&desc, IID_PPV_ARGS(&rtv_heap))); - } - - const UINT rtv_handle_size = device->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_RTV); - D3D12_CPU_DESCRIPTOR_HANDLE rtv_handle = rtv_heap->GetCPUDescriptorHandleForHeapStart(); - - HCHECK(device->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(&cmd_alloc))); - - for (UINT i = 0; i < ARRAYSIZE(backbuffers); ++i, rtv_handle.ptr += rtv_handle_size) - { - HCHECK(swapchain->GetBuffer(i, IID_PPV_ARGS(&backbuffers[i]))); - - device->CreateRenderTargetView(backbuffers[i].get(), nullptr, rtv_handle); - - HCHECK(device->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, cmd_alloc.get(), nullptr, IID_PPV_ARGS(&cmd_lists[i]))); - - D3D12_RESOURCE_BARRIER barrier = { D3D12_RESOURCE_BARRIER_TYPE_TRANSITION }; - barrier.Transition.pResource = backbuffers[i].get(); - barrier.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES; - barrier.Transition.StateBefore = D3D12_RESOURCE_STATE_PRESENT; - barrier.Transition.StateAfter = D3D12_RESOURCE_STATE_RENDER_TARGET; - cmd_lists[i]->ResourceBarrier(1, &barrier); - - const float color[4] = { 0.5f, 0.5f, 0.5f, 1.0f }; - cmd_lists[i]->ClearRenderTargetView(rtv_handle, color, 0, nullptr); - - barrier.Transition.StateBefore = D3D12_RESOURCE_STATE_RENDER_TARGET; - barrier.Transition.StateAfter = D3D12_RESOURCE_STATE_PRESENT; - cmd_lists[i]->ResourceBarrier(1, &barrier); - - HCHECK(cmd_lists[i]->Close()); - } - - while (msg.message != WM_QUIT) - { - while (msg.message != WM_QUIT && - PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE)) - DispatchMessage(&msg); - - const UINT swap_index = swapchain->GetCurrentBackBufferIndex(); - - ID3D12CommandList *const cmd_list = cmd_lists[swap_index].get(); - command_queue->ExecuteCommandLists(1, &cmd_list); - - // Synchronization is handled in "runtime_d3d12::on_present" - swapchain->Present(1, 0); - } - - reshade::hooks::uninstall(); - - FreeLibrary(d3d12_module); - - return static_cast(msg.wParam); - } - #pragma endregion - - #pragma region OpenGL Implementation - if (strstr(lpCmdLine, "-opengl")) - { - hooks::register_module("opengl32.dll"); - const auto opengl_module = LoadLibrary(TEXT("opengl32.dll")); - - // Initialize OpenGL - const HDC hdc = GetDC(window_handle); - - PIXELFORMATDESCRIPTOR pfd = { sizeof(pfd) }; - pfd.nVersion = 1; - pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER; - pfd.iPixelType = PFD_TYPE_RGBA; - pfd.cColorBits = 32; - - const int pf = ChoosePixelFormat(hdc, &pfd); - SetPixelFormat(hdc, pf, &pfd); - - const HGLRC hglrc = wglCreateContext(hdc); - if (hglrc == nullptr) - return 0; - - wglMakeCurrent(hdc, hglrc); - - while (msg.message != WM_QUIT) - { - while (msg.message != WM_QUIT && - PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE)) - DispatchMessage(&msg); - - if (s_resize_w != 0) - { - glViewport(0, 0, s_resize_w, s_resize_h); - - s_resize_w = s_resize_h = 0; - } - - glClearColor(0.5f, 0.5f, 0.5f, 1.0f); - glClear(GL_COLOR_BUFFER_BIT); - - SwapBuffers(hdc); - } - - wglMakeCurrent(nullptr, nullptr); - wglDeleteContext(hglrc); - - reshade::hooks::uninstall(); - - FreeLibrary(opengl_module); - - return static_cast(msg.wParam); - } - #pragma endregion - - return EXIT_FAILURE; -} - -#else - -BOOL APIENTRY DllMain(HMODULE hModule, DWORD fdwReason, LPVOID) -{ - using namespace reshade; - - switch (fdwReason) - { - case DLL_PROCESS_ATTACH: - g_module_handle = hModule; - g_reshade_dll_path = get_module_path(hModule); - g_target_executable_path = get_module_path(nullptr); - - log::open(std::filesystem::path(g_reshade_dll_path).replace_extension(".log")); - -#ifdef WIN64 - LOG(INFO) << "Initializing crosire's ReShade version '" VERSION_STRING_FILE "' (64-bit) built on '" VERSION_DATE " " VERSION_TIME "' loaded from " << g_reshade_dll_path << " to " << g_target_executable_path << " ..."; -#else - LOG(INFO) << "Initializing crosire's ReShade version '" VERSION_STRING_FILE "' (32-bit) built on '" VERSION_DATE " " VERSION_TIME "' loaded from " << g_reshade_dll_path << " to " << g_target_executable_path << " ..."; -#endif - - hooks::register_module("user32.dll"); - hooks::register_module("ws2_32.dll"); - - hooks::register_module(get_system_path() / "d3d9.dll"); - hooks::register_module(get_system_path() / "d3d10.dll"); - hooks::register_module(get_system_path() / "d3d10_1.dll"); - hooks::register_module(get_system_path() / "d3d11.dll"); - hooks::register_module(get_system_path() / "d3d12.dll"); - hooks::register_module(get_system_path() / "dxgi.dll"); - hooks::register_module(get_system_path() / "opengl32.dll"); - - LOG(INFO) << "Initialized."; - break; - case DLL_PROCESS_DETACH: - LOG(INFO) << "Exiting ..."; - - hooks::uninstall(); - - LOG(INFO) << "Exited."; - break; - } - - return TRUE; -} - -#endif diff --git a/msvc/source/dxgi/dxgi.cpp b/msvc/source/dxgi/dxgi.cpp deleted file mode 100644 index a8f51bf..0000000 --- a/msvc/source/dxgi/dxgi.cpp +++ /dev/null @@ -1,341 +0,0 @@ -/** - * Copyright (C) 2014 Patrick Mours. All rights reserved. - * License: https://github.com/crosire/reshade#license - */ - -#include "log.hpp" -#include "hook_manager.hpp" -#include "dxgi_device.hpp" -#include "dxgi_swapchain.hpp" -#include "d3d10/d3d10_device.hpp" -#include "d3d10/runtime_d3d10.hpp" -#include "d3d11/d3d11_device.hpp" -#include "d3d11/runtime_d3d11.hpp" -#include "d3d12/d3d12_device.hpp" -#include "d3d12/d3d12_command_queue.hpp" -#include "d3d12/runtime_d3d12.hpp" - -static void query_device(IUnknown *&device, com_ptr &device_d3d10, com_ptr &device_d3d11, com_ptr &command_queue_d3d12) -{ - if (SUCCEEDED(device->QueryInterface(&device_d3d10))) - device = device_d3d10->_orig; - else if (SUCCEEDED(device->QueryInterface(&device_d3d11))) - device = device_d3d11->_orig; // Set device pointer back to original object so that the swapchain creation functions work as expected - else if (SUCCEEDED(device->QueryInterface(&command_queue_d3d12))) - device = command_queue_d3d12->_orig; -} - -static void dump_swapchain_desc(const DXGI_SWAP_CHAIN_DESC &desc) -{ - LOG(INFO) << "> Dumping swap chain description:"; - LOG(INFO) << " +-----------------------------------------+-----------------------------------------+"; - LOG(INFO) << " | Parameter | Value |"; - LOG(INFO) << " +-----------------------------------------+-----------------------------------------+"; - LOG(INFO) << " | Width | " << std::setw(39) << desc.BufferDesc.Width << " |"; - LOG(INFO) << " | Height | " << std::setw(39) << desc.BufferDesc.Height << " |"; - LOG(INFO) << " | RefreshRate | " << std::setw(19) << desc.BufferDesc.RefreshRate.Numerator << ' ' << std::setw(19) << desc.BufferDesc.RefreshRate.Denominator << " |"; - LOG(INFO) << " | Format | " << std::setw(39) << desc.BufferDesc.Format << " |"; - LOG(INFO) << " | ScanlineOrdering | " << std::setw(39) << desc.BufferDesc.ScanlineOrdering << " |"; - LOG(INFO) << " | Scaling | " << std::setw(39) << desc.BufferDesc.Scaling << " |"; - LOG(INFO) << " | SampleCount | " << std::setw(39) << desc.SampleDesc.Count << " |"; - LOG(INFO) << " | SampleQuality | " << std::setw(39) << desc.SampleDesc.Quality << " |"; - LOG(INFO) << " | BufferUsage | " << std::setw(39) << desc.BufferUsage << " |"; - LOG(INFO) << " | BufferCount | " << std::setw(39) << desc.BufferCount << " |"; - LOG(INFO) << " | OutputWindow | " << std::setw(39) << desc.OutputWindow << " |"; - LOG(INFO) << " | Windowed | " << std::setw(39) << (desc.Windowed != FALSE ? "TRUE" : "FALSE") << " |"; - LOG(INFO) << " | SwapEffect | " << std::setw(39) << desc.SwapEffect << " |"; - LOG(INFO) << " | Flags | " << std::setw(39) << std::hex << desc.Flags << std::dec << " |"; - LOG(INFO) << " +-----------------------------------------+-----------------------------------------+"; - - if (desc.SampleDesc.Count > 1) - LOG(WARN) << "> Multisampling is enabled. This is not compatible with depth buffer access, which was therefore disabled."; -} -static void dump_swapchain_desc(const DXGI_SWAP_CHAIN_DESC1 &desc) -{ - LOG(INFO) << "> Dumping swap chain description:"; - LOG(INFO) << " +-----------------------------------------+-----------------------------------------+"; - LOG(INFO) << " | Parameter | Value |"; - LOG(INFO) << " +-----------------------------------------+-----------------------------------------+"; - LOG(INFO) << " | Width | " << std::setw(39) << desc.Width << " |"; - LOG(INFO) << " | Height | " << std::setw(39) << desc.Height << " |"; - LOG(INFO) << " | Format | " << std::setw(39) << desc.Format << " |"; - LOG(INFO) << " | Stereo | " << std::setw(39) << (desc.Stereo != FALSE ? "TRUE" : "FALSE") << " |"; - LOG(INFO) << " | SampleCount | " << std::setw(39) << desc.SampleDesc.Count << " |"; - LOG(INFO) << " | SampleQuality | " << std::setw(39) << desc.SampleDesc.Quality << " |"; - LOG(INFO) << " | BufferUsage | " << std::setw(39) << desc.BufferUsage << " |"; - LOG(INFO) << " | BufferCount | " << std::setw(39) << desc.BufferCount << " |"; - LOG(INFO) << " | Scaling | " << std::setw(39) << desc.Scaling << " |"; - LOG(INFO) << " | SwapEffect | " << std::setw(39) << desc.SwapEffect << " |"; - LOG(INFO) << " | AlphaMode | " << std::setw(39) << desc.AlphaMode << " |"; - LOG(INFO) << " | Flags | " << std::setw(39) << std::hex << desc.Flags << std::dec << " |"; - LOG(INFO) << " +-----------------------------------------+-----------------------------------------+"; - - if (desc.SampleDesc.Count > 1) - LOG(WARN) << "> Multisampling is enabled. This is not compatible with depth buffer access, which was therefore disabled."; -} - -template -static void init_reshade_runtime_d3d(T *&swapchain, com_ptr &device_d3d10, com_ptr &device_d3d11, com_ptr &command_queue_d3d12) -{ - DXGI_SWAP_CHAIN_DESC desc; - if (FAILED(swapchain->GetDesc(&desc))) - return; // This doesn't happen, but let's be safe - - if ((desc.BufferUsage & DXGI_USAGE_RENDER_TARGET_OUTPUT) == 0) - { - LOG(WARN) << "> Skipping swap chain due to missing 'DXGI_USAGE_RENDER_TARGET_OUTPUT' flag."; - } - else if (device_d3d10 != nullptr) - { - const auto runtime = std::make_shared(device_d3d10->_orig, swapchain); - - if (!runtime->on_init(desc)) - LOG(ERROR) << "Failed to initialize Direct3D 10 runtime environment on runtime " << runtime.get() << '.'; - - device_d3d10->_runtimes.push_back(runtime); - - swapchain = new DXGISwapChain(device_d3d10.get(), swapchain, runtime); - } - else if (device_d3d11 != nullptr) - { - const auto runtime = std::make_shared(device_d3d11->_orig, swapchain); - - if (!runtime->on_init(desc)) - LOG(ERROR) << "Failed to initialize Direct3D 11 runtime environment on runtime " << runtime.get() << '.'; - - device_d3d11->_runtimes.push_back(runtime); - - swapchain = new DXGISwapChain(device_d3d11.get(), swapchain, runtime); // Overwrite returned swapchain pointer with hooked object - } - else if (command_queue_d3d12 != nullptr) - { - if (com_ptr swapchain3; SUCCEEDED(swapchain->QueryInterface(&swapchain3))) - { - const auto runtime = std::make_shared(command_queue_d3d12->_device->_orig, command_queue_d3d12->_orig, swapchain3.get()); - - if (!runtime->on_init(desc)) - LOG(ERROR) << "Failed to initialize Direct3D 12 runtime environment on runtime " << runtime.get() << '.'; - - swapchain = new DXGISwapChain(command_queue_d3d12->_device, swapchain3.get(), runtime); - } - else - { - LOG(WARN) << "> Skipping swap chain because it is missing support for the IDXGISwapChain3 interface."; - } - } - else - { - LOG(WARN) << "> Skipping swap chain because it was created without a (hooked) Direct3D device."; - } - -#if RESHADE_VERBOSE_LOG - LOG(DEBUG) << "Returning IDXGISwapChain object " << swapchain << '.'; -#endif -} - -reshade::log::message &operator<<(reshade::log::message &m, REFIID riid) -{ - OLECHAR riid_string[40]; - StringFromGUID2(riid, riid_string, ARRAYSIZE(riid_string)); - return m << riid_string; -} - -HRESULT STDMETHODCALLTYPE IDXGIFactory_CreateSwapChain(IDXGIFactory *pFactory, IUnknown *pDevice, DXGI_SWAP_CHAIN_DESC *pDesc, IDXGISwapChain **ppSwapChain) -{ - LOG(INFO) << "Redirecting IDXGIFactory::CreateSwapChain" << '(' << pFactory << ", " << pDevice << ", " << pDesc << ", " << ppSwapChain << ')' << " ..."; - - if (pDevice == nullptr || pDesc == nullptr || ppSwapChain == nullptr) - return DXGI_ERROR_INVALID_CALL; - - com_ptr device_d3d10; - com_ptr device_d3d11; - com_ptr command_queue_d3d12; - query_device(pDevice, - device_d3d10, - device_d3d11, - command_queue_d3d12); - dump_swapchain_desc(*pDesc); - - const HRESULT hr = reshade::hooks::call(IDXGIFactory_CreateSwapChain, vtable_from_instance(pFactory) + 10)(pFactory, pDevice, pDesc, ppSwapChain); - - if (FAILED(hr)) - { - LOG(WARN) << "> IDXGIFactory::CreateSwapChain failed with error code " << std::hex << hr << std::dec << '!'; - return hr; - } - - init_reshade_runtime_d3d(*ppSwapChain, - device_d3d10, - device_d3d11, - command_queue_d3d12); - - return hr; -} - -HRESULT STDMETHODCALLTYPE IDXGIFactory2_CreateSwapChainForHwnd(IDXGIFactory2 *pFactory, IUnknown *pDevice, HWND hWnd, const DXGI_SWAP_CHAIN_DESC1 *pDesc, const DXGI_SWAP_CHAIN_FULLSCREEN_DESC *pFullscreenDesc, IDXGIOutput *pRestrictToOutput, IDXGISwapChain1 **ppSwapChain) -{ - LOG(INFO) << "Redirecting IDXGIFactory2::CreateSwapChainForHwnd" << '(' << pFactory << ", " << pDevice << ", " << hWnd << ", " << pDesc << ", " << pFullscreenDesc << ", " << pRestrictToOutput << ", " << ppSwapChain << ')' << " ..."; - - if (pDevice == nullptr || pDesc == nullptr || ppSwapChain == nullptr) - return DXGI_ERROR_INVALID_CALL; - - com_ptr device_d3d10; - com_ptr device_d3d11; - com_ptr command_queue_d3d12; - query_device(pDevice, - device_d3d10, - device_d3d11, - command_queue_d3d12); - dump_swapchain_desc(*pDesc); - - const HRESULT hr = reshade::hooks::call(IDXGIFactory2_CreateSwapChainForHwnd, vtable_from_instance(pFactory) + 15)(pFactory, pDevice, hWnd, pDesc, pFullscreenDesc, pRestrictToOutput, ppSwapChain); - - if (FAILED(hr)) - { - LOG(WARN) << "> IDXGIFactory2::CreateSwapChainForHwnd failed with error code " << std::hex << hr << std::dec << '!'; - return hr; - } - - init_reshade_runtime_d3d(*ppSwapChain, - device_d3d10, - device_d3d11, - command_queue_d3d12); - - return hr; -} -HRESULT STDMETHODCALLTYPE IDXGIFactory2_CreateSwapChainForCoreWindow(IDXGIFactory2 *pFactory, IUnknown *pDevice, IUnknown *pWindow, const DXGI_SWAP_CHAIN_DESC1 *pDesc, IDXGIOutput *pRestrictToOutput, IDXGISwapChain1 **ppSwapChain) -{ - LOG(INFO) << "Redirecting IDXGIFactory2::CreateSwapChainForCoreWindow" << '(' << pFactory << ", " << pDevice << ", " << pWindow << ", " << pDesc << ", " << pRestrictToOutput << ", " << ppSwapChain << ')' << " ..."; - - if (pDevice == nullptr || pDesc == nullptr || ppSwapChain == nullptr) - return DXGI_ERROR_INVALID_CALL; - - com_ptr device_d3d10; - com_ptr device_d3d11; - com_ptr command_queue_d3d12; - query_device(pDevice, - device_d3d10, - device_d3d11, - command_queue_d3d12); - dump_swapchain_desc(*pDesc); - - const HRESULT hr = reshade::hooks::call(IDXGIFactory2_CreateSwapChainForCoreWindow, vtable_from_instance(pFactory + 16))(pFactory, pDevice, pWindow, pDesc, pRestrictToOutput, ppSwapChain); - - if (FAILED(hr)) - { - LOG(WARN) << "> IDXGIFactory2::CreateSwapChainForCoreWindow failed with error code " << std::hex << hr << std::dec << '!'; - return hr; - } - - init_reshade_runtime_d3d(*ppSwapChain, - device_d3d10, - device_d3d11, - command_queue_d3d12); - - return hr; -} -HRESULT STDMETHODCALLTYPE IDXGIFactory2_CreateSwapChainForComposition(IDXGIFactory2 *pFactory, IUnknown *pDevice, const DXGI_SWAP_CHAIN_DESC1 *pDesc, IDXGIOutput *pRestrictToOutput, IDXGISwapChain1 **ppSwapChain) -{ - LOG(INFO) << "Redirecting IDXGIFactory2::CreateSwapChainForComposition" << '(' << pFactory << ", " << pDevice << ", " << pDesc << ", " << pRestrictToOutput << ", " << ppSwapChain << ')' << " ..."; - - if (pDevice == nullptr || pDesc == nullptr || ppSwapChain == nullptr) - return DXGI_ERROR_INVALID_CALL; - - com_ptr device_d3d10; - com_ptr device_d3d11; - com_ptr command_queue_d3d12; - query_device(pDevice, - device_d3d10, - device_d3d11, - command_queue_d3d12); - dump_swapchain_desc(*pDesc); - - const HRESULT hr = reshade::hooks::call(IDXGIFactory2_CreateSwapChainForComposition, vtable_from_instance(pFactory) + 24)(pFactory, pDevice, pDesc, pRestrictToOutput, ppSwapChain); - - if (FAILED(hr)) - { - LOG(WARN) << "> IDXGIFactory2::CreateSwapChainForComposition failed with error code " << std::hex << hr << std::dec << '!'; - return hr; - } - - init_reshade_runtime_d3d(*ppSwapChain, - device_d3d10, - device_d3d11, - command_queue_d3d12); - - return hr; -} - -HOOK_EXPORT HRESULT WINAPI CreateDXGIFactory(REFIID riid, void **ppFactory) -{ - LOG(INFO) << "Redirecting CreateDXGIFactory" << '(' << riid << ", " << ppFactory << ')' << " ..."; - LOG(INFO) << "> Passing on to CreateDXGIFactory1:"; - - // DXGI1.1 should always be available, so to simplify code just 'CreateDXGIFactory' which is otherwise identical - return CreateDXGIFactory1(riid, ppFactory); -} -HOOK_EXPORT HRESULT WINAPI CreateDXGIFactory1(REFIID riid, void **ppFactory) -{ - LOG(INFO) << "Redirecting CreateDXGIFactory1" << '(' << riid << ", " << ppFactory << ')' << " ..."; - - const HRESULT hr = reshade::hooks::call(CreateDXGIFactory1)(riid, ppFactory); - - if (FAILED(hr)) - { - LOG(WARN) << "> CreateDXGIFactory1 failed with error code " << std::hex << hr << std::dec << '!'; - return hr; - } - - IDXGIFactory *const factory = static_cast(*ppFactory); - - reshade::hooks::install("IDXGIFactory::CreateSwapChain", vtable_from_instance(factory), 10, reinterpret_cast(&IDXGIFactory_CreateSwapChain)); - - // Check for DXGI1.2 support and install IDXGIFactory2 hooks if it exists - if (com_ptr factory2; SUCCEEDED(factory->QueryInterface(&factory2))) - { - reshade::hooks::install("IDXGIFactory2::CreateSwapChainForHwnd", vtable_from_instance(factory2.get()), 15, reinterpret_cast(&IDXGIFactory2_CreateSwapChainForHwnd)); - reshade::hooks::install("IDXGIFactory2::CreateSwapChainForCoreWindow", vtable_from_instance(factory2.get()), 16, reinterpret_cast(&IDXGIFactory2_CreateSwapChainForCoreWindow)); - reshade::hooks::install("IDXGIFactory2::CreateSwapChainForComposition", vtable_from_instance(factory2.get()), 24, reinterpret_cast(&IDXGIFactory2_CreateSwapChainForComposition)); - } - -#if RESHADE_VERBOSE_LOG - LOG(DEBUG) << "Returning IDXGIFactory1 object " << *ppFactory << '.'; -#endif - return hr; -} -HOOK_EXPORT HRESULT WINAPI CreateDXGIFactory2(UINT flags, REFIID riid, void **ppFactory) -{ - LOG(INFO) << "Redirecting CreateDXGIFactory2" << '(' << flags << ", " << riid << ", " << ppFactory << ')' << " ..."; - - static const auto trampoline = reshade::hooks::call(CreateDXGIFactory2); - - // CreateDXGIFactory2 is not available on Windows 7, so fall back to CreateDXGIFactory1 if the application calls it - // This needs to happen because some applications only check if CreateDXGIFactory2 exists, which is always the case if they load ReShade, to decide whether to call it or CreateDXGIFactory1 - if (trampoline == nullptr) - { - LOG(INFO) << "> Passing on to CreateDXGIFactory1:"; - - return CreateDXGIFactory1(riid, ppFactory); - } - - const HRESULT hr = trampoline(flags, riid, ppFactory); - - if (FAILED(hr)) - { - LOG(WARN) << "> CreateDXGIFactory2 failed with error code " << std::hex << hr << std::dec << '!'; - return hr; - } - - // We can pretty much assume support for DXGI1.2 at this point - IDXGIFactory2 *const factory = static_cast(*ppFactory); - - reshade::hooks::install("IDXGIFactory::CreateSwapChain", vtable_from_instance(factory), 10, reinterpret_cast(&IDXGIFactory_CreateSwapChain)); - reshade::hooks::install("IDXGIFactory2::CreateSwapChainForHwnd", vtable_from_instance(factory), 15, reinterpret_cast(&IDXGIFactory2_CreateSwapChainForHwnd)); - reshade::hooks::install("IDXGIFactory2::CreateSwapChainForCoreWindow", vtable_from_instance(factory), 16, reinterpret_cast(&IDXGIFactory2_CreateSwapChainForCoreWindow)); - reshade::hooks::install("IDXGIFactory2::CreateSwapChainForComposition", vtable_from_instance(factory), 24, reinterpret_cast(&IDXGIFactory2_CreateSwapChainForComposition)); - -#if RESHADE_VERBOSE_LOG - LOG(DEBUG) << "Returning IDXGIFactory2 object " << *ppFactory << '.'; -#endif - return hr; -} diff --git a/msvc/source/dxgi/dxgi_d3d10.cpp b/msvc/source/dxgi/dxgi_d3d10.cpp deleted file mode 100644 index a402c65..0000000 --- a/msvc/source/dxgi/dxgi_d3d10.cpp +++ /dev/null @@ -1,38 +0,0 @@ -/** - * Copyright (C) 2014 Patrick Mours. All rights reserved. - * License: https://github.com/crosire/reshade#license - */ - -#include "hook_manager.hpp" -#include -#include - -// These are filtered out during hook installation (see hook_manager.cpp) -HOOK_EXPORT HRESULT WINAPI DXGIDumpJournal() -{ - assert(false); - return E_NOTIMPL; -} -HOOK_EXPORT HRESULT WINAPI DXGIReportAdapterConfiguration() -{ - assert(false); - return E_NOTIMPL; -} - -// These are actually called internally by the Direct3D driver on some versions of Windows, so just pass them through -HOOK_EXPORT HRESULT WINAPI DXGID3D10CreateDevice(HMODULE hModule, IDXGIFactory *pFactory, IDXGIAdapter *pAdapter, UINT Flags, void *pUnknown, void **ppDevice) -{ - return reshade::hooks::call(DXGID3D10CreateDevice)(hModule, pFactory, pAdapter, Flags, pUnknown, ppDevice); -} -HOOK_EXPORT HRESULT WINAPI DXGID3D10CreateLayeredDevice(void *pUnknown1, void *pUnknown2, void *pUnknown3, void *pUnknown4, void *pUnknown5) -{ - return reshade::hooks::call(DXGID3D10CreateLayeredDevice)(pUnknown1, pUnknown2, pUnknown3, pUnknown4, pUnknown5); -} -HOOK_EXPORT SIZE_T WINAPI DXGID3D10GetLayeredDeviceSize(const void *pLayers, UINT NumLayers) -{ - return reshade::hooks::call(DXGID3D10GetLayeredDeviceSize)(pLayers, NumLayers); -} -HOOK_EXPORT HRESULT WINAPI DXGID3D10RegisterLayers(const void *pLayers, UINT NumLayers) -{ - return reshade::hooks::call(DXGID3D10RegisterLayers)(pLayers, NumLayers); -} diff --git a/msvc/source/dxgi/dxgi_device.cpp b/msvc/source/dxgi/dxgi_device.cpp deleted file mode 100644 index 5184b33..0000000 --- a/msvc/source/dxgi/dxgi_device.cpp +++ /dev/null @@ -1,178 +0,0 @@ -/** - * Copyright (C) 2014 Patrick Mours. All rights reserved. - * License: https://github.com/crosire/reshade#license - */ - -#include "log.hpp" -#include "dxgi_device.hpp" -#include "dxgi_swapchain.hpp" -#include - -DXGIDevice::DXGIDevice(IDXGIDevice1 *original, IUnknown *direct3d_device) : - _orig(original), - _interface_version(1), - _direct3d_device(direct3d_device) { - assert(original != nullptr); -} - -bool DXGIDevice::check_and_upgrade_interface(REFIID riid) -{ - static const IID iid_lookup[] = { - __uuidof(IDXGIDevice ), - __uuidof(IDXGIDevice1), - __uuidof(IDXGIDevice2), - __uuidof(IDXGIDevice3), - __uuidof(IDXGIDevice4), - }; - - for (unsigned int new_version = _interface_version + 1; new_version < ARRAYSIZE(iid_lookup); ++new_version) - { - if (riid == iid_lookup[new_version]) - { - IUnknown *new_interface = nullptr; - if (FAILED(_orig->QueryInterface(riid, reinterpret_cast(&new_interface)))) - return false; -#if RESHADE_VERBOSE_LOG - LOG(DEBUG) << "Upgraded IDXGIDevice" << _interface_version << " object " << this << " to IDXGIDevice" << new_version << '.'; -#endif - _orig->Release(); - _orig = static_cast(new_interface); - _interface_version = new_version; - break; - } - } - - return true; -} - -HRESULT STDMETHODCALLTYPE DXGIDevice::QueryInterface(REFIID riid, void **ppvObj) -{ - if (ppvObj == nullptr) - return E_POINTER; - - if (riid == __uuidof(this) || - riid == __uuidof(IUnknown) || // This is the IID_IUnknown identity object - riid == __uuidof(IDXGIObject) || - riid == __uuidof(IDXGIDevice) || - riid == __uuidof(IDXGIDevice1) || - riid == __uuidof(IDXGIDevice2) || - riid == __uuidof(IDXGIDevice3) || - riid == __uuidof(IDXGIDevice4)) - { - if (!check_and_upgrade_interface(riid)) - return E_NOINTERFACE; - - AddRef(); - *ppvObj = this; - return S_OK; - } - - return _direct3d_device->QueryInterface(riid, ppvObj); -} -ULONG STDMETHODCALLTYPE DXGIDevice::AddRef() -{ - ++_ref; - - return _orig->AddRef(); -} -ULONG STDMETHODCALLTYPE DXGIDevice::Release() -{ - --_ref; - - const ULONG ref = _orig->Release(); - - // The D3D device still holds a reference, so check reference count against one instead of zero - if (ref > 1 && _ref != 0) - return ref; - else if (ref != 1) - LOG(WARN) << "Reference count for IDXGIDevice" << _interface_version << " object " << this << " is inconsistent: " << ref << ", but expected 1."; - - assert(_ref <= 0); -#if RESHADE_VERBOSE_LOG - LOG(DEBUG) << "Destroyed IDXGIDevice" << _interface_version << " object " << this << '.'; -#endif - delete this; - return 0; -} - -HRESULT STDMETHODCALLTYPE DXGIDevice::SetPrivateData(REFGUID Name, UINT DataSize, const void *pData) -{ - return _orig->SetPrivateData(Name, DataSize, pData); -} -HRESULT STDMETHODCALLTYPE DXGIDevice::SetPrivateDataInterface(REFGUID Name, const IUnknown *pUnknown) -{ - return _orig->SetPrivateDataInterface(Name, pUnknown); -} -HRESULT STDMETHODCALLTYPE DXGIDevice::GetPrivateData(REFGUID Name, UINT *pDataSize, void *pData) -{ - return _orig->GetPrivateData(Name, pDataSize, pData); -} -HRESULT STDMETHODCALLTYPE DXGIDevice::GetParent(REFIID riid, void **ppParent) -{ - return _orig->GetParent(riid, ppParent); -} - -HRESULT STDMETHODCALLTYPE DXGIDevice::GetAdapter(IDXGIAdapter **pAdapter) -{ - return _orig->GetAdapter(pAdapter); -} -HRESULT STDMETHODCALLTYPE DXGIDevice::CreateSurface(const DXGI_SURFACE_DESC *pDesc, UINT NumSurfaces, DXGI_USAGE Usage, const DXGI_SHARED_RESOURCE *pSharedResource, IDXGISurface **ppSurface) -{ - return _orig->CreateSurface(pDesc, NumSurfaces, Usage, pSharedResource, ppSurface); -} -HRESULT STDMETHODCALLTYPE DXGIDevice::QueryResourceResidency(IUnknown *const *ppResources, DXGI_RESIDENCY *pResidencyStatus, UINT NumResources) -{ - return _orig->QueryResourceResidency(ppResources, pResidencyStatus, NumResources); -} -HRESULT STDMETHODCALLTYPE DXGIDevice::SetGPUThreadPriority(INT Priority) -{ - return _orig->SetGPUThreadPriority(Priority); -} -HRESULT STDMETHODCALLTYPE DXGIDevice::GetGPUThreadPriority(INT *pPriority) -{ - return _orig->GetGPUThreadPriority(pPriority); -} - -HRESULT STDMETHODCALLTYPE DXGIDevice::SetMaximumFrameLatency(UINT MaxLatency) -{ - assert(_interface_version >= 1); - return _orig->SetMaximumFrameLatency(MaxLatency); -} -HRESULT STDMETHODCALLTYPE DXGIDevice::GetMaximumFrameLatency(UINT *pMaxLatency) -{ - assert(_interface_version >= 1); - return _orig->GetMaximumFrameLatency(pMaxLatency); -} - -HRESULT STDMETHODCALLTYPE DXGIDevice::OfferResources(UINT NumResources, IDXGIResource *const *ppResources, DXGI_OFFER_RESOURCE_PRIORITY Priority) -{ - assert(_interface_version >= 2); - return static_cast(_orig)->OfferResources(NumResources, ppResources, Priority); -} -HRESULT STDMETHODCALLTYPE DXGIDevice::ReclaimResources(UINT NumResources, IDXGIResource *const *ppResources, BOOL *pDiscarded) -{ - assert(_interface_version >= 2); - return static_cast(_orig)->ReclaimResources(NumResources, ppResources, pDiscarded); -} -HRESULT STDMETHODCALLTYPE DXGIDevice::EnqueueSetEvent(HANDLE hEvent) -{ - assert(_interface_version >= 2); - return static_cast(_orig)->EnqueueSetEvent(hEvent); -} - -void STDMETHODCALLTYPE DXGIDevice::Trim() -{ - assert(_interface_version >= 3); - return static_cast(_orig)->Trim(); -} - -HRESULT STDMETHODCALLTYPE DXGIDevice::OfferResources1(UINT NumResources, IDXGIResource *const *ppResources, DXGI_OFFER_RESOURCE_PRIORITY Priority, UINT Flags) -{ - assert(_interface_version >= 4); - return static_cast(_orig)->OfferResources1(NumResources, ppResources, Priority, Flags); -} -HRESULT STDMETHODCALLTYPE DXGIDevice::ReclaimResources1(UINT NumResources, IDXGIResource *const *ppResources, DXGI_RECLAIM_RESOURCE_RESULTS *pResults) -{ - assert(_interface_version >= 4); - return static_cast(_orig)->ReclaimResources1(NumResources, ppResources, pResults); -} diff --git a/msvc/source/dxgi/dxgi_device.hpp b/msvc/source/dxgi/dxgi_device.hpp deleted file mode 100644 index d37ed43..0000000 --- a/msvc/source/dxgi/dxgi_device.hpp +++ /dev/null @@ -1,57 +0,0 @@ -/** - * Copyright (C) 2014 Patrick Mours. All rights reserved. - * License: https://github.com/crosire/reshade#license - */ - -#pragma once - -#include - -struct __declspec(uuid("CB285C3B-3677-4332-98C7-D6339B9782B1")) DXGIDevice : IDXGIDevice4 -{ - DXGIDevice(IDXGIDevice1 *original, IUnknown *direct3d_device); - - DXGIDevice(const DXGIDevice &) = delete; - DXGIDevice &operator=(const DXGIDevice &) = delete; - - HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void **ppvObj) override; - ULONG STDMETHODCALLTYPE AddRef() override; - ULONG STDMETHODCALLTYPE Release() override; - - #pragma region IDXGIObject - HRESULT STDMETHODCALLTYPE SetPrivateData(REFGUID Name, UINT DataSize, const void *pData) override; - HRESULT STDMETHODCALLTYPE SetPrivateDataInterface(REFGUID Name, const IUnknown *pUnknown) override; - HRESULT STDMETHODCALLTYPE GetPrivateData(REFGUID Name, UINT *pDataSize, void *pData) override; - HRESULT STDMETHODCALLTYPE GetParent(REFIID riid, void **ppParent) override; - #pragma endregion - #pragma region IDXGIDevice - HRESULT STDMETHODCALLTYPE GetAdapter(IDXGIAdapter **pAdapter) override; - HRESULT STDMETHODCALLTYPE CreateSurface(const DXGI_SURFACE_DESC *pDesc, UINT NumSurfaces, DXGI_USAGE Usage, const DXGI_SHARED_RESOURCE *pSharedResource, IDXGISurface **ppSurface) override; - HRESULT STDMETHODCALLTYPE QueryResourceResidency(IUnknown *const *ppResources, DXGI_RESIDENCY *pResidencyStatus, UINT NumResources) override; - HRESULT STDMETHODCALLTYPE SetGPUThreadPriority(INT Priority) override; - HRESULT STDMETHODCALLTYPE GetGPUThreadPriority(INT *pPriority) override; - #pragma endregion - #pragma region IDXGIDevice1 - HRESULT STDMETHODCALLTYPE SetMaximumFrameLatency(UINT MaxLatency) override; - HRESULT STDMETHODCALLTYPE GetMaximumFrameLatency(UINT *pMaxLatency) override; - #pragma endregion - #pragma region IDXGIDevice2 - HRESULT STDMETHODCALLTYPE OfferResources(UINT NumResources, IDXGIResource *const *ppResources, DXGI_OFFER_RESOURCE_PRIORITY Priority) override; - HRESULT STDMETHODCALLTYPE ReclaimResources(UINT NumResources, IDXGIResource *const *ppResources, BOOL *pDiscarded) override; - HRESULT STDMETHODCALLTYPE EnqueueSetEvent(HANDLE hEvent) override; - #pragma endregion - #pragma region IDXGIDevice3 - void STDMETHODCALLTYPE Trim() override; - #pragma endregion - #pragma region IDXGIDevice4 - HRESULT STDMETHODCALLTYPE OfferResources1(UINT NumResources, IDXGIResource *const *ppResources, DXGI_OFFER_RESOURCE_PRIORITY Priority, UINT Flags) override; - HRESULT STDMETHODCALLTYPE ReclaimResources1(UINT NumResources, IDXGIResource *const *ppResources, DXGI_RECLAIM_RESOURCE_RESULTS *pResults) override; - #pragma endregion - - bool check_and_upgrade_interface(REFIID riid); - - LONG _ref = 1; - IDXGIDevice1 *_orig; - unsigned int _interface_version; - IUnknown *const _direct3d_device; -}; diff --git a/msvc/source/dxgi/dxgi_swapchain.cpp b/msvc/source/dxgi/dxgi_swapchain.cpp deleted file mode 100644 index 26845b5..0000000 --- a/msvc/source/dxgi/dxgi_swapchain.cpp +++ /dev/null @@ -1,479 +0,0 @@ -/** - * Copyright (C) 2014 Patrick Mours. All rights reserved. - * License: https://github.com/crosire/reshade#license - */ - -#include "log.hpp" -#include "dxgi_swapchain.hpp" -#include "d3d10/d3d10_device.hpp" -#include "d3d10/runtime_d3d10.hpp" -#include "d3d11/d3d11_device.hpp" -#include "d3d11/d3d11_device_context.hpp" -#include "d3d11/runtime_d3d11.hpp" -#include "d3d12/d3d12_device.hpp" -#include "d3d12/d3d12_command_queue.hpp" -#include "d3d12/runtime_d3d12.hpp" -#include - -DXGISwapChain::DXGISwapChain(D3D10Device *device, IDXGISwapChain *original, const std::shared_ptr &runtime) : - _orig(original), - _interface_version(0), - _direct3d_device(device, false), // Explicitly add a reference to the device - _direct3d_version(10), - _runtime(runtime) {} -DXGISwapChain::DXGISwapChain(D3D10Device *device, IDXGISwapChain1 *original, const std::shared_ptr &runtime) : - _orig(original), - _interface_version(1), - _direct3d_device(device, false), - _direct3d_version(10), - _runtime(runtime) {} -DXGISwapChain::DXGISwapChain(D3D11Device *device, IDXGISwapChain *original, const std::shared_ptr &runtime) : - _orig(original), - _interface_version(0), - _direct3d_device(device, false), - _direct3d_version(11), - _runtime(runtime) {} -DXGISwapChain::DXGISwapChain(D3D11Device *device, IDXGISwapChain1 *original, const std::shared_ptr &runtime) : - _orig(original), - _interface_version(1), - _direct3d_device(device, false), - _direct3d_version(11), - _runtime(runtime) {} -DXGISwapChain::DXGISwapChain(D3D12Device *device, IDXGISwapChain3 *original, const std::shared_ptr &runtime) : - _orig(original), - _interface_version(3), - _direct3d_device(device, false), - _direct3d_version(12), - _runtime(runtime) {} - -void DXGISwapChain::perform_present(UINT PresentFlags) -{ - // Some D3D11 games test presentation for timing and composition purposes. - // These calls are not rendering related, but rather a status request for the D3D runtime and as such should be ignored. - if (PresentFlags & DXGI_PRESENT_TEST) - return; - - assert(_runtime != nullptr); - - switch (_direct3d_version) - { - case 10: { - const auto device = static_cast(_direct3d_device.get()); - std::static_pointer_cast(_runtime)->on_present(device->_draw_call_tracker); - device->clear_drawcall_stats(); - break; } - case 11: { - const auto device = static_cast(_direct3d_device.get()); - std::static_pointer_cast(_runtime)->on_present(device->_immediate_context->_draw_call_tracker); - device->clear_drawcall_stats(); - break; } - case 12: - std::static_pointer_cast(_runtime)->on_present(); - break; - } -} - -bool DXGISwapChain::check_and_upgrade_interface(REFIID riid) -{ - static const IID iid_lookup[] = { - __uuidof(IDXGISwapChain), - __uuidof(IDXGISwapChain1), - __uuidof(IDXGISwapChain2), - __uuidof(IDXGISwapChain3), - __uuidof(IDXGISwapChain4), - }; - - for (unsigned int new_version = _interface_version + 1; new_version < ARRAYSIZE(iid_lookup); ++new_version) - { - if (riid == iid_lookup[new_version]) - { - IUnknown *new_interface = nullptr; - if (FAILED(_orig->QueryInterface(riid, reinterpret_cast(&new_interface)))) - return false; - _orig->Release(); - _orig = static_cast(new_interface); - _interface_version = new_version; -#if RESHADE_VERBOSE_LOG - LOG(DEBUG) << "Upgraded IDXGISwapChain" << _interface_version << " object " << this << " to IDXGISwapChain" << new_version << '.'; -#endif - break; - } - } - - return true; -} - -HRESULT STDMETHODCALLTYPE DXGISwapChain::QueryInterface(REFIID riid, void **ppvObj) -{ - if (ppvObj == nullptr) - return E_POINTER; - - if (riid == __uuidof(this) || - riid == __uuidof(IUnknown) || - riid == __uuidof(IDXGIObject) || - riid == __uuidof(IDXGIDeviceSubObject) || - riid == __uuidof(IDXGISwapChain) || - riid == __uuidof(IDXGISwapChain1) || - riid == __uuidof(IDXGISwapChain2) || - riid == __uuidof(IDXGISwapChain3) || - riid == __uuidof(IDXGISwapChain4)) - { - if (!check_and_upgrade_interface(riid)) - return E_NOINTERFACE; - - AddRef(); - *ppvObj = this; - return S_OK; - } - - return _orig->QueryInterface(riid, ppvObj); -} -ULONG STDMETHODCALLTYPE DXGISwapChain::AddRef() -{ - ++_ref; - - return _orig->AddRef(); -} -ULONG STDMETHODCALLTYPE DXGISwapChain::Release() -{ - if (--_ref == 0) - { - switch (_direct3d_version) - { - case 10: { - assert(_runtime != nullptr); - const auto device = static_cast(_direct3d_device.get()); - const auto runtime = std::static_pointer_cast(_runtime); - runtime->on_reset(); - device->clear_drawcall_stats(); - device->_runtimes.erase(std::remove(device->_runtimes.begin(), device->_runtimes.end(), runtime), device->_runtimes.end()); - break; } - case 11: { - assert(_runtime != nullptr); - const auto device = static_cast(_direct3d_device.get()); - const auto runtime = std::static_pointer_cast(_runtime); - runtime->on_reset(); - device->clear_drawcall_stats(); // Release any live references to depth buffers etc. - device->_runtimes.erase(std::remove(device->_runtimes.begin(), device->_runtimes.end(), runtime), device->_runtimes.end()); - break; } - case 12: { - const auto runtime = std::static_pointer_cast(_runtime); - runtime->on_reset(); - break; } - } - - _runtime.reset(); - _direct3d_device.reset(); - } - - const ULONG ref = _orig->Release(); - - if (ref != 0 && _ref != 0) - return ref; - else if (ref != 0) - LOG(WARN) << "Reference count for IDXGISwapChain" << _interface_version << " object " << this << " is inconsistent: " << ref << ", but expected 0."; - - assert(_ref <= 0); -#if RESHADE_VERBOSE_LOG - LOG(DEBUG) << "Destroyed IDXGISwapChain" << _interface_version << " object " << this << '.'; -#endif - delete this; - return 0; -} - -HRESULT STDMETHODCALLTYPE DXGISwapChain::SetPrivateData(REFGUID Name, UINT DataSize, const void *pData) -{ - return _orig->SetPrivateData(Name, DataSize, pData); -} -HRESULT STDMETHODCALLTYPE DXGISwapChain::SetPrivateDataInterface(REFGUID Name, const IUnknown *pUnknown) -{ - return _orig->SetPrivateDataInterface(Name, pUnknown); -} -HRESULT STDMETHODCALLTYPE DXGISwapChain::GetPrivateData(REFGUID Name, UINT *pDataSize, void *pData) -{ - return _orig->GetPrivateData(Name, pDataSize, pData); -} -HRESULT STDMETHODCALLTYPE DXGISwapChain::GetParent(REFIID riid, void **ppParent) -{ - return _orig->GetParent(riid, ppParent); -} - -HRESULT STDMETHODCALLTYPE DXGISwapChain::GetDevice(REFIID riid, void **ppDevice) -{ - return _direct3d_device->QueryInterface(riid, ppDevice); -} - -HRESULT STDMETHODCALLTYPE DXGISwapChain::Present(UINT SyncInterval, UINT Flags) -{ - perform_present(Flags); - return _orig->Present(SyncInterval, Flags); -} -HRESULT STDMETHODCALLTYPE DXGISwapChain::GetBuffer(UINT Buffer, REFIID riid, void **ppSurface) -{ - return _orig->GetBuffer(Buffer, riid, ppSurface); -} -HRESULT STDMETHODCALLTYPE DXGISwapChain::SetFullscreenState(BOOL Fullscreen, IDXGIOutput *pTarget) -{ - LOG(INFO) << "Redirecting IDXGISwapChain::SetFullscreenState" << '(' << this << ", " << (Fullscreen != FALSE ? "TRUE" : "FALSE") << ", " << pTarget << ')' << " ..."; - - return _orig->SetFullscreenState(Fullscreen, pTarget); -} -HRESULT STDMETHODCALLTYPE DXGISwapChain::GetFullscreenState(BOOL *pFullscreen, IDXGIOutput **ppTarget) -{ - return _orig->GetFullscreenState(pFullscreen, ppTarget); -} -HRESULT STDMETHODCALLTYPE DXGISwapChain::GetDesc(DXGI_SWAP_CHAIN_DESC *pDesc) -{ - return _orig->GetDesc(pDesc); -} -HRESULT STDMETHODCALLTYPE DXGISwapChain::ResizeBuffers(UINT BufferCount, UINT Width, UINT Height, DXGI_FORMAT NewFormat, UINT SwapChainFlags) -{ - LOG(INFO) << "Redirecting IDXGISwapChain::ResizeBuffers" << '(' << this << ", " << BufferCount << ", " << Width << ", " << Height << ", " << NewFormat << ", " << std::hex << SwapChainFlags << std::dec << ')' << " ..."; - - switch (_direct3d_version) - { - case 10: - assert(_runtime != nullptr); - static_cast(_direct3d_device.get())->clear_drawcall_stats(); - std::static_pointer_cast(_runtime)->on_reset(); - break; - case 11: - assert(_runtime != nullptr); - static_cast(_direct3d_device.get())->clear_drawcall_stats(); - std::static_pointer_cast(_runtime)->on_reset(); - break; - case 12: - assert(_runtime != nullptr); - std::static_pointer_cast(_runtime)->on_reset(); - break; - } - - const HRESULT hr = _orig->ResizeBuffers(BufferCount, Width, Height, NewFormat, SwapChainFlags); - - if (hr == DXGI_ERROR_INVALID_CALL) // Ignore invalid call errors since the device is still in a usable state afterwards - { - LOG(WARN) << "> IDXGISwapChain::ResizeBuffers failed with 'DXGI_ERROR_INVALID_CALL'!"; - } - else if (FAILED(hr)) - { - LOG(ERROR) << "> IDXGISwapChain::ResizeBuffers failed with error code " << std::hex << hr << std::dec << '!'; - return hr; - } - - DXGI_SWAP_CHAIN_DESC desc; - _orig->GetDesc(&desc); - - bool initialized = false; - switch (_direct3d_version) - { - case 10: - assert(_runtime != nullptr); - initialized = std::static_pointer_cast(_runtime)->on_init(desc); - break; - case 11: - assert(_runtime != nullptr); - initialized = std::static_pointer_cast(_runtime)->on_init(desc); - break; - case 12: - assert(_runtime != nullptr); - initialized = std::static_pointer_cast(_runtime)->on_init(desc); - break; - } - - if (!initialized) - LOG(ERROR) << "Failed to recreate Direct3D " << _direct3d_version << " runtime environment on runtime " << _runtime.get() << '.'; - - return hr; -} -HRESULT STDMETHODCALLTYPE DXGISwapChain::ResizeTarget(const DXGI_MODE_DESC *pNewTargetParameters) -{ - return _orig->ResizeTarget(pNewTargetParameters); -} -HRESULT STDMETHODCALLTYPE DXGISwapChain::GetContainingOutput(IDXGIOutput **ppOutput) -{ - return _orig->GetContainingOutput(ppOutput); -} -HRESULT STDMETHODCALLTYPE DXGISwapChain::GetFrameStatistics(DXGI_FRAME_STATISTICS *pStats) -{ - return _orig->GetFrameStatistics(pStats); -} -HRESULT STDMETHODCALLTYPE DXGISwapChain::GetLastPresentCount(UINT *pLastPresentCount) -{ - return _orig->GetLastPresentCount(pLastPresentCount); -} - -HRESULT STDMETHODCALLTYPE DXGISwapChain::GetDesc1(DXGI_SWAP_CHAIN_DESC1 *pDesc) -{ - assert(_interface_version >= 1); - return static_cast(_orig)->GetDesc1(pDesc); -} -HRESULT STDMETHODCALLTYPE DXGISwapChain::GetFullscreenDesc(DXGI_SWAP_CHAIN_FULLSCREEN_DESC *pDesc) -{ - assert(_interface_version >= 1); - return static_cast(_orig)->GetFullscreenDesc(pDesc); -} -HRESULT STDMETHODCALLTYPE DXGISwapChain::GetHwnd(HWND *pHwnd) -{ - assert(_interface_version >= 1); - return static_cast(_orig)->GetHwnd(pHwnd); -} -HRESULT STDMETHODCALLTYPE DXGISwapChain::GetCoreWindow(REFIID refiid, void **ppUnk) -{ - assert(_interface_version >= 1); - return static_cast(_orig)->GetCoreWindow(refiid, ppUnk); -} -HRESULT STDMETHODCALLTYPE DXGISwapChain::Present1(UINT SyncInterval, UINT PresentFlags, const DXGI_PRESENT_PARAMETERS *pPresentParameters) -{ - assert(_interface_version >= 1); - perform_present(PresentFlags); - return static_cast(_orig)->Present1(SyncInterval, PresentFlags, pPresentParameters); -} -BOOL STDMETHODCALLTYPE DXGISwapChain::IsTemporaryMonoSupported() -{ - assert(_interface_version >= 1); - return static_cast(_orig)->IsTemporaryMonoSupported(); -} -HRESULT STDMETHODCALLTYPE DXGISwapChain::GetRestrictToOutput(IDXGIOutput **ppRestrictToOutput) -{ - assert(_interface_version >= 1); - return static_cast(_orig)->GetRestrictToOutput(ppRestrictToOutput); -} -HRESULT STDMETHODCALLTYPE DXGISwapChain::SetBackgroundColor(const DXGI_RGBA *pColor) -{ - assert(_interface_version >= 1); - return static_cast(_orig)->SetBackgroundColor(pColor); -} -HRESULT STDMETHODCALLTYPE DXGISwapChain::GetBackgroundColor(DXGI_RGBA *pColor) -{ - assert(_interface_version >= 1); - return static_cast(_orig)->GetBackgroundColor(pColor); -} -HRESULT STDMETHODCALLTYPE DXGISwapChain::SetRotation(DXGI_MODE_ROTATION Rotation) -{ - assert(_interface_version >= 1); - return static_cast(_orig)->SetRotation(Rotation); -} -HRESULT STDMETHODCALLTYPE DXGISwapChain::GetRotation(DXGI_MODE_ROTATION *pRotation) -{ - assert(_interface_version >= 1); - return static_cast(_orig)->GetRotation(pRotation); -} - -HRESULT STDMETHODCALLTYPE DXGISwapChain::SetSourceSize(UINT Width, UINT Height) -{ - assert(_interface_version >= 2); - return static_cast(_orig)->SetSourceSize(Width, Height); -} -HRESULT STDMETHODCALLTYPE DXGISwapChain::GetSourceSize(UINT *pWidth, UINT *pHeight) -{ - assert(_interface_version >= 2); - return static_cast(_orig)->GetSourceSize(pWidth, pHeight); -} -HRESULT STDMETHODCALLTYPE DXGISwapChain::SetMaximumFrameLatency(UINT MaxLatency) -{ - assert(_interface_version >= 2); - return static_cast(_orig)->SetMaximumFrameLatency(MaxLatency); -} -HRESULT STDMETHODCALLTYPE DXGISwapChain::GetMaximumFrameLatency(UINT *pMaxLatency) -{ - assert(_interface_version >= 2); - return static_cast(_orig)->GetMaximumFrameLatency(pMaxLatency); -} -HANDLE STDMETHODCALLTYPE DXGISwapChain::GetFrameLatencyWaitableObject() -{ - assert(_interface_version >= 2); - return static_cast(_orig)->GetFrameLatencyWaitableObject(); -} -HRESULT STDMETHODCALLTYPE DXGISwapChain::SetMatrixTransform(const DXGI_MATRIX_3X2_F *pMatrix) -{ - assert(_interface_version >= 2); - return static_cast(_orig)->SetMatrixTransform(pMatrix); -} -HRESULT STDMETHODCALLTYPE DXGISwapChain::GetMatrixTransform(DXGI_MATRIX_3X2_F *pMatrix) -{ - assert(_interface_version >= 2); - return static_cast(_orig)->GetMatrixTransform(pMatrix); -} - -UINT STDMETHODCALLTYPE DXGISwapChain::GetCurrentBackBufferIndex() -{ - assert(_interface_version >= 3); - return static_cast(_orig)->GetCurrentBackBufferIndex(); -} -HRESULT STDMETHODCALLTYPE DXGISwapChain::CheckColorSpaceSupport(DXGI_COLOR_SPACE_TYPE ColorSpace, UINT *pColorSpaceSupport) -{ - assert(_interface_version >= 3); - return static_cast(_orig)->CheckColorSpaceSupport(ColorSpace, pColorSpaceSupport); -} -HRESULT STDMETHODCALLTYPE DXGISwapChain::SetColorSpace1(DXGI_COLOR_SPACE_TYPE ColorSpace) -{ - assert(_interface_version >= 3); - return static_cast(_orig)->SetColorSpace1(ColorSpace); -} -HRESULT STDMETHODCALLTYPE DXGISwapChain::ResizeBuffers1(UINT BufferCount, UINT Width, UINT Height, DXGI_FORMAT Format, UINT SwapChainFlags, const UINT *pCreationNodeMask, IUnknown *const *ppPresentQueue) -{ - assert(_interface_version >= 3); - - LOG(INFO) << "Redirecting IDXGISwapChain3::ResizeBuffers1" << '(' << this << ", " << BufferCount << ", " << Width << ", " << Height << ", " << Format << ", " << std::hex << SwapChainFlags << std::dec << ", " << pCreationNodeMask << ", " << ppPresentQueue << ')' << " ..."; - - switch (_direct3d_version) - { - case 10: - assert(_runtime != nullptr); - static_cast(_direct3d_device.get())->clear_drawcall_stats(); - std::static_pointer_cast(_runtime)->on_reset(); - break; - case 11: - assert(_runtime != nullptr); - static_cast(_direct3d_device.get())->clear_drawcall_stats(); - std::static_pointer_cast(_runtime)->on_reset(); - break; - case 12: - assert(_runtime != nullptr); - std::static_pointer_cast(_runtime)->on_reset(); - break; - } - - const HRESULT hr = static_cast(_orig)->ResizeBuffers1(BufferCount, Width, Height, Format, SwapChainFlags, pCreationNodeMask, ppPresentQueue); - - if (hr == DXGI_ERROR_INVALID_CALL) - { - LOG(WARN) << "> IDXGISwapChain3::ResizeBuffers1 failed with 'DXGI_ERROR_INVALID_CALL'!"; - } - else if (FAILED(hr)) - { - LOG(ERROR) << "> IDXGISwapChain3::ResizeBuffers1 failed with error code " << std::hex << hr << std::dec << '!'; - return hr; - } - - DXGI_SWAP_CHAIN_DESC desc; - _orig->GetDesc(&desc); - - bool initialized = false; - switch (_direct3d_version) - { - case 10: - assert(_runtime != nullptr); - initialized = std::static_pointer_cast(_runtime)->on_init(desc); - break; - case 11: - assert(_runtime != nullptr); - initialized = std::static_pointer_cast(_runtime)->on_init(desc); - break; - case 12: - assert(_runtime != nullptr); - initialized = std::static_pointer_cast(_runtime)->on_init(desc); - break; - } - - if (!initialized) - LOG(ERROR) << "Failed to recreate Direct3D " << _direct3d_version << " runtime environment on runtime " << _runtime.get() << '.'; - - return hr; -} - -HRESULT STDMETHODCALLTYPE DXGISwapChain::SetHDRMetaData(DXGI_HDR_METADATA_TYPE Type, UINT Size, void *pMetaData) -{ - assert(_interface_version >= 4); - return static_cast(_orig)->SetHDRMetaData(Type, Size, pMetaData); -} diff --git a/msvc/source/dxgi/dxgi_swapchain.hpp b/msvc/source/dxgi/dxgi_swapchain.hpp deleted file mode 100644 index 4f411f3..0000000 --- a/msvc/source/dxgi/dxgi_swapchain.hpp +++ /dev/null @@ -1,95 +0,0 @@ -/** - * Copyright (C) 2014 Patrick Mours. All rights reserved. - * License: https://github.com/crosire/reshade#license - */ - -#pragma once - -#include -#include "com_ptr.hpp" -#include - -struct D3D10Device; -struct D3D11Device; -struct D3D12Device; -namespace reshade { class runtime; } - -struct __declspec(uuid("1F445F9F-9887-4C4C-9055-4E3BADAFCCA8")) DXGISwapChain : IDXGISwapChain4 -{ - DXGISwapChain(D3D10Device *device, IDXGISwapChain *original, const std::shared_ptr &runtime); - DXGISwapChain(D3D10Device *device, IDXGISwapChain1 *original, const std::shared_ptr &runtime); - DXGISwapChain(D3D11Device *device, IDXGISwapChain *original, const std::shared_ptr &runtime); - DXGISwapChain(D3D11Device *device, IDXGISwapChain1 *original, const std::shared_ptr &runtime); - DXGISwapChain(D3D12Device *device, IDXGISwapChain3 *original, const std::shared_ptr &runtime); - - DXGISwapChain(const DXGISwapChain &) = delete; - DXGISwapChain &operator=(const DXGISwapChain &) = delete; - - HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void **ppvObj) override; - ULONG STDMETHODCALLTYPE AddRef() override; - ULONG STDMETHODCALLTYPE Release() override; - - #pragma region IDXGIObject - HRESULT STDMETHODCALLTYPE SetPrivateData(REFGUID Name, UINT DataSize, const void *pData) override; - HRESULT STDMETHODCALLTYPE SetPrivateDataInterface(REFGUID Name, const IUnknown *pUnknown) override; - HRESULT STDMETHODCALLTYPE GetPrivateData(REFGUID Name, UINT *pDataSize, void *pData) override; - HRESULT STDMETHODCALLTYPE GetParent(REFIID riid, void **ppParent) override; - #pragma endregion - #pragma region IDXGIDeviceSubObject - HRESULT STDMETHODCALLTYPE GetDevice(REFIID riid, void **ppDevice) override; - #pragma endregion - #pragma region IDXGISwapChain - HRESULT STDMETHODCALLTYPE Present(UINT SyncInterval, UINT Flags) override; - HRESULT STDMETHODCALLTYPE GetBuffer(UINT Buffer, REFIID riid, void **ppSurface) override; - HRESULT STDMETHODCALLTYPE SetFullscreenState(BOOL Fullscreen, IDXGIOutput *pTarget) override; - HRESULT STDMETHODCALLTYPE GetFullscreenState(BOOL *pFullscreen, IDXGIOutput **ppTarget) override; - HRESULT STDMETHODCALLTYPE GetDesc(DXGI_SWAP_CHAIN_DESC *pDesc) override; - HRESULT STDMETHODCALLTYPE ResizeBuffers(UINT BufferCount, UINT Width, UINT Height, DXGI_FORMAT NewFormat, UINT SwapChainFlags) override; - HRESULT STDMETHODCALLTYPE ResizeTarget(const DXGI_MODE_DESC *pNewTargetParameters) override; - HRESULT STDMETHODCALLTYPE GetContainingOutput(IDXGIOutput **ppOutput) override; - HRESULT STDMETHODCALLTYPE GetFrameStatistics(DXGI_FRAME_STATISTICS *pStats) override; - HRESULT STDMETHODCALLTYPE GetLastPresentCount(UINT *pLastPresentCount) override; - #pragma endregion - #pragma region IDXGISwapChain1 - HRESULT STDMETHODCALLTYPE GetDesc1(DXGI_SWAP_CHAIN_DESC1 *pDesc) override; - HRESULT STDMETHODCALLTYPE GetFullscreenDesc(DXGI_SWAP_CHAIN_FULLSCREEN_DESC *pDesc) override; - HRESULT STDMETHODCALLTYPE GetHwnd(HWND *pHwnd) override; - HRESULT STDMETHODCALLTYPE GetCoreWindow(REFIID refiid, void **ppUnk) override; - HRESULT STDMETHODCALLTYPE Present1(UINT SyncInterval, UINT PresentFlags, const DXGI_PRESENT_PARAMETERS *pPresentParameters) override; - BOOL STDMETHODCALLTYPE IsTemporaryMonoSupported() override; - HRESULT STDMETHODCALLTYPE GetRestrictToOutput(IDXGIOutput **ppRestrictToOutput) override; - HRESULT STDMETHODCALLTYPE SetBackgroundColor(const DXGI_RGBA *pColor) override; - HRESULT STDMETHODCALLTYPE GetBackgroundColor(DXGI_RGBA *pColor) override; - HRESULT STDMETHODCALLTYPE SetRotation(DXGI_MODE_ROTATION Rotation) override; - HRESULT STDMETHODCALLTYPE GetRotation(DXGI_MODE_ROTATION *pRotation) override; - #pragma endregion - #pragma region IDXGISwapChain2 - HRESULT STDMETHODCALLTYPE SetSourceSize(UINT Width, UINT Height) override; - HRESULT STDMETHODCALLTYPE GetSourceSize(UINT *pWidth, UINT *pHeight) override; - HRESULT STDMETHODCALLTYPE SetMaximumFrameLatency(UINT MaxLatency) override; - HRESULT STDMETHODCALLTYPE GetMaximumFrameLatency(UINT *pMaxLatency) override; - HANDLE STDMETHODCALLTYPE GetFrameLatencyWaitableObject() override; - HRESULT STDMETHODCALLTYPE SetMatrixTransform(const DXGI_MATRIX_3X2_F *pMatrix) override; - HRESULT STDMETHODCALLTYPE GetMatrixTransform(DXGI_MATRIX_3X2_F *pMatrix) override; - #pragma endregion - #pragma region IDXGISwapChain3 - UINT STDMETHODCALLTYPE GetCurrentBackBufferIndex() override; - HRESULT STDMETHODCALLTYPE CheckColorSpaceSupport(DXGI_COLOR_SPACE_TYPE ColorSpace, UINT *pColorSpaceSupport) override; - HRESULT STDMETHODCALLTYPE SetColorSpace1(DXGI_COLOR_SPACE_TYPE ColorSpace) override; - HRESULT STDMETHODCALLTYPE ResizeBuffers1(UINT BufferCount, UINT Width, UINT Height, DXGI_FORMAT Format, UINT SwapChainFlags, const UINT *pCreationNodeMask, IUnknown *const *ppPresentQueue) override; - #pragma endregion - #pragma region IDXGISwapChain4 - HRESULT STDMETHODCALLTYPE SetHDRMetaData(DXGI_HDR_METADATA_TYPE Type, UINT Size, void *pMetaData) override; - #pragma endregion - - void perform_present(UINT PresentFlags); - - bool check_and_upgrade_interface(REFIID riid); - - LONG _ref = 1; - IDXGISwapChain *_orig; - unsigned int _interface_version; - com_ptr _direct3d_device; - const unsigned int _direct3d_version; - std::shared_ptr _runtime; -}; diff --git a/msvc/source/dxgi/format_utils.hpp b/msvc/source/dxgi/format_utils.hpp deleted file mode 100644 index fbbccf2..0000000 --- a/msvc/source/dxgi/format_utils.hpp +++ /dev/null @@ -1,108 +0,0 @@ -/** - * Copyright (C) 2014 Patrick Mours. All rights reserved. - * License: https://github.com/crosire/reshade#license - */ - -#pragma once - -#include - -inline DXGI_FORMAT make_dxgi_format_dsv(DXGI_FORMAT format) -{ - switch (format) - { - case DXGI_FORMAT_R32G8X24_TYPELESS: - return DXGI_FORMAT_D32_FLOAT_S8X24_UINT; - case DXGI_FORMAT_R32_TYPELESS: - return DXGI_FORMAT_D32_FLOAT; - case DXGI_FORMAT_R24G8_TYPELESS: - return DXGI_FORMAT_D24_UNORM_S8_UINT; - case DXGI_FORMAT_R16_TYPELESS: - return DXGI_FORMAT_D16_UNORM; - default: - return format; - } -} - -inline DXGI_FORMAT make_dxgi_format_srgb(DXGI_FORMAT format) -{ - switch (format) - { - case DXGI_FORMAT_R8G8B8A8_TYPELESS: - case DXGI_FORMAT_R8G8B8A8_UNORM: - return DXGI_FORMAT_R8G8B8A8_UNORM_SRGB; - case DXGI_FORMAT_BC1_TYPELESS: - case DXGI_FORMAT_BC1_UNORM: - return DXGI_FORMAT_BC1_UNORM_SRGB; - case DXGI_FORMAT_BC2_TYPELESS: - case DXGI_FORMAT_BC2_UNORM: - return DXGI_FORMAT_BC2_UNORM_SRGB; - case DXGI_FORMAT_BC3_TYPELESS: - case DXGI_FORMAT_BC3_UNORM: - return DXGI_FORMAT_BC3_UNORM_SRGB; - default: - return format; - } -} - -inline DXGI_FORMAT make_dxgi_format_normal(DXGI_FORMAT format) -{ - switch (format) - { - case DXGI_FORMAT_R32G8X24_TYPELESS: - return DXGI_FORMAT_R32_FLOAT_X8X24_TYPELESS; - case DXGI_FORMAT_R8G8B8A8_TYPELESS: - case DXGI_FORMAT_R8G8B8A8_UNORM_SRGB: - return DXGI_FORMAT_R8G8B8A8_UNORM; - case DXGI_FORMAT_R32_TYPELESS: - return DXGI_FORMAT_R32_FLOAT; - case DXGI_FORMAT_R24G8_TYPELESS: - return DXGI_FORMAT_R24_UNORM_X8_TYPELESS; - case DXGI_FORMAT_R16_TYPELESS: - return DXGI_FORMAT_R16_FLOAT; - case DXGI_FORMAT_BC1_TYPELESS: - case DXGI_FORMAT_BC1_UNORM_SRGB: - return DXGI_FORMAT_BC1_UNORM; - case DXGI_FORMAT_BC2_TYPELESS: - case DXGI_FORMAT_BC2_UNORM_SRGB: - return DXGI_FORMAT_BC2_UNORM; - case DXGI_FORMAT_BC3_TYPELESS: - case DXGI_FORMAT_BC3_UNORM_SRGB: - return DXGI_FORMAT_BC3_UNORM; - default: - return format; - } -} - -inline DXGI_FORMAT make_dxgi_format_typeless(DXGI_FORMAT format) -{ - switch (format) - { - case DXGI_FORMAT_D32_FLOAT_S8X24_UINT: - case DXGI_FORMAT_R32_FLOAT_X8X24_TYPELESS: - return DXGI_FORMAT_R32G8X24_TYPELESS; - case DXGI_FORMAT_R8G8B8A8_UNORM: - case DXGI_FORMAT_R8G8B8A8_UNORM_SRGB: - return DXGI_FORMAT_R8G8B8A8_TYPELESS; - case DXGI_FORMAT_D32_FLOAT: - case DXGI_FORMAT_R32_FLOAT: - return DXGI_FORMAT_R32_TYPELESS; - case DXGI_FORMAT_D24_UNORM_S8_UINT: - case DXGI_FORMAT_R24_UNORM_X8_TYPELESS: - return DXGI_FORMAT_R24G8_TYPELESS; - case DXGI_FORMAT_D16_UNORM: - case DXGI_FORMAT_R16_FLOAT: - return DXGI_FORMAT_R16_TYPELESS; - case DXGI_FORMAT_BC1_UNORM: - case DXGI_FORMAT_BC1_UNORM_SRGB: - return DXGI_FORMAT_BC1_TYPELESS; - case DXGI_FORMAT_BC2_UNORM: - case DXGI_FORMAT_BC2_UNORM_SRGB: - return DXGI_FORMAT_BC2_TYPELESS; - case DXGI_FORMAT_BC3_UNORM: - case DXGI_FORMAT_BC3_UNORM_SRGB: - return DXGI_FORMAT_BC3_TYPELESS; - default: - return format; - } -} diff --git a/msvc/source/effect_codegen.hpp b/msvc/source/effect_codegen.hpp deleted file mode 100644 index 39d7b90..0000000 --- a/msvc/source/effect_codegen.hpp +++ /dev/null @@ -1,323 +0,0 @@ -/** - * Copyright (C) 2014 Patrick Mours. All rights reserved. - * License: https://github.com/crosire/reshade#license - */ - -#pragma once - -#include "effect_lexer.hpp" -#include -#include - -namespace reshadefx -{ - /// - /// A SSA code generation back-end interface for the parser to call into. - /// - class codegen abstract - { - public: - /// - /// Virtual destructor to guarantee that memory of the implementations deriving from this interface is properly destroyed. - /// - virtual ~codegen() {} - - /// - /// Write result of the code generation to the specified . - /// - /// The target module to fill. - virtual void write_result(module &module) = 0; - - public: - /// - /// An opaque ID referring to a SSA value or basic block. - /// - using id = uint32_t; - - /// - /// Define a new struct type. - /// - /// Source location matching this definition (for debugging). - /// The struct type description. - /// New SSA ID of the type. - virtual id define_struct(const location &loc, struct_info &info) = 0; - /// - /// Define a new texture binding. - /// - /// Source location matching this definition (for debugging). - /// The texture description. - /// New SSA ID of the binding. - virtual id define_texture(const location &loc, texture_info &info) = 0; - /// - /// Define a new sampler binding. - /// - /// Source location matching this definition (for debugging). - /// The sampler description. - /// New SSA ID of the binding. - virtual id define_sampler(const location &loc, sampler_info &info) = 0; - /// - /// Define a new uniform variable. - /// - /// Source location matching this definition (for debugging). - /// The uniform variable description. - /// New SSA ID of the variable. - virtual id define_uniform(const location &loc, uniform_info &info) = 0; - /// - /// Define a new variable. - /// - /// Source location matching this definition (for debugging). - /// The variable type. - /// The variable name. - /// true if this variable is in global scope, false otherwise. - /// SSA ID of an optional initializer value. - /// New SSA ID of the variable. - virtual id define_variable(const location &loc, const type &type, std::string name = std::string(), bool global = false, id initializer_value = 0) = 0; - /// - /// Define a new function and its function parameters and make it current. Any code added after this call is added to this function. - /// - /// Source location matching this definition (for debugging). - /// The function description. - /// New SSA ID of the function. - virtual id define_function(const location &loc, function_info &info) = 0; - - /// - /// Define a new effect technique. - /// - /// Source location matching this definition (for debugging). - /// The technique description. - void define_technique(technique_info &info) { _module.techniques.push_back(info); } - /// - /// Make a function a shader entry point. - /// - /// The function to use as entry point. - /// true if this is a pixel shader, false if it is a vertex shader. - virtual void define_entry_point(const function_info &function, bool is_ps) = 0; - - /// - /// Resolve the access chain and add a load operation to the output. - /// - /// The access chain pointing to the variable to load from. - /// New SSA ID with the loaded value. - virtual id emit_load(const expression &chain) = 0; - /// - /// Resolve the access chain and add a store operation to the output. - /// - /// The access chain pointing to the variable to store to. - /// The SSA ID of the value to store. - virtual void emit_store(const expression &chain, id value) = 0; - - /// - /// Create a SSA constant value. - /// - /// The data type of the constant. - /// The actual constant data to convert into a SSA ID. - /// New SSA ID with the constant value. - virtual id emit_constant(const type &type, const constant &data) = 0; - - /// - /// Add an unary operation to the output (built-in operation with one argument). - /// - /// Source location matching this operation (for debugging). - /// The unary operator to use. - /// The data type of the input value. - /// The SSA ID of value to perform the operation on. - /// New SSA ID with the result of the operation. - virtual id emit_unary_op(const location &loc, tokenid op, const type &type, id val) = 0; - /// - /// Add a binary operation to the output (built-in operation with two arguments). - /// - /// Source location matching this operation (for debugging). - /// The binary operator to use. - /// The data type of the result. - /// The data type of the input values. - /// The SSA ID of the value on the left-hand side of the binary operation. - /// The SSA ID of the value on the right-hand side of the binary operation. - /// New SSA ID with the result of the operation. - virtual id emit_binary_op(const location &loc, tokenid op, const type &res_type, const type &type, id lhs, id rhs) = 0; - id emit_binary_op(const location &loc, tokenid op, const type &type, id lhs, id rhs) { return emit_binary_op(loc, op, type, type, lhs, rhs); } - /// - /// Add a ternary operation to the output (built-in operation with three arguments). - /// - /// Source location matching this operation (for debugging). - /// The ternary operator to use. - /// The data type of the input values. - /// The SSA ID of the condition value of the ternary operation. - /// The SSA ID of the first value of the ternary operation. - /// The SSA ID of the second value of the ternary operation. - /// New SSA ID with the result of the operation. - virtual id emit_ternary_op(const location &loc, tokenid op, const type &type, id condition, id true_value, id false_value) = 0; - /// - /// Add a function call to the output. - /// - /// Source location matching this operation (for debugging). - /// The SSA ID of the function to call. - /// The data type of the call result. - /// A list of SSA IDs representing the call arguments. - /// New SSA ID with the result of the function call. - virtual id emit_call(const location &loc, id function, const type &res_type, const std::vector &args) = 0; - /// - /// Add an intrinsic function call to the output. - /// - /// Source location matching this operation (for debugging). - /// The intrinsic to call. - /// The data type of the call result. - /// A list of SSA IDs representing the call arguments. - /// New SSA ID with the result of the function call. - virtual id emit_call_intrinsic(const location &loc, id function, const type &res_type, const std::vector &args) = 0; - /// - /// Add a type constructor call to the output. - /// - /// The data type to construct. - /// A list of SSA IDs representing the scalar constructor arguments. - /// New SSA ID with the constructed value. - virtual id emit_construct(const location &loc, const type &type, const std::vector &args) = 0; - - /// - /// Add a structured branch control flow to the output. - /// - /// Source location matching this branch (for debugging). - /// 0 - default, 1 - flatten, 2 - do not flatten - virtual void emit_if(const location &loc, id condition_value, id condition_block, id true_statement_block, id false_statement_block, unsigned int flags) = 0; - /// - /// Add a branch control flow with a SSA phi operation to the output. - /// - /// Source location matching this branch (for debugging). - /// New SSA ID with the result of the phi operation. - virtual id emit_phi(const location &loc, id condition_value, id condition_block, id true_value, id true_statement_block, id false_value, id false_statement_block, const type &type) = 0; - /// - /// Add a structured loop control flow to the output. - /// - /// Source location matching this loop (for debugging). - /// 0 - default, 1 - unroll, 2 - do not unroll - virtual void emit_loop(const location &loc, id condition_value, id prev_block, id header_block, id condition_block, id loop_block, id continue_block, unsigned int flags) = 0; - /// - /// Add a structured switch control flow to the output. - /// - /// Source location matching this switch (for debugging). - /// 0 - default, 1 - flatten, 2 - do not flatten - virtual void emit_switch(const location &loc, id selector_value, id selector_block, id default_label, const std::vector &case_literal_and_labels, unsigned int flags) = 0; - - /// - /// Returns true if code is currently added to a basic block. - /// - bool is_in_block() const { return _current_block != 0; } - /// - /// Returns true if code is currently added to a function. - /// - virtual bool is_in_function() const { return is_in_block(); } - - /// - /// Create a new basic block. - /// - /// New SSA ID of the basic block. - virtual id create_block() { return make_id(); } - /// - /// Overwrite the current block ID. - /// - /// The ID of the block to make current. - /// The ID of the previous basic block. - virtual id set_block(id id) = 0; - /// - /// Create a new basic block and make it current. - /// - /// The ID of the basic block to create and make current. - virtual void enter_block(id id) = 0; - /// - /// Return from the current basic block and kill the shader invocation. - /// - /// The ID of the current basic block. - virtual id leave_block_and_kill() = 0; - /// - /// Return from the current basic block and hand control flow over to the function call side. - /// - /// Optional SSA ID of a return value. - /// The ID of the current basic block. - virtual id leave_block_and_return(id value = 0) = 0; - /// - /// Diverge the current control flow and enter a switch. - /// - /// SSA ID of the selector value to decide the switch path. - /// The ID of the current basic block. - virtual id leave_block_and_switch(id value, id default_target) = 0; - /// - /// Diverge the current control flow and jump to the specified target block. - /// - /// The ID of the basic block to jump to. - /// True if this corresponds to a loop continue statement. - /// The ID of the current basic block. - virtual id leave_block_and_branch(id target, unsigned int loop_flow = 0) = 0; - /// - /// Diverge the current control flow and jump to one of the specified target blocks, depending on the condition. - /// - /// The SSA ID of a value used to choose which path to take. - /// The ID of the basic block to jump to when the condition is true. - /// The ID of the basic block to jump to when the condition is false. - /// The ID of the current basic block. - virtual id leave_block_and_branch_conditional(id condition, id true_target, id false_target) = 0; - /// - /// Leave the current function. Any code added after this call is added in the global scope. - /// - virtual void leave_function() = 0; - - /// - /// Look up an existing struct definition. - /// - /// The SSA ID of the struct type to find. - /// A reference to the struct description. - struct_info &find_struct(id id) - { - return *std::find_if(_structs.begin(), _structs.end(), - [id](const auto &it) { return it.definition == id; }); - } - /// - /// Look up an existing texture definition. - /// - /// The SSA ID of the texture variable to find. - /// A reference to the texture description. - texture_info &find_texture(id id) - { - return *std::find_if(_module.textures.begin(), _module.textures.end(), - [id](const auto &it) { return it.id == id; }); - } - /// - /// Look up an existing function definition. - /// - /// The SSA ID of the function variable to find. - /// A reference to the function description. - function_info &find_function(id id) - { - return *std::find_if(_functions.begin(), _functions.end(), - [id](const auto &it) { return it->definition == id; })->get(); - } - - protected: - id make_id() { return _next_id++; } - - module _module; - std::vector _structs; - std::vector> _functions; - id _next_id = 1; - id _last_block = 0; - id _current_block = 0; - }; - - /// - /// Create a back-end implementation for GLSL code generation. - /// - /// Whether to append debug information like line directives to the generated code. - /// Whether to convert uniform variables to specialization constants. - codegen *create_codegen_glsl(bool debug_info, bool uniforms_to_spec_constants); - /// - /// Create a back-end implementation for HLSL code generation. - /// - /// The HLSL shader model version (e.g. 30, 41, 50, 60, ...) - /// Whether to append debug information like line directives to the generated code. - /// Whether to convert uniform variables to specialization constants. - codegen *create_codegen_hlsl(unsigned int shader_model, bool debug_info, bool uniforms_to_spec_constants); - /// - /// Create a back-end implementation for SPIR-V code generation. - /// - /// Whether to append debug information like line directives to the generated code. - /// Whether to convert uniform variables to specialization constants. - codegen *create_codegen_spirv(bool debug_info, bool uniforms_to_spec_constants); -} diff --git a/msvc/source/effect_codegen_glsl.cpp b/msvc/source/effect_codegen_glsl.cpp deleted file mode 100644 index 9714420..0000000 --- a/msvc/source/effect_codegen_glsl.cpp +++ /dev/null @@ -1,1365 +0,0 @@ -/** - * Copyright (C) 2014 Patrick Mours. All rights reserved. - * License: https://github.com/crosire/reshade#license - */ - -#include "effect_parser.hpp" -#include "effect_codegen.hpp" -#include -#include - -using namespace reshadefx; - -class codegen_glsl final : public codegen -{ -public: - codegen_glsl(bool debug_info, bool uniforms_to_spec_constants) - : _debug_info(debug_info), _uniforms_to_spec_constants(uniforms_to_spec_constants) - { - // Create default block and reserve a memory block to avoid frequent reallocations - std::string &block = _blocks.emplace(0, std::string()).first->second; - block.reserve(8192); - } - -private: - enum class naming - { - // After escaping, will be numbered when clashing with another name - general, - // After escaping, name should already be unique, so no additional steps are taken - unique, - // This is a special name that is not modified and should be unique - reserved, - }; - - std::string _ubo_block; - std::unordered_map _names; - std::unordered_map _blocks; - bool _debug_info = false; - bool _uniforms_to_spec_constants = false; - unsigned int _current_ubo_offset = 0; - std::unordered_map _remapped_sampler_variables; - - void write_result(module &module) override - { - module = std::move(_module); - - module.hlsl += - "float hlsl_fmod(float x, float y) { return x - y * trunc(x / y); }\n" - " vec2 hlsl_fmod( vec2 x, vec2 y) { return x - y * trunc(x / y); }\n" - " vec3 hlsl_fmod( vec3 x, vec3 y) { return x - y * trunc(x / y); }\n" - " vec4 hlsl_fmod( vec4 x, vec4 y) { return x - y * trunc(x / y); }\n" - " mat2 hlsl_fmod( mat2 x, mat2 y) { return x - matrixCompMult(y, mat2(trunc(x[0] / y[0]), trunc(x[1] / y[1]))); }\n" - " mat3 hlsl_fmod( mat3 x, mat3 y) { return x - matrixCompMult(y, mat3(trunc(x[0] / y[0]), trunc(x[1] / y[1]), trunc(x[2] / y[2]))); }\n" - " mat4 hlsl_fmod( mat4 x, mat4 y) { return x - matrixCompMult(y, mat4(trunc(x[0] / y[0]), trunc(x[1] / y[1]), trunc(x[2] / y[2]), trunc(x[3] / y[3]))); }\n"; - - if (!_ubo_block.empty()) - module.hlsl += "layout(std140, binding = 0) uniform _Globals {\n" + _ubo_block + "};\n"; - module.hlsl += _blocks.at(0); - } - - template - void write_type(std::string &s, const type &type) const - { - if constexpr (is_decl) - { - if (type.has(type::q_precise)) - s += "precise "; - } - - if constexpr (is_interface) - { - if (type.has(type::q_linear)) - s += "smooth "; - if (type.has(type::q_noperspective)) - s += "noperspective "; - if (type.has(type::q_centroid)) - s += "centroid "; - if (type.has(type::q_nointerpolation)) - s += "flat "; - } - - if constexpr (is_interface || is_param) - { - if (type.has(type::q_inout)) - s += "inout "; - else if (type.has(type::q_in)) - s += "in "; - else if (type.has(type::q_out)) - s += "out "; - } - - switch (type.base) - { - case type::t_void: - s += "void"; - break; - case type::t_bool: - if (type.cols > 1) - s += "mat" + std::to_string(type.rows) + 'x' + std::to_string(type.cols); - else if (type.rows > 1) - s += "bvec" + std::to_string(type.rows); - else - s += "bool"; - break; - case type::t_int: - if (type.cols > 1) - s += "mat" + std::to_string(type.rows) + 'x' + std::to_string(type.cols); - else if (type.rows > 1) - s += "ivec" + std::to_string(type.rows); - else - s += "int"; - break; - case type::t_uint: - if (type.cols > 1) - s += "mat" + std::to_string(type.rows) + 'x' + std::to_string(type.cols); - else if (type.rows > 1) - s += "uvec" + std::to_string(type.rows); - else - s += "uint"; - break; - case type::t_float: - if (type.cols > 1) - s += "mat" + std::to_string(type.rows) + 'x' + std::to_string(type.cols); - else if (type.rows > 1) - s += "vec" + std::to_string(type.rows); - else - s += "float"; - break; - case type::t_struct: - s += id_to_name(type.definition); - break; - case type::t_sampler: - s += "sampler2D"; - break; - default: - assert(false); - } - } - void write_constant(std::string &s, const type &type, const constant &data) const - { - if (type.is_array()) - { - auto elem_type = type; - elem_type.array_length = 0; - - write_type(s, elem_type); - s += "[]("; - - for (int i = 0; i < type.array_length; ++i) - { - write_constant(s, elem_type, i < static_cast(data.array_data.size()) ? data.array_data[i] : constant()); - - if (i < type.array_length - 1) - s += ", "; - } - - s += ')'; - return; - } - - // There can only be numeric constants - assert(type.is_numeric()); - - if (!type.is_scalar()) - { - if (type.is_matrix()) - s += "transpose("; - - write_type(s, type); - s += '('; - } - - for (unsigned int i = 0, components = type.components(); i < components; ++i) - { - switch (type.base) - { - case type::t_bool: - s += data.as_uint[i] ? "true" : "false"; - break; - case type::t_int: - s += std::to_string(data.as_int[i]); - break; - case type::t_uint: - s += std::to_string(data.as_uint[i]) + 'u'; - break; - case type::t_float: - std::string temp(_scprintf("%.8f", data.as_float[i]), '\0'); - sprintf_s(temp.data(), temp.size() + 1, "%.8f", data.as_float[i]); - s += temp; - break; - } - - if (i < components - 1) - s += ", "; - } - - if (!type.is_scalar()) - { - if (type.is_matrix()) - s += ')'; - - s += ')'; - } - } - void write_location(std::string &s, const location &loc) const - { - if (loc.source.empty() || !_debug_info) - return; - - s += "#line " + std::to_string(loc.line) + '\n'; - } - - std::string id_to_name(id id) const - { - if (const auto it = _remapped_sampler_variables.find(id); it != _remapped_sampler_variables.end()) - id = it->second; - assert(id != 0); - if (const auto it = _names.find(id); it != _names.end()) - return it->second; - return '_' + std::to_string(id); - } - - template - void define_name(const id id, std::string name) - { - if constexpr (naming != naming::reserved) - escape_name(name); - if constexpr (naming == naming::general) - if (std::find_if(_names.begin(), _names.end(), [&name](const auto &it) { return it.second == name; }) != _names.end()) - name += '_' + std::to_string(id); - // Remove double underscore symbols from name which can occur due to namespaces but are not allowed in GLSL - for (size_t pos = 0; (pos = name.find("__", pos)) != std::string::npos; pos += 3) - name.replace(pos, 2, "_US"); - _names[id] = std::move(name); - } - - static void escape_name(std::string &name) - { - static const std::unordered_set s_reserverd_names = { - "common", "partition", "input", "output", "active", "filter", "superp", "invariant", - "lowp", "mediump", "highp", "precision", "patch", "subroutine", - "abs", "sign", "all", "any", "sin", "sinh", "cos", "cosh", "tan", "tanh", "asin", "acos", "atan", - "exp", "exp2", "log", "log2", "sqrt", "inversesqrt", "ceil", "floor", "fract", "trunc", "round", - "radians", "degrees", "length", "normalize", "transpose", "determinant", "intBitsToFloat", "uintBitsToFloat", - "floatBitsToInt", "floatBitsToUint", "matrixCompMult", "not", "lessThan", "greaterThan", "lessThanEqual", - "greaterThanEqual", "equal", "notEqual", "dot", "cross", "distance", "pow", "modf", "frexp", "ldexp", - "min", "max", "step", "reflect", "texture", "textureOffset", "fma", "mix", "clamp", "smoothstep", "refract", - "faceforward", "textureLod", "textureLodOffset", "texelFetch", "main" - }; - - if (name.compare(0, 3, "gl_") == 0 || s_reserverd_names.count(name)) - name += '_'; - } - - static void increase_indentation_level(std::string &block) - { - if (block.empty()) - return; - - for (size_t pos = 0; (pos = block.find("\n\t", pos)) != std::string::npos; pos += 3) - block.replace(pos, 2, "\n\t\t"); - - block.insert(block.begin(), '\t'); - } - - id define_struct(const location &loc, struct_info &info) override - { - info.definition = make_id(); - define_name(info.definition, info.unique_name); - - _structs.push_back(info); - - std::string &code = _blocks.at(_current_block); - - write_location(code, loc); - - code += "struct " + id_to_name(info.definition) + "\n{\n"; - - for (const auto &member : info.member_list) - { - code += '\t'; - write_type(code, member.type); // GLSL does not allow interpolation attributes on struct members - code += ' ' + member.name; - if (member.type.is_array()) - code += '[' + std::to_string(member.type.array_length) + ']'; - code += ";\n"; - } - - if (info.member_list.empty()) - code += "float _dummy;\n"; - - code += "};\n"; - - return info.definition; - } - id define_texture(const location &, texture_info &info) override - { - info.id = make_id(); - - _module.textures.push_back(info); - - return info.id; - } - id define_sampler(const location &loc, sampler_info &info) override - { - info.id = make_id(); - info.binding = _module.num_sampler_bindings++; - - define_name(info.id, info.unique_name); - - _module.samplers.push_back(info); - - std::string &code = _blocks.at(_current_block); - - write_location(code, loc); - - code += "layout(binding = " + std::to_string(info.binding) + ") uniform sampler2D " + id_to_name(info.id) + ";\n"; - - return info.id; - } - id define_uniform(const location &loc, uniform_info &info) override - { - const id res = make_id(); - - define_name(res, "_Globals_" + info.name); - - if (_uniforms_to_spec_constants && info.has_initializer_value) - { - std::string &code = _blocks.at(_current_block); - - write_location(code, loc); - - code += "const "; - write_type(code, info.type); - code += ' ' + id_to_name(res) + " = "; - if (!info.type.is_scalar()) - write_type(code, info.type); - code += "(SPEC_CONSTANT_" + info.name + ");\n"; - - _module.spec_constants.push_back(info); - } - else - { - // GLSL specification on std140 layout: - // 1. If the member is a scalar consuming N basic machine units, the base alignment is N. - // 2. If the member is a two- or four-component vector with components consuming N basic machine units, the base alignment is 2N or 4N, respectively. - // 3. If the member is a three-component vector with components consuming N basic machine units, the base alignment is 4N. - const unsigned int size = 4 * info.type.rows * info.type.cols * std::max(1, info.type.array_length); - const unsigned int alignment = 4 * (info.type.rows == 3 ? 4 : info.type.rows) * info.type.cols * std::max(1, info.type.array_length); - - info.size = size; - info.offset = (_current_ubo_offset % alignment != 0) ? _current_ubo_offset + alignment - _current_ubo_offset % alignment : _current_ubo_offset; - _current_ubo_offset = info.offset + info.size; - - write_location(_ubo_block, loc); - - _ubo_block += '\t'; - write_type(_ubo_block, info.type); - _ubo_block += ' ' + id_to_name(res) + ";\n"; - - _module.uniforms.push_back(info); - } - - return res; - } - id define_variable(const location &loc, const type &type, std::string name, bool global, id initializer_value) override - { - const id res = make_id(); - - // GLSL does not allow local sampler variables, so try to remap those - if (!global && type.is_sampler()) - return (_remapped_sampler_variables[res] = 0), res; - - if (!name.empty()) - define_name(res, name); - - std::string &code = _blocks.at(_current_block); - - write_location(code, loc); - - if (!global) - code += '\t'; - - write_type(code, type); - code += ' ' + id_to_name(res); - - if (type.is_array()) - code += '[' + std::to_string(type.array_length) + ']'; - - if (initializer_value != 0) - code += " = " + id_to_name(initializer_value); - - code += ";\n"; - - return res; - } - id define_function(const location &loc, function_info &info) override - { - return define_function(loc, info, false); - } - id define_function(const location &loc, function_info &info, bool is_entry_point) - { - info.definition = make_id(); - - if (is_entry_point) - define_name(info.definition, "main"); - else - define_name(info.definition, info.unique_name); - - std::string &code = _blocks.at(_current_block); - - write_location(code, loc); - - write_type(code, info.return_type); - code += ' ' + id_to_name(info.definition) + '('; - - assert(info.parameter_list.empty() || !is_entry_point); - - for (size_t i = 0, num_params = info.parameter_list.size(); i < num_params; ++i) - { - auto ¶m = info.parameter_list[i]; - - param.definition = make_id(); - define_name(param.definition, param.name); - - code += '\n'; - write_location(code, param.location); - code += '\t'; - write_type(code, param.type); // GLSL does not allow interpolation attributes on function parameters - code += ' ' + id_to_name(param.definition); - - if (param.type.is_array()) - code += '[' + std::to_string(param.type.array_length) + ']'; - - if (i < num_params - 1) - code += ','; - } - - code += ")\n"; - - _functions.push_back(std::make_unique(info)); - - return info.definition; - } - void define_entry_point(const function_info &func, bool is_ps) override - { - if (const auto it = std::find_if(_module.entry_points.begin(), _module.entry_points.end(), - [&func](const auto &ep) { return ep.first == func.unique_name; }); it != _module.entry_points.end()) - return; - - _module.entry_points.push_back({ func.unique_name, is_ps }); - - _blocks.at(0) += "#ifdef ENTRY_POINT_" + func.unique_name + '\n'; - - function_info entry_point; - entry_point.return_type = { type::t_void }; - - const auto is_color_semantic = [](const std::string &semantic) { return semantic.compare(0, 9, "SV_TARGET") == 0 || semantic.compare(0, 5, "COLOR") == 0; }; - - const auto escape_name_with_builtins = [this, is_ps](std::string name, const std::string &semantic) -> std::string - { - if (semantic == "SV_VERTEXID" || semantic == "VERTEXID") - return "gl_VertexID"; - else if (semantic == "SV_POSITION" || semantic == "POSITION" || semantic == "VPOS") - return is_ps ? "gl_FragCoord" : "gl_Position"; - else if (semantic == "SV_DEPTH" || semantic == "DEPTH") - return "gl_FragDepth"; - - escape_name(name); - return name; - }; - const auto visit_shader_param = [this, is_ps, &escape_name_with_builtins](type type, unsigned int quals, const std::string &name, const std::string &semantic) { - type.qualifiers = quals; - - // OpenGL does not allow varying of type boolean - if (type.base == type::t_bool) - type.base = type::t_float; - - std::string &code = _blocks.at(_current_block); - - unsigned long location = 0; - - for (int i = 0, array_length = std::max(1, type.array_length); i < array_length; ++i) - { - if (!escape_name_with_builtins(std::string(), semantic).empty()) - continue; - else if (semantic.compare(0, 5, "COLOR") == 0) - location = strtoul(semantic.c_str() + 5, nullptr, 10); - else if (semantic.compare(0, 8, "TEXCOORD") == 0) - location = strtoul(semantic.c_str() + 8, nullptr, 10) + 1; - else if (semantic.compare(0, 9, "SV_TARGET") == 0) - location = strtoul(semantic.c_str() + 9, nullptr, 10); - - code += "layout(location = " + std::to_string(location + i) + ") "; - write_type(code, type); - code += ' ' + name; - if (type.is_array()) - code += std::to_string(i); - code += ";\n"; - } - }; - - // Translate function parameters to input/output variables - if (func.return_type.is_struct()) - for (const auto &member : find_struct(func.return_type.definition).member_list) - visit_shader_param(member.type, type::q_out, "_return_" + member.name, member.semantic); - else if (!func.return_type.is_void()) - visit_shader_param(func.return_type, type::q_out, "_return", func.return_semantic); - - for (const auto ¶m : func.parameter_list) - if (param.type.is_struct()) - for (const auto &member : find_struct(param.type.definition).member_list) - visit_shader_param(member.type, param.type.qualifiers | member.type.qualifiers, "_param_" + param.name + '_' + member.name, member.semantic); - else - visit_shader_param(param.type, param.type.qualifiers, "_param_" + param.name, param.semantic); - - define_function({}, entry_point, true); - enter_block(create_block()); - - std::string &code = _blocks.at(_current_block); - - // Handle input parameters - for (const auto ¶m : func.parameter_list) - { - for (int i = 0, array_length = std::max(1, param.type.array_length); i < array_length; i++) - { - // Build struct from separate member input variables - if (param.type.is_struct()) - { - code += '\t'; - write_type(code, param.type); - code += " _param_" + param.name; - if (param.type.is_array()) - code += std::to_string(i); - code += " = "; - write_type(code, param.type); - code += '('; - - for (const auto &member : find_struct(param.type.definition).member_list) - code += escape_name_with_builtins("_param_" + param.name + '_' + member.name + (param.type.is_array() ? std::to_string(i) : std::string()), member.semantic) + ", "; - - code.pop_back(); - code.pop_back(); - - code += ");\n"; - } - } - - if (param.type.is_array()) - { - code += '\t'; - write_type(code, param.type); - code += " _param_" + param.name + "[] = "; - write_type(code, param.type); - code += "[]("; - - for (int i = 0; i < param.type.array_length; ++i) - { - code += "_param_" + param.name + std::to_string(i); - if (i < param.type.array_length - 1) - code += ", "; - } - - code += ");\n"; - } - } - - code += '\t'; - // Structs cannot be output variables, so have to write to a temporary first and then output each member separately - if (func.return_type.is_struct()) - write_type(code, func.return_type), code += " _return = "; - // All other output types can write to the output variable directly - else if (!func.return_type.is_void()) - code += "_return = "; - - // Call the function this entry point refers to - code += id_to_name(func.definition) + '('; - - for (size_t i = 0, num_params = func.parameter_list.size(); i < num_params; ++i) - { - code += escape_name_with_builtins("_param_" + func.parameter_list[i].name, func.parameter_list[i].semantic); - - if (i < num_params - 1) - code += ", "; - } - - code += ");\n"; - - // Handle output parameters - for (const auto ¶m : func.parameter_list) - { - if (!param.type.has(type::q_out)) - continue; - - for (int i = 0; i < param.type.array_length; i++) - code += "\t_param_" + param.name + std::to_string(i) + " = _param_" + param.name + '[' + std::to_string(i) + "];\n"; - - for (int i = 0; i < std::max(1, param.type.array_length); i++) - if (param.type.is_struct()) - for (const auto &member : find_struct(param.type.definition).member_list) - if (param.type.is_array()) - code += "\t_param_" + param.name + '_' + member.name + std::to_string(i) + " = _param_" + param.name + '.' + member.name + '[' + std::to_string(i) + "];\n"; - else - code += "\t_param_" + param.name + '_' + member.name + " = _param_" + param.name + '.' + member.name + ";\n"; - } - - // Handle return struct output variables - if (func.return_type.is_struct()) - for (const auto &member : find_struct(func.return_type.definition).member_list) - code += '\t' + escape_name_with_builtins("_return_" + member.name, member.semantic) + " = _return." + member.name + ";\n"; - - leave_block_and_return(0); - leave_function(); - - _blocks.at(0) += "#endif\n"; - } - - id emit_load(const expression &exp) override - { - if (exp.is_constant) - return emit_constant(exp.type, exp.constant); - else if (exp.chain.empty()) // Can refer to values without access chain directly - return exp.base; - - const id res = make_id(); - - std::string &code = _blocks.at(_current_block); - - write_location(code, exp.location); - - code += '\t'; - write_type(code, exp.type); - code += ' ' + id_to_name(res); - - if (exp.type.is_array()) - code += '[' + std::to_string(exp.type.array_length) + ']'; - - code += " = "; - - std::string newcode = id_to_name(exp.base); - - for (const auto &op : exp.chain) - { - switch (op.op) - { - case expression::operation::op_cast: - { std::string type; write_type(type, op.to); - newcode = type + '(' + newcode + ')'; } - break; - case expression::operation::op_member: - newcode += '.'; - newcode += find_struct(op.from.definition).member_list[op.index].name; - break; - case expression::operation::op_dynamic_index: - newcode += '[' + id_to_name(op.index) + ']'; - break; - case expression::operation::op_constant_index: - newcode += '[' + std::to_string(op.index) + ']'; - break; - case expression::operation::op_swizzle: - if (op.from.is_matrix()) - { - if (op.swizzle[1] < 0) - { - const unsigned int row = op.swizzle[0] % 4; - const unsigned int col = (op.swizzle[0] - row) / 4; - - newcode += '[' + std::to_string(row) + "][" + std::to_string(col) + ']'; - } - else - { - // TODO: Implement matrix to vector swizzles - assert(false); - } - } - else - { - newcode += '.'; - for (unsigned int i = 0; i < 4 && op.swizzle[i] >= 0; ++i) - newcode += "xyzw"[op.swizzle[i]]; - } - break; - } - } - - code += newcode; - code += ";\n"; - - return res; - } - void emit_store(const expression &exp, id value) override - { - if (const auto it = _remapped_sampler_variables.find(exp.base); it != _remapped_sampler_variables.end()) - { - assert(it->second == 0); - it->second = value; - return; - } - - std::string &code = _blocks.at(_current_block); - - write_location(code, exp.location); - - code += '\t' + id_to_name(exp.base); - - for (const auto &op : exp.chain) - { - switch (op.op) - { - case expression::operation::op_member: - code += '.'; - code += find_struct(op.from.definition).member_list[op.index].name; - break; - case expression::operation::op_dynamic_index: - code += '[' + id_to_name(op.index) + ']'; - break; - case expression::operation::op_constant_index: - code += '[' + std::to_string(op.index) + ']'; - break; - case expression::operation::op_swizzle: - if (op.from.is_matrix()) - { - if (op.swizzle[1] < 0) - { - const unsigned int row = op.swizzle[0] % 4; - const unsigned int col = (op.swizzle[0] - row) / 4; - - code += '[' + std::to_string(row) + "][" + std::to_string(col) + ']'; - } - else - { - // TODO: Implement matrix to vector swizzles - assert(false); - } - } - else - { - code += '.'; - for (unsigned int i = 0; i < 4 && op.swizzle[i] >= 0; ++i) - code += "xyzw"[op.swizzle[i]]; - } - break; - } - } - - code += " = " + id_to_name(value) + ";\n"; - } - - id emit_constant(const type &type, const constant &data) override - { - const id res = make_id(); - - std::string &code = _blocks.at(_current_block); - - // Struct initialization is not supported right now - if (type.is_struct()) - { - code += '\t'; - write_type(code, type); - code += ' ' + id_to_name(res) + ";\n"; - return res; - } - - code += "\tconst "; - write_type(code, type); - code += ' ' + id_to_name(res); - - if (type.is_array()) - code += '[' + std::to_string(type.array_length) + ']'; - - code += " = "; - write_constant(code, type, data); - code += ";\n"; - - return res; - } - - id emit_unary_op(const location &loc, tokenid op, const type &res_type, id val) override - { - const id res = make_id(); - - std::string &code = _blocks.at(_current_block); - - write_location(code, loc); - - code += '\t'; - write_type(code, res_type); - code += ' ' + id_to_name(res) + " = "; - - switch (op) - { - case tokenid::minus: - code += '-'; - break; - case tokenid::tilde: - code += '~'; - break; - case tokenid::exclaim: - if (res_type.is_vector()) - code += "not"; - else - code += "!bool"; - break; - default: - assert(false); - } - - code += '(' + id_to_name(val) + ");\n"; - - return res; - } - id emit_binary_op(const location &loc, tokenid op, const type &res_type, const type &type, id lhs, id rhs) override - { - const id res = make_id(); - - std::string &code = _blocks.at(_current_block); - - write_location(code, loc); - - code += '\t'; - write_type(code, res_type); - code += ' ' + id_to_name(res) + " = "; - - std::string intrinsic, operator_code; - - switch (op) - { - case tokenid::plus: - case tokenid::plus_plus: - case tokenid::plus_equal: - operator_code = '+'; - break; - case tokenid::minus: - case tokenid::minus_minus: - case tokenid::minus_equal: - operator_code = '-'; - break; - case tokenid::star: - case tokenid::star_equal: - if (type.is_matrix()) - intrinsic = "matrixCompMult"; - else - operator_code = '*'; - break; - case tokenid::slash: - case tokenid::slash_equal: - operator_code = '/'; - break; - case tokenid::percent: - case tokenid::percent_equal: - if (type.is_floating_point()) - intrinsic = "hlsl_fmod"; - else - operator_code = '%'; - break; - case tokenid::caret: - case tokenid::caret_equal: - operator_code = '^'; - break; - case tokenid::pipe: - case tokenid::pipe_equal: - operator_code = '|'; - break; - case tokenid::ampersand: - case tokenid::ampersand_equal: - operator_code = '&'; - break; - case tokenid::less_less: - case tokenid::less_less_equal: - operator_code = "<<"; - break; - case tokenid::greater_greater: - case tokenid::greater_greater_equal: - operator_code = ">>"; - break; - case tokenid::pipe_pipe: - operator_code = "||"; - break; - case tokenid::ampersand_ampersand: - operator_code = "&&"; - break; - case tokenid::less: - if (type.is_vector()) - intrinsic = "lessThan"; - else - operator_code = '<'; - break; - case tokenid::less_equal: - if (type.is_vector()) - intrinsic = "lessThanEqual"; - else - operator_code = "<="; - break; - case tokenid::greater: - if (type.is_vector()) - intrinsic = "greaterThan"; - else - operator_code = '>'; - break; - case tokenid::greater_equal: - if (type.is_vector()) - intrinsic = "greaterThanEqual"; - else - operator_code = ">="; - break; - case tokenid::equal_equal: - if (type.is_vector()) - intrinsic = "equal"; - else - operator_code = "=="; - break; - case tokenid::exclaim_equal: - if (type.is_vector()) - intrinsic = "notEqual"; - else - operator_code = "!="; - break; - default: - assert(false); - } - - if (!intrinsic.empty()) - code += intrinsic + '(' + id_to_name(lhs) + ", " + id_to_name(rhs) + ')'; - else - code += id_to_name(lhs) + ' ' + operator_code + ' ' + id_to_name(rhs); - - code += ";\n"; - - return res; - } - id emit_ternary_op(const location &loc, tokenid op, const type &res_type, id condition, id true_value, id false_value) override - { - assert(op == tokenid::question); - - const id res = make_id(); - - std::string &code = _blocks.at(_current_block); - - write_location(code, loc); - - code += '\t'; - write_type(code, res_type); - code += ' ' + id_to_name(res); - - if (res_type.is_array()) - code += '[' + std::to_string(res_type.array_length) + ']'; - - code += " = "; - - // GLSL requires the selection first expression to be a scalar boolean - if (!res_type.is_scalar()) - code += "all(" + id_to_name(condition) + ')'; - else - code += id_to_name(condition); - - code += " ? " + id_to_name(true_value) + " : " + id_to_name(false_value) + ";\n"; - - return res; - } - id emit_call(const location &loc, id function, const type &res_type, const std::vector &args) override - { - for (const auto &arg : args) - assert(arg.chain.empty() && arg.base != 0); - - const id res = make_id(); - - std::string &code = _blocks.at(_current_block); - - write_location(code, loc); - - code += '\t'; - - if (!res_type.is_void()) - { - write_type(code, res_type); - code += ' ' + id_to_name(res); - - if (res_type.is_array()) - code += '[' + std::to_string(res_type.array_length) + ']'; - - code += " = "; - } - - code += id_to_name(function) + '('; - - for (size_t i = 0, num_args = args.size(); i < num_args; ++i) - { - code += id_to_name(args[i].base); - - if (i < num_args - 1) - code += ", "; - } - - code += ");\n"; - - return res; - } - id emit_call_intrinsic(const location &loc, id intrinsic, const type &res_type, const std::vector &args) override - { - for (const auto &arg : args) - assert(arg.chain.empty() && arg.base != 0); - - const id res = make_id(); - - std::string &code = _blocks.at(_current_block); - - write_location(code, loc); - - code += '\t'; - - if (!res_type.is_void()) - { - write_type(code, res_type); - code += ' ' + id_to_name(res) + " = "; - } - - enum - { -#define IMPLEMENT_INTRINSIC_GLSL(name, i, code) name##i, -#include "effect_symbol_table_intrinsics.inl" - }; - - switch (intrinsic) - { -#define IMPLEMENT_INTRINSIC_GLSL(name, i, code) case name##i: code break; -#include "effect_symbol_table_intrinsics.inl" - default: - assert(false); - } - - code += ";\n"; - - return res; - } - id emit_construct(const location &loc, const type &type, const std::vector &args) override - { - for (const auto &arg : args) - assert((arg.type.is_scalar() || type.is_array()) && arg.chain.empty() && arg.base != 0); - - const id res = make_id(); - - std::string &code = _blocks.at(_current_block); - - write_location(code, loc); - - code += '\t'; - write_type(code, type); - code += ' ' + id_to_name(res); - - if (type.is_array()) - code += '[' + std::to_string(type.array_length) + ']'; - - code += " = "; - - if (!type.is_array() && type.is_matrix()) - code += "transpose("; - - write_type(code, type); - - if (type.is_array()) - code += '[' + std::to_string(type.array_length) + ']'; - - code += '('; - - for (size_t i = 0, num_args = args.size(); i < num_args; ++i) - { - code += id_to_name(args[i].base); - - if (i < num_args - 1) - code += ", "; - } - - if (!type.is_array() && type.is_matrix()) - code += ')'; - - code += ");\n"; - - return res; - } - - void emit_if(const location &loc, id condition_value, id condition_block, id true_statement_block, id false_statement_block, unsigned int) override - { - assert(condition_value != 0 && condition_block != 0 && true_statement_block != 0 && false_statement_block != 0); - - std::string &code = _blocks.at(_current_block); - - std::string &true_statement_data = _blocks.at(true_statement_block); - std::string &false_statement_data = _blocks.at(false_statement_block); - - increase_indentation_level(true_statement_data); - increase_indentation_level(false_statement_data); - - code += _blocks.at(condition_block); - - write_location(code, loc); - - code += "\tif (" + id_to_name(condition_value) + ")\n\t{\n"; - code += true_statement_data; - code += "\t}\n"; - - if (!false_statement_data.empty()) - { - code += "\telse\n\t{\n"; - code += false_statement_data; - code += "\t}\n"; - } - - // Remove consumed blocks to save memory - _blocks.erase(condition_block); - _blocks.erase(true_statement_block); - _blocks.erase(false_statement_block); - } - id emit_phi(const location &loc, id condition_value, id condition_block, id true_value, id true_statement_block, id false_value, id false_statement_block, const type &type) override - { - assert(condition_value != 0 && condition_block != 0 && true_value != 0 && true_statement_block != 0 && false_value != 0 && false_statement_block != 0); - - std::string &code = _blocks.at(_current_block); - - std::string &true_statement_data = _blocks.at(true_statement_block); - std::string &false_statement_data = _blocks.at(false_statement_block); - - increase_indentation_level(true_statement_data); - increase_indentation_level(false_statement_data); - - const id res = make_id(); - - code += _blocks.at(condition_block); - - code += '\t'; - write_type(code, type); - code += ' ' + id_to_name(res) + ";\n"; - - write_location(code, loc); - - code += "\tif (" + id_to_name(condition_value) + ")\n\t{\n"; - code += (true_statement_block != condition_block ? true_statement_data : std::string()); - code += "\t\t" + id_to_name(res) + " = " + id_to_name(true_value) + ";\n"; - code += "\t}\n\telse\n\t{\n"; - code += (false_statement_block != condition_block ? false_statement_data : std::string()); - code += "\t\t" + id_to_name(res) + " = " + id_to_name(false_value) + ";\n"; - code += "\t}\n"; - - // Remove consumed blocks to save memory - _blocks.erase(condition_block); - _blocks.erase(true_statement_block); - _blocks.erase(false_statement_block); - - return res; - } - void emit_loop(const location &loc, id condition_value, id prev_block, id header_block, id condition_block, id loop_block, id continue_block, unsigned int) override - { - assert(condition_value != 0 && prev_block != 0 && header_block != 0 && loop_block != 0 && continue_block != 0); - - std::string &code = _blocks.at(_current_block); - - std::string &loop_data = _blocks.at(loop_block); - std::string &continue_data = _blocks.at(continue_block); - - increase_indentation_level(loop_data); - increase_indentation_level(loop_data); - increase_indentation_level(continue_data); - - code += _blocks.at(prev_block); - - if (condition_block == 0) - code += "\tbool " + id_to_name(condition_value) + ";\n"; - else - code += _blocks.at(condition_block); - - write_location(code, loc); - - code += '\t'; - - if (condition_block == 0) - { - // Convert variable initializer to assignment statement - auto pos_assign = continue_data.rfind(id_to_name(condition_value)); - auto pos_prev_assign = continue_data.rfind('\t', pos_assign); - continue_data.erase(pos_prev_assign + 1, pos_assign - pos_prev_assign - 1); - - // We need to add the continue block to all "continue" statements as well - const std::string continue_id = "__CONTINUE__" + std::to_string(continue_block); - for (size_t offset = 0; (offset = loop_data.find(continue_id, offset)) != std::string::npos; offset += continue_data.size()) - loop_data.replace(offset, continue_id.size(), continue_data); - - code += "do\n\t{\n\t\t{\n"; - code += loop_data; // Encapsulate loop body into another scope, so not to confuse any local variables with the current iteration variable accessed in the continue block below - code += "\t\t}\n"; - code += continue_data; - code += "\t}\n\twhile (" + id_to_name(condition_value) + ");\n"; - } - else - { - std::string &condition_data = _blocks.at(condition_block); - - increase_indentation_level(condition_data); - - // Convert variable initializer to assignment statement - auto pos_assign = condition_data.rfind(id_to_name(condition_value)); - auto pos_prev_assign = condition_data.rfind('\t', pos_assign); - condition_data.erase(pos_prev_assign + 1, pos_assign - pos_prev_assign - 1); - - const std::string continue_id = "__CONTINUE__" + std::to_string(continue_block); - for (size_t offset = 0; (offset = loop_data.find(continue_id, offset)) != std::string::npos; offset += continue_data.size()) - loop_data.replace(offset, continue_id.size(), continue_data + condition_data); - - code += "while (" + id_to_name(condition_value) + ")\n\t{\n\t\t{\n"; - code += loop_data; - code += "\t\t}\n"; - code += continue_data; - code += condition_data; - code += "\t}\n"; - - _blocks.erase(condition_block); - } - - // Remove consumed blocks to save memory - _blocks.erase(prev_block); - _blocks.erase(header_block); - _blocks.erase(loop_block); - _blocks.erase(continue_block); - } - void emit_switch(const location &loc, id selector_value, id selector_block, id default_label, const std::vector &case_literal_and_labels, unsigned int) override - { - assert(selector_value != 0 && selector_block != 0 && default_label != 0); - - std::string &code = _blocks.at(_current_block); - - code += _blocks.at(selector_block); - - write_location(code, loc); - - code += "\tswitch (" + id_to_name(selector_value) + ")\n\t{\n"; - - for (size_t i = 0; i < case_literal_and_labels.size(); i += 2) - { - assert(case_literal_and_labels[i + 1] != 0); - - std::string &case_data = _blocks.at(case_literal_and_labels[i + 1]); - - increase_indentation_level(case_data); - - code += "\tcase " + std::to_string(case_literal_and_labels[i]) + ": {\n"; - code += case_data; - code += "\t}\n"; - } - - if (default_label != _current_block) - { - std::string &default_data = _blocks.at(default_label); - - increase_indentation_level(default_data); - - code += "\tdefault: {\n"; - code += default_data; - code += "\t}\n"; - - _blocks.erase(default_label); - } - - code += "\t}\n"; - - // Remove consumed blocks to save memory - _blocks.erase(selector_block); - for (size_t i = 0; i < case_literal_and_labels.size(); i += 2) - _blocks.erase(case_literal_and_labels[i + 1]); - } - - id create_block() override - { - const id res = make_id(); - - std::string &block = _blocks.emplace(res, std::string()).first->second; - // Reserve a decently big enough memory block to avoid frequent reallocations - block.reserve(4096); - - return res; - } - id set_block(id id) override - { - _last_block = _current_block; - _current_block = id; - - return _last_block; - } - void enter_block(id id) override - { - _current_block = id; - } - id leave_block_and_kill() override - { - if (!is_in_block()) - return 0; - - std::string &code = _blocks.at(_current_block); - - code += "\tdiscard;\n"; - - return set_block(0); - } - id leave_block_and_return(id value) override - { - if (!is_in_block()) - return 0; - - // Skip implicit return statement - if (!_functions.back()->return_type.is_void() && value == 0) - return set_block(0); - - std::string &code = _blocks.at(_current_block); - - code += "\treturn"; - - if (value != 0) - code += ' ' + id_to_name(value); - - code += ";\n"; - - return set_block(0); - } - id leave_block_and_switch(id, id) override - { - if (!is_in_block()) - return _last_block; - - return set_block(0); - } - id leave_block_and_branch(id target, unsigned int loop_flow) override - { - if (!is_in_block()) - return _last_block; - - std::string &code = _blocks.at(_current_block); - - switch (loop_flow) - { - case 1: - code += "\tbreak;\n"; - break; - case 2: // Keep track of continue target block, so we can insert its code here later - code += "__CONTINUE__" + std::to_string(target) + "\tcontinue;\n"; - break; - } - - return set_block(0); - } - id leave_block_and_branch_conditional(id, id, id) override - { - if (!is_in_block()) - return _last_block; - - return set_block(0); - } - void leave_function() override - { - assert(_last_block != 0); - - _blocks.at(0) += "{\n" + _blocks.at(_last_block) + "}\n"; - } -}; - -codegen *reshadefx::create_codegen_glsl(bool debug_info, bool uniforms_to_spec_constants) -{ - return new codegen_glsl(debug_info, uniforms_to_spec_constants); -} diff --git a/msvc/source/effect_codegen_hlsl.cpp b/msvc/source/effect_codegen_hlsl.cpp deleted file mode 100644 index f569d92..0000000 --- a/msvc/source/effect_codegen_hlsl.cpp +++ /dev/null @@ -1,1355 +0,0 @@ -/** - * Copyright (C) 2014 Patrick Mours. All rights reserved. - * License: https://github.com/crosire/reshade#license - */ - -#include "effect_parser.hpp" -#include "effect_codegen.hpp" -#include - -using namespace reshadefx; - -class codegen_hlsl final : public codegen -{ -public: - codegen_hlsl(unsigned int shader_model, bool debug_info, bool uniforms_to_spec_constants) - : _shader_model(shader_model), _debug_info(debug_info), _uniforms_to_spec_constants(uniforms_to_spec_constants) - { - // Create default block and reserve a memory block to avoid frequent reallocations - std::string &block = _blocks.emplace(0, std::string()).first->second; - block.reserve(8192); - } - -private: - enum class naming - { - // Will be numbered when clashing with another name - general, - // Name should already be unique, so no additional steps are taken - unique, - }; - - std::string _cbuffer_block; - std::string _current_location; - std::unordered_map _names; - std::unordered_map _blocks; - bool _debug_info = false; - bool _uniforms_to_spec_constants = false; - unsigned int _shader_model = 0; - unsigned int _current_cbuffer_size = 0; - - void write_result(module &module) override - { - module = std::move(_module); - - if (_shader_model >= 40) - { - module.hlsl += "struct __sampler2D { Texture2D t; SamplerState s; };\n"; - - if (!_cbuffer_block.empty()) - module.hlsl += "cbuffer _Globals {\n" + _cbuffer_block + "};\n"; - } - else - { - module.hlsl += "struct __sampler2D { sampler2D s; float2 pixelsize; };\nuniform float2 __TEXEL_SIZE__ : register(c255);\n"; - - if (!_cbuffer_block.empty()) - module.hlsl += _cbuffer_block; - } - - module.hlsl += _blocks.at(0); - } - - template - void write_type(std::string &s, const type &type) const - { - if constexpr (is_decl) - { - if (type.has(type::q_precise)) - s += "precise "; - } - - if constexpr (is_param) - { - if (type.has(type::q_linear)) - s += "linear "; - if (type.has(type::q_noperspective)) - s += "noperspective "; - if (type.has(type::q_centroid)) - s += "centroid "; - if (type.has(type::q_nointerpolation)) - s += "nointerpolation "; - - if (type.has(type::q_inout)) - s += "inout "; - else if (type.has(type::q_in)) - s += "in "; - else if (type.has(type::q_out)) - s += "out "; - } - - switch (type.base) - { - case type::t_void: - s += "void"; - break; - case type::t_bool: - s += "bool"; - break; - case type::t_int: - s += "int"; - break; - case type::t_uint: - // In shader model 3, uints can only be used with known-positive values, so use ints instead - s += _shader_model >= 40 ? "uint" : "int"; - break; - case type::t_float: - s += "float"; - break; - case type::t_struct: - s += id_to_name(type.definition); - break; - case type::t_sampler: - s += "__sampler2D"; - break; - default: - assert(false); - } - - if (type.rows > 1) - s += std::to_string(type.rows); - if (type.cols > 1) - s += 'x' + std::to_string(type.cols); - } - void write_constant(std::string &s, const type &type, const constant &data) const - { - if (type.is_array()) - { - auto elem_type = type; - elem_type.array_length = 0; - - s += "{ "; - - for (int i = 0; i < type.array_length; ++i) - { - write_constant(s, elem_type, i < static_cast(data.array_data.size()) ? data.array_data[i] : constant()); - - if (i < type.array_length - 1) - s += ", "; - } - - s += " }"; - return; - } - - if (type.is_struct()) - { - assert(data.as_uint[0] == 0); - - s += '(' + id_to_name(type.definition) + ")0"; - return; - } - - // There can only be numeric constants - assert(type.is_numeric()); - - if (!type.is_scalar()) - { - write_type(s, type); - s += '('; - } - - for (unsigned int i = 0, components = type.components(); i < components; ++i) - { - switch (type.base) - { - case type::t_bool: - s += data.as_uint[i] ? "true" : "false"; - break; - case type::t_int: - s += std::to_string(data.as_int[i]); - break; - case type::t_uint: - s += std::to_string(data.as_uint[i]); - break; - case type::t_float: - std::string temp(_scprintf("%.8f", data.as_float[i]), '\0'); - sprintf_s(temp.data(), temp.size() + 1, "%.8f", data.as_float[i]); - s += temp; - break; - } - - if (i < components - 1) - s += ", "; - } - - if (!type.is_scalar()) - { - s += ')'; - } - } - template - void write_location(std::string &s, const location &loc) - { - if (loc.source.empty() || !_debug_info) - return; - - s += "#line " + std::to_string(loc.line); - - // Avoid writing the file name every time to reduce output text size - if constexpr (force_source) - { - s += " \"" + loc.source + '\"'; - } - else if (loc.source != _current_location) - { - s += " \"" + loc.source + '\"'; - - _current_location = loc.source; - } - - s += '\n'; - } - - std::string convert_semantic(const std::string &semantic) const - { - if (_shader_model < 40) - { - if (semantic == "SV_VERTEXID" || semantic == "VERTEXID") - return "TEXCOORD0 /* VERTEXID */"; - else if (semantic == "SV_POSITION") - return "POSITION"; - else if (semantic.compare(0, 9, "SV_TARGET") == 0) - return "COLOR" + semantic.substr(9); - else if (semantic == "SV_DEPTH") - return "DEPTH"; - } - else - { - if (semantic == "VERTEXID") - return "SV_VERTEXID"; - else if (semantic == "POSITION" || semantic == "VPOS") - return "SV_POSITION"; - else if (semantic.compare(0, 5, "COLOR") == 0) - return "SV_TARGET" + semantic.substr(5); - else if (semantic == "DEPTH") - return "SV_DEPTH"; - } - - return semantic; - } - - std::string id_to_name(id id) const - { - if (const auto it = _names.find(id); it != _names.end()) - return it->second; - return '_' + std::to_string(id); - } - - template - void define_name(const id id, std::string name) - { - if constexpr (naming == naming::general) - if (std::find_if(_names.begin(), _names.end(), [&name](const auto &it) { return it.second == name; }) != _names.end()) - name += '_' + std::to_string(id); - _names[id] = std::move(name); - } - - static void increase_indentation_level(std::string &block) - { - if (block.empty()) - return; - - for (size_t pos = 0; (pos = block.find("\n\t", pos)) != std::string::npos; pos += 3) - block.replace(pos, 2, "\n\t\t"); - - block.insert(block.begin(), '\t'); - } - - id define_struct(const location &loc, struct_info &info) override - { - info.definition = make_id(); - define_name(info.definition, info.unique_name); - - _structs.push_back(info); - - std::string &code = _blocks.at(_current_block); - - write_location(code, loc); - - code += "struct " + id_to_name(info.definition) + "\n{\n"; - - for (const auto &member : info.member_list) - { - code += '\t'; - write_type(code, member.type); // HLSL allows interpolation attributes on struct members, so handle this like a parameter - code += ' ' + member.name; - if (member.type.is_array()) - code += '[' + std::to_string(member.type.array_length) + ']'; - if (!member.semantic.empty()) - code += " : " + convert_semantic(member.semantic); - code += ";\n"; - } - - code += "};\n"; - - return info.definition; - } - id define_texture(const location &loc, texture_info &info) override - { - info.id = make_id(); - info.binding = _module.num_texture_bindings; - - _module.textures.push_back(info); - - std::string &code = _blocks.at(_current_block); - - if (_shader_model >= 40) - { - write_location(code, loc); - - code += "Texture2D " + info.unique_name + " : register(t" + std::to_string(info.binding + 0) + ");\n"; - code += "Texture2D __srgb" + info.unique_name + " : register(t" + std::to_string(info.binding + 1) + ");\n"; - - _module.num_texture_bindings += 2; - } - - return info.id; - } - id define_sampler(const location &loc, sampler_info &info) override - { - info.id = make_id(); - - define_name(info.id, info.unique_name); - - const auto texture = std::find_if(_module.textures.begin(), _module.textures.end(), - [&info](const auto &it) { return it.unique_name == info.texture_name; }); - assert(texture != _module.textures.end()); - - std::string &code = _blocks.at(_current_block); - - if (_shader_model >= 40) - { - // Try and reuse a sampler binding with the same sampler description - const auto existing_sampler = std::find_if(_module.samplers.begin(), _module.samplers.end(), - [&info](const auto &it) { return it.filter == info.filter && it.address_u == info.address_u && it.address_v == info.address_v && it.address_w == info.address_w && it.min_lod == info.min_lod && it.max_lod == info.max_lod && it.lod_bias == info.lod_bias; }); - - if (existing_sampler != _module.samplers.end()) - { - info.binding = existing_sampler->binding; - } - else - { - info.binding = _module.num_sampler_bindings++; - - code += "SamplerState __s" + std::to_string(info.binding) + " : register(s" + std::to_string(info.binding) + ");\n"; - } - - assert(info.srgb == 0 || info.srgb == 1); - info.texture_binding = texture->binding + info.srgb; // Offset binding by one to choose the SRGB variant - - write_location(code, loc); - - code += "static const __sampler2D " + id_to_name(info.id) + " = { " + (info.srgb ? "__srgb" : "") + info.texture_name + ", __s" + std::to_string(info.binding) + " };\n"; - } - else - { - info.binding = _module.num_sampler_bindings++; - - code += "sampler2D __" + info.unique_name + "_s : register(s" + std::to_string(info.binding) + ");\n"; - - write_location(code, loc); - - code += "static const __sampler2D " + id_to_name(info.id) + " = { __" + info.unique_name + "_s, float2("; - - if (texture->semantic.empty()) - code += std::to_string(1.0f / texture->width) + ", " + std::to_string(1.0f / texture->height); - else - code += texture->semantic + "_PIXEL_SIZE"; // Expect application to set inverse texture size via a define if it is not known here - - code += ") }; \n"; - } - - _module.samplers.push_back(info); - - return info.id; - } - id define_uniform(const location &loc, uniform_info &info) override - { - const id res = make_id(); - - define_name(res, "_Globals_" + info.name); - - if (_uniforms_to_spec_constants && info.has_initializer_value) - { - std::string &code = _blocks.at(_current_block); - - write_location(code, loc); - - code += "static const "; - write_type(code, info.type); - code += ' ' + id_to_name(res) + " = "; - if (!info.type.is_scalar()) - write_type(code, info.type); - code += "(SPEC_CONSTANT_" + info.name + ");\n"; - - _module.spec_constants.push_back(info); - } - else - { - const unsigned int size = info.type.rows * info.type.cols * std::max(1, info.type.array_length) * 4; - const unsigned int alignment = 16 - (_current_cbuffer_size % 16); - - _current_cbuffer_size += (size > alignment && (alignment != 16 || size <= 16)) ? size + alignment : size; - - info.size = size; - info.offset = _current_cbuffer_size - size; - - write_location(_cbuffer_block, loc); - - if (_shader_model < 40) - { - type type = info.type; - // The HLSL compiler tries to evaluate boolean values with temporary registers, which breaks branches, so force it to use constant float registers - if (type.is_boolean()) - type.base = type::t_float; - - // Simply put each uniform into a separate constant register in shader model 3 for now - info.offset *= 4; - - // Every constant register is 16 bytes wide, so divide memory offset by 16 to get the constant register index - write_type(_cbuffer_block, type); - _cbuffer_block += ' ' + id_to_name(res) + " : register(c" + std::to_string(info.offset / 16) + ");\n"; - } - else - { - _cbuffer_block += '\t'; - write_type(_cbuffer_block, info.type); - _cbuffer_block += ' ' + id_to_name(res) + ";\n"; - } - - _module.uniforms.push_back(info); - } - - return res; - } - id define_variable(const location &loc, const type &type, std::string name, bool global, id initializer_value) override - { - const id res = make_id(); - - if (!name.empty()) - define_name(res, name); - - std::string &code = _blocks.at(_current_block); - - write_location(code, loc); - - if (!global) - code += '\t'; - - write_type(code, type); - code += ' ' + id_to_name(res); - - if (type.is_array()) - code += '[' + std::to_string(type.array_length) + ']'; - - if (initializer_value != 0) - code += " = " + id_to_name(initializer_value); - - code += ";\n"; - - return res; - } - id define_function(const location &loc, function_info &info) override - { - return define_function(loc, info, _shader_model >= 40); - } - id define_function(const location &loc, function_info &info, bool is_entry_point) - { - std::string name = info.unique_name; - if (!is_entry_point) - name += '_'; - - info.definition = make_id(); - define_name(info.definition, name); - - std::string &code = _blocks.at(_current_block); - - write_location(code, loc); - - write_type(code, info.return_type); - code += ' ' + id_to_name(info.definition) + '('; - - for (size_t i = 0, num_params = info.parameter_list.size(); i < num_params; ++i) - { - auto ¶m = info.parameter_list[i]; - - param.definition = make_id(); - define_name(param.definition, param.name); - - code += '\n'; - write_location(code, param.location); - code += '\t'; - write_type(code, param.type); - code += ' ' + id_to_name(param.definition); - - if (param.type.is_array()) - code += '[' + std::to_string(param.type.array_length) + ']'; - - if (is_entry_point && !param.semantic.empty()) - code += " : " + convert_semantic(param.semantic); - - if (i < num_params - 1) - code += ','; - } - - code += ')'; - - if (is_entry_point && !info.return_semantic.empty()) - code += " : " + convert_semantic(info.return_semantic); - - code += '\n'; - - _functions.push_back(std::make_unique(info)); - - return info.definition; - } - void define_entry_point(const function_info &func, bool is_ps) override - { - if (const auto it = std::find_if(_module.entry_points.begin(), _module.entry_points.end(), - [&func](const auto &ep) { return ep.first == func.unique_name; }); it != _module.entry_points.end()) - return; - - _module.entry_points.push_back({ func.unique_name, is_ps }); - - // Only have to rewrite the entry point function signature in shader model 3 - if (_shader_model >= 40) - return; - - auto entry_point = func; - - const auto is_color_semantic = [](const std::string &semantic) { return semantic.compare(0, 9, "SV_TARGET") == 0 || semantic.compare(0, 5, "COLOR") == 0; }; - const auto is_position_semantic = [](const std::string &semantic) { return semantic == "SV_POSITION" || semantic == "POSITION"; }; - - const auto ret = make_id(); - define_name(ret, "ret"); - - std::string position_variable_name; - - if (func.return_type.is_struct() && !is_ps) - // If this function returns a struct which contains a position output, keep track of its member name - for (const auto &member : find_struct(func.return_type.definition).member_list) - if (is_position_semantic(member.semantic)) - position_variable_name = id_to_name(ret) + '.' + member.name; - if (is_color_semantic(func.return_semantic)) - // The COLOR output semantic has to be a four-component vector in shader model 3, so enforce that - entry_point.return_type.rows = 4; - else if (is_position_semantic(func.return_semantic) && !is_ps) - position_variable_name = id_to_name(ret); - - for (auto ¶m : entry_point.parameter_list) - if (is_color_semantic(param.semantic)) - param.type.rows = 4; - else if (is_position_semantic(param.semantic)) - if (is_ps) // Change the position input semantic in pixel shaders - param.semantic = "VPOS"; - else // Keep track of the position output variable - position_variable_name = param.name; - - define_function({}, entry_point, true); - enter_block(create_block()); - - std::string &code = _blocks.at(_current_block); - - // Clear all color output parameters so no component is left uninitialized - for (auto ¶m : entry_point.parameter_list) - if (is_color_semantic(param.semantic)) - code += '\t' + param.name + " = float4(0.0, 0.0, 0.0, 0.0);\n"; - - code += '\t'; - if (is_color_semantic(func.return_semantic)) - code += "const float4 " + id_to_name(ret) + " = float4("; - else if (!func.return_type.is_void()) - write_type(code, func.return_type), code += ' ' + id_to_name(ret) + " = "; - - // Call the function this entry point refers to - code += id_to_name(func.definition) + '('; - - for (size_t i = 0, num_params = func.parameter_list.size(); i < num_params; ++i) - { - code += func.parameter_list[i].name; - - if (is_color_semantic(func.parameter_list[i].semantic)) - { - code += '.'; - for (unsigned int k = 0; k < func.parameter_list[i].type.rows; k++) - code += "xyzw"[k]; - } - - if (i < num_params - 1) - code += ", "; - } - - code += ')'; - - // Cast the output value to a four-component vector - if (is_color_semantic(func.return_semantic)) - { - for (unsigned int i = 0; i < 4 - func.return_type.rows; i++) - code += ", 0.0"; - code += ')'; - } - - code += ";\n"; - - // Shift everything by half a viewport pixel to workaround the different half-pixel offset in D3D9 (https://aras-p.info/blog/2016/04/08/solving-dx9-half-pixel-offset/) - if (!position_variable_name.empty() && !is_ps) // Check if we are in a vertex shader definition - code += '\t' + position_variable_name + ".xy += __TEXEL_SIZE__ * " + position_variable_name + ".ww;\n"; - - leave_block_and_return(func.return_type.is_void() ? 0 : ret); - leave_function(); - } - - id emit_load(const expression &exp) override - { - if (exp.is_constant) - return emit_constant(exp.type, exp.constant); - else if (exp.chain.empty()) // Can refer to values without access chain directly - return exp.base; - - const id res = make_id(); - - std::string &code = _blocks.at(_current_block); - - write_location(code, exp.location); - - code += '\t'; - write_type(code, exp.type); - code += ' ' + id_to_name(res); - - if (exp.type.is_array()) - code += '[' + std::to_string(exp.type.array_length) + ']'; - - code += " = "; - - static const char s_matrix_swizzles[16][5] = { - "_m00", "_m01", "_m02", "_m03", - "_m10", "_m11", "_m12", "_m13", - "_m20", "_m21", "_m22", "_m23", - "_m30", "_m31", "_m32", "_m33" - }; - - std::string newcode = id_to_name(exp.base); - - for (const auto &op : exp.chain) - { - switch (op.op) - { - case expression::operation::op_cast: - { std::string type; write_type(type, op.to); - newcode = "((" + type + ')' + newcode + ')'; } // Cast in parentheses so that a subsequent operation operates on the casted value - break; - case expression::operation::op_member: - newcode += '.'; - newcode += find_struct(op.from.definition).member_list[op.index].name; - break; - case expression::operation::op_constant_index: - newcode += '[' + std::to_string(op.index) + ']'; - break; - case expression::operation::op_dynamic_index: - newcode += '[' + id_to_name(op.index) + ']'; - break; - case expression::operation::op_swizzle: - newcode += '.'; - for (unsigned int i = 0; i < 4 && op.swizzle[i] >= 0; ++i) - if (op.from.is_matrix()) - newcode += s_matrix_swizzles[op.swizzle[i]]; - else - newcode += "xyzw"[op.swizzle[i]]; - break; - } - } - - code += newcode; - code += ";\n"; - - return res; - } - void emit_store(const expression &exp, id value) override - { - std::string &code = _blocks.at(_current_block); - - write_location(code, exp.location); - - code += '\t' + id_to_name(exp.base); - - static const char s_matrix_swizzles[16][5] = { - "_m00", "_m01", "_m02", "_m03", - "_m10", "_m11", "_m12", "_m13", - "_m20", "_m21", "_m22", "_m23", - "_m30", "_m31", "_m32", "_m33" - }; - - for (const auto &op : exp.chain) - { - switch (op.op) - { - case expression::operation::op_member: - code += '.'; - code += find_struct(op.from.definition).member_list[op.index].name; - break; - case expression::operation::op_dynamic_index: - code += '[' + id_to_name(op.index) + ']'; - break; - case expression::operation::op_constant_index: - code += '[' + std::to_string(op.index) + ']'; - break; - case expression::operation::op_swizzle: - code += '.'; - for (unsigned int i = 0; i < 4 && op.swizzle[i] >= 0; ++i) - if (op.from.is_matrix()) - code += s_matrix_swizzles[op.swizzle[i]]; - else - code += "xyzw"[op.swizzle[i]]; - break; - } - } - - code += " = " + id_to_name(value) + ";\n"; - } - - id emit_constant(const type &type, const constant &data) override - { - const id res = make_id(); - - std::string &code = _blocks.at(_current_block); - - code += "\tconst "; - write_type(code, type); - code += ' ' + id_to_name(res); - - if (type.is_array()) - code += '[' + std::to_string(type.array_length) + ']'; - - code += " = "; - write_constant(code, type, data); - code += ";\n"; - - return res; - } - - id emit_unary_op(const location &loc, tokenid op, const type &res_type, id val) override - { - const id res = make_id(); - - std::string &code = _blocks.at(_current_block); - - write_location(code, loc); - - code += '\t'; - write_type(code, res_type); - code += ' ' + id_to_name(res) + " = "; - - if (_shader_model < 40 && op == tokenid::tilde) - code += "0xFFFFFFFF - "; // Emulate bitwise not operator on shader model 3 - else - code += char(op); - - code += id_to_name(val) + ";\n"; - - return res; - } - id emit_binary_op(const location &loc, tokenid op, const type &res_type, const type &, id lhs, id rhs) override - { - const id res = make_id(); - - std::string &code = _blocks.at(_current_block); - - write_location(code, loc); - - code += '\t'; - write_type(code, res_type); - code += ' ' + id_to_name(res) + " = "; - - if (_shader_model < 40) - { - // See bitwise shift operator emulation below - if (op == tokenid::less_less || op == tokenid::less_less_equal) - code += '('; - else if (op == tokenid::greater_greater || op == tokenid::greater_greater_equal) - code += "floor("; - } - - code += id_to_name(lhs) + ' '; - - switch (op) - { - case tokenid::plus: - case tokenid::plus_plus: - case tokenid::plus_equal: - code += '+'; - break; - case tokenid::minus: - case tokenid::minus_minus: - case tokenid::minus_equal: - code += '-'; - break; - case tokenid::star: - case tokenid::star_equal: - code += '*'; - break; - case tokenid::slash: - case tokenid::slash_equal: - code += '/'; - break; - case tokenid::percent: - case tokenid::percent_equal: - code += '%'; - break; - case tokenid::caret: - case tokenid::caret_equal: - code += '^'; - break; - case tokenid::pipe: - case tokenid::pipe_equal: - code += '|'; - break; - case tokenid::ampersand: - case tokenid::ampersand_equal: - code += '&'; - break; - case tokenid::less_less: - case tokenid::less_less_equal: - code += _shader_model >= 40 ? "<<" : ") * exp2("; // Emulate bitwise shift operators on shader model 3 - break; - case tokenid::greater_greater: - case tokenid::greater_greater_equal: - code += _shader_model >= 40 ? ">>" : ") / exp2("; - break; - case tokenid::pipe_pipe: - code += "||"; - break; - case tokenid::ampersand_ampersand: - code += "&&"; - break; - case tokenid::less: - code += '<'; - break; - case tokenid::less_equal: - code += "<="; - break; - case tokenid::greater: - code += '>'; - break; - case tokenid::greater_equal: - code += ">="; - break; - case tokenid::equal_equal: - code += "=="; - break; - case tokenid::exclaim_equal: - code += "!="; - break; - default: - assert(false); - } - - code += ' ' + id_to_name(rhs); - - if (_shader_model < 40) - { - // See bitwise shift operator emulation above - if (op == tokenid::less_less || op == tokenid::less_less_equal || - op == tokenid::greater_greater || op == tokenid::greater_greater_equal) - code += ')'; - } - - code += ";\n"; - - return res; - } - id emit_ternary_op(const location &loc, tokenid op, const type &res_type, id condition, id true_value, id false_value) override - { - assert(op == tokenid::question); - - const id res = make_id(); - - std::string &code = _blocks.at(_current_block); - - write_location(code, loc); - - code += '\t'; - write_type(code, res_type); - code += ' ' + id_to_name(res); - - if (res_type.is_array()) - code += '[' + std::to_string(res_type.array_length) + ']'; - - code += " = " + id_to_name(condition) + " ? " + id_to_name(true_value) + " : " + id_to_name(false_value) + ";\n"; - - return res; - } - id emit_call(const location &loc, id function, const type &res_type, const std::vector &args) override - { - for (const auto &arg : args) - assert(arg.chain.empty() && arg.base != 0); - - const id res = make_id(); - - std::string &code = _blocks.at(_current_block); - - write_location(code, loc); - - code += '\t'; - - if (!res_type.is_void()) - { - write_type(code, res_type); - code += ' ' + id_to_name(res); - - if (res_type.is_array()) - code += '[' + std::to_string(res_type.array_length) + ']'; - - code += " = "; - } - - code += id_to_name(function) + '('; - - for (size_t i = 0, num_args = args.size(); i < num_args; ++i) - { - code += id_to_name(args[i].base); - - if (i < num_args - 1) - code += ", "; - } - - code += ");\n"; - - return res; - } - id emit_call_intrinsic(const location &loc, id intrinsic, const type &res_type, const std::vector &args) override - { - for (const auto &arg : args) - assert(arg.chain.empty() && arg.base != 0); - - const id res = make_id(); - - std::string &code = _blocks.at(_current_block); - - write_location(code, loc); - - code += '\t'; - - enum - { -#define IMPLEMENT_INTRINSIC_HLSL(name, i, code) name##i, -#include "effect_symbol_table_intrinsics.inl" - }; - - if (_shader_model >= 40 && (intrinsic == tex2Dsize0 || intrinsic == tex2Dsize1)) - { - // Implementation of the 'tex2Dsize' intrinsic passes the result variable into 'GetDimensions' as output argument - write_type(code, res_type); - code += ' ' + id_to_name(res) + "; "; - } - else if (!res_type.is_void()) - { - write_type(code, res_type); - code += ' ' + id_to_name(res) + " = "; - } - - switch (intrinsic) - { -#define IMPLEMENT_INTRINSIC_HLSL(name, i, code) case name##i: code break; -#include "effect_symbol_table_intrinsics.inl" - default: - assert(false); - } - - code += ";\n"; - - return res; - } - id emit_construct(const location &loc, const type &type, const std::vector &args) override - { - for (const auto &arg : args) - assert((arg.type.is_scalar() || type.is_array()) && arg.chain.empty() && arg.base != 0); - - const id res = make_id(); - - std::string &code = _blocks.at(_current_block); - - write_location(code, loc); - - code += '\t'; - write_type(code, type); - code += ' ' + id_to_name(res); - - if (type.is_array()) - code += '[' + std::to_string(type.array_length) + ']'; - - code += " = "; - - if (type.is_array()) - code += "{ "; - else - write_type(code, type), code += '('; - - for (size_t i = 0, num_args = args.size(); i < num_args; ++i) - { - code += id_to_name(args[i].base); - - if (i < num_args - 1) - code += ", "; - } - - if (type.is_array()) - code += " }"; - else - code += ')'; - - code += ";\n"; - - return res; - } - - void emit_if(const location &loc, id condition_value, id condition_block, id true_statement_block, id false_statement_block, unsigned int flags) override - { - assert(condition_value != 0 && condition_block != 0 && true_statement_block != 0 && false_statement_block != 0); - - std::string &code = _blocks.at(_current_block); - - std::string &true_statement_data = _blocks.at(true_statement_block); - std::string &false_statement_data = _blocks.at(false_statement_block); - - increase_indentation_level(true_statement_data); - increase_indentation_level(false_statement_data); - - code += _blocks.at(condition_block); - - write_location(code, loc); - - code += '\t'; - - if (flags & 0x1) code += "[flatten] "; - if (flags & 0x2) code += "[branch] "; - - code += "if (" + id_to_name(condition_value) + ")\n\t{\n"; - code += true_statement_data; - code += "\t}\n"; - - if (!false_statement_data.empty()) - { - code += "\telse\n\t{\n"; - code += false_statement_data; - code += "\t}\n"; - } - - // Remove consumed blocks to save memory - _blocks.erase(condition_block); - _blocks.erase(true_statement_block); - _blocks.erase(false_statement_block); - } - id emit_phi(const location &loc, id condition_value, id condition_block, id true_value, id true_statement_block, id false_value, id false_statement_block, const type &type) override - { - assert(condition_value != 0 && condition_block != 0 && true_value != 0 && true_statement_block != 0 && false_value != 0 && false_statement_block != 0); - - std::string &code = _blocks.at(_current_block); - - std::string &true_statement_data = _blocks.at(true_statement_block); - std::string &false_statement_data = _blocks.at(false_statement_block); - - increase_indentation_level(true_statement_data); - increase_indentation_level(false_statement_data); - - const id res = make_id(); - - code += _blocks.at(condition_block); - - code += '\t'; - write_type(code, type); - code += ' ' + id_to_name(res) + ";\n"; - - write_location(code, loc); - - code += "\tif (" + id_to_name(condition_value) + ")\n\t{\n"; - code += (true_statement_block != condition_block ? true_statement_data : std::string()); - code += "\t\t" + id_to_name(res) + " = " + id_to_name(true_value) + ";\n"; - code += "\t}\n\telse\n\t{\n"; - code += (false_statement_block != condition_block ? false_statement_data : std::string()); - code += "\t\t" + id_to_name(res) + " = " + id_to_name(false_value) + ";\n"; - code += "\t}\n"; - - // Remove consumed blocks to save memory - _blocks.erase(condition_block); - _blocks.erase(true_statement_block); - _blocks.erase(false_statement_block); - - return res; - } - void emit_loop(const location &loc, id condition_value, id prev_block, id header_block, id condition_block, id loop_block, id continue_block, unsigned int flags) override - { - assert(condition_value != 0 && prev_block != 0 && header_block != 0 && loop_block != 0 && continue_block != 0); - - std::string &code = _blocks.at(_current_block); - - std::string &loop_data = _blocks.at(loop_block); - std::string &continue_data = _blocks.at(continue_block); - - increase_indentation_level(loop_data); - increase_indentation_level(loop_data); - increase_indentation_level(continue_data); - - code += _blocks.at(prev_block); - - if (condition_block == 0) - code += "\tbool " + id_to_name(condition_value) + ";\n"; - else - code += _blocks.at(condition_block); - - write_location(code, loc); - - code += '\t'; - - if (flags & 0x1) code += "[unroll] "; - if (flags & 0x2) code += "[loop] "; - - if (condition_block == 0) - { - // Convert variable initializer to assignment statement - auto pos_assign = continue_data.rfind(id_to_name(condition_value)); - auto pos_prev_assign = continue_data.rfind('\t', pos_assign); - continue_data.erase(pos_prev_assign + 1, pos_assign - pos_prev_assign - 1); - - // We need to add the continue block to all "continue" statements as well - const std::string continue_id = "__CONTINUE__" + std::to_string(continue_block); - for (size_t offset = 0; (offset = loop_data.find(continue_id, offset)) != std::string::npos; offset += continue_data.size()) - loop_data.replace(offset, continue_id.size(), continue_data); - - code += "do\n\t{\n\t\t{\n"; - code += loop_data; // Encapsulate loop body into another scope, so not to confuse any local variables with the current iteration variable accessed in the continue block below - code += "\t\t}\n"; - code += continue_data; - code += "\t}\n\twhile (" + id_to_name(condition_value) + ");\n"; - } - else - { - std::string &condition_data = _blocks.at(condition_block); - - increase_indentation_level(condition_data); - - // Convert variable initializer to assignment statement - auto pos_assign = condition_data.rfind(id_to_name(condition_value)); - auto pos_prev_assign = condition_data.rfind('\t', pos_assign); - condition_data.erase(pos_prev_assign + 1, pos_assign - pos_prev_assign - 1); - - const std::string continue_id = "__CONTINUE__" + std::to_string(continue_block); - for (size_t offset = 0; (offset = loop_data.find(continue_id, offset)) != std::string::npos; offset += continue_data.size()) - loop_data.replace(offset, continue_id.size(), continue_data + condition_data); - - code += "while (" + id_to_name(condition_value) + ")\n\t{\n\t\t{\n"; - code += loop_data; - code += "\t\t}\n"; - code += continue_data; - code += condition_data; - code += "\t}\n"; - - _blocks.erase(condition_block); - } - - // Remove consumed blocks to save memory - _blocks.erase(prev_block); - _blocks.erase(header_block); - _blocks.erase(loop_block); - _blocks.erase(continue_block); - } - void emit_switch(const location &loc, id selector_value, id selector_block, id default_label, const std::vector &case_literal_and_labels, unsigned int flags) override - { - assert(selector_value != 0 && selector_block != 0 && default_label != 0); - - std::string &code = _blocks.at(_current_block); - - code += _blocks.at(selector_block); - - // Switch statements do not work correctly in shader model 3 if a constant is used as selector value (this is a D3DCompiler bug), so replace them with if statements instead there - if (_shader_model >= 40) - { - write_location(code, loc); - - code += '\t'; - - if (flags & 0x1) code += "[flatten] "; - if (flags & 0x2) code += "[branch] "; - - code += "switch (" + id_to_name(selector_value) + ")\n\t{\n"; - - for (size_t i = 0; i < case_literal_and_labels.size(); i += 2) - { - assert(case_literal_and_labels[i + 1] != 0); - - std::string &case_data = _blocks.at(case_literal_and_labels[i + 1]); - - increase_indentation_level(case_data); - - code += "\tcase " + std::to_string(case_literal_and_labels[i]) + ": {\n"; - code += case_data; - code += "\t}\n"; - } - - if (default_label != _current_block) - { - std::string &default_data = _blocks.at(default_label); - - increase_indentation_level(default_data); - - code += "\tdefault: {\n"; - code += default_data; - code += "\t}\n"; - - _blocks.erase(default_label); - } - - code += "\t}\n"; - } - else - { - write_location(code, loc); - - code += "\t[unroll] do { "; // This dummy loop makes "break" statements work - - if (flags & 0x1) code += "[flatten] "; - if (flags & 0x2) code += "[branch] "; - - for (size_t i = 0; i < case_literal_and_labels.size(); i += 2) - { - assert(case_literal_and_labels[i + 1] != 0); - - std::string &case_data = _blocks.at(case_literal_and_labels[i + 1]); - - increase_indentation_level(case_data); - - code += "if (" + id_to_name(selector_value) + " == " + std::to_string(case_literal_and_labels[i]) + ")\n\t{\n"; - code += case_data; - code += "\t}\n\telse\n\t"; - - } - - code += "{\n"; - - if (default_label != _current_block) - { - std::string &default_data = _blocks.at(default_label); - - increase_indentation_level(default_data); - - code += default_data; - - _blocks.erase(default_label); - } - - code += "\t} } while (false);\n"; - } - - // Remove consumed blocks to save memory - _blocks.erase(selector_block); - for (size_t i = 0; i < case_literal_and_labels.size(); i += 2) - _blocks.erase(case_literal_and_labels[i + 1]); - } - - id create_block() override - { - const id res = make_id(); - - std::string &block = _blocks.emplace(res, std::string()).first->second; - // Reserve a decently big enough memory block to avoid frequent reallocations - block.reserve(4096); - - return res; - } - id set_block(id id) override - { - _last_block = _current_block; - _current_block = id; - - return _last_block; - } - void enter_block(id id) override - { - _current_block = id; - } - id leave_block_and_kill() override - { - if (!is_in_block()) - return 0; - - std::string &code = _blocks.at(_current_block); - - code += "\tdiscard;\n"; - - return set_block(0); - } - id leave_block_and_return(id value) override - { - if (!is_in_block()) - return 0; - - // Skip implicit return statement - if (!_functions.back()->return_type.is_void() && value == 0) - return set_block(0); - - std::string &code = _blocks.at(_current_block); - - code += "\treturn"; - - if (value != 0) - code += ' ' + id_to_name(value); - - code += ";\n"; - - return set_block(0); - } - id leave_block_and_switch(id, id) override - { - if (!is_in_block()) - return _last_block; - - return set_block(0); - } - id leave_block_and_branch(id target, unsigned int loop_flow) override - { - if (!is_in_block()) - return _last_block; - - std::string &code = _blocks.at(_current_block); - - switch (loop_flow) - { - case 1: - code += "\tbreak;\n"; - break; - case 2: // Keep track of continue target block, so we can insert its code here later - code += "__CONTINUE__" + std::to_string(target) + "\tcontinue;\n"; - break; - } - - return set_block(0); - } - id leave_block_and_branch_conditional(id, id, id) override - { - if (!is_in_block()) - return _last_block; - - return set_block(0); - } - void leave_function() override - { - assert(_last_block != 0); - - _blocks.at(0) += "{\n" + _blocks.at(_last_block) + "}\n"; - } -}; - -codegen *reshadefx::create_codegen_hlsl(unsigned int shader_model, bool debug_info, bool uniforms_to_spec_constants) -{ - return new codegen_hlsl(shader_model, debug_info, uniforms_to_spec_constants); -} diff --git a/msvc/source/effect_expression.cpp b/msvc/source/effect_expression.cpp deleted file mode 100644 index 42c7017..0000000 --- a/msvc/source/effect_expression.cpp +++ /dev/null @@ -1,451 +0,0 @@ -/** - * Copyright (C) 2014 Patrick Mours. All rights reserved. - * License: https://github.com/crosire/reshade#license - */ - -#include "effect_expression.hpp" -#include "effect_lexer.hpp" -#include "effect_codegen.hpp" -#include - -reshadefx::type reshadefx::type::merge(const type &lhs, const type &rhs) -{ - type result = { std::max(lhs.base, rhs.base) }; - - // If one side of the expression is scalar, it needs to be promoted to the same dimension as the other side - if ((lhs.rows == 1 && lhs.cols == 1) || (rhs.rows == 1 && rhs.cols == 1)) - { - result.rows = std::max(lhs.rows, rhs.rows); - result.cols = std::max(lhs.cols, rhs.cols); - } - else // Otherwise dimensions match or one side is truncated to match the other one - { - result.rows = std::min(lhs.rows, rhs.rows); - result.cols = std::min(lhs.cols, rhs.cols); - } - - // Some qualifiers propagate to the result - result.qualifiers = (lhs.qualifiers & type::q_precise) | (rhs.qualifiers & type::q_precise); - - return result; -} - -void reshadefx::expression::reset_to_lvalue(const reshadefx::location &loc, reshadefx::codegen::id in_base, const reshadefx::type &in_type) -{ - type = in_type; - base = in_base; - location = loc; - is_lvalue = true; - is_constant = false; - chain.clear(); -} -void reshadefx::expression::reset_to_rvalue(const reshadefx::location &loc, reshadefx::codegen::id in_base, const reshadefx::type &in_type) -{ - type = in_type; - type.qualifiers |= type::q_const; - base = in_base; - location = loc; - is_lvalue = false; - is_constant = false; - chain.clear(); -} - -void reshadefx::expression::reset_to_rvalue_constant(const reshadefx::location &loc, bool data) -{ - type = { type::t_bool, 1, 1, type::q_const }; - base = 0; constant = {}; constant.as_uint[0] = data; - location = loc; - is_lvalue = false; - is_constant = true; - chain.clear(); -} -void reshadefx::expression::reset_to_rvalue_constant(const reshadefx::location &loc, float data) -{ - type = { type::t_float, 1, 1, type::q_const }; - base = 0; constant = {}; constant.as_float[0] = data; - location = loc; - is_lvalue = false; - is_constant = true; - chain.clear(); -} -void reshadefx::expression::reset_to_rvalue_constant(const reshadefx::location &loc, int32_t data) -{ - type = { type::t_int, 1, 1, type::q_const }; - base = 0; constant = {}; constant.as_int[0] = data; - location = loc; - is_lvalue = false; - is_constant = true; - chain.clear(); -} -void reshadefx::expression::reset_to_rvalue_constant(const reshadefx::location &loc, uint32_t data) -{ - type = { type::t_uint, 1, 1, type::q_const }; - base = 0; constant = {}; constant.as_uint[0] = data; - location = loc; - is_lvalue = false; - is_constant = true; - chain.clear(); -} -void reshadefx::expression::reset_to_rvalue_constant(const reshadefx::location &loc, std::string data) -{ - type = { type::t_string, 0, 0, type::q_const }; - base = 0; constant = {}; constant.string_data = std::move(data); - location = loc; - is_lvalue = false; - is_constant = true; - chain.clear(); -} -void reshadefx::expression::reset_to_rvalue_constant(const reshadefx::location &loc, reshadefx::constant data, const reshadefx::type &in_type) -{ - type = in_type; - type.qualifiers |= type::q_const; - base = 0; constant = std::move(data); - location = loc; - is_lvalue = false; - is_constant = true; - chain.clear(); -} - -void reshadefx::expression::add_cast_operation(const reshadefx::type &cast_type) -{ - // First try to simplify the cast with a swizzle operation (only works with scalars and vectors) - if (type.cols == 1 && cast_type.cols == 1 && type.rows != cast_type.rows) - { - signed char swizzle[] = { 0, 1, 2, 3 }; - // Ignore components in a demotion cast - for (unsigned int i = cast_type.rows; i < 4; ++i) - swizzle[i] = -1; - // Use the last component to fill in a promotion cast - for (unsigned int i = type.rows; i < cast_type.rows; ++i) - swizzle[i] = swizzle[type.rows - 1]; - - add_swizzle_access(swizzle, cast_type.rows); - } - - if (type == cast_type) - return; // There is nothing more to do if the expression is already of the target type at this point - - if (is_constant) - { - const auto cast_constant = [](reshadefx::constant &constant, const reshadefx::type &from, const reshadefx::type &to) { - // Handle scalar to vector promotion first - if (from.is_scalar() && !to.is_scalar()) - for (unsigned int i = 1; i < to.components(); ++i) - constant.as_uint[i] = constant.as_uint[0]; - - // Next check whether the type needs casting as well (and don't convert between signed/unsigned, since that is handled by the union) - if (from.base == to.base || from.is_floating_point() == to.is_floating_point()) - return; - - if (!to.is_floating_point()) - for (unsigned int i = 0; i < to.components(); ++i) - constant.as_uint[i] = static_cast(constant.as_float[i]); - else - for (unsigned int i = 0; i < to.components(); ++i) - constant.as_float[i] = static_cast(constant.as_int[i]); - }; - - for (auto &element : constant.array_data) - cast_constant(element, type, cast_type); - - cast_constant(constant, type, cast_type); - } - else - { - assert(!type.is_array() && !cast_type.is_array()); - - chain.push_back({ operation::op_cast, type, cast_type }); - } - - type = cast_type; -} -void reshadefx::expression::add_member_access(unsigned int index, const reshadefx::type &in_type) -{ - assert(type.is_struct()); - - chain.push_back({ operation::op_member, type, in_type, index }); - - // The type is now the type of the member that was accessed - type = in_type; - is_constant = false; -} -void reshadefx::expression::add_dynamic_index_access(reshadefx::codegen::id index_expression) -{ - assert(type.is_numeric() && !is_constant); - - auto prev_type = type; - - if (type.is_array()) - { - type.array_length = 0; - } - else if (type.is_matrix()) - { - type.rows = type.cols; - type.cols = 1; - } - else if (type.is_vector()) - { - type.rows = 1; - } - - chain.push_back({ operation::op_dynamic_index, prev_type, type, index_expression }); -} -void reshadefx::expression::add_constant_index_access(unsigned int index) -{ - assert(type.is_numeric() && !type.is_scalar()); - - auto prev_type = type; - - if (type.is_array()) - { - type.array_length = 0; - } - else if (type.is_matrix()) - { - type.rows = type.cols; - type.cols = 1; - } - else if (type.is_vector()) - { - type.rows = 1; - } - - if (is_constant) - { - if (prev_type.is_array()) - { - constant = constant.array_data[index]; - } - else if (prev_type.is_matrix()) // Indexing into a matrix returns a row of it as a vector - { - for (unsigned int i = 0; i < 4; ++i) - constant.as_uint[i] = constant.as_uint[index * 4 + i]; - } - else // Indexing into a vector returns the element as a scalar - { - constant.as_uint[0] = constant.as_uint[index]; - } - } - else - { - chain.push_back({ operation::op_constant_index, prev_type, type, index }); - } -} -void reshadefx::expression::add_swizzle_access(const signed char swizzle[4], unsigned int length) -{ - assert(type.is_numeric() && !type.is_array()); - - const auto prev_type = type; - - type.rows = length; - type.cols = 1; - - if (is_constant) - { - assert(constant.array_data.empty()); - - uint32_t data[16]; - memcpy(data, &constant.as_uint[0], sizeof(data)); - for (unsigned int i = 0; i < length; ++i) - constant.as_uint[i] = data[swizzle[i]]; - memset(&constant.as_uint[length], 0, sizeof(uint32_t) * (16 - length)); // Clear the rest of the constant - } - else if (length == 1 && prev_type.is_vector()) // Use indexing when possible since the code generation logic is simpler in SPIR-V - { - chain.push_back({ operation::op_constant_index, prev_type, type, static_cast(swizzle[0]) }); - } - else - { - chain.push_back({ operation::op_swizzle, prev_type, type, 0, { swizzle[0], swizzle[1], swizzle[2], swizzle[3] } }); - } -} - -bool reshadefx::expression::evaluate_constant_expression(reshadefx::tokenid op) -{ - if (!is_constant) - return false; - - switch (op) - { - case tokenid::exclaim: - for (unsigned int i = 0; i < type.components(); ++i) - constant.as_uint[i] = !constant.as_uint[i]; - break; - case tokenid::minus: - if (type.is_floating_point()) - for (unsigned int i = 0; i < type.components(); ++i) - constant.as_float[i] = -constant.as_float[i]; - else - for (unsigned int i = 0; i < type.components(); ++i) - constant.as_int[i] = -constant.as_int[i]; - break; - case tokenid::tilde: - for (unsigned int i = 0; i < type.components(); ++i) - constant.as_uint[i] = ~constant.as_uint[i]; - break; - } - - return true; -} -bool reshadefx::expression::evaluate_constant_expression(reshadefx::tokenid op, const reshadefx::constant &rhs) -{ - if (!is_constant) - return false; - - switch (op) - { - case tokenid::percent: - if (type.is_floating_point()) { - for (unsigned int i = 0; i < type.components(); ++i) - if (rhs.as_float[i] != 0) - constant.as_float[i] = fmodf(constant.as_float[i], rhs.as_float[i]); - } - else if (type.is_signed()) { - for (unsigned int i = 0; i < type.components(); ++i) - if (rhs.as_int[i] != 0) - constant.as_int[i] %= rhs.as_int[i]; - } - else { - for (unsigned int i = 0; i < type.components(); ++i) - if (rhs.as_uint[i] != 0) - constant.as_uint[i] %= rhs.as_uint[i]; - } - break; - case tokenid::star: - if (type.is_floating_point()) - for (unsigned int i = 0; i < type.components(); ++i) - constant.as_float[i] *= rhs.as_float[i]; - else - for (unsigned int i = 0; i < type.components(); ++i) - constant.as_uint[i] *= rhs.as_uint[i]; - break; - case tokenid::plus: - if (type.is_floating_point()) - for (unsigned int i = 0; i < type.components(); ++i) - constant.as_float[i] += rhs.as_float[i]; - else - for (unsigned int i = 0; i < type.components(); ++i) - constant.as_uint[i] += rhs.as_uint[i]; - break; - case tokenid::minus: - if (type.is_floating_point()) - for (unsigned int i = 0; i < type.components(); ++i) - constant.as_float[i] -= rhs.as_float[i]; - else - for (unsigned int i = 0; i < type.components(); ++i) - constant.as_uint[i] -= rhs.as_uint[i]; - break; - case tokenid::slash: - if (type.is_floating_point()) { - for (unsigned int i = 0; i < type.components(); ++i) - if (rhs.as_float[i] != 0) // TODO: Maybe throw an error on divide by zero? - constant.as_float[i] /= rhs.as_float[i]; - } - else if (type.is_signed()) { - for (unsigned int i = 0; i < type.components(); ++i) - if (rhs.as_int[i] != 0) - constant.as_int[i] /= rhs.as_int[i]; - } - else { - for (unsigned int i = 0; i < type.components(); ++i) - if (rhs.as_uint[i] != 0) - constant.as_uint[i] /= rhs.as_uint[i]; - } - break; - case tokenid::ampersand: - case tokenid::ampersand_ampersand: - for (unsigned int i = 0; i < type.components(); ++i) - constant.as_uint[i] &= rhs.as_uint[i]; - break; - case tokenid::pipe: - case tokenid::pipe_pipe: - for (unsigned int i = 0; i < type.components(); ++i) - constant.as_uint[i] |= rhs.as_uint[i]; - break; - case tokenid::caret: - for (unsigned int i = 0; i < type.components(); ++i) - constant.as_uint[i] ^= rhs.as_uint[i]; - break; - case tokenid::less: - if (type.is_floating_point()) - for (unsigned int i = 0; i < type.components(); ++i) - constant.as_uint[i] = constant.as_float[i] < rhs.as_float[i]; - else if (type.is_signed()) - for (unsigned int i = 0; i < type.components(); ++i) - constant.as_uint[i] = constant.as_int[i] < rhs.as_int[i]; - else - for (unsigned int i = 0; i < type.components(); ++i) - constant.as_uint[i] = constant.as_uint[i] < rhs.as_uint[i]; - type.base = type::t_bool; // Logic operations change the type to boolean - break; - case tokenid::less_equal: - if (type.is_floating_point()) - for (unsigned int i = 0; i < type.components(); ++i) - constant.as_uint[i] = constant.as_float[i] <= rhs.as_float[i]; - else if (type.is_signed()) - for (unsigned int i = 0; i < type.components(); ++i) - constant.as_uint[i] = constant.as_int[i] <= rhs.as_int[i]; - else - for (unsigned int i = 0; i < type.components(); ++i) - constant.as_uint[i] = constant.as_uint[i] <= rhs.as_uint[i]; - type.base = type::t_bool; - break; - case tokenid::greater: - if (type.is_floating_point()) - for (unsigned int i = 0; i < type.components(); ++i) - constant.as_uint[i] = constant.as_float[i] > rhs.as_float[i]; - else if (type.is_signed()) - for (unsigned int i = 0; i < type.components(); ++i) - constant.as_uint[i] = constant.as_int[i] > rhs.as_int[i]; - else - for (unsigned int i = 0; i < type.components(); ++i) - constant.as_uint[i] = constant.as_uint[i] > rhs.as_uint[i]; - type.base = type::t_bool; - break; - case tokenid::greater_equal: - if (type.is_floating_point()) - for (unsigned int i = 0; i < type.components(); ++i) - constant.as_uint[i] = constant.as_float[i] >= rhs.as_float[i]; - else if (type.is_signed()) - for (unsigned int i = 0; i < type.components(); ++i) - constant.as_uint[i] = constant.as_int[i] >= rhs.as_int[i]; - else - for (unsigned int i = 0; i < type.components(); ++i) - constant.as_uint[i] = constant.as_uint[i] >= rhs.as_uint[i]; - type.base = type::t_bool; - break; - case tokenid::equal_equal: - if (type.is_floating_point()) - for (unsigned int i = 0; i < type.components(); ++i) - constant.as_uint[i] = constant.as_float[i] == rhs.as_float[i]; - else - for (unsigned int i = 0; i < type.components(); ++i) - constant.as_uint[i] = constant.as_uint[i] == rhs.as_uint[i]; - type.base = type::t_bool; - break; - case tokenid::exclaim_equal: - if (type.is_floating_point()) - for (unsigned int i = 0; i < type.components(); ++i) - constant.as_uint[i] = constant.as_float[i] != rhs.as_float[i]; - else - for (unsigned int i = 0; i < type.components(); ++i) - constant.as_uint[i] = constant.as_uint[i] != rhs.as_uint[i]; - type.base = type::t_bool; - break; - case tokenid::less_less: - for (unsigned int i = 0; i < type.components(); ++i) - constant.as_uint[i] <<= rhs.as_uint[i]; - break; - case tokenid::greater_greater: - if (type.is_signed()) - for (unsigned int i = 0; i < type.components(); ++i) - constant.as_int[i] >>= rhs.as_int[i]; - else - for (unsigned int i = 0; i < type.components(); ++i) - constant.as_uint[i] >>= rhs.as_uint[i]; - break; - } - - return true; -} diff --git a/msvc/source/effect_expression.hpp b/msvc/source/effect_expression.hpp deleted file mode 100644 index ac9da19..0000000 --- a/msvc/source/effect_expression.hpp +++ /dev/null @@ -1,381 +0,0 @@ -/** - * Copyright (C) 2014 Patrick Mours. All rights reserved. - * License: https://github.com/crosire/reshade#license - */ - -#pragma once - -#include -#include -#include - -namespace reshadefx -{ - /// - /// Structure which keeps track of a code location - /// - struct location - { - location() : line(1), column(1) { } - explicit location(unsigned int line, unsigned int column = 1) : line(line), column(column) { } - explicit location(std::string source, unsigned int line, unsigned int column = 1) : source(std::move(source)), line(line), column(column) { } - - std::string source; - unsigned int line, column; - }; - - /// - /// Structure which encapsulates a parsed value type - /// - struct type - { - enum datatype : uint8_t - { - t_void, - t_bool, - t_int, - t_uint, - t_float, - t_string, - t_struct, - t_sampler, - t_texture, - t_function, - }; - enum qualifier : uint32_t - { - q_extern = 1 << 0, - q_static = 1 << 1, - q_uniform = 1 << 2, - q_volatile = 1 << 3, - q_precise = 1 << 4, - q_in = 1 << 5, - q_out = 1 << 6, - q_inout = q_in | q_out, - q_const = 1 << 8, - q_linear = 1 << 10, - q_noperspective = 1 << 11, - q_centroid = 1 << 12, - q_nointerpolation = 1 << 13, - }; - - /// - /// Get the result type of an operation involving the two input types. - /// - static type merge(const type &lhs, const type &rhs); - - /// - /// Calculate the ranking between two types which can be used to select the best matching function overload. The higher the rank, the better the match. A value of zero indicates that the types are not compatible. - /// - static unsigned int rank(const type &src, const type &dst); - - bool has(qualifier x) const { return (qualifiers & x) == x; } - bool is_array() const { return array_length != 0; } - bool is_scalar() const { return !is_array() && !is_matrix() && !is_vector() && is_numeric(); } - bool is_vector() const { return rows > 1 && cols == 1; } - bool is_matrix() const { return rows >= 1 && cols > 1; } - bool is_signed() const { return base == t_int || base == t_float; } - bool is_numeric() const { return is_integral() || is_floating_point(); } - bool is_void() const { return base == t_void; } - bool is_boolean() const { return base == t_bool; } - bool is_integral() const { return base == t_bool || base == t_int || base == t_uint; } - bool is_floating_point() const { return base == t_float; } - bool is_struct() const { return base == t_struct; } - bool is_texture() const { return base == t_texture; } - bool is_sampler() const { return base == t_sampler; } - bool is_function() const { return base == t_function; } - - unsigned int components() const { return rows * cols; } - - friend inline bool operator==(const type &lhs, const type &rhs) - { - return lhs.base == rhs.base && lhs.rows == rhs.rows && lhs.cols == rhs.cols && lhs.array_length == rhs.array_length && lhs.definition == rhs.definition; - } - friend inline bool operator!=(const type &lhs, const type &rhs) - { - return !operator==(lhs, rhs); - } - - datatype base = t_void; // Underlying base type ('int', 'float', ...) - unsigned int rows = 0; // Number of rows if this is a vector type - unsigned int cols = 0; // Number of columns if this is a matrix type - unsigned int qualifiers = 0; // Bit mask of all the qualifiers decorating the type - int array_length = 0; // Negative if an unsized array, otherwise the number of elements if this is an array type - uint32_t definition = 0; // ID of the matching struct if this is a struct type - }; - - /// - /// Structure which encapsulates a parsed constant value - /// - struct constant - { - union - { - float as_float[16]; - int32_t as_int[16]; - uint32_t as_uint[16]; - }; - - // Optional string associated with this constant - std::string string_data; - // Optional additional elements if this is an array constant - std::vector array_data; - }; - - /// - /// Structures which keeps track of the access chain of an expression - /// - struct expression - { - struct operation - { - enum op_type - { - op_cast, - op_member, - op_dynamic_index, - op_constant_index, - op_swizzle, - }; - - op_type op; - type from, to; - uint32_t index; - signed char swizzle[4]; - }; - - type type = {}; - uint32_t base = 0; - constant constant = {}; - bool is_lvalue = false; - bool is_constant = false; - location location; - std::vector chain; - - /// - /// Initialize the expression to a l-value. - /// - /// The code location of the expression. - /// The ID of the l-value. - /// The value type of the expression result. - void reset_to_lvalue(const reshadefx::location &loc, uint32_t base, const reshadefx::type &type); - /// - /// Initialize the expression to a r-value. - /// - /// The code location of the expression. - /// The ID of the r-value. - /// The value type of the expression result. - void reset_to_rvalue(const reshadefx::location &loc, uint32_t base, const reshadefx::type &type); - - /// - /// Initialize the expression to a constant value. - /// - /// The code location of the constant expression. - /// The constant value. - void reset_to_rvalue_constant(const reshadefx::location &loc, bool data); - void reset_to_rvalue_constant(const reshadefx::location &loc, float data); - void reset_to_rvalue_constant(const reshadefx::location &loc, int32_t data); - void reset_to_rvalue_constant(const reshadefx::location &loc, uint32_t data); - void reset_to_rvalue_constant(const reshadefx::location &loc, std::string data); - void reset_to_rvalue_constant(const reshadefx::location &loc, reshadefx::constant data, const reshadefx::type &type); - - /// - /// Add a cast operation to the current access chain. - /// - /// The type to cast the expression to. - void add_cast_operation(const reshadefx::type &type); - /// - /// Add a struct member lookup to the current access chain. - /// - /// The index of the member to dereference. - /// The value type of the member. - void add_member_access(unsigned int index, const reshadefx::type &type); - /// - /// Add an index operation to the current access chain. - /// - /// The SSA ID of the indexing value. - void add_dynamic_index_access(uint32_t index_expression); - /// - /// Add an constant index operation to the current access chain. - /// - /// The constant indexing value. - void add_constant_index_access(unsigned int index); - /// - /// Add a swizzle operation to the current access chain. - /// - /// The swizzle for each component. -1 = unused, 0 = x, 1 = y, 2 = z, 3 = w. - /// The number of components in the swizzle. The maximum is 4. - void add_swizzle_access(const signed char swizzle[4], unsigned int length); - - /// - /// Apply an unary operation to this constant expression. - /// - /// The unary operator to apply. - bool evaluate_constant_expression(enum class tokenid op); - /// - /// Apply a binary operation to this constant expression. - /// - /// The binary operator to apply. - /// The constant to use as right-hand side of the binary operation. - bool evaluate_constant_expression(enum class tokenid op, const reshadefx::constant &rhs); - }; - - - struct struct_info - { - std::string name; - std::string unique_name; - std::vector member_list; - uint32_t definition = 0; - }; - - struct struct_member_info - { - type type; - std::string name; - std::string semantic; - location location; - uint32_t definition = 0; - }; - - struct uniform_info - { - std::string name; - type type; - uint32_t size = 0; - uint32_t offset = 0; - std::unordered_map> annotations; - bool has_initializer_value = false; - constant initializer_value; - }; - - enum class texture_filter - { - min_mag_mip_point = 0, - min_mag_point_mip_linear = 0x1, - min_point_mag_linear_mip_point = 0x4, - min_point_mag_mip_linear = 0x5, - min_linear_mag_mip_point = 0x10, - min_linear_mag_point_mip_linear = 0x11, - min_mag_linear_mip_point = 0x14, - min_mag_mip_linear = 0x15 - }; - - enum class texture_format - { - unknown, - - r8, - r16f, - r32f, - rg8, - rg16, - rg16f, - rg32f, - rgba8, - rgba16, - rgba16f, - rgba32f, - rgb10a2, - }; - - enum class texture_address_mode - { - wrap = 1, - mirror = 2, - clamp = 3, - border = 4 - }; - - struct texture_info - { - uint32_t id = 0; - uint32_t binding = 0; - std::string semantic; - std::string unique_name; - std::unordered_map> annotations; - uint32_t width = 1; - uint32_t height = 1; - uint32_t levels = 1; - texture_format format = texture_format::rgba8; - }; - - struct sampler_info - { - uint32_t id = 0; - uint32_t binding = 0; - uint32_t texture_binding = 0; - std::string unique_name; - std::string texture_name; - std::unordered_map> annotations; - texture_filter filter = texture_filter::min_mag_mip_linear; - texture_address_mode address_u = texture_address_mode::clamp; - texture_address_mode address_v = texture_address_mode::clamp; - texture_address_mode address_w = texture_address_mode::clamp; - float min_lod = -FLT_MAX; - float max_lod = +FLT_MAX; - float lod_bias = 0.0f; - uint8_t srgb = false; - }; - - struct function_info - { - uint32_t definition; - std::string name; - std::string unique_name; - type return_type; - std::string return_semantic; - std::vector parameter_list; - }; - - struct pass_info - { - std::string render_target_names[8] = {}; - std::string vs_entry_point; - std::string ps_entry_point; - uint8_t clear_render_targets = false; - uint8_t srgb_write_enable = false; - uint8_t blend_enable = false; - uint8_t stencil_enable = false; - uint8_t color_write_mask = 0xF; - uint8_t stencil_read_mask = 0xFF; - uint8_t stencil_write_mask = 0xFF; - uint32_t blend_op = 1; // ADD - uint32_t blend_op_alpha = 1; // ADD - uint32_t src_blend = 1; // ONE - uint32_t dest_blend = 0; // ZERO - uint32_t src_blend_alpha = 1; // ONE - uint32_t dest_blend_alpha = 0; // ZERO - uint32_t stencil_comparison_func = 8; // ALWAYS - uint32_t stencil_reference_value = 0; - uint32_t stencil_op_pass = 1; // KEEP - uint32_t stencil_op_fail = 1; // KEEP - uint32_t stencil_op_depth_fail = 1; // KEEP - uint32_t viewport_width = 0; - uint32_t viewport_height = 0; - }; - - struct technique_info - { - std::string name; - std::vector passes; - std::unordered_map> annotations; - }; - - - /// - /// In-memory representation of an effect file. - /// - struct module - { - std::string hlsl; - std::vector spirv; - std::vector textures; - std::vector samplers; - std::vector uniforms, spec_constants; - std::vector techniques; - std::vector> entry_points; - uint32_t num_sampler_bindings = 0; - uint32_t num_texture_bindings = 0; - }; -} diff --git a/msvc/source/effect_lexer.cpp b/msvc/source/effect_lexer.cpp deleted file mode 100644 index d248165..0000000 --- a/msvc/source/effect_lexer.cpp +++ /dev/null @@ -1,1004 +0,0 @@ -/** - * Copyright (C) 2014 Patrick Mours. All rights reserved. - * License: https://github.com/crosire/reshade#license - */ - -#include "effect_lexer.hpp" -#include - -using namespace reshadefx; - -enum token_type -{ - DIGIT = '0', - IDENT = 'A', - SPACE = ' ', -}; - -// Lookup table which translates a given char to a token type -static const unsigned type_lookup[256] = { - 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, SPACE, - '\n', SPACE, SPACE, SPACE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, SPACE, '!', '"', '#', '$', '%', '&', '\'', - '(', ')', '*', '+', ',', '-', '.', '/', DIGIT, DIGIT, - DIGIT, DIGIT, DIGIT, DIGIT, DIGIT, DIGIT, DIGIT, DIGIT, ':', ';', - '<', '=', '>', '?', '@', IDENT, IDENT, IDENT, IDENT, IDENT, - IDENT, IDENT, IDENT, IDENT, IDENT, IDENT, IDENT, IDENT, IDENT, IDENT, - IDENT, IDENT, IDENT, IDENT, IDENT, IDENT, IDENT, IDENT, IDENT, IDENT, - IDENT, '[', '\\', ']', '^', IDENT, 0x00, IDENT, IDENT, IDENT, - IDENT, IDENT, IDENT, IDENT, IDENT, IDENT, IDENT, IDENT, IDENT, IDENT, - IDENT, IDENT, IDENT, IDENT, IDENT, IDENT, IDENT, IDENT, IDENT, IDENT, - IDENT, IDENT, IDENT, '{', '|', '}', '~', 0x00, 0x00, 0x00, -}; - -// Lookup tables which translate a given string literal to a token and backwards -static const std::unordered_map token_lookup = { - { tokenid::end_of_file, "end of file" }, - { tokenid::exclaim, "!" }, - { tokenid::hash, "#" }, - { tokenid::dollar, "$" }, - { tokenid::percent, "%" }, - { tokenid::ampersand, "&" }, - { tokenid::parenthesis_open, "(" }, - { tokenid::parenthesis_close, ")" }, - { tokenid::star, "*" }, - { tokenid::plus, "+" }, - { tokenid::comma, "," }, - { tokenid::minus, "-" }, - { tokenid::dot, "." }, - { tokenid::slash, "/" }, - { tokenid::colon, ":" }, - { tokenid::semicolon, ";" }, - { tokenid::less, "<" }, - { tokenid::equal, "=" }, - { tokenid::greater, ">" }, - { tokenid::question, "?" }, - { tokenid::at, "@" }, - { tokenid::bracket_open, "[" }, - { tokenid::backslash, "\\" }, - { tokenid::bracket_close, "]" }, - { tokenid::caret, "^" }, - { tokenid::brace_open, "{" }, - { tokenid::pipe, "|" }, - { tokenid::brace_close, "}" }, - { tokenid::tilde, "~" }, - { tokenid::exclaim_equal, "!=" }, - { tokenid::percent_equal, "%=" }, - { tokenid::ampersand_ampersand, "&&" }, - { tokenid::ampersand_equal, "&=" }, - { tokenid::star_equal, "*=" }, - { tokenid::plus_plus, "++" }, - { tokenid::plus_equal, "+=" }, - { tokenid::minus_minus, "--" }, - { tokenid::minus_equal, "-=" }, - { tokenid::arrow, "->" }, - { tokenid::ellipsis, "..." }, - { tokenid::slash_equal, "|=" }, - { tokenid::colon_colon, "::" }, - { tokenid::less_less_equal, "<<=" }, - { tokenid::less_less, "<<" }, - { tokenid::less_equal, "<=" }, - { tokenid::equal_equal, "==" }, - { tokenid::greater_greater_equal, ">>=" }, - { tokenid::greater_greater, ">>" }, - { tokenid::greater_equal, ">=" }, - { tokenid::caret_equal, "^=" }, - { tokenid::pipe_equal, "|=" }, - { tokenid::pipe_pipe, "||" }, - { tokenid::identifier, "identifier" }, - { tokenid::reserved, "reserved word" }, - { tokenid::true_literal, "true" }, - { tokenid::false_literal, "false" }, - { tokenid::int_literal, "integral literal" }, - { tokenid::uint_literal, "integral literal" }, - { tokenid::float_literal, "floating point literal" }, - { tokenid::double_literal, "floating point literal" }, - { tokenid::string_literal, "string literal" }, - { tokenid::namespace_, "namespace" }, - { tokenid::struct_, "struct" }, - { tokenid::technique, "technique" }, - { tokenid::pass, "pass" }, - { tokenid::for_, "for" }, - { tokenid::while_, "while" }, - { tokenid::do_, "do" }, - { tokenid::if_, "if" }, - { tokenid::else_, "else" }, - { tokenid::switch_, "switch" }, - { tokenid::case_, "case" }, - { tokenid::default_, "default" }, - { tokenid::break_, "break" }, - { tokenid::continue_, "continue" }, - { tokenid::return_, "return" }, - { tokenid::discard_, "discard" }, - { tokenid::extern_, "extern" }, - { tokenid::static_, "static" }, - { tokenid::uniform_, "uniform" }, - { tokenid::volatile_, "volatile" }, - { tokenid::precise, "precise" }, - { tokenid::in, "in" }, - { tokenid::out, "out" }, - { tokenid::inout, "inout" }, - { tokenid::const_, "const" }, - { tokenid::linear, "linear" }, - { tokenid::noperspective, "noperspective" }, - { tokenid::centroid, "centroid" }, - { tokenid::nointerpolation, "nointerpolation" }, - { tokenid::void_, "void" }, - { tokenid::bool_, "bool" }, - { tokenid::bool2, "bool2" }, - { tokenid::bool3, "bool3" }, - { tokenid::bool4, "bool4" }, - { tokenid::bool2x2, "bool2x2" }, - { tokenid::bool3x3, "bool3x3" }, - { tokenid::bool4x4, "bool4x4" }, - { tokenid::int_, "int" }, - { tokenid::int2, "int2" }, - { tokenid::int3, "int3" }, - { tokenid::int4, "int4" }, - { tokenid::int2x2, "int2x2" }, - { tokenid::int3x3, "int3x3" }, - { tokenid::int4x4, "int4x4" }, - { tokenid::uint_, "uint" }, - { tokenid::uint2, "uint2" }, - { tokenid::uint3, "uint3" }, - { tokenid::uint4, "uint4" }, - { tokenid::uint2x2, "uint2x2" }, - { tokenid::uint3x3, "uint3x3" }, - { tokenid::uint4x4, "uint4x4" }, - { tokenid::float_, "float" }, - { tokenid::float2, "float2" }, - { tokenid::float3, "float3" }, - { tokenid::float4, "float4" }, - { tokenid::float2x2, "float2x2" }, - { tokenid::float3x3, "float3x3" }, - { tokenid::float4x4, "float4x4" }, - { tokenid::vector, "vector" }, - { tokenid::matrix, "matrix" }, - { tokenid::string_, "string" }, - { tokenid::texture, "texture" }, - { tokenid::sampler, "sampler" }, -}; -static const std::unordered_map keyword_lookup = { - { "asm", tokenid::reserved }, - { "asm_fragment", tokenid::reserved }, - { "auto", tokenid::reserved }, - { "bool", tokenid::bool_ }, - { "bool2", tokenid::bool2 }, - { "bool2x2", tokenid::bool2x2 }, - { "bool3", tokenid::bool3 }, - { "bool3x3", tokenid::bool3x3 }, - { "bool4", tokenid::bool4 }, - { "bool4x4", tokenid::bool4x4 }, - { "break", tokenid::break_ }, - { "case", tokenid::case_ }, - { "cast", tokenid::reserved }, - { "catch", tokenid::reserved }, - { "centroid", tokenid::reserved }, - { "char", tokenid::reserved }, - { "class", tokenid::reserved }, - { "column_major", tokenid::reserved }, - { "compile", tokenid::reserved }, - { "const", tokenid::const_ }, - { "const_cast", tokenid::reserved }, - { "continue", tokenid::continue_ }, - { "default", tokenid::default_ }, - { "delete", tokenid::reserved }, - { "discard", tokenid::discard_ }, - { "do", tokenid::do_ }, - { "double", tokenid::reserved }, - { "dword", tokenid::uint_ }, - { "dword2", tokenid::uint2 }, - { "dword2x2", tokenid::uint2x2 }, - { "dword3", tokenid::uint3, }, - { "dword3x3", tokenid::uint3x3 }, - { "dword4", tokenid::uint4 }, - { "dword4x4", tokenid::uint4x4 }, - { "dynamic_cast", tokenid::reserved }, - { "else", tokenid::else_ }, - { "enum", tokenid::reserved }, - { "explicit", tokenid::reserved }, - { "extern", tokenid::extern_ }, - { "external", tokenid::reserved }, - { "false", tokenid::false_literal }, - { "FALSE", tokenid::false_literal }, - { "float", tokenid::float_ }, - { "float2", tokenid::float2 }, - { "float2x2", tokenid::float2x2 }, - { "float3", tokenid::float3 }, - { "float3x3", tokenid::float3x3 }, - { "float4", tokenid::float4 }, - { "float4x4", tokenid::float4x4 }, - { "for", tokenid::for_ }, - { "foreach", tokenid::reserved }, - { "friend", tokenid::reserved }, - { "globallycoherent", tokenid::reserved }, - { "goto", tokenid::reserved }, - { "groupshared", tokenid::reserved }, - { "half", tokenid::reserved }, - { "half2", tokenid::reserved }, - { "half2x2", tokenid::reserved }, - { "half3", tokenid::reserved }, - { "half3x3", tokenid::reserved }, - { "half4", tokenid::reserved }, - { "half4x4", tokenid::reserved }, - { "if", tokenid::if_ }, - { "in", tokenid::in }, - { "inline", tokenid::reserved }, - { "inout", tokenid::inout }, - { "int", tokenid::int_ }, - { "int2", tokenid::int2 }, - { "int2x2", tokenid::int2x2 }, - { "int3", tokenid::int3 }, - { "int3x3", tokenid::int3x3 }, - { "int4", tokenid::int4 }, - { "int4x4", tokenid::int4x4 }, - { "interface", tokenid::reserved }, - { "linear", tokenid::linear }, - { "long", tokenid::reserved }, - { "matrix", tokenid::matrix }, - { "mutable", tokenid::reserved }, - { "namespace", tokenid::namespace_ }, - { "new", tokenid::reserved }, - { "noinline", tokenid::reserved }, - { "nointerpolation", tokenid::nointerpolation }, - { "noperspective", tokenid::noperspective }, - { "operator", tokenid::reserved }, - { "out", tokenid::out }, - { "packed", tokenid::reserved }, - { "packoffset", tokenid::reserved }, - { "pass", tokenid::pass }, - { "precise", tokenid::precise }, - { "private", tokenid::reserved }, - { "protected", tokenid::reserved }, - { "public", tokenid::reserved }, - { "register", tokenid::reserved }, - { "reinterpret_cast", tokenid::reserved }, - { "return", tokenid::return_ }, - { "row_major", tokenid::reserved }, - { "sample", tokenid::reserved }, - { "sampler", tokenid::sampler }, - { "sampler1D", tokenid::sampler }, - { "sampler1DArray", tokenid::reserved }, - { "sampler1DArrayShadow", tokenid::reserved }, - { "sampler1DShadow", tokenid::reserved }, - { "sampler2D", tokenid::sampler }, - { "sampler2DArray", tokenid::reserved }, - { "sampler2DArrayShadow", tokenid::reserved }, - { "sampler2DMS", tokenid::reserved }, - { "sampler2DMSArray", tokenid::reserved }, - { "sampler2DShadow", tokenid::reserved }, - { "sampler3D", tokenid::sampler }, - { "sampler_state", tokenid::reserved }, - { "samplerCUBE", tokenid::reserved }, - { "samplerRECT", tokenid::reserved }, - { "SamplerState", tokenid::reserved }, - { "shared", tokenid::reserved }, - { "short", tokenid::reserved }, - { "signed", tokenid::reserved }, - { "sizeof", tokenid::reserved }, - { "snorm", tokenid::reserved }, - { "static", tokenid::static_ }, - { "static_cast", tokenid::reserved }, - { "string", tokenid::string_ }, - { "struct", tokenid::struct_ }, - { "switch", tokenid::switch_ }, - { "technique", tokenid::technique }, - { "template", tokenid::reserved }, - { "texture", tokenid::texture }, - { "Texture1D", tokenid::reserved }, - { "texture1D", tokenid::texture }, - { "Texture1DArray", tokenid::reserved }, - { "Texture2D", tokenid::reserved }, - { "texture2D", tokenid::texture }, - { "Texture2DArray", tokenid::reserved }, - { "Texture2DMS", tokenid::reserved }, - { "Texture2DMSArray", tokenid::reserved }, - { "Texture3D", tokenid::reserved }, - { "texture3D", tokenid::texture }, - { "textureCUBE", tokenid::reserved }, - { "TextureCube", tokenid::reserved }, - { "TextureCubeArray", tokenid::reserved }, - { "textureRECT", tokenid::reserved }, - { "this", tokenid::reserved }, - { "true", tokenid::true_literal }, - { "TRUE", tokenid::true_literal }, - { "try", tokenid::reserved }, - { "typedef", tokenid::reserved }, - { "uint", tokenid::uint_ }, - { "uint2", tokenid::uint2 }, - { "uint2x2", tokenid::uint2x2 }, - { "uint3", tokenid::uint3 }, - { "uint3x3", tokenid::uint3x3 }, - { "uint4", tokenid::uint4 }, - { "uint4x4", tokenid::uint4x4 }, - { "uniform", tokenid::uniform_ }, - { "union", tokenid::reserved }, - { "unorm", tokenid::reserved }, - { "unsigned", tokenid::reserved }, - { "vector", tokenid::vector }, - { "virtual", tokenid::reserved }, - { "void", tokenid::void_ }, - { "volatile", tokenid::volatile_ }, - { "while", tokenid::while_ } -}; -static const std::unordered_map pp_directive_lookup = { - { "define", tokenid::hash_def }, - { "undef", tokenid::hash_undef }, - { "if", tokenid::hash_if }, - { "ifdef", tokenid::hash_ifdef }, - { "ifndef", tokenid::hash_ifndef }, - { "else", tokenid::hash_else }, - { "elif", tokenid::hash_elif }, - { "endif", tokenid::hash_endif }, - { "error", tokenid::hash_error }, - { "warning", tokenid::hash_warning }, - { "pragma", tokenid::hash_pragma }, - { "include", tokenid::hash_include }, -}; - -inline bool is_octal_digit(char c) -{ - return static_cast(c - '0') < 8; -} -inline bool is_decimal_digit(char c) -{ - return static_cast(c - '0') < 10; -} -inline bool is_hexadecimal_digit(char c) -{ - return is_decimal_digit(c) || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'); -} -bool is_digit(char c, int radix) -{ - switch (radix) - { - case 8: - return is_octal_digit(c); - case 10: - return is_decimal_digit(c); - case 16: - return is_hexadecimal_digit(c); - } - - return false; -} -inline long long octal_to_decimal(long long n) -{ - long long m = 0; - - while (n != 0) - { - m *= 8; - m += n & 7; - n >>= 3; - } - - while (m != 0) - { - n *= 10; - n += m & 7; - m >>= 3; - } - - return n; -} - -std::string reshadefx::token::id_to_name(tokenid id) -{ - const auto it = token_lookup.find(id); - - return it != token_lookup.end() ? it->second : "unknown"; -} - -reshadefx::token reshadefx::lexer::lex() -{ - bool is_at_line_begin = _cur_location.column <= 1; - - token tok; -next_token: - // Reset token data - tok.location = _cur_location; - tok.offset = _cur - _input.data(); - tok.length = 1; - tok.literal_as_double = 0; - - // Do a character type lookup for the current character - switch (type_lookup[static_cast(*_cur)]) - { - case 0xFF: // EOF - tok.id = tokenid::end_of_file; - return tok; - case SPACE: - skip_space(); - if (_ignore_whitespace || is_at_line_begin || *_cur == '\n') - goto next_token; - tok.id = tokenid::space; - tok.length = _cur - _input.data() - tok.offset; - return tok; - case '\n': - _cur++; - _cur_location.line++; - _cur_location.column = 1; - is_at_line_begin = true; - if (_ignore_whitespace) - goto next_token; - tok.id = tokenid::end_of_line; - return tok; - case DIGIT: - parse_numeric_literal(tok); - break; - case IDENT: - parse_identifier(tok); - break; - case '!': - if (_cur[1] == '=') - tok.id = tokenid::exclaim_equal, - tok.length = 2; - else - tok.id = tokenid::exclaim; - break; - case '"': - parse_string_literal(tok, _escape_string_literals); - break; - case '#': - if (is_at_line_begin) - { - if (!parse_pp_directive(tok) || _ignore_pp_directives) - { - skip_to_next_line(); - goto next_token; - } - } // These braces are important so the 'else' is matched to the right 'if' statement - else - tok.id = tokenid::hash; - break; - case '$': - tok.id = tokenid::dollar; - break; - case '%': - if (_cur[1] == '=') - tok.id = tokenid::percent_equal, - tok.length = 2; - else - tok.id = tokenid::percent; - break; - case '&': - if (_cur[1] == '&') - tok.id = tokenid::ampersand_ampersand, - tok.length = 2; - else if (_cur[1] == '=') - tok.id = tokenid::ampersand_equal, - tok.length = 2; - else - tok.id = tokenid::ampersand; - break; - case '(': - tok.id = tokenid::parenthesis_open; - break; - case ')': - tok.id = tokenid::parenthesis_close; - break; - case '*': - if (_cur[1] == '=') - tok.id = tokenid::star_equal, - tok.length = 2; - else - tok.id = tokenid::star; - break; - case '+': - if (_cur[1] == '+') - tok.id = tokenid::plus_plus, - tok.length = 2; - else if (_cur[1] == '=') - tok.id = tokenid::plus_equal, - tok.length = 2; - else - tok.id = tokenid::plus; - break; - case ',': - tok.id = tokenid::comma; - break; - case '-': - if (_cur[1] == '-') - tok.id = tokenid::minus_minus, - tok.length = 2; - else if (_cur[1] == '=') - tok.id = tokenid::minus_equal, - tok.length = 2; - else if (_cur[1] == '>') - tok.id = tokenid::arrow, - tok.length = 2; - else - tok.id = tokenid::minus; - break; - case '.': - if (type_lookup[_cur[1]] == DIGIT) - parse_numeric_literal(tok); - else if (_cur[1] == '.' && _cur[2] == '.') - tok.id = tokenid::ellipsis, - tok.length = 3; - else - tok.id = tokenid::dot; - break; - case '/': - if (_cur[1] == '/') - { - skip_to_next_line(); - if (_ignore_comments) - goto next_token; - tok.id = tokenid::single_line_comment; - tok.length = _cur - _input.data() - tok.offset; - return tok; - } - else if (_cur[1] == '*') - { - while (_cur < _end) - { - if (*_cur == '\n') - { - _cur_location.line++; - _cur_location.column = 1; - } - else if (_cur[0] == '*' && _cur[1] == '/') - { - skip(2); - break; - } - skip(1); - } - if (_ignore_comments) - goto next_token; - tok.id = tokenid::multi_line_comment; - tok.length = _cur - _input.data() - tok.offset; - return tok; - } - else if (_cur[1] == '=') - tok.id = tokenid::slash_equal, - tok.length = 2; - else - tok.id = tokenid::slash; - break; - case ':': - if (_cur[1] == ':') - tok.id = tokenid::colon_colon, - tok.length = 2; - else - tok.id = tokenid::colon; - break; - case ';': - tok.id = tokenid::semicolon; - break; - case '<': - if (_cur[1] == '<') - if (_cur[2] == '=') - tok.id = tokenid::less_less_equal, - tok.length = 3; - else - tok.id = tokenid::less_less, - tok.length = 2; - else if (_cur[1] == '=') - tok.id = tokenid::less_equal, - tok.length = 2; - else - tok.id = tokenid::less; - break; - case '=': - if (_cur[1] == '=') - tok.id = tokenid::equal_equal, - tok.length = 2; - else - tok.id = tokenid::equal; - break; - case '>': - if (_cur[1] == '>') - if (_cur[2] == '=') - tok.id = tokenid::greater_greater_equal, - tok.length = 3; - else - tok.id = tokenid::greater_greater, - tok.length = 2; - else if (_cur[1] == '=') - tok.id = tokenid::greater_equal, - tok.length = 2; - else - tok.id = tokenid::greater; - break; - case '?': - tok.id = tokenid::question; - break; - case '@': - tok.id = tokenid::at; - break; - case '[': - tok.id = tokenid::bracket_open; - break; - case '\\': - tok.id = tokenid::backslash; - break; - case ']': - tok.id = tokenid::bracket_close; - break; - case '^': - if (_cur[1] == '=') - tok.id = tokenid::caret_equal, - tok.length = 2; - else - tok.id = tokenid::caret; - break; - case '{': - tok.id = tokenid::brace_open; - break; - case '|': - if (_cur[1] == '=') - tok.id = tokenid::pipe_equal, - tok.length = 2; - else if (_cur[1] == '|') - tok.id = tokenid::pipe_pipe, - tok.length = 2; - else - tok.id = tokenid::pipe; - break; - case '}': - tok.id = tokenid::brace_close; - break; - case '~': - tok.id = tokenid::tilde; - break; - default: - tok.id = tokenid::unknown; - break; - } - - skip(tok.length); - - return tok; -} - -void reshadefx::lexer::skip(size_t length) -{ - _cur += length; - _cur_location.column += static_cast(length); -} -void reshadefx::lexer::skip_space() -{ - // Skip each character until a space is found - while (type_lookup[*_cur] == SPACE && _cur < _end) - skip(1); -} -void reshadefx::lexer::skip_to_next_line() -{ - // Skip each character until a new line feed is found - while (*_cur != '\n' && _cur < _end) - skip(1); -} - -void reshadefx::lexer::parse_identifier(token &tok) const -{ - auto *const begin = _cur, *end = begin; - - // Skip to the end of the identifier sequence - do end++; while (type_lookup[*end] == IDENT || type_lookup[*end] == DIGIT); - - tok.id = tokenid::identifier; - tok.offset = begin - _input.data(); - tok.length = end - begin; - tok.literal_as_string.assign(begin, end); - - if (_ignore_keywords) - return; - - const auto it = keyword_lookup.find(tok.literal_as_string); - - if (it != keyword_lookup.end()) - { - tok.id = it->second; - } -} -bool reshadefx::lexer::parse_pp_directive(token &tok) -{ - skip(1); // Skip the '#' - skip_space(); // Skip any space between the '#' and directive - parse_identifier(tok); - - const auto it = pp_directive_lookup.find(tok.literal_as_string); - - if (it != pp_directive_lookup.end()) - { - tok.id = it->second; - - return true; - } - else if (!_ignore_line_directives && tok.literal_as_string == "line") // The #line directive needs special handling - { - skip(tok.length); // The 'parse_identifier' does not update the pointer to the current character, so do that now - skip_space(); - parse_numeric_literal(tok); - skip(tok.length); - - _cur_location.line = tok.literal_as_int; - - // Need to subtract one since the line containing #line does not count into the statistics - if (_cur_location.line != 0) - _cur_location.line--; - - skip_space(); - - // Check if this #line directive has an filename attached to it - if (_cur[0] == '"') - { - token temptok; - parse_string_literal(temptok, false); - - _cur_location.source = std::move(temptok.literal_as_string); - } - - // Do not return the #line directive as token to the caller - return false; - } - - tok.id = tokenid::hash_unknown; - - return true; -} -void reshadefx::lexer::parse_string_literal(token &tok, bool escape) const -{ - auto *const begin = _cur, *end = begin + 1; - - for (auto c = *end; c != '"'; c = *++end) - { - if (c == '\n' || end >= _end) - { - // Line feed reached, the string literal is done (technically this should be an error, but the lexer does not report errors, so ignore it) - end--; - break; - } - if (c == '\\' && end[1] == '\n') - { - // Escape character found at end of line, the string literal continues on to the next line - end++; - continue; - } - - // Handle escape sequences - if (c == '\\' && escape) - { - unsigned int n = 0; - - // Any character following the '\' is not parsed as usual, so increment pointer here (this makes sure '\"' does not abort the outer loop as well) - switch (c = *++end) - { - case '0': - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - for (unsigned int i = 0; i < 3 && is_octal_digit(*end) && end < _end; i++) - { - c = *end++; - n = (n << 3) | (c - '0'); - } - // For simplicity the number is limited to what fits in a single character - c = n & 0xFF; - // The octal parsing loop above incremented one pass the escape sequence, so step back - end--; - break; - case 'a': - c = '\a'; - break; - case 'b': - c = '\b'; - break; - case 'f': - c = '\f'; - break; - case 'n': - c = '\n'; - break; - case 'r': - c = '\r'; - break; - case 't': - c = '\t'; - break; - case 'v': - c = '\v'; - break; - case 'x': - if (is_hexadecimal_digit(*++end)) - { - while (is_hexadecimal_digit(*end) && end < _end) - { - c = *end++; - n = (n << 4) | (is_decimal_digit(c) ? c - '0' : c - 55 - 32 * (c & 0x20)); - } - - // For simplicity the number is limited to what fits in a single character - c = n & 0xFF; - } - // The hexadecimal parsing loop and check above incremented one pass the escape sequence, so step back - end--; - break; - } - } - - tok.literal_as_string += c; - } - - tok.id = tokenid::string_literal; - tok.length = end - begin + 1; -} -void reshadefx::lexer::parse_numeric_literal(token &tok) const -{ - // This routine handles both integer and floating point numbers - auto *const begin = _cur, *end = _cur; - int mantissa_size = 0, decimal_location = -1, radix = 10; - long long fraction = 0, exponent = 0; - - // If a literal starts with '0' it is either an octal or hexadecimal ('0x') value - if (begin[0] == '0') - { - if (begin[1] == 'x' || begin[1] == 'X') - { - end = begin + 2; - radix = 16; - } - else - { - radix = 8; - } - } - - for (; mantissa_size <= 18; mantissa_size++, end++) - { - auto c = *end; - - if (is_decimal_digit(c)) - { - c -= '0'; - - if (c >= radix) - break; - } - else if (radix == 16) - { - // Hexadecimal values can contain the letters A to F - if (c >= 'A' && c <= 'F') - c -= 'A' - 10; - else if (c >= 'a' && c <= 'f') - c -= 'a' - 10; - else - break; - } - else - { - if (c != '.' || decimal_location >= 0) - break; - - // Found a decimal character, as such convert current values - if (radix == 8) - { - radix = 10; - fraction = octal_to_decimal(fraction); - } - - decimal_location = mantissa_size; - continue; - } - - fraction *= radix; - fraction += c; - } - - // Ignore additional digits that cannot affect the value - while (is_digit(*end, radix)) - { - end++; - } - - // If a decimal character was found, this is a floating point value, otherwise an integer one - if (decimal_location < 0) - { - tok.id = tokenid::int_literal; - - decimal_location = mantissa_size; - } - else - { - tok.id = tokenid::float_literal; - - mantissa_size -= 1; - } - - // Literals can be followed by an exponent - if (*end == 'E' || *end == 'e') - { - auto tmp = end + 1; - const bool negative = *tmp == '-'; - - if (negative || *tmp == '+') - tmp++; - - if (is_decimal_digit(*tmp)) - { - end = tmp; - - tok.id = tokenid::float_literal; - - do { - exponent *= 10; - exponent += *end++ - '0'; - } while (is_decimal_digit(*end)); - - if (negative) - exponent = -exponent; - } - } - - // Various suffixes force specific literal types - if (*end == 'F' || *end == 'f') - { - end++; // Consume the suffix - - tok.id = tokenid::float_literal; - } - else if (*end == 'L' || *end == 'l') - { - end++; // Consume the suffix - - tok.id = tokenid::double_literal; - } - else if (tok.id == tokenid::int_literal && (*end == 'U' || *end == 'u')) // The 'u' suffix is only valid on integers and needs to be ignored otherwise - { - end++; // Consume the suffix - - tok.id = tokenid::uint_literal; - } - - if (tok.id == tokenid::float_literal || tok.id == tokenid::double_literal) - { - exponent += decimal_location - mantissa_size; - - const bool exponent_negative = exponent < 0; - - if (exponent_negative) - exponent = -exponent; - - // Limit exponent - if (exponent > 511) - exponent = 511; - - // Quick exponent calculation - double e = 1.0; - const double powers_of_10[] = { - 10., - 100., - 1.0e4, - 1.0e8, - 1.0e16, - 1.0e32, - 1.0e64, - 1.0e128, - 1.0e256 - }; - - for (auto d = powers_of_10; exponent != 0; exponent >>= 1, d++) - if (exponent & 1) - e *= *d; - - if (tok.id == tokenid::float_literal) - tok.literal_as_float = exponent_negative ? fraction / static_cast(e) : fraction * static_cast(e); - else - tok.literal_as_double = exponent_negative ? fraction / e : fraction * e; - } - else - { - // Limit the maximum value to what fits into our token structure - tok.literal_as_uint = static_cast(fraction & 0xFFFFFFFF); - } - - tok.length = end - begin; -} diff --git a/msvc/source/effect_lexer.hpp b/msvc/source/effect_lexer.hpp deleted file mode 100644 index 01171db..0000000 --- a/msvc/source/effect_lexer.hpp +++ /dev/null @@ -1,281 +0,0 @@ -/** - * Copyright (C) 2014 Patrick Mours. All rights reserved. - * License: https://github.com/crosire/reshade#license - */ - -#pragma once - -#include "effect_expression.hpp" - -namespace reshadefx -{ - /// - /// A collection of identifiers for various possible tokens. - /// - enum class tokenid - { - unknown = -1, - end_of_file = 0, - end_of_line = '\n', - - // operators - space = ' ', - exclaim = '!', - hash = '#', - dollar = '$', - percent = '%', - ampersand = '&', - parenthesis_open = '(', - parenthesis_close = ')', - star = '*', - plus = '+', - comma = ',', - minus = '-', - dot = '.', - slash = '/', - colon = ':', - semicolon = ';', - less = '<', - equal = '=', - greater = '>', - question = '?', - at = '@', - bracket_open = '[', - backslash = '\\', - bracket_close = ']', - caret = '^', - brace_open = '{', - pipe = '|', - brace_close = '}', - tilde = '~', - exclaim_equal = 256 /* != */, - percent_equal /* %= */, - ampersand_ampersand /* && */, - ampersand_equal /* &= */, - star_equal /* *= */, - plus_plus /* ++*/, - plus_equal /* += */, - minus_minus /* -- */, - minus_equal /* -= */, - arrow /* -> */, - ellipsis /* ... */, - slash_equal /* /= */, - colon_colon /* :: */, - less_less_equal /* <<= */, - less_less /* << */, - less_equal /* <= */, - equal_equal /* == */, - greater_greater_equal /* >>= */, - greater_greater /* >> */, - greater_equal /* >= */, - caret_equal /* ^= */, - pipe_equal /* |= */, - pipe_pipe /* || */, - - // identifiers - reserved, - identifier, - - // literals - true_literal, - false_literal, - int_literal, - uint_literal, - float_literal, - double_literal, - string_literal, - - // keywords - namespace_, - struct_, - technique, - pass, - for_, - while_, - do_, - if_, - else_, - switch_, - case_, - default_, - break_, - continue_, - return_, - discard_, - extern_, - static_, - uniform_, - volatile_, - precise, - in, - out, - inout, - const_, - linear, - noperspective, - centroid, - nointerpolation, - - void_, - bool_, - bool2, - bool3, - bool4, - bool2x2, - bool3x3, - bool4x4, - int_, - int2, - int3, - int4, - int2x2, - int3x3, - int4x4, - uint_, - uint2, - uint3, - uint4, - uint2x2, - uint3x3, - uint4x4, - float_, - float2, - float3, - float4, - float2x2, - float3x3, - float4x4, - vector, - matrix, - string_, - texture, - sampler, - - // preprocessor directives - hash_def, - hash_undef, - hash_if, - hash_ifdef, - hash_ifndef, - hash_else, - hash_elif, - hash_endif, - hash_error, - hash_warning, - hash_pragma, - hash_include, - hash_unknown, - - single_line_comment, - multi_line_comment, - }; - - /// - /// A structure describing a single token in the input string. - /// - struct token - { - tokenid id; - location location; - size_t offset, length; - union - { - int literal_as_int; - unsigned int literal_as_uint; - float literal_as_float; - double literal_as_double; - }; - std::string literal_as_string; - - inline operator tokenid() const { return id; } - - static std::string id_to_name(tokenid id); - }; - - /// - /// A lexical analyzer for C-like languages. - /// - class lexer - { - public: - explicit lexer( - std::string input, - bool ignore_comments = true, - bool ignore_whitespace = true, - bool ignore_pp_directives = true, - bool ignore_line_directives = false, - bool ignore_keywords = false, - bool escape_string_literals = true) : - _input(std::move(input)), - _ignore_comments(ignore_comments), - _ignore_whitespace(ignore_whitespace), - _ignore_pp_directives(ignore_pp_directives), - _ignore_line_directives(ignore_line_directives), - _ignore_keywords(ignore_keywords), - _escape_string_literals(escape_string_literals) - { - _cur = _input.data(); - _end = _cur + _input.size(); - } - - lexer(const lexer &lexer) { operator=(lexer); } - lexer &operator=(const lexer &lexer) - { - _input = lexer._input; - _cur_location = lexer._cur_location; - _cur = _input.data() + (lexer._cur - lexer._input.data()); - _end = _input.data() + _input.size(); - _ignore_comments = lexer._ignore_comments; - _ignore_whitespace = lexer._ignore_whitespace; - _ignore_pp_directives = lexer._ignore_pp_directives; - _ignore_keywords = lexer._ignore_keywords; - _escape_string_literals = lexer._escape_string_literals; - _ignore_line_directives = lexer._ignore_line_directives; - - return *this; - } - - /// - /// Get the input string this lexical analyzer works on. - /// - /// A constant reference to the input string. - const std::string &input_string() const { return _input; } - - /// - /// Perform lexical analysis on the input string and return the next token in sequence. - /// - /// The next token from the input string. - token lex(); - - /// - /// Advances to the next token that is not whitespace. - /// - void skip_space(); - /// - /// Advances to the next new line, ignoring all tokens. - /// - void skip_to_next_line(); - - private: - /// - /// Skips an arbitrary amount of characters in the input string. - /// - /// The number of input characters to skip. - void skip(size_t length); - - void parse_identifier(token &tok) const; - bool parse_pp_directive(token &tok); - void parse_string_literal(token &tok, bool escape) const; - void parse_numeric_literal(token &tok) const; - - std::string _input; - location _cur_location; - const std::string::value_type *_cur, *_end; - bool _ignore_comments; - bool _ignore_whitespace; - bool _ignore_pp_directives; - bool _ignore_line_directives; - bool _ignore_keywords; - bool _escape_string_literals; - }; -} diff --git a/msvc/source/effect_parser.cpp b/msvc/source/effect_parser.cpp deleted file mode 100644 index f2dd78e..0000000 --- a/msvc/source/effect_parser.cpp +++ /dev/null @@ -1,2872 +0,0 @@ -/** - * Copyright (C) 2014 Patrick Mours. All rights reserved. - * License: https://github.com/crosire/reshade#license - */ - -#include "effect_parser.hpp" -#include "effect_codegen.hpp" -#include -#include -#include - -struct on_scope_exit -{ - template - on_scope_exit(F lambda) : leave(lambda) { } - ~on_scope_exit() { leave(); } - std::function leave; -}; - -bool reshadefx::parser::parse(std::string input, codegen *backend) -{ - _lexer.reset(new lexer(std::move(input))); - _lexer_backup.reset(); - - // Set backend for subsequent code-generation - _codegen = backend; - - consume(); - - bool success = true; - while (!peek(tokenid::end_of_file)) - if (!parse_top()) - success = false; - - return success; -} - -// -- Error Handling -- // - -void reshadefx::parser::error(const location &location, unsigned int code, const std::string &message) -{ - _errors += location.source; - _errors += '(' + std::to_string(location.line) + ", " + std::to_string(location.column) + ')' + ": error"; - _errors += (code == 0) ? ": " : " X" + std::to_string(code) + ": "; - _errors += message; - _errors += '\n'; -} -void reshadefx::parser::warning(const location &location, unsigned int code, const std::string &message) -{ - _errors += location.source; - _errors += '(' + std::to_string(location.line) + ", " + std::to_string(location.column) + ')' + ": warning"; - _errors += (code == 0) ? ": " : " X" + std::to_string(code) + ": "; - _errors += message; - _errors += '\n'; -} - -// -- Token Management -- // - -void reshadefx::parser::backup() -{ - _lexer.swap(_lexer_backup); - _lexer.reset(new lexer(*_lexer_backup)); - _token_backup = _token_next; -} -void reshadefx::parser::restore() -{ - _lexer.swap(_lexer_backup); - _token_next = _token_backup; -} - -void reshadefx::parser::consume() -{ - _token = std::move(_token_next); - _token_next = _lexer->lex(); -} -void reshadefx::parser::consume_until(tokenid tokid) -{ - while (!accept(tokid) && !peek(tokenid::end_of_file)) - { - consume(); - } -} - -bool reshadefx::parser::accept(tokenid tokid) -{ - if (peek(tokid)) - { - consume(); - return true; - } - - return false; -} -bool reshadefx::parser::expect(tokenid tokid) -{ - if (!accept(tokid)) - { - error(_token_next.location, 3000, "syntax error: unexpected '" + token::id_to_name(_token_next.id) + "', expected '" + token::id_to_name(tokid) + '\''); - return false; - } - - return true; -} - -// -- Type Parsing -- // - -bool reshadefx::parser::accept_type_class(type &type) -{ - type.rows = type.cols = 0; - - if (peek(tokenid::identifier)) - { - type.base = type::t_struct; - - const symbol symbol = find_symbol(_token_next.literal_as_string); - - if (symbol.id && symbol.op == symbol_type::structure) - { - type.definition = symbol.id; - - consume(); - return true; - } - - return false; - } - else if (accept(tokenid::vector)) - { - type.base = type::t_float; // Default to float4 unless a type is specified (see below) - type.rows = 4, type.cols = 1; - - if (accept('<')) - { - if (!accept_type_class(type)) // This overwrites the base type again - return error(_token_next.location, 3000, "syntax error: unexpected '" + token::id_to_name(_token_next.id) + "', expected vector element type"), false; - else if (!type.is_scalar()) - return error(_token.location, 3122, "vector element type must be a scalar type"), false; - - if (!expect(',') || !expect(tokenid::int_literal)) - return false; - else if (_token.literal_as_int < 1 || _token.literal_as_int > 4) - return error(_token.location, 3052, "vector dimension must be between 1 and 4"), false; - - type.rows = _token.literal_as_int; - - if (!expect('>')) - return false; - } - - return true; - } - else if (accept(tokenid::matrix)) - { - type.base = type::t_float; // Default to float4x4 unless a type is specified (see below) - type.rows = 4, type.cols = 4; - - if (accept('<')) - { - if (!accept_type_class(type)) // This overwrites the base type again - return error(_token_next.location, 3000, "syntax error: unexpected '" + token::id_to_name(_token_next.id) + "', expected matrix element type"), false; - else if (!type.is_scalar()) - return error(_token.location, 3123, "matrix element type must be a scalar type"), false; - - if (!expect(',') || !expect(tokenid::int_literal)) - return false; - else if (_token.literal_as_int < 1 || _token.literal_as_int > 4) - return error(_token.location, 3053, "matrix dimensions must be between 1 and 4"), false; - - type.rows = _token.literal_as_int; - - if (!expect(',') || !expect(tokenid::int_literal)) - return false; - else if (_token.literal_as_int < 1 || _token.literal_as_int > 4) - return error(_token.location, 3053, "matrix dimensions must be between 1 and 4"), false; - - type.cols = _token.literal_as_int; - - if (!expect('>')) - return false; - } - - return true; - } - - switch (_token_next.id) - { - case tokenid::void_: - type.base = type::t_void; - break; - case tokenid::bool_: - case tokenid::bool2: - case tokenid::bool3: - case tokenid::bool4: - type.base = type::t_bool; - type.rows = 1 + unsigned int(_token_next.id) - unsigned int(tokenid::bool_); - type.cols = 1; - break; - case tokenid::bool2x2: - case tokenid::bool3x3: - case tokenid::bool4x4: - type.base = type::t_bool; - type.rows = 2 + unsigned int(_token_next.id) - unsigned int(tokenid::bool2x2); - type.cols = type.rows; - break; - case tokenid::int_: - case tokenid::int2: - case tokenid::int3: - case tokenid::int4: - type.base = type::t_int; - type.rows = 1 + unsigned int(_token_next.id) - unsigned int(tokenid::int_); - type.cols = 1; - break; - case tokenid::int2x2: - case tokenid::int3x3: - case tokenid::int4x4: - type.base = type::t_int; - type.rows = 2 + unsigned int(_token_next.id) - unsigned int(tokenid::int2x2); - type.cols = type.rows; - break; - case tokenid::uint_: - case tokenid::uint2: - case tokenid::uint3: - case tokenid::uint4: - type.base = type::t_uint; - type.rows = 1 + unsigned int(_token_next.id) - unsigned int(tokenid::uint_); - type.cols = 1; - break; - case tokenid::uint2x2: - case tokenid::uint3x3: - case tokenid::uint4x4: - type.base = type::t_uint; - type.rows = 2 + unsigned int(_token_next.id) - unsigned int(tokenid::uint2x2); - type.cols = type.rows; - break; - case tokenid::float_: - case tokenid::float2: - case tokenid::float3: - case tokenid::float4: - type.base = type::t_float; - type.rows = 1 + unsigned int(_token_next.id) - unsigned int(tokenid::float_); - type.cols = 1; - break; - case tokenid::float2x2: - case tokenid::float3x3: - case tokenid::float4x4: - type.base = type::t_float; - type.rows = 2 + unsigned int(_token_next.id) - unsigned int(tokenid::float2x2); - type.cols = type.rows; - break; - case tokenid::string_: - type.base = type::t_string; - break; - case tokenid::texture: - type.base = type::t_texture; - break; - case tokenid::sampler: - type.base = type::t_sampler; - break; - default: - return false; - } - - consume(); - - return true; -} -bool reshadefx::parser::accept_type_qualifiers(type &type) -{ - unsigned int qualifiers = 0; - - // Storage - if (accept(tokenid::extern_)) - qualifiers |= type::q_extern; - if (accept(tokenid::static_)) - qualifiers |= type::q_static; - if (accept(tokenid::uniform_)) - qualifiers |= type::q_uniform; - if (accept(tokenid::volatile_)) - qualifiers |= type::q_volatile; - if (accept(tokenid::precise)) - qualifiers |= type::q_precise; - - if (accept(tokenid::in)) - qualifiers |= type::q_in; - if (accept(tokenid::out)) - qualifiers |= type::q_out; - if (accept(tokenid::inout)) - qualifiers |= type::q_inout; - - // Modifiers - if (accept(tokenid::const_)) - qualifiers |= type::q_const; - - // Interpolation - if (accept(tokenid::linear)) - qualifiers |= type::q_linear; - if (accept(tokenid::noperspective)) - qualifiers |= type::q_noperspective; - if (accept(tokenid::centroid)) - qualifiers |= type::q_centroid; - if (accept(tokenid::nointerpolation)) - qualifiers |= type::q_nointerpolation; - - if (qualifiers == 0) - return false; - if ((type.qualifiers & qualifiers) == qualifiers) - warning(_token.location, 3048, "duplicate usages specified"); - - type.qualifiers |= qualifiers; - - // Continue parsing potential additional qualifiers until no more are found - accept_type_qualifiers(type); - - return true; -} - -bool reshadefx::parser::parse_type(type &type) -{ - type.qualifiers = 0; - - accept_type_qualifiers(type); - - if (!accept_type_class(type)) - return false; - - if (type.is_integral() && (type.has(type::q_centroid) || type.has(type::q_noperspective))) - return error(_token.location, 4576, "signature specifies invalid interpolation mode for integer component type"), false; - else if (type.has(type::q_centroid) && !type.has(type::q_noperspective)) - type.qualifiers |= type::q_linear; - - return true; -} - -bool reshadefx::parser::parse_array_size(type &type) -{ - // Reset array length to zero before checking if one exists - type.array_length = 0; - - if (accept('[')) - { - if (accept(']')) - { - // No length expression, so this is an unsized array - type.array_length = -1; - } - else if (expression expression; parse_expression(expression) && expect(']')) - { - if (!expression.is_constant || !(expression.type.is_scalar() && expression.type.is_integral())) - return error(expression.location, 3058, "array dimensions must be literal scalar expressions"), false; - - type.array_length = expression.constant.as_uint[0]; - - if (type.array_length < 1 || type.array_length > 65536) - return error(expression.location, 3059, "array dimension must be between 1 and 65536"), false; - } - else - { - return false; - } - } - - return true; -} - -// -- Expression Parsing -- // - -bool reshadefx::parser::accept_unary_op() -{ - switch (_token_next.id) - { - case tokenid::exclaim: // !x (logical not) - case tokenid::plus: // +x - case tokenid::minus: // -x (negate) - case tokenid::tilde: // ~x (bitwise not) - case tokenid::plus_plus: // ++x - case tokenid::minus_minus: // --x - break; - default: - return false; - } - - consume(); - - return true; -} -bool reshadefx::parser::accept_postfix_op() -{ - switch (_token_next.id) - { - case tokenid::plus_plus: // ++x - case tokenid::minus_minus: // --x - break; - default: - return false; - } - - consume(); - - return true; -} -bool reshadefx::parser::peek_multary_op(unsigned int &precedence) const -{ - // Precedence values taken from https://cppreference.com/w/cpp/language/operator_precedence - switch (_token_next.id) - { - case tokenid::question: precedence = 1; break; // x ? a : b - case tokenid::pipe_pipe: precedence = 2; break; // a || b (logical or) - case tokenid::ampersand_ampersand: precedence = 3; break; // a && b (logical and) - case tokenid::pipe: precedence = 4; break; // a | b (bitwise or) - case tokenid::caret: precedence = 5; break; // a ^ b (bitwise xor) - case tokenid::ampersand: precedence = 6; break; // a & b (bitwise and) - case tokenid::equal_equal: precedence = 7; break; // a == b (equal) - case tokenid::exclaim_equal: precedence = 7; break; // a != b (not equal) - case tokenid::less: precedence = 8; break; // a < b - case tokenid::greater: precedence = 8; break; // a > b - case tokenid::less_equal: precedence = 8; break; // a <= b - case tokenid::greater_equal: precedence = 8; break; // a >= b - case tokenid::less_less: precedence = 9; break; // a << b (left shift) - case tokenid::greater_greater: precedence = 9; break; // a >> b (right shift) - case tokenid::plus: precedence = 10; break; // a + b (add) - case tokenid::minus: precedence = 10; break; // a - b (subtract) - case tokenid::star: precedence = 11; break; // a * b (multiply) - case tokenid::slash: precedence = 11; break; // a / b (divide) - case tokenid::percent: precedence = 11; break; // a % b (modulo) - default: - return false; - } - - // Do not consume token yet since the expression may be skipped due to precedence - return true; -} -bool reshadefx::parser::accept_assignment_op() -{ - switch (_token_next.id) - { - case tokenid::equal: // a = b - case tokenid::percent_equal: // a %= b - case tokenid::ampersand_equal: // a &= b - case tokenid::star_equal: // a *= b - case tokenid::plus_equal: // a += b - case tokenid::minus_equal: // a -= b - case tokenid::slash_equal: // a /= b - case tokenid::less_less_equal: // a <<= b - case tokenid::greater_greater_equal: // a >>= b - case tokenid::caret_equal: // a ^= b - case tokenid::pipe_equal: // a |= b - break; - default: - return false; - } - - consume(); - - return true; -} - -bool reshadefx::parser::parse_expression(expression &exp) -{ - // Parse first expression - if (!parse_expression_assignment(exp)) - return false; - - // Continue parsing if an expression sequence is next (in the form "a, b, c, ...") - while (accept(',')) - // Overwrite 'exp' since conveniently the last expression in the sequence is the result - if (!parse_expression_assignment(exp)) - return false; - - return true; -} - -bool reshadefx::parser::parse_expression_unary(expression &exp) -{ - auto location = _token_next.location; - - #pragma region Prefix Expression - // Check if a prefix operator exists - if (accept_unary_op()) - { - // Remember the operator token before parsing the expression that follows it - const tokenid op = _token.id; - - // Parse the actual expression - if (!parse_expression_unary(exp)) - return false; - - // Unary operators are only valid on basic types - if (!exp.type.is_scalar() && !exp.type.is_vector() && !exp.type.is_matrix()) - return error(exp.location, 3022, "scalar, vector, or matrix expected"), false; - - // Special handling for the "++" and "--" operators - if (op == tokenid::plus_plus || op == tokenid::minus_minus) - { - if (exp.type.has(type::q_const) || exp.type.has(type::q_uniform) || !exp.is_lvalue) - return error(location, 3025, "l-value specifies const object"), false; - - // Create a constant one in the type of the expression - constant one = {}; - for (unsigned int i = 0; i < exp.type.components(); ++i) - if (exp.type.is_floating_point()) one.as_float[i] = 1.0f; else one.as_uint[i] = 1u; - - const auto value = _codegen->emit_load(exp); - const auto result = _codegen->emit_binary_op(location, op, exp.type, value, - _codegen->emit_constant(exp.type, one)); - - // The "++" and "--" operands modify the source variable, so store result back into it - _codegen->emit_store(exp, result); - } - else if (op != tokenid::plus) // Ignore "+" operator since it does not actually do anything - { - // The "~" bitwise operator is only valid on integral types - if (op == tokenid::tilde && !exp.type.is_integral()) - return error(exp.location, 3082, "int or unsigned int type required"), false; - // The logical not operator expects a boolean type as input, so perform cast if necessary - if (op == tokenid::exclaim && !exp.type.is_boolean()) - exp.add_cast_operation({ type::t_bool, exp.type.rows, exp.type.cols }); // Note: The result will be boolean as well - - // Constant expressions can be evaluated at compile time - if (!exp.evaluate_constant_expression(op)) - { - const auto value = _codegen->emit_load(exp); - const auto result = _codegen->emit_unary_op(location, op, exp.type, value); - - exp.reset_to_rvalue(location, result, exp.type); - } - } - } - else if (accept('(')) - { - backup(); - - // Check if this is a C-style cast expression - if (type cast_type; accept_type_class(cast_type)) - { - if (peek('(')) - { - // This is not a C-style cast but a constructor call, so need to roll-back and parse that instead - restore(); - } - else if (expect(')')) - { - // Parse the expression behind cast operator - if (!parse_expression_unary(exp)) - return false; - - // Check if the types already match, in which case there is nothing to do - if (exp.type == cast_type) - return true; - - // Check if a cast between these types is valid - if (!type::rank(exp.type, cast_type)) - return error(location, 3017, "cannot convert these types"), false; - - exp.add_cast_operation(cast_type); - return true; - } - else - { - // Type name was not followed by a closing parenthesis - return false; - } - } - - // Parse expression between the parentheses - if (!parse_expression(exp) || !expect(')')) - return false; - } - else if (accept('{')) - { - bool is_constant = true; - std::vector elements; - type composite_type = { type::t_bool, 1, 1 }; - - while (!peek('}')) - { - // There should be a comma between arguments - if (!elements.empty() && !expect(',')) - return consume_until('}'), false; - - // Initializer lists might contain a comma at the end, so break out of the loop if nothing follows afterwards - if (peek('}')) - break; - - // Parse the argument expression - if (!parse_expression_assignment(elements.emplace_back())) - return consume_until('}'), false; - - expression &element = elements.back(); - - is_constant &= element.is_constant; // Result is only constant if all arguments are constant - composite_type = type::merge(composite_type, element.type); - } - - // Constant arrays can be constructed at compile time - if (is_constant) - { - constant res = {}; - for (expression &element : elements) - { - element.add_cast_operation(composite_type); - res.array_data.push_back(element.constant); - } - - composite_type.array_length = static_cast(elements.size()); - - exp.reset_to_rvalue_constant(location, std::move(res), composite_type); - } - else - { - composite_type.array_length = static_cast(elements.size()); - - // Resolve all access chains - for (expression &element : elements) - { - element.reset_to_rvalue(element.location, _codegen->emit_load(element), element.type); - } - - const auto result = _codegen->emit_construct(location, composite_type, elements); - - exp.reset_to_rvalue(location, result, composite_type); - } - - return expect('}'); - } - else if (accept(tokenid::true_literal)) - { - exp.reset_to_rvalue_constant(location, true); - } - else if (accept(tokenid::false_literal)) - { - exp.reset_to_rvalue_constant(location, false); - } - else if (accept(tokenid::int_literal)) - { - exp.reset_to_rvalue_constant(location, _token.literal_as_int); - } - else if (accept(tokenid::uint_literal)) - { - exp.reset_to_rvalue_constant(location, _token.literal_as_uint); - } - else if (accept(tokenid::float_literal)) - { - exp.reset_to_rvalue_constant(location, _token.literal_as_float); - } - else if (accept(tokenid::double_literal)) - { - // Convert double literal to float literal for now - warning(location, 5000, "double literal truncated to float literal"); - - exp.reset_to_rvalue_constant(location, static_cast(_token.literal_as_double)); - } - else if (accept(tokenid::string_literal)) - { - std::string value = std::move(_token.literal_as_string); - - // Multiple string literals in sequence are concatenated into a single string literal - while (accept(tokenid::string_literal)) - value += _token.literal_as_string; - - exp.reset_to_rvalue_constant(location, std::move(value)); - } - else if (type type; accept_type_class(type)) // Check if this is a constructor call expression - { - if (!expect('(')) - return false; - if (!type.is_numeric()) - return error(location, 3037, "constructors only defined for numeric base types"), false; - - // Empty constructors do not exist - if (accept(')')) - return error(location, 3014, "incorrect number of arguments to numeric-type constructor"), false; - - // Parse entire argument expression list - bool is_constant = true; - unsigned int num_components = 0; - std::vector arguments; - - while (!peek(')')) - { - // There should be a comma between arguments - if (!arguments.empty() && !expect(',')) - return false; - - // Parse the argument expression - if (!parse_expression_assignment(arguments.emplace_back())) - return false; - - expression &argument = arguments.back(); - - // Constructors are only defined for numeric base types - if (!argument.type.is_numeric()) - return error(argument.location, 3017, "cannot convert non-numeric types"), false; - - is_constant &= argument.is_constant; // Result is only constant if all arguments are constant - num_components += argument.type.components(); - } - - // The list should be terminated with a parenthesis - if (!expect(')')) - return false; - - // The total number of argument elements needs to match the number of elements in the result type - if (num_components != type.components()) - return error(location, 3014, "incorrect number of arguments to numeric-type constructor"), false; - - assert(num_components > 0 && num_components <= 16 && !type.is_array()); - - if (is_constant) // Constants can be converted at compile time - { - constant res = {}; - unsigned int i = 0; - for (expression &argument : arguments) - { - argument.add_cast_operation({ type.base, argument.type.rows, argument.type.cols }); - for (unsigned int k = 0; k < argument.type.components(); ++k) - res.as_uint[i++] = argument.constant.as_uint[k]; - } - - exp.reset_to_rvalue_constant(location, std::move(res), type); - } - else if (arguments.size() > 1) - { - // Flatten all arguments to a list of scalars - for (auto it = arguments.begin(); it != arguments.end();) - { - // Argument is a scalar already, so only need to cast it - if (it->type.is_scalar()) - { - expression &argument = *it++; - - auto scalar_type = argument.type; - scalar_type.base = type.base; - argument.add_cast_operation(scalar_type); - - argument.reset_to_rvalue(argument.location, _codegen->emit_load(argument), scalar_type); - } - else - { - const expression argument = *it; - it = arguments.erase(it); - - // Convert to a scalar value and re-enter the loop in the next iteration (in case a cast is necessary too) - for (unsigned int i = argument.type.components(); i > 0; --i) - { - expression scalar = argument; - scalar.add_constant_index_access(i - 1); - - it = arguments.insert(it, scalar); - } - } - } - - const auto result = _codegen->emit_construct(location, type, arguments); - - exp.reset_to_rvalue(location, result, type); - } - else // A constructor call with a single argument is identical to a cast - { - assert(!arguments.empty()); - - // Reset expression to only argument and add cast to expression access chain - exp = std::move(arguments[0]); exp.add_cast_operation(type); - } - } - else // At this point only identifiers are left to check and resolve - { - // Starting an identifier with '::' restricts the symbol search to the global namespace level - const bool exclusive = accept(tokenid::colon_colon); - - std::string identifier; - - if (exclusive ? expect(tokenid::identifier) : accept(tokenid::identifier)) - identifier = std::move(_token.literal_as_string); - else - return false; // Warning: This may leave the expression path without issuing an error, so need to catch that at the call side! - - // Can concatenate multiple '::' to force symbol search for a specific namespace level - while (accept(tokenid::colon_colon)) - { - if (!expect(tokenid::identifier)) - return false; - identifier += "::" + std::move(_token.literal_as_string); - } - - // Figure out which scope to start searching in - scope scope = { "::", 0, 0 }; - if (!exclusive) scope = current_scope(); - - // Lookup name in the symbol table - symbol symbol = find_symbol(identifier, scope, exclusive); - - // Check if this is a function call or variable reference - if (accept('(')) - { - // Can only call symbols that are functions, but do not abort yet if no symbol was found since the identifier may reference an intrinsic - if (symbol.id && symbol.op != symbol_type::function) - return error(location, 3005, "identifier '" + identifier + "' represents a variable, not a function"), false; - - // Parse entire argument expression list - std::vector arguments; - - while (!peek(')')) - { - // There should be a comma between arguments - if (!arguments.empty() && !expect(',')) - return false; - - // Parse the argument expression - if (!parse_expression_assignment(arguments.emplace_back())) - return false; - } - - // The list should be terminated with a parenthesis - if (!expect(')')) - return false; - - // Try to resolve the call by searching through both function symbols and intrinsics - bool undeclared = !symbol.id, ambiguous = false; - - if (!resolve_function_call(identifier, arguments, scope, symbol, ambiguous)) - { - if (undeclared) - error(location, 3004, "undeclared identifier or no matching intrinsic overload for '" + identifier + '\''); - else if (ambiguous) - error(location, 3067, "ambiguous function call to '" + identifier + '\''); - else - error(location, 3013, "no matching function overload for '" + identifier + '\''); - return false; - } - - assert(symbol.function != nullptr); - - std::vector parameters(arguments.size()); - - // We need to allocate some temporary variables to pass in and load results from pointer parameters - for (size_t i = 0; i < arguments.size(); ++i) - { - const auto ¶m_type = symbol.function->parameter_list[i].type; - - if (arguments[i].type.components() > param_type.components()) - warning(arguments[i].location, 3206, "implicit truncation of vector type"); - - arguments[i].add_cast_operation(param_type); - - if (symbol.op == symbol_type::function || param_type.has(type::q_out)) - { - // All user-defined functions actually accept pointers as arguments, same applies to intrinsics with 'out' parameters - const auto temp_variable = _codegen->define_variable(arguments[i].location, param_type); - parameters[i].reset_to_lvalue(arguments[i].location, temp_variable, param_type); - } - else - { - parameters[i].reset_to_rvalue(arguments[i].location, _codegen->emit_load(arguments[i]), param_type); - } - } - - // Copy in parameters from the argument access chains to parameter variables - for (size_t i = 0; i < arguments.size(); ++i) - if (parameters[i].is_lvalue && parameters[i].type.has(type::q_in)) // Only do this for pointer parameters as discovered above - _codegen->emit_store(parameters[i], _codegen->emit_load(arguments[i])); - - // Check if the call resolving found an intrinsic or function and invoke the corresponding code - const auto result = symbol.op == symbol_type::function ? - _codegen->emit_call(location, symbol.id, symbol.type, parameters) : - _codegen->emit_call_intrinsic(location, symbol.id, symbol.type, parameters); - - exp.reset_to_rvalue(location, result, symbol.type); - - // Copy out parameters from parameter variables back to the argument access chains - for (size_t i = 0; i < arguments.size(); ++i) - if (parameters[i].is_lvalue && parameters[i].type.has(type::q_out)) // Only do this for pointer parameters as discovered above - _codegen->emit_store(arguments[i], _codegen->emit_load(parameters[i])); - } - else if (symbol.op == symbol_type::invalid) - { - // Show error if no symbol matching the identifier was found - return error(location, 3004, "undeclared identifier '" + identifier + '\''), false; - } - else if (symbol.op == symbol_type::variable) - { - assert(symbol.id != 0); - // Simply return the pointer to the variable, dereferencing is done on site where necessary - exp.reset_to_lvalue(location, symbol.id, symbol.type); - } - else if (symbol.op == symbol_type::constant) - { - // Constants are loaded into the access chain - exp.reset_to_rvalue_constant(location, symbol.constant, symbol.type); - } - else - { - // Can only reference variables and constants by name, functions need to be called - return error(location, 3005, "identifier '" + identifier + "' represents a function, not a variable"), false; - } - } - #pragma endregion - - #pragma region Postfix Expression - while (!peek(tokenid::end_of_file)) - { - location = _token_next.location; - - // Check if a postfix operator exists - if (accept_postfix_op()) - { - // Unary operators are only valid on basic types - if (!exp.type.is_scalar() && !exp.type.is_vector() && !exp.type.is_matrix()) - return error(exp.location, 3022, "scalar, vector, or matrix expected"), false; - if (exp.type.has(type::q_const) || exp.type.has(type::q_uniform) || !exp.is_lvalue) - return error(exp.location, 3025, "l-value specifies const object"), false; - - // Create a constant one in the type of the expression - constant one = {}; - for (unsigned int i = 0; i < exp.type.components(); ++i) - if (exp.type.is_floating_point()) one.as_float[i] = 1.0f; else one.as_uint[i] = 1u; - - const auto value = _codegen->emit_load(exp); - const auto result = _codegen->emit_binary_op(location, _token.id, exp.type, value, _codegen->emit_constant(exp.type, one)); - - // The "++" and "--" operands modify the source variable, so store result back into it - _codegen->emit_store(exp, result); - - // All postfix operators return a r-value rather than a l-value to the variable - exp.reset_to_rvalue(location, result, exp.type); - } - else if (accept('.')) - { - if (!expect(tokenid::identifier)) - return false; - - location = std::move(_token.location); - const auto subscript = std::move(_token.literal_as_string); - - if (accept('(')) // Methods (function calls on types) are not supported right now - { - if (!exp.type.is_struct() || exp.type.is_array()) - error(location, 3087, "object does not have methods"); - else - error(location, 3088, "structures do not have methods"); - return false; - } - else if (exp.type.is_array()) // Arrays do not have subscripts - { - error(location, 3018, "invalid subscript on array"); - return false; - } - else if (exp.type.is_vector()) - { - const size_t length = subscript.size(); - if (length > 4) - return error(location, 3018, "invalid subscript '" + subscript + "', swizzle too long"), false; - - bool is_const = false; - signed char offsets[4] = { -1, -1, -1, -1 }; - enum { xyzw, rgba, stpq } set[4]; - - for (size_t i = 0; i < length; ++i) - { - switch (subscript[i]) - { - case 'x': offsets[i] = 0, set[i] = xyzw; break; - case 'y': offsets[i] = 1, set[i] = xyzw; break; - case 'z': offsets[i] = 2, set[i] = xyzw; break; - case 'w': offsets[i] = 3, set[i] = xyzw; break; - case 'r': offsets[i] = 0, set[i] = rgba; break; - case 'g': offsets[i] = 1, set[i] = rgba; break; - case 'b': offsets[i] = 2, set[i] = rgba; break; - case 'a': offsets[i] = 3, set[i] = rgba; break; - case 's': offsets[i] = 0, set[i] = stpq; break; - case 't': offsets[i] = 1, set[i] = stpq; break; - case 'p': offsets[i] = 2, set[i] = stpq; break; - case 'q': offsets[i] = 3, set[i] = stpq; break; - default: - return error(location, 3018, "invalid subscript '" + subscript + '\''), false; - } - - if (i > 0 && (set[i] != set[i - 1])) - return error(location, 3018, "invalid subscript '" + subscript + "', mixed swizzle sets"), false; - if (static_cast(offsets[i]) >= exp.type.rows) - return error(location, 3018, "invalid subscript '" + subscript + "', swizzle out of range"), false; - - // The result is not modifiable if a swizzle appears multiple times - for (size_t k = 0; k < i; ++k) - if (offsets[k] == offsets[i]) { - is_const = true; - break; - } - } - - // Add swizzle to current access chain - exp.add_swizzle_access(offsets, static_cast(length)); - - if (is_const || exp.type.has(type::q_uniform)) - exp.type.qualifiers = (exp.type.qualifiers | type::q_const) & ~type::q_uniform; - } - else if (exp.type.is_matrix()) - { - const size_t length = subscript.size(); - if (length < 3) - return error(location, 3018, "invalid subscript '" + subscript + '\''), false; - - bool is_const = false; - signed char offsets[4] = { -1, -1, -1, -1 }; - const unsigned int set = subscript[1] == 'm'; - const int coefficient = !set; - - for (size_t i = 0, j = 0; i < length; i += 3 + set, ++j) - { - if (subscript[i] != '_' || subscript[i + set + 1] < '0' + coefficient || subscript[i + set + 1] > '3' + coefficient || subscript[i + set + 2] < '0' + coefficient || subscript[i + set + 2] > '3' + coefficient) - return error(location, 3018, "invalid subscript '" + subscript + '\''), false; - if (set && subscript[i + 1] != 'm') - return error(location, 3018, "invalid subscript '" + subscript + "', mixed swizzle sets"), false; - - const unsigned int row = subscript[i + set + 1] - '0' - coefficient; - const unsigned int col = subscript[i + set + 2] - '0' - coefficient; - - if ((row >= exp.type.rows || col >= exp.type.cols) || j > 3) - return error(location, 3018, "invalid subscript '" + subscript + "', swizzle out of range"), false; - - offsets[j] = static_cast(row * 4 + col); - - // The result is not modifiable if a swizzle appears multiple times - for (size_t k = 0; k < j; ++k) - if (offsets[k] == offsets[j]) { - is_const = true; - break; - } - } - - // Add swizzle to current access chain - exp.add_swizzle_access(offsets, static_cast(length / (3 + set))); - - if (is_const || exp.type.has(type::q_uniform)) - exp.type.qualifiers = (exp.type.qualifiers | type::q_const) & ~type::q_uniform; - } - else if (exp.type.is_struct()) - { - const auto &member_list = _codegen->find_struct(exp.type.definition).member_list; - - // Find member with matching name is structure definition - uint32_t member_index = 0; - for (const struct_member_info &member : member_list) { - if (member.name == subscript) - break; - ++member_index; - } - - if (member_index >= member_list.size()) - return error(location, 3018, "invalid subscript '" + subscript + '\''), false; - - // Add field index to current access chain - exp.add_member_access(member_index, member_list[member_index].type); - - if (exp.type.has(type::q_uniform)) // Member access to uniform structure is not modifiable - exp.type.qualifiers = (exp.type.qualifiers | type::q_const) & ~type::q_uniform; - } - else if (exp.type.is_scalar()) - { - const size_t length = subscript.size(); - if (length > 4) - return error(location, 3018, "invalid subscript '" + subscript + "', swizzle too long"), false; - - for (size_t i = 0; i < length; ++i) - if ((subscript[i] != 'x' && subscript[i] != 'r' && subscript[i] != 's') || i > 3) - return error(location, 3018, "invalid subscript '" + subscript + '\''), false; - - // Promote scalar to vector type using cast - auto target_type = exp.type; - target_type.rows = static_cast(length); - - exp.add_cast_operation(target_type); - } - else - { - error(location, 3018, "invalid subscript '" + subscript + '\''); - return false; - } - } - else if (accept('[')) - { - if (!exp.type.is_array() && !exp.type.is_vector() && !exp.type.is_matrix()) - return error(_token.location, 3121, "array, matrix, vector, or indexable object type expected in index expression"), false; - - // Parse index expression - expression index; - if (!parse_expression(index) || !expect(']')) - return false; - else if (!index.type.is_scalar() || !index.type.is_integral()) - return error(index.location, 3120, "invalid type for index - index must be an integer scalar"), false; - - // Add index expression to current access chain - if (index.is_constant) - { - // Check array bounds if known - if (exp.type.array_length > 0 && index.constant.as_uint[0] >= static_cast(exp.type.array_length)) - return error(index.location, 3504, "array index out of bounds"), false; - - exp.add_constant_index_access(index.constant.as_uint[0]); - } - else - { - if (exp.is_constant) - { - // To handle a dynamic index into a constant means we need to create a local variable first or else any of the indexing instructions do not work - const auto temp_variable = _codegen->define_variable(location, exp.type, std::string(), false, _codegen->emit_constant(exp.type, exp.constant)); - exp.reset_to_lvalue(exp.location, temp_variable, exp.type); - } - - exp.add_dynamic_index_access(_codegen->emit_load(index)); - } - } - else - { - break; - } - } - #pragma endregion - - return true; -} - -bool reshadefx::parser::parse_expression_multary(expression &lhs, unsigned int left_precedence) -{ - // Parse left hand side of the expression - if (!parse_expression_unary(lhs)) - return false; - - // Check if an operator exists so that this is a binary or ternary expression - unsigned int right_precedence; - - while (peek_multary_op(right_precedence)) - { - // Only process this operator if it has a lower precedence than the current operation, otherwise leave it for later and abort - if (right_precedence <= left_precedence) - break; - - // Finally consume the operator token - consume(); - - const tokenid op = _token.id; - - // Check if this is a binary or ternary operation - if (op != tokenid::question) - { - #pragma region Binary Expression -#if RESHADEFX_SHORT_CIRCUIT - codegen::id lhs_block = 0; - codegen::id rhs_block = 0; - codegen::id merge_block = 0; - - // Switch block to a new one before parsing right-hand side value in case it needs to be skipped during short-circuiting - if (op == tokenid::ampersand_ampersand || op == tokenid::pipe_pipe) - { - lhs_block = _codegen->set_block(0); - rhs_block = _codegen->create_block(); - merge_block = _codegen->create_block(); - - _codegen->enter_block(rhs_block); - } -#endif - // Parse the right hand side of the binary operation - expression rhs; - if (!parse_expression_multary(rhs, right_precedence)) - return false; - - // Deduce the result base type based on implicit conversion rules - type type = type::merge(lhs.type, rhs.type); - bool is_bool_result = false; - - // Do some error checking depending on the operator - if (op == tokenid::equal_equal || op == tokenid::exclaim_equal) - { - // Equality checks return a boolean value - is_bool_result = true; - - // Cannot check equality between incompatible types - if (lhs.type.is_array() || rhs.type.is_array() || lhs.type.definition != rhs.type.definition) - return error(rhs.location, 3020, "type mismatch"), false; - } - else if (op == tokenid::ampersand || op == tokenid::pipe || op == tokenid::caret) - { - // Cannot perform bitwise operations on non-integral types - if (!lhs.type.is_integral()) - return error(lhs.location, 3082, "int or unsigned int type required"), false; - if (!rhs.type.is_integral()) - return error(rhs.location, 3082, "int or unsigned int type required"), false; - } - else - { - if (op == tokenid::ampersand_ampersand || op == tokenid::pipe_pipe) - type.base = type::t_bool; - - // Logical operations return a boolean value - if (op == tokenid::less || op == tokenid::less_equal || op == tokenid::greater || op == tokenid::greater_equal) - is_bool_result = true; - - // Cannot perform arithmetic operations on non-basic types - if (!lhs.type.is_scalar() && !lhs.type.is_vector() && !lhs.type.is_matrix()) - return error(lhs.location, 3022, "scalar, vector, or matrix expected"), false; - if (!rhs.type.is_scalar() && !rhs.type.is_vector() && !rhs.type.is_matrix()) - return error(rhs.location, 3022, "scalar, vector, or matrix expected"), false; - } - - // Perform implicit type conversion - if (lhs.type.components() > type.components()) - warning(lhs.location, 3206, "implicit truncation of vector type"); - if (rhs.type.components() > type.components()) - warning(rhs.location, 3206, "implicit truncation of vector type"); - - lhs.add_cast_operation(type); - rhs.add_cast_operation(type); - -#if RESHADEFX_SHORT_CIRCUIT - // Reset block to left-hand side since the load of the left-hand side value has to happen in there - if (op == tokenid::ampersand_ampersand || op == tokenid::pipe_pipe) - _codegen->set_block(lhs_block); -#endif - - // Constant expressions can be evaluated at compile time - if (rhs.is_constant && lhs.evaluate_constant_expression(op, rhs.constant)) - continue; - - const auto lhs_value = _codegen->emit_load(lhs); - -#if RESHADEFX_SHORT_CIRCUIT - // Short circuit for logical && and || operators - if (op == tokenid::ampersand_ampersand || op == tokenid::pipe_pipe) - { - // Emit "if ( lhs) result = rhs" for && expression - codegen::id condition_value = lhs_value; - // Emit "if (!lhs) result = rhs" for || expression - if (op == tokenid::pipe_pipe) - condition_value = _codegen->emit_unary_op(lhs.location, tokenid::exclaim, type, lhs_value); - - _codegen->leave_block_and_branch_conditional(condition_value, rhs_block, merge_block); - - _codegen->set_block(rhs_block); - // Only load value of right hand side expression after entering the second block - const auto rhs_value = _codegen->emit_load(rhs); - _codegen->leave_block_and_branch(merge_block); - - _codegen->enter_block(merge_block); - - const auto result_value = _codegen->emit_phi(lhs.location, condition_value, lhs_block, rhs_value, rhs_block, lhs_value, lhs_block, type); - - lhs.reset_to_rvalue(lhs.location, result_value, type); - continue; - } -#endif - const auto rhs_value = _codegen->emit_load(rhs); - - // Certain operations return a boolean type instead of the type of the input expressions - if (is_bool_result) - type = { type::t_bool, type.rows, type.cols }; - - const auto result_value = _codegen->emit_binary_op(lhs.location, op, type, lhs.type, lhs_value, rhs_value); - - lhs.reset_to_rvalue(lhs.location, result_value, type); - #pragma endregion - } - else - { - #pragma region Ternary Expression - // A conditional expression needs a scalar or vector type condition - if (!lhs.type.is_scalar() && !lhs.type.is_vector()) - return error(lhs.location, 3022, "boolean or vector expression expected"), false; - -#if RESHADEFX_SHORT_CIRCUIT - // Switch block to a new one before parsing first part in case it needs to be skipped during short-circuiting - const codegen::id merge_block = _codegen->create_block(); - const codegen::id condition_block = _codegen->set_block(0); - codegen::id true_block = _codegen->create_block(); - codegen::id false_block = _codegen->create_block(); - - _codegen->enter_block(true_block); -#endif - // Parse the first part of the right hand side of the ternary operation - expression true_exp; - if (!parse_expression(true_exp)) - return false; - - if (!expect(':')) - return false; - -#if RESHADEFX_SHORT_CIRCUIT - // Switch block to a new one before parsing second part in case it needs to be skipped during short-circuiting - _codegen->set_block(0); - _codegen->enter_block(false_block); -#endif - // Parse the second part of the right hand side of the ternary operation - expression false_exp; - if (!parse_expression_assignment(false_exp)) - return false; - - // Check that the condition dimension matches that of at least one side - if (lhs.type.is_vector() && lhs.type.rows != true_exp.type.rows && lhs.type.cols != true_exp.type.cols) - return error(lhs.location, 3020, "dimension of conditional does not match value"), false; - - // Check that the two value expressions can be converted between each other - if (true_exp.type.array_length != false_exp.type.array_length || true_exp.type.definition != false_exp.type.definition) - return error(false_exp.location, 3020, "type mismatch between conditional values"), false; - - // Deduce the result base type based on implicit conversion rules - const type type = type::merge(true_exp.type, false_exp.type); - - if (true_exp.type.components() > type.components()) - warning(true_exp.location, 3206, "implicit truncation of vector type"); - if (false_exp.type.components() > type.components()) - warning(false_exp.location, 3206, "implicit truncation of vector type"); - -#if RESHADEFX_SHORT_CIRCUIT - // Reset block to left-hand side since the load of the condition value has to happen in there - _codegen->set_block(condition_block); -#else - // The conditional operator instruction expects the condition to be a boolean type - lhs.add_cast_operation({ type::t_bool, type.rows, 1 }); -#endif - true_exp.add_cast_operation(type); - false_exp.add_cast_operation(type); - - // Load condition value from expression - const auto condition_value = _codegen->emit_load(lhs); - -#if RESHADEFX_SHORT_CIRCUIT - _codegen->leave_block_and_branch_conditional(condition_value, true_block, false_block); - - _codegen->set_block(true_block); - // Only load true expression value after entering the first block - const auto true_value = _codegen->emit_load(true_exp); - true_block = _codegen->leave_block_and_branch(merge_block); - - _codegen->set_block(false_block); - // Only load false expression value after entering the second block - const auto false_value = _codegen->emit_load(false_exp); - false_block = _codegen->leave_block_and_branch(merge_block); - - _codegen->enter_block(merge_block); - - const auto result_value = _codegen->emit_phi(lhs.location, condition_value, condition_block, true_value, true_block, false_value, false_block, type); -#else - const auto true_value = _codegen->emit_load(true_exp); - const auto false_value = _codegen->emit_load(false_exp); - - const auto result_value = _codegen->emit_ternary_op(lhs.location, op, type, condition_value, true_value, false_value); -#endif - lhs.reset_to_rvalue(lhs.location, result_value, type); - #pragma endregion - } - } - - return true; -} - -bool reshadefx::parser::parse_expression_assignment(expression &lhs) -{ - // Parse left hand side of the expression - if (!parse_expression_multary(lhs)) - return false; - - // Check if an operator exists so that this is an assignment - if (accept_assignment_op()) - { - // Remember the operator token before parsing the expression that follows it - const tokenid op = _token.id; - - // Parse right hand side of the assignment expression - expression rhs; - if (!parse_expression_multary(rhs)) - return false; - - // Check if the assignment is valid - if (lhs.type.has(type::q_const) || lhs.type.has(type::q_uniform) || !lhs.is_lvalue) - return error(lhs.location, 3025, "l-value specifies const object"), false; - if (!type::rank(lhs.type, rhs.type)) - return error(rhs.location, 3020, "cannot convert these types"), false; - - // Cannot perform bitwise operations on non-integral types - if (!lhs.type.is_integral() && (op == tokenid::ampersand_equal || op == tokenid::pipe_equal || op == tokenid::caret_equal)) - return error(lhs.location, 3082, "int or unsigned int type required"), false; - - // Perform implicit type conversion of right hand side value - if (rhs.type.components() > lhs.type.components()) - warning(rhs.location, 3206, "implicit truncation of vector type"); - - rhs.add_cast_operation(lhs.type); - - auto result = _codegen->emit_load(rhs); - - // Check if this is an assignment with an additional arithmetic instruction - if (op != tokenid::equal) - { - // Load value for modification - const auto value = _codegen->emit_load(lhs); - - // Handle arithmetic assignment operation - result = _codegen->emit_binary_op(lhs.location, op, lhs.type, value, result); - } - - // Write result back to variable - _codegen->emit_store(lhs, result); - - // Return the result value since you can write assignments within expressions - lhs.reset_to_rvalue(lhs.location, result, lhs.type); - } - - return true; -} - -bool reshadefx::parser::parse_annotations(std::unordered_map> &annotations) -{ - // Check if annotations exist and return early if none do - if (!accept('<')) - return true; - - bool parse_success = true; - - while (!peek('>')) - { - if (type type; accept_type_class(type)) - warning(_token.location, 4717, "type prefixes for annotations are deprecated and ignored"); - - if (!expect(tokenid::identifier)) - return false; - - const auto name = std::move(_token.literal_as_string); - - if (expression expression; !expect('=') || !parse_expression_unary(expression) || !expect(';')) - return consume_until('>'), false; // Probably a syntax error, so abort parsing - else if (expression.is_constant) - annotations[name] = { expression.type, expression.constant }; - else // Continue parsing annotations despite this not being a constant, since the syntax is still correct - error(expression.location, 3011, "value must be a literal expression"), parse_success = false; - } - - return expect('>') && parse_success; -} - -// -- Statement & Declaration Parsing -- // - -bool reshadefx::parser::parse_statement(bool scoped) -{ - if (!_codegen->is_in_block()) - return error(_token_next.location, 0, "unreachable code"), false; - - unsigned int loop_control = 0; - unsigned int selection_control = 0; - - // Read any loop and branch control attributes first - while (accept('[')) - { - enum control_mask - { - unroll = 0x1, - dont_unroll = 0x2, - flatten = 0x4, - dont_flatten = 0x8, - }; - - const auto attribute = std::move(_token_next.literal_as_string); - - if (!expect(tokenid::identifier) || !expect(']')) - return false; - - if (attribute == "unroll") - loop_control |= unroll; - else if (attribute == "loop" || attribute == "fastopt") - loop_control |= dont_unroll; - else if (attribute == "flatten") - selection_control |= flatten; - else if (attribute == "branch") - selection_control |= dont_flatten; - else - warning(_token.location, 0, "unknown attribute"); - - if ((loop_control & (unroll | dont_unroll)) == (unroll | dont_unroll)) - return error(_token.location, 3524, "can't use loop and unroll attributes together"), false; - if ((selection_control & (flatten | dont_flatten)) == (flatten | dont_flatten)) - return error(_token.location, 3524, "can't use branch and flatten attributes together"), false; - } - - // Shift by two so that the possible values are 0x01 for 'flatten' and 0x02 for 'dont_flatten', equivalent to 'unroll' and 'dont_unroll' - selection_control >>= 2; - - if (peek('{')) // Parse statement block - return parse_statement_block(scoped); - else if (accept(';')) // Ignore empty statements - return true; - - // Most statements with the exception of declarations are only valid inside functions - if (_codegen->is_in_function()) - { - const auto location = _token_next.location; - - #pragma region If - if (accept(tokenid::if_)) - { - codegen::id true_block = _codegen->create_block(); // Block which contains the statements executed when the condition is true - codegen::id false_block = _codegen->create_block(); // Block which contains the statements executed when the condition is false - const codegen::id merge_block = _codegen->create_block(); // Block that is executed after the branch re-merged with the current control flow - - expression condition; - if (!expect('(') || !parse_expression(condition) || !expect(')')) - return false; - else if (!condition.type.is_scalar()) - return error(condition.location, 3019, "if statement conditional expressions must evaluate to a scalar"), false; - - // Load condition and convert to boolean value as required by 'OpBranchConditional' - condition.add_cast_operation({ type::t_bool, 1, 1 }); - - const codegen::id condition_value = _codegen->emit_load(condition); - const codegen::id condition_block = _codegen->leave_block_and_branch_conditional(condition_value, true_block, false_block); - - { // Then block of the if statement - _codegen->enter_block(true_block); - - if (!parse_statement(true)) - return false; - - true_block = _codegen->leave_block_and_branch(merge_block); - } - { // Else block of the if statement - _codegen->enter_block(false_block); - - if (accept(tokenid::else_) && !parse_statement(true)) - return false; - - false_block = _codegen->leave_block_and_branch(merge_block); - } - - _codegen->enter_block(merge_block); - - // Emit structured control flow for an if statement and connect all basic blocks - _codegen->emit_if(location, condition_value, condition_block, true_block, false_block, selection_control); - - return true; - } - #pragma endregion - - #pragma region Switch - if (accept(tokenid::switch_)) - { - const codegen::id merge_block = _codegen->create_block(); // Block that is executed after the switch re-merged with the current control flow - - expression selector_exp; - if (!expect('(') || !parse_expression(selector_exp) || !expect(')')) - return false; - else if (!selector_exp.type.is_scalar()) - return error(selector_exp.location, 3019, "switch statement expression must evaluate to a scalar"), false; - - // Load selector and convert to integral value as required by switch instruction - selector_exp.add_cast_operation({ type::t_int, 1, 1 }); - - const auto selector_value = _codegen->emit_load(selector_exp); - const auto selector_block = _codegen->leave_block_and_switch(selector_value, merge_block); - - if (!expect('{')) - return false; - - _loop_break_target_stack.push_back(merge_block); - on_scope_exit _([this]() { _loop_break_target_stack.pop_back(); }); - - bool success = true; - codegen::id default_label = merge_block; // The default case jumps to the end of the switch statement if not overwritten - std::vector case_literal_and_labels; - size_t last_case_label_index = 0; - - // Enter first switch statement body block - _codegen->enter_block(_codegen->create_block()); - - while (!peek(tokenid::end_of_file)) - { - while (accept(tokenid::case_) || accept(tokenid::default_)) - { - if (_token.id == tokenid::case_) - { - expression case_label; - if (!parse_expression(case_label)) - return consume_until('}'), false; - else if (!case_label.type.is_scalar() || !case_label.type.is_integral() || !case_label.is_constant) - return error(case_label.location, 3020, "invalid type for case expression - value must be an integer scalar"), consume_until('}'), false; - - // Check for duplicate case values - for (size_t i = 0; i < case_literal_and_labels.size(); i += 2) - { - if (case_literal_and_labels[i] == case_label.constant.as_uint[0]) - { - error(case_label.location, 3532, "duplicate case " + std::to_string(case_label.constant.as_uint[0])); - success = false; - break; - } - } - - case_literal_and_labels.push_back(case_label.constant.as_uint[0]); - case_literal_and_labels.emplace_back(); // This is set to the actual block below - } - else - { - // Check if the default label was already changed by a previous 'default' statement - if (default_label != merge_block) - { - error(_token.location, 3532, "duplicate default in switch statement"); - success = false; - } - - default_label = 0; // This is set to the actual block below - } - - if (!expect(':')) - return consume_until('}'), false; - } - - // It is valid for no statement to follow if this is the last label in the switch body - const bool end_of_switch = peek('}'); - - if (!end_of_switch && !parse_statement(true)) - return consume_until('}'), false; - - // Handle fall-through case and end of switch statement - if (peek(tokenid::case_) || peek(tokenid::default_) || end_of_switch) - { - if (_codegen->is_in_block()) // Disallow fall-through for now - { - error(_token_next.location, 3533, "non-empty case statements must have break or return"); - success = false; - } - - const codegen::id next_block = end_of_switch ? merge_block : _codegen->create_block(); - const codegen::id current_block = _codegen->leave_block_and_branch(next_block); - - assert(current_block != 0); - - if (default_label == 0) - default_label = current_block; - else - for (size_t i = last_case_label_index; i < case_literal_and_labels.size(); i += 2) - case_literal_and_labels[i + 1] = current_block; - - _codegen->enter_block(next_block); - - if (end_of_switch) // We reached the end, nothing more to do - break; - - last_case_label_index = case_literal_and_labels.size(); - } - } - - if (case_literal_and_labels.empty() && default_label == merge_block) - warning(location, 5002, "switch statement contains no 'case' or 'default' labels"); - - // Emit structured control flow for a switch statement and connect all basic blocks - _codegen->emit_switch(location, selector_value, selector_block, default_label, case_literal_and_labels, selection_control); - - return expect('}') && success; - } - #pragma endregion - - #pragma region For - if (accept(tokenid::for_)) - { - if (!expect('(')) - return false; - - enter_scope(); - on_scope_exit _([this]() { leave_scope(); }); - - // Parse initializer first - if (type type; parse_type(type)) - { - unsigned int count = 0; - do { // There may be multiple declarations behind a type, so loop through them - if (count++ > 0 && !expect(',')) - return false; - if (!expect(tokenid::identifier) || !parse_variable(type, std::move(_token.literal_as_string))) - return false; - } while (!peek(';')); - } - else // Initializer can also contain an expression if not a variable declaration list - { - expression expression; - parse_expression(expression); // It is valid for there to be no initializer expression, so ignore result - } - - if (!expect(';')) - return false; - - const codegen::id merge_block = _codegen->create_block(); // Block that is executed after the loop - const codegen::id header_label = _codegen->create_block(); // Pointer to the loop merge instruction - const codegen::id continue_label = _codegen->create_block(); // Pointer to the continue block - codegen::id loop_block = _codegen->create_block(); // Pointer to the main loop body block - codegen::id condition_block = _codegen->create_block(); // Pointer to the condition check - codegen::id condition_value = 0; - - // End current block by branching to the next label - const codegen::id prev_block = _codegen->leave_block_and_branch(header_label); - - { // Begin loop block (this header is used for explicit structured control flow) - _codegen->enter_block(header_label); - - _codegen->leave_block_and_branch(condition_block); - } - - { // Parse condition block - _codegen->enter_block(condition_block); - - if (expression condition; parse_expression(condition)) - { - if (!condition.type.is_scalar()) - return error(condition.location, 3019, "scalar value expected"), false; - - // Evaluate condition and branch to the right target - condition.add_cast_operation({ type::t_bool, 1, 1 }); - - condition_value = _codegen->emit_load(condition); - - condition_block = _codegen->leave_block_and_branch_conditional(condition_value, loop_block, merge_block); - } - else // It is valid for there to be no condition expression - { - condition_block = _codegen->leave_block_and_branch(loop_block); - } - - if (!expect(';')) - return false; - } - - { // Parse loop continue block into separate block so it can be appended to the end down the line - _codegen->enter_block(continue_label); - - expression continue_exp; - parse_expression(continue_exp); // It is valid for there to be no continue expression, so ignore result - - if (!expect(')')) - return false; - - // Branch back to the loop header at the end of the continue block - _codegen->leave_block_and_branch(header_label); - } - - { // Parse loop body block - _codegen->enter_block(loop_block); - - _loop_break_target_stack.push_back(merge_block); - _loop_continue_target_stack.push_back(continue_label); - - const bool parse_success = parse_statement(false); - - _loop_break_target_stack.pop_back(); - _loop_continue_target_stack.pop_back(); - - if (!parse_success) - return false; - - loop_block = _codegen->leave_block_and_branch(continue_label); - } - - // Add merge block label to the end of the loop - _codegen->enter_block(merge_block); - - // Emit structured control flow for a loop statement and connect all basic blocks - _codegen->emit_loop(location, condition_value, prev_block, header_label, condition_block, loop_block, continue_label, loop_control); - - return true; - } - #pragma endregion - - #pragma region While - if (accept(tokenid::while_)) - { - enter_scope(); - on_scope_exit _([this]() { leave_scope(); }); - - const codegen::id merge_block = _codegen->create_block(); - const codegen::id header_label = _codegen->create_block(); - const codegen::id continue_label = _codegen->create_block(); - codegen::id loop_block = _codegen->create_block(); - codegen::id condition_block = _codegen->create_block(); - codegen::id condition_value = 0; - - // End current block by branching to the next label - const codegen::id prev_block = _codegen->leave_block_and_branch(header_label); - - { // Begin loop block - _codegen->enter_block(header_label); - - _codegen->leave_block_and_branch(loop_block); - } - - { // Parse condition block - _codegen->enter_block(condition_block); - - expression condition; - if (!expect('(') || !parse_expression(condition) || !expect(')')) - return false; - else if (!condition.type.is_scalar()) - return error(condition.location, 3019, "scalar value expected"), false; - - // Evaluate condition and branch to the right target - condition.add_cast_operation({ type::t_bool, 1, 1 }); - - condition_value = _codegen->emit_load(condition); - - condition_block = _codegen->leave_block_and_branch_conditional(condition_value, loop_block, merge_block); - } - - { // Parse loop body block - _codegen->enter_block(loop_block); - - _loop_break_target_stack.push_back(merge_block); - _loop_continue_target_stack.push_back(continue_label); - - const bool parse_success = parse_statement(false); - - _loop_break_target_stack.pop_back(); - _loop_continue_target_stack.pop_back(); - - if (!parse_success) - return false; - - loop_block = _codegen->leave_block_and_branch(continue_label); - } - - { // Branch back to the loop header in empty continue block - _codegen->enter_block(continue_label); - - _codegen->leave_block_and_branch(header_label); - } - - // Add merge block label to the end of the loop - _codegen->enter_block(merge_block); - - // Emit structured control flow for a loop statement and connect all basic blocks - _codegen->emit_loop(location, condition_value, prev_block, header_label, condition_block, loop_block, continue_label, loop_control); - - return true; - } - #pragma endregion - - #pragma region DoWhile - if (accept(tokenid::do_)) - { - const codegen::id merge_block = _codegen->create_block(); - const codegen::id header_label = _codegen->create_block(); - const codegen::id continue_label = _codegen->create_block(); - codegen::id loop_block = _codegen->create_block(); - codegen::id condition_value = 0; - - // End current block by branching to the next label - const codegen::id prev_block = _codegen->leave_block_and_branch(header_label); - - { // Begin loop block - _codegen->enter_block(header_label); - - _codegen->leave_block_and_branch(loop_block); - } - - { // Parse loop body block - _codegen->enter_block(loop_block); - - _loop_break_target_stack.push_back(merge_block); - _loop_continue_target_stack.push_back(continue_label); - - const bool parse_success = parse_statement(true); - - _loop_break_target_stack.pop_back(); - _loop_continue_target_stack.pop_back(); - - if (!parse_success) - return false; - - loop_block = _codegen->leave_block_and_branch(continue_label); - } - - { // Continue block does the condition evaluation - _codegen->enter_block(continue_label); - - expression condition; - if (!expect(tokenid::while_) || !expect('(') || !parse_expression(condition) || !expect(')') || !expect(';')) - return false; - else if (!condition.type.is_scalar()) - return error(condition.location, 3019, "scalar value expected"), false; - - // Evaluate condition and branch to the right target - condition.add_cast_operation({ type::t_bool, 1, 1 }); - - condition_value = _codegen->emit_load(condition); - - _codegen->leave_block_and_branch_conditional(condition_value, header_label, merge_block); - } - - // Add merge block label to the end of the loop - _codegen->enter_block(merge_block); - - // Emit structured control flow for a loop statement and connect all basic blocks - _codegen->emit_loop(location, condition_value, prev_block, header_label, 0, loop_block, continue_label, loop_control); - - return true; - } - #pragma endregion - - #pragma region Break - if (accept(tokenid::break_)) - { - if (_loop_break_target_stack.empty()) - return error(location, 3518, "break must be inside loop"), false; - - // Branch to the break target of the inner most loop on the stack - _codegen->leave_block_and_branch(_loop_break_target_stack.back(), 1); - - return expect(';'); - } - #pragma endregion - - #pragma region Continue - if (accept(tokenid::continue_)) - { - if (_loop_continue_target_stack.empty()) - return error(location, 3519, "continue must be inside loop"), false; - - // Branch to the continue target of the inner most loop on the stack - _codegen->leave_block_and_branch(_loop_continue_target_stack.back(), 2); - - return expect(';'); - } - #pragma endregion - - #pragma region Return - if (accept(tokenid::return_)) - { - const type &ret_type = _current_return_type; - - if (!peek(';')) - { - expression expression; - if (!parse_expression(expression)) - return error(_token_next.location, 3000, "syntax error: unexpected '" + token::id_to_name(_token_next.id) + "', expected expression"), consume_until(';'), false; - - // Cannot return to void - if (ret_type.is_void()) - // Consume the semicolon that follows the return expression so that parsing may continue - return error(location, 3079, "void functions cannot return a value"), accept(';'), false; - - // Cannot return arrays from a function - if (expression.type.is_array() || !type::rank(expression.type, ret_type)) - return error(location, 3017, "expression does not match function return type"), accept(';'), false; - - // Load return value and perform implicit cast to function return type - if (expression.type.components() > ret_type.components()) - warning(expression.location, 3206, "implicit truncation of vector type"); - - expression.add_cast_operation(ret_type); - - const auto return_value = _codegen->emit_load(expression); - - _codegen->leave_block_and_return(return_value); - } - else if (!ret_type.is_void()) - { - // No return value was found, but the function expects one - error(location, 3080, "function must return a value"); - - // Consume the semicolon that follows the return expression so that parsing may continue - accept(';'); - - return false; - } - else - { - _codegen->leave_block_and_return(); - } - - return expect(';'); - } - #pragma endregion - - #pragma region Discard - if (accept(tokenid::discard_)) - { - // Leave the current function block - _codegen->leave_block_and_kill(); - - return expect(';'); - } - #pragma endregion - } - - #pragma region Declaration - // Handle variable declarations - if (type type; parse_type(type)) - { - unsigned int count = 0; - do { // There may be multiple declarations behind a type, so loop through them - if (count++ > 0 && !expect(',')) - // Try to consume the rest of the declaration so that parsing may continue despite the error - return consume_until(';'), false; - if (!expect(tokenid::identifier) || !parse_variable(type, std::move(_token.literal_as_string))) - return consume_until(';'), false; - } while (!peek(';')); - - return expect(';'); - } - #pragma endregion - - // Handle expression statements - if (expression expression; parse_expression(expression)) - return expect(';'); // A statement has to be terminated with a semicolon - - // No token should come through here, since all statements and expressions should have been handled above, so this is an error in the syntax - error(_token_next.location, 3000, "syntax error: unexpected '" + token::id_to_name(_token_next.id) + '\''); - - // Gracefully consume any remaining characters until the statement would usually end, so that parsing may continue despite the error - consume_until(';'); - - return false; -} - -bool reshadefx::parser::parse_statement_block(bool scoped) -{ - if (!expect('{')) - return false; - - if (scoped) - enter_scope(); - - // Parse statements until the end of the block is reached - while (!peek('}') && !peek(tokenid::end_of_file)) - { - if (!parse_statement(true)) - { - if (scoped) - leave_scope(); - - // Ignore the rest of this block - unsigned int level = 0; - - while (!peek(tokenid::end_of_file)) - { - if (accept('{')) - { - ++level; - } - else if (accept('}')) - { - if (level-- == 0) - break; - } // These braces are necessary to match the 'else' to the correct 'if' statement - else - { - consume(); - } - } - - return false; - } - } - - if (scoped) - leave_scope(); - - return expect('}'); -} - -bool reshadefx::parser::parse_top() -{ - if (accept(tokenid::namespace_)) - { - // Anonymous namespaces are not supported right now, so an identifier is a must - if (!expect(tokenid::identifier)) - return false; - - const auto name = std::move(_token.literal_as_string); - - if (!expect('{')) - return false; - - enter_namespace(name); - - bool success = true; - // Recursively parse top level statements until the namespace is closed again - while (!peek('}')) // Empty namespaces are valid - if (!parse_top()) - success = false; // Continue parsing even after encountering an error - - leave_namespace(); - - return expect('}') && success; - } - else if (accept(tokenid::struct_)) // Structure keyword found, parse the structure definition - { - if (!parse_struct() || !expect(';')) // Structure definitions are terminated with a semicolon - return false; - } - else if (accept(tokenid::technique)) // Technique keyword found, parse the technique definition - { - if (!parse_technique()) - return false; - } - else if (type type; parse_type(type)) // Type found, this can be either a variable or a function declaration - { - if (!expect(tokenid::identifier)) - return false; - - if (peek('(')) - { - const auto name = std::move(_token.literal_as_string); - // This is definitely a function declaration, so parse it - if (!parse_function(type, name)) { - // Insert dummy function into symbol table, so later references can be resolved despite the error - insert_symbol(name, { symbol_type::function, ~0u, { type::t_function } }, true); - return false; - } - } - else - { - // There may be multiple variable names after the type, handle them all - unsigned int count = 0; - do { - if (count++ > 0 && !(expect(',') && expect(tokenid::identifier))) - return false; - const auto name = std::move(_token.literal_as_string); - if (!parse_variable(type, name, true)) { - // Insert dummy variable into symbol table, so later references can be resolved despite the error - insert_symbol(name, { symbol_type::variable, ~0u, type }, true); - return consume_until(';'), false; // Skip the rest of the statement in case of an error - } - } while (!peek(';')); - - if (!expect(';')) // Variable declarations are terminated with a semicolon - return false; - } - } - else if (!accept(';')) // Ignore single semicolons in the source - { - consume(); // Unexpected token in source stream, consume and report an error about it - error(_token.location, 3000, "syntax error: unexpected '" + token::id_to_name(_token.id) + '\''); - return false; - } - - return true; -} - -bool reshadefx::parser::parse_struct() -{ - const auto location = std::move(_token.location); - - struct_info info; - // The structure name is optional - if (accept(tokenid::identifier)) - info.name = std::move(_token.literal_as_string); - else - info.name = "_anonymous_struct_" + std::to_string(location.line) + '_' + std::to_string(location.column); - - info.unique_name = 'S' + current_scope().name + info.name; - std::replace(info.unique_name.begin(), info.unique_name.end(), ':', '_'); - - if (!expect('{')) - return false; - - while (!peek('}')) // Empty structures are possible - { - struct_member_info member; - - if (!parse_type(member.type)) - return error(_token_next.location, 3000, "syntax error: unexpected '" + token::id_to_name(_token_next.id) + "', expected struct member type"), consume_until('}'), false; - - if (member.type.is_void()) - return error(_token_next.location, 3038, "struct members cannot be void"), consume_until('}'), false; - if (member.type.has(type::q_in) || member.type.has(type::q_out)) - return error(_token_next.location, 3055, "struct members cannot be declared 'in' or 'out'"), consume_until('}'), false; - - if (member.type.is_struct()) // Nesting structures would make input/output argument flattening more complicated, so prevent it for now - return error(_token_next.location, 3090, "nested struct members are not supported"), consume_until('}'), false; - - unsigned int count = 0; - do { - if (count++ > 0 && !expect(',')) - return consume_until('}'), false; - - if (!expect(tokenid::identifier)) - return consume_until('}'), false; - - member.name = std::move(_token.literal_as_string); - member.location = std::move(_token.location); - - // Modify member specific type, so that following members in the declaration list are not affected by this - if (!parse_array_size(member.type)) - return consume_until('}'), false; - else if (member.type.array_length < 0) - return error(member.location, 3072, '\'' + member.name + "': array dimensions of struct members must be explicit"), consume_until('}'), false; - - // Structure members may have semantics to use them as input/output types - if (accept(':')) - { - if (!expect(tokenid::identifier)) - return consume_until('}'), false; - - member.semantic = std::move(_token.literal_as_string); - // Make semantic upper case to simplify comparison later on - std::transform(member.semantic.begin(), member.semantic.end(), member.semantic.begin(), [](char c) { return static_cast(toupper(c)); }); - } - - // Save member name and type for book keeping - info.member_list.push_back(member); - } while (!peek(';')); - - if (!expect(';')) - return consume_until('}'), false; - } - - // Empty structures are valid, but not usually intended, so emit a warning - if (info.member_list.empty()) - warning(location, 5001, "struct has no members"); - - // Define the structure now that information about all the member types was gathered - const auto id = _codegen->define_struct(location, info); - - // Insert the symbol into the symbol table - const symbol symbol = { symbol_type::structure, id }; - - if (!insert_symbol(info.name, symbol, true)) - return error(location, 3003, "redefinition of '" + info.name + '\''), false; - - return expect('}'); -} - -bool reshadefx::parser::parse_function(type type, std::string name) -{ - const auto location = std::move(_token.location); - - if (!expect('(')) // Functions always have a parameter list - return false; - if (type.qualifiers != 0) - return error(location, 3047, '\'' + name + "': function return type cannot have any qualifiers"), false; - - function_info info; - info.name = name; - info.unique_name = 'F' + current_scope().name + name; - std::replace(info.unique_name.begin(), info.unique_name.end(), ':', '_'); - - info.return_type = type; - _current_return_type = info.return_type; - - // Enter function scope - enter_scope(); on_scope_exit _([this]() { leave_scope(); _codegen->leave_function(); }); - - while (!peek(')')) - { - if (!info.parameter_list.empty() && !expect(',')) - return false; - - struct_member_info param; - - if (!parse_type(param.type)) - return error(_token_next.location, 3000, "syntax error: unexpected '" + token::id_to_name(_token_next.id) + "', expected parameter type"), false; - - if (!expect(tokenid::identifier)) - return false; - - param.name = std::move(_token.literal_as_string); - param.location = std::move(_token.location); - - if (param.type.is_void()) - return error(param.location, 3038, '\'' + param.name + "': function parameters cannot be void"), false; - if (param.type.has(type::q_extern)) - return error(param.location, 3006, '\'' + param.name + "': function parameters cannot be declared 'extern'"), false; - if (param.type.has(type::q_static)) - return error(param.location, 3007, '\'' + param.name + "': function parameters cannot be declared 'static'"), false; - if (param.type.has(type::q_uniform)) - return error(param.location, 3047, '\'' + param.name + "': function parameters cannot be declared 'uniform', consider placing in global scope instead"), false; - - if (param.type.has(type::q_out) && param.type.has(type::q_const)) - return error(param.location, 3046, '\'' + param.name + "': output parameters cannot be declared 'const'"), false; - else if (!param.type.has(type::q_out)) - param.type.qualifiers |= type::q_in; // Function parameters are implicitly 'in' if not explicitly defined as 'out' - - if (!parse_array_size(param.type)) - return false; - else if (param.type.array_length < 0) - return error(param.location, 3072, '\'' + param.name + "': array dimensions of function parameters must be explicit"), false; - - // Handle parameter type semantic - if (accept(':')) - { - if (!expect(tokenid::identifier)) - return false; - - param.semantic = std::move(_token.literal_as_string); - // Make semantic upper case to simplify comparison later on - std::transform(param.semantic.begin(), param.semantic.end(), param.semantic.begin(), [](char c) { return static_cast(toupper(c)); }); - } - - info.parameter_list.push_back(std::move(param)); - } - - if (!expect(')')) - return false; - - // Handle return type semantic - if (accept(':')) - { - if (!expect(tokenid::identifier)) - return false; - if (type.is_void()) - return error(_token.location, 3076, '\'' + name + "': void function cannot have a semantic"), false; - - info.return_semantic = std::move(_token.literal_as_string); - // Make semantic upper case to simplify comparison later on - std::transform(info.return_semantic.begin(), info.return_semantic.end(), info.return_semantic.begin(), [](char c) { return static_cast(toupper(c)); }); - } - - // Check if this is a function declaration without a body - if (accept(';')) - return error(location, 3510, '\'' + name + "': function is missing an implementation"), false; - - // Define the function now that information about the declaration was gathered - const auto id = _codegen->define_function(location, info); - - // Insert the function and parameter symbols into the symbol table - symbol symbol = { symbol_type::function, id, { type::t_function } }; - symbol.function = &_codegen->find_function(id); - - if (!insert_symbol(name, symbol, true)) - return error(location, 3003, "redefinition of '" + name + '\''), false; - - for (const auto ¶m : info.parameter_list) - if (!insert_symbol(param.name, { symbol_type::variable, param.definition, param.type })) - return error(param.location, 3003, "redefinition of '" + param.name + '\''), false; - - // A function has to start with a new block - _codegen->enter_block(_codegen->create_block()); - - const bool parse_success = parse_statement_block(false); - - // Add implicit return statement to the end of functions - if (_codegen->is_in_block()) - _codegen->leave_block_and_return(); - - return parse_success; -} - -bool reshadefx::parser::parse_variable(type type, std::string name, bool global) -{ - const auto location = std::move(_token.location); - - if (type.is_void()) - return error(location, 3038, '\'' + name + "': variables cannot be void"), false; - if (type.has(type::q_in) || type.has(type::q_out)) - return error(location, 3055, '\'' + name + "': variables cannot be declared 'in' or 'out'"), false; - - // Local and global variables have different requirements - if (global) - { - // Check that type qualifier combinations are valid - if (type.has(type::q_static)) - { - // Global variables that are 'static' cannot be of another storage class - if (type.has(type::q_uniform)) - return error(location, 3007, '\'' + name + "': uniform global variables cannot be declared 'static'"), false; - // The 'volatile qualifier is only valid memory object declarations that are storage images or uniform blocks - if (type.has(type::q_volatile)) - return error(location, 3008, '\'' + name + "': global variables cannot be declared 'volatile'"), false; - } - else - { - // Make all global variables 'uniform' by default, since they should be externally visible without the 'static' keyword - if (!type.has(type::q_uniform) && !(type.is_texture() || type.is_sampler())) - warning(location, 5000, '\'' + name + "': global variables are considered 'uniform' by default"); - - // Global variables that are not 'static' are always 'extern' and 'uniform' - type.qualifiers |= type::q_extern | type::q_uniform; - - // It is invalid to make 'uniform' variables constant, since they can be modified externally - if (type.has(type::q_const)) - return error(location, 3035, '\'' + name + "': variables which are 'uniform' cannot be declared 'const'"), false; - } - } - else - { - if (type.has(type::q_extern)) - return error(location, 3006, '\'' + name + "': local variables cannot be declared 'extern'"), false; - if (type.has(type::q_uniform)) - return error(location, 3047, '\'' + name + "': local variables cannot be declared 'uniform'"), false; - - if (type.is_texture() || type.is_sampler()) - return error(location, 3038, '\'' + name + "': local variables cannot be textures or samplers"), false; - } - - // The variable name may be followed by an optional array size expression - if (!parse_array_size(type)) - return false; - - expression initializer; - texture_info texture_info; - sampler_info sampler_info; - - if (accept(':')) - { - if (!expect(tokenid::identifier)) - return false; - else if (!global) // Only global variables can have a semantic - return error(_token.location, 3043, '\'' + name + "': local variables cannot have semantics"), false; - - std::string &semantic = texture_info.semantic; - semantic = std::move(_token.literal_as_string); - - // Make semantic upper case to simplify comparison later on - std::transform(semantic.begin(), semantic.end(), semantic.begin(), [](char c) { return static_cast(toupper(c)); }); - } - else - { - // Global variables can have optional annotations - if (global && !parse_annotations(texture_info.annotations)) - return false; - - // Variables without a semantic may have an optional initializer - if (accept('=')) - { - if (!parse_expression_assignment(initializer)) - return error(_token_next.location, 3000, "syntax error: unexpected '" + token::id_to_name(_token_next.id) + "', expected expression"), false; - - if (global && !initializer.is_constant) // TODO: This could be resolved by initializing these at the beginning of the entry point - return error(initializer.location, 3011, '\'' + name + "': initial value must be a literal expression"), false; - - // Check type compatibility - if ((type.array_length >= 0 && initializer.type.array_length != type.array_length) || !type::rank(initializer.type, type)) - return error(initializer.location, 3017, '\'' + name + "': initial value does not match variable type"), false; - if ((initializer.type.rows < type.rows || initializer.type.cols < type.cols) && !initializer.type.is_scalar()) - return error(initializer.location, 3017, "cannot implicitly convert these vector types"), false; - - // Deduce array size from the initializer expression - if (initializer.type.is_array()) - type.array_length = initializer.type.array_length; - - // Perform implicit cast from initializer expression to variable type - if (initializer.type.components() > type.components()) - warning(initializer.location, 3206, "implicit truncation of vector type"); - - initializer.add_cast_operation(type); - } - else if (type.is_numeric() || type.is_struct()) // Numeric variables without an initializer need special handling - { - if (type.has(type::q_const)) // Constants have to have an initial value - return error(location, 3012, '\'' + name + "': missing initial value"), false; - else if (!type.has(type::q_uniform)) // Zero initialize all global variables - initializer.reset_to_rvalue_constant(location, {}, type); - } - else if (global && accept('{')) // Textures and samplers can have a property block attached to their declaration - { - if (type.has(type::q_const)) // Non-numeric variables cannot be constants - return error(location, 3035, '\'' + name + "': this variable type cannot be declared 'const'"), false; - - while (!peek('}')) - { - if (!expect(tokenid::identifier)) - return consume_until('}'), false; - - const auto property_name = std::move(_token.literal_as_string); - const auto property_location = std::move(_token.location); - - if (!expect('=')) - return consume_until('}'), false; - - backup(); - - expression expression; - - if (accept(tokenid::identifier)) // Handle special enumeration names for property values - { - // Transform identifier to uppercase to do case-insensitive comparison - std::transform(_token.literal_as_string.begin(), _token.literal_as_string.end(), _token.literal_as_string.begin(), [](char c) { return static_cast(toupper(c)); }); - - static const std::pair s_values[] = { - { "NONE", 0 }, { "POINT", 0 }, - { "LINEAR", 1 }, - { "WRAP", uint32_t(texture_address_mode::wrap) }, { "REPEAT", uint32_t(texture_address_mode::wrap) }, - { "MIRROR", uint32_t(texture_address_mode::mirror) }, - { "CLAMP", uint32_t(texture_address_mode::clamp) }, - { "BORDER", uint32_t(texture_address_mode::border) }, - { "R8", uint32_t(texture_format::r8) }, - { "R16F", uint32_t(texture_format::r16f) }, - { "R32F", uint32_t(texture_format::r32f) }, - { "RG8", uint32_t(texture_format::rg8) }, { "R8G8", uint32_t(texture_format::rg8) }, - { "RG16", uint32_t(texture_format::rg16) }, { "R16G16", uint32_t(texture_format::rg16) }, - { "RG16F", uint32_t(texture_format::rg16f) }, { "R16G16F", uint32_t(texture_format::rg16f) }, - { "RG32F", uint32_t(texture_format::rg32f) }, { "R32G32F", uint32_t(texture_format::rg32f) }, - { "RGBA8", uint32_t(texture_format::rgba8) }, { "R8G8B8A8", uint32_t(texture_format::rgba8) }, - { "RGBA16", uint32_t(texture_format::rgba16) }, { "R16G16B16A16", uint32_t(texture_format::rgba16) }, - { "RGBA16F", uint32_t(texture_format::rgba16f) }, { "R16G16B16A16F", uint32_t(texture_format::rgba16f) }, - { "RGBA32F", uint32_t(texture_format::rgba32f) }, { "R32G32B32A32F", uint32_t(texture_format::rgba32f) }, - { "RGB10A2", uint32_t(texture_format::rgb10a2) }, { "R10G10B10A2", uint32_t(texture_format::rgb10a2) }, - }; - - // Look up identifier in list of possible enumeration names - const auto it = std::find_if(std::begin(s_values), std::end(s_values), - [this](const auto &it) { return it.first == _token.literal_as_string; }); - - if (it != std::end(s_values)) - expression.reset_to_rvalue_constant(_token.location, it->second); - else // No match found, so rewind to parser state before the identifier was consumed and try parsing it as a normal expression - restore(); - } - - // Parse right hand side as normal expression if no special enumeration name was matched already - if (!expression.is_constant && !parse_expression_multary(expression)) - return error(_token_next.location, 3000, "syntax error: unexpected '" + token::id_to_name(_token_next.id) + "', expected expression"), consume_until('}'), false; - - if (property_name == "Texture") - { - // Ignore invalid symbols that were added during error recovery - if (expression.base == 0xFFFFFFFF) - return consume_until('}'), false; - - if (!expression.type.is_texture()) - return error(expression.location, 3020, "type mismatch, expected texture name"), consume_until('}'), false; - - sampler_info.texture_name = _codegen->find_texture(expression.base).unique_name; - } - else - { - if (!expression.is_constant || !expression.type.is_scalar()) - return error(expression.location, 3538, "value must be a literal scalar expression"), consume_until('}'), false; - - // All states below expect the value to be of an unsigned integer type - expression.add_cast_operation({ type::t_uint, 1, 1 }); - const unsigned int value = expression.constant.as_uint[0]; - - if (property_name == "Width") - texture_info.width = value > 0 ? value : 1; - else if (property_name == "Height") - texture_info.height = value > 0 ? value : 1; - else if (property_name == "MipLevels") - texture_info.levels = value > 0 ? value : 1; - else if (property_name == "Format") - texture_info.format = static_cast(value); - else if (property_name == "SRGBTexture" || property_name == "SRGBReadEnable") - sampler_info.srgb = value != 0; - else if (property_name == "AddressU") - sampler_info.address_u = static_cast(value); - else if (property_name == "AddressV") - sampler_info.address_v = static_cast(value); - else if (property_name == "AddressW") - sampler_info.address_w = static_cast(value); - else if (property_name == "MinFilter") - sampler_info.filter = static_cast((uint32_t(sampler_info.filter) & 0x0F) | ((value << 4) & 0x30)); // Combine sampler filter components into a single filter enumeration value - else if (property_name == "MagFilter") - sampler_info.filter = static_cast((uint32_t(sampler_info.filter) & 0x33) | ((value << 2) & 0x0C)); - else if (property_name == "MipFilter") - sampler_info.filter = static_cast((uint32_t(sampler_info.filter) & 0x3C) | (value & 0x03)); - else if (property_name == "MinLOD" || property_name == "MaxMipLevel") - sampler_info.min_lod = static_cast(value); - else if (property_name == "MaxLOD") - sampler_info.max_lod = static_cast(value); - else if (property_name == "MipLODBias" || property_name == "MipMapLodBias") - sampler_info.lod_bias = static_cast(value); - else - return error(property_location, 3004, "unrecognized property '" + property_name + '\''), consume_until('}'), false; - } - - if (!expect(';')) - return consume_until('}'), false; - } - - if (!expect('}')) - return false; - } - } - - // At this point the array size should be known (either from the declaration or the initializer) - if (type.array_length < 0) - return error(location, 3074, '\'' + name + "': implicit array missing initial value"), false; - - symbol symbol; - - if (type.is_numeric() && type.has(type::q_const) && initializer.is_constant) // Variables with a constant initializer and constant type are named constants - { - // Named constants are special symbols - symbol = { symbol_type::constant, 0, type, initializer.constant }; - } - else if (type.is_texture()) - { - assert(global); - - // Add namespace scope to avoid name clashes - texture_info.unique_name = 'V' + current_scope().name + name; - std::replace(texture_info.unique_name.begin(), texture_info.unique_name.end(), ':', '_'); - - symbol = { symbol_type::variable, 0, type }; - symbol.id = _codegen->define_texture(location, texture_info); - } - else if (type.is_sampler()) // Samplers are actually combined image samplers - { - assert(global); - - if (sampler_info.texture_name.empty()) - return error(location, 3012, '\'' + name + "': missing 'Texture' property"), false; - - // Add namespace scope to avoid name clashes - sampler_info.unique_name = 'V' + current_scope().name + name; - std::replace(sampler_info.unique_name.begin(), sampler_info.unique_name.end(), ':', '_'); - - sampler_info.annotations = std::move(texture_info.annotations); - - symbol = { symbol_type::variable, 0, type }; - symbol.id = _codegen->define_sampler(location, sampler_info); - } - else if (type.has(type::q_uniform)) // Uniform variables are put into a global uniform buffer structure - { - assert(global); - - uniform_info uniform_info; - uniform_info.name = name; - uniform_info.type = type; - - uniform_info.annotations = std::move(texture_info.annotations); - - uniform_info.initializer_value = std::move(initializer.constant); - uniform_info.has_initializer_value = initializer.is_constant; - - symbol = { symbol_type::variable, 0, type }; - symbol.id = _codegen->define_uniform(location, uniform_info); - } - else // All other variables are separate entities - { - symbol = { symbol_type::variable, 0, type }; - - // Update global variable names to contain the namespace scope to avoid name clashes - std::string unique_name = global ? 'V' + current_scope().name + name : name; - std::replace(unique_name.begin(), unique_name.end(), ':', '_'); - - // The initializer expression for variables must be a constant - // Also, only use the variable initializer on global variables, since local variables for e.g. "for" statements need to be assigned in their respective scope and not their declaration - if (global && initializer.is_constant) - { - symbol.id = _codegen->define_variable(location, type, std::move(unique_name), global, _codegen->emit_constant(initializer.type, initializer.constant)); - } - else // Non-constant initializers are explicitly stored in the variable at the definition location instead - { - const auto initializer_value = _codegen->emit_load(initializer); - - symbol.id = _codegen->define_variable(location, type, std::move(unique_name), global); - - if (initializer_value != 0) - { - assert(!global); // Global variables cannot have a dynamic initializer - - expression variable; variable.reset_to_lvalue(location, symbol.id, type); - - _codegen->emit_store(variable, initializer_value); - } - } - } - - // Insert the symbol into the symbol table - if (!insert_symbol(name, symbol, global)) - return error(location, 3003, "redefinition of '" + name + '\''), false; - - return true; -} - -bool reshadefx::parser::parse_technique() -{ - if (!expect(tokenid::identifier)) - return false; - - technique_info info; - info.name = std::move(_token.literal_as_string); - - if (!parse_annotations(info.annotations) || !expect('{')) - return false; - - while (!peek('}')) - { - if (pass_info pass; parse_technique_pass(pass)) - info.passes.push_back(std::move(pass)); - else if (!peek(tokenid::pass)) // If there is another pass definition following, try to parse that despite the error - return consume_until('}'), false; - } - - _codegen->define_technique(info); - - return expect('}'); -} - -bool reshadefx::parser::parse_technique_pass(pass_info &info) -{ - if (!expect(tokenid::pass)) - return false; - - // Passes can have an optional name, so consume and ignore that if it exists - accept(tokenid::identifier); - - if (!expect('{')) - return false; - - while (!peek('}')) - { - // Parse pass states - if (!expect(tokenid::identifier)) - return consume_until('}'), false; - - auto location = std::move(_token.location); - const auto state = std::move(_token.literal_as_string); - - if (!expect('=')) - return consume_until('}'), false; - - const bool is_shader_state = state == "VertexShader" || state == "PixelShader"; - const bool is_texture_state = state.compare(0, 12, "RenderTarget") == 0 && (state.size() == 12 || (state[12] >= '0' && state[12] < '8')); - - // Shader and render target assignment looks up values in the symbol table, so handle those separately from the other states - if (is_shader_state || is_texture_state) - { - // Starting an identifier with '::' restricts the symbol search to the global namespace level - const bool exclusive = accept(tokenid::colon_colon); - - std::string identifier; - - if (expect(tokenid::identifier)) - identifier = std::move(_token.literal_as_string); - else - return consume_until('}'), false; - - // Can concatenate multiple '::' to force symbol search for a specific namespace level - while (accept(tokenid::colon_colon)) - { - if (!expect(tokenid::identifier)) - return consume_until('}'), false; - - identifier += "::" + std::move(_token.literal_as_string); - } - - location = std::move(_token.location); - - // Figure out which scope to start searching in - scope scope = { "::", 0, 0 }; - if (!exclusive) scope = current_scope(); - - // Lookup name in the symbol table - const symbol symbol = find_symbol(identifier, scope, exclusive); - - // Ignore invalid symbols that were added during error recovery - if (symbol.id == 0xFFFFFFFF) - return consume_until('}'), false; - - if (is_shader_state) - { - if (!symbol.id) - return error(location, 3501, "undeclared identifier '" + identifier + "', expected function name"), consume_until('}'), false; - else if (!symbol.type.is_function()) - return error(location, 3020, "type mismatch, expected function name"), consume_until('}'), false; - - const bool is_vs = state[0] == 'V'; - const bool is_ps = state[0] == 'P'; - - // Look up the matching function info for this function definition - function_info &function_info = _codegen->find_function(symbol.id); - - // We potentially need to generate a special entry point function which translates between function parameters and input/output variables - _codegen->define_entry_point(function_info, is_ps); - - if (is_vs) - info.vs_entry_point = function_info.unique_name; - if (is_ps) - info.ps_entry_point = function_info.unique_name; - } - else - { - assert(is_texture_state); - - if (!symbol.id) - return error(location, 3004, "undeclared identifier '" + identifier + "', expected texture name"), consume_until('}'), false; - else if (!symbol.type.is_texture()) - return error(location, 3020, "type mismatch, expected texture name"), consume_until('}'), false; - - const texture_info &target_info = _codegen->find_texture(symbol.id); - - // Verify that all render targets in this pass have the same dimensions - if (info.viewport_width != 0 && info.viewport_height != 0 && (target_info.width != info.viewport_width || target_info.height != info.viewport_height)) - return error(location, 4545, "cannot use multiple render targets with different texture dimensions (is " + std::to_string(target_info.width) + 'x' + std::to_string(target_info.height) + ", but expected " + std::to_string(info.viewport_width) + 'x' + std::to_string(info.viewport_height) + ')'), false; - - info.viewport_width = target_info.width; - info.viewport_height = target_info.height; - - const size_t target_index = state.size() > 12 ? (state[12] - '0') : 0; - - info.render_target_names[target_index] = target_info.unique_name; - } - } - else // Handle the rest of the pass states - { - backup(); - - expression expression; - - if (accept(tokenid::identifier)) // Handle special enumeration names for pass states - { - // Transform identifier to uppercase to do case-insensitive comparison - std::transform(_token.literal_as_string.begin(), _token.literal_as_string.end(), _token.literal_as_string.begin(), [](char c) { return static_cast(toupper(c)); }); - - static const std::pair s_enum_values[] = { - { "NONE", 0 }, { "ZERO", 0 }, { "ONE", 1 }, - { "SRCCOLOR", 2 }, { "SRCALPHA", 3 }, { "INVSRCCOLOR", 4 }, { "INVSRCALPHA", 5 }, { "DESTCOLOR", 8 }, { "DESTALPHA", 6 }, { "INVDESTCOLOR", 9 }, { "INVDESTALPHA", 7 }, - { "ADD", 1 }, { "SUBTRACT", 2 }, { "REVSUBTRACT", 3 }, { "MIN", 4 }, { "MAX", 5 }, - { "KEEP", 1 }, { "REPLACE", 3 }, { "INVERT", 6 }, { "INCR", 7 }, { "INCRSAT", 4 }, { "DECR", 8 }, { "DECRSAT", 5 }, - { "NEVER", 1 }, { "ALWAYS", 8 }, { "LESS", 2 }, { "GREATER", 5 }, { "LEQUAL", 4 }, { "LESSEQUAL", 4 }, { "GEQUAL", 7 }, { "GREATEREQUAL", 7 }, { "EQUAL", 3 }, { "NEQUAL", 6 }, { "NOTEQUAL", 6 }, - }; - - // Look up identifier in list of possible enumeration names - const auto it = std::find_if(std::begin(s_enum_values), std::end(s_enum_values), - [this](const auto &it) { return it.first == _token.literal_as_string; }); - - if (it != std::end(s_enum_values)) - expression.reset_to_rvalue_constant(_token.location, it->second); - else // No match found, so rewind to parser state before the identifier was consumed and try parsing it as a normal expression - restore(); - } - - // Parse right hand side as normal expression if no special enumeration name was matched already - if (!expression.is_constant && !parse_expression_multary(expression)) - return error(_token_next.location, 3000, "syntax error: unexpected '" + token::id_to_name(_token_next.id) + "', expected expression"), consume_until('}'), false; - else if (!expression.is_constant || !expression.type.is_scalar()) - return error(expression.location, 3011, "pass state value must be a literal scalar expression"), consume_until('}'), false; - - // All states below expect the value to be of an unsigned integer type - expression.add_cast_operation({ type::t_uint, 1, 1 }); - const unsigned int value = expression.constant.as_uint[0]; - - if (state == "SRGBWriteEnable") - info.srgb_write_enable = value != 0; - else if (state == "BlendEnable") - info.blend_enable = value != 0; - else if (state == "StencilEnable") - info.stencil_enable = value != 0; - else if (state == "ClearRenderTargets") - info.clear_render_targets = value != 0; - else if (state == "RenderTargetWriteMask" || state == "ColorWriteMask") - info.color_write_mask = value & 0xFF; - else if (state == "StencilReadMask" || state == "StencilMask") - info.stencil_read_mask = value & 0xFF; - else if (state == "StencilWriteMask") - info.stencil_write_mask = value & 0xFF; - else if (state == "BlendOp") - info.blend_op = value; - else if (state == "BlendOpAlpha") - info.blend_op_alpha = value; - else if (state == "SrcBlend") - info.src_blend = value; - else if (state == "SrcBlendAlpha") - info.src_blend_alpha = value; - else if (state == "DestBlend") - info.dest_blend = value; - else if (state == "DestBlendAlpha") - info.dest_blend_alpha = value; - else if (state == "StencilFunc") - info.stencil_comparison_func = value; - else if (state == "StencilRef") - info.stencil_reference_value = value; - else if (state == "StencilPass" || state == "StencilPassOp") - info.stencil_op_pass = value; - else if (state == "StencilFail" || state == "StencilFailOp") - info.stencil_op_fail = value; - else if (state == "StencilZFail" || state == "StencilDepthFail" || state == "StencilDepthFailOp") - info.stencil_op_depth_fail = value; - else - return error(location, 3004, "unrecognized pass state '" + state + '\''), consume_until('}'), false; - } - - if (!expect(';')) - return consume_until('}'), false; - } - - return expect('}'); -} diff --git a/msvc/source/effect_parser.hpp b/msvc/source/effect_parser.hpp deleted file mode 100644 index 51dbbb8..0000000 --- a/msvc/source/effect_parser.hpp +++ /dev/null @@ -1,89 +0,0 @@ -/** - * Copyright (C) 2014 Patrick Mours. All rights reserved. - * License: https://github.com/crosire/reshade#license - */ - -#pragma once - -#include "effect_lexer.hpp" -#include "effect_symbol_table.hpp" -#include - -namespace reshadefx -{ - /// - /// A parser for the ReShade FX shader language. - /// - class parser : symbol_table - { - public: - /// - /// Parse the provided input string. - /// - /// The string to analyze. - /// The code generation implementation to use. - /// A boolean value indicating whether parsing was successful or not. - bool parse(std::string source, class codegen *backend); - - /// - /// Get the list of error messages. - /// - std::string &errors() { return _errors; } - const std::string &errors() const { return _errors; } - - private: - void error(const location &location, unsigned int code, const std::string &message); - void warning(const location &location, unsigned int code, const std::string &message); - - void backup(); - void restore(); - - bool peek(char tok) const { return _token_next.id == static_cast(tok); } - bool peek(tokenid tokid) const { return _token_next.id == tokid; } - void consume(); - void consume_until(char tok) { return consume_until(static_cast(tok)); } - void consume_until(tokenid tokid); - bool accept(char tok) { return accept(static_cast(tok)); } - bool accept(tokenid tokid); - bool expect(char tok) { return expect(static_cast(tok)); } - bool expect(tokenid tokid); - - bool accept_type_class(type &type); - bool accept_type_qualifiers(type &type); - - bool parse_type(type &type); - - bool parse_array_size(type &type); - - bool accept_unary_op(); - bool accept_postfix_op(); - bool peek_multary_op(unsigned int &precedence) const; - bool accept_assignment_op(); - - bool parse_expression(expression &expression); - bool parse_expression_unary(expression &expression); - bool parse_expression_multary(expression &expression, unsigned int precedence = 0); - bool parse_expression_assignment(expression &expression); - - bool parse_annotations(std::unordered_map> &annotations); - - bool parse_statement(bool scoped); - bool parse_statement_block(bool scoped); - - bool parse_top(); - bool parse_struct(); - bool parse_function(type type, std::string name); - bool parse_variable(type type, std::string name, bool global = false); - bool parse_technique(); - bool parse_technique_pass(pass_info &info); - - std::string _errors; - token _token, _token_next, _token_backup; - std::unique_ptr _lexer, _lexer_backup; - codegen *_codegen = nullptr; - - std::vector _loop_break_target_stack; - std::vector _loop_continue_target_stack; - type _current_return_type; - }; -} diff --git a/msvc/source/effect_preprocessor.cpp b/msvc/source/effect_preprocessor.cpp deleted file mode 100644 index 606e10d..0000000 --- a/msvc/source/effect_preprocessor.cpp +++ /dev/null @@ -1,1068 +0,0 @@ -/** - * Copyright (C) 2014 Patrick Mours. All rights reserved. - * License: https://github.com/crosire/reshade#license - */ - -#include "effect_preprocessor.hpp" -#include - -enum macro_replacement -{ - macro_replacement_start = '\x00', - macro_replacement_argument = '\xFA', - macro_replacement_concat = '\xFF', - macro_replacement_stringize = '\xFE', - macro_replacement_space = '\xFD', - macro_replacement_break = '\xFC', - macro_replacement_expand = '\xFB', -}; - -static bool read_file(const std::filesystem::path &path, std::string &data) -{ - FILE *file = nullptr; - if (_wfopen_s(&file, path.c_str(), L"rb") != 0) - return false; - - // Read file contents into memory - std::vector mem(static_cast(std::filesystem::file_size(path) + 1)); - const size_t eof = fread(mem.data(), 1, mem.size() - 1, file); - - // Append a new line feed to the end of the input string to avoid issues with parsing - mem[eof] = '\n'; - - // No longer need to have a handle open to the file, since all data was read, so can safely close it - fclose(file); - - std::string_view filedata(mem.data(), mem.size()); - - // Remove BOM (0xefbbbf means 0xfeff) - if (filedata.size() >= 3 && - static_cast(filedata[0]) == 0xef && - static_cast(filedata[1]) == 0xbb && - static_cast(filedata[2]) == 0xbf) - filedata = std::string_view(filedata.data() + 3, filedata.size() - 3); - - data = filedata; - return true; -} - -void reshadefx::preprocessor::add_include_path(const std::filesystem::path &path) -{ - assert(!path.empty()); - - _include_paths.push_back(path); -} -bool reshadefx::preprocessor::add_macro_definition(const std::string &name, const macro ¯o) -{ - assert(!name.empty()); - - return _macros.emplace(name, macro).second; -} -bool reshadefx::preprocessor::add_macro_definition(const std::string &name, std::string value) -{ - macro macro; - macro.replacement_list = std::move(value); - - return add_macro_definition(name, macro); -} - -bool reshadefx::preprocessor::append_file(const std::filesystem::path &path) -{ - std::string data; - if (!read_file(path, data)) - return false; - - _success = true; - push(std::move(data), path.u8string()); - parse(); - - return _success; -} -bool reshadefx::preprocessor::append_string(const std::string &source_code) -{ - // Enforce all input strings to end with a line feed - assert(!source_code.empty() && source_code.back() == '\n'); - - _success = true; - push(source_code); - parse(); - - return _success; -} - -void reshadefx::preprocessor::error(const location &location, const std::string &message) -{ - _errors += location.source + '(' + std::to_string(location.line) + ", " + std::to_string(location.column) + ')' + ": preprocessor error: " + message + '\n'; - _success = false; -} -void reshadefx::preprocessor::warning(const location &location, const std::string &message) -{ - _errors += location.source + '(' + std::to_string(location.line) + ", " + std::to_string(location.column) + ')' + ": preprocessor warning: " + message + '\n'; -} - -reshadefx::lexer &reshadefx::preprocessor::current_lexer() -{ - assert(!_input_stack.empty()); - - return *_input_stack.top().lexer; -} -std::stack &reshadefx::preprocessor::current_if_stack() -{ - assert(!_input_stack.empty()); - - return _input_stack.top().if_stack; -} - -void reshadefx::preprocessor::push(std::string input, const std::string &name) -{ - input_level level = {}; - level.name = name; - level.lexer.reset(new lexer(std::move(input), true, false, false, false, true, false)); - level.parent = _input_stack.empty() ? nullptr : &_input_stack.top(); - level.next_token.id = tokenid::unknown; - - if (name.empty()) - { - if (level.parent != nullptr) - level.name = level.parent->name; - } - else - { - _output_location.source = name; - - _output += "#line 1 \"" + name + "\"\n"; - } - - _input_stack.push(std::move(level)); - - consume(); -} - -bool reshadefx::preprocessor::peek(tokenid token) const -{ - assert(!_input_stack.empty()); - - return _input_stack.top().next_token == token; -} -void reshadefx::preprocessor::consume() -{ - assert(!_input_stack.empty()); - - auto &input_level = _input_stack.top(); - const auto &input_string = input_level.lexer->input_string(); - - _token = std::move(input_level.next_token); - _token.location.source = _output_location.source; - _current_token_raw_data = input_string.substr(_token.offset, _token.length); - - // Get the next token - input_level.next_token = - input_level.lexer->lex(); - - // Pop input level if lexical analysis has reached the end of it - while (_input_stack.top().next_token == tokenid::end_of_file) - { - if (!current_if_stack().empty()) - error(current_if_stack().top().token.location, "unterminated #if"); - - _input_stack.pop(); - - if (_input_stack.empty()) - break; - - const auto &top = _input_stack.top(); - - if (top.name != _output_location.source) - { - _output_location.line = 1; - _output_location.source = top.name; - - _output += "#line 1 \"" + top.name + "\"\n"; - } - } -} -void reshadefx::preprocessor::consume_until(tokenid token) -{ - while (!accept(token) && !peek(tokenid::end_of_file)) - { - consume(); - } -} -bool reshadefx::preprocessor::accept(tokenid token) -{ - while (peek(tokenid::space)) - { - consume(); - } - - if (peek(token)) - { - consume(); - - return true; - } - - return false; -} -bool reshadefx::preprocessor::expect(tokenid token) -{ - if (!accept(token)) - { - assert(!_input_stack.empty()); - - auto actual_token = _input_stack.top().next_token; - actual_token.location.source = _output_location.source; - - error(actual_token.location, "syntax error: unexpected token '" + current_lexer().input_string().substr(actual_token.offset, actual_token.length) + "'"); - - return false; - } - - return true; -} - -void reshadefx::preprocessor::parse() -{ - std::string line; - - while (!_input_stack.empty()) - { - _recursion_count = 0; - - const bool skip = !current_if_stack().empty() && current_if_stack().top().skipping; - - consume(); - - switch (_token) - { - case tokenid::hash_if: - parse_if(); - if (!expect(tokenid::end_of_line)) - consume_until(tokenid::end_of_line); - continue; - case tokenid::hash_ifdef: - parse_ifdef(); - if (!expect(tokenid::end_of_line)) - consume_until(tokenid::end_of_line); - continue; - case tokenid::hash_ifndef: - parse_ifndef(); - if (!expect(tokenid::end_of_line)) - consume_until(tokenid::end_of_line); - continue; - case tokenid::hash_else: - parse_else(); - if (!expect(tokenid::end_of_line)) - consume_until(tokenid::end_of_line); - continue; - case tokenid::hash_elif: - parse_elif(); - if (!expect(tokenid::end_of_line)) - consume_until(tokenid::end_of_line); - continue; - case tokenid::hash_endif: - parse_endif(); - if (!expect(tokenid::end_of_line)) - consume_until(tokenid::end_of_line); - continue; - } - - if (skip) - continue; - - switch (_token) - { - case tokenid::hash_def: - parse_def(); - if (!expect(tokenid::end_of_line)) - consume_until(tokenid::end_of_line); - continue; - case tokenid::hash_undef: - parse_undef(); - if (!expect(tokenid::end_of_line)) - consume_until(tokenid::end_of_line); - continue; - case tokenid::hash_error: - parse_error(); - if (!expect(tokenid::end_of_line)) - consume_until(tokenid::end_of_line); - continue; - case tokenid::hash_warning: - parse_warning(); - if (!expect(tokenid::end_of_line)) - consume_until(tokenid::end_of_line); - continue; - case tokenid::hash_pragma: - parse_pragma(); - if (!expect(tokenid::end_of_line)) - consume_until(tokenid::end_of_line); - continue; - case tokenid::hash_include: - parse_include(); - continue; - case tokenid::hash_unknown: - error(_token.location, "unrecognized preprocessing directive '" + _token.literal_as_string + "'"); - consume_until(tokenid::end_of_line); - continue; - case tokenid::end_of_line: - if (line.empty()) - continue; - if (++_output_location.line != _token.location.line) - _output += "#line " + std::to_string(_output_location.line = _token.location.line) + '\n'; - _output += line; - _output += '\n'; - line.clear(); - continue; - case tokenid::identifier: - if (evaluate_identifier_as_macro()) - continue; - default: - line += _current_token_raw_data; - break; - } - } - - _output += line; -} - -void reshadefx::preprocessor::parse_def() -{ - if (!expect(tokenid::identifier)) - return; - else if (_token.literal_as_string == "defined") - return warning(_token.location, "macro name 'defined' is reserved"); - - macro m; - const auto location = std::move(_token.location); - const auto macro_name = std::move(_token.literal_as_string); - const auto macro_name_end_offset = _token.offset + _token.length; - - if (current_lexer().input_string()[macro_name_end_offset] == '(') - { - accept(tokenid::parenthesis_open); - - m.is_function_like = true; - - while (accept(tokenid::identifier)) - { - m.parameters.push_back(_token.literal_as_string); - - if (!accept(tokenid::comma)) - break; - } - - if (accept(tokenid::ellipsis)) - { - m.is_variadic = true; - m.parameters.push_back("__VA_ARGS__"); - - // TODO: Implement variadic macros - error(_token.location, "variadic macros are not currently supported"); - return; - } - - if (!expect(tokenid::parenthesis_close)) - return; - } - - create_macro_replacement_list(m); - - if (!add_macro_definition(macro_name, m)) - return error(location, "redefinition of '" + macro_name + "'"); -} -void reshadefx::preprocessor::parse_undef() -{ - if (!expect(tokenid::identifier)) - return; - else if (_token.literal_as_string == "defined") - return warning(_token.location, "macro name 'defined' is reserved"); - - _macros.erase(_token.literal_as_string); -} - -void reshadefx::preprocessor::parse_if() -{ - const bool condition_result = evaluate_expression(); - - if_level level; - level.token = _token; - level.value = condition_result; - level.parent = current_if_stack().empty() ? nullptr : ¤t_if_stack().top(); - level.skipping = (level.parent != nullptr && level.parent->skipping) || !level.value; - - current_if_stack().push(level); -} -void reshadefx::preprocessor::parse_ifdef() -{ - if_level level; - level.token = _token; - - if (!expect(tokenid::identifier)) - return; - - level.value = _macros.find(_token.literal_as_string) != _macros.end(); - level.parent = current_if_stack().empty() ? nullptr : ¤t_if_stack().top(); - level.skipping = (level.parent != nullptr && level.parent->skipping) || !level.value; - - current_if_stack().push(level); -} -void reshadefx::preprocessor::parse_ifndef() -{ - if_level level; - level.token = _token; - - if (!expect(tokenid::identifier)) - return; - - level.value = _macros.find(_token.literal_as_string) == _macros.end(); - level.parent = current_if_stack().empty() ? nullptr : ¤t_if_stack().top(); - level.skipping = (level.parent != nullptr && level.parent->skipping) || !level.value; - - current_if_stack().push(level); -} -void reshadefx::preprocessor::parse_elif() -{ - if (current_if_stack().empty()) - return error(_token.location, "missing #if for #elif"); - - if_level &level = current_if_stack().top(); - - if (level.token == tokenid::hash_else) - return error(_token.location, "#elif is not allowed after #else"); - - const bool condition_result = evaluate_expression(); - - level.token = _token; - level.skipping = (level.parent != nullptr && level.parent->skipping) || level.value || !condition_result; - - if (!level.value) level.value = condition_result; -} -void reshadefx::preprocessor::parse_else() -{ - if (current_if_stack().empty()) - return error(_token.location, "missing #if for #else"); - - if_level &level = current_if_stack().top(); - - if (level.token == tokenid::hash_else) - return error(_token.location, "#else is not allowed after #else"); - - level.token = _token; - level.skipping = (level.parent != nullptr && level.parent->skipping) || level.value; - - if (!level.value) level.value = true; -} -void reshadefx::preprocessor::parse_endif() -{ - if (current_if_stack().empty()) - return error(_token.location, "missing #if for #endif"); - - current_if_stack().pop(); -} - -void reshadefx::preprocessor::parse_error() -{ - const auto keyword_location = std::move(_token.location); - - if (!expect(tokenid::string_literal)) - return; - - error(keyword_location, _token.literal_as_string); -} -void reshadefx::preprocessor::parse_warning() -{ - const auto keyword_location = std::move(_token.location); - - if (!expect(tokenid::string_literal)) - return; - - warning(keyword_location, _token.literal_as_string); -} - -void reshadefx::preprocessor::parse_pragma() -{ - const auto keyword_location = std::move(_token.location); - - if (!expect(tokenid::identifier)) - return; - - std::string pragma = std::move(_token.literal_as_string); - - while (!peek(tokenid::end_of_line) && !peek(tokenid::end_of_file)) - { - consume(); - - if (_token == tokenid::identifier && evaluate_identifier_as_macro()) - continue; - - pragma += _current_token_raw_data; - } - - if (pragma == "once") - { - if (const auto it = _filecache.find(_output_location.source); it != _filecache.end()) - it->second.clear(); - return; - } - - warning(keyword_location, "unknown pragma ignored"); -} - -void reshadefx::preprocessor::parse_include() -{ - const auto keyword_location = std::move(_token.location); - - while (accept(tokenid::identifier)) - { - if (evaluate_identifier_as_macro()) - continue; - - error(_token.location, "syntax error: unexpected identifier in #include"); - consume_until(tokenid::end_of_line); - return; - } - - if (!expect(tokenid::string_literal)) - { - consume_until(tokenid::end_of_line); - return; - } - - const std::filesystem::path filename = std::filesystem::u8path(_token.literal_as_string); - - std::error_code ec; - std::filesystem::path filepath = std::filesystem::u8path(_output_location.source); - filepath.replace_filename(filename); - - if (!std::filesystem::exists(filepath, ec)) - for (const auto &include_path : _include_paths) - if (std::filesystem::exists(filepath = include_path / filename, ec)) - break; - - auto it = _filecache.find(filepath.u8string()); - - if (it == _filecache.end()) - { - std::string data; - if (!read_file(filepath, data)) - { - error(keyword_location, "could not open included file '" + filepath.u8string() + "'"); - consume_until(tokenid::end_of_line); - return; - } - - it = _filecache.emplace(filepath.u8string(), std::move(data)).first; - } - - push(it->second, filepath.u8string()); -} - -bool reshadefx::preprocessor::evaluate_expression() -{ - enum op_type - { - op_none = -1, - - op_or, - op_and, - op_bitor, - op_bitxor, - op_bitand, - op_not_equal, - op_equal, - op_less, - op_greater, - op_less_equal, - op_greater_equal, - op_leftshift, - op_rightshift, - op_add, - op_subtract, - op_modulo, - op_divide, - op_multiply, - op_plus, - op_negate, - op_not, - op_bitnot, - op_parentheses - }; - struct rpn_token - { - bool is_op; - int value; - }; - - int stack[128]; - rpn_token rpn[128]; - size_t stack_count = 0, rpn_count = 0; - tokenid previous_token = _token; - const int precedence[] = { 0, 1, 2, 3, 4, 5, 6, 7, 7, 7, 7, 8, 8, 9, 9, 10, 10, 10, 11, 11, 11, 11 }; - - // Run shunting-yard algorithm - while (!peek(tokenid::end_of_line)) - { - if (stack_count >= _countof(stack) || rpn_count >= _countof(rpn)) - { - error(_token.location, "expression evaluator ran out of stack space"); - return false; - } - int op = op_none; - bool is_left_associative = true; - - consume(); - - switch (_token) - { - case tokenid::space: - continue; - case tokenid::backslash: - // Skip to next line if the line ends with a backslash - if (accept(tokenid::end_of_line)) - continue; - else // Otherwise continue on processing the token (it is not valid here, but make that an error below) - break; - case tokenid::exclaim: - op = op_not; - is_left_associative = false; - break; - case tokenid::percent: - op = op_modulo; - break; - case tokenid::ampersand: - op = op_bitand; - break; - case tokenid::star: - op = op_multiply; - break; - case tokenid::plus: - is_left_associative = - previous_token == tokenid::int_literal || - previous_token == tokenid::uint_literal || - previous_token == tokenid::identifier || - previous_token == tokenid::parenthesis_close; - op = is_left_associative ? op_add : op_plus; - break; - case tokenid::minus: - is_left_associative = - previous_token == tokenid::int_literal || - previous_token == tokenid::uint_literal || - previous_token == tokenid::identifier || - previous_token == tokenid::parenthesis_close; - op = is_left_associative ? op_subtract : op_negate; - break; - case tokenid::slash: - op = op_divide; - break; - case tokenid::less: - op = op_less; - break; - case tokenid::greater: - op = op_greater; - break; - case tokenid::caret: - op = op_bitxor; - break; - case tokenid::pipe: - op = op_bitor; - break; - case tokenid::tilde: - op = op_bitnot; - is_left_associative = false; - break; - case tokenid::exclaim_equal: - op = op_not_equal; - break; - case tokenid::ampersand_ampersand: - op = op_and; - break; - case tokenid::less_less: - op = op_leftshift; - break; - case tokenid::less_equal: - op = op_less_equal; - break; - case tokenid::equal_equal: - op = op_equal; - break; - case tokenid::greater_greater: - op = op_rightshift; - break; - case tokenid::greater_equal: - op = op_greater_equal; - break; - case tokenid::pipe_pipe: - op = op_or; - break; - } - - switch (_token) - { - case tokenid::parenthesis_open: - stack[stack_count++] = op_parentheses; - break; - case tokenid::parenthesis_close: - { - bool matched = false; - - while (stack_count > 0) - { - const int op2 = stack[--stack_count]; - - if (op2 == op_parentheses) - { - matched = true; - break; - } - - rpn[rpn_count].is_op = true; - rpn[rpn_count++].value = op2; - } - - if (!matched) - { - error(_token.location, "unmatched ')'"); - return false; - } - break; - } - case tokenid::identifier: - { - if (evaluate_identifier_as_macro()) - { - continue; - } - else if (_token.literal_as_string == "exists") - { - const bool has_parentheses = accept(tokenid::parenthesis_open); - - while (accept(tokenid::identifier)) - { - if (!evaluate_identifier_as_macro()) - { - error(_token.location, "syntax error: unexpected identifier after 'exists'"); - return false; - } - } - - if (!expect(tokenid::string_literal)) - return false; - - const std::filesystem::path filename = std::filesystem::u8path(_token.literal_as_string); - - if (has_parentheses && !expect(tokenid::parenthesis_close)) - return false; - - std::error_code ec; - std::filesystem::path filepath = std::filesystem::u8path(_output_location.source); - filepath.replace_filename(filename); - - if (!std::filesystem::exists(filepath, ec)) - for (const auto &include_path : _include_paths) - if (std::filesystem::exists(filepath = include_path / filename, ec)) - break; - - rpn[rpn_count].is_op = false; - rpn[rpn_count++].value = std::filesystem::exists(filepath, ec); - continue; - } - else if (_token.literal_as_string == "defined") - { - const bool has_parentheses = accept(tokenid::parenthesis_open); - - if (!expect(tokenid::identifier)) - return false; - - const bool is_macro_defined = _macros.find(_token.literal_as_string) != _macros.end(); - - if (has_parentheses && !expect(tokenid::parenthesis_close)) - return false; - - rpn[rpn_count].is_op = false; - rpn[rpn_count++].value = is_macro_defined; - continue; - } - - // An identifier that cannot be replaced with a number becomes zero - rpn[rpn_count].is_op = false; - rpn[rpn_count++].value = 0; - break; - } - case tokenid::int_literal: - case tokenid::uint_literal: - { - rpn[rpn_count].is_op = false; - rpn[rpn_count++].value = _token.literal_as_int; - break; - } - default: - { - if (op == op_none) - { - error(_token.location, "invalid expression"); - return false; - } - - const int precedence1 = precedence[op]; - - while (stack_count > 0) - { - const int op2 = stack[stack_count - 1]; - - if (op2 == op_parentheses) - break; - - const int precedence2 = precedence[op2]; - - if ((is_left_associative && (precedence1 <= precedence2)) || (!is_left_associative && (precedence1 < precedence2))) - { - stack_count--; - rpn[rpn_count].is_op = true; - rpn[rpn_count++].value = op2; - } - else - { - break; - } - } - - stack[stack_count++] = op; - break; - } - } - - previous_token = _token; - } - - while (stack_count > 0) - { - const int op = stack[--stack_count]; - - if (op == op_parentheses) - { - error(_token.location, "unmatched ')'"); - return false; - } - - rpn[rpn_count].is_op = true; - rpn[rpn_count++].value = static_cast(op); - } - - // Evaluate reverse polish notation output - for (rpn_token *token = rpn; rpn_count-- != 0; token++) - { - if (token->is_op) - { -#define UNARY_OPERATION(op) { \ - if (stack_count < 1) \ - return error(_token.location, "invalid expression"), 0; \ - stack[stack_count - 1] = op stack[stack_count - 1]; \ - } -#define BINARY_OPERATION(op) { \ - if (stack_count < 2) \ - return error(_token.location, "invalid expression"), 0; \ - stack[stack_count - 2] = stack[stack_count - 2] op stack[stack_count - 1]; \ - stack_count--; \ - } - switch (token->value) - { - case op_or: BINARY_OPERATION(||); break; - case op_and: BINARY_OPERATION(&&); break; - case op_bitor: BINARY_OPERATION(|); break; - case op_bitxor: BINARY_OPERATION(^); break; - case op_bitand: BINARY_OPERATION(&); break; - case op_not_equal: BINARY_OPERATION(!=); break; - case op_equal: BINARY_OPERATION(==); break; - case op_less: BINARY_OPERATION(<); break; - case op_greater: BINARY_OPERATION(>); break; - case op_less_equal: BINARY_OPERATION(<=); break; - case op_greater_equal: BINARY_OPERATION(>=); break; - case op_leftshift: BINARY_OPERATION(<<); break; - case op_rightshift: BINARY_OPERATION(>>); break; - case op_add: BINARY_OPERATION(+); break; - case op_subtract: BINARY_OPERATION(-); break; - case op_modulo: BINARY_OPERATION(%); break; - case op_divide: BINARY_OPERATION(/); break; - case op_multiply: BINARY_OPERATION(*); break; - case op_plus: UNARY_OPERATION(+); break; - case op_negate: UNARY_OPERATION(-); break; - case op_not: UNARY_OPERATION(!); break; - case op_bitnot: UNARY_OPERATION(~); break; - } - } - else - { - stack[stack_count++] = token->value; - } - } - - if (stack_count != 1) - { - error(_token.location, "invalid expression"); - return false; - } - - return stack[0] != 0; -} -bool reshadefx::preprocessor::evaluate_identifier_as_macro() -{ - if (_recursion_count++ >= 256) - { - error(_token.location, "macro recursion too high"); - return false; - } - - const auto it = _macros.find(_token.literal_as_string); - - if (it == _macros.end()) - return false; - - const auto ¯o = it->second; - std::vector arguments; - - if (macro.is_function_like) - { - if (!accept(tokenid::parenthesis_open)) - return false; - - while (true) - { - int parentheses_level = 0; - std::string argument; - - while (true) - { - consume(); - - if (_token == tokenid::parenthesis_open) - parentheses_level++; - else if ( - (_token == tokenid::parenthesis_close && --parentheses_level < 0) || - (_token == tokenid::comma && parentheses_level == 0)) - break; - - argument += _current_token_raw_data; - } - - if (!argument.empty() && argument.back() == ' ') - argument.pop_back(); - if (!argument.empty() && argument.front() == ' ') - argument.erase(0, 1); - - arguments.push_back(argument); - - if (parentheses_level < 0) - break; - } - } - - std::string input; - expand_macro(it->second, arguments, input); - push(std::move(input)); - - return true; -} - -void reshadefx::preprocessor::expand_macro(const macro ¯o, const std::vector &arguments, std::string &out) -{ - for (auto it = macro.replacement_list.begin(); it != macro.replacement_list.end(); ++it) - { - if (*it != macro_replacement_start) - { - out += *it; - continue; - } - - switch (*++it) - { - case macro_replacement_concat: - continue; - case macro_replacement_stringize: - out += '"' + arguments.at(*++it) + '"'; - break; - case macro_replacement_argument: - push(arguments.at(*++it) + static_cast(macro_replacement_argument)); - while (!accept(tokenid::unknown)) - { - consume(); - if (_token == tokenid::identifier && evaluate_identifier_as_macro()) - continue; - out += _current_token_raw_data; - } - assert(_current_token_raw_data[0] == macro_replacement_argument); - break; - } - } -} -void reshadefx::preprocessor::create_macro_replacement_list(macro ¯o) -{ - // Since the number of parameters is encoded in the string, it may not exceed the available size of a char - if (macro.parameters.size() >= std::numeric_limits::max()) - return error(_token.location, "too many macro parameters"); - - while (!peek(tokenid::end_of_file) && !peek(tokenid::end_of_line)) - { - consume(); - - switch (_token) - { - case tokenid::hash: - { - if (accept(tokenid::hash)) - { - if (peek(tokenid::end_of_line)) - { - error(_token.location, "## cannot appear at end of macro text"); - return; - } - - // the ## token concatenation operator - macro.replacement_list += macro_replacement_start; - macro.replacement_list += macro_replacement_concat; - continue; - } - else if (macro.is_function_like) - { - if (!expect(tokenid::identifier)) - return; - - const auto it = std::find(macro.parameters.begin(), macro.parameters.end(), _token.literal_as_string); - - if (it == macro.parameters.end()) - return error(_token.location, "# must be followed by parameter name"); - - // the # stringize operator - macro.replacement_list += macro_replacement_start; - macro.replacement_list += macro_replacement_stringize; - macro.replacement_list += static_cast(std::distance(macro.parameters.begin(), it)); - continue; - } - break; - } - case tokenid::backslash: - { - if (peek(tokenid::end_of_line)) - { - consume(); - continue; - } - break; - } - case tokenid::identifier: - { - const auto it = std::find(macro.parameters.begin(), macro.parameters.end(), _token.literal_as_string); - - if (it != macro.parameters.end()) - { - macro.replacement_list += macro_replacement_start; - macro.replacement_list += macro_replacement_argument; - macro.replacement_list += static_cast(std::distance(macro.parameters.begin(), it)); - continue; - } - break; - } - } - - macro.replacement_list += _current_token_raw_data; - } -} diff --git a/msvc/source/effect_preprocessor.hpp b/msvc/source/effect_preprocessor.hpp deleted file mode 100644 index 0a9c8f7..0000000 --- a/msvc/source/effect_preprocessor.hpp +++ /dev/null @@ -1,135 +0,0 @@ -/** - * Copyright (C) 2014 Patrick Mours. All rights reserved. - * License: https://github.com/crosire/reshade#license - */ - -#pragma once - -#include -#include -#include -#include -#include -#include "effect_lexer.hpp" - -namespace reshadefx -{ - /// - /// A C-style preprocessor implementation. - /// - class preprocessor - { - public: - struct macro - { - std::string replacement_list; - bool is_function_like = false, is_variadic = false; - std::vector parameters; - }; - - /// - /// Add an include directory to the list of search paths used when resolving #include directives. - /// - /// The path to the directory to add. - void add_include_path(const std::filesystem::path &path); - - /// - /// Add a new macro definition. This is equal to appending '#define name macro' to this preprocessor instance. - /// - /// The name of the macro to define. - /// The definition of the macro function or value. - /// - bool add_macro_definition(const std::string &name, const macro ¯o); - /// - /// Add a new macro value definition. This is equal to appending '#define name macro' to this preprocessor instance. - /// - /// The name of the macro to define. - /// The value to define that macro to. - /// - bool add_macro_definition(const std::string &name, std::string value = "1"); - - /// - /// Open the specified file, parse its contents and append them to the output. - /// - /// The path to the file to parse. - /// A boolean value indicating whether parsing was successful or not. - bool append_file(const std::filesystem::path &path); - /// - /// Parse the specified string and append it to the output. - /// - /// The string to parse. - /// A boolean value indicating whether parsing was successful or not. - bool append_string(const std::string &source_code); - - /// - /// Get the list of error messages. - /// - std::string &errors() { return _errors; } - const std::string &errors() const { return _errors; } - /// - /// Get the current pre-processed output string. - /// - std::string &output() { return _output; } - const std::string &output() const { return _output; } - - private: - struct if_level - { - token token; - bool value, skipping; - if_level *parent; - }; - struct input_level - { - std::string name; - std::unique_ptr lexer; - token next_token; - std::stack if_stack; - input_level *parent; - }; - - void error(const location &location, const std::string &message); - void warning(const location &location, const std::string &message); - - lexer ¤t_lexer(); - std::stack ¤t_if_stack(); - - void push(std::string input, const std::string &name = std::string()); - - bool peek(tokenid token) const; - void consume(); - void consume_until(tokenid token); - bool accept(tokenid token); - bool expect(tokenid token); - - void parse(); - void parse_def(); - void parse_undef(); - void parse_if(); - void parse_ifdef(); - void parse_ifndef(); - void parse_elif(); - void parse_else(); - void parse_endif(); - void parse_error(); - void parse_warning(); - void parse_pragma(); - void parse_include(); - - bool evaluate_expression(); - bool evaluate_identifier_as_macro(); - - void expand_macro(const macro ¯o, const std::vector &arguments, std::string &out); - void create_macro_replacement_list(macro ¯o); - - bool _success = true; - token _token; - std::stack _input_stack; - location _output_location; - std::string _output, _errors, _current_token_raw_data; - int _recursion_count = 0; - std::unordered_map _macros; - std::vector _include_paths; - std::unordered_map _filecache; - }; -} diff --git a/msvc/source/effect_symbol_table.cpp b/msvc/source/effect_symbol_table.cpp deleted file mode 100644 index 7520e48..0000000 --- a/msvc/source/effect_symbol_table.cpp +++ /dev/null @@ -1,399 +0,0 @@ -/** - * Copyright (C) 2014 Patrick Mours. All rights reserved. - * License: https://github.com/crosire/reshade#license - */ - -#include "effect_parser.hpp" -#include "effect_symbol_table.hpp" -#include -#include -#include - -#pragma region Import intrinsic functions - -using namespace reshadefx; - -struct intrinsic -{ - intrinsic(const char *name, unsigned int id, const type &ret_type, std::initializer_list arg_types) : id(id) - { - function.name = name; - function.return_type = ret_type; - function.parameter_list.reserve(arg_types.size()); - for (const type &arg_type : arg_types) - function.parameter_list.push_back({ arg_type }); - } - - unsigned int id; - function_info function; -}; - -// Import intrinsic callback functions -#define IMPLEMENT_INTRINSIC_SPIRV(name, i, code) name##i, -enum { -#include "effect_symbol_table_intrinsics.inl" -}; - -#define void { reshadefx::type::t_void } -#define bool { reshadefx::type::t_bool, 1, 1 } -#define bool2 { reshadefx::type::t_bool, 2, 1 } -#define bool3 { reshadefx::type::t_bool, 3, 1 } -#define bool4 { reshadefx::type::t_bool, 4, 1 } -#define int { reshadefx::type::t_int, 1, 1 } -#define int2 { reshadefx::type::t_int, 2, 1 } -#define int3 { reshadefx::type::t_int, 3, 1 } -#define int4 { reshadefx::type::t_int, 4, 1 } -#define uint { reshadefx::type::t_uint, 1, 1 } -#define uint2 { reshadefx::type::t_uint, 2, 1 } -#define uint3 { reshadefx::type::t_uint, 3, 1 } -#define uint4 { reshadefx::type::t_uint, 4, 1 } -#define float { reshadefx::type::t_float, 1, 1 } -#define float2 { reshadefx::type::t_float, 2, 1 } -#define float3 { reshadefx::type::t_float, 3, 1 } -#define float4 { reshadefx::type::t_float, 4, 1 } -#define float2x2 { reshadefx::type::t_float, 2, 2 } -#define float3x3 { reshadefx::type::t_float, 3, 3 } -#define float4x4 { reshadefx::type::t_float, 4, 4 } -#define out_float { reshadefx::type::t_float, 1, 1, reshadefx::type::q_out } -#define out_float2 { reshadefx::type::t_float, 2, 1, reshadefx::type::q_out } -#define out_float3 { reshadefx::type::t_float, 3, 1, reshadefx::type::q_out } -#define out_float4 { reshadefx::type::t_float, 4, 1, reshadefx::type::q_out } -#define sampler { reshadefx::type::t_sampler } - -// Import intrinsic function definitions -#define DEFINE_INTRINSIC(name, i, ret_type, ...) intrinsic(#name, name##i, ret_type, { __VA_ARGS__ }), -static const intrinsic s_intrinsics[] = { -#include "effect_symbol_table_intrinsics.inl" -}; - -#undef void -#undef bool -#undef bool2 -#undef bool3 -#undef bool4 -#undef int -#undef int2 -#undef int3 -#undef int4 -#undef uint -#undef uint2 -#undef uint3 -#undef uint4 -#undef float1 -#undef float2 -#undef float3 -#undef float4 -#undef float2x2 -#undef float3x3 -#undef float4x4 -#undef out_float -#undef out_float2 -#undef out_float3 -#undef out_float4 -#undef sampler - -#pragma endregion - -unsigned int reshadefx::type::rank(const type &src, const type &dst) -{ - if (src.is_array() != dst.is_array() || (src.array_length != dst.array_length && src.array_length > 0 && dst.array_length > 0)) - return 0; // Arrays of different sizes are not compatible - if (src.is_struct() || dst.is_struct()) - return src.definition == dst.definition ? 32 : 0; // Structs are only compatible if they are the same type - if (!src.is_numeric() || !dst.is_numeric()) - return src.base == dst.base ? 32 : 0; // Numeric values are not compatible with other types - - // This table is based on the following rules: - // - Floating point has a higher rank than integer types - // - Integer to floating point promotion has a higher rank than floating point to integer conversion - // - Signed to unsigned integer conversion has a higher rank than unsigned to signed integer conversion - static const int ranks[4][4] = { - { 5, 4, 4, 4 }, - { 3, 5, 2, 4 }, - { 3, 1, 5, 4 }, - { 3, 3, 3, 6 } - }; - - assert(src.base > 0 && src.base <= 4); - assert(dst.base > 0 && dst.base <= 4); - - const int rank = ranks[src.base - 1][dst.base - 1] << 2; - - if (src.is_scalar() && dst.is_vector()) - return rank >> 1; // Scalar to vector promotion has a lower rank - if (src.is_vector() && dst.is_scalar() || (src.is_vector() == dst.is_vector() && src.rows > dst.rows && src.cols >= dst.cols)) - return rank >> 2; // Vector to scalar conversion has an even lower rank - if (src.is_vector() != dst.is_vector() || src.is_matrix() != dst.is_matrix() || src.components() != dst.components()) - return 0; // If components weren't converted at this point, the types are not compatible - - return rank * src.components(); // More components causes a higher rank -} - -reshadefx::symbol_table::symbol_table() -{ - _current_scope.name = "::"; - _current_scope.level = 0; - _current_scope.namespace_level = 0; -} - -void reshadefx::symbol_table::enter_scope() -{ - _current_scope.level++; -} -void reshadefx::symbol_table::enter_namespace(const std::string &name) -{ - _current_scope.name += name + "::"; - _current_scope.level++; - _current_scope.namespace_level++; -} -void reshadefx::symbol_table::leave_scope() -{ - assert(_current_scope.level > 0); - - for (auto &symbol : _symbol_stack) - { - std::vector &scope_list = symbol.second; - - for (auto scope_it = scope_list.begin(); scope_it != scope_list.end();) - { - if (scope_it->scope.level > scope_it->scope.namespace_level && - scope_it->scope.level >= _current_scope.level) - { - scope_it = scope_list.erase(scope_it); - } - else - { - ++scope_it; - } - } - } - - _current_scope.level--; -} -void reshadefx::symbol_table::leave_namespace() -{ - assert(_current_scope.level > 0); - assert(_current_scope.namespace_level > 0); - - _current_scope.name.erase(_current_scope.name.substr(0, _current_scope.name.size() - 2).rfind("::") + 2); - _current_scope.level--; - _current_scope.namespace_level--; -} - -bool reshadefx::symbol_table::insert_symbol(const std::string &name, const symbol &symbol, bool global) -{ - assert(symbol.id != 0 || symbol.op == symbol_type::constant); - - // Make sure the symbol does not exist yet - if (symbol.op != symbol_type::function && find_symbol(name, _current_scope, true).id != 0) - return false; - - // Insertion routine which keeps the symbol stack sorted by namespace level - const auto insert_sorted = [](auto &vec, const auto &item) { - return vec.insert( - std::upper_bound(vec.begin(), vec.end(), item, - [](auto lhs, auto rhs) { - return lhs.scope.namespace_level < rhs.scope.namespace_level; - }), item); - }; - - // Global symbols are accessible from every scope - if (global) - { - scope scope = { "", 0, 0 }; - - // Walk scope chain from global scope back to current one - for (size_t pos = 0; pos != std::string::npos; pos = _current_scope.name.find("::", pos)) - { - // Extract scope name - scope.name = _current_scope.name.substr(0, pos += 2); - const auto previous_scope_name = _current_scope.name.substr(pos); - - // Insert symbol into this scope - insert_sorted(_symbol_stack[previous_scope_name + name], scoped_symbol { symbol, scope }); - - // Continue walking up the scope chain - scope.level = ++scope.namespace_level; - } - } - else - { - // This is a local symbol so it's sufficient to update the symbol stack with just the current scope - insert_sorted(_symbol_stack[name], scoped_symbol { symbol, _current_scope }); - } - - return true; -} - -reshadefx::symbol reshadefx::symbol_table::find_symbol(const std::string &name) const -{ - // Default to start search with current scope and walk back the scope chain - return find_symbol(name, _current_scope, false); -} -reshadefx::symbol reshadefx::symbol_table::find_symbol(const std::string &name, const scope &scope, bool exclusive) const -{ - const auto stack_it = _symbol_stack.find(name); - - // Check if symbol does exist - if (stack_it == _symbol_stack.end() || stack_it->second.empty()) - return {}; - - // Walk up the scope chain starting at the requested scope level and find a matching symbol - symbol result = {}; - - for (auto it = stack_it->second.rbegin(), end = stack_it->second.rend(); it != end; ++it) - { - if (it->scope.level > scope.level || - it->scope.namespace_level > scope.namespace_level || (it->scope.namespace_level == scope.namespace_level && it->scope.name != scope.name)) - continue; - if (exclusive && it->scope.level < scope.level) - continue; - - if (it->op == symbol_type::constant || it->op == symbol_type::variable || it->op == symbol_type::structure) - return *it; // Variables and structures have the highest priority and are always picked immediately - else if (result.id == 0) - result = *it; // Function names have a lower priority, so continue searching in case a variable with the same name exists - } - - return result; -} - -static int compare_functions(const std::vector &arguments, const reshadefx::function_info *function1, const reshadefx::function_info *function2) -{ - const size_t num_arguments = arguments.size(); - - // Check if the first function matches the argument types - bool function1_viable = true; - const auto function1_ranks = static_cast(alloca(num_arguments * sizeof(unsigned int))); - for (size_t i = 0; i < num_arguments; ++i) - if ((function1_ranks[i] = reshadefx::type::rank(arguments[i].type, function1->parameter_list[i].type)) == 0) - { - function1_viable = false; - break; - } - - // Catch case where the second function does not exist - if (function2 == nullptr) - return function1_viable ? -1 : 1; // If the first function is not viable, this compare fails - - // Check if the second function matches the argument types - bool function2_viable = true; - const auto function2_ranks = static_cast(alloca(num_arguments * sizeof(unsigned int))); - for (size_t i = 0; i < num_arguments; ++i) - if ((function2_ranks[i] = reshadefx::type::rank(arguments[i].type, function2->parameter_list[i].type)) == 0) - { - function2_viable = false; - break; - } - - // If one of the functions is not viable, then the other one automatically wins - if (!function1_viable || !function2_viable) - return function2_viable - function1_viable; - - // Both functions are possible, so find the one with the higher ranking - std::sort(function1_ranks, function1_ranks + num_arguments, std::greater()); - std::sort(function2_ranks, function2_ranks + num_arguments, std::greater()); - - for (size_t i = 0; i < num_arguments; ++i) - if (function1_ranks[i] > function2_ranks[i]) - return -1; // Left function wins - else if (function2_ranks[i] > function1_ranks[i]) - return +1; // Right function wins - - return 0; // Both functions are equally viable -} - -bool reshadefx::symbol_table::resolve_function_call(const std::string &name, const std::vector &arguments, const scope &scope, symbol &out_data, bool &is_ambiguous) const -{ - out_data.op = symbol_type::function; - - const function_info *result = nullptr; - unsigned int num_overloads = 0; - unsigned int overload_namespace = scope.namespace_level; - - // Look up function name in the symbol stack and loop through the associated symbols - const auto stack_it = _symbol_stack.find(name); - - if (stack_it != _symbol_stack.end() && !stack_it->second.empty()) - { - for (auto it = stack_it->second.rbegin(), end = stack_it->second.rend(); it != end; ++it) - { - if (it->scope.level > scope.level || - it->scope.namespace_level > scope.namespace_level || - it->op != symbol_type::function) - continue; - - const function_info *const function = it->function; - - if (function == nullptr) - continue; - - if (function->parameter_list.empty()) - { - if (arguments.empty()) - { - out_data.id = it->id; - out_data.type = function->return_type; - out_data.function = result = function; - num_overloads = 1; - break; - } - else - { - continue; - } - } - else if (arguments.size() != function->parameter_list.size()) - { - continue; - } - - // A new possibly-matching function was found, compare it against the current result - const int comparison = compare_functions(arguments, function, result); - - if (comparison < 0) // The new function is a better match - { - out_data.id = it->id; - out_data.type = function->return_type; - out_data.function = result = function; - num_overloads = 1; - overload_namespace = it->scope.namespace_level; - } - else if (comparison == 0 && overload_namespace == it->scope.namespace_level) // Both functions are equally viable, so the call is ambiguous - { - ++num_overloads; - } - } - } - - // Try matching against intrinsic functions if no matching user-defined function was found up to this point - if (num_overloads == 0) - { - for (const intrinsic &intrinsic : s_intrinsics) - { - if (intrinsic.function.name != name || intrinsic.function.parameter_list.size() != arguments.size()) - continue; - - // A new possibly-matching intrinsic function was found, compare it against the current result - const int comparison = compare_functions(arguments, &intrinsic.function, result); - - if (comparison < 0) // The new function is a better match - { - out_data.op = symbol_type::intrinsic; - out_data.id = intrinsic.id; - out_data.type = intrinsic.function.return_type; - out_data.function = &intrinsic.function; - result = out_data.function; - num_overloads = 1; - } - else if (comparison == 0 && overload_namespace == 0) // Both functions are equally viable, so the call is ambiguous (intrinsics are always in the global namespace) - { - ++num_overloads; - } - } - } - - is_ambiguous = num_overloads > 1; - - return num_overloads == 1; -} diff --git a/msvc/source/effect_symbol_table.hpp b/msvc/source/effect_symbol_table.hpp deleted file mode 100644 index 9952d77..0000000 --- a/msvc/source/effect_symbol_table.hpp +++ /dev/null @@ -1,102 +0,0 @@ -/** - * Copyright (C) 2014 Patrick Mours. All rights reserved. - * License: https://github.com/crosire/reshade#license - */ - -#pragma once - -#include "effect_expression.hpp" - -namespace reshadefx -{ - /// - /// A scope encapsulating symbols - /// - struct scope - { - std::string name; - unsigned int level, namespace_level; - }; - - /// - /// Enumeration of all possible symbol types - /// - enum class symbol_type - { - invalid, - variable, - constant, - function, - intrinsic, - structure, - }; - - /// - /// A single symbol in the symbol table - /// - struct symbol - { - symbol_type op = symbol_type::invalid; - uint32_t id = 0; - type type = {}; - constant constant = {}; - const function_info *function = nullptr; - }; - - /// - /// A symbol table managing a list of scopes and symbols - /// - class symbol_table - { - public: - symbol_table(); - - /// - /// Enter a new scope as child of the current one. - /// - void enter_scope(); - /// - /// Enter a new namespace as child of the current one. - /// - void enter_namespace(const std::string &name); - /// - /// Leave the current scope and enter the parent one. - /// - void leave_scope(); - /// - /// Leave the current namespace and enter the parent one. - /// - void leave_namespace(); - - /// - /// Get the current scope the symbol table operates in. - /// - /// - const scope ¤t_scope() const { return _current_scope; } - - /// - /// Insert an new symbol in the symbol table. Returns false if a symbol by that name and type already exists. - /// - bool insert_symbol(const std::string &name, const symbol &symbol, bool global = false); - - /// - /// Look for an existing symbol with the specified . - /// - symbol find_symbol(const std::string &name) const; - symbol find_symbol(const std::string &name, const scope &scope, bool exclusive) const; - - /// - /// Search for the best function or intrinsic overload matching the argument list. - /// - bool resolve_function_call(const std::string &name, const std::vector &args, const scope &scope, symbol &data, bool &ambiguous) const; - - private: - struct scoped_symbol : symbol { - scope scope; // Store scope with symbol data - }; - - scope _current_scope; - std::unordered_map> _symbol_stack; - }; -} diff --git a/msvc/source/effect_symbol_table_intrinsics.inl b/msvc/source/effect_symbol_table_intrinsics.inl deleted file mode 100644 index 143f069..0000000 --- a/msvc/source/effect_symbol_table_intrinsics.inl +++ /dev/null @@ -1,1705 +0,0 @@ -/** - * Copyright (C) 2014 Patrick Mours. All rights reserved. - * License: https://github.com/crosire/reshade#license - */ - -#ifdef __INTELLISENSE__ -#undef DEFINE_INTRINSIC -#undef IMPLEMENT_INTRINSIC_GLSL -#undef IMPLEMENT_INTRINSIC_HLSL -#undef IMPLEMENT_INTRINSIC_SPIRV -#endif - -#ifndef DEFINE_INTRINSIC -#define DEFINE_INTRINSIC(name, i, ret_type, ...) -#endif -#ifndef IMPLEMENT_INTRINSIC_GLSL -#define IMPLEMENT_INTRINSIC_GLSL(name, i, code) -#endif -#ifndef IMPLEMENT_INTRINSIC_HLSL -#define IMPLEMENT_INTRINSIC_HLSL(name, i, code) -#endif -#ifndef IMPLEMENT_INTRINSIC_SPIRV -#define IMPLEMENT_INTRINSIC_SPIRV(name, i, code) -#endif - -// ret abs(x) -DEFINE_INTRINSIC(abs, 0, int, int) -DEFINE_INTRINSIC(abs, 0, int2, int2) -DEFINE_INTRINSIC(abs, 0, int3, int3) -DEFINE_INTRINSIC(abs, 0, int4, int4) -DEFINE_INTRINSIC(abs, 1, float, float) -DEFINE_INTRINSIC(abs, 1, float2, float2) -DEFINE_INTRINSIC(abs, 1, float3, float3) -DEFINE_INTRINSIC(abs, 1, float4, float4) -IMPLEMENT_INTRINSIC_GLSL(abs, 0, { - code += "abs(" + id_to_name(args[0].base) + ')'; - }) -IMPLEMENT_INTRINSIC_GLSL(abs, 1, { - code += "abs(" + id_to_name(args[0].base) + ')'; - }) -IMPLEMENT_INTRINSIC_HLSL(abs, 0, { - code += "abs(" + id_to_name(args[0].base) + ')'; - }) -IMPLEMENT_INTRINSIC_HLSL(abs, 1, { - code += "abs(" + id_to_name(args[0].base) + ')'; - }) -IMPLEMENT_INTRINSIC_SPIRV(abs, 0, { - return add_instruction(spv::OpExtInst, convert_type(res_type)) - .add(_glsl_ext) - .add(spv::GLSLstd450SAbs) - .add(args[0].base) - .result; - }) -IMPLEMENT_INTRINSIC_SPIRV(abs, 1, { - return add_instruction(spv::OpExtInst, convert_type(res_type)) - .add(_glsl_ext) - .add(spv::GLSLstd450FAbs) - .add(args[0].base) - .result; - }) - -// ret all(x) -DEFINE_INTRINSIC(all, 0, bool, bool) -DEFINE_INTRINSIC(all, 1, bool, bool2) -DEFINE_INTRINSIC(all, 1, bool, bool3) -DEFINE_INTRINSIC(all, 1, bool, bool4) -IMPLEMENT_INTRINSIC_GLSL(all, 0, { - code += id_to_name(args[0].base); - }) -IMPLEMENT_INTRINSIC_GLSL(all, 1, { - code += "all(" + id_to_name(args[0].base) + ')'; - }) -IMPLEMENT_INTRINSIC_HLSL(all, 0, { - code += id_to_name(args[0].base); - }) -IMPLEMENT_INTRINSIC_HLSL(all, 1, { - code += "all(" + id_to_name(args[0].base) + ')'; - }) -IMPLEMENT_INTRINSIC_SPIRV(all, 0, { - return args[0].base; - }) -IMPLEMENT_INTRINSIC_SPIRV(all, 1, { - return add_instruction(spv::OpAll, convert_type(res_type)) - .add(args[0].base) - .result; - }) - -// ret any(x) -DEFINE_INTRINSIC(any, 0, bool, bool) -DEFINE_INTRINSIC(any, 1, bool, bool2) -DEFINE_INTRINSIC(any, 1, bool, bool3) -DEFINE_INTRINSIC(any, 1, bool, bool4) -IMPLEMENT_INTRINSIC_GLSL(any, 0, { - code += id_to_name(args[0].base); - }) -IMPLEMENT_INTRINSIC_GLSL(any, 1, { - code += "any(" + id_to_name(args[0].base) + ')'; - }) -IMPLEMENT_INTRINSIC_HLSL(any, 0, { - code += id_to_name(args[0].base); - }) -IMPLEMENT_INTRINSIC_HLSL(any, 1, { - code += "any(" + id_to_name(args[0].base) + ')'; - }) -IMPLEMENT_INTRINSIC_SPIRV(any, 0, { - return args[0].base; - }) -IMPLEMENT_INTRINSIC_SPIRV(any, 1, { - return add_instruction(spv::OpAny, convert_type(res_type)) - .add(args[0].base) - .result; - }) - -// ret asin(x) -DEFINE_INTRINSIC(asin, 0, float, float) -DEFINE_INTRINSIC(asin, 0, float2, float2) -DEFINE_INTRINSIC(asin, 0, float3, float3) -DEFINE_INTRINSIC(asin, 0, float4, float4) -IMPLEMENT_INTRINSIC_GLSL(asin, 0, { - code += "asin(" + id_to_name(args[0].base) + ')'; - }) -IMPLEMENT_INTRINSIC_HLSL(asin, 0, { - code += "asin(" + id_to_name(args[0].base) + ')'; - }) -IMPLEMENT_INTRINSIC_SPIRV(asin, 0, { - return add_instruction(spv::OpExtInst, convert_type(res_type)) - .add(_glsl_ext) - .add(spv::GLSLstd450Asin) - .add(args[0].base) - .result; - }) - -// ret acos(x) -DEFINE_INTRINSIC(acos, 0, float, float) -DEFINE_INTRINSIC(acos, 0, float2, float2) -DEFINE_INTRINSIC(acos, 0, float3, float3) -DEFINE_INTRINSIC(acos, 0, float4, float4) -IMPLEMENT_INTRINSIC_GLSL(acos, 0, { - code += "acos(" + id_to_name(args[0].base) + ')'; - }) -IMPLEMENT_INTRINSIC_HLSL(acos, 0, { - code += "acos(" + id_to_name(args[0].base) + ')'; - }) -IMPLEMENT_INTRINSIC_SPIRV(acos, 0, { - return add_instruction(spv::OpExtInst, convert_type(res_type)) - .add(_glsl_ext) - .add(spv::GLSLstd450Acos) - .add(args[0].base) - .result; - }) - -// ret atan(x) -DEFINE_INTRINSIC(atan, 0, float, float) -DEFINE_INTRINSIC(atan, 0, float2, float2) -DEFINE_INTRINSIC(atan, 0, float3, float3) -DEFINE_INTRINSIC(atan, 0, float4, float4) -IMPLEMENT_INTRINSIC_GLSL(atan, 0, { - code += "atan(" + id_to_name(args[0].base) + ')'; - }) -IMPLEMENT_INTRINSIC_HLSL(atan, 0, { - code += "atan(" + id_to_name(args[0].base) + ')'; - }) -IMPLEMENT_INTRINSIC_SPIRV(atan, 0, { - return add_instruction(spv::OpExtInst, convert_type(res_type)) - .add(_glsl_ext) - .add(spv::GLSLstd450Atan) - .add(args[0].base) - .result; - }) - -// ret atan2(x, y) -DEFINE_INTRINSIC(atan2, 0, float, float, float) -DEFINE_INTRINSIC(atan2, 0, float2, float2, float2) -DEFINE_INTRINSIC(atan2, 0, float3, float3, float3) -DEFINE_INTRINSIC(atan2, 0, float4, float4, float4) -IMPLEMENT_INTRINSIC_GLSL(atan2, 0, { - code += "atan(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ')'; - }) -IMPLEMENT_INTRINSIC_HLSL(atan2, 0, { - code += "atan2(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ')'; - }) -IMPLEMENT_INTRINSIC_SPIRV(atan2, 0, { - return add_instruction(spv::OpExtInst, convert_type(res_type)) - .add(_glsl_ext) - .add(spv::GLSLstd450Atan2) - .add(args[0].base) - .add(args[1].base) - .result; - }) - -// ret sin(x) -DEFINE_INTRINSIC(sin, 0, float, float) -DEFINE_INTRINSIC(sin, 0, float2, float2) -DEFINE_INTRINSIC(sin, 0, float3, float3) -DEFINE_INTRINSIC(sin, 0, float4, float4) -IMPLEMENT_INTRINSIC_GLSL(sin, 0, { - code += "sin(" + id_to_name(args[0].base) + ')'; - }) -IMPLEMENT_INTRINSIC_HLSL(sin, 0, { - code += "sin(" + id_to_name(args[0].base) + ')'; - }) -IMPLEMENT_INTRINSIC_SPIRV(sin, 0, { - return add_instruction(spv::OpExtInst, convert_type(res_type)) - .add(_glsl_ext) - .add(spv::GLSLstd450Sin) - .add(args[0].base) - .result; - }) - -// ret sinh(x) -DEFINE_INTRINSIC(sinh, 0, float, float) -DEFINE_INTRINSIC(sinh, 0, float2, float2) -DEFINE_INTRINSIC(sinh, 0, float3, float3) -DEFINE_INTRINSIC(sinh, 0, float4, float4) -IMPLEMENT_INTRINSIC_GLSL(sinh, 0, { - code += "sinh(" + id_to_name(args[0].base) + ')'; - }) -IMPLEMENT_INTRINSIC_HLSL(sinh, 0, { - code += "sinh(" + id_to_name(args[0].base) + ')'; - }) -IMPLEMENT_INTRINSIC_SPIRV(sinh, 0, { - return add_instruction(spv::OpExtInst, convert_type(res_type)) - .add(_glsl_ext) - .add(spv::GLSLstd450Sinh) - .add(args[0].base) - .result; - }) - -// ret cos(x) -DEFINE_INTRINSIC(cos, 0, float, float) -DEFINE_INTRINSIC(cos, 0, float2, float2) -DEFINE_INTRINSIC(cos, 0, float3, float3) -DEFINE_INTRINSIC(cos, 0, float4, float4) -IMPLEMENT_INTRINSIC_GLSL(cos, 0, { - code += "cos(" + id_to_name(args[0].base) + ')'; - }) -IMPLEMENT_INTRINSIC_HLSL(cos, 0, { - code += "cos(" + id_to_name(args[0].base) + ')'; - }) -IMPLEMENT_INTRINSIC_SPIRV(cos, 0, { - return add_instruction(spv::OpExtInst, convert_type(res_type)) - .add(_glsl_ext) - .add(spv::GLSLstd450Cos) - .add(args[0].base) - .result; - }) - -// ret cosh(x) -DEFINE_INTRINSIC(cosh, 0, float, float) -DEFINE_INTRINSIC(cosh, 0, float2, float2) -DEFINE_INTRINSIC(cosh, 0, float3, float3) -DEFINE_INTRINSIC(cosh, 0, float4, float4) -IMPLEMENT_INTRINSIC_GLSL(cosh, 0, { - code += "cosh(" + id_to_name(args[0].base) + ')'; - }) -IMPLEMENT_INTRINSIC_HLSL(cosh, 0, { - code += "cosh(" + id_to_name(args[0].base) + ')'; - }) -IMPLEMENT_INTRINSIC_SPIRV(cosh, 0, { - return add_instruction(spv::OpExtInst, convert_type(res_type)) - .add(_glsl_ext) - .add(spv::GLSLstd450Cosh) - .add(args[0].base) - .result; - }) - -// ret tan(x) -DEFINE_INTRINSIC(tan, 0, float, float) -DEFINE_INTRINSIC(tan, 0, float2, float2) -DEFINE_INTRINSIC(tan, 0, float3, float3) -DEFINE_INTRINSIC(tan, 0, float4, float4) -IMPLEMENT_INTRINSIC_GLSL(tan, 0, { - code += "tan(" + id_to_name(args[0].base) + ')'; - }) -IMPLEMENT_INTRINSIC_HLSL(tan, 0, { - code += "tan(" + id_to_name(args[0].base) + ')'; - }) -IMPLEMENT_INTRINSIC_SPIRV(tan, 0, { - return add_instruction(spv::OpExtInst, convert_type(res_type)) - .add(_glsl_ext) - .add(spv::GLSLstd450Tan) - .add(args[0].base) - .result; - }) - -// ret tanh(x) -DEFINE_INTRINSIC(tanh, 0, float, float) -DEFINE_INTRINSIC(tanh, 0, float2, float2) -DEFINE_INTRINSIC(tanh, 0, float3, float3) -DEFINE_INTRINSIC(tanh, 0, float4, float4) -IMPLEMENT_INTRINSIC_GLSL(tanh, 0, { - code += "tanh(" + id_to_name(args[0].base) + ')'; - }) -IMPLEMENT_INTRINSIC_HLSL(tanh, 0, { - code += "tanh(" + id_to_name(args[0].base) + ')'; - }) -IMPLEMENT_INTRINSIC_SPIRV(tanh, 0, { - return add_instruction(spv::OpExtInst, convert_type(res_type)) - .add(_glsl_ext) - .add(spv::GLSLstd450Tanh) - .add(args[0].base) - .result; - }) - -// sincos(x, out s, out c) -DEFINE_INTRINSIC(sincos, 0, void, float, out_float, out_float) -DEFINE_INTRINSIC(sincos, 0, void, float2, out_float2, out_float2) -DEFINE_INTRINSIC(sincos, 0, void, float3, out_float3, out_float3) -DEFINE_INTRINSIC(sincos, 0, void, float4, out_float4, out_float4) -IMPLEMENT_INTRINSIC_GLSL(sincos, 0, { - code += id_to_name(args[1].base) + " = sin(" + id_to_name(args[0].base) + "), " + id_to_name(args[2].base) + " = cos(" + id_to_name(args[0].base) + ')'; - }) -IMPLEMENT_INTRINSIC_HLSL(sincos, 0, { - code += id_to_name(args[1].base) + " = sin(" + id_to_name(args[0].base) + "), " + id_to_name(args[2].base) + " = cos(" + id_to_name(args[0].base) + ')'; - }) -IMPLEMENT_INTRINSIC_SPIRV(sincos, 0, { - const spv::Id sin_result = add_instruction(spv::OpExtInst, convert_type(args[0].type)) - .add(_glsl_ext) - .add(spv::GLSLstd450Sin) - .add(args[0].base) - .result; - const spv::Id cos_result = add_instruction(spv::OpExtInst, convert_type(args[0].type)) - .add(_glsl_ext) - .add(spv::GLSLstd450Cos) - .add(args[0].base) - .result; - - add_instruction_without_result(spv::OpStore) - .add(args[1].base) - .add(sin_result); - add_instruction_without_result(spv::OpStore) - .add(args[2].base) - .add(cos_result); - - return 0; - }) - -// ret asint(x) -DEFINE_INTRINSIC(asint, 0, int, float) -DEFINE_INTRINSIC(asint, 0, int2, float2) -DEFINE_INTRINSIC(asint, 0, int3, float3) -DEFINE_INTRINSIC(asint, 0, int4, float4) -IMPLEMENT_INTRINSIC_GLSL(asint, 0, { - code += "floatBitsToInt(" + id_to_name(args[0].base) + ')'; - }) -IMPLEMENT_INTRINSIC_HLSL(asint, 0, { - code += "asint(" + id_to_name(args[0].base) + ')'; - }) -IMPLEMENT_INTRINSIC_SPIRV(asint, 0, { - return add_instruction(spv::OpBitcast, convert_type(res_type)) - .add(args[0].base) - .result; - }) - -// ret asuint(x) -DEFINE_INTRINSIC(asuint, 0, uint, float) -DEFINE_INTRINSIC(asuint, 0, uint2, float2) -DEFINE_INTRINSIC(asuint, 0, uint3, float3) -DEFINE_INTRINSIC(asuint, 0, uint4, float4) -IMPLEMENT_INTRINSIC_GLSL(asuint, 0, { - code += "floatBitsToUint(" + id_to_name(args[0].base) + ')'; - }) -IMPLEMENT_INTRINSIC_HLSL(asuint, 0, { - code += "asuint(" + id_to_name(args[0].base) + ')'; - }) -IMPLEMENT_INTRINSIC_SPIRV(asuint, 0, { - return add_instruction(spv::OpBitcast, convert_type(res_type)) - .add(args[0].base) - .result; - }) - -// ret asfloat(x) -DEFINE_INTRINSIC(asfloat, 0, float, int) -DEFINE_INTRINSIC(asfloat, 0, float2, int2) -DEFINE_INTRINSIC(asfloat, 0, float3, int3) -DEFINE_INTRINSIC(asfloat, 0, float4, int4) -DEFINE_INTRINSIC(asfloat, 1, float, uint) -DEFINE_INTRINSIC(asfloat, 1, float2, uint2) -DEFINE_INTRINSIC(asfloat, 1, float3, uint3) -DEFINE_INTRINSIC(asfloat, 1, float4, uint4) -IMPLEMENT_INTRINSIC_GLSL(asfloat, 0, { - code += "intBitsToFloat(" + id_to_name(args[0].base) + ')'; - }) -IMPLEMENT_INTRINSIC_GLSL(asfloat, 1, { - code += "uintBitsToFloat(" + id_to_name(args[0].base) + ')'; - }) -IMPLEMENT_INTRINSIC_HLSL(asfloat, 0, { - code += "asfloat(" + id_to_name(args[0].base) + ')'; - }) -IMPLEMENT_INTRINSIC_HLSL(asfloat, 1, { - code += "asfloat(" + id_to_name(args[0].base) + ')'; - }) -IMPLEMENT_INTRINSIC_SPIRV(asfloat, 0, { - return add_instruction(spv::OpBitcast, convert_type(res_type)) - .add(args[0].base) - .result; - }) -IMPLEMENT_INTRINSIC_SPIRV(asfloat, 1, { - return add_instruction(spv::OpBitcast, convert_type(res_type)) - .add(args[0].base) - .result; - }) - -// ret ceil(x) -DEFINE_INTRINSIC(ceil, 0, float, float) -DEFINE_INTRINSIC(ceil, 0, float2, float2) -DEFINE_INTRINSIC(ceil, 0, float3, float3) -DEFINE_INTRINSIC(ceil, 0, float4, float4) -IMPLEMENT_INTRINSIC_GLSL(ceil, 0, { - code += "ceil(" + id_to_name(args[0].base) + ')'; - }) -IMPLEMENT_INTRINSIC_HLSL(ceil, 0, { - code += "ceil(" + id_to_name(args[0].base) + ')'; - }) -IMPLEMENT_INTRINSIC_SPIRV(ceil, 0, { - return add_instruction(spv::OpExtInst, convert_type(res_type)) - .add(_glsl_ext) - .add(spv::GLSLstd450Ceil) - .add(args[0].base) - .result; - }) - -// ret floor(x) -DEFINE_INTRINSIC(floor, 0, float, float) -DEFINE_INTRINSIC(floor, 0, float2, float2) -DEFINE_INTRINSIC(floor, 0, float3, float3) -DEFINE_INTRINSIC(floor, 0, float4, float4) -IMPLEMENT_INTRINSIC_GLSL(floor, 0, { - code += "floor(" + id_to_name(args[0].base) + ')'; - }) -IMPLEMENT_INTRINSIC_HLSL(floor, 0, { - code += "floor(" + id_to_name(args[0].base) + ')'; - }) -IMPLEMENT_INTRINSIC_SPIRV(floor, 0, { - return add_instruction(spv::OpExtInst, convert_type(res_type)) - .add(_glsl_ext) - .add(spv::GLSLstd450Floor) - .add(args[0].base) - .result; - }) - -// ret clamp(x, min, max) -DEFINE_INTRINSIC(clamp, 0, int, int, int, int) -DEFINE_INTRINSIC(clamp, 0, int2, int2, int2, int2) -DEFINE_INTRINSIC(clamp, 0, int3, int3, int3, int3) -DEFINE_INTRINSIC(clamp, 0, int4, int4, int4, int4) -DEFINE_INTRINSIC(clamp, 1, uint, uint, uint, uint) -DEFINE_INTRINSIC(clamp, 1, uint2, uint2, uint2, uint2) -DEFINE_INTRINSIC(clamp, 1, uint3, uint3, uint3, uint3) -DEFINE_INTRINSIC(clamp, 1, uint4, uint4, uint4, uint4) -DEFINE_INTRINSIC(clamp, 2, float, float, float, float) -DEFINE_INTRINSIC(clamp, 2, float2, float2, float2, float2) -DEFINE_INTRINSIC(clamp, 2, float3, float3, float3, float3) -DEFINE_INTRINSIC(clamp, 2, float4, float4, float4, float4) -IMPLEMENT_INTRINSIC_GLSL(clamp, 0, { - code += "clamp(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ", " + id_to_name(args[2].base) + ')'; - }) -IMPLEMENT_INTRINSIC_GLSL(clamp, 1, { - code += "clamp(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ", " + id_to_name(args[2].base) + ')'; - }) -IMPLEMENT_INTRINSIC_GLSL(clamp, 2, { - code += "clamp(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ", " + id_to_name(args[2].base) + ')'; - }) -IMPLEMENT_INTRINSIC_HLSL(clamp, 0, { - code += "clamp(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ", " + id_to_name(args[2].base) + ')'; - }) -IMPLEMENT_INTRINSIC_HLSL(clamp, 1, { - code += "clamp(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ", " + id_to_name(args[2].base) + ')'; - }) -IMPLEMENT_INTRINSIC_HLSL(clamp, 2, { - code += "clamp(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ", " + id_to_name(args[2].base) + ')'; - }) -IMPLEMENT_INTRINSIC_SPIRV(clamp, 0, { - return add_instruction(spv::OpExtInst, convert_type(res_type)) - .add(_glsl_ext) - .add(spv::GLSLstd450SClamp) - .add(args[0].base) - .add(args[1].base) - .add(args[2].base) - .result; - }) -IMPLEMENT_INTRINSIC_SPIRV(clamp, 1, { - return add_instruction(spv::OpExtInst, convert_type(res_type)) - .add(_glsl_ext) - .add(spv::GLSLstd450UClamp) - .add(args[0].base) - .add(args[1].base) - .add(args[2].base) - .result; - }) -IMPLEMENT_INTRINSIC_SPIRV(clamp, 2, { - return add_instruction(spv::OpExtInst, convert_type(res_type)) - .add(_glsl_ext) - .add(spv::GLSLstd450FClamp) - .add(args[0].base) - .add(args[1].base) - .add(args[2].base) - .result; - }) - -// ret saturate(x) -DEFINE_INTRINSIC(saturate, 0, float, float) -DEFINE_INTRINSIC(saturate, 0, float2, float2) -DEFINE_INTRINSIC(saturate, 0, float3, float3) -DEFINE_INTRINSIC(saturate, 0, float4, float4) -IMPLEMENT_INTRINSIC_GLSL(saturate, 0, { - code += "clamp(" + id_to_name(args[0].base) + ", 0.0, 1.0)"; - }) -IMPLEMENT_INTRINSIC_HLSL(saturate, 0, { - code += "saturate(" + id_to_name(args[0].base) + ')'; - }) -IMPLEMENT_INTRINSIC_SPIRV(saturate, 0, { - const spv::Id constant_one = emit_constant(args[0].type, { 1.0f, 1.0f, 1.0f, 1.0f }); - const spv::Id constant_zero = emit_constant(args[0].type, { 0.0f, 0.0f, 0.0f, 0.0f }); - - return add_instruction(spv::OpExtInst, convert_type(res_type)) - .add(_glsl_ext) - .add(spv::GLSLstd450FClamp) - .add(args[0].base) - .add(constant_zero) - .add(constant_one) - .result; - }) - -// ret mad(mvalue, avalue, bvalue) -DEFINE_INTRINSIC(mad, 0, float, float, float, float) -DEFINE_INTRINSIC(mad, 0, float2, float2, float2, float2) -DEFINE_INTRINSIC(mad, 0, float3, float3, float3, float3) -DEFINE_INTRINSIC(mad, 0, float4, float4, float4, float4) -IMPLEMENT_INTRINSIC_GLSL(mad, 0, { - code += "fma(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ", " + id_to_name(args[2].base) + ')'; - }) -IMPLEMENT_INTRINSIC_HLSL(mad, 0, { - if (_shader_model >= 50u) - code += "mad(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ", " + id_to_name(args[2].base) + ')'; - else - code += id_to_name(args[0].base) + " * " + id_to_name(args[1].base) + " + " + id_to_name(args[2].base); - }) -IMPLEMENT_INTRINSIC_SPIRV(mad, 0, { - return add_instruction(spv::OpExtInst, convert_type(res_type)) - .add(_glsl_ext) - .add(spv::GLSLstd450Fma) - .add(args[0].base) - .add(args[1].base) - .add(args[2].base) - .result; - }) - -// ret rcp(x) -DEFINE_INTRINSIC(rcp, 0, float, float) -DEFINE_INTRINSIC(rcp, 0, float2, float2) -DEFINE_INTRINSIC(rcp, 0, float3, float3) -DEFINE_INTRINSIC(rcp, 0, float4, float4) -IMPLEMENT_INTRINSIC_GLSL(rcp, 0, { - code += "1.0 / " + id_to_name(args[0].base); - }) -IMPLEMENT_INTRINSIC_HLSL(rcp, 0, { - if (_shader_model >= 50u) - code += "rcp(" + id_to_name(args[0].base) + ')'; - else - code += "1.0 / " + id_to_name(args[0].base); - }) -IMPLEMENT_INTRINSIC_SPIRV(rcp, 0, { - const spv::Id constant_one = emit_constant(args[0].type, { 1.0f, 1.0f, 1.0f, 1.0f }); - - return add_instruction(spv::OpFDiv, convert_type(res_type)) - .add(constant_one) - .add(args[0].base) - .result; - }) - -// ret pow(x, y) -DEFINE_INTRINSIC(pow, 0, float, float, float) -DEFINE_INTRINSIC(pow, 0, float2, float2, float2) -DEFINE_INTRINSIC(pow, 0, float3, float3, float3) -DEFINE_INTRINSIC(pow, 0, float4, float4, float4) -IMPLEMENT_INTRINSIC_GLSL(pow, 0, { - code += "pow(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ')'; - }) -IMPLEMENT_INTRINSIC_HLSL(pow, 0, { - code += "pow(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ')'; - }) -IMPLEMENT_INTRINSIC_SPIRV(pow, 0, { - return add_instruction(spv::OpExtInst, convert_type(res_type)) - .add(_glsl_ext) - .add(spv::GLSLstd450Pow) - .add(args[0].base) - .add(args[1].base) - .result; - }) - -// ret exp(x) -DEFINE_INTRINSIC(exp, 0, float, float) -DEFINE_INTRINSIC(exp, 0, float2, float2) -DEFINE_INTRINSIC(exp, 0, float3, float3) -DEFINE_INTRINSIC(exp, 0, float4, float4) -IMPLEMENT_INTRINSIC_GLSL(exp, 0, { - code += "exp(" + id_to_name(args[0].base) + ')'; - }) -IMPLEMENT_INTRINSIC_HLSL(exp, 0, { - code += "exp(" + id_to_name(args[0].base) + ')'; - }) -IMPLEMENT_INTRINSIC_SPIRV(exp, 0, { - return add_instruction(spv::OpExtInst, convert_type(res_type)) - .add(_glsl_ext) - .add(spv::GLSLstd450Exp) - .add(args[0].base) - .result; - }) - -// ret exp2(x) -DEFINE_INTRINSIC(exp2, 0, float, float) -DEFINE_INTRINSIC(exp2, 0, float2, float2) -DEFINE_INTRINSIC(exp2, 0, float3, float3) -DEFINE_INTRINSIC(exp2, 0, float4, float4) -IMPLEMENT_INTRINSIC_GLSL(exp2, 0, { - code += "exp2(" + id_to_name(args[0].base) + ')'; - }) -IMPLEMENT_INTRINSIC_HLSL(exp2, 0, { - code += "exp2(" + id_to_name(args[0].base) + ')'; - }) -IMPLEMENT_INTRINSIC_SPIRV(exp2, 0, { - return add_instruction(spv::OpExtInst, convert_type(res_type)) - .add(_glsl_ext) - .add(spv::GLSLstd450Exp2) - .add(args[0].base) - .result; - }) - -// ret log(x) -DEFINE_INTRINSIC(log, 0, float, float) -DEFINE_INTRINSIC(log, 0, float2, float2) -DEFINE_INTRINSIC(log, 0, float3, float3) -DEFINE_INTRINSIC(log, 0, float4, float4) -IMPLEMENT_INTRINSIC_GLSL(log, 0, { - code += "log(" + id_to_name(args[0].base) + ')'; - }) -IMPLEMENT_INTRINSIC_HLSL(log, 0, { - code += "log(" + id_to_name(args[0].base) + ')'; - }) -IMPLEMENT_INTRINSIC_SPIRV(log, 0, { - return add_instruction(spv::OpExtInst, convert_type(res_type)) - .add(_glsl_ext) - .add(spv::GLSLstd450Log) - .add(args[0].base) - .result; - }) - -// ret log2(x) -DEFINE_INTRINSIC(log2, 0, float, float) -DEFINE_INTRINSIC(log2, 0, float2, float2) -DEFINE_INTRINSIC(log2, 0, float3, float3) -DEFINE_INTRINSIC(log2, 0, float4, float4) -IMPLEMENT_INTRINSIC_GLSL(log2, 0, { - code += "log2(" + id_to_name(args[0].base) + ')'; - }) -IMPLEMENT_INTRINSIC_HLSL(log2, 0, { - code += "log2(" + id_to_name(args[0].base) + ')'; - }) -IMPLEMENT_INTRINSIC_SPIRV(log2, 0, { - return add_instruction(spv::OpExtInst, convert_type(res_type)) - .add(_glsl_ext) - .add(spv::GLSLstd450Log2) - .add(args[0].base) - .result; - }) - -// ret log10(x) -DEFINE_INTRINSIC(log10, 0, float, float) -DEFINE_INTRINSIC(log10, 0, float2, float2) -DEFINE_INTRINSIC(log10, 0, float3, float3) -DEFINE_INTRINSIC(log10, 0, float4, float4) -IMPLEMENT_INTRINSIC_GLSL(log10, 0, { - code += "(log2(" + id_to_name(args[0].base) + ") / 2.302585093)"; - }) -IMPLEMENT_INTRINSIC_HLSL(log10, 0, { - code += "(log2(" + id_to_name(args[0].base) + ") / 2.302585093)"; - }) -IMPLEMENT_INTRINSIC_SPIRV(log10, 0, { - const spv::Id log2 = add_instruction(spv::OpExtInst, convert_type(res_type)) - .add(_glsl_ext) - .add(spv::GLSLstd450Log2) - .add(args[0].base) - .result; - - const spv::Id log10 = emit_constant(args[0].type, - { 2.302585093f, 2.302585093f, 2.302585093f, 2.302585093f }); - - return add_instruction(spv::OpFDiv, convert_type(res_type)) - .add(log2) - .add(log10) - .result; }) - -// ret sign(x) -DEFINE_INTRINSIC(sign, 0, int, int) -DEFINE_INTRINSIC(sign, 0, int2, int2) -DEFINE_INTRINSIC(sign, 0, int3, int3) -DEFINE_INTRINSIC(sign, 0, int4, int4) -DEFINE_INTRINSIC(sign, 1, float, float) -DEFINE_INTRINSIC(sign, 1, float2, float2) -DEFINE_INTRINSIC(sign, 1, float3, float3) -DEFINE_INTRINSIC(sign, 1, float4, float4) -IMPLEMENT_INTRINSIC_GLSL(sign, 0, { - code += "sign(" + id_to_name(args[0].base) + ')'; - }) -IMPLEMENT_INTRINSIC_GLSL(sign, 1, { - code += "sign(" + id_to_name(args[0].base) + ')'; - }) -IMPLEMENT_INTRINSIC_HLSL(sign, 0, { - code += "sign(" + id_to_name(args[0].base) + ')'; - }) -IMPLEMENT_INTRINSIC_HLSL(sign, 1, { - code += "sign(" + id_to_name(args[0].base) + ')'; - }) -IMPLEMENT_INTRINSIC_SPIRV(sign, 0, { - return add_instruction(spv::OpExtInst, convert_type(res_type)) - .add(_glsl_ext) - .add(spv::GLSLstd450SSign) - .add(args[0].base) - .result; - }) -IMPLEMENT_INTRINSIC_SPIRV(sign, 1, { - return add_instruction(spv::OpExtInst, convert_type(res_type)) - .add(_glsl_ext) - .add(spv::GLSLstd450FSign) - .add(args[0].base) - .result; - }) - -// ret sqrt(x) -DEFINE_INTRINSIC(sqrt, 0, float, float) -DEFINE_INTRINSIC(sqrt, 0, float2, float2) -DEFINE_INTRINSIC(sqrt, 0, float3, float3) -DEFINE_INTRINSIC(sqrt, 0, float4, float4) -IMPLEMENT_INTRINSIC_GLSL(sqrt, 0, { - code += "sqrt(" + id_to_name(args[0].base) + ')'; - }) -IMPLEMENT_INTRINSIC_HLSL(sqrt, 0, { - code += "sqrt(" + id_to_name(args[0].base) + ')'; - }) -IMPLEMENT_INTRINSIC_SPIRV(sqrt, 0, { - return add_instruction(spv::OpExtInst, convert_type(res_type)) - .add(_glsl_ext) - .add(spv::GLSLstd450Sqrt) - .add(args[0].base) - .result; - }) - -// ret rsqrt(x) -DEFINE_INTRINSIC(rsqrt, 0, float, float) -DEFINE_INTRINSIC(rsqrt, 0, float2, float2) -DEFINE_INTRINSIC(rsqrt, 0, float3, float3) -DEFINE_INTRINSIC(rsqrt, 0, float4, float4) -IMPLEMENT_INTRINSIC_GLSL(rsqrt, 0, { - code += "inversesqrt(" + id_to_name(args[0].base) + ')'; - }) -IMPLEMENT_INTRINSIC_HLSL(rsqrt, 0, { - code += "rsqrt(" + id_to_name(args[0].base) + ')'; - }) -IMPLEMENT_INTRINSIC_SPIRV(rsqrt, 0, { - return add_instruction(spv::OpExtInst, convert_type(res_type)) - .add(_glsl_ext) - .add(spv::GLSLstd450InverseSqrt) - .add(args[0].base) - .result; - }) - -// ret lerp(x, y, s) -DEFINE_INTRINSIC(lerp, 0, float, float, float, float) -DEFINE_INTRINSIC(lerp, 0, float2, float2, float2, float2) -DEFINE_INTRINSIC(lerp, 0, float3, float3, float3, float3) -DEFINE_INTRINSIC(lerp, 0, float4, float4, float4, float4) -IMPLEMENT_INTRINSIC_GLSL(lerp, 0, { - code += "mix(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ", " + id_to_name(args[2].base) + ')'; - }) -IMPLEMENT_INTRINSIC_HLSL(lerp, 0, { - code += "lerp(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ", " + id_to_name(args[2].base) + ')'; - }) -IMPLEMENT_INTRINSIC_SPIRV(lerp, 0, { - return add_instruction(spv::OpExtInst, convert_type(res_type)) - .add(_glsl_ext) - .add(spv::GLSLstd450FMix) - .add(args[0].base) - .add(args[1].base) - .add(args[2].base) - .result; - }) - -// ret step(y, x) -DEFINE_INTRINSIC(step, 0, float, float, float) -DEFINE_INTRINSIC(step, 0, float2, float2, float2) -DEFINE_INTRINSIC(step, 0, float3, float3, float3) -DEFINE_INTRINSIC(step, 0, float4, float4, float4) -IMPLEMENT_INTRINSIC_GLSL(step, 0, { - code += "step(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ')'; - }) -IMPLEMENT_INTRINSIC_HLSL(step, 0, { - code += "step(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ')'; - }) -IMPLEMENT_INTRINSIC_SPIRV(step, 0, { - return add_instruction(spv::OpExtInst, convert_type(res_type)) - .add(_glsl_ext) - .add(spv::GLSLstd450Step) - .add(args[0].base) - .add(args[1].base) - .result; - }) - -// ret smoothstep(min, max, x) -DEFINE_INTRINSIC(smoothstep, 0, float, float, float, float) -DEFINE_INTRINSIC(smoothstep, 0, float2, float2, float2, float2) -DEFINE_INTRINSIC(smoothstep, 0, float3, float3, float3, float3) -DEFINE_INTRINSIC(smoothstep, 0, float4, float4, float4, float4) -IMPLEMENT_INTRINSIC_GLSL(smoothstep, 0, { - code += "smoothstep(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ", " + id_to_name(args[2].base) + ')'; - }) -IMPLEMENT_INTRINSIC_HLSL(smoothstep, 0, { - code += "smoothstep(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ", " + id_to_name(args[2].base) + ')'; - }) -IMPLEMENT_INTRINSIC_SPIRV(smoothstep, 0, { - return add_instruction(spv::OpExtInst, convert_type(args[2].type)) - .add(_glsl_ext) - .add(spv::GLSLstd450SmoothStep) - .add(args[0].base) - .add(args[1].base) - .add(args[2].base) - .result; - }) - -// ret frac(x) -DEFINE_INTRINSIC(frac, 0, float, float) -DEFINE_INTRINSIC(frac, 0, float2, float2) -DEFINE_INTRINSIC(frac, 0, float3, float3) -DEFINE_INTRINSIC(frac, 0, float4, float4) -IMPLEMENT_INTRINSIC_GLSL(frac, 0, { - code += "fract(" + id_to_name(args[0].base) + ')'; - }) -IMPLEMENT_INTRINSIC_HLSL(frac, 0, { - code += "frac(" + id_to_name(args[0].base) + ')'; - }) -IMPLEMENT_INTRINSIC_SPIRV(frac, 0, { - return add_instruction(spv::OpExtInst, convert_type(res_type)) - .add(_glsl_ext) - .add(spv::GLSLstd450Fract) - .add(args[0].base) - .result; - }) - -// ret ldexp(x, exp) -DEFINE_INTRINSIC(ldexp, 0, float, float, int) -DEFINE_INTRINSIC(ldexp, 0, float2, float2, int2) -DEFINE_INTRINSIC(ldexp, 0, float3, float3, int3) -DEFINE_INTRINSIC(ldexp, 0, float4, float4, int4) -IMPLEMENT_INTRINSIC_GLSL(ldexp, 0, { - code += "ldexp(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ')'; - }) -IMPLEMENT_INTRINSIC_HLSL(ldexp, 0, { - code += "ldexp(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ')'; - }) -IMPLEMENT_INTRINSIC_SPIRV(ldexp, 0, { - return add_instruction(spv::OpExtInst, convert_type(res_type)) - .add(_glsl_ext) - .add(spv::GLSLstd450Ldexp) - .add(args[0].base) - .add(args[1].base) - .result; - }) - -// ret modf(x, out ip) -DEFINE_INTRINSIC(modf, 0, float, float, out_float) -DEFINE_INTRINSIC(modf, 0, float2, float2, out_float2) -DEFINE_INTRINSIC(modf, 0, float3, float3, out_float3) -DEFINE_INTRINSIC(modf, 0, float4, float4, out_float4) -IMPLEMENT_INTRINSIC_GLSL(modf, 0, { - code += "modf(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ')'; - }) -IMPLEMENT_INTRINSIC_HLSL(modf, 0, { - code += "modf(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ')'; - }) -IMPLEMENT_INTRINSIC_SPIRV(modf, 0, { - return add_instruction(spv::OpExtInst, convert_type(res_type)) - .add(_glsl_ext) - .add(spv::GLSLstd450Modf) - .add(args[0].base) - .add(args[1].base) - .result; - }) - -// ret frexp(x, out exp) -DEFINE_INTRINSIC(frexp, 0, float, float, out_float) -DEFINE_INTRINSIC(frexp, 0, float2, float2, out_float2) -DEFINE_INTRINSIC(frexp, 0, float3, float3, out_float3) -DEFINE_INTRINSIC(frexp, 0, float4, float4, out_float4) -IMPLEMENT_INTRINSIC_GLSL(frexp, 0, { - code += "frexp(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ')'; - }) -IMPLEMENT_INTRINSIC_HLSL(frexp, 0, { - code += "frexp(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ')'; - }) -IMPLEMENT_INTRINSIC_SPIRV(frexp, 0, { - return add_instruction(spv::OpExtInst, convert_type(res_type)) - .add(_glsl_ext) - .add(spv::GLSLstd450Frexp) - .add(args[0].base) - .add(args[1].base) - .result; - }) - -// ret trunc(x) -DEFINE_INTRINSIC(trunc, 0, float, float) -DEFINE_INTRINSIC(trunc, 0, float2, float2) -DEFINE_INTRINSIC(trunc, 0, float3, float3) -DEFINE_INTRINSIC(trunc, 0, float4, float4) -IMPLEMENT_INTRINSIC_GLSL(trunc, 0, { - code += "trunc(" + id_to_name(args[0].base) + ')'; - }) -IMPLEMENT_INTRINSIC_HLSL(trunc, 0, { - code += "trunc(" + id_to_name(args[0].base) + ')'; - }) -IMPLEMENT_INTRINSIC_SPIRV(trunc, 0, { - return add_instruction(spv::OpExtInst, convert_type(res_type)) - .add(_glsl_ext) - .add(spv::GLSLstd450Trunc) - .add(args[0].base) - .result; - }) - -// ret round(x) -DEFINE_INTRINSIC(round, 0, float, float) -DEFINE_INTRINSIC(round, 0, float2, float2) -DEFINE_INTRINSIC(round, 0, float3, float3) -DEFINE_INTRINSIC(round, 0, float4, float4) -IMPLEMENT_INTRINSIC_GLSL(round, 0, { - code += "round(" + id_to_name(args[0].base) + ')'; - }) -IMPLEMENT_INTRINSIC_HLSL(round, 0, { - code += "round(" + id_to_name(args[0].base) + ')'; - }) -IMPLEMENT_INTRINSIC_SPIRV(round, 0, { - return add_instruction(spv::OpExtInst, convert_type(res_type)) - .add(_glsl_ext) - .add(spv::GLSLstd450Round) - .add(args[0].base) - .result; - }) - -// ret min(x, y) -DEFINE_INTRINSIC(min, 0, int, int, int) -DEFINE_INTRINSIC(min, 0, int2, int2, int2) -DEFINE_INTRINSIC(min, 0, int3, int3, int3) -DEFINE_INTRINSIC(min, 0, int4, int4, int4) -DEFINE_INTRINSIC(min, 1, float, float, float) -DEFINE_INTRINSIC(min, 1, float2, float2, float2) -DEFINE_INTRINSIC(min, 1, float3, float3, float3) -DEFINE_INTRINSIC(min, 1, float4, float4, float4) -IMPLEMENT_INTRINSIC_GLSL(min, 0, { - code += "min(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ')'; - }) -IMPLEMENT_INTRINSIC_GLSL(min, 1, { - code += "min(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ')'; - }) -IMPLEMENT_INTRINSIC_HLSL(min, 0, { - code += "min(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ')'; - }) -IMPLEMENT_INTRINSIC_HLSL(min, 1, { - code += "min(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ')'; - }) -IMPLEMENT_INTRINSIC_SPIRV(min, 0, { - return add_instruction(spv::OpExtInst, convert_type(res_type)) - .add(_glsl_ext) - .add(spv::GLSLstd450SMin) - .add(args[0].base) - .add(args[1].base) - .result; - }) -IMPLEMENT_INTRINSIC_SPIRV(min, 1, { - return add_instruction(spv::OpExtInst, convert_type(res_type)) - .add(_glsl_ext) - .add(spv::GLSLstd450FMin) - .add(args[0].base) - .add(args[1].base) - .result; - }) - -// ret max(x, y) -DEFINE_INTRINSIC(max, 0, int, int, int) -DEFINE_INTRINSIC(max, 0, int2, int2, int2) -DEFINE_INTRINSIC(max, 0, int3, int3, int3) -DEFINE_INTRINSIC(max, 0, int4, int4, int4) -DEFINE_INTRINSIC(max, 1, float, float, float) -DEFINE_INTRINSIC(max, 1, float2, float2, float2) -DEFINE_INTRINSIC(max, 1, float3, float3, float3) -DEFINE_INTRINSIC(max, 1, float4, float4, float4) -IMPLEMENT_INTRINSIC_GLSL(max, 0, { - code += "max(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ')'; - }) -IMPLEMENT_INTRINSIC_GLSL(max, 1, { - code += "max(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ')'; - }) -IMPLEMENT_INTRINSIC_HLSL(max, 0, { - code += "max(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ')'; - }) -IMPLEMENT_INTRINSIC_HLSL(max, 1, { - code += "max(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ')'; - }) -IMPLEMENT_INTRINSIC_SPIRV(max, 0, { - return add_instruction(spv::OpExtInst, convert_type(res_type)) - .add(_glsl_ext) - .add(spv::GLSLstd450SMax) - .add(args[0].base) - .add(args[1].base) - .result; - }) -IMPLEMENT_INTRINSIC_SPIRV(max, 1, { - return add_instruction(spv::OpExtInst, convert_type(res_type)) - .add(_glsl_ext) - .add(spv::GLSLstd450FMax) - .add(args[0].base) - .add(args[1].base) - .result; - }) - -// ret degree(x) -DEFINE_INTRINSIC(degrees, 0, float, float) -DEFINE_INTRINSIC(degrees, 0, float2, float2) -DEFINE_INTRINSIC(degrees, 0, float3, float3) -DEFINE_INTRINSIC(degrees, 0, float4, float4) -IMPLEMENT_INTRINSIC_GLSL(degrees, 0, { - code += "degrees(" + id_to_name(args[0].base) + ')'; - }) -IMPLEMENT_INTRINSIC_HLSL(degrees, 0, { - code += "degrees(" + id_to_name(args[0].base) + ')'; - }) -IMPLEMENT_INTRINSIC_SPIRV(degrees, 0, { - return add_instruction(spv::OpExtInst, convert_type(res_type)) - .add(_glsl_ext) - .add(spv::GLSLstd450Degrees) - .add(args[0].base) - .result; - }) - -// ret radians(x) -DEFINE_INTRINSIC(radians, 0, float, float) -DEFINE_INTRINSIC(radians, 0, float2, float2) -DEFINE_INTRINSIC(radians, 0, float3, float3) -DEFINE_INTRINSIC(radians, 0, float4, float4) -IMPLEMENT_INTRINSIC_GLSL(radians, 0, { - code += "radians(" + id_to_name(args[0].base) + ')'; - }) -IMPLEMENT_INTRINSIC_HLSL(radians, 0, { - code += "radians(" + id_to_name(args[0].base) + ')'; - }) -IMPLEMENT_INTRINSIC_SPIRV(radians, 0, { - return add_instruction(spv::OpExtInst, convert_type(res_type)) - .add(_glsl_ext) - .add(spv::GLSLstd450Radians) - .add(args[0].base) - .result; - }) - -// ret ddx(x) -DEFINE_INTRINSIC(ddx, 0, float, float) -DEFINE_INTRINSIC(ddx, 0, float2, float2) -DEFINE_INTRINSIC(ddx, 0, float3, float3) -DEFINE_INTRINSIC(ddx, 0, float4, float4) -IMPLEMENT_INTRINSIC_GLSL(ddx, 0, { - code += "dFdx(" + id_to_name(args[0].base) + ')'; - }) -IMPLEMENT_INTRINSIC_HLSL(ddx, 0, { - code += "ddx(" + id_to_name(args[0].base) + ')'; - }) -IMPLEMENT_INTRINSIC_SPIRV(ddx, 0, { - return add_instruction(spv::OpDPdx, convert_type(res_type)) - .add(args[0].base) - .result; - }) - -// ret ddy(x) -DEFINE_INTRINSIC(ddy, 0, float, float) -DEFINE_INTRINSIC(ddy, 0, float2, float2) -DEFINE_INTRINSIC(ddy, 0, float3, float3) -DEFINE_INTRINSIC(ddy, 0, float4, float4) -IMPLEMENT_INTRINSIC_GLSL(ddy, 0, { - code += "dFdy(" + id_to_name(args[0].base) + ')'; - }) -IMPLEMENT_INTRINSIC_HLSL(ddy, 0, { - code += "ddy(" + id_to_name(args[0].base) + ')'; - }) -IMPLEMENT_INTRINSIC_SPIRV(ddy, 0, { - return add_instruction(spv::OpDPdy, convert_type(res_type)) - .add(args[0].base) - .result; - }) - -// ret fwidth(x) -DEFINE_INTRINSIC(fwidth, 0, float, float) -DEFINE_INTRINSIC(fwidth, 0, float2, float2) -DEFINE_INTRINSIC(fwidth, 0, float3, float3) -DEFINE_INTRINSIC(fwidth, 0, float4, float4) -IMPLEMENT_INTRINSIC_GLSL(fwidth, 0, { - code += "fwidth(" + id_to_name(args[0].base) + ')'; - }) -IMPLEMENT_INTRINSIC_HLSL(fwidth, 0, { - code += "fwidth(" + id_to_name(args[0].base) + ')'; - }) -IMPLEMENT_INTRINSIC_SPIRV(fwidth, 0, { - return add_instruction(spv::OpFwidth, convert_type(res_type)) - .add(args[0].base) - .result; - }) - -// ret dot(x, y) -DEFINE_INTRINSIC(dot, 0, float, float2, float2) -DEFINE_INTRINSIC(dot, 0, float, float3, float3) -DEFINE_INTRINSIC(dot, 0, float, float4, float4) -IMPLEMENT_INTRINSIC_GLSL(dot, 0, { - code += "dot(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ')'; - }) -IMPLEMENT_INTRINSIC_HLSL(dot, 0, { - code += "dot(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ')'; - }) -IMPLEMENT_INTRINSIC_SPIRV(dot, 0, { - return add_instruction(spv::OpDot, convert_type(res_type)) - .add(args[0].base) - .add(args[1].base) - .result; - }) - -// ret cross(x, y) -DEFINE_INTRINSIC(cross, 0, float3, float3, float3) -IMPLEMENT_INTRINSIC_GLSL(cross, 0, { - code += "cross(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ')'; - }) -IMPLEMENT_INTRINSIC_HLSL(cross, 0, { - code += "cross(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ')'; - }) -IMPLEMENT_INTRINSIC_SPIRV(cross, 0, { - return add_instruction(spv::OpExtInst, convert_type(res_type)) - .add(_glsl_ext) - .add(spv::GLSLstd450Cross) - .add(args[0].base) - .add(args[1].base) - .result; - }) - -// ret length(x) -DEFINE_INTRINSIC(length, 0, float, float) -DEFINE_INTRINSIC(length, 0, float, float2) -DEFINE_INTRINSIC(length, 0, float, float3) -DEFINE_INTRINSIC(length, 0, float, float4) -IMPLEMENT_INTRINSIC_GLSL(length, 0, { - code += "length(" + id_to_name(args[0].base) + ')'; - }) -IMPLEMENT_INTRINSIC_HLSL(length, 0, { - code += "length(" + id_to_name(args[0].base) + ')'; - }) -IMPLEMENT_INTRINSIC_SPIRV(length, 0, { - return add_instruction(spv::OpExtInst, convert_type(res_type)) - .add(_glsl_ext) - .add(spv::GLSLstd450Length) - .add(args[0].base) - .result; - }) - -// ret distance(x, y) -DEFINE_INTRINSIC(distance, 0, float, float, float) -DEFINE_INTRINSIC(distance, 0, float, float2, float2) -DEFINE_INTRINSIC(distance, 0, float, float3, float3) -DEFINE_INTRINSIC(distance, 0, float, float4, float4) -IMPLEMENT_INTRINSIC_GLSL(distance, 0, { - code += "distance(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ')'; - }) -IMPLEMENT_INTRINSIC_HLSL(distance, 0, { - code += "distance(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ')'; - }) -IMPLEMENT_INTRINSIC_SPIRV(distance, 0, { - return add_instruction(spv::OpExtInst, convert_type(res_type)) - .add(_glsl_ext) - .add(spv::GLSLstd450Distance) - .add(args[0].base) - .add(args[1].base) - .result; - }) - -// ret normalize(x) -DEFINE_INTRINSIC(normalize, 0, float2, float2) -DEFINE_INTRINSIC(normalize, 0, float3, float3) -DEFINE_INTRINSIC(normalize, 0, float4, float4) -IMPLEMENT_INTRINSIC_GLSL(normalize, 0, { - code += "normalize(" + id_to_name(args[0].base) + ')'; - }) -IMPLEMENT_INTRINSIC_HLSL(normalize, 0, { - code += "normalize(" + id_to_name(args[0].base) + ')'; - }) -IMPLEMENT_INTRINSIC_SPIRV(normalize, 0, { - return add_instruction(spv::OpExtInst, convert_type(res_type)) - .add(_glsl_ext) - .add(spv::GLSLstd450Normalize) - .add(args[0].base) - .result; - }) - -// ret transpose(x) -DEFINE_INTRINSIC(transpose, 0, float2x2, float2x2) -DEFINE_INTRINSIC(transpose, 0, float3x3, float3x3) -DEFINE_INTRINSIC(transpose, 0, float4x4, float4x4) -IMPLEMENT_INTRINSIC_GLSL(transpose, 0, { - code += "transpose(" + id_to_name(args[0].base) + ')'; - }) -IMPLEMENT_INTRINSIC_HLSL(transpose, 0, { - code += "transpose(" + id_to_name(args[0].base) + ')'; - }) -IMPLEMENT_INTRINSIC_SPIRV(transpose, 0, { - return add_instruction(spv::OpTranspose, convert_type(res_type)) - .add(args[0].base) - .result; - }) - -// ret determinant(m) -DEFINE_INTRINSIC(determinant, 0, float, float2x2) -DEFINE_INTRINSIC(determinant, 0, float, float3x3) -DEFINE_INTRINSIC(determinant, 0, float, float4x4) -IMPLEMENT_INTRINSIC_GLSL(determinant, 0, { - code += "determinant(" + id_to_name(args[0].base) + ')'; - }) -IMPLEMENT_INTRINSIC_HLSL(determinant, 0, { - code += "determinant(" + id_to_name(args[0].base) + ')'; - }) -IMPLEMENT_INTRINSIC_SPIRV(determinant, 0, { - return add_instruction(spv::OpExtInst, convert_type(res_type)) - .add(_glsl_ext) - .add(spv::GLSLstd450Determinant) - .add(args[0].base) - .result; - }) - -// ret reflect(i, n) -DEFINE_INTRINSIC(reflect, 0, float2, float2, float2) -DEFINE_INTRINSIC(reflect, 0, float3, float3, float3) -DEFINE_INTRINSIC(reflect, 0, float4, float4, float4) -IMPLEMENT_INTRINSIC_GLSL(reflect, 0, { - code += "reflect(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ')'; - }) -IMPLEMENT_INTRINSIC_HLSL(reflect, 0, { - code += "reflect(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ')'; - }) -IMPLEMENT_INTRINSIC_SPIRV(reflect, 0, { - return add_instruction(spv::OpExtInst, convert_type(res_type)) - .add(_glsl_ext) - .add(spv::GLSLstd450Reflect) - .add(args[0].base) - .add(args[1].base) - .result; - }) - -// ret refract(i, n, eta) -DEFINE_INTRINSIC(refract, 0, float2, float2, float2, float) -DEFINE_INTRINSIC(refract, 0, float3, float3, float3, float) -DEFINE_INTRINSIC(refract, 0, float4, float4, float4, float) -IMPLEMENT_INTRINSIC_GLSL(refract, 0, { - code += "refract(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ", " + id_to_name(args[1].base) + ')'; - }) -IMPLEMENT_INTRINSIC_HLSL(refract, 0, { - code += "refract(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ", " + id_to_name(args[1].base) + ')'; - }) -IMPLEMENT_INTRINSIC_SPIRV(refract, 0, { - return add_instruction(spv::OpExtInst, convert_type(res_type)) - .add(_glsl_ext) - .add(spv::GLSLstd450Refract) - .add(args[0].base) - .add(args[1].base) - .add(args[2].base) - .result; - }) - -// ret faceforward(n, i, ng) -DEFINE_INTRINSIC(faceforward, 0, float, float, float, float) -DEFINE_INTRINSIC(faceforward, 0, float2, float2, float2, float2) -DEFINE_INTRINSIC(faceforward, 0, float3, float3, float3, float3) -DEFINE_INTRINSIC(faceforward, 0, float4, float4, float4, float4) -IMPLEMENT_INTRINSIC_GLSL(faceforward, 0, { - code += "faceforward(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ", " + id_to_name(args[1].base) + ')'; - }) -IMPLEMENT_INTRINSIC_HLSL(faceforward, 0, { - code += "faceforward(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ", " + id_to_name(args[1].base) + ')'; - }) -IMPLEMENT_INTRINSIC_SPIRV(faceforward, 0, { - return add_instruction(spv::OpExtInst, convert_type(res_type)) - .add(_glsl_ext) - .add(spv::GLSLstd450FaceForward) - .add(args[0].base) - .add(args[1].base) - .add(args[2].base) - .result; - }) - -// ret mul(x, y) -DEFINE_INTRINSIC(mul, 0, float2, float, float2) -DEFINE_INTRINSIC(mul, 0, float3, float, float3) -DEFINE_INTRINSIC(mul, 0, float4, float, float4) -IMPLEMENT_INTRINSIC_GLSL(mul, 0, { - code += '(' + id_to_name(args[0].base) + " * " + id_to_name(args[1].base) + ')'; - }) -IMPLEMENT_INTRINSIC_HLSL(mul, 0, { - code += '(' + id_to_name(args[0].base) + " * " + id_to_name(args[1].base) + ')'; - }) -IMPLEMENT_INTRINSIC_SPIRV(mul, 0, { - return add_instruction(spv::OpVectorTimesScalar, convert_type(res_type)) - .add(args[1].base) - .add(args[0].base) - .result; - }) -DEFINE_INTRINSIC(mul, 1, float2, float2, float) -DEFINE_INTRINSIC(mul, 1, float3, float3, float) -DEFINE_INTRINSIC(mul, 1, float4, float4, float) -IMPLEMENT_INTRINSIC_GLSL(mul, 1, { - code += '(' + id_to_name(args[0].base) + " * " + id_to_name(args[1].base) + ')'; - }) -IMPLEMENT_INTRINSIC_HLSL(mul, 1, { - code += '(' + id_to_name(args[0].base) + " * " + id_to_name(args[1].base) + ')'; - }) -IMPLEMENT_INTRINSIC_SPIRV(mul, 1, { - return add_instruction(spv::OpVectorTimesScalar, convert_type(res_type)) - .add(args[0].base) - .add(args[1].base) - .result; - }) - -DEFINE_INTRINSIC(mul, 2, float2x2, float, float2x2) -DEFINE_INTRINSIC(mul, 2, float3x3, float, float3x3) -DEFINE_INTRINSIC(mul, 2, float4x4, float, float4x4) -IMPLEMENT_INTRINSIC_GLSL(mul, 2, { - code += '(' + id_to_name(args[0].base) + " * " + id_to_name(args[1].base) + ')'; - }) -IMPLEMENT_INTRINSIC_HLSL(mul, 2, { - code += '(' + id_to_name(args[0].base) + " * " + id_to_name(args[1].base) + ')'; - }) -IMPLEMENT_INTRINSIC_SPIRV(mul, 2, { - return add_instruction(spv::OpMatrixTimesScalar, convert_type(res_type)) - .add(args[1].base) - .add(args[0].base) - .result; - }) -DEFINE_INTRINSIC(mul, 3, float2x2, float2x2, float) -DEFINE_INTRINSIC(mul, 3, float3x3, float3x3, float) -DEFINE_INTRINSIC(mul, 3, float4x4, float4x4, float) -IMPLEMENT_INTRINSIC_GLSL(mul, 3, { - code += '(' + id_to_name(args[0].base) + " * " + id_to_name(args[1].base) + ')'; - }) -IMPLEMENT_INTRINSIC_HLSL(mul, 3, { - code += '(' + id_to_name(args[0].base) + " * " + id_to_name(args[1].base) + ')'; - }) -IMPLEMENT_INTRINSIC_SPIRV(mul, 3, { - return add_instruction(spv::OpMatrixTimesScalar, convert_type(res_type)) - .add(args[0].base) - .add(args[1].base) - .result; - }) - -DEFINE_INTRINSIC(mul, 4, float2, float2, float2x2) -DEFINE_INTRINSIC(mul, 4, float3, float3, float3x3) -DEFINE_INTRINSIC(mul, 4, float4, float4, float4x4) -IMPLEMENT_INTRINSIC_GLSL(mul, 4, { - code += '(' + id_to_name(args[0].base) + " * " + id_to_name(args[1].base) + ')'; - }) -IMPLEMENT_INTRINSIC_HLSL(mul, 4, { - code += "mul(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ')'; - }) -IMPLEMENT_INTRINSIC_SPIRV(mul, 4, { - return add_instruction(spv::OpVectorTimesMatrix, convert_type(res_type)) - .add(args[0].base) - .add(args[1].base) - .result; - }) -DEFINE_INTRINSIC(mul, 5, float2, float2x2, float2) -DEFINE_INTRINSIC(mul, 5, float3, float3x3, float3) -DEFINE_INTRINSIC(mul, 5, float4, float4x4, float4) -IMPLEMENT_INTRINSIC_GLSL(mul, 5, { - code += '(' + id_to_name(args[0].base) + " * " + id_to_name(args[1].base) + ')'; - }) -IMPLEMENT_INTRINSIC_HLSL(mul, 5, { - code += "mul(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ')'; - }) -IMPLEMENT_INTRINSIC_SPIRV(mul, 5, { - return add_instruction(spv::OpMatrixTimesVector, convert_type(res_type)) - .add(args[0].base) - .add(args[1].base) - .result; - }) - -DEFINE_INTRINSIC(mul, 6, float2x2, float2x2, float2x2) -DEFINE_INTRINSIC(mul, 6, float3x3, float3x3, float3x3) -DEFINE_INTRINSIC(mul, 6, float4x4, float4x4, float4x4) -IMPLEMENT_INTRINSIC_GLSL(mul, 6, { - code += '(' + id_to_name(args[0].base) + " * " + id_to_name(args[1].base) + ')'; - }) -IMPLEMENT_INTRINSIC_HLSL(mul, 6, { - code += "mul(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ')'; - }) -IMPLEMENT_INTRINSIC_SPIRV(mul, 6, { - return add_instruction(spv::OpMatrixTimesMatrix, convert_type(res_type)) - .add(args[0].base) - .add(args[1].base) - .result; - }) - -// ret isinf(x) -DEFINE_INTRINSIC(isinf, 0, bool, float) -DEFINE_INTRINSIC(isinf, 0, bool2, float2) -DEFINE_INTRINSIC(isinf, 0, bool3, float3) -DEFINE_INTRINSIC(isinf, 0, bool4, float4) -IMPLEMENT_INTRINSIC_GLSL(isinf, 0, { - code += "isinf(" + id_to_name(args[0].base) + ')'; - }) -IMPLEMENT_INTRINSIC_HLSL(isinf, 0, { - code += "isinf(" + id_to_name(args[0].base) + ')'; - }) -IMPLEMENT_INTRINSIC_SPIRV(isinf, 0, { - return add_instruction(spv::OpIsInf, convert_type(res_type)) - .add(args[0].base) - .result; - }) - -// ret isnan(x) -DEFINE_INTRINSIC(isnan, 0, bool, float) -DEFINE_INTRINSIC(isnan, 0, bool2, float2) -DEFINE_INTRINSIC(isnan, 0, bool3, float3) -DEFINE_INTRINSIC(isnan, 0, bool4, float4) -IMPLEMENT_INTRINSIC_GLSL(isnan, 0, { - code += "isnan(" + id_to_name(args[0].base) + ')'; - }) -IMPLEMENT_INTRINSIC_HLSL(isnan, 0, { - code += "isnan(" + id_to_name(args[0].base) + ')'; - }) -IMPLEMENT_INTRINSIC_SPIRV(isnan, 0, { - return add_instruction(spv::OpIsNan, convert_type(res_type)) - .add(args[0].base) - .result; - }) - -// ret tex2D(s, coords) -DEFINE_INTRINSIC(tex2D, 0, float4, sampler, float2) -IMPLEMENT_INTRINSIC_GLSL(tex2D, 0, { - code += "texture(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + " * vec2(1.0, -1.0) + vec2(0.0, 1.0))"; - }) -IMPLEMENT_INTRINSIC_HLSL(tex2D, 0, { - if (_shader_model >= 40u) - code += id_to_name(args[0].base) + ".t.Sample(" + id_to_name(args[0].base) + ".s, " + id_to_name(args[1].base) + ')'; - else - code += "tex2D(" + id_to_name(args[0].base) + ".s, " + id_to_name(args[1].base) + ')'; - }) -IMPLEMENT_INTRINSIC_SPIRV(tex2D, 0, { - return add_instruction(spv::OpImageSampleImplicitLod, convert_type(res_type)) - .add(args[0].base) - .add(args[1].base) - .add(spv::ImageOperandsMaskNone) - .result; - }) -// ret tex2Doffset(s, coords, offset) -DEFINE_INTRINSIC(tex2Doffset, 0, float4, sampler, float2, int2) -IMPLEMENT_INTRINSIC_GLSL(tex2Doffset, 0, { - code += "textureOffset(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + " * vec2(1.0, -1.0) + vec2(0.0, 1.0), " + id_to_name(args[2].base) + " * ivec2(1, -1))"; - }) -IMPLEMENT_INTRINSIC_HLSL(tex2Doffset, 0, { - if (_shader_model >= 40u) - code += id_to_name(args[0].base) + ".t.Sample(" + id_to_name(args[0].base) + ".s, " + id_to_name(args[1].base) + ", " + id_to_name(args[2].base) + ')'; - else - code += "tex2D(" + id_to_name(args[0].base) + ".s, " + id_to_name(args[1].base) + " + " + id_to_name(args[2].base) + " * " + id_to_name(args[0].base) + ".pixelsize)"; - }) -IMPLEMENT_INTRINSIC_SPIRV(tex2Doffset, 0, { - add_capability(spv::CapabilityImageGatherExtended); - - return add_instruction(spv::OpImageSampleImplicitLod, convert_type(res_type)) - .add(args[0].base) - .add(args[1].base) - .add(spv::ImageOperandsOffsetMask) - .add(args[2].base) - .result; - }) - -// ret tex2Dlod(s, coords) -DEFINE_INTRINSIC(tex2Dlod, 0, float4, sampler, float4) -IMPLEMENT_INTRINSIC_GLSL(tex2Dlod, 0, { - code += "textureLod(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ".xy * vec2(1.0, -1.0) + vec2(0.0, 1.0), " + id_to_name(args[1].base) + ".w)"; - }) -IMPLEMENT_INTRINSIC_HLSL(tex2Dlod, 0, { - if (_shader_model >= 40u) - code += id_to_name(args[0].base) + ".t.SampleLevel(" + id_to_name(args[0].base) + ".s, " + id_to_name(args[1].base) + ".xy, " + id_to_name(args[1].base) + ".w)"; - else - code += "tex2Dlod(" + id_to_name(args[0].base) + ".s, " + id_to_name(args[1].base) + ')'; - }) -IMPLEMENT_INTRINSIC_SPIRV(tex2Dlod, 0, { - const spv::Id xy = add_instruction(spv::OpVectorShuffle, convert_type({ type::t_float, 2, 1 })) - .add(args[1].base) - .add(args[1].base) - .add(0) // .x - .add(1) // .y - .result; - const spv::Id lod = add_instruction(spv::OpCompositeExtract, convert_type({ type::t_float, 1, 1 })) - .add(args[1].base) - .add(3) // .w - .result; - - return add_instruction(spv::OpImageSampleExplicitLod, convert_type(res_type)) - .add(args[0].base) - .add(xy) - .add(spv::ImageOperandsLodMask) - .add(lod) - .result; - }) -// ret tex2Dlodoffset(s, coords, offset) -DEFINE_INTRINSIC(tex2Dlodoffset, 0, float4, sampler, float4, int2) -IMPLEMENT_INTRINSIC_GLSL(tex2Dlodoffset, 0, { - code += "textureLodOffset(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ".xy * vec2(1.0, -1.0) + vec2(0.0, 1.0), " + id_to_name(args[1].base) + ".w, " + id_to_name(args[2].base) + " * ivec2(1, -1))"; - }) -IMPLEMENT_INTRINSIC_HLSL(tex2Dlodoffset, 0, { - if (_shader_model >= 40u) - code += id_to_name(args[0].base) + ".t.SampleLevel(" + id_to_name(args[0].base) + ".s, " + id_to_name(args[1].base) + ".xy, " + id_to_name(args[1].base) + ".w, " + id_to_name(args[2].base) + ')'; - else - code += "tex2Dlod(" + id_to_name(args[0].base) + ".s, " + id_to_name(args[1].base) + " + float4(" + id_to_name(args[2].base) + " * " + id_to_name(args[0].base) + ".pixelsize, 0, 0))"; - }) -IMPLEMENT_INTRINSIC_SPIRV(tex2Dlodoffset, 0, { - add_capability(spv::CapabilityImageGatherExtended); - - const spv::Id xy = add_instruction(spv::OpVectorShuffle, convert_type({ type::t_float, 2, 1 })) - .add(args[1].base) - .add(args[1].base) - .add(0) // .x - .add(1) // .y - .result; - const spv::Id lod = add_instruction(spv::OpCompositeExtract, convert_type({ type::t_float, 1, 1 })) - .add(args[1].base) - .add(3) // .w - .result; - - return add_instruction(spv::OpImageSampleExplicitLod, convert_type(res_type)) - .add(args[0].base) - .add(xy) - .add(spv::ImageOperandsLodMask | spv::ImageOperandsOffsetMask) - .add(lod) - .add(args[2].base) - .result; - }) - -// ret tex2Dsize(s) -// ret tex2Dsize(s, lod) -DEFINE_INTRINSIC(tex2Dsize, 0, int2, sampler) -DEFINE_INTRINSIC(tex2Dsize, 1, int2, sampler, int) -IMPLEMENT_INTRINSIC_GLSL(tex2Dsize, 0, { - code += "textureSize(" + id_to_name(args[0].base) + ", 0)"; - }) -IMPLEMENT_INTRINSIC_GLSL(tex2Dsize, 1, { - code += "textureSize(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ')'; - }) -IMPLEMENT_INTRINSIC_HLSL(tex2Dsize, 0, { - if (_shader_model >= 40u) - code += id_to_name(args[0].base) + ".t.GetDimensions(" + id_to_name(res) + ".x, " + id_to_name(res) + ".y)"; - else - code += "int2(1.0 / " + id_to_name(args[0].base) + ".pixelsize)"; - }) -IMPLEMENT_INTRINSIC_HLSL(tex2Dsize, 1, { - if (_shader_model >= 40u) - code += "uint temp" + std::to_string(res) + "; " + id_to_name(args[0].base) + ".t.GetDimensions(" + id_to_name(args[1].base) + ", " + id_to_name(res) + ".x, " + id_to_name(res) + ".y, temp" + std::to_string(res) + ')'; - else - code += "int2(1.0 / " + id_to_name(args[0].base) + ".pixelsize) / exp2(" + id_to_name(args[1].base) + ')'; - }) -IMPLEMENT_INTRINSIC_SPIRV(tex2Dsize, 0, { - add_capability(spv::CapabilityImageQuery); - - const spv::Id image = add_instruction(spv::OpImage, convert_type({ type::t_texture })) - .add(args[0].base) - .result; - - return add_instruction(spv::OpImageQuerySize, convert_type(res_type)) - .add(image) - .result; - }) -IMPLEMENT_INTRINSIC_SPIRV(tex2Dsize, 1, { - add_capability(spv::CapabilityImageQuery); - - const spv::Id image = add_instruction(spv::OpImage, convert_type({ type::t_texture })) - .add(args[0].base) - .result; - - return add_instruction(spv::OpImageQuerySizeLod, convert_type(res_type)) - .add(image) - .add(args[1].base) - .result; - }) - -// ret tex2Dfetch(s, coords) -DEFINE_INTRINSIC(tex2Dfetch, 0, float4, sampler, int4) -IMPLEMENT_INTRINSIC_GLSL(tex2Dfetch, 0, { - code += "texelFetch(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ".xy - ivec2(vec2(0, 1.0 - 1.0 / exp2(float(" + id_to_name(args[1].base) + ".w))) * textureSize(" + id_to_name(args[0].base) + ", 0)), " + id_to_name(args[1].base) + ".w)"; - }) -IMPLEMENT_INTRINSIC_HLSL(tex2Dfetch, 0, { - if (_shader_model >= 40u) - code += id_to_name(args[0].base) + ".t.Load(" + id_to_name(args[1].base) + ".xyw)"; - else - code += "tex2Dlod(" + id_to_name(args[0].base) + ".s, float4((" + id_to_name(args[1].base) + ".xy * exp2(" + id_to_name(args[1].base) + ".w) + 0.5 /* half-pixel offset */) * " + id_to_name(args[0].base) + ".pixelsize, 0, " + id_to_name(args[1].base) + ".w))"; - }) -IMPLEMENT_INTRINSIC_SPIRV(tex2Dfetch, 0, { - const spv::Id xy = add_instruction(spv::OpVectorShuffle, convert_type({ type::t_int, 2, 1 })) - .add(args[1].base) - .add(args[1].base) - .add(0) // .x - .add(1) // .y - .result; - const spv::Id lod = add_instruction(spv::OpCompositeExtract, convert_type({ type::t_int, 1, 1 })) - .add(args[1].base) - .add(3) // .w - .result; - - const spv::Id image = add_instruction(spv::OpImage, convert_type({ type::t_texture })) - .add(args[0].base) - .result; - - return add_instruction(spv::OpImageFetch, convert_type(res_type)) - .add(image) - .add(xy) - .add(spv::ImageOperandsLodMask) - .add(lod) - .result; - }) - -#define COMMA , - -// ret tex2Dgather(s, coords, component) -DEFINE_INTRINSIC(tex2Dgather, 0, float4, sampler, float2, int) -IMPLEMENT_INTRINSIC_GLSL(tex2Dgather, 0, { - code += "textureGather(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + " * vec2(1.0, -1.0) + vec2(0.0, 1.0), " + id_to_name(args[2].base) + ')'; - }) -IMPLEMENT_INTRINSIC_HLSL(tex2Dgather, 0, { - if (_shader_model >= 50u) { - const char *const names[4] = { "GatherRed" COMMA "GatherGreen" COMMA "GatherBlue" COMMA "GatherAlpha" }; - for (unsigned int c = 0; c < 4; ++c) - code += id_to_name(args[2].base) + " == " + std::to_string(c) + " ? " + id_to_name(args[0].base) + ".t." + names[c] + '(' + id_to_name(args[0].base) + ".s, " + id_to_name(args[1].base) + ") : "; - } else if (_shader_model >= 40u) { - for (unsigned int c = 0; c < 4; ++c) - code += id_to_name(args[2].base) + " == " + std::to_string(c) + " ? float4(" + - id_to_name(args[0].base) + ".t.SampleLevel(" + id_to_name(args[0].base) + ".s, " + id_to_name(args[1].base) + ", 0, int2(0, 0))." + "rgba"[c] + ", " + - id_to_name(args[0].base) + ".t.SampleLevel(" + id_to_name(args[0].base) + ".s, " + id_to_name(args[1].base) + ", 0, int2(0, 1))." + "rgba"[c] + ", " + - id_to_name(args[0].base) + ".t.SampleLevel(" + id_to_name(args[0].base) + ".s, " + id_to_name(args[1].base) + ", 0, int2(1, 1))." + "rgba"[c] + ", " + - id_to_name(args[0].base) + ".t.SampleLevel(" + id_to_name(args[0].base) + ".s, " + id_to_name(args[1].base) + ", 0, int2(1, 0))." + "rgba"[c] + ") : "; - } else { - for (unsigned int c = 0; c < 4; ++c) - code += id_to_name(args[2].base) + " == " + std::to_string(c) + " ? float4(" - "tex2Dlod(" + id_to_name(args[0].base) + ".s, float4(" + id_to_name(args[1].base) + " + float2(0, 0) * s.pixelsize, 0, 0))." + "rgba"[c] + ", " - "tex2Dlod(" + id_to_name(args[0].base) + ".s, float4(" + id_to_name(args[1].base) + " + float2(0, 1) * s.pixelsize, 0, 0))." + "rgba"[c] + ", " - "tex2Dlod(" + id_to_name(args[0].base) + ".s, float4(" + id_to_name(args[1].base) + " + float2(1, 1) * s.pixelsize, 0, 0))." + "rgba"[c] + ", " - "tex2Dlod(" + id_to_name(args[0].base) + ".s, float4(" + id_to_name(args[1].base) + " + float2(1, 0) * s.pixelsize, 0, 0))." + "rgba"[c] + ") : "; - } - code += '0'; - }) -IMPLEMENT_INTRINSIC_SPIRV(tex2Dgather, 0, { - return add_instruction(spv::OpImageGather, convert_type(res_type)) - .add(args[0].base) - .add(args[1].base) - .add(args[2].base) - .add(spv::ImageOperandsMaskNone) - .result; - }) -// ret tex2Dgatheroffset(s, coords, offset, component) -DEFINE_INTRINSIC(tex2Dgatheroffset, 0, float4, sampler, float2, int2, int) -IMPLEMENT_INTRINSIC_GLSL(tex2Dgatheroffset, 0, { - code += "textureGatherOffset(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + " * vec2(1.0, -1.0) + vec2(0.0, 1.0), " + id_to_name(args[2].base) + " * ivec2(1, -1), " + id_to_name(args[3].base) + ')'; - }) -IMPLEMENT_INTRINSIC_HLSL(tex2Dgatheroffset, 0, { - if (_shader_model >= 50u) { - const char *const names[4] = { "GatherRed" COMMA "GatherGreen" COMMA "GatherBlue" COMMA "GatherAlpha" }; - for (unsigned int c = 0; c < 4; ++c) - code += id_to_name(args[3].base) + " == " + std::to_string(c) + " ? " + id_to_name(args[0].base) + ".t." + names[c] + '(' + id_to_name(args[0].base) + ".s, " + id_to_name(args[1].base) + ", " + id_to_name(args[2].base) + ") : "; - } else if (_shader_model >= 40u) { - for (unsigned int c = 0; c < 4; ++c) - code += id_to_name(args[3].base) + " == " + std::to_string(c) + " ? float4(" + - id_to_name(args[0].base) + ".t.SampleLevel(" + id_to_name(args[0].base) + ".s, " + id_to_name(args[1].base) + ", 0, " + id_to_name(args[2].base) + " + int2(0, 0))." + "rgba"[c] + ", " + - id_to_name(args[0].base) + ".t.SampleLevel(" + id_to_name(args[0].base) + ".s, " + id_to_name(args[1].base) + ", 0, " + id_to_name(args[2].base) + " + int2(0, 1))." + "rgba"[c] + ", " + - id_to_name(args[0].base) + ".t.SampleLevel(" + id_to_name(args[0].base) + ".s, " + id_to_name(args[1].base) + ", 0, " + id_to_name(args[2].base) + " + int2(1, 1))." + "rgba"[c] + ", " + - id_to_name(args[0].base) + ".t.SampleLevel(" + id_to_name(args[0].base) + ".s, " + id_to_name(args[1].base) + ", 0, " + id_to_name(args[2].base) + " + int2(1, 0))." + "rgba"[c] + ") : "; - } else { - for (unsigned int c = 0; c < 4; ++c) - code += id_to_name(args[3].base) + " == " + std::to_string(c) + " ? float4(" + - "tex2Dlod(" + id_to_name(args[0].base) + ".s, float4(" + id_to_name(args[1].base) + " + (" + id_to_name(args[2].base) + " + float2(0, 0)) * s.pixelsize, 0, 0))." + "rgba"[c] + ", " - "tex2Dlod(" + id_to_name(args[0].base) + ".s, float4(" + id_to_name(args[1].base) + " + (" + id_to_name(args[2].base) + " + float2(0, 1)) * s.pixelsize, 0, 0))." + "rgba"[c] + ", " - "tex2Dlod(" + id_to_name(args[0].base) + ".s, float4(" + id_to_name(args[1].base) + " + (" + id_to_name(args[2].base) + " + float2(1, 1)) * s.pixelsize, 0, 0))." + "rgba"[c] + ", " - "tex2Dlod(" + id_to_name(args[0].base) + ".s, float4(" + id_to_name(args[1].base) + " + (" + id_to_name(args[2].base) + " + float2(1, 0)) * s.pixelsize, 0, 0))." + "rgba"[c] + ')'; - } - code += '0'; - }) -IMPLEMENT_INTRINSIC_SPIRV(tex2Dgatheroffset, 0, { - add_capability(spv::CapabilityImageGatherExtended); - - return add_instruction(spv::OpImageGather, convert_type(res_type)) - .add(args[0].base) - .add(args[1].base) - .add(args[3].base) - .add(spv::ImageOperandsOffsetMask) - .add(args[2].base) - .result; - }) - -#undef DEFINE_INTRINSIC -#undef IMPLEMENT_INTRINSIC_GLSL -#undef IMPLEMENT_INTRINSIC_HLSL -#undef IMPLEMENT_INTRINSIC_SPIRV diff --git a/msvc/source/gui.cpp b/msvc/source/gui.cpp deleted file mode 100644 index 4493314..0000000 --- a/msvc/source/gui.cpp +++ /dev/null @@ -1,2313 +0,0 @@ -/** - * Copyright (C) 2014 Patrick Mours. All rights reserved. - * License: https://github.com/crosire/reshade#license - */ - -#if RESHADE_GUI - -#include "log.hpp" -#include "version.h" -#include "runtime.hpp" -#include "runtime_objects.hpp" -#include "input.hpp" -#include "ini_file.hpp" -#include "gui_widgets.hpp" -#include -#include -#include -#include - -extern volatile long g_network_traffic; -extern std::filesystem::path g_reshade_dll_path; -extern std::filesystem::path g_target_executable_path; -static char g_reshadegui_ini_path[260 * 3] = {}; - -const ImVec4 COLOR_RED = ImColor(240, 100, 100); -const ImVec4 COLOR_YELLOW = ImColor(204, 204, 0); - -void reshade::runtime::init_ui() -{ - (g_reshade_dll_path.parent_path() / "ReShadeGUI.ini").u8string() - .copy(g_reshadegui_ini_path, sizeof(g_reshadegui_ini_path)); - - // Default shortcut: Home - _menu_key_data[0] = 0x24; - _menu_key_data[1] = false; - _menu_key_data[2] = false; - _menu_key_data[3] = false; - - _variable_editor_height = 300; - - _imgui_context = ImGui::CreateContext(); - - auto &imgui_io = _imgui_context->IO; - auto &imgui_style = _imgui_context->Style; - imgui_io.IniFilename = nullptr; - imgui_io.KeyMap[ImGuiKey_Tab] = 0x09; // VK_TAB - imgui_io.KeyMap[ImGuiKey_LeftArrow] = 0x25; // VK_LEFT - imgui_io.KeyMap[ImGuiKey_RightArrow] = 0x27; // VK_RIGHT - imgui_io.KeyMap[ImGuiKey_UpArrow] = 0x26; // VK_UP - imgui_io.KeyMap[ImGuiKey_DownArrow] = 0x28; // VK_DOWN - imgui_io.KeyMap[ImGuiKey_PageUp] = 0x21; // VK_PRIOR - imgui_io.KeyMap[ImGuiKey_PageDown] = 0x22; // VK_NEXT - imgui_io.KeyMap[ImGuiKey_Home] = 0x24; // VK_HOME - imgui_io.KeyMap[ImGuiKey_End] = 0x23; // VK_END - imgui_io.KeyMap[ImGuiKey_Insert] = 0x2D; // VK_INSERT - imgui_io.KeyMap[ImGuiKey_Delete] = 0x2E; // VK_DELETE - imgui_io.KeyMap[ImGuiKey_Backspace] = 0x08; // VK_BACK - imgui_io.KeyMap[ImGuiKey_Space] = 0x20; // VK_SPACE - imgui_io.KeyMap[ImGuiKey_Enter] = 0x0D; // VK_RETURN - imgui_io.KeyMap[ImGuiKey_Escape] = 0x1B; // VK_ESCAPE - imgui_io.KeyMap[ImGuiKey_A] = 'A'; - imgui_io.KeyMap[ImGuiKey_C] = 'C'; - imgui_io.KeyMap[ImGuiKey_V] = 'V'; - imgui_io.KeyMap[ImGuiKey_X] = 'X'; - imgui_io.KeyMap[ImGuiKey_Y] = 'Y'; - imgui_io.KeyMap[ImGuiKey_Z] = 'Z'; - imgui_io.ConfigFlags = ImGuiConfigFlags_DockingEnable | ImGuiConfigFlags_NavEnableKeyboard; - imgui_io.BackendFlags = ImGuiBackendFlags_HasMouseCursors; - - // Disable rounding by default - imgui_style.GrabRounding = 0.0f; - imgui_style.FrameRounding = 0.0f; - imgui_style.ChildRounding = 0.0f; - imgui_style.ScrollbarRounding = 0.0f; - imgui_style.WindowRounding = 0.0f; - imgui_style.WindowBorderSize = 0.0f; - - ImGui::SetCurrentContext(nullptr); - - subscribe_to_ui("Home", [this]() { draw_overlay_menu_home(); }); - subscribe_to_ui("Settings", [this]() { draw_overlay_menu_settings(); }); - subscribe_to_ui("Statistics", [this]() { draw_overlay_menu_statistics(); }); - subscribe_to_ui("Log", [this]() { draw_overlay_menu_log(); }); - subscribe_to_ui("About", [this]() { draw_overlay_menu_about(); }); - - _load_config_callables.push_back([this](const ini_file &config) { - bool save_imgui_window_state = false; - - config.get("INPUT", "KeyMenu", _menu_key_data); - config.get("INPUT", "InputProcessing", _input_processing_mode); - - config.get("GENERAL", "ShowClock", _show_clock); - config.get("GENERAL", "ShowFPS", _show_fps); - config.get("GENERAL", "ShowFrameTime", _show_frametime); - config.get("GENERAL", "ShowScreenshotMessage", _show_screenshot_message); - config.get("GENERAL", "ClockFormat", _clock_format); - config.get("GENERAL", "NoFontScaling", _no_font_scaling); - config.get("GENERAL", "SaveWindowState", save_imgui_window_state); - config.get("GENERAL", "TutorialProgress", _tutorial_index); - config.get("GENERAL", "NewVariableUI", _variable_editor_tabs); - - config.get("STYLE", "Alpha", _imgui_context->Style.Alpha); - config.get("STYLE", "GrabRounding", _imgui_context->Style.GrabRounding); - config.get("STYLE", "FrameRounding", _imgui_context->Style.FrameRounding); - config.get("STYLE", "ChildRounding", _imgui_context->Style.ChildRounding); - config.get("STYLE", "PopupRounding", _imgui_context->Style.PopupRounding); - config.get("STYLE", "WindowRounding", _imgui_context->Style.WindowRounding); - config.get("STYLE", "ScrollbarRounding", _imgui_context->Style.ScrollbarRounding); - config.get("STYLE", "TabRounding", _imgui_context->Style.TabRounding); - config.get("STYLE", "FPSScale", _fps_scale); - config.get("STYLE", "ColFPSText", _fps_col); - config.get("STYLE", "Font", _font); - config.get("STYLE", "FontSize", _font_size); - config.get("STYLE", "EditorFont", _editor_font); - config.get("STYLE", "EditorFontSize", _editor_font_size); - config.get("STYLE", "StyleIndex", _style_index); - config.get("STYLE", "EditorStyleIndex", _editor_style_index); - - _imgui_context->IO.IniFilename = save_imgui_window_state ? g_reshadegui_ini_path : nullptr; - - // For compatibility with older versions, set the alpha value if it is missing - if (_fps_col[3] == 0.0f) _fps_col[3] = 1.0f; - - ImVec4 *const colors = _imgui_context->Style.Colors; - switch (_style_index) - { - case 0: - ImGui::StyleColorsDark(&_imgui_context->Style); - break; - case 1: - ImGui::StyleColorsLight(&_imgui_context->Style); - break; - case 2: - colors[ImGuiCol_Text] = ImVec4(0.862745f, 0.862745f, 0.862745f, 1.00f); - colors[ImGuiCol_TextDisabled] = ImVec4(0.862745f, 0.862745f, 0.862745f, 0.58f); - colors[ImGuiCol_WindowBg] = ImVec4(0.117647f, 0.117647f, 0.117647f, 1.00f); - colors[ImGuiCol_ChildBg] = ImVec4(0.156863f, 0.156863f, 0.156863f, 0.00f); - colors[ImGuiCol_Border] = ImVec4(0.862745f, 0.862745f, 0.862745f, 0.30f); - colors[ImGuiCol_FrameBg] = ImVec4(0.156863f, 0.156863f, 0.156863f, 1.00f); - colors[ImGuiCol_FrameBgHovered] = ImVec4(0.392157f, 0.588235f, 0.941176f, 0.470588f); - colors[ImGuiCol_FrameBgActive] = ImVec4(0.392157f, 0.588235f, 0.941176f, 0.588235f); - colors[ImGuiCol_TitleBg] = ImVec4(0.392157f, 0.588235f, 0.941176f, 0.45f); - colors[ImGuiCol_TitleBgCollapsed] = ImVec4(0.392157f, 0.588235f, 0.941176f, 0.35f); - colors[ImGuiCol_TitleBgActive] = ImVec4(0.392157f, 0.588235f, 0.941176f, 0.58f); - colors[ImGuiCol_MenuBarBg] = ImVec4(0.156863f, 0.156863f, 0.156863f, 0.57f); - colors[ImGuiCol_ScrollbarBg] = ImVec4(0.156863f, 0.156863f, 0.156863f, 1.00f); - colors[ImGuiCol_ScrollbarGrab] = ImVec4(0.392157f, 0.588235f, 0.941176f, 0.31f); - colors[ImGuiCol_ScrollbarGrabHovered] = ImVec4(0.392157f, 0.588235f, 0.941176f, 0.78f); - colors[ImGuiCol_ScrollbarGrabActive] = ImVec4(0.392157f, 0.588235f, 0.941176f, 1.00f); - colors[ImGuiCol_PopupBg] = ImVec4(0.117647f, 0.117647f, 0.117647f, 0.92f); - colors[ImGuiCol_CheckMark] = ImVec4(0.392157f, 0.588235f, 0.941176f, 0.80f); - colors[ImGuiCol_SliderGrab] = ImVec4(0.392157f, 0.588235f, 0.941176f, 0.784314f); - colors[ImGuiCol_SliderGrabActive] = ImVec4(0.392157f, 0.588235f, 0.941176f, 1.00f); - colors[ImGuiCol_Button] = ImVec4(0.392157f, 0.588235f, 0.941176f, 0.44f); - colors[ImGuiCol_ButtonHovered] = ImVec4(0.392157f, 0.588235f, 0.941176f, 0.86f); - colors[ImGuiCol_ButtonActive] = ImVec4(0.392157f, 0.588235f, 0.941176f, 1.00f); - colors[ImGuiCol_Header] = ImVec4(0.392157f, 0.588235f, 0.941176f, 0.76f); - colors[ImGuiCol_HeaderHovered] = ImVec4(0.392157f, 0.588235f, 0.941176f, 0.86f); - colors[ImGuiCol_HeaderActive] = ImVec4(0.392157f, 0.588235f, 0.941176f, 1.00f); - colors[ImGuiCol_Separator] = ImVec4(0.862745f, 0.862745f, 0.862745f, 0.32f); - colors[ImGuiCol_SeparatorHovered] = ImVec4(0.862745f, 0.862745f, 0.862745f, 0.78f); - colors[ImGuiCol_SeparatorActive] = ImVec4(0.862745f, 0.862745f, 0.862745f, 1.00f); - colors[ImGuiCol_ResizeGrip] = ImVec4(0.392157f, 0.588235f, 0.941176f, 0.20f); - colors[ImGuiCol_ResizeGripHovered] = ImVec4(0.392157f, 0.588235f, 0.941176f, 0.78f); - colors[ImGuiCol_ResizeGripActive] = ImVec4(0.392157f, 0.588235f, 0.941176f, 1.00f); - colors[ImGuiCol_Tab] = colors[ImGuiCol_Button]; - colors[ImGuiCol_TabActive] = colors[ImGuiCol_ButtonActive]; - colors[ImGuiCol_TabHovered] = colors[ImGuiCol_ButtonHovered]; - colors[ImGuiCol_TabUnfocused] = ImLerp(colors[ImGuiCol_Tab], colors[ImGuiCol_TitleBg], 0.80f); - colors[ImGuiCol_TabUnfocusedActive] = ImLerp(colors[ImGuiCol_TabActive], colors[ImGuiCol_TitleBg], 0.40f); - colors[ImGuiCol_DockingPreview] = colors[ImGuiCol_Header] * ImVec4(1.0f, 1.0f, 1.0f, 0.7f); - colors[ImGuiCol_DockingEmptyBg] = ImVec4(0.20f, 0.20f, 0.20f, 1.00f); - colors[ImGuiCol_PlotLines] = ImVec4(0.862745f, 0.862745f, 0.862745f, 0.63f); - colors[ImGuiCol_PlotLinesHovered] = ImVec4(0.392157f, 0.588235f, 0.941176f, 1.00f); - colors[ImGuiCol_PlotHistogram] = ImVec4(0.862745f, 0.862745f, 0.862745f, 0.63f); - colors[ImGuiCol_PlotHistogramHovered] = ImVec4(0.392157f, 0.588235f, 0.941176f, 1.00f); - colors[ImGuiCol_TextSelectedBg] = ImVec4(0.392157f, 0.588235f, 0.941176f, 0.43f); - break; - default: - for (ImGuiCol i = 0; i < ImGuiCol_COUNT; i++) - config.get("STYLE", ImGui::GetStyleColorName(i), (float(&)[4])colors[i]); - break; - } - - switch (_editor_style_index) - { - case 0: - _editor.set_palette({ // Dark - 0xffffffff, 0xffd69c56, 0xff00ff00, 0xff7070e0, 0xffffffff, 0xff409090, 0xffaaaaaa, - 0xff9bc64d, 0xffc040a0, 0xff206020, 0xff406020, 0xff101010, 0xffe0e0e0, 0x80a06020, - 0x800020ff, 0x8000ffff, 0xff707000, 0x40000000, 0x40808080, 0x40a0a0a0 }); - break; - case 1: - _editor.set_palette({ // Light - 0xff000000, 0xffff0c06, 0xff008000, 0xff2020a0, 0xff000000, 0xff409090, 0xff404040, - 0xff606010, 0xffc040a0, 0xff205020, 0xff405020, 0xffffffff, 0xff000000, 0x80600000, - 0xa00010ff, 0x8000ffff, 0xff505000, 0x40000000, 0x40808080, 0x40000000 }); - break; - default: - ImVec4 value; // Note: This expects that all colors exist in the config - for (ImGuiCol i = 0; i < imgui_code_editor::color_palette_max; i++) - config.get("STYLE", imgui_code_editor::get_palette_color_name(i), (float(&)[4])value), - _editor.get_palette_index(i) = ImGui::ColorConvertFloat4ToU32(value); - break; - } - }); - _save_config_callables.push_back([this](ini_file &config) { - config.set("INPUT", "KeyMenu", _menu_key_data); - config.set("INPUT", "InputProcessing", _input_processing_mode); - - config.set("GENERAL", "ShowClock", _show_clock); - config.set("GENERAL", "ShowFPS", _show_fps); - config.set("GENERAL", "ShowFrameTime", _show_frametime); - config.set("GENERAL", "ShowScreenshotMessage", _show_screenshot_message); - config.set("GENERAL", "ClockFormat", _clock_format); - config.set("GENERAL", "NoFontScaling", _no_font_scaling); - config.set("GENERAL", "SaveWindowState", _imgui_context->IO.IniFilename != nullptr); - config.set("GENERAL", "TutorialProgress", _tutorial_index); - config.set("GENERAL", "NewVariableUI", _variable_editor_tabs); - - config.set("STYLE", "Alpha", _imgui_context->Style.Alpha); - config.set("STYLE", "GrabRounding", _imgui_context->Style.GrabRounding); - config.set("STYLE", "FrameRounding", _imgui_context->Style.FrameRounding); - config.set("STYLE", "ChildRounding", _imgui_context->Style.ChildRounding); - config.set("STYLE", "PopupRounding", _imgui_context->Style.PopupRounding); - config.set("STYLE", "WindowRounding", _imgui_context->Style.WindowRounding); - config.set("STYLE", "ScrollbarRounding", _imgui_context->Style.ScrollbarRounding); - config.set("STYLE", "TabRounding", _imgui_context->Style.TabRounding); - config.set("STYLE", "FPSScale", _fps_scale); - config.set("STYLE", "ColFPSText", _fps_col); - config.set("STYLE", "Font", _font); - config.set("STYLE", "FontSize", _font_size); - config.set("STYLE", "EditorFont", _editor_font); - config.set("STYLE", "EditorFontSize", _editor_font_size); - config.set("STYLE", "StyleIndex", _style_index); - config.set("STYLE", "EditorStyleIndex", _editor_style_index); - - if (_style_index > 2) - { - for (ImGuiCol i = 0; i < ImGuiCol_COUNT; i++) - config.set("STYLE", ImGui::GetStyleColorName(i), (const float(&)[4])_imgui_context->Style.Colors[i]); - } - - if (_editor_style_index > 1) - { - ImVec4 value; - for (ImGuiCol i = 0; i < imgui_code_editor::color_palette_max; i++) - value = ImGui::ColorConvertU32ToFloat4(_editor.get_palette_index(i)), - config.set("STYLE", imgui_code_editor::get_palette_color_name(i), (const float(&)[4])value); - } - }); -} -void reshade::runtime::deinit_ui() -{ - ImGui::DestroyContext(_imgui_context); -} - -void reshade::runtime::build_font_atlas() -{ - ImGui::SetCurrentContext(_imgui_context); - - const auto atlas = _imgui_context->IO.Fonts; - - atlas->Clear(); - - for (unsigned int i = 0; i < 2; ++i) - { - ImFontConfig cfg; - cfg.SizePixels = static_cast(i == 0 ? _font_size : _editor_font_size); - - const std::filesystem::path &font_path = i == 0 ? _font : _editor_font; - - if (std::error_code ec; !std::filesystem::is_regular_file(font_path, ec) || !atlas->AddFontFromFileTTF(font_path.u8string().c_str(), cfg.SizePixels)) - atlas->AddFontDefault(&cfg); // Use default font if custom font failed to load or does not exist - } - - // If unable to build font atlas due to an invalid font, revert to the default font - if (!atlas->Build()) - { - _font.clear(); - _editor_font.clear(); - - atlas->Clear(); - - for (unsigned int i = 0; i < 2; ++i) - { - ImFontConfig cfg; - cfg.SizePixels = static_cast(i == 0 ? _font_size : _editor_font_size); - - atlas->AddFontDefault(&cfg); - atlas->AddFontDefault(&cfg); - } - } - - destroy_font_atlas(); - - _show_splash = true; - _rebuild_font_atlas = false; - - int width, height; - unsigned char *pixels; - atlas->GetTexDataAsRGBA32(&pixels, &width, &height); - - ImGui::SetCurrentContext(nullptr); - - _imgui_font_atlas = std::make_unique(); - _imgui_font_atlas->width = width; - _imgui_font_atlas->height = height; - _imgui_font_atlas->format = reshadefx::texture_format::rgba8; - _imgui_font_atlas->unique_name = "ImGUI Font Atlas"; - if (init_texture(*_imgui_font_atlas)) - upload_texture(*_imgui_font_atlas, pixels); -} -void reshade::runtime::destroy_font_atlas() -{ - _imgui_font_atlas.reset(); -} - -void reshade::runtime::draw_ui() -{ - const bool show_splash = _show_splash && (_reload_remaining_effects != std::numeric_limits::max() || !_reload_compile_queue.empty() || (_last_present_time - _last_reload_time) < std::chrono::seconds(5)); - const bool show_screenshot_message = _show_screenshot_message && _last_present_time - _last_screenshot_time < std::chrono::seconds(_screenshot_save_success ? 3 : 5); - - if (_show_menu && !_ignore_shortcuts && !_imgui_context->IO.NavVisible && _input->is_key_pressed(0x1B /* VK_ESCAPE */)) - _show_menu = false; // Close when pressing the escape button and not currently navigating with the keyboard - else if (!_ignore_shortcuts && _input->is_key_pressed(_menu_key_data)) - _show_menu = !_show_menu; - - _ignore_shortcuts = false; - _effects_expanded_state &= 2; - - if (_rebuild_font_atlas) - build_font_atlas(); - if (_reload_remaining_effects != std::numeric_limits::max()) - _selected_effect = std::numeric_limits::max(), - _selected_effect_changed = true, // Force editor to clear text after effects where reloaded - _effect_filter_buffer[0] = '\0'; // And reset filter too, since the list of techniques might have changed - - ImGui::SetCurrentContext(_imgui_context); - auto &imgui_io = _imgui_context->IO; - imgui_io.DeltaTime = _last_frame_duration.count() * 1e-9f; - imgui_io.MouseDrawCursor = _show_menu; - imgui_io.DisplaySize.x = static_cast(_width); - imgui_io.DisplaySize.y = static_cast(_height); - imgui_io.Fonts->TexID = _imgui_font_atlas->impl.get(); - - // Scale mouse position in case render resolution does not match the window size - imgui_io.MousePos.x = _input->mouse_position_x() * (imgui_io.DisplaySize.x / _window_width); - imgui_io.MousePos.y = _input->mouse_position_y() * (imgui_io.DisplaySize.y / _window_height); - - // Add wheel delta to the current absolute mouse wheel position - imgui_io.MouseWheel += _input->mouse_wheel_delta(); - - // Update all the button states - imgui_io.KeyAlt = _input->is_key_down(0x12); // VK_MENU - imgui_io.KeyCtrl = _input->is_key_down(0x11); // VK_CONTROL - imgui_io.KeyShift = _input->is_key_down(0x10); // VK_SHIFT - for (unsigned int i = 0; i < 256; i++) - imgui_io.KeysDown[i] = _input->is_key_down(i); - for (unsigned int i = 0; i < 5; i++) - imgui_io.MouseDown[i] = _input->is_mouse_button_down(i); - for (wchar_t c : _input->text_input()) - imgui_io.AddInputCharacter(c); - - ImGui::NewFrame(); - - ImVec2 viewport_offset = ImVec2(0, 0); - - // Create ImGui widgets and windows - if (show_splash || show_screenshot_message || (!_show_menu && _tutorial_index == 0)) - { - ImGui::SetNextWindowPos(ImVec2(10, 10)); - ImGui::SetNextWindowSize(ImVec2(imgui_io.DisplaySize.x - 20.0f, 0.0f)); - ImGui::PushStyleVar(ImGuiStyleVar_Alpha, 1.0f); - ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.862745f, 0.862745f, 0.862745f, 1.0f)); - ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0.117647f, 0.117647f, 0.117647f, 0.7f)); - ImGui::Begin("Splash Screen", nullptr, - ImGuiWindowFlags_NoDecoration | - ImGuiWindowFlags_NoNav | - ImGuiWindowFlags_NoMove | - ImGuiWindowFlags_NoInputs | - ImGuiWindowFlags_NoSavedSettings | - ImGuiWindowFlags_NoDocking | - ImGuiWindowFlags_NoFocusOnAppearing); - - if (show_screenshot_message) - { - if (!_screenshot_save_success) - ImGui::TextColored(COLOR_RED, "Unable to save screenshot because path doesn't exist: %s", _screenshot_path.u8string().c_str()); - else - ImGui::Text("Screenshot successfully saved to %s", _last_screenshot_file.u8string().c_str()); - } - else - { - ImGui::TextUnformatted("ReShade " VERSION_STRING_FILE " by crosire"); - - if (_needs_update) - { - ImGui::TextColored(COLOR_YELLOW, - "An update is available! Please visit https://reshade.me and install the new version (v%lu.%lu.%lu).", - _latest_version[0], _latest_version[1], _latest_version[2]); - } - else - { - ImGui::TextUnformatted("Visit https://reshade.me for news, updates, shaders and discussion."); - } - - ImGui::Spacing(); - - if (_reload_remaining_effects != 0 && _reload_remaining_effects != std::numeric_limits::max()) - { - ImGui::ProgressBar(1.0f - _reload_remaining_effects / float(_reload_total_effects), ImVec2(-1, 0), ""); - ImGui::SameLine(15); - ImGui::Text( - "Loading (%zu effects remaining) ... " - "This might take a while. The application could become unresponsive for some time.", - _reload_remaining_effects.load()); - } - else if (!_reload_compile_queue.empty()) - { - ImGui::ProgressBar(1.0f - _reload_compile_queue.size() / float(_reload_total_effects), ImVec2(-1, 0), ""); - ImGui::SameLine(15); - ImGui::Text( - "Compiling (%zu effects remaining) ... " - "This might take a while. The application could become unresponsive for some time.", - _reload_compile_queue.size()); - } - else if (_tutorial_index == 0) - { - ImGui::Text( - "ReShade is now installed successfully! Press '%s' to start the tutorial.", input::key_name(_menu_key_data).c_str()); - } - else - { - ImGui::Text( - "Press '%s' to open the configuration menu.", input::key_name(_menu_key_data).c_str()); - ImGui::Dummy(ImVec2(0, _imgui_context->Style.FramePadding.y - 1.0f)); // Add small offset to align with progress bar from reload screen - } - - if (!_last_reload_successful) - { - ImGui::Spacing(); - ImGui::TextColored(COLOR_RED, - "There were errors compiling some shaders. " - "Open the configuration menu and switch to the 'Statistics' tab for more details."); - } - } - - viewport_offset.y += ImGui::GetWindowHeight() + 10; // Add small space between windows - - ImGui::End(); - ImGui::PopStyleColor(2); - ImGui::PopStyleVar(); - } - else if (_show_clock || _show_fps || _show_frametime) - { - ImGui::SetNextWindowPos(ImVec2(imgui_io.DisplaySize.x - 200.0f, 5)); - ImGui::SetNextWindowSize(ImVec2(200.0f, 200.0f)); - ImGui::PushStyleColor(ImGuiCol_Text, (const ImVec4 &)_fps_col); - ImGui::Begin("FPS", nullptr, - ImGuiWindowFlags_NoDecoration | - ImGuiWindowFlags_NoNav | - ImGuiWindowFlags_NoMove | - ImGuiWindowFlags_NoInputs | - ImGuiWindowFlags_NoSavedSettings | - ImGuiWindowFlags_NoDocking | - ImGuiWindowFlags_NoFocusOnAppearing | - ImGuiWindowFlags_NoBackground); - - ImGui::SetWindowFontScale(_fps_scale); - - char temp[512]; - - if (_show_clock) - { - const int hour = _date[3] / 3600; - const int minute = (_date[3] - hour * 3600) / 60; - const int seconds = _date[3] - hour * 3600 - minute * 60; - - ImFormatString(temp, sizeof(temp), _clock_format != 0 ? " %02u:%02u:%02u" : " %02u:%02u", hour, minute, seconds); - ImGui::SetCursorPosX(ImGui::GetWindowContentRegionWidth() - ImGui::CalcTextSize(temp).x); - ImGui::TextUnformatted(temp); - } - if (_show_fps) - { - ImFormatString(temp, sizeof(temp), "%.0f fps", imgui_io.Framerate); - ImGui::SetCursorPosX(ImGui::GetWindowContentRegionWidth() - ImGui::CalcTextSize(temp).x); - ImGui::TextUnformatted(temp); - } - if (_show_frametime) - { - ImFormatString(temp, sizeof(temp), "%5.2f ms", 1000.0f / imgui_io.Framerate); - ImGui::SetCursorPosX(ImGui::GetWindowContentRegionWidth() - ImGui::CalcTextSize(temp).x); - ImGui::TextUnformatted(temp); - } - - ImGui::End(); - ImGui::PopStyleColor(); - } - - if (_show_menu && _reload_remaining_effects == std::numeric_limits::max()) - { - // Change font size if user presses the control key and moves the mouse wheel - if (imgui_io.KeyCtrl && imgui_io.MouseWheel != 0 && !_no_font_scaling) - { - _font_size = ImClamp(_font_size + static_cast(imgui_io.MouseWheel), 8, 32); - _editor_font_size = ImClamp(_editor_font_size + static_cast(imgui_io.MouseWheel), 8, 32); - _rebuild_font_atlas = true; - save_config(); - } - - const ImGuiID root_space_id = ImGui::GetID("Dockspace"); - const ImGuiViewport *const viewport = ImGui::GetMainViewport(); - - // Set up default dock layout if this was not done yet - const bool init_window_layout = !ImGui::DockBuilderGetNode(root_space_id); - if (init_window_layout) - { - // Add the root node - ImGui::DockBuilderAddNode(root_space_id, ImGuiDockNodeFlags_Dockspace); - ImGui::DockBuilderSetNodeSize(root_space_id, viewport->Size); - - // Split root node into two spaces - ImGuiID main_space_id = 0; - ImGuiID right_space_id = 0; - ImGui::DockBuilderSplitNode(root_space_id, ImGuiDir_Left, 0.35f, &main_space_id, &right_space_id); - - // Attach most windows to the main dock space - for (const auto &widget : _menu_callables) - ImGui::DockBuilderDockWindow(widget.first.c_str(), main_space_id); - - // Attach editor window to the remaining dock space - ImGui::DockBuilderDockWindow("###editor", right_space_id); - - // Commit the layout - ImGui::DockBuilderFinish(root_space_id); - } - - ImGui::SetNextWindowPos(viewport->Pos + viewport_offset); - ImGui::SetNextWindowSize(viewport->Size - viewport_offset); - ImGui::SetNextWindowViewport(viewport->ID); - ImGui::Begin("Viewport", nullptr, - ImGuiWindowFlags_NoDecoration | - ImGuiWindowFlags_NoNav | - ImGuiWindowFlags_NoMove | - ImGuiWindowFlags_NoDocking | // This is the background viewport, the docking space is a child of it - ImGuiWindowFlags_NoFocusOnAppearing | - ImGuiWindowFlags_NoBringToFrontOnFocus | - ImGuiWindowFlags_NoBackground); - ImGui::DockSpace(root_space_id, ImVec2(0, 0), ImGuiDockNodeFlags_PassthruDockspace); - ImGui::End(); - - for (const auto &widget : _menu_callables) - { - if (ImGui::Begin(widget.first.c_str(), nullptr, ImGuiWindowFlags_NoFocusOnAppearing)) // No focus so that window state is preserved between opening/closing the UI - widget.second(); - ImGui::End(); - } - - if (_show_code_editor) - { - const std::string title = _selected_effect < _loaded_effects.size() ? - "Editing " + _loaded_effects[_selected_effect].source_file.filename().u8string() + " ###editor" : "Viewing code###editor"; - - if (ImGui::Begin(title.c_str(), &_show_code_editor)) - draw_code_editor(); - ImGui::End(); - } - - for (auto it = _texture_previews.begin(); it != _texture_previews.end();) - { - const texture *texture = *it; - - bool open = true; - char window_name[64]; - sprintf_s(window_name, "%s##%p", texture->unique_name.c_str(), *it); - - ImGui::Begin(window_name, &open, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoDocking); - imgui_image_with_checkerboard_background(texture->impl.get(), ImVec2(std::max(texture->width * 0.5f, 500.0f), std::max(texture->height * 0.5f, texture->height * 500.0f / texture->width))); - ImGui::End(); - - if (!open) // Close window by removing it from the list so it is not rendered next frame - it = _texture_previews.erase(it); - else ++it; - } - } - - // Render ImGui widgets and windows - ImGui::Render(); - - _input->block_mouse_input(_input_processing_mode != 0 && _show_menu && (imgui_io.WantCaptureMouse || _input_processing_mode == 2)); - _input->block_keyboard_input(_input_processing_mode != 0 && _show_menu && (imgui_io.WantCaptureKeyboard || _input_processing_mode == 2)); - - if (const auto draw_data = ImGui::GetDrawData(); draw_data != nullptr && draw_data->CmdListsCount != 0 && draw_data->TotalVtxCount != 0) - { - render_imgui_draw_data(draw_data); - } -} - -void reshade::runtime::draw_overlay_menu_home() -{ - if (!_effects_enabled) - ImGui::Text("Effects are disabled. Press '%s' to enable them again.", input::key_name(_effects_key_data).c_str()); - - const char *tutorial_text = - "Welcome! Since this is the first time you start ReShade, we'll go through a quick tutorial covering the most important features.\n\n" - "Before we continue: If you have difficulties reading this text, press the 'Ctrl' key and adjust the font size with your mouse wheel. " - "The window size is variable as well, just grab the bottom right corner and move it around.\n\n" - "You can also use the keyboard for navigation in case mouse input does not work. Use the arrow keys to navigate, space bar to confirm an action or enter a control and the 'Esc' key to leave a control. " - "Press 'Ctrl + Tab' to switch between tabs and windows.\n\n" - "Click on the 'Continue' button to continue the tutorial."; - - // It is not possible to follow some of the tutorial steps while performance mode is active, so skip them - if (_performance_mode && _tutorial_index <= 3) - _tutorial_index = 4; - - if (_tutorial_index > 0) - { - if (_tutorial_index == 1) - { - tutorial_text = - "This is the preset selection. All changes will be saved to the selected file.\n\n" - "Click on the '+' button to name and add a new one.\n" - "Make sure you always have a preset selected here before starting to tweak any values later, or else your changes won't be saved!"; - - ImGui::PushStyleColor(ImGuiCol_FrameBg, COLOR_RED); - ImGui::PushStyleColor(ImGuiCol_Button, COLOR_RED); - } - - draw_preset_explorer(); - - if (_tutorial_index == 1) - ImGui::PopStyleColor(2); - } - - if (_tutorial_index > 1) - { - ImGui::Spacing(); - ImGui::Separator(); - ImGui::Spacing(); - - const bool show_clear_button = strcmp(_effect_filter_buffer, "Search") != 0 && _effect_filter_buffer[0] != '\0'; - ImGui::PushItemWidth((_variable_editor_tabs ? -130.0f : -260.0f) - (show_clear_button ? ImGui::GetFrameHeight() + _imgui_context->Style.ItemSpacing.x : 0)); - - if (ImGui::InputText("##filter", _effect_filter_buffer, sizeof(_effect_filter_buffer), ImGuiInputTextFlags_AutoSelectAll)) - { - _effects_expanded_state = 3; - - if (_effect_filter_buffer[0] == '\0') - { - // Reset visibility state - for (technique &technique : _techniques) - technique.hidden = technique.annotation_as_int("hidden") != 0; - } - else - { - const std::string filter = _effect_filter_buffer; - - for (technique &technique : _techniques) - technique.hidden = technique.annotation_as_int("hidden") != 0 || - std::search(technique.name.begin(), technique.name.end(), filter.begin(), filter.end(), - [](auto c1, auto c2) { return tolower(c1) == tolower(c2); }) == technique.name.end() && _loaded_effects[technique.effect_index].source_file.filename().u8string().find(filter) == std::string::npos; - } - } - else if (!ImGui::IsItemActive() && _effect_filter_buffer[0] == '\0') - { - strcpy(_effect_filter_buffer, "Search"); - } - - ImGui::PopItemWidth(); - - ImGui::SameLine(); - - if (show_clear_button && ImGui::Button("X", ImVec2(ImGui::GetFrameHeight(), 0))) - { - strcpy(_effect_filter_buffer, "Search"); - // Reset visibility state - for (technique &technique : _techniques) - technique.hidden = technique.annotation_as_int("hidden") != 0; - } - - ImGui::SameLine(); - - if (ImGui::Button("Active to top", ImVec2(130 - _imgui_context->Style.ItemSpacing.x, 0))) - { - for (auto i = _techniques.begin(); i != _techniques.end(); ++i) - { - if (!i->enabled && i->toggle_key_data[0] == 0) - { - for (auto k = i + 1; k != _techniques.end(); ++k) - { - if (k->enabled || k->toggle_key_data[0] != 0) - { - std::iter_swap(i, k); - break; - } - } - } - } - - if (const auto it = std::find_if_not(_techniques.begin(), _techniques.end(), [](const reshade::technique &a) { - return a.enabled || a.toggle_key_data[0] != 0; - }); it != _techniques.end()) - { - std::stable_sort(it, _techniques.end(), [](const reshade::technique &lhs, const reshade::technique &rhs) { - std::string lhs_label(lhs.annotation_as_string("ui_label")); - if (lhs_label.empty()) lhs_label = lhs.name; - std::transform(lhs_label.begin(), lhs_label.end(), lhs_label.begin(), tolower); - std::string rhs_label(rhs.annotation_as_string("ui_label")); - if (rhs_label.empty()) rhs_label = rhs.name; - std::transform(rhs_label.begin(), rhs_label.end(), rhs_label.begin(), tolower); - return lhs_label < rhs_label; - }); - } - - save_current_preset(); - } - - ImGui::SameLine(); - - if (ImGui::Button(_effects_expanded_state & 2 ? "Collapse all" : "Expand all", ImVec2(130 - _imgui_context->Style.ItemSpacing.x, 0))) - _effects_expanded_state = (~_effects_expanded_state & 2) | 1; - - if (_tutorial_index == 2) - { - tutorial_text = - "This is the list of techniques. It contains all techniques in the effect files (*.fx) that were found in the effect search paths as specified in the settings.\n" - "Enter text in the box at the top to filter it and search for specific techniques.\n\n" - "Click on a technique to enable or disable it or drag it to a new location in the list to change the order in which the effects are applied.\n" - "Use the right mouse button and click on an item to open the context menu with additional options.\n\n"; - - ImGui::PushStyleColor(ImGuiCol_Border, COLOR_RED); - } - - ImGui::Spacing(); - - const float bottom_height = _performance_mode ? ImGui::GetFrameHeightWithSpacing() + _imgui_context->Style.ItemSpacing.y : (_variable_editor_height + (_tutorial_index == 3 ? 175 : 0)); - - if (ImGui::BeginChild("##techniques", ImVec2(0, -bottom_height), true)) - draw_overlay_technique_editor(); - ImGui::EndChild(); - - if (_tutorial_index == 2) - ImGui::PopStyleColor(); - } - - if (_tutorial_index > 2 && !_performance_mode) - { - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0, 0)); - ImGui::ButtonEx("##splitter", ImVec2(ImGui::GetContentRegionAvailWidth(), 5)); - ImGui::PopStyleVar(); - - if (ImGui::IsItemHovered()) - ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeNS); - if (ImGui::IsItemActive()) - _variable_editor_height -= _imgui_context->IO.MouseDelta.y; - - if (_tutorial_index == 3) - { - tutorial_text = - "This is the list of variables. It contains all tweakable options the effects expose. All values here apply in real-time. Press 'Ctrl' and click on a widget to manually edit the value.\n\n" - "Enter text in the box at the top to filter it and search for specific variables.\n\n" - "Use the right mouse button and click on an item to open the context menu with additional options.\n\n" - "Once you have finished tweaking your preset, be sure to enable the 'Performance Mode' check box. " - "This will recompile all shaders into a more optimal representation that can give a performance boost, but will disable variable tweaking and this list."; - - ImGui::PushStyleColor(ImGuiCol_Border, COLOR_RED); - } - - const float bottom_height = ImGui::GetFrameHeightWithSpacing() + _imgui_context->Style.ItemSpacing.y + (_tutorial_index == 3 ? 175 : 0); - - if (ImGui::BeginChild("##variables", ImVec2(0, -bottom_height), true)) - draw_overlay_variable_editor(); - ImGui::EndChild(); - - if (_tutorial_index == 3) - ImGui::PopStyleColor(); - } - - if (_tutorial_index > 3) - { - ImGui::Spacing(); - - if (ImGui::Button("Reload", ImVec2(-150, 0))) - { - _show_splash = true; - _effect_filter_buffer[0] = '\0'; // Reset filter - - load_effects(); - } - - ImGui::SameLine(); - - if (ImGui::Checkbox("Performance Mode", &_performance_mode)) - { - _show_splash = true; - _effect_filter_buffer[0] = '\0'; // Reset filter - - save_config(); - load_effects(); // Reload effects after switching - } - } - else - { - ImGui::BeginChildFrame(ImGui::GetID("tutorial"), ImVec2(0, 175)); - ImGui::TextWrapped(tutorial_text); - ImGui::EndChildFrame(); - - const float max_button_width = ImGui::GetContentRegionAvailWidth(); - - if (ImGui::Button(_tutorial_index == 3 ? "Finish" : "Continue", ImVec2(max_button_width * 0.66666666f, 0))) - { - // Disable font scaling after tutorial - if (_tutorial_index++ == 3) - _no_font_scaling = true; - - save_config(); - } - - ImGui::SameLine(); - - if (ImGui::Button("Skip Tutorial", ImVec2(max_button_width * 0.33333333f - _imgui_context->Style.ItemSpacing.x, 0))) - { - _tutorial_index = 4; - _no_font_scaling = true; - - save_config(); - } - } -} - -void reshade::runtime::draw_overlay_menu_settings() -{ - bool modified = false; - bool reload_style = false; - const float button_size = ImGui::GetFrameHeight(); - const float button_spacing = _imgui_context->Style.ItemInnerSpacing.x; - - if (ImGui::CollapsingHeader("General", ImGuiTreeNodeFlags_DefaultOpen)) - { - modified |= imgui_key_input("Overlay Key", _menu_key_data, *_input); - _ignore_shortcuts |= ImGui::IsItemActive(); - - modified |= imgui_key_input("Effect Reload Key", _reload_key_data, *_input); - _ignore_shortcuts |= ImGui::IsItemActive(); - modified |= imgui_key_input("Effect Toggle Key", _effects_key_data, *_input); - _ignore_shortcuts |= ImGui::IsItemActive(); - - modified |= ImGui::Combo("Input Processing", &_input_processing_mode, - "Pass on all input\0" - "Block input when cursor is on overlay\0" - "Block all input when overlay is visible\0"); - - modified |= imgui_path_list("Effect Search Paths", _effect_search_paths, _file_selection_path, g_reshade_dll_path.parent_path()); - modified |= imgui_path_list("Texture Search Paths", _texture_search_paths, _file_selection_path, g_reshade_dll_path.parent_path()); - - if (ImGui::Button("Restart Tutorial", ImVec2(ImGui::CalcItemWidth(), 0))) - _tutorial_index = 0; - } - - if (ImGui::CollapsingHeader("Screenshots", ImGuiTreeNodeFlags_DefaultOpen)) - { - modified |= imgui_key_input("Screenshot Key", _screenshot_key_data, *_input); - _ignore_shortcuts |= ImGui::IsItemActive(); - - modified |= imgui_directory_input_box("Screenshot Path", _screenshot_path, _file_selection_path); - modified |= ImGui::Combo("Screenshot Format", &_screenshot_format, "Bitmap (*.bmp)\0Portable Network Graphics (*.png)\0"); - modified |= ImGui::Checkbox("Include Current Preset", &_screenshot_include_preset); - } - - if (ImGui::CollapsingHeader("User Interface", ImGuiTreeNodeFlags_DefaultOpen)) - { - modified |= ImGui::Checkbox("Show Screenshot Message", &_show_screenshot_message); - - bool save_imgui_window_state = _imgui_context->IO.IniFilename != nullptr; - if (ImGui::Checkbox("Save Window State (ReShadeGUI.ini)", &save_imgui_window_state)) - { - modified = true; - _imgui_context->IO.IniFilename = save_imgui_window_state ? g_reshadegui_ini_path : nullptr; - } - - modified |= ImGui::Checkbox("Group effect files with tabs instead of a tree", &_variable_editor_tabs); - - #pragma region Style - if (ImGui::Combo("Style", &_style_index, "Dark\0Light\0Default\0Custom Simple\0Custom Advanced\0")) - { - modified = true; - reload_style = true; - } - - if (_style_index == 3) // Custom Simple - { - ImVec4 *const colors = _imgui_context->Style.Colors; - - if (ImGui::BeginChild("##colors", ImVec2(0, 105), true, ImGuiWindowFlags_AlwaysVerticalScrollbar | ImGuiWindowFlags_NavFlattened)) - { - ImGui::PushItemWidth(-160); - modified |= ImGui::ColorEdit3("Background", &colors[ImGuiCol_WindowBg].x); - modified |= ImGui::ColorEdit3("ItemBackground", &colors[ImGuiCol_FrameBg].x); - modified |= ImGui::ColorEdit3("Text", &colors[ImGuiCol_Text].x); - modified |= ImGui::ColorEdit3("ActiveItem", &colors[ImGuiCol_ButtonActive].x); - ImGui::PopItemWidth(); - } ImGui::EndChild(); - - // Change all colors using the above as base - if (modified) - { - colors[ImGuiCol_PopupBg] = colors[ImGuiCol_WindowBg]; colors[ImGuiCol_PopupBg].w = 0.92f; - - colors[ImGuiCol_ChildBg] = colors[ImGuiCol_FrameBg]; colors[ImGuiCol_ChildBg].w = 0.00f; - colors[ImGuiCol_MenuBarBg] = colors[ImGuiCol_FrameBg]; colors[ImGuiCol_MenuBarBg].w = 0.57f; - colors[ImGuiCol_ScrollbarBg] = colors[ImGuiCol_FrameBg]; colors[ImGuiCol_ScrollbarBg].w = 1.00f; - - colors[ImGuiCol_TextDisabled] = colors[ImGuiCol_Text]; colors[ImGuiCol_TextDisabled].w = 0.58f; - colors[ImGuiCol_Border] = colors[ImGuiCol_Text]; colors[ImGuiCol_Border].w = 0.30f; - colors[ImGuiCol_Separator] = colors[ImGuiCol_Text]; colors[ImGuiCol_Separator].w = 0.32f; - colors[ImGuiCol_SeparatorHovered] = colors[ImGuiCol_Text]; colors[ImGuiCol_SeparatorHovered].w = 0.78f; - colors[ImGuiCol_SeparatorActive] = colors[ImGuiCol_Text]; colors[ImGuiCol_SeparatorActive].w = 1.00f; - colors[ImGuiCol_PlotLines] = colors[ImGuiCol_Text]; colors[ImGuiCol_PlotLines].w = 0.63f; - colors[ImGuiCol_PlotHistogram] = colors[ImGuiCol_Text]; colors[ImGuiCol_PlotHistogram].w = 0.63f; - - colors[ImGuiCol_FrameBgHovered] = colors[ImGuiCol_ButtonActive]; colors[ImGuiCol_FrameBgHovered].w = 0.68f; - colors[ImGuiCol_FrameBgActive] = colors[ImGuiCol_ButtonActive]; colors[ImGuiCol_FrameBgActive].w = 1.00f; - colors[ImGuiCol_TitleBg] = colors[ImGuiCol_ButtonActive]; colors[ImGuiCol_TitleBg].w = 0.45f; - colors[ImGuiCol_TitleBgCollapsed] = colors[ImGuiCol_ButtonActive]; colors[ImGuiCol_TitleBgCollapsed].w = 0.35f; - colors[ImGuiCol_TitleBgActive] = colors[ImGuiCol_ButtonActive]; colors[ImGuiCol_TitleBgActive].w = 0.58f; - colors[ImGuiCol_ScrollbarGrab] = colors[ImGuiCol_ButtonActive]; colors[ImGuiCol_ScrollbarGrab].w = 0.31f; - colors[ImGuiCol_ScrollbarGrabHovered] = colors[ImGuiCol_ButtonActive]; colors[ImGuiCol_ScrollbarGrabHovered].w = 0.78f; - colors[ImGuiCol_ScrollbarGrabActive] = colors[ImGuiCol_ButtonActive]; colors[ImGuiCol_ScrollbarGrabActive].w = 1.00f; - colors[ImGuiCol_CheckMark] = colors[ImGuiCol_ButtonActive]; colors[ImGuiCol_CheckMark].w = 0.80f; - colors[ImGuiCol_SliderGrab] = colors[ImGuiCol_ButtonActive]; colors[ImGuiCol_SliderGrab].w = 0.24f; - colors[ImGuiCol_SliderGrabActive] = colors[ImGuiCol_ButtonActive]; colors[ImGuiCol_SliderGrabActive].w = 1.00f; - colors[ImGuiCol_Button] = colors[ImGuiCol_ButtonActive]; colors[ImGuiCol_Button].w = 0.44f; - colors[ImGuiCol_ButtonHovered] = colors[ImGuiCol_ButtonActive]; colors[ImGuiCol_ButtonHovered].w = 0.86f; - colors[ImGuiCol_Header] = colors[ImGuiCol_ButtonActive]; colors[ImGuiCol_Header].w = 0.76f; - colors[ImGuiCol_HeaderHovered] = colors[ImGuiCol_ButtonActive]; colors[ImGuiCol_HeaderHovered].w = 0.86f; - colors[ImGuiCol_HeaderActive] = colors[ImGuiCol_ButtonActive]; colors[ImGuiCol_HeaderActive].w = 1.00f; - colors[ImGuiCol_ResizeGrip] = colors[ImGuiCol_ButtonActive]; colors[ImGuiCol_ResizeGrip].w = 0.20f; - colors[ImGuiCol_ResizeGripHovered] = colors[ImGuiCol_ButtonActive]; colors[ImGuiCol_ResizeGripHovered].w = 0.78f; - colors[ImGuiCol_ResizeGripActive] = colors[ImGuiCol_ButtonActive]; colors[ImGuiCol_ResizeGripActive].w = 1.00f; - colors[ImGuiCol_PlotLinesHovered] = colors[ImGuiCol_ButtonActive]; colors[ImGuiCol_PlotLinesHovered].w = 1.00f; - colors[ImGuiCol_PlotHistogramHovered] = colors[ImGuiCol_ButtonActive]; colors[ImGuiCol_PlotHistogramHovered].w = 1.00f; - colors[ImGuiCol_TextSelectedBg] = colors[ImGuiCol_ButtonActive]; colors[ImGuiCol_TextSelectedBg].w = 0.43f; - - colors[ImGuiCol_Tab] = colors[ImGuiCol_Button]; - colors[ImGuiCol_TabActive] = colors[ImGuiCol_ButtonActive]; - colors[ImGuiCol_TabHovered] = colors[ImGuiCol_ButtonHovered]; - colors[ImGuiCol_TabUnfocused] = ImLerp(colors[ImGuiCol_Tab], colors[ImGuiCol_TitleBg], 0.80f); - colors[ImGuiCol_TabUnfocusedActive] = ImLerp(colors[ImGuiCol_TabActive], colors[ImGuiCol_TitleBg], 0.40f); - colors[ImGuiCol_DockingPreview] = colors[ImGuiCol_Header] * ImVec4(1.0f, 1.0f, 1.0f, 0.7f); - colors[ImGuiCol_DockingEmptyBg] = ImVec4(0.20f, 0.20f, 0.20f, 1.00f); - } - } - if (_style_index == 4) // Custom Advanced - { - if (ImGui::BeginChild("##colors", ImVec2(0, 300), true, ImGuiWindowFlags_AlwaysVerticalScrollbar | ImGuiWindowFlags_NavFlattened)) - { - ImGui::PushItemWidth(-160); - for (ImGuiCol i = 0; i < ImGuiCol_COUNT; i++) - { - ImGui::PushID(i); - modified |= ImGui::ColorEdit4("##color", &_imgui_context->Style.Colors[i].x, ImGuiColorEditFlags_AlphaBar | ImGuiColorEditFlags_AlphaPreview); - ImGui::SameLine(); ImGui::TextUnformatted(ImGui::GetStyleColorName(i)); - ImGui::PopID(); - } - ImGui::PopItemWidth(); - } ImGui::EndChild(); - } - #pragma endregion - - #pragma region Editor Style - if (ImGui::Combo("Editor Style", &_editor_style_index, "Dark\0Light\0Custom\0")) - { - modified = true; - reload_style = true; - } - - if (_editor_style_index == 2) - { - if (ImGui::BeginChild("##editor_colors", ImVec2(0, 300), true, ImGuiWindowFlags_AlwaysVerticalScrollbar | ImGuiWindowFlags_NavFlattened)) - { - ImGui::PushItemWidth(-160); - for (ImGuiCol i = 0; i < imgui_code_editor::color_palette_max; i++) - { - ImVec4 color = ImGui::ColorConvertU32ToFloat4(_editor.get_palette_index(i)); - ImGui::PushID(i); - modified |= ImGui::ColorEdit4("##editor_color", &color.x, ImGuiColorEditFlags_AlphaBar | ImGuiColorEditFlags_AlphaPreview); - ImGui::SameLine(); ImGui::TextUnformatted(imgui_code_editor::get_palette_color_name(i)); - ImGui::PopID(); - _editor.get_palette_index(i) = ImGui::ColorConvertFloat4ToU32(color); - } - ImGui::PopItemWidth(); - } ImGui::EndChild(); - } - #pragma endregion - - if (imgui_font_select("Font", _font, _font_size)) - { - modified = true; - _rebuild_font_atlas = true; - } - - if (imgui_font_select("Editor Font", _editor_font, _editor_font_size)) - { - modified = true; - _rebuild_font_atlas = true; - } - - if (float &alpha = _imgui_context->Style.Alpha; ImGui::SliderFloat("Global Alpha", &alpha, 0.1f, 1.0f, "%.2f")) - { - // Prevent user from setting alpha to zero - alpha = std::max(alpha, 0.1f); - modified = true; - } - - if (float &rounding = _imgui_context->Style.FrameRounding; ImGui::SliderFloat("Frame Rounding", &rounding, 0.0f, 12.0f, "%.0f")) - { - // Apply the same rounding to everything - _imgui_context->Style.GrabRounding = _imgui_context->Style.TabRounding = _imgui_context->Style.ScrollbarRounding = rounding; - _imgui_context->Style.WindowRounding = _imgui_context->Style.ChildRounding = _imgui_context->Style.PopupRounding = rounding; - modified = true; - } - - modified |= ImGui::Checkbox("Show Clock", &_show_clock); - ImGui::SameLine(0, 10); modified |= ImGui::Checkbox("Show FPS", &_show_fps); - ImGui::SameLine(0, 10); modified |= ImGui::Checkbox("Show Frame Time", &_show_frametime); - modified |= ImGui::Combo("Clock Format", &_clock_format, "HH:MM\0HH:MM:SS\0"); - modified |= ImGui::SliderFloat("FPS Text Size", &_fps_scale, 0.2f, 2.5f, "%.1f"); - modified |= ImGui::ColorEdit4("FPS Text Color", _fps_col, ImGuiColorEditFlags_AlphaBar | ImGuiColorEditFlags_AlphaPreview); - } - - if (modified) - save_config(); - if (reload_style) // Style is applied in "load_config()". - load_config(); -} - -void reshade::runtime::draw_overlay_menu_statistics() -{ - uint64_t post_processing_time_cpu = 0; - uint64_t post_processing_time_gpu = 0; - - for (const auto &technique : _techniques) - { - post_processing_time_cpu += technique.average_cpu_duration; - post_processing_time_gpu += technique.average_gpu_duration; - } - - if (ImGui::CollapsingHeader("General", ImGuiTreeNodeFlags_DefaultOpen)) - { - ImGui::PushItemWidth(ImGui::GetContentRegionAvailWidth()); - ImGui::PlotLines("##framerate", - _imgui_context->FramerateSecPerFrame, 120, - _imgui_context->FramerateSecPerFrameIdx, - nullptr, - _imgui_context->FramerateSecPerFrameAccum / 120 * 0.5f, - _imgui_context->FramerateSecPerFrameAccum / 120 * 1.5f, - ImVec2(0, 50)); - ImGui::PopItemWidth(); - - ImGui::BeginGroup(); - - ImGui::TextUnformatted("Application:"); - ImGui::TextUnformatted("Date:"); - ImGui::TextUnformatted("Device:"); - ImGui::TextUnformatted("FPS:"); - ImGui::TextUnformatted("Post-Processing:"); - ImGui::TextUnformatted("Draw Calls:"); - ImGui::Text("Frame %llu:", _framecount + 1); - ImGui::TextUnformatted("Timer:"); - ImGui::TextUnformatted("Network:"); - - ImGui::EndGroup(); - ImGui::SameLine(ImGui::GetWindowWidth() * 0.33333333f); - ImGui::BeginGroup(); - - ImGui::Text("%X", std::hash()(g_target_executable_path.stem().u8string())); - ImGui::Text("%d-%d-%d %d", _date[0], _date[1], _date[2], _date[3]); - ImGui::Text("%X %d", _vendor_id, _device_id); - ImGui::Text("%.2f", _imgui_context->IO.Framerate); - ImGui::Text("%f ms (CPU)", post_processing_time_cpu * 1e-6f); - ImGui::Text("%u (%u vertices)", _drawcalls, _vertices); - ImGui::Text("%f ms", _last_frame_duration.count() * 1e-6f); - ImGui::Text("%f ms", std::chrono::duration_cast(_last_present_time - _start_time).count() * 1e-6f); - ImGui::Text("%u B", g_network_traffic); - - ImGui::EndGroup(); - ImGui::SameLine(ImGui::GetWindowWidth() * 0.66666666f); - ImGui::BeginGroup(); - - ImGui::NewLine(); - ImGui::NewLine(); - ImGui::NewLine(); - ImGui::NewLine(); - if (post_processing_time_gpu != 0) - ImGui::Text("%f ms (GPU)", (post_processing_time_gpu * 1e-6f)); - - ImGui::EndGroup(); - } - - if (ImGui::CollapsingHeader("Effects", ImGuiTreeNodeFlags_DefaultOpen)) - { - std::vector current_textures; - current_textures.reserve(_textures.size()); - std::vector current_techniques; - current_techniques.reserve(_techniques.size()); - - ImGui::Checkbox("Show only active techniques", &_statistics_effects_show_enabled); - - for (size_t index = 0; index < _loaded_effects.size(); ++index) - { - const effect_data &effect = _loaded_effects[index]; - - // Ignore unloaded effects, filter for active effects if enabled - if (effect.source_file.empty() || (_statistics_effects_show_enabled && effect.rendering == 0)) - continue; - - ImGui::PushID(static_cast(index)); - - ImGui::AlignTextToFramePadding(); - - if (_selected_effect == index && _show_code_editor) - { - ImGui::TextUnformatted(">"); - ImGui::SameLine(); - } - - const float button_spacing = _imgui_context->Style.ItemInnerSpacing.x; - const float button_offset = ImGui::GetWindowContentRegionWidth() - (50 + button_spacing + 120); - - auto tree_id = ImGui::GetID("tree_open"); - bool tree_open = ImGui::GetStateStorage()->GetInt(tree_id, true); - bool tree_toggle = false; - - // Hide parent path if window is small - if (ImGui::CalcTextSize(effect.source_file.u8string().c_str()).x < button_offset) - { - ImGui::TextDisabled("%s%lc", effect.source_file.parent_path().u8string().c_str(), std::filesystem::path::preferred_separator); - tree_toggle = ImGui::IsItemClicked(); - ImGui::SameLine(0, 0); - } - - ImGui::TextUnformatted(effect.source_file.filename().u8string().c_str()); - tree_toggle |= ImGui::IsItemClicked(); - - // Update tree state - if (tree_toggle) - ImGui::GetStateStorage()->SetInt(tree_id, tree_open = !tree_open); - - ImGui::SameLine(button_offset + _imgui_context->Style.ItemSpacing.x, 0); - if (ImGui::Button("Edit", ImVec2(50, 0))) - { - _selected_effect = index; - _selected_effect_changed = true; - _show_code_editor = true; - } - - ImGui::SameLine(0, button_spacing); - if (ImGui::Button("Show HLSL/GLSL", ImVec2(120, 0))) - { - const std::string source_code = effect.preamble + effect.module.hlsl; - - // Act as a toggle when already showing the generated code - if (_show_code_editor - && _selected_effect == std::numeric_limits::max() - && _editor.get_text() == source_code) - { - _show_code_editor = false; - } - else - { - _editor.set_text(source_code); - _selected_effect = std::numeric_limits::max(); - _selected_effect_changed = false; // Prevent editor from being cleared, since we already set the text here - _show_code_editor = true; - } - } - - if (tree_open) - { - if (!effect.errors.empty()) - { - ImGui::PushStyleColor(ImGuiCol_Text, effect.errors.find("error") != std::string::npos ? COLOR_RED : COLOR_YELLOW); - ImGui::PushTextWrapPos(); - ImGui::TextUnformatted(effect.errors.c_str()); - ImGui::PopTextWrapPos(); - ImGui::PopStyleColor(); - ImGui::Spacing(); - } - - #pragma region Techniques - current_techniques.clear(); - for (const auto &technique : _techniques) - if (technique.effect_index == index && technique.impl != nullptr) - current_techniques.push_back(&technique); - - if (!current_techniques.empty()) - { - if (ImGui::BeginChild("Techniques", ImVec2(0, current_techniques.size() * ImGui::GetTextLineHeightWithSpacing() + _imgui_context->Style.FramePadding.y * 4), true, ImGuiWindowFlags_NoScrollWithMouse)) - { - ImGui::BeginGroup(); - - for (const auto &technique : current_techniques) - { - ImGui::PushStyleColor(ImGuiCol_Text, _imgui_context->Style.Colors[technique->enabled ? ImGuiCol_Text : ImGuiCol_TextDisabled]); - - if (technique->passes.size() > 1) - ImGui::Text("%s (%zu passes)", technique->name.c_str(), technique->passes.size()); - else - ImGui::TextUnformatted(technique->name.c_str()); - - ImGui::PopStyleColor(); - } - - ImGui::EndGroup(); - ImGui::SameLine(ImGui::GetWindowWidth() * 0.33333333f); - ImGui::BeginGroup(); - - for (const technique *technique : current_techniques) - if (technique->enabled) - ImGui::Text("%f ms (CPU) (%.0f%%)", technique->average_cpu_duration * 1e-6f, 100 * (technique->average_cpu_duration * 1e-6f) / (post_processing_time_cpu * 1e-6f)); - else - ImGui::NewLine(); - - ImGui::EndGroup(); - ImGui::SameLine(ImGui::GetWindowWidth() * 0.66666666f); - ImGui::BeginGroup(); - - for (const technique *technique : current_techniques) - if (technique->enabled && technique->average_gpu_duration != 0) - ImGui::Text("%f ms (GPU) (%.0f%%)", technique->average_gpu_duration * 1e-6f, 100 * (technique->average_gpu_duration * 1e-6f) / (post_processing_time_gpu * 1e-6f)); - else - ImGui::NewLine(); - - ImGui::EndGroup(); - } ImGui::EndChild(); - } - #pragma endregion - - #pragma region Textures - current_textures.clear(); - for (const auto &texture : _textures) - if (texture.effect_index == index && texture.impl != nullptr && texture.impl_reference == texture_reference::none) - current_textures.push_back(&texture); - - if (!current_textures.empty()) - { - if (ImGui::BeginChild("Textures", ImVec2(0, current_textures.size() * ImGui::GetTextLineHeightWithSpacing() + _imgui_context->Style.FramePadding.y * 4), true, ImGuiWindowFlags_NoScrollWithMouse)) - { - const char *texture_formats[] = { - "unknown", - "R8", "R16F", "R32F", "RG8", "RG16", "RG16F", "RG32F", "RGBA8", "RGBA16", "RGBA16F", "RGBA32F", "RGB10A2" - }; - - static_assert(_countof(texture_formats) - 1 == static_cast(reshadefx::texture_format::rgb10a2)); - - for (const texture *texture : current_textures) - { - ImGui::Text("%s (%ux%u +%u %s)", texture->unique_name.c_str(), texture->width, texture->height, (texture->levels - 1), texture_formats[static_cast(texture->format)]); - - if (std::find(_texture_previews.begin(), _texture_previews.end(), texture) != _texture_previews.end()) - continue; - - if (ImGui::IsItemClicked()) - { - _texture_previews.push_back(texture); - } - else if (ImGui::IsItemHovered()) - { - ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f)); - ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f); - ImGui::PushStyleColor(ImGuiCol_PopupBg, IM_COL32(204, 204, 204, 255)); - ImGui::BeginTooltip(); - imgui_image_with_checkerboard_background(texture->impl.get(), ImVec2(std::max(texture->width * 0.5f, 500.0f), std::max(texture->height * 0.5f, texture->height * 500.0f / texture->width))); - ImGui::EndTooltip(); - ImGui::PopStyleColor(); - ImGui::PopStyleVar(2); - } - } - } ImGui::EndChild(); - } - #pragma endregion - } - - ImGui::PopID(); - - ImGui::Spacing(); - } - } -} - -void reshade::runtime::draw_overlay_menu_log() -{ - if (ImGui::Button("Clear Log")) - reshade::log::lines.clear(); - - ImGui::SameLine(); - ImGui::Checkbox("Word Wrap", &_log_wordwrap); - ImGui::SameLine(); - - static ImGuiTextFilter filter; // TODO: Better make this a member of the runtime class, in case there are multiple instances. - filter.Draw("Filter (inc, -exc)", -150); - - if (ImGui::BeginChild("log", ImVec2(0, 0), true, _log_wordwrap ? 0 : ImGuiWindowFlags_AlwaysHorizontalScrollbar)) - { - std::vector lines; - for (auto &line : reshade::log::lines) - if (filter.PassFilter(line.c_str())) - lines.push_back(line); - - ImGuiListClipper clipper(static_cast(lines.size()), ImGui::GetTextLineHeightWithSpacing()); - - while (clipper.Step()) - { - for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; ++i) - { - ImVec4 textcol = _imgui_context->Style.Colors[ImGuiCol_Text]; - - if (lines[i].find("ERROR |") != std::string::npos) - textcol = COLOR_RED; - else if (lines[i].find("WARN |") != std::string::npos) - textcol = COLOR_YELLOW; - else if (lines[i].find("DEBUG |") != std::string::npos) - textcol = ImColor(100, 100, 255); - - ImGui::PushStyleColor(ImGuiCol_Text, textcol); - if (_log_wordwrap) ImGui::PushTextWrapPos(); - - ImGui::TextUnformatted(lines[i].c_str()); - - if (_log_wordwrap) ImGui::PopTextWrapPos(); - ImGui::PopStyleColor(); - } - } - } ImGui::EndChild(); -} - -void reshade::runtime::draw_overlay_menu_about() -{ - ImGui::TextUnformatted("ReShade " VERSION_STRING_FILE); - - ImGui::PushTextWrapPos(); - ImGui::TextUnformatted(R"(Copyright (C) 2014 Patrick Mours. All rights reserved. - -https://github.com/crosire/reshade - -Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - - 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.)"); - - if (ImGui::CollapsingHeader("MinHook")) - { - ImGui::TextUnformatted(R"(Copyright (C) 2009-2016 Tsuda Kageyu. All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - - 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.)"); - } - if (ImGui::CollapsingHeader("Hacker Disassembler Engine 32/64 C")) - { - ImGui::TextUnformatted(R"(Copyright (C) 2008-2009 Vyacheslav Patkov. All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - - 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.)"); - } - if (ImGui::CollapsingHeader("dear imgui")) - { - ImGui::TextUnformatted(R"(Copyright (C) 2014-2015 Omar Cornut and ImGui contributors - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.)"); - } - if (ImGui::CollapsingHeader("ImGuiColorTextEdit")) - { - ImGui::TextUnformatted(R"(Copyright (C) 2017 BalazsJako - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.)"); - } - if (ImGui::CollapsingHeader("gl3w")) - { - ImGui::TextUnformatted("Slavomir Kaslev"); - } - if (ImGui::CollapsingHeader("stb_image, stb_image_write")) - { - ImGui::TextUnformatted("Sean Barrett and contributors"); - } - if (ImGui::CollapsingHeader("DDS loading from SOIL")) - { - ImGui::TextUnformatted("Jonathan \"lonesock\" Dummer"); - } - - ImGui::PopTextWrapPos(); -} - -void reshade::runtime::draw_code_editor() -{ - const auto parse_errors = [this](const std::string &errors) { - _editor.clear_errors(); - - for (size_t offset = 0, next; offset != std::string::npos; offset = next) - { - const size_t pos_error = errors.find(": ", offset); - const size_t pos_error_line = errors.rfind('(', pos_error); // Paths can contain '(', but no ": ", so search backwards from th error location to find the line info - if (pos_error == std::string::npos || pos_error_line == std::string::npos) - break; - - const size_t pos_linefeed = errors.find('\n', pos_error); - - next = pos_linefeed != std::string::npos ? pos_linefeed + 1 : std::string::npos; - - // Ignore errors that aren't in the main source file - if (const std::string_view error_file(errors.c_str() + offset, pos_error_line - offset); - error_file != _loaded_effects[_selected_effect].source_file.u8string()) - continue; - - const int error_line = std::strtol(errors.c_str() + pos_error_line + 1, nullptr, 10); - const std::string error_text = errors.substr(pos_error + 2 /* skip space */, pos_linefeed - pos_error - 2); - - _editor.add_error(error_line, error_text, error_text.find("warning") != std::string::npos); - } - }; - - if (_selected_effect < _loaded_effects.size() && (ImGui::Button("Save & Compile (Ctrl + S)") || _input->is_key_pressed('S', true, false, false))) - { - // Hide splash bar during compile - _show_splash = false; - - const std::string text = _editor.get_text(); - - const std::filesystem::path source_file = _loaded_effects[_selected_effect].source_file; - - // Write editor text to file - std::ofstream(source_file, std::ios::trunc).write(text.c_str(), text.size()); - - // Reload effect file - _textures_loaded = false; - _reload_total_effects = 1; - _reload_remaining_effects = 1; - unload_effect(_selected_effect); - load_effect(source_file, _selected_effect); - assert(_reload_remaining_effects == 0); - - parse_errors(_loaded_effects[_selected_effect].errors); - } - - ImGui::SameLine(); - - ImGui::PushItemWidth(ImGui::GetContentRegionAvailWidth()); - - if (ImGui::BeginCombo("##file", _selected_effect < _loaded_effects.size() ? _loaded_effects[_selected_effect].source_file.u8string().c_str() : "", ImGuiComboFlags_HeightLarge)) - { - for (size_t i = 0; i < _loaded_effects.size(); ++i) - { - const auto &effect = _loaded_effects[i]; - - // Ignore unloaded effects - if (effect.source_file.empty()) - continue; - - if (ImGui::Selectable(effect.source_file.u8string().c_str(), _selected_effect == i)) - { - _selected_effect = i; - _selected_effect_changed = true; - } - } - - ImGui::EndCombo(); - } - - ImGui::PopItemWidth(); - - if (_selected_effect_changed) - { - if (_selected_effect < _loaded_effects.size()) - { - const auto &effect = _loaded_effects[_selected_effect]; - - // Load file to string and update editor text - _editor.set_text(std::string(std::istreambuf_iterator(std::ifstream(effect.source_file).rdbuf()), std::istreambuf_iterator())); - - parse_errors(effect.errors); - } - else - { - _editor.clear_text(); - } - - _selected_effect_changed = false; - } - - // Select editor font - ImGui::PushFont(_imgui_context->IO.Fonts->Fonts[1]); - - _editor.render("##editor"); - - ImGui::PopFont(); - - // Disable keyboard shortcuts when the window is focused so they don't get triggered while editing text - const bool is_focused = ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows); - _ignore_shortcuts |= is_focused; - - // Disable keyboard navigation starting with next frame when editor is focused so that the Alt key can be used without it switching focus to the menu bar - if (is_focused) - _imgui_context->IO.ConfigFlags &= ~ImGuiConfigFlags_NavEnableKeyboard; - else // Enable navigation again if focus is lost - _imgui_context->IO.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; -} - -void reshade::runtime::draw_overlay_variable_editor() -{ - const ImVec2 popup_pos = ImGui::GetCursorScreenPos() + ImVec2(std::max(0.f, ImGui::GetWindowContentRegionWidth() * 0.5f - 200.0f), ImGui::GetFrameHeightWithSpacing()); - - if (imgui_popup_button("Edit global preprocessor definitions", ImGui::GetContentRegionAvailWidth(), ImGuiWindowFlags_NoMove)) - { - ImGui::SetWindowPos(popup_pos); - - bool modified = false; - float popup_height = (std::max(_global_preprocessor_definitions.size(), _preset_preprocessor_definitions.size()) + 2) * ImGui::GetFrameHeightWithSpacing(); - popup_height = std::min(popup_height, _window_height - popup_pos.y - 20.0f); - const float button_size = ImGui::GetFrameHeight(); - const float button_spacing = _imgui_context->Style.ItemInnerSpacing.x; - - ImGui::BeginChild("##definitions", ImVec2(400.0f, popup_height), false, ImGuiWindowFlags_NoScrollWithMouse); - - if (ImGui::BeginTabBar("##definition_types", ImGuiTabBarFlags_NoTooltip)) - { - if (ImGui::BeginTabItem("Global")) - { - for (size_t i = 0; i < _global_preprocessor_definitions.size(); ++i) - { - char name[128] = ""; - char value[128] = ""; - - const size_t equals_index = _global_preprocessor_definitions[i].find('='); - _global_preprocessor_definitions[i].copy(name, std::min(equals_index, sizeof(name) - 1)); - if (equals_index != std::string::npos) - _global_preprocessor_definitions[i].copy(value, sizeof(value) - 1, equals_index + 1); - - ImGui::PushID(static_cast(i)); - - ImGui::PushItemWidth(ImGui::GetWindowContentRegionWidth() * 0.66666666f - (button_spacing)); - modified |= ImGui::InputText("##name", name, sizeof(name)); - ImGui::PopItemWidth(); - - ImGui::SameLine(0, button_spacing); - - ImGui::PushItemWidth(ImGui::GetWindowContentRegionWidth() * 0.33333333f - (button_spacing + button_size) + 1); - modified |= ImGui::InputText("##value", value, sizeof(value)); - ImGui::PopItemWidth(); - - ImGui::SameLine(0, button_spacing); - - if (ImGui::Button("-", ImVec2(button_size, 0))) - { - modified = true; - _global_preprocessor_definitions.erase(_global_preprocessor_definitions.begin() + i--); - } - else if (modified) - { - _global_preprocessor_definitions[i] = std::string(name) + '=' + std::string(value); - } - - ImGui::PopID(); - } - - ImGui::Dummy(ImVec2()); - ImGui::SameLine(0, ImGui::GetWindowContentRegionWidth() - button_size); - if (ImGui::Button("+", ImVec2(button_size, 0))) - _global_preprocessor_definitions.emplace_back(); - - ImGui::EndTabItem(); - } - - if (ImGui::BeginTabItem("Current Preset")) - { - for (size_t i = 0; i < _preset_preprocessor_definitions.size(); ++i) - { - char name[128] = ""; - char value[128] = ""; - - const size_t equals_index = _preset_preprocessor_definitions[i].find('='); - _preset_preprocessor_definitions[i].copy(name, std::min(equals_index, sizeof(name) - 1)); - if (equals_index != std::string::npos) - _preset_preprocessor_definitions[i].copy(value, sizeof(value) - 1, equals_index + 1); - - ImGui::PushID(static_cast(i)); - - ImGui::PushItemWidth(ImGui::GetWindowContentRegionWidth() * 0.66666666f - (button_spacing)); - modified |= ImGui::InputText("##name", name, sizeof(name)); - ImGui::PopItemWidth(); - - ImGui::SameLine(0, button_spacing); - - ImGui::PushItemWidth(ImGui::GetWindowContentRegionWidth() * 0.33333333f - (button_spacing + button_size) + 1); - modified |= ImGui::InputText("##value", value, sizeof(value)); - ImGui::PopItemWidth(); - - ImGui::SameLine(0, button_spacing); - - if (ImGui::Button("-", ImVec2(button_size, 0))) - { - modified = true; - _preset_preprocessor_definitions.erase(_preset_preprocessor_definitions.begin() + i--); - } - else if (modified) - { - _preset_preprocessor_definitions[i] = std::string(name) + '=' + std::string(value); - } - - ImGui::PopID(); - } - - ImGui::Dummy(ImVec2()); - ImGui::SameLine(0, ImGui::GetWindowContentRegionWidth() - button_size); - if (ImGui::Button("+", ImVec2(button_size, 0))) - _preset_preprocessor_definitions.emplace_back(); - - ImGui::EndTabItem(); - } - - ImGui::EndTabBar(); - } - - ImGui::EndChild(); - - if (modified) - save_config(), save_current_preset(), _was_preprocessor_popup_edited = true; - - ImGui::EndPopup(); - } - else if (_was_preprocessor_popup_edited) - { - _was_preprocessor_popup_edited = false; - - _show_splash = true; - _effect_filter_buffer[0] = '\0'; // Reset filter - - load_effects(); - } - - ImGui::BeginChild("##variables"); - - bool current_tree_is_open = false; - bool current_category_is_closed = false; - size_t current_effect = std::numeric_limits::max(); - std::string current_category; - - if (_variable_editor_tabs) - { - ImGui::BeginTabBar("##variables"); - } - - ImGui::PushItemWidth(ImGui::GetWindowWidth() * 0.5f); - - for (size_t index = 0; index < _uniforms.size(); ++index) - { - uniform &variable = _uniforms[index]; - - // Skip hidden and special variables - if (variable.annotation_as_int("hidden") || variable.special != special_uniform::none) - continue; - - // Hide variables that are not currently used in any of the active effects - if (!_loaded_effects[variable.effect_index].rendering) - continue; - assert(_loaded_effects[variable.effect_index].compile_sucess); - - // Create separate tab for every effect file - if (variable.effect_index != current_effect) - { - current_effect = variable.effect_index; - - const bool is_focused = _focused_effect == variable.effect_index; - - const std::string filename = _loaded_effects[current_effect].source_file.filename().u8string(); - - if (_variable_editor_tabs) - { - if (current_tree_is_open) - ImGui::EndTabItem(); - - current_tree_is_open = ImGui::BeginTabItem(filename.c_str()); - } - else - { - if (current_tree_is_open) - ImGui::TreePop(); - - if (is_focused || _effects_expanded_state & 1) - ImGui::SetNextTreeNodeOpen(is_focused || (_effects_expanded_state >> 1) != 0); - - current_tree_is_open = ImGui::TreeNodeEx(filename.c_str(), ImGuiTreeNodeFlags_DefaultOpen); - } - - if (is_focused) - { - ImGui::SetScrollHereY(0.0f); - _focused_effect = std::numeric_limits::max(); - } - - if (current_tree_is_open) - { - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(_imgui_context->Style.FramePadding.x, 0)); - if (imgui_popup_button("Reset all to default", _variable_editor_tabs ? ImGui::GetContentRegionAvailWidth() : ImGui::CalcItemWidth())) - { - ImGui::Text("Do you really want to reset all values in '%s' to their defaults?", filename.c_str()); - - if (ImGui::Button("Yes", ImVec2(ImGui::GetContentRegionAvailWidth(), 0))) - { - for (uniform &reset_variable : _uniforms) - if (reset_variable.effect_index == current_effect) - reset_uniform_value(reset_variable); - - save_current_preset(); - - ImGui::CloseCurrentPopup(); - } - - ImGui::EndPopup(); - } - ImGui::PopStyleVar(); - } - } - - // Skip rendering invisible items - if (!current_tree_is_open) - continue; - - if (const std::string_view category = variable.annotation_as_string("ui_category"); - category != current_category) - { - current_category = category; - - if (!category.empty()) - { - std::string label(category.data(), category.size()); - if (!_variable_editor_tabs) - for (float x = 0, space_x = ImGui::CalcTextSize(" ").x, width = (ImGui::CalcItemWidth() - ImGui::CalcTextSize(label.data()).x - 45) / 2; x < width; x += space_x) - label.insert(0, " "); - - current_category_is_closed = !ImGui::TreeNodeEx(label.c_str(), ImGuiTreeNodeFlags_DefaultOpen | ImGuiTreeNodeFlags_NoTreePushOnOpen); - } - else - { - current_category_is_closed = false; - } - } - - // Skip rendering invisible items - if (current_category_is_closed) - continue; - - bool modified = false; - std::string_view label = variable.annotation_as_string("ui_label"); - if (label.empty()) label = variable.name; - const std::string_view ui_type = variable.annotation_as_string("ui_type"); - - ImGui::PushID(static_cast(index)); - - switch (variable.type.base) - { - case reshadefx::type::t_bool: { - bool data; - get_uniform_value(variable, &data, 1); - - if (ui_type == "combo") - { - int current_item = data ? 1 : 0; - modified = ImGui::Combo(label.data(), ¤t_item, "Off\0On\0"); - data = current_item != 0; - } - else - { - modified = ImGui::Checkbox(label.data(), &data); - } - - if (modified) - set_uniform_value(variable, &data, 1); - break; } - case reshadefx::type::t_int: - case reshadefx::type::t_uint: { - int data[4]; - get_uniform_value(variable, data, 4); - - const auto ui_min_val = variable.annotation_as_int("ui_min"); - const auto ui_max_val = variable.annotation_as_int("ui_max"); - const auto ui_stp_val = std::max(1, variable.annotation_as_int("ui_step")); - - if (ui_type == "slider") - modified = imgui_slider_with_buttons(label.data(), variable.type.is_signed() ? ImGuiDataType_S32 : ImGuiDataType_U32, data, variable.type.rows, &ui_stp_val, &ui_min_val, &ui_max_val); - else if (ui_type == "drag") - modified = variable.annotations.find("ui_step") == variable.annotations.end() ? - ImGui::DragScalarN(label.data(), variable.type.is_signed() ? ImGuiDataType_S32 : ImGuiDataType_U32, data, variable.type.rows, 1.0f, &ui_min_val, &ui_max_val) : - imgui_drag_with_buttons(label.data(), variable.type.is_signed() ? ImGuiDataType_S32 : ImGuiDataType_U32, data, variable.type.rows, &ui_stp_val, &ui_min_val, &ui_max_val); - else if (ui_type == "list") - modified = imgui_list_with_buttons(label.data(), variable.annotation_as_string("ui_items"), data[0]); - else if (ui_type == "combo") { - const std::string_view ui_items = variable.annotation_as_string("ui_items"); - std::string items(ui_items.data(), ui_items.size()); - // Make sure list is terminated with a zero in case user forgot so no invalid memory is read accidentally - if (ui_items.empty() || ui_items.back() != '\0') - items.push_back('\0'); - - modified = ImGui::Combo(label.data(), data, items.c_str()); - } - else if (ui_type == "radio") { - const std::string_view ui_items = variable.annotation_as_string("ui_items"); - ImGui::BeginGroup(); - for (size_t offset = 0, next, i = 0; (next = ui_items.find('\0', offset)) != std::string::npos; offset = next + 1, ++i) - modified |= ImGui::RadioButton(ui_items.data() + offset, data, static_cast(i)); - ImGui::EndGroup(); - } - else - modified = ImGui::InputScalarN(label.data(), variable.type.is_signed() ? ImGuiDataType_S32 : ImGuiDataType_U32, data, variable.type.rows); - - if (modified) - set_uniform_value(variable, data, 4); - break; } - case reshadefx::type::t_float: { - float data[4]; - get_uniform_value(variable, data, 4); - - const auto ui_min_val = variable.annotation_as_float("ui_min"); - const auto ui_max_val = variable.annotation_as_float("ui_max"); - const auto ui_stp_val = std::max(0.001f, variable.annotation_as_float("ui_step")); - - // Calculate display precision based on step value - char precision_format[] = "%.0f"; - for (float x = ui_stp_val; x < 1.0f && precision_format[2] < '9'; x *= 10.0f) - ++precision_format[2]; // This changes the text to "%.1f", "%.2f", "%.3f", ... - - if (ui_type == "slider") - modified = imgui_slider_with_buttons(label.data(), ImGuiDataType_Float, data, variable.type.rows, &ui_stp_val, &ui_min_val, &ui_max_val, precision_format); - else if (ui_type == "drag") - modified = variable.annotations.find("ui_step") == variable.annotations.end() ? - ImGui::DragScalarN(label.data(), ImGuiDataType_Float, data, variable.type.rows, ui_stp_val, &ui_min_val, &ui_max_val, precision_format) : - imgui_drag_with_buttons(label.data(), ImGuiDataType_Float, data, variable.type.rows, &ui_stp_val, &ui_min_val, &ui_max_val, precision_format); - else if (ui_type == "color" && variable.type.rows == 1) - modified = imgui_slider_for_alpha(label.data(), data); - else if (ui_type == "color" && variable.type.rows == 3) - modified = ImGui::ColorEdit3(label.data(), data, ImGuiColorEditFlags_NoOptions); - else if (ui_type == "color" && variable.type.rows == 4) - modified = ImGui::ColorEdit4(label.data(), data, ImGuiColorEditFlags_NoOptions | ImGuiColorEditFlags_AlphaPreview | ImGuiColorEditFlags_AlphaBar); - else - modified = ImGui::InputScalarN(label.data(), ImGuiDataType_Float, data, variable.type.rows); - - if (modified) - set_uniform_value(variable, data, 4); - break; } - } - - // Display tooltip - if (const std::string_view tooltip = variable.annotation_as_string("ui_tooltip"); - !tooltip.empty() && ImGui::IsItemHovered()) - ImGui::SetTooltip("%s", tooltip.data()); - - // Create context menu - if (ImGui::BeginPopupContextItem("##context")) - { - if (ImGui::Button("Reset to default", ImVec2(200, 0))) - { - modified = true; - reset_uniform_value(variable); - ImGui::CloseCurrentPopup(); - } - - ImGui::EndPopup(); - } - - ImGui::PopID(); - - // A value has changed, so save the current preset - if (modified) - save_current_preset(); - } - - ImGui::PopItemWidth(); - - if (_variable_editor_tabs) - { - if (current_tree_is_open) - ImGui::EndTabItem(); - - ImGui::EndTabBar(); - } - else - { - if (current_tree_is_open) - ImGui::TreePop(); - } - - ImGui::EndChild(); -} - -void reshade::runtime::draw_overlay_technique_editor() -{ - size_t hovered_technique_index = std::numeric_limits::max(); - - for (size_t index = 0; index < _techniques.size(); ++index) - { - technique &technique = _techniques[index]; - - // Skip hidden techniques - if (technique.hidden) - continue; - - ImGui::PushID(static_cast(index)); - - // Draw border around the item if it is selected - const bool draw_border = _selected_technique == index; - if (draw_border) - ImGui::Separator(); - - const bool clicked = _imgui_context->IO.MouseClicked[0]; - const bool compile_success = _loaded_effects[technique.effect_index].compile_sucess; - assert(compile_success || !technique.enabled); - - // Prevent user from enabling the technique when the effect failed to compile - ImGui::PushItemFlag(ImGuiItemFlags_Disabled, !compile_success); - // Gray out disabled techniques and mark techniques which failed to compile red - ImGui::PushStyleColor(ImGuiCol_Text, compile_success ? _imgui_context->Style.Colors[technique.enabled ? ImGuiCol_Text : ImGuiCol_TextDisabled] : COLOR_RED); - - std::string_view ui_label = technique.annotation_as_string("ui_label"); - if (ui_label.empty() || !compile_success) ui_label = technique.name; - std::string label(ui_label.data(), ui_label.size()); - label += " [" + _loaded_effects[technique.effect_index].source_file.filename().u8string() + ']' + (!compile_success ? " (failed to compile)" : ""); - - if (bool status = technique.enabled; ImGui::Checkbox(label.data(), &status)) - { - if (status) - enable_technique(technique); - else - disable_technique(technique); - save_current_preset(); - } - - ImGui::PopStyleColor(); - ImGui::PopItemFlag(); - - if (ImGui::IsItemActive()) - _selected_technique = index; - if (ImGui::IsItemClicked()) - _focused_effect = technique.effect_index; - if (ImGui::IsItemHovered(ImGuiHoveredFlags_RectOnly)) - hovered_technique_index = index; - - // Display tooltip - if (const std::string_view tooltip = compile_success ? technique.annotation_as_string("ui_tooltip") : _loaded_effects[technique.effect_index].errors; - !tooltip.empty() && ImGui::IsItemHovered()) - { - ImGui::BeginTooltip(); - if (!compile_success) ImGui::PushStyleColor(ImGuiCol_Text, COLOR_RED); - ImGui::TextUnformatted(tooltip.data()); - if (!compile_success) ImGui::PopStyleColor(); - ImGui::EndTooltip(); - } - - // Create context menu - if (ImGui::BeginPopupContextItem("##context")) - { - if (imgui_key_input("Toggle Key", technique.toggle_key_data, *_input)) - save_current_preset(); - _ignore_shortcuts |= ImGui::IsItemActive(); - - ImGui::Separator(); - - const bool is_not_top = index > 0; - const bool is_not_bottom = index < _techniques.size() - 1; - const float button_width = ImGui::CalcItemWidth(); - - if (is_not_top && ImGui::Button("Move up", ImVec2(button_width, 0))) - { - std::swap(_techniques[index], _techniques[index - 1]); - save_current_preset(); - ImGui::CloseCurrentPopup(); - } - if (is_not_bottom && ImGui::Button("Move down", ImVec2(button_width, 0))) - { - std::swap(_techniques[index], _techniques[index + 1]); - save_current_preset(); - ImGui::CloseCurrentPopup(); - } - - if (is_not_top && ImGui::Button("Move to top", ImVec2(button_width, 0))) - { - _techniques.insert(_techniques.begin(), std::move(_techniques[index])); - _techniques.erase(_techniques.begin() + 1 + index); - save_current_preset(); - ImGui::CloseCurrentPopup(); - } - if (is_not_bottom && ImGui::Button("Move to bottom", ImVec2(button_width, 0))) - { - _techniques.push_back(std::move(_techniques[index])); - _techniques.erase(_techniques.begin() + index); - save_current_preset(); - ImGui::CloseCurrentPopup(); - } - - ImGui::EndPopup(); - } - - if (technique.toggle_key_data[0] != 0 && compile_success) - { - ImGui::SameLine(ImGui::GetWindowContentRegionWidth() * 0.75f); - ImGui::TextDisabled("%s", reshade::input::key_name(technique.toggle_key_data).c_str()); - } - - if (draw_border) - ImGui::Separator(); - - ImGui::PopID(); - } - - // Move the selected technique to the position of the mouse in the list - if (_selected_technique < _techniques.size() && ImGui::IsMouseDragging()) - { - if (hovered_technique_index < _techniques.size() && hovered_technique_index != _selected_technique) - { - std::swap(_techniques[hovered_technique_index], _techniques[_selected_technique]); - _selected_technique = hovered_technique_index; - save_current_preset(); - } - } - else - { - _selected_technique = std::numeric_limits::max(); - } -} - -void reshade::runtime::draw_preset_explorer() -{ - static const std::filesystem::path reshade_container_path = g_reshade_dll_path.parent_path(); - const ImVec2 cursor_pos = ImGui::GetCursorScreenPos(); - const float button_size = ImGui::GetFrameHeight(); - const float button_spacing = _imgui_context->Style.ItemInnerSpacing.x; - const float root_window_width = ImGui::GetWindowContentRegionWidth(); - - std::error_code ec; - - enum class condition { pass, select, popup_add, create, backward, forward, reload, cancel }; - condition condition = condition::pass; - - if (ImGui::ButtonEx("<", ImVec2(button_size, 0), ImGuiButtonFlags_NoNavFocus)) - condition = condition::backward, _current_browse_path = _current_preset_path; - - if (ImGui::SameLine(0, button_spacing); - ImGui::ButtonEx(">", ImVec2(button_size, 0), ImGuiButtonFlags_NoNavFocus)) - condition = condition::forward, _current_browse_path = _current_preset_path; - - ImGui::PushStyleVar(ImGuiStyleVar_ButtonTextAlign, ImVec2(0.0f, 0.5f)); - if (ImGui::SameLine(0, button_spacing); - ImGui::ButtonEx(_current_preset_path.stem().u8string().c_str(), ImVec2(root_window_width - (button_spacing + button_size) * 3, 0), ImGuiButtonFlags_NoNavFocus)) - if (ImGui::OpenPopup("##explore"), _imgui_context->IO.KeyCtrl) - _browse_path_is_input_mode = true; - ImGui::PopStyleVar(); - - if (ImGui::SameLine(0, button_spacing); ImGui::ButtonEx("+", ImVec2(button_size, 0), ImGuiButtonFlags_PressedOnClick | ImGuiButtonFlags_NoNavFocus)) - condition = condition::popup_add, _current_browse_path = _current_preset_path.parent_path(); - - ImGui::SetNextWindowPos(cursor_pos - _imgui_context->Style.WindowPadding); - const bool is_explore_open = ImGui::BeginPopup("##explore"); - if (is_explore_open) - { - if (ImGui::ButtonEx("<", ImVec2(button_size, 0), ImGuiButtonFlags_NoNavFocus)) - condition = condition::backward, _current_browse_path = _current_preset_path; - - if (ImGui::SameLine(0, button_spacing); - ImGui::ButtonEx(">", ImVec2(button_size, 0), ImGuiButtonFlags_NoNavFocus)) - condition = condition::forward, _current_browse_path = _current_preset_path; - - ImGui::SameLine(0, button_spacing); - if (_browse_path_is_input_mode) - { - char buf[_MAX_PATH]{}; - _current_browse_path.u8string().copy(buf, sizeof(buf) - 1); - - const bool is_edited = ImGui::InputTextEx("##path", buf, sizeof(buf), ImVec2(root_window_width - (button_spacing + button_size) * 3, 0), ImGuiInputTextFlags_None); - const bool is_returned = ImGui::IsKeyPressedMap(ImGuiKey_Enter); - - if (ImGui::IsWindowAppearing()) - ImGui::SetKeyboardFocusHere(); - else if (!ImGui::IsItemActive()) - _browse_path_is_input_mode = false; - - if (is_edited || is_returned) - { - std::filesystem::path input_preset_path = std::filesystem::u8path(buf); - std::filesystem::file_type file_type = std::filesystem::status(reshade_container_path / input_preset_path, ec).type(); - - if (is_edited && ec.value() != 0x7b) // 0x7b: ERROR_INVALID_NAME - _current_browse_path = std::move(input_preset_path); - - if (is_returned) - { - if (_current_browse_path.empty()) - condition = condition::cancel; - else if (ec.value() == 0x7b) // 0x7b: ERROR_INVALID_NAME - condition = condition::pass; - else if (file_type == std::filesystem::file_type::directory) - condition = condition::popup_add; - else - { - if (_current_browse_path.has_filename()) - if (const std::wstring extension(_current_browse_path.extension()); extension != L".ini" && extension != L".txt") - _current_browse_path += L".ini", - file_type = std::filesystem::status(reshade_container_path / _current_browse_path, ec).type(); - - if (ec.value() == 0x7b) // 0x7b: ERROR_INVALID_NAME - condition = condition::pass; - else if (file_type == std::filesystem::file_type::directory) - condition = condition::popup_add; - else if (file_type == std::filesystem::file_type::not_found) - if (_current_browse_path.has_filename()) - condition = condition::create; - else - condition = condition::popup_add; - else - condition = condition::select; - } - } - } - } - else - { - ImGui::PushStyleVar(ImGuiStyleVar_ButtonTextAlign, ImVec2(0.0f, 0.5f)); - if (ImGui::ButtonEx(_current_preset_path.stem().u8string().c_str(), ImVec2(root_window_width - (button_spacing + button_size) * 3, 0), ImGuiButtonFlags_NoNavFocus)) - if (_imgui_context->IO.KeyCtrl) - ImGui::ActivateItem(ImGui::GetID("##path")), _browse_path_is_input_mode = true; - else - condition = condition::cancel; - else if (ImGui::IsKeyPressedMap(ImGuiKey_Enter)) - condition = condition::reload, _current_browse_path = _current_preset_path; - ImGui::PopStyleVar(); - } - } - - if (is_explore_open || condition == condition::backward || condition == condition::forward) - { - std::filesystem::path preset_container_path = std::filesystem::absolute(reshade_container_path / _current_browse_path); - if (!std::filesystem::is_directory(preset_container_path, ec) && preset_container_path.has_filename()) - preset_container_path = preset_container_path.parent_path(); - - std::vector preset_container; - for (const auto &entry : std::filesystem::directory_iterator(preset_container_path, std::filesystem::directory_options::skip_permission_denied, ec)) - preset_container.push_back(entry); - - if (condition == condition::backward || condition == condition::forward) - { - std::vector preset_paths; - for (const auto &entry : preset_container) - if (!entry.is_directory()) - if (const std::wstring extension(entry.path().extension()); extension == L".ini" || extension == L".txt") - if (const reshade::ini_file preset(entry); preset.has("", "TechniqueSorting")) - preset_paths.push_back(entry); - - if (preset_paths.begin() == preset_paths.end()) - condition = condition::pass; - else - { - const std::filesystem::path ¤t_preset_path = _current_preset_path; - if (auto it = std::find_if(preset_paths.begin(), preset_paths.end(), [&ec, ¤t_preset_path](const std::filesystem::directory_entry &entry) { return std::filesystem::equivalent(entry, current_preset_path, ec); }); it == preset_paths.end()) - if (condition == condition::backward) - _current_preset_path = _current_browse_path = preset_paths.back(); - else - _current_preset_path = _current_browse_path = preset_paths.front(); - else - if (condition == condition::backward) - _current_preset_path = _current_browse_path = it == preset_paths.begin() ? preset_paths.back() : *--it; - else - _current_preset_path = _current_browse_path = it == preset_paths.end() - 1 ? preset_paths.front() : *++it; - } - } - - if (is_explore_open) - { - if (ImGui::SameLine(0, button_spacing); ImGui::ButtonEx("+", ImVec2(button_size, 0), ImGuiButtonFlags_PressedOnClick | ImGuiButtonFlags_NoNavFocus)) - if (const std::filesystem::file_type file_type = std::filesystem::status(reshade_container_path / _current_browse_path, ec).type(); ec.value() != 0x7b) // 0x7b: ERROR_INVALID_NAME - if (condition = condition::popup_add; file_type == std::filesystem::file_type::not_found || file_type != std::filesystem::file_type::directory) - _current_browse_path = _current_browse_path.parent_path(); - - if (ImGui::IsWindowAppearing() || condition == condition::backward || condition == condition::forward) - ImGui::SetNextWindowFocus(); - - if (ImGui::BeginChild("##paths", ImVec2(0, 300), true)) - { - if (ImGui::Selectable("..")) - { - for (_current_browse_path = std::filesystem::absolute(reshade_container_path / _current_browse_path); - !std::filesystem::is_directory(_current_browse_path, ec) && _current_browse_path.parent_path() != _current_browse_path;) - _current_browse_path = _current_browse_path.parent_path(); - _current_browse_path = _current_browse_path.parent_path(); - - if (std::filesystem::equivalent(reshade_container_path, _current_browse_path)) - _current_browse_path = L"."; - else if (std::equal(reshade_container_path.begin(), reshade_container_path.end(), _current_browse_path.begin())) - _current_browse_path = _current_browse_path.lexically_proximate(reshade_container_path); - } - - std::vector preset_paths; - for (const auto &entry : preset_container) - if (const std::filesystem::file_type file_type = entry.status(ec).type(); file_type == std::filesystem::file_type::directory) - if (ImGui::Selectable((" " + entry.path().filename().u8string()).c_str())) - if (std::filesystem::equivalent(reshade_container_path, entry)) - _current_browse_path = L"."; - else if (std::equal(reshade_container_path.begin(), reshade_container_path.end(), entry.path().begin())) - _current_browse_path = entry.path().lexically_proximate(reshade_container_path); - else - _current_browse_path = entry; - else {} - else if (const std::wstring extension(entry.path().extension()); extension == L".ini" || extension == L".txt") - preset_paths.push_back(entry); - - for (const auto &entry : preset_paths) - { - const bool is_current_preset = std::filesystem::equivalent(entry, _current_preset_path, ec); - - if (ImGui::Selectable(entry.path().filename().u8string().c_str(), static_cast(is_current_preset))) - condition = condition::select, _current_browse_path = entry.path().lexically_proximate(reshade_container_path); - - if (is_current_preset) - if (_current_preset_is_covered && !ImGui::IsWindowAppearing()) - _current_preset_is_covered = false, ImGui::SetScrollHereY(); - else if (condition == condition::backward || condition == condition::forward) - ImGui::SetScrollHereY(); - } - } - ImGui::EndChild(); - } - } - - if (condition == condition::select) - if (const reshade::ini_file preset(reshade_container_path / _current_browse_path); !preset.has("", "TechniqueSorting")) - condition = condition::pass; - - if (condition == condition::popup_add) - condition = condition::pass, ImGui::OpenPopup("##name"); - - ImGui::SetNextWindowPos(cursor_pos + ImVec2(root_window_width + button_size - 230, button_size)); - if (ImGui::BeginPopup("##name")) - { - if (ImGui::IsWindowAppearing()) - ImGui::SetKeyboardFocusHere(); - - char filename[_MAX_PATH]{}; - ImGui::InputText("Name", filename, sizeof(filename)); - - if (ImGui::IsKeyPressedMap(ImGuiKey_Enter)) - { - if (std::filesystem::path input_preset_path = std::filesystem::u8path(filename); input_preset_path.has_filename()) - { - if (const std::wstring extension(input_preset_path.extension()); extension != L".ini" && extension != L".txt") - input_preset_path += L".ini"; - if (const std::filesystem::file_type file_type = std::filesystem::status(reshade_container_path / _current_browse_path / input_preset_path, ec).type(); ec.value() == 0x7b) // 0x7b: ERROR_INVALID_NAME - condition = condition::pass; - else if (file_type == std::filesystem::file_type::directory) - condition = condition::pass; - else if (file_type == std::filesystem::file_type::not_found) - condition = condition::create; - else if (const reshade::ini_file preset(reshade_container_path / _current_browse_path / input_preset_path); preset.has("", "TechniqueSorting")) - condition = condition::select; - else - condition = condition::pass; - - if (condition != condition::pass) - _current_browse_path /= input_preset_path; - } - } - - if (condition != condition::pass) - ImGui::CloseCurrentPopup(); - - ImGui::EndPopup(); - } - - if (condition != condition::pass) - { - if (condition != condition::cancel) - { - _show_splash = true; - set_current_preset(); - - save_config(); - load_current_preset(); - } - if (is_explore_open && condition != condition::backward && condition != condition::forward) - ImGui::CloseCurrentPopup(); - } - - if (is_explore_open) - ImGui::EndPopup(); - else if (!_current_preset_is_covered) - _current_preset_is_covered = true; -} - -#endif diff --git a/msvc/source/gui_code_editor.cpp b/msvc/source/gui_code_editor.cpp deleted file mode 100644 index bcc4903..0000000 --- a/msvc/source/gui_code_editor.cpp +++ /dev/null @@ -1,1518 +0,0 @@ -/** - * Copyright (C) 2018 Patrick Mours - * Copyright (C) 2017 BalazsJako - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#include "gui_code_editor.hpp" -#include "effect_lexer.hpp" -#include - -const char *imgui_code_editor::get_palette_color_name(unsigned int col) -{ - switch (col) - { - case color_default: - return "Default"; - case color_keyword: - return "Keyword"; - case color_number_literal: - return "NumberLiteral"; - case color_string_literal: - return "StringLiteral"; - case color_punctuation: - return "Punctuation"; - case color_preprocessor: - return "Preprocessor"; - case color_identifier: - return "Identifier"; - case color_known_identifier: - return "KnownIdentifier"; - case color_preprocessor_identifier: - return "PreprocessorIdentifier"; - case color_comment: - return "Comment"; - case color_multiline_comment: - return "MultilineComment"; - case color_background: - return "Background"; - case color_cursor: - return "Cursor"; - case color_selection: - return "Selection"; - case color_error_marker: - return "ErrorMarker"; - case color_warning_marker: - return "WarningMarker"; - case color_line_number: - return "LineNumber"; - case color_current_line_fill: - return "LineNumberFill"; - case color_current_line_fill_inactive: - return "LineNumberFillInactive"; - case color_current_line_edge: - return "CurrentLineEdge"; - } - return nullptr; -} - -imgui_code_editor::imgui_code_editor() -{ - _lines.emplace_back(); -} - -void imgui_code_editor::render(const char *title, bool border) -{ - assert(!_lines.empty()); - - ImGui::PushStyleColor(ImGuiCol_ChildBg, ImGui::ColorConvertU32ToFloat4(_palette[color_background])); - ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0.0f, 0.0f)); - - ImGui::BeginChild(title, ImVec2(), border, ImGuiWindowFlags_HorizontalScrollbar | ImGuiWindowFlags_AlwaysHorizontalScrollbar | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoNavInputs); - ImGui::PushAllowKeyboardFocus(true); - - char buf[128] = "", *buf_end = buf; - - // 'ImGui::CalcTextSize' cancels out character spacing at the end, but we do not want that, hence the custom function - const auto calc_text_size = [](const char *text, const char *text_end = nullptr) { - return ImGui::GetFont()->CalcTextSizeA(ImGui::GetFontSize(), FLT_MAX, -1.0f, text, text_end, nullptr); - }; - - // Deduce text start offset by evaluating maximum number of lines plus two spaces as text width - snprintf(buf, 16, " %zu ", _lines.size()); - const float text_start = ImGui::CalcTextSize(buf).x + _left_margin; - // The following holds the approximate width and height of a default character for offset calculation - const ImVec2 char_advance = ImVec2(calc_text_size(" ").x, ImGui::GetTextLineHeightWithSpacing() * _line_spacing); - - ImGuiIO &io = ImGui::GetIO(); - _cursor_anim += io.DeltaTime; - - const bool ctrl = io.KeyCtrl, shift = io.KeyShift, alt = io.KeyAlt; - - // Handle keyboard input - if (ImGui::IsWindowFocused()) - { - if (ImGui::IsWindowHovered()) - ImGui::SetMouseCursor(ImGuiMouseCursor_TextInput); - - io.WantTextInput = true; - io.WantCaptureKeyboard = true; - - if (ctrl && !shift && !alt && ImGui::IsKeyPressed('Z')) - undo(); - else if (ctrl && !shift && !alt && ImGui::IsKeyPressed('Y')) - redo(); - else if (!ctrl && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_UpArrow))) - if (alt && !shift) // Alt + Up moves the current line one up - move_lines_up(); - else - move_up(1, shift); - else if (!ctrl && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_DownArrow))) - if (alt && !shift) // Alt + Down moves the current line one down - move_lines_down(); - else - move_down(1, shift); - else if (!alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_LeftArrow))) - move_left(1, shift, ctrl); - else if (!alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_RightArrow))) - move_right(1, shift, ctrl); - else if (!alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_PageUp))) - move_up(static_cast(floor((ImGui::GetWindowHeight() - 20.0f) / char_advance.y) - 4), shift); - else if (!alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_PageDown))) - move_down(static_cast(floor((ImGui::GetWindowHeight() - 20.0f) / char_advance.y) - 4), shift); - else if (!alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Home))) - ctrl ? move_top(shift) : move_home(shift); - else if (!alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_End))) - ctrl ? move_bottom(shift) : move_end(shift); - else if (!ctrl && !shift && !alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Delete))) - delete_next(); - else if (!ctrl && !shift && !alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Backspace))) - delete_previous(); - else if (!alt && ImGui::IsKeyPressed(45)) // Insert - ctrl ? clipboard_copy() : shift ? clipboard_paste() : _overwrite ^= true; - else if (ctrl && !shift && !alt && ImGui::IsKeyPressed('C')) - clipboard_copy(); - else if (ctrl && !shift && !alt && ImGui::IsKeyPressed('V')) - clipboard_paste(); - else if (ctrl && !shift && !alt && ImGui::IsKeyPressed('X')) - clipboard_cut(); - else if (ctrl && !shift && !alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_A))) - select_all(); - else if (!ctrl && !shift && !alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Enter))) - insert_character('\n', true); - else - for (ImWchar c : io.InputQueueCharacters) - if (c != 0 && (isprint(c) || isspace(c))) - insert_character(static_cast(c), true); - } - - // Handle mouse input - if (ImGui::IsWindowHovered() && !alt) - { - const auto mouse_to_text_pos = [this, text_start, &char_advance, &calc_text_size]() { - const ImVec2 pos(ImGui::GetMousePos().x - ImGui::GetCursorScreenPos().x, ImGui::GetMousePos().y - ImGui::GetCursorScreenPos().y); - - text_pos res; - res.line = std::max(0, static_cast(floor(pos.y / char_advance.y))); - res.line = std::min(res.line, _lines.size() - 1); - - float column_width = 0.0f; - std::string cumulated_string = ""; - float cumulated_string_width[2] = { 0.0f, 0.0f }; // [0] is the latest, [1] is the previous. I use that trick to check where cursor is exactly (important for tabs). - - const auto &line = _lines[res.line]; - - // First we find the hovered column - while (text_start + cumulated_string_width[0] < pos.x && res.column < line.size()) - { - cumulated_string_width[1] = cumulated_string_width[0]; - cumulated_string += line[res.column].c; - cumulated_string_width[0] = calc_text_size(cumulated_string.c_str()).x; - column_width = (cumulated_string_width[0] - cumulated_string_width[1]); - res.column++; - } - - // Then we reduce by 1 column if cursor is on the left side of the hovered column - if (text_start + cumulated_string_width[0] - column_width / 2.0f > pos.x && res.column > 0) - res.column--; - - return res; - }; - - const bool is_clicked = ImGui::IsMouseClicked(0); - const bool is_double_click = !shift && ImGui::IsMouseDoubleClicked(0); - const bool is_triple_click = !shift && is_clicked && !is_double_click && ImGui::GetTime() - _last_click_time < io.MouseDoubleClickTime; - - if (is_triple_click) - { - if (!ctrl) - { - _cursor_pos = mouse_to_text_pos(); - _interactive_beg = _cursor_pos; - _interactive_end = _cursor_pos; - - select(_interactive_beg, _interactive_end, selection_mode::line); - } - - _last_click_time = -1.0; - } - else if (is_double_click) - { - if (!ctrl) - { - _cursor_pos = mouse_to_text_pos(); - _interactive_beg = _cursor_pos; - _interactive_end = _cursor_pos; - - select(_interactive_beg, _interactive_end, selection_mode::word); - } - - _last_click_time = ImGui::GetTime(); - } - else if (is_clicked) - { - const bool flip_selection = _cursor_pos > _select_beg; - - _cursor_pos = mouse_to_text_pos(); - _interactive_beg = _cursor_pos; - _interactive_end = _cursor_pos; - - if (shift) - if (flip_selection) - _interactive_beg = _select_beg; - else - _interactive_end = _select_end; - - select(_interactive_beg, _interactive_end, ctrl ? selection_mode::word : selection_mode::normal); - - _last_click_time = ImGui::GetTime(); - } - else if (ImGui::IsMouseDragging(0) && ImGui::IsMouseDown(0)) // Update selection while left mouse is dragging - { - io.WantCaptureMouse = true; - - _cursor_pos = mouse_to_text_pos(); - _interactive_end = _cursor_pos; - - select(_interactive_beg, _interactive_end, selection_mode::normal); - } - } - - // Update glyph colors - colorize(); - - const auto draw_list = ImGui::GetWindowDrawList(); - - float longest_line = 0.0f; - const float space_size = calc_text_size(" ").x; - - size_t line_no = static_cast(floor(ImGui::GetScrollY() / char_advance.y)); - size_t line_max = std::max(0, std::min(_lines.size() - 1, line_no + static_cast(floor((ImGui::GetScrollY() + ImGui::GetWindowContentRegionMax().y) / char_advance.y)))); - - const auto calc_text_distance_to_line_begin = [this, space_size, &calc_text_size](const text_pos &from) { - float distance = 0.0f; - const auto &line = _lines[from.line]; - for (size_t i = 0u; i < line.size() && i < from.column; ++i) - if (line[i].c == '\t') - distance += _tab_size * space_size; - else - distance += calc_text_size(&line[i].c, &line[i].c + 1).x; - return distance; - }; - - for (; line_no <= line_max; ++line_no, buf_end = buf) - { - const auto &line = _lines[line_no]; - - // Position of the line number - const ImVec2 line_screen_pos = ImVec2(ImGui::GetCursorScreenPos().x, ImGui::GetCursorScreenPos().y + line_no * char_advance.y); - // Position of the text inside the editor - const ImVec2 text_screen_pos = ImVec2(line_screen_pos.x + text_start, line_screen_pos.y); - - const text_pos line_beg_pos(line_no, 0); - const text_pos line_end_pos(line_no, line.size()); - longest_line = std::max(calc_text_distance_to_line_begin(line_end_pos), longest_line); - - // Calculate selection rectangle - float selection_beg = -1.0f; - if (_select_beg <= line_end_pos) - selection_beg = _select_beg > line_beg_pos ? calc_text_distance_to_line_begin(_select_beg) : 0.0f; - float selection_end = -1.0f; - if (_select_end > line_beg_pos) - selection_end = calc_text_distance_to_line_begin(_select_end < line_end_pos ? _select_end : line_end_pos); - - // Add small overhead rectangle at the end of selected lines - if (_select_end.line > line_no) - selection_end += char_advance.x; - - // Draw selected area - if (selection_beg != -1 && selection_end != -1 && selection_beg < selection_end) - { - const ImVec2 beg = ImVec2(text_screen_pos.x + selection_beg, text_screen_pos.y); - const ImVec2 end = ImVec2(text_screen_pos.x + selection_end, text_screen_pos.y + char_advance.y); - - draw_list->AddRectFilled(beg, end, _palette[color_selection]); - } - - // Find any highlighted words and draw a selection rectangle below them - if (!_highlighted.empty()) - { - size_t begin_column = 0; - size_t highlight_index = 0; - - for (size_t i = 0; i < line.size(); ++i) - { - if (line[i].c == _highlighted[highlight_index] && line[i].col == color_identifier) - { - if (highlight_index == 0) - begin_column = i; - - ++highlight_index; - - if (highlight_index == _highlighted.size()) - { - if ((begin_column == 0 || line[begin_column - 1].col != color_identifier) && (i + 1 == line.size() || line[i + 1].col != color_identifier)) // Make sure this is a whole word and not just part of one - { - // We found a matching text block - const ImVec2 beg = ImVec2(text_screen_pos.x + calc_text_distance_to_line_begin(text_pos(line_no, begin_column)), text_screen_pos.y); - const ImVec2 end = ImVec2(text_screen_pos.x + calc_text_distance_to_line_begin(text_pos(line_no, i + 1)), text_screen_pos.y + char_advance.y); - - draw_list->AddRectFilled(beg, end, _palette[color_selection]); - } - } - else - { - // Continue search - continue; - } - } - - // Current text no longer matches, reset search - highlight_index = 0; - } - } - - // Draw error markers - if (auto it = _errors.find(line_no + 1); it != _errors.end()) - { - const ImVec2 beg = ImVec2(line_screen_pos.x + ImGui::GetScrollX(), line_screen_pos.y); - const ImVec2 end = ImVec2(line_screen_pos.x + ImGui::GetWindowContentRegionMax().x + 2.0f * ImGui::GetScrollX(), line_screen_pos.y + char_advance.y); - - draw_list->AddRectFilled(beg, end, _palette[it->second.second ? color_warning_marker : color_error_marker]); - - if (ImGui::IsMouseHoveringRect(beg, end)) - { - ImGui::BeginTooltip(); - ImGui::Text("%s", it->second.first.c_str()); - ImGui::EndTooltip(); - } - } - - // Draw line number (right aligned) - snprintf(buf, 16, "%zu ", line_no + 1); - - draw_list->AddText(ImVec2(text_screen_pos.x - ImGui::CalcTextSize(buf).x, line_screen_pos.y), _palette[color_line_number], buf); - - // Highlight the current line (where the cursor is) - if (_cursor_pos.line == line_no) - { - const bool is_focused = ImGui::IsWindowFocused(); - - if (!has_selection()) - { - const ImVec2 beg = ImVec2(line_screen_pos.x + ImGui::GetScrollX(), line_screen_pos.y); - const ImVec2 end = ImVec2(line_screen_pos.x + ImGui::GetWindowContentRegionMax().x + 2.0f * ImGui::GetScrollX(), line_screen_pos.y + char_advance.y); - - draw_list->AddRectFilled(beg, end, _palette[is_focused ? color_current_line_fill : color_current_line_fill_inactive]); - draw_list->AddRect(beg, end, _palette[color_current_line_edge], 1.0f); - } - - // Draw the cursor animation - if (is_focused && io.ConfigInputTextCursorBlink && fmodf(_cursor_anim, 1.0f) <= 0.5f) - { - const float cx = calc_text_distance_to_line_begin(_cursor_pos); - - const ImVec2 beg = ImVec2(text_screen_pos.x + cx, line_screen_pos.y); - const ImVec2 end = ImVec2(text_screen_pos.x + cx + (_overwrite ? char_advance.x : 1.0f), line_screen_pos.y + char_advance.y); // Larger cursor while overwriting - - draw_list->AddRectFilled(beg, end, _palette[color_cursor]); - } - } - - // Nothing to draw if the line is empty, so continue on - if (line.empty()) - continue; - - // Draw colorized line text - auto text_offset = 0.0f; - auto current_color = line[0].col; - - // Fill temporary buffer with glyph characters and commit it every time the color changes or a tab character is encountered - for (const glyph &glyph : line) - { - if (buf != buf_end && (glyph.col != current_color || glyph.c == '\t' || buf_end - buf >= sizeof(buf))) - { - draw_list->AddText(ImVec2(text_screen_pos.x + text_offset, text_screen_pos.y), _palette[current_color], buf, buf_end); - - text_offset += calc_text_size(buf, buf_end).x; buf_end = buf; // Reset temporary buffer - } - - if (glyph.c != '\t') - *buf_end++ = glyph.c; - else - text_offset += _tab_size * space_size; - - current_color = glyph.col; - } - - // Draw any text still in the temporary buffer that was not yet committed - if (buf != buf_end) - draw_list->AddText(ImVec2(text_screen_pos.x + text_offset, text_screen_pos.y), _palette[current_color], buf, buf_end); - } - - // Create dummy widget so a horizontal scrollbar appears - ImGui::Dummy(ImVec2(text_start + longest_line, _lines.size() * char_advance.y)); - - if (_scroll_to_cursor) - { - const float len = calc_text_distance_to_line_begin(_cursor_pos); - const float extra_space = 8.0f; - - const float max_scroll_width = ImGui::GetWindowWidth() - 16.0f; - const float max_scroll_height = ImGui::GetWindowHeight() - 32.0f; - - if (_cursor_pos.line < (ImGui::GetScrollY()) / char_advance.y) // No additional space when scrolling up - ImGui::SetScrollY(std::max(0.0f, _cursor_pos.line * char_advance.y)); - if (_cursor_pos.line > (ImGui::GetScrollY() + max_scroll_height - extra_space) / char_advance.y) - ImGui::SetScrollY(std::max(0.0f, _cursor_pos.line * char_advance.y + extra_space - max_scroll_height)); - - if (len + text_start < (ImGui::GetScrollX() + extra_space)) - ImGui::SetScrollX(std::max(0.0f, len + text_start - extra_space)); - if (len + text_start > (ImGui::GetScrollX() + max_scroll_width - extra_space)) - ImGui::SetScrollX(std::max(0.0f, len + text_start + extra_space - max_scroll_width)); - - ImGui::SetWindowFocus(); - - _scroll_to_cursor = false; - } - - ImGui::PopAllowKeyboardFocus(); - ImGui::EndChild(); - - ImGui::PopStyleVar(); - ImGui::PopStyleColor(); -} - -void imgui_code_editor::select(const text_pos &beg, const text_pos &end, selection_mode mode) -{ - assert(beg.line < _lines.size()); - assert(end.line < _lines.size()); - assert(beg.column <= _lines[beg.line].size()); // The last column is after the last character in the line - assert(end.column <= _lines[end.line].size()); - - if (end > beg) - _select_beg = beg, - _select_end = end; - else - _select_end = beg, - _select_beg = end; - - const auto select_word = [this](text_pos &beg, text_pos &end) { - const auto &beg_line = _lines[beg.line]; - const auto &end_line = _lines[end.line]; - // Empty lines cannot have any words, so abort - if (beg_line.empty() || end_line.empty()) - return; - // Whitespace has a special meaning in that if we select the space next to a word, then that word is precedence over the whitespace - if (beg.column == beg_line.size() || (beg.column > 0 && beg_line[beg.column].col == color_default)) - beg.column--; - if (end.column == end_line.size() || (end.column > 0 && end_line[end.column].col == color_default)) - end.column--; - // Search from the first position backwards until a character with a different color is found - for (auto word_color = beg_line[beg.column].col; - beg.column > 0 && beg_line[beg.column - 1].col == word_color; - --beg.column) continue; - // Search from the selection end position forwards until a character with a different color is found - for (auto word_color = end_line[end.column].col; - end.column < end_line.size() && end_line[end.column].col == word_color; - ++end.column) continue; - }; - - // Reset cursor animation (so it is always visible when clicking something) - _cursor_anim = 0; - - // Find the identifier under the cursor position - text_pos highlight_beg = _select_beg; - text_pos highlight_end = _select_end; - select_word(highlight_beg, highlight_end); - _highlighted = _lines[highlight_beg.line].size() > highlight_beg.column && _lines[highlight_beg.line][highlight_beg.column].col == color_identifier ? - get_text(highlight_beg, highlight_end) : std::string(); - - switch (mode) - { - case selection_mode::word: - select_word(_select_beg, _select_end); - break; - case selection_mode::line: - _select_beg.column = 0; - _select_end.column = _lines[end.line].size(); - break; - } -} -void imgui_code_editor::select_all() -{ - if (_lines.empty()) - return; // Cannot select anything if no text is set - - // Move cursor to end of text - _cursor_pos = text_pos(_lines.size() - 1, _lines.back().size()); - - // Update selection to contain everything - _interactive_beg = text_pos(0, 0); - _interactive_end = _cursor_pos; - - select(_interactive_beg, _interactive_end); -} - -void imgui_code_editor::set_text(const std::string &text) -{ - _undo.clear(); - _undo_index = 0; - - _lines.clear(); - _lines.emplace_back(); - - _errors.clear(); - - for (char c : text) - { - if (c == '\r') - continue; // Ignore the carriage return character - else if (c == '\n') - _lines.emplace_back(); - else - _lines.back().push_back({ c, color_default }); - } - - // Restrict cursor position to new text bounds - _select_beg = _select_end = text_pos(); - _interactive_beg = _interactive_end = text_pos(); - _cursor_pos = std::min(_cursor_pos, text_pos(_lines.size() - 1, _lines.back().size())); - - _colorize_line_beg = 0; - _colorize_line_end = _lines.size(); -} -void imgui_code_editor::clear_text() -{ - set_text(std::string()); -} -void imgui_code_editor::insert_text(const std::string &text) -{ - // Insert all characters of the text - for (const char c : text) - insert_character(c, false); - - // Move cursor to end of inserted text - select(_cursor_pos, _cursor_pos); -} -void imgui_code_editor::insert_character(char c, bool auto_indent) -{ - undo_record u; - - if (has_selection()) - { - if (c == '\t' && auto_indent) // Pressing tab with a selection indents the entire selection - { - auto &beg = _select_beg; - auto &end = _select_end; - - _colorize_line_beg = std::min(_colorize_line_beg, beg.line); - _colorize_line_end = std::max(_colorize_line_end, end.line + 1); - - beg.column = 0; - if (end.column == 0 && end.line > 0) - end.column = _lines[--end.line].size(); - - u.removed = get_text(beg, end); - u.removed_beg = beg; - u.removed_end = end; - - for (size_t i = beg.line; i <= end.line; i++) - { - auto &line = _lines[i]; - - if (ImGui::GetIO().KeyShift) - { - if (line[0].c == '\t') - { - line.erase(line.begin()); - if (i == end.line && end.column > 0) - end.column--; - } - else for (size_t j = 0; j < _tab_size && line[0].c == ' '; j++) // Do the same for spaces - { - line.erase(line.begin()); - if (i == end.line && end.column > 0) - end.column--; - } - } - else - { - line.insert(line.begin(), { '\t', color_background }); - if (i == end.line) - ++end.column; - } - } - - u.added = get_text(beg, end); - u.added_beg = beg; - u.added_end = end; - record_undo(std::move(u)); - return; - } - - // Otherwise overwrite the selection - delete_selection(); - } - - assert(!_lines.empty()); - - u.added = c; - u.added_beg = _cursor_pos; - - _colorize_line_beg = std::min(_colorize_line_beg, _cursor_pos.line); - - // New line feed requires insertion of a new line - if (c == '\n') - { - // Move all error markers after the new line one up - std::unordered_map> errors; - errors.reserve(_errors.size()); - for (auto &i : _errors) - errors.insert({ i.first >= _cursor_pos.line + 1 ? i.first + 1 : i.first, i.second }); - _errors = std::move(errors); - - auto &new_line = *_lines.emplace(_lines.begin() + _cursor_pos.line + 1); - auto &line = _lines[_cursor_pos.line]; - - // Auto indentation - if (auto_indent && _cursor_pos.column == line.size()) - for (size_t i = 0; i < line.size() && isblank(line[i].c); ++i) - new_line.push_back(line[i]), u.added.push_back(line[i].c); - const size_t indentation = new_line.size(); - - new_line.insert(new_line.end(), line.begin() + _cursor_pos.column, line.end()); - line.erase(line.begin() + _cursor_pos.column, line.begin() + line.size()); - - _cursor_pos.line++; - _cursor_pos.column = indentation; - } - else if (c != '\r') // Ignore carriage return - { - auto &line = _lines[_cursor_pos.line]; - - if (_overwrite && _cursor_pos.column < line.size()) - line[_cursor_pos.column] = { c, color_default }; - else - line.insert(line.begin() + _cursor_pos.column, { c, color_default }); - - _cursor_pos.column++; - } - - u.added_end = _cursor_pos; - record_undo(std::move(u)); - - // Reset cursor animation - _cursor_anim = 0; - - _scroll_to_cursor = true; - - _colorize_line_end = std::max(_colorize_line_end, _cursor_pos.line + 1); -} - -std::string imgui_code_editor::get_text() const -{ - return get_text(0, _lines.size()); -} -std::string imgui_code_editor::get_text(const text_pos &beg, const text_pos &end) const -{ - std::string result; - - for (auto it = beg; it < end;) - { - const auto &line = _lines[it.line]; - - if (it.column < line.size()) - { - result.push_back(line[it.column].c); - - it.column++; - } - else - { - it.line++; - it.column = 0; - - if (it.line < _lines.size()) - // Reached end of line, so append a new line feed - result.push_back('\n'); - } - } - - return result; -} -std::string imgui_code_editor::get_selected_text() const -{ - assert(has_selection()); - - return get_text(_select_beg, _select_end); -} - -void imgui_code_editor::undo(unsigned int steps) -{ - // Reset selection - _select_beg = _select_end = _cursor_pos; - _interactive_beg = _interactive_end = _cursor_pos; - - _in_undo_operation = true; - - while (can_undo() && steps-- > 0) - { - const undo_record &record = _undo[--_undo_index]; - - if (!record.added.empty()) - { - _cursor_pos = record.added_beg; - select(record.added_beg, record.added_end); - delete_selection(); - } - if (!record.removed.empty()) - { - _cursor_pos = record.removed_beg; - insert_text(record.removed); - } - } - - _scroll_to_cursor = true; - _in_undo_operation = false; -} -void imgui_code_editor::redo(unsigned int steps) -{ - // Reset selection - _select_beg = _select_end = _cursor_pos; - _interactive_beg = _interactive_end = _cursor_pos; - - _in_undo_operation = true; - - while (can_redo() && steps-- > 0) - { - const undo_record &record = _undo[_undo_index++]; - - if (!record.removed.empty()) - { - _cursor_pos = record.removed_beg; - select(record.removed_beg, record.removed_end); - delete_selection(); - } - if (!record.added.empty()) - { - _cursor_pos = record.added_beg; - insert_text(record.added); - } - } - - _scroll_to_cursor = true; - _in_undo_operation = false; -} - -void imgui_code_editor::record_undo(undo_record &&record) -{ - if (_in_undo_operation) - return; - - _undo.resize(_undo_index); // Remove all undo records after the current one - _undo.push_back(std::move(record)); // Append new record to the list - _undo_index++; -} - -void imgui_code_editor::delete_next() -{ - if (has_selection()) - { - delete_selection(); - return; - } - - assert(!_lines.empty()); - - auto &line = _lines[_cursor_pos.line]; - - undo_record u; - u.removed_beg = _cursor_pos; - u.removed_end = _cursor_pos; - - // If at end of line, move next line into the current one - if (_cursor_pos.column == line.size()) - { - if (_cursor_pos.line == _lines.size() - 1) - return; // This already is the last line - - const auto &next_line = _lines[_cursor_pos.line + 1]; - - u.removed = '\n'; - u.removed_end.line++; - u.removed_end.column = 0; - - // Copy next line into current line - line.insert(line.end(), next_line.begin(), next_line.end()); - - // Remove the line - delete_lines(_cursor_pos.line + 1, _cursor_pos.line + 1); - } - else - { - u.removed = line[_cursor_pos.column].c; - u.removed_end.column++; - - // Otherwise just remove the character at the cursor position - line.erase(line.begin() + _cursor_pos.column); - } - - record_undo(std::move(u)); - - _colorize_line_beg = std::min(_colorize_line_beg, _cursor_pos.line); - _colorize_line_end = std::max(_colorize_line_end, _cursor_pos.line + 1); -} -void imgui_code_editor::delete_previous() -{ - if (has_selection()) - { - delete_selection(); - return; - } - - assert(!_lines.empty()); - - auto &line = _lines[_cursor_pos.line]; - - undo_record u; - u.removed_end = _cursor_pos; - - // If at beginning of line, move previous line into the current one - if (_cursor_pos.column == 0) - { - if (_cursor_pos.line == 0) - return; // This already is the first line - - auto &prev_line = _lines[_cursor_pos.line - 1]; - _cursor_pos.line--; - _cursor_pos.column = prev_line.size(); - - u.removed = '\n'; - - // Copy current line into previous line - prev_line.insert(prev_line.end(), line.begin(), line.end()); - - // Remove the line - delete_lines(_cursor_pos.line + 1, _cursor_pos.line + 1); - } - else - { - _cursor_pos.column--; - - u.removed = line[_cursor_pos.column].c; - - // Otherwise remove the character next to the cursor position - line.erase(line.begin() + _cursor_pos.column); - } - - u.removed_beg = _cursor_pos; - record_undo(std::move(u)); - - _scroll_to_cursor = true; - - _colorize_line_beg = std::min(_colorize_line_beg, _cursor_pos.line); - _colorize_line_end = std::max(_colorize_line_end, _cursor_pos.line + 1); -} -void imgui_code_editor::delete_selection() -{ - if (!has_selection()) - return; - - assert(!_lines.empty()); - - undo_record u; - u.removed = get_selected_text(); - u.removed_beg = _select_beg; - u.removed_end = _select_end; - record_undo(std::move(u)); - - if (_select_beg.line == _select_end.line) - { - auto &line = _lines[_select_beg.line]; - - if (_select_end.column >= line.size()) - line.erase(line.begin() + _select_beg.column, line.end()); - else - line.erase(line.begin() + _select_beg.column, line.begin() + _select_end.column); - } - else - { - auto &beg_line = _lines[_select_beg.line]; - auto &end_line = _lines[_select_end.line]; - - beg_line.erase(beg_line.begin() + _select_beg.column, beg_line.end()); - end_line.erase(end_line.begin(), end_line.begin() + _select_end.column); - - if (_select_beg.line < _select_end.line) - { - beg_line.insert(beg_line.end(), end_line.begin(), end_line.end()); - - delete_lines(_select_beg.line + 1, _select_end.line); - } - - assert(!_lines.empty()); - } - - _colorize_line_beg = std::min(_colorize_line_beg, _select_beg.line); - _colorize_line_end = std::max(_colorize_line_end, _select_end.line + 1); - - // Reset selection - _cursor_pos = _select_beg; - select(_cursor_pos, _cursor_pos); -} -void imgui_code_editor::delete_lines(size_t first_line, size_t last_line) -{ - // Move all error markers after the deleted lines down - std::unordered_map> errors; - errors.reserve(_errors.size()); - for (auto &i : _errors) - if (i.first < first_line && i.first > last_line) - errors.insert({ i.first > last_line ? i.first - (last_line - first_line) : i.first, i.second }); - _errors = std::move(errors); - - _lines.erase(_lines.begin() + first_line, _lines.begin() + last_line + 1); -} - -void imgui_code_editor::clipboard_copy() -{ - if (has_selection()) - { - ImGui::SetClipboardText(get_selected_text().c_str()); - } - else if (!_lines.empty()) // Copy current line if there is no selection - { - std::string line_text; - line_text.reserve(_lines[_cursor_pos.line].size()); - for (const glyph &glyph : _lines[_cursor_pos.line]) - line_text.push_back(glyph.c); - - ImGui::SetClipboardText(line_text.c_str()); - } -} -void imgui_code_editor::clipboard_cut() -{ - if (!has_selection()) - return; - - clipboard_copy(); - delete_selection(); -} -void imgui_code_editor::clipboard_paste() -{ - const char *const text = ImGui::GetClipboardText(); - - if (text == nullptr || *text == '\0') - return; - - undo_record u; - - _in_undo_operation = true; - - if (has_selection()) - { - u.removed = get_selected_text(); - u.removed_beg = _select_beg; - u.removed_end = _select_end; - - delete_selection(); - } - - u.added = text; - u.added_beg = _cursor_pos; - - insert_text(text); - - u.added_end = _cursor_pos; - - _in_undo_operation = false; - - record_undo(std::move(u)); -} - -void imgui_code_editor::move_up(size_t amount, bool selection) -{ - assert(!_lines.empty()); - - const auto prev_pos = _cursor_pos; - _cursor_pos.line = std::max(0, _cursor_pos.line - amount); - - // The line before could be shorter, so adjust column - _cursor_pos.column = std::min(_cursor_pos.column, _lines[_cursor_pos.line].size()); - - if (prev_pos == _cursor_pos) - return; - - if (selection) - { - if (prev_pos == _interactive_beg) - _interactive_beg = _cursor_pos; - else if (prev_pos == _interactive_end) - _interactive_end = _cursor_pos; - else - _interactive_beg = _cursor_pos, - _interactive_end = prev_pos; - } - else - { - _interactive_beg = _cursor_pos; - _interactive_end = _cursor_pos; - } - - select(_interactive_beg, _interactive_end); - _scroll_to_cursor = true; -} -void imgui_code_editor::move_down(size_t amount, bool selection) -{ - assert(!_lines.empty()); - - const auto prev_pos = _cursor_pos; - _cursor_pos.line = std::min(_cursor_pos.line + amount, _lines.size() - 1); - - // The line after could be shorter, so adjust column - _cursor_pos.column = std::min(_cursor_pos.column, _lines[_cursor_pos.line].size()); - - if (prev_pos == _cursor_pos) - return; - - if (selection) - { - if (prev_pos == _interactive_beg) - _interactive_beg = _cursor_pos; - else if (prev_pos == _interactive_end) - _interactive_end = _cursor_pos; - else - _interactive_beg = prev_pos, - _interactive_end = _cursor_pos; - } - else - { - _interactive_beg = _cursor_pos; - _interactive_end = _cursor_pos; - } - - select(_interactive_beg, _interactive_end); - _scroll_to_cursor = true; -} -void imgui_code_editor::move_left(size_t amount, bool selection, bool word_mode) -{ - assert(!_lines.empty()); - - const auto prev_pos = _cursor_pos; - - // Move cursor to selection start when moving left and no longer selecting - if (!selection && _interactive_beg != _interactive_end) - { - _cursor_pos = _interactive_beg; - _interactive_end = _cursor_pos; - } - else - { - while (amount-- > 0) - { - if (_cursor_pos.column == 0) // At the beginning of the current line, so move on to previous - { - if (_cursor_pos.line == 0) - break; - - _cursor_pos.line--; - _cursor_pos.column = _lines[_cursor_pos.line].size(); - } - else if (word_mode) - { - for (const auto word_color = _lines[_cursor_pos.line][_cursor_pos.column - 1].col; _cursor_pos.column > 0; --_cursor_pos.column) - if (_lines[_cursor_pos.line][_cursor_pos.column - 1].col != word_color) - break; - } - else - { - _cursor_pos.column--; - } - } - - if (selection) - { - if (prev_pos == _interactive_beg) - _interactive_beg = _cursor_pos; - else if (prev_pos == _interactive_end) - _interactive_end = _cursor_pos; - else - _interactive_beg = _cursor_pos, - _interactive_end = prev_pos; - } - else - { - _interactive_beg = _cursor_pos; - _interactive_end = _cursor_pos; - } - } - - select(_interactive_beg, _interactive_end); - _scroll_to_cursor = true; -} -void imgui_code_editor::move_right(size_t amount, bool selection, bool word_mode) -{ - assert(!_lines.empty()); - - const auto prev_pos = _cursor_pos; - - while (amount-- > 0) - { - auto &line = _lines[_cursor_pos.line]; - - if (_cursor_pos.column >= line.size()) // At the end of the current line, so move on to next - { - if (_cursor_pos.line >= _lines.size() - 1) - break; // Reached end of input - - _cursor_pos.line++; - _cursor_pos.column = 0; - } - else if (word_mode) - { - for (const auto word_color = _lines[_cursor_pos.line][_cursor_pos.column].col; _cursor_pos.column < _lines[_cursor_pos.line].size(); ++_cursor_pos.column) - if (_lines[_cursor_pos.line][_cursor_pos.column].col != word_color) - break; - } - else - { - _cursor_pos.column++; - } - } - - if (selection) - { - if (prev_pos == _interactive_end) - _interactive_end = _cursor_pos; - else if (prev_pos == _interactive_beg) - _interactive_beg = _cursor_pos; - else - _interactive_beg = prev_pos, - _interactive_end = _cursor_pos; - } - else - { - _interactive_beg = _interactive_end = _cursor_pos; - } - - select(_interactive_beg, _interactive_end); - _scroll_to_cursor = true; -} -void imgui_code_editor::move_top(bool selection) -{ - assert(!_lines.empty()); - - const auto prev_pos = _cursor_pos; - _cursor_pos = text_pos(0, 0); - - if (prev_pos == _cursor_pos) - return; - - if (selection) - { - // This logic ensures the selection is updated depending on which direction it was created in (mimics behavior of popular text editors) - if (_interactive_beg > _interactive_end) - std::swap(_interactive_beg, _interactive_end); - if (prev_pos != _interactive_beg) - _interactive_end = _interactive_beg; - - _interactive_beg = _cursor_pos; - } - else - { - _interactive_beg = _cursor_pos; - _interactive_end = _cursor_pos; - } - - select(_interactive_beg, _interactive_end); - _scroll_to_cursor = true; -} -void imgui_code_editor::move_bottom(bool selection) -{ - assert(!_lines.empty()); - - const auto prev_pos = _cursor_pos; - _cursor_pos = text_pos(_lines.size() - 1, 0); - - if (prev_pos == _cursor_pos) - return; - - if (selection) - { - if (_interactive_beg > _interactive_end) - std::swap(_interactive_beg, _interactive_end); - if (prev_pos != _interactive_end) - _interactive_beg = _interactive_end; - - _interactive_end = _cursor_pos; - } - else - { - _interactive_beg = _cursor_pos; - _interactive_end = _cursor_pos; - } - - select(_interactive_beg, _interactive_end); - _scroll_to_cursor = true; -} -void imgui_code_editor::move_home(bool selection) -{ - assert(!_lines.empty()); - - const auto prev_pos = _cursor_pos; - _cursor_pos.column = 0; - - if (prev_pos == _cursor_pos && - _interactive_beg == _interactive_end) // This ensures that deselection works even when cursor is already at begin - return; - - if (selection) - { - if (prev_pos == _interactive_beg) - _interactive_beg = _cursor_pos; - else if (prev_pos == _interactive_end) - _interactive_end = _cursor_pos; - else - _interactive_beg = _cursor_pos, - _interactive_end = prev_pos; - } - else - { - _interactive_beg = _interactive_end = _cursor_pos; - } - - select(_interactive_beg, _interactive_end); - _scroll_to_cursor = true; -} -void imgui_code_editor::move_end(bool selection) -{ - assert(!_lines.empty()); - - const auto prev_pos = _cursor_pos; - _cursor_pos.column = _lines[_cursor_pos.line].size(); - - if (prev_pos == _cursor_pos && - _interactive_beg == _interactive_end) // This ensures that deselection works even when cursor is already at end - return; - - if (selection) - { - if (prev_pos == _interactive_end) - _interactive_end = _cursor_pos; - else if (prev_pos == _interactive_beg) - _interactive_beg = _cursor_pos; - else - _interactive_beg = prev_pos, - _interactive_end = _cursor_pos; - } - else - { - _interactive_beg = _interactive_end = _cursor_pos; - } - - select(_interactive_beg, _interactive_end); - _scroll_to_cursor = true; -} -void imgui_code_editor::move_lines_up() -{ - if (_select_beg.line == 0) - return; - - for (size_t line = _select_beg.line; line <= _select_end.line; ++line) - std::swap(_lines[line], _lines[line - 1]); - - _select_beg.line--; - _select_end.line--; - _cursor_pos.line--; -} -void imgui_code_editor::move_lines_down() -{ - if (_select_end.line + 1 >= _lines.size()) - return; - - for (size_t line = _select_end.line; line >= _select_beg.line && line < _lines.size(); --line) - std::swap(_lines[line], _lines[line + 1]); - - _select_beg.line++; - _select_end.line++; - _cursor_pos.line++; -} - -void imgui_code_editor::colorize() -{ - if (_colorize_line_beg >= _colorize_line_end) - return; - - // Step through code incrementally rather than coloring everything at once - const size_t from = _colorize_line_beg, to = std::min(from + 1000, _colorize_line_end); - _colorize_line_beg = to; - - // Reset coloring range if we have finished coloring it after this iteration - if (_colorize_line_beg == _colorize_line_end) - { - _colorize_line_beg = std::numeric_limits::max(); - _colorize_line_end = 0; - } - - // Copy lines into string for consumption by the lexer - std::string input_string; - for (size_t l = from; l < to && l < _lines.size(); ++l, input_string.push_back('\n')) - for (size_t k = 0; k < _lines[l].size(); ++k) - input_string.push_back(_lines[l][k].c); - - reshadefx::lexer lexer(input_string, false, true, false, true, false, true); - - for (reshadefx::token tok; (tok = lexer.lex()).id != reshadefx::tokenid::end_of_file;) - { - color col = color_default; - - switch (tok.id) - { - case reshadefx::tokenid::exclaim: - case reshadefx::tokenid::percent: - case reshadefx::tokenid::ampersand: - case reshadefx::tokenid::parenthesis_open: - case reshadefx::tokenid::parenthesis_close: - case reshadefx::tokenid::star: - case reshadefx::tokenid::plus: - case reshadefx::tokenid::comma: - case reshadefx::tokenid::minus: - case reshadefx::tokenid::dot: - case reshadefx::tokenid::slash: - case reshadefx::tokenid::colon: - case reshadefx::tokenid::semicolon: - case reshadefx::tokenid::less: - case reshadefx::tokenid::equal: - case reshadefx::tokenid::greater: - case reshadefx::tokenid::question: - case reshadefx::tokenid::bracket_open: - case reshadefx::tokenid::backslash: - case reshadefx::tokenid::bracket_close: - case reshadefx::tokenid::caret: - case reshadefx::tokenid::brace_open: - case reshadefx::tokenid::pipe: - case reshadefx::tokenid::brace_close: - case reshadefx::tokenid::tilde: - case reshadefx::tokenid::exclaim_equal: - case reshadefx::tokenid::percent_equal: - case reshadefx::tokenid::ampersand_ampersand: - case reshadefx::tokenid::ampersand_equal: - case reshadefx::tokenid::star_equal: - case reshadefx::tokenid::plus_plus: - case reshadefx::tokenid::plus_equal: - case reshadefx::tokenid::minus_minus: - case reshadefx::tokenid::minus_equal: - case reshadefx::tokenid::arrow: - case reshadefx::tokenid::ellipsis: - case reshadefx::tokenid::slash_equal: - case reshadefx::tokenid::colon_colon: - case reshadefx::tokenid::less_less_equal: - case reshadefx::tokenid::less_less: - case reshadefx::tokenid::less_equal: - case reshadefx::tokenid::equal_equal: - case reshadefx::tokenid::greater_greater_equal: - case reshadefx::tokenid::greater_greater: - case reshadefx::tokenid::greater_equal: - case reshadefx::tokenid::caret_equal: - case reshadefx::tokenid::pipe_equal: - case reshadefx::tokenid::pipe_pipe: - col = color_punctuation; - break; - case reshadefx::tokenid::identifier: - col = color_identifier; - break; - case reshadefx::tokenid::int_literal: - case reshadefx::tokenid::uint_literal: - case reshadefx::tokenid::float_literal: - case reshadefx::tokenid::double_literal: - col = color_number_literal; - break; - case reshadefx::tokenid::string_literal: - col = color_string_literal; - break; - case reshadefx::tokenid::true_literal: - case reshadefx::tokenid::false_literal: - case reshadefx::tokenid::namespace_: - case reshadefx::tokenid::struct_: - case reshadefx::tokenid::technique: - case reshadefx::tokenid::pass: - case reshadefx::tokenid::for_: - case reshadefx::tokenid::while_: - case reshadefx::tokenid::do_: - case reshadefx::tokenid::if_: - case reshadefx::tokenid::else_: - case reshadefx::tokenid::switch_: - case reshadefx::tokenid::case_: - case reshadefx::tokenid::default_: - case reshadefx::tokenid::break_: - case reshadefx::tokenid::continue_: - case reshadefx::tokenid::return_: - case reshadefx::tokenid::discard_: - case reshadefx::tokenid::extern_: - case reshadefx::tokenid::static_: - case reshadefx::tokenid::uniform_: - case reshadefx::tokenid::volatile_: - case reshadefx::tokenid::precise: - case reshadefx::tokenid::in: - case reshadefx::tokenid::out: - case reshadefx::tokenid::inout: - case reshadefx::tokenid::const_: - case reshadefx::tokenid::linear: - case reshadefx::tokenid::noperspective: - case reshadefx::tokenid::centroid: - case reshadefx::tokenid::nointerpolation: - case reshadefx::tokenid::void_: - case reshadefx::tokenid::bool_: - case reshadefx::tokenid::bool2: - case reshadefx::tokenid::bool3: - case reshadefx::tokenid::bool4: - case reshadefx::tokenid::bool2x2: - case reshadefx::tokenid::bool3x3: - case reshadefx::tokenid::bool4x4: - case reshadefx::tokenid::int_: - case reshadefx::tokenid::int2: - case reshadefx::tokenid::int3: - case reshadefx::tokenid::int4: - case reshadefx::tokenid::int2x2: - case reshadefx::tokenid::int3x3: - case reshadefx::tokenid::int4x4: - case reshadefx::tokenid::uint_: - case reshadefx::tokenid::uint2: - case reshadefx::tokenid::uint3: - case reshadefx::tokenid::uint4: - case reshadefx::tokenid::uint2x2: - case reshadefx::tokenid::uint3x3: - case reshadefx::tokenid::uint4x4: - case reshadefx::tokenid::float_: - case reshadefx::tokenid::float2: - case reshadefx::tokenid::float3: - case reshadefx::tokenid::float4: - case reshadefx::tokenid::float2x2: - case reshadefx::tokenid::float3x3: - case reshadefx::tokenid::float4x4: - case reshadefx::tokenid::vector: - case reshadefx::tokenid::matrix: - case reshadefx::tokenid::string_: - case reshadefx::tokenid::texture: - case reshadefx::tokenid::sampler: - col = color_keyword; - break; - case reshadefx::tokenid::hash_def: - case reshadefx::tokenid::hash_undef: - case reshadefx::tokenid::hash_if: - case reshadefx::tokenid::hash_ifdef: - case reshadefx::tokenid::hash_ifndef: - case reshadefx::tokenid::hash_else: - case reshadefx::tokenid::hash_elif: - case reshadefx::tokenid::hash_endif: - case reshadefx::tokenid::hash_error: - case reshadefx::tokenid::hash_warning: - case reshadefx::tokenid::hash_pragma: - case reshadefx::tokenid::hash_include: - case reshadefx::tokenid::hash_unknown: - col = color_preprocessor; - tok.offset--; // Add # to token - tok.length++; - break; - case reshadefx::tokenid::single_line_comment: - col = color_comment; - break; - case reshadefx::tokenid::multi_line_comment: - col = color_multiline_comment; - break; - } - - // Update character range matching the current the token - size_t line = from + tok.location.line - 1; - size_t column = tok.location.column - 1; - - for (size_t k = 0; k < tok.length; ++k) - { - if (column >= _lines[line].size()) - { - line++; - column = 0; - continue; - } - - _lines[line][column++].col = col; - } - } -} diff --git a/msvc/source/gui_code_editor.hpp b/msvc/source/gui_code_editor.hpp deleted file mode 100644 index 8160bdc..0000000 --- a/msvc/source/gui_code_editor.hpp +++ /dev/null @@ -1,165 +0,0 @@ -/** - * Copyright (C) 2014 Patrick Mours. All rights reserved. - * License: https://github.com/crosire/reshade#license - */ - -#pragma once - -#include -#include -#include -#include - -class imgui_code_editor -{ -public: - struct text_pos - { - size_t line, column; - - text_pos() : line(0), column(0) {} - text_pos(size_t line, size_t column = 0) : line(line), column(column) {} - - bool operator ==(const text_pos& o) const { return line == o.line && column == o.column; } - bool operator !=(const text_pos& o) const { return line != o.line || column != o.column; } - bool operator < (const text_pos& o) const { return line != o.line ? line < o.line : column < o.column; } - bool operator <=(const text_pos& o) const { return line != o.line ? line < o.line : column <= o.column; } - bool operator > (const text_pos& o) const { return line != o.line ? line > o.line : column > o.column; } - bool operator >=(const text_pos& o) const { return line != o.line ? line > o.line : column >= o.column; } - }; - - enum color - { - color_default, - color_keyword, - color_number_literal, - color_string_literal, - color_punctuation, - color_preprocessor, - color_identifier, - color_known_identifier, - color_preprocessor_identifier, - color_comment, - color_multiline_comment, - color_background, - color_cursor, - color_selection, - color_error_marker, - color_warning_marker, - color_line_number, - color_current_line_fill, - color_current_line_fill_inactive, - color_current_line_edge, - - color_palette_max - }; - - enum class selection_mode - { - normal, - word, - line - }; - - imgui_code_editor(); - - void render(const char *title, bool border = false); - - void select(const text_pos &beg, const text_pos &end, selection_mode mode = selection_mode::normal); - void select_all(); - bool has_selection() const { return _select_end > _select_beg; } - - void set_text(const std::string &text); - void clear_text(); - void insert_text(const std::string &text); - std::string get_text() const; - std::string get_text(const text_pos &beg, const text_pos &end) const; - std::string get_selected_text() const; - - void undo(unsigned int steps = 1); - bool can_undo() const { return _undo_index > 0; } - void redo(unsigned int steps = 1); - bool can_redo() const { return _undo_index < _undo.size(); } - - void add_error(size_t line, const std::string &message, bool warning = false) { _errors.insert({ line, { message, warning } }); } - void clear_errors() { _errors.clear(); } - - void set_tab_size(unsigned int size) { _tab_size = size; } - void set_left_margin(float margin) { _left_margin = margin; } - void set_line_spacing(float spacing) { _line_spacing = spacing; } - - void set_palette(const std::array &palette) { _palette = palette; } - uint32_t &get_palette_index(unsigned int index) { return _palette[index]; } - static const char *get_palette_color_name(unsigned int index); - -private: - struct glyph - { - char c = '\0'; - color col = color_default; - }; - - struct undo_record - { - text_pos added_beg; - text_pos added_end; - std::string added; - text_pos removed_beg; - text_pos removed_end; - std::string removed; - }; - - void record_undo(undo_record &&record); - - void insert_character(char c, bool auto_indent); - - void delete_next(); - void delete_previous(); - void delete_selection(); - void delete_lines(size_t first_line, size_t last_line); - - void clipboard_copy(); - void clipboard_cut(); - void clipboard_paste(); - - void move_up(size_t amount = 1, bool select = false); - void move_down(size_t amount = 1, bool select = false); - void move_left(size_t amount = 1, bool select = false, bool word_mode = false); - void move_right(size_t amount = 1, bool select = false, bool word_mode = false); - void move_top(bool select = false); - void move_bottom(bool select = false); - void move_home(bool select = false); - void move_end(bool select = false); - void move_lines_up(); - void move_lines_down(); - - void colorize(); - - float _left_margin = 10.0f; - float _line_spacing = 1.0f; - unsigned int _tab_size = 4; - bool _overwrite = false; - bool _scroll_to_cursor = false; - float _cursor_anim = 0.0f; - double _last_click_time = -1.0; - - std::array _palette; - - std::vector> _lines; - - text_pos _cursor_pos; - text_pos _select_beg; - text_pos _select_end; - text_pos _interactive_beg; - text_pos _interactive_end; - std::string _highlighted; - - size_t _undo_index = 0; - std::vector _undo; - bool _in_undo_operation = false; - - size_t _colorize_line_beg = 0; - size_t _colorize_line_end = 0; - - std::unordered_map> _errors; -}; diff --git a/msvc/source/gui_widgets.cpp b/msvc/source/gui_widgets.cpp deleted file mode 100644 index a283555..0000000 --- a/msvc/source/gui_widgets.cpp +++ /dev/null @@ -1,512 +0,0 @@ -/** - * Copyright (C) 2014 Patrick Mours. All rights reserved. - * License: https://github.com/crosire/reshade#license - */ - -#include "input.hpp" -#include "gui_widgets.hpp" -#include -#include - -bool imgui_key_input(const char *name, unsigned int key_data[4], const reshade::input &input) -{ - bool res = false; - - char buf[48] = ""; - reshade::input::key_name(key_data).copy(buf, sizeof(buf) - 1); - - ImGui::InputText(name, buf, sizeof(buf), ImGuiInputTextFlags_ReadOnly); - - if (ImGui::IsItemActive()) - { - const unsigned int last_key_pressed = input.last_key_pressed(); - - if (last_key_pressed != 0) - { - if (last_key_pressed == 0x08) // Backspace - { - key_data[0] = 0; - key_data[1] = 0; - key_data[2] = 0; - key_data[3] = 0; - } - else if (last_key_pressed < 0x10 || last_key_pressed > 0x12) - { - key_data[0] = last_key_pressed; - key_data[1] = input.is_key_down(0x11); - key_data[2] = input.is_key_down(0x10); - key_data[3] = input.is_key_down(0x12); - } - - res = true; - } - } - else if (ImGui::IsItemHovered()) - { - ImGui::SetTooltip("Click in the field and press any key to change the shortcut to that key."); - } - - return res; -} - -bool imgui_font_select(const char *name, std::filesystem::path &path, int &size) -{ - bool res = false; - const float spacing = ImGui::GetStyle().ItemInnerSpacing.x; - - ImGui::BeginGroup(); - ImGui::PushID(name); - - char buf[260] = ""; - if (path.empty()) - strcpy(buf, "ProggyClean.ttf"); - else - path.u8string().copy(buf, sizeof(buf) - 1); - - ImGui::PushItemWidth(ImGui::CalcItemWidth() - spacing - 80); - if (ImGui::InputText("##font", buf, sizeof(buf))) - { - std::error_code ec; - const std::filesystem::path new_path = std::filesystem::u8path(buf); - - if ((new_path.empty() || new_path == "ProggyClean.ttf") || (std::filesystem::is_regular_file(new_path, ec) && new_path.extension() == ".ttf")) - { - res = true; - path = new_path; - } - } - ImGui::PopItemWidth(); - - ImGui::SameLine(0, spacing); - ImGui::PushItemWidth(80); - if (ImGui::SliderInt("##size", &size, 8, 32)) - res = true; - ImGui::PopItemWidth(); - - ImGui::PopID(); - - ImGui::SameLine(0, spacing); - ImGui::TextUnformatted(name); - - ImGui::EndGroup(); - - return res; -} - -bool imgui_directory_dialog(const char *name, std::filesystem::path &path) -{ - bool ok = false, cancel = false; - - if (!ImGui::BeginPopup(name)) - return false; - - char buf[_MAX_PATH] = ""; - path.u8string().copy(buf, sizeof(buf) - 1); - - ImGui::PushItemWidth(400); - if (ImGui::InputText("##path", buf, sizeof(buf))) - path = std::filesystem::u8path(buf); - ImGui::PopItemWidth(); - - ImGui::SameLine(); - if (ImGui::Button("Ok", ImVec2(100, 0))) - ok = true; - ImGui::SameLine(); - if (ImGui::Button("Cancel", ImVec2(100, 0))) - cancel = true; - - ImGui::BeginChild("##files", ImVec2(0, 300)); - - std::error_code ec; - - if (path.is_relative()) - path = std::filesystem::absolute(path); - if (!std::filesystem::is_directory(path, ec)) - path.remove_filename(); - else if (!path.has_filename()) - path = path.parent_path(); - - if (ImGui::Selectable(".")) - ok = true; - if (path.has_parent_path() && ImGui::Selectable("..")) - path = path.parent_path(); - - for (const auto &entry : std::filesystem::directory_iterator(path, ec)) - { - if (!entry.is_directory(ec)) - continue; // Only show directories - - std::string label = entry.path().filename().u8string(); - label = " " + label; - - if (ImGui::Selectable(label.c_str(), entry.path() == path)) - { - path = entry.path(); - break; - } - } - - ImGui::EndChild(); - - if (ok || cancel) - ImGui::CloseCurrentPopup(); - - ImGui::EndPopup(); - - return ok; -} - -bool imgui_directory_input_box(const char *name, std::filesystem::path &path, std::filesystem::path &dialog_path) -{ - bool res = false; - const float button_size = ImGui::GetFrameHeight(); - const float button_spacing = ImGui::GetStyle().ItemInnerSpacing.x; - - ImGui::PushID(name); - ImGui::BeginGroup(); - - char buf[_MAX_PATH] = ""; - path.u8string().copy(buf, sizeof(buf) - 1); - - ImGui::PushItemWidth(ImGui::CalcItemWidth() - (button_spacing + button_size)); - if (ImGui::InputText("##path", buf, sizeof(buf))) - path = std::filesystem::u8path(buf), res = true; - ImGui::PopItemWidth(); - - ImGui::SameLine(0, button_spacing); - if (ImGui::Button("..", ImVec2(button_size, 0))) - dialog_path = path, ImGui::OpenPopup("##select"); - - ImGui::SameLine(0, button_spacing); - ImGui::TextUnformatted(name); - ImGui::EndGroup(); - - if (imgui_directory_dialog("##select", dialog_path)) - path = dialog_path, res = true; - - ImGui::PopID(); - - return res; -} - -bool imgui_path_list(const char *label, std::vector &paths, std::filesystem::path &dialog_path, const std::filesystem::path &default_path) -{ - bool res = false; - const float item_width = ImGui::CalcItemWidth(); - const float item_height = ImGui::GetFrameHeightWithSpacing(); - const float button_size = ImGui::GetFrameHeight(); - const float button_spacing = ImGui::GetStyle().ItemInnerSpacing.x; - - ImGui::BeginGroup(); - ImGui::PushID(label); - - char buf[_MAX_PATH]; - - if (ImGui::BeginChild("##paths", ImVec2(item_width, (paths.size() + 1) * item_height), false, ImGuiWindowFlags_NoScrollWithMouse)) - { - for (size_t i = 0; i < paths.size(); ++i) - { - ImGui::PushID(static_cast(i)); - - memset(buf, 0, sizeof(buf)); - paths[i].u8string().copy(buf, sizeof(buf) - 1); - - ImGui::PushItemWidth(ImGui::GetWindowContentRegionWidth() - (button_spacing + button_size)); - if (ImGui::InputText("##path", buf, sizeof(buf))) - { - res = true; - paths[i] = std::filesystem::u8path(buf); - } - ImGui::PopItemWidth(); - - ImGui::SameLine(0, button_spacing); - if (ImGui::Button("-", ImVec2(button_size, 0))) - { - res = true; - paths.erase(paths.begin() + i--); - } - - ImGui::PopID(); - } - - ImGui::Dummy(ImVec2(0, 0)); - ImGui::SameLine(0, ImGui::GetWindowContentRegionWidth() - button_size); - if (ImGui::Button("+", ImVec2(button_size, 0))) - { - dialog_path = default_path; - ImGui::OpenPopup("##select"); - } - - if (imgui_directory_dialog("##select", dialog_path)) - { - res = true; - paths.push_back(dialog_path); - } - } - - ImGui::EndChild(); - - ImGui::PopID(); - - ImGui::SameLine(0, button_spacing); - ImGui::TextUnformatted(label); - - ImGui::EndGroup(); - - return res; -} - -bool imgui_popup_button(const char *label, float width, ImGuiWindowFlags flags) -{ - if (ImGui::Button(label, ImVec2(width, 0))) - ImGui::OpenPopup(label); // Popups can have the same ID as other items without conflict - return ImGui::BeginPopup(label, flags); -} - -bool imgui_list_with_buttons(const char *label, const std::string_view ui_items, int &v) -{ - const auto imgui_context = ImGui::GetCurrentContext(); - - bool modified = false; - - std::vector items; - if (!ui_items.empty()) - for (std::string_view item = ui_items.data(); ui_items.data() + ui_items.size() > item.data(); - item = item.data() + item.size() + 1) - items.push_back(item); - - ImGui::BeginGroup(); - - const float button_size = ImGui::GetFrameHeight(); - const float button_spacing = imgui_context->Style.ItemInnerSpacing.x; - const ImVec2 hover_pos = ImGui::GetCursorScreenPos() + ImVec2(0, button_size); - - ImGui::PushItemWidth(ImGui::CalcItemWidth() - (button_spacing * 2 + button_size * 2)); - - if (ImGui::BeginCombo("##v", items.size() > static_cast(v) && v >= 0 ? items[v].data() : nullptr, ImGuiComboFlags_NoArrowButton)) - { - auto it = items.begin(); - for (size_t i = 0; it != items.end(); ++it, ++i) - { - bool selected = v == static_cast(i); - if (ImGui::Selectable(it->data(), &selected)) - v = static_cast(i), modified = true; - if (selected) - ImGui::SetItemDefaultFocus(); - } - - ImGui::EndCombo(); - } - - ImGui::PopItemWidth(); - - ImGui::SameLine(0, button_spacing); - if (ImGui::ButtonEx("<", ImVec2(button_size, 0), items.size() > 0 ? 0 : ImGuiButtonFlags_Disabled)) - { - modified = true; - if (v == 0) v = static_cast(items.size() - 1); - else --v; - } - - ImGui::SameLine(0, button_spacing); - if (ImGui::ButtonEx(">", ImVec2(button_size, 0), items.size() > 0 ? 0 : ImGuiButtonFlags_Disabled)) - { - modified = true; - if (v == static_cast(items.size()) - 1) v = 0; - else ++v; - } - - ImGui::EndGroup(); - const bool is_hovered = ImGui::IsItemHovered(); - - ImGui::SameLine(0, button_spacing); - ImGui::TextUnformatted(label); - - if (is_hovered) - { - ImGui::SetNextWindowPos(hover_pos); - ImGui::SetNextWindowSizeConstraints(ImVec2(ImGui::CalcItemWidth(), 0.0f), ImVec2(FLT_MAX, (imgui_context->FontSize + imgui_context->Style.ItemSpacing.y) * 8 - imgui_context->Style.ItemSpacing.y + (imgui_context->Style.WindowPadding.y * 2))); // 8 by ImGuiComboFlags_HeightRegular - ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(imgui_context->Style.FramePadding.x, imgui_context->Style.WindowPadding.y)); - ImGui::Begin("##spinner_items", NULL, ImGuiWindowFlags_NoInputs | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoDocking); - ImGui::PopStyleVar(); - - auto it = items.begin(); - for (size_t i = 0; it != items.end(); ++it, ++i) - { - bool selected = v == static_cast(i); - ImGui::Selectable(it->data(), &selected); - if (selected) - ImGui::SetScrollHereY(); - } - - ImGui::End(); - } - - return modified; -} - -template -bool imgui_drag_with_buttons(const char *label, T *v, int components, T v_speed, T v_min, T v_max, const char *format) -{ - const float button_size = ImGui::GetFrameHeight(); - const float button_spacing = ImGui::GetStyle().ItemInnerSpacing.x; - - ImGui::BeginGroup(); - ImGui::PushID(label); - - ImGui::PushItemWidth(ImGui::CalcItemWidth() - (button_spacing * 2 + button_size * 2)); - bool value_changed = ImGui::DragScalarN("##v", data_type, v, components, static_cast(v_speed), &v_min, &v_max, format); - ImGui::PopItemWidth(); - - ImGui::SameLine(0, 0); - if (ImGui::ButtonEx("<", ImVec2(button_size, 0), ImGuiButtonFlags_PressedOnClick | ImGuiButtonFlags_Repeat) && v[0] > v_min) - { - for (int c = 0; c < components; ++c) - v[c] -= v_speed; - value_changed = true; - } - ImGui::SameLine(0, button_spacing); - if (ImGui::ButtonEx(">", ImVec2(button_size, 0), ImGuiButtonFlags_PressedOnClick | ImGuiButtonFlags_Repeat) && v[0] < v_max) - { - for (int c = 0; c < components; ++c) - v[c] += v_speed; - value_changed = true; - } - - ImGui::PopID(); - - ImGui::SameLine(0, button_spacing); - ImGui::TextUnformatted(label); - - ImGui::EndGroup(); - - return value_changed; - -} -bool imgui_drag_with_buttons(const char *label, ImGuiDataType data_type, void *v, int components, const void *v_speed, const void *v_min, const void *v_max, const char *format) -{ - switch (data_type) - { - default: - return false; - case ImGuiDataType_S32: - return imgui_drag_with_buttons(label, static_cast(v), components, *static_cast(v_speed), *static_cast(v_min), *static_cast(v_max), format); - case ImGuiDataType_U32: - return imgui_drag_with_buttons(label, static_cast(v), components, *static_cast(v_speed), *static_cast(v_min), *static_cast(v_max), format); - case ImGuiDataType_S64: - return imgui_drag_with_buttons(label, static_cast(v), components, *static_cast(v_speed), *static_cast(v_min), *static_cast(v_max), format); - case ImGuiDataType_U64: - return imgui_drag_with_buttons(label, static_cast(v), components, *static_cast(v_speed), *static_cast(v_min), *static_cast(v_max), format); - case ImGuiDataType_Float: - return imgui_drag_with_buttons(label, static_cast(v), components, *static_cast(v_speed), *static_cast(v_min), *static_cast(v_max), format); - case ImGuiDataType_Double: - return imgui_drag_with_buttons(label, static_cast(v), components, *static_cast(v_speed), *static_cast(v_min), *static_cast(v_max), format); - } -} - -template -bool imgui_slider_with_buttons(const char *label, T *v, int components, T v_speed, T v_min, T v_max, const char *format) -{ - const float button_size = ImGui::GetFrameHeight(); - const float button_spacing = ImGui::GetStyle().ItemInnerSpacing.x; - - ImGui::BeginGroup(); - ImGui::PushID(label); - - ImGui::PushItemWidth(ImGui::CalcItemWidth() - (button_spacing * 2 + button_size * 2)); - bool value_changed = ImGui::SliderScalarN("##v", data_type, v, components, &v_min, &v_max, format); - ImGui::PopItemWidth(); - - ImGui::SameLine(0, 0); - if (ImGui::ButtonEx("<", ImVec2(button_size, 0), ImGuiButtonFlags_PressedOnClick | ImGuiButtonFlags_Repeat) && v[0] > v_min) - { - for (int c = 0; c < components; ++c) - v[c] -= v_speed; - value_changed = true; - } - ImGui::SameLine(0, button_spacing); - if (ImGui::ButtonEx(">", ImVec2(button_size, 0), ImGuiButtonFlags_PressedOnClick | ImGuiButtonFlags_Repeat) && v[0] < v_max) - { - for (int c = 0; c < components; ++c) - v[c] += v_speed; - value_changed = true; - } - - ImGui::PopID(); - - ImGui::SameLine(0, button_spacing); - ImGui::TextUnformatted(label); - - ImGui::EndGroup(); - - return value_changed; - -} -bool imgui_slider_with_buttons(const char *label, ImGuiDataType data_type, void *v, int components, const void *v_speed, const void *v_min, const void *v_max, const char *format) -{ - switch (data_type) - { - default: - return false; - case ImGuiDataType_S32: - return imgui_slider_with_buttons(label, static_cast(v), components, *static_cast(v_speed), *static_cast(v_min), *static_cast(v_max), format); - case ImGuiDataType_U32: - return imgui_slider_with_buttons(label, static_cast(v), components, *static_cast(v_speed), *static_cast(v_min), *static_cast(v_max), format); - case ImGuiDataType_S64: - return imgui_slider_with_buttons(label, static_cast(v), components, *static_cast(v_speed), *static_cast(v_min), *static_cast(v_max), format); - case ImGuiDataType_U64: - return imgui_slider_with_buttons(label, static_cast(v), components, *static_cast(v_speed), *static_cast(v_min), *static_cast(v_max), format); - case ImGuiDataType_Float: - return imgui_slider_with_buttons(label, static_cast(v), components, *static_cast(v_speed), *static_cast(v_min), *static_cast(v_max), format); - case ImGuiDataType_Double: - return imgui_slider_with_buttons(label, static_cast(v), components, *static_cast(v_speed), *static_cast(v_min), *static_cast(v_max), format); - } -} - -bool imgui_slider_for_alpha(const char *label, float *v) -{ - const float button_size = ImGui::GetFrameHeight(); - const float button_spacing = ImGui::GetStyle().ItemInnerSpacing.x; - - ImGui::BeginGroup(); - ImGui::PushID(label); - - ImGui::PushItemWidth(ImGui::CalcItemWidth() - button_spacing - button_size); - bool value_changed = ImGui::SliderFloat("##v", v, 0.0f, 1.0f); - ImGui::PopItemWidth(); - - ImGui::SameLine(0, button_spacing); - ImGui::ColorButton("##preview", ImVec4(1.0f, 1.0f, 1.0f, *v), ImGuiColorEditFlags_NoOptions | ImGuiColorEditFlags_AlphaPreview | ImGuiColorEditFlags_NoPicker); - - ImGui::PopID(); - - ImGui::SameLine(0, button_spacing); - ImGui::TextUnformatted(label); - - ImGui::EndGroup(); - - return value_changed; - -} - -void imgui_image_with_checkerboard_background(ImTextureID user_texture_id, const ImVec2 &size) -{ - const auto draw_list = ImGui::GetWindowDrawList(); - - // Render background checkerboard pattern - const ImVec2 pos_min = ImGui::GetCursorScreenPos(); - const ImVec2 pos_max = ImVec2(pos_min.x + size.x, pos_min.y + size.y); int yi = 0; - - for (float y = pos_min.y, grid_size = 25.0f; y < pos_max.y; y += grid_size, yi++) - { - const float y1 = std::min(y, pos_max.y), y2 = std::min(y + grid_size, pos_max.y); - for (float x = pos_min.x + (yi & 1) * grid_size; x < pos_max.x; x += grid_size * 2.0f) - { - const float x1 = std::min(x, pos_max.x), x2 = std::min(x + grid_size, pos_max.x); - draw_list->AddRectFilled(ImVec2(x1, y1), ImVec2(x2, y2), IM_COL32(128, 128, 128, 255)); - } - } - - // Add image on top - ImGui::Image(user_texture_id, size); -} diff --git a/msvc/source/gui_widgets.hpp b/msvc/source/gui_widgets.hpp deleted file mode 100644 index cd25bb8..0000000 --- a/msvc/source/gui_widgets.hpp +++ /dev/null @@ -1,31 +0,0 @@ -/** - * Copyright (C) 2014 Patrick Mours. All rights reserved. - * License: https://github.com/crosire/reshade#license - */ - -#pragma once - -#include -#include - -bool imgui_key_input(const char *name, unsigned int key_data[4], const reshade::input &input); - -bool imgui_font_select(const char *name, std::filesystem::path &path, int &size); - -bool imgui_directory_dialog(const char *name, std::filesystem::path &path); - -bool imgui_directory_input_box(const char *name, std::filesystem::path &path, std::filesystem::path &dialog_path); - -bool imgui_path_list(const char *label, std::vector &paths, std::filesystem::path &dialog_path, const std::filesystem::path &default_path = std::filesystem::path()); - -bool imgui_popup_button(const char *label, float width = 0.0f, ImGuiWindowFlags flags = 0); - -bool imgui_list_with_buttons(const char *label, const std::string_view ui_items, int &v); - -bool imgui_drag_with_buttons(const char *label, ImGuiDataType data_type, void *v, int components, const void *v_speed, const void *v_min, const void *v_max, const char *format = nullptr); - -bool imgui_slider_with_buttons(const char *label, ImGuiDataType data_type, void *v, int components, const void *v_speed, const void *v_min, const void *v_max, const char *format = nullptr); - -bool imgui_slider_for_alpha(const char *label, float *v); - -void imgui_image_with_checkerboard_background(ImTextureID user_texture_id, const ImVec2 &size); diff --git a/msvc/source/hook.cpp b/msvc/source/hook.cpp deleted file mode 100644 index 369647b..0000000 --- a/msvc/source/hook.cpp +++ /dev/null @@ -1,74 +0,0 @@ -/** - * Copyright (C) 2014 Patrick Mours. All rights reserved. - * License: https://github.com/crosire/reshade#license - */ - -#include "hook.hpp" -#include -#include - -static unsigned long s_reference_count = 0; - -void reshade::hook::enable(bool enable) const -{ - if (enable) - MH_QueueEnableHook(target); - else - MH_QueueDisableHook(target); -} - -reshade::hook::status reshade::hook::install() -{ - if (!valid()) - return hook::status::unsupported_function; - - // Only leave MinHook active as long as any hooks exist - if (s_reference_count++ == 0) - MH_Initialize(); - - const MH_STATUS statuscode = MH_CreateHook(target, replacement, &trampoline); - - if (statuscode == MH_OK || statuscode == MH_ERROR_ALREADY_CREATED) - { - // Installation was successful, so enable the hook and return - enable(true); - - return hook::status::success; - } - - if (--s_reference_count == 0) - MH_Uninitialize(); - - return static_cast(statuscode); -} -reshade::hook::status reshade::hook::uninstall() -{ - if (!valid()) - return hook::status::unsupported_function; - - const MH_STATUS statuscode = MH_RemoveHook(target); - - if (statuscode == MH_ERROR_NOT_CREATED) - return hook::status::success; // If the hook was never created, then uninstallation is implicitly successful - else if (statuscode != MH_OK) - return static_cast(statuscode); - - trampoline = nullptr; - - if (--s_reference_count == 0) - MH_Uninitialize(); // If this was the last active hook, MinHook can now be uninialized, since no more are active - - return hook::status::success; -} - -reshade::hook::address reshade::hook::call() const -{ - assert(installed()); - - return trampoline; -} - -bool reshade::hook::apply_queued_actions() -{ - return MH_ApplyQueued() == MH_OK; -} diff --git a/msvc/source/hook.hpp b/msvc/source/hook.hpp deleted file mode 100644 index 35399aa..0000000 --- a/msvc/source/hook.hpp +++ /dev/null @@ -1,73 +0,0 @@ -/** - * Copyright (C) 2014 Patrick Mours. All rights reserved. - * License: https://github.com/crosire/reshade#license - */ - -#pragma once - -namespace reshade -{ - struct hook - { - /// - /// Type which holds the address of a function. - /// - using address = void *; - - /// - /// Enumeration of status codes returned by the hook installation functions. - /// - enum class status - { - unknown = -1, - success, - not_executable = 7, - unsupported_function, - allocation_failure, - memory_protection_failure, - }; - - /// - /// Actually enable or disable any queued hooks. - /// - static bool apply_queued_actions(); - - /// - /// Returns whether this hook is valid. - /// - bool valid() const { return target != nullptr && replacement != nullptr && target != replacement; } - /// - /// Returns whether this hook is currently installed. - /// - bool installed() const { return trampoline != nullptr; } - /// - /// Returns whether this hook is not currently installed. - /// - bool uninstalled() const { return trampoline == nullptr; } - - /// - /// Enable or disable this hook. This queues the action for later execution in . - /// - /// Boolean indicating if hook should be enabled or disabled. - void enable(bool enable) const; - /// - /// Install this hook. - /// - hook::status install(); - /// - /// Uninstall this hook. - /// - hook::status uninstall(); - - /// - /// Returns the trampoline function address of the hook. - /// - address call() const; - template - T call() const { return reinterpret_cast(call()); } - - address target = nullptr; - address trampoline = nullptr; - address replacement = nullptr; - }; -} diff --git a/msvc/source/hook_manager.cpp b/msvc/source/hook_manager.cpp deleted file mode 100644 index a40e935..0000000 --- a/msvc/source/hook_manager.cpp +++ /dev/null @@ -1,463 +0,0 @@ -/** - * Copyright (C) 2014 Patrick Mours. All rights reserved. - * License: https://github.com/crosire/reshade#license - */ - -#include "log.hpp" -#include "hook_manager.hpp" -#include -#include -#include -#include -#include -#include -#include - -enum class hook_method -{ - export_hook, - function_hook, - vtable_hook -}; - -struct module_export -{ - reshade::hook::address address; - const char *name; - unsigned short ordinal; -}; - -extern HMODULE g_module_handle; -extern std::filesystem::path g_reshade_dll_path; -static std::filesystem::path s_export_hook_path; -static std::vector s_delayed_hook_paths; -static std::vector> s_hooks; -static std::mutex s_mutex_hooks; -static std::mutex s_mutex_delayed_hook_paths; - -std::vector get_module_exports(HMODULE handle) -{ - const auto imagebase = reinterpret_cast(handle); - const auto imageheader = reinterpret_cast(imagebase + - reinterpret_cast(imagebase)->e_lfanew); - - if (imageheader->Signature != IMAGE_NT_SIGNATURE || - imageheader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].Size == 0) - return {}; - - const auto exportdir = reinterpret_cast(imagebase + - imageheader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress); - const auto exportbase = static_cast(exportdir->Base); - - if (exportdir->NumberOfFunctions == 0) - return {}; - - std::vector exports; - exports.reserve(exportdir->NumberOfNames); - - for (size_t i = 0; i < exports.capacity(); i++) - { - module_export symbol; - symbol.ordinal = reinterpret_cast(imagebase + - exportdir->AddressOfNameOrdinals)[i] + exportbase; - symbol.name = reinterpret_cast(imagebase + - reinterpret_cast(imagebase + exportdir->AddressOfNames)[i]); - symbol.address = const_cast(reinterpret_cast(imagebase + - reinterpret_cast(imagebase + exportdir->AddressOfFunctions)[symbol.ordinal - exportbase])); - - exports.push_back(std::move(symbol)); - } - - return exports; -} - -static bool install_internal(const char *name, reshade::hook &hook, hook_method method) -{ - // It does not make sense to install a hook which points to itself, so avoid that - if (hook.target == hook.replacement) - return false; - -#if RESHADE_VERBOSE_LOG - LOG(DEBUG) << "Installing hook for " << name << " at 0x" << hook.target << " with 0x" << hook.replacement << " using method " << static_cast(method) << " ..."; -#endif - auto status = reshade::hook::status::unknown; - - switch (method) - { - case hook_method::export_hook: - status = reshade::hook::status::success; - break; - case hook_method::function_hook: - status = hook.install(); - break; - case hook_method::vtable_hook: - if (DWORD protection = PAGE_READWRITE; - VirtualProtect(hook.target, sizeof(reshade::hook::address), protection, &protection)) - { - *reinterpret_cast(hook.target) = hook.replacement; - - VirtualProtect(hook.target, sizeof(reshade::hook::address), protection, &protection); - - status = reshade::hook::status::success; - } - else - { - status = reshade::hook::status::memory_protection_failure; - } - break; - } - - if (status != reshade::hook::status::success) - { - LOG(ERROR) << "Failed to install hook for " << name << " with status code " << static_cast(status) << '.'; - return false; - } - - { const std::lock_guard lock(s_mutex_hooks); - s_hooks.push_back(std::make_tuple(name, hook, method)); - } - -#if RESHADE_VERBOSE_LOG - LOG(DEBUG) << "> Succeeded."; -#endif - - return true; -} -static bool install_internal(HMODULE target_module, HMODULE replacement_module, hook_method method) -{ - assert(target_module != nullptr && replacement_module != nullptr); - assert(target_module != replacement_module); - - // Load export tables - const auto target_exports = get_module_exports(target_module); - const auto replacement_exports = get_module_exports(replacement_module); - - if (target_exports.empty()) - { - LOG(INFO) << "> Empty export table! Skipped."; - return false; - } - - size_t install_count = 0; - std::vector> matches; - matches.reserve(replacement_exports.size()); - -#if RESHADE_VERBOSE_LOG - LOG(DEBUG) << "> Dumping matches in export table:"; - LOG(DEBUG) << " +--------------------+---------+----------------------------------------------------+"; - LOG(DEBUG) << " | Address | Ordinal | Name |"; - LOG(DEBUG) << " +--------------------+---------+----------------------------------------------------+"; -#endif - - // Analyze export table - for (const auto &symbol : target_exports) - { - if (symbol.name == nullptr || symbol.address == nullptr) - continue; - - // Find appropriate replacement - const auto it = std::find_if(replacement_exports.cbegin(), replacement_exports.cend(), - [&symbol](const auto &moduleexport) { - return std::strcmp(moduleexport.name, symbol.name) == 0; - }); - - // Filter uninteresting functions - if (it != replacement_exports.cend() && - std::strcmp(symbol.name, "DXGIReportAdapterConfiguration") != 0 && - std::strcmp(symbol.name, "DXGIDumpJournal") != 0) - { -#if RESHADE_VERBOSE_LOG - LOG(DEBUG) << " | 0x" << std::setw(16) << symbol.address << " | " << std::setw(7) << symbol.ordinal << " | " << std::setw(50) << symbol.name << " |"; -#endif - matches.push_back(std::make_tuple(symbol.name, symbol.address, it->address)); - } - } - -#if RESHADE_VERBOSE_LOG - LOG(DEBUG) << " +--------------------+---------+----------------------------------------------------+"; -#endif - LOG(INFO) << "> Found " << matches.size() << " match(es). Installing ..."; - - // Hook matching exports - for (const auto &match : matches) - { - reshade::hook hook; - hook.target = std::get<1>(match); - hook.trampoline = hook.target; - hook.replacement = std::get<2>(match); - - if (install_internal(std::get<0>(match), hook, method)) - install_count++; - } - - return install_count != 0; -} -static bool uninstall_internal(const char *name, reshade::hook &hook, hook_method method) -{ -#if RESHADE_VERBOSE_LOG - LOG(DEBUG) << "Uninstalling hook for " << name << " ..."; -#endif - if (hook.uninstalled()) - { - LOG(WARN) << "Hook for " << name << " was already uninstalled."; - return true; - } - - auto status = reshade::hook::status::unknown; - - switch (method) - { - case hook_method::export_hook: -#if RESHADE_VERBOSE_LOG - LOG(DEBUG) << "> Skipped."; -#endif - return true; - case hook_method::function_hook: - status = hook.uninstall(); - break; - case hook_method::vtable_hook: - if (DWORD protection = PAGE_READWRITE; - VirtualProtect(hook.target, sizeof(reshade::hook::address), protection, &protection)) - { - *reinterpret_cast(hook.target) = hook.trampoline; - - VirtualProtect(hook.target, sizeof(reshade::hook::address), protection, &protection); - - status = reshade::hook::status::success; - } - else - { - status = reshade::hook::status::memory_protection_failure; - } - break; - } - - if (status != reshade::hook::status::success) - { - LOG(WARN) << "Failed to uninstall hook for " << name << " with status code " << static_cast(status) << '.'; - return false; - } - -#if RESHADE_VERBOSE_LOG - LOG(DEBUG) << "> Succeeded."; -#endif - hook.trampoline = nullptr; - - return true; -} - -static void install_delayed_hooks(const std::filesystem::path &loaded_path) -{ - // Ignore this call if unable to acquire the mutex to avoid possible deadlock - if (std::unique_lock lock(s_mutex_delayed_hook_paths, std::try_to_lock); lock.owns_lock()) - { - const auto remove = std::remove_if(s_delayed_hook_paths.begin(), s_delayed_hook_paths.end(), - [&loaded_path](const auto &path) { - // Pin the module so it cannot be unloaded by the application and cause problems when ReShade tries to call into it afterwards - HMODULE delayed_handle = nullptr; - if (!GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_PIN, path.wstring().c_str(), &delayed_handle)) - return false; - - LOG(INFO) << "Installing delayed hooks for " << path << " (Just loaded via LoadLibrary(" << loaded_path << ")) ..."; - - return install_internal(delayed_handle, g_module_handle, hook_method::function_hook) && reshade::hook::apply_queued_actions(); - }); - - s_delayed_hook_paths.erase(remove, s_delayed_hook_paths.end()); - } - else - { - LOG(WARN) << "Ignoring LoadLibrary(" << loaded_path << ") call to avoid possible deadlock."; - } -} - -static reshade::hook find_internal(reshade::hook::address target, reshade::hook::address replacement) -{ - const std::lock_guard lock(s_mutex_hooks); - - const auto it = std::find_if(s_hooks.cbegin(), s_hooks.cend(), - [target, replacement](const auto &hook) { - return std::get<1>(hook).replacement == replacement && - (target == nullptr || std::get<1>(hook).target == target); - }); - - return it != s_hooks.cend() ? std::get<1>(*it) : reshade::hook {}; -} - -template -static inline T call_unchecked(T replacement) -{ - return reinterpret_cast(find_internal(nullptr, reinterpret_cast(replacement)).call()); -} - -HMODULE WINAPI HookLoadLibraryA(LPCSTR lpFileName) -{ - static const auto trampoline = call_unchecked(&HookLoadLibraryA); - - const HMODULE handle = trampoline(lpFileName); - if (handle != nullptr && handle != g_module_handle) - install_delayed_hooks(lpFileName); - - return handle; -} -HMODULE WINAPI HookLoadLibraryExA(LPCSTR lpFileName, HANDLE hFile, DWORD dwFlags) -{ - static const auto trampoline = call_unchecked(&HookLoadLibraryExA); - - const HMODULE handle = trampoline(lpFileName, hFile, dwFlags); - if (dwFlags == 0 && handle != nullptr && handle != g_module_handle) - install_delayed_hooks(lpFileName); - - return handle; -} -HMODULE WINAPI HookLoadLibraryW(LPCWSTR lpFileName) -{ - static const auto trampoline = call_unchecked(&HookLoadLibraryW); - - const HMODULE handle = trampoline(lpFileName); - if (handle != nullptr && handle != g_module_handle) - install_delayed_hooks(lpFileName); - - return handle; -} -HMODULE WINAPI HookLoadLibraryExW(LPCWSTR lpFileName, HANDLE hFile, DWORD dwFlags) -{ - static const auto trampoline = call_unchecked(&HookLoadLibraryExW); - - const HMODULE handle = trampoline(lpFileName, hFile, dwFlags); - if (dwFlags == 0 && handle != nullptr && handle != g_module_handle) - install_delayed_hooks(lpFileName); - - return handle; -} - -bool reshade::hooks::install(const char *name, hook::address target, hook::address replacement, bool queue_enable) -{ - assert(target != nullptr); - assert(replacement != nullptr); - - hook hook = find_internal(nullptr, replacement); - // If the hook was already installed, make sure it was installed for the same target function - if (hook.installed()) - return target == hook.target; - - // Otherwise, set up the new hook and install it - hook.target = target; - hook.replacement = replacement; - - return install_internal(name, hook, hook_method::function_hook) && - (!queue_enable || hook::apply_queued_actions()); // Can optionally only queue up the hooks instead of installing them right away -} -bool reshade::hooks::install(const char *name, hook::address vtable[], unsigned int offset, hook::address replacement) -{ - assert(vtable != nullptr); - assert(replacement != nullptr); - - hook hook = find_internal(nullptr, replacement); - // Check if the hook was already installed to this vtable - if (hook.installed() && vtable[offset] == hook.replacement) - return true; - - hook.target = &vtable[offset]; // Target is the address of the vtable entry - hook.trampoline = vtable[offset]; // The current function in that entry is the original function to call - hook.replacement = replacement; - - return install_internal(name, hook, hook_method::vtable_hook); -} -void reshade::hooks::uninstall() -{ - LOG(INFO) << "Uninstalling " << s_hooks.size() << " hook(s) ..."; - - // Disable all hooks in a single batch job - for (auto &hook_info : s_hooks) - std::get<1>(hook_info).enable(false); - - hook::apply_queued_actions(); - - // Afterwards remove all hooks from the list - for (auto &hook_info : s_hooks) - uninstall_internal( - std::get<0>(hook_info), - std::get<1>(hook_info), - std::get<2>(hook_info)); - - s_hooks.clear(); -} - -void reshade::hooks::register_module(const std::filesystem::path &target_path) -{ - install("LoadLibraryA", reinterpret_cast(&LoadLibraryA), reinterpret_cast(&HookLoadLibraryA), true); - install("LoadLibraryExA", reinterpret_cast(&LoadLibraryExA), reinterpret_cast(&HookLoadLibraryExA), true); - install("LoadLibraryW", reinterpret_cast(&LoadLibraryW), reinterpret_cast(&HookLoadLibraryW), true); - install("LoadLibraryExW", reinterpret_cast(&LoadLibraryExW), reinterpret_cast(&HookLoadLibraryExW), true); - - // Install all "LoadLibrary" hooks in one go immediately - hook::apply_queued_actions(); - - LOG(INFO) << "Registering hooks for " << target_path << " ..."; - - // Compare module names and delay export hooks for later installation since we cannot call "LoadLibrary" from this function (it is called from "DLLMain", which does not allow this) - const std::filesystem::path target_name = target_path.stem(); - const std::filesystem::path replacement_name = g_reshade_dll_path.stem(); - - if (_wcsicmp(target_name.c_str(), replacement_name.c_str()) == 0) - { - assert(target_path != g_reshade_dll_path); - - LOG(INFO) << "> Delayed until first call to an exported function."; - - s_export_hook_path = target_path; - } - // Similarly, if the target module was not loaded yet, wait for it to get loaded in the "LoadLibrary" hooks and install it then - // Pin the module so it cannot be unloaded by the application and cause problems when ReShade tries to call into it afterwards - else if (HMODULE handle; !GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_PIN, target_path.wstring().c_str(), &handle)) - { - LOG(INFO) << "> Delayed."; - - s_delayed_hook_paths.push_back(target_path); - } - else // The target module is already loaded, so we can safely install hooks right away - { - LOG(INFO) << "> Libraries loaded."; - - install_internal(handle, g_module_handle, hook_method::function_hook); - - hook::apply_queued_actions(); - } -} - -reshade::hook::address reshade::hooks::call(hook::address target, hook::address replacement) -{ - const hook hook = find_internal(target, replacement); - - if (hook.valid()) - { - return hook.call(); - } - else if (!s_export_hook_path.empty()) // If the hook does not exist yet, delay-load export hooks and try again - { - // Note: Could use LoadLibraryExW with LOAD_LIBRARY_SEARCH_SYSTEM32 here, but absolute paths should to the trick as well - const HMODULE handle = LoadLibraryW(s_export_hook_path.wstring().c_str()); - - LOG(INFO) << "Installing export hooks for " << s_export_hook_path << " ..."; - - if (handle != nullptr) - { - assert(handle != g_module_handle); - - install_internal(handle, g_module_handle, hook_method::export_hook); - - s_export_hook_path.clear(); - - return call(replacement); - } - else - { - LOG(ERROR) << "Failed to load " << s_export_hook_path << '!'; - } - } - - LOG(ERROR) << "Unable to resolve hook for 0x" << replacement << '!'; - - return nullptr; -} diff --git a/msvc/source/hook_manager.hpp b/msvc/source/hook_manager.hpp deleted file mode 100644 index 98a59ae..0000000 --- a/msvc/source/hook_manager.hpp +++ /dev/null @@ -1,66 +0,0 @@ -/** - * Copyright (C) 2014 Patrick Mours. All rights reserved. - * License: https://github.com/crosire/reshade#license - */ - -#pragma once - -#include "hook.hpp" -#include - -#define HOOK_EXPORT extern "C" - -template -inline reshade::hook::address *vtable_from_instance(T *instance) -{ - static_assert(std::is_polymorphic::value, "can only get virtual function table from polymorphic types"); - - return *reinterpret_cast(instance); -} - -namespace reshade::hooks -{ - /// - /// Install hook for the specified target function. - /// - /// The function name. This is used for debugging only. - /// The address of the target function. - /// The address of the hook function. - /// Set this to true to queue the enable action instead of immediately executing it. - /// The status of the hook installation. - bool install(const char *name, hook::address target, hook::address replacement, bool queue_enable = false); - /// - /// Install hook for the specified virtual function table entry. - /// - /// The function name. This is used for debugging only. - /// The virtual function table pointer of the object to targeted object. - /// The index of the target function in the virtual function table. - /// The address of the hook function. - /// The status of the hook installation. - bool install(const char *name, hook::address vtable[], unsigned int offset, hook::address replacement); - /// - /// Uninstall all previously installed hooks. - /// Only call this function as long as the loader-lock is active, since it is not thread-safe. - /// - void uninstall(); - - /// - /// Register the matching exports in the specified module and install or delay their hooking. - /// Only call this function as long as the loader-lock is active, since it is not thread-safe. - /// - /// The file path to the target module. - void register_module(const std::filesystem::path &path); - - /// - /// Call the original/trampoline function for the specified hook. - /// - /// The original target address the hook was installed to (optional). - /// The address of the hook function which was previously used to install a hook. - /// The address of original/trampoline function. - hook::address call(hook::address target, hook::address replacement); - template - inline T call(T replacement, hook::address target = nullptr) - { - return reinterpret_cast(call(target, reinterpret_cast(replacement))); - } -} diff --git a/msvc/source/ini_file.cpp b/msvc/source/ini_file.cpp deleted file mode 100644 index f58a6e9..0000000 --- a/msvc/source/ini_file.cpp +++ /dev/null @@ -1,145 +0,0 @@ -/** - * Copyright (C) 2014 Patrick Mours. All rights reserved. - * License: https://github.com/crosire/reshade#license - */ - -#include "ini_file.hpp" -#include - -static inline void trim(std::string &str, const char *chars = " \t") -{ - str.erase(0, str.find_first_not_of(chars)); - str.erase(str.find_last_not_of(chars) + 1); -} -static inline std::string trim(const std::string &str, const char *chars = " \t") -{ - std::string res(str); - trim(res, chars); - return res; -} - -reshade::ini_file::ini_file(const std::filesystem::path &path) - : _path(path), _save_path(path) -{ - load(); -} -reshade::ini_file::ini_file(const std::filesystem::path &path, const std::filesystem::path &save_path) - : _path(path), _save_path(save_path) -{ - load(); -} -reshade::ini_file::~ini_file() -{ - save(); -} - -void reshade::ini_file::load() -{ - std::string line, section; - std::ifstream file(_path); - file.imbue(std::locale("en-us.UTF-8")); - - // Remove BOM (0xefbbbf means 0xfeff) - if (file.get() != 0xef || file.get() != 0xbb || file.get() != 0xbf) - file.seekg(0, std::ios::beg); - - while (std::getline(file, line)) - { - trim(line); - - if (line.empty() || line[0] == ';' || line[0] == '/') - continue; - - // Read section name - if (line[0] == '[') - { - section = trim(line.substr(0, line.find(']')), " \t[]"); - continue; - } - - // Read section content - const auto assign_index = line.find('='); - - if (assign_index != std::string::npos) - { - const auto key = trim(line.substr(0, assign_index)); - const auto value = trim(line.substr(assign_index + 1)); - std::vector value_splitted; - - for (size_t i = 0, len = value.size(), found; i < len; i = found + 1) - { - found = value.find_first_of(',', i); - - if (found == std::string::npos) - found = len; - - value_splitted.push_back(value.substr(i, found - i)); - } - - _sections[section][key] = value_splitted; - } - else - { - _sections[section][line] = {}; - } - } -} -void reshade::ini_file::save() const -{ - if (!_modified) - return; - - std::ofstream file(_save_path); - file.imbue(std::locale("en-us.UTF-8")); - - const auto it = _sections.find(""); - - if (it != _sections.end()) - { - for (const auto §ion_line : it->second) - { - file << section_line.first << '='; - - size_t i = 0; - - for (const auto &item : section_line.second) - { - if (i++ != 0) - file << ','; - - file << item; - } - - file << '\n'; - } - - file << '\n'; - } - - for (const auto §ion : _sections) - { - if (section.first.empty()) - continue; - - file << '[' << section.first << ']' << '\n'; - - for (const auto §ion_line : section.second) - { - file << section_line.first << '='; - - size_t i = 0; - - for (const auto &item : section_line.second) - { - if (i++ != 0) - file << ','; - - file << item; - } - - file << '\n'; - } - - file << '\n'; - } -} diff --git a/msvc/source/ini_file.hpp b/msvc/source/ini_file.hpp deleted file mode 100644 index a00ccf5..0000000 --- a/msvc/source/ini_file.hpp +++ /dev/null @@ -1,196 +0,0 @@ -/** - * Copyright (C) 2014 Patrick Mours. All rights reserved. - * License: https://github.com/crosire/reshade#license - */ - -#pragma once - -#include -#include -#include -#include - -namespace reshade -{ - class ini_file - { - public: - explicit ini_file(const std::filesystem::path &path); - ini_file(const std::filesystem::path &path, const std::filesystem::path &save_path); - ~ini_file(); - - bool has(const std::string §ion, const std::string &key) const - { - const auto it1 = _sections.find(section); - if (it1 == _sections.end()) - return false; - const auto it2 = it1->second.find(key); - if (it2 == it1->second.end()) - return false; - return true; - } - - template - void get(const std::string §ion, const std::string &key, T &value) const - { - const auto it1 = _sections.find(section); - if (it1 == _sections.end()) - return; - const auto it2 = it1->second.find(key); - if (it2 == it1->second.end()) - return; - value = convert(it2->second, 0); - } - template - void get(const std::string §ion, const std::string &key, T(&values)[SIZE]) const - { - const auto it1 = _sections.find(section); - if (it1 == _sections.end()) - return; - const auto it2 = it1->second.find(key); - if (it2 == it1->second.end()) - return; - for (size_t i = 0; i < SIZE; ++i) - values[i] = convert(it2->second, i); - } - template - void get(const std::string §ion, const std::string &key, std::vector &values) const - { - const auto it1 = _sections.find(section); - if (it1 == _sections.end()) - return; - const auto it2 = it1->second.find(key); - if (it2 == it1->second.end()) - return; - values.resize(it2->second.size()); - for (size_t i = 0; i < it2->second.size(); ++i) - values[i] = convert(it2->second, i); - } - - template - void set(const std::string §ion, const std::string &key, const T &value) - { - set(section, key, std::to_string(value)); - } - template <> - void set(const std::string §ion, const std::string &key, const bool &value) - { - set(section, key, value ? "1" : "0"); - } - template <> - void set(const std::string §ion, const std::string &key, const std::string &value) - { - auto &v = _sections[section][key]; - v.assign(1, value); - _modified = true; - } - template <> - void set(const std::string §ion, const std::string &key, const std::filesystem::path &value) - { - set(section, key, value.u8string()); - } - template - void set(const std::string §ion, const std::string &key, const T(&values)[SIZE], size_t size = SIZE) - { - auto &v = _sections[section][key]; - v.resize(size); - for (size_t i = 0; i < size; ++i) - v[i] = std::to_string(values[i]); - _modified = true; - } - template - void set(const std::string §ion, const std::string &key, const std::vector &values) - { - auto &v = _sections[section][key]; - v.resize(values.size()); - for (size_t i = 0; i < values.size(); ++i) - v[i] = std::to_string(values[i]); - _modified = true; - } - template <> - void set(const std::string §ion, const std::string &key, const std::vector &values) - { - auto &v = _sections[section][key]; - v = values; - _modified = true; - } - template <> - void set(const std::string §ion, const std::string &key, const std::vector &values) - { - auto &v = _sections[section][key]; - v.resize(values.size()); - for (size_t i = 0; i < values.size(); ++i) - v[i] = values[i].u8string(); - _modified = true; - } - - private: - void load(); - void save() const; - - template - static const T convert(const std::vector &values, size_t i) = delete; - template <> - static const bool convert(const std::vector &values, size_t i) - { - return convert(values, i) != 0 || i < values.size() && (values[i] == "true" || values[i] == "True" || values[i] == "TRUE"); - } - template <> - static const int convert(const std::vector &values, size_t i) - { - return static_cast(convert(values, i)); - } - template <> - static const unsigned int convert(const std::vector &values, size_t i) - { - return static_cast(convert(values, i)); - } - template <> - static const long convert(const std::vector &values, size_t i) - { - return i < values.size() ? std::strtol(values[i].c_str(), nullptr, 10) : 0l; - } - template <> - static const unsigned long convert(const std::vector &values, size_t i) - { - return i < values.size() ? std::strtoul(values[i].c_str(), nullptr, 10) : 0ul; - } - template <> - static const long long convert(const std::vector &values, size_t i) - { - return i < values.size() ? std::strtoll(values[i].c_str(), nullptr, 10) : 0ll; - } - template <> - static const unsigned long long convert(const std::vector &values, size_t i) - { - return i < values.size() ? std::strtoull(values[i].c_str(), nullptr, 10) : 0ull; - } - template <> - static const float convert(const std::vector &values, size_t i) - { - return static_cast(convert(values, i)); - } - template <> - static const double convert(const std::vector &values, size_t i) - { - return i < values.size() ? std::strtod(values[i].c_str(), nullptr) : 0.0; - } - template <> - static const std::string convert(const std::vector &values, size_t i) - { - return i < values.size() ? values[i] : std::string(); - } - template <> - static const std::filesystem::path convert(const std::vector &values, size_t i) - { - return i < values.size() ? std::filesystem::u8path(values[i]) : std::filesystem::path(); - } - - bool _modified = false; - std::filesystem::path _path; - std::filesystem::path _save_path; - using value = std::vector; - using section = std::unordered_map; - std::unordered_map _sections; - }; -} diff --git a/msvc/source/input.cpp b/msvc/source/input.cpp deleted file mode 100644 index b6ad5ec..0000000 --- a/msvc/source/input.cpp +++ /dev/null @@ -1,581 +0,0 @@ -/** - * Copyright (C) 2014 Patrick Mours. All rights reserved. - * License: https://github.com/crosire/reshade#license - */ - -#include "log.hpp" -#include "input.hpp" -#include "hook_manager.hpp" -#include -#include -#include -#include -#include - -static std::mutex s_windows_mutex; -static std::unordered_map s_raw_input_windows; -static std::unordered_map> s_windows; - -reshade::input::input(window_handle window) - : _window(window) -{ - assert(window != nullptr); -} - -void reshade::input::register_window_with_raw_input(window_handle window, bool no_legacy_keyboard, bool no_legacy_mouse) -{ - const std::lock_guard lock(s_windows_mutex); - - const auto flags = (no_legacy_keyboard ? 0x1u : 0u) | (no_legacy_mouse ? 0x2u : 0u); - const auto insert = s_raw_input_windows.emplace(static_cast(window), flags); - - if (!insert.second) insert.first->second |= flags; -} -std::shared_ptr reshade::input::register_window(window_handle window) -{ - const std::lock_guard lock(s_windows_mutex); - - const auto insert = s_windows.emplace(static_cast(window), std::weak_ptr()); - - if (insert.second || insert.first->second.expired()) - { -#if RESHADE_VERBOSE_LOG - LOG(DEBUG) << "Starting input capture for window " << window << " ..."; -#endif - - const auto instance = std::make_shared(window); - - insert.first->second = instance; - - return instance; - } - else - { - return insert.first->second.lock(); - } -} - -bool reshade::input::handle_window_message(const void *message_data) -{ - assert(message_data != nullptr); - - MSG details = *static_cast(message_data); - - bool is_mouse_message = details.message >= WM_MOUSEFIRST && details.message <= WM_MOUSELAST; - bool is_keyboard_message = details.message >= WM_KEYFIRST && details.message <= WM_KEYLAST; - - // Ignore messages that are not related to mouse or keyboard input - if (details.message != WM_INPUT && !is_mouse_message && !is_keyboard_message) - return false; - - // Guard access to windows list against race conditions - std::unique_lock lock(s_windows_mutex); - - // Remove any expired entry from the list - for (auto it = s_windows.begin(); it != s_windows.end();) - it->second.expired() ? it = s_windows.erase(it) : ++it; - - // Look up the window in the list of known input windows - auto input_window = s_windows.find(details.hwnd); - const auto raw_input_window = s_raw_input_windows.find(details.hwnd); - - if (input_window == s_windows.end()) - { - // Walk through the window chain and until an known window is found - EnumChildWindows(details.hwnd, [](HWND hwnd, LPARAM lparam) -> BOOL { - auto &input_window = *reinterpret_cast(lparam); - // Return true to continue enumeration - return (input_window = s_windows.find(hwnd)) == s_windows.end(); - }, reinterpret_cast(&input_window)); - } - - if (input_window == s_windows.end() && raw_input_window != s_raw_input_windows.end()) - { - // Reroute this raw input message to the window with the most rendering - input_window = std::max_element(s_windows.begin(), s_windows.end(), - [](auto lhs, auto rhs) { return lhs.second.lock()->_frame_count < rhs.second.lock()->_frame_count; }); - } - - if (input_window == s_windows.end()) - return false; - - const std::shared_ptr input = input_window->second.lock(); - - // At this point we have a shared pointer to the input object and no longer reference any memory from the windows list, so can release the lock - lock.unlock(); - - // Prevent input threads from modifying input while it is accessed elsewhere - std::lock_guard input_lock(input->_mutex); - - // Calculate window client mouse position - ScreenToClient(static_cast(input->_window), &details.pt); - - input->_mouse_position[0] = details.pt.x; - input->_mouse_position[1] = details.pt.y; - - switch (details.message) - { - case WM_INPUT: { - RAWINPUT raw_data = {}; - if (UINT raw_data_size = sizeof(raw_data); - GET_RAWINPUT_CODE_WPARAM(details.wParam) != RIM_INPUT || - GetRawInputData(reinterpret_cast(details.lParam), RID_INPUT, &raw_data, &raw_data_size, sizeof(raw_data.header)) == UINT(-1)) - break; - - switch (raw_data.header.dwType) - { - case RIM_TYPEMOUSE: - is_mouse_message = true; - - if (raw_input_window == s_raw_input_windows.end() || (raw_input_window->second & 0x2) == 0) - break; // Input is already handled (since legacy mouse messages are enabled), so nothing to do here - - if (raw_data.data.mouse.usButtonFlags & RI_MOUSE_LEFT_BUTTON_DOWN) - input->_mouse_buttons[0] = 0x88; - else if (raw_data.data.mouse.usButtonFlags & RI_MOUSE_LEFT_BUTTON_UP) - input->_mouse_buttons[0] = 0x08; - if (raw_data.data.mouse.usButtonFlags & RI_MOUSE_RIGHT_BUTTON_DOWN) - input->_mouse_buttons[1] = 0x88; - else if (raw_data.data.mouse.usButtonFlags & RI_MOUSE_RIGHT_BUTTON_UP) - input->_mouse_buttons[1] = 0x08; - if (raw_data.data.mouse.usButtonFlags & RI_MOUSE_MIDDLE_BUTTON_DOWN) - input->_mouse_buttons[2] = 0x88; - else if (raw_data.data.mouse.usButtonFlags & RI_MOUSE_MIDDLE_BUTTON_UP) - input->_mouse_buttons[2] = 0x08; - - if (raw_data.data.mouse.usButtonFlags & RI_MOUSE_BUTTON_4_DOWN) - input->_mouse_buttons[3] = 0x88; - else if (raw_data.data.mouse.usButtonFlags & RI_MOUSE_BUTTON_4_UP) - input->_mouse_buttons[3] = 0x08; - - if (raw_data.data.mouse.usButtonFlags & RI_MOUSE_BUTTON_5_DOWN) - input->_mouse_buttons[4] = 0x88; - else if (raw_data.data.mouse.usButtonFlags & RI_MOUSE_BUTTON_5_UP) - input->_mouse_buttons[4] = 0x08; - - if (raw_data.data.mouse.usButtonFlags & RI_MOUSE_WHEEL) - input->_mouse_wheel_delta += static_cast(raw_data.data.mouse.usButtonData) / WHEEL_DELTA; - break; - case RIM_TYPEKEYBOARD: - is_keyboard_message = true; - - if (raw_input_window == s_raw_input_windows.end() || (raw_input_window->second & 0x1) == 0) - break; // Input is already handled by 'WM_KEYDOWN' and friends (since legacy keyboard messages are enabled), so nothing to do here - - if (raw_data.data.keyboard.VKey != 0xFF) - input->_keys[raw_data.data.keyboard.VKey] = (raw_data.data.keyboard.Flags & RI_KEY_BREAK) == 0 ? 0x88 : 0x08; - - // No 'WM_CHAR' messages are sent if legacy keyboard messages are disabled, so need to generate text input manually here - // Cannot use the ToAscii function always as it seems to reset dead key state and thus calling it can break subsequent application input, should be fine here though since the application is already explicitly using raw input - if (WORD ch = 0; (raw_data.data.keyboard.Flags & RI_KEY_BREAK) == 0 && ToAscii(raw_data.data.keyboard.VKey, raw_data.data.keyboard.MakeCode, input->_keys, &ch, 0)) - input->_text_input += ch; - break; - } - break; } - case WM_CHAR: - input->_text_input += static_cast(details.wParam); - break; - case WM_KEYDOWN: - case WM_SYSKEYDOWN: - assert(details.wParam < _countof(input->_keys)); - input->_keys[details.wParam] = 0x88; - break; - case WM_KEYUP: - case WM_SYSKEYUP: - assert(details.wParam < _countof(input->_keys)); - input->_keys[details.wParam] = 0x08; - break; - case WM_LBUTTONDOWN: - input->_mouse_buttons[0] = 0x88; - break; - case WM_LBUTTONUP: - input->_mouse_buttons[0] = 0x08; - break; - case WM_RBUTTONDOWN: - input->_mouse_buttons[1] = 0x88; - break; - case WM_RBUTTONUP: - input->_mouse_buttons[1] = 0x08; - break; - case WM_MBUTTONDOWN: - input->_mouse_buttons[2] = 0x88; - break; - case WM_MBUTTONUP: - input->_mouse_buttons[2] = 0x08; - break; - case WM_MOUSEWHEEL: - input->_mouse_wheel_delta += GET_WHEEL_DELTA_WPARAM(details.wParam) / WHEEL_DELTA; - break; - case WM_XBUTTONDOWN: - assert(2 + HIWORD(details.wParam) < _countof(input->_mouse_buttons)); - input->_mouse_buttons[2 + HIWORD(details.wParam)] = 0x88; - break; - case WM_XBUTTONUP: - assert(2 + HIWORD(details.wParam) < _countof(input->_mouse_buttons)); - input->_mouse_buttons[2 + HIWORD(details.wParam)] = 0x08; - break; - } - - return (is_mouse_message && input->_block_mouse) || (is_keyboard_message && input->_block_keyboard); -} - -bool reshade::input::is_key_down(unsigned int keycode) const -{ - assert(keycode < _countof(_keys)); - return keycode < _countof(_keys) && (_keys[keycode] & 0x80) == 0x80; -} -bool reshade::input::is_key_pressed(unsigned int keycode) const -{ - assert(keycode < _countof(_keys)); - return keycode < _countof(_keys) && (_keys[keycode] & 0x88) == 0x88; -} -bool reshade::input::is_key_pressed(unsigned int keycode, bool ctrl, bool shift, bool alt) const -{ - return is_key_pressed(keycode) && ctrl == is_key_down(VK_CONTROL) && shift == is_key_down(VK_SHIFT) && alt == is_key_down(VK_MENU); -} -bool reshade::input::is_key_released(unsigned int keycode) const -{ - assert(keycode < _countof(_keys)); - return keycode < _countof(_keys) && (_keys[keycode] & 0x88) == 0x08; -} - -bool reshade::input::is_any_key_down() const -{ - for (unsigned int i = 0; i < _countof(_keys); i++) - if (is_key_down(i)) - return true; - return false; -} -bool reshade::input::is_any_key_pressed() const -{ - return last_key_pressed() != 0; -} -bool reshade::input::is_any_key_released() const -{ - return last_key_released() != 0; -} - -unsigned int reshade::input::last_key_pressed() const -{ - for (unsigned int i = 0; i < _countof(_keys); i++) - if (is_key_pressed(i)) - return i; - return 0; -} -unsigned int reshade::input::last_key_released() const -{ - for (unsigned int i = 0; i < _countof(_keys); i++) - if (is_key_released(i)) - return i; - return 0; -} - -bool reshade::input::is_mouse_button_down(unsigned int button) const -{ - assert(button < _countof(_mouse_buttons)); - return button < _countof(_mouse_buttons) && (_mouse_buttons[button] & 0x80) == 0x80; -} -bool reshade::input::is_mouse_button_pressed(unsigned int button) const -{ - assert(button < _countof(_mouse_buttons)); - return button < _countof(_mouse_buttons) && (_mouse_buttons[button] & 0x88) == 0x88; -} -bool reshade::input::is_mouse_button_released(unsigned int button) const -{ - assert(button < _countof(_mouse_buttons)); - return button < _countof(_mouse_buttons) && (_mouse_buttons[button] & 0x88) == 0x08; -} - -bool reshade::input::is_any_mouse_button_down() const -{ - for (unsigned int i = 0; i < _countof(_mouse_buttons); i++) - if (is_mouse_button_down(i)) - return true; - return false; -} -bool reshade::input::is_any_mouse_button_pressed() const -{ - for (unsigned int i = 0; i < _countof(_mouse_buttons); i++) - if (is_mouse_button_pressed(i)) - return true; - return false; -} -bool reshade::input::is_any_mouse_button_released() const -{ - for (unsigned int i = 0; i < _countof(_mouse_buttons); i++) - if (is_mouse_button_released(i)) - return true; - return false; -} - -void reshade::input::block_mouse_input(bool enable) -{ - _block_mouse = enable; - - // Some applications clip the mouse cursor, so disable that while we want full control over mouse input - if (enable) - ClipCursor(nullptr); -} -void reshade::input::block_keyboard_input(bool enable) -{ - _block_keyboard = enable; -} - -void reshade::input::next_frame() -{ - _frame_count++; - - for (auto &state : _keys) - state &= ~0x8; - for (auto &state : _mouse_buttons) - state &= ~0x8; - - _text_input.clear(); - _mouse_wheel_delta = 0; - _last_mouse_position[0] = _mouse_position[0]; - _last_mouse_position[1] = _mouse_position[1]; - - // Update caps lock state - _keys[VK_CAPITAL] |= GetKeyState(VK_CAPITAL) & 0x1; - - // Update modifier key state - if ((_keys[VK_MENU] & 0x88) != 0 && - (GetKeyState(VK_MENU) & 0x8000) == 0) - _keys[VK_MENU] = 0x08; - - // Update print screen state - if ((_keys[VK_SNAPSHOT] & 0x80) == 0 && - (GetAsyncKeyState(VK_SNAPSHOT) & 0x8000) != 0) - _keys[VK_SNAPSHOT] = 0x88; -} - -std::string reshade::input::key_name(unsigned int keycode) -{ - if (keycode >= 256) - return std::string(); - - static const char *keyboard_keys_german[256] = { - "", "", "", "Cancel", "", "", "", "", "Backspace", "Tab", "", "", "Clear", "Enter", "", "", - "Shift", "Control", "Alt", "Pause", "Caps Lock", "", "", "", "", "", "", "Escape", "", "", "", "", - "Leertaste", "Bild auf", "Bild ab", "Ende", "Pos 1", "Left Arrow", "Up Arrow", "Right Arrow", "Down Arrow", "Select", "", "", "Druck", "Einfg", "Entf", "Hilfe", - "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "", "", "", "", "", "", - "", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", - "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "Left Windows", "Right Windows", "Apps", "", "Sleep", - "Numpad 0", "Numpad 1", "Numpad 2", "Numpad 3", "Numpad 4", "Numpad 5", "Numpad 6", "Numpad 7", "Numpad 8", "Numpad 9", "Numpad *", "Numpad +", "", "Numpad -", "Numpad ,", "Numpad /", - "F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9", "F10", "F11", "F12", "F13", "F14", "F15", "F16", - "F17", "F18", "F19", "F20", "F21", "F22", "F23", "F24", "", "", "", "", "", "", "", "", - "Num Lock", "Scroll Lock", "", "", "", "", "", "", "", "", "", "", "", "", "", "", - "Left Shift", "Right Shift", "Left Control", "Right Control", "Left Menu", "Right Menu", "Browser Back", "Browser Forward", "Browser Refresh", "Browser Stop", "Browser Search", "Browser Favorites", "Browser Home", "Volume Mute", "Volume Down", "Volume Up", - "Next Track", "Previous Track", "Media Stop", "Media Play/Pause", "Mail", "Media Select", "Launch App 1", "Launch App 2", "", "", u8"Ü", "OEM +", "OEM ,", "OEM -", "OEM .", "OEM #", - u8"Ö", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", - "", "", "", "", "", "", "", "", "", "", "", u8"OEM ß", "OEM ^", u8"OEM ´", u8"Ä", "OEM 8", - "", "", "OEM <", "", "", "", "", "", "", "", "", "", "", "", "", "", - "", "", "", "", "", "", "Attn", "CrSel", "ExSel", "Erase EOF", "Play", "Zoom", "", "PA1", "OEM Clear", "" - }; - static const char *keyboard_keys_international[256] = { - "", "", "", "Cancel", "", "", "", "", "Backspace", "Tab", "", "", "Clear", "Enter", "", "", - "Shift", "Control", "Alt", "Pause", "Caps Lock", "", "", "", "", "", "", "Escape", "", "", "", "", - "Space", "Page Up", "Page Down", "End", "Home", "Left Arrow", "Up Arrow", "Right Arrow", "Down Arrow", "Select", "", "", "Print Screen", "Insert", "Delete", "Help", - "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "", "", "", "", "", "", - "", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", - "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "Left Windows", "Right Windows", "Apps", "", "Sleep", - "Numpad 0", "Numpad 1", "Numpad 2", "Numpad 3", "Numpad 4", "Numpad 5", "Numpad 6", "Numpad 7", "Numpad 8", "Numpad 9", "Numpad *", "Numpad +", "", "Numpad -", "Numpad Decimal", "Numpad /", - "F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9", "F10", "F11", "F12", "F13", "F14", "F15", "F16", - "F17", "F18", "F19", "F20", "F21", "F22", "F23", "F24", "", "", "", "", "", "", "", "", - "Num Lock", "Scroll Lock", "", "", "", "", "", "", "", "", "", "", "", "", "", "", - "Left Shift", "Right Shift", "Left Control", "Right Control", "Left Menu", "Right Menu", "Browser Back", "Browser Forward", "Browser Refresh", "Browser Stop", "Browser Search", "Browser Favorites", "Browser Home", "Volume Mute", "Volume Down", "Volume Up", - "Next Track", "Previous Track", "Media Stop", "Media Play/Pause", "Mail", "Media Select", "Launch App 1", "Launch App 2", "", "", "OEM ;", "OEM +", "OEM ,", "OEM -", "OEM .", "OEM /", - "OEM ~", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", - "", "", "", "", "", "", "", "", "", "", "", "OEM [", "OEM \\", "OEM ]", "OEM '", "OEM 8", - "", "", "OEM <", "", "", "", "", "", "", "", "", "", "", "", "", "", - "", "", "", "", "", "", "Attn", "CrSel", "ExSel", "Erase EOF", "Play", "Zoom", "", "PA1", "OEM Clear", "" - }; - - const LANGID language = LOWORD(GetKeyboardLayout(0)); - - return ((language & 0xFF) == LANG_GERMAN) ? - keyboard_keys_german[keycode] : keyboard_keys_international[keycode]; -} -std::string reshade::input::key_name(const unsigned int key[4]) -{ - return (key[1] ? "Ctrl + " : std::string()) + (key[2] ? "Shift + " : std::string()) + (key[3] ? "Alt + " : std::string()) + key_name(key[0]); -} - -static inline bool is_blocking_mouse_input() -{ - const auto predicate = [](auto input_window) { - return !input_window.second.expired() && input_window.second.lock()->is_blocking_mouse_input(); - }; - return std::any_of(s_windows.cbegin(), s_windows.cend(), predicate); -} -static inline bool is_blocking_keyboard_input() -{ - const auto predicate = [](auto input_window) { - return !input_window.second.expired() && input_window.second.lock()->is_blocking_keyboard_input(); - }; - return std::any_of(s_windows.cbegin(), s_windows.cend(), predicate); -} - -HOOK_EXPORT BOOL WINAPI HookGetMessageA(LPMSG lpMsg, HWND hWnd, UINT wMsgFilterMin, UINT wMsgFilterMax) -{ - static const auto trampoline = reshade::hooks::call(HookGetMessageA); - - if (!trampoline(lpMsg, hWnd, wMsgFilterMin, wMsgFilterMax)) - return FALSE; - - assert(lpMsg != nullptr); - - if (lpMsg->hwnd != nullptr && reshade::input::handle_window_message(lpMsg)) - { - // We still want 'WM_CHAR' messages, so translate message - TranslateMessage(lpMsg); - - // Change message so it is ignored by the recipient window - lpMsg->message = WM_NULL; - } - - return TRUE; -} -HOOK_EXPORT BOOL WINAPI HookGetMessageW(LPMSG lpMsg, HWND hWnd, UINT wMsgFilterMin, UINT wMsgFilterMax) -{ - static const auto trampoline = reshade::hooks::call(HookGetMessageW); - - if (!trampoline(lpMsg, hWnd, wMsgFilterMin, wMsgFilterMax)) - return FALSE; - - assert(lpMsg != nullptr); - - if (lpMsg->hwnd != nullptr && reshade::input::handle_window_message(lpMsg)) - { - // We still want 'WM_CHAR' messages, so translate message - TranslateMessage(lpMsg); - - // Change message so it is ignored by the recipient window - lpMsg->message = WM_NULL; - } - - return TRUE; -} -HOOK_EXPORT BOOL WINAPI HookPeekMessageA(LPMSG lpMsg, HWND hWnd, UINT wMsgFilterMin, UINT wMsgFilterMax, UINT wRemoveMsg) -{ - static const auto trampoline = reshade::hooks::call(HookPeekMessageA); - - if (!trampoline(lpMsg, hWnd, wMsgFilterMin, wMsgFilterMax, wRemoveMsg)) - return FALSE; - - assert(lpMsg != nullptr); - - if (lpMsg->hwnd != nullptr && (wRemoveMsg & PM_REMOVE) != 0 && reshade::input::handle_window_message(lpMsg)) - { - // We still want 'WM_CHAR' messages, so translate message - TranslateMessage(lpMsg); - - // Change message so it is ignored by the recipient window - lpMsg->message = WM_NULL; - } - - return TRUE; -} -HOOK_EXPORT BOOL WINAPI HookPeekMessageW(LPMSG lpMsg, HWND hWnd, UINT wMsgFilterMin, UINT wMsgFilterMax, UINT wRemoveMsg) -{ - static const auto trampoline = reshade::hooks::call(HookPeekMessageW); - - if (!trampoline(lpMsg, hWnd, wMsgFilterMin, wMsgFilterMax, wRemoveMsg)) - return FALSE; - - assert(lpMsg != nullptr); - - if (lpMsg->hwnd != nullptr && (wRemoveMsg & PM_REMOVE) != 0 && reshade::input::handle_window_message(lpMsg)) - { - // We still want 'WM_CHAR' messages, so translate message - TranslateMessage(lpMsg); - - // Change message so it is ignored by the recipient window - lpMsg->message = WM_NULL; - } - - return TRUE; -} - -HOOK_EXPORT BOOL WINAPI HookPostMessageA(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam) -{ - // Do not allow mouse movement simulation while we block input - if (is_blocking_mouse_input() && Msg == WM_MOUSEMOVE) - return TRUE; - - static const auto trampoline = reshade::hooks::call(HookPostMessageA); - return trampoline(hWnd, Msg, wParam, lParam); -} -HOOK_EXPORT BOOL WINAPI HookPostMessageW(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam) -{ - if (is_blocking_mouse_input() && Msg == WM_MOUSEMOVE) - return TRUE; - - static const auto trampoline = reshade::hooks::call(HookPostMessageW); - return trampoline(hWnd, Msg, wParam, lParam); -} - -HOOK_EXPORT BOOL WINAPI HookRegisterRawInputDevices(PCRAWINPUTDEVICE pRawInputDevices, UINT uiNumDevices, UINT cbSize) -{ -#if RESHADE_VERBOSE_LOG - LOG(DEBUG) << "Redirecting RegisterRawInputDevices" << '(' << pRawInputDevices << ", " << uiNumDevices << ", " << cbSize << ')' << " ..."; -#endif - for (UINT i = 0; i < uiNumDevices; ++i) - { - const auto &device = pRawInputDevices[i]; - -#if RESHADE_VERBOSE_LOG - LOG(DEBUG) << "> Dumping device registration at index " << i << ":"; - LOG(DEBUG) << " +-----------------------------------------+-----------------------------------------+"; - LOG(DEBUG) << " | Parameter | Value |"; - LOG(DEBUG) << " +-----------------------------------------+-----------------------------------------+"; - LOG(DEBUG) << " | UsagePage | " << std::setw(39) << std::hex << device.usUsagePage << std::dec << " |"; - LOG(DEBUG) << " | Usage | " << std::setw(39) << std::hex << device.usUsage << std::dec << " |"; - LOG(DEBUG) << " | Flags | " << std::setw(39) << std::hex << device.dwFlags << std::dec << " |"; - LOG(DEBUG) << " | TargetWindow | " << std::setw(39) << device.hwndTarget << " |"; - LOG(DEBUG) << " +-----------------------------------------+-----------------------------------------+"; -#endif - - if (device.usUsagePage != 1 || device.hwndTarget == nullptr) - continue; - - reshade::input::register_window_with_raw_input(device.hwndTarget, device.usUsage == 0x06 && (device.dwFlags & RIDEV_NOLEGACY) != 0, device.usUsage == 0x02 && (device.dwFlags & RIDEV_NOLEGACY) != 0); - } - - if (!reshade::hooks::call(HookRegisterRawInputDevices)(pRawInputDevices, uiNumDevices, cbSize)) - { - LOG(WARN) << "'RegisterRawInputDevices' failed with error code " << GetLastError() << "!"; - return FALSE; - } - - return TRUE; -} - -static POINT last_cursor_position = {}; - -HOOK_EXPORT BOOL WINAPI HookSetCursorPosition(int X, int Y) -{ - last_cursor_position.x = X; - last_cursor_position.y = Y; - - if (is_blocking_mouse_input()) - return TRUE; - - static const auto trampoline = reshade::hooks::call(HookSetCursorPosition); - return trampoline(X, Y); -} -HOOK_EXPORT BOOL WINAPI HookGetCursorPosition(LPPOINT lpPoint) -{ - if (is_blocking_mouse_input()) - { - assert(lpPoint != nullptr); - - // Just return the last cursor position before we started to block mouse input, to stop it from moving - *lpPoint = last_cursor_position; - - return TRUE; - } - - static const auto trampoline = reshade::hooks::call(HookGetCursorPosition); - return trampoline(lpPoint); -} diff --git a/msvc/source/input.hpp b/msvc/source/input.hpp deleted file mode 100644 index ba0e718..0000000 --- a/msvc/source/input.hpp +++ /dev/null @@ -1,72 +0,0 @@ -/** - * Copyright (C) 2014 Patrick Mours. All rights reserved. - * License: https://github.com/crosire/reshade#license - */ - -#pragma once - -#include -#include -#include - -namespace reshade -{ - class input - { - public: - using window_handle = void *; - - explicit input(window_handle window); - - static void register_window_with_raw_input(window_handle window, bool no_legacy_keyboard, bool no_legacy_mouse); - static std::shared_ptr register_window(window_handle window); - - bool is_key_down(unsigned int keycode) const; - bool is_key_pressed(unsigned int keycode) const; - bool is_key_pressed(unsigned int keycode, bool ctrl, bool shift, bool alt) const; - bool is_key_pressed(const unsigned int key[4]) const { return is_key_pressed(key[0], key[1] != 0, key[2] != 0, key[3] != 0); } - bool is_key_released(unsigned int keycode) const; - bool is_any_key_down() const; - bool is_any_key_pressed() const; - bool is_any_key_released() const; - unsigned int last_key_pressed() const; - unsigned int last_key_released() const; - bool is_mouse_button_down(unsigned int button) const; - bool is_mouse_button_pressed(unsigned int button) const; - bool is_mouse_button_released(unsigned int button) const; - bool is_any_mouse_button_down() const; - bool is_any_mouse_button_pressed() const; - bool is_any_mouse_button_released() const; - short mouse_wheel_delta() const { return _mouse_wheel_delta; } - int mouse_movement_delta_x() const { return _mouse_position[0] - _last_mouse_position[0]; } - int mouse_movement_delta_y() const { return _mouse_position[1] - _last_mouse_position[1]; } - unsigned int mouse_position_x() const { return _mouse_position[0]; } - unsigned int mouse_position_y() const { return _mouse_position[1]; } - const std::wstring &text_input() const { return _text_input; } - - void block_mouse_input(bool enable); - void block_keyboard_input(bool enable); - - bool is_blocking_mouse_input() const { return _block_mouse; } - bool is_blocking_keyboard_input() const { return _block_keyboard; } - - auto lock() { return std::lock_guard(_mutex); } - void next_frame(); - - static std::string key_name(unsigned int keycode); - static std::string key_name(const unsigned int key[4]); - - static bool handle_window_message(const void *message_data); - - private: - std::mutex _mutex; - window_handle _window; - bool _block_mouse = false, _block_keyboard = false; - uint8_t _keys[256] = {}, _mouse_buttons[5] = {}; - short _mouse_wheel_delta = 0; - unsigned int _mouse_position[2] = {}; - unsigned int _last_mouse_position[2] = {}; - uint64_t _frame_count = 0; - std::wstring _text_input; - }; -} diff --git a/msvc/source/log.cpp b/msvc/source/log.cpp deleted file mode 100644 index 712017d..0000000 --- a/msvc/source/log.cpp +++ /dev/null @@ -1,73 +0,0 @@ -/** - * Copyright (C) 2014 Patrick Mours. All rights reserved. - * License: https://github.com/crosire/reshade#license - */ - -#include "log.hpp" -#include -#include -#include - -std::ofstream reshade::log::stream; -std::ostringstream reshade::log::linestream; -std::vector reshade::log::lines; -static std::mutex s_mutex; - -reshade::log::message::message(level level) -{ - SYSTEMTIME time; - GetLocalTime(&time); - - const char level_names[][6] = { "ERROR", "WARN ", "INFO ", "DEBUG" }; - assert(static_cast(level) - 1 < _countof(level_names)); - - // Lock the stream until the message is complete - s_mutex.lock(); - - // Start a new line - linestream.str(""); - linestream.clear(); - - stream << std::right << std::setfill('0') -#if RESHADE_VERBOSE_LOG - << std::setw(4) << time.wYear << '-' - << std::setw(2) << time.wMonth << '-' - << std::setw(2) << time.wDay << 'T' -#endif - << std::setw(2) << time.wHour << ':' - << std::setw(2) << time.wMinute << ':' - << std::setw(2) << time.wSecond << ':' - << std::setw(3) << time.wMilliseconds << ' ' - << '[' << std::setw(5) << GetCurrentThreadId() << ']' << std::setfill(' ') << " | " - << level_names[static_cast(level) - 1] << " | " << std::left; - linestream - << level_names[static_cast(level) - 1] << " | "; -} -reshade::log::message::~message() -{ - // Finish the line - stream << std::endl; - linestream << std::endl; - - lines.push_back(linestream.str()); - - // The message is finished, we can unlock the stream - s_mutex.unlock(); -} - -bool reshade::log::open(const std::filesystem::path &path) -{ - stream.open(path, std::ios::out | std::ios::trunc); - - if (!stream.is_open()) - return false; - - stream.setf(std::ios::left); - stream.setf(std::ios::showbase); - stream.flush(); - - linestream.setf(std::ios::left); - linestream.setf(std::ios::showbase); - - return true; -} diff --git a/msvc/source/log.hpp b/msvc/source/log.hpp deleted file mode 100644 index 0cc1269..0000000 --- a/msvc/source/log.hpp +++ /dev/null @@ -1,82 +0,0 @@ -/** - * Copyright (C) 2014 Patrick Mours. All rights reserved. - * License: https://github.com/crosire/reshade#license - */ - -#pragma once - -#include -#include -#include -#include -#include - -#define LOG(LEVEL) LOG_##LEVEL() -#define LOG_INFO() reshade::log::message(reshade::log::level::info) -#define LOG_ERROR() reshade::log::message(reshade::log::level::error) -#define LOG_WARN() reshade::log::message(reshade::log::level::warning) -#define LOG_DEBUG() reshade::log::message(reshade::log::level::debug) - -namespace reshade::log -{ - enum class level - { - info = 3, - error = 1, - warning = 2, - debug = 4, - }; - - extern std::ofstream stream; - extern std::ostringstream linestream; - extern std::vector lines; - - struct message - { - message(level level); - ~message(); - - template - inline message &operator<<(const T &value) - { - stream << value; - linestream << value; - return *this; - } - - template <> - inline message &operator<<(const std::wstring &message) - { - static_assert(sizeof(std::wstring::value_type) == sizeof(uint16_t), "expected 'std::wstring' to use UTF-16 encoding"); - std::string utf8_message; - utf8_message.reserve(message.size()); - utf8::unchecked::utf16to8(message.begin(), message.end(), std::back_inserter(utf8_message)); - return operator<<(utf8_message); - } - template <> - inline message &operator<<(const std::filesystem::path &path) - { - return operator<<('"' + path.u8string() + '"'); - } - - inline message &operator<<(const char *message) - { - stream << message; - linestream << message; - return *this; - } - inline message &operator<<(const wchar_t *message) - { - static_assert(sizeof(wchar_t) == sizeof(uint16_t), "expected 'wchar_t' to use UTF-16 encoding"); - std::string utf8_message; - utf8::unchecked::utf16to8(message, message + wcslen(message), std::back_inserter(utf8_message)); - return operator<<(utf8_message); - } - }; - - /// - /// Open a log file for writing. - /// - /// The path to the log file. - bool open(const std::filesystem::path &path); -} diff --git a/msvc/source/moving_average.hpp b/msvc/source/moving_average.hpp deleted file mode 100644 index aa48821..0000000 --- a/msvc/source/moving_average.hpp +++ /dev/null @@ -1,38 +0,0 @@ -/** - * Copyright (C) 2014 Patrick Mours. All rights reserved. - * License: https://github.com/crosire/reshade#license - */ - -#pragma once - -template -class moving_average -{ -public: - moving_average() : _index(0), _average(0), _tick_sum(0), _tick_list() { } - - inline operator T() const { return _average; } - - void clear() - { - _index = 0; - _average = 0; - _tick_sum = 0; - - for (size_t i = 0; i < SAMPLES; i++) - { - _tick_list[i] = 0; - } - } - void append(T value) - { - _tick_sum -= _tick_list[_index]; - _tick_sum += _tick_list[_index] = value; - _index = ++_index % SAMPLES; - _average = _tick_sum / SAMPLES; - } - -private: - size_t _index; - T _average, _tick_sum, _tick_list[SAMPLES]; -}; diff --git a/msvc/source/opengl/opengl.hpp b/msvc/source/opengl/opengl.hpp deleted file mode 100644 index ae1ad63..0000000 --- a/msvc/source/opengl/opengl.hpp +++ /dev/null @@ -1,1367 +0,0 @@ -/** - * Copyright (C) 2014 Patrick Mours. All rights reserved. - * License: https://github.com/crosire/reshade#license - */ - -#include - -#ifndef NDEBUG - -#include - -#ifndef GLCHECK -#define GLCHECK(call) \ - { \ - glGetError(); \ - call; \ - GLenum __e = glGetError(); \ - if (__e != GL_NO_ERROR) { \ - char __m[1024]; \ - sprintf_s(__m, "OpenGL error %x in %s at line %d: %s", __e, __FILE__, __LINE__, #call); \ - MessageBoxA(nullptr, __m, 0, MB_ICONERROR); \ - } \ - } -#endif - -#undef glActiveShaderProgram -#define glActiveShaderProgram(...) GLCHECK(gl3wProcs.gl.ActiveShaderProgram(__VA_ARGS__)) -#undef glActiveTexture -#define glActiveTexture(...) GLCHECK(gl3wProcs.gl.ActiveTexture(__VA_ARGS__)) -#undef glAttachShader -#define glAttachShader(...) GLCHECK(gl3wProcs.gl.AttachShader(__VA_ARGS__)) -#undef glBeginConditionalRender -#define glBeginConditionalRender(...) GLCHECK(gl3wProcs.gl.BeginConditionalRender(__VA_ARGS__)) -#undef glBeginQuery -#define glBeginQuery(...) GLCHECK(gl3wProcs.gl.BeginQuery(__VA_ARGS__)) -#undef glBeginQueryIndexed -#define glBeginQueryIndexed(...) GLCHECK(gl3wProcs.gl.BeginQueryIndexed(__VA_ARGS__)) -#undef glBeginTransformFeedback -#define glBeginTransformFeedback(...) GLCHECK(gl3wProcs.gl.BeginTransformFeedback(__VA_ARGS__)) -#undef glBindAttribLocation -#define glBindAttribLocation(...) GLCHECK(gl3wProcs.gl.BindAttribLocation(__VA_ARGS__)) -#undef glBindBuffer -#define glBindBuffer(...) GLCHECK(gl3wProcs.gl.BindBuffer(__VA_ARGS__)) -#undef glBindBufferBase -#define glBindBufferBase(...) GLCHECK(gl3wProcs.gl.BindBufferBase(__VA_ARGS__)) -#undef glBindBufferRange -#define glBindBufferRange(...) GLCHECK(gl3wProcs.gl.BindBufferRange(__VA_ARGS__)) -#undef glBindBuffersBase -#define glBindBuffersBase(...) GLCHECK(gl3wProcs.gl.BindBuffersBase(__VA_ARGS__)) -#undef glBindBuffersRange -#define glBindBuffersRange(...) GLCHECK(gl3wProcs.gl.BindBuffersRange(__VA_ARGS__)) -#undef glBindFragDataLocation -#define glBindFragDataLocation(...) GLCHECK(gl3wProcs.gl.BindFragDataLocation(__VA_ARGS__)) -#undef glBindFragDataLocationIndexed -#define glBindFragDataLocationIndexed(...) GLCHECK(gl3wProcs.gl.BindFragDataLocationIndexed(__VA_ARGS__)) -#undef glBindFramebuffer -#define glBindFramebuffer(...) GLCHECK(gl3wProcs.gl.BindFramebuffer(__VA_ARGS__)) -#undef glBindImageTexture -#define glBindImageTexture(...) GLCHECK(gl3wProcs.gl.BindImageTexture(__VA_ARGS__)) -#undef glBindImageTextures -#define glBindImageTextures(...) GLCHECK(gl3wProcs.gl.BindImageTextures(__VA_ARGS__)) -#undef glBindProgramPipeline -#define glBindProgramPipeline(...) GLCHECK(gl3wProcs.gl.BindProgramPipeline(__VA_ARGS__)) -#undef glBindRenderbuffer -#define glBindRenderbuffer(...) GLCHECK(gl3wProcs.gl.BindRenderbuffer(__VA_ARGS__)) -#undef glBindSampler -#define glBindSampler(...) GLCHECK(gl3wProcs.gl.BindSampler(__VA_ARGS__)) -#undef glBindSamplers -#define glBindSamplers(...) GLCHECK(gl3wProcs.gl.BindSamplers(__VA_ARGS__)) -#undef glBindTexture -#define glBindTexture(...) GLCHECK(gl3wProcs.gl.BindTexture(__VA_ARGS__)) -#undef glBindTextureUnit -#define glBindTextureUnit(...) GLCHECK(gl3wProcs.gl.BindTextureUnit(__VA_ARGS__)) -#undef glBindTextures -#define glBindTextures(...) GLCHECK(gl3wProcs.gl.BindTextures(__VA_ARGS__)) -#undef glBindTransformFeedback -#define glBindTransformFeedback(...) GLCHECK(gl3wProcs.gl.BindTransformFeedback(__VA_ARGS__)) -#undef glBindVertexArray -#define glBindVertexArray(...) GLCHECK(gl3wProcs.gl.BindVertexArray(__VA_ARGS__)) -#undef glBindVertexBuffer -#define glBindVertexBuffer(...) GLCHECK(gl3wProcs.gl.BindVertexBuffer(__VA_ARGS__)) -#undef glBindVertexBuffers -#define glBindVertexBuffers(...) GLCHECK(gl3wProcs.gl.BindVertexBuffers(__VA_ARGS__)) -#undef glBlendColor -#define glBlendColor(...) GLCHECK(gl3wProcs.gl.BlendColor(__VA_ARGS__)) -#undef glBlendEquation -#define glBlendEquation(...) GLCHECK(gl3wProcs.gl.BlendEquation(__VA_ARGS__)) -#undef glBlendEquationSeparate -#define glBlendEquationSeparate(...) GLCHECK(gl3wProcs.gl.BlendEquationSeparate(__VA_ARGS__)) -#undef glBlendEquationSeparatei -#define glBlendEquationSeparatei(...) GLCHECK(gl3wProcs.gl.BlendEquationSeparatei(__VA_ARGS__)) -#undef glBlendEquationSeparateiARB -#define glBlendEquationSeparateiARB(...) GLCHECK(gl3wProcs.gl.BlendEquationSeparateiARB(__VA_ARGS__)) -#undef glBlendEquationi -#define glBlendEquationi(...) GLCHECK(gl3wProcs.gl.BlendEquationi(__VA_ARGS__)) -#undef glBlendEquationiARB -#define glBlendEquationiARB(...) GLCHECK(gl3wProcs.gl.BlendEquationiARB(__VA_ARGS__)) -#undef glBlendFunc -#define glBlendFunc(...) GLCHECK(gl3wProcs.gl.BlendFunc(__VA_ARGS__)) -#undef glBlendFuncSeparate -#define glBlendFuncSeparate(...) GLCHECK(gl3wProcs.gl.BlendFuncSeparate(__VA_ARGS__)) -#undef glBlendFuncSeparatei -#define glBlendFuncSeparatei(...) GLCHECK(gl3wProcs.gl.BlendFuncSeparatei(__VA_ARGS__)) -#undef glBlendFuncSeparateiARB -#define glBlendFuncSeparateiARB(...) GLCHECK(gl3wProcs.gl.BlendFuncSeparateiARB(__VA_ARGS__)) -#undef glBlendFunci -#define glBlendFunci(...) GLCHECK(gl3wProcs.gl.BlendFunci(__VA_ARGS__)) -#undef glBlendFunciARB -#define glBlendFunciARB(...) GLCHECK(gl3wProcs.gl.BlendFunciARB(__VA_ARGS__)) -#undef glBlitFramebuffer -#define glBlitFramebuffer(...) GLCHECK(gl3wProcs.gl.BlitFramebuffer(__VA_ARGS__)) -#undef glBlitNamedFramebuffer -#define glBlitNamedFramebuffer(...) GLCHECK(gl3wProcs.gl.BlitNamedFramebuffer(__VA_ARGS__)) -#undef glBufferData -#define glBufferData(...) GLCHECK(gl3wProcs.gl.BufferData(__VA_ARGS__)) -#undef glBufferPageCommitmentARB -#define glBufferPageCommitmentARB(...) GLCHECK(gl3wProcs.gl.BufferPageCommitmentARB(__VA_ARGS__)) -#undef glBufferStorage -#define glBufferStorage(...) GLCHECK(gl3wProcs.gl.BufferStorage(__VA_ARGS__)) -#undef glBufferSubData -#define glBufferSubData(...) GLCHECK(gl3wProcs.gl.BufferSubData(__VA_ARGS__)) -#undef glClampColor -#define glClampColor(...) GLCHECK(gl3wProcs.gl.ClampColor(__VA_ARGS__)) -#undef glClear -#define glClear(...) GLCHECK(gl3wProcs.gl.Clear(__VA_ARGS__)) -#undef glClearBufferData -#define glClearBufferData(...) GLCHECK(gl3wProcs.gl.ClearBufferData(__VA_ARGS__)) -#undef glClearBufferSubData -#define glClearBufferSubData(...) GLCHECK(gl3wProcs.gl.ClearBufferSubData(__VA_ARGS__)) -#undef glClearBufferfi -#define glClearBufferfi(...) GLCHECK(gl3wProcs.gl.ClearBufferfi(__VA_ARGS__)) -#undef glClearBufferfv -#define glClearBufferfv(...) GLCHECK(gl3wProcs.gl.ClearBufferfv(__VA_ARGS__)) -#undef glClearBufferiv -#define glClearBufferiv(...) GLCHECK(gl3wProcs.gl.ClearBufferiv(__VA_ARGS__)) -#undef glClearBufferuiv -#define glClearBufferuiv(...) GLCHECK(gl3wProcs.gl.ClearBufferuiv(__VA_ARGS__)) -#undef glClearColor -#define glClearColor(...) GLCHECK(gl3wProcs.gl.ClearColor(__VA_ARGS__)) -#undef glClearDepth -#define glClearDepth(...) GLCHECK(gl3wProcs.gl.ClearDepth(__VA_ARGS__)) -#undef glClearDepthf -#define glClearDepthf(...) GLCHECK(gl3wProcs.gl.ClearDepthf(__VA_ARGS__)) -#undef glClearNamedBufferData -#define glClearNamedBufferData(...) GLCHECK(gl3wProcs.gl.ClearNamedBufferData(__VA_ARGS__)) -#undef glClearNamedBufferSubData -#define glClearNamedBufferSubData(...) GLCHECK(gl3wProcs.gl.ClearNamedBufferSubData(__VA_ARGS__)) -#undef glClearNamedFramebufferfi -#define glClearNamedFramebufferfi(...) GLCHECK(gl3wProcs.gl.ClearNamedFramebufferfi(__VA_ARGS__)) -#undef glClearNamedFramebufferfv -#define glClearNamedFramebufferfv(...) GLCHECK(gl3wProcs.gl.ClearNamedFramebufferfv(__VA_ARGS__)) -#undef glClearNamedFramebufferiv -#define glClearNamedFramebufferiv(...) GLCHECK(gl3wProcs.gl.ClearNamedFramebufferiv(__VA_ARGS__)) -#undef glClearNamedFramebufferuiv -#define glClearNamedFramebufferuiv(...) GLCHECK(gl3wProcs.gl.ClearNamedFramebufferuiv(__VA_ARGS__)) -#undef glClearStencil -#define glClearStencil(...) GLCHECK(gl3wProcs.gl.ClearStencil(__VA_ARGS__)) -#undef glClearTexImage -#define glClearTexImage(...) GLCHECK(gl3wProcs.gl.ClearTexImage(__VA_ARGS__)) -#undef glClearTexSubImage -#define glClearTexSubImage(...) GLCHECK(gl3wProcs.gl.ClearTexSubImage(__VA_ARGS__)) -#undef glClientWaitSync -#define glClientWaitSync(...) GLCHECK(gl3wProcs.gl.ClientWaitSync(__VA_ARGS__)) -#undef glClipControl -#define glClipControl(...) GLCHECK(gl3wProcs.gl.ClipControl(__VA_ARGS__)) -#undef glColorMask -#define glColorMask(...) GLCHECK(gl3wProcs.gl.ColorMask(__VA_ARGS__)) -#undef glColorMaski -#define glColorMaski(...) GLCHECK(gl3wProcs.gl.ColorMaski(__VA_ARGS__)) -#undef glCompileShader -#define glCompileShader(...) GLCHECK(gl3wProcs.gl.CompileShader(__VA_ARGS__)) -#undef glCompileShaderIncludeARB -#define glCompileShaderIncludeARB(...) GLCHECK(gl3wProcs.gl.CompileShaderIncludeARB(__VA_ARGS__)) -#undef glCompressedTexImage1D -#define glCompressedTexImage1D(...) GLCHECK(gl3wProcs.gl.CompressedTexImage1D(__VA_ARGS__)) -#undef glCompressedTexImage2D -#define glCompressedTexImage2D(...) GLCHECK(gl3wProcs.gl.CompressedTexImage2D(__VA_ARGS__)) -#undef glCompressedTexImage3D -#define glCompressedTexImage3D(...) GLCHECK(gl3wProcs.gl.CompressedTexImage3D(__VA_ARGS__)) -#undef glCompressedTexSubImage1D -#define glCompressedTexSubImage1D(...) GLCHECK(gl3wProcs.gl.CompressedTexSubImage1D(__VA_ARGS__)) -#undef glCompressedTexSubImage2D -#define glCompressedTexSubImage2D(...) GLCHECK(gl3wProcs.gl.CompressedTexSubImage2D(__VA_ARGS__)) -#undef glCompressedTexSubImage3D -#define glCompressedTexSubImage3D(...) GLCHECK(gl3wProcs.gl.CompressedTexSubImage3D(__VA_ARGS__)) -#undef glCompressedTextureSubImage1D -#define glCompressedTextureSubImage1D(...) GLCHECK(gl3wProcs.gl.CompressedTextureSubImage1D(__VA_ARGS__)) -#undef glCompressedTextureSubImage2D -#define glCompressedTextureSubImage2D(...) GLCHECK(gl3wProcs.gl.CompressedTextureSubImage2D(__VA_ARGS__)) -#undef glCompressedTextureSubImage3D -#define glCompressedTextureSubImage3D(...) GLCHECK(gl3wProcs.gl.CompressedTextureSubImage3D(__VA_ARGS__)) -#undef glCopyBufferSubData -#define glCopyBufferSubData(...) GLCHECK(gl3wProcs.gl.CopyBufferSubData(__VA_ARGS__)) -#undef glCopyImageSubData -#define glCopyImageSubData(...) GLCHECK(gl3wProcs.gl.CopyImageSubData(__VA_ARGS__)) -#undef glCopyNamedBufferSubData -#define glCopyNamedBufferSubData(...) GLCHECK(gl3wProcs.gl.CopyNamedBufferSubData(__VA_ARGS__)) -#undef glCopyTexImage1D -#define glCopyTexImage1D(...) GLCHECK(gl3wProcs.gl.CopyTexImage1D(__VA_ARGS__)) -#undef glCopyTexImage2D -#define glCopyTexImage2D(...) GLCHECK(gl3wProcs.gl.CopyTexImage2D(__VA_ARGS__)) -#undef glCopyTexSubImage1D -#define glCopyTexSubImage1D(...) GLCHECK(gl3wProcs.gl.CopyTexSubImage1D(__VA_ARGS__)) -#undef glCopyTexSubImage2D -#define glCopyTexSubImage2D(...) GLCHECK(gl3wProcs.gl.CopyTexSubImage2D(__VA_ARGS__)) -#undef glCopyTexSubImage3D -#define glCopyTexSubImage3D(...) GLCHECK(gl3wProcs.gl.CopyTexSubImage3D(__VA_ARGS__)) -#undef glCopyTextureSubImage1D -#define glCopyTextureSubImage1D(...) GLCHECK(gl3wProcs.gl.CopyTextureSubImage1D(__VA_ARGS__)) -#undef glCopyTextureSubImage2D -#define glCopyTextureSubImage2D(...) GLCHECK(gl3wProcs.gl.CopyTextureSubImage2D(__VA_ARGS__)) -#undef glCopyTextureSubImage3D -#define glCopyTextureSubImage3D(...) GLCHECK(gl3wProcs.gl.CopyTextureSubImage3D(__VA_ARGS__)) -#undef glCreateBuffers -#define glCreateBuffers(...) GLCHECK(gl3wProcs.gl.CreateBuffers(__VA_ARGS__)) -#undef glCreateFramebuffers -#define glCreateFramebuffers(...) GLCHECK(gl3wProcs.gl.CreateFramebuffers(__VA_ARGS__)) -#undef glCreateProgramPipelines -#define glCreateProgramPipelines(...) GLCHECK(gl3wProcs.gl.CreateProgramPipelines(__VA_ARGS__)) -#undef glCreateQueries -#define glCreateQueries(...) GLCHECK(gl3wProcs.gl.CreateQueries(__VA_ARGS__)) -#undef glCreateRenderbuffers -#define glCreateRenderbuffers(...) GLCHECK(gl3wProcs.gl.CreateRenderbuffers(__VA_ARGS__)) -#undef glCreateSamplers -#define glCreateSamplers(...) GLCHECK(gl3wProcs.gl.CreateSamplers(__VA_ARGS__)) -#undef glCreateShaderProgramv -#define glCreateShaderProgramv(...) GLCHECK(gl3wProcs.gl.CreateShaderProgramv(__VA_ARGS__)) -#undef glCreateSyncFromCLeventARB -#define glCreateSyncFromCLeventARB(...) GLCHECK(gl3wProcs.gl.CreateSyncFromCLeventARB(__VA_ARGS__)) -#undef glCreateTextures -#define glCreateTextures(...) GLCHECK(gl3wProcs.gl.CreateTextures(__VA_ARGS__)) -#undef glCreateTransformFeedbacks -#define glCreateTransformFeedbacks(...) GLCHECK(gl3wProcs.gl.CreateTransformFeedbacks(__VA_ARGS__)) -#undef glCreateVertexArrays -#define glCreateVertexArrays(...) GLCHECK(gl3wProcs.gl.CreateVertexArrays(__VA_ARGS__)) -#undef glCullFace -#define glCullFace(...) GLCHECK(gl3wProcs.gl.CullFace(__VA_ARGS__)) -#undef glDebugMessageCallback -#define glDebugMessageCallback(...) GLCHECK(gl3wProcs.gl.DebugMessageCallback(__VA_ARGS__)) -#undef glDebugMessageCallbackARB -#define glDebugMessageCallbackARB(...) GLCHECK(gl3wProcs.gl.DebugMessageCallbackARB(__VA_ARGS__)) -#undef glDebugMessageControl -#define glDebugMessageControl(...) GLCHECK(gl3wProcs.gl.DebugMessageControl(__VA_ARGS__)) -#undef glDebugMessageControlARB -#define glDebugMessageControlARB(...) GLCHECK(gl3wProcs.gl.DebugMessageControlARB(__VA_ARGS__)) -#undef glDebugMessageInsert -#define glDebugMessageInsert(...) GLCHECK(gl3wProcs.gl.DebugMessageInsert(__VA_ARGS__)) -#undef glDebugMessageInsertARB -#define glDebugMessageInsertARB(...) GLCHECK(gl3wProcs.gl.DebugMessageInsertARB(__VA_ARGS__)) -#undef glDeleteBuffers -#define glDeleteBuffers(...) GLCHECK(gl3wProcs.gl.DeleteBuffers(__VA_ARGS__)) -#undef glDeleteFramebuffers -#define glDeleteFramebuffers(...) GLCHECK(gl3wProcs.gl.DeleteFramebuffers(__VA_ARGS__)) -#undef glDeleteNamedStringARB -#define glDeleteNamedStringARB(...) GLCHECK(gl3wProcs.gl.DeleteNamedStringARB(__VA_ARGS__)) -#undef glDeleteProgram -#define glDeleteProgram(...) GLCHECK(gl3wProcs.gl.DeleteProgram(__VA_ARGS__)) -#undef glDeleteProgramPipelines -#define glDeleteProgramPipelines(...) GLCHECK(gl3wProcs.gl.DeleteProgramPipelines(__VA_ARGS__)) -#undef glDeleteQueries -#define glDeleteQueries(...) GLCHECK(gl3wProcs.gl.DeleteQueries(__VA_ARGS__)) -#undef glDeleteRenderbuffers -#define glDeleteRenderbuffers(...) GLCHECK(gl3wProcs.gl.DeleteRenderbuffers(__VA_ARGS__)) -#undef glDeleteSamplers -#define glDeleteSamplers(...) GLCHECK(gl3wProcs.gl.DeleteSamplers(__VA_ARGS__)) -#undef glDeleteShader -#define glDeleteShader(...) GLCHECK(gl3wProcs.gl.DeleteShader(__VA_ARGS__)) -#undef glDeleteSync -#define glDeleteSync(...) GLCHECK(gl3wProcs.gl.DeleteSync(__VA_ARGS__)) -#undef glDeleteTextures -#define glDeleteTextures(...) GLCHECK(gl3wProcs.gl.DeleteTextures(__VA_ARGS__)) -#undef glDeleteTransformFeedbacks -#define glDeleteTransformFeedbacks(...) GLCHECK(gl3wProcs.gl.DeleteTransformFeedbacks(__VA_ARGS__)) -#undef glDeleteVertexArrays -#define glDeleteVertexArrays(...) GLCHECK(gl3wProcs.gl.DeleteVertexArrays(__VA_ARGS__)) -#undef glDepthFunc -#define glDepthFunc(...) GLCHECK(gl3wProcs.gl.DepthFunc(__VA_ARGS__)) -#undef glDepthMask -#define glDepthMask(...) GLCHECK(gl3wProcs.gl.DepthMask(__VA_ARGS__)) -#undef glDepthRange -#define glDepthRange(...) GLCHECK(gl3wProcs.gl.DepthRange(__VA_ARGS__)) -#undef glDepthRangeArrayv -#define glDepthRangeArrayv(...) GLCHECK(gl3wProcs.gl.DepthRangeArrayv(__VA_ARGS__)) -#undef glDepthRangeIndexed -#define glDepthRangeIndexed(...) GLCHECK(gl3wProcs.gl.DepthRangeIndexed(__VA_ARGS__)) -#undef glDepthRangef -#define glDepthRangef(...) GLCHECK(gl3wProcs.gl.DepthRangef(__VA_ARGS__)) -#undef glDetachShader -#define glDetachShader(...) GLCHECK(gl3wProcs.gl.DetachShader(__VA_ARGS__)) -#undef glDisable -#define glDisable(...) GLCHECK(gl3wProcs.gl.Disable(__VA_ARGS__)) -#undef glDisableVertexArrayAttrib -#define glDisableVertexArrayAttrib(...) GLCHECK(gl3wProcs.gl.DisableVertexArrayAttrib(__VA_ARGS__)) -#undef glDisableVertexAttribArray -#define glDisableVertexAttribArray(...) GLCHECK(gl3wProcs.gl.DisableVertexAttribArray(__VA_ARGS__)) -#undef glDisablei -#define glDisablei(...) GLCHECK(gl3wProcs.gl.Disablei(__VA_ARGS__)) -#undef glDispatchCompute -#define glDispatchCompute(...) GLCHECK(gl3wProcs.gl.DispatchCompute(__VA_ARGS__)) -#undef glDispatchComputeGroupSizeARB -#define glDispatchComputeGroupSizeARB(...) GLCHECK(gl3wProcs.gl.DispatchComputeGroupSizeARB(__VA_ARGS__)) -#undef glDispatchComputeIndirect -#define glDispatchComputeIndirect(...) GLCHECK(gl3wProcs.gl.DispatchComputeIndirect(__VA_ARGS__)) -#undef glDrawArrays -#define glDrawArrays(...) GLCHECK(gl3wProcs.gl.DrawArrays(__VA_ARGS__)) -#undef glDrawArraysIndirect -#define glDrawArraysIndirect(...) GLCHECK(gl3wProcs.gl.DrawArraysIndirect(__VA_ARGS__)) -#undef glDrawArraysInstanced -#define glDrawArraysInstanced(...) GLCHECK(gl3wProcs.gl.DrawArraysInstanced(__VA_ARGS__)) -#undef glDrawArraysInstancedBaseInstance -#define glDrawArraysInstancedBaseInstance(...) GLCHECK(gl3wProcs.gl.DrawArraysInstancedBaseInstance(__VA_ARGS__)) -#undef glDrawBuffer -#define glDrawBuffer(...) GLCHECK(gl3wProcs.gl.DrawBuffer(__VA_ARGS__)) -#undef glDrawBuffers -#define glDrawBuffers(...) GLCHECK(gl3wProcs.gl.DrawBuffers(__VA_ARGS__)) -#undef glDrawElements -#define glDrawElements(...) GLCHECK(gl3wProcs.gl.DrawElements(__VA_ARGS__)) -#undef glDrawElementsBaseVertex -#define glDrawElementsBaseVertex(...) GLCHECK(gl3wProcs.gl.DrawElementsBaseVertex(__VA_ARGS__)) -#undef glDrawElementsIndirect -#define glDrawElementsIndirect(...) GLCHECK(gl3wProcs.gl.DrawElementsIndirect(__VA_ARGS__)) -#undef glDrawElementsInstanced -#define glDrawElementsInstanced(...) GLCHECK(gl3wProcs.gl.DrawElementsInstanced(__VA_ARGS__)) -#undef glDrawElementsInstancedBaseInstance -#define glDrawElementsInstancedBaseInstance(...) GLCHECK(gl3wProcs.gl.DrawElementsInstancedBaseInstance(__VA_ARGS__)) -#undef glDrawElementsInstancedBaseVertex -#define glDrawElementsInstancedBaseVertex(...) GLCHECK(gl3wProcs.gl.DrawElementsInstancedBaseVertex(__VA_ARGS__)) -#undef glDrawElementsInstancedBaseVertexBaseInstance -#define glDrawElementsInstancedBaseVertexBaseInstance(...) GLCHECK(gl3wProcs.gl.DrawElementsInstancedBaseVertexBaseInstance(__VA_ARGS__)) -#undef glDrawRangeElements -#define glDrawRangeElements(...) GLCHECK(gl3wProcs.gl.DrawRangeElements(__VA_ARGS__)) -#undef glDrawRangeElementsBaseVertex -#define glDrawRangeElementsBaseVertex(...) GLCHECK(gl3wProcs.gl.DrawRangeElementsBaseVertex(__VA_ARGS__)) -#undef glDrawTransformFeedback -#define glDrawTransformFeedback(...) GLCHECK(gl3wProcs.gl.DrawTransformFeedback(__VA_ARGS__)) -#undef glDrawTransformFeedbackInstanced -#define glDrawTransformFeedbackInstanced(...) GLCHECK(gl3wProcs.gl.DrawTransformFeedbackInstanced(__VA_ARGS__)) -#undef glDrawTransformFeedbackStream -#define glDrawTransformFeedbackStream(...) GLCHECK(gl3wProcs.gl.DrawTransformFeedbackStream(__VA_ARGS__)) -#undef glDrawTransformFeedbackStreamInstanced -#define glDrawTransformFeedbackStreamInstanced(...) GLCHECK(gl3wProcs.gl.DrawTransformFeedbackStreamInstanced(__VA_ARGS__)) -#undef glEnable -#define glEnable(...) GLCHECK(gl3wProcs.gl.Enable(__VA_ARGS__)) -#undef glEnableVertexArrayAttrib -#define glEnableVertexArrayAttrib(...) GLCHECK(gl3wProcs.gl.EnableVertexArrayAttrib(__VA_ARGS__)) -#undef glEnableVertexAttribArray -#define glEnableVertexAttribArray(...) GLCHECK(gl3wProcs.gl.EnableVertexAttribArray(__VA_ARGS__)) -#undef glEnablei -#define glEnablei(...) GLCHECK(gl3wProcs.gl.Enablei(__VA_ARGS__)) -#undef glEndConditionalRender -#define glEndConditionalRender(...) GLCHECK(gl3wProcs.gl.EndConditionalRender(__VA_ARGS__)) -#undef glEndQuery -#define glEndQuery(...) GLCHECK(gl3wProcs.gl.EndQuery(__VA_ARGS__)) -#undef glEndQueryIndexed -#define glEndQueryIndexed(...) GLCHECK(gl3wProcs.gl.EndQueryIndexed(__VA_ARGS__)) -#undef glEndTransformFeedback -#define glEndTransformFeedback(...) GLCHECK(gl3wProcs.gl.EndTransformFeedback(__VA_ARGS__)) -#undef glFenceSync -#define glFenceSync(...) GLCHECK(gl3wProcs.gl.FenceSync(__VA_ARGS__)) -#undef glFinish -#define glFinish(...) GLCHECK(gl3wProcs.gl.Finish(__VA_ARGS__)) -#undef glFlush -#define glFlush(...) GLCHECK(gl3wProcs.gl.Flush(__VA_ARGS__)) -#undef glFlushMappedBufferRange -#define glFlushMappedBufferRange(...) GLCHECK(gl3wProcs.gl.FlushMappedBufferRange(__VA_ARGS__)) -#undef glFlushMappedNamedBufferRange -#define glFlushMappedNamedBufferRange(...) GLCHECK(gl3wProcs.gl.FlushMappedNamedBufferRange(__VA_ARGS__)) -#undef glFramebufferParameteri -#define glFramebufferParameteri(...) GLCHECK(gl3wProcs.gl.FramebufferParameteri(__VA_ARGS__)) -#undef glFramebufferRenderbuffer -#define glFramebufferRenderbuffer(...) GLCHECK(gl3wProcs.gl.FramebufferRenderbuffer(__VA_ARGS__)) -#undef glFramebufferTexture -#define glFramebufferTexture(...) GLCHECK(gl3wProcs.gl.FramebufferTexture(__VA_ARGS__)) -#undef glFramebufferTexture1D -#define glFramebufferTexture1D(...) GLCHECK(gl3wProcs.gl.FramebufferTexture1D(__VA_ARGS__)) -#undef glFramebufferTexture2D -#define glFramebufferTexture2D(...) GLCHECK(gl3wProcs.gl.FramebufferTexture2D(__VA_ARGS__)) -#undef glFramebufferTexture3D -#define glFramebufferTexture3D(...) GLCHECK(gl3wProcs.gl.FramebufferTexture3D(__VA_ARGS__)) -#undef glFramebufferTextureLayer -#define glFramebufferTextureLayer(...) GLCHECK(gl3wProcs.gl.FramebufferTextureLayer(__VA_ARGS__)) -#undef glFrontFace -#define glFrontFace(...) GLCHECK(gl3wProcs.gl.FrontFace(__VA_ARGS__)) -#undef glGenBuffers -#define glGenBuffers(...) GLCHECK(gl3wProcs.gl.GenBuffers(__VA_ARGS__)) -#undef glGenFramebuffers -#define glGenFramebuffers(...) GLCHECK(gl3wProcs.gl.GenFramebuffers(__VA_ARGS__)) -#undef glGenProgramPipelines -#define glGenProgramPipelines(...) GLCHECK(gl3wProcs.gl.GenProgramPipelines(__VA_ARGS__)) -#undef glGenQueries -#define glGenQueries(...) GLCHECK(gl3wProcs.gl.GenQueries(__VA_ARGS__)) -#undef glGenRenderbuffers -#define glGenRenderbuffers(...) GLCHECK(gl3wProcs.gl.GenRenderbuffers(__VA_ARGS__)) -#undef glGenSamplers -#define glGenSamplers(...) GLCHECK(gl3wProcs.gl.GenSamplers(__VA_ARGS__)) -#undef glGenTextures -#define glGenTextures(...) GLCHECK(gl3wProcs.gl.GenTextures(__VA_ARGS__)) -#undef glGenTransformFeedbacks -#define glGenTransformFeedbacks(...) GLCHECK(gl3wProcs.gl.GenTransformFeedbacks(__VA_ARGS__)) -#undef glGenVertexArrays -#define glGenVertexArrays(...) GLCHECK(gl3wProcs.gl.GenVertexArrays(__VA_ARGS__)) -#undef glGenerateMipmap -#define glGenerateMipmap(...) GLCHECK(gl3wProcs.gl.GenerateMipmap(__VA_ARGS__)) -#undef glGenerateTextureMipmap -#define glGenerateTextureMipmap(...) GLCHECK(gl3wProcs.gl.GenerateTextureMipmap(__VA_ARGS__)) -#undef glGetActiveAtomicCounterBufferiv -#define glGetActiveAtomicCounterBufferiv(...) GLCHECK(gl3wProcs.gl.GetActiveAtomicCounterBufferiv(__VA_ARGS__)) -#undef glGetActiveAttrib -#define glGetActiveAttrib(...) GLCHECK(gl3wProcs.gl.GetActiveAttrib(__VA_ARGS__)) -#undef glGetActiveSubroutineName -#define glGetActiveSubroutineName(...) GLCHECK(gl3wProcs.gl.GetActiveSubroutineName(__VA_ARGS__)) -#undef glGetActiveSubroutineUniformName -#define glGetActiveSubroutineUniformName(...) GLCHECK(gl3wProcs.gl.GetActiveSubroutineUniformName(__VA_ARGS__)) -#undef glGetActiveSubroutineUniformiv -#define glGetActiveSubroutineUniformiv(...) GLCHECK(gl3wProcs.gl.GetActiveSubroutineUniformiv(__VA_ARGS__)) -#undef glGetActiveUniform -#define glGetActiveUniform(...) GLCHECK(gl3wProcs.gl.GetActiveUniform(__VA_ARGS__)) -#undef glGetActiveUniformBlockName -#define glGetActiveUniformBlockName(...) GLCHECK(gl3wProcs.gl.GetActiveUniformBlockName(__VA_ARGS__)) -#undef glGetActiveUniformBlockiv -#define glGetActiveUniformBlockiv(...) GLCHECK(gl3wProcs.gl.GetActiveUniformBlockiv(__VA_ARGS__)) -#undef glGetActiveUniformName -#define glGetActiveUniformName(...) GLCHECK(gl3wProcs.gl.GetActiveUniformName(__VA_ARGS__)) -#undef glGetActiveUniformsiv -#define glGetActiveUniformsiv(...) GLCHECK(gl3wProcs.gl.GetActiveUniformsiv(__VA_ARGS__)) -#undef glGetAttachedShaders -#define glGetAttachedShaders(...) GLCHECK(gl3wProcs.gl.GetAttachedShaders(__VA_ARGS__)) -#undef glGetBooleani_v -#define glGetBooleani_v(...) GLCHECK(gl3wProcs.gl.GetBooleani_v(__VA_ARGS__)) -#undef glGetBooleanv -#define glGetBooleanv(...) GLCHECK(gl3wProcs.gl.GetBooleanv(__VA_ARGS__)) -#undef glGetBufferParameteri64v -#define glGetBufferParameteri64v(...) GLCHECK(gl3wProcs.gl.GetBufferParameteri64v(__VA_ARGS__)) -#undef glGetBufferParameteriv -#define glGetBufferParameteriv(...) GLCHECK(gl3wProcs.gl.GetBufferParameteriv(__VA_ARGS__)) -#undef glGetBufferPointerv -#define glGetBufferPointerv(...) GLCHECK(gl3wProcs.gl.GetBufferPointerv(__VA_ARGS__)) -#undef glGetBufferSubData -#define glGetBufferSubData(...) GLCHECK(gl3wProcs.gl.GetBufferSubData(__VA_ARGS__)) -#undef glGetCompressedTexImage -#define glGetCompressedTexImage(...) GLCHECK(gl3wProcs.gl.GetCompressedTexImage(__VA_ARGS__)) -#undef glGetCompressedTextureImage -#define glGetCompressedTextureImage(...) GLCHECK(gl3wProcs.gl.GetCompressedTextureImage(__VA_ARGS__)) -#undef glGetCompressedTextureSubImage -#define glGetCompressedTextureSubImage(...) GLCHECK(gl3wProcs.gl.GetCompressedTextureSubImage(__VA_ARGS__)) -#undef glGetDebugMessageLog -#define glGetDebugMessageLog(...) GLCHECK(gl3wProcs.gl.GetDebugMessageLog(__VA_ARGS__)) -#undef glGetDebugMessageLogARB -#define glGetDebugMessageLogARB(...) GLCHECK(gl3wProcs.gl.GetDebugMessageLogARB(__VA_ARGS__)) -#undef glGetDoublei_v -#define glGetDoublei_v(...) GLCHECK(gl3wProcs.gl.GetDoublei_v(__VA_ARGS__)) -#undef glGetDoublev -#define glGetDoublev(...) GLCHECK(gl3wProcs.gl.GetDoublev(__VA_ARGS__)) -#undef glGetFloati_v -#define glGetFloati_v(...) GLCHECK(gl3wProcs.gl.GetFloati_v(__VA_ARGS__)) -#undef glGetFloatv -#define glGetFloatv(...) GLCHECK(gl3wProcs.gl.GetFloatv(__VA_ARGS__)) -#undef glGetFragDataIndex -#define glGetFragDataIndex(...) GLCHECK(gl3wProcs.gl.GetFragDataIndex(__VA_ARGS__)) -#undef glGetFragDataLocation -#define glGetFragDataLocation(...) GLCHECK(gl3wProcs.gl.GetFragDataLocation(__VA_ARGS__)) -#undef glGetFramebufferAttachmentParameteriv -#define glGetFramebufferAttachmentParameteriv(...) GLCHECK(gl3wProcs.gl.GetFramebufferAttachmentParameteriv(__VA_ARGS__)) -#undef glGetFramebufferParameteriv -#define glGetFramebufferParameteriv(...) GLCHECK(gl3wProcs.gl.GetFramebufferParameteriv(__VA_ARGS__)) -#undef glGetGraphicsResetStatus -#define glGetGraphicsResetStatus(...) GLCHECK(gl3wProcs.gl.GetGraphicsResetStatus(__VA_ARGS__)) -#undef glGetGraphicsResetStatusARB -#define glGetGraphicsResetStatusARB(...) GLCHECK(gl3wProcs.gl.GetGraphicsResetStatusARB(__VA_ARGS__)) -#undef glGetImageHandleARB -#define glGetImageHandleARB(...) GLCHECK(gl3wProcs.gl.GetImageHandleARB(__VA_ARGS__)) -#undef glGetInteger64i_v -#define glGetInteger64i_v(...) GLCHECK(gl3wProcs.gl.GetInteger64i_v(__VA_ARGS__)) -#undef glGetInteger64v -#define glGetInteger64v(...) GLCHECK(gl3wProcs.gl.GetInteger64v(__VA_ARGS__)) -#undef glGetIntegeri_v -#define glGetIntegeri_v(...) GLCHECK(gl3wProcs.gl.GetIntegeri_v(__VA_ARGS__)) -#undef glGetIntegerv -#define glGetIntegerv(...) GLCHECK(gl3wProcs.gl.GetIntegerv(__VA_ARGS__)) -#undef glGetInternalformati64v -#define glGetInternalformati64v(...) GLCHECK(gl3wProcs.gl.GetInternalformati64v(__VA_ARGS__)) -#undef glGetInternalformativ -#define glGetInternalformativ(...) GLCHECK(gl3wProcs.gl.GetInternalformativ(__VA_ARGS__)) -#undef glGetMultisamplefv -#define glGetMultisamplefv(...) GLCHECK(gl3wProcs.gl.GetMultisamplefv(__VA_ARGS__)) -#undef glGetNamedBufferParameteri64v -#define glGetNamedBufferParameteri64v(...) GLCHECK(gl3wProcs.gl.GetNamedBufferParameteri64v(__VA_ARGS__)) -#undef glGetNamedBufferParameteriv -#define glGetNamedBufferParameteriv(...) GLCHECK(gl3wProcs.gl.GetNamedBufferParameteriv(__VA_ARGS__)) -#undef glGetNamedBufferPointerv -#define glGetNamedBufferPointerv(...) GLCHECK(gl3wProcs.gl.GetNamedBufferPointerv(__VA_ARGS__)) -#undef glGetNamedBufferSubData -#define glGetNamedBufferSubData(...) GLCHECK(gl3wProcs.gl.GetNamedBufferSubData(__VA_ARGS__)) -#undef glGetNamedFramebufferAttachmentParameteriv -#define glGetNamedFramebufferAttachmentParameteriv(...) GLCHECK(gl3wProcs.gl.GetNamedFramebufferAttachmentParameteriv(__VA_ARGS__)) -#undef glGetNamedFramebufferParameteriv -#define glGetNamedFramebufferParameteriv(...) GLCHECK(gl3wProcs.gl.GetNamedFramebufferParameteriv(__VA_ARGS__)) -#undef glGetNamedRenderbufferParameteriv -#define glGetNamedRenderbufferParameteriv(...) GLCHECK(gl3wProcs.gl.GetNamedRenderbufferParameteriv(__VA_ARGS__)) -#undef glGetNamedStringARB -#define glGetNamedStringARB(...) GLCHECK(gl3wProcs.gl.GetNamedStringARB(__VA_ARGS__)) -#undef glGetNamedStringivARB -#define glGetNamedStringivARB(...) GLCHECK(gl3wProcs.gl.GetNamedStringivARB(__VA_ARGS__)) -#undef glGetObjectLabel -#define glGetObjectLabel(...) GLCHECK(gl3wProcs.gl.GetObjectLabel(__VA_ARGS__)) -#undef glGetObjectPtrLabel -#define glGetObjectPtrLabel(...) GLCHECK(gl3wProcs.gl.GetObjectPtrLabel(__VA_ARGS__)) -#undef glGetPointerv -#define glGetPointerv(...) GLCHECK(gl3wProcs.gl.GetPointerv(__VA_ARGS__)) -#undef glGetProgramBinary -#define glGetProgramBinary(...) GLCHECK(gl3wProcs.gl.GetProgramBinary(__VA_ARGS__)) -#undef glGetProgramInfoLog -#define glGetProgramInfoLog(...) GLCHECK(gl3wProcs.gl.GetProgramInfoLog(__VA_ARGS__)) -#undef glGetProgramInterfaceiv -#define glGetProgramInterfaceiv(...) GLCHECK(gl3wProcs.gl.GetProgramInterfaceiv(__VA_ARGS__)) -#undef glGetProgramPipelineInfoLog -#define glGetProgramPipelineInfoLog(...) GLCHECK(gl3wProcs.gl.GetProgramPipelineInfoLog(__VA_ARGS__)) -#undef glGetProgramPipelineiv -#define glGetProgramPipelineiv(...) GLCHECK(gl3wProcs.gl.GetProgramPipelineiv(__VA_ARGS__)) -#undef glGetProgramResourceIndex -#define glGetProgramResourceIndex(...) GLCHECK(gl3wProcs.gl.GetProgramResourceIndex(__VA_ARGS__)) -#undef glGetProgramResourceLocation -#define glGetProgramResourceLocation(...) GLCHECK(gl3wProcs.gl.GetProgramResourceLocation(__VA_ARGS__)) -#undef glGetProgramResourceLocationIndex -#define glGetProgramResourceLocationIndex(...) GLCHECK(gl3wProcs.gl.GetProgramResourceLocationIndex(__VA_ARGS__)) -#undef glGetProgramResourceName -#define glGetProgramResourceName(...) GLCHECK(gl3wProcs.gl.GetProgramResourceName(__VA_ARGS__)) -#undef glGetProgramResourceiv -#define glGetProgramResourceiv(...) GLCHECK(gl3wProcs.gl.GetProgramResourceiv(__VA_ARGS__)) -#undef glGetProgramStageiv -#define glGetProgramStageiv(...) GLCHECK(gl3wProcs.gl.GetProgramStageiv(__VA_ARGS__)) -#undef glGetProgramiv -#define glGetProgramiv(...) GLCHECK(gl3wProcs.gl.GetProgramiv(__VA_ARGS__)) -#undef glGetQueryBufferObjecti64v -#define glGetQueryBufferObjecti64v(...) GLCHECK(gl3wProcs.gl.GetQueryBufferObjecti64v(__VA_ARGS__)) -#undef glGetQueryBufferObjectiv -#define glGetQueryBufferObjectiv(...) GLCHECK(gl3wProcs.gl.GetQueryBufferObjectiv(__VA_ARGS__)) -#undef glGetQueryBufferObjectui64v -#define glGetQueryBufferObjectui64v(...) GLCHECK(gl3wProcs.gl.GetQueryBufferObjectui64v(__VA_ARGS__)) -#undef glGetQueryBufferObjectuiv -#define glGetQueryBufferObjectuiv(...) GLCHECK(gl3wProcs.gl.GetQueryBufferObjectuiv(__VA_ARGS__)) -#undef glGetQueryIndexediv -#define glGetQueryIndexediv(...) GLCHECK(gl3wProcs.gl.GetQueryIndexediv(__VA_ARGS__)) -#undef glGetQueryObjecti64v -#define glGetQueryObjecti64v(...) GLCHECK(gl3wProcs.gl.GetQueryObjecti64v(__VA_ARGS__)) -#undef glGetQueryObjectiv -#define glGetQueryObjectiv(...) GLCHECK(gl3wProcs.gl.GetQueryObjectiv(__VA_ARGS__)) -#undef glGetQueryObjectui64v -#define glGetQueryObjectui64v(...) GLCHECK(gl3wProcs.gl.GetQueryObjectui64v(__VA_ARGS__)) -#undef glGetQueryObjectuiv -#define glGetQueryObjectuiv(...) GLCHECK(gl3wProcs.gl.GetQueryObjectuiv(__VA_ARGS__)) -#undef glGetQueryiv -#define glGetQueryiv(...) GLCHECK(gl3wProcs.gl.GetQueryiv(__VA_ARGS__)) -#undef glGetRenderbufferParameteriv -#define glGetRenderbufferParameteriv(...) GLCHECK(gl3wProcs.gl.GetRenderbufferParameteriv(__VA_ARGS__)) -#undef glGetSamplerParameterIiv -#define glGetSamplerParameterIiv(...) GLCHECK(gl3wProcs.gl.GetSamplerParameterIiv(__VA_ARGS__)) -#undef glGetSamplerParameterIuiv -#define glGetSamplerParameterIuiv(...) GLCHECK(gl3wProcs.gl.GetSamplerParameterIuiv(__VA_ARGS__)) -#undef glGetSamplerParameterfv -#define glGetSamplerParameterfv(...) GLCHECK(gl3wProcs.gl.GetSamplerParameterfv(__VA_ARGS__)) -#undef glGetSamplerParameteriv -#define glGetSamplerParameteriv(...) GLCHECK(gl3wProcs.gl.GetSamplerParameteriv(__VA_ARGS__)) -#undef glGetShaderInfoLog -#define glGetShaderInfoLog(...) GLCHECK(gl3wProcs.gl.GetShaderInfoLog(__VA_ARGS__)) -#undef glGetShaderPrecisionFormat -#define glGetShaderPrecisionFormat(...) GLCHECK(gl3wProcs.gl.GetShaderPrecisionFormat(__VA_ARGS__)) -#undef glGetShaderSource -#define glGetShaderSource(...) GLCHECK(gl3wProcs.gl.GetShaderSource(__VA_ARGS__)) -#undef glGetShaderiv -#define glGetShaderiv(...) GLCHECK(gl3wProcs.gl.GetShaderiv(__VA_ARGS__)) -#undef glGetSubroutineIndex -#define glGetSubroutineIndex(...) GLCHECK(gl3wProcs.gl.GetSubroutineIndex(__VA_ARGS__)) -#undef glGetSubroutineUniformLocation -#define glGetSubroutineUniformLocation(...) GLCHECK(gl3wProcs.gl.GetSubroutineUniformLocation(__VA_ARGS__)) -#undef glGetSynciv -#define glGetSynciv(...) GLCHECK(gl3wProcs.gl.GetSynciv(__VA_ARGS__)) -#undef glGetTexImage -#define glGetTexImage(...) GLCHECK(gl3wProcs.gl.GetTexImage(__VA_ARGS__)) -#undef glGetTexLevelParameterfv -#define glGetTexLevelParameterfv(...) GLCHECK(gl3wProcs.gl.GetTexLevelParameterfv(__VA_ARGS__)) -#undef glGetTexLevelParameteriv -#define glGetTexLevelParameteriv(...) GLCHECK(gl3wProcs.gl.GetTexLevelParameteriv(__VA_ARGS__)) -#undef glGetTexParameterIiv -#define glGetTexParameterIiv(...) GLCHECK(gl3wProcs.gl.GetTexParameterIiv(__VA_ARGS__)) -#undef glGetTexParameterIuiv -#define glGetTexParameterIuiv(...) GLCHECK(gl3wProcs.gl.GetTexParameterIuiv(__VA_ARGS__)) -#undef glGetTexParameterfv -#define glGetTexParameterfv(...) GLCHECK(gl3wProcs.gl.GetTexParameterfv(__VA_ARGS__)) -#undef glGetTexParameteriv -#define glGetTexParameteriv(...) GLCHECK(gl3wProcs.gl.GetTexParameteriv(__VA_ARGS__)) -#undef glGetTextureHandleARB -#define glGetTextureHandleARB(...) GLCHECK(gl3wProcs.gl.GetTextureHandleARB(__VA_ARGS__)) -#undef glGetTextureImage -#define glGetTextureImage(...) GLCHECK(gl3wProcs.gl.GetTextureImage(__VA_ARGS__)) -#undef glGetTextureLevelParameterfv -#define glGetTextureLevelParameterfv(...) GLCHECK(gl3wProcs.gl.GetTextureLevelParameterfv(__VA_ARGS__)) -#undef glGetTextureLevelParameteriv -#define glGetTextureLevelParameteriv(...) GLCHECK(gl3wProcs.gl.GetTextureLevelParameteriv(__VA_ARGS__)) -#undef glGetTextureParameterIiv -#define glGetTextureParameterIiv(...) GLCHECK(gl3wProcs.gl.GetTextureParameterIiv(__VA_ARGS__)) -#undef glGetTextureParameterIuiv -#define glGetTextureParameterIuiv(...) GLCHECK(gl3wProcs.gl.GetTextureParameterIuiv(__VA_ARGS__)) -#undef glGetTextureParameterfv -#define glGetTextureParameterfv(...) GLCHECK(gl3wProcs.gl.GetTextureParameterfv(__VA_ARGS__)) -#undef glGetTextureParameteriv -#define glGetTextureParameteriv(...) GLCHECK(gl3wProcs.gl.GetTextureParameteriv(__VA_ARGS__)) -#undef glGetTextureSamplerHandleARB -#define glGetTextureSamplerHandleARB(...) GLCHECK(gl3wProcs.gl.GetTextureSamplerHandleARB(__VA_ARGS__)) -#undef glGetTextureSubImage -#define glGetTextureSubImage(...) GLCHECK(gl3wProcs.gl.GetTextureSubImage(__VA_ARGS__)) -#undef glGetTransformFeedbackVarying -#define glGetTransformFeedbackVarying(...) GLCHECK(gl3wProcs.gl.GetTransformFeedbackVarying(__VA_ARGS__)) -#undef glGetTransformFeedbacki64_v -#define glGetTransformFeedbacki64_v(...) GLCHECK(gl3wProcs.gl.GetTransformFeedbacki64_v(__VA_ARGS__)) -#undef glGetTransformFeedbacki_v -#define glGetTransformFeedbacki_v(...) GLCHECK(gl3wProcs.gl.GetTransformFeedbacki_v(__VA_ARGS__)) -#undef glGetTransformFeedbackiv -#define glGetTransformFeedbackiv(...) GLCHECK(gl3wProcs.gl.GetTransformFeedbackiv(__VA_ARGS__)) -#undef glGetUniformBlockIndex -#define glGetUniformBlockIndex(...) GLCHECK(gl3wProcs.gl.GetUniformBlockIndex(__VA_ARGS__)) -#undef glGetUniformIndices -#define glGetUniformIndices(...) GLCHECK(gl3wProcs.gl.GetUniformIndices(__VA_ARGS__)) -#undef glGetUniformSubroutineuiv -#define glGetUniformSubroutineuiv(...) GLCHECK(gl3wProcs.gl.GetUniformSubroutineuiv(__VA_ARGS__)) -#undef glGetUniformdv -#define glGetUniformdv(...) GLCHECK(gl3wProcs.gl.GetUniformdv(__VA_ARGS__)) -#undef glGetUniformfv -#define glGetUniformfv(...) GLCHECK(gl3wProcs.gl.GetUniformfv(__VA_ARGS__)) -#undef glGetUniformiv -#define glGetUniformiv(...) GLCHECK(gl3wProcs.gl.GetUniformiv(__VA_ARGS__)) -#undef glGetUniformuiv -#define glGetUniformuiv(...) GLCHECK(gl3wProcs.gl.GetUniformuiv(__VA_ARGS__)) -#undef glGetVertexArrayIndexed64iv -#define glGetVertexArrayIndexed64iv(...) GLCHECK(gl3wProcs.gl.GetVertexArrayIndexed64iv(__VA_ARGS__)) -#undef glGetVertexArrayIndexediv -#define glGetVertexArrayIndexediv(...) GLCHECK(gl3wProcs.gl.GetVertexArrayIndexediv(__VA_ARGS__)) -#undef glGetVertexArrayiv -#define glGetVertexArrayiv(...) GLCHECK(gl3wProcs.gl.GetVertexArrayiv(__VA_ARGS__)) -#undef glGetVertexAttribIiv -#define glGetVertexAttribIiv(...) GLCHECK(gl3wProcs.gl.GetVertexAttribIiv(__VA_ARGS__)) -#undef glGetVertexAttribIuiv -#define glGetVertexAttribIuiv(...) GLCHECK(gl3wProcs.gl.GetVertexAttribIuiv(__VA_ARGS__)) -#undef glGetVertexAttribLdv -#define glGetVertexAttribLdv(...) GLCHECK(gl3wProcs.gl.GetVertexAttribLdv(__VA_ARGS__)) -#undef glGetVertexAttribLui64vARB -#define glGetVertexAttribLui64vARB(...) GLCHECK(gl3wProcs.gl.GetVertexAttribLui64vARB(__VA_ARGS__)) -#undef glGetVertexAttribPointerv -#define glGetVertexAttribPointerv(...) GLCHECK(gl3wProcs.gl.GetVertexAttribPointerv(__VA_ARGS__)) -#undef glGetVertexAttribdv -#define glGetVertexAttribdv(...) GLCHECK(gl3wProcs.gl.GetVertexAttribdv(__VA_ARGS__)) -#undef glGetVertexAttribfv -#define glGetVertexAttribfv(...) GLCHECK(gl3wProcs.gl.GetVertexAttribfv(__VA_ARGS__)) -#undef glGetVertexAttribiv -#define glGetVertexAttribiv(...) GLCHECK(gl3wProcs.gl.GetVertexAttribiv(__VA_ARGS__)) -#undef glGetnCompressedTexImage -#define glGetnCompressedTexImage(...) GLCHECK(gl3wProcs.gl.GetnCompressedTexImage(__VA_ARGS__)) -#undef glGetnCompressedTexImageARB -#define glGetnCompressedTexImageARB(...) GLCHECK(gl3wProcs.gl.GetnCompressedTexImageARB(__VA_ARGS__)) -#undef glGetnTexImage -#define glGetnTexImage(...) GLCHECK(gl3wProcs.gl.GetnTexImage(__VA_ARGS__)) -#undef glGetnTexImageARB -#define glGetnTexImageARB(...) GLCHECK(gl3wProcs.gl.GetnTexImageARB(__VA_ARGS__)) -#undef glGetnUniformdv -#define glGetnUniformdv(...) GLCHECK(gl3wProcs.gl.GetnUniformdv(__VA_ARGS__)) -#undef glGetnUniformdvARB -#define glGetnUniformdvARB(...) GLCHECK(gl3wProcs.gl.GetnUniformdvARB(__VA_ARGS__)) -#undef glGetnUniformfv -#define glGetnUniformfv(...) GLCHECK(gl3wProcs.gl.GetnUniformfv(__VA_ARGS__)) -#undef glGetnUniformfvARB -#define glGetnUniformfvARB(...) GLCHECK(gl3wProcs.gl.GetnUniformfvARB(__VA_ARGS__)) -#undef glGetnUniformiv -#define glGetnUniformiv(...) GLCHECK(gl3wProcs.gl.GetnUniformiv(__VA_ARGS__)) -#undef glGetnUniformivARB -#define glGetnUniformivARB(...) GLCHECK(gl3wProcs.gl.GetnUniformivARB(__VA_ARGS__)) -#undef glGetnUniformuiv -#define glGetnUniformuiv(...) GLCHECK(gl3wProcs.gl.GetnUniformuiv(__VA_ARGS__)) -#undef glGetnUniformuivARB -#define glGetnUniformuivARB(...) GLCHECK(gl3wProcs.gl.GetnUniformuivARB(__VA_ARGS__)) -#undef glHint -#define glHint(...) GLCHECK(gl3wProcs.gl.Hint(__VA_ARGS__)) -#undef glInvalidateBufferData -#define glInvalidateBufferData(...) GLCHECK(gl3wProcs.gl.InvalidateBufferData(__VA_ARGS__)) -#undef glInvalidateBufferSubData -#define glInvalidateBufferSubData(...) GLCHECK(gl3wProcs.gl.InvalidateBufferSubData(__VA_ARGS__)) -#undef glInvalidateFramebuffer -#define glInvalidateFramebuffer(...) GLCHECK(gl3wProcs.gl.InvalidateFramebuffer(__VA_ARGS__)) -#undef glInvalidateNamedFramebufferData -#define glInvalidateNamedFramebufferData(...) GLCHECK(gl3wProcs.gl.InvalidateNamedFramebufferData(__VA_ARGS__)) -#undef glInvalidateNamedFramebufferSubData -#define glInvalidateNamedFramebufferSubData(...) GLCHECK(gl3wProcs.gl.InvalidateNamedFramebufferSubData(__VA_ARGS__)) -#undef glInvalidateSubFramebuffer -#define glInvalidateSubFramebuffer(...) GLCHECK(gl3wProcs.gl.InvalidateSubFramebuffer(__VA_ARGS__)) -#undef glInvalidateTexImage -#define glInvalidateTexImage(...) GLCHECK(gl3wProcs.gl.InvalidateTexImage(__VA_ARGS__)) -#undef glInvalidateTexSubImage -#define glInvalidateTexSubImage(...) GLCHECK(gl3wProcs.gl.InvalidateTexSubImage(__VA_ARGS__)) -#undef glLineWidth -#define glLineWidth(...) GLCHECK(gl3wProcs.gl.LineWidth(__VA_ARGS__)) -#undef glLinkProgram -#define glLinkProgram(...) GLCHECK(gl3wProcs.gl.LinkProgram(__VA_ARGS__)) -#undef glLogicOp -#define glLogicOp(...) GLCHECK(gl3wProcs.gl.LogicOp(__VA_ARGS__)) -#undef glMakeImageHandleNonResidentARB -#define glMakeImageHandleNonResidentARB(...) GLCHECK(gl3wProcs.gl.MakeImageHandleNonResidentARB(__VA_ARGS__)) -#undef glMakeImageHandleResidentARB -#define glMakeImageHandleResidentARB(...) GLCHECK(gl3wProcs.gl.MakeImageHandleResidentARB(__VA_ARGS__)) -#undef glMakeTextureHandleNonResidentARB -#define glMakeTextureHandleNonResidentARB(...) GLCHECK(gl3wProcs.gl.MakeTextureHandleNonResidentARB(__VA_ARGS__)) -#undef glMakeTextureHandleResidentARB -#define glMakeTextureHandleResidentARB(...) GLCHECK(gl3wProcs.gl.MakeTextureHandleResidentARB(__VA_ARGS__)) -#undef glMemoryBarrier -#define glMemoryBarrier(...) GLCHECK(gl3wProcs.gl.MemoryBarrier(__VA_ARGS__)) -#undef glMemoryBarrierByRegion -#define glMemoryBarrierByRegion(...) GLCHECK(gl3wProcs.gl.MemoryBarrierByRegion(__VA_ARGS__)) -#undef glMinSampleShading -#define glMinSampleShading(...) GLCHECK(gl3wProcs.gl.MinSampleShading(__VA_ARGS__)) -#undef glMinSampleShadingARB -#define glMinSampleShadingARB(...) GLCHECK(gl3wProcs.gl.MinSampleShadingARB(__VA_ARGS__)) -#undef glMultiDrawArrays -#define glMultiDrawArrays(...) GLCHECK(gl3wProcs.gl.MultiDrawArrays(__VA_ARGS__)) -#undef glMultiDrawArraysIndirect -#define glMultiDrawArraysIndirect(...) GLCHECK(gl3wProcs.gl.MultiDrawArraysIndirect(__VA_ARGS__)) -#undef glMultiDrawArraysIndirectCountARB -#define glMultiDrawArraysIndirectCountARB(...) GLCHECK(gl3wProcs.gl.MultiDrawArraysIndirectCountARB(__VA_ARGS__)) -#undef glMultiDrawElements -#define glMultiDrawElements(...) GLCHECK(gl3wProcs.gl.MultiDrawElements(__VA_ARGS__)) -#undef glMultiDrawElementsBaseVertex -#define glMultiDrawElementsBaseVertex(...) GLCHECK(gl3wProcs.gl.MultiDrawElementsBaseVertex(__VA_ARGS__)) -#undef glMultiDrawElementsIndirect -#define glMultiDrawElementsIndirect(...) GLCHECK(gl3wProcs.gl.MultiDrawElementsIndirect(__VA_ARGS__)) -#undef glMultiDrawElementsIndirectCountARB -#define glMultiDrawElementsIndirectCountARB(...) GLCHECK(gl3wProcs.gl.MultiDrawElementsIndirectCountARB(__VA_ARGS__)) -#undef glNamedBufferData -#define glNamedBufferData(...) GLCHECK(gl3wProcs.gl.NamedBufferData(__VA_ARGS__)) -#undef glNamedBufferPageCommitmentARB -#define glNamedBufferPageCommitmentARB(...) GLCHECK(gl3wProcs.gl.NamedBufferPageCommitmentARB(__VA_ARGS__)) -#undef glNamedBufferPageCommitmentEXT -#define glNamedBufferPageCommitmentEXT(...) GLCHECK(gl3wProcs.gl.NamedBufferPageCommitmentEXT(__VA_ARGS__)) -#undef glNamedBufferStorage -#define glNamedBufferStorage(...) GLCHECK(gl3wProcs.gl.NamedBufferStorage(__VA_ARGS__)) -#undef glNamedBufferSubData -#define glNamedBufferSubData(...) GLCHECK(gl3wProcs.gl.NamedBufferSubData(__VA_ARGS__)) -#undef glNamedFramebufferDrawBuffer -#define glNamedFramebufferDrawBuffer(...) GLCHECK(gl3wProcs.gl.NamedFramebufferDrawBuffer(__VA_ARGS__)) -#undef glNamedFramebufferDrawBuffers -#define glNamedFramebufferDrawBuffers(...) GLCHECK(gl3wProcs.gl.NamedFramebufferDrawBuffers(__VA_ARGS__)) -#undef glNamedFramebufferParameteri -#define glNamedFramebufferParameteri(...) GLCHECK(gl3wProcs.gl.NamedFramebufferParameteri(__VA_ARGS__)) -#undef glNamedFramebufferReadBuffer -#define glNamedFramebufferReadBuffer(...) GLCHECK(gl3wProcs.gl.NamedFramebufferReadBuffer(__VA_ARGS__)) -#undef glNamedFramebufferRenderbuffer -#define glNamedFramebufferRenderbuffer(...) GLCHECK(gl3wProcs.gl.NamedFramebufferRenderbuffer(__VA_ARGS__)) -#undef glNamedFramebufferTexture -#define glNamedFramebufferTexture(...) GLCHECK(gl3wProcs.gl.NamedFramebufferTexture(__VA_ARGS__)) -#undef glNamedFramebufferTextureLayer -#define glNamedFramebufferTextureLayer(...) GLCHECK(gl3wProcs.gl.NamedFramebufferTextureLayer(__VA_ARGS__)) -#undef glNamedRenderbufferStorage -#define glNamedRenderbufferStorage(...) GLCHECK(gl3wProcs.gl.NamedRenderbufferStorage(__VA_ARGS__)) -#undef glNamedRenderbufferStorageMultisample -#define glNamedRenderbufferStorageMultisample(...) GLCHECK(gl3wProcs.gl.NamedRenderbufferStorageMultisample(__VA_ARGS__)) -#undef glNamedStringARB -#define glNamedStringARB(...) GLCHECK(gl3wProcs.gl.NamedStringARB(__VA_ARGS__)) -#undef glObjectLabel -#define glObjectLabel(...) GLCHECK(gl3wProcs.gl.ObjectLabel(__VA_ARGS__)) -#undef glObjectPtrLabel -#define glObjectPtrLabel(...) GLCHECK(gl3wProcs.gl.ObjectPtrLabel(__VA_ARGS__)) -#undef glPatchParameterfv -#define glPatchParameterfv(...) GLCHECK(gl3wProcs.gl.PatchParameterfv(__VA_ARGS__)) -#undef glPatchParameteri -#define glPatchParameteri(...) GLCHECK(gl3wProcs.gl.PatchParameteri(__VA_ARGS__)) -#undef glPauseTransformFeedback -#define glPauseTransformFeedback(...) GLCHECK(gl3wProcs.gl.PauseTransformFeedback(__VA_ARGS__)) -#undef glPixelStoref -#define glPixelStoref(...) GLCHECK(gl3wProcs.gl.PixelStoref(__VA_ARGS__)) -#undef glPixelStorei -#define glPixelStorei(...) GLCHECK(gl3wProcs.gl.PixelStorei(__VA_ARGS__)) -#undef glPointParameterf -#define glPointParameterf(...) GLCHECK(gl3wProcs.gl.PointParameterf(__VA_ARGS__)) -#undef glPointParameterfv -#define glPointParameterfv(...) GLCHECK(gl3wProcs.gl.PointParameterfv(__VA_ARGS__)) -#undef glPointParameteri -#define glPointParameteri(...) GLCHECK(gl3wProcs.gl.PointParameteri(__VA_ARGS__)) -#undef glPointParameteriv -#define glPointParameteriv(...) GLCHECK(gl3wProcs.gl.PointParameteriv(__VA_ARGS__)) -#undef glPointSize -#define glPointSize(...) GLCHECK(gl3wProcs.gl.PointSize(__VA_ARGS__)) -#undef glPolygonMode -#define glPolygonMode(...) GLCHECK(gl3wProcs.gl.PolygonMode(__VA_ARGS__)) -#undef glPolygonOffset -#define glPolygonOffset(...) GLCHECK(gl3wProcs.gl.PolygonOffset(__VA_ARGS__)) -#undef glPopDebugGroup -#define glPopDebugGroup(...) GLCHECK(gl3wProcs.gl.PopDebugGroup(__VA_ARGS__)) -#undef glPrimitiveRestartIndex -#define glPrimitiveRestartIndex(...) GLCHECK(gl3wProcs.gl.PrimitiveRestartIndex(__VA_ARGS__)) -#undef glProgramBinary -#define glProgramBinary(...) GLCHECK(gl3wProcs.gl.ProgramBinary(__VA_ARGS__)) -#undef glProgramParameteri -#define glProgramParameteri(...) GLCHECK(gl3wProcs.gl.ProgramParameteri(__VA_ARGS__)) -#undef glProgramUniform1d -#define glProgramUniform1d(...) GLCHECK(gl3wProcs.gl.ProgramUniform1d(__VA_ARGS__)) -#undef glProgramUniform1dv -#define glProgramUniform1dv(...) GLCHECK(gl3wProcs.gl.ProgramUniform1dv(__VA_ARGS__)) -#undef glProgramUniform1f -#define glProgramUniform1f(...) GLCHECK(gl3wProcs.gl.ProgramUniform1f(__VA_ARGS__)) -#undef glProgramUniform1fv -#define glProgramUniform1fv(...) GLCHECK(gl3wProcs.gl.ProgramUniform1fv(__VA_ARGS__)) -#undef glProgramUniform1i -#define glProgramUniform1i(...) GLCHECK(gl3wProcs.gl.ProgramUniform1i(__VA_ARGS__)) -#undef glProgramUniform1iv -#define glProgramUniform1iv(...) GLCHECK(gl3wProcs.gl.ProgramUniform1iv(__VA_ARGS__)) -#undef glProgramUniform1ui -#define glProgramUniform1ui(...) GLCHECK(gl3wProcs.gl.ProgramUniform1ui(__VA_ARGS__)) -#undef glProgramUniform1uiv -#define glProgramUniform1uiv(...) GLCHECK(gl3wProcs.gl.ProgramUniform1uiv(__VA_ARGS__)) -#undef glProgramUniform2d -#define glProgramUniform2d(...) GLCHECK(gl3wProcs.gl.ProgramUniform2d(__VA_ARGS__)) -#undef glProgramUniform2dv -#define glProgramUniform2dv(...) GLCHECK(gl3wProcs.gl.ProgramUniform2dv(__VA_ARGS__)) -#undef glProgramUniform2f -#define glProgramUniform2f(...) GLCHECK(gl3wProcs.gl.ProgramUniform2f(__VA_ARGS__)) -#undef glProgramUniform2fv -#define glProgramUniform2fv(...) GLCHECK(gl3wProcs.gl.ProgramUniform2fv(__VA_ARGS__)) -#undef glProgramUniform2i -#define glProgramUniform2i(...) GLCHECK(gl3wProcs.gl.ProgramUniform2i(__VA_ARGS__)) -#undef glProgramUniform2iv -#define glProgramUniform2iv(...) GLCHECK(gl3wProcs.gl.ProgramUniform2iv(__VA_ARGS__)) -#undef glProgramUniform2ui -#define glProgramUniform2ui(...) GLCHECK(gl3wProcs.gl.ProgramUniform2ui(__VA_ARGS__)) -#undef glProgramUniform2uiv -#define glProgramUniform2uiv(...) GLCHECK(gl3wProcs.gl.ProgramUniform2uiv(__VA_ARGS__)) -#undef glProgramUniform3d -#define glProgramUniform3d(...) GLCHECK(gl3wProcs.gl.ProgramUniform3d(__VA_ARGS__)) -#undef glProgramUniform3dv -#define glProgramUniform3dv(...) GLCHECK(gl3wProcs.gl.ProgramUniform3dv(__VA_ARGS__)) -#undef glProgramUniform3f -#define glProgramUniform3f(...) GLCHECK(gl3wProcs.gl.ProgramUniform3f(__VA_ARGS__)) -#undef glProgramUniform3fv -#define glProgramUniform3fv(...) GLCHECK(gl3wProcs.gl.ProgramUniform3fv(__VA_ARGS__)) -#undef glProgramUniform3i -#define glProgramUniform3i(...) GLCHECK(gl3wProcs.gl.ProgramUniform3i(__VA_ARGS__)) -#undef glProgramUniform3iv -#define glProgramUniform3iv(...) GLCHECK(gl3wProcs.gl.ProgramUniform3iv(__VA_ARGS__)) -#undef glProgramUniform3ui -#define glProgramUniform3ui(...) GLCHECK(gl3wProcs.gl.ProgramUniform3ui(__VA_ARGS__)) -#undef glProgramUniform3uiv -#define glProgramUniform3uiv(...) GLCHECK(gl3wProcs.gl.ProgramUniform3uiv(__VA_ARGS__)) -#undef glProgramUniform4d -#define glProgramUniform4d(...) GLCHECK(gl3wProcs.gl.ProgramUniform4d(__VA_ARGS__)) -#undef glProgramUniform4dv -#define glProgramUniform4dv(...) GLCHECK(gl3wProcs.gl.ProgramUniform4dv(__VA_ARGS__)) -#undef glProgramUniform4f -#define glProgramUniform4f(...) GLCHECK(gl3wProcs.gl.ProgramUniform4f(__VA_ARGS__)) -#undef glProgramUniform4fv -#define glProgramUniform4fv(...) GLCHECK(gl3wProcs.gl.ProgramUniform4fv(__VA_ARGS__)) -#undef glProgramUniform4i -#define glProgramUniform4i(...) GLCHECK(gl3wProcs.gl.ProgramUniform4i(__VA_ARGS__)) -#undef glProgramUniform4iv -#define glProgramUniform4iv(...) GLCHECK(gl3wProcs.gl.ProgramUniform4iv(__VA_ARGS__)) -#undef glProgramUniform4ui -#define glProgramUniform4ui(...) GLCHECK(gl3wProcs.gl.ProgramUniform4ui(__VA_ARGS__)) -#undef glProgramUniform4uiv -#define glProgramUniform4uiv(...) GLCHECK(gl3wProcs.gl.ProgramUniform4uiv(__VA_ARGS__)) -#undef glProgramUniformHandleui64ARB -#define glProgramUniformHandleui64ARB(...) GLCHECK(gl3wProcs.gl.ProgramUniformHandleui64ARB(__VA_ARGS__)) -#undef glProgramUniformHandleui64vARB -#define glProgramUniformHandleui64vARB(...) GLCHECK(gl3wProcs.gl.ProgramUniformHandleui64vARB(__VA_ARGS__)) -#undef glProgramUniformMatrix2dv -#define glProgramUniformMatrix2dv(...) GLCHECK(gl3wProcs.gl.ProgramUniformMatrix2dv(__VA_ARGS__)) -#undef glProgramUniformMatrix2fv -#define glProgramUniformMatrix2fv(...) GLCHECK(gl3wProcs.gl.ProgramUniformMatrix2fv(__VA_ARGS__)) -#undef glProgramUniformMatrix2x3dv -#define glProgramUniformMatrix2x3dv(...) GLCHECK(gl3wProcs.gl.ProgramUniformMatrix2x3dv(__VA_ARGS__)) -#undef glProgramUniformMatrix2x3fv -#define glProgramUniformMatrix2x3fv(...) GLCHECK(gl3wProcs.gl.ProgramUniformMatrix2x3fv(__VA_ARGS__)) -#undef glProgramUniformMatrix2x4dv -#define glProgramUniformMatrix2x4dv(...) GLCHECK(gl3wProcs.gl.ProgramUniformMatrix2x4dv(__VA_ARGS__)) -#undef glProgramUniformMatrix2x4fv -#define glProgramUniformMatrix2x4fv(...) GLCHECK(gl3wProcs.gl.ProgramUniformMatrix2x4fv(__VA_ARGS__)) -#undef glProgramUniformMatrix3dv -#define glProgramUniformMatrix3dv(...) GLCHECK(gl3wProcs.gl.ProgramUniformMatrix3dv(__VA_ARGS__)) -#undef glProgramUniformMatrix3fv -#define glProgramUniformMatrix3fv(...) GLCHECK(gl3wProcs.gl.ProgramUniformMatrix3fv(__VA_ARGS__)) -#undef glProgramUniformMatrix3x2dv -#define glProgramUniformMatrix3x2dv(...) GLCHECK(gl3wProcs.gl.ProgramUniformMatrix3x2dv(__VA_ARGS__)) -#undef glProgramUniformMatrix3x2fv -#define glProgramUniformMatrix3x2fv(...) GLCHECK(gl3wProcs.gl.ProgramUniformMatrix3x2fv(__VA_ARGS__)) -#undef glProgramUniformMatrix3x4dv -#define glProgramUniformMatrix3x4dv(...) GLCHECK(gl3wProcs.gl.ProgramUniformMatrix3x4dv(__VA_ARGS__)) -#undef glProgramUniformMatrix3x4fv -#define glProgramUniformMatrix3x4fv(...) GLCHECK(gl3wProcs.gl.ProgramUniformMatrix3x4fv(__VA_ARGS__)) -#undef glProgramUniformMatrix4dv -#define glProgramUniformMatrix4dv(...) GLCHECK(gl3wProcs.gl.ProgramUniformMatrix4dv(__VA_ARGS__)) -#undef glProgramUniformMatrix4fv -#define glProgramUniformMatrix4fv(...) GLCHECK(gl3wProcs.gl.ProgramUniformMatrix4fv(__VA_ARGS__)) -#undef glProgramUniformMatrix4x2dv -#define glProgramUniformMatrix4x2dv(...) GLCHECK(gl3wProcs.gl.ProgramUniformMatrix4x2dv(__VA_ARGS__)) -#undef glProgramUniformMatrix4x2fv -#define glProgramUniformMatrix4x2fv(...) GLCHECK(gl3wProcs.gl.ProgramUniformMatrix4x2fv(__VA_ARGS__)) -#undef glProgramUniformMatrix4x3dv -#define glProgramUniformMatrix4x3dv(...) GLCHECK(gl3wProcs.gl.ProgramUniformMatrix4x3dv(__VA_ARGS__)) -#undef glProgramUniformMatrix4x3fv -#define glProgramUniformMatrix4x3fv(...) GLCHECK(gl3wProcs.gl.ProgramUniformMatrix4x3fv(__VA_ARGS__)) -#undef glProvokingVertex -#define glProvokingVertex(...) GLCHECK(gl3wProcs.gl.ProvokingVertex(__VA_ARGS__)) -#undef glPushDebugGroup -#define glPushDebugGroup(...) GLCHECK(gl3wProcs.gl.PushDebugGroup(__VA_ARGS__)) -#undef glQueryCounter -#define glQueryCounter(...) GLCHECK(gl3wProcs.gl.QueryCounter(__VA_ARGS__)) -#undef glReadBuffer -#define glReadBuffer(...) GLCHECK(gl3wProcs.gl.ReadBuffer(__VA_ARGS__)) -#undef glReadPixels -#define glReadPixels(...) GLCHECK(gl3wProcs.gl.ReadPixels(__VA_ARGS__)) -#undef glReadnPixels -#define glReadnPixels(...) GLCHECK(gl3wProcs.gl.ReadnPixels(__VA_ARGS__)) -#undef glReadnPixelsARB -#define glReadnPixelsARB(...) GLCHECK(gl3wProcs.gl.ReadnPixelsARB(__VA_ARGS__)) -#undef glReleaseShaderCompiler -#define glReleaseShaderCompiler(...) GLCHECK(gl3wProcs.gl.ReleaseShaderCompiler(__VA_ARGS__)) -#undef glRenderbufferStorage -#define glRenderbufferStorage(...) GLCHECK(gl3wProcs.gl.RenderbufferStorage(__VA_ARGS__)) -#undef glRenderbufferStorageMultisample -#define glRenderbufferStorageMultisample(...) GLCHECK(gl3wProcs.gl.RenderbufferStorageMultisample(__VA_ARGS__)) -#undef glResumeTransformFeedback -#define glResumeTransformFeedback(...) GLCHECK(gl3wProcs.gl.ResumeTransformFeedback(__VA_ARGS__)) -#undef glSampleCoverage -#define glSampleCoverage(...) GLCHECK(gl3wProcs.gl.SampleCoverage(__VA_ARGS__)) -#undef glSampleMaski -#define glSampleMaski(...) GLCHECK(gl3wProcs.gl.SampleMaski(__VA_ARGS__)) -#undef glSamplerParameterIiv -#define glSamplerParameterIiv(...) GLCHECK(gl3wProcs.gl.SamplerParameterIiv(__VA_ARGS__)) -#undef glSamplerParameterIuiv -#define glSamplerParameterIuiv(...) GLCHECK(gl3wProcs.gl.SamplerParameterIuiv(__VA_ARGS__)) -#undef glSamplerParameterf -#define glSamplerParameterf(...) GLCHECK(gl3wProcs.gl.SamplerParameterf(__VA_ARGS__)) -#undef glSamplerParameterfv -#define glSamplerParameterfv(...) GLCHECK(gl3wProcs.gl.SamplerParameterfv(__VA_ARGS__)) -#undef glSamplerParameteri -#define glSamplerParameteri(...) GLCHECK(gl3wProcs.gl.SamplerParameteri(__VA_ARGS__)) -#undef glSamplerParameteriv -#define glSamplerParameteriv(...) GLCHECK(gl3wProcs.gl.SamplerParameteriv(__VA_ARGS__)) -#undef glScissor -#define glScissor(...) GLCHECK(gl3wProcs.gl.Scissor(__VA_ARGS__)) -#undef glScissorArrayv -#define glScissorArrayv(...) GLCHECK(gl3wProcs.gl.ScissorArrayv(__VA_ARGS__)) -#undef glScissorIndexed -#define glScissorIndexed(...) GLCHECK(gl3wProcs.gl.ScissorIndexed(__VA_ARGS__)) -#undef glScissorIndexedv -#define glScissorIndexedv(...) GLCHECK(gl3wProcs.gl.ScissorIndexedv(__VA_ARGS__)) -#undef glShaderBinary -#define glShaderBinary(...) GLCHECK(gl3wProcs.gl.ShaderBinary(__VA_ARGS__)) -#undef glShaderSource -#define glShaderSource(...) GLCHECK(gl3wProcs.gl.ShaderSource(__VA_ARGS__)) -#undef glShaderStorageBlockBinding -#define glShaderStorageBlockBinding(...) GLCHECK(gl3wProcs.gl.ShaderStorageBlockBinding(__VA_ARGS__)) -#undef glStencilFunc -#define glStencilFunc(...) GLCHECK(gl3wProcs.gl.StencilFunc(__VA_ARGS__)) -#undef glStencilFuncSeparate -#define glStencilFuncSeparate(...) GLCHECK(gl3wProcs.gl.StencilFuncSeparate(__VA_ARGS__)) -#undef glStencilMask -#define glStencilMask(...) GLCHECK(gl3wProcs.gl.StencilMask(__VA_ARGS__)) -#undef glStencilMaskSeparate -#define glStencilMaskSeparate(...) GLCHECK(gl3wProcs.gl.StencilMaskSeparate(__VA_ARGS__)) -#undef glStencilOp -#define glStencilOp(...) GLCHECK(gl3wProcs.gl.StencilOp(__VA_ARGS__)) -#undef glStencilOpSeparate -#define glStencilOpSeparate(...) GLCHECK(gl3wProcs.gl.StencilOpSeparate(__VA_ARGS__)) -#undef glTexBuffer -#define glTexBuffer(...) GLCHECK(gl3wProcs.gl.TexBuffer(__VA_ARGS__)) -#undef glTexBufferRange -#define glTexBufferRange(...) GLCHECK(gl3wProcs.gl.TexBufferRange(__VA_ARGS__)) -#undef glTexImage1D -#define glTexImage1D(...) GLCHECK(gl3wProcs.gl.TexImage1D(__VA_ARGS__)) -#undef glTexImage2D -#define glTexImage2D(...) GLCHECK(gl3wProcs.gl.TexImage2D(__VA_ARGS__)) -#undef glTexImage2DMultisample -#define glTexImage2DMultisample(...) GLCHECK(gl3wProcs.gl.TexImage2DMultisample(__VA_ARGS__)) -#undef glTexImage3D -#define glTexImage3D(...) GLCHECK(gl3wProcs.gl.TexImage3D(__VA_ARGS__)) -#undef glTexImage3DMultisample -#define glTexImage3DMultisample(...) GLCHECK(gl3wProcs.gl.TexImage3DMultisample(__VA_ARGS__)) -#undef glTexPageCommitmentARB -#define glTexPageCommitmentARB(...) GLCHECK(gl3wProcs.gl.TexPageCommitmentARB(__VA_ARGS__)) -#undef glTexParameterIiv -#define glTexParameterIiv(...) GLCHECK(gl3wProcs.gl.TexParameterIiv(__VA_ARGS__)) -#undef glTexParameterIuiv -#define glTexParameterIuiv(...) GLCHECK(gl3wProcs.gl.TexParameterIuiv(__VA_ARGS__)) -#undef glTexParameterf -#define glTexParameterf(...) GLCHECK(gl3wProcs.gl.TexParameterf(__VA_ARGS__)) -#undef glTexParameterfv -#define glTexParameterfv(...) GLCHECK(gl3wProcs.gl.TexParameterfv(__VA_ARGS__)) -#undef glTexParameteri -#define glTexParameteri(...) GLCHECK(gl3wProcs.gl.TexParameteri(__VA_ARGS__)) -#undef glTexParameteriv -#define glTexParameteriv(...) GLCHECK(gl3wProcs.gl.TexParameteriv(__VA_ARGS__)) -#undef glTexStorage1D -#define glTexStorage1D(...) GLCHECK(gl3wProcs.gl.TexStorage1D(__VA_ARGS__)) -#undef glTexStorage2D -#define glTexStorage2D(...) GLCHECK(gl3wProcs.gl.TexStorage2D(__VA_ARGS__)) -#undef glTexStorage2DMultisample -#define glTexStorage2DMultisample(...) GLCHECK(gl3wProcs.gl.TexStorage2DMultisample(__VA_ARGS__)) -#undef glTexStorage3D -#define glTexStorage3D(...) GLCHECK(gl3wProcs.gl.TexStorage3D(__VA_ARGS__)) -#undef glTexStorage3DMultisample -#define glTexStorage3DMultisample(...) GLCHECK(gl3wProcs.gl.TexStorage3DMultisample(__VA_ARGS__)) -#undef glTexSubImage1D -#define glTexSubImage1D(...) GLCHECK(gl3wProcs.gl.TexSubImage1D(__VA_ARGS__)) -#undef glTexSubImage2D -#define glTexSubImage2D(...) GLCHECK(gl3wProcs.gl.TexSubImage2D(__VA_ARGS__)) -#undef glTexSubImage3D -#define glTexSubImage3D(...) GLCHECK(gl3wProcs.gl.TexSubImage3D(__VA_ARGS__)) -#undef glTextureBarrier -#define glTextureBarrier(...) GLCHECK(gl3wProcs.gl.TextureBarrier(__VA_ARGS__)) -#undef glTextureBuffer -#define glTextureBuffer(...) GLCHECK(gl3wProcs.gl.TextureBuffer(__VA_ARGS__)) -#undef glTextureBufferRange -#define glTextureBufferRange(...) GLCHECK(gl3wProcs.gl.TextureBufferRange(__VA_ARGS__)) -#undef glTextureParameterIiv -#define glTextureParameterIiv(...) GLCHECK(gl3wProcs.gl.TextureParameterIiv(__VA_ARGS__)) -#undef glTextureParameterIuiv -#define glTextureParameterIuiv(...) GLCHECK(gl3wProcs.gl.TextureParameterIuiv(__VA_ARGS__)) -#undef glTextureParameterf -#define glTextureParameterf(...) GLCHECK(gl3wProcs.gl.TextureParameterf(__VA_ARGS__)) -#undef glTextureParameterfv -#define glTextureParameterfv(...) GLCHECK(gl3wProcs.gl.TextureParameterfv(__VA_ARGS__)) -#undef glTextureParameteri -#define glTextureParameteri(...) GLCHECK(gl3wProcs.gl.TextureParameteri(__VA_ARGS__)) -#undef glTextureParameteriv -#define glTextureParameteriv(...) GLCHECK(gl3wProcs.gl.TextureParameteriv(__VA_ARGS__)) -#undef glTextureStorage1D -#define glTextureStorage1D(...) GLCHECK(gl3wProcs.gl.TextureStorage1D(__VA_ARGS__)) -#undef glTextureStorage2D -#define glTextureStorage2D(...) GLCHECK(gl3wProcs.gl.TextureStorage2D(__VA_ARGS__)) -#undef glTextureStorage2DMultisample -#define glTextureStorage2DMultisample(...) GLCHECK(gl3wProcs.gl.TextureStorage2DMultisample(__VA_ARGS__)) -#undef glTextureStorage3D -#define glTextureStorage3D(...) GLCHECK(gl3wProcs.gl.TextureStorage3D(__VA_ARGS__)) -#undef glTextureStorage3DMultisample -#define glTextureStorage3DMultisample(...) GLCHECK(gl3wProcs.gl.TextureStorage3DMultisample(__VA_ARGS__)) -#undef glTextureSubImage1D -#define glTextureSubImage1D(...) GLCHECK(gl3wProcs.gl.TextureSubImage1D(__VA_ARGS__)) -#undef glTextureSubImage2D -#define glTextureSubImage2D(...) GLCHECK(gl3wProcs.gl.TextureSubImage2D(__VA_ARGS__)) -#undef glTextureSubImage3D -#define glTextureSubImage3D(...) GLCHECK(gl3wProcs.gl.TextureSubImage3D(__VA_ARGS__)) -#undef glTextureView -#define glTextureView(...) GLCHECK(gl3wProcs.gl.TextureView(__VA_ARGS__)) -#undef glTransformFeedbackBufferBase -#define glTransformFeedbackBufferBase(...) GLCHECK(gl3wProcs.gl.TransformFeedbackBufferBase(__VA_ARGS__)) -#undef glTransformFeedbackBufferRange -#define glTransformFeedbackBufferRange(...) GLCHECK(gl3wProcs.gl.TransformFeedbackBufferRange(__VA_ARGS__)) -#undef glTransformFeedbackVaryings -#define glTransformFeedbackVaryings(...) GLCHECK(gl3wProcs.gl.TransformFeedbackVaryings(__VA_ARGS__)) -#undef glUniform1d -#define glUniform1d(...) GLCHECK(gl3wProcs.gl.Uniform1d(__VA_ARGS__)) -#undef glUniform1dv -#define glUniform1dv(...) GLCHECK(gl3wProcs.gl.Uniform1dv(__VA_ARGS__)) -#undef glUniform1f -#define glUniform1f(...) GLCHECK(gl3wProcs.gl.Uniform1f(__VA_ARGS__)) -#undef glUniform1fv -#define glUniform1fv(...) GLCHECK(gl3wProcs.gl.Uniform1fv(__VA_ARGS__)) -#undef glUniform1i -#define glUniform1i(...) GLCHECK(gl3wProcs.gl.Uniform1i(__VA_ARGS__)) -#undef glUniform1iv -#define glUniform1iv(...) GLCHECK(gl3wProcs.gl.Uniform1iv(__VA_ARGS__)) -#undef glUniform1ui -#define glUniform1ui(...) GLCHECK(gl3wProcs.gl.Uniform1ui(__VA_ARGS__)) -#undef glUniform1uiv -#define glUniform1uiv(...) GLCHECK(gl3wProcs.gl.Uniform1uiv(__VA_ARGS__)) -#undef glUniform2d -#define glUniform2d(...) GLCHECK(gl3wProcs.gl.Uniform2d(__VA_ARGS__)) -#undef glUniform2dv -#define glUniform2dv(...) GLCHECK(gl3wProcs.gl.Uniform2dv(__VA_ARGS__)) -#undef glUniform2f -#define glUniform2f(...) GLCHECK(gl3wProcs.gl.Uniform2f(__VA_ARGS__)) -#undef glUniform2fv -#define glUniform2fv(...) GLCHECK(gl3wProcs.gl.Uniform2fv(__VA_ARGS__)) -#undef glUniform2i -#define glUniform2i(...) GLCHECK(gl3wProcs.gl.Uniform2i(__VA_ARGS__)) -#undef glUniform2iv -#define glUniform2iv(...) GLCHECK(gl3wProcs.gl.Uniform2iv(__VA_ARGS__)) -#undef glUniform2ui -#define glUniform2ui(...) GLCHECK(gl3wProcs.gl.Uniform2ui(__VA_ARGS__)) -#undef glUniform2uiv -#define glUniform2uiv(...) GLCHECK(gl3wProcs.gl.Uniform2uiv(__VA_ARGS__)) -#undef glUniform3d -#define glUniform3d(...) GLCHECK(gl3wProcs.gl.Uniform3d(__VA_ARGS__)) -#undef glUniform3dv -#define glUniform3dv(...) GLCHECK(gl3wProcs.gl.Uniform3dv(__VA_ARGS__)) -#undef glUniform3f -#define glUniform3f(...) GLCHECK(gl3wProcs.gl.Uniform3f(__VA_ARGS__)) -#undef glUniform3fv -#define glUniform3fv(...) GLCHECK(gl3wProcs.gl.Uniform3fv(__VA_ARGS__)) -#undef glUniform3i -#define glUniform3i(...) GLCHECK(gl3wProcs.gl.Uniform3i(__VA_ARGS__)) -#undef glUniform3iv -#define glUniform3iv(...) GLCHECK(gl3wProcs.gl.Uniform3iv(__VA_ARGS__)) -#undef glUniform3ui -#define glUniform3ui(...) GLCHECK(gl3wProcs.gl.Uniform3ui(__VA_ARGS__)) -#undef glUniform3uiv -#define glUniform3uiv(...) GLCHECK(gl3wProcs.gl.Uniform3uiv(__VA_ARGS__)) -#undef glUniform4d -#define glUniform4d(...) GLCHECK(gl3wProcs.gl.Uniform4d(__VA_ARGS__)) -#undef glUniform4dv -#define glUniform4dv(...) GLCHECK(gl3wProcs.gl.Uniform4dv(__VA_ARGS__)) -#undef glUniform4f -#define glUniform4f(...) GLCHECK(gl3wProcs.gl.Uniform4f(__VA_ARGS__)) -#undef glUniform4fv -#define glUniform4fv(...) GLCHECK(gl3wProcs.gl.Uniform4fv(__VA_ARGS__)) -#undef glUniform4i -#define glUniform4i(...) GLCHECK(gl3wProcs.gl.Uniform4i(__VA_ARGS__)) -#undef glUniform4iv -#define glUniform4iv(...) GLCHECK(gl3wProcs.gl.Uniform4iv(__VA_ARGS__)) -#undef glUniform4ui -#define glUniform4ui(...) GLCHECK(gl3wProcs.gl.Uniform4ui(__VA_ARGS__)) -#undef glUniform4uiv -#define glUniform4uiv(...) GLCHECK(gl3wProcs.gl.Uniform4uiv(__VA_ARGS__)) -#undef glUniformBlockBinding -#define glUniformBlockBinding(...) GLCHECK(gl3wProcs.gl.UniformBlockBinding(__VA_ARGS__)) -#undef glUniformHandleui64ARB -#define glUniformHandleui64ARB(...) GLCHECK(gl3wProcs.gl.UniformHandleui64ARB(__VA_ARGS__)) -#undef glUniformHandleui64vARB -#define glUniformHandleui64vARB(...) GLCHECK(gl3wProcs.gl.UniformHandleui64vARB(__VA_ARGS__)) -#undef glUniformMatrix2dv -#define glUniformMatrix2dv(...) GLCHECK(gl3wProcs.gl.UniformMatrix2dv(__VA_ARGS__)) -#undef glUniformMatrix2fv -#define glUniformMatrix2fv(...) GLCHECK(gl3wProcs.gl.UniformMatrix2fv(__VA_ARGS__)) -#undef glUniformMatrix2x3dv -#define glUniformMatrix2x3dv(...) GLCHECK(gl3wProcs.gl.UniformMatrix2x3dv(__VA_ARGS__)) -#undef glUniformMatrix2x3fv -#define glUniformMatrix2x3fv(...) GLCHECK(gl3wProcs.gl.UniformMatrix2x3fv(__VA_ARGS__)) -#undef glUniformMatrix2x4dv -#define glUniformMatrix2x4dv(...) GLCHECK(gl3wProcs.gl.UniformMatrix2x4dv(__VA_ARGS__)) -#undef glUniformMatrix2x4fv -#define glUniformMatrix2x4fv(...) GLCHECK(gl3wProcs.gl.UniformMatrix2x4fv(__VA_ARGS__)) -#undef glUniformMatrix3dv -#define glUniformMatrix3dv(...) GLCHECK(gl3wProcs.gl.UniformMatrix3dv(__VA_ARGS__)) -#undef glUniformMatrix3fv -#define glUniformMatrix3fv(...) GLCHECK(gl3wProcs.gl.UniformMatrix3fv(__VA_ARGS__)) -#undef glUniformMatrix3x2dv -#define glUniformMatrix3x2dv(...) GLCHECK(gl3wProcs.gl.UniformMatrix3x2dv(__VA_ARGS__)) -#undef glUniformMatrix3x2fv -#define glUniformMatrix3x2fv(...) GLCHECK(gl3wProcs.gl.UniformMatrix3x2fv(__VA_ARGS__)) -#undef glUniformMatrix3x4dv -#define glUniformMatrix3x4dv(...) GLCHECK(gl3wProcs.gl.UniformMatrix3x4dv(__VA_ARGS__)) -#undef glUniformMatrix3x4fv -#define glUniformMatrix3x4fv(...) GLCHECK(gl3wProcs.gl.UniformMatrix3x4fv(__VA_ARGS__)) -#undef glUniformMatrix4dv -#define glUniformMatrix4dv(...) GLCHECK(gl3wProcs.gl.UniformMatrix4dv(__VA_ARGS__)) -#undef glUniformMatrix4fv -#define glUniformMatrix4fv(...) GLCHECK(gl3wProcs.gl.UniformMatrix4fv(__VA_ARGS__)) -#undef glUniformMatrix4x2dv -#define glUniformMatrix4x2dv(...) GLCHECK(gl3wProcs.gl.UniformMatrix4x2dv(__VA_ARGS__)) -#undef glUniformMatrix4x2fv -#define glUniformMatrix4x2fv(...) GLCHECK(gl3wProcs.gl.UniformMatrix4x2fv(__VA_ARGS__)) -#undef glUniformMatrix4x3dv -#define glUniformMatrix4x3dv(...) GLCHECK(gl3wProcs.gl.UniformMatrix4x3dv(__VA_ARGS__)) -#undef glUniformMatrix4x3fv -#define glUniformMatrix4x3fv(...) GLCHECK(gl3wProcs.gl.UniformMatrix4x3fv(__VA_ARGS__)) -#undef glUniformSubroutinesuiv -#define glUniformSubroutinesuiv(...) GLCHECK(gl3wProcs.gl.UniformSubroutinesuiv(__VA_ARGS__)) -#undef glUnmapBuffer -#define glUnmapBuffer(...) GLCHECK(gl3wProcs.gl.UnmapBuffer(__VA_ARGS__)) -#undef glUnmapNamedBuffer -#define glUnmapNamedBuffer(...) GLCHECK(gl3wProcs.gl.UnmapNamedBuffer(__VA_ARGS__)) -#undef glUseProgram -#define glUseProgram(...) GLCHECK(gl3wProcs.gl.UseProgram(__VA_ARGS__)) -#undef glUseProgramStages -#define glUseProgramStages(...) GLCHECK(gl3wProcs.gl.UseProgramStages(__VA_ARGS__)) -#undef glValidateProgram -#define glValidateProgram(...) GLCHECK(gl3wProcs.gl.ValidateProgram(__VA_ARGS__)) -#undef glValidateProgramPipeline -#define glValidateProgramPipeline(...) GLCHECK(gl3wProcs.gl.ValidateProgramPipeline(__VA_ARGS__)) -#undef glVertexArrayAttribBinding -#define glVertexArrayAttribBinding(...) GLCHECK(gl3wProcs.gl.VertexArrayAttribBinding(__VA_ARGS__)) -#undef glVertexArrayAttribFormat -#define glVertexArrayAttribFormat(...) GLCHECK(gl3wProcs.gl.VertexArrayAttribFormat(__VA_ARGS__)) -#undef glVertexArrayAttribIFormat -#define glVertexArrayAttribIFormat(...) GLCHECK(gl3wProcs.gl.VertexArrayAttribIFormat(__VA_ARGS__)) -#undef glVertexArrayAttribLFormat -#define glVertexArrayAttribLFormat(...) GLCHECK(gl3wProcs.gl.VertexArrayAttribLFormat(__VA_ARGS__)) -#undef glVertexArrayBindingDivisor -#define glVertexArrayBindingDivisor(...) GLCHECK(gl3wProcs.gl.VertexArrayBindingDivisor(__VA_ARGS__)) -#undef glVertexArrayElementBuffer -#define glVertexArrayElementBuffer(...) GLCHECK(gl3wProcs.gl.VertexArrayElementBuffer(__VA_ARGS__)) -#undef glVertexArrayVertexBuffer -#define glVertexArrayVertexBuffer(...) GLCHECK(gl3wProcs.gl.VertexArrayVertexBuffer(__VA_ARGS__)) -#undef glVertexArrayVertexBuffers -#define glVertexArrayVertexBuffers(...) GLCHECK(gl3wProcs.gl.VertexArrayVertexBuffers(__VA_ARGS__)) -#undef glVertexAttrib1d -#define glVertexAttrib1d(...) GLCHECK(gl3wProcs.gl.VertexAttrib1d(__VA_ARGS__)) -#undef glVertexAttrib1dv -#define glVertexAttrib1dv(...) GLCHECK(gl3wProcs.gl.VertexAttrib1dv(__VA_ARGS__)) -#undef glVertexAttrib1f -#define glVertexAttrib1f(...) GLCHECK(gl3wProcs.gl.VertexAttrib1f(__VA_ARGS__)) -#undef glVertexAttrib1fv -#define glVertexAttrib1fv(...) GLCHECK(gl3wProcs.gl.VertexAttrib1fv(__VA_ARGS__)) -#undef glVertexAttrib1s -#define glVertexAttrib1s(...) GLCHECK(gl3wProcs.gl.VertexAttrib1s(__VA_ARGS__)) -#undef glVertexAttrib1sv -#define glVertexAttrib1sv(...) GLCHECK(gl3wProcs.gl.VertexAttrib1sv(__VA_ARGS__)) -#undef glVertexAttrib2d -#define glVertexAttrib2d(...) GLCHECK(gl3wProcs.gl.VertexAttrib2d(__VA_ARGS__)) -#undef glVertexAttrib2dv -#define glVertexAttrib2dv(...) GLCHECK(gl3wProcs.gl.VertexAttrib2dv(__VA_ARGS__)) -#undef glVertexAttrib2f -#define glVertexAttrib2f(...) GLCHECK(gl3wProcs.gl.VertexAttrib2f(__VA_ARGS__)) -#undef glVertexAttrib2fv -#define glVertexAttrib2fv(...) GLCHECK(gl3wProcs.gl.VertexAttrib2fv(__VA_ARGS__)) -#undef glVertexAttrib2s -#define glVertexAttrib2s(...) GLCHECK(gl3wProcs.gl.VertexAttrib2s(__VA_ARGS__)) -#undef glVertexAttrib2sv -#define glVertexAttrib2sv(...) GLCHECK(gl3wProcs.gl.VertexAttrib2sv(__VA_ARGS__)) -#undef glVertexAttrib3d -#define glVertexAttrib3d(...) GLCHECK(gl3wProcs.gl.VertexAttrib3d(__VA_ARGS__)) -#undef glVertexAttrib3dv -#define glVertexAttrib3dv(...) GLCHECK(gl3wProcs.gl.VertexAttrib3dv(__VA_ARGS__)) -#undef glVertexAttrib3f -#define glVertexAttrib3f(...) GLCHECK(gl3wProcs.gl.VertexAttrib3f(__VA_ARGS__)) -#undef glVertexAttrib3fv -#define glVertexAttrib3fv(...) GLCHECK(gl3wProcs.gl.VertexAttrib3fv(__VA_ARGS__)) -#undef glVertexAttrib3s -#define glVertexAttrib3s(...) GLCHECK(gl3wProcs.gl.VertexAttrib3s(__VA_ARGS__)) -#undef glVertexAttrib3sv -#define glVertexAttrib3sv(...) GLCHECK(gl3wProcs.gl.VertexAttrib3sv(__VA_ARGS__)) -#undef glVertexAttrib4Nbv -#define glVertexAttrib4Nbv(...) GLCHECK(gl3wProcs.gl.VertexAttrib4Nbv(__VA_ARGS__)) -#undef glVertexAttrib4Niv -#define glVertexAttrib4Niv(...) GLCHECK(gl3wProcs.gl.VertexAttrib4Niv(__VA_ARGS__)) -#undef glVertexAttrib4Nsv -#define glVertexAttrib4Nsv(...) GLCHECK(gl3wProcs.gl.VertexAttrib4Nsv(__VA_ARGS__)) -#undef glVertexAttrib4Nub -#define glVertexAttrib4Nub(...) GLCHECK(gl3wProcs.gl.VertexAttrib4Nub(__VA_ARGS__)) -#undef glVertexAttrib4Nubv -#define glVertexAttrib4Nubv(...) GLCHECK(gl3wProcs.gl.VertexAttrib4Nubv(__VA_ARGS__)) -#undef glVertexAttrib4Nuiv -#define glVertexAttrib4Nuiv(...) GLCHECK(gl3wProcs.gl.VertexAttrib4Nuiv(__VA_ARGS__)) -#undef glVertexAttrib4Nusv -#define glVertexAttrib4Nusv(...) GLCHECK(gl3wProcs.gl.VertexAttrib4Nusv(__VA_ARGS__)) -#undef glVertexAttrib4bv -#define glVertexAttrib4bv(...) GLCHECK(gl3wProcs.gl.VertexAttrib4bv(__VA_ARGS__)) -#undef glVertexAttrib4d -#define glVertexAttrib4d(...) GLCHECK(gl3wProcs.gl.VertexAttrib4d(__VA_ARGS__)) -#undef glVertexAttrib4dv -#define glVertexAttrib4dv(...) GLCHECK(gl3wProcs.gl.VertexAttrib4dv(__VA_ARGS__)) -#undef glVertexAttrib4f -#define glVertexAttrib4f(...) GLCHECK(gl3wProcs.gl.VertexAttrib4f(__VA_ARGS__)) -#undef glVertexAttrib4fv -#define glVertexAttrib4fv(...) GLCHECK(gl3wProcs.gl.VertexAttrib4fv(__VA_ARGS__)) -#undef glVertexAttrib4iv -#define glVertexAttrib4iv(...) GLCHECK(gl3wProcs.gl.VertexAttrib4iv(__VA_ARGS__)) -#undef glVertexAttrib4s -#define glVertexAttrib4s(...) GLCHECK(gl3wProcs.gl.VertexAttrib4s(__VA_ARGS__)) -#undef glVertexAttrib4sv -#define glVertexAttrib4sv(...) GLCHECK(gl3wProcs.gl.VertexAttrib4sv(__VA_ARGS__)) -#undef glVertexAttrib4ubv -#define glVertexAttrib4ubv(...) GLCHECK(gl3wProcs.gl.VertexAttrib4ubv(__VA_ARGS__)) -#undef glVertexAttrib4uiv -#define glVertexAttrib4uiv(...) GLCHECK(gl3wProcs.gl.VertexAttrib4uiv(__VA_ARGS__)) -#undef glVertexAttrib4usv -#define glVertexAttrib4usv(...) GLCHECK(gl3wProcs.gl.VertexAttrib4usv(__VA_ARGS__)) -#undef glVertexAttribBinding -#define glVertexAttribBinding(...) GLCHECK(gl3wProcs.gl.VertexAttribBinding(__VA_ARGS__)) -#undef glVertexAttribDivisor -#define glVertexAttribDivisor(...) GLCHECK(gl3wProcs.gl.VertexAttribDivisor(__VA_ARGS__)) -#undef glVertexAttribFormat -#define glVertexAttribFormat(...) GLCHECK(gl3wProcs.gl.VertexAttribFormat(__VA_ARGS__)) -#undef glVertexAttribI1i -#define glVertexAttribI1i(...) GLCHECK(gl3wProcs.gl.VertexAttribI1i(__VA_ARGS__)) -#undef glVertexAttribI1iv -#define glVertexAttribI1iv(...) GLCHECK(gl3wProcs.gl.VertexAttribI1iv(__VA_ARGS__)) -#undef glVertexAttribI1ui -#define glVertexAttribI1ui(...) GLCHECK(gl3wProcs.gl.VertexAttribI1ui(__VA_ARGS__)) -#undef glVertexAttribI1uiv -#define glVertexAttribI1uiv(...) GLCHECK(gl3wProcs.gl.VertexAttribI1uiv(__VA_ARGS__)) -#undef glVertexAttribI2i -#define glVertexAttribI2i(...) GLCHECK(gl3wProcs.gl.VertexAttribI2i(__VA_ARGS__)) -#undef glVertexAttribI2iv -#define glVertexAttribI2iv(...) GLCHECK(gl3wProcs.gl.VertexAttribI2iv(__VA_ARGS__)) -#undef glVertexAttribI2ui -#define glVertexAttribI2ui(...) GLCHECK(gl3wProcs.gl.VertexAttribI2ui(__VA_ARGS__)) -#undef glVertexAttribI2uiv -#define glVertexAttribI2uiv(...) GLCHECK(gl3wProcs.gl.VertexAttribI2uiv(__VA_ARGS__)) -#undef glVertexAttribI3i -#define glVertexAttribI3i(...) GLCHECK(gl3wProcs.gl.VertexAttribI3i(__VA_ARGS__)) -#undef glVertexAttribI3iv -#define glVertexAttribI3iv(...) GLCHECK(gl3wProcs.gl.VertexAttribI3iv(__VA_ARGS__)) -#undef glVertexAttribI3ui -#define glVertexAttribI3ui(...) GLCHECK(gl3wProcs.gl.VertexAttribI3ui(__VA_ARGS__)) -#undef glVertexAttribI3uiv -#define glVertexAttribI3uiv(...) GLCHECK(gl3wProcs.gl.VertexAttribI3uiv(__VA_ARGS__)) -#undef glVertexAttribI4bv -#define glVertexAttribI4bv(...) GLCHECK(gl3wProcs.gl.VertexAttribI4bv(__VA_ARGS__)) -#undef glVertexAttribI4i -#define glVertexAttribI4i(...) GLCHECK(gl3wProcs.gl.VertexAttribI4i(__VA_ARGS__)) -#undef glVertexAttribI4iv -#define glVertexAttribI4iv(...) GLCHECK(gl3wProcs.gl.VertexAttribI4iv(__VA_ARGS__)) -#undef glVertexAttribI4sv -#define glVertexAttribI4sv(...) GLCHECK(gl3wProcs.gl.VertexAttribI4sv(__VA_ARGS__)) -#undef glVertexAttribI4ubv -#define glVertexAttribI4ubv(...) GLCHECK(gl3wProcs.gl.VertexAttribI4ubv(__VA_ARGS__)) -#undef glVertexAttribI4ui -#define glVertexAttribI4ui(...) GLCHECK(gl3wProcs.gl.VertexAttribI4ui(__VA_ARGS__)) -#undef glVertexAttribI4uiv -#define glVertexAttribI4uiv(...) GLCHECK(gl3wProcs.gl.VertexAttribI4uiv(__VA_ARGS__)) -#undef glVertexAttribI4usv -#define glVertexAttribI4usv(...) GLCHECK(gl3wProcs.gl.VertexAttribI4usv(__VA_ARGS__)) -#undef glVertexAttribIFormat -#define glVertexAttribIFormat(...) GLCHECK(gl3wProcs.gl.VertexAttribIFormat(__VA_ARGS__)) -#undef glVertexAttribIPointer -#define glVertexAttribIPointer(...) GLCHECK(gl3wProcs.gl.VertexAttribIPointer(__VA_ARGS__)) -#undef glVertexAttribL1d -#define glVertexAttribL1d(...) GLCHECK(gl3wProcs.gl.VertexAttribL1d(__VA_ARGS__)) -#undef glVertexAttribL1dv -#define glVertexAttribL1dv(...) GLCHECK(gl3wProcs.gl.VertexAttribL1dv(__VA_ARGS__)) -#undef glVertexAttribL1ui64ARB -#define glVertexAttribL1ui64ARB(...) GLCHECK(gl3wProcs.gl.VertexAttribL1ui64ARB(__VA_ARGS__)) -#undef glVertexAttribL1ui64vARB -#define glVertexAttribL1ui64vARB(...) GLCHECK(gl3wProcs.gl.VertexAttribL1ui64vARB(__VA_ARGS__)) -#undef glVertexAttribL2d -#define glVertexAttribL2d(...) GLCHECK(gl3wProcs.gl.VertexAttribL2d(__VA_ARGS__)) -#undef glVertexAttribL2dv -#define glVertexAttribL2dv(...) GLCHECK(gl3wProcs.gl.VertexAttribL2dv(__VA_ARGS__)) -#undef glVertexAttribL3d -#define glVertexAttribL3d(...) GLCHECK(gl3wProcs.gl.VertexAttribL3d(__VA_ARGS__)) -#undef glVertexAttribL3dv -#define glVertexAttribL3dv(...) GLCHECK(gl3wProcs.gl.VertexAttribL3dv(__VA_ARGS__)) -#undef glVertexAttribL4d -#define glVertexAttribL4d(...) GLCHECK(gl3wProcs.gl.VertexAttribL4d(__VA_ARGS__)) -#undef glVertexAttribL4dv -#define glVertexAttribL4dv(...) GLCHECK(gl3wProcs.gl.VertexAttribL4dv(__VA_ARGS__)) -#undef glVertexAttribLFormat -#define glVertexAttribLFormat(...) GLCHECK(gl3wProcs.gl.VertexAttribLFormat(__VA_ARGS__)) -#undef glVertexAttribLPointer -#define glVertexAttribLPointer(...) GLCHECK(gl3wProcs.gl.VertexAttribLPointer(__VA_ARGS__)) -#undef glVertexAttribP1ui -#define glVertexAttribP1ui(...) GLCHECK(gl3wProcs.gl.VertexAttribP1ui(__VA_ARGS__)) -#undef glVertexAttribP1uiv -#define glVertexAttribP1uiv(...) GLCHECK(gl3wProcs.gl.VertexAttribP1uiv(__VA_ARGS__)) -#undef glVertexAttribP2ui -#define glVertexAttribP2ui(...) GLCHECK(gl3wProcs.gl.VertexAttribP2ui(__VA_ARGS__)) -#undef glVertexAttribP2uiv -#define glVertexAttribP2uiv(...) GLCHECK(gl3wProcs.gl.VertexAttribP2uiv(__VA_ARGS__)) -#undef glVertexAttribP3ui -#define glVertexAttribP3ui(...) GLCHECK(gl3wProcs.gl.VertexAttribP3ui(__VA_ARGS__)) -#undef glVertexAttribP3uiv -#define glVertexAttribP3uiv(...) GLCHECK(gl3wProcs.gl.VertexAttribP3uiv(__VA_ARGS__)) -#undef glVertexAttribP4ui -#define glVertexAttribP4ui(...) GLCHECK(gl3wProcs.gl.VertexAttribP4ui(__VA_ARGS__)) -#undef glVertexAttribP4uiv -#define glVertexAttribP4uiv(...) GLCHECK(gl3wProcs.gl.VertexAttribP4uiv(__VA_ARGS__)) -#undef glVertexAttribPointer -#define glVertexAttribPointer(...) GLCHECK(gl3wProcs.gl.VertexAttribPointer(__VA_ARGS__)) -#undef glVertexBindingDivisor -#define glVertexBindingDivisor(...) GLCHECK(gl3wProcs.gl.VertexBindingDivisor(__VA_ARGS__)) -#undef glViewport -#define glViewport(...) GLCHECK(gl3wProcs.gl.Viewport(__VA_ARGS__)) -#undef glViewportArrayv -#define glViewportArrayv(...) GLCHECK(gl3wProcs.gl.ViewportArrayv(__VA_ARGS__)) -#undef glViewportIndexedf -#define glViewportIndexedf(...) GLCHECK(gl3wProcs.gl.ViewportIndexedf(__VA_ARGS__)) -#undef glViewportIndexedfv -#define glViewportIndexedfv(...) GLCHECK(gl3wProcs.gl.ViewportIndexedfv(__VA_ARGS__)) -#undef glWaitSync -#define glWaitSync(...) GLCHECK(gl3wProcs.gl.WaitSync(__VA_ARGS__)) - -#endif diff --git a/msvc/source/opengl/opengl_hooks.cpp b/msvc/source/opengl/opengl_hooks.cpp deleted file mode 100644 index 4f7ca89..0000000 --- a/msvc/source/opengl/opengl_hooks.cpp +++ /dev/null @@ -1,2163 +0,0 @@ -/** - * Copyright (C) 2014 Patrick Mours. All rights reserved. - * License: https://github.com/crosire/reshade#license - */ - -#include "log.hpp" -#include "hook_manager.hpp" -#include "runtime_opengl.hpp" -#include "opengl_hooks.hpp" -#include - -extern thread_local reshade::opengl::runtime_opengl *g_current_runtime; - -HOOK_EXPORT void WINAPI glAccum(GLenum op, GLfloat value) -{ - static const auto trampoline = reshade::hooks::call(glAccum); - trampoline(op, value); -} - -HOOK_EXPORT void WINAPI glAlphaFunc(GLenum func, GLclampf ref) -{ - static const auto trampoline = reshade::hooks::call(glAlphaFunc); - trampoline(func, ref); -} - -HOOK_EXPORT GLboolean WINAPI glAreTexturesResident(GLsizei n, const GLuint *textures, GLboolean *residences) -{ - static const auto trampoline = reshade::hooks::call(glAreTexturesResident); - return trampoline(n, textures, residences); -} - -HOOK_EXPORT void WINAPI glArrayElement(GLint i) -{ - static const auto trampoline = reshade::hooks::call(glArrayElement); - trampoline(i); -} - -HOOK_EXPORT void WINAPI glBegin(GLenum mode) -{ - if (g_current_runtime) g_current_runtime->_current_vertex_count = 0; - - static const auto trampoline = reshade::hooks::call(glBegin); - trampoline(mode); -} - -HOOK_EXPORT void WINAPI glBindTexture(GLenum target, GLuint texture) -{ - static const auto trampoline = reshade::hooks::call(glBindTexture); - trampoline(target, texture); -} - -HOOK_EXPORT void WINAPI glBitmap(GLsizei width, GLsizei height, GLfloat xorig, GLfloat yorig, GLfloat xmove, GLfloat ymove, const GLubyte *bitmap) -{ - static const auto trampoline = reshade::hooks::call(glBitmap); - trampoline(width, height, xorig, yorig, xmove, ymove, bitmap); -} - -HOOK_EXPORT void WINAPI glBlendFunc(GLenum sfactor, GLenum dfactor) -{ - static const auto trampoline = reshade::hooks::call(glBlendFunc); - trampoline(sfactor, dfactor); -} - -HOOK_EXPORT void WINAPI glCallList(GLuint list) -{ - static const auto trampoline = reshade::hooks::call(glCallList); - trampoline(list); -} -HOOK_EXPORT void WINAPI glCallLists(GLsizei n, GLenum type, const GLvoid *lists) -{ - static const auto trampoline = reshade::hooks::call(glCallLists); - trampoline(n, type, lists); -} - -HOOK_EXPORT void WINAPI glClear(GLbitfield mask) -{ - static const auto trampoline = reshade::hooks::call(glClear); - trampoline(mask); -} -HOOK_EXPORT void WINAPI glClearAccum(GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha) -{ - static const auto trampoline = reshade::hooks::call(glClearAccum); - trampoline(red, green, blue, alpha); -} -HOOK_EXPORT void WINAPI glClearColor(GLclampf red, GLclampf green, GLclampf blue, GLclampf alpha) -{ - static const auto trampoline = reshade::hooks::call(glClearColor); - trampoline(red, green, blue, alpha); -} -HOOK_EXPORT void WINAPI glClearDepth(GLclampd depth) -{ - static const auto trampoline = reshade::hooks::call(glClearDepth); - trampoline(depth); -} -HOOK_EXPORT void WINAPI glClearIndex(GLfloat c) -{ - static const auto trampoline = reshade::hooks::call(glClearIndex); - trampoline(c); -} -HOOK_EXPORT void WINAPI glClearStencil(GLint s) -{ - static const auto trampoline = reshade::hooks::call(glClearStencil); - trampoline(s); -} - -HOOK_EXPORT void WINAPI glClipPlane(GLenum plane, const GLdouble *equation) -{ - static const auto trampoline = reshade::hooks::call(glClipPlane); - trampoline(plane, equation); -} - -HOOK_EXPORT void WINAPI glColor3b(GLbyte red, GLbyte green, GLbyte blue) -{ - static const auto trampoline = reshade::hooks::call(glColor3b); - trampoline(red, green, blue); -} -HOOK_EXPORT void WINAPI glColor3bv(const GLbyte *v) -{ - static const auto trampoline = reshade::hooks::call(glColor3bv); - trampoline(v); -} -HOOK_EXPORT void WINAPI glColor3d(GLdouble red, GLdouble green, GLdouble blue) -{ - static const auto trampoline = reshade::hooks::call(glColor3d); - trampoline(red, green, blue); -} -HOOK_EXPORT void WINAPI glColor3dv(const GLdouble *v) -{ - static const auto trampoline = reshade::hooks::call(glColor3dv); - trampoline(v); -} -HOOK_EXPORT void WINAPI glColor3f(GLfloat red, GLfloat green, GLfloat blue) -{ - static const auto trampoline = reshade::hooks::call(glColor3f); - trampoline(red, green, blue); -} -HOOK_EXPORT void WINAPI glColor3fv(const GLfloat *v) -{ - static const auto trampoline = reshade::hooks::call(glColor3fv); - trampoline(v); -} -HOOK_EXPORT void WINAPI glColor3i(GLint red, GLint green, GLint blue) -{ - static const auto trampoline = reshade::hooks::call(glColor3i); - trampoline(red, green, blue); -} -HOOK_EXPORT void WINAPI glColor3iv(const GLint *v) -{ - static const auto trampoline = reshade::hooks::call(glColor3iv); - trampoline(v); -} -HOOK_EXPORT void WINAPI glColor3s(GLshort red, GLshort green, GLshort blue) -{ - static const auto trampoline = reshade::hooks::call(glColor3s); - trampoline(red, green, blue); -} -HOOK_EXPORT void WINAPI glColor3sv(const GLshort *v) -{ - static const auto trampoline = reshade::hooks::call(glColor3sv); - trampoline(v); -} -HOOK_EXPORT void WINAPI glColor3ub(GLubyte red, GLubyte green, GLubyte blue) -{ - static const auto trampoline = reshade::hooks::call(glColor3ub); - trampoline(red, green, blue); -} -HOOK_EXPORT void WINAPI glColor3ubv(const GLubyte *v) -{ - static const auto trampoline = reshade::hooks::call(glColor3ubv); - trampoline(v); -} -HOOK_EXPORT void WINAPI glColor3ui(GLuint red, GLuint green, GLuint blue) -{ - static const auto trampoline = reshade::hooks::call(glColor3ui); - trampoline(red, green, blue); -} -HOOK_EXPORT void WINAPI glColor3uiv(const GLuint *v) -{ - static const auto trampoline = reshade::hooks::call(glColor3uiv); - trampoline(v); -} -HOOK_EXPORT void WINAPI glColor3us(GLushort red, GLushort green, GLushort blue) -{ - static const auto trampoline = reshade::hooks::call(glColor3us); - trampoline(red, green, blue); -} -HOOK_EXPORT void WINAPI glColor3usv(const GLushort *v) -{ - static const auto trampoline = reshade::hooks::call(glColor3usv); - trampoline(v); -} -HOOK_EXPORT void WINAPI glColor4b(GLbyte red, GLbyte green, GLbyte blue, GLbyte alpha) -{ - static const auto trampoline = reshade::hooks::call(glColor4b); - trampoline(red, green, blue, alpha); -} -HOOK_EXPORT void WINAPI glColor4bv(const GLbyte *v) -{ - static const auto trampoline = reshade::hooks::call(glColor4bv); - trampoline(v); -} -HOOK_EXPORT void WINAPI glColor4d(GLdouble red, GLdouble green, GLdouble blue, GLdouble alpha) -{ - static const auto trampoline = reshade::hooks::call(glColor4d); - trampoline(red, green, blue, alpha); -} -HOOK_EXPORT void WINAPI glColor4dv(const GLdouble *v) -{ - static const auto trampoline = reshade::hooks::call(glColor4dv); - trampoline(v); -} -HOOK_EXPORT void WINAPI glColor4f(GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha) -{ - static const auto trampoline = reshade::hooks::call(glColor4f); - trampoline(red, green, blue, alpha); -} -HOOK_EXPORT void WINAPI glColor4fv(const GLfloat *v) -{ - static const auto trampoline = reshade::hooks::call(glColor4fv); - trampoline(v); -} -HOOK_EXPORT void WINAPI glColor4i(GLint red, GLint green, GLint blue, GLint alpha) -{ - static const auto trampoline = reshade::hooks::call(glColor4i); - trampoline(red, green, blue, alpha); -} -HOOK_EXPORT void WINAPI glColor4iv(const GLint *v) -{ - static const auto trampoline = reshade::hooks::call(glColor4iv); - trampoline(v); -} -HOOK_EXPORT void WINAPI glColor4s(GLshort red, GLshort green, GLshort blue, GLshort alpha) -{ - static const auto trampoline = reshade::hooks::call(glColor4s); - trampoline(red, green, blue, alpha); -} -HOOK_EXPORT void WINAPI glColor4sv(const GLshort *v) -{ - static const auto trampoline = reshade::hooks::call(glColor4sv); - trampoline(v); -} -HOOK_EXPORT void WINAPI glColor4ub(GLubyte red, GLubyte green, GLubyte blue, GLubyte alpha) -{ - static const auto trampoline = reshade::hooks::call(glColor4ub); - trampoline(red, green, blue, alpha); -} -HOOK_EXPORT void WINAPI glColor4ubv(const GLubyte *v) -{ - static const auto trampoline = reshade::hooks::call(glColor4ubv); - trampoline(v); -} -HOOK_EXPORT void WINAPI glColor4ui(GLuint red, GLuint green, GLuint blue, GLuint alpha) -{ - static const auto trampoline = reshade::hooks::call(glColor4ui); - trampoline(red, green, blue, alpha); -} -HOOK_EXPORT void WINAPI glColor4uiv(const GLuint *v) -{ - static const auto trampoline = reshade::hooks::call(glColor4uiv); - trampoline(v); -} -HOOK_EXPORT void WINAPI glColor4us(GLushort red, GLushort green, GLushort blue, GLushort alpha) -{ - static const auto trampoline = reshade::hooks::call(glColor4us); - trampoline(red, green, blue, alpha); -} -HOOK_EXPORT void WINAPI glColor4usv(const GLushort *v) -{ - static const auto trampoline = reshade::hooks::call(glColor4usv); - trampoline(v); -} - -HOOK_EXPORT void WINAPI glColorMask(GLboolean red, GLboolean green, GLboolean blue, GLboolean alpha) -{ - static const auto trampoline = reshade::hooks::call(glColorMask); - trampoline(red, green, blue, alpha); -} - -HOOK_EXPORT void WINAPI glColorMaterial(GLenum face, GLenum mode) -{ - static const auto trampoline = reshade::hooks::call(glColorMaterial); - trampoline(face, mode); -} - -HOOK_EXPORT void WINAPI glColorPointer(GLint size, GLenum type, GLsizei stride, const GLvoid *pointer) -{ - static const auto trampoline = reshade::hooks::call(glColorPointer); - trampoline(size, type, stride, pointer); -} - -HOOK_EXPORT void WINAPI glCopyPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum type) -{ - static const auto trampoline = reshade::hooks::call(glCopyPixels); - trampoline(x, y, width, height, type); -} - -HOOK_EXPORT void WINAPI glCopyTexImage1D(GLenum target, GLint level, GLenum internalFormat, GLint x, GLint y, GLsizei width, GLint border) -{ - static const auto trampoline = reshade::hooks::call(glCopyTexImage1D); - trampoline(target, level, internalFormat, x, y, width, border); -} -HOOK_EXPORT void WINAPI glCopyTexImage2D(GLenum target, GLint level, GLenum internalFormat, GLint x, GLint y, GLsizei width, GLsizei height, GLint border) -{ - static const auto trampoline = reshade::hooks::call(glCopyTexImage2D); - trampoline(target, level, internalFormat, x, y, width, height, border); -} -HOOK_EXPORT void WINAPI glCopyTexSubImage1D(GLenum target, GLint level, GLint xoffset, GLint x, GLint y, GLsizei width) -{ - static const auto trampoline = reshade::hooks::call(glCopyTexSubImage1D); - trampoline(target, level, xoffset, x, y, width); -} -HOOK_EXPORT void WINAPI glCopyTexSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height) -{ - static const auto trampoline = reshade::hooks::call(glCopyTexSubImage2D); - trampoline(target, level, xoffset, yoffset, x, y, width, height); -} - -HOOK_EXPORT void WINAPI glCullFace(GLenum mode) -{ - static const auto trampoline = reshade::hooks::call(glCullFace); - trampoline(mode); -} - -HOOK_EXPORT void WINAPI glDeleteLists(GLuint list, GLsizei range) -{ - static const auto trampoline = reshade::hooks::call(glDeleteLists); - trampoline(list, range); -} - -HOOK_EXPORT void WINAPI glDeleteTextures(GLsizei n, const GLuint *textures) -{ - static const auto trampoline = reshade::hooks::call(glDeleteTextures); - trampoline(n, textures); -} - -HOOK_EXPORT void WINAPI glDepthFunc(GLenum func) -{ - static const auto trampoline = reshade::hooks::call(glDepthFunc); - trampoline(func); -} -HOOK_EXPORT void WINAPI glDepthMask(GLboolean flag) -{ - static const auto trampoline = reshade::hooks::call(glDepthMask); - trampoline(flag); -} -HOOK_EXPORT void WINAPI glDepthRange(GLclampd zNear, GLclampd zFar) -{ - static const auto trampoline = reshade::hooks::call(glDepthRange); - trampoline(zNear, zFar); -} - -HOOK_EXPORT void WINAPI glDisable(GLenum cap) -{ - static const auto trampoline = reshade::hooks::call(glDisable); - trampoline(cap); -} -HOOK_EXPORT void WINAPI glDisableClientState(GLenum array) -{ - static const auto trampoline = reshade::hooks::call(glDisableClientState); - trampoline(array); -} - -HOOK_EXPORT void WINAPI glDrawArrays(GLenum mode, GLint first, GLsizei count) -{ - if (g_current_runtime) g_current_runtime->on_draw_call(count); - - static const auto trampoline = reshade::hooks::call(glDrawArrays); - trampoline(mode, first, count); -} -extern "C" void WINAPI glDrawArraysIndirect(GLenum mode, const GLvoid *indirect) -{ - static const auto trampoline = reshade::hooks::call(glDrawArraysIndirect); - trampoline(mode, indirect); -} -extern "C" void WINAPI glDrawArraysInstanced(GLenum mode, GLint first, GLsizei count, GLsizei primcount) -{ - if (g_current_runtime) g_current_runtime->on_draw_call(primcount * count); - - static const auto trampoline = reshade::hooks::call(glDrawArraysInstanced); - trampoline(mode, first, count, primcount); -} -extern "C" void WINAPI glDrawArraysInstancedARB(GLenum mode, GLint first, GLsizei count, GLsizei primcount) -{ - if (g_current_runtime) g_current_runtime->on_draw_call(primcount * count); - - static const auto trampoline = reshade::hooks::call(glDrawArraysInstancedARB); - trampoline(mode, first, count, primcount); -} -extern "C" void WINAPI glDrawArraysInstancedEXT(GLenum mode, GLint first, GLsizei count, GLsizei primcount) -{ - if (g_current_runtime) g_current_runtime->on_draw_call(primcount * count); - - static const auto trampoline = reshade::hooks::call(glDrawArraysInstancedEXT); - trampoline(mode, first, count, primcount); -} -extern "C" void WINAPI glDrawArraysInstancedBaseInstance(GLenum mode, GLint first, GLsizei count, GLsizei primcount, GLuint baseinstance) -{ - if (g_current_runtime) g_current_runtime->on_draw_call(primcount * count); - - static const auto trampoline = reshade::hooks::call(glDrawArraysInstancedBaseInstance); - trampoline(mode, first, count, primcount, baseinstance); -} - -HOOK_EXPORT void WINAPI glDrawBuffer(GLenum mode) -{ - static const auto trampoline = reshade::hooks::call(glDrawBuffer); - trampoline(mode); -} - -HOOK_EXPORT void WINAPI glDrawElements(GLenum mode, GLsizei count, GLenum type, const GLvoid *indices) -{ - if (g_current_runtime) g_current_runtime->on_draw_call(count); - - static const auto trampoline = reshade::hooks::call(glDrawElements); - trampoline(mode, count, type, indices); -} -extern "C" void WINAPI glDrawElementsBaseVertex(GLenum mode, GLsizei count, GLenum type, const GLvoid *indices, GLint basevertex) -{ - if (g_current_runtime) g_current_runtime->on_draw_call(count); - - static const auto trampoline = reshade::hooks::call(glDrawElementsBaseVertex); - trampoline(mode, count, type, indices, basevertex); -} -extern "C" void WINAPI glDrawElementsIndirect(GLenum mode, GLenum type, const GLvoid *indirect) -{ - static const auto trampoline = reshade::hooks::call(glDrawElementsIndirect); - trampoline(mode, type, indirect); -} -extern "C" void WINAPI glDrawElementsInstanced(GLenum mode, GLsizei count, GLenum type, const GLvoid *indices, GLsizei primcount) -{ - if (g_current_runtime) g_current_runtime->on_draw_call(primcount * count); - - static const auto trampoline = reshade::hooks::call(glDrawElementsInstanced); - trampoline(mode, count, type, indices, primcount); -} -extern "C" void WINAPI glDrawElementsInstancedARB(GLenum mode, GLsizei count, GLenum type, const GLvoid *indices, GLsizei primcount) -{ - if (g_current_runtime) g_current_runtime->on_draw_call(primcount * count); - - static const auto trampoline = reshade::hooks::call(glDrawElementsInstancedARB); - trampoline(mode, count, type, indices, primcount); -} -extern "C" void WINAPI glDrawElementsInstancedEXT(GLenum mode, GLsizei count, GLenum type, const GLvoid *indices, GLsizei primcount) -{ - if (g_current_runtime) g_current_runtime->on_draw_call(primcount * count); - - static const auto trampoline = reshade::hooks::call(glDrawElementsInstancedEXT); - trampoline(mode, count, type, indices, primcount); -} -extern "C" void WINAPI glDrawElementsInstancedBaseVertex(GLenum mode, GLsizei count, GLenum type, const GLvoid *indices, GLsizei primcount, GLint basevertex) -{ - if (g_current_runtime) g_current_runtime->on_draw_call(primcount * count); - - static const auto trampoline = reshade::hooks::call(glDrawElementsInstancedBaseVertex); - trampoline(mode, count, type, indices, primcount, basevertex); -} -extern "C" void WINAPI glDrawElementsInstancedBaseInstance(GLenum mode, GLsizei count, GLenum type, const GLvoid *indices, GLsizei primcount, GLuint baseinstance) -{ - if (g_current_runtime) g_current_runtime->on_draw_call(primcount * count); - - static const auto trampoline = reshade::hooks::call(glDrawElementsInstancedBaseInstance); - trampoline(mode, count, type, indices, primcount, baseinstance); -} -extern "C" void WINAPI glDrawElementsInstancedBaseVertexBaseInstance(GLenum mode, GLsizei count, GLenum type, const GLvoid *indices, GLsizei primcount, GLint basevertex, GLuint baseinstance) -{ - if (g_current_runtime) g_current_runtime->on_draw_call(primcount * count); - - static const auto trampoline = reshade::hooks::call(glDrawElementsInstancedBaseVertexBaseInstance); - trampoline(mode, count, type, indices, primcount, basevertex, baseinstance); -} - -HOOK_EXPORT void WINAPI glDrawPixels(GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *pixels) -{ - static const auto trampoline = reshade::hooks::call(glDrawPixels); - trampoline(width, height, format, type, pixels); -} - -extern "C" void WINAPI glDrawRangeElements(GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const GLvoid *indices) -{ - if (g_current_runtime) g_current_runtime->on_draw_call(count); - - static const auto trampoline = reshade::hooks::call(glDrawRangeElements); - trampoline(mode, start, end, count, type, indices); -} -extern "C" void WINAPI glDrawRangeElementsBaseVertex(GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const GLvoid *indices, GLint basevertex) -{ - if (g_current_runtime) g_current_runtime->on_draw_call(count); - - static const auto trampoline = reshade::hooks::call(glDrawRangeElementsBaseVertex); - trampoline(mode, start, end, count, type, indices, basevertex); -} - -HOOK_EXPORT void WINAPI glEdgeFlag(GLboolean flag) -{ - static const auto trampoline = reshade::hooks::call(glEdgeFlag); - trampoline(flag); -} -HOOK_EXPORT void WINAPI glEdgeFlagPointer(GLsizei stride, const GLvoid *pointer) -{ - static const auto trampoline = reshade::hooks::call(glEdgeFlagPointer); - trampoline(stride, pointer); -} -HOOK_EXPORT void WINAPI glEdgeFlagv(const GLboolean *flag) -{ - static const auto trampoline = reshade::hooks::call(glEdgeFlagv); - trampoline(flag); -} - -HOOK_EXPORT void WINAPI glEnable(GLenum cap) -{ - static const auto trampoline = reshade::hooks::call(glEnable); - trampoline(cap); -} -HOOK_EXPORT void WINAPI glEnableClientState(GLenum array) -{ - static const auto trampoline = reshade::hooks::call(glEnableClientState); - trampoline(array); -} - -HOOK_EXPORT void WINAPI glEnd() -{ - static const auto trampoline = reshade::hooks::call(glEnd); - trampoline(); - - if (g_current_runtime) g_current_runtime->on_draw_call(g_current_runtime->_current_vertex_count); -} - -HOOK_EXPORT void WINAPI glEndList() -{ - static const auto trampoline = reshade::hooks::call(glEndList); - trampoline(); -} - -HOOK_EXPORT void WINAPI glEvalCoord1d(GLdouble u) -{ - static const auto trampoline = reshade::hooks::call(glEvalCoord1d); - trampoline(u); -} -HOOK_EXPORT void WINAPI glEvalCoord1dv(const GLdouble *u) -{ - static const auto trampoline = reshade::hooks::call(glEvalCoord1dv); - trampoline(u); -} -HOOK_EXPORT void WINAPI glEvalCoord1f(GLfloat u) -{ - static const auto trampoline = reshade::hooks::call(glEvalCoord1f); - trampoline(u); -} -HOOK_EXPORT void WINAPI glEvalCoord1fv(const GLfloat *u) -{ - static const auto trampoline = reshade::hooks::call(glEvalCoord1fv); - trampoline(u); -} -HOOK_EXPORT void WINAPI glEvalCoord2d(GLdouble u, GLdouble v) -{ - static const auto trampoline = reshade::hooks::call(glEvalCoord2d); - trampoline(u, v); -} -HOOK_EXPORT void WINAPI glEvalCoord2dv(const GLdouble *u) -{ - static const auto trampoline = reshade::hooks::call(glEvalCoord2dv); - trampoline(u); -} -HOOK_EXPORT void WINAPI glEvalCoord2f(GLfloat u, GLfloat v) -{ - static const auto trampoline = reshade::hooks::call(glEvalCoord2f); - trampoline(u, v); -} -HOOK_EXPORT void WINAPI glEvalCoord2fv(const GLfloat *u) -{ - static const auto trampoline = reshade::hooks::call(glEvalCoord2fv); - trampoline(u); -} - -HOOK_EXPORT void WINAPI glEvalMesh1(GLenum mode, GLint i1, GLint i2) -{ - static const auto trampoline = reshade::hooks::call(glEvalMesh1); - trampoline(mode, i1, i2); -} -HOOK_EXPORT void WINAPI glEvalMesh2(GLenum mode, GLint i1, GLint i2, GLint j1, GLint j2) -{ - static const auto trampoline = reshade::hooks::call(glEvalMesh2); - trampoline(mode, i1, i2, j1, j2); -} - -HOOK_EXPORT void WINAPI glEvalPoint1(GLint i) -{ - static const auto trampoline = reshade::hooks::call(glEvalPoint1); - trampoline(i); -} -HOOK_EXPORT void WINAPI glEvalPoint2(GLint i, GLint j) -{ - static const auto trampoline = reshade::hooks::call(glEvalPoint2); - trampoline(i, j); -} - -HOOK_EXPORT void WINAPI glFeedbackBuffer(GLsizei size, GLenum type, GLfloat *buffer) -{ - static const auto trampoline = reshade::hooks::call(glFeedbackBuffer); - trampoline(size, type, buffer); -} - -HOOK_EXPORT void WINAPI glFinish() -{ - static const auto trampoline = reshade::hooks::call(glFinish); - trampoline(); -} -HOOK_EXPORT void WINAPI glFlush() -{ - static const auto trampoline = reshade::hooks::call(glFlush); - trampoline(); -} - -HOOK_EXPORT void WINAPI glFogf(GLenum pname, GLfloat param) -{ - static const auto trampoline = reshade::hooks::call(glFogf); - trampoline(pname, param); -} -HOOK_EXPORT void WINAPI glFogfv(GLenum pname, const GLfloat *params) -{ - static const auto trampoline = reshade::hooks::call(glFogfv); - trampoline(pname, params); -} -HOOK_EXPORT void WINAPI glFogi(GLenum pname, GLint param) -{ - static const auto trampoline = reshade::hooks::call(glFogi); - trampoline(pname, param); -} -HOOK_EXPORT void WINAPI glFogiv(GLenum pname, const GLint *params) -{ - static const auto trampoline = reshade::hooks::call(glFogiv); - trampoline(pname, params); -} - -extern "C" void WINAPI glFramebufferRenderbuffer(GLenum target, GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer) -{ - static const auto trampoline = reshade::hooks::call(glFramebufferRenderbuffer); - trampoline(target, attachment, renderbuffertarget, renderbuffer); - - if (g_current_runtime) g_current_runtime->on_fbo_attachment(attachment, renderbuffertarget, renderbuffer, 0); -} -extern "C" void WINAPI glFramebufferRenderbufferEXT(GLenum target, GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer) -{ - static const auto trampoline = reshade::hooks::call(glFramebufferRenderbufferEXT); - trampoline(target, attachment, renderbuffertarget, renderbuffer); - - if (g_current_runtime) g_current_runtime->on_fbo_attachment(attachment, renderbuffertarget, renderbuffer, 0); -} -extern "C" void WINAPI glFramebufferTexture(GLenum target, GLenum attachment, GLuint texture, GLint level) -{ - static const auto trampoline = reshade::hooks::call(glFramebufferTexture); - trampoline(target, attachment, texture, level); - - if (g_current_runtime) g_current_runtime->on_fbo_attachment(attachment, GL_TEXTURE_2D, texture, level); -} -extern "C" void WINAPI glFramebufferTextureARB(GLenum target, GLenum attachment, GLuint texture, GLint level) -{ - static const auto trampoline = reshade::hooks::call(glFramebufferTextureARB); - trampoline(target, attachment, texture, level); - - if (g_current_runtime) g_current_runtime->on_fbo_attachment(attachment, GL_TEXTURE_2D, texture, level); -} -extern "C" void WINAPI glFramebufferTextureEXT(GLenum target, GLenum attachment, GLuint texture, GLint level) -{ - static const auto trampoline = reshade::hooks::call(glFramebufferTextureEXT); - trampoline(target, attachment, texture, level); - - if (g_current_runtime) g_current_runtime->on_fbo_attachment(attachment, GL_TEXTURE_2D, texture, level); -} -extern "C" void WINAPI glFramebufferTexture1D(GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level) -{ - static const auto trampoline = reshade::hooks::call(glFramebufferTexture1D); - trampoline(target, attachment, textarget, texture, level); - - if (g_current_runtime) g_current_runtime->on_fbo_attachment(attachment, textarget, texture, level); -} -extern "C" void WINAPI glFramebufferTexture1DEXT(GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level) -{ - static const auto trampoline = reshade::hooks::call(glFramebufferTexture1DEXT); - trampoline(target, attachment, textarget, texture, level); - - if (g_current_runtime) g_current_runtime->on_fbo_attachment(attachment, textarget, texture, level); -} -extern "C" void WINAPI glFramebufferTexture2D(GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level) -{ - static const auto trampoline = reshade::hooks::call(glFramebufferTexture2D); - trampoline(target, attachment, textarget, texture, level); - - if (g_current_runtime) g_current_runtime->on_fbo_attachment(attachment, textarget, texture, level); -} -extern "C" void WINAPI glFramebufferTexture2DEXT(GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level) -{ - static const auto trampoline = reshade::hooks::call(glFramebufferTexture2DEXT); - trampoline(target, attachment, textarget, texture, level); - - if (g_current_runtime) g_current_runtime->on_fbo_attachment(attachment, textarget, texture, level); -} -extern "C" void WINAPI glFramebufferTexture3D(GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level, GLint zoffset) -{ - static const auto trampoline = reshade::hooks::call(glFramebufferTexture3D); - trampoline(target, attachment, textarget, texture, level, zoffset); - - if (g_current_runtime) g_current_runtime->on_fbo_attachment(attachment, textarget, texture, level); -} -extern "C" void WINAPI glFramebufferTexture3DEXT(GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level, GLint zoffset) -{ - static const auto trampoline = reshade::hooks::call(glFramebufferTexture3DEXT); - trampoline(target, attachment, textarget, texture, level, zoffset); - - if (g_current_runtime) g_current_runtime->on_fbo_attachment(attachment, textarget, texture, level); -} -extern "C" void WINAPI glFramebufferTextureLayer(GLenum target, GLenum attachment, GLuint texture, GLint level, GLint layer) -{ - static const auto trampoline = reshade::hooks::call(glFramebufferTextureLayer); - trampoline(target, attachment, texture, level, layer); - - if (g_current_runtime) g_current_runtime->on_fbo_attachment(attachment, GL_TEXTURE_2D, texture, level); -} -extern "C" void WINAPI glFramebufferTextureLayerARB(GLenum target, GLenum attachment, GLuint texture, GLint level, GLint layer) -{ - static const auto trampoline = reshade::hooks::call(glFramebufferTextureLayerARB); - trampoline(target, attachment, texture, level, layer); - - if (g_current_runtime) g_current_runtime->on_fbo_attachment(attachment, GL_TEXTURE_2D, texture, level); -} -extern "C" void WINAPI glFramebufferTextureLayerEXT(GLenum target, GLenum attachment, GLuint texture, GLint level, GLint layer) -{ - static const auto trampoline = reshade::hooks::call(glFramebufferTextureLayerEXT); - trampoline(target, attachment, texture, level, layer); - - if (g_current_runtime) g_current_runtime->on_fbo_attachment(attachment, GL_TEXTURE_2D, texture, level); -} - -HOOK_EXPORT void WINAPI glFrontFace(GLenum mode) -{ - static const auto trampoline = reshade::hooks::call(glFrontFace); - trampoline(mode); -} - -HOOK_EXPORT void WINAPI glFrustum(GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble zNear, GLdouble zFar) -{ - static const auto trampoline = reshade::hooks::call(glFrustum); - trampoline(left, right, bottom, top, zNear, zFar); -} - -HOOK_EXPORT GLuint WINAPI glGenLists(GLsizei range) -{ - static const auto trampoline = reshade::hooks::call(glGenLists); - return trampoline(range); -} - -HOOK_EXPORT void WINAPI glGenTextures(GLsizei n, GLuint *textures) -{ - static const auto trampoline = reshade::hooks::call(glGenTextures); - trampoline(n, textures); -} - -HOOK_EXPORT void WINAPI glGetBooleanv(GLenum pname, GLboolean *params) -{ - static const auto trampoline = reshade::hooks::call(glGetBooleanv); - trampoline(pname, params); -} -HOOK_EXPORT void WINAPI glGetDoublev(GLenum pname, GLdouble *params) -{ - static const auto trampoline = reshade::hooks::call(glGetDoublev); - trampoline(pname, params); -} -HOOK_EXPORT void WINAPI glGetFloatv(GLenum pname, GLfloat *params) -{ - static const auto trampoline = reshade::hooks::call(glGetFloatv); - trampoline(pname, params); -} -HOOK_EXPORT void WINAPI glGetIntegerv(GLenum pname, GLint *params) -{ - static const auto trampoline = reshade::hooks::call(glGetIntegerv); - trampoline(pname, params); -} - -HOOK_EXPORT void WINAPI glGetClipPlane(GLenum plane, GLdouble *equation) -{ - static const auto trampoline = reshade::hooks::call(glGetClipPlane); - trampoline(plane, equation); -} - -HOOK_EXPORT GLenum WINAPI glGetError() -{ - static const auto trampoline = reshade::hooks::call(glGetError); - return trampoline(); -} - -HOOK_EXPORT void WINAPI glGetLightfv(GLenum light, GLenum pname, GLfloat *params) -{ - static const auto trampoline = reshade::hooks::call(glGetLightfv); - trampoline(light, pname, params); -} -HOOK_EXPORT void WINAPI glGetLightiv(GLenum light, GLenum pname, GLint *params) -{ - static const auto trampoline = reshade::hooks::call(glGetLightiv); - trampoline(light, pname, params); -} - -HOOK_EXPORT void WINAPI glGetMapdv(GLenum target, GLenum query, GLdouble *v) -{ - static const auto trampoline = reshade::hooks::call(glGetMapdv); - trampoline(target, query, v); -} -HOOK_EXPORT void WINAPI glGetMapfv(GLenum target, GLenum query, GLfloat *v) -{ - static const auto trampoline = reshade::hooks::call(glGetMapfv); - trampoline(target, query, v); -} -HOOK_EXPORT void WINAPI glGetMapiv(GLenum target, GLenum query, GLint *v) -{ - static const auto trampoline = reshade::hooks::call(glGetMapiv); - trampoline(target, query, v); -} - -HOOK_EXPORT void WINAPI glGetMaterialfv(GLenum face, GLenum pname, GLfloat *params) -{ - static const auto trampoline = reshade::hooks::call(glGetMaterialfv); - trampoline(face, pname, params); -} -HOOK_EXPORT void WINAPI glGetMaterialiv(GLenum face, GLenum pname, GLint *params) -{ - static const auto trampoline = reshade::hooks::call(glGetMaterialiv); - trampoline(face, pname, params); -} - -HOOK_EXPORT void WINAPI glGetPixelMapfv(GLenum map, GLfloat *values) -{ - static const auto trampoline = reshade::hooks::call(glGetPixelMapfv); - trampoline(map, values); -} -HOOK_EXPORT void WINAPI glGetPixelMapuiv(GLenum map, GLuint *values) -{ - static const auto trampoline = reshade::hooks::call(glGetPixelMapuiv); - trampoline(map, values); -} -HOOK_EXPORT void WINAPI glGetPixelMapusv(GLenum map, GLushort *values) -{ - static const auto trampoline = reshade::hooks::call(glGetPixelMapusv); - trampoline(map, values); -} - -HOOK_EXPORT void WINAPI glGetPointerv(GLenum pname, GLvoid **params) -{ - static const auto trampoline = reshade::hooks::call(glGetPointerv); - trampoline(pname, params); -} - -HOOK_EXPORT void WINAPI glGetPolygonStipple(GLubyte *mask) -{ - static const auto trampoline = reshade::hooks::call(glGetPolygonStipple); - trampoline(mask); -} - -HOOK_EXPORT const GLubyte *WINAPI glGetString(GLenum name) -{ - static const auto trampoline = reshade::hooks::call(glGetString); - return trampoline(name); -} - -HOOK_EXPORT void WINAPI glGetTexEnvfv(GLenum target, GLenum pname, GLfloat *params) -{ - static const auto trampoline = reshade::hooks::call(glGetTexEnvfv); - trampoline(target, pname, params); -} -HOOK_EXPORT void WINAPI glGetTexEnviv(GLenum target, GLenum pname, GLint *params) -{ - static const auto trampoline = reshade::hooks::call(glGetTexEnviv); - trampoline(target, pname, params); -} - -HOOK_EXPORT void WINAPI glGetTexGendv(GLenum coord, GLenum pname, GLdouble *params) -{ - static const auto trampoline = reshade::hooks::call(glGetTexGendv); - trampoline(coord, pname, params); -} -HOOK_EXPORT void WINAPI glGetTexGenfv(GLenum coord, GLenum pname, GLfloat *params) -{ - static const auto trampoline = reshade::hooks::call(glGetTexGenfv); - trampoline(coord, pname, params); -} -HOOK_EXPORT void WINAPI glGetTexGeniv(GLenum coord, GLenum pname, GLint *params) -{ - static const auto trampoline = reshade::hooks::call(glGetTexGeniv); - trampoline(coord, pname, params); -} - -HOOK_EXPORT void WINAPI glGetTexImage(GLenum target, GLint level, GLenum format, GLenum type, GLvoid *pixels) -{ - static const auto trampoline = reshade::hooks::call(glGetTexImage); - trampoline(target, level, format, type, pixels); -} - -HOOK_EXPORT void WINAPI glGetTexLevelParameterfv(GLenum target, GLint level, GLenum pname, GLfloat *params) -{ - static const auto trampoline = reshade::hooks::call(glGetTexLevelParameterfv); - trampoline(target, level, pname, params); -} -HOOK_EXPORT void WINAPI glGetTexLevelParameteriv(GLenum target, GLint level, GLenum pname, GLint *params) -{ - static const auto trampoline = reshade::hooks::call(glGetTexLevelParameteriv); - trampoline(target, level, pname, params); -} -HOOK_EXPORT void WINAPI glGetTexParameterfv(GLenum target, GLenum pname, GLfloat *params) -{ - static const auto trampoline = reshade::hooks::call(glGetTexParameterfv); - trampoline(target, pname, params); -} -HOOK_EXPORT void WINAPI glGetTexParameteriv(GLenum target, GLenum pname, GLint *params) -{ - static const auto trampoline = reshade::hooks::call(glGetTexParameteriv); - trampoline(target, pname, params); -} - -HOOK_EXPORT void WINAPI glHint(GLenum target, GLenum mode) -{ - static const auto trampoline = reshade::hooks::call(glHint); - trampoline(target, mode); -} - -HOOK_EXPORT void WINAPI glIndexMask(GLuint mask) -{ - static const auto trampoline = reshade::hooks::call(glIndexMask); - trampoline(mask); -} - -HOOK_EXPORT void WINAPI glIndexPointer(GLenum type, GLsizei stride, const GLvoid *pointer) -{ - static const auto trampoline = reshade::hooks::call(glIndexPointer); - trampoline(type, stride, pointer); -} - -HOOK_EXPORT void WINAPI glIndexd(GLdouble c) -{ - static const auto trampoline = reshade::hooks::call(glIndexd); - trampoline(c); -} -HOOK_EXPORT void WINAPI glIndexdv(const GLdouble *c) -{ - static const auto trampoline = reshade::hooks::call(glIndexdv); - trampoline(c); -} -HOOK_EXPORT void WINAPI glIndexf(GLfloat c) -{ - static const auto trampoline = reshade::hooks::call(glIndexf); - trampoline(c); -} -HOOK_EXPORT void WINAPI glIndexfv(const GLfloat *c) -{ - static const auto trampoline = reshade::hooks::call(glIndexfv); - trampoline(c); -} -HOOK_EXPORT void WINAPI glIndexi(GLint c) -{ - static const auto trampoline = reshade::hooks::call(glIndexi); - trampoline(c); -} -HOOK_EXPORT void WINAPI glIndexiv(const GLint *c) -{ - static const auto trampoline = reshade::hooks::call(glIndexiv); - trampoline(c); -} -HOOK_EXPORT void WINAPI glIndexs(GLshort c) -{ - static const auto trampoline = reshade::hooks::call(glIndexs); - trampoline(c); -} -HOOK_EXPORT void WINAPI glIndexsv(const GLshort *c) -{ - static const auto trampoline = reshade::hooks::call(glIndexsv); - trampoline(c); -} -HOOK_EXPORT void WINAPI glIndexub(GLubyte c) -{ - static const auto trampoline = reshade::hooks::call(glIndexub); - trampoline(c); -} -HOOK_EXPORT void WINAPI glIndexubv(const GLubyte *c) -{ - static const auto trampoline = reshade::hooks::call(glIndexubv); - trampoline(c); -} - -HOOK_EXPORT void WINAPI glInitNames() -{ - static const auto trampoline = reshade::hooks::call(glInitNames); - trampoline(); -} - -HOOK_EXPORT void WINAPI glInterleavedArrays(GLenum format, GLsizei stride, const GLvoid *pointer) -{ - static const auto trampoline = reshade::hooks::call(glInterleavedArrays); - trampoline(format, stride, pointer); -} - -HOOK_EXPORT GLboolean WINAPI glIsEnabled(GLenum cap) -{ - static const auto trampoline = reshade::hooks::call(glIsEnabled); - return trampoline(cap); -} - -HOOK_EXPORT GLboolean WINAPI glIsList(GLuint list) -{ - static const auto trampoline = reshade::hooks::call(glIsList); - return trampoline(list); -} -HOOK_EXPORT GLboolean WINAPI glIsTexture(GLuint texture) -{ - static const auto trampoline = reshade::hooks::call(glIsTexture); - return trampoline(texture); -} - -HOOK_EXPORT void WINAPI glLightModelf(GLenum pname, GLfloat param) -{ - static const auto trampoline = reshade::hooks::call(glLightModelf); - trampoline(pname, param); -} -HOOK_EXPORT void WINAPI glLightModelfv(GLenum pname, const GLfloat *params) -{ - static const auto trampoline = reshade::hooks::call(glLightModelfv); - trampoline(pname, params); -} -HOOK_EXPORT void WINAPI glLightModeli(GLenum pname, GLint param) -{ - static const auto trampoline = reshade::hooks::call(glLightModeli); - trampoline(pname, param); -} -HOOK_EXPORT void WINAPI glLightModeliv(GLenum pname, const GLint *params) -{ - static const auto trampoline = reshade::hooks::call(glLightModeliv); - trampoline(pname, params); -} - -HOOK_EXPORT void WINAPI glLightf(GLenum light, GLenum pname, GLfloat param) -{ - static const auto trampoline = reshade::hooks::call(glLightf); - trampoline(light, pname, param); -} -HOOK_EXPORT void WINAPI glLightfv(GLenum light, GLenum pname, const GLfloat *params) -{ - static const auto trampoline = reshade::hooks::call(glLightfv); - trampoline(light, pname, params); -} -HOOK_EXPORT void WINAPI glLighti(GLenum light, GLenum pname, GLint param) -{ - static const auto trampoline = reshade::hooks::call(glLighti); - trampoline(light, pname, param); -} -HOOK_EXPORT void WINAPI glLightiv(GLenum light, GLenum pname, const GLint *params) -{ - static const auto trampoline = reshade::hooks::call(glLightiv); - trampoline(light, pname, params); -} - -HOOK_EXPORT void WINAPI glLineStipple(GLint factor, GLushort pattern) -{ - static const auto trampoline = reshade::hooks::call(glLineStipple); - trampoline(factor, pattern); -} -HOOK_EXPORT void WINAPI glLineWidth(GLfloat width) -{ - static const auto trampoline = reshade::hooks::call(glLineWidth); - trampoline(width); -} - -HOOK_EXPORT void WINAPI glListBase(GLuint base) -{ - static const auto trampoline = reshade::hooks::call(glListBase); - trampoline(base); -} - -HOOK_EXPORT void WINAPI glLoadIdentity() -{ - static const auto trampoline = reshade::hooks::call(glLoadIdentity); - trampoline(); -} -HOOK_EXPORT void WINAPI glLoadMatrixd(const GLdouble *m) -{ - static const auto trampoline = reshade::hooks::call(glLoadMatrixd); - trampoline(m); -} -HOOK_EXPORT void WINAPI glLoadMatrixf(const GLfloat *m) -{ - static const auto trampoline = reshade::hooks::call(glLoadMatrixf); - trampoline(m); -} - -HOOK_EXPORT void WINAPI glLoadName(GLuint name) -{ - static const auto trampoline = reshade::hooks::call(glLoadName); - trampoline(name); -} - -HOOK_EXPORT void WINAPI glLogicOp(GLenum opcode) -{ - static const auto trampoline = reshade::hooks::call(glLogicOp); - trampoline(opcode); -} - -HOOK_EXPORT void WINAPI glMap1d(GLenum target, GLdouble u1, GLdouble u2, GLint stride, GLint order, const GLdouble *points) -{ - static const auto trampoline = reshade::hooks::call(glMap1d); - trampoline(target, u1, u2, stride, order, points); -} -HOOK_EXPORT void WINAPI glMap1f(GLenum target, GLfloat u1, GLfloat u2, GLint stride, GLint order, const GLfloat *points) -{ - static const auto trampoline = reshade::hooks::call(glMap1f); - trampoline(target, u1, u2, stride, order, points); -} -HOOK_EXPORT void WINAPI glMap2d(GLenum target, GLdouble u1, GLdouble u2, GLint ustride, GLint uorder, GLdouble v1, GLdouble v2, GLint vstride, GLint vorder, const GLdouble *points) -{ - static const auto trampoline = reshade::hooks::call(glMap2d); - trampoline(target, u1, u2, ustride, uorder, v1, v2, vstride, vorder, points); -} -HOOK_EXPORT void WINAPI glMap2f(GLenum target, GLfloat u1, GLfloat u2, GLint ustride, GLint uorder, GLfloat v1, GLfloat v2, GLint vstride, GLint vorder, const GLfloat *points) -{ - static const auto trampoline = reshade::hooks::call(glMap2f); - trampoline(target, u1, u2, ustride, uorder, v1, v2, vstride, vorder, points); -} - -HOOK_EXPORT void WINAPI glMapGrid1d(GLint un, GLdouble u1, GLdouble u2) -{ - static const auto trampoline = reshade::hooks::call(glMapGrid1d); - trampoline(un, u1, u2); -} -HOOK_EXPORT void WINAPI glMapGrid1f(GLint un, GLfloat u1, GLfloat u2) -{ - static const auto trampoline = reshade::hooks::call(glMapGrid1f); - trampoline(un, u1, u2); -} -HOOK_EXPORT void WINAPI glMapGrid2d(GLint un, GLdouble u1, GLdouble u2, GLint vn, GLdouble v1, GLdouble v2) -{ - static const auto trampoline = reshade::hooks::call(glMapGrid2d); - trampoline(un, u1, u2, vn, v1, v2); -} -HOOK_EXPORT void WINAPI glMapGrid2f(GLint un, GLfloat u1, GLfloat u2, GLint vn, GLfloat v1, GLfloat v2) -{ - static const auto trampoline = reshade::hooks::call(glMapGrid2f); - trampoline(un, u1, u2, vn, v1, v2); -} - -HOOK_EXPORT void WINAPI glMaterialf(GLenum face, GLenum pname, GLfloat param) -{ - static const auto trampoline = reshade::hooks::call(glMaterialf); - trampoline(face, pname, param); -} -HOOK_EXPORT void WINAPI glMaterialfv(GLenum face, GLenum pname, const GLfloat *params) -{ - static const auto trampoline = reshade::hooks::call(glMaterialfv); - trampoline(face, pname, params); -} -HOOK_EXPORT void WINAPI glMateriali(GLenum face, GLenum pname, GLint param) -{ - static const auto trampoline = reshade::hooks::call(glMateriali); - trampoline(face, pname, param); -} -HOOK_EXPORT void WINAPI glMaterialiv(GLenum face, GLenum pname, const GLint *params) -{ - static const auto trampoline = reshade::hooks::call(glMaterialiv); - trampoline(face, pname, params); -} - -HOOK_EXPORT void WINAPI glMatrixMode(GLenum mode) -{ - static const auto trampoline = reshade::hooks::call(glMatrixMode); - trampoline(mode); -} - -HOOK_EXPORT void WINAPI glMultMatrixd(const GLdouble *m) -{ - static const auto trampoline = reshade::hooks::call(glMultMatrixd); - trampoline(m); -} -HOOK_EXPORT void WINAPI glMultMatrixf(const GLfloat *m) -{ - static const auto trampoline = reshade::hooks::call(glMultMatrixf); - trampoline(m); -} - -extern "C" void WINAPI glMultiDrawArrays(GLenum mode, const GLint *first, const GLsizei *count, GLsizei drawcount) -{ - if (g_current_runtime) g_current_runtime->on_draw_call(std::accumulate(count, count + drawcount, 0)); - - static const auto trampoline = reshade::hooks::call(glMultiDrawArrays); - trampoline(mode, first, count, drawcount); -} -extern "C" void WINAPI glMultiDrawArraysIndirect(GLenum mode, const void *indirect, GLsizei drawcount, GLsizei stride) -{ - static const auto trampoline = reshade::hooks::call(glMultiDrawArraysIndirect); - trampoline(mode, indirect, drawcount, stride); -} -extern "C" void WINAPI glMultiDrawElements(GLenum mode, const GLsizei *count, GLenum type, const GLvoid *const *indices, GLsizei drawcount) -{ - if (g_current_runtime) g_current_runtime->on_draw_call(std::accumulate(count, count + drawcount, 0)); - - static const auto trampoline = reshade::hooks::call(glMultiDrawElements); - trampoline(mode, count, type, indices, drawcount); -} -extern "C" void WINAPI glMultiDrawElementsBaseVertex(GLenum mode, const GLsizei *count, GLenum type, const GLvoid *const *indices, GLsizei drawcount, const GLint *basevertex) -{ - if (g_current_runtime) g_current_runtime->on_draw_call(std::accumulate(count, count + drawcount, 0)); - - static const auto trampoline = reshade::hooks::call(glMultiDrawElementsBaseVertex); - trampoline(mode, count, type, indices, drawcount, basevertex); -} -extern "C" void WINAPI glMultiDrawElementsIndirect(GLenum mode, GLenum type, const void *indirect, GLsizei drawcount, GLsizei stride) -{ - static const auto trampoline = reshade::hooks::call(glMultiDrawElementsIndirect); - trampoline(mode, type, indirect, drawcount, stride); -} - -HOOK_EXPORT void WINAPI glNewList(GLuint list, GLenum mode) -{ - static const auto trampoline = reshade::hooks::call(glNewList); - trampoline(list, mode); -} - -HOOK_EXPORT void WINAPI glNormal3b(GLbyte nx, GLbyte ny, GLbyte nz) -{ - static const auto trampoline = reshade::hooks::call(glNormal3b); - trampoline(nx, ny, nz); -} -HOOK_EXPORT void WINAPI glNormal3bv(const GLbyte *v) -{ - static const auto trampoline = reshade::hooks::call(glNormal3bv); - trampoline(v); -} -HOOK_EXPORT void WINAPI glNormal3d(GLdouble nx, GLdouble ny, GLdouble nz) -{ - static const auto trampoline = reshade::hooks::call(glNormal3d); - trampoline(nx, ny, nz); -} -HOOK_EXPORT void WINAPI glNormal3dv(const GLdouble *v) -{ - static const auto trampoline = reshade::hooks::call(glNormal3dv); - trampoline(v); -} -HOOK_EXPORT void WINAPI glNormal3f(GLfloat nx, GLfloat ny, GLfloat nz) -{ - static const auto trampoline = reshade::hooks::call(glNormal3f); - trampoline(nx, ny, nz); -} -HOOK_EXPORT void WINAPI glNormal3fv(const GLfloat *v) -{ - static const auto trampoline = reshade::hooks::call(glNormal3fv); - trampoline(v); -} -HOOK_EXPORT void WINAPI glNormal3i(GLint nx, GLint ny, GLint nz) -{ - static const auto trampoline = reshade::hooks::call(glNormal3i); - trampoline(nx, ny, nz); -} -HOOK_EXPORT void WINAPI glNormal3iv(const GLint *v) -{ - static const auto trampoline = reshade::hooks::call(glNormal3iv); - trampoline(v); -} -HOOK_EXPORT void WINAPI glNormal3s(GLshort nx, GLshort ny, GLshort nz) -{ - static const auto trampoline = reshade::hooks::call(glNormal3s); - trampoline(nx, ny, nz); -} -HOOK_EXPORT void WINAPI glNormal3sv(const GLshort *v) -{ - static const auto trampoline = reshade::hooks::call(glNormal3sv); - trampoline(v); -} - -HOOK_EXPORT void WINAPI glNormalPointer(GLenum type, GLsizei stride, const GLvoid *pointer) -{ - static const auto trampoline = reshade::hooks::call(glNormalPointer); - trampoline(type, stride, pointer); -} - -HOOK_EXPORT void WINAPI glOrtho(GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble zNear, GLdouble zFar) -{ - static const auto trampoline = reshade::hooks::call(glOrtho); - trampoline(left, right, bottom, top, zNear, zFar); -} - -HOOK_EXPORT void WINAPI glPassThrough(GLfloat token) -{ - static const auto trampoline = reshade::hooks::call(glPassThrough); - trampoline(token); -} - -HOOK_EXPORT void WINAPI glPixelMapfv(GLenum map, GLsizei mapsize, const GLfloat *values) -{ - static const auto trampoline = reshade::hooks::call(glPixelMapfv); - trampoline(map, mapsize, values); -} -HOOK_EXPORT void WINAPI glPixelMapuiv(GLenum map, GLsizei mapsize, const GLuint *values) -{ - static const auto trampoline = reshade::hooks::call(glPixelMapuiv); - trampoline(map, mapsize, values); -} -HOOK_EXPORT void WINAPI glPixelMapusv(GLenum map, GLsizei mapsize, const GLushort *values) -{ - static const auto trampoline = reshade::hooks::call(glPixelMapusv); - trampoline(map, mapsize, values); -} - -HOOK_EXPORT void WINAPI glPixelStoref(GLenum pname, GLfloat param) -{ - static const auto trampoline = reshade::hooks::call(glPixelStoref); - trampoline(pname, param); -} -HOOK_EXPORT void WINAPI glPixelStorei(GLenum pname, GLint param) -{ - static const auto trampoline = reshade::hooks::call(glPixelStorei); - trampoline(pname, param); -} - -HOOK_EXPORT void WINAPI glPixelTransferf(GLenum pname, GLfloat param) -{ - static const auto trampoline = reshade::hooks::call(glPixelTransferf); - trampoline(pname, param); -} -HOOK_EXPORT void WINAPI glPixelTransferi(GLenum pname, GLint param) -{ - static const auto trampoline = reshade::hooks::call(glPixelTransferi); - trampoline(pname, param); -} - -HOOK_EXPORT void WINAPI glPixelZoom(GLfloat xfactor, GLfloat yfactor) -{ - static const auto trampoline = reshade::hooks::call(glPixelZoom); - trampoline(xfactor, yfactor); -} - -HOOK_EXPORT void WINAPI glPointSize(GLfloat size) -{ - static const auto trampoline = reshade::hooks::call(glPointSize); - trampoline(size); -} - -HOOK_EXPORT void WINAPI glPolygonMode(GLenum face, GLenum mode) -{ - static const auto trampoline = reshade::hooks::call(glPolygonMode); - trampoline(face, mode); -} -HOOK_EXPORT void WINAPI glPolygonOffset(GLfloat factor, GLfloat units) -{ - static const auto trampoline = reshade::hooks::call(glPolygonOffset); - trampoline(factor, units); -} -HOOK_EXPORT void WINAPI glPolygonStipple(const GLubyte *mask) -{ - static const auto trampoline = reshade::hooks::call(glPolygonStipple); - trampoline(mask); -} - -HOOK_EXPORT void WINAPI glPopAttrib() -{ - static const auto trampoline = reshade::hooks::call(glPopAttrib); - trampoline(); -} -HOOK_EXPORT void WINAPI glPopClientAttrib() -{ - static const auto trampoline = reshade::hooks::call(glPopClientAttrib); - trampoline(); -} -HOOK_EXPORT void WINAPI glPopMatrix() -{ - static const auto trampoline = reshade::hooks::call(glPopMatrix); - trampoline(); -} -HOOK_EXPORT void WINAPI glPopName() -{ - static const auto trampoline = reshade::hooks::call(glPopName); - trampoline(); -} - -HOOK_EXPORT void WINAPI glPrioritizeTextures(GLsizei n, const GLuint *textures, const GLclampf *priorities) -{ - static const auto trampoline = reshade::hooks::call(glPrioritizeTextures); - trampoline(n, textures, priorities); -} - -HOOK_EXPORT void WINAPI glPushAttrib(GLbitfield mask) -{ - static const auto trampoline = reshade::hooks::call(glPushAttrib); - trampoline(mask); -} -HOOK_EXPORT void WINAPI glPushClientAttrib(GLbitfield mask) -{ - static const auto trampoline = reshade::hooks::call(glPushClientAttrib); - trampoline(mask); -} -HOOK_EXPORT void WINAPI glPushMatrix() -{ - static const auto trampoline = reshade::hooks::call(glPushMatrix); - trampoline(); -} -HOOK_EXPORT void WINAPI glPushName(GLuint name) -{ - static const auto trampoline = reshade::hooks::call(glPushName); - trampoline(name); -} - -HOOK_EXPORT void WINAPI glRasterPos2d(GLdouble x, GLdouble y) -{ - static const auto trampoline = reshade::hooks::call(glRasterPos2d); - trampoline(x, y); -} -HOOK_EXPORT void WINAPI glRasterPos2dv(const GLdouble *v) -{ - static const auto trampoline = reshade::hooks::call(glRasterPos2dv); - trampoline(v); -} -HOOK_EXPORT void WINAPI glRasterPos2f(GLfloat x, GLfloat y) -{ - static const auto trampoline = reshade::hooks::call(glRasterPos2f); - trampoline(x, y); -} -HOOK_EXPORT void WINAPI glRasterPos2fv(const GLfloat *v) -{ - static const auto trampoline = reshade::hooks::call(glRasterPos2fv); - trampoline(v); -} -HOOK_EXPORT void WINAPI glRasterPos2i(GLint x, GLint y) -{ - static const auto trampoline = reshade::hooks::call(glRasterPos2i); - trampoline(x, y); -} -HOOK_EXPORT void WINAPI glRasterPos2iv(const GLint *v) -{ - static const auto trampoline = reshade::hooks::call(glRasterPos2iv); - trampoline(v); -} -HOOK_EXPORT void WINAPI glRasterPos2s(GLshort x, GLshort y) -{ - static const auto trampoline = reshade::hooks::call(glRasterPos2s); - trampoline(x, y); -} -HOOK_EXPORT void WINAPI glRasterPos2sv(const GLshort *v) -{ - static const auto trampoline = reshade::hooks::call(glRasterPos2sv); - trampoline(v); -} -HOOK_EXPORT void WINAPI glRasterPos3d(GLdouble x, GLdouble y, GLdouble z) -{ - static const auto trampoline = reshade::hooks::call(glRasterPos3d); - trampoline(x, y, z); -} -HOOK_EXPORT void WINAPI glRasterPos3dv(const GLdouble *v) -{ - static const auto trampoline = reshade::hooks::call(glRasterPos3dv); - trampoline(v); -} -HOOK_EXPORT void WINAPI glRasterPos3f(GLfloat x, GLfloat y, GLfloat z) -{ - static const auto trampoline = reshade::hooks::call(glRasterPos3f); - trampoline(x, y, z); -} -HOOK_EXPORT void WINAPI glRasterPos3fv(const GLfloat *v) -{ - static const auto trampoline = reshade::hooks::call(glRasterPos3fv); - trampoline(v); -} -HOOK_EXPORT void WINAPI glRasterPos3i(GLint x, GLint y, GLint z) -{ - static const auto trampoline = reshade::hooks::call(glRasterPos3i); - trampoline(x, y, z); -} -HOOK_EXPORT void WINAPI glRasterPos3iv(const GLint *v) -{ - static const auto trampoline = reshade::hooks::call(glRasterPos3iv); - trampoline(v); -} -HOOK_EXPORT void WINAPI glRasterPos3s(GLshort x, GLshort y, GLshort z) -{ - static const auto trampoline = reshade::hooks::call(glRasterPos3s); - trampoline(x, y, z); -} -HOOK_EXPORT void WINAPI glRasterPos3sv(const GLshort *v) -{ - static const auto trampoline = reshade::hooks::call(glRasterPos3sv); - trampoline(v); -} -HOOK_EXPORT void WINAPI glRasterPos4d(GLdouble x, GLdouble y, GLdouble z, GLdouble w) -{ - static const auto trampoline = reshade::hooks::call(glRasterPos4d); - trampoline(x, y, z, w); -} -HOOK_EXPORT void WINAPI glRasterPos4dv(const GLdouble *v) -{ - static const auto trampoline = reshade::hooks::call(glRasterPos4dv); - trampoline(v); -} -HOOK_EXPORT void WINAPI glRasterPos4f(GLfloat x, GLfloat y, GLfloat z, GLfloat w) -{ - static const auto trampoline = reshade::hooks::call(glRasterPos4f); - trampoline(x, y, z, w); -} -HOOK_EXPORT void WINAPI glRasterPos4fv(const GLfloat *v) -{ - static const auto trampoline = reshade::hooks::call(glRasterPos4fv); - trampoline(v); -} -HOOK_EXPORT void WINAPI glRasterPos4i(GLint x, GLint y, GLint z, GLint w) -{ - static const auto trampoline = reshade::hooks::call(glRasterPos4i); - trampoline(x, y, z, w); -} -HOOK_EXPORT void WINAPI glRasterPos4iv(const GLint *v) -{ - static const auto trampoline = reshade::hooks::call(glRasterPos4iv); - trampoline(v); -} -HOOK_EXPORT void WINAPI glRasterPos4s(GLshort x, GLshort y, GLshort z, GLshort w) -{ - static const auto trampoline = reshade::hooks::call(glRasterPos4s); - trampoline(x, y, z, w); -} -HOOK_EXPORT void WINAPI glRasterPos4sv(const GLshort *v) -{ - static const auto trampoline = reshade::hooks::call(glRasterPos4sv); - trampoline(v); -} - -HOOK_EXPORT void WINAPI glReadBuffer(GLenum mode) -{ - static const auto trampoline = reshade::hooks::call(glReadBuffer); - trampoline(mode); -} - -HOOK_EXPORT void WINAPI glReadPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, GLvoid *pixels) -{ - static const auto trampoline = reshade::hooks::call(glReadPixels); - trampoline(x, y, width, height, format, type, pixels); -} - -HOOK_EXPORT void WINAPI glRectd(GLdouble x1, GLdouble y1, GLdouble x2, GLdouble y2) -{ - static const auto trampoline = reshade::hooks::call(glRectd); - trampoline(x1, y1, x2, y2); -} -HOOK_EXPORT void WINAPI glRectdv(const GLdouble *v1, const GLdouble *v2) -{ - static const auto trampoline = reshade::hooks::call(glRectdv); - trampoline(v1, v2); -} -HOOK_EXPORT void WINAPI glRectf(GLfloat x1, GLfloat y1, GLfloat x2, GLfloat y2) -{ - static const auto trampoline = reshade::hooks::call(glRectf); - trampoline(x1, y1, x2, y2); -} -HOOK_EXPORT void WINAPI glRectfv(const GLfloat *v1, const GLfloat *v2) -{ - static const auto trampoline = reshade::hooks::call(glRectfv); - trampoline(v1, v2); -} -HOOK_EXPORT void WINAPI glRecti(GLint x1, GLint y1, GLint x2, GLint y2) -{ - static const auto trampoline = reshade::hooks::call(glRecti); - trampoline(x1, y1, x2, y2); -} -HOOK_EXPORT void WINAPI glRectiv(const GLint *v1, const GLint *v2) -{ - static const auto trampoline = reshade::hooks::call(glRectiv); - trampoline(v1, v2); -} - -HOOK_EXPORT void WINAPI glRects(GLshort x1, GLshort y1, GLshort x2, GLshort y2) -{ - static const auto trampoline = reshade::hooks::call(glRects); - trampoline(x1, y1, x2, y2); -} -HOOK_EXPORT void WINAPI glRectsv(const GLshort *v1, const GLshort *v2) -{ - static const auto trampoline = reshade::hooks::call(glRectsv); - trampoline(v1, v2); -} - -HOOK_EXPORT GLint WINAPI glRenderMode(GLenum mode) -{ - static const auto trampoline = reshade::hooks::call(glRenderMode); - return trampoline(mode); -} - -HOOK_EXPORT void WINAPI glRotated(GLdouble angle, GLdouble x, GLdouble y, GLdouble z) -{ - static const auto trampoline = reshade::hooks::call(glRotated); - trampoline(angle, x, y, z); -} -HOOK_EXPORT void WINAPI glRotatef(GLfloat angle, GLfloat x, GLfloat y, GLfloat z) -{ - static const auto trampoline = reshade::hooks::call(glRotatef); - trampoline(angle, x, y, z); -} - -HOOK_EXPORT void WINAPI glScaled(GLdouble x, GLdouble y, GLdouble z) -{ - static const auto trampoline = reshade::hooks::call(glScaled); - trampoline(x, y, z); -} -HOOK_EXPORT void WINAPI glScalef(GLfloat x, GLfloat y, GLfloat z) -{ - static const auto trampoline = reshade::hooks::call(glScalef); - trampoline(x, y, z); -} - -HOOK_EXPORT void WINAPI glScissor(GLint x, GLint y, GLsizei width, GLsizei height) -{ - static const auto trampoline = reshade::hooks::call(glScissor); - trampoline(x, y, width, height); -} - -HOOK_EXPORT void WINAPI glSelectBuffer(GLsizei size, GLuint *buffer) -{ - static const auto trampoline = reshade::hooks::call(glSelectBuffer); - trampoline(size, buffer); -} - -HOOK_EXPORT void WINAPI glShadeModel(GLenum mode) -{ - static const auto trampoline = reshade::hooks::call(glShadeModel); - trampoline(mode); -} - -HOOK_EXPORT void WINAPI glStencilFunc(GLenum func, GLint ref, GLuint mask) -{ - static const auto trampoline = reshade::hooks::call(glStencilFunc); - trampoline(func, ref, mask); -} -HOOK_EXPORT void WINAPI glStencilMask(GLuint mask) -{ - static const auto trampoline = reshade::hooks::call(glStencilMask); - trampoline(mask); -} -HOOK_EXPORT void WINAPI glStencilOp(GLenum fail, GLenum zfail, GLenum zpass) -{ - static const auto trampoline = reshade::hooks::call(glStencilOp); - trampoline(fail, zfail, zpass); -} - -HOOK_EXPORT void WINAPI glTexCoord1d(GLdouble s) -{ - static const auto trampoline = reshade::hooks::call(glTexCoord1d); - trampoline(s); -} -HOOK_EXPORT void WINAPI glTexCoord1dv(const GLdouble *v) -{ - static const auto trampoline = reshade::hooks::call(glTexCoord1dv); - trampoline(v); -} -HOOK_EXPORT void WINAPI glTexCoord1f(GLfloat s) -{ - static const auto trampoline = reshade::hooks::call(glTexCoord1f); - trampoline(s); -} -HOOK_EXPORT void WINAPI glTexCoord1fv(const GLfloat *v) -{ - static const auto trampoline = reshade::hooks::call(glTexCoord1fv); - trampoline(v); -} -HOOK_EXPORT void WINAPI glTexCoord1i(GLint s) -{ - static const auto trampoline = reshade::hooks::call(glTexCoord1i); - trampoline(s); -} -HOOK_EXPORT void WINAPI glTexCoord1iv(const GLint *v) -{ - static const auto trampoline = reshade::hooks::call(glTexCoord1iv); - trampoline(v); -} -HOOK_EXPORT void WINAPI glTexCoord1s(GLshort s) -{ - static const auto trampoline = reshade::hooks::call(glTexCoord1s); - trampoline(s); -} -HOOK_EXPORT void WINAPI glTexCoord1sv(const GLshort *v) -{ - static const auto trampoline = reshade::hooks::call(glTexCoord1sv); - trampoline(v); -} -HOOK_EXPORT void WINAPI glTexCoord2d(GLdouble s, GLdouble t) -{ - static const auto trampoline = reshade::hooks::call(glTexCoord2d); - trampoline(s, t); -} -HOOK_EXPORT void WINAPI glTexCoord2dv(const GLdouble *v) -{ - static const auto trampoline = reshade::hooks::call(glTexCoord2dv); - trampoline(v); -} -HOOK_EXPORT void WINAPI glTexCoord2f(GLfloat s, GLfloat t) -{ - static const auto trampoline = reshade::hooks::call(glTexCoord2f); - trampoline(s, t); -} -HOOK_EXPORT void WINAPI glTexCoord2fv(const GLfloat *v) -{ - static const auto trampoline = reshade::hooks::call(glTexCoord2fv); - trampoline(v); -} -HOOK_EXPORT void WINAPI glTexCoord2i(GLint s, GLint t) -{ - static const auto trampoline = reshade::hooks::call(glTexCoord2i); - trampoline(s, t); -} -HOOK_EXPORT void WINAPI glTexCoord2iv(const GLint *v) -{ - static const auto trampoline = reshade::hooks::call(glTexCoord2iv); - trampoline(v); -} -HOOK_EXPORT void WINAPI glTexCoord2s(GLshort s, GLshort t) -{ - static const auto trampoline = reshade::hooks::call(glTexCoord2s); - trampoline(s, t); -} -HOOK_EXPORT void WINAPI glTexCoord2sv(const GLshort *v) -{ - static const auto trampoline = reshade::hooks::call(glTexCoord2sv); - trampoline(v); -} -HOOK_EXPORT void WINAPI glTexCoord3d(GLdouble s, GLdouble t, GLdouble r) -{ - static const auto trampoline = reshade::hooks::call(glTexCoord3d); - trampoline(s, t, r); -} -HOOK_EXPORT void WINAPI glTexCoord3dv(const GLdouble *v) -{ - static const auto trampoline = reshade::hooks::call(glTexCoord3dv); - trampoline(v); -} -HOOK_EXPORT void WINAPI glTexCoord3f(GLfloat s, GLfloat t, GLfloat r) -{ - static const auto trampoline = reshade::hooks::call(glTexCoord3f); - trampoline(s, t, r); -} -HOOK_EXPORT void WINAPI glTexCoord3fv(const GLfloat *v) -{ - static const auto trampoline = reshade::hooks::call(glTexCoord3fv); - trampoline(v); -} -HOOK_EXPORT void WINAPI glTexCoord3i(GLint s, GLint t, GLint r) -{ - static const auto trampoline = reshade::hooks::call(glTexCoord3i); - trampoline(s, t, r); -} -HOOK_EXPORT void WINAPI glTexCoord3iv(const GLint *v) -{ - static const auto trampoline = reshade::hooks::call(glTexCoord3iv); - trampoline(v); -} -HOOK_EXPORT void WINAPI glTexCoord3s(GLshort s, GLshort t, GLshort r) -{ - static const auto trampoline = reshade::hooks::call(glTexCoord3s); - trampoline(s, t, r); -} -HOOK_EXPORT void WINAPI glTexCoord3sv(const GLshort *v) -{ - static const auto trampoline = reshade::hooks::call(glTexCoord3sv); - trampoline(v); -} -HOOK_EXPORT void WINAPI glTexCoord4d(GLdouble s, GLdouble t, GLdouble r, GLdouble q) -{ - static const auto trampoline = reshade::hooks::call(glTexCoord4d); - trampoline(s, t, r, q); -} -HOOK_EXPORT void WINAPI glTexCoord4dv(const GLdouble *v) -{ - static const auto trampoline = reshade::hooks::call(glTexCoord4dv); - trampoline(v); -} -HOOK_EXPORT void WINAPI glTexCoord4f(GLfloat s, GLfloat t, GLfloat r, GLfloat q) -{ - static const auto trampoline = reshade::hooks::call(glTexCoord4f); - trampoline(s, t, r, q); -} -HOOK_EXPORT void WINAPI glTexCoord4fv(const GLfloat *v) -{ - static const auto trampoline = reshade::hooks::call(glTexCoord4fv); - trampoline(v); -} -HOOK_EXPORT void WINAPI glTexCoord4i(GLint s, GLint t, GLint r, GLint q) -{ - static const auto trampoline = reshade::hooks::call(glTexCoord4i); - trampoline(s, t, r, q); -} -HOOK_EXPORT void WINAPI glTexCoord4iv(const GLint *v) -{ - static const auto trampoline = reshade::hooks::call(glTexCoord4iv); - trampoline(v); -} -HOOK_EXPORT void WINAPI glTexCoord4s(GLshort s, GLshort t, GLshort r, GLshort q) -{ - static const auto trampoline = reshade::hooks::call(glTexCoord4s); - trampoline(s, t, r, q); -} -HOOK_EXPORT void WINAPI glTexCoord4sv(const GLshort *v) -{ - static const auto trampoline = reshade::hooks::call(glTexCoord4sv); - trampoline(v); -} - -HOOK_EXPORT void WINAPI glTexCoordPointer(GLint size, GLenum type, GLsizei stride, const GLvoid *pointer) -{ - static const auto trampoline = reshade::hooks::call(glTexCoordPointer); - trampoline(size, type, stride, pointer); -} - -HOOK_EXPORT void WINAPI glTexEnvf(GLenum target, GLenum pname, GLfloat param) -{ - static const auto trampoline = reshade::hooks::call(glTexEnvf); - trampoline(target, pname, param); -} -HOOK_EXPORT void WINAPI glTexEnvfv(GLenum target, GLenum pname, const GLfloat *params) -{ - static const auto trampoline = reshade::hooks::call(glTexEnvfv); - trampoline(target, pname, params); -} -HOOK_EXPORT void WINAPI glTexEnvi(GLenum target, GLenum pname, GLint param) -{ - static const auto trampoline = reshade::hooks::call(glTexEnvi); - trampoline(target, pname, param); -} -HOOK_EXPORT void WINAPI glTexEnviv(GLenum target, GLenum pname, const GLint *params) -{ - static const auto trampoline = reshade::hooks::call(glTexEnviv); - trampoline(target, pname, params); -} - -HOOK_EXPORT void WINAPI glTexGend(GLenum coord, GLenum pname, GLdouble param) -{ - static const auto trampoline = reshade::hooks::call(glTexGend); - trampoline(coord, pname, param); -} -HOOK_EXPORT void WINAPI glTexGendv(GLenum coord, GLenum pname, const GLdouble *params) -{ - static const auto trampoline = reshade::hooks::call(glTexGendv); - trampoline(coord, pname, params); -} -HOOK_EXPORT void WINAPI glTexGenf(GLenum coord, GLenum pname, GLfloat param) -{ - static const auto trampoline = reshade::hooks::call(glTexGenf); - trampoline(coord, pname, param); -} -HOOK_EXPORT void WINAPI glTexGenfv(GLenum coord, GLenum pname, const GLfloat *params) -{ - static const auto trampoline = reshade::hooks::call(glTexGenfv); - trampoline(coord, pname, params); -} -HOOK_EXPORT void WINAPI glTexGeni(GLenum coord, GLenum pname, GLint param) -{ - static const auto trampoline = reshade::hooks::call(glTexGeni); - trampoline(coord, pname, param); -} -HOOK_EXPORT void WINAPI glTexGeniv(GLenum coord, GLenum pname, const GLint *params) -{ - static const auto trampoline = reshade::hooks::call(glTexGeniv); - trampoline(coord, pname, params); -} - -HOOK_EXPORT void WINAPI glTexImage1D(GLenum target, GLint level, GLint internalformat, GLsizei width, GLint border, GLenum format, GLenum type, const GLvoid *pixels) -{ - static const auto trampoline = reshade::hooks::call(glTexImage1D); - - switch (internalformat) - { - case GL_RED: - internalformat = GL_R8; - break; - case GL_RG: - internalformat = GL_RG8; - break; - case GL_RGB: - internalformat = GL_RGB8; - break; - case GL_RGBA: - internalformat = GL_RGBA8; - break; - case GL_DEPTH_COMPONENT: - internalformat = GL_DEPTH_COMPONENT16; - break; - case GL_DEPTH_STENCIL: - internalformat = GL_DEPTH24_STENCIL8; - break; - } - trampoline(target, level, internalformat, width, border, format, type, pixels); -} -HOOK_EXPORT void WINAPI glTexImage2D(GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const GLvoid *pixels) -{ - static const auto trampoline = reshade::hooks::call(glTexImage2D); - - switch (internalformat) - { - case GL_RED: - internalformat = GL_R8; - break; - case GL_RG: - internalformat = GL_RG8; - break; - case GL_RGB: - internalformat = GL_RGB8; - break; - case GL_RGBA: - internalformat = GL_RGBA8; - break; - case GL_DEPTH_COMPONENT: - internalformat = GL_DEPTH_COMPONENT16; - break; - case GL_DEPTH_STENCIL: - internalformat = GL_DEPTH24_STENCIL8; - break; - } - trampoline(target, level, internalformat, width, height, border, format, type, pixels); -} -extern "C" void WINAPI glTexImage3D(GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLenum format, GLenum type, const GLvoid *pixels) -{ - static const auto trampoline = reshade::hooks::call(glTexImage3D); - - switch (internalformat) - { - case GL_RED: - internalformat = GL_R8; - break; - case GL_RG: - internalformat = GL_RG8; - break; - case GL_RGB: - internalformat = GL_RGB8; - break; - case GL_RGBA: - internalformat = GL_RGBA8; - break; - case GL_DEPTH_COMPONENT: - internalformat = GL_DEPTH_COMPONENT16; - break; - case GL_DEPTH_STENCIL: - internalformat = GL_DEPTH24_STENCIL8; - break; - } - trampoline(target, level, internalformat, width, height, depth, border, format, type, pixels); -} - -HOOK_EXPORT void WINAPI glTexParameterf(GLenum target, GLenum pname, GLfloat param) -{ - static const auto trampoline = reshade::hooks::call(glTexParameterf); - trampoline(target, pname, param); -} -HOOK_EXPORT void WINAPI glTexParameterfv(GLenum target, GLenum pname, const GLfloat *params) -{ - static const auto trampoline = reshade::hooks::call(glTexParameterfv); - trampoline(target, pname, params); -} -HOOK_EXPORT void WINAPI glTexParameteri(GLenum target, GLenum pname, GLint param) -{ - static const auto trampoline = reshade::hooks::call(glTexParameteri); - trampoline(target, pname, param); -} -HOOK_EXPORT void WINAPI glTexParameteriv(GLenum target, GLenum pname, const GLint *params) -{ - static const auto trampoline = reshade::hooks::call(glTexParameteriv); - trampoline(target, pname, params); -} - -HOOK_EXPORT void WINAPI glTexSubImage1D(GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLenum type, const GLvoid *pixels) -{ - static const auto trampoline = reshade::hooks::call(glTexSubImage1D); - trampoline(target, level, xoffset, width, format, type, pixels); -} -HOOK_EXPORT void WINAPI glTexSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *pixels) -{ - static const auto trampoline = reshade::hooks::call(glTexSubImage2D); - trampoline(target, level, xoffset, yoffset, width, height, format, type, pixels); -} - -HOOK_EXPORT void WINAPI glTranslated(GLdouble x, GLdouble y, GLdouble z) -{ - static const auto trampoline = reshade::hooks::call(glTranslated); - trampoline(x, y, z); -} -HOOK_EXPORT void WINAPI glTranslatef(GLfloat x, GLfloat y, GLfloat z) -{ - static const auto trampoline = reshade::hooks::call(glTranslatef); - trampoline(x, y, z); -} - -HOOK_EXPORT void WINAPI glVertex2d(GLdouble x, GLdouble y) -{ - if (g_current_runtime) g_current_runtime->_current_vertex_count += 2; - - static const auto trampoline = reshade::hooks::call(glVertex2d); - trampoline(x, y); -} -HOOK_EXPORT void WINAPI glVertex2dv(const GLdouble *v) -{ - if (g_current_runtime) g_current_runtime->_current_vertex_count += 2; - - static const auto trampoline = reshade::hooks::call(glVertex2dv); - trampoline(v); -} -HOOK_EXPORT void WINAPI glVertex2f(GLfloat x, GLfloat y) -{ - if (g_current_runtime) g_current_runtime->_current_vertex_count += 2; - - static const auto trampoline = reshade::hooks::call(glVertex2f); - trampoline(x, y); -} -HOOK_EXPORT void WINAPI glVertex2fv(const GLfloat *v) -{ - if (g_current_runtime) g_current_runtime->_current_vertex_count += 2; - - static const auto trampoline = reshade::hooks::call(glVertex2fv); - trampoline(v); -} -HOOK_EXPORT void WINAPI glVertex2i(GLint x, GLint y) -{ - if (g_current_runtime) g_current_runtime->_current_vertex_count += 2; - - static const auto trampoline = reshade::hooks::call(glVertex2i); - trampoline(x, y); -} -HOOK_EXPORT void WINAPI glVertex2iv(const GLint *v) -{ - if (g_current_runtime) g_current_runtime->_current_vertex_count += 2; - - static const auto trampoline = reshade::hooks::call(glVertex2iv); - trampoline(v); -} -HOOK_EXPORT void WINAPI glVertex2s(GLshort x, GLshort y) -{ - if (g_current_runtime) g_current_runtime->_current_vertex_count += 2; - - static const auto trampoline = reshade::hooks::call(glVertex2s); - trampoline(x, y); -} -HOOK_EXPORT void WINAPI glVertex2sv(const GLshort *v) -{ - if (g_current_runtime) g_current_runtime->_current_vertex_count += 2; - - static const auto trampoline = reshade::hooks::call(glVertex2sv); - trampoline(v); -} -HOOK_EXPORT void WINAPI glVertex3d(GLdouble x, GLdouble y, GLdouble z) -{ - if (g_current_runtime) g_current_runtime->_current_vertex_count += 3; - - static const auto trampoline = reshade::hooks::call(glVertex3d); - trampoline(x, y, z); -} -HOOK_EXPORT void WINAPI glVertex3dv(const GLdouble *v) -{ - if (g_current_runtime) g_current_runtime->_current_vertex_count += 3; - - static const auto trampoline = reshade::hooks::call(glVertex3dv); - trampoline(v); -} -HOOK_EXPORT void WINAPI glVertex3f(GLfloat x, GLfloat y, GLfloat z) -{ - if (g_current_runtime) g_current_runtime->_current_vertex_count += 3; - - static const auto trampoline = reshade::hooks::call(glVertex3f); - trampoline(x, y, z); -} -HOOK_EXPORT void WINAPI glVertex3fv(const GLfloat *v) -{ - if (g_current_runtime) g_current_runtime->_current_vertex_count += 3; - - static const auto trampoline = reshade::hooks::call(glVertex3fv); - trampoline(v); -} -HOOK_EXPORT void WINAPI glVertex3i(GLint x, GLint y, GLint z) -{ - if (g_current_runtime) g_current_runtime->_current_vertex_count += 3; - - static const auto trampoline = reshade::hooks::call(glVertex3i); - trampoline(x, y, z); -} -HOOK_EXPORT void WINAPI glVertex3iv(const GLint *v) -{ - if (g_current_runtime) g_current_runtime->_current_vertex_count += 3; - - static const auto trampoline = reshade::hooks::call(glVertex3iv); - trampoline(v); -} -HOOK_EXPORT void WINAPI glVertex3s(GLshort x, GLshort y, GLshort z) -{ - if (g_current_runtime) g_current_runtime->_current_vertex_count += 3; - - static const auto trampoline = reshade::hooks::call(glVertex3s); - trampoline(x, y, z); -} -HOOK_EXPORT void WINAPI glVertex3sv(const GLshort *v) -{ - if (g_current_runtime) g_current_runtime->_current_vertex_count += 3; - - static const auto trampoline = reshade::hooks::call(glVertex3sv); - trampoline(v); -} -HOOK_EXPORT void WINAPI glVertex4d(GLdouble x, GLdouble y, GLdouble z, GLdouble w) -{ - if (g_current_runtime) g_current_runtime->_current_vertex_count += 4; - - static const auto trampoline = reshade::hooks::call(glVertex4d); - trampoline(x, y, z, w); -} -HOOK_EXPORT void WINAPI glVertex4dv(const GLdouble *v) -{ - if (g_current_runtime) g_current_runtime->_current_vertex_count += 4; - - static const auto trampoline = reshade::hooks::call(glVertex4dv); - trampoline(v); -} -HOOK_EXPORT void WINAPI glVertex4f(GLfloat x, GLfloat y, GLfloat z, GLfloat w) -{ - if (g_current_runtime) g_current_runtime->_current_vertex_count += 4; - - static const auto trampoline = reshade::hooks::call(glVertex4f); - trampoline(x, y, z, w); -} -HOOK_EXPORT void WINAPI glVertex4fv(const GLfloat *v) -{ - if (g_current_runtime) g_current_runtime->_current_vertex_count += 4; - - static const auto trampoline = reshade::hooks::call(glVertex4fv); - trampoline(v); -} -HOOK_EXPORT void WINAPI glVertex4i(GLint x, GLint y, GLint z, GLint w) -{ - if (g_current_runtime) g_current_runtime->_current_vertex_count += 4; - - static const auto trampoline = reshade::hooks::call(glVertex4i); - trampoline(x, y, z, w); -} -HOOK_EXPORT void WINAPI glVertex4iv(const GLint *v) -{ - if (g_current_runtime) g_current_runtime->_current_vertex_count += 4; - - static const auto trampoline = reshade::hooks::call(glVertex4iv); - trampoline(v); -} -HOOK_EXPORT void WINAPI glVertex4s(GLshort x, GLshort y, GLshort z, GLshort w) -{ - if (g_current_runtime) g_current_runtime->_current_vertex_count += 4; - - static const auto trampoline = reshade::hooks::call(glVertex4s); - trampoline(x, y, z, w); -} -HOOK_EXPORT void WINAPI glVertex4sv(const GLshort *v) -{ - if (g_current_runtime) g_current_runtime->_current_vertex_count += 4; - - static const auto trampoline = reshade::hooks::call(glVertex4sv); - trampoline(v); -} - -HOOK_EXPORT void WINAPI glVertexPointer(GLint size, GLenum type, GLsizei stride, const GLvoid *pointer) -{ - static const auto trampoline = reshade::hooks::call(glVertexPointer); - trampoline(size, type, stride, pointer); -} - -HOOK_EXPORT void WINAPI glViewport(GLint x, GLint y, GLsizei width, GLsizei height) -{ - static const auto trampoline = reshade::hooks::call(glViewport); - trampoline(x, y, width, height); -} diff --git a/msvc/source/opengl/opengl_hooks.hpp b/msvc/source/opengl/opengl_hooks.hpp deleted file mode 100644 index 6d7ef67..0000000 --- a/msvc/source/opengl/opengl_hooks.hpp +++ /dev/null @@ -1,195 +0,0 @@ -/** - * Copyright (C) 2014 Patrick Mours. All rights reserved. - * License: https://github.com/crosire/reshade#license - */ - -#include - -#undef glBindTexture -extern "C" void WINAPI glBindTexture(GLenum target, GLuint texture); -#undef glBlendFunc -extern "C" void WINAPI glBlendFunc(GLenum sfactor, GLenum dfactor); -#undef glClear -extern "C" void WINAPI glClear(GLbitfield mask); -#undef glClearColor -extern "C" void WINAPI glClearColor(GLclampf red, GLclampf green, GLclampf blue, GLclampf alpha); -#undef glClearDepth -extern "C" void WINAPI glClearDepth(GLclampd depth); -#undef glClearStencil -extern "C" void WINAPI glClearStencil(GLint s); -#undef glColorMask -extern "C" void WINAPI glColorMask(GLboolean red, GLboolean green, GLboolean blue, GLboolean alpha); -#undef glCopyTexImage1D -extern "C" void WINAPI glCopyTexImage1D(GLenum target, GLint level, GLenum internalFormat, GLint x, GLint y, GLsizei width, GLint border); -#undef glCopyTexImage2D -extern "C" void WINAPI glCopyTexImage2D(GLenum target, GLint level, GLenum internalFormat, GLint x, GLint y, GLsizei width, GLsizei height, GLint border); -#undef glCopyTexSubImage1D -extern "C" void WINAPI glCopyTexSubImage1D(GLenum target, GLint level, GLint xoffset, GLint x, GLint y, GLsizei width); -#undef glCopyTexSubImage2D -extern "C" void WINAPI glCopyTexSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height); -#undef glCullFace -extern "C" void WINAPI glCullFace(GLenum mode); -#undef glDeleteTextures -extern "C" void WINAPI glDeleteTextures(GLsizei n, const GLuint *textures); -#undef glDepthFunc -extern "C" void WINAPI glDepthFunc(GLenum func); -#undef glDepthMask -extern "C" void WINAPI glDepthMask(GLboolean flag); -#undef glDepthRange -extern "C" void WINAPI glDepthRange(GLclampd zNear, GLclampd zFar); -#undef glDisable -extern "C" void WINAPI glDisable(GLenum cap); -#undef glDrawArrays -extern "C" void WINAPI glDrawArrays(GLenum mode, GLint first, GLsizei count); -#undef glDrawArraysIndirect -extern "C" void WINAPI glDrawArraysIndirect(GLenum mode, const GLvoid *indirect); -#undef glDrawArraysInstanced -extern "C" void WINAPI glDrawArraysInstanced(GLenum mode, GLint first, GLsizei count, GLsizei primcount); -#undef glDrawArraysInstancedBaseInstance -extern "C" void WINAPI glDrawArraysInstancedBaseInstance(GLenum mode, GLint first, GLsizei count, GLsizei primcount, GLuint baseinstance); -#undef glDrawArraysInstancedARB -extern "C" void WINAPI glDrawArraysInstancedARB(GLenum mode, GLint first, GLsizei count, GLsizei primcount); -#undef glDrawArraysInstancedEXT -extern "C" void WINAPI glDrawArraysInstancedEXT(GLenum mode, GLint first, GLsizei count, GLsizei primcount); -#undef glDrawBuffer -extern "C" void WINAPI glDrawBuffer(GLenum mode); -#undef glDrawElements -extern "C" void WINAPI glDrawElements(GLenum mode, GLsizei count, GLenum type, const GLvoid *indices); -#undef glDrawElementsBaseVertex -extern "C" void WINAPI glDrawElementsBaseVertex(GLenum mode, GLsizei count, GLenum type, const GLvoid *indices, GLint basevertex); -#undef glDrawElementsIndirect -extern "C" void WINAPI glDrawElementsIndirect(GLenum mode, GLenum type, const GLvoid *indirect); -#undef glDrawElementsInstanced -extern "C" void WINAPI glDrawElementsInstanced(GLenum mode, GLsizei count, GLenum type, const GLvoid *indices, GLsizei primcount); -#undef glDrawElementsInstancedBaseVertex -extern "C" void WINAPI glDrawElementsInstancedBaseVertex(GLenum mode, GLsizei count, GLenum type, const GLvoid *indices, GLsizei primcount, GLint basevertex); -#undef glDrawElementsInstancedBaseInstance -extern "C" void WINAPI glDrawElementsInstancedBaseInstance(GLenum mode, GLsizei count, GLenum type, const GLvoid *indices, GLsizei primcount, GLuint baseinstance); -#undef glDrawElementsInstancedBaseVertexBaseInstance -extern "C" void WINAPI glDrawElementsInstancedBaseVertexBaseInstance(GLenum mode, GLsizei count, GLenum type, const GLvoid *indices, GLsizei primcount, GLint basevertex, GLuint baseinstance); -#undef glDrawElementsInstancedARB -extern "C" void WINAPI glDrawElementsInstancedARB(GLenum mode, GLsizei count, GLenum type, const GLvoid *indices, GLsizei primcount); -#undef glDrawElementsInstancedEXT -extern "C" void WINAPI glDrawElementsInstancedEXT(GLenum mode, GLsizei count, GLenum type, const GLvoid *indices, GLsizei primcount); -#undef glDrawRangeElements -extern "C" void WINAPI glDrawRangeElements(GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const GLvoid *indices); -#undef glDrawRangeElementsBaseVertex -extern "C" void WINAPI glDrawRangeElementsBaseVertex(GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const GLvoid *indices, GLint basevertex); -#undef glEnable -extern "C" void WINAPI glEnable(GLenum cap); -#undef glFinish -extern "C" void WINAPI glFinish(); -#undef glFlush -extern "C" void WINAPI glFlush(); -#undef glFramebufferRenderbuffer -extern "C" void WINAPI glFramebufferRenderbuffer(GLenum target, GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer); -extern "C" void WINAPI glFramebufferRenderbufferEXT(GLenum target, GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer); -#undef glFramebufferTexture -extern "C" void WINAPI glFramebufferTexture(GLenum target, GLenum attachment, GLuint texture, GLint level); -#undef glFramebufferTexture1D -extern "C" void WINAPI glFramebufferTexture1D(GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level); -extern "C" void WINAPI glFramebufferTexture1DEXT(GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level); -#undef glFramebufferTexture2D -extern "C" void WINAPI glFramebufferTexture2D(GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level); -extern "C" void WINAPI glFramebufferTexture2DEXT(GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level); -#undef glFramebufferTexture3D -extern "C" void WINAPI glFramebufferTexture3D(GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level, GLint zoffset); -extern "C" void WINAPI glFramebufferTexture3DEXT(GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level, GLint zoffset); -#undef glFramebufferTextureARB -extern "C" void WINAPI glFramebufferTextureARB(GLenum target, GLenum attachment, GLuint texture, GLint level); -extern "C" void WINAPI glFramebufferTextureEXT(GLenum target, GLenum attachment, GLuint texture, GLint level); -#undef glFramebufferTextureLayer -extern "C" void WINAPI glFramebufferTextureLayer(GLenum target, GLenum attachment, GLuint texture, GLint level, GLint layer); -#undef glFramebufferTextureLayerARB -extern "C" void WINAPI glFramebufferTextureLayerARB(GLenum target, GLenum attachment, GLuint texture, GLint level, GLint layer); -extern "C" void WINAPI glFramebufferTextureLayerEXT(GLenum target, GLenum attachment, GLuint texture, GLint level, GLint layer); -#undef glFrontFace -extern "C" void WINAPI glFrontFace(GLenum mode); -#undef glGenTextures -extern "C" void WINAPI glGenTextures(GLsizei n, GLuint *textures); -#undef glGetBooleanv -extern "C" void WINAPI glGetBooleanv(GLenum pname, GLboolean *params); -#undef glGetDoublev -extern "C" void WINAPI glGetDoublev(GLenum pname, GLdouble *params); -#undef glGetFloatv -extern "C" void WINAPI glGetFloatv(GLenum pname, GLfloat *params); -#undef glGetIntegerv -extern "C" void WINAPI glGetIntegerv(GLenum pname, GLint *params); -#undef glGetError -extern "C" GLenum WINAPI glGetError(); -#undef glGetPointerv -extern "C" void WINAPI glGetPointerv(GLenum pname, GLvoid **params); -#undef glGetString -extern "C" const GLubyte *WINAPI glGetString(GLenum name); -#undef glGetTexImage -extern "C" void WINAPI glGetTexImage(GLenum target, GLint level, GLenum format, GLenum type, GLvoid *pixels); -#undef glGetTexLevelParameterfv -extern "C" void WINAPI glGetTexLevelParameterfv(GLenum target, GLint level, GLenum pname, GLfloat *params); -#undef glGetTexLevelParameteriv -extern "C" void WINAPI glGetTexLevelParameteriv(GLenum target, GLint level, GLenum pname, GLint *params); -#undef glGetTexParameterfv -extern "C" void WINAPI glGetTexParameterfv(GLenum target, GLenum pname, GLfloat *params); -#undef glGetTexParameteriv -extern "C" void WINAPI glGetTexParameteriv(GLenum target, GLenum pname, GLint *params); -#undef glHint -extern "C" void WINAPI glHint(GLenum target, GLenum mode); -#undef glIsEnabled -extern "C" GLboolean WINAPI glIsEnabled(GLenum cap); -#undef glIsTexture -extern "C" GLboolean WINAPI glIsTexture(GLuint texture); -#undef glLineWidth -extern "C" void WINAPI glLineWidth(GLfloat width); -#undef glLogicOp -extern "C" void WINAPI glLogicOp(GLenum opcode); -#undef glMultiDrawArrays -extern "C" void WINAPI glMultiDrawArrays(GLenum mode, const GLint *first, const GLsizei *count, GLsizei drawcount); -#undef glMultiDrawArraysIndirect -extern "C" void WINAPI glMultiDrawArraysIndirect(GLenum mode, const void *indirect, GLsizei drawcount, GLsizei stride); -#undef glMultiDrawElements -extern "C" void WINAPI glMultiDrawElements(GLenum mode, const GLsizei *count, GLenum type, const GLvoid *const *indices, GLsizei drawcount); -#undef glMultiDrawElementsBaseVertex -extern "C" void WINAPI glMultiDrawElementsBaseVertex(GLenum mode, const GLsizei *count, GLenum type, const GLvoid *const *indices, GLsizei drawcount, const GLint *basevertex); -#undef glMultiDrawElementsIndirect -extern "C" void WINAPI glMultiDrawElementsIndirect(GLenum mode, GLenum type, const void *indirect, GLsizei drawcount, GLsizei stride); -#undef glPixelStoref -extern "C" void WINAPI glPixelStoref(GLenum pname, GLfloat param); -#undef glPixelStorei -extern "C" void WINAPI glPixelStorei(GLenum pname, GLint param); -#undef glPointSize -extern "C" void WINAPI glPointSize(GLfloat size); -#undef glPolygonMode -extern "C" void WINAPI glPolygonMode(GLenum face, GLenum mode); -#undef glPolygonOffset -extern "C" void WINAPI glPolygonOffset(GLfloat factor, GLfloat units); -#undef glReadBuffer -extern "C" void WINAPI glReadBuffer(GLenum mode); -#undef glReadPixels -extern "C" void WINAPI glReadPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, GLvoid *pixels); -#undef glScissor -extern "C" void WINAPI glScissor(GLint x, GLint y, GLsizei width, GLsizei height); -#undef glStencilFunc -extern "C" void WINAPI glStencilFunc(GLenum func, GLint ref, GLuint mask); -#undef glStencilMask -extern "C" void WINAPI glStencilMask(GLuint mask); -#undef glStencilOp -extern "C" void WINAPI glStencilOp(GLenum fail, GLenum zfail, GLenum zpass); -#undef glTexImage1D -extern "C" void WINAPI glTexImage1D(GLenum target, GLint level, GLint internalformat, GLsizei width, GLint border, GLenum format, GLenum type, const GLvoid *pixels); -#undef glTexImage2D -extern "C" void WINAPI glTexImage2D(GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const GLvoid *pixels); -#undef glTexImage3D -extern "C" void WINAPI glTexImage3D(GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLenum format, GLenum type, const GLvoid *pixels); -#undef glTexParameterf -extern "C" void WINAPI glTexParameterf(GLenum target, GLenum pname, GLfloat param); -#undef glTexParameterfv -extern "C" void WINAPI glTexParameterfv(GLenum target, GLenum pname, const GLfloat *params); -#undef glTexParameteri -extern "C" void WINAPI glTexParameteri(GLenum target, GLenum pname, GLint param); -#undef glTexParameteriv -extern "C" void WINAPI glTexParameteriv(GLenum target, GLenum pname, const GLint *params); -#undef glTexSubImage1D -extern "C" void WINAPI glTexSubImage1D(GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLenum type, const GLvoid *pixels); -#undef glTexSubImage2D -extern "C" void WINAPI glTexSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *pixels); -#undef glViewport -extern "C" void WINAPI glViewport(GLint x, GLint y, GLsizei width, GLsizei height); diff --git a/msvc/source/opengl/opengl_hooks_wgl.cpp b/msvc/source/opengl/opengl_hooks_wgl.cpp deleted file mode 100644 index 1c0ed99..0000000 --- a/msvc/source/opengl/opengl_hooks_wgl.cpp +++ /dev/null @@ -1,1088 +0,0 @@ -/** - * Copyright (C) 2014 Patrick Mours. All rights reserved. - * License: https://github.com/crosire/reshade#license - */ - -#include "log.hpp" -#include "hook_manager.hpp" -#include "runtime_opengl.hpp" -#include "opengl_hooks.hpp" -#include -#include -#include -#include -#include - -DECLARE_HANDLE(HPBUFFERARB); - -static std::mutex s_mutex; -static std::unordered_set s_pbuffer_device_contexts; -static std::unordered_map s_shared_contexts; -static std::unordered_map s_opengl_runtimes; -thread_local reshade::opengl::runtime_opengl *g_current_runtime = nullptr; - -HOOK_EXPORT int WINAPI wglChoosePixelFormat(HDC hdc, const PIXELFORMATDESCRIPTOR *ppfd) -{ - LOG(INFO) << "Redirecting wglChoosePixelFormat" << '(' << hdc << ", " << ppfd << ')' << " ..."; - - assert(ppfd != nullptr); - - LOG(INFO) << "> Dumping pixel format descriptor:"; - LOG(INFO) << " +-----------------------------------------+-----------------------------------------+"; - LOG(INFO) << " | Name | Value |"; - LOG(INFO) << " +-----------------------------------------+-----------------------------------------+"; - LOG(INFO) << " | Flags | " << std::setw(39) << std::hex << ppfd->dwFlags << std::dec << " |"; - LOG(INFO) << " | ColorBits | " << std::setw(39) << static_cast(ppfd->cColorBits) << " |"; - LOG(INFO) << " | DepthBits | " << std::setw(39) << static_cast(ppfd->cDepthBits) << " |"; - LOG(INFO) << " | StencilBits | " << std::setw(39) << static_cast(ppfd->cStencilBits) << " |"; - LOG(INFO) << " +-----------------------------------------+-----------------------------------------+"; - - if (ppfd->iLayerType != PFD_MAIN_PLANE || ppfd->bReserved != 0) - { - LOG(ERROR) << "> Layered OpenGL contexts of type " << static_cast(ppfd->iLayerType) << " are not supported."; - - SetLastError(ERROR_INVALID_PARAMETER); - - return 0; - } - else if ((ppfd->dwFlags & PFD_DOUBLEBUFFER) == 0) - { - LOG(WARN) << "> Single buffered OpenGL contexts are not supported."; - } - - // Note: Windows calls into 'wglDescribePixelFormat' repeatedly from this, so make sure it reports correct results - const int format = reshade::hooks::call(wglChoosePixelFormat)(hdc, ppfd); - - if (format != 0) - LOG(INFO) << "> Returned format: " << format; - else - LOG(WARN) << "> wglChoosePixelFormat failed with error code " << (GetLastError() & 0xFFFF) << '!'; - - return format; -} - BOOL WINAPI wglChoosePixelFormatARB(HDC hdc, const int *piAttribIList, const FLOAT *pfAttribFList, UINT nMaxFormats, int *piFormats, UINT *nNumFormats) -{ - LOG(INFO) << "Redirecting wglChoosePixelFormatARB" << '(' << hdc << ", " << piAttribIList << ", " << pfAttribFList << ", " << nMaxFormats << ", " << piFormats << ", " << nNumFormats << ')' << " ..."; - - struct attribute - { - enum - { - WGL_NUMBER_PIXEL_FORMATS_ARB = 0x2000, - WGL_DRAW_TO_WINDOW_ARB = 0x2001, - WGL_DRAW_TO_BITMAP_ARB = 0x2002, - WGL_ACCELERATION_ARB = 0x2003, - WGL_NEED_PALETTE_ARB = 0x2004, - WGL_NEED_SYSTEM_PALETTE_ARB = 0x2005, - WGL_SWAP_LAYER_BUFFERS_ARB = 0x2006, - WGL_SWAP_METHOD_ARB = 0x2007, - WGL_NUMBER_OVERLAYS_ARB = 0x2008, - WGL_NUMBER_UNDERLAYS_ARB = 0x2009, - WGL_TRANSPARENT_ARB = 0x200A, - WGL_TRANSPARENT_RED_VALUE_ARB = 0x2037, - WGL_TRANSPARENT_GREEN_VALUE_ARB = 0x2038, - WGL_TRANSPARENT_BLUE_VALUE_ARB = 0x2039, - WGL_TRANSPARENT_ALPHA_VALUE_ARB = 0x203A, - WGL_TRANSPARENT_INDEX_VALUE_ARB = 0x203B, - WGL_SHARE_DEPTH_ARB = 0x200C, - WGL_SHARE_STENCIL_ARB = 0x200D, - WGL_SHARE_ACCUM_ARB = 0x200E, - WGL_SUPPORT_GDI_ARB = 0x200F, - WGL_SUPPORT_OPENGL_ARB = 0x2010, - WGL_DOUBLE_BUFFER_ARB = 0x2011, - WGL_STEREO_ARB = 0x2012, - WGL_PIXEL_TYPE_ARB = 0x2013, - WGL_COLOR_BITS_ARB = 0x2014, - WGL_RED_BITS_ARB = 0x2015, - WGL_RED_SHIFT_ARB = 0x2016, - WGL_GREEN_BITS_ARB = 0x2017, - WGL_GREEN_SHIFT_ARB = 0x2018, - WGL_BLUE_BITS_ARB = 0x2019, - WGL_BLUE_SHIFT_ARB = 0x201A, - WGL_ALPHA_BITS_ARB = 0x201B, - WGL_ALPHA_SHIFT_ARB = 0x201C, - WGL_ACCUM_BITS_ARB = 0x201D, - WGL_ACCUM_RED_BITS_ARB = 0x201E, - WGL_ACCUM_GREEN_BITS_ARB = 0x201F, - WGL_ACCUM_BLUE_BITS_ARB = 0x2020, - WGL_ACCUM_ALPHA_BITS_ARB = 0x2021, - WGL_DEPTH_BITS_ARB = 0x2022, - WGL_STENCIL_BITS_ARB = 0x2023, - WGL_AUX_BUFFERS_ARB = 0x2024, - WGL_SAMPLE_BUFFERS_ARB = 0x2041, - WGL_SAMPLES_ARB = 0x2042, - WGL_DRAW_TO_PBUFFER_ARB = 0x202D, - WGL_BIND_TO_TEXTURE_RGB_ARB = 0x2070, - WGL_BIND_TO_TEXTURE_RGBA_ARB = 0x2071, - - WGL_NO_ACCELERATION_ARB = 0x2025, - WGL_GENERIC_ACCELERATION_ARB = 0x2026, - WGL_FULL_ACCELERATION_ARB = 0x2027, - WGL_SWAP_EXCHANGE_ARB = 0x2028, - WGL_SWAP_COPY_ARB = 0x2029, - WGL_SWAP_UNDEFINED_ARB = 0x202A, - WGL_TYPE_RGBA_ARB = 0x202B, - WGL_TYPE_COLORINDEX_ARB = 0x202C, - }; - }; - - bool layerplanes = false, doublebuffered = false; - - LOG(INFO) << "> Dumping attributes:"; - LOG(INFO) << " +-----------------------------------------+-----------------------------------------+"; - LOG(INFO) << " | Attribute | Value |"; - LOG(INFO) << " +-----------------------------------------+-----------------------------------------+"; - - for (const int *attrib = piAttribIList; attrib != nullptr && *attrib != 0; attrib += 2) - { - switch (attrib[0]) - { - case attribute::WGL_DRAW_TO_WINDOW_ARB: - LOG(INFO) << " | WGL_DRAW_TO_WINDOW_ARB | " << std::setw(39) << (attrib[1] != FALSE ? "TRUE" : "FALSE") << " |"; - break; - case attribute::WGL_DRAW_TO_BITMAP_ARB: - LOG(INFO) << " | WGL_DRAW_TO_BITMAP_ARB | " << std::setw(39) << (attrib[1] != FALSE ? "TRUE" : "FALSE") << " |"; - break; - case attribute::WGL_ACCELERATION_ARB: - LOG(INFO) << " | WGL_ACCELERATION_ARB | " << std::setw(39) << std::hex << attrib[1] << std::dec << " |"; - break; - case attribute::WGL_SWAP_LAYER_BUFFERS_ARB: - layerplanes = layerplanes || attrib[1] != FALSE; - LOG(INFO) << " | WGL_SWAP_LAYER_BUFFERS_ARB | " << std::setw(39) << (attrib[1] != FALSE ? "TRUE" : "FALSE") << " |"; - break; - case attribute::WGL_SWAP_METHOD_ARB: - LOG(INFO) << " | WGL_SWAP_METHOD_ARB | " << std::setw(39) << std::hex << attrib[1] << std::dec << " |"; - break; - case attribute::WGL_NUMBER_OVERLAYS_ARB: - layerplanes = layerplanes || attrib[1] != 0; - LOG(INFO) << " | WGL_NUMBER_OVERLAYS_ARB | " << std::setw(39) << attrib[1] << " |"; - break; - case attribute::WGL_NUMBER_UNDERLAYS_ARB: - layerplanes = layerplanes || attrib[1] != 0; - LOG(INFO) << " | WGL_NUMBER_UNDERLAYS_ARB | " << std::setw(39) << attrib[1] << " |"; - break; - case attribute::WGL_SUPPORT_GDI_ARB: - LOG(INFO) << " | WGL_SUPPORT_GDI_ARB | " << std::setw(39) << (attrib[1] != FALSE ? "TRUE" : "FALSE") << " |"; - break; - case attribute::WGL_SUPPORT_OPENGL_ARB: - LOG(INFO) << " | WGL_SUPPORT_OPENGL_ARB | " << std::setw(39) << (attrib[1] != FALSE ? "TRUE" : "FALSE") << " |"; - break; - case attribute::WGL_DOUBLE_BUFFER_ARB: - doublebuffered = attrib[1] != FALSE; - LOG(INFO) << " | WGL_DOUBLE_BUFFER_ARB | " << std::setw(39) << (attrib[1] != FALSE ? "TRUE" : "FALSE") << " |"; - break; - case attribute::WGL_STEREO_ARB: - LOG(INFO) << " | WGL_STEREO_ARB | " << std::setw(39) << (attrib[1] != FALSE ? "TRUE" : "FALSE") << " |"; - break; - case attribute::WGL_RED_BITS_ARB: - LOG(INFO) << " | WGL_RED_BITS_ARB | " << std::setw(39) << attrib[1] << " |"; - break; - case attribute::WGL_GREEN_BITS_ARB: - LOG(INFO) << " | WGL_GREEN_BITS_ARB | " << std::setw(39) << attrib[1] << " |"; - break; - case attribute::WGL_BLUE_BITS_ARB: - LOG(INFO) << " | WGL_BLUE_BITS_ARB | " << std::setw(39) << attrib[1] << " |"; - break; - case attribute::WGL_ALPHA_BITS_ARB: - LOG(INFO) << " | WGL_ALPHA_BITS_ARB | " << std::setw(39) << attrib[1] << " |"; - break; - case attribute::WGL_COLOR_BITS_ARB: - LOG(INFO) << " | WGL_COLOR_BITS_ARB | " << std::setw(39) << attrib[1] << " |"; - break; - case attribute::WGL_DEPTH_BITS_ARB: - LOG(INFO) << " | WGL_DEPTH_BITS_ARB | " << std::setw(39) << attrib[1] << " |"; - break; - case attribute::WGL_STENCIL_BITS_ARB: - LOG(INFO) << " | WGL_STENCIL_BITS_ARB | " << std::setw(39) << attrib[1] << " |"; - break; - case attribute::WGL_SAMPLE_BUFFERS_ARB: - LOG(INFO) << " | WGL_SAMPLE_BUFFERS_ARB | " << std::setw(39) << attrib[1] << " |"; - break; - case attribute::WGL_SAMPLES_ARB: - LOG(INFO) << " | WGL_SAMPLES_ARB | " << std::setw(39) << attrib[1] << " |"; - break; - case attribute::WGL_DRAW_TO_PBUFFER_ARB: - LOG(INFO) << " | WGL_DRAW_TO_PBUFFER_ARB | " << std::setw(39) << (attrib[1] != FALSE ? "TRUE" : "FALSE") << " |"; - break; - default: - LOG(INFO) << " | " << std::hex << std::setw(39) << attrib[0] << " | " << std::setw(39) << attrib[1] << std::dec << " |"; - break; - } - } - - for (const FLOAT *attrib = pfAttribFList; attrib != nullptr && *attrib != 0.0f; attrib += 2) - { - LOG(INFO) << " | " << std::hex << std::setw(39) << static_cast(attrib[0]) << " | " << std::setw(39) << attrib[1] << std::dec << " |"; - } - - LOG(INFO) << " +-----------------------------------------+-----------------------------------------+"; - - if (layerplanes) - { - LOG(ERROR) << "> Layered OpenGL contexts are not supported."; - - SetLastError(ERROR_INVALID_PARAMETER); - - return FALSE; - } - else if (!doublebuffered) - { - LOG(WARN) << "> Single buffered OpenGL contexts are not supported."; - } - - if (!reshade::hooks::call(wglChoosePixelFormatARB)(hdc, piAttribIList, pfAttribFList, nMaxFormats, piFormats, nNumFormats)) - { - LOG(WARN) << "> wglChoosePixelFormatARB failed with error code " << (GetLastError() & 0xFFFF) << '!'; - - return FALSE; - } - - assert(nNumFormats != nullptr); - - if (*nNumFormats < nMaxFormats) - nMaxFormats = *nNumFormats; - - std::string formats; - - for (UINT i = 0; i < nMaxFormats; ++i) - { - assert(piFormats[i] != 0); - - formats += " " + std::to_string(piFormats[i]); - } - - LOG(INFO) << "> Returned format(s):" << formats; - - return TRUE; -} -HOOK_EXPORT int WINAPI wglGetPixelFormat(HDC hdc) -{ - return reshade::hooks::call(wglGetPixelFormat)(hdc); -} - BOOL WINAPI wglGetPixelFormatAttribivARB(HDC hdc, int iPixelFormat, int iLayerPlane, UINT nAttributes, const int *piAttributes, int *piValues) -{ - if (iLayerPlane != 0) - { - LOG(WARN) << "Access to layer plane at index " << iLayerPlane << " is unsupported."; - - SetLastError(ERROR_INVALID_PARAMETER); - - return FALSE; - } - - return reshade::hooks::call(wglGetPixelFormatAttribivARB)(hdc, iPixelFormat, 0, nAttributes, piAttributes, piValues); -} - BOOL WINAPI wglGetPixelFormatAttribfvARB(HDC hdc, int iPixelFormat, int iLayerPlane, UINT nAttributes, const int *piAttributes, FLOAT *pfValues) -{ - if (iLayerPlane != 0) - { - LOG(WARN) << "Access to layer plane at index " << iLayerPlane << " is unsupported."; - - SetLastError(ERROR_INVALID_PARAMETER); - - return FALSE; - } - - return reshade::hooks::call(wglGetPixelFormatAttribfvARB)(hdc, iPixelFormat, 0, nAttributes, piAttributes, pfValues); -} -HOOK_EXPORT BOOL WINAPI wglSetPixelFormat(HDC hdc, int iPixelFormat, const PIXELFORMATDESCRIPTOR *ppfd) -{ - LOG(INFO) << "Redirecting wglSetPixelFormat" << '(' << hdc << ", " << iPixelFormat << ", " << ppfd << ')' << " ..."; - - if (!reshade::hooks::call(wglSetPixelFormat)(hdc, iPixelFormat, ppfd)) - { - LOG(WARN) << "> wglSetPixelFormat failed with error code " << (GetLastError() & 0xFFFF) << '!'; - - return FALSE; - } - - if (GetPixelFormat(hdc) == 0) - { - LOG(WARN) << "> Application mistakenly called wglSetPixelFormat directly. Passing on to SetPixelFormat:"; - - SetPixelFormat(hdc, iPixelFormat, ppfd); - } - - return TRUE; -} -HOOK_EXPORT int WINAPI wglDescribePixelFormat(HDC hdc, int iPixelFormat, UINT nBytes, LPPIXELFORMATDESCRIPTOR ppfd) -{ - return reshade::hooks::call(wglDescribePixelFormat)(hdc, iPixelFormat, nBytes, ppfd); -} - -HOOK_EXPORT BOOL WINAPI wglDescribeLayerPlane(HDC hdc, int iPixelFormat, int iLayerPlane, UINT nBytes, LPLAYERPLANEDESCRIPTOR plpd) -{ - LOG(INFO) << "Redirecting wglDescribeLayerPlane" << '(' << hdc << ", " << iPixelFormat << ", " << iLayerPlane << ", " << nBytes << ", " << plpd << ')' << " ..."; - LOG(WARN) << "Access to layer plane at index " << iLayerPlane << " is unsupported."; - - SetLastError(ERROR_NOT_SUPPORTED); - - return FALSE; -} -HOOK_EXPORT BOOL WINAPI wglRealizeLayerPalette(HDC hdc, int iLayerPlane, BOOL b) -{ - LOG(INFO) << "Redirecting wglRealizeLayerPalette" << '(' << hdc << ", " << iLayerPlane << ", " << b << ')' << " ..."; - LOG(WARN) << "Access to layer plane at index " << iLayerPlane << " is unsupported."; - - SetLastError(ERROR_NOT_SUPPORTED); - - return FALSE; -} -HOOK_EXPORT int WINAPI wglGetLayerPaletteEntries(HDC hdc, int iLayerPlane, int iStart, int cEntries, COLORREF *pcr) -{ - LOG(INFO) << "Redirecting wglGetLayerPaletteEntries" << '(' << hdc << ", " << iLayerPlane << ", " << iStart << ", " << cEntries << ", " << pcr << ')' << " ..."; - LOG(WARN) << "Access to layer plane at index " << iLayerPlane << " is unsupported."; - - SetLastError(ERROR_NOT_SUPPORTED); - - return 0; -} -HOOK_EXPORT int WINAPI wglSetLayerPaletteEntries(HDC hdc, int iLayerPlane, int iStart, int cEntries, const COLORREF *pcr) -{ - LOG(INFO) << "Redirecting wglSetLayerPaletteEntries" << '(' << hdc << ", " << iLayerPlane << ", " << iStart << ", " << cEntries << ", " << pcr << ')' << " ..."; - LOG(WARN) << "Access to layer plane at index " << iLayerPlane << " is unsupported."; - - SetLastError(ERROR_NOT_SUPPORTED); - - return 0; -} - -HOOK_EXPORT HGLRC WINAPI wglCreateContext(HDC hdc) -{ - LOG(INFO) << "Redirecting wglCreateContext" << '(' << hdc << ')' << " ..."; - LOG(INFO) << "> Passing on to wglCreateLayerContext ..."; - - return wglCreateLayerContext(hdc, 0); -} - HGLRC WINAPI wglCreateContextAttribsARB(HDC hdc, HGLRC hShareContext, const int *piAttribList) -{ - LOG(INFO) << "Redirecting wglCreateContextAttribsARB" << '(' << hdc << ", " << hShareContext << ", " << piAttribList << ')' << " ..."; - - struct attribute - { - enum - { - WGL_CONTEXT_MAJOR_VERSION_ARB = 0x2091, - WGL_CONTEXT_MINOR_VERSION_ARB = 0x2092, - WGL_CONTEXT_LAYER_PLANE_ARB = 0x2093, - WGL_CONTEXT_FLAGS_ARB = 0x2094, - WGL_CONTEXT_PROFILE_MASK_ARB = 0x9126, - - WGL_CONTEXT_DEBUG_BIT_ARB = 0x0001, - WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB = 0x0002, - WGL_CONTEXT_CORE_PROFILE_BIT_ARB = 0x00000001, - WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB = 0x00000002, - }; - - int name, value; - }; - - int i = 0, major = 1, minor = 0, flags = 0; - bool core = true, compatibility = false; - attribute attribs[8] = { }; - - for (const int *attrib = piAttribList; attrib != nullptr && *attrib != 0 && i < 5; attrib += 2, ++i) - { - attribs[i].name = attrib[0]; - attribs[i].value = attrib[1]; - - switch (attrib[0]) - { - case attribute::WGL_CONTEXT_MAJOR_VERSION_ARB: - major = attrib[1]; - break; - case attribute::WGL_CONTEXT_MINOR_VERSION_ARB: - minor = attrib[1]; - break; - case attribute::WGL_CONTEXT_FLAGS_ARB: - flags = attrib[1]; - break; - case attribute::WGL_CONTEXT_PROFILE_MASK_ARB: - core = (attrib[1] & attribute::WGL_CONTEXT_CORE_PROFILE_BIT_ARB) != 0; - compatibility = (attrib[1] & attribute::WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB) != 0; - break; - } - } - - if (major < 3 || minor < 2) - core = compatibility = false; - -#ifdef _DEBUG - flags |= attribute::WGL_CONTEXT_DEBUG_BIT_ARB; -#endif - - // This works because the specs specifically note that "If an attribute is specified more than once, then the last value specified is used." - attribs[i].name = attribute::WGL_CONTEXT_FLAGS_ARB; - attribs[i++].value = flags; - attribs[i].name = attribute::WGL_CONTEXT_PROFILE_MASK_ARB; - attribs[i++].value = compatibility ? attribute::WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB : attribute::WGL_CONTEXT_CORE_PROFILE_BIT_ARB; - - LOG(INFO) << "> Requesting " << (core ? "core " : compatibility ? "compatibility " : "") << "OpenGL context for version " << major << '.' << minor << " ..."; - - if (major < 4 || minor < 3) - { - LOG(WARN) << "> Replacing requested version with 4.3 ..."; - - for (int k = 0; k < i; ++k) - { - switch (attribs[k].name) - { - case attribute::WGL_CONTEXT_MAJOR_VERSION_ARB: - attribs[k].value = 4; - break; - case attribute::WGL_CONTEXT_MINOR_VERSION_ARB: - attribs[k].value = 3; - break; - case attribute::WGL_CONTEXT_PROFILE_MASK_ARB: - attribs[k].value = attribute::WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB; - break; - } - } - } - - const HGLRC hglrc = reshade::hooks::call(wglCreateContextAttribsARB)(hdc, hShareContext, reinterpret_cast(attribs)); - - if (hglrc == nullptr) - { - LOG(WARN) << "> wglCreateContextAttribsARB failed with error code " << (GetLastError() & 0xFFFF) << '!'; - - return nullptr; - } - - { const std::lock_guard lock(s_mutex); - s_shared_contexts.emplace(hglrc, hShareContext); - - if (hShareContext != nullptr) - { - // Find root share context - auto it = s_shared_contexts.find(hShareContext); - - while (it != s_shared_contexts.end() && it->second != nullptr) - { - it = s_shared_contexts.find(s_shared_contexts.at(hglrc) = it->second); - } - } - } - - LOG(INFO) << "> Returning OpenGL context " << hglrc << '.'; - - return hglrc; -} -HOOK_EXPORT HGLRC WINAPI wglCreateLayerContext(HDC hdc, int iLayerPlane) -{ - LOG(INFO) << "Redirecting wglCreateLayerContext" << '(' << hdc << ", " << iLayerPlane << ')' << " ..."; - - if (iLayerPlane != 0) - { - LOG(WARN) << "Access to layer plane at index " << iLayerPlane << " is unsupported."; - - SetLastError(ERROR_INVALID_PARAMETER); - - return nullptr; - } - - const HGLRC hglrc = reshade::hooks::call(wglCreateLayerContext)(hdc, iLayerPlane); - - if (hglrc == nullptr) - { - LOG(WARN) << "> wglCreateLayerContext failed with error code " << (GetLastError() & 0xFFFF) << '!'; - - return nullptr; - } - - { const std::lock_guard lock(s_mutex); - s_shared_contexts.emplace(hglrc, nullptr); - } - - LOG(INFO) << "> Returning OpenGL context " << hglrc << '.'; - - return hglrc; -} -HOOK_EXPORT BOOL WINAPI wglCopyContext(HGLRC hglrcSrc, HGLRC hglrcDst, UINT mask) -{ - return reshade::hooks::call(wglCopyContext)(hglrcSrc, hglrcDst, mask); -} -HOOK_EXPORT BOOL WINAPI wglDeleteContext(HGLRC hglrc) -{ - LOG(INFO) << "Redirecting wglDeleteContext" << '(' << hglrc << ')' << " ..."; - - if (const auto it = s_opengl_runtimes.find(hglrc); it != s_opengl_runtimes.end()) - { - LOG(INFO) << "> Cleaning up runtime " << it->second << " ..."; - - // Choose a random device context to make current with (and hope that is still alive) - const HDC hdc = *it->second->_hdcs.begin(); - - // Set the render context current so its resources can be cleaned up - if (reshade::hooks::call(wglMakeCurrent)(hdc, hglrc)) - { - it->second->on_reset(); - - delete it->second; - } - else - { - LOG(WARN) << "> Unable to make context current, leaking resources ..."; - } - - s_opengl_runtimes.erase(it); - } - - { const std::lock_guard lock(s_mutex); - for (auto it = s_shared_contexts.begin(); it != s_shared_contexts.end();) - { - if (it->first == hglrc) - { - it = s_shared_contexts.erase(it); - continue; - } - else if (it->second == hglrc) - { - it->second = nullptr; - } - - ++it; - } - } - - if (!reshade::hooks::call(wglDeleteContext)(hglrc)) - { - LOG(WARN) << "> wglDeleteContext failed with error code " << (GetLastError() & 0xFFFF) << '!'; - - return FALSE; - } - - return TRUE; -} - -HOOK_EXPORT BOOL WINAPI wglShareLists(HGLRC hglrc1, HGLRC hglrc2) -{ - LOG(INFO) << "Redirecting wglShareLists" << '(' << hglrc1 << ", " << hglrc2 << ')' << " ..."; - - if (!reshade::hooks::call(wglShareLists)(hglrc1, hglrc2)) - { - LOG(WARN) << "> wglShareLists failed with error code " << (GetLastError() & 0xFFFF) << '!'; - - return FALSE; - } - - { const std::lock_guard lock(s_mutex); - s_shared_contexts[hglrc2] = hglrc1; - } - - return TRUE; -} - -HOOK_EXPORT BOOL WINAPI wglMakeCurrent(HDC hdc, HGLRC hglrc) -{ - static const auto trampoline = reshade::hooks::call(wglMakeCurrent); - -#if RESHADE_VERBOSE_LOG - LOG(DEBUG) << "Redirecting wglMakeCurrent" << '(' << hdc << ", " << hglrc << ')' << " ..."; -#endif - - const HGLRC prev_hglrc = wglGetCurrentContext(); - - if (!trampoline(hdc, hglrc)) - { -#if RESHADE_VERBOSE_LOG - LOG(WARN) << "> wglMakeCurrent failed with error code " << (GetLastError() & 0xFFFF) << '!'; -#endif - return FALSE; - } - - if (hglrc == prev_hglrc) - { - // Nothing has changed, so there is nothing more to do - return TRUE; - } - else if (hglrc == nullptr) - { - g_current_runtime = nullptr; - - return TRUE; - } - - const std::lock_guard lock(s_mutex); - - if (s_shared_contexts.at(hglrc) != nullptr) - { - hglrc = s_shared_contexts.at(hglrc); - -#if RESHADE_VERBOSE_LOG - LOG(DEBUG) << "> Using shared OpenGL context " << hglrc << '.'; -#endif - } - - if (const auto it = s_opengl_runtimes.find(hglrc); it != s_opengl_runtimes.end()) - { - if (it->second != g_current_runtime) - { - g_current_runtime = it->second; - -#if RESHADE_VERBOSE_LOG - LOG(DEBUG) << "> Switched to existing runtime " << it->second << '.'; -#endif - } - - // Keep track of all device contexts that were used with this render context - // Do this outside the above if statement since the game may change the device context without changing the render context and thus the current runtime - it->second->_hdcs.insert(hdc); - } - else - { - const HWND hwnd = WindowFromDC(hdc); - - if (hwnd == nullptr || s_pbuffer_device_contexts.find(hdc) != s_pbuffer_device_contexts.end()) - { - LOG(WARN) << "Skipping render context " << hglrc << " because there is no window associated with its device context " << hdc << '.'; - - return TRUE; - } - else if ((GetClassLongPtr(hwnd, GCL_STYLE) & CS_OWNDC) == 0) - { - LOG(WARN) << "Window class style of window " << hwnd << " is missing 'CS_OWNDC' flag."; - } - - // Load original OpenGL functions instead of using the hooked ones - gl3wInit2([](const char *name) { - extern std::filesystem::path get_system_path(); - // First attempt to load from the OpenGL ICD - FARPROC address = reshade::hooks::call(wglGetProcAddress)(name); - if (address == nullptr) address = GetProcAddress( // Load from the Windows OpenGL DLL if that fails - GetModuleHandleW((get_system_path() / "opengl32.dll").c_str()), name); - return reinterpret_cast(address); - }); - - if (gl3wIsSupported(4, 3)) - { - const auto runtime = new reshade::opengl::runtime_opengl(); - runtime->_hdcs.insert(hdc); - - g_current_runtime = s_opengl_runtimes[hglrc] = runtime; - -#if RESHADE_VERBOSE_LOG - LOG(DEBUG) << "> Switched to new runtime " << runtime << '.'; -#endif - } - else - { - LOG(ERROR) << "Your graphics card does not seem to support OpenGL 4.3. Initialization failed."; - - g_current_runtime = nullptr; - } - } - - return TRUE; -} - -HOOK_EXPORT HDC WINAPI wglGetCurrentDC() -{ - static const auto trampoline = reshade::hooks::call(wglGetCurrentDC); - return trampoline(); -} -HOOK_EXPORT HGLRC WINAPI wglGetCurrentContext() -{ - static const auto trampoline = reshade::hooks::call(wglGetCurrentContext); - return trampoline(); -} - - HPBUFFERARB WINAPI wglCreatePbufferARB(HDC hdc, int iPixelFormat, int iWidth, int iHeight, const int *piAttribList) -{ - LOG(INFO) << "Redirecting wglCreatePbufferARB" << '(' << hdc << ", " << iPixelFormat << ", " << iWidth << ", " << iHeight << ", " << piAttribList << ')' << " ..."; - - struct attribute - { - enum - { - WGL_PBUFFER_LARGEST_ARB = 0x2033, - WGL_TEXTURE_FORMAT_ARB = 0x2072, - WGL_TEXTURE_TARGET_ARB = 0x2073, - WGL_MIPMAP_TEXTURE_ARB = 0x2074, - - WGL_TEXTURE_RGB_ARB = 0x2075, - WGL_TEXTURE_RGBA_ARB = 0x2076, - WGL_NO_TEXTURE_ARB = 0x2077, - }; - }; - - LOG(INFO) << "> Dumping attributes:"; - LOG(INFO) << " +-----------------------------------------+-----------------------------------------+"; - LOG(INFO) << " | Attribute | Value |"; - LOG(INFO) << " +-----------------------------------------+-----------------------------------------+"; - - for (const int *attrib = piAttribList; attrib != nullptr && *attrib != 0; attrib += 2) - { - switch (attrib[0]) - { - case attribute::WGL_PBUFFER_LARGEST_ARB: - LOG(INFO) << " | WGL_PBUFFER_LARGEST_ARB | " << std::setw(39) << (attrib[1] != FALSE ? "TRUE" : "FALSE") << " |"; - break; - case attribute::WGL_TEXTURE_FORMAT_ARB: - LOG(INFO) << " | WGL_TEXTURE_FORMAT_ARB | " << std::setw(39) << std::hex << attrib[1] << std::dec << " |"; - break; - case attribute::WGL_TEXTURE_TARGET_ARB: - LOG(INFO) << " | WGL_TEXTURE_TARGET_ARB | " << std::setw(39) << std::hex << attrib[1] << std::dec << " |"; - break; - default: - LOG(INFO) << " | " << std::hex << std::setw(39) << attrib[0] << " | " << std::setw(39) << attrib[1] << std::dec << " |"; - break; - } - } - - LOG(INFO) << " +-----------------------------------------+-----------------------------------------+"; - - const HPBUFFERARB hpbuffer = reshade::hooks::call(wglCreatePbufferARB)(hdc, iPixelFormat, iWidth, iHeight, piAttribList); - - if (hpbuffer == nullptr) - { - LOG(WARN) << "> wglCreatePbufferARB failed with error code " << (GetLastError() & 0xFFFF) << '!'; - - return nullptr; - } - -#if RESHADE_VERBOSE_LOG - LOG(DEBUG) << "> Returning pixel buffer " << hpbuffer << '.'; -#endif - - return hpbuffer; -} - BOOL WINAPI wglDestroyPbufferARB(HPBUFFERARB hPbuffer) -{ - LOG(INFO) << "Redirecting wglDestroyPbufferARB" << '(' << hPbuffer << ')' << " ..."; - - if (!reshade::hooks::call(wglDestroyPbufferARB)(hPbuffer)) - { - LOG(WARN) << "> wglDestroyPbufferARB failed with error code " << (GetLastError() & 0xFFFF) << '!'; - return FALSE; - } - - return TRUE; -} - BOOL WINAPI wglQueryPbufferARB(HPBUFFERARB hPbuffer, int iAttribute, int *piValue) -{ - return reshade::hooks::call(wglQueryPbufferARB)(hPbuffer, iAttribute, piValue); -} - HDC WINAPI wglGetPbufferDCARB(HPBUFFERARB hPbuffer) -{ - LOG(INFO) << "Redirecting wglGetPbufferDCARB" << '(' << hPbuffer << ')' << " ..."; - - const HDC hdc = reshade::hooks::call(wglGetPbufferDCARB)(hPbuffer); - - if (hdc == nullptr) - { - LOG(WARN) << "> wglGetPbufferDCARB failed with error code " << (GetLastError() & 0xFFFF) << '!'; - - return nullptr; - } - - { const std::lock_guard lock(s_mutex); - s_pbuffer_device_contexts.insert(hdc); - } - -#if RESHADE_VERBOSE_LOG - LOG(DEBUG) << "> Returning pixel buffer device context " << hdc << '.'; -#endif - - return hdc; -} - int WINAPI wglReleasePbufferDCARB(HPBUFFERARB hPbuffer, HDC hdc) -{ - LOG(INFO) << "Redirecting wglReleasePbufferDCARB" << '(' << hPbuffer << ')' << " ..."; - - if (!reshade::hooks::call(wglReleasePbufferDCARB)(hPbuffer, hdc)) - { - LOG(WARN) << "> wglReleasePbufferDCARB failed with error code " << (GetLastError() & 0xFFFF) << '!'; - - return FALSE; - } - - { const std::lock_guard lock(s_mutex); - s_pbuffer_device_contexts.erase(hdc); - } - - return TRUE; -} - - BOOL WINAPI wglSwapIntervalEXT(int interval) -{ - static const auto trampoline = reshade::hooks::call(wglSwapIntervalEXT); - return trampoline(interval); -} - int WINAPI wglGetSwapIntervalEXT() -{ - static const auto trampoline = reshade::hooks::call(wglGetSwapIntervalEXT); - return trampoline(); -} - -HOOK_EXPORT BOOL WINAPI wglSwapBuffers(HDC hdc) -{ - static const auto trampoline = reshade::hooks::call(wglSwapBuffers); - - const HWND hwnd = WindowFromDC(hdc); - - // Find the runtime that is associated with this device context - const auto it = std::find_if(s_opengl_runtimes.begin(), s_opengl_runtimes.end(), - [hdc](const std::pair &it) { return it.second->_hdcs.count(hdc); }); - - // The window handle can be invalid if the window was already destroyed - if (hwnd != nullptr && it != s_opengl_runtimes.end()) - { - RECT rect = { 0, 0, 0, 0 }; - GetClientRect(hwnd, &rect); - - const auto width = static_cast(rect.right - rect.left); - const auto height = static_cast(rect.bottom - rect.top); - - if (width != it->second->frame_width() || height != it->second->frame_height()) - { - LOG(INFO) << "Resizing runtime " << it->second << " on device context " << hdc << " to " << width << "x" << height << " ..."; - - it->second->on_reset(); - - if (!(width == 0 && height == 0) && !it->second->on_init(hwnd, width, height)) - LOG(ERROR) << "Failed to recreate OpenGL runtime environment on runtime " << it->second << '.'; - } - - // Assume that the correct OpenGL context is still current here - it->second->on_present(); - } - - return trampoline(hdc); -} -HOOK_EXPORT BOOL WINAPI wglSwapLayerBuffers(HDC hdc, UINT i) -{ - if (i != WGL_SWAP_MAIN_PLANE) - { - const int index = i >= WGL_SWAP_UNDERLAY1 ? static_cast(-std::log(i >> 16) / std::log(2) - 1) : static_cast(std::log(i) / std::log(2)); - - LOG(INFO) << "Redirecting wglSwapLayerBuffers" << '(' << hdc << ", " << i << ')' << " ..."; - LOG(WARN) << "Access to layer plane at index " << index << " is unsupported."; - - SetLastError(ERROR_INVALID_PARAMETER); - - return FALSE; - } - - return wglSwapBuffers(hdc); -} -HOOK_EXPORT DWORD WINAPI wglSwapMultipleBuffers(UINT cNumBuffers, const WGLSWAP *pBuffers) -{ - for (UINT i = 0; i < cNumBuffers; ++i) - { - assert(pBuffers != nullptr); - - wglSwapBuffers(pBuffers[i].hdc); - } - - return 0; -} - -HOOK_EXPORT BOOL WINAPI wglUseFontBitmapsA(HDC hdc, DWORD dw1, DWORD dw2, DWORD dw3) -{ - static const auto trampoline = reshade::hooks::call(wglUseFontBitmapsA); - return trampoline(hdc, dw1, dw2, dw3); -} -HOOK_EXPORT BOOL WINAPI wglUseFontBitmapsW(HDC hdc, DWORD dw1, DWORD dw2, DWORD dw3) -{ - static const auto trampoline = reshade::hooks::call(wglUseFontBitmapsW); - return trampoline(hdc, dw1, dw2, dw3); -} -HOOK_EXPORT BOOL WINAPI wglUseFontOutlinesA(HDC hdc, DWORD dw1, DWORD dw2, DWORD dw3, FLOAT f1, FLOAT f2, int i, LPGLYPHMETRICSFLOAT pgmf) -{ - static const auto trampoline = reshade::hooks::call(wglUseFontOutlinesA); - return trampoline(hdc, dw1, dw2, dw3, f1, f2, i, pgmf); -} -HOOK_EXPORT BOOL WINAPI wglUseFontOutlinesW(HDC hdc, DWORD dw1, DWORD dw2, DWORD dw3, FLOAT f1, FLOAT f2, int i, LPGLYPHMETRICSFLOAT pgmf) -{ - static const auto trampoline = reshade::hooks::call(wglUseFontOutlinesW); - return trampoline(hdc, dw1, dw2, dw3, f1, f2, i, pgmf); -} - -HOOK_EXPORT PROC WINAPI wglGetProcAddress(LPCSTR lpszProc) -{ - static const auto trampoline = reshade::hooks::call(wglGetProcAddress); - const PROC address = trampoline(lpszProc); - - if (address == nullptr || lpszProc == nullptr) - return nullptr; - else if (0 == strcmp(lpszProc, "glBindTexture")) - return reinterpret_cast(glBindTexture); - else if (0 == strcmp(lpszProc, "glBlendFunc")) - return reinterpret_cast(glBlendFunc); - else if (0 == strcmp(lpszProc, "glClear")) - return reinterpret_cast(glClear); - else if (0 == strcmp(lpszProc, "glClearColor")) - return reinterpret_cast(glClearColor); - else if (0 == strcmp(lpszProc, "glClearDepth")) - return reinterpret_cast(glClearDepth); - else if (0 == strcmp(lpszProc, "glClearStencil")) - return reinterpret_cast(glClearStencil); - else if (0 == strcmp(lpszProc, "glCopyTexImage1D")) - return reinterpret_cast(glCopyTexImage1D); - else if (0 == strcmp(lpszProc, "glCopyTexImage2D")) - return reinterpret_cast(glCopyTexImage2D); - else if (0 == strcmp(lpszProc, "glCopyTexSubImage1D")) - return reinterpret_cast(glCopyTexSubImage1D); - else if (0 == strcmp(lpszProc, "glCopyTexSubImage2D")) - return reinterpret_cast(glCopyTexSubImage2D); - else if (0 == strcmp(lpszProc, "glCullFace")) - return reinterpret_cast(glCullFace); - else if (0 == strcmp(lpszProc, "glDeleteTextures")) - return reinterpret_cast(glDeleteTextures); - else if (0 == strcmp(lpszProc, "glDepthFunc")) - return reinterpret_cast(glDepthFunc); - else if (0 == strcmp(lpszProc, "glDepthMask")) - return reinterpret_cast(glDepthMask); - else if (0 == strcmp(lpszProc, "glDepthRange")) - return reinterpret_cast(glDepthRange); - else if (0 == strcmp(lpszProc, "glDisable")) - return reinterpret_cast(glDisable); - else if (0 == strcmp(lpszProc, "glDrawArrays")) - return reinterpret_cast(glDrawArrays); - else if (0 == strcmp(lpszProc, "glDrawBuffer")) - return reinterpret_cast(glDrawBuffer); - else if (0 == strcmp(lpszProc, "glDrawElements")) - return reinterpret_cast(glDrawElements); - else if (0 == strcmp(lpszProc, "glEnable")) - return reinterpret_cast(glEnable); - else if (0 == strcmp(lpszProc, "glFinish")) - return reinterpret_cast(glFinish); - else if (0 == strcmp(lpszProc, "glFlush")) - return reinterpret_cast(glFlush); - else if (0 == strcmp(lpszProc, "glFrontFace")) - return reinterpret_cast(glFrontFace); - else if (0 == strcmp(lpszProc, "glGenTextures")) - return reinterpret_cast(glGenTextures); - else if (0 == strcmp(lpszProc, "glGetBooleanv")) - return reinterpret_cast(glGetBooleanv); - else if (0 == strcmp(lpszProc, "glGetDoublev")) - return reinterpret_cast(glGetDoublev); - else if (0 == strcmp(lpszProc, "glGetFloatv")) - return reinterpret_cast(glGetFloatv); - else if (0 == strcmp(lpszProc, "glGetIntegerv")) - return reinterpret_cast(glGetIntegerv); - else if (0 == strcmp(lpszProc, "glGetError")) - return reinterpret_cast(glGetError); - else if (0 == strcmp(lpszProc, "glGetPointerv")) - return reinterpret_cast(glGetPointerv); - else if (0 == strcmp(lpszProc, "glGetString")) - return reinterpret_cast(glGetString); - else if (0 == strcmp(lpszProc, "glGetTexImage")) - return reinterpret_cast(glGetTexImage); - else if (0 == strcmp(lpszProc, "glGetTexLevelParameterfv")) - return reinterpret_cast(glGetTexLevelParameterfv); - else if (0 == strcmp(lpszProc, "glGetTexLevelParameteriv")) - return reinterpret_cast(glGetTexLevelParameteriv); - else if (0 == strcmp(lpszProc, "glGetTexParameterfv")) - return reinterpret_cast(glGetTexParameterfv); - else if (0 == strcmp(lpszProc, "glGetTexParameteriv")) - return reinterpret_cast(glGetTexParameteriv); - else if (0 == strcmp(lpszProc, "glHint")) - return reinterpret_cast(glHint); - else if (0 == strcmp(lpszProc, "glIsEnabled")) - return reinterpret_cast(glIsEnabled); - else if (0 == strcmp(lpszProc, "glIsTexture")) - return reinterpret_cast(glIsTexture); - else if (0 == strcmp(lpszProc, "glLineWidth")) - return reinterpret_cast(glLineWidth); - else if (0 == strcmp(lpszProc, "glLogicOp")) - return reinterpret_cast(glLogicOp); - else if (0 == strcmp(lpszProc, "glPixelStoref")) - return reinterpret_cast(glPixelStoref); - else if (0 == strcmp(lpszProc, "glPixelStorei")) - return reinterpret_cast(glPixelStorei); - else if (0 == strcmp(lpszProc, "glPointSize")) - return reinterpret_cast(glPointSize); - else if (0 == strcmp(lpszProc, "glPolygonMode")) - return reinterpret_cast(glPolygonMode); - else if (0 == strcmp(lpszProc, "glPolygonOffset")) - return reinterpret_cast(glPolygonOffset); - else if (0 == strcmp(lpszProc, "glReadBuffer")) - return reinterpret_cast(glReadBuffer); - else if (0 == strcmp(lpszProc, "glReadPixels")) - return reinterpret_cast(glReadPixels); - else if (0 == strcmp(lpszProc, "glScissor")) - return reinterpret_cast(glScissor); - else if (0 == strcmp(lpszProc, "glStencilFunc")) - return reinterpret_cast(glStencilFunc); - else if (0 == strcmp(lpszProc, "glStencilMask")) - return reinterpret_cast(glStencilMask); - else if (0 == strcmp(lpszProc, "glStencilOp")) - return reinterpret_cast(glStencilOp); - else if (0 == strcmp(lpszProc, "glTexImage1D")) - return reinterpret_cast(glTexImage1D); - else if (0 == strcmp(lpszProc, "glTexImage2D")) - return reinterpret_cast(glTexImage2D); - else if (0 == strcmp(lpszProc, "glTexParameterf")) - return reinterpret_cast(glTexParameterf); - else if (0 == strcmp(lpszProc, "glTexParameterfv")) - return reinterpret_cast(glTexParameterfv); - else if (0 == strcmp(lpszProc, "glTexParameteri")) - return reinterpret_cast(glTexParameteri); - else if (0 == strcmp(lpszProc, "glTexParameteriv")) - return reinterpret_cast(glTexParameteriv); - else if (0 == strcmp(lpszProc, "glTexSubImage1D")) - return reinterpret_cast(glTexSubImage1D); - else if (0 == strcmp(lpszProc, "glTexSubImage2D")) - return reinterpret_cast(glTexSubImage2D); - else if (0 == strcmp(lpszProc, "glViewport")) - return reinterpret_cast(glViewport); - else if (static bool s_hooks_not_installed = true; s_hooks_not_installed) - { - // Install all OpenGL hooks in a single batch job - reshade::hooks::install("glDrawArraysIndirect", reinterpret_cast(trampoline("glDrawArraysIndirect")), reinterpret_cast(glDrawArraysIndirect), true); - reshade::hooks::install("glDrawArraysInstanced", reinterpret_cast(trampoline("glDrawArraysInstanced")), reinterpret_cast(glDrawArraysInstanced), true); - reshade::hooks::install("glDrawArraysInstancedARB", reinterpret_cast(trampoline("glDrawArraysInstancedARB")), reinterpret_cast(glDrawArraysInstancedARB), true); - reshade::hooks::install("glDrawArraysInstancedEXT", reinterpret_cast(trampoline("glDrawArraysInstancedEXT")), reinterpret_cast(glDrawArraysInstancedEXT), true); - reshade::hooks::install("glDrawArraysInstancedBaseInstance", reinterpret_cast(trampoline("glDrawArraysInstancedBaseInstance")), reinterpret_cast(glDrawArraysInstancedBaseInstance), true); - reshade::hooks::install("glDrawElementsBaseVertex", reinterpret_cast(trampoline("glDrawElementsBaseVertex")), reinterpret_cast(glDrawElementsBaseVertex), true); - reshade::hooks::install("glDrawElementsIndirect", reinterpret_cast(trampoline("glDrawElementsIndirect")), reinterpret_cast(glDrawElementsIndirect), true); - reshade::hooks::install("glDrawElementsInstanced", reinterpret_cast(trampoline("glDrawElementsInstanced")), reinterpret_cast(glDrawElementsInstanced), true); - reshade::hooks::install("glDrawElementsInstancedARB", reinterpret_cast(trampoline("glDrawElementsInstancedARB")), reinterpret_cast(glDrawElementsInstancedARB), true); - reshade::hooks::install("glDrawElementsInstancedEXT", reinterpret_cast(trampoline("glDrawElementsInstancedEXT")), reinterpret_cast(glDrawElementsInstancedEXT), true); - reshade::hooks::install("glDrawElementsInstancedBaseVertex", reinterpret_cast(trampoline("glDrawElementsInstancedBaseVertex")), reinterpret_cast(glDrawElementsInstancedBaseVertex), true); - reshade::hooks::install("glDrawElementsInstancedBaseInstance", reinterpret_cast(trampoline("glDrawElementsInstancedBaseInstance")), reinterpret_cast(glDrawElementsInstancedBaseInstance), true); - reshade::hooks::install("glDrawElementsInstancedBaseVertexBaseInstance", reinterpret_cast(trampoline("glDrawElementsInstancedBaseVertexBaseInstance")), reinterpret_cast(glDrawElementsInstancedBaseVertexBaseInstance), true); - reshade::hooks::install("glDrawRangeElements", reinterpret_cast(trampoline("glDrawRangeElements")), reinterpret_cast(glDrawRangeElements), true); - reshade::hooks::install("glDrawRangeElementsBaseVertex", reinterpret_cast(trampoline("glDrawRangeElementsBaseVertex")), reinterpret_cast(glDrawRangeElementsBaseVertex), true); - reshade::hooks::install("glFramebufferRenderbuffer", reinterpret_cast(trampoline("glFramebufferRenderbuffer")), reinterpret_cast(glFramebufferRenderbuffer), true); - reshade::hooks::install("glFramebufferRenderbufferEXT", reinterpret_cast(trampoline("glFramebufferRenderbufferEXT")), reinterpret_cast(glFramebufferRenderbufferEXT), true); - reshade::hooks::install("glFramebufferTexture", reinterpret_cast(trampoline("glFramebufferTexture")), reinterpret_cast(glFramebufferTexture), true); - reshade::hooks::install("glFramebufferTextureARB", reinterpret_cast(trampoline("glFramebufferTextureARB")), reinterpret_cast(glFramebufferTextureARB), true); - reshade::hooks::install("glFramebufferTextureEXT", reinterpret_cast(trampoline("glFramebufferTextureEXT")), reinterpret_cast(glFramebufferTextureEXT), true); - reshade::hooks::install("glFramebufferTexture1D", reinterpret_cast(trampoline("glFramebufferTexture1D")), reinterpret_cast(glFramebufferTexture1D), true); - reshade::hooks::install("glFramebufferTexture1DEXT", reinterpret_cast(trampoline("glFramebufferTexture1DEXT")), reinterpret_cast(glFramebufferTexture1DEXT), true); - reshade::hooks::install("glFramebufferTexture2D", reinterpret_cast(trampoline("glFramebufferTexture2D")), reinterpret_cast(glFramebufferTexture2D), true); - reshade::hooks::install("glFramebufferTexture2DEXT", reinterpret_cast(trampoline("glFramebufferTexture2DEXT")), reinterpret_cast(glFramebufferTexture2DEXT), true); - reshade::hooks::install("glFramebufferTexture3D", reinterpret_cast(trampoline("glFramebufferTexture3D")), reinterpret_cast(glFramebufferTexture3D), true); - reshade::hooks::install("glFramebufferTexture3DEXT", reinterpret_cast(trampoline("glFramebufferTexture3DEXT")), reinterpret_cast(glFramebufferTexture3DEXT), true); - reshade::hooks::install("glFramebufferTextureLayer", reinterpret_cast(trampoline("glFramebufferTextureLayer")), reinterpret_cast(glFramebufferTextureLayer), true); - reshade::hooks::install("glFramebufferTextureLayerARB", reinterpret_cast(trampoline("glFramebufferTextureLayerARB")), reinterpret_cast(glFramebufferTextureLayerARB), true); - reshade::hooks::install("glFramebufferTextureLayerEXT", reinterpret_cast(trampoline("glFramebufferTextureLayerEXT")), reinterpret_cast(glFramebufferTextureLayerEXT), true); - reshade::hooks::install("glMultiDrawArrays", reinterpret_cast(trampoline("glMultiDrawArrays")), reinterpret_cast(glMultiDrawArrays), true); - reshade::hooks::install("glMultiDrawArraysIndirect", reinterpret_cast(trampoline("glMultiDrawArraysIndirect")), reinterpret_cast(glMultiDrawArraysIndirect), true); - reshade::hooks::install("glMultiDrawElements", reinterpret_cast(trampoline("glMultiDrawElements")), reinterpret_cast(glMultiDrawElements), true); - reshade::hooks::install("glMultiDrawElementsBaseVertex", reinterpret_cast(trampoline("glMultiDrawElementsBaseVertex")), reinterpret_cast(glMultiDrawElementsBaseVertex), true); - reshade::hooks::install("glMultiDrawElementsIndirect", reinterpret_cast(trampoline("glMultiDrawElementsIndirect")), reinterpret_cast(glMultiDrawElementsIndirect), true); - reshade::hooks::install("glTexImage3D", reinterpret_cast(trampoline("glTexImage3D")), reinterpret_cast(glTexImage3D), true); - - reshade::hooks::install("wglChoosePixelFormatARB", reinterpret_cast(trampoline("wglChoosePixelFormatARB")), reinterpret_cast(wglChoosePixelFormatARB), true); - reshade::hooks::install("wglCreateContextAttribsARB", reinterpret_cast(trampoline("wglCreateContextAttribsARB")), reinterpret_cast(wglCreateContextAttribsARB), true); - reshade::hooks::install("wglCreatePbufferARB", reinterpret_cast(trampoline("wglCreatePbufferARB")), reinterpret_cast(wglCreatePbufferARB), true); - reshade::hooks::install("wglDestroyPbufferARB", reinterpret_cast(trampoline("wglDestroyPbufferARB")), reinterpret_cast(wglDestroyPbufferARB), true); - reshade::hooks::install("wglGetPbufferDCARB", reinterpret_cast(trampoline("wglGetPbufferDCARB")), reinterpret_cast(wglGetPbufferDCARB), true); - reshade::hooks::install("wglGetPixelFormatAttribivARB", reinterpret_cast(trampoline("wglGetPixelFormatAttribivARB")), reinterpret_cast(wglGetPixelFormatAttribivARB), true); - reshade::hooks::install("wglGetPixelFormatAttribfvARB", reinterpret_cast(trampoline("wglGetPixelFormatAttribfvARB")), reinterpret_cast(wglGetPixelFormatAttribfvARB), true); - reshade::hooks::install("wglQueryPbufferARB", reinterpret_cast(trampoline("wglQueryPbufferARB")), reinterpret_cast(wglQueryPbufferARB), true); - reshade::hooks::install("wglReleasePbufferDCARB", reinterpret_cast(trampoline("wglReleasePbufferDCARB")), reinterpret_cast(wglReleasePbufferDCARB), true); - reshade::hooks::install("wglGetSwapIntervalEXT", reinterpret_cast(trampoline("wglGetSwapIntervalEXT")), reinterpret_cast(wglGetSwapIntervalEXT), true); - reshade::hooks::install("wglSwapIntervalEXT", reinterpret_cast(trampoline("wglSwapIntervalEXT")), reinterpret_cast(wglSwapIntervalEXT), true); - - reshade::hook::apply_queued_actions(); - - s_hooks_not_installed = false; - } - - return address; -} diff --git a/msvc/source/opengl/runtime_opengl.cpp b/msvc/source/opengl/runtime_opengl.cpp deleted file mode 100644 index 4cdeadd..0000000 --- a/msvc/source/opengl/runtime_opengl.cpp +++ /dev/null @@ -1,1229 +0,0 @@ -/** - * Copyright (C) 2014 Patrick Mours. All rights reserved. - * License: https://github.com/crosire/reshade#license - */ - -#include "log.hpp" -#include "runtime_opengl.hpp" -#include "runtime_objects.hpp" -#include "ini_file.hpp" -#include - -namespace reshade::opengl -{ - struct opengl_tex_data : base_object - { - ~opengl_tex_data() - { - if (should_delete) - glDeleteTextures(2, id); - } - - bool should_delete = false; - GLuint id[2] = {}; - }; - - struct opengl_sampler_data - { - GLuint id; - opengl_tex_data *texture; - bool is_srgb; - bool has_mipmaps; - }; - - struct opengl_pass_data : base_object - { - ~opengl_pass_data() - { - if (program) - glDeleteProgram(program); - glDeleteFramebuffers(1, &fbo); - } - - GLuint fbo = 0; - GLuint program = 0; - GLenum blend_eq_color = GL_NONE; - GLenum blend_eq_alpha = GL_NONE; - GLenum blend_src = GL_NONE; - GLenum blend_src_alpha = GL_NONE; - GLenum blend_dest = GL_NONE; - GLenum blend_dest_alpha = GL_NONE; - GLenum stencil_func = GL_NONE; - GLenum stencil_op_fail = GL_NONE; - GLenum stencil_op_z_fail = GL_NONE; - GLenum stencil_op_z_pass = GL_NONE; - GLenum draw_targets[8] = {}; - GLuint draw_textures[8] = {}; - GLsizei viewport_width = 0; - GLsizei viewport_height = 0; - }; - - struct opengl_technique_data : base_object - { - ~opengl_technique_data() - { - glDeleteQueries(1, &query); - } - - GLuint query = 0; - bool query_in_flight = false; - std::vector samplers; - ptrdiff_t uniform_storage_index = -1; - ptrdiff_t uniform_storage_offset = 0; - }; -} - -reshade::opengl::runtime_opengl::runtime_opengl() -{ - GLint major = 0, minor = 0; - glGetIntegerv(GL_MAJOR_VERSION, &major); - glGetIntegerv(GL_MAJOR_VERSION, &minor); - _renderer_id = 0x10000 | (major << 12) | (minor << 8); - - // Query vendor and device ID from Windows assuming we are running on the primary display device - // This is done because the information reported by OpenGL is not always reflecting the actual rendering device (e.g. on NVIDIA Optimus laptops) - DISPLAY_DEVICEA dd = { sizeof(dd) }; - for (DWORD i = 0; EnumDisplayDevicesA(nullptr, i, &dd, 0) != FALSE; ++i) - { - if ((dd.StateFlags & DISPLAY_DEVICE_PRIMARY_DEVICE) != 0) - { - // Format: PCI\VEN_XXXX&DEV_XXXX... - const std::string id = dd.DeviceID; - - if (id.length() > 20) - { - _vendor_id = std::stoi(id.substr(8, 4), nullptr, 16); - _device_id = std::stoi(id.substr(17, 4), nullptr, 16); - } - break; - } - } - - subscribe_to_load_config([this](const ini_file &config) { - size_t num_reserve_texture_names = 0; - config.get("OPENGL", "ReserveTextureNames", num_reserve_texture_names); - _reserved_texture_names.resize(num_reserve_texture_names); - }); -} - -bool reshade::opengl::runtime_opengl::on_init(HWND hwnd, unsigned int width, unsigned int height) -{ - RECT window_rect = {}; - GetClientRect(hwnd, &window_rect); - - _width = width; - _height = height; - _window_width = window_rect.right - window_rect.left; - _window_height = window_rect.bottom - window_rect.top; - - // Initialize information for the default depth buffer - _depth_source_table[0] = { _width, _height, 0, GL_DEPTH24_STENCIL8 }; - - // Capture and later restore so that the resource creation code below does not affect the application state - _app_state.capture(); - - // Some games (like Hot Wheels Velocity X) use fixed texture names, which can clash with the ones ReShade generates below, since most implementations will return values linearly - // Reserve a configurable range of names for those games to work around this - glGenTextures(GLsizei(_reserved_texture_names.size()), _reserved_texture_names.data()); - - glGenBuffers(NUM_BUF, _buf); - glGenTextures(NUM_TEX, _tex); - glGenVertexArrays(NUM_VAO, _vao); - glGenFramebuffers(NUM_FBO, _fbo); - glGenRenderbuffers(NUM_RBO, _rbo); - - glBindTexture(GL_TEXTURE_2D, _tex[TEX_BACK]); - glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, _width, _height); - glTextureView(_tex[TEX_BACK_SRGB], GL_TEXTURE_2D, _tex[TEX_BACK], GL_SRGB8_ALPHA8, 0, 1, 0, 1); - - glBindTexture(GL_TEXTURE_2D, _tex[TEX_DEPTH]); - glTexStorage2D(GL_TEXTURE_2D, 1, GL_DEPTH24_STENCIL8, _width, _height); - - glBindRenderbuffer(GL_RENDERBUFFER, _rbo[RBO_COLOR]); - glRenderbufferStorage(GL_RENDERBUFFER, GL_SRGB8_ALPHA8, _width, _height); - glBindRenderbuffer(GL_RENDERBUFFER, _rbo[RBO_DEPTH]); - glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, _width, _height); - - glBindFramebuffer(GL_FRAMEBUFFER, _fbo[FBO_BACK]); - glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, _rbo[RBO_COLOR]); - glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, _rbo[RBO_DEPTH]); - assert(glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE); - - glBindFramebuffer(GL_FRAMEBUFFER, _fbo[FBO_BLIT]); - glFramebufferTexture(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, _tex[TEX_DEPTH], 0); - glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, _tex[TEX_BACK_SRGB], 0); - assert(glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE); - -#if RESHADE_GUI - init_imgui_resources(); -#endif - - _app_state.apply(); - - return runtime::on_init(hwnd); -} -void reshade::opengl::runtime_opengl::on_reset() -{ - runtime::on_reset(); - - glDeleteBuffers(NUM_BUF, _buf); - glDeleteTextures(NUM_TEX, _tex); - glDeleteTextures(GLsizei(_reserved_texture_names.size()), _reserved_texture_names.data()); - glDeleteVertexArrays(NUM_VAO, _vao); - glDeleteFramebuffers(NUM_FBO, _fbo); - glDeleteRenderbuffers(NUM_RBO, _rbo); - - memset(_buf, 0, sizeof(_vao)); - memset(_tex, 0, sizeof(_tex)); - memset(_vao, 0, sizeof(_vao)); - memset(_fbo, 0, sizeof(_fbo)); - memset(_rbo, 0, sizeof(_rbo)); - - _depth_source = 0; - -#if RESHADE_GUI - glDeleteProgram(_imgui_program); - _imgui_program = 0; -#endif -} - -void reshade::opengl::runtime_opengl::on_present() -{ - if (!_is_initialized) - return; - - _app_state.capture(); - - detect_depth_source(); - - // Copy back buffer to RBO - glDisable(GL_SCISSOR_TEST); - glDisable(GL_FRAMEBUFFER_SRGB); - glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, _fbo[FBO_BACK]); - glReadBuffer(GL_BACK); - glDrawBuffer(GL_COLOR_ATTACHMENT0); - glBlitFramebuffer(0, 0, _width, _height, 0, 0, _width, _height, GL_COLOR_BUFFER_BIT, GL_NEAREST); - - // Copy depth from FBO to depth texture - glBindFramebuffer(GL_READ_FRAMEBUFFER, _depth_source != 0 ? _fbo[FBO_DEPTH] : 0); - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, _fbo[FBO_BLIT]); - glBlitFramebuffer(0, 0, _width, _height, 0, 0, _width, _height, GL_DEPTH_BUFFER_BIT, GL_NEAREST); - - // Set clip space to something consistent - if (gl3wProcs.gl.ClipControl != nullptr) - glClipControl(GL_LOWER_LEFT, GL_ZERO_TO_ONE); - - update_and_render_effects(); - - // Copy results from RBO to back buffer - glDisable(GL_SCISSOR_TEST); - glDisable(GL_FRAMEBUFFER_SRGB); - glBindFramebuffer(GL_READ_FRAMEBUFFER, _fbo[FBO_BACK]); - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); - glReadBuffer(GL_COLOR_ATTACHMENT0); - glDrawBuffer(GL_BACK); - glBlitFramebuffer(0, 0, _width, _height, 0, 0, _width, _height, GL_COLOR_BUFFER_BIT, GL_NEAREST); - - runtime::on_present(); - - // Apply previous state from application - _app_state.apply(); -} - -void reshade::opengl::runtime_opengl::on_draw_call(unsigned int vertices) -{ - _vertices += vertices; - _drawcalls += 1; - - GLint object = 0; - GLint target = GL_NONE; - glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &object); - if (object != 0) { // Zero is valid too, in which case the default depth buffer is referenced, instead of a FBO - glGetFramebufferAttachmentParameteriv(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE, &target); - if (target == GL_NONE) return; - glGetFramebufferAttachmentParameteriv(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME, &object); - } - - const auto it = _depth_source_table.find(object | (target == GL_RENDERBUFFER ? 0x80000000 : 0)); - if (it != _depth_source_table.end()) - { - it->second.num_vertices += vertices; - it->second.num_drawcalls = _drawcalls; - } -} -void reshade::opengl::runtime_opengl::on_fbo_attachment(GLenum attachment, GLenum target, GLuint object, GLint level) -{ - if (object == 0 || (attachment != GL_DEPTH_ATTACHMENT && attachment != GL_DEPTH_STENCIL_ATTACHMENT)) - return; - - const GLuint id = object | (target == GL_RENDERBUFFER ? 0x80000000 : 0); - - if (_depth_source_table.find(id) != _depth_source_table.end()) - return; - - depth_source_info info = { 0, 0, level, GL_NONE }; - - if (target == GL_RENDERBUFFER) - { - GLint previous_rbo = 0; - glGetIntegerv(GL_RENDERBUFFER_BINDING, &previous_rbo); - - // Get depth stencil parameters from RBO - glBindRenderbuffer(GL_RENDERBUFFER, object); - glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_WIDTH, reinterpret_cast(&info.width)); - glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_HEIGHT, reinterpret_cast(&info.height)); - glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_INTERNAL_FORMAT, &info.format); - - glBindRenderbuffer(GL_RENDERBUFFER, previous_rbo); - } - else - { - const auto target_to_binding = [](GLenum target) -> GLenum { - switch (target) - { - default: - case GL_TEXTURE_2D: - return GL_TEXTURE_BINDING_2D; - case GL_TEXTURE_2D_ARRAY: - return GL_TEXTURE_BINDING_2D_ARRAY; - case GL_TEXTURE_2D_MULTISAMPLE: - return GL_TEXTURE_BINDING_2D_MULTISAMPLE; - case GL_TEXTURE_2D_MULTISAMPLE_ARRAY: - return GL_TEXTURE_BINDING_2D_MULTISAMPLE_ARRAY; - case GL_TEXTURE_3D: - return GL_TEXTURE_BINDING_3D; - case GL_TEXTURE_CUBE_MAP: - case GL_TEXTURE_CUBE_MAP_NEGATIVE_X: - case GL_TEXTURE_CUBE_MAP_NEGATIVE_Y: - case GL_TEXTURE_CUBE_MAP_NEGATIVE_Z: - case GL_TEXTURE_CUBE_MAP_POSITIVE_X: - case GL_TEXTURE_CUBE_MAP_POSITIVE_Y: - case GL_TEXTURE_CUBE_MAP_POSITIVE_Z: - return GL_TEXTURE_BINDING_CUBE_MAP; - case GL_TEXTURE_CUBE_MAP_ARRAY: - return GL_TEXTURE_BINDING_CUBE_MAP_ARRAY; - } - }; - - GLint previous_tex = 0; - glGetIntegerv(target_to_binding(target), &previous_tex); - - // Get depth stencil parameters from texture - glBindTexture(target, object); - glGetTexLevelParameteriv(target, level, GL_TEXTURE_WIDTH, reinterpret_cast(&info.width)); - glGetTexLevelParameteriv(target, level, GL_TEXTURE_HEIGHT, reinterpret_cast(&info.height)); - glGetTexLevelParameteriv(target, level, GL_TEXTURE_INTERNAL_FORMAT, &info.format); - - glBindTexture(target, previous_tex); - } - - _depth_source_table.emplace(id, info); -} - -void reshade::opengl::runtime_opengl::capture_screenshot(uint8_t *buffer) const -{ - glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); - glReadBuffer(GL_BACK); - glReadPixels(0, 0, GLsizei(_width), GLsizei(_height), GL_RGBA, GL_UNSIGNED_BYTE, buffer); - - // Flip image horizontally - for (unsigned int y = 0, pitch = _width * 4; y * 2 < _height; ++y) - { - const auto i1 = y * pitch; - const auto i2 = (_height - 1 - y) * pitch; - - for (unsigned int x = 0; x < pitch; x += 4) - { - buffer[i1 + x + 3] = 0xFF; // Clear alpha channel - buffer[i2 + x + 3] = 0xFF; - - std::swap(buffer[i1 + x + 0], buffer[i2 + x + 0]); - std::swap(buffer[i1 + x + 1], buffer[i2 + x + 1]); - std::swap(buffer[i1 + x + 2], buffer[i2 + x + 2]); - } - } -} - -bool reshade::opengl::runtime_opengl::init_texture(texture &texture) -{ - texture.impl = std::make_unique(); - - if (texture.impl_reference != texture_reference::none) - return update_texture_reference(texture); - - GLenum internalformat = GL_RGBA8, internalformat_srgb = GL_SRGB8_ALPHA8; - - switch (texture.format) - { - case reshadefx::texture_format::r8: - internalformat = internalformat_srgb = GL_R8; - break; - case reshadefx::texture_format::r16f: - internalformat = internalformat_srgb = GL_R16F; - break; - case reshadefx::texture_format::r32f: - internalformat = internalformat_srgb = GL_R32F; - break; - case reshadefx::texture_format::rg8: - internalformat = internalformat_srgb = GL_RG8; - break; - case reshadefx::texture_format::rg16: - internalformat = internalformat_srgb = GL_RG16; - break; - case reshadefx::texture_format::rg16f: - internalformat = internalformat_srgb = GL_RG16F; - break; - case reshadefx::texture_format::rg32f: - internalformat = internalformat_srgb = GL_RG32F; - break; - case reshadefx::texture_format::rgba8: - internalformat = GL_RGBA8; - internalformat_srgb = GL_SRGB8_ALPHA8; - break; - case reshadefx::texture_format::rgba16: - internalformat = internalformat_srgb = GL_RGBA16; - break; - case reshadefx::texture_format::rgba16f: - internalformat = internalformat_srgb = GL_RGBA16F; - break; - case reshadefx::texture_format::rgba32f: - internalformat = internalformat_srgb = GL_RGBA32F; - break; - case reshadefx::texture_format::rgb10a2: - internalformat = internalformat_srgb = GL_RGB10_A2; - break; - } - - const auto texture_data = texture.impl->as(); - texture_data->should_delete = true; - - GLint previous_tex = 0; - glGetIntegerv(GL_TEXTURE_BINDING_2D, &previous_tex); - GLint previous_fbo = 0; - glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &previous_fbo); - - glGenTextures(2, texture_data->id); - glBindTexture(GL_TEXTURE_2D, texture_data->id[0]); - glTexStorage2D(GL_TEXTURE_2D, texture.levels, internalformat, texture.width, texture.height); - glTextureView(texture_data->id[1], GL_TEXTURE_2D, texture_data->id[0], internalformat_srgb, 0, texture.levels, 0, 1); - - // Clear texture to black since by default its contents are undefined - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, _fbo[FBO_BLIT]); - glFramebufferTexture(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, texture_data->id[0], 0); - assert(glCheckFramebufferStatus(GL_DRAW_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE); - glDrawBuffer(GL_COLOR_ATTACHMENT1); - const GLuint clear_color[4] = { 0, 0, 0, 0 }; - glClearBufferuiv(GL_COLOR, 0, clear_color); - glFramebufferTexture(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, 0, 0); - - // Apply previous state from application - glBindTexture(GL_TEXTURE_2D, previous_tex); - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, previous_fbo); - - return true; -} -void reshade::opengl::runtime_opengl::upload_texture(texture &texture, const uint8_t *pixels) -{ - assert(texture.impl_reference == texture_reference::none && pixels != nullptr); - - // Flip image data horizontally - const uint32_t pitch = texture.width * 4; - std::vector data_flipped(pixels, pixels + pitch * texture.height); - const auto temp = static_cast(alloca(pitch)); - - for (uint32_t y = 0; 2 * y < texture.height; y++) - { - const auto line1 = data_flipped.data() + pitch * (y); - const auto line2 = data_flipped.data() + pitch * (texture.height - 1 - y); - - std::memcpy(temp, line1, pitch); - std::memcpy(line1, line2, pitch); - std::memcpy(line2, temp, pitch); - } - - const auto texture_impl = texture.impl->as(); - assert(texture_impl != nullptr); - - // Unset any existing unpack buffer so pointer is not interpreted as offset - glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); - - // Clear pixel storage modes to defaults (texture uploads can break otherwise) - glPixelStorei(GL_UNPACK_LSB_FIRST, GL_FALSE); - glPixelStorei(GL_UNPACK_SWAP_BYTES, GL_FALSE); - glPixelStorei(GL_UNPACK_ALIGNMENT, 4); // RGBA data is 4-byte aligned - glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); - glPixelStorei(GL_UNPACK_IMAGE_HEIGHT, 0); - glPixelStorei(GL_UNPACK_SKIP_ROWS, 0); - glPixelStorei(GL_UNPACK_SKIP_PIXELS, 0); - glPixelStorei(GL_UNPACK_SKIP_IMAGES, 0); - - // Bind and update texture - GLint previous_tex = 0; - glGetIntegerv(GL_TEXTURE_BINDING_2D, &previous_tex); - - glBindTexture(GL_TEXTURE_2D, texture_impl->id[0]); - glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, texture.width, texture.height, GL_RGBA, GL_UNSIGNED_BYTE, data_flipped.data()); - - if (texture.levels > 1) - glGenerateMipmap(GL_TEXTURE_2D); - - // Apply previous state from application - glBindTexture(GL_TEXTURE_2D, previous_tex); -} -bool reshade::opengl::runtime_opengl::update_texture_reference(texture &texture) -{ - GLuint new_reference[2] = {}; - - switch (texture.impl_reference) - { - case texture_reference::back_buffer: - new_reference[0] = _tex[TEX_BACK]; - new_reference[1] = _tex[TEX_BACK_SRGB]; - break; - case texture_reference::depth_buffer: - new_reference[0] = _tex[TEX_DEPTH]; - new_reference[1] = _tex[TEX_DEPTH]; - break; - default: - return false; - } - - const auto texture_impl = texture.impl->as(); - assert(texture_impl != nullptr); - - if (texture_impl->id[0] == new_reference[0] && - texture_impl->id[1] == new_reference[1]) - return true; - - if (texture_impl->should_delete) - glDeleteTextures(2, texture_impl->id); - - texture_impl->id[0] = new_reference[0]; - texture_impl->id[1] = new_reference[1]; - texture_impl->should_delete = false; - - return true; -} -void reshade::opengl::runtime_opengl::update_texture_references(texture_reference type) -{ - for (auto &tex : _textures) - if (tex.impl != nullptr && tex.impl_reference == type) - update_texture_reference(tex); -} - -bool reshade::opengl::runtime_opengl::compile_effect(effect_data &effect) -{ - assert(_app_state.has_state); // Make sure all binds below are reset later when application state is restored - - // Add specialization constant defines to source code -#if 0 - std::vector spec_constants; - std::vector spec_constant_values; - for (const auto &constant : module.spec_constants) - { - spec_constants.push_back(constant.offset); - spec_constant_values.push_back(constant.initializer_value.as_uint[0]); - } -#else - effect.preamble = "#version 430\n" + effect.preamble; -#endif - - std::unordered_map entry_points; - - // Compile all entry points - for (const auto &entry_point : effect.module.entry_points) - { - GLuint shader_id = glCreateShader(entry_point.second ? GL_FRAGMENT_SHADER : GL_VERTEX_SHADER); - entry_points[entry_point.first] = shader_id; - -#if 0 - glShaderBinary(1, &shader_id, GL_SHADER_BINARY_FORMAT_SPIR_V, module.spirv.data(), module.spirv.size() * sizeof(uint32_t)); - glSpecializeShader(shader_id, entry_point.first.c_str(), GLuint(spec_constants.size()), spec_constants.data(), spec_constant_values.data()); -#else - std::string defines = effect.preamble; - defines += "#define ENTRY_POINT_" + entry_point.first + " 1\n"; - if (!entry_point.second) // OpenGL does not allow using 'discard' in the vertex shader profile - defines += "#define discard\n" - "#define dFdx(x) x\n" // 'dFdx', 'dFdx' and 'fwidth' too are only available in fragment shaders - "#define dFdy(y) y\n" - "#define fwidth(p) p\n"; - - GLsizei lengths[] = { static_cast(defines.size()), static_cast(effect.module.hlsl.size()) }; - const GLchar *sources[] = { defines.c_str(), effect.module.hlsl.c_str() }; - glShaderSource(shader_id, 2, sources, lengths); - glCompileShader(shader_id); -#endif - - GLint status = GL_FALSE; - glGetShaderiv(shader_id, GL_COMPILE_STATUS, &status); - if (GL_FALSE == status) - { - GLint log_size = 0; - glGetShaderiv(shader_id, GL_INFO_LOG_LENGTH, &log_size); - std::string log(log_size, '\0'); - glGetShaderInfoLog(shader_id, log_size, nullptr, log.data()); - - effect.errors += log; - - for (auto &it : entry_points) - glDeleteShader(it.second); - - // No need to setup resources if any of the shaders failed to compile - return false; - } - } - - if (effect.storage_size != 0) - { - GLuint ubo = 0; - glGenBuffers(1, &ubo); - glBindBuffer(GL_UNIFORM_BUFFER, ubo); - glBufferData(GL_UNIFORM_BUFFER, effect.storage_size, _uniform_data_storage.data() + effect.storage_offset, GL_DYNAMIC_DRAW); - - _effect_ubos.emplace_back(ubo, effect.storage_size); - } - - bool success = true; - - opengl_technique_data technique_init; - technique_init.uniform_storage_index = _effect_ubos.size() - 1; - technique_init.uniform_storage_offset = effect.storage_offset; - - for (const reshadefx::sampler_info &info : effect.module.samplers) - success &= add_sampler(info, technique_init); - - for (technique &technique : _techniques) - if (technique.impl == nullptr && technique.effect_index == effect.index) - success &= init_technique(technique, technique_init, entry_points, effect.errors); - - for (auto &it : entry_points) - glDeleteShader(it.second); - - return success; -} -void reshade::opengl::runtime_opengl::unload_effects() -{ - runtime::unload_effects(); - - for (const auto &info : _effect_ubos) - glDeleteBuffers(1, &info.first); - _effect_ubos.clear(); - - for (const auto &info : _effect_sampler_states) - glDeleteSamplers(1, &info.second); - _effect_sampler_states.clear(); -} - -bool reshade::opengl::runtime_opengl::add_sampler(const reshadefx::sampler_info &info, opengl_technique_data &technique_init) -{ - const auto existing_texture = std::find_if(_textures.begin(), _textures.end(), - [&texture_name = info.texture_name](const auto &item) { - return item.unique_name == texture_name && item.impl != nullptr; - }); - if (existing_texture == _textures.end()) - return false; - - // Hash sampler state to avoid duplicated sampler objects - size_t hash = 2166136261; - hash = (hash * 16777619) ^ static_cast(info.address_u); - hash = (hash * 16777619) ^ static_cast(info.address_v); - hash = (hash * 16777619) ^ static_cast(info.address_w); - hash = (hash * 16777619) ^ static_cast(info.filter); - hash = (hash * 16777619) ^ reinterpret_cast(info.lod_bias); - hash = (hash * 16777619) ^ reinterpret_cast(info.min_lod); - hash = (hash * 16777619) ^ reinterpret_cast(info.max_lod); - - auto it = _effect_sampler_states.find(hash); - - if (it == _effect_sampler_states.end()) - { - GLenum minfilter = GL_NONE, magfilter = GL_NONE; - - switch (info.filter) - { - case reshadefx::texture_filter::min_mag_mip_point: - minfilter = GL_NEAREST_MIPMAP_NEAREST; - magfilter = GL_NEAREST; - break; - case reshadefx::texture_filter::min_mag_point_mip_linear: - minfilter = GL_NEAREST_MIPMAP_LINEAR; - magfilter = GL_NEAREST; - break; - case reshadefx::texture_filter::min_point_mag_linear_mip_point: - minfilter = GL_NEAREST_MIPMAP_NEAREST; - magfilter = GL_LINEAR; - break; - case reshadefx::texture_filter::min_point_mag_mip_linear: - minfilter = GL_NEAREST_MIPMAP_LINEAR; - magfilter = GL_LINEAR; - break; - case reshadefx::texture_filter::min_linear_mag_mip_point: - minfilter = GL_LINEAR_MIPMAP_NEAREST; - magfilter = GL_NEAREST; - break; - case reshadefx::texture_filter::min_linear_mag_point_mip_linear: - minfilter = GL_LINEAR_MIPMAP_LINEAR; - magfilter = GL_NEAREST; - break; - case reshadefx::texture_filter::min_mag_linear_mip_point: - minfilter = GL_LINEAR_MIPMAP_NEAREST; - magfilter = GL_LINEAR; - break; - case reshadefx::texture_filter::min_mag_mip_linear: - minfilter = GL_LINEAR_MIPMAP_LINEAR; - magfilter = GL_LINEAR; - break; - } - - const auto convert_address_mode = [](reshadefx::texture_address_mode value) { - switch (value) - { - case reshadefx::texture_address_mode::wrap: - return GL_REPEAT; - case reshadefx::texture_address_mode::mirror: - return GL_MIRRORED_REPEAT; - case reshadefx::texture_address_mode::clamp: - return GL_CLAMP_TO_EDGE; - case reshadefx::texture_address_mode::border: - return GL_CLAMP_TO_BORDER; - default: - return GL_NONE; - } - }; - - GLuint sampler_id = 0; - glGenSamplers(1, &sampler_id); - glSamplerParameteri(sampler_id, GL_TEXTURE_WRAP_S, convert_address_mode(info.address_u)); - glSamplerParameteri(sampler_id, GL_TEXTURE_WRAP_T, convert_address_mode(info.address_v)); - glSamplerParameteri(sampler_id, GL_TEXTURE_WRAP_R, convert_address_mode(info.address_w)); - glSamplerParameteri(sampler_id, GL_TEXTURE_MAG_FILTER, magfilter); - glSamplerParameteri(sampler_id, GL_TEXTURE_MIN_FILTER, minfilter); - glSamplerParameterf(sampler_id, GL_TEXTURE_LOD_BIAS, info.lod_bias); - glSamplerParameterf(sampler_id, GL_TEXTURE_MIN_LOD, info.min_lod); - glSamplerParameterf(sampler_id, GL_TEXTURE_MAX_LOD, info.max_lod); - - it = _effect_sampler_states.emplace(hash, sampler_id).first; - } - - opengl_sampler_data sampler; - sampler.id = it->second; - sampler.texture = existing_texture->impl->as(); - sampler.is_srgb = info.srgb; - sampler.has_mipmaps = existing_texture->levels > 1; - - technique_init.samplers.resize(std::max(technique_init.samplers.size(), size_t(info.binding + 1))); - - technique_init.samplers[info.binding] = std::move(sampler); - - return true; -} -bool reshade::opengl::runtime_opengl::init_technique(technique &technique, const opengl_technique_data &impl_init, const std::unordered_map &entry_points, std::string &errors) -{ - assert(_app_state.has_state); - - // Copy construct new technique implementation instead of move because effect may contain multiple techniques - technique.impl = std::make_unique(impl_init); - - const auto technique_data = technique.impl->as(); - - glGenQueries(1, &technique_data->query); - - for (size_t i = 0; i < technique.passes.size(); ++i) - { - technique.passes_data.push_back(std::make_unique()); - - auto &pass = *technique.passes_data.back()->as(); - const auto &pass_info = technique.passes[i]; - - const auto literal_to_blend_eq = [](unsigned int value) -> GLenum { - switch (value) - { - default: - case 1: return GL_FUNC_ADD; - case 2: return GL_FUNC_SUBTRACT; - case 3: return GL_FUNC_REVERSE_SUBTRACT; - case 4: return GL_MIN; - case 5: return GL_MAX; - } - }; - const auto literal_to_blend_func = [](unsigned int value) -> GLenum { - switch (value) - { - default: - case 0: return GL_ZERO; - case 1: return GL_ONE; - case 2: return GL_SRC_COLOR; - case 3: return GL_SRC_ALPHA; - case 4: return GL_ONE_MINUS_SRC_COLOR; - case 5: return GL_ONE_MINUS_SRC_ALPHA; - case 8: return GL_DST_COLOR; - case 6: return GL_DST_ALPHA; - case 9: return GL_ONE_MINUS_DST_COLOR; - case 7: return GL_ONE_MINUS_DST_ALPHA; - } - }; - const auto literal_to_comp_func = [](unsigned int value) -> GLenum { - switch (value) - { - default: - case 8: return GL_ALWAYS; - case 1: return GL_NEVER; - case 3: return GL_EQUAL; - case 6: return GL_NOTEQUAL; - case 2: return GL_LESS; - case 4: return GL_LEQUAL; - case 5: return GL_GREATER; - case 7: return GL_GEQUAL; - } - }; - const auto literal_to_stencil_op = [](unsigned int value) -> GLenum { - switch (value) - { - default: - case 1: return GL_KEEP; - case 0: return GL_ZERO; - case 3: return GL_REPLACE; - case 7: return GL_INCR_WRAP; - case 4: return GL_INCR; - case 8: return GL_DECR_WRAP; - case 5: return GL_DECR; - case 6: return GL_INVERT; - } - }; - - pass.blend_eq_color = literal_to_blend_eq(pass_info.blend_op); - pass.blend_eq_alpha = literal_to_blend_eq(pass_info.blend_op_alpha); - pass.blend_src = literal_to_blend_func(pass_info.src_blend); - pass.blend_dest = literal_to_blend_func(pass_info.dest_blend); - pass.blend_src_alpha = literal_to_blend_func(pass_info.src_blend_alpha); - pass.blend_dest_alpha = literal_to_blend_func(pass_info.dest_blend_alpha); - pass.stencil_func = literal_to_comp_func(pass_info.stencil_comparison_func); - pass.stencil_op_z_pass = literal_to_stencil_op(pass_info.stencil_op_pass); - pass.stencil_op_fail = literal_to_stencil_op(pass_info.stencil_op_fail); - pass.stencil_op_z_fail = literal_to_stencil_op(pass_info.stencil_op_depth_fail); - - glGenFramebuffers(1, &pass.fbo); - glBindFramebuffer(GL_FRAMEBUFFER, pass.fbo); - - bool backbuffer_fbo = true; - - for (unsigned int k = 0; k < 8; ++k) - { - if (pass_info.render_target_names[k].empty()) - continue; // Skip unbound render targets - - const auto render_target_texture = std::find_if(_textures.begin(), _textures.end(), - [&render_target = pass_info.render_target_names[k]](const auto &item) { - return item.unique_name == render_target; - }); - if (render_target_texture == _textures.end()) - return assert(false), false; - - backbuffer_fbo = false; - - const auto texture_impl = render_target_texture->impl->as(); - assert(texture_impl != nullptr); - - glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + k, texture_impl->id[pass_info.srgb_write_enable], 0); - - pass.draw_targets[k] = GL_COLOR_ATTACHMENT0 + k; - pass.draw_textures[k] = texture_impl->id[pass_info.srgb_write_enable]; - } - - if (backbuffer_fbo) - { - glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, _rbo[RBO_COLOR]); - - pass.draw_targets[0] = GL_COLOR_ATTACHMENT0; - pass.draw_textures[0] = _tex[TEX_BACK_SRGB]; - - pass.viewport_width = static_cast(_width); - pass.viewport_height = static_cast(_height); - } - else - { - // Effect compiler sets the viewport to the render target dimensions - pass.viewport_width = pass_info.viewport_width; - pass.viewport_height = pass_info.viewport_height; - } - - assert(pass.viewport_width != 0); - assert(pass.viewport_height != 0); - - if (pass.viewport_width == GLsizei(_width) && pass.viewport_height == GLsizei(_height)) - { - // Only attach depth-stencil when viewport matches back buffer or else the frame buffer will always be resized to those dimensions - glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, _rbo[RBO_DEPTH]); - } - - assert(glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE); - - // Link program from input shaders - pass.program = glCreateProgram(); - const GLuint vs_shader_id = entry_points.at(pass_info.vs_entry_point); - const GLuint fs_shader_id = entry_points.at(pass_info.ps_entry_point); - glAttachShader(pass.program, vs_shader_id); - glAttachShader(pass.program, fs_shader_id); - glLinkProgram(pass.program); - glDetachShader(pass.program, vs_shader_id); - glDetachShader(pass.program, fs_shader_id); - - GLint status = GL_FALSE; - glGetProgramiv(pass.program, GL_LINK_STATUS, &status); - if (GL_FALSE == status) - { - GLint log_size = 0; - glGetProgramiv(pass.program, GL_INFO_LOG_LENGTH, &log_size); - std::string log(log_size, '\0'); - glGetProgramInfoLog(pass.program, log_size, nullptr, log.data()); - - errors += log; - - LOG(ERROR) << "Failed to link program for pass " << i << " in technique '" << technique.name << "'."; - return false; - } - } - - return true; -} - -void reshade::opengl::runtime_opengl::render_technique(technique &technique) -{ - assert(_app_state.has_state); - - opengl_technique_data &technique_data = *technique.impl->as(); - - if (technique_data.query_in_flight) - { - GLuint64 elapsed_time = 0; - glGetQueryObjectui64v(technique_data.query, GL_QUERY_RESULT, &elapsed_time); - technique.average_gpu_duration.append(elapsed_time); - technique_data.query_in_flight = false; // Reset query status - } - - if (!technique_data.query_in_flight) - glBeginQuery(GL_TIME_ELAPSED, technique_data.query); - - // Clear depth stencil - glBindFramebuffer(GL_FRAMEBUFFER, _fbo[FBO_BACK]); - glClearBufferfi(GL_DEPTH_STENCIL, 0, 1.0f, 0); - - // Set up global states - glDisable(GL_CULL_FACE); - glDisable(GL_DEPTH_TEST); - glDisable(GL_SCISSOR_TEST); - glFrontFace(GL_CCW); - glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); - glDepthMask(GL_FALSE); // No need to write to the depth buffer - glBindVertexArray(_vao[VAO_FX]); // This is an empty vertex array object - - // Set up shader constants - if (technique_data.uniform_storage_index >= 0) - { - glBindBufferBase(GL_UNIFORM_BUFFER, 0, _effect_ubos[technique_data.uniform_storage_index].first); - glBufferSubData(GL_UNIFORM_BUFFER, 0, _effect_ubos[technique_data.uniform_storage_index].second, _uniform_data_storage.data() + technique_data.uniform_storage_offset); - } - - // Set up shader resources - for (size_t i = 0; i < technique_data.samplers.size(); i++) - { - glActiveTexture(GL_TEXTURE0 + GLenum(i)); - glBindTexture(GL_TEXTURE_2D, technique_data.samplers[i].texture->id[technique_data.samplers[i].is_srgb]); - glBindSampler(GLuint(i), technique_data.samplers[i].id); - } - - for (size_t i = 0; i < technique.passes.size(); ++i) - { - const auto &pass = *technique.passes_data[i]->as(); - const auto &pass_info = technique.passes[i]; - - // Copy back buffer of previous pass to texture - glDisable(GL_FRAMEBUFFER_SRGB); - glBindFramebuffer(GL_READ_FRAMEBUFFER, _fbo[FBO_BACK]); - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, _fbo[FBO_BLIT]); - glReadBuffer(GL_COLOR_ATTACHMENT0); - glDrawBuffer(GL_COLOR_ATTACHMENT0); - glBlitFramebuffer(0, 0, _width, _height, 0, 0, _width, _height, GL_COLOR_BUFFER_BIT, GL_NEAREST); - - // Set up pass specific state - glViewport(0, 0, pass.viewport_width, pass.viewport_height); - glUseProgram(pass.program); - glBindFramebuffer(GL_FRAMEBUFFER, pass.fbo); - glDrawBuffers(8, pass.draw_targets); - - if (pass_info.blend_enable) { - glEnable(GL_BLEND); - glBlendFuncSeparate(pass.blend_src, pass.blend_dest, pass.blend_src_alpha, pass.blend_dest_alpha); - glBlendEquationSeparate(pass.blend_eq_color, pass.blend_eq_alpha); - } - else { - glDisable(GL_BLEND); - } - - if (pass_info.stencil_enable) { - glEnable(GL_STENCIL_TEST); - glStencilOp(pass.stencil_op_fail, pass.stencil_op_z_fail, pass.stencil_op_z_pass); - glStencilMask(pass_info.stencil_write_mask); - glStencilFunc(pass.stencil_func, pass_info.stencil_reference_value, pass_info.stencil_read_mask); - } - else { - glDisable(GL_STENCIL_TEST); - } - - if (pass_info.srgb_write_enable) { - glEnable(GL_FRAMEBUFFER_SRGB); - } else { - glDisable(GL_FRAMEBUFFER_SRGB); - } - - glColorMask( - (pass_info.color_write_mask & (1 << 0)) != 0, - (pass_info.color_write_mask & (1 << 1)) != 0, - (pass_info.color_write_mask & (1 << 2)) != 0, - (pass_info.color_write_mask & (1 << 3)) != 0); - - if (pass_info.clear_render_targets) - for (GLuint k = 0; k < 8; k++) - { - if (pass.draw_targets[k] == GL_NONE) - continue; // Ignore unbound render targets - const GLfloat color[4] = { 0.0f, 0.0f, 0.0f, 0.0f }; - glClearBufferfv(GL_COLOR, k, color); - } - - glDrawArrays(GL_TRIANGLES, 0, 3); - - _vertices += 3; - _drawcalls += 1; - - // Regenerate mipmaps of any textures bound as render target - for (GLuint texture_id : pass.draw_textures) - { - for (size_t k = 0; k < technique_data.samplers.size(); ++k) - { - const auto texture = technique_data.samplers[k].texture; - if (technique_data.samplers[k].has_mipmaps && - (texture->id[0] == texture_id || texture->id[1] == texture_id)) - { - glActiveTexture(GL_TEXTURE0 + GLenum(k)); - glGenerateMipmap(GL_TEXTURE_2D); - } - } - } - } - - if (!technique_data.query_in_flight) - glEndQuery(GL_TIME_ELAPSED); - technique_data.query_in_flight = true; -} - -#if RESHADE_GUI -void reshade::opengl::runtime_opengl::init_imgui_resources() -{ - assert(_app_state.has_state); - - const GLchar *vertex_shader[] = { - "#version 330\n" - "uniform mat4 ProjMtx;\n" - "in vec2 Position, UV;\n" - "in vec4 Color;\n" - "out vec2 Frag_UV;\n" - "out vec4 Frag_Color;\n" - "void main()\n" - "{\n" - " Frag_UV = UV * vec2(1.0, -1.0) + vec2(0.0, 1.0);\n" // Texture coordinates were flipped in 'update_texture' - " Frag_Color = Color;\n" - " gl_Position = ProjMtx * vec4(Position.xy, 0, 1);\n" - "}\n" - }; - const GLchar *fragment_shader[] = { - "#version 330\n" - "uniform sampler2D Texture;\n" - "in vec2 Frag_UV;\n" - "in vec4 Frag_Color;\n" - "out vec4 Out_Color;\n" - "void main()\n" - "{\n" - " Out_Color = Frag_Color * texture(Texture, Frag_UV.st);\n" - "}\n" - }; - - const GLuint imgui_vs = glCreateShader(GL_VERTEX_SHADER); - glShaderSource(imgui_vs, 1, vertex_shader, 0); - glCompileShader(imgui_vs); - const GLuint imgui_fs = glCreateShader(GL_FRAGMENT_SHADER); - glShaderSource(imgui_fs, 1, fragment_shader, 0); - glCompileShader(imgui_fs); - - _imgui_program = glCreateProgram(); - glAttachShader(_imgui_program, imgui_vs); - glAttachShader(_imgui_program, imgui_fs); - glLinkProgram(_imgui_program); - glDeleteShader(imgui_vs); - glDeleteShader(imgui_fs); - - _imgui_uniform_tex = glGetUniformLocation(_imgui_program, "Texture"); - _imgui_uniform_proj = glGetUniformLocation(_imgui_program, "ProjMtx"); - - const int attrib_pos = glGetAttribLocation(_imgui_program, "Position"); - const int attrib_uv = glGetAttribLocation(_imgui_program, "UV"); - const int attrib_col = glGetAttribLocation(_imgui_program, "Color"); - - glBindBuffer(GL_ARRAY_BUFFER, _buf[VBO_IMGUI]); - glBindVertexArray(_vao[VAO_IMGUI]); - glEnableVertexAttribArray(attrib_pos); - glEnableVertexAttribArray(attrib_uv ); - glEnableVertexAttribArray(attrib_col); - glVertexAttribPointer(attrib_pos, 2, GL_FLOAT, GL_FALSE, sizeof(ImDrawVert), reinterpret_cast(offsetof(ImDrawVert, pos))); - glVertexAttribPointer(attrib_uv , 2, GL_FLOAT, GL_FALSE, sizeof(ImDrawVert), reinterpret_cast(offsetof(ImDrawVert, uv ))); - glVertexAttribPointer(attrib_col, 4, GL_UNSIGNED_BYTE, GL_TRUE, sizeof(ImDrawVert), reinterpret_cast(offsetof(ImDrawVert, col))); -} - -void reshade::opengl::runtime_opengl::render_imgui_draw_data(ImDrawData *draw_data) -{ - assert(_app_state.has_state); - - glDisable(GL_CULL_FACE); - glDisable(GL_DEPTH_TEST); - glFrontFace(GL_CCW); - glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); - glEnable(GL_BLEND); - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - glBlendEquation(GL_FUNC_ADD); - glEnable(GL_SCISSOR_TEST); - glDisable(GL_STENCIL_TEST); - glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); - glDepthMask(GL_FALSE); - glActiveTexture(GL_TEXTURE0); // Bind texture at location zero below - glUseProgram(_imgui_program); - glBindSampler(0, 0); // Do not use separate sampler object, since state is already set in texture - glBindVertexArray(_vao[VAO_IMGUI]); - - glViewport(0, 0, GLsizei(draw_data->DisplaySize.x), GLsizei(draw_data->DisplaySize.y)); - - const float ortho_projection[16] = { - 2.0f / draw_data->DisplaySize.x, 0.0f, 0.0f, 0.0f, - 0.0f, -2.0f / draw_data->DisplaySize.y, 0.0f, 0.0f, - 0.0f, 0.0f, -1.0f, 0.0f, - -(2 * draw_data->DisplayPos.x + draw_data->DisplaySize.x) / draw_data->DisplaySize.x, - +(2 * draw_data->DisplayPos.y + draw_data->DisplaySize.y) / draw_data->DisplaySize.y, 0.0f, 1.0f, - }; - - glUniform1i(_imgui_uniform_tex, 0); // Set to GL_TEXTURE0 - glUniformMatrix4fv(_imgui_uniform_proj, 1, GL_FALSE, ortho_projection); - - for (int n = 0; n < draw_data->CmdListsCount; ++n) - { - const ImDrawIdx *index_offset = 0; - ImDrawList *const draw_list = draw_data->CmdLists[n]; - - glBindBuffer(GL_ARRAY_BUFFER, _buf[VBO_IMGUI]); - glBufferData(GL_ARRAY_BUFFER, draw_list->VtxBuffer.Size * sizeof(ImDrawVert), draw_list->VtxBuffer.Data, GL_STREAM_DRAW); - - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _buf[IBO_IMGUI]); - glBufferData(GL_ELEMENT_ARRAY_BUFFER, draw_list->IdxBuffer.Size * sizeof(ImDrawIdx), draw_list->IdxBuffer.Data, GL_STREAM_DRAW); - - for (const ImDrawCmd &cmd : draw_list->CmdBuffer) - { - assert(cmd.TextureId != 0); - assert(cmd.UserCallback == nullptr); - - const ImVec4 scissor_rect( - cmd.ClipRect.x - draw_data->DisplayPos.x, - cmd.ClipRect.y - draw_data->DisplayPos.y, - cmd.ClipRect.z - draw_data->DisplayPos.x, - cmd.ClipRect.w - draw_data->DisplayPos.y); - glScissor( - static_cast(scissor_rect.x), - static_cast(_height - scissor_rect.w), - static_cast(scissor_rect.z - scissor_rect.x), - static_cast(scissor_rect.w - scissor_rect.y)); - - glBindTexture(GL_TEXTURE_2D, static_cast(cmd.TextureId)->id[0]); - - glDrawElements(GL_TRIANGLES, cmd.ElemCount, sizeof(ImDrawIdx) == 2 ? GL_UNSIGNED_SHORT : GL_UNSIGNED_INT, index_offset); - - index_offset += cmd.ElemCount; - } - } -} -#endif - -void reshade::opengl::runtime_opengl::detect_depth_source() -{ - if (_framecount % 30) - return; // Only execute detection heuristic every 30 frames to avoid too frequent changes - - if (_has_high_network_activity) - { - _depth_source = 0; - glDeleteTextures(1, &_tex[TEX_DEPTH]); - _tex[TEX_DEPTH] = 0; - update_texture_references(texture_reference::depth_buffer); - return; - } - - assert(_app_state.has_state); - - GLuint best_match = 0; - depth_source_info best_info = _depth_source_table.at(0); // Always fall back to default depth buffer if no better match is found - - for (auto &it : _depth_source_table) - { - if (it.second.num_drawcalls == 0) - continue; // Skip candidates that were not used during rendering - - // Detection heuristic based on dimensions and usage - if (((it.second.width > _width * 0.95 && it.second.width < _width * 1.05) && (it.second.height > _height * 0.95 && it.second.height < _height * 1.05)) && - ((it.second.num_vertices * (1.2f - float(it.second.num_drawcalls) / _drawcalls)) >= (best_info.num_vertices * (1.2f - float(best_info.num_drawcalls) / _drawcalls)))) - { - best_info = it.second; - best_match = it.first; - } - - // Reset statistics for next frame - it.second.num_vertices = 0; - it.second.num_drawcalls = 0; - } - - if (_depth_source != best_match || !_tex[TEX_DEPTH]) - { - const auto &previous_info = _depth_source_table.at(_depth_source); - - // Resize depth texture if it dimensions have changed - if (best_info.width != previous_info.width || best_info.height != previous_info.height || best_info.format != previous_info.format || !_tex[TEX_DEPTH]) - { - if (best_info.format == GL_DEPTH_STENCIL) - best_info.format = GL_UNSIGNED_INT_24_8; - - // Recreate depth texture name (since the storage is immutable after the first call to glTexStorage) - glDeleteTextures(1, &_tex[TEX_DEPTH]); glGenTextures(1, &_tex[TEX_DEPTH]); - - glBindTexture(GL_TEXTURE_2D, _tex[TEX_DEPTH]); - glTexStorage2D(GL_TEXTURE_2D, 1, best_info.format, best_info.width, best_info.height); - - // Update FBO attachment - glBindFramebuffer(GL_FRAMEBUFFER, _fbo[FBO_BLIT]); - glFramebufferTexture(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, _tex[TEX_DEPTH], 0); - assert(glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE); - - update_texture_references(texture_reference::depth_buffer); - } - - _depth_source = best_match; - - if (best_match != 0) - { - glBindFramebuffer(GL_FRAMEBUFFER, _fbo[FBO_DEPTH]); - - if ((best_match & 0x80000000) == 0) { - glFramebufferTexture(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, best_match, best_info.level); - } else { - glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, best_match ^ 0x80000000); - } - - assert(glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE); - } - } -} diff --git a/msvc/source/opengl/runtime_opengl.hpp b/msvc/source/opengl/runtime_opengl.hpp deleted file mode 100644 index 9d62268..0000000 --- a/msvc/source/opengl/runtime_opengl.hpp +++ /dev/null @@ -1,117 +0,0 @@ -/** - * Copyright (C) 2014 Patrick Mours. All rights reserved. - * License: https://github.com/crosire/reshade#license - */ - -#pragma once - -#include "runtime.hpp" -#include "state_block.hpp" -#include - -namespace reshade { enum class texture_reference; } -namespace reshadefx { struct sampler_info; } - -namespace reshade::opengl -{ - class runtime_opengl : public runtime - { - public: - runtime_opengl(); - - bool on_init(HWND hwnd, unsigned int width, unsigned int height); - void on_reset(); - void on_present(); - - void on_draw_call(unsigned int vertices); - void on_fbo_attachment(GLenum attachment, GLenum target, GLuint object, GLint level); - - void capture_screenshot(uint8_t *buffer) const override; - - GLuint _current_vertex_count = 0; // Used to calculate vertex count inside glBegin/glEnd pairs - std::unordered_set _hdcs; - - private: - struct depth_source_info - { - unsigned int width, height; - GLint level, format; - unsigned int num_drawcalls, num_vertices; - }; - - bool init_texture(texture &info) override; - void upload_texture(texture &texture, const uint8_t *data) override; - bool update_texture_reference(texture &texture); - void update_texture_references(texture_reference type); - - bool compile_effect(effect_data &effect) override; - void unload_effects() override; - - bool add_sampler(const reshadefx::sampler_info &info, struct opengl_technique_data &technique_init); - bool init_technique(technique &info, const struct opengl_technique_data &technique_init, const std::unordered_map &entry_points, std::string &errors); - - void render_technique(technique &technique) override; - -#if RESHADE_GUI - void init_imgui_resources(); - void render_imgui_draw_data(ImDrawData *data) override; -#endif - - void detect_depth_source(); - - state_block _app_state; - GLuint _depth_source = 0; - std::unordered_map _depth_source_table; - - enum BUF - { -#if RESHADE_GUI - VBO_IMGUI, - IBO_IMGUI, -#endif - NUM_BUF - }; - enum TEX - { - TEX_BACK, - TEX_BACK_SRGB, - TEX_DEPTH, - NUM_TEX - }; - enum VAO - { - VAO_FX, -#if RESHADE_GUI - VAO_IMGUI, -#endif - NUM_VAO - }; - enum FBO - { - FBO_BACK, - FBO_DEPTH, - FBO_BLIT, - NUM_FBO - }; - enum RBO - { - RBO_COLOR, - RBO_DEPTH, - NUM_RBO - }; - - GLuint _buf[NUM_BUF] = {}; - GLuint _tex[NUM_TEX] = {}; - GLuint _vao[NUM_VAO] = {}; - GLuint _fbo[NUM_FBO] = {}; - GLuint _rbo[NUM_RBO] = {}; -#if RESHADE_GUI - GLuint _imgui_program = 0; - int _imgui_uniform_tex = 0; - int _imgui_uniform_proj = 0; -#endif - std::vector _reserved_texture_names; - std::unordered_map _effect_sampler_states; - std::vector> _effect_ubos; - }; -} diff --git a/msvc/source/opengl/state_block.cpp b/msvc/source/opengl/state_block.cpp deleted file mode 100644 index 8aa881a..0000000 --- a/msvc/source/opengl/state_block.cpp +++ /dev/null @@ -1,137 +0,0 @@ -/** - * Copyright (C) 2014 Patrick Mours. All rights reserved. - * License: https://github.com/crosire/reshade#license - */ - -#include "state_block.hpp" - -reshade::opengl::state_block::state_block() -{ - memset(this, 0, sizeof(*this)); -} - -void reshade::opengl::state_block::capture() -{ -#ifndef NDEBUG - has_state = true; -#endif - - glGetIntegerv(GL_VERTEX_ARRAY_BINDING, &_vao); - glGetIntegerv(GL_ARRAY_BUFFER_BINDING, &_vbo); - glGetIntegerv(GL_UNIFORM_BUFFER_BINDING, &_ubo); - glGetIntegerv(GL_CURRENT_PROGRAM, &_program); - - glGetIntegerv(GL_ACTIVE_TEXTURE, &_active_texture); - for (GLuint i = 0; i < 32; i++) - { - glActiveTexture(GL_TEXTURE0 + i); - glGetIntegerv(GL_TEXTURE_BINDING_2D, &_textures2d[i]); - glGetIntegerv(GL_SAMPLER_BINDING, &_samplers[i]); - } - - glGetIntegerv(GL_VIEWPORT, _viewport); - glGetIntegerv(GL_SCISSOR_BOX, _scissor_rect); - _scissor_test = glIsEnabled(GL_SCISSOR_TEST); - _blend = glIsEnabled(GL_BLEND); - glGetIntegerv(GL_BLEND_SRC, &_blend_src); - glGetIntegerv(GL_BLEND_DST, &_blend_dest); - glGetIntegerv(GL_BLEND_EQUATION_RGB, &_blend_eq_color); - glGetIntegerv(GL_BLEND_EQUATION_ALPHA, &_blend_eq_alpha); - _depth_test = glIsEnabled(GL_DEPTH_TEST); - glGetBooleanv(GL_DEPTH_WRITEMASK, &_depth_mask); - glGetIntegerv(GL_DEPTH_FUNC, &_depth_func); - _stencil_test = glIsEnabled(GL_STENCIL_TEST); - glGetIntegerv(GL_STENCIL_REF, &_stencil_ref); - glGetIntegerv(GL_STENCIL_FUNC, &_stencil_func); - glGetIntegerv(GL_STENCIL_FAIL, &_stencil_op_fail); - glGetIntegerv(GL_STENCIL_PASS_DEPTH_FAIL, &_stencil_op_zfail); - glGetIntegerv(GL_STENCIL_PASS_DEPTH_PASS, &_stencil_op_zpass); - glGetIntegerv(GL_STENCIL_VALUE_MASK, &_stencil_read_mask); - glGetIntegerv(GL_STENCIL_WRITEMASK, &_stencil_mask); - glGetIntegerv(GL_POLYGON_MODE, &_polygon_mode); - glGetIntegerv(GL_FRONT_FACE, &_frontface); - _cullface = glIsEnabled(GL_CULL_FACE); - glGetIntegerv(GL_CULL_FACE_MODE, &_cullface_mode); - glGetIntegerv(GL_FRAMEBUFFER_BINDING, &_fbo); - _srgb = glIsEnabled(GL_FRAMEBUFFER_SRGB); - glGetBooleanv(GL_COLOR_WRITEMASK, _color_mask); - - for (GLuint i = 0; i < 8; i++) - { - GLint drawbuffer = GL_NONE; - glGetIntegerv(GL_DRAW_BUFFER0 + i, &drawbuffer); - _drawbuffers[i] = static_cast(drawbuffer); - } - - if (gl3wProcs.gl.ClipControl != nullptr) - { - glGetIntegerv(GL_CLIP_ORIGIN, &clip_origin); - glGetIntegerv(GL_CLIP_DEPTH_MODE, &clip_depthmode); - } -} -void reshade::opengl::state_block::apply() const -{ -#ifndef NDEBUG - has_state = false; -#endif - - glBindVertexArray(_vao); - glBindBuffer(GL_ARRAY_BUFFER, _vbo); - glBindBuffer(GL_UNIFORM_BUFFER, _ubo); - glUseProgram(_program); - - for (GLuint i = 0; i < 32; i++) - { - glActiveTexture(GL_TEXTURE0 + i); - glBindTexture(GL_TEXTURE_2D, _textures2d[i]); - glBindSampler(i, _samplers[i]); - } - - glActiveTexture(_active_texture); - glViewport(_viewport[0], _viewport[1], _viewport[2], _viewport[3]); - glScissor(_scissor_rect[0], _scissor_rect[1], _scissor_rect[2], _scissor_rect[3]); - if (_scissor_test) { glEnable(GL_SCISSOR_TEST); } - else { glDisable(GL_SCISSOR_TEST); } - if (_blend) { glEnable(GL_BLEND); } - else { glDisable(GL_BLEND); } - glBlendFunc(_blend_src, _blend_dest); - glBlendEquationSeparate(_blend_eq_color, _blend_eq_alpha); - if (_depth_test) { glEnable(GL_DEPTH_TEST); } - else { glDisable(GL_DEPTH_TEST); } - glDepthMask(_depth_mask); - glDepthFunc(_depth_func); - if (_stencil_test) { glEnable(GL_STENCIL_TEST); } - else { glDisable(GL_STENCIL_TEST); } - glStencilFunc(_stencil_func, _stencil_ref, _stencil_read_mask); - glStencilOp(_stencil_op_fail, _stencil_op_zfail, _stencil_op_zpass); - glStencilMask(_stencil_mask); - glPolygonMode(GL_FRONT_AND_BACK, _polygon_mode); - glFrontFace(_frontface); - if (_cullface) { glEnable(GL_CULL_FACE); } - else { glDisable(GL_CULL_FACE); } - glCullFace(_cullface_mode); - glBindFramebuffer(GL_FRAMEBUFFER, _fbo); - if (_srgb) { glEnable(GL_FRAMEBUFFER_SRGB); } - else { glDisable(GL_FRAMEBUFFER_SRGB); } - glColorMask(_color_mask[0], _color_mask[1], _color_mask[2], _color_mask[3]); - - if (_drawbuffers[1] == GL_NONE && - _drawbuffers[2] == GL_NONE && - _drawbuffers[3] == GL_NONE && - _drawbuffers[4] == GL_NONE && - _drawbuffers[5] == GL_NONE && - _drawbuffers[6] == GL_NONE && - _drawbuffers[7] == GL_NONE) - { - glDrawBuffer(_drawbuffers[0]); - } - else - { - glDrawBuffers(8, _drawbuffers); - } - - if (gl3wProcs.gl.ClipControl != nullptr) - { - glClipControl(clip_origin, clip_depthmode); - } -} diff --git a/msvc/source/opengl/state_block.hpp b/msvc/source/opengl/state_block.hpp deleted file mode 100644 index aa69b64..0000000 --- a/msvc/source/opengl/state_block.hpp +++ /dev/null @@ -1,54 +0,0 @@ -/** - * Copyright (C) 2014 Patrick Mours. All rights reserved. - * License: https://github.com/crosire/reshade#license - */ - -#pragma once - -#include "opengl.hpp" - -namespace reshade::opengl -{ - class state_block - { - public: - state_block(); - - void capture(); - void apply() const; - -#ifndef NDEBUG - mutable bool has_state = false; -#endif - - private: - GLint _vao; - GLint _vbo; - GLint _ubo; - GLint _program; - GLint _textures2d[32], _samplers[32]; - GLint _active_texture; - GLint _viewport[4]; - GLint _scissor_rect[4]; - GLint _scissor_test; - GLint _blend; - GLint _blend_src, _blend_dest; - GLint _blend_eq_color, _blend_eq_alpha; - GLint _depth_test; - GLboolean _depth_mask; - GLint _depth_func; - GLint _stencil_test; - GLint _stencil_ref; - GLint _stencil_func; - GLint _stencil_op_fail, _stencil_op_zfail, _stencil_op_zpass; - GLint _stencil_read_mask, _stencil_mask; - GLint _polygon_mode, _frontface; - GLint _cullface, _cullface_mode; - GLint _fbo; - GLint _srgb; - GLint clip_origin; - GLint clip_depthmode; - GLboolean _color_mask[4]; - GLenum _drawbuffers[8]; - }; -} diff --git a/msvc/source/resource_loading.cpp b/msvc/source/resource_loading.cpp deleted file mode 100644 index 6175a1e..0000000 --- a/msvc/source/resource_loading.cpp +++ /dev/null @@ -1,36 +0,0 @@ -/** - * Copyright (C) 2014 Patrick Mours. All rights reserved. - * License: https://github.com/crosire/reshade#license - */ - -#include "resource_loading.hpp" -#include - -extern HMODULE g_module_handle; - -reshade::resources::data_resource reshade::resources::load_data_resource(unsigned int id) -{ - const HRSRC info = FindResource(g_module_handle, MAKEINTRESOURCE(id), RT_RCDATA); - const HGLOBAL handle = LoadResource(g_module_handle, info); - - data_resource result; - result.data = LockResource(handle); - result.data_size = SizeofResource(g_module_handle, info); - - return result; -} -reshade::resources::image_resource reshade::resources::load_image_resource(unsigned int id) -{ - DIBSECTION dib = {}; - const HANDLE handle = LoadImage(g_module_handle, MAKEINTRESOURCE(id), IMAGE_BITMAP, 0, 0, LR_CREATEDIBSECTION); - GetObject(handle, sizeof(dib), &dib); - - image_resource result; - result.width = dib.dsBm.bmWidth; - result.height = dib.dsBm.bmHeight; - result.bits_per_pixel = dib.dsBm.bmBitsPixel; - result.data = dib.dsBm.bmBits; - result.data_size = dib.dsBmih.biSizeImage; - - return result; -} diff --git a/msvc/source/resource_loading.hpp b/msvc/source/resource_loading.hpp deleted file mode 100644 index 18e7d9e..0000000 --- a/msvc/source/resource_loading.hpp +++ /dev/null @@ -1,24 +0,0 @@ -/** - * Copyright (C) 2014 Patrick Mours. All rights reserved. - * License: https://github.com/crosire/reshade#license - */ - -#pragma once - -#include "resource.h" - -namespace reshade::resources -{ - struct data_resource - { - size_t data_size; - const void *data; - }; - struct image_resource : public data_resource - { - unsigned int width, height, bits_per_pixel; - }; - - data_resource load_data_resource(unsigned int id); - image_resource load_image_resource(unsigned int id); -} diff --git a/msvc/source/runtime.cpp b/msvc/source/runtime.cpp deleted file mode 100644 index 30419ca..0000000 --- a/msvc/source/runtime.cpp +++ /dev/null @@ -1,1283 +0,0 @@ -/** - * Copyright (C) 2014 Patrick Mours. All rights reserved. - * License: https://github.com/crosire/reshade#license - */ - -#include "log.hpp" -#include "version.h" -#include "runtime.hpp" -#include "runtime_objects.hpp" -#include "effect_parser.hpp" -#include "effect_codegen.hpp" -#include "effect_preprocessor.hpp" -#include "input.hpp" -#include "ini_file.hpp" -#include -#include -#include -#include -#include -#include -#include - -extern volatile long g_network_traffic; -extern std::filesystem::path g_reshade_dll_path; -extern std::filesystem::path g_target_executable_path; - -static bool find_file(const std::vector &search_paths, std::filesystem::path &path) -{ - std::error_code ec; - if (path.is_relative()) - for (const auto &search_path : search_paths) - { - std::filesystem::path canonical_search_path = search_path; - if (search_path.is_relative()) - canonical_search_path = std::filesystem::canonical(g_reshade_dll_path.parent_path() / search_path, ec); - if (ec || canonical_search_path.empty()) - continue; - - if (std::filesystem::path result = canonical_search_path / path; std::filesystem::exists(result, ec)) - { - path = std::move(result); - return true; - } - } - return std::filesystem::exists(path, ec); -} - -static std::vector find_files(const std::vector &search_paths, std::initializer_list extensions) -{ - std::error_code ec; - std::vector files; - for (const auto &search_path : search_paths) - { - std::filesystem::path canonical_search_path = search_path; - if (search_path.is_relative()) // Ignore the working directory and instead start relative paths at the DLL location - canonical_search_path = std::filesystem::canonical(g_reshade_dll_path.parent_path() / search_path, ec); - if (ec || canonical_search_path.empty()) - continue; // Failed to find a valid directory matching the search path - - for (const auto &entry : std::filesystem::directory_iterator(canonical_search_path, ec)) - for (auto ext : extensions) - if (entry.path().extension() == ext) - files.push_back(entry.path()); - } - return files; -} - -reshade::runtime::runtime() : - _start_time(std::chrono::high_resolution_clock::now()), - _last_present_time(std::chrono::high_resolution_clock::now()), - _last_frame_duration(std::chrono::milliseconds(1)), - _effect_search_paths({ ".\\" }), - _texture_search_paths({ ".\\" }), - _global_preprocessor_definitions({ - "RESHADE_DEPTH_LINEARIZATION_FAR_PLANE=1000.0", - "RESHADE_DEPTH_INPUT_IS_UPSIDE_DOWN=0", - "RESHADE_DEPTH_INPUT_IS_REVERSED=1", - "RESHADE_DEPTH_INPUT_IS_LOGARITHMIC=0" }), - _reload_key_data(), - _effects_key_data(), - _screenshot_key_data(), - _screenshot_path(g_target_executable_path.parent_path()) -{ - // Default shortcut PrtScrn - _screenshot_key_data[0] = 0x2C; - - _configuration_path = g_reshade_dll_path; - _configuration_path.replace_extension(".ini"); - if (std::error_code ec; !std::filesystem::exists(_configuration_path, ec)) - _configuration_path = g_reshade_dll_path.parent_path() / "ReShade.ini"; - - _needs_update = check_for_update(_latest_version); - -#if RESHADE_GUI - init_ui(); -#endif - load_config(); -} -reshade::runtime::~runtime() -{ - assert(_worker_threads.empty()); - assert(!_is_initialized && _techniques.empty()); - -#if RESHADE_GUI - deinit_ui(); -#endif -} - -bool reshade::runtime::on_init(input::window_handle window) -{ - LOG(INFO) << "Recreated runtime environment on runtime " << this << '.'; - - _input = input::register_window(window); - - // Reset frame count to zero so effects are loaded in 'update_and_render_effects' - _framecount = 0; - - _is_initialized = true; - _last_reload_time = std::chrono::high_resolution_clock::now(); - -#if RESHADE_GUI - build_font_atlas(); -#endif - - return true; -} -void reshade::runtime::on_reset() -{ - unload_effects(); - - if (!_is_initialized) - return; - -#if RESHADE_GUI - destroy_font_atlas(); -#endif - - LOG(INFO) << "Destroyed runtime environment on runtime " << this << '.'; - - _width = _height = 0; - _is_initialized = false; -} -void reshade::runtime::on_present() -{ - // Get current time and date - time_t t = std::time(nullptr); tm tm; - localtime_s(&tm, &t); - _date[0] = tm.tm_year + 1900; - _date[1] = tm.tm_mon + 1; - _date[2] = tm.tm_mday; - _date[3] = tm.tm_hour * 3600 + tm.tm_min * 60 + tm.tm_sec; - - // Advance various statistics - _framecount++; - const auto current_time = std::chrono::high_resolution_clock::now(); - _last_frame_duration = current_time - _last_present_time; _last_present_time = current_time; - - // Lock input so it cannot be modified by other threads while we are reading it here - const auto input_lock = _input->lock(); - - // Handle keyboard shortcuts - if (!_ignore_shortcuts) - { - if (_input->is_key_pressed(_reload_key_data)) - load_effects(); - - if (_input->is_key_pressed(_effects_key_data)) - _effects_enabled = !_effects_enabled; - - if (_input->is_key_pressed(_screenshot_key_data)) - save_screenshot(); - } - -#if RESHADE_GUI - // Draw overlay - draw_ui(); -#endif - - // Reset input status - _input->next_frame(); - - static int cooldown = 0, traffic = 0; - - if (cooldown-- > 0) - { - traffic += g_network_traffic > 0; - } - else - { - _has_high_network_activity = traffic > 10; - traffic = 0; - cooldown = 30; - } - - g_network_traffic = 0; - _drawcalls = _vertices = 0; -} - -void reshade::runtime::load_effect(const std::filesystem::path &path, size_t &out_id) -{ - effect_data effect; - effect.source_file = path; - effect.compile_sucess = true; - - { // Load, pre-process and compile the source file - reshadefx::preprocessor pp; - if (path.is_absolute()) - pp.add_include_path(path.parent_path()); - - for (const auto &include_path : _effect_search_paths) - { - std::error_code ec; - std::filesystem::path canonical_include_path = include_path; - if (include_path.is_relative()) // Ignore the working directory and instead start relative paths at the DLL location - canonical_include_path = std::filesystem::canonical(g_reshade_dll_path.parent_path() / include_path, ec); - - if (!ec && !canonical_include_path.empty()) - pp.add_include_path(canonical_include_path); - } - - pp.add_macro_definition("__RESHADE__", std::to_string(VERSION_MAJOR * 10000 + VERSION_MINOR * 100 + VERSION_REVISION)); - pp.add_macro_definition("__RESHADE_PERFORMANCE_MODE__", _performance_mode ? "1" : "0"); - pp.add_macro_definition("__VENDOR__", std::to_string(_vendor_id)); - pp.add_macro_definition("__DEVICE__", std::to_string(_device_id)); - pp.add_macro_definition("__RENDERER__", std::to_string(_renderer_id)); - pp.add_macro_definition("__APPLICATION__", std::to_string(std::hash()(g_target_executable_path.stem().u8string()))); - pp.add_macro_definition("BUFFER_WIDTH", std::to_string(_width)); - pp.add_macro_definition("BUFFER_HEIGHT", std::to_string(_height)); - pp.add_macro_definition("BUFFER_RCP_WIDTH", "(1.0 / BUFFER_WIDTH)"); - pp.add_macro_definition("BUFFER_RCP_HEIGHT", "(1.0 / BUFFER_HEIGHT)"); - - std::vector preprocessor_definitions = _global_preprocessor_definitions; - preprocessor_definitions.insert(preprocessor_definitions.end(), _preset_preprocessor_definitions.begin(), _preset_preprocessor_definitions.end()); - - for (const auto &definition : preprocessor_definitions) - { - if (definition.empty()) - continue; // Skip invalid definitions - - const size_t equals_index = definition.find('='); - if (equals_index != std::string::npos) - pp.add_macro_definition( - definition.substr(0, equals_index), - definition.substr(equals_index + 1)); - else - pp.add_macro_definition(definition); - } - - if (!pp.append_file(path)) - { - LOG(ERROR) << "Failed to load " << path << ":\n" << pp.errors(); - effect.compile_sucess = false; - } - - unsigned shader_model; - if (_renderer_id == 0x9000) - shader_model = 30; - else if (_renderer_id < 0xa100) - shader_model = 40; - else if (_renderer_id < 0xb000) - shader_model = 41; - else if (_renderer_id < 0xc000) - shader_model = 50; - else - shader_model = 60; - - std::unique_ptr codegen; - if ((_renderer_id & 0xF0000) == 0) - codegen.reset(reshadefx::create_codegen_hlsl(shader_model, true, _performance_mode)); - else if (_renderer_id < 0x20000) - codegen.reset(reshadefx::create_codegen_glsl(true, _performance_mode)); - else // Vulkan uses SPIR-V input - codegen.reset(reshadefx::create_codegen_spirv(true, _performance_mode)); - - reshadefx::parser parser; - - // Compile the pre-processed source code (try the compile even if the preprocessor step failed to get additional error information) - if (!parser.parse(std::move(pp.output()), codegen.get())) - { - LOG(ERROR) << "Failed to compile " << path << ":\n" << parser.errors(); - effect.compile_sucess = false; - } - - // Append preprocessor and parser errors to the error list - effect.errors = std::move(pp.errors()) + std::move(parser.errors()); - - // Write result to effect module - codegen->write_result(effect.module); - } - - // Fill all specialization constants with values from the current preset - if (_performance_mode && !_current_preset_path.empty() && effect.compile_sucess) - { - const ini_file preset(_current_preset_path); - const std::string section(path.filename().u8string()); - - for (reshadefx::uniform_info &constant : effect.module.spec_constants) - { - effect.preamble += "#define SPEC_CONSTANT_" + constant.name + ' '; - - switch (constant.type.base) - { - case reshadefx::type::t_int: - preset.get(section, constant.name, constant.initializer_value.as_int); - break; - case reshadefx::type::t_bool: - case reshadefx::type::t_uint: - preset.get(section, constant.name, constant.initializer_value.as_uint); - break; - case reshadefx::type::t_float: - preset.get(section, constant.name, constant.initializer_value.as_float); - break; - } - - for (unsigned int i = 0; i < constant.type.components(); ++i) - { - switch (constant.type.base) - { - case reshadefx::type::t_bool: - effect.preamble += constant.initializer_value.as_uint[i] ? "true" : "false"; - break; - case reshadefx::type::t_int: - effect.preamble += std::to_string(constant.initializer_value.as_int[i]); - break; - case reshadefx::type::t_uint: - effect.preamble += std::to_string(constant.initializer_value.as_uint[i]); - break; - case reshadefx::type::t_float: - effect.preamble += std::to_string(constant.initializer_value.as_float[i]); - break; - } - - if (i + 1 < constant.type.components()) - effect.preamble += ", "; - } - - effect.preamble += '\n'; - } - } - - // Guard access to shared variables - const std::lock_guard lock(_reload_mutex); - - effect.index = out_id = _loaded_effects.size(); - effect.storage_offset = _uniform_data_storage.size(); - - for (const reshadefx::uniform_info &info : effect.module.uniforms) - { - uniform &variable = _uniforms.emplace_back(info); - variable.effect_index = effect.index; - - variable.storage_offset = effect.storage_offset + variable.offset; - // Create space for the new variable in the storage area and fill it with the initializer value - _uniform_data_storage.resize(variable.storage_offset + variable.size); - - // Copy initial data into uniform storage area - reset_uniform_value(variable); - - const std::string_view special = variable.annotation_as_string("source"); - if (special.empty()) /* Ignore if annotation is missing */; - else if (special == "frametime") - variable.special = special_uniform::frame_time; - else if (special == "framecount") - variable.special = special_uniform::frame_count; - else if (special == "random") - variable.special = special_uniform::random; - else if (special == "pingpong") - variable.special = special_uniform::ping_pong; - else if (special == "date") - variable.special = special_uniform::date; - else if (special == "timer") - variable.special = special_uniform::timer; - else if (special == "key") - variable.special = special_uniform::key; - else if (special == "mousepoint") - variable.special = special_uniform::mouse_point; - else if (special == "mousedelta") - variable.special = special_uniform::mouse_delta; - else if (special == "mousebutton") - variable.special = special_uniform::mouse_button; - } - - effect.storage_size = (_uniform_data_storage.size() - effect.storage_offset + 15) & ~15; - _uniform_data_storage.resize(effect.storage_offset + effect.storage_size); - - for (const reshadefx::texture_info &info : effect.module.textures) - { - // Try to share textures with the same name across effects - if (const auto existing_texture = std::find_if(_textures.begin(), _textures.end(), - [&info](const auto &item) { return item.unique_name == info.unique_name; }); - existing_texture != _textures.end()) - { - if (info.semantic.empty() && ( - existing_texture->width != info.width || - existing_texture->height != info.height || - existing_texture->levels != info.levels || - existing_texture->format != info.format)) - { - effect.errors += "warning: " + info.unique_name + ": another effect already created a texture with the same name but different dimensions; textures are shared across all effects, so either rename the variable or adjust the dimensions so they match\n"; - } - - existing_texture->shared = true; - continue; - } - - texture &texture = _textures.emplace_back(info); - texture.effect_index = effect.index; - - if (info.semantic == "COLOR") - texture.impl_reference = texture_reference::back_buffer; - else if (info.semantic == "DEPTH") - texture.impl_reference = texture_reference::depth_buffer; - else if (!info.semantic.empty()) - effect.errors += "warning: " + info.unique_name + ": unknown semantic '" + info.semantic + "'\n"; - } - - _loaded_effects.push_back(effect); // The 'enable_technique' call below needs to access this, so append the effect now - - for (const reshadefx::technique_info &info : effect.module.techniques) - { - technique &technique = _techniques.emplace_back(info); - technique.effect_index = effect.index; - - technique.hidden = technique.annotation_as_int("hidden") != 0; - technique.timeout = technique.annotation_as_int("timeout"); - technique.timeleft = technique.timeout; - technique.toggle_key_data[0] = technique.annotation_as_int("toggle"); - technique.toggle_key_data[1] = technique.annotation_as_int("togglectrl"); - technique.toggle_key_data[2] = technique.annotation_as_int("toggleshift"); - technique.toggle_key_data[3] = technique.annotation_as_int("togglealt"); - - if (technique.annotation_as_int("enabled")) - enable_technique(technique); - } - - if (effect.compile_sucess) - if (effect.errors.empty()) - LOG(INFO) << "Successfully loaded " << path << '.'; - else - LOG(WARN) << "Successfully loaded " << path << " with warnings:\n" << effect.errors; - - _reload_remaining_effects--; - _last_reload_successful &= effect.compile_sucess; -} -void reshade::runtime::load_effects() -{ - // Clear out any previous effects - unload_effects(); - - _last_reload_successful = true; - - // Reload preprocessor definitions from current preset before compiling - if (!_current_preset_path.empty()) - { - _preset_preprocessor_definitions.clear(); - - const ini_file preset(_current_preset_path); - preset.get("", "PreprocessorDefinitions", _preset_preprocessor_definitions); - } - - // Build a list of effect files by walking through the effect search paths - const std::vector effect_files = - find_files(_effect_search_paths, { ".fx" }); - - _reload_total_effects = effect_files.size(); - _reload_remaining_effects = _reload_total_effects; - - // Now that we have a list of files, load them in parallel - for (const std::filesystem::path &file : effect_files) - _worker_threads.emplace_back([this, file]() { size_t id; load_effect(file, id); }); // Keep track of the spawned threads, so the runtime cannot be destroyed while they are still running -} -void reshade::runtime::load_textures() -{ - LOG(INFO) << "Loading image files for textures ..."; - - for (texture &texture : _textures) - { - if (texture.impl == nullptr || texture.impl_reference != texture_reference::none) - continue; // Ignore textures that are not created yet and those that are handled in the runtime implementation - - std::filesystem::path source_path = std::filesystem::u8path( - texture.annotation_as_string("source")); - // Ignore textures that have no image file attached to them (e.g. plain render targets) - if (source_path.empty()) - continue; - - // Search for image file using the provided search paths unless the path provided is already absolute - if (!find_file(_texture_search_paths, source_path)) { - LOG(ERROR) << "> Source " << source_path << " for texture '" << texture.unique_name << "' could not be found in any of the texture search paths."; - continue; - } - - unsigned char *filedata = nullptr; - int width = 0, height = 0, channels = 0; - - if (FILE *file; _wfopen_s(&file, source_path.c_str(), L"rb") == 0) - { - // Read texture data into memory in one go since that is faster than reading chunk by chunk - std::vector mem(static_cast(std::filesystem::file_size(source_path))); - fread(mem.data(), 1, mem.size(), file); - fclose(file); - - if (stbi_dds_test_memory(mem.data(), static_cast(mem.size()))) - filedata = stbi_dds_load_from_memory(mem.data(), static_cast(mem.size()), &width, &height, &channels, STBI_rgb_alpha); - else - filedata = stbi_load_from_memory(mem.data(), static_cast(mem.size()), &width, &height, &channels, STBI_rgb_alpha); - } - - if (filedata == nullptr) { - LOG(ERROR) << "> Source " << source_path << " for texture '" << texture.unique_name << "' could not be loaded! Make sure it is of a compatible file format."; - continue; - } - - if (texture.width != uint32_t(width) || texture.height != uint32_t(height)) - { - LOG(INFO) << "> Resizing image data for texture '" << texture.unique_name << "' from " << width << "x" << height << " to " << texture.width << "x" << texture.height << " ..."; - - std::vector resized(texture.width * texture.height * 4); - stbir_resize_uint8(filedata, width, height, 0, resized.data(), texture.width, texture.height, 0, 4); - upload_texture(texture, resized.data()); - } - else - { - upload_texture(texture, filedata); - } - - stbi_image_free(filedata); - } - - _textures_loaded = true; -} - -void reshade::runtime::unload_effect(size_t id) -{ - _uniforms.erase(std::remove_if(_uniforms.begin(), _uniforms.end(), - [id](const auto &it) { return it.effect_index == id; }), _uniforms.end()); - _textures.erase(std::remove_if(_textures.begin(), _textures.end(), - [id](const auto &it) { return it.effect_index == id; }), _textures.end()); - _techniques.erase(std::remove_if(_techniques.begin(), _techniques.end(), - [id](const auto &it) { return it.effect_index == id; }), _techniques.end()); - - _loaded_effects[id].source_file.clear(); - -#if RESHADE_GUI - // Remove all texture preview windows since some may no longer be valid - _texture_previews.clear(); -#endif -} -void reshade::runtime::unload_effects() -{ - // Make sure no threads are still accessing effect data - for (std::thread &thread : _worker_threads) - thread.join(); - _worker_threads.clear(); - - _uniforms.clear(); - _textures.clear(); - _techniques.clear(); - - _loaded_effects.clear(); - _uniform_data_storage.clear(); - - _textures_loaded = false; - -#if RESHADE_GUI - // Remove all texture preview windows since those textures were deleted above - _texture_previews.clear(); -#endif -} - -void reshade::runtime::update_and_render_effects() -{ - // Delay first load to the first render call to avoid loading while the application is still initializing - if (_framecount == 0 && !_no_reload_on_init) - load_effects(); - - if (_reload_remaining_effects == 0) - { - // Finished loading effects, so apply preset to figure out which ones need compiling - load_current_preset(); - - _last_reload_time = std::chrono::high_resolution_clock::now(); - _reload_total_effects = 0; - _reload_remaining_effects = std::numeric_limits::max(); - } - else if (_reload_remaining_effects != std::numeric_limits::max()) - { - return; // Cannot render while effects are still being loaded - } - else - { - if (!_reload_compile_queue.empty()) - { - // Pop an effect from the queue - size_t effect_index = _reload_compile_queue.back(); - _reload_compile_queue.pop_back(); - - effect_data &effect = _loaded_effects[effect_index]; - - // Create textures now, since they are referenced when building samplers in the 'compile_effect' call below - bool success = true; - for (texture &texture : _textures) - if (texture.impl == nullptr && (texture.effect_index == effect_index || texture.shared)) - success &= init_texture(texture); - - // Compile the effect with the back-end implementation - if (success && !compile_effect(effect)) - { - success = false; - - // De-duplicate error lines (D3DCompiler sometimes repeats the same error multiple times) - for (size_t cur_line_offset = 0, next_line_offset, end_offset; - (next_line_offset = effect.errors.find('\n', cur_line_offset)) != std::string::npos && (end_offset = effect.errors.find('\n', next_line_offset + 1)) != std::string::npos; cur_line_offset = next_line_offset + 1) - { - const std::string_view cur_line(effect.errors.c_str() + cur_line_offset, next_line_offset - cur_line_offset); - const std::string_view next_line(effect.errors.c_str() + next_line_offset + 1, end_offset - next_line_offset - 1); - - if (cur_line == next_line) - { - effect.errors.erase(next_line_offset, end_offset - next_line_offset); - next_line_offset = cur_line_offset - 1; - } - } - - LOG(ERROR) << "Failed to compile " << effect.source_file << ":\n" << effect.errors; - } - - if (!success) - { - // Destroy all textures belonging to this effect - for (texture &texture : _textures) - if (texture.effect_index == effect_index && !texture.shared) - texture.impl.reset(); - // Disable all techniques belonging to this effect - for (technique &technique : _techniques) - if (technique.effect_index == effect_index) - disable_technique(technique); - - effect.compile_sucess = false; - _last_reload_successful = false; - } - - // An effect has changed, need to reload textures - _textures_loaded = false; - } - else if (!_textures_loaded) - { - load_textures(); - } - } - - // Lock input so it cannot be modified by other threads while we are reading it here - // TODO: This does not catch input happening between now and 'on_present' - const auto input_lock = _input->lock(); - - // Nothing to do here if effects are disabled globally - if (!_effects_enabled) - return; - - // Update special uniform variables - for (uniform &variable : _uniforms) - { - switch (variable.special) - { - case special_uniform::frame_time: - set_uniform_value(variable, _last_frame_duration.count() * 1e-6f, 0.0f, 0.0f, 0.0f); - break; - case special_uniform::frame_count: - if (variable.type.is_boolean()) - set_uniform_value(variable, (_framecount % 2) == 0); - else - set_uniform_value(variable, static_cast(_framecount % UINT_MAX)); - break; - case special_uniform::random: { - const int min = variable.annotation_as_int("min"); - const int max = variable.annotation_as_int("max"); - set_uniform_value(variable, min + (std::rand() % (max - min + 1))); - break; } - case special_uniform::ping_pong: { - const float min = variable.annotation_as_float("min"); - const float max = variable.annotation_as_float("max"); - const float step_min = variable.annotation_as_float("step", 0); - const float step_max = variable.annotation_as_float("step", 1); - float increment = step_max == 0 ? step_min : (step_min + std::fmodf(static_cast(std::rand()), step_max - step_min + 1)); - const float smoothing = variable.annotation_as_float("smoothing"); - - float value[2] = { 0, 0 }; - get_uniform_value(variable, value, 2); - if (value[1] >= 0) - { - increment = std::max(increment - std::max(0.0f, smoothing - (max - value[0])), 0.05f); - increment *= _last_frame_duration.count() * 1e-9f; - - if ((value[0] += increment) >= max) - value[0] = max, value[1] = -1; - } - else - { - increment = std::max(increment - std::max(0.0f, smoothing - (value[0] - min)), 0.05f); - increment *= _last_frame_duration.count() * 1e-9f; - - if ((value[0] -= increment) <= min) - value[0] = min, value[1] = +1; - } - set_uniform_value(variable, value, 2); - break; } - case special_uniform::date: - set_uniform_value(variable, _date, 4); - break; - case special_uniform::timer: - set_uniform_value(variable, static_cast(std::chrono::duration_cast(_last_present_time - _start_time).count())); - break; - case special_uniform::key: - if (const int keycode = variable.annotation_as_int("keycode"); - keycode > 7 && keycode < 256) - if (const std::string_view mode = variable.annotation_as_string("mode"); - mode == "toggle" || variable.annotation_as_int("toggle")) { - bool current_value = false; - get_uniform_value(variable, ¤t_value, 1); - if (_input->is_key_pressed(keycode)) - set_uniform_value(variable, !current_value); - } else if (mode == "press") - set_uniform_value(variable, _input->is_key_pressed(keycode)); - else - set_uniform_value(variable, _input->is_key_down(keycode)); - break; - case special_uniform::mouse_point: - set_uniform_value(variable, _input->mouse_position_x(), _input->mouse_position_y()); - break; - case special_uniform::mouse_delta: - set_uniform_value(variable, _input->mouse_movement_delta_x(), _input->mouse_movement_delta_y()); - break; - case special_uniform::mouse_button: - if (const int keycode = variable.annotation_as_int("keycode"); - keycode >= 0 && keycode < 5) - if (const std::string_view mode = variable.annotation_as_string("mode"); - mode == "toggle" || variable.annotation_as_int("toggle")) { - bool current_value = false; - get_uniform_value(variable, ¤t_value, 1); - if (_input->is_mouse_button_pressed(keycode)) - set_uniform_value(variable, !current_value); - } else if (mode == "press") - set_uniform_value(variable, _input->is_mouse_button_pressed(keycode)); - else - set_uniform_value(variable, _input->is_mouse_button_down(keycode)); - break; - } - } - - // Render all enabled techniques - for (technique &technique : _techniques) - { - if (technique.timeleft > 0) - { - technique.timeleft -= std::chrono::duration_cast(_last_frame_duration).count(); - if (technique.timeleft <= 0) - disable_technique(technique); - } - else if (!_ignore_shortcuts && (_input->is_key_pressed(technique.toggle_key_data) || - (technique.toggle_key_data[0] >= 0x01 && technique.toggle_key_data[0] <= 0x06 && _input->is_mouse_button_pressed(technique.toggle_key_data[0] - 1)))) - { - if (!technique.enabled) - enable_technique(technique); - else - disable_technique(technique); - } - - if (technique.impl == nullptr || !technique.enabled) - continue; // Ignore techniques that are not fully loaded or currently disabled - - const auto time_technique_started = std::chrono::high_resolution_clock::now(); - render_technique(technique); - const auto time_technique_finished = std::chrono::high_resolution_clock::now(); - - technique.average_cpu_duration.append(std::chrono::duration_cast(time_technique_finished - time_technique_started).count()); - } -} - -void reshade::runtime::enable_technique(technique &technique) -{ - if (!_loaded_effects[technique.effect_index].compile_sucess) - return; // Cannot enable techniques that failed to compile - - const bool status_changed = !technique.enabled; - technique.enabled = true; - technique.timeleft = technique.timeout; - - // Queue effect file for compilation if it was not fully loaded yet - if (technique.impl == nullptr && // Avoid adding the same effect multiple times to the queue if it contains multiple techniques that were enabled simultaneously - std::find(_reload_compile_queue.begin(), _reload_compile_queue.end(), technique.effect_index) == _reload_compile_queue.end()) - { - _reload_total_effects++; - _reload_compile_queue.push_back(technique.effect_index); - } - - if (status_changed) // Increase rendering reference count - _loaded_effects[technique.effect_index].rendering++; -} -void reshade::runtime::disable_technique(technique &technique) -{ - const bool status_changed = technique.enabled; - technique.enabled = false; - technique.timeleft = 0; - technique.average_cpu_duration.clear(); - technique.average_gpu_duration.clear(); - - if (status_changed) // Decrease rendering reference count - _loaded_effects[technique.effect_index].rendering--; -} - -void reshade::runtime::subscribe_to_load_config(std::function function) -{ - _load_config_callables.push_back(function); - - const ini_file config(_configuration_path); - function(config); -} -void reshade::runtime::subscribe_to_save_config(std::function function) -{ - _save_config_callables.push_back(function); - - ini_file config(_configuration_path); - function(config); -} - -void reshade::runtime::load_config() -{ - const ini_file config(_configuration_path); - - std::filesystem::path current_preset_path; - - config.get("INPUT", "KeyReload", _reload_key_data); - config.get("INPUT", "KeyEffects", _effects_key_data); - config.get("INPUT", "KeyScreenshot", _screenshot_key_data); - - config.get("GENERAL", "PerformanceMode", _performance_mode); - config.get("GENERAL", "EffectSearchPaths", _effect_search_paths); - config.get("GENERAL", "TextureSearchPaths", _texture_search_paths); - config.get("GENERAL", "PreprocessorDefinitions", _global_preprocessor_definitions); - config.get("GENERAL", "CurrentPresetPath", current_preset_path); - config.get("GENERAL", "ScreenshotPath", _screenshot_path); - config.get("GENERAL", "ScreenshotFormat", _screenshot_format); - config.get("GENERAL", "ScreenshotIncludePreset", _screenshot_include_preset); - config.get("GENERAL", "NoReloadOnInit", _no_reload_on_init); - - if (current_preset_path.empty()) - { - size_t preset_index = 0; - std::vector preset_files; - config.get("GENERAL", "PresetFiles", preset_files); - config.get("GENERAL", "CurrentPreset", preset_index); - - if (preset_index < preset_files.size()) - current_preset_path = preset_files[preset_index]; - } - - set_current_preset(current_preset_path); - - for (const auto &callback : _load_config_callables) - callback(config); -} -void reshade::runtime::save_config() const -{ - save_config(_configuration_path); -} -void reshade::runtime::save_config(const std::filesystem::path &path) const -{ - ini_file config(_configuration_path, path); - - config.set("INPUT", "KeyReload", _reload_key_data); - config.set("INPUT", "KeyEffects", _effects_key_data); - config.set("INPUT", "KeyScreenshot", _screenshot_key_data); - - config.set("GENERAL", "PerformanceMode", _performance_mode); - config.set("GENERAL", "EffectSearchPaths", _effect_search_paths); - config.set("GENERAL", "TextureSearchPaths", _texture_search_paths); - config.set("GENERAL", "PreprocessorDefinitions", _global_preprocessor_definitions); - config.set("GENERAL", "CurrentPresetPath", _current_preset_path); - config.set("GENERAL", "ScreenshotPath", _screenshot_path); - config.set("GENERAL", "ScreenshotFormat", _screenshot_format); - config.set("GENERAL", "ScreenshotIncludePreset", _screenshot_include_preset); - config.set("GENERAL", "NoReloadOnInit", _no_reload_on_init); - - for (const auto &callback : _save_config_callables) - callback(config); -} - -void reshade::runtime::load_preset(const std::filesystem::path &path) -{ - const ini_file preset(path); - - std::vector technique_list; - preset.get("", "Techniques", technique_list); - std::vector sorted_technique_list; - preset.get("", "TechniqueSorting", sorted_technique_list); - std::vector preset_preprocessor_definitions; - preset.get("", "PreprocessorDefinitions", preset_preprocessor_definitions); - - // Recompile effects if preprocessor definitions have changed or running in performance mode (in which case all preset values are compile-time constants) - if (_reload_remaining_effects != 0 && // ... unless this is the 'load_current_preset' call in 'update_and_render_effects' - (_performance_mode || preset_preprocessor_definitions != _preset_preprocessor_definitions)) - { - assert(path == _current_preset_path); - _preset_preprocessor_definitions = std::move(preset_preprocessor_definitions); - load_effects(); - return; // Preset values are loaded in 'update_and_render_effects' during effect loading - } - - // Reorder techniques - if (sorted_technique_list.empty()) - sorted_technique_list = technique_list; - - std::sort(_techniques.begin(), _techniques.end(), - [&sorted_technique_list](const auto &lhs, const auto &rhs) { - return (std::find(sorted_technique_list.begin(), sorted_technique_list.end(), lhs.name) - sorted_technique_list.begin()) < - (std::find(sorted_technique_list.begin(), sorted_technique_list.end(), rhs.name) - sorted_technique_list.begin()); - }); - - for (uniform &variable : _uniforms) - { - reshadefx::constant values; - - switch (variable.type.base) - { - case reshadefx::type::t_int: - get_uniform_value(variable, values.as_int, 16); - preset.get(_loaded_effects[variable.effect_index].source_file.filename().u8string(), variable.name, values.as_int); - set_uniform_value(variable, values.as_int, 16); - break; - case reshadefx::type::t_bool: - case reshadefx::type::t_uint: - get_uniform_value(variable, values.as_uint, 16); - preset.get(_loaded_effects[variable.effect_index].source_file.filename().u8string(), variable.name, values.as_uint); - set_uniform_value(variable, values.as_uint, 16); - break; - case reshadefx::type::t_float: - get_uniform_value(variable, values.as_float, 16); - preset.get(_loaded_effects[variable.effect_index].source_file.filename().u8string(), variable.name, values.as_float); - set_uniform_value(variable, values.as_float, 16); - break; - } - } - - for (technique &technique : _techniques) - { - // Ignore preset if "enabled" annotation is set - if (technique.annotation_as_int("enabled") - || std::find(technique_list.begin(), technique_list.end(), technique.name) != technique_list.end()) - enable_technique(technique); - else - disable_technique(technique); - - // Reset toggle key first, since it may not exist in the preset - std::fill_n(technique.toggle_key_data, _countof(technique.toggle_key_data), 0); - preset.get("", "Key" + technique.name, technique.toggle_key_data); - } -} -void reshade::runtime::load_current_preset() -{ - load_preset(_current_preset_path); -} -void reshade::runtime::save_preset(const std::filesystem::path &path) const -{ - ini_file preset(path); - - std::vector effect_list; - std::vector technique_list; - std::vector sorted_technique_list; - - for (const technique &technique : _techniques) - { - if (technique.enabled) - technique_list.push_back(technique.name); - if (technique.enabled || technique.toggle_key_data[0] != 0) - effect_list.push_back(technique.effect_index); - - // Keep track of the order of all techniques and not just the enabled ones - sorted_technique_list.push_back(technique.name); - - if (technique.toggle_key_data[0] != 0) - preset.set("", "Key" + technique.name, technique.toggle_key_data); - else if (int value = 0; preset.get("", "Key" + technique.name, value), value != 0) - preset.set("", "Key" + technique.name, 0); // Clear toggle key data - } - - preset.set("", "Techniques", std::move(technique_list)); - preset.set("", "TechniqueSorting", std::move(sorted_technique_list)); - preset.set("", "PreprocessorDefinitions", _preset_preprocessor_definitions); - - // TODO: Do we want to save spec constants here too? The preset will be rather empty in performance mode otherwise. - for (const uniform &variable : _uniforms) - { - if (variable.special != special_uniform::none - || std::find(effect_list.begin(), effect_list.end(), variable.effect_index) == effect_list.end()) - continue; - - const std::string section = - _loaded_effects[variable.effect_index].source_file.filename().u8string(); - reshadefx::constant values; - - assert(variable.type.components() <= 16); - - switch (variable.type.base) - { - case reshadefx::type::t_int: - get_uniform_value(variable, values.as_int, 16); - preset.set(section, variable.name, values.as_int, variable.type.components()); - break; - case reshadefx::type::t_bool: - case reshadefx::type::t_uint: - get_uniform_value(variable, values.as_uint, 16); - preset.set(section, variable.name, values.as_uint, variable.type.components()); - break; - case reshadefx::type::t_float: - get_uniform_value(variable, values.as_float, 16); - preset.set(section, variable.name, values.as_float, variable.type.components()); - break; - } - } -} -void reshade::runtime::save_current_preset() const -{ - save_preset(_current_preset_path); -} - -void reshade::runtime::save_screenshot() -{ - std::vector data(_width * _height * 4); - capture_screenshot(data.data()); - - const int hour = _date[3] / 3600; - const int minute = (_date[3] - hour * 3600) / 60; - const int seconds = _date[3] - hour * 3600 - minute * 60; - - char filename[21]; - sprintf_s(filename, " %.4d-%.2d-%.2d %.2d-%.2d-%.2d", _date[0], _date[1], _date[2], hour, minute, seconds); - - const std::wstring least = (_screenshot_path.is_relative() ? g_target_executable_path.parent_path() / _screenshot_path : _screenshot_path) / g_target_executable_path.stem().concat(filename); - const std::wstring screenshot_path = least + (_screenshot_format == 0 ? L".bmp" : L".png"); - - LOG(INFO) << "Saving screenshot to " << screenshot_path << " ..."; - - _screenshot_save_success = false; // Default to a save failure unless it is reported to succeed below - - if (FILE *file; _wfopen_s(&file, screenshot_path.c_str(), L"wb") == 0) - { - const auto write_callback = [](void *context, void *data, int size) { - fwrite(data, 1, size, static_cast(context)); - }; - - switch (_screenshot_format) - { - case 0: - _screenshot_save_success = stbi_write_bmp_to_func(write_callback, file, _width, _height, 4, data.data()) != 0; - break; - case 1: - _screenshot_save_success = stbi_write_png_to_func(write_callback, file, _width, _height, 4, data.data(), 0) != 0; - break; - } - - fclose(file); - } - - _last_screenshot_file = screenshot_path; - _last_screenshot_time = std::chrono::high_resolution_clock::now(); - - if (!_screenshot_save_success) - { - LOG(ERROR) << "Failed to write screenshot to " << screenshot_path << '!'; - } - else if (_screenshot_include_preset) - { - save_preset(least + L".ini"); - } -} - -void reshade::runtime::get_uniform_value(const uniform &variable, uint8_t *data, size_t size) const -{ - assert(data != nullptr); - - size = std::min(size, size_t(variable.size)); - - assert(variable.storage_offset + size <= _uniform_data_storage.size()); - - std::memcpy(data, &_uniform_data_storage[variable.storage_offset], size); -} -void reshade::runtime::get_uniform_value(const uniform &variable, bool *values, size_t count) const -{ - count = std::min(count, size_t(variable.size / 4)); - - assert(values != nullptr); - - const auto data = static_cast(alloca(variable.size)); - get_uniform_value(variable, data, variable.size); - - for (size_t i = 0; i < count; i++) - values[i] = reinterpret_cast(data)[i] != 0; -} -void reshade::runtime::get_uniform_value(const uniform &variable, int32_t *values, size_t count) const -{ - if (!variable.type.is_floating_point() && _renderer_id != 0x9000) - { - get_uniform_value(variable, reinterpret_cast(values), count * sizeof(int32_t)); - return; - } - - count = std::min(count, variable.size / sizeof(float)); - - assert(values != nullptr); - - const auto data = static_cast(alloca(variable.size)); - get_uniform_value(variable, data, variable.size); - - for (size_t i = 0; i < count; i++) - values[i] = static_cast(reinterpret_cast(data)[i]); -} -void reshade::runtime::get_uniform_value(const uniform &variable, uint32_t *values, size_t count) const -{ - get_uniform_value(variable, reinterpret_cast(values), count); -} -void reshade::runtime::get_uniform_value(const uniform &variable, float *values, size_t count) const -{ - if (variable.type.is_floating_point() || _renderer_id == 0x9000) - { - get_uniform_value(variable, reinterpret_cast(values), count * sizeof(float)); - return; - } - - count = std::min(count, variable.size / sizeof(int32_t)); - - assert(values != nullptr); - - const auto data = static_cast(alloca(variable.size)); - get_uniform_value(variable, data, variable.size); - - for (size_t i = 0; i < count; ++i) - if (variable.type.is_signed()) - values[i] = static_cast(reinterpret_cast(data)[i]); - else - values[i] = static_cast(reinterpret_cast(data)[i]); -} -void reshade::runtime::set_uniform_value(uniform &variable, const uint8_t *data, size_t size) -{ - assert(data != nullptr); - - size = std::min(size, size_t(variable.size)); - - assert(variable.storage_offset + size <= _uniform_data_storage.size()); - - std::memcpy(&_uniform_data_storage[variable.storage_offset], data, size); -} -void reshade::runtime::set_uniform_value(uniform &variable, const bool *values, size_t count) -{ - const auto data = static_cast(alloca(count * 4)); - switch (_renderer_id != 0x9000 ? variable.type.base : reshadefx::type::t_float) - { - case reshadefx::type::t_bool: - for (size_t i = 0; i < count; ++i) - reinterpret_cast(data)[i] = values[i] ? -1 : 0; - break; - case reshadefx::type::t_int: - case reshadefx::type::t_uint: - for (size_t i = 0; i < count; ++i) - reinterpret_cast(data)[i] = values[i] ? 1 : 0; - break; - case reshadefx::type::t_float: - for (size_t i = 0; i < count; ++i) - reinterpret_cast(data)[i] = values[i] ? 1.0f : 0.0f; - break; - } - - set_uniform_value(variable, data, count * 4); -} -void reshade::runtime::set_uniform_value(uniform &variable, const int32_t *values, size_t count) -{ - if (!variable.type.is_floating_point() && _renderer_id != 0x9000) - { - set_uniform_value(variable, reinterpret_cast(values), count * sizeof(int)); - return; - } - - const auto data = static_cast(alloca(count * sizeof(float))); - for (size_t i = 0; i < count; ++i) - data[i] = static_cast(values[i]); - - set_uniform_value(variable, reinterpret_cast(data), count * sizeof(float)); -} -void reshade::runtime::set_uniform_value(uniform &variable, const uint32_t *values, size_t count) -{ - if (!variable.type.is_floating_point() && _renderer_id != 0x9000) - { - set_uniform_value(variable, reinterpret_cast(values), count * sizeof(int)); - return; - } - - const auto data = static_cast(alloca(count * sizeof(float))); - for (size_t i = 0; i < count; ++i) - data[i] = static_cast(values[i]); - - set_uniform_value(variable, reinterpret_cast(data), count * sizeof(float)); -} -void reshade::runtime::set_uniform_value(uniform &variable, const float *values, size_t count) -{ - if (variable.type.is_floating_point() || _renderer_id == 0x9000) - { - set_uniform_value(variable, reinterpret_cast(values), count * sizeof(float)); - return; - } - - const auto data = static_cast(alloca(count * sizeof(int32_t))); - for (size_t i = 0; i < count; ++i) - data[i] = static_cast(values[i]); - - set_uniform_value(variable, reinterpret_cast(data), count * sizeof(int32_t)); -} - -void reshade::runtime::reset_uniform_value(uniform &variable) -{ - if (!variable.has_initializer_value) - { - memset(_uniform_data_storage.data() + variable.storage_offset, 0, variable.size); - return; - } - - if (_renderer_id != 0x9000) - { - memcpy(_uniform_data_storage.data() + variable.storage_offset, variable.initializer_value.as_uint, variable.size); - return; - } - - // Force all uniforms to floating-point in D3D9 - for (size_t i = 0; i < variable.size / sizeof(float); i++) - { - switch (variable.type.base) - { - case reshadefx::type::t_int: - reinterpret_cast(_uniform_data_storage.data() + variable.storage_offset)[i] = static_cast(variable.initializer_value.as_int[i]); - break; - case reshadefx::type::t_bool: - case reshadefx::type::t_uint: - reinterpret_cast(_uniform_data_storage.data() + variable.storage_offset)[i] = static_cast(variable.initializer_value.as_uint[i]); - break; - case reshadefx::type::t_float: - reinterpret_cast(_uniform_data_storage.data() + variable.storage_offset)[i] = variable.initializer_value.as_float[i]; - break; - } - } -} - -void reshade::runtime::set_current_preset() -{ - set_current_preset(_current_browse_path); -} -void reshade::runtime::set_current_preset(std::filesystem::path path) -{ - std::error_code ec; - std::filesystem::path reshade_container_path = g_reshade_dll_path.parent_path(); - - enum class path_state { invalid, valid }; - path_state path_state = path_state::invalid; - - if (path.has_filename()) - if (const std::wstring extension(path.extension()); extension == L".ini" || extension == L".txt") - if (!std::filesystem::exists(reshade_container_path / path, ec)) - path_state = path_state::valid; - else if (const reshade::ini_file preset(reshade_container_path / path); preset.has("", "TechniqueSorting")) - path_state = path_state::valid; - - // Select a default preset file if none exists yet or not own - if (path_state == path_state::invalid) - path = "DefaultPreset.ini"; - else if (const std::filesystem::path preset_canonical_path = std::filesystem::weakly_canonical(reshade_container_path / path, ec); - std::equal(reshade_container_path.begin(), reshade_container_path.end(), preset_canonical_path.begin())) - path = preset_canonical_path.lexically_proximate(reshade_container_path); - else if (const std::filesystem::path preset_absolute_path = std::filesystem::absolute(reshade_container_path / path, ec); - std::equal(reshade_container_path.begin(), reshade_container_path.end(), preset_absolute_path.begin())) - path = preset_absolute_path.lexically_proximate(reshade_container_path); - else - path = preset_canonical_path; - - _current_browse_path = path; - _current_preset_path = reshade_container_path / path; -} diff --git a/msvc/source/runtime.hpp b/msvc/source/runtime.hpp deleted file mode 100644 index 74f0ecd..0000000 --- a/msvc/source/runtime.hpp +++ /dev/null @@ -1,368 +0,0 @@ -/** - * Copyright (C) 2014 Patrick Mours. All rights reserved. - * License: https://github.com/crosire/reshade#license - */ - -#pragma once - -#include -#include -#include -#include -#include -#include - -#if RESHADE_GUI -#include "gui_code_editor.hpp" - -struct ImDrawData; -struct ImGuiContext; -#endif - -namespace reshade -{ - class ini_file; // Some forward declarations to keep number of includes small - struct uniform; - struct texture; - struct technique; - struct effect_data; - - /// - /// Platform independent base class for the main ReShade runtime. - /// This class needs to be implemented for all supported rendering APIs. - /// - class runtime abstract - { - public: - /// - /// Return the frame width in pixels. - /// - unsigned int frame_width() const { return _width; } - /// - /// Return the frame height in pixels. - /// - unsigned int frame_height() const { return _height; } - - /// - /// Create a copy of the current frame image in system memory. - /// - /// The 32bpp RGBA buffer to save the screenshot to. - virtual void capture_screenshot(uint8_t *buffer) const = 0; - - /// - /// Save user configuration to disk. - /// - void save_config() const; - - /// - /// Create a new texture with the specified dimensions. - /// - /// The texture description. - virtual bool init_texture(texture &texture) = 0; - /// - /// Upload the image data of a texture. - /// - /// The texture to update. - /// The 32bpp RGBA image data to update the texture with. - virtual void upload_texture(texture &texture, const uint8_t *pixels) = 0; - - /// - /// Get the value of a uniform variable. - /// - /// The variable to retrieve the value from. - /// The buffer to store the value data in. - /// The number of components the value. - void get_uniform_value(const uniform &variable, bool *values, size_t count) const; - void get_uniform_value(const uniform &variable, int32_t *values, size_t count) const; - void get_uniform_value(const uniform &variable, uint32_t *values, size_t count) const; - void get_uniform_value(const uniform &variable, float *values, size_t count) const; - /// - /// Update the value of a uniform variable. - /// - /// The variable to update. - /// The value data to update the variable to. - /// The number of components the value. - void set_uniform_value(uniform &variable, const bool *values, size_t count); - void set_uniform_value(uniform &variable, bool x, bool y = false, bool z = false, bool w = false) { const bool data[4] = { x, y, z, w }; set_uniform_value(variable, data, 4); } - void set_uniform_value(uniform &variable, const int32_t *values, size_t count); - void set_uniform_value(uniform &variable, int32_t x, int32_t y = 0, int32_t z = 0, int32_t w = 0) { const int32_t data[4] = { x, y, z, w }; set_uniform_value(variable, data, 4); } - void set_uniform_value(uniform &variable, const uint32_t *values, size_t count); - void set_uniform_value(uniform &variable, uint32_t x, uint32_t y = 0u, uint32_t z = 0u, uint32_t w = 0u) { const uint32_t data[4] = { x, y, z, w }; set_uniform_value(variable, data, 4); } - void set_uniform_value(uniform &variable, const float *values, size_t count); - void set_uniform_value(uniform &variable, float x, float y = 0.0f, float z = 0.0f, float w = 0.0f) { const float data[4] = { x, y, z, w }; set_uniform_value(variable, data, 4); } - - /// - /// Reset a uniform variable to its initial value. - /// - /// The variable to update. - void reset_uniform_value(uniform &variable); - -#if RESHADE_GUI - /// - /// Register a function to be called when the UI is drawn. - /// - /// Name of the widget. - /// The callback function. - void subscribe_to_ui(std::string label, std::function function) { _menu_callables.push_back({ label, function }); } -#endif - /// - /// Register a function to be called when user configuration is loaded. - /// - /// The callback function. - void subscribe_to_load_config(std::function function); - /// - /// Register a function to be called when user configuration is stored. - /// - /// The callback function. - void subscribe_to_save_config(std::function function); - - protected: - runtime(); - virtual ~runtime(); - - /// - /// Callback function called when the runtime is initialized. - /// - /// Returns if the initialization succeeded. - bool on_init(void *window); - /// - /// Callback function called when the runtime is uninitialized. - /// - void on_reset(); - /// - /// Callback function called every frame. - /// - void on_present(); - - /// - /// Load all effects found in the effect search paths. - /// - virtual void load_effects(); - /// - /// Unload all effects currently loaded. - /// - virtual void unload_effects(); - /// - /// Load image files and update textures with image data. - /// - void load_textures(); - - /// - /// Compile effect from the specified effect module. - /// - /// The effect module to compile. - virtual bool compile_effect(effect_data &effect) = 0; - - /// - /// Apply post-processing effects to the frame. - /// - void update_and_render_effects(); - /// - /// Render all passes in a technique. - /// - /// The technique to render. - virtual void render_technique(technique &technique) = 0; -#if RESHADE_GUI - /// - /// Render command lists obtained from ImGui. - /// - /// The draw data to render. - virtual void render_imgui_draw_data(ImDrawData *draw_data) = 0; -#endif - - bool _is_initialized = false; - bool _has_high_network_activity = false; - unsigned int _width = 0; - unsigned int _height = 0; - unsigned int _window_width = 0; - unsigned int _window_height = 0; - unsigned int _vendor_id = 0; - unsigned int _device_id = 0; - unsigned int _renderer_id = 0; - uint64_t _framecount = 0; - unsigned int _vertices = 0; - unsigned int _drawcalls = 0; - std::vector _textures; - std::vector _uniforms; - std::vector _techniques; - std::vector _uniform_data_storage; - - private: - /// - /// Compare current version against the latest published one. - /// - /// Contains the latest version after this function returned. - /// true if an update is available, false otherwise - static bool check_for_update(unsigned long latest_version[3]); - - /// - /// Compile effect from the specified source file and initialize textures, uniforms and techniques. - /// - /// The path to an effect source code file. - /// The ID of the effect. - void load_effect(const std::filesystem::path &path, size_t &out_id); - /// - /// Unload the specified effect. - /// - /// The ID of the effect. - void unload_effect(size_t id); - - /// - /// Enable a technique so it is rendered. - /// - /// - void enable_technique(technique &technique); - /// - /// Disable a technique so that it is no longer rendered. - /// - /// - void disable_technique(technique &technique); - - /// - /// Load user configuration from disk. - /// - void load_config(); - /// - /// Save user configuration to the specified file on disk. - /// - /// Output configuration path. - void save_config(const std::filesystem::path &path) const; - - /// - /// Load a preset from the specified file and apply it. - /// - /// The preset file to load. - void load_preset(const std::filesystem::path &path); - /// - /// Load the selected preset and apply it. - /// - void load_current_preset(); - /// - /// Save the current value configuration as a preset to the specified file. - /// - /// The preset file to save to. - void save_preset(const std::filesystem::path &path) const; - /// - /// Save the current value configuration to the currently selected preset. - /// - void save_current_preset() const; - - /// - /// Create a copy of the current frame and write it to an image file on disk. - /// - void save_screenshot(); - - void get_uniform_value(const uniform &variable, uint8_t *data, size_t size) const; - void set_uniform_value(uniform &variable, const uint8_t *data, size_t size); - - void set_current_preset(); - void set_current_preset(std::filesystem::path path); - - bool _needs_update = false; - unsigned long _latest_version[3] = {}; - std::shared_ptr _input; - - bool _effects_enabled = true; - bool _ignore_shortcuts = false; - unsigned int _reload_key_data[4]; - unsigned int _effects_key_data[4]; - unsigned int _screenshot_key_data[4]; - int _screenshot_format = 1; - std::filesystem::path _screenshot_path; - std::filesystem::path _configuration_path; - std::filesystem::path _last_screenshot_file; - bool _screenshot_save_success = false; - bool _screenshot_include_preset = false; - - std::filesystem::path _current_preset_path; - - std::vector _global_preprocessor_definitions; - std::vector _preset_preprocessor_definitions; - std::vector _effect_search_paths; - std::vector _texture_search_paths; - - bool _textures_loaded = false; - bool _performance_mode = false; - bool _no_reload_on_init = false; - bool _last_reload_successful = true; - std::mutex _reload_mutex; - size_t _reload_total_effects = 1; - std::vector _reload_compile_queue; - std::atomic _reload_remaining_effects = 0; - std::vector _loaded_effects; - std::vector _worker_threads; - - int _date[4] = {}; - std::chrono::high_resolution_clock::duration _last_frame_duration; - std::chrono::high_resolution_clock::time_point _start_time; - std::chrono::high_resolution_clock::time_point _last_reload_time; - std::chrono::high_resolution_clock::time_point _last_present_time; - std::chrono::high_resolution_clock::time_point _last_screenshot_time; - - std::vector> _save_config_callables; - std::vector> _load_config_callables; - -#if RESHADE_GUI - void init_ui(); - void deinit_ui(); - void build_font_atlas(); - void destroy_font_atlas(); - - void draw_ui(); - - void draw_overlay_menu_home(); - void draw_overlay_menu_settings(); - void draw_overlay_menu_statistics(); - void draw_overlay_menu_log(); - void draw_overlay_menu_about(); - void draw_code_editor(); - void draw_overlay_variable_editor(); - void draw_overlay_technique_editor(); - void draw_preset_explorer(); - - std::vector _texture_previews; - std::vector>> _menu_callables; - std::unique_ptr _imgui_font_atlas; - ImGuiContext *_imgui_context = nullptr; - size_t _focused_effect = std::numeric_limits::max(); - size_t _selected_effect = std::numeric_limits::max(); - size_t _selected_technique = std::numeric_limits::max(); - int _input_processing_mode = 2; - int _style_index = 2; - int _editor_style_index = 0; - int _font_size = 13; - int _editor_font_size = 13; - int _clock_format = 0; - std::filesystem::path _font; - std::filesystem::path _editor_font; - unsigned int _menu_key_data[4]; - bool _show_menu = false; - bool _show_clock = false; - bool _show_fps = false; - bool _show_frametime = false; - bool _show_splash = true; - bool _show_code_editor = false; - bool _show_screenshot_message = true; - bool _no_font_scaling = false; - bool _log_wordwrap = false; - bool _variable_editor_tabs = false; - bool _selected_effect_changed = false; - bool _rebuild_font_atlas = false; - bool _was_preprocessor_popup_edited = false; - bool _statistics_effects_show_enabled = false; - float _fps_col[4] = { 1.0f, 1.0f, 0.784314f, 1.0f }; - float _fps_scale = 1.0f; - float _variable_editor_height = 0.0f; - unsigned int _tutorial_index = 0; - unsigned int _effects_expanded_state = 2; - char _effect_filter_buffer[64] = {}; - std::filesystem::path _file_selection_path; - imgui_code_editor _editor; - - // used by preset explorer - bool _browse_path_is_input_mode = false; - bool _current_preset_is_covered = true; - std::filesystem::path _current_browse_path; -#endif - }; -} diff --git a/msvc/source/runtime_objects.hpp b/msvc/source/runtime_objects.hpp deleted file mode 100644 index 5d42109..0000000 --- a/msvc/source/runtime_objects.hpp +++ /dev/null @@ -1,151 +0,0 @@ -/** - * Copyright (C) 2014 Patrick Mours. All rights reserved. - * License: https://github.com/crosire/reshade#license - */ - -#pragma once - -#include "effect_expression.hpp" -#include "moving_average.hpp" -#include - -namespace reshade -{ - enum class special_uniform - { - none, - frame_time, - frame_count, - random, - ping_pong, - date, - timer, - key, - mouse_point, - mouse_delta, - mouse_button, - }; - - enum class texture_reference - { - none, - back_buffer, - depth_buffer - }; - - class base_object abstract - { - public: - virtual ~base_object() {} - - template - T *as() { return dynamic_cast(this); } - template - const T *as() const { return dynamic_cast(this); } - }; - - struct effect_data - { - size_t index = std::numeric_limits::max(); - unsigned int rendering = 0; - bool compile_sucess = false; - std::string errors; - std::string preamble; - reshadefx::module module; - std::filesystem::path source_file; - size_t storage_offset = 0, storage_size = 0; - }; - - struct texture final : reshadefx::texture_info - { - texture() {} - texture(const reshadefx::texture_info &init) : texture_info(init) { } - - int annotation_as_int(const char *ann_name, size_t i = 0) const - { - const auto it = annotations.find(ann_name); - if (it == annotations.end()) return 0; - return it->second.first.is_integral() ? it->second.second.as_int[i] : static_cast(it->second.second.as_float[i]); - } - float annotation_as_float(const char *ann_name, size_t i = 0) const - { - const auto it = annotations.find(ann_name); - if (it == annotations.end()) return 0.0f; - return it->second.first.is_floating_point() ? it->second.second.as_float[i] : static_cast(it->second.second.as_int[i]); - } - std::string_view annotation_as_string(const char *ann_name) const - { - const auto it = annotations.find(ann_name); - if (it == annotations.end()) return std::string_view(); - return it->second.second.string_data; - } - - size_t effect_index = std::numeric_limits::max(); - texture_reference impl_reference = texture_reference::none; - std::unique_ptr impl; - bool shared = false; - }; - - struct uniform final : reshadefx::uniform_info - { - uniform(const reshadefx::uniform_info &init) : uniform_info(init) {} - - int annotation_as_int(const char *ann_name, size_t i = 0) const - { - const auto it = annotations.find(ann_name); - if (it == annotations.end()) return 0; - return it->second.first.is_integral() ? it->second.second.as_int[i] : static_cast(it->second.second.as_float[i]); - } - float annotation_as_float(const char *ann_name, size_t i = 0) const - { - const auto it = annotations.find(ann_name); - if (it == annotations.end()) return 0.0f; - return it->second.first.is_floating_point() ? it->second.second.as_float[i] : static_cast(it->second.second.as_int[i]); - } - std::string_view annotation_as_string(const char *ann_name) const - { - const auto it = annotations.find(ann_name); - if (it == annotations.end()) return std::string_view(); - return it->second.second.string_data; - } - - size_t effect_index = std::numeric_limits::max(); - size_t storage_offset = 0; - special_uniform special = special_uniform::none; - }; - - struct technique final : reshadefx::technique_info - { - technique(const reshadefx::technique_info &init) : technique_info(init) {} - - int annotation_as_int(const char *ann_name, size_t i = 0) const - { - const auto it = annotations.find(ann_name); - if (it == annotations.end()) return 0; - return it->second.first.is_integral() ? it->second.second.as_int[i] : static_cast(it->second.second.as_float[i]); - } - float annotation_as_float(const char *ann_name, size_t i = 0) const - { - const auto it = annotations.find(ann_name); - if (it == annotations.end()) return 0.0f; - return it->second.first.is_floating_point() ? it->second.second.as_float[i] : static_cast(it->second.second.as_int[i]); - } - std::string_view annotation_as_string(const char *ann_name) const - { - const auto it = annotations.find(ann_name); - if (it == annotations.end()) return std::string_view(); - return it->second.second.string_data; - } - - size_t effect_index = std::numeric_limits::max(); - std::vector> passes_data; - bool hidden = false; - bool enabled = false; - int32_t timeout = 0; - int64_t timeleft = 0; - uint32_t toggle_key_data[4]; - moving_average average_cpu_duration; - moving_average average_gpu_duration; - std::unique_ptr impl; - }; -} diff --git a/msvc/source/update_check.cpp b/msvc/source/update_check.cpp deleted file mode 100644 index 323f078..0000000 --- a/msvc/source/update_check.cpp +++ /dev/null @@ -1,59 +0,0 @@ -#include "version.h" -#include "runtime.hpp" -#include -#include - -struct scoped_handle -{ - scoped_handle(HINTERNET handle) : handle(handle) {} - ~scoped_handle() { InternetCloseHandle(handle); } - - inline operator HINTERNET() const { return handle; } - -private: - HINTERNET handle; -}; - -bool reshade::runtime::check_for_update(unsigned long latest_version[3]) -{ - memset(latest_version, 0, 3 * sizeof(unsigned long)); - -#if !defined(_DEBUG) - const scoped_handle handle = InternetOpen(L"reshade", INTERNET_OPEN_TYPE_PRECONFIG, nullptr, nullptr, 0); - - if (handle == nullptr) - return false; - - constexpr auto api_url = TEXT("https://api.github.com/repos/crosire/reshade/tags"); - - const scoped_handle request = InternetOpenUrl(handle, api_url, nullptr, 0, INTERNET_FLAG_RELOAD | INTERNET_FLAG_PRAGMA_NOCACHE | INTERNET_FLAG_NO_CACHE_WRITE, 0); - - if (request == nullptr) - return false; - - CHAR response_data[32]; - DWORD response_length = 0; - - if (InternetReadFile(request, response_data, sizeof(response_data) - 1, &response_length) && response_length > 0) - { - response_data[response_length] = '\0'; - - const char *version_major_offset = std::strchr(response_data, 'v'); - if (version_major_offset == nullptr) return false; else version_major_offset++; - const char *version_minor_offset = std::strchr(version_major_offset, '.'); - if (version_minor_offset == nullptr) return false; else version_minor_offset++; - const char *version_revision_offset = std::strchr(version_minor_offset, '.'); - if (version_revision_offset == nullptr) return false; else version_revision_offset++; - - latest_version[0] = std::strtoul(version_major_offset, nullptr, 10); - latest_version[1] = std::strtoul(version_minor_offset, nullptr, 10); - latest_version[2] = std::strtoul(version_revision_offset, nullptr, 10); - - return (latest_version[0] > VERSION_MAJOR) || - (latest_version[0] == VERSION_MAJOR && latest_version[1] > VERSION_MINOR) || - (latest_version[0] == VERSION_MAJOR && latest_version[1] == VERSION_MINOR && latest_version[2] > VERSION_REVISION); - } -#endif - - return false; -} diff --git a/msvc/source/windows/user32.cpp b/msvc/source/windows/user32.cpp deleted file mode 100644 index 61a4c34..0000000 --- a/msvc/source/windows/user32.cpp +++ /dev/null @@ -1,91 +0,0 @@ -/** - * Copyright (C) 2014 Patrick Mours. All rights reserved. - * License: https://github.com/crosire/reshade#license - */ - -#include "log.hpp" -#include "hook_manager.hpp" -#include "input.hpp" -#include -#include - -HOOK_EXPORT ATOM WINAPI HookRegisterClassA(const WNDCLASSA *lpWndClass) -{ - assert(lpWndClass != nullptr); - - auto wndclass = *lpWndClass; - - if (wndclass.hInstance == GetModuleHandle(nullptr)) - { - LOG(INFO) << "Redirecting RegisterClassA" << '(' << lpWndClass << " { " << wndclass.lpszClassName << " }" << ')' << " ..."; - - if ((wndclass.style & CS_OWNDC) == 0) - { - LOG(INFO) << "> Adding 'CS_OWNDC' window class style flag to '" << wndclass.lpszClassName << "'."; - - wndclass.style |= CS_OWNDC; - } - } - - return reshade::hooks::call(HookRegisterClassA)(&wndclass); -} -HOOK_EXPORT ATOM WINAPI HookRegisterClassW(const WNDCLASSW *lpWndClass) -{ - assert(lpWndClass != nullptr); - - auto wndclass = *lpWndClass; - - if (wndclass.hInstance == GetModuleHandle(nullptr)) - { - LOG(INFO) << "Redirecting RegisterClassW" << '(' << lpWndClass << " { " << wndclass.lpszClassName << " }" << ')' << " ..."; - - if ((wndclass.style & CS_OWNDC) == 0) - { - LOG(INFO) << "> Adding 'CS_OWNDC' window class style flag to '" << wndclass.lpszClassName << "'."; - - wndclass.style |= CS_OWNDC; - } - } - - return reshade::hooks::call(HookRegisterClassW)(&wndclass); -} -HOOK_EXPORT ATOM WINAPI HookRegisterClassExA(const WNDCLASSEXA *lpWndClassEx) -{ - assert(lpWndClassEx != nullptr); - - auto wndclass = *lpWndClassEx; - - if (wndclass.hInstance == GetModuleHandle(nullptr)) - { - LOG(INFO) << "Redirecting RegisterClassExA" << '(' << lpWndClassEx << " { " << wndclass.lpszClassName << " }" << ')' << " ..."; - - if ((wndclass.style & CS_OWNDC) == 0) - { - LOG(INFO) << "> Adding 'CS_OWNDC' window class style flag to '" << wndclass.lpszClassName << "'."; - - wndclass.style |= CS_OWNDC; - } - } - - return reshade::hooks::call(HookRegisterClassExA)(&wndclass); -} -HOOK_EXPORT ATOM WINAPI HookRegisterClassExW(const WNDCLASSEXW *lpWndClassEx) -{ - assert(lpWndClassEx != nullptr); - - auto wndclass = *lpWndClassEx; - - if (wndclass.hInstance == GetModuleHandle(nullptr)) - { - LOG(INFO) << "Redirecting RegisterClassExW" << '(' << lpWndClassEx << " { " << wndclass.lpszClassName << " }" << ')' << " ..."; - - if ((wndclass.style & CS_OWNDC) == 0) - { - LOG(INFO) << "> Adding 'CS_OWNDC' window class style flag to '" << wndclass.lpszClassName << "'."; - - wndclass.style |= CS_OWNDC; - } - } - - return reshade::hooks::call(HookRegisterClassExW)(&wndclass); -} diff --git a/msvc/source/windows/ws2_32.cpp b/msvc/source/windows/ws2_32.cpp deleted file mode 100644 index 9d58029..0000000 --- a/msvc/source/windows/ws2_32.cpp +++ /dev/null @@ -1,88 +0,0 @@ -/** - * Copyright (C) 2014 Patrick Mours. All rights reserved. - * License: https://github.com/crosire/reshade#license - */ - -#include "hook_manager.hpp" -#include -#include - -volatile long g_network_traffic = 0; - -HOOK_EXPORT int WSAAPI HookWSASend(SOCKET s, LPWSABUF lpBuffers, DWORD dwBufferCount, LPDWORD lpNumberOfBytesSent, DWORD dwFlags, LPWSAOVERLAPPED lpOverlapped, LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine) -{ - for (DWORD i = 0; i < dwBufferCount; ++i) - InterlockedAdd(&g_network_traffic, lpBuffers[i].len); - - static const auto trampoline = reshade::hooks::call(HookWSASend); - return trampoline(s, lpBuffers, dwBufferCount, lpNumberOfBytesSent, dwFlags, lpOverlapped, lpCompletionRoutine); -} -HOOK_EXPORT int WSAAPI HookWSASendTo(SOCKET s, LPWSABUF lpBuffers, DWORD dwBufferCount, LPDWORD lpNumberOfBytesSent, DWORD dwFlags, const struct sockaddr *lpTo, int iToLen, LPWSAOVERLAPPED lpOverlapped, LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine) -{ - for (DWORD i = 0; i < dwBufferCount; ++i) - InterlockedAdd(&g_network_traffic, lpBuffers[i].len); - - static const auto trampoline = reshade::hooks::call(HookWSASendTo); - return trampoline(s, lpBuffers, dwBufferCount, lpNumberOfBytesSent, dwFlags, lpTo, iToLen, lpOverlapped, lpCompletionRoutine); -} -HOOK_EXPORT int WSAAPI HookWSARecv(SOCKET s, LPWSABUF lpBuffers, DWORD dwBufferCount, LPDWORD lpNumberOfBytesRecvd, LPDWORD lpFlags, LPWSAOVERLAPPED lpOverlapped, LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine) -{ - static const auto trampoline = reshade::hooks::call(HookWSARecv); - const auto status = trampoline(s, lpBuffers, dwBufferCount, lpNumberOfBytesRecvd, lpFlags, lpOverlapped, lpCompletionRoutine); - - if (status == 0 && lpNumberOfBytesRecvd != nullptr) - InterlockedAdd(&g_network_traffic, *lpNumberOfBytesRecvd); - - return status; -} -HOOK_EXPORT int WSAAPI HookWSARecvFrom(SOCKET s, LPWSABUF lpBuffers, DWORD dwBufferCount, LPDWORD lpNumberOfBytesRecvd, LPDWORD lpFlags, struct sockaddr *lpFrom, LPINT lpFromlen, LPWSAOVERLAPPED lpOverlapped, LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine) -{ - static const auto trampoline = reshade::hooks::call(HookWSARecvFrom); - const auto status = trampoline(s, lpBuffers, dwBufferCount, lpNumberOfBytesRecvd, lpFlags, lpFrom, lpFromlen, lpOverlapped, lpCompletionRoutine); - - if (status == 0 && lpNumberOfBytesRecvd != nullptr) - InterlockedAdd(&g_network_traffic, *lpNumberOfBytesRecvd); - - return status; -} - -HOOK_EXPORT int WSAAPI HookSend(SOCKET s, const char *buf, int len, int flags) -{ - static const auto trampoline = reshade::hooks::call(HookSend); - const auto num_bytes_send = trampoline(s, buf, len, flags); - - if (num_bytes_send != SOCKET_ERROR) - InterlockedAdd(&g_network_traffic, num_bytes_send); - - return num_bytes_send; -} -HOOK_EXPORT int WSAAPI HookSendTo(SOCKET s, const char *buf, int len, int flags, const struct sockaddr *to, int tolen) -{ - static const auto trampoline = reshade::hooks::call(HookSendTo); - const auto num_bytes_send = trampoline(s, buf, len, flags, to, tolen); - - if (num_bytes_send != SOCKET_ERROR) - InterlockedAdd(&g_network_traffic, num_bytes_send); - - return num_bytes_send; -} -HOOK_EXPORT int WSAAPI HookRecv(SOCKET s, char *buf, int len, int flags) -{ - static const auto trampoline = reshade::hooks::call(HookRecv); - const auto num_bytes_recieved = trampoline(s, buf, len, flags); - - if (num_bytes_recieved != SOCKET_ERROR) - InterlockedAdd(&g_network_traffic, num_bytes_recieved); - - return num_bytes_recieved; -} -HOOK_EXPORT int WSAAPI HookRecvFrom(SOCKET s, char *buf, int len, int flags, struct sockaddr *from, int *fromlen) -{ - static const auto trampoline = reshade::hooks::call(HookRecvFrom); - const auto num_bytes_recieved = trampoline(s, buf, len, flags, from, fromlen); - - if (num_bytes_recieved != SOCKET_ERROR) - InterlockedAdd(&g_network_traffic, num_bytes_recieved); - - return num_bytes_recieved; -} diff --git a/msvc/tools/7za.exe b/msvc/tools/7za.exe deleted file mode 100644 index d3055c8f4effc5a84d3bb2b48e85d44dbf8d8b71..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1144320 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

  • Eft#FE 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

    HrpF!#ypDah8 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 diff --git a/msvc/tools/fxc.cpp b/msvc/tools/fxc.cpp deleted file mode 100644 index 4136971..0000000 --- a/msvc/tools/fxc.cpp +++ /dev/null @@ -1,179 +0,0 @@ -/** - * Copyright (C) 2014 Patrick Mours. All rights reserved. - * License: https://github.com/crosire/reshade#license - */ - -#include "effect_parser.hpp" -#include "effect_codegen.hpp" -#include "effect_preprocessor.hpp" -#include "version.h" -#include -#include -#include - -void print_usage(const char *path) -{ - printf(R"(usage: %s [options] - -Options: - -h, --help Print this help. - --version Print ReShade version. - - -D = Define a preprocessor macro. - -I Add directory to include search path. - -P Pre-process to file. If is "-", then result is written to standard output instead. - - -Fo Output SPIR-V binary to the given file. - -Fe Output warnings and errors to the given file. - - --glsl Print GLSL code for the previously specified entry point. - --hlsl Print HLSL code for the previously specified entry point. - --shader-model HLSL shader model version. Can be 30, 40, 41, 50, ... - - -Zi Enable debug information. - )", path); -} - -int main(int argc, char *argv[]) -{ - const char *filename = nullptr; - const char *preprocess = nullptr; - const char *errorfile = nullptr; - const char *objectfile = nullptr; - bool print_glsl = false; - bool print_hlsl = false; - bool debug_info = false; - unsigned int shader_model = 50; - - reshadefx::parser parser; - reshadefx::preprocessor pp; - pp.add_macro_definition("__RESHADE__", std::to_string(VERSION_MAJOR * 10000 + VERSION_MINOR * 100 + VERSION_REVISION)); - pp.add_macro_definition("__RESHADE_PERFORMANCE_MODE__", "0"); - pp.add_macro_definition("BUFFER_WIDTH", "800"); - pp.add_macro_definition("BUFFER_HEIGHT", "600"); - pp.add_macro_definition("BUFFER_RCP_WIDTH", "(1.0 / BUFFER_WIDTH)"); - pp.add_macro_definition("BUFFER_RCP_HEIGHT", "(1.0 / BUFFER_HEIGHT)"); - - // Parse command-line arguments - for (int i = 1; i < argc; ++i) - { - if (const char *arg = argv[i]; arg[0] == '-') - { - if (0 == strcmp(arg, "-h") || 0 == strcmp(arg, "--help")) - { - print_usage(argv[0]); - return 0; - } - else if (0 == strcmp(arg, "--version")) - { - printf("%s\n", VERSION_STRING_PRODUCT); - return 0; - } - else if (0 == strcmp(arg, "-D")) - { - char *macro = argv[++i]; - char *value = strchr(macro, '='); - if (value) *value++ = '\0'; - pp.add_macro_definition(macro, value ? value : "1"); - } - else if (0 == strcmp(arg, "-I")) - { - pp.add_include_path(argv[++i]); - } - else if (0 == strcmp(arg, "-P")) - { - preprocess = argv[++i]; - } - else if (0 == strcmp(arg, "-Fe")) - { - errorfile = argv[++i]; - } - else if (0 == strcmp(arg, "-Fo")) - { - objectfile = argv[++i]; - } - else if (0 == strcmp(arg, "-Zi")) - { - debug_info = true; - } - else if (0 == strcmp(arg, "--glsl")) - { - print_glsl = true; - } - else if (0 == strcmp(arg, "--hlsl")) - { - print_hlsl = true; - } - else if (0 == strcmp(arg, "--shader-model")) - { - shader_model = std::strtol(argv[++i], nullptr, 10); - } - } - else - { - if (filename != nullptr) - { - std::cout << "error: More than one input file specified" << std::endl; - return 1; - } - - filename = arg; - } - } - - if (filename == nullptr) - { - print_usage(argv[0]); - return 1; - } - - if (!pp.append_file(filename)) - { - if (errorfile == nullptr) - std::cout << pp.errors() << std::endl; - else - std::ofstream(errorfile) << pp.errors(); - return 1; - } - - if (preprocess != nullptr) - { - if (strcmp(preprocess, "-") == 0) - std::cout << pp.output() << std::endl; - else - std::ofstream(preprocess) << pp.output(); - return 0; - } - - std::unique_ptr backend; - if (print_glsl) - backend.reset(reshadefx::create_codegen_glsl(debug_info, false)); - else if (print_hlsl) - backend.reset(reshadefx::create_codegen_hlsl(shader_model, debug_info, false)); - else - backend.reset(reshadefx::create_codegen_spirv(debug_info, false)); - - if (!parser.parse(pp.output(), backend.get())) - { - if (errorfile == nullptr) - std::cout << pp.errors() << parser.errors() << std::endl; - else - std::ofstream(errorfile) << pp.errors() << parser.errors(); - return 1; - } - - reshadefx::module module; - backend->write_result(module); - - if (print_glsl || print_hlsl) - { - std::cout << module.hlsl << std::endl; - } - else if (objectfile != nullptr) - { - std::ofstream(objectfile, std::ios::binary).write( - reinterpret_cast(module.spirv.data()), module.spirv.size() * sizeof(uint32_t)); - } - - return 0; -} diff --git a/msvc/tools/verbuild.exe b/msvc/tools/verbuild.exe deleted file mode 100644 index e9a6d8aa2a91c5570dfc5e49c926e624c70d2a2d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 34816 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 Date: Mon, 10 Jun 2019 17:28:36 -0600 Subject: [PATCH 41/55] Revert "Update .gitignore" This reverts commit 19c87a75fd5f77e5966ed1c6e92dda19ca1cd361. --- .gitignore | 38 -------------------------------------- 1 file changed, 38 deletions(-) diff --git a/.gitignore b/.gitignore index a18d6e3..a4b8ec5 100644 --- a/.gitignore +++ b/.gitignore @@ -6,41 +6,3 @@ src/core/version.h private/ - -# Build results -[Bb]in/ -[Oo]bj/ -[Ii]ntermediate/ -[Bb]uild/ -[Dd]ebug/ -[Rr]elease/ -*.obj -*.cso - -# Visual Studio cache files -.vs/ -ipch/ -packages/ -*.aps -*.VC.db -*.VC.opendb -*.sdf -*.opensdf -*.suo -*.vcxproj.user - -# Visual Studio performance profiling -*.vsp -*.psess - -# Code signing -*.pfx -*.pvk - -# Temporary OS files -.DS_Store -Thumbs.db - -# Versioning -/res/Version.h - From bbe0240280e12163ae476cd37fda75ce556c4049 Mon Sep 17 00:00:00 2001 From: Aubrey Buchanan Date: Tue, 11 Jun 2019 14:09:02 -0600 Subject: [PATCH 42/55] Fragile: including CUDA in gl_screen.c I had to make edits to gl/GL.H in the Windows kit for this to work. Skeptical. --- msvc/plugin-common.vcxproj | 4 ++-- src/plugin/common/gl_core_3_3/gl_core_3_3.h | 4 +--- src/plugin/common/gl_screen.c | 3 +++ 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/msvc/plugin-common.vcxproj b/msvc/plugin-common.vcxproj index 6e14819..46da322 100644 --- a/msvc/plugin-common.vcxproj +++ b/msvc/plugin-common.vcxproj @@ -34,7 +34,7 @@ {13F7DDD7-2282-408A-AEF9-72266E0236DC} Win32Proj - 10.0.16299.0 + 10.0.17763.0 plugin-common @@ -156,7 +156,7 @@ true StreamingSIMDExtensions2 true - $(SolutionDir)..\src;%(AdditionalIncludeDirectories) + $(SolutionDir)..\src;C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v10.1\include;%(AdditionalIncludeDirectories) Windows diff --git a/src/plugin/common/gl_core_3_3/gl_core_3_3.h b/src/plugin/common/gl_core_3_3/gl_core_3_3.h index ae1e93a..ed3f7e6 100644 --- a/src/plugin/common/gl_core_3_3/gl_core_3_3.h +++ b/src/plugin/common/gl_core_3_3/gl_core_3_3.h @@ -4,9 +4,7 @@ #if defined(__glew_h__) || defined(__GLEW_H__) #error Attempt to include auto-generated header after including glew.h #endif -#if defined(__gl_h_) || defined(__GL_H__) -#error Attempt to include auto-generated header after including gl.h -#endif + #if defined(__glext_h_) || defined(__GLEXT_H_) #error Attempt to include auto-generated header after including glext.h #endif diff --git a/src/plugin/common/gl_screen.c b/src/plugin/common/gl_screen.c index cb7896a..0cb4ca3 100644 --- a/src/plugin/common/gl_screen.c +++ b/src/plugin/common/gl_screen.c @@ -4,6 +4,9 @@ #include #include +#include +#include + #ifdef GLES #include #define SHADER_HEADER "#version 300 es\nprecision lowp float;\n" From dd372779944683301c889d5ed8e27c9dc04f9f48 Mon Sep 17 00:00:00 2001 From: Aubrey Buchanan Date: Wed, 12 Jun 2019 15:34:17 -0600 Subject: [PATCH 43/55] Tiny bit more of CUDA setup --- msvc/plugin-common.vcxproj | 2 +- src/plugin/common/gl_screen.c | 13 +++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/msvc/plugin-common.vcxproj b/msvc/plugin-common.vcxproj index 46da322..cf4ad2e 100644 --- a/msvc/plugin-common.vcxproj +++ b/msvc/plugin-common.vcxproj @@ -156,7 +156,7 @@ true StreamingSIMDExtensions2 true - $(SolutionDir)..\src;C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v10.1\include;%(AdditionalIncludeDirectories) + $(SolutionDir)..\src;C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v10.1\include;C:\ProgramData\NVIDIA Corporation\CUDA Samples\v10.1\common\inc;%(AdditionalIncludeDirectories) Windows diff --git a/src/plugin/common/gl_screen.c b/src/plugin/common/gl_screen.c index 0cb4ca3..1156468 100644 --- a/src/plugin/common/gl_screen.c +++ b/src/plugin/common/gl_screen.c @@ -7,6 +7,9 @@ #include #include +#include + + #ifdef GLES #include #define SHADER_HEADER "#version 300 es\nprecision lowp float;\n" @@ -32,6 +35,16 @@ static int32_t tex_height; static int32_t tex_display_height; +/** + * CUDA devices corresponding to the current OpenGL context + */ +enum cudaGLDeviceList +{ + cudaGLDeviceListAll = 1, /**< The CUDA devices for all GPUs used by the current OpenGL context */ + cudaGLDeviceListCurrentFrame = 2, /**< The CUDA devices for the GPUs used by the current OpenGL context in its currently rendering frame */ + cudaGLDeviceListNextFrame = 3 /**< The CUDA devices for the GPUs to be used by the current OpenGL context in the next frame */ +}; + #ifdef _DEBUG static void gl_check_errors(void) { From 00b778e3750e2b4cd41dc63e509f08938f9a38e0 Mon Sep 17 00:00:00 2001 From: Aubrey Buchanan Date: Wed, 12 Jun 2019 18:49:41 -0600 Subject: [PATCH 44/55] Revert "Tiny bit more of CUDA setup" This reverts commit dd372779944683301c889d5ed8e27c9dc04f9f48. --- msvc/plugin-common.vcxproj | 2 +- src/plugin/common/gl_screen.c | 13 ------------- 2 files changed, 1 insertion(+), 14 deletions(-) diff --git a/msvc/plugin-common.vcxproj b/msvc/plugin-common.vcxproj index cf4ad2e..46da322 100644 --- a/msvc/plugin-common.vcxproj +++ b/msvc/plugin-common.vcxproj @@ -156,7 +156,7 @@ true StreamingSIMDExtensions2 true - $(SolutionDir)..\src;C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v10.1\include;C:\ProgramData\NVIDIA Corporation\CUDA Samples\v10.1\common\inc;%(AdditionalIncludeDirectories) + $(SolutionDir)..\src;C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v10.1\include;%(AdditionalIncludeDirectories) Windows diff --git a/src/plugin/common/gl_screen.c b/src/plugin/common/gl_screen.c index 1156468..0cb4ca3 100644 --- a/src/plugin/common/gl_screen.c +++ b/src/plugin/common/gl_screen.c @@ -7,9 +7,6 @@ #include #include -#include - - #ifdef GLES #include #define SHADER_HEADER "#version 300 es\nprecision lowp float;\n" @@ -35,16 +32,6 @@ static int32_t tex_height; static int32_t tex_display_height; -/** - * CUDA devices corresponding to the current OpenGL context - */ -enum cudaGLDeviceList -{ - cudaGLDeviceListAll = 1, /**< The CUDA devices for all GPUs used by the current OpenGL context */ - cudaGLDeviceListCurrentFrame = 2, /**< The CUDA devices for the GPUs used by the current OpenGL context in its currently rendering frame */ - cudaGLDeviceListNextFrame = 3 /**< The CUDA devices for the GPUs to be used by the current OpenGL context in the next frame */ -}; - #ifdef _DEBUG static void gl_check_errors(void) { From 29a56ebf41281455e84de88b671a5cd00a2440a2 Mon Sep 17 00:00:00 2001 From: Aubrey Buchanan Date: Wed, 12 Jun 2019 18:49:43 -0600 Subject: [PATCH 45/55] Revert "Fragile: including CUDA in gl_screen.c" This reverts commit bbe0240280e12163ae476cd37fda75ce556c4049. --- msvc/plugin-common.vcxproj | 4 ++-- src/plugin/common/gl_core_3_3/gl_core_3_3.h | 4 +++- src/plugin/common/gl_screen.c | 3 --- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/msvc/plugin-common.vcxproj b/msvc/plugin-common.vcxproj index 46da322..6e14819 100644 --- a/msvc/plugin-common.vcxproj +++ b/msvc/plugin-common.vcxproj @@ -34,7 +34,7 @@ {13F7DDD7-2282-408A-AEF9-72266E0236DC} Win32Proj - 10.0.17763.0 + 10.0.16299.0 plugin-common @@ -156,7 +156,7 @@ true StreamingSIMDExtensions2 true - $(SolutionDir)..\src;C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v10.1\include;%(AdditionalIncludeDirectories) + $(SolutionDir)..\src;%(AdditionalIncludeDirectories) Windows diff --git a/src/plugin/common/gl_core_3_3/gl_core_3_3.h b/src/plugin/common/gl_core_3_3/gl_core_3_3.h index ed3f7e6..ae1e93a 100644 --- a/src/plugin/common/gl_core_3_3/gl_core_3_3.h +++ b/src/plugin/common/gl_core_3_3/gl_core_3_3.h @@ -4,7 +4,9 @@ #if defined(__glew_h__) || defined(__GLEW_H__) #error Attempt to include auto-generated header after including glew.h #endif - +#if defined(__gl_h_) || defined(__GL_H__) +#error Attempt to include auto-generated header after including gl.h +#endif #if defined(__glext_h_) || defined(__GLEXT_H_) #error Attempt to include auto-generated header after including glext.h #endif diff --git a/src/plugin/common/gl_screen.c b/src/plugin/common/gl_screen.c index 0cb4ca3..cb7896a 100644 --- a/src/plugin/common/gl_screen.c +++ b/src/plugin/common/gl_screen.c @@ -4,9 +4,6 @@ #include #include -#include -#include - #ifdef GLES #include #define SHADER_HEADER "#version 300 es\nprecision lowp float;\n" From a7de73c921f0cb2ae430a47a7e7878e214fd3572 Mon Sep 17 00:00:00 2001 From: Aubrey Buchanan Date: Wed, 12 Jun 2019 19:02:14 -0600 Subject: [PATCH 46/55] setup plugin-cuda-common --- .gitignore | 1 + msvc/angrylion-plus-x.sln | 11 ++ msvc/plugin-cuda-common/kernel.cu | 121 ++++++++++++++++++ .../plugin-cuda-common.vcxproj | 103 +++++++++++++++ .../plugin-cuda-common.vcxproj.filters | 22 ++++ 5 files changed, 258 insertions(+) create mode 100644 msvc/plugin-cuda-common/kernel.cu create mode 100644 msvc/plugin-cuda-common/plugin-cuda-common.vcxproj create mode 100644 msvc/plugin-cuda-common/plugin-cuda-common.vcxproj.filters diff --git a/.gitignore b/.gitignore index a4b8ec5..d775efa 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ src/core/version.h private/ +*.nvuser diff --git a/msvc/angrylion-plus-x.sln b/msvc/angrylion-plus-x.sln index 4e9d4fc..0085089 100644 --- a/msvc/angrylion-plus-x.sln +++ b/msvc/angrylion-plus-x.sln @@ -21,6 +21,11 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "plugin-zilmar", "plugin-zil {13F7DDD7-2282-408A-AEF9-72266E0236DC} = {13F7DDD7-2282-408A-AEF9-72266E0236DC} EndProjectSection EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "plugin-cuda-common", "plugin-cuda-common\plugin-cuda-common.vcxproj", "{C4A8ED89-6F2F-40C2-8EDA-4577EB49DE07}" + ProjectSection(ProjectDependencies) = postProject + {D86C6E84-3371-4B20-8620-A703FF3F2CC5} = {D86C6E84-3371-4B20-8620-A703FF3F2CC5} + EndProjectSection +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|x64 = Debug|x64 @@ -61,6 +66,12 @@ Global {722AD25D-C281-48FB-9A58-7699E50F8E6D}.Release|x64.Build.0 = Release|x64 {722AD25D-C281-48FB-9A58-7699E50F8E6D}.Release|x86.ActiveCfg = Release|Win32 {722AD25D-C281-48FB-9A58-7699E50F8E6D}.Release|x86.Build.0 = Release|Win32 + {C4A8ED89-6F2F-40C2-8EDA-4577EB49DE07}.Debug|x64.ActiveCfg = Debug|x64 + {C4A8ED89-6F2F-40C2-8EDA-4577EB49DE07}.Debug|x64.Build.0 = Debug|x64 + {C4A8ED89-6F2F-40C2-8EDA-4577EB49DE07}.Debug|x86.ActiveCfg = Debug|x64 + {C4A8ED89-6F2F-40C2-8EDA-4577EB49DE07}.Release|x64.ActiveCfg = Release|x64 + {C4A8ED89-6F2F-40C2-8EDA-4577EB49DE07}.Release|x64.Build.0 = Release|x64 + {C4A8ED89-6F2F-40C2-8EDA-4577EB49DE07}.Release|x86.ActiveCfg = Release|x64 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/msvc/plugin-cuda-common/kernel.cu b/msvc/plugin-cuda-common/kernel.cu new file mode 100644 index 0000000..90d489d --- /dev/null +++ b/msvc/plugin-cuda-common/kernel.cu @@ -0,0 +1,121 @@ + +#include "cuda_runtime.h" +#include "device_launch_parameters.h" + +#include + +cudaError_t addWithCuda(int *c, const int *a, const int *b, unsigned int size); + +__global__ void addKernel(int *c, const int *a, const int *b) +{ + int i = threadIdx.x; + c[i] = a[i] + b[i]; +} + +int main() +{ + const int arraySize = 5; + const int a[arraySize] = { 1, 2, 3, 4, 5 }; + const int b[arraySize] = { 10, 20, 30, 40, 50 }; + int c[arraySize] = { 0 }; + + // Add vectors in parallel. + cudaError_t cudaStatus = addWithCuda(c, a, b, arraySize); + if (cudaStatus != cudaSuccess) { + fprintf(stderr, "addWithCuda failed!"); + return 1; + } + + printf("{1,2,3,4,5} + {10,20,30,40,50} = {%d,%d,%d,%d,%d}\n", + c[0], c[1], c[2], c[3], c[4]); + + // cudaDeviceReset must be called before exiting in order for profiling and + // tracing tools such as Nsight and Visual Profiler to show complete traces. + cudaStatus = cudaDeviceReset(); + if (cudaStatus != cudaSuccess) { + fprintf(stderr, "cudaDeviceReset failed!"); + return 1; + } + + return 0; +} + +// Helper function for using CUDA to add vectors in parallel. +cudaError_t addWithCuda(int *c, const int *a, const int *b, unsigned int size) +{ + int *dev_a = 0; + int *dev_b = 0; + int *dev_c = 0; + cudaError_t cudaStatus; + + // Choose which GPU to run on, change this on a multi-GPU system. + cudaStatus = cudaSetDevice(0); + if (cudaStatus != cudaSuccess) { + fprintf(stderr, "cudaSetDevice failed! Do you have a CUDA-capable GPU installed?"); + goto Error; + } + + // Allocate GPU buffers for three vectors (two input, one output) . + cudaStatus = cudaMalloc((void**)&dev_c, size * sizeof(int)); + if (cudaStatus != cudaSuccess) { + fprintf(stderr, "cudaMalloc failed!"); + goto Error; + } + + cudaStatus = cudaMalloc((void**)&dev_a, size * sizeof(int)); + if (cudaStatus != cudaSuccess) { + fprintf(stderr, "cudaMalloc failed!"); + goto Error; + } + + cudaStatus = cudaMalloc((void**)&dev_b, size * sizeof(int)); + if (cudaStatus != cudaSuccess) { + fprintf(stderr, "cudaMalloc failed!"); + goto Error; + } + + // Copy input vectors from host memory to GPU buffers. + cudaStatus = cudaMemcpy(dev_a, a, size * sizeof(int), cudaMemcpyHostToDevice); + if (cudaStatus != cudaSuccess) { + fprintf(stderr, "cudaMemcpy failed!"); + goto Error; + } + + cudaStatus = cudaMemcpy(dev_b, b, size * sizeof(int), cudaMemcpyHostToDevice); + if (cudaStatus != cudaSuccess) { + fprintf(stderr, "cudaMemcpy failed!"); + goto Error; + } + + // Launch a kernel on the GPU with one thread for each element. + addKernel<<<1, size>>>(dev_c, dev_a, dev_b); + + // Check for any errors launching the kernel + cudaStatus = cudaGetLastError(); + if (cudaStatus != cudaSuccess) { + fprintf(stderr, "addKernel launch failed: %s\n", cudaGetErrorString(cudaStatus)); + goto Error; + } + + // cudaDeviceSynchronize waits for the kernel to finish, and returns + // any errors encountered during the launch. + cudaStatus = cudaDeviceSynchronize(); + if (cudaStatus != cudaSuccess) { + fprintf(stderr, "cudaDeviceSynchronize returned error code %d after launching addKernel!\n", cudaStatus); + goto Error; + } + + // Copy output vector from GPU buffer to host memory. + cudaStatus = cudaMemcpy(c, dev_c, size * sizeof(int), cudaMemcpyDeviceToHost); + if (cudaStatus != cudaSuccess) { + fprintf(stderr, "cudaMemcpy failed!"); + goto Error; + } + +Error: + cudaFree(dev_c); + cudaFree(dev_a); + cudaFree(dev_b); + + return cudaStatus; +} diff --git a/msvc/plugin-cuda-common/plugin-cuda-common.vcxproj b/msvc/plugin-cuda-common/plugin-cuda-common.vcxproj new file mode 100644 index 0000000..ffeaae9 --- /dev/null +++ b/msvc/plugin-cuda-common/plugin-cuda-common.vcxproj @@ -0,0 +1,103 @@ + + + + + Debug + x64 + + + Release + x64 + + + + {C4A8ED89-6F2F-40C2-8EDA-4577EB49DE07} + plugin_cuda_common + 10.0.17763.0 + + + + Application + true + MultiByte + v141 + + + StaticLibrary + false + true + MultiByte + v141 + + + + + + + + + + + + + + true + + + $(SolutionDir)build\$(Configuration)\ + + + + Level3 + Disabled + WIN32;WIN64;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + + + true + Console + cudart_static.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + 64 + + + + + Level3 + MaxSpeed + true + true + WIN32;WIN64;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + $(SolutionDir)..\src;%(AdditionalIncludeDirectories) + true + + + true + true + true + Console + cudart_static.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + 64 + + + + + + + + + + + + true + true + + + + + + + + \ No newline at end of file diff --git a/msvc/plugin-cuda-common/plugin-cuda-common.vcxproj.filters b/msvc/plugin-cuda-common/plugin-cuda-common.vcxproj.filters new file mode 100644 index 0000000..edd99e3 --- /dev/null +++ b/msvc/plugin-cuda-common/plugin-cuda-common.vcxproj.filters @@ -0,0 +1,22 @@ + + + + + + + + {2b9f3339-0de6-496e-bd9b-87c8fd544d6d} + + + + + gl_core_3_3 + + + + + + gl_core_3_3 + + + \ No newline at end of file From b9c84075190f742f7793a5b432e27e8578194cfd Mon Sep 17 00:00:00 2001 From: Aubrey Buchanan Date: Wed, 12 Jun 2019 19:06:40 -0600 Subject: [PATCH 47/55] Update angrylion-plus-x.sln --- msvc/angrylion-plus-x.sln | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/msvc/angrylion-plus-x.sln b/msvc/angrylion-plus-x.sln index 0085089..b8c6f6f 100644 --- a/msvc/angrylion-plus-x.sln +++ b/msvc/angrylion-plus-x.sln @@ -18,7 +18,7 @@ EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "plugin-zilmar", "plugin-zilmar.vcxproj", "{722AD25D-C281-48FB-9A58-7699E50F8E6D}" ProjectSection(ProjectDependencies) = postProject {D86C6E84-3371-4B20-8620-A703FF3F2CC5} = {D86C6E84-3371-4B20-8620-A703FF3F2CC5} - {13F7DDD7-2282-408A-AEF9-72266E0236DC} = {13F7DDD7-2282-408A-AEF9-72266E0236DC} + {C4A8ED89-6F2F-40C2-8EDA-4577EB49DE07} = {C4A8ED89-6F2F-40C2-8EDA-4577EB49DE07} EndProjectSection EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "plugin-cuda-common", "plugin-cuda-common\plugin-cuda-common.vcxproj", "{C4A8ED89-6F2F-40C2-8EDA-4577EB49DE07}" From 2f94f86b6a259869e5b095c14439c57a32fe2143 Mon Sep 17 00:00:00 2001 From: Aubrey Buchanan Date: Wed, 12 Jun 2019 20:45:51 -0600 Subject: [PATCH 48/55] propping up CUDA --- msvc/angrylion-plus-x.sln | 25 +++++----- msvc/plugin-common.vcxproj | 10 +++- msvc/plugin-common.vcxproj.filters | 12 +++++ msvc/plugin-zilmar.vcxproj | 15 +++++- src/plugin/common/gl_core_3_3/gl_core_3_3.c | 10 ++++ src/plugin/common/gl_core_3_3/gl_core_3_3.h | 43 ++++++++++------ src/plugin/common/gl_screen.c | 55 +++++++++++++++++++++ 7 files changed, 137 insertions(+), 33 deletions(-) diff --git a/msvc/angrylion-plus-x.sln b/msvc/angrylion-plus-x.sln index b8c6f6f..ef84f89 100644 --- a/msvc/angrylion-plus-x.sln +++ b/msvc/angrylion-plus-x.sln @@ -4,11 +4,6 @@ VisualStudioVersion = 15.0.27130.2003 MinimumVisualStudioVersion = 10.0.40219.1 Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "core", "core.vcxproj", "{D86C6E84-3371-4B20-8620-A703FF3F2CC5}" EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "plugin-common", "plugin-common.vcxproj", "{13F7DDD7-2282-408A-AEF9-72266E0236DC}" - ProjectSection(ProjectDependencies) = postProject - {D86C6E84-3371-4B20-8620-A703FF3F2CC5} = {D86C6E84-3371-4B20-8620-A703FF3F2CC5} - EndProjectSection -EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "plugin-mupen64plus", "plugin-mupen64plus.vcxproj", "{54BDC4EF-2526-47F7-A73E-C1C0FBC0237C}" ProjectSection(ProjectDependencies) = postProject {D86C6E84-3371-4B20-8620-A703FF3F2CC5} = {D86C6E84-3371-4B20-8620-A703FF3F2CC5} @@ -18,7 +13,7 @@ EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "plugin-zilmar", "plugin-zilmar.vcxproj", "{722AD25D-C281-48FB-9A58-7699E50F8E6D}" ProjectSection(ProjectDependencies) = postProject {D86C6E84-3371-4B20-8620-A703FF3F2CC5} = {D86C6E84-3371-4B20-8620-A703FF3F2CC5} - {C4A8ED89-6F2F-40C2-8EDA-4577EB49DE07} = {C4A8ED89-6F2F-40C2-8EDA-4577EB49DE07} + {13F7DDD7-2282-408A-AEF9-72266E0236DC} = {13F7DDD7-2282-408A-AEF9-72266E0236DC} EndProjectSection EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "plugin-cuda-common", "plugin-cuda-common\plugin-cuda-common.vcxproj", "{C4A8ED89-6F2F-40C2-8EDA-4577EB49DE07}" @@ -26,6 +21,8 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "plugin-cuda-common", "plugi {D86C6E84-3371-4B20-8620-A703FF3F2CC5} = {D86C6E84-3371-4B20-8620-A703FF3F2CC5} EndProjectSection EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "plugin-common", "plugin-common.vcxproj", "{13F7DDD7-2282-408A-AEF9-72266E0236DC}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|x64 = Debug|x64 @@ -42,14 +39,6 @@ Global {D86C6E84-3371-4B20-8620-A703FF3F2CC5}.Release|x64.Build.0 = Release|x64 {D86C6E84-3371-4B20-8620-A703FF3F2CC5}.Release|x86.ActiveCfg = Release|Win32 {D86C6E84-3371-4B20-8620-A703FF3F2CC5}.Release|x86.Build.0 = Release|Win32 - {13F7DDD7-2282-408A-AEF9-72266E0236DC}.Debug|x64.ActiveCfg = Debug|x64 - {13F7DDD7-2282-408A-AEF9-72266E0236DC}.Debug|x64.Build.0 = Debug|x64 - {13F7DDD7-2282-408A-AEF9-72266E0236DC}.Debug|x86.ActiveCfg = Debug|Win32 - {13F7DDD7-2282-408A-AEF9-72266E0236DC}.Debug|x86.Build.0 = Debug|Win32 - {13F7DDD7-2282-408A-AEF9-72266E0236DC}.Release|x64.ActiveCfg = Release|x64 - {13F7DDD7-2282-408A-AEF9-72266E0236DC}.Release|x64.Build.0 = Release|x64 - {13F7DDD7-2282-408A-AEF9-72266E0236DC}.Release|x86.ActiveCfg = Release|Win32 - {13F7DDD7-2282-408A-AEF9-72266E0236DC}.Release|x86.Build.0 = Release|Win32 {54BDC4EF-2526-47F7-A73E-C1C0FBC0237C}.Debug|x64.ActiveCfg = Debug|x64 {54BDC4EF-2526-47F7-A73E-C1C0FBC0237C}.Debug|x64.Build.0 = Debug|x64 {54BDC4EF-2526-47F7-A73E-C1C0FBC0237C}.Debug|x86.ActiveCfg = Debug|Win32 @@ -72,6 +61,14 @@ Global {C4A8ED89-6F2F-40C2-8EDA-4577EB49DE07}.Release|x64.ActiveCfg = Release|x64 {C4A8ED89-6F2F-40C2-8EDA-4577EB49DE07}.Release|x64.Build.0 = Release|x64 {C4A8ED89-6F2F-40C2-8EDA-4577EB49DE07}.Release|x86.ActiveCfg = Release|x64 + {13F7DDD7-2282-408A-AEF9-72266E0236DC}.Debug|x64.ActiveCfg = Debug|x64 + {13F7DDD7-2282-408A-AEF9-72266E0236DC}.Debug|x64.Build.0 = Debug|x64 + {13F7DDD7-2282-408A-AEF9-72266E0236DC}.Debug|x86.ActiveCfg = Debug|Win32 + {13F7DDD7-2282-408A-AEF9-72266E0236DC}.Debug|x86.Build.0 = Debug|Win32 + {13F7DDD7-2282-408A-AEF9-72266E0236DC}.Release|x64.ActiveCfg = Release|x64 + {13F7DDD7-2282-408A-AEF9-72266E0236DC}.Release|x64.Build.0 = Release|x64 + {13F7DDD7-2282-408A-AEF9-72266E0236DC}.Release|x86.ActiveCfg = Release|Win32 + {13F7DDD7-2282-408A-AEF9-72266E0236DC}.Release|x86.Build.0 = Release|Win32 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/msvc/plugin-common.vcxproj b/msvc/plugin-common.vcxproj index 6e14819..2e58d2e 100644 --- a/msvc/plugin-common.vcxproj +++ b/msvc/plugin-common.vcxproj @@ -30,11 +30,15 @@ + + + + {13F7DDD7-2282-408A-AEF9-72266E0236DC} Win32Proj - 10.0.16299.0 + 10.0.17763.0 plugin-common @@ -62,6 +66,7 @@ + @@ -113,7 +118,7 @@ Level3 ProgramDatabase - $(SolutionDir)..\src;%(AdditionalIncludeDirectories) + $(SolutionDir)..\src;%(AdditionalIncludeDirectories);C:\ProgramData\NVIDIA Corporation\CUDA Samples\v10.1\common\inc\;$(CudaToolkitIncludeDir) Windows @@ -191,5 +196,6 @@ + \ No newline at end of file diff --git a/msvc/plugin-common.vcxproj.filters b/msvc/plugin-common.vcxproj.filters index 54e8e7c..6236624 100644 --- a/msvc/plugin-common.vcxproj.filters +++ b/msvc/plugin-common.vcxproj.filters @@ -28,5 +28,17 @@ Source Files\gl_core_3_3 + + Resource Files + + + Resource Files + + + Resource Files + + + Resource Files + \ No newline at end of file diff --git a/msvc/plugin-zilmar.vcxproj b/msvc/plugin-zilmar.vcxproj index b3a78cd..315a0f5 100644 --- a/msvc/plugin-zilmar.vcxproj +++ b/msvc/plugin-zilmar.vcxproj @@ -65,6 +65,7 @@ + @@ -120,7 +121,7 @@ Level3 ProgramDatabase - $(SolutionDir)..\src;%(AdditionalIncludeDirectories) + $(SolutionDir)..\src;%(AdditionalIncludeDirectories);C:\ProgramData\NVIDIA Corporation\CUDA Samples\v10.1\common\inc; ddraw.lib dxguid.lib %(AdditionalOptions) @@ -130,8 +131,17 @@ $(OutDir)angrylion.lib MachineX86 opengl32.lib;plugin-common.lib;core.lib;shlwapi.lib;%(AdditionalDependencies) - $(OutDir);%(AdditionalLibraryDirectories) + $(OutDir);$(SolutionDir)..\src;%(AdditionalLibraryDirectories);$(CudaToolkitLibDir) + + true + + + $(OutDir);$(SolutionDir)..\src;%(AdditionalLibraryDirectories);$(CudaToolkitLibDir) + + + MT + @@ -234,5 +244,6 @@ + \ No newline at end of file diff --git a/src/plugin/common/gl_core_3_3/gl_core_3_3.c b/src/plugin/common/gl_core_3_3/gl_core_3_3.c index df01dad..ec291ed 100644 --- a/src/plugin/common/gl_core_3_3/gl_core_3_3.c +++ b/src/plugin/common/gl_core_3_3/gl_core_3_3.c @@ -3,6 +3,9 @@ #include #include + +#define __dv(v) + void* IntGetProcAddress(const char *name); void (CODEGEN_FUNCPTR *_ptrc_glBlendFunc)(GLenum sfactor, GLenum dfactor) = NULL; @@ -361,6 +364,13 @@ void (CODEGEN_FUNCPTR *_ptrc_glVertexAttribP3uiv)(GLuint index, GLenum type, GLb void (CODEGEN_FUNCPTR *_ptrc_glVertexAttribP4ui)(GLuint index, GLenum type, GLboolean normalized, GLuint value) = NULL; void (CODEGEN_FUNCPTR *_ptrc_glVertexAttribP4uiv)(GLuint index, GLenum type, GLboolean normalized, const GLuint * value) = NULL; +void (CODEGEN_FUNCPTR *_ptrc_cudaMalloc)(void **p, size_t s) = NULL; +void (CODEGEN_FUNCPTR *_ptrc_cudaGraphicsUnmapResources)(int count, cudaGraphicsResource_t *resources, cudaStream_t stream __dv(0)) = NULL; +void (CODEGEN_FUNCPTR *_ptrc_cudaDeviceReset)(void) = NULL; +void (CODEGEN_FUNCPTR *_ptrc_cudaGraphicsGLRegisterBuffer)(struct cudaGraphicsResource **resource, GLuint buffer, unsigned int flags) = NULL; +void (CODEGEN_FUNCPTR *_ptrc_cudaGraphicsMapResources)(void **p, size_t s) = NULL; +void (CODEGEN_FUNCPTR *_ptrc_cudaGraphicsResourceGetMappedPointer)(void **p, size_t s) = NULL; + static int Load_Version_3_3(void) { int numFailed = 0; diff --git a/src/plugin/common/gl_core_3_3/gl_core_3_3.h b/src/plugin/common/gl_core_3_3/gl_core_3_3.h index ae1e93a..ad6b338 100644 --- a/src/plugin/common/gl_core_3_3/gl_core_3_3.h +++ b/src/plugin/common/gl_core_3_3/gl_core_3_3.h @@ -1,21 +1,21 @@ #ifndef POINTER_C_GENERATED_HEADER_OPENGL_H #define POINTER_C_GENERATED_HEADER_OPENGL_H -#if defined(__glew_h__) || defined(__GLEW_H__) -#error Attempt to include auto-generated header after including glew.h -#endif -#if defined(__gl_h_) || defined(__GL_H__) -#error Attempt to include auto-generated header after including gl.h -#endif -#if defined(__glext_h_) || defined(__GLEXT_H_) -#error Attempt to include auto-generated header after including glext.h -#endif -#if defined(__gltypes_h_) -#error Attempt to include auto-generated header after gltypes.h -#endif -#if defined(__gl_ATI_h_) -#error Attempt to include auto-generated header after including glATI.h -#endif +//#if defined(__glew_h__) || defined(__GLEW_H__) +//#error Attempt to include auto-generated header after including glew.h +//#endif +//#if defined(__gl_h_) || defined(__GL_H__) +//#error Attempt to include auto-generated header after including gl.h +//#endif +//#if defined(__glext_h_) || defined(__GLEXT_H_) +//#error Attempt to include auto-generated header after including glext.h +//#endif +//#if defined(__gltypes_h_) +//#error Attempt to include auto-generated header after gltypes.h +//#endif +//#if defined(__gl_ATI_h_) +//#error Attempt to include auto-generated header after including glATI.h +//#endif #define __glew_h__ #define __GLEW_H__ @@ -1681,6 +1681,19 @@ extern void (CODEGEN_FUNCPTR *_ptrc_glVertexAttribP4ui)(GLuint index, GLenum typ extern void (CODEGEN_FUNCPTR *_ptrc_glVertexAttribP4uiv)(GLuint index, GLenum type, GLboolean normalized, const GLuint * value); #define glVertexAttribP4uiv _ptrc_glVertexAttribP4uiv +extern void (CODEGEN_FUNCPTR *_ptrc_cudaMalloc)(void **p, size_t s); +#define cudaMalloc _ptrc_cudaMalloc +extern void (CODEGEN_FUNCPTR *_ptrc_cudaGraphicsUnmapResources)(int count, cudaGraphicsResource_t *resources, cudaStream_t stream); +#define cudaGraphicsUnmapResources _ptrc_cudaGraphicsUnmapResources +extern void (CODEGEN_FUNCPTR *_ptrc_cudaDeviceReset)(void); +#define cudaDeviceReset _ptrc_cudaDeviceReset +extern void (CODEGEN_FUNCPTR *_ptrc_cudaGraphicsGLRegisterBuffer)(struct cudaGraphicsResource **resource, GLuint buffer, unsigned int flags); +#define cudaGraphicsGLRegisterBuffer _ptrc_cudaGraphicsGLRegisterBuffer +extern void (CODEGEN_FUNCPTR *_ptrc_cudaGraphicsMapResources)(void **p, size_t s); +#define cudaGraphicsMapResources _ptrc_cudaGraphicsMapResources +extern void (CODEGEN_FUNCPTR *_ptrc_cudaGraphicsResourceGetMappedPointer)(void **p, size_t s); +#define cudaGraphicsResourceGetMappedPointer _ptrc_cudaGraphicsResourceGetMappedPointer + enum ogl_LoadStatus { ogl_LOAD_FAILED = 0, diff --git a/src/plugin/common/gl_screen.c b/src/plugin/common/gl_screen.c index cb7896a..61591b2 100644 --- a/src/plugin/common/gl_screen.c +++ b/src/plugin/common/gl_screen.c @@ -3,6 +3,12 @@ #include #include +#include + +#include +#include +#include +#include #ifdef GLES #include @@ -18,6 +24,10 @@ static GLuint program; static GLuint vao; +static GLuint vbo; +struct cudaGraphicsResource *cuda_vbo_resource; +void *d_vbo_buffer = NULL; + static GLuint texture = 1; static GLuint depth_texture = 2; @@ -27,6 +37,8 @@ static GLint depthValueTextureLocation; static int32_t tex_width; static int32_t tex_height; +static float g_fAnim = 0.0; + static int32_t tex_display_height; #ifdef _DEBUG @@ -74,6 +86,12 @@ static void gl_check_errors(void) #define gl_check_errors(...) #endif +static void gl_check_error_handle_cuda(void) { + gl_check_errors(); + cudaDeviceReset(); + exit(EXIT_FAILURE); +} + static GLuint gl_shader_compile(GLenum type, const GLchar* source) { GLuint shader = glCreateShader(type); @@ -169,6 +187,9 @@ void gl_screen_init(struct rdp_config* config) glUniform1i(depthValueTextureLocation, 0); glUniform1i(colorValueTextureLocation, 1); + // create VBO + cudaMalloc((void **)&d_vbo_buffer, tex_width*tex_height * 4 * sizeof(float)); + // prepare dummy VAO glGenVertexArrays(1, &vao); glBindVertexArray(vao); @@ -204,10 +225,39 @@ void gl_screen_init(struct rdp_config* config) gl_check_errors(); } +void runCuda(struct cudaGraphicsResource **vbo_resource, struct rdp_frame_buffer* fb) +{ + // map OpenGL buffer object for writing from CUDA + float4 *dptr; + cudaGraphicsMapResources(1, vbo_resource, 0); + size_t num_bytes; + cudaGraphicsResourceGetMappedPointer((void **)&dptr, &num_bytes, *vbo_resource); + //printf("CUDA mapped VBO: May access %ld bytes\n", num_bytes); + + // execute the kernel + // dim3 block(8, 8, 1); + // dim3 grid(mesh_width / block.x, mesh_height / block.y, 1); + // kernel<<< grid, block>>>(dptr, mesh_width, mesh_height, g_fAnim); + + //launch_kernel(dptr, fb->width, fb->height, g_fAnim); + + // unmap buffer object + cudaGraphicsUnmapResources(1, vbo_resource, 0); +} + bool gl_screen_write(struct rdp_frame_buffer* fb, int32_t output_height) { bool buffer_size_changed = tex_width != fb->width || tex_height != fb->height; + // create buffer object + glGenBuffers(1, vbo); + glBindBuffer(GL_ARRAY_BUFFER, vbo); + + // initialize buffer object + GLuint size = tex_width * tex_height * 4 * sizeof(float); + glBufferData(GL_ARRAY_BUFFER, size, 0, GL_DYNAMIC_DRAW); + glBindBuffer(GL_ARRAY_BUFFER, 0); + // check if the framebuffer size has changed if (buffer_size_changed) { tex_width = fb->width; @@ -262,6 +312,11 @@ bool gl_screen_write(struct rdp_frame_buffer* fb, int32_t output_height) } + // register this buffer object with CUDA + cudaGraphicsGLRegisterBuffer(&cuda_vbo_resource, vbo, -1); + + gl_check_error_handle_cuda(); + // update output size tex_display_height = output_height; From 857f3b2ca5e8bc0331b3b0e63d7b008fc540085e Mon Sep 17 00:00:00 2001 From: Aubrey Buchanan Date: Wed, 12 Jun 2019 20:46:52 -0600 Subject: [PATCH 49/55] Tidying up dummy project --- msvc/angrylion-plus-x.sln | 11 -- msvc/plugin-cuda-common/kernel.cu | 121 ------------------ .../plugin-cuda-common.vcxproj | 103 --------------- .../plugin-cuda-common.vcxproj.filters | 22 ---- 4 files changed, 257 deletions(-) delete mode 100644 msvc/plugin-cuda-common/kernel.cu delete mode 100644 msvc/plugin-cuda-common/plugin-cuda-common.vcxproj delete mode 100644 msvc/plugin-cuda-common/plugin-cuda-common.vcxproj.filters diff --git a/msvc/angrylion-plus-x.sln b/msvc/angrylion-plus-x.sln index ef84f89..a58c8a7 100644 --- a/msvc/angrylion-plus-x.sln +++ b/msvc/angrylion-plus-x.sln @@ -16,11 +16,6 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "plugin-zilmar", "plugin-zil {13F7DDD7-2282-408A-AEF9-72266E0236DC} = {13F7DDD7-2282-408A-AEF9-72266E0236DC} EndProjectSection EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "plugin-cuda-common", "plugin-cuda-common\plugin-cuda-common.vcxproj", "{C4A8ED89-6F2F-40C2-8EDA-4577EB49DE07}" - ProjectSection(ProjectDependencies) = postProject - {D86C6E84-3371-4B20-8620-A703FF3F2CC5} = {D86C6E84-3371-4B20-8620-A703FF3F2CC5} - EndProjectSection -EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "plugin-common", "plugin-common.vcxproj", "{13F7DDD7-2282-408A-AEF9-72266E0236DC}" EndProject Global @@ -55,12 +50,6 @@ Global {722AD25D-C281-48FB-9A58-7699E50F8E6D}.Release|x64.Build.0 = Release|x64 {722AD25D-C281-48FB-9A58-7699E50F8E6D}.Release|x86.ActiveCfg = Release|Win32 {722AD25D-C281-48FB-9A58-7699E50F8E6D}.Release|x86.Build.0 = Release|Win32 - {C4A8ED89-6F2F-40C2-8EDA-4577EB49DE07}.Debug|x64.ActiveCfg = Debug|x64 - {C4A8ED89-6F2F-40C2-8EDA-4577EB49DE07}.Debug|x64.Build.0 = Debug|x64 - {C4A8ED89-6F2F-40C2-8EDA-4577EB49DE07}.Debug|x86.ActiveCfg = Debug|x64 - {C4A8ED89-6F2F-40C2-8EDA-4577EB49DE07}.Release|x64.ActiveCfg = Release|x64 - {C4A8ED89-6F2F-40C2-8EDA-4577EB49DE07}.Release|x64.Build.0 = Release|x64 - {C4A8ED89-6F2F-40C2-8EDA-4577EB49DE07}.Release|x86.ActiveCfg = Release|x64 {13F7DDD7-2282-408A-AEF9-72266E0236DC}.Debug|x64.ActiveCfg = Debug|x64 {13F7DDD7-2282-408A-AEF9-72266E0236DC}.Debug|x64.Build.0 = Debug|x64 {13F7DDD7-2282-408A-AEF9-72266E0236DC}.Debug|x86.ActiveCfg = Debug|Win32 diff --git a/msvc/plugin-cuda-common/kernel.cu b/msvc/plugin-cuda-common/kernel.cu deleted file mode 100644 index 90d489d..0000000 --- a/msvc/plugin-cuda-common/kernel.cu +++ /dev/null @@ -1,121 +0,0 @@ - -#include "cuda_runtime.h" -#include "device_launch_parameters.h" - -#include - -cudaError_t addWithCuda(int *c, const int *a, const int *b, unsigned int size); - -__global__ void addKernel(int *c, const int *a, const int *b) -{ - int i = threadIdx.x; - c[i] = a[i] + b[i]; -} - -int main() -{ - const int arraySize = 5; - const int a[arraySize] = { 1, 2, 3, 4, 5 }; - const int b[arraySize] = { 10, 20, 30, 40, 50 }; - int c[arraySize] = { 0 }; - - // Add vectors in parallel. - cudaError_t cudaStatus = addWithCuda(c, a, b, arraySize); - if (cudaStatus != cudaSuccess) { - fprintf(stderr, "addWithCuda failed!"); - return 1; - } - - printf("{1,2,3,4,5} + {10,20,30,40,50} = {%d,%d,%d,%d,%d}\n", - c[0], c[1], c[2], c[3], c[4]); - - // cudaDeviceReset must be called before exiting in order for profiling and - // tracing tools such as Nsight and Visual Profiler to show complete traces. - cudaStatus = cudaDeviceReset(); - if (cudaStatus != cudaSuccess) { - fprintf(stderr, "cudaDeviceReset failed!"); - return 1; - } - - return 0; -} - -// Helper function for using CUDA to add vectors in parallel. -cudaError_t addWithCuda(int *c, const int *a, const int *b, unsigned int size) -{ - int *dev_a = 0; - int *dev_b = 0; - int *dev_c = 0; - cudaError_t cudaStatus; - - // Choose which GPU to run on, change this on a multi-GPU system. - cudaStatus = cudaSetDevice(0); - if (cudaStatus != cudaSuccess) { - fprintf(stderr, "cudaSetDevice failed! Do you have a CUDA-capable GPU installed?"); - goto Error; - } - - // Allocate GPU buffers for three vectors (two input, one output) . - cudaStatus = cudaMalloc((void**)&dev_c, size * sizeof(int)); - if (cudaStatus != cudaSuccess) { - fprintf(stderr, "cudaMalloc failed!"); - goto Error; - } - - cudaStatus = cudaMalloc((void**)&dev_a, size * sizeof(int)); - if (cudaStatus != cudaSuccess) { - fprintf(stderr, "cudaMalloc failed!"); - goto Error; - } - - cudaStatus = cudaMalloc((void**)&dev_b, size * sizeof(int)); - if (cudaStatus != cudaSuccess) { - fprintf(stderr, "cudaMalloc failed!"); - goto Error; - } - - // Copy input vectors from host memory to GPU buffers. - cudaStatus = cudaMemcpy(dev_a, a, size * sizeof(int), cudaMemcpyHostToDevice); - if (cudaStatus != cudaSuccess) { - fprintf(stderr, "cudaMemcpy failed!"); - goto Error; - } - - cudaStatus = cudaMemcpy(dev_b, b, size * sizeof(int), cudaMemcpyHostToDevice); - if (cudaStatus != cudaSuccess) { - fprintf(stderr, "cudaMemcpy failed!"); - goto Error; - } - - // Launch a kernel on the GPU with one thread for each element. - addKernel<<<1, size>>>(dev_c, dev_a, dev_b); - - // Check for any errors launching the kernel - cudaStatus = cudaGetLastError(); - if (cudaStatus != cudaSuccess) { - fprintf(stderr, "addKernel launch failed: %s\n", cudaGetErrorString(cudaStatus)); - goto Error; - } - - // cudaDeviceSynchronize waits for the kernel to finish, and returns - // any errors encountered during the launch. - cudaStatus = cudaDeviceSynchronize(); - if (cudaStatus != cudaSuccess) { - fprintf(stderr, "cudaDeviceSynchronize returned error code %d after launching addKernel!\n", cudaStatus); - goto Error; - } - - // Copy output vector from GPU buffer to host memory. - cudaStatus = cudaMemcpy(c, dev_c, size * sizeof(int), cudaMemcpyDeviceToHost); - if (cudaStatus != cudaSuccess) { - fprintf(stderr, "cudaMemcpy failed!"); - goto Error; - } - -Error: - cudaFree(dev_c); - cudaFree(dev_a); - cudaFree(dev_b); - - return cudaStatus; -} diff --git a/msvc/plugin-cuda-common/plugin-cuda-common.vcxproj b/msvc/plugin-cuda-common/plugin-cuda-common.vcxproj deleted file mode 100644 index ffeaae9..0000000 --- a/msvc/plugin-cuda-common/plugin-cuda-common.vcxproj +++ /dev/null @@ -1,103 +0,0 @@ - - - - - Debug - x64 - - - Release - x64 - - - - {C4A8ED89-6F2F-40C2-8EDA-4577EB49DE07} - plugin_cuda_common - 10.0.17763.0 - - - - Application - true - MultiByte - v141 - - - StaticLibrary - false - true - MultiByte - v141 - - - - - - - - - - - - - - true - - - $(SolutionDir)build\$(Configuration)\ - - - - Level3 - Disabled - WIN32;WIN64;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) - - - true - Console - cudart_static.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) - - - 64 - - - - - Level3 - MaxSpeed - true - true - WIN32;WIN64;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) - $(SolutionDir)..\src;%(AdditionalIncludeDirectories) - true - - - true - true - true - Console - cudart_static.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) - - - 64 - - - - - - - - - - - - true - true - - - - - - - - \ No newline at end of file diff --git a/msvc/plugin-cuda-common/plugin-cuda-common.vcxproj.filters b/msvc/plugin-cuda-common/plugin-cuda-common.vcxproj.filters deleted file mode 100644 index edd99e3..0000000 --- a/msvc/plugin-cuda-common/plugin-cuda-common.vcxproj.filters +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - - {2b9f3339-0de6-496e-bd9b-87c8fd544d6d} - - - - - gl_core_3_3 - - - - - - gl_core_3_3 - - - \ No newline at end of file From 48bdb777855306d1a8af6faca8799b270c4d634f Mon Sep 17 00:00:00 2001 From: Aubrey Buchanan Date: Wed, 12 Jun 2019 21:00:14 -0600 Subject: [PATCH 50/55] organization --- src/plugin/common/gl_screen.c | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/plugin/common/gl_screen.c b/src/plugin/common/gl_screen.c index 61591b2..f4d2a25 100644 --- a/src/plugin/common/gl_screen.c +++ b/src/plugin/common/gl_screen.c @@ -23,7 +23,6 @@ #endif static GLuint program; -static GLuint vao; static GLuint vbo; struct cudaGraphicsResource *cuda_vbo_resource; void *d_vbo_buffer = NULL; @@ -191,8 +190,8 @@ void gl_screen_init(struct rdp_config* config) cudaMalloc((void **)&d_vbo_buffer, tex_width*tex_height * 4 * sizeof(float)); // prepare dummy VAO - glGenVertexArrays(1, &vao); - glBindVertexArray(vao); + glGenVertexArrays(1, &vbo); + glBindVertexArray(vbo); // select interpolation method GLint filter; @@ -314,8 +313,10 @@ bool gl_screen_write(struct rdp_frame_buffer* fb, int32_t output_height) // register this buffer object with CUDA cudaGraphicsGLRegisterBuffer(&cuda_vbo_resource, vbo, -1); - - gl_check_error_handle_cuda(); + // run CUDA + runCuda(&cuda_vbo_resource, fb); + // check if there was an error when using any of the commands above + gl_check_errors(); // update output size tex_display_height = output_height; @@ -392,6 +393,6 @@ void gl_screen_close(void) tex_display_height = 0; glDeleteTextures(1, &texture); - glDeleteVertexArrays(1, &vao); + glDeleteVertexArrays(1, &vbo); glDeleteProgram(program); } From e2138f9f76413b1bf6ae1e71b22e72f888491e94 Mon Sep 17 00:00:00 2001 From: Aubrey Buchanan Date: Wed, 12 Jun 2019 21:09:46 -0600 Subject: [PATCH 51/55] Update README.md --- README.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index aadbc53..ae8be36 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,11 @@ -# Angrylion RDP X +# angrylion-rdp-x -This is a conservative fork of angrylion's RDP plugin that aims to improve performance add new features while retaining the accuracy of the original plugin. +angrylion-rdp-x is a fork of angrylion-rdp-plus, which is itself a fork of angrylion's RDP plugin. While angrylion-rdp-plus has aimed to improve performance and add new features while retaining the accuracy of the original plugin, angrylion-rdp-x is a project of mine to attempt to explore what can be done with Nintendo 64 graphics emulation. -### Current features +### New features in angrylion-rdp-x + * Population of depth buffer for ReShade compatibility. + +### Features from angrylion-rdp-plus * More maintainable code base by dividing the huge n64video.cpp into smaller pieces. * Improved portability by separating the emulator plugin interface and window management from the RDP emulation core. * Multi-threaded rendering support, which increases performance on multi-core CPUs significantly. From e341068f0b551b6fc3d694c34bc0cd53fbd16c92 Mon Sep 17 00:00:00 2001 From: Aubrey Buchanan Date: Wed, 12 Jun 2019 21:31:46 -0600 Subject: [PATCH 52/55] Update README.md --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index ae8be36..734074b 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,10 @@ # angrylion-rdp-x -angrylion-rdp-x is a fork of angrylion-rdp-plus, which is itself a fork of angrylion's RDP plugin. While angrylion-rdp-plus has aimed to improve performance and add new features while retaining the accuracy of the original plugin, angrylion-rdp-x is a project of mine to attempt to explore what can be done with Nintendo 64 graphics emulation. +angrylion-rdp-x is a fork of angrylion-rdp-plus, which is itself a fork of angrylion's RDP plugin. While angrylion-rdp-plus has aimed to improve performance and add new features while retaining the accuracy of the original plugin, angrylion-rdp-x is a project of mine to attempt to explore Nintendo 64 graphics emulation. ### New features in angrylion-rdp-x * Population of depth buffer for ReShade compatibility. + * (WIP) some sort of CUDA implementation. ### Features from angrylion-rdp-plus * More maintainable code base by dividing the huge n64video.cpp into smaller pieces. From 76943d814a5a8c6782d46b85180f0f116d241a3d Mon Sep 17 00:00:00 2001 From: Aubrey Buchanan Date: Wed, 12 Jun 2019 21:44:03 -0600 Subject: [PATCH 53/55] Various (and needed) tweaks --- src/plugin/common/gl_core_3_3/gl_core_3_3.c | 4 ++-- src/plugin/common/gl_core_3_3/gl_core_3_3.h | 6 ++++-- src/plugin/common/gl_screen.c | 2 +- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/plugin/common/gl_core_3_3/gl_core_3_3.c b/src/plugin/common/gl_core_3_3/gl_core_3_3.c index ec291ed..908e721 100644 --- a/src/plugin/common/gl_core_3_3/gl_core_3_3.c +++ b/src/plugin/common/gl_core_3_3/gl_core_3_3.c @@ -368,8 +368,8 @@ void (CODEGEN_FUNCPTR *_ptrc_cudaMalloc)(void **p, size_t s) = NULL; void (CODEGEN_FUNCPTR *_ptrc_cudaGraphicsUnmapResources)(int count, cudaGraphicsResource_t *resources, cudaStream_t stream __dv(0)) = NULL; void (CODEGEN_FUNCPTR *_ptrc_cudaDeviceReset)(void) = NULL; void (CODEGEN_FUNCPTR *_ptrc_cudaGraphicsGLRegisterBuffer)(struct cudaGraphicsResource **resource, GLuint buffer, unsigned int flags) = NULL; -void (CODEGEN_FUNCPTR *_ptrc_cudaGraphicsMapResources)(void **p, size_t s) = NULL; -void (CODEGEN_FUNCPTR *_ptrc_cudaGraphicsResourceGetMappedPointer)(void **p, size_t s) = NULL; +void (CODEGEN_FUNCPTR *_ptrc_cudaGraphicsMapResources)(int count, cudaGraphicsResource_t *resources, cudaStream_t stream __dv(0)) = NULL; +void (CODEGEN_FUNCPTR *_ptrc_cudaGraphicsResourceGetMappedPointer)(void **devPtr, size_t *size, cudaGraphicsResource_t resource) = NULL; static int Load_Version_3_3(void) { diff --git a/src/plugin/common/gl_core_3_3/gl_core_3_3.h b/src/plugin/common/gl_core_3_3/gl_core_3_3.h index ad6b338..8f20fa9 100644 --- a/src/plugin/common/gl_core_3_3/gl_core_3_3.h +++ b/src/plugin/common/gl_core_3_3/gl_core_3_3.h @@ -26,6 +26,8 @@ #define __gltypes_h_ #define __gl_ATI_h_ +#define __dv(v) + #ifndef APIENTRY #if defined(__MINGW32__) #ifndef WIN32_LEAN_AND_MEAN @@ -1689,9 +1691,9 @@ extern void (CODEGEN_FUNCPTR *_ptrc_cudaDeviceReset)(void); #define cudaDeviceReset _ptrc_cudaDeviceReset extern void (CODEGEN_FUNCPTR *_ptrc_cudaGraphicsGLRegisterBuffer)(struct cudaGraphicsResource **resource, GLuint buffer, unsigned int flags); #define cudaGraphicsGLRegisterBuffer _ptrc_cudaGraphicsGLRegisterBuffer -extern void (CODEGEN_FUNCPTR *_ptrc_cudaGraphicsMapResources)(void **p, size_t s); +extern void (CODEGEN_FUNCPTR *_ptrc_cudaGraphicsMapResources)(int count, cudaGraphicsResource_t *resources, cudaStream_t stream __dv(0)); #define cudaGraphicsMapResources _ptrc_cudaGraphicsMapResources -extern void (CODEGEN_FUNCPTR *_ptrc_cudaGraphicsResourceGetMappedPointer)(void **p, size_t s); +extern void (CODEGEN_FUNCPTR *_ptrc_cudaGraphicsResourceGetMappedPointer)(void **devPtr, size_t *size, cudaGraphicsResource_t resource); #define cudaGraphicsResourceGetMappedPointer _ptrc_cudaGraphicsResourceGetMappedPointer enum ogl_LoadStatus diff --git a/src/plugin/common/gl_screen.c b/src/plugin/common/gl_screen.c index f4d2a25..6786218 100644 --- a/src/plugin/common/gl_screen.c +++ b/src/plugin/common/gl_screen.c @@ -312,7 +312,7 @@ bool gl_screen_write(struct rdp_frame_buffer* fb, int32_t output_height) } // register this buffer object with CUDA - cudaGraphicsGLRegisterBuffer(&cuda_vbo_resource, vbo, -1); + cudaGraphicsGLRegisterBuffer(&cuda_vbo_resource, vbo, (unsigned int)-1); // run CUDA runCuda(&cuda_vbo_resource, fb); // check if there was an error when using any of the commands above From be8195659338b7a7ce7ad541c1f64152839116e0 Mon Sep 17 00:00:00 2001 From: Aubrey Buchanan Date: Mon, 13 Nov 2023 17:55:48 -0800 Subject: [PATCH 54/55] Revert changes unrelated to depth shaders --- .gitignore | 1 - README.md | 10 +-- ...ngrylion-plus-x.sln => angrylion-plus.sln} | 23 ++++--- msvc/plugin-common.vcxproj | 10 +-- msvc/plugin-common.vcxproj.filters | 12 ---- msvc/plugin-zilmar.vcxproj | 15 +---- src/plugin/common/gl_core_3_3/gl_core_3_3.c | 10 --- src/plugin/common/gl_core_3_3/gl_core_3_3.h | 45 +++++-------- src/plugin/common/gl_screen.c | 64 ++----------------- 9 files changed, 39 insertions(+), 151 deletions(-) rename msvc/{angrylion-plus-x.sln => angrylion-plus.sln} (96%) diff --git a/.gitignore b/.gitignore index d775efa..a4b8ec5 100644 --- a/.gitignore +++ b/.gitignore @@ -6,4 +6,3 @@ src/core/version.h private/ -*.nvuser diff --git a/README.md b/README.md index 734074b..675c349 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,8 @@ -# angrylion-rdp-x +# Angrylion RDP Plus -angrylion-rdp-x is a fork of angrylion-rdp-plus, which is itself a fork of angrylion's RDP plugin. While angrylion-rdp-plus has aimed to improve performance and add new features while retaining the accuracy of the original plugin, angrylion-rdp-x is a project of mine to attempt to explore Nintendo 64 graphics emulation. +This is a conservative fork of angrylion's RDP plugin that aims to improve performance add new features while retaining the accuracy of the original plugin. -### New features in angrylion-rdp-x - * Population of depth buffer for ReShade compatibility. - * (WIP) some sort of CUDA implementation. - -### Features from angrylion-rdp-plus +### Current features * More maintainable code base by dividing the huge n64video.cpp into smaller pieces. * Improved portability by separating the emulator plugin interface and window management from the RDP emulation core. * Multi-threaded rendering support, which increases performance on multi-core CPUs significantly. diff --git a/msvc/angrylion-plus-x.sln b/msvc/angrylion-plus.sln similarity index 96% rename from msvc/angrylion-plus-x.sln rename to msvc/angrylion-plus.sln index a58c8a7..4e9d4fc 100644 --- a/msvc/angrylion-plus-x.sln +++ b/msvc/angrylion-plus.sln @@ -4,6 +4,11 @@ VisualStudioVersion = 15.0.27130.2003 MinimumVisualStudioVersion = 10.0.40219.1 Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "core", "core.vcxproj", "{D86C6E84-3371-4B20-8620-A703FF3F2CC5}" EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "plugin-common", "plugin-common.vcxproj", "{13F7DDD7-2282-408A-AEF9-72266E0236DC}" + ProjectSection(ProjectDependencies) = postProject + {D86C6E84-3371-4B20-8620-A703FF3F2CC5} = {D86C6E84-3371-4B20-8620-A703FF3F2CC5} + EndProjectSection +EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "plugin-mupen64plus", "plugin-mupen64plus.vcxproj", "{54BDC4EF-2526-47F7-A73E-C1C0FBC0237C}" ProjectSection(ProjectDependencies) = postProject {D86C6E84-3371-4B20-8620-A703FF3F2CC5} = {D86C6E84-3371-4B20-8620-A703FF3F2CC5} @@ -16,8 +21,6 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "plugin-zilmar", "plugin-zil {13F7DDD7-2282-408A-AEF9-72266E0236DC} = {13F7DDD7-2282-408A-AEF9-72266E0236DC} EndProjectSection EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "plugin-common", "plugin-common.vcxproj", "{13F7DDD7-2282-408A-AEF9-72266E0236DC}" -EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|x64 = Debug|x64 @@ -34,6 +37,14 @@ Global {D86C6E84-3371-4B20-8620-A703FF3F2CC5}.Release|x64.Build.0 = Release|x64 {D86C6E84-3371-4B20-8620-A703FF3F2CC5}.Release|x86.ActiveCfg = Release|Win32 {D86C6E84-3371-4B20-8620-A703FF3F2CC5}.Release|x86.Build.0 = Release|Win32 + {13F7DDD7-2282-408A-AEF9-72266E0236DC}.Debug|x64.ActiveCfg = Debug|x64 + {13F7DDD7-2282-408A-AEF9-72266E0236DC}.Debug|x64.Build.0 = Debug|x64 + {13F7DDD7-2282-408A-AEF9-72266E0236DC}.Debug|x86.ActiveCfg = Debug|Win32 + {13F7DDD7-2282-408A-AEF9-72266E0236DC}.Debug|x86.Build.0 = Debug|Win32 + {13F7DDD7-2282-408A-AEF9-72266E0236DC}.Release|x64.ActiveCfg = Release|x64 + {13F7DDD7-2282-408A-AEF9-72266E0236DC}.Release|x64.Build.0 = Release|x64 + {13F7DDD7-2282-408A-AEF9-72266E0236DC}.Release|x86.ActiveCfg = Release|Win32 + {13F7DDD7-2282-408A-AEF9-72266E0236DC}.Release|x86.Build.0 = Release|Win32 {54BDC4EF-2526-47F7-A73E-C1C0FBC0237C}.Debug|x64.ActiveCfg = Debug|x64 {54BDC4EF-2526-47F7-A73E-C1C0FBC0237C}.Debug|x64.Build.0 = Debug|x64 {54BDC4EF-2526-47F7-A73E-C1C0FBC0237C}.Debug|x86.ActiveCfg = Debug|Win32 @@ -50,14 +61,6 @@ Global {722AD25D-C281-48FB-9A58-7699E50F8E6D}.Release|x64.Build.0 = Release|x64 {722AD25D-C281-48FB-9A58-7699E50F8E6D}.Release|x86.ActiveCfg = Release|Win32 {722AD25D-C281-48FB-9A58-7699E50F8E6D}.Release|x86.Build.0 = Release|Win32 - {13F7DDD7-2282-408A-AEF9-72266E0236DC}.Debug|x64.ActiveCfg = Debug|x64 - {13F7DDD7-2282-408A-AEF9-72266E0236DC}.Debug|x64.Build.0 = Debug|x64 - {13F7DDD7-2282-408A-AEF9-72266E0236DC}.Debug|x86.ActiveCfg = Debug|Win32 - {13F7DDD7-2282-408A-AEF9-72266E0236DC}.Debug|x86.Build.0 = Debug|Win32 - {13F7DDD7-2282-408A-AEF9-72266E0236DC}.Release|x64.ActiveCfg = Release|x64 - {13F7DDD7-2282-408A-AEF9-72266E0236DC}.Release|x64.Build.0 = Release|x64 - {13F7DDD7-2282-408A-AEF9-72266E0236DC}.Release|x86.ActiveCfg = Release|Win32 - {13F7DDD7-2282-408A-AEF9-72266E0236DC}.Release|x86.Build.0 = Release|Win32 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/msvc/plugin-common.vcxproj b/msvc/plugin-common.vcxproj index 2e58d2e..6e14819 100644 --- a/msvc/plugin-common.vcxproj +++ b/msvc/plugin-common.vcxproj @@ -30,15 +30,11 @@ - - - - {13F7DDD7-2282-408A-AEF9-72266E0236DC} Win32Proj - 10.0.17763.0 + 10.0.16299.0 plugin-common @@ -66,7 +62,6 @@ - @@ -118,7 +113,7 @@ Level3 ProgramDatabase - $(SolutionDir)..\src;%(AdditionalIncludeDirectories);C:\ProgramData\NVIDIA Corporation\CUDA Samples\v10.1\common\inc\;$(CudaToolkitIncludeDir) + $(SolutionDir)..\src;%(AdditionalIncludeDirectories) Windows @@ -196,6 +191,5 @@ - \ No newline at end of file diff --git a/msvc/plugin-common.vcxproj.filters b/msvc/plugin-common.vcxproj.filters index 6236624..54e8e7c 100644 --- a/msvc/plugin-common.vcxproj.filters +++ b/msvc/plugin-common.vcxproj.filters @@ -28,17 +28,5 @@ Source Files\gl_core_3_3 - - Resource Files - - - Resource Files - - - Resource Files - - - Resource Files - \ No newline at end of file diff --git a/msvc/plugin-zilmar.vcxproj b/msvc/plugin-zilmar.vcxproj index 315a0f5..b3a78cd 100644 --- a/msvc/plugin-zilmar.vcxproj +++ b/msvc/plugin-zilmar.vcxproj @@ -65,7 +65,6 @@ - @@ -121,7 +120,7 @@ Level3 ProgramDatabase - $(SolutionDir)..\src;%(AdditionalIncludeDirectories);C:\ProgramData\NVIDIA Corporation\CUDA Samples\v10.1\common\inc; + $(SolutionDir)..\src;%(AdditionalIncludeDirectories) ddraw.lib dxguid.lib %(AdditionalOptions) @@ -131,17 +130,8 @@ $(OutDir)angrylion.lib MachineX86 opengl32.lib;plugin-common.lib;core.lib;shlwapi.lib;%(AdditionalDependencies) - $(OutDir);$(SolutionDir)..\src;%(AdditionalLibraryDirectories);$(CudaToolkitLibDir) + $(OutDir);%(AdditionalLibraryDirectories) - - true - - - $(OutDir);$(SolutionDir)..\src;%(AdditionalLibraryDirectories);$(CudaToolkitLibDir) - - - MT - @@ -244,6 +234,5 @@ - \ No newline at end of file diff --git a/src/plugin/common/gl_core_3_3/gl_core_3_3.c b/src/plugin/common/gl_core_3_3/gl_core_3_3.c index 908e721..df01dad 100644 --- a/src/plugin/common/gl_core_3_3/gl_core_3_3.c +++ b/src/plugin/common/gl_core_3_3/gl_core_3_3.c @@ -3,9 +3,6 @@ #include #include - -#define __dv(v) - void* IntGetProcAddress(const char *name); void (CODEGEN_FUNCPTR *_ptrc_glBlendFunc)(GLenum sfactor, GLenum dfactor) = NULL; @@ -364,13 +361,6 @@ void (CODEGEN_FUNCPTR *_ptrc_glVertexAttribP3uiv)(GLuint index, GLenum type, GLb void (CODEGEN_FUNCPTR *_ptrc_glVertexAttribP4ui)(GLuint index, GLenum type, GLboolean normalized, GLuint value) = NULL; void (CODEGEN_FUNCPTR *_ptrc_glVertexAttribP4uiv)(GLuint index, GLenum type, GLboolean normalized, const GLuint * value) = NULL; -void (CODEGEN_FUNCPTR *_ptrc_cudaMalloc)(void **p, size_t s) = NULL; -void (CODEGEN_FUNCPTR *_ptrc_cudaGraphicsUnmapResources)(int count, cudaGraphicsResource_t *resources, cudaStream_t stream __dv(0)) = NULL; -void (CODEGEN_FUNCPTR *_ptrc_cudaDeviceReset)(void) = NULL; -void (CODEGEN_FUNCPTR *_ptrc_cudaGraphicsGLRegisterBuffer)(struct cudaGraphicsResource **resource, GLuint buffer, unsigned int flags) = NULL; -void (CODEGEN_FUNCPTR *_ptrc_cudaGraphicsMapResources)(int count, cudaGraphicsResource_t *resources, cudaStream_t stream __dv(0)) = NULL; -void (CODEGEN_FUNCPTR *_ptrc_cudaGraphicsResourceGetMappedPointer)(void **devPtr, size_t *size, cudaGraphicsResource_t resource) = NULL; - static int Load_Version_3_3(void) { int numFailed = 0; diff --git a/src/plugin/common/gl_core_3_3/gl_core_3_3.h b/src/plugin/common/gl_core_3_3/gl_core_3_3.h index 8f20fa9..ae1e93a 100644 --- a/src/plugin/common/gl_core_3_3/gl_core_3_3.h +++ b/src/plugin/common/gl_core_3_3/gl_core_3_3.h @@ -1,21 +1,21 @@ #ifndef POINTER_C_GENERATED_HEADER_OPENGL_H #define POINTER_C_GENERATED_HEADER_OPENGL_H -//#if defined(__glew_h__) || defined(__GLEW_H__) -//#error Attempt to include auto-generated header after including glew.h -//#endif -//#if defined(__gl_h_) || defined(__GL_H__) -//#error Attempt to include auto-generated header after including gl.h -//#endif -//#if defined(__glext_h_) || defined(__GLEXT_H_) -//#error Attempt to include auto-generated header after including glext.h -//#endif -//#if defined(__gltypes_h_) -//#error Attempt to include auto-generated header after gltypes.h -//#endif -//#if defined(__gl_ATI_h_) -//#error Attempt to include auto-generated header after including glATI.h -//#endif +#if defined(__glew_h__) || defined(__GLEW_H__) +#error Attempt to include auto-generated header after including glew.h +#endif +#if defined(__gl_h_) || defined(__GL_H__) +#error Attempt to include auto-generated header after including gl.h +#endif +#if defined(__glext_h_) || defined(__GLEXT_H_) +#error Attempt to include auto-generated header after including glext.h +#endif +#if defined(__gltypes_h_) +#error Attempt to include auto-generated header after gltypes.h +#endif +#if defined(__gl_ATI_h_) +#error Attempt to include auto-generated header after including glATI.h +#endif #define __glew_h__ #define __GLEW_H__ @@ -26,8 +26,6 @@ #define __gltypes_h_ #define __gl_ATI_h_ -#define __dv(v) - #ifndef APIENTRY #if defined(__MINGW32__) #ifndef WIN32_LEAN_AND_MEAN @@ -1683,19 +1681,6 @@ extern void (CODEGEN_FUNCPTR *_ptrc_glVertexAttribP4ui)(GLuint index, GLenum typ extern void (CODEGEN_FUNCPTR *_ptrc_glVertexAttribP4uiv)(GLuint index, GLenum type, GLboolean normalized, const GLuint * value); #define glVertexAttribP4uiv _ptrc_glVertexAttribP4uiv -extern void (CODEGEN_FUNCPTR *_ptrc_cudaMalloc)(void **p, size_t s); -#define cudaMalloc _ptrc_cudaMalloc -extern void (CODEGEN_FUNCPTR *_ptrc_cudaGraphicsUnmapResources)(int count, cudaGraphicsResource_t *resources, cudaStream_t stream); -#define cudaGraphicsUnmapResources _ptrc_cudaGraphicsUnmapResources -extern void (CODEGEN_FUNCPTR *_ptrc_cudaDeviceReset)(void); -#define cudaDeviceReset _ptrc_cudaDeviceReset -extern void (CODEGEN_FUNCPTR *_ptrc_cudaGraphicsGLRegisterBuffer)(struct cudaGraphicsResource **resource, GLuint buffer, unsigned int flags); -#define cudaGraphicsGLRegisterBuffer _ptrc_cudaGraphicsGLRegisterBuffer -extern void (CODEGEN_FUNCPTR *_ptrc_cudaGraphicsMapResources)(int count, cudaGraphicsResource_t *resources, cudaStream_t stream __dv(0)); -#define cudaGraphicsMapResources _ptrc_cudaGraphicsMapResources -extern void (CODEGEN_FUNCPTR *_ptrc_cudaGraphicsResourceGetMappedPointer)(void **devPtr, size_t *size, cudaGraphicsResource_t resource); -#define cudaGraphicsResourceGetMappedPointer _ptrc_cudaGraphicsResourceGetMappedPointer - enum ogl_LoadStatus { ogl_LOAD_FAILED = 0, diff --git a/src/plugin/common/gl_screen.c b/src/plugin/common/gl_screen.c index 6786218..cb7896a 100644 --- a/src/plugin/common/gl_screen.c +++ b/src/plugin/common/gl_screen.c @@ -3,12 +3,6 @@ #include #include -#include - -#include -#include -#include -#include #ifdef GLES #include @@ -23,10 +17,7 @@ #endif static GLuint program; -static GLuint vbo; -struct cudaGraphicsResource *cuda_vbo_resource; -void *d_vbo_buffer = NULL; - +static GLuint vao; static GLuint texture = 1; static GLuint depth_texture = 2; @@ -36,8 +27,6 @@ static GLint depthValueTextureLocation; static int32_t tex_width; static int32_t tex_height; -static float g_fAnim = 0.0; - static int32_t tex_display_height; #ifdef _DEBUG @@ -85,12 +74,6 @@ static void gl_check_errors(void) #define gl_check_errors(...) #endif -static void gl_check_error_handle_cuda(void) { - gl_check_errors(); - cudaDeviceReset(); - exit(EXIT_FAILURE); -} - static GLuint gl_shader_compile(GLenum type, const GLchar* source) { GLuint shader = glCreateShader(type); @@ -186,12 +169,9 @@ void gl_screen_init(struct rdp_config* config) glUniform1i(depthValueTextureLocation, 0); glUniform1i(colorValueTextureLocation, 1); - // create VBO - cudaMalloc((void **)&d_vbo_buffer, tex_width*tex_height * 4 * sizeof(float)); - // prepare dummy VAO - glGenVertexArrays(1, &vbo); - glBindVertexArray(vbo); + glGenVertexArrays(1, &vao); + glBindVertexArray(vao); // select interpolation method GLint filter; @@ -224,39 +204,10 @@ void gl_screen_init(struct rdp_config* config) gl_check_errors(); } -void runCuda(struct cudaGraphicsResource **vbo_resource, struct rdp_frame_buffer* fb) -{ - // map OpenGL buffer object for writing from CUDA - float4 *dptr; - cudaGraphicsMapResources(1, vbo_resource, 0); - size_t num_bytes; - cudaGraphicsResourceGetMappedPointer((void **)&dptr, &num_bytes, *vbo_resource); - //printf("CUDA mapped VBO: May access %ld bytes\n", num_bytes); - - // execute the kernel - // dim3 block(8, 8, 1); - // dim3 grid(mesh_width / block.x, mesh_height / block.y, 1); - // kernel<<< grid, block>>>(dptr, mesh_width, mesh_height, g_fAnim); - - //launch_kernel(dptr, fb->width, fb->height, g_fAnim); - - // unmap buffer object - cudaGraphicsUnmapResources(1, vbo_resource, 0); -} - bool gl_screen_write(struct rdp_frame_buffer* fb, int32_t output_height) { bool buffer_size_changed = tex_width != fb->width || tex_height != fb->height; - // create buffer object - glGenBuffers(1, vbo); - glBindBuffer(GL_ARRAY_BUFFER, vbo); - - // initialize buffer object - GLuint size = tex_width * tex_height * 4 * sizeof(float); - glBufferData(GL_ARRAY_BUFFER, size, 0, GL_DYNAMIC_DRAW); - glBindBuffer(GL_ARRAY_BUFFER, 0); - // check if the framebuffer size has changed if (buffer_size_changed) { tex_width = fb->width; @@ -311,13 +262,6 @@ bool gl_screen_write(struct rdp_frame_buffer* fb, int32_t output_height) } - // register this buffer object with CUDA - cudaGraphicsGLRegisterBuffer(&cuda_vbo_resource, vbo, (unsigned int)-1); - // run CUDA - runCuda(&cuda_vbo_resource, fb); - // check if there was an error when using any of the commands above - gl_check_errors(); - // update output size tex_display_height = output_height; @@ -393,6 +337,6 @@ void gl_screen_close(void) tex_display_height = 0; glDeleteTextures(1, &texture); - glDeleteVertexArrays(1, &vbo); + glDeleteVertexArrays(1, &vao); glDeleteProgram(program); } From d44a9daecb55ff8c1282242ead571f663258d384 Mon Sep 17 00:00:00 2001 From: Aubrey Buchanan Date: Mon, 13 Nov 2023 18:41:04 -0800 Subject: [PATCH 55/55] Whitespace corrections --- src/core/rdp/vi.c | 12 +-- src/plugin/common/gl_screen.c | 152 +++++++++++++++++----------------- 2 files changed, 80 insertions(+), 84 deletions(-) diff --git a/src/core/rdp/vi.c b/src/core/rdp/vi.c index f697a7b..d088a3c 100644 --- a/src/core/rdp/vi.c +++ b/src/core/rdp/vi.c @@ -116,7 +116,7 @@ static void vi_init(void) vi_restore_init(); memset(prescale, 0, sizeof(prescale)); - memset(prescale_depth, 0, sizeof(prescale_depth)); + memset(prescale_depth, 0, sizeof(prescale_depth)); prevvicurrent = 0; emucontrolsvicurrent = -1; @@ -547,7 +547,7 @@ static void vi_process_end(void) fb.height = (ispal ? V_RES_PAL : V_RES_NTSC) >> !ctrl.serrate; output_height = V_RES_NTSC; } - + if (config.vi.widescreen) { output_height = output_height * 3 / 4; } @@ -600,13 +600,13 @@ static void vi_process_fast(uint32_t worker_id) int32_t line = y * vi_width_low; uint32_t* dst = prescale + y * hres_raw; uint32_t* d = prescale_depth + y * hres_raw; - + for (x = 0; x < hres_raw; x++) { uint32_t r, g, b, zr, zg, zb; switch (config.vi.mode) { case VI_MODE_COLOR: - zr = zg = zb = rdram_read_idx16((rdp_states[worker_id].zb_address >> 1) + line + x) >> 8; + zr = zg = zb = rdram_read_idx16((rdp_states[worker_id].zb_address >> 1) + line + x) >> 8; switch (ctrl.type) { case VI_TYPE_RGBA5551: { uint16_t pix = rdram_read_idx16((frame_buffer >> 1) + line + x); @@ -640,7 +640,7 @@ static void vi_process_fast(uint32_t worker_id) uint16_t pix; rdram_read_pair16(&pix, &hval, (frame_buffer >> 1) + line + x); r = g = b = (((pix & 1) << 2) | hval) << 5; - zr = zg = zb = rdram_read_idx16((rdp_states[0].zb_address >> 1) + line + x) >> 8; + zr = zg = zb = rdram_read_idx16((rdp_states[0].zb_address >> 1) + line + x) >> 8; break; } @@ -649,7 +649,7 @@ static void vi_process_fast(uint32_t worker_id) } gamma_filters(&r, &g, &b, ctrl, &rdp_states[worker_id].rand_vi); - gamma_filters(&zr, &zg, &zb, ctrl, &rdp_states[worker_id].rand_vi); + gamma_filters(&zr, &zg, &zb, ctrl, &rdp_states[worker_id].rand_vi); dst[x] = (b << 16) | (g << 8) | r; d[x] = (zb << 16) | (zg << 8) | zr; diff --git a/src/plugin/common/gl_screen.c b/src/plugin/common/gl_screen.c index cb7896a..08ed54b 100644 --- a/src/plugin/common/gl_screen.c +++ b/src/plugin/common/gl_screen.c @@ -141,7 +141,7 @@ void gl_screen_init(struct rdp_config* config) "in vec2 uv;\n" "layout(location = 0) out vec4 color;\n" - "uniform sampler2D ColorValueTexture;\n" + "uniform sampler2D ColorValueTexture;\n" "uniform sampler2D DepthValueTexture;\n" "void main(void) {\n" @@ -150,7 +150,7 @@ void gl_screen_init(struct rdp_config* config) #else " color.bgra = texture(ColorValueTexture, uv);\n" #endif - " gl_FragDepth = texture(DepthValueTexture, uv).r;\n" + " gl_FragDepth = texture(DepthValueTexture, uv).r;\n" "}\n"; // compile and link OpenGL program @@ -158,47 +158,47 @@ void gl_screen_init(struct rdp_config* config) GLuint frag = gl_shader_compile(GL_FRAGMENT_SHADER, frag_shader); program = gl_shader_link(vert, frag); - // get the uniform variables location - depthValueTextureLocation = glGetUniformLocation(program, "DepthValueTexture"); - colorValueTextureLocation = glGetUniformLocation(program, "ColorValueTexture"); + // get the uniform variables location + depthValueTextureLocation = glGetUniformLocation(program, "DepthValueTexture"); + colorValueTextureLocation = glGetUniformLocation(program, "ColorValueTexture"); - // specify the shader program to use + // specify the shader program to use glUseProgram(program); - // bind the uniform variables locations - glUniform1i(depthValueTextureLocation, 0); - glUniform1i(colorValueTextureLocation, 1); + // bind the uniform variables locations + glUniform1i(depthValueTextureLocation, 0); + glUniform1i(colorValueTextureLocation, 1); // prepare dummy VAO glGenVertexArrays(1, &vao); glBindVertexArray(vao); - // select interpolation method - GLint filter; - switch (config->vi.interp) { - case VI_INTERP_LINEAR: - filter = GL_LINEAR; - break; - case VI_INTERP_NEAREST: - default: - filter = GL_NEAREST; - } + // select interpolation method + GLint filter; + switch (config->vi.interp) { + case VI_INTERP_LINEAR: + filter = GL_LINEAR; + break; + case VI_INTERP_NEAREST: + default: + filter = GL_NEAREST; + } // prepare depth texture - glActiveTexture(GL_TEXTURE0 + 0); + glActiveTexture(GL_TEXTURE0 + 0); glGenTextures(1, &depth_texture); glBindTexture(GL_TEXTURE_2D, depth_texture); - // configure interpolation method - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filter); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filter); + // configure interpolation method + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filter); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filter); // prepare color texture - glActiveTexture(GL_TEXTURE0 + 1); + glActiveTexture(GL_TEXTURE0 + 1); glGenTextures(1, &texture); glBindTexture(GL_TEXTURE_2D, texture); - // configure interpolation method - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filter); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filter); + // configure interpolation method + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filter); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filter); // check if there was an error when using any of the commands above gl_check_errors(); @@ -207,59 +207,57 @@ void gl_screen_init(struct rdp_config* config) bool gl_screen_write(struct rdp_frame_buffer* fb, int32_t output_height) { bool buffer_size_changed = tex_width != fb->width || tex_height != fb->height; - + // check if the framebuffer size has changed if (buffer_size_changed) { tex_width = fb->width; tex_height = fb->height; - // select the color value binding - glActiveTexture(GL_TEXTURE0 + 1); - glBindTexture(GL_TEXTURE_2D, texture); + // select the color value binding + glActiveTexture(GL_TEXTURE0 + 1); + glBindTexture(GL_TEXTURE_2D, texture); - // set pitch for all unpacking operations - glPixelStorei(GL_UNPACK_ROW_LENGTH, fb->pitch); + // set pitch for all unpacking operations + glPixelStorei(GL_UNPACK_ROW_LENGTH, fb->pitch); // reallocate texture buffer on GPU - glDepthMask(false); + glDepthMask(false); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, tex_width, tex_height, 0, TEX_FORMAT, TEX_TYPE, fb->pixels); - glDepthMask(true); - - // select the depth value binding - glActiveTexture(GL_TEXTURE0 + 0); - glBindTexture(GL_TEXTURE_2D, depth_texture); + glDepthMask(true); - // set pitch for all unpacking operations - glPixelStorei(GL_UNPACK_ROW_LENGTH, fb->pitch); - // reallocate texture buffer on GPU - glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); - glEnable(GL_DEPTH_TEST); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, tex_width, tex_height, 0, TEX_FORMAT, TEX_TYPE, fb->depth); - glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); - glDisable(GL_DEPTH_TEST); + // select the depth value binding + glActiveTexture(GL_TEXTURE0 + 0); + glBindTexture(GL_TEXTURE_2D, depth_texture); + // set pitch for all unpacking operations + glPixelStorei(GL_UNPACK_ROW_LENGTH, fb->pitch); + // reallocate texture buffer on GPU + glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); + glEnable(GL_DEPTH_TEST); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, tex_width, tex_height, 0, TEX_FORMAT, TEX_TYPE, fb->depth); + glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); + glDisable(GL_DEPTH_TEST); msg_debug("%s: resized framebuffer texture: %dx%d", __FUNCTION__, tex_width, tex_height); } else { - // select the color value binding - glActiveTexture(GL_TEXTURE0 + 1); - glBindTexture(GL_TEXTURE_2D, texture); + // select the color value binding + glActiveTexture(GL_TEXTURE0 + 1); + glBindTexture(GL_TEXTURE_2D, texture); // copy local buffer to GPU texture buffer - glDepthMask(false); + glDepthMask(false); glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, tex_width, tex_height, TEX_FORMAT, TEX_TYPE, fb->pixels); - glDepthMask(true); - - // select the depth value binding - glActiveTexture(GL_TEXTURE0 + 0); - glBindTexture(GL_TEXTURE_2D, depth_texture); + glDepthMask(true); - // copy local buffer to GPU texture buffer - glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); - glEnable(GL_DEPTH_TEST); - glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, tex_width, tex_height, TEX_FORMAT, TEX_TYPE, fb->depth); - glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); - glDisable(GL_DEPTH_TEST); + // select the depth value binding + glActiveTexture(GL_TEXTURE0 + 0); + glBindTexture(GL_TEXTURE_2D, depth_texture); + // copy local buffer to GPU texture buffer + glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); + glEnable(GL_DEPTH_TEST); + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, tex_width, tex_height, TEX_FORMAT, TEX_TYPE, fb->depth); + glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); + glDisable(GL_DEPTH_TEST); } // update output size @@ -299,26 +297,24 @@ void gl_screen_render(int32_t win_width, int32_t win_height, int32_t win_x, int3 win_height = h_max; } - // configure viewport - glViewport(win_x, win_y, win_width, win_height); - - - // select the color value binding - glActiveTexture(GL_TEXTURE0 + 1); - glBindTexture(GL_TEXTURE_2D, texture); + // configure viewport + glViewport(win_x, win_y, win_width, win_height); - // draw - glDrawArrays(GL_TRIANGLES, 0, 3); + // select the color value binding + glActiveTexture(GL_TEXTURE0 + 1); + glBindTexture(GL_TEXTURE_2D, texture); - // select the depth value binding - glActiveTexture(GL_TEXTURE0 + 0); - glBindTexture(GL_TEXTURE_2D, depth_texture); + // draw + glDrawArrays(GL_TRIANGLES, 0, 3); - // draw - glEnable(GL_DEPTH_TEST); - glDrawArrays(GL_TRIANGLES, 0, 3); - glDisable(GL_DEPTH_TEST); + // select the depth value binding + glActiveTexture(GL_TEXTURE0 + 0); + glBindTexture(GL_TEXTURE_2D, depth_texture); + // draw + glEnable(GL_DEPTH_TEST); + glDrawArrays(GL_TRIANGLES, 0, 3); + glDisable(GL_DEPTH_TEST); // check if there was an error when using any of the commands above gl_check_errors();