From 3334a71f620f547ded01eca178c6fce1cb0e45d0 Mon Sep 17 00:00:00 2001 From: Ehbw Date: Thu, 1 Jan 2026 17:59:44 +0000 Subject: [PATCH] feat(nui): game capture rework --- .../components/nui-core/include/EpoxyScript.h | 341 ++++++++++ code/components/nui-core/src/NUIApp.cpp | 7 +- code/components/nui-core/src/NUIClient.cpp | 18 + .../rage-graphics-five/src/RenderHooks.cpp | 629 ++++++++++-------- .../rage-graphics-rdr3/include/D3D12Helper.h | 52 ++ .../rage-graphics-rdr3/include/DrawCommands.h | 5 +- .../rage-graphics-rdr3/include/VulkanHelper.h | 112 +++- .../rage-graphics-rdr3/include/grcTexture.h | 31 +- .../rage-graphics-rdr3/src/CefGameCapture.cpp | 569 ++++++++++++++++ .../rage-graphics-rdr3/src/GfxSpec.cpp | 17 + 10 files changed, 1509 insertions(+), 272 deletions(-) create mode 100644 code/components/nui-core/include/EpoxyScript.h create mode 100644 code/components/rage-graphics-rdr3/include/D3D12Helper.h create mode 100644 code/components/rage-graphics-rdr3/src/CefGameCapture.cpp diff --git a/code/components/nui-core/include/EpoxyScript.h b/code/components/nui-core/include/EpoxyScript.h new file mode 100644 index 0000000000..1ea134fc4d --- /dev/null +++ b/code/components/nui-core/include/EpoxyScript.h @@ -0,0 +1,341 @@ +#pragma once + +#include + +static std::string g_epoxyScript = R"( +// Replace type="application/x-cfx-game-view" with improved canvas painting +class CfxGameViewRenderer { + #gl; + #texture; + #animationFrame; + + constructor(canvas) { + const gl = canvas.getContext('webgl', { + antialias: false, + depth: false, + alpha: false, + stencil: false, + desynchronized: true, + powerPreference: 'high-performance', + }); + + if (!gl) { + throw new Error('Failed to acquire webgl context for GameViewRenderer'); + } + + this.#gl = gl; + + this.#texture = this.#createTexture(gl); + const { program, vloc, tloc } = this.#createProgram(gl); + const { vertexBuff, texBuff } = this.#createBuffers(gl); + + gl.useProgram(program); + + gl.bindTexture(gl.TEXTURE_2D, this.#texture); + + gl.uniform1i(gl.getUniformLocation(program, "external_texture"), 0); + + gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuff); + gl.vertexAttribPointer(vloc, 2, gl.FLOAT, false, 0, 0); + gl.enableVertexAttribArray(vloc); + + gl.bindBuffer(gl.ARRAY_BUFFER, texBuff); + gl.vertexAttribPointer(tloc, 2, gl.FLOAT, false, 0, 0); + gl.enableVertexAttribArray(tloc); + + this.#render(); + } + + #compileAndLinkShaders(gl, program, vs, fs) { + gl.compileShader(vs); + gl.compileShader(fs); + + gl.linkProgram(program); + + if (gl.getProgramParameter(program, gl.LINK_STATUS)) + { + return; + } + + console.error('Link failed:', gl.getProgramInfoLog(program)); + console.error('vs log:', gl.getShaderInfoLog(vs)); + console.error('fs log:', gl.getShaderInfoLog(fs)); + + throw new Error('Failed to compile shaders'); + } + + #attachShader(gl, program, type, src) { + const shader = gl.createShader(type); + + gl.shaderSource(shader, src); + gl.attachShader(program, shader); + + return shader; + } + + #createProgram(gl) { + const program = gl.createProgram(); + + const vertexShaderSrc = ` + attribute vec2 a_position; + attribute vec2 a_texcoord; + uniform mat3 u_matrix; + varying vec2 textureCoordinate; + void main() { + gl_Position = vec4(a_position, 0.0, 1.0); + textureCoordinate = a_texcoord; + } + `; + + const fragmentShaderSrc = ` + varying highp vec2 textureCoordinate; + uniform sampler2D external_texture; + void main() + { + gl_FragColor = texture2D(external_texture, textureCoordinate); + } + `; + + const vertexShader = this.#attachShader(gl, program, gl.VERTEX_SHADER, vertexShaderSrc); + const fragmentShader = this.#attachShader(gl, program, gl.FRAGMENT_SHADER, fragmentShaderSrc); + + this.#compileAndLinkShaders(gl, program, vertexShader, fragmentShader); + + gl.useProgram(program); + + const vloc = gl.getAttribLocation(program, "a_position"); + const tloc = gl.getAttribLocation(program, "a_texcoord"); + + return { program, vloc, tloc }; + } + + #createTexture(gl) { + const tex = gl.createTexture(); + + const texPixels = new Uint8Array([0, 0, 255, 255]); + + gl.bindTexture(gl.TEXTURE_2D, tex); + gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, texPixels); + + gl.texParameterf(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); + gl.texParameterf(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); + gl.texParameterf(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); + + // Magic hook sequence + gl.texParameterf(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); + gl.texParameterf(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.MIRRORED_REPEAT); + gl.texParameterf(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.REPEAT); + + // Reset + gl.texParameterf(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); + + return tex; + } + + #createBuffers(gl) { + const vertexBuff = gl.createBuffer(); + gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuff); + gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([ + -1, -1, + 1, -1, + -1, 1, + 1, 1, + ]), gl.STATIC_DRAW); + + const texBuff = gl.createBuffer(); + gl.bindBuffer(gl.ARRAY_BUFFER, texBuff); + gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([ + 0, 1, + 1, 1, + 0, 0, + 1, 0, + ]), gl.STATIC_DRAW); + + return { vertexBuff, texBuff }; + } + + resize(width, height) { + this.#gl.viewport(0, 0, width, height); + this.#gl.canvas.width = width; + this.#gl.canvas.height = height; + } + + destroy() { + if (this.#animationFrame) { + cancelAnimationFrame(this.#animationFrame); + } + this.#texture = null; + } + + #render = () => { + const gl = this.#gl; + if (gl) + { + gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4); + } + this.#animationFrame = requestAnimationFrame(this.#render); + }; +} + +let __cfx_game_view = { + +ReplaceGameView: function(obj, cb) +{ + // don't replace an object if its marked as being replaced already. + if (obj.hasAttribute("cfx-game-view-compatibility")) + { + return obj; + } + + const canvas = document.createElement('canvas'); + if (obj.id) + { + canvas.id = obj.id; + } + if (obj.className) + { + canvas.className = obj.className; + } + + // copy width and height attributes seperately + if (obj.hasAttribute('width') && parseInt(obj.getAttribute('width'))) + { + canvas.style.width = obj.getAttribute('width'); + } + + if (obj.hasAttribute('height') && parseInt(obj.getAttribute('height'))) + { + canvas.style.height = obj.getAttribute('height'); + } + + // Clearly indicate that its been replaced + canvas.setAttribute("cfx-game-view-compatibility", true); + + Array.from(obj.attributes).forEach(attr => { + let name = attr.name.toLowerCase() + if (name !== "type" && name !== "width" && name != "height") + { + canvas.setAttribute(attr.name, attr.value); + } + }); + + if (!canvas.width || !canvas.height || !canvas.style.width || !canvas.style.height) + { + const cs = window.getComputedStyle(obj); + const w = cs.width; + const h = cs.height; + if (w && h) + { + canvas.width = w; + canvas.height = h; + canvas.style.width = cs.width; + canvas.style.height = cs.height; + } + } + + // replace in DOM + obj.parentNode && obj.parentNode.replaceChild(canvas, obj); + + // Initalize GameViewRender + if (cb) + { + cb(canvas, obj) + } + return canvas; +}, + +FindLegacyGameView: function(cb) { + const objects = Array.from(document.querySelectorAll('[type="application/x-cfx-game-view"]')); + objects.map(obj => this.ReplaceGameView(obj, cb)); +}, + +CreateCanvasRenderer: function(canvas) +{ + const renderer = new CfxGameViewRenderer(canvas); + const resizeObserver = new ResizeObserver(() => { + renderer.resize(canvas.clientWidth, canvas.clientHeight); + }); + + resizeObserver.observe(canvas); + canvas.addEventListener('remove', () => { + renderer.destroy(); + resizeObserver.disconnect(); + }); +} +}; + +// Account for DX -> GL coordinate conversion. +const targetComparsion = new Float32Array([ + 0, 0, + 1, 0, + 0, 1, + 1, 1, +]); + +const newArrayData = new Float32Array([ + 0, 1, + 1, 1, + 0, 0, + 1, 0, +]); + +const originalBufferData = WebGLRenderingContext.prototype.bufferData; +WebGLRenderingContext.prototype.bufferData = function(target, data, usage) { + if (!(data instanceof Float32Array) || target != 0x8892 /*ARRAY_BUFFER*/ || usage != 0x88E4 /*STATIC_DRAW*/) + { + return originalBufferData.call(this, target, data, usage); + } + + const areBuffersEqual = (data) => { + if (data.length != targetComparsion.length) + { + return false; + } + + for (let i = 0; i < data.length; i++) + { + if (data[i] != targetComparsion[i]) + { + return false; + } + } + + return true; + } + + if (areBuffersEqual(data)) + { + return originalBufferData.call(this, target, newArrayData, usage); + } + + return originalBufferData.call(this, target, data, usage); +} + +const originalReadPixels = WebGLRenderingContext.prototype.readPixels; +WebGLRenderingContext.prototype.readPixels = function(x, y, width, height, format, type, pixels) { + const result = originalReadPixels.apply(this, arguments); + + // screenshot-basic/three.js game-view compatability. + if (x != 0 || y != 0 || width != window.innerWidth || height != window.innerHeight || format != 6408/*GL_RGBA*/ + || type != 5121/*GL_UNSIGNED_BYTE*/ || pixels.length != (width * height * 4 /*RGBA*/)) + { + return result; + } + + const framebuffer = this.getParameter(this.FRAMEBUFFER_BINDING); + if (framebuffer) { + const rowSize = width * 4; + const tempRow = new Uint8Array(rowSize); + for (let row = 0; row < Math.floor(height / 2); row++) { + const topOffset = row * rowSize; + const bottomOffset = (height - 1 - row) * rowSize; + + tempRow.set(pixels.subarray(topOffset, topOffset + rowSize)); + pixels.copyWithin(topOffset, bottomOffset, bottomOffset + rowSize); + pixels.set(tempRow, bottomOffset); + } + } + + return result; +}; +)"; diff --git a/code/components/nui-core/src/NUIApp.cpp b/code/components/nui-core/src/NUIApp.cpp index f9d8b2ef1b..1af1f60c90 100644 --- a/code/components/nui-core/src/NUIApp.cpp +++ b/code/components/nui-core/src/NUIApp.cpp @@ -14,6 +14,7 @@ #include #include +#include #include #include @@ -128,6 +129,10 @@ void NUIApp::OnContextCreated(CefRefPtr browser, CefRefPtr window->SetValue("nuiTargetGameBuild", CefV8Value::CreateInt(xbr::GetRequestedGameBuild()), V8_PROPERTY_ATTRIBUTE_READONLY); window->SetValue("nuiTargetGamePureLevel", CefV8Value::CreateInt(fx::client::GetPureLevel()), V8_PROPERTY_ATTRIBUTE_READONLY); + if (auto parent = frame->GetParent(); parent && parent->IsMain()) + { + frame->ExecuteJavaScript(g_epoxyScript, "nui://epoxy", 0); + } // FxDK API { @@ -224,7 +229,7 @@ void NUIApp::OnBeforeCommandLineProcessing(const CefString& process_type, CefRef // register the CitizenFX game view plugin #if !GTA_NY - command_line->AppendSwitchWithValue("register-pepper-plugins", fmt::sprintf("%s;application/x-cfx-game-view", ToNarrow(MakeRelativeCitPath(L"bin\\d3d_rendering.dll")))); + //command_line->AppendSwitchWithValue("register-pepper-plugins", fmt::sprintf("%s;application/x-cfx-game-view", ToNarrow(MakeRelativeCitPath(L"bin\\d3d_rendering.dll")))); #endif } diff --git a/code/components/nui-core/src/NUIClient.cpp b/code/components/nui-core/src/NUIClient.cpp index 3d14217149..4a5bf3e0ea 100644 --- a/code/components/nui-core/src/NUIClient.cpp +++ b/code/components/nui-core/src/NUIClient.cpp @@ -156,6 +156,24 @@ Object.prototype.__defineGetter__ = function(prop, func) { } return oldDefineGetter.call(this, prop, func); }; + +const __cfx_game_view_observer = new MutationObserver(() => { + const node = document.querySelector( + '[type="application/x-cfx-game-view"]' + ); + + if (node) { + __cfx_game_view.ReplaceGameView(node, __cfx_game_view.CreateCanvasRenderer); + } +}); + +__cfx_game_view_observer.observe(document.documentElement, { + childList: true, + subtree: true +}); + +// Replace all legacy canvas's at startup. +__cfx_game_view.FindLegacyGameView(__cfx_game_view.CreateCanvasRenderer); )", "nui://patches", 0); } diff --git a/code/components/rage-graphics-five/src/RenderHooks.cpp b/code/components/rage-graphics-five/src/RenderHooks.cpp index 15d77f7985..2b9d803dc7 100644 --- a/code/components/rage-graphics-five/src/RenderHooks.cpp +++ b/code/components/rage-graphics-five/src/RenderHooks.cpp @@ -810,188 +810,186 @@ bool WrapVideoModeChange(VideoModeInfo* info) } #pragma region shaders -const BYTE quadPS[] = -{ - 68, 88, 66, 67, 189, 87, - 5, 130, 168, 148, 229, 231, - 171, 37, 224, 4, 165, 41, - 28, 80, 1, 0, 0, 0, - 84, 1, 0, 0, 3, 0, - 0, 0, 44, 0, 0, 0, - 132, 0, 0, 0, 184, 0, - 0, 0, 73, 83, 71, 78, - 80, 0, 0, 0, 2, 0, - 0, 0, 8, 0, 0, 0, - 56, 0, 0, 0, 0, 0, - 0, 0, 1, 0, 0, 0, - 3, 0, 0, 0, 0, 0, - 0, 0, 15, 0, 0, 0, - 68, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 3, 0, 0, 0, 1, 0, - 0, 0, 3, 3, 0, 0, - 83, 86, 95, 80, 79, 83, - 73, 84, 73, 79, 78, 0, - 84, 69, 88, 67, 79, 79, - 82, 68, 0, 171, 171, 171, - 79, 83, 71, 78, 44, 0, - 0, 0, 1, 0, 0, 0, - 8, 0, 0, 0, 32, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 3, 0, - 0, 0, 0, 0, 0, 0, - 15, 0, 0, 0, 83, 86, - 95, 84, 65, 82, 71, 69, - 84, 0, 171, 171, 83, 72, - 68, 82, 148, 0, 0, 0, - 64, 0, 0, 0, 37, 0, - 0, 0, 90, 0, 0, 3, - 0, 96, 16, 0, 0, 0, - 0, 0, 88, 24, 0, 4, - 0, 112, 16, 0, 0, 0, - 0, 0, 85, 85, 0, 0, - 98, 16, 0, 3, 50, 16, - 16, 0, 1, 0, 0, 0, - 101, 0, 0, 3, 242, 32, - 16, 0, 0, 0, 0, 0, - 104, 0, 0, 2, 1, 0, - 0, 0, 69, 0, 0, 9, - 242, 0, 16, 0, 0, 0, - 0, 0, 70, 16, 16, 0, - 1, 0, 0, 0, 70, 126, - 16, 0, 0, 0, 0, 0, - 0, 96, 16, 0, 0, 0, - 0, 0, 54, 0, 0, 5, - 114, 32, 16, 0, 0, 0, - 0, 0, 70, 2, 16, 0, - 0, 0, 0, 0, 54, 0, - 0, 5, 130, 32, 16, 0, - 0, 0, 0, 0, 1, 64, - 0, 0, 0, 0, 128, 63, - 62, 0, 0, 1 +const BYTE quadPS[] = { + 68, 88, 66, 67, 189, 87, + 5, 130, 168, 148, 229, 231, + 171, 37, 224, 4, 165, 41, + 28, 80, 1, 0, 0, 0, + 84, 1, 0, 0, 3, 0, + 0, 0, 44, 0, 0, 0, + 132, 0, 0, 0, 184, 0, + 0, 0, 73, 83, 71, 78, + 80, 0, 0, 0, 2, 0, + 0, 0, 8, 0, 0, 0, + 56, 0, 0, 0, 0, 0, + 0, 0, 1, 0, 0, 0, + 3, 0, 0, 0, 0, 0, + 0, 0, 15, 0, 0, 0, + 68, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 3, 0, 0, 0, 1, 0, + 0, 0, 3, 3, 0, 0, + 83, 86, 95, 80, 79, 83, + 73, 84, 73, 79, 78, 0, + 84, 69, 88, 67, 79, 79, + 82, 68, 0, 171, 171, 171, + 79, 83, 71, 78, 44, 0, + 0, 0, 1, 0, 0, 0, + 8, 0, 0, 0, 32, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 3, 0, + 0, 0, 0, 0, 0, 0, + 15, 0, 0, 0, 83, 86, + 95, 84, 65, 82, 71, 69, + 84, 0, 171, 171, 83, 72, + 68, 82, 148, 0, 0, 0, + 64, 0, 0, 0, 37, 0, + 0, 0, 90, 0, 0, 3, + 0, 96, 16, 0, 0, 0, + 0, 0, 88, 24, 0, 4, + 0, 112, 16, 0, 0, 0, + 0, 0, 85, 85, 0, 0, + 98, 16, 0, 3, 50, 16, + 16, 0, 1, 0, 0, 0, + 101, 0, 0, 3, 242, 32, + 16, 0, 0, 0, 0, 0, + 104, 0, 0, 2, 1, 0, + 0, 0, 69, 0, 0, 9, + 242, 0, 16, 0, 0, 0, + 0, 0, 70, 16, 16, 0, + 1, 0, 0, 0, 70, 126, + 16, 0, 0, 0, 0, 0, + 0, 96, 16, 0, 0, 0, + 0, 0, 54, 0, 0, 5, + 114, 32, 16, 0, 0, 0, + 0, 0, 70, 2, 16, 0, + 0, 0, 0, 0, 54, 0, + 0, 5, 130, 32, 16, 0, + 0, 0, 0, 0, 1, 64, + 0, 0, 0, 0, 128, 63, + 62, 0, 0, 1 }; -const BYTE quadVS[] = -{ - 68, 88, 66, 67, 203, 141, - 78, 146, 5, 246, 239, 246, - 166, 36, 242, 232, 80, 1, - 231, 115, 1, 0, 0, 0, - 208, 2, 0, 0, 5, 0, - 0, 0, 52, 0, 0, 0, - 128, 0, 0, 0, 180, 0, - 0, 0, 12, 1, 0, 0, - 84, 2, 0, 0, 82, 68, - 69, 70, 68, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 28, 0, 0, 0, 0, 4, - 254, 255, 0, 1, 0, 0, - 28, 0, 0, 0, 77, 105, +const BYTE quadVS[] = { + 68, 88, 66, 67, 203, 141, + 78, 146, 5, 246, 239, 246, + 166, 36, 242, 232, 80, 1, + 231, 115, 1, 0, 0, 0, + 208, 2, 0, 0, 5, 0, + 0, 0, 52, 0, 0, 0, + 128, 0, 0, 0, 180, 0, + 0, 0, 12, 1, 0, 0, + 84, 2, 0, 0, 82, 68, + 69, 70, 68, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 28, 0, 0, 0, 0, 4, + 254, 255, 0, 1, 0, 0, + 28, 0, 0, 0, 77, 105, 99, 114, 111, 115, 111, 102, - 116, 32, 40, 82, 41, 32, - 72, 76, 83, 76, 32, 83, - 104, 97, 100, 101, 114, 32, + 116, 32, 40, 82, 41, 32, + 72, 76, 83, 76, 32, 83, + 104, 97, 100, 101, 114, 32, 67, 111, 109, 112, 105, 108, - 101, 114, 32, 49, 48, 46, - 49, 0, 73, 83, 71, 78, - 44, 0, 0, 0, 1, 0, - 0, 0, 8, 0, 0, 0, - 32, 0, 0, 0, 0, 0, - 0, 0, 6, 0, 0, 0, - 1, 0, 0, 0, 0, 0, - 0, 0, 1, 1, 0, 0, - 83, 86, 95, 86, 69, 82, - 84, 69, 88, 73, 68, 0, - 79, 83, 71, 78, 80, 0, - 0, 0, 2, 0, 0, 0, - 8, 0, 0, 0, 56, 0, - 0, 0, 0, 0, 0, 0, - 1, 0, 0, 0, 3, 0, - 0, 0, 0, 0, 0, 0, - 15, 0, 0, 0, 68, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 3, 0, - 0, 0, 1, 0, 0, 0, - 3, 12, 0, 0, 83, 86, - 95, 80, 79, 83, 73, 84, - 73, 79, 78, 0, 84, 69, - 88, 67, 79, 79, 82, 68, - 0, 171, 171, 171, 83, 72, - 68, 82, 64, 1, 0, 0, - 64, 0, 1, 0, 80, 0, - 0, 0, 96, 0, 0, 4, - 18, 16, 16, 0, 0, 0, - 0, 0, 6, 0, 0, 0, - 103, 0, 0, 4, 242, 32, - 16, 0, 0, 0, 0, 0, - 1, 0, 0, 0, 101, 0, - 0, 3, 50, 32, 16, 0, - 1, 0, 0, 0, 104, 0, - 0, 2, 2, 0, 0, 0, - 54, 0, 0, 8, 194, 32, - 16, 0, 0, 0, 0, 0, - 2, 64, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 128, 63, 1, 0, 0, 7, - 18, 0, 16, 0, 0, 0, - 0, 0, 10, 16, 16, 0, - 0, 0, 0, 0, 1, 64, - 0, 0, 1, 0, 0, 0, - 85, 0, 0, 7, 130, 0, - 16, 0, 0, 0, 0, 0, - 10, 16, 16, 0, 0, 0, - 0, 0, 1, 64, 0, 0, - 1, 0, 0, 0, 86, 0, - 0, 5, 50, 0, 16, 0, - 0, 0, 0, 0, 198, 0, - 16, 0, 0, 0, 0, 0, - 0, 0, 0, 10, 50, 0, - 16, 0, 1, 0, 0, 0, - 70, 0, 16, 0, 0, 0, - 0, 0, 2, 64, 0, 0, - 0, 0, 0, 191, 0, 0, - 0, 191, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 8, 66, 0, 16, 0, - 0, 0, 0, 0, 26, 0, - 16, 128, 65, 0, 0, 0, - 0, 0, 0, 0, 1, 64, - 0, 0, 0, 0, 128, 63, - 54, 0, 0, 5, 50, 32, - 16, 0, 1, 0, 0, 0, - 134, 0, 16, 0, 0, 0, - 0, 0, 0, 0, 0, 7, - 18, 32, 16, 0, 0, 0, - 0, 0, 10, 0, 16, 0, - 1, 0, 0, 0, 10, 0, - 16, 0, 1, 0, 0, 0, - 56, 0, 0, 7, 34, 32, - 16, 0, 0, 0, 0, 0, - 26, 0, 16, 0, 1, 0, - 0, 0, 1, 64, 0, 0, - 0, 0, 0, 192, 62, 0, - 0, 1, 83, 84, 65, 84, - 116, 0, 0, 0, 10, 0, - 0, 0, 2, 0, 0, 0, - 0, 0, 0, 0, 3, 0, - 0, 0, 4, 0, 0, 0, - 0, 0, 0, 0, 2, 0, - 0, 0, 1, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 2, 0, 0, 0, - 0, 0, 0, 0, 1, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0 + 101, 114, 32, 49, 48, 46, + 49, 0, 73, 83, 71, 78, + 44, 0, 0, 0, 1, 0, + 0, 0, 8, 0, 0, 0, + 32, 0, 0, 0, 0, 0, + 0, 0, 6, 0, 0, 0, + 1, 0, 0, 0, 0, 0, + 0, 0, 1, 1, 0, 0, + 83, 86, 95, 86, 69, 82, + 84, 69, 88, 73, 68, 0, + 79, 83, 71, 78, 80, 0, + 0, 0, 2, 0, 0, 0, + 8, 0, 0, 0, 56, 0, + 0, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 3, 0, + 0, 0, 0, 0, 0, 0, + 15, 0, 0, 0, 68, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 3, 0, + 0, 0, 1, 0, 0, 0, + 3, 12, 0, 0, 83, 86, + 95, 80, 79, 83, 73, 84, + 73, 79, 78, 0, 84, 69, + 88, 67, 79, 79, 82, 68, + 0, 171, 171, 171, 83, 72, + 68, 82, 64, 1, 0, 0, + 64, 0, 1, 0, 80, 0, + 0, 0, 96, 0, 0, 4, + 18, 16, 16, 0, 0, 0, + 0, 0, 6, 0, 0, 0, + 103, 0, 0, 4, 242, 32, + 16, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 101, 0, + 0, 3, 50, 32, 16, 0, + 1, 0, 0, 0, 104, 0, + 0, 2, 2, 0, 0, 0, + 54, 0, 0, 8, 194, 32, + 16, 0, 0, 0, 0, 0, + 2, 64, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 128, 63, 1, 0, 0, 7, + 18, 0, 16, 0, 0, 0, + 0, 0, 10, 16, 16, 0, + 0, 0, 0, 0, 1, 64, + 0, 0, 1, 0, 0, 0, + 85, 0, 0, 7, 130, 0, + 16, 0, 0, 0, 0, 0, + 10, 16, 16, 0, 0, 0, + 0, 0, 1, 64, 0, 0, + 1, 0, 0, 0, 86, 0, + 0, 5, 50, 0, 16, 0, + 0, 0, 0, 0, 198, 0, + 16, 0, 0, 0, 0, 0, + 0, 0, 0, 10, 50, 0, + 16, 0, 1, 0, 0, 0, + 70, 0, 16, 0, 0, 0, + 0, 0, 2, 64, 0, 0, + 0, 0, 0, 191, 0, 0, + 0, 191, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 8, 66, 0, 16, 0, + 0, 0, 0, 0, 26, 0, + 16, 128, 65, 0, 0, 0, + 0, 0, 0, 0, 1, 64, + 0, 0, 0, 0, 128, 63, + 54, 0, 0, 5, 50, 32, + 16, 0, 1, 0, 0, 0, + 134, 0, 16, 0, 0, 0, + 0, 0, 0, 0, 0, 7, + 18, 32, 16, 0, 0, 0, + 0, 0, 10, 0, 16, 0, + 1, 0, 0, 0, 10, 0, + 16, 0, 1, 0, 0, 0, + 56, 0, 0, 7, 34, 32, + 16, 0, 0, 0, 0, 0, + 26, 0, 16, 0, 1, 0, + 0, 0, 1, 64, 0, 0, + 0, 0, 0, 192, 62, 0, + 0, 1, 83, 84, 65, 84, + 116, 0, 0, 0, 10, 0, + 0, 0, 2, 0, 0, 0, + 0, 0, 0, 0, 3, 0, + 0, 0, 4, 0, 0, 0, + 0, 0, 0, 0, 2, 0, + 0, 0, 1, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 2, 0, 0, 0, + 0, 0, 0, 0, 1, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0 }; #pragma endregion @@ -1035,7 +1033,9 @@ static auto GetInvariantD3D11DeviceContext() return realDeviceContext; } -void RenderBufferToBuffer(ID3D11RenderTargetView* rtv, int width = 0, int height = 0) +// For the profiler, the RTV it gets copied to is 1/4th of the game's resolution +// on top of this the texture does need to be flipped here rather then in CEF. +static void RenderBufferToBufferScreenshot(ID3D11RenderTargetView* rtv, int width = 0, int height = 0) { static auto didCallCrashometry = ([]() { @@ -1055,7 +1055,148 @@ void RenderBufferToBuffer(ID3D11RenderTargetView* rtv, int width = 0, int height } } - // guess what we can't just CopyResource, so time for copy/pasted D3D11 garbage + if (!backBuf) + { + return; + } + + WRL::ComPtr realSrvUnk; + WRL::ComPtr realSrv; + + backBuf->m_srv2->QueryInterface(IID_PPV_ARGS(&realSrvUnk)); + realSrvUnk.As(&realSrv); + + auto realDevice = GetInvariantD3D11Device(); + auto realDeviceContext = GetInvariantD3D11DeviceContext(); + if (!realDevice) + { + return; + } + + auto m_width = resDesc.Width; + auto m_height = resDesc.Height; + + static ID3D11BlendState* bs; + static ID3D11SamplerState* ss; + static ID3D11VertexShader* vs; + static ID3D11PixelShader* ps; + + static std::once_flag of; + std::call_once(of, [&realDevice]() + { + D3D11_SAMPLER_DESC sd = CD3D11_SAMPLER_DESC(CD3D11_DEFAULT()); + realDevice->CreateSamplerState(&sd, &ss); + + D3D11_BLEND_DESC bd = CD3D11_BLEND_DESC(CD3D11_DEFAULT()); + bd.RenderTarget[0].BlendEnable = FALSE; + + realDevice->CreateBlendState(&bd, &bs); + + realDevice->CreateVertexShader(quadVS, sizeof(quadVS), nullptr, &vs); + realDevice->CreatePixelShader(quadPS, sizeof(quadPS), nullptr, &ps); + }); + + WRL::ComPtr pPerf = NULL; + realDeviceContext->QueryInterface(IID_PPV_ARGS(&pPerf)); + + if (pPerf) + { + pPerf->BeginEvent(L"DrawRenderTexture"); + } + + auto deviceContext = realDeviceContext; + + WRL::ComPtr oldRtv; + WRL::ComPtr oldDsv; + deviceContext->OMGetRenderTargets(1, &oldRtv, &oldDsv); + + WRL::ComPtr oldSs; + WRL::ComPtr oldBs; + WRL::ComPtr oldPs; + WRL::ComPtr oldVs; + WRL::ComPtr oldSrv; + + D3D11_VIEWPORT oldVp; + UINT numVPs = 1; + + deviceContext->RSGetViewports(&numVPs, &oldVp); + + CD3D11_VIEWPORT vp = CD3D11_VIEWPORT(0.0f, 0.0f, width ? width : m_width, height ? height : m_height); + deviceContext->RSSetViewports(1, &vp); + + deviceContext->OMGetBlendState(&oldBs, nullptr, nullptr); + + deviceContext->PSGetShader(&oldPs, nullptr, nullptr); + deviceContext->PSGetSamplers(0, 1, &oldSs); + deviceContext->PSGetShaderResources(0, 1, &oldSrv); + + deviceContext->VSGetShader(&oldVs, nullptr, nullptr); + + deviceContext->OMSetRenderTargets(1, &rtv, nullptr); + deviceContext->OMSetBlendState(bs, nullptr, 0xffffffff); + + ID3D11ShaderResourceView* srvs[] = { + realSrv.Get() + }; + + deviceContext->PSSetShader(ps, nullptr, 0); + deviceContext->PSSetSamplers(0, 1, &ss); + deviceContext->PSSetShaderResources(0, 1, srvs); + + deviceContext->VSSetShader(vs, nullptr, 0); + + D3D11_PRIMITIVE_TOPOLOGY oldTopo; + deviceContext->IAGetPrimitiveTopology(&oldTopo); + + ID3D11InputLayout* oldLayout; + deviceContext->IAGetInputLayout(&oldLayout); + + deviceContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP); + deviceContext->IASetInputLayout(nullptr); + + FLOAT blank[] = { 0.0f, 0.0f, 0.0f, 1.0f }; + deviceContext->ClearRenderTargetView(rtv, blank); + + deviceContext->Draw(4, 0); + + deviceContext->OMSetRenderTargets(1, oldRtv.GetAddressOf(), oldDsv.Get()); + + deviceContext->IASetPrimitiveTopology(oldTopo); + deviceContext->IASetInputLayout(oldLayout); + + deviceContext->VSSetShader(oldVs.Get(), nullptr, 0); + deviceContext->PSSetShader(oldPs.Get(), nullptr, 0); + deviceContext->PSSetSamplers(0, 1, oldSs.GetAddressOf()); + deviceContext->PSSetShaderResources(0, 1, oldSrv.GetAddressOf()); + deviceContext->OMSetBlendState(oldBs.Get(), nullptr, 0xffffffff); + deviceContext->RSSetViewports(1, &oldVp); + + if (pPerf) + { + pPerf->EndEvent(); + } +} + +void RenderBufferToBuffer(ID3D11RenderTargetView* rtv, int width = 0, int height = 0) +{ + static auto didCallCrashometry = ([]() + { + AddCrashometry("did_render_backbuf", "true"); + + return true; + })(); + + D3D11_TEXTURE2D_DESC resDesc = { 0 }; + auto backBuf = GetBackbuf(); + + if (backBuf) + { + if (backBuf->texture) + { + ((ID3D11Texture2D*)backBuf->texture)->GetDesc(&resDesc); + } + } + if (backBuf) { WRL::ComPtr realSrvUnk; @@ -1074,29 +1215,6 @@ void RenderBufferToBuffer(ID3D11RenderTargetView* rtv, int width = 0, int height auto m_width = resDesc.Width; auto m_height = resDesc.Height; - // - // LOTS of D3D11 garbage to flip a texture... - // - static ID3D11BlendState* bs; - static ID3D11SamplerState* ss; - static ID3D11VertexShader* vs; - static ID3D11PixelShader* ps; - - static std::once_flag of; - std::call_once(of, [&realDevice]() - { - D3D11_SAMPLER_DESC sd = CD3D11_SAMPLER_DESC(CD3D11_DEFAULT()); - realDevice->CreateSamplerState(&sd, &ss); - - D3D11_BLEND_DESC bd = CD3D11_BLEND_DESC(CD3D11_DEFAULT()); - bd.RenderTarget[0].BlendEnable = FALSE; - - realDevice->CreateBlendState(&bd, &bs); - - realDevice->CreateVertexShader(quadVS, sizeof(quadVS), nullptr, &vs); - realDevice->CreatePixelShader(quadPS, sizeof(quadPS), nullptr, &ps); - }); - WRL::ComPtr pPerf = NULL; realDeviceContext->QueryInterface(IID_PPV_ARGS(&pPerf)); @@ -1105,73 +1223,60 @@ void RenderBufferToBuffer(ID3D11RenderTargetView* rtv, int width = 0, int height pPerf->BeginEvent(L"DrawRenderTexture"); } - auto deviceContext = realDeviceContext; - - WRL::ComPtr oldRtv; - WRL::ComPtr oldDsv; - deviceContext->OMGetRenderTargets(1, &oldRtv, &oldDsv); - - WRL::ComPtr oldSs; - WRL::ComPtr oldBs; - WRL::ComPtr oldPs; - WRL::ComPtr oldVs; - WRL::ComPtr oldSrv; - - D3D11_VIEWPORT oldVp; - UINT numVPs = 1; - - deviceContext->RSGetViewports(&numVPs, &oldVp); - - CD3D11_VIEWPORT vp = CD3D11_VIEWPORT(0.0f, 0.0f, width ? width : m_width, height ? height : m_height); - deviceContext->RSSetViewports(1, &vp); - - deviceContext->OMGetBlendState(&oldBs, nullptr, nullptr); - - deviceContext->PSGetShader(&oldPs, nullptr, nullptr); - deviceContext->PSGetSamplers(0, 1, &oldSs); - deviceContext->PSGetShaderResources(0, 1, &oldSrv); - - deviceContext->VSGetShader(&oldVs, nullptr, nullptr); - - deviceContext->OMSetRenderTargets(1, &rtv, nullptr); - deviceContext->OMSetBlendState(bs, nullptr, 0xffffffff); - - ID3D11ShaderResourceView* srvs[] = + WRL::ComPtr srcRes; + backBuf->m_srv2->GetResource(srcRes.GetAddressOf()); + if (!srcRes) { - realSrv.Get() - }; - - deviceContext->PSSetShader(ps, nullptr, 0); - deviceContext->PSSetSamplers(0, 1, &ss); - deviceContext->PSSetShaderResources(0, 1, srvs); + return; + } - deviceContext->VSSetShader(vs, nullptr, 0); + WRL::ComPtr srcTex; + if (FAILED(srcRes.As(&srcTex))) + { + return; + } - D3D11_PRIMITIVE_TOPOLOGY oldTopo; - deviceContext->IAGetPrimitiveTopology(&oldTopo); + WRL::ComPtr dstRes; + rtv->GetResource(&dstRes); + if (!dstRes) + { + return; + } - ID3D11InputLayout* oldLayout; - deviceContext->IAGetInputLayout(&oldLayout); + WRL::ComPtr dstTex; + if (FAILED(dstRes.As(&dstTex))) + { + return; + } - deviceContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP); - deviceContext->IASetInputLayout(nullptr); + D3D11_TEXTURE2D_DESC srcDesc, dstDesc; + srcTex->GetDesc(&srcDesc); + dstTex->GetDesc(&dstDesc); - FLOAT blank[] = { 0.0f, 0.0f, 0.0f, 1.0f }; - deviceContext->ClearRenderTargetView(rtv, blank); + if (srcDesc.Format != dstDesc.Format) + { + return; + } - deviceContext->Draw(4, 0); + if (srcDesc.Width != dstDesc.Width || srcDesc.Height != dstDesc.Height || srcDesc.Format != dstDesc.Format) + { + return; + } - deviceContext->OMSetRenderTargets(1, oldRtv.GetAddressOf(), oldDsv.Get()); + realDeviceContext->CopyResource(dstTex.Get(), srcTex.Get()); - deviceContext->IASetPrimitiveTopology(oldTopo); - deviceContext->IASetInputLayout(oldLayout); + static ID3D11Query* copyQuery = nullptr; + if (!copyQuery) + { + D3D11_QUERY_DESC qd{}; + qd.Query = D3D11_QUERY_EVENT; + realDevice->CreateQuery(&qd, ©Query); + } - deviceContext->VSSetShader(oldVs.Get(), nullptr, 0); - deviceContext->PSSetShader(oldPs.Get(), nullptr, 0); - deviceContext->PSSetSamplers(0, 1, oldSs.GetAddressOf()); - deviceContext->PSSetShaderResources(0, 1, oldSrv.GetAddressOf()); - deviceContext->OMSetBlendState(oldBs.Get(), nullptr, 0xffffffff); - deviceContext->RSSetViewports(1, &oldVp); + if (copyQuery) + { + realDeviceContext->End(copyQuery); + } if (pPerf) { @@ -1276,7 +1381,7 @@ void CaptureInternalScreenshot() return; } - RenderBufferToBuffer(rtv, resDesc.Width / 4, resDesc.Height / 4); + RenderBufferToBufferScreenshot(rtv, resDesc.Width / 4, resDesc.Height / 4); GetInvariantD3D11DeviceContext()->CopyResource(myStagingTexture, myTexture); diff --git a/code/components/rage-graphics-rdr3/include/D3D12Helper.h b/code/components/rage-graphics-rdr3/include/D3D12Helper.h new file mode 100644 index 0000000000..b50079d6a8 --- /dev/null +++ b/code/components/rage-graphics-rdr3/include/D3D12Helper.h @@ -0,0 +1,52 @@ +#pragma once + +#include +#include +#include + +namespace d3d12 +{ + // This commandQueue is created by us in order to not interfere with the games command queue when possible. + static Microsoft::WRL::ComPtr GetCommandQueue() + { + ID3D12Device* device = (ID3D12Device*)GetGraphicsDriverHandle(); + + static Microsoft::WRL::ComPtr commandQueue; + + if (!device) + { + return nullptr; + } + + if (commandQueue) + { + return commandQueue; + } + + D3D12_COMMAND_QUEUE_DESC queueDesc = {}; + queueDesc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT; + queueDesc.Priority = D3D12_COMMAND_QUEUE_PRIORITY_NORMAL; + queueDesc.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE; + queueDesc.NodeMask = 0; + + HRESULT hr = ((ID3D12Device*)GetGraphicsDriverHandle())->CreateCommandQueue(&queueDesc, IID_PPV_ARGS(&commandQueue)); + if (FAILED(hr)) + { + trace("Unable to create CommandQueue 0x%x\n", hr); + return nullptr; + } + + return commandQueue; + } + + static void SetupResourceBarrier(Microsoft::WRL::ComPtr commandList, ID3D12Resource* resource, D3D12_RESOURCE_STATES stateBefore, D3D12_RESOURCE_STATES stateAfter, UINT subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES) + { + D3D12_RESOURCE_BARRIER barrier = {}; + barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION; + barrier.Transition.pResource = resource; + barrier.Transition.StateBefore = stateBefore; + barrier.Transition.StateAfter = stateAfter; + barrier.Transition.Subresource = subresource; + commandList->ResourceBarrier(1, &barrier); + } +} diff --git a/code/components/rage-graphics-rdr3/include/DrawCommands.h b/code/components/rage-graphics-rdr3/include/DrawCommands.h index 163301712f..81720a82ed 100644 --- a/code/components/rage-graphics-rdr3/include/DrawCommands.h +++ b/code/components/rage-graphics-rdr3/include/DrawCommands.h @@ -73,8 +73,11 @@ enum class GraphicsAPI extern GFX_EXPORT GraphicsAPI GetCurrentGraphicsAPI(); // VK context or D3D12 device -extern GFX_EXPORT void* GetGraphicsDriverHandle(); +extern GFX_EXPORT void* GetGraphicsDriverHandle(); +// Vulkan Specific +extern GFX_EXPORT void* GetVulkanPhysicalDevice(); + namespace rage::sga { class GFX_EXPORT GraphicsContext diff --git a/code/components/rage-graphics-rdr3/include/VulkanHelper.h b/code/components/rage-graphics-rdr3/include/VulkanHelper.h index 886bf30b58..8210266676 100644 --- a/code/components/rage-graphics-rdr3/include/VulkanHelper.h +++ b/code/components/rage-graphics-rdr3/include/VulkanHelper.h @@ -1,8 +1,10 @@ #pragma once #if IS_RDR3 -#include -#include +#include +#include +#include +#include inline std::string_view ResultToString(VkResult result) { @@ -71,5 +73,109 @@ inline std::string_view ResultToString(VkResult result) default: return std::to_string(static_cast(result)); } -} +} + +static uint32_t FindMemoryType(VkPhysicalDevice physicalDevice, uint32_t typeFilter, VkMemoryPropertyFlags properties) +{ + VkPhysicalDeviceMemoryProperties memProperties; + vkGetPhysicalDeviceMemoryProperties(physicalDevice, &memProperties); + + for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) + { + if ((typeFilter & (1 << i)) && (memProperties.memoryTypes[i].propertyFlags & properties) == properties) + { + return i; + } + } + + return VK_NULL_HANDLE; +} + +static void CreateVKImageFromShareHandle(VkDevice& device, HANDLE handle, unsigned int width, unsigned int height, VkImage& outImage, VkDeviceMemory& outMemory, bool throwFatal = true) +{ + VkExternalMemoryImageCreateInfo ExternalMemoryImageCreateInfo = { VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_IMAGE_CREATE_INFO }; + ExternalMemoryImageCreateInfo.handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_D3D11_TEXTURE_BIT; + VkImageCreateInfo ImageCreateInfo = { VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO }; + ImageCreateInfo.pNext = &ExternalMemoryImageCreateInfo; + ImageCreateInfo.imageType = VK_IMAGE_TYPE_2D; + ImageCreateInfo.format = VK_FORMAT_B8G8R8A8_UNORM; + ImageCreateInfo.extent = { width, height, 1 }; + ImageCreateInfo.mipLevels = 1; + ImageCreateInfo.arrayLayers = 1; + ImageCreateInfo.samples = VK_SAMPLE_COUNT_1_BIT; + ImageCreateInfo.tiling = VK_IMAGE_TILING_OPTIMAL; + ImageCreateInfo.usage = VK_IMAGE_USAGE_SAMPLED_BIT; + ImageCreateInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + ImageCreateInfo.initialLayout = VK_IMAGE_LAYOUT_PREINITIALIZED; + + VkResult result = vkCreateImage(device, &ImageCreateInfo, nullptr, &outImage); + + if (result != VK_SUCCESS && throwFatal) + { + FatalError("Failed to create a Vulkan image. VkResult: %s", ResultToString(result)); + } + + VkMemoryRequirements MemoryRequirements; + vkGetImageMemoryRequirements(device, outImage, &MemoryRequirements); + + VkMemoryDedicatedAllocateInfo MemoryDedicatedAllocateInfo = { VK_STRUCTURE_TYPE_MEMORY_DEDICATED_ALLOCATE_INFO }; + MemoryDedicatedAllocateInfo.image = outImage; + VkImportMemoryWin32HandleInfoKHR ImportMemoryWin32HandleInfo = { VK_STRUCTURE_TYPE_IMPORT_MEMORY_WIN32_HANDLE_INFO_KHR }; + ImportMemoryWin32HandleInfo.pNext = &MemoryDedicatedAllocateInfo; + ImportMemoryWin32HandleInfo.handleType = VK_EXTERNAL_MEMORY_HANDLE_TYPE_D3D11_TEXTURE_BIT; + ImportMemoryWin32HandleInfo.handle = handle; + VkMemoryAllocateInfo MemoryAllocateInfo = { VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO }; + MemoryAllocateInfo.pNext = &ImportMemoryWin32HandleInfo; + MemoryAllocateInfo.allocationSize = MemoryRequirements.size; + + MemoryAllocateInfo.memoryTypeIndex = FindMemoryType((VkPhysicalDevice)GetVulkanPhysicalDevice(), MemoryRequirements.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); + + static auto _vkBindImageMemory2 = (PFN_vkBindImageMemory2)vkGetDeviceProcAddr(device, "vkBindImageMemory2"); + + if (!_vkBindImageMemory2) + { + FatalError("Unable to find 'vkBindImageMemory2' in vulkan."); + } + + result = vkAllocateMemory(device, &MemoryAllocateInfo, nullptr, &outMemory); + + if (result != VK_SUCCESS && throwFatal) + { + FatalError("Failed to allocate memory for Vulkan. VkResult: %s", ResultToString(result)); + } + + VkBindImageMemoryInfo BindImageMemoryInfo = { VK_STRUCTURE_TYPE_BIND_IMAGE_MEMORY_INFO }; + BindImageMemoryInfo.image = outImage; + BindImageMemoryInfo.memory = outMemory; + + result = _vkBindImageMemory2(device, 1, &BindImageMemoryInfo); + + if (result != VK_SUCCESS && throwFatal) + { + FatalError("Failed to bind Vulkan image memory. VkResult: %s", ResultToString(result)); + } +} + +static void SetupImageBarrier(VkCommandBuffer cmdBuf, VkImage image, VkImageLayout oldLayout, VkImageLayout newLayout, + VkAccessFlags srcAccessMask, VkAccessFlags dstAccessMask, VkPipelineStageFlags srcStage, VkPipelineStageFlags dstStage) +{ + VkImageMemoryBarrier barrier = {}; + barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; + barrier.oldLayout = oldLayout; + barrier.newLayout = newLayout; + barrier.srcAccessMask = srcAccessMask; + barrier.dstAccessMask = dstAccessMask; + barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.image = image; + + barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + barrier.subresourceRange.baseMipLevel = 0; + barrier.subresourceRange.levelCount = 1; + barrier.subresourceRange.baseArrayLayer = 0; + barrier.subresourceRange.layerCount = 1; + + vkCmdPipelineBarrier(cmdBuf, srcStage, dstStage, 0, 0, nullptr, 0, nullptr, 1, &barrier); +} + #endif diff --git a/code/components/rage-graphics-rdr3/include/grcTexture.h b/code/components/rage-graphics-rdr3/include/grcTexture.h index 2b93fb93f8..d58c2bd879 100644 --- a/code/components/rage-graphics-rdr3/include/grcTexture.h +++ b/code/components/rage-graphics-rdr3/include/grcTexture.h @@ -162,8 +162,14 @@ namespace rage class TextureD3D12 : public Texture { - public: - char pad[64]; + public: + char pad[5]; + uint8_t unkFlags1; + char pad2[2]; + uint8_t unkFlags2; + char pad3[8]; + ID3D12Resource* stagingResource; + char pad4[32]; ID3D12Resource* resource; }; @@ -174,10 +180,14 @@ namespace rage { VkDeviceMemory memory; VkImage image; - uint32_t pad[24]; + void* unkPtr; + char pad[88]; }; - char pad[64]; + char pad[0x10]; + uint16_t width; + uint16_t height; + char pad2[44]; ImageData* image; }; @@ -214,7 +224,18 @@ namespace rage void GFX_EXPORT Driver_Create_ShaderResourceView(Texture* texture, const TextureViewDesc& desc); - void GFX_EXPORT Driver_Destroy_Texture(Texture* texture); + void GFX_EXPORT Driver_Destroy_Texture(Texture* texture); + + struct BackBufferData + { + void* vtbl; + rage::sga::Texture* m_texture; + uint32_t m_flags; + uint32_t m_miscState; + char pad[32]; + }; + + BackBufferData* Driver_GetBackBuffer(); } // rage::grcImage, in reality diff --git a/code/components/rage-graphics-rdr3/src/CefGameCapture.cpp b/code/components/rage-graphics-rdr3/src/CefGameCapture.cpp new file mode 100644 index 0000000000..ba690ef757 --- /dev/null +++ b/code/components/rage-graphics-rdr3/src/CefGameCapture.cpp @@ -0,0 +1,569 @@ +#include "StdInc.h" + +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +#include +#include + +#include + +#include +#pragma comment(lib, "vulkan-1.lib") +#pragma comment(lib, "d3d11.lib") +#pragma comment(lib, "dxgi.lib") +#pragma comment(lib, "dxguid.lib") + + +#include +namespace WRL = Microsoft::WRL; + +struct GameRenderData +{ + HANDLE handle = NULL; + int width = 0; + int height = 0; + bool requested = false; +}; + +static void* g_lastBackbufTexture; + +static void RenderBufferToBuffer(ID3D12Resource* resource, int width = 0, int height = 0) +{ + static auto didCallCrashometry = ([]() + { + AddCrashometry("did_render_backbuf", "true"); + + return true; + })(); + + ID3D12Device* device = (ID3D12Device*)GetGraphicsDriverHandle(); + + if (!device) + { + return; + } + + static WRL::ComPtr commandAllocator; + static WRL::ComPtr commandList; + + if (!commandAllocator) + { + device->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(&commandAllocator)); + } + else + { + commandAllocator->Reset(); + } + + if (!commandList) + { + device->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, commandAllocator.Get(), nullptr, IID_PPV_ARGS(&commandList)); + } + else + { + commandList->Reset(commandAllocator.Get(), nullptr); + } + + rage::sga::BackBufferData* data = rage::sga::Driver_GetBackBuffer(); + if (!data || !data->m_texture) + { + return; + } + + ID3D12Resource* backBuffer = ((rage::sga::TextureD3D12*)data->m_texture)->resource; + if (!backBuffer) + { + return; + } + + D3D12_RESOURCE_DESC srcDesc = backBuffer->GetDesc(); + D3D12_RESOURCE_DESC dstDesc = resource->GetDesc(); + + if (srcDesc.Width != dstDesc.Width || srcDesc.Height != dstDesc.Height || srcDesc.Format != dstDesc.Format) + { + return; + } + + d3d12::SetupResourceBarrier(commandList, backBuffer, D3D12_RESOURCE_STATE_PRESENT, D3D12_RESOURCE_STATE_COPY_SOURCE); + commandList->CopyResource(resource, backBuffer); + d3d12::SetupResourceBarrier(commandList, backBuffer, D3D12_RESOURCE_STATE_COPY_SOURCE, D3D12_RESOURCE_STATE_PRESENT); + + HRESULT hr = commandList->Close(); + + if (FAILED(hr)) + { + return; + } + + WRL::ComPtr queue = d3d12::GetCommandQueue(); + + if (!queue) + { + return; + } + + static WRL::ComPtr fence; + static HANDLE fenceEvent = nullptr; + static UINT64 fenceValue = 0; + + if (!fence) + { + hr = device->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&fence)); + assert(SUCCEEDED(hr)); + fenceEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr); + assert(fenceEvent != nullptr); + } + + ID3D12CommandList* ppCommandLists[] = { commandList.Get() }; + queue->ExecuteCommandLists(_countof(ppCommandLists), ppCommandLists); + + hr = queue->Signal(fence.Get(), ++fenceValue); + assert(SUCCEEDED(hr)); + + if (fence->GetCompletedValue() < fenceValue) + { + hr = fence->SetEventOnCompletion(fenceValue, fenceEvent); + assert(SUCCEEDED(hr)); + WaitForSingleObject(fenceEvent, INFINITE); + } +} + +static void CaptureBufferOutputDX() +{ + static HostSharedData handleData("CfxGameRenderHandle"); + static D3D12_RESOURCE_DESC resDesc; + static int lastWidth = 1; + static int lastHeight = 1; + + rage::sga::BackBufferData* data = rage::sga::Driver_GetBackBuffer(); + if (!data) + { + return; + } + + rage::sga::TextureD3D12* sgaTexture = ((rage::sga::TextureD3D12*)data->m_texture); + if (!sgaTexture) + { + return; + } + + ID3D12Resource* backBuf = sgaTexture->resource; + if (!backBuf) + { + return; + } + + resDesc = backBuf->GetDesc(); + handleData->width = resDesc.Width; + handleData->height = resDesc.Height; + + bool shouldChange = false; + static ID3D12Resource* resource = nullptr; + + if (lastWidth != handleData->width || lastHeight != handleData->height || !resource) + { + lastWidth = handleData->width; + lastHeight = handleData->height; + + if (resource) + { + resource->Release(); + resource = nullptr; + } + + shouldChange = true; + } + + if (shouldChange) + { + D3D12_RESOURCE_DESC texDesc = {}; + texDesc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D; + texDesc.Width = resDesc.Width; + texDesc.Height = resDesc.Height; + texDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM; + texDesc.MipLevels = 1; + texDesc.DepthOrArraySize = 1; + texDesc.SampleDesc.Count = 1; + texDesc.SampleDesc.Quality = 0; + texDesc.Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN; + texDesc.Flags = D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET; + + D3D12_HEAP_PROPERTIES heapProps = {}; + heapProps.Type = D3D12_HEAP_TYPE_DEFAULT; + heapProps.CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN; + heapProps.MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN; + heapProps.CreationNodeMask = 1; + heapProps.VisibleNodeMask = 1; + + ID3D12Device* device = (ID3D12Device*)GetGraphicsDriverHandle(); + if (!device) + { + return; + } + + HRESULT hr = device->CreateCommittedResource(&heapProps, D3D12_HEAP_FLAG_SHARED, &texDesc, D3D12_RESOURCE_STATE_COPY_DEST, nullptr, IID_PPV_ARGS(&resource)); + + if (FAILED(hr)) + { return; + } + + D3D12_DESCRIPTOR_HEAP_DESC rtvHeapDesc = {}; + rtvHeapDesc.NumDescriptors = 1; + rtvHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_RTV; + rtvHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE; + + Microsoft::WRL::ComPtr rtvHeap; + device->CreateDescriptorHeap(&rtvHeapDesc, IID_PPV_ARGS(&rtvHeap)); + + D3D12_CPU_DESCRIPTOR_HANDLE rtvHandle = rtvHeap->GetCPUDescriptorHandleForHeapStart(); + + D3D12_RENDER_TARGET_VIEW_DESC rtvDesc = {}; + rtvDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM; + rtvDesc.ViewDimension = D3D12_RTV_DIMENSION_TEXTURE2D; + rtvDesc.Texture2D.MipSlice = 0; + rtvDesc.Texture2D.PlaneSlice = 0; + + device->CreateRenderTargetView(resource, &rtvDesc, rtvHandle); + + HANDLE sharedHandle = nullptr; + hr = device->CreateSharedHandle(resource, nullptr, GENERIC_ALL, nullptr, &sharedHandle); + + if (FAILED(hr)) + { + return; + } + + handleData->handle = sharedHandle; + } + + if (!resource) + { + return; + } + + if (!handleData->requested) + { + return; + } + + RenderBufferToBuffer(resource); +} + +namespace WRL = Microsoft::WRL; + +// These will only ever exist when VK driver is being used and exclusively for game view capture. +static ID3D11Device* g_d3d11VKDevice = nullptr; +static ID3D11DeviceContext* g_d3d11VKDeviceContext = nullptr; +static ID3D11Texture2D* g_d3d11VKStagingTexture = nullptr; +static WRL::ComPtr g_d3d11VKTexture = nullptr; + +static HANDLE g_vkDX11StagingHandle = NULL; + +// Experimental (and currently not fully functional). Make the VK image a share handle, write to it, open it in D3D11, copy the contents to a new texture and provide a safe handle for GLES. +static void RenderBufferToBuffer(VkImage image, VkDeviceMemory memory, uint32_t width = 0, uint32_t height = 0) +{ + static HostSharedData handleData("CfxGameRenderHandle"); + + static auto didCallCrashometry = ([]() + { + AddCrashometry("did_render_backbufvk", "true"); + + return true; + })(); + + rage::sga::BackBufferData* data = rage::sga::Driver_GetBackBuffer(); + if (!data) + { + return; + } + + rage::sga::TextureVK* texture = (rage::sga::TextureVK*)data->m_texture; + if (!texture) + { + return; + } + + VkDevice device = (VkDevice)GetGraphicsDriverHandle(); + VkPhysicalDevice physicalDevice = (VkPhysicalDevice)GetVulkanPhysicalDevice(); + + if (!device || !physicalDevice) + { + return; + } + + VkImage backbufferImage = texture->image->image; + VkDeviceMemory backbufferMemory = texture->image->memory; + + static bool isInitalized = false; + static VkCommandPool commandPool; + static VkQueue graphicsQueue; + static VkFence copyFence = VK_NULL_HANDLE; + + if (!copyFence) + { + VkFenceCreateInfo fenceInfo = { VK_STRUCTURE_TYPE_FENCE_CREATE_INFO }; + vkCreateFence(device, &fenceInfo, nullptr, ©Fence); + } + else + { + vkResetFences(device, 1, ©Fence); + } + + if (!graphicsQueue) + { + uint32_t queueFamilyCount = 0; + vkGetPhysicalDeviceQueueFamilyProperties(physicalDevice, &queueFamilyCount, nullptr); + + std::vector queueFamilies(queueFamilyCount); + vkGetPhysicalDeviceQueueFamilyProperties(physicalDevice, &queueFamilyCount, queueFamilies.data()); + + int graphicsQueueFamilyIndex = -1; + + for (uint32_t i = 0; i < queueFamilies.size(); i++) + { + if (queueFamilies[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) + { + graphicsQueueFamilyIndex = i; + break; + } + } + vkGetDeviceQueue(device, graphicsQueueFamilyIndex, 0, &graphicsQueue); + } + + if (!commandPool) + { + VkCommandPoolCreateInfo poolInfo = {}; + poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; + poolInfo.queueFamilyIndex = 0; + poolInfo.flags = VK_COMMAND_POOL_CREATE_TRANSIENT_BIT; + vkCreateCommandPool(device, &poolInfo, nullptr, &commandPool); + } +} + +static void CaptureBufferOutputVK() +{ + static HostSharedData handleData("CfxGameRenderHandle"); + static int lastWidth = 1; + static int lastHeight = 1; + + // OpenGL ES (GLES) doesn't play nice with vulkan texture share handles and will usuaully crash on trying to handle them. In order to handle capturing game-view for users. + // We create a dummy D3D11 device that sole purpose is to handle copying the vulkan owned backbuffer to a d3d11 device and pass that to GLES + if (!g_d3d11VKDevice) + { + // We should be safe to assume that the users GPU (that is currently running at minimum vulkan 1.2) supports a basic feature level of DX11. + D3D_FEATURE_LEVEL featureLevels[] = { + D3D_FEATURE_LEVEL_11_1, + D3D_FEATURE_LEVEL_11_0, + }; + + IDXGIAdapter* ppAdapter = nullptr; + { + WRL::ComPtr dxgiFactory; + CreateDXGIFactory1(IID_IDXGIFactory1, &dxgiFactory); + + WRL::ComPtr adapter; + WRL::ComPtr factory6; + HRESULT hr = dxgiFactory.As(&factory6); + if (SUCCEEDED(hr)) + { + for (UINT adapterIndex = 0; + DXGI_ERROR_NOT_FOUND != factory6->EnumAdapterByGpuPreference(adapterIndex, DXGI_GPU_PREFERENCE_HIGH_PERFORMANCE, IID_PPV_ARGS(adapter.ReleaseAndGetAddressOf())); + adapterIndex++) + { + DXGI_ADAPTER_DESC1 desc; + adapter->GetDesc1(&desc); + + if (desc.Flags & DXGI_ADAPTER_FLAG_SOFTWARE) + { + // Don't select the Basic Render Driver adapter. + continue; + } + + adapter.CopyTo(&ppAdapter); + break; + } + } + } + + if (FAILED(D3D11CreateDevice(ppAdapter, + D3D_DRIVER_TYPE_HARDWARE, + nullptr, + D3D11_CREATE_DEVICE_BGRA_SUPPORT, + featureLevels, + ARRAYSIZE(featureLevels), + D3D11_SDK_VERSION, + &g_d3d11VKDevice, + nullptr, + &g_d3d11VKDeviceContext))) + { + trace("Unable to create D3D11 Device for game capture\n"); + return; + } + } + + rage::sga::BackBufferData* data = rage::sga::Driver_GetBackBuffer(); + if (!data) + { + return; + } + + rage::sga::TextureVK* texture = (rage::sga::TextureVK*)data->m_texture; + + if (!texture) + { + return; + } + + VkDevice device = (VkDevice)GetGraphicsDriverHandle(); + VkPhysicalDevice physicalDevice = (VkPhysicalDevice)GetVulkanPhysicalDevice(); + + handleData->width = texture->width; + handleData->height = texture->height; + + static VkImage vkImage = nullptr; + static VkDeviceMemory vkMemory = nullptr; + + if (!g_d3d11VKTexture || (handleData->width != lastWidth || handleData->height != lastHeight)) + { + if (g_d3d11VKTexture) + { + g_d3d11VKTexture->Release(); + g_d3d11VKTexture = nullptr; + } + + D3D11_TEXTURE2D_DESC texDesc = {}; + texDesc.Width = handleData->width; + texDesc.Height = handleData->height; + texDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM; + texDesc.MipLevels = 1; + texDesc.ArraySize = 1; + texDesc.SampleDesc.Count = 1; + texDesc.SampleDesc.Quality = 0; + texDesc.Usage = D3D11_USAGE_DEFAULT; + texDesc.BindFlags = D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_RENDER_TARGET; + texDesc.CPUAccessFlags = 0; + texDesc.MiscFlags = D3D11_RESOURCE_MISC_SHARED; + + g_d3d11VKDevice->CreateTexture2D(&texDesc, nullptr, &g_d3d11VKTexture); + + WRL::ComPtr dxgiResource; + HANDLE sharedHandle; + HRESULT hr = g_d3d11VKTexture.As(&dxgiResource); + if (FAILED(hr)) + { + return; + } + + hr = dxgiResource->GetSharedHandle(&sharedHandle); + if (FAILED(hr)) + { + return; + } + handleData->handle = sharedHandle; + } + + if (handleData->width != lastWidth || handleData->height != lastHeight) + { + lastWidth = handleData->width; + lastHeight = handleData->height; + + VkExternalMemoryImageCreateInfo externalInfo{}; + externalInfo.sType = VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_IMAGE_CREATE_INFO; + externalInfo.handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_D3D11_TEXTURE_KMT_BIT; + + VkImageCreateInfo imageInfo{}; + imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; + imageInfo.imageType = VK_IMAGE_TYPE_2D; + imageInfo.format = VK_FORMAT_B8G8R8A8_UNORM; + imageInfo.extent = { (unsigned int)handleData->width, (unsigned int)handleData->height, 1 }; + imageInfo.mipLevels = 1; + imageInfo.arrayLayers = 1; + imageInfo.samples = VK_SAMPLE_COUNT_1_BIT; + imageInfo.tiling = VK_IMAGE_TILING_LINEAR; + imageInfo.usage = VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; + imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + imageInfo.pNext = &externalInfo; + vkCreateImage(device, &imageInfo, nullptr, &vkImage); + + VkExportMemoryAllocateInfo exportAlloc{}; + exportAlloc.sType = VK_STRUCTURE_TYPE_EXPORT_MEMORY_ALLOCATE_INFO; + exportAlloc.handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_D3D11_TEXTURE_KMT_BIT; + + VkMemoryRequirements memReq; + vkGetImageMemoryRequirements(device, vkImage, &memReq); + + uint32_t index = FindMemoryType(physicalDevice, memReq.memoryTypeBits, + VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); + + VkMemoryAllocateInfo allocInfo{}; + allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; + allocInfo.allocationSize = memReq.size; + allocInfo.memoryTypeIndex = index; + allocInfo.pNext = &exportAlloc; + + vkAllocateMemory(device, &allocInfo, nullptr, &vkMemory); + vkBindImageMemory(device, vkImage, vkMemory, 0); + + VkMemoryGetWin32HandleInfoKHR handleInfo{}; + handleInfo.sType = VK_STRUCTURE_TYPE_MEMORY_GET_WIN32_HANDLE_INFO_KHR; + handleInfo.memory = vkMemory; + handleInfo.handleType = VK_EXTERNAL_MEMORY_HANDLE_TYPE_D3D11_TEXTURE_KMT_BIT; + + static PFN_vkGetMemoryWin32HandleKHR vkGetMemoryWin32HandleKHR = (PFN_vkGetMemoryWin32HandleKHR)vkGetDeviceProcAddr(device, "vkGetMemoryWin32HandleKHR"); + VkResult res = vkGetMemoryWin32HandleKHR(device, &handleInfo, &g_vkDX11StagingHandle); + + if (res != VK_SUCCESS) + { + return; + } + } + + if (!vkImage || !vkMemory) + { + return; + } + + if (!handleData->requested) + { + return; + } + + RenderBufferToBuffer(vkImage, vkMemory, lastWidth, lastHeight); +} + +static HookFunction hookFunction([]() +{ + OnPostFrontendRender.Connect([]() + { + uintptr_t a1 = 0; + uintptr_t a2 = 0; + + EnqueueGenericDrawCommand([](uintptr_t, uintptr_t) + { + if (GetCurrentGraphicsAPI() == GraphicsAPI::D3D12) + { + CaptureBufferOutputDX(); + } + else + { + CaptureBufferOutputVK(); + } + }, + &a1, &a2); + }, + INT32_MAX); +}); diff --git a/code/components/rage-graphics-rdr3/src/GfxSpec.cpp b/code/components/rage-graphics-rdr3/src/GfxSpec.cpp index 87e5198beb..60a57011fb 100644 --- a/code/components/rage-graphics-rdr3/src/GfxSpec.cpp +++ b/code/components/rage-graphics-rdr3/src/GfxSpec.cpp @@ -554,6 +554,7 @@ GraphicsAPI GetCurrentGraphicsAPI() void** g_d3d12Device; VkDevice* g_vkHandle; +VkPhysicalDevice** g_vkPhysicalDevice; void* GetGraphicsDriverHandle() { @@ -566,6 +567,16 @@ void* GetGraphicsDriverHandle() default: return nullptr; } +} + +void* GetVulkanPhysicalDevice() +{ + if (GetCurrentGraphicsAPI() == GraphicsAPI::Vulkan) + { + return *g_vkPhysicalDevice; + } + + return nullptr; } namespace rage::sga @@ -578,6 +589,11 @@ namespace rage::sga void Driver_Destroy_Texture(rage::sga::Texture* texture) { (*(void(__fastcall**)(__int64, void*))(**(uint64_t**)sgaDriver + 440i64))(*(uint64_t*)sgaDriver, texture); + } + + BackBufferData* Driver_GetBackBuffer() + { + return (*(BackBufferData*(__fastcall**)(__int64))(**(uint64_t**)sgaDriver + g_swapchainBackbufferOffset))(*(uint64_t*)sgaDriver); } GraphicsContext* GraphicsContext::GetCurrent() @@ -722,6 +738,7 @@ static HookFunction hookFunction([]() g_d3d12Device = hook::get_address(hook::get_pattern("48 8B 01 FF 50 78 48 8B 0B 48 8D", -7)); g_vkHandle = hook::get_address(hook::get_pattern("8D 50 41 8B CA 44 8B C2 F3 48 AB 48 8B 0D", 14)); + g_vkPhysicalDevice = hook::get_address(hook::get_pattern("48 8B 0D ? ? ? ? 45 33 C9 83 65", 3)); { auto location = hook::get_pattern("83 25 ? ? ? ? 00 83 25 ? ? ? ? 00 D1 F8 89 05", -0x26);