diff --git a/plugins/win-capture/CMakeLists.txt b/plugins/win-capture/CMakeLists.txt index 123cfb896..17f2b9a60 100644 --- a/plugins/win-capture/CMakeLists.txt +++ b/plugins/win-capture/CMakeLists.txt @@ -24,5 +24,6 @@ target_link_libraries(win-capture install_obs_plugin_with_data(win-capture data) +add_subdirectory(graphics-hook) add_subdirectory(get-graphics-offsets) add_subdirectory(inject-helper) diff --git a/plugins/win-capture/graphics-hook/CMakeLists.txt b/plugins/win-capture/graphics-hook/CMakeLists.txt new file mode 100644 index 000000000..308a80a65 --- /dev/null +++ b/plugins/win-capture/graphics-hook/CMakeLists.txt @@ -0,0 +1,39 @@ +project(graphics-hook) + +set(graphics-hook_HEADERS + graphics-hook.h + ../graphics-hook-info.h + ../hook-helpers.h + ../funchook.h + ../obfuscate.h + gl-decs.h + d3d9-patches.hpp) + +set(graphics-hook_SOURCES + graphics-hook.c + ../funchook.c + ../obfuscate.c + gl-capture.c + d3d9-capture.cpp + dxgi-capture.cpp + d3d10-capture.cpp + d3d11-capture.cpp) + +add_library(graphics-hook MODULE + ${graphics-hook_SOURCES} + ${graphics-hook_HEADERS}) + +target_link_libraries(graphics-hook + ipc-util) + +if(CMAKE_SIZEOF_VOID_P EQUAL 8) + set(_output_suffix "64") +else() + set(_output_suffix "32") +endif() + +set_target_properties(graphics-hook + PROPERTIES + OUTPUT_NAME "graphics-hook${_output_suffix}") + +install_obs_datatarget(graphics-hook "obs-plugins/win-capture") diff --git a/plugins/win-capture/graphics-hook/d3d10-capture.cpp b/plugins/win-capture/graphics-hook/d3d10-capture.cpp new file mode 100644 index 000000000..18f7b2061 --- /dev/null +++ b/plugins/win-capture/graphics-hook/d3d10-capture.cpp @@ -0,0 +1,805 @@ +#define _CRT_SECURE_NO_WARNINGS +#include +#include + +#include "dxgi-helpers.hpp" +#include "graphics-hook.h" +#include "../funchook.h" + +struct d3d10_data { + ID3D10Device *device; /* do not release */ + uint32_t base_cx; + uint32_t base_cy; + uint32_t cx; + uint32_t cy; + DXGI_FORMAT format; + bool using_shtex : 1; + bool using_scale : 1; + bool multisampled : 1; + + ID3D10Texture2D *scale_tex; + ID3D10ShaderResourceView *scale_resource; + + ID3D10VertexShader *vertex_shader; + ID3D10InputLayout *vertex_layout; + ID3D10PixelShader *pixel_shader; + + ID3D10SamplerState *sampler_state; + ID3D10BlendState *blend_state; + ID3D10DepthStencilState *zstencil_state; + ID3D10RasterizerState *raster_state; + + ID3D10Buffer *vertex_buffer; + + union { + /* shared texture */ + struct { + struct shtex_data *shtex_info; + ID3D10Texture2D *texture; + ID3D10RenderTargetView *render_target; + HANDLE handle; + }; + /* shared memory */ + struct { + struct shmem_data *shmem_info; + ID3D10Texture2D *copy_surfaces[NUM_BUFFERS]; + ID3D10Texture2D *textures[NUM_BUFFERS]; + ID3D10RenderTargetView *render_targets[NUM_BUFFERS]; + bool texture_ready[NUM_BUFFERS]; + bool texture_mapped[NUM_BUFFERS]; + uint32_t pitch; + int cur_tex; + int copy_wait; + }; + }; +}; + +struct d3d10_data data = {}; + +void d3d10_free(void) +{ + if (data.scale_tex) + data.scale_tex->Release(); + if (data.scale_resource) + data.scale_resource->Release(); + if (data.vertex_shader) + data.vertex_shader->Release(); + if (data.vertex_layout) + data.vertex_layout->Release(); + if (data.pixel_shader) + data.pixel_shader->Release(); + if (data.sampler_state) + data.sampler_state->Release(); + if (data.blend_state) + data.blend_state->Release(); + if (data.zstencil_state) + data.zstencil_state->Release(); + if (data.raster_state) + data.raster_state->Release(); + if (data.vertex_buffer) + data.vertex_buffer->Release(); + + capture_free(); + + if (data.using_shtex) { + if (data.texture) + data.texture->Release(); + if (data.render_target) + data.render_target->Release(); + } else { + for (size_t i = 0; i < NUM_BUFFERS; i++) { + if (data.copy_surfaces[i]) { + if (data.texture_mapped[i]) + data.copy_surfaces[i]->Unmap(0); + data.copy_surfaces[i]->Release(); + } + if (data.textures[i]) + data.textures[i]->Release(); + if (data.render_targets[i]) + data.render_targets[i]->Release(); + } + } + + memset(&data, 0, sizeof(data)); + + hlog("----------------- d3d10 capture freed ----------------"); +} + +static bool create_d3d10_stage_surface(ID3D10Texture2D **tex) +{ + HRESULT hr; + + D3D10_TEXTURE2D_DESC desc = {}; + desc.Width = data.cx; + desc.Height = data.cy; + desc.Format = data.format; + desc.MipLevels = 1; + desc.ArraySize = 1; + desc.SampleDesc.Count = 1; + desc.Usage = D3D10_USAGE_STAGING; + desc.CPUAccessFlags = D3D10_CPU_ACCESS_READ; + + hr = data.device->CreateTexture2D(&desc, nullptr, tex); + if (FAILED(hr)) { + hlog_hr("create_d3d10_stage_surface: failed to create texture", + hr); + return false; + } + + return true; +} + +static bool create_d3d10_tex(uint32_t cx, uint32_t cy, + ID3D10Texture2D **tex, + ID3D10ShaderResourceView **resource, + ID3D10RenderTargetView **render_target, + HANDLE *handle) +{ + UINT flags = 0; + UINT misc_flags = 0; + HRESULT hr; + + if (!!resource) + flags |= D3D10_BIND_SHADER_RESOURCE; + if (!!render_target) + flags |= D3D10_BIND_RENDER_TARGET; + if (!!handle) + misc_flags |= D3D10_RESOURCE_MISC_SHARED; + + D3D10_TEXTURE2D_DESC desc = {}; + desc.Width = cx; + desc.Height = cy; + desc.MipLevels = 1; + desc.ArraySize = 1; + desc.Format = data.format; + desc.BindFlags = flags; + desc.SampleDesc.Count = 1; + desc.Usage = D3D10_USAGE_DEFAULT; + desc.MiscFlags = misc_flags; + + hr = data.device->CreateTexture2D(&desc, nullptr, tex); + if (FAILED(hr)) { + hlog_hr("create_d3d10_tex: failed to create texture", hr); + return false; + } + + if (!!resource) { + D3D10_SHADER_RESOURCE_VIEW_DESC res_desc = {}; + res_desc.Format = data.format; + res_desc.ViewDimension = D3D10_SRV_DIMENSION_TEXTURE2D; + res_desc.Texture2D.MipLevels = 1; + + hr = data.device->CreateShaderResourceView(*tex, &res_desc, + resource); + if (FAILED(hr)) { + hlog_hr("create_d3d10_tex: failed to create resource " + "view", hr); + return false; + } + } + + if (!!render_target) { + hr = data.device->CreateRenderTargetView(*tex, nullptr, + render_target); + if (FAILED(hr)) { + hlog_hr("create_d3d10_tex: failed to create render " + "target view", hr); + return false; + } + } + + if (!!handle) { + IDXGIResource *dxgi_res; + hr = (*tex)->QueryInterface(__uuidof(IDXGIResource), + (void**)&dxgi_res); + if (FAILED(hr)) { + hlog_hr("create_d3d10_tex: failed to query " + "IDXGIResource interface from texture", hr); + return false; + } + + hr = dxgi_res->GetSharedHandle(handle); + dxgi_res->Release(); + if (FAILED(hr)) { + hlog_hr("create_d3d10_tex: failed to get shared handle", + hr); + return false; + } + } + + return true; +} + +static inline bool d3d10_init_format(IDXGISwapChain *swap, HWND &window) +{ + DXGI_SWAP_CHAIN_DESC desc; + HRESULT hr; + + hr = swap->GetDesc(&desc); + if (FAILED(hr)) { + hlog_hr("d3d10_init_format: swap->GetDesc failed", hr); + return false; + } + + data.format = fix_dxgi_format(desc.BufferDesc.Format); + data.multisampled = desc.SampleDesc.Count > 1; + window = desc.OutputWindow; + data.base_cx = desc.BufferDesc.Width; + data.base_cy = desc.BufferDesc.Height; + + if (data.using_scale) { + data.cx = global_hook_info->cx; + data.cy = global_hook_info->cy; + } else { + data.cx = desc.BufferDesc.Width; + data.cy = desc.BufferDesc.Height; + } + return true; +} + +static inline bool d3d10_init_vertex_shader(void) +{ + D3D10_INPUT_ELEMENT_DESC desc[2]; + uint8_t *vs_data; + size_t size; + HRESULT hr; + + vs_data = get_d3d1x_vertex_shader(&size); + + hr = data.device->CreateVertexShader(vs_data, size, + &data.vertex_shader); + if (FAILED(hr)) { + hlog_hr("d3d10_init_vertex_shader: failed to create shader", + hr); + return false; + } + + desc[0].SemanticName = "SV_Position"; + desc[0].SemanticIndex = 0; + desc[0].Format = DXGI_FORMAT_R32G32B32A32_FLOAT; + desc[0].InputSlot = 0; + desc[0].AlignedByteOffset = 0; + desc[0].InputSlotClass = D3D10_INPUT_PER_VERTEX_DATA; + desc[0].InstanceDataStepRate = 0; + + desc[1].SemanticName = "TEXCOORD"; + desc[1].SemanticIndex = 0; + desc[1].Format = DXGI_FORMAT_R32G32_FLOAT; + desc[1].InputSlot = 0; + desc[1].AlignedByteOffset = D3D10_APPEND_ALIGNED_ELEMENT; + desc[1].InputSlotClass = D3D10_INPUT_PER_VERTEX_DATA; + desc[1].InstanceDataStepRate = 0; + + hr = data.device->CreateInputLayout(desc, 2, vs_data, size, + &data.vertex_layout); + if (FAILED(hr)) { + hlog_hr("d3d10_init_vertex_shader: failed to create layout", + hr); + return false; + } + + return true; +} + +static inline bool d3d10_init_pixel_shader(void) +{ + uint8_t *ps_data; + size_t size; + HRESULT hr; + + ps_data = get_d3d1x_pixel_shader(&size); + + hr = data.device->CreatePixelShader(ps_data, size, &data.pixel_shader); + if (FAILED(hr)) { + hlog_hr("d3d10_init_pixel_shader: failed to create shader", hr); + return false; + } + + return true; +} + +static inline bool d3d10_init_sampler_state(void) +{ + HRESULT hr; + + D3D10_SAMPLER_DESC desc = {}; + desc.Filter = D3D10_FILTER_MIN_MAG_MIP_LINEAR; + desc.AddressU = D3D10_TEXTURE_ADDRESS_CLAMP; + desc.AddressV = D3D10_TEXTURE_ADDRESS_CLAMP; + desc.AddressW = D3D10_TEXTURE_ADDRESS_CLAMP; + + hr = data.device->CreateSamplerState(&desc, &data.sampler_state); + if (FAILED(hr)) { + hlog_hr("d3d10_init_sampler_state: failed to create sampler " + "state", hr); + return false; + } + + return true; +} + +static inline bool d3d10_init_blend_state(void) +{ + D3D10_BLEND_DESC desc = {}; + HRESULT hr; + + for (size_t i = 0; i < 8; i++) + desc.RenderTargetWriteMask[i] = D3D10_COLOR_WRITE_ENABLE_ALL; + + hr = data.device->CreateBlendState(&desc, &data.blend_state); + if (FAILED(hr)) { + hlog_hr("d3d10_init_blend_state: failed to create blend state", + hr); + return false; + } + + return true; +} + +static inline bool d3d10_init_zstencil_state(void) +{ + D3D10_DEPTH_STENCIL_DESC desc = {}; /* defaults all to off */ + HRESULT hr; + + hr = data.device->CreateDepthStencilState(&desc, &data.zstencil_state); + if (FAILED(hr)) { + hlog_hr("d3d10_init_zstencil_state: failed to create " + "zstencil state", hr); + return false; + } + + return true; +} + +static inline bool d3d10_init_raster_state(void) +{ + D3D10_RASTERIZER_DESC desc = {}; + HRESULT hr; + + desc.FillMode = D3D10_FILL_SOLID; + desc.CullMode = D3D10_CULL_NONE; + + hr = data.device->CreateRasterizerState(&desc, &data.raster_state); + if (FAILED(hr)) { + hlog_hr("d3d10_init_raster_state: failed to create raster " + "state", hr); + return false; + } + + return true; +} + +#define NUM_VERTS 4 + +static inline bool d3d10_init_vertex_buffer(void) +{ + HRESULT hr; + const vertex verts[NUM_VERTS] = { + {{-1.0f, 1.0f, 0.0f, 1.0f}, {0.0f, 0.0f}}, + {{-1.0f, -1.0f, 0.0f, 1.0f}, {0.0f, 1.0f}}, + {{ 1.0f, 1.0f, 0.0f, 1.0f}, {1.0f, 0.0f}}, + {{ 1.0f, -1.0f, 0.0f, 1.0f}, {1.0f, 1.0f}} + }; + + D3D10_BUFFER_DESC desc; + desc.ByteWidth = sizeof(vertex) * NUM_VERTS; + desc.Usage = D3D10_USAGE_DEFAULT; + desc.BindFlags = D3D10_BIND_VERTEX_BUFFER; + desc.CPUAccessFlags = 0; + desc.MiscFlags = 0; + + D3D10_SUBRESOURCE_DATA srd = {}; + srd.pSysMem = (const void*)verts; + + hr = data.device->CreateBuffer(&desc, &srd, &data.vertex_buffer); + if (FAILED(hr)) { + hlog_hr("d3d10_init_vertex_buffer: failed to create vertex " + "buffer", hr); + return false; + } + + return true; +} + +static bool d3d10_init_scaling(void) +{ + bool success; + + success = create_d3d10_tex(data.base_cx, data.base_cy, + &data.scale_tex, &data.scale_resource, nullptr, + nullptr); + if (!success) { + hlog("d3d10_init_scaling: failed to create scale texture"); + return false; + } + + if (!d3d10_init_vertex_shader()) { + return false; + } + if (!d3d10_init_pixel_shader()) { + return false; + } + if (!d3d10_init_sampler_state()) { + return false; + } + if (!d3d10_init_blend_state()) { + return false; + } + if (!d3d10_init_zstencil_state()) { + return false; + } + if (!d3d10_init_raster_state()) { + return false; + } + if (!d3d10_init_vertex_buffer()) { + return false; + } + + return true; +} + +static bool d3d10_shmem_init_buffers(size_t idx) +{ + bool success; + + success = create_d3d10_stage_surface(&data.copy_surfaces[idx]); + if (!success) { + hlog("d3d10_shmem_init_buffers: failed to create copy surface"); + return false; + } + + if (idx == 0) { + D3D10_MAPPED_TEXTURE2D map = {}; + HRESULT hr; + + hr = data.copy_surfaces[idx]->Map(0, D3D10_MAP_READ, 0, &map); + if (FAILED(hr)) { + hlog_hr("d3d10_shmem_init_buffers: failed to get " + "pitch", hr); + return false; + } + + data.pitch = map.RowPitch; + data.copy_surfaces[idx]->Unmap(0); + } + + success = create_d3d10_tex(data.cx, data.cy, &data.textures[idx], + nullptr, &data.render_targets[idx], nullptr); + if (!success) { + hlog("d3d10_shmem_init_buffers: failed to create texture"); + return false; + } + + return true; +} + +static bool d3d10_shmem_init(HWND window) +{ + data.using_shtex = false; + + for (size_t i = 0; i < NUM_BUFFERS; i++) { + if (!d3d10_shmem_init_buffers(i)) { + return false; + } + } + if (!capture_init_shmem(&data.shmem_info, window, + data.base_cx, data.base_cy, data.cx, data.cy, + data.pitch, data.format, false)) { + return false; + } + + hlog("d3d10 memory capture successful"); + return true; +} + +static bool d3d10_shtex_init(HWND window) +{ + ID3D10ShaderResourceView *resource = nullptr; + bool success; + + data.using_shtex = true; + + success = create_d3d10_tex(data.cx, data.cy, &data.texture, &resource, + &data.render_target, &data.handle); + if (resource) + resource->Release(); + + if (!success) { + hlog("d3d10_shtex_init: failed to create texture"); + return false; + } + if (!capture_init_shtex(&data.shtex_info, window, + data.base_cx, data.base_cy, data.cx, data.cy, + data.format, false, (uint32_t)data.handle)) { + return false; + } + + hlog("d3d10 shared texture capture successful"); + return true; +} + +static void d3d10_init(IDXGISwapChain *swap) +{ + bool success = true; + HWND window; + HRESULT hr; + + data.using_scale = global_hook_info->use_scale; + + hr = swap->GetDevice(__uuidof(ID3D10Device), (void**)&data.device); + if (FAILED(hr)) { + hlog_hr("d3d10_init: failed to get device from swap", hr); + return; + } + + /* remove the unneeded extra reference */ + data.device->Release(); + + if (!d3d10_init_format(swap, window)) { + return; + } + if (data.using_scale && !d3d10_init_scaling()) { + hlog("d3d10_init: failed to initialize scaling"); + success = false; + } + if (success) { + if (global_hook_info->force_shmem) { + success = d3d10_shmem_init(window); + } else { + success = d3d10_shtex_init(window); + } + } + + if (!success) + d3d10_free(); +} + +#define MAX_RENDER_TARGETS D3D10_SIMULTANEOUS_RENDER_TARGET_COUNT +#define MAX_SO_TARGETS 4 + +struct d3d10_state { + ID3D10GeometryShader *geom_shader; + ID3D10InputLayout *vertex_layout; + D3D10_PRIMITIVE_TOPOLOGY topology; + ID3D10Buffer *vertex_buffer; + UINT vb_stride; + UINT vb_offset; + ID3D10BlendState *blend_state; + float blend_factor[4]; + UINT sample_mask; + ID3D10DepthStencilState *zstencil_state; + UINT zstencil_ref; + ID3D10RenderTargetView *render_targets[MAX_RENDER_TARGETS]; + ID3D10DepthStencilView *zstencil_view; + ID3D10SamplerState *sampler_state; + ID3D10PixelShader *pixel_shader; + ID3D10ShaderResourceView *resource; + ID3D10RasterizerState *raster_state; + UINT num_viewports; + D3D10_VIEWPORT *viewports; + ID3D10Buffer *stream_output_targets[MAX_SO_TARGETS]; + UINT so_offsets[MAX_SO_TARGETS]; + ID3D10VertexShader *vertex_shader; +}; + +static inline void d3d10_save_state(struct d3d10_state *state) +{ + data.device->GSGetShader(&state->geom_shader); + data.device->IAGetInputLayout(&state->vertex_layout); + data.device->IAGetPrimitiveTopology(&state->topology); + data.device->IAGetVertexBuffers(0, 1, &state->vertex_buffer, + &state->vb_stride, &state->vb_offset); + data.device->OMGetBlendState(&state->blend_state, state->blend_factor, + &state->sample_mask); + data.device->OMGetDepthStencilState(&state->zstencil_state, + &state->zstencil_ref); + data.device->OMGetRenderTargets(MAX_RENDER_TARGETS, + state->render_targets, &state->zstencil_view); + data.device->PSGetSamplers(0, 1, &state->sampler_state); + data.device->PSGetShader(&state->pixel_shader); + data.device->PSGetShaderResources(0, 1, &state->resource); + data.device->RSGetState(&state->raster_state); + data.device->RSGetViewports(&state->num_viewports, nullptr); + if (state->num_viewports) { + state->viewports = (D3D10_VIEWPORT*)malloc( + sizeof(D3D10_VIEWPORT) * state->num_viewports); + data.device->RSGetViewports(&state->num_viewports, + state->viewports); + } + data.device->SOGetTargets(MAX_SO_TARGETS, state->stream_output_targets, + state->so_offsets); + data.device->VSGetShader(&state->vertex_shader); +} + +static inline void safe_release(IUnknown *p) +{ + if (p) p->Release(); +} + +static inline void d3d10_restore_state(struct d3d10_state *state) +{ + data.device->GSSetShader(state->geom_shader); + data.device->IASetInputLayout(state->vertex_layout); + data.device->IASetPrimitiveTopology(state->topology); + data.device->IASetVertexBuffers(0, 1, &state->vertex_buffer, + &state->vb_stride, &state->vb_offset); + data.device->OMSetBlendState(state->blend_state, state->blend_factor, + state->sample_mask); + data.device->OMSetDepthStencilState(state->zstencil_state, + state->zstencil_ref); + data.device->OMSetRenderTargets(MAX_RENDER_TARGETS, + state->render_targets, state->zstencil_view); + data.device->PSSetSamplers(0, 1, &state->sampler_state); + data.device->PSSetShader(state->pixel_shader); + data.device->PSSetShaderResources(0, 1, &state->resource); + data.device->RSSetState(state->raster_state); + data.device->RSSetViewports(state->num_viewports, state->viewports); + data.device->SOSetTargets(MAX_SO_TARGETS, state->stream_output_targets, + state->so_offsets); + data.device->VSSetShader(state->vertex_shader); + safe_release(state->geom_shader); + safe_release(state->vertex_layout); + safe_release(state->vertex_buffer); + safe_release(state->blend_state); + safe_release(state->zstencil_state); + for (size_t i = 0; i < MAX_RENDER_TARGETS; i++) + safe_release(state->render_targets[i]); + safe_release(state->zstencil_view); + safe_release(state->sampler_state); + safe_release(state->pixel_shader); + safe_release(state->resource); + safe_release(state->raster_state); + for (size_t i = 0; i < MAX_SO_TARGETS; i++) + safe_release(state->stream_output_targets[i]); + safe_release(state->vertex_shader); + free(state->viewports); + memset(state, 0, sizeof(*state)); +} + +static inline void d3d10_setup_pipeline(ID3D10RenderTargetView *target, + ID3D10ShaderResourceView *resource) +{ + D3D10_VIEWPORT viewport = {0}; + const float factor[4] = {1.0f, 1.0f, 1.0f, 1.0f}; + void *emptyptr = nullptr; + UINT stride = sizeof(vertex); + UINT zero = 0; + + viewport.Width = data.cx; + viewport.Height = data.cy; + viewport.MaxDepth = 1.0f; + + data.device->GSSetShader(nullptr); + data.device->IASetInputLayout(data.vertex_layout); + data.device->IASetPrimitiveTopology( + D3D10_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP); + data.device->IASetVertexBuffers(0, 1, &data.vertex_buffer, &stride, + &zero); + data.device->OMSetBlendState(data.blend_state, factor, 0xFFFFFFFF); + data.device->OMSetDepthStencilState(data.zstencil_state, 0); + data.device->OMSetRenderTargets(1, &target, nullptr); + data.device->PSSetSamplers(0, 1, &data.sampler_state); + data.device->PSSetShader(data.pixel_shader); + data.device->PSSetShaderResources(0, 1, &resource); + data.device->RSSetState(data.raster_state); + data.device->RSSetViewports(1, &viewport); + data.device->SOSetTargets(1, (ID3D10Buffer**)&emptyptr, &zero); + data.device->VSSetShader(data.vertex_shader); +} + +static inline void d3d10_scale_texture(ID3D10RenderTargetView *target, + ID3D10ShaderResourceView *resource) +{ + struct d3d10_state old_state = {}; + + d3d10_save_state(&old_state); + d3d10_setup_pipeline(target, resource); + data.device->Draw(4, 0); + d3d10_restore_state(&old_state); +} + +static inline void d3d10_copy_texture(ID3D10Resource *dst, ID3D10Resource *src) +{ + if (data.multisampled) { + data.device->ResolveSubresource(dst, 0, src, 0, data.format); + } else { + data.device->CopyResource(dst, src); + } +} + +static inline void d3d10_shtex_capture(ID3D10Resource *backbuffer) +{ + if (data.using_scale) { + d3d10_copy_texture(data.scale_tex, backbuffer); + d3d10_scale_texture(data.render_target, data.scale_resource); + } else { + d3d10_copy_texture(data.texture, backbuffer); + } +} + +static inline void d3d10_shmem_queue_copy() +{ + for (size_t i = 0; i < NUM_BUFFERS; i++) { + D3D10_MAPPED_TEXTURE2D map; + HRESULT hr; + + if (data.texture_ready[i]) { + data.texture_ready[i] = false; + + hr = data.copy_surfaces[i]->Map(0, D3D10_MAP_READ, + 0, &map); + if (SUCCEEDED(hr)) { + data.texture_mapped[i] = true; + shmem_copy_data(i, map.pData); + } + break; + } + } +} + +static inline void d3d10_shmem_capture(ID3D10Resource *backbuffer) +{ + int next_tex; + + d3d10_shmem_queue_copy(); + + next_tex = (data.cur_tex == NUM_BUFFERS - 1) ? 0 : data.cur_tex + 1; + + if (data.using_scale) { + d3d10_copy_texture(data.scale_tex, backbuffer); + d3d10_scale_texture(data.render_targets[data.cur_tex], + data.scale_resource); + } else { + d3d10_copy_texture(data.textures[data.cur_tex], backbuffer); + } + + if (data.copy_wait < NUM_BUFFERS - 1) { + data.copy_wait++; + } else { + ID3D10Texture2D *src = data.textures[next_tex]; + ID3D10Texture2D *dst = data.copy_surfaces[next_tex]; + + if (shmem_texture_data_lock(next_tex)) { + dst->Unmap(0); + data.texture_mapped[next_tex] = false; + shmem_texture_data_unlock(next_tex); + } + + d3d10_copy_texture(dst, src); + data.texture_ready[next_tex] = true; + } + + data.cur_tex = next_tex; +} + +void d3d10_capture(void *swap_ptr, void *backbuffer_ptr) +{ + IDXGIResource *dxgi_backbuffer = (IDXGIResource*)backbuffer_ptr; + IDXGISwapChain *swap = (IDXGISwapChain*)swap_ptr; + + HRESULT hr; + if (capture_should_stop()) { + d3d10_free(); + } + if (capture_should_init()) { + d3d10_init(swap); + } + if (capture_ready()) { + ID3D10Resource *backbuffer; + + hr = dxgi_backbuffer->QueryInterface(__uuidof(ID3D10Resource), + (void**)&backbuffer); + if (FAILED(hr)) { + hlog_hr("d3d10_shtex_capture: failed to get " + "backbuffer", hr); + return; + } + + if (data.using_shtex) + d3d10_shtex_capture(backbuffer); + else + d3d10_shmem_capture(backbuffer); + + backbuffer->Release(); + } +} diff --git a/plugins/win-capture/graphics-hook/d3d11-capture.cpp b/plugins/win-capture/graphics-hook/d3d11-capture.cpp new file mode 100644 index 000000000..6e65a2a69 --- /dev/null +++ b/plugins/win-capture/graphics-hook/d3d11-capture.cpp @@ -0,0 +1,847 @@ +#define _CRT_SECURE_NO_WARNINGS +#include +#include + +#include "dxgi-helpers.hpp" +#include "graphics-hook.h" +#include "../funchook.h" + +struct d3d11_data { + ID3D11Device *device; /* do not release */ + ID3D11DeviceContext *context; /* do not release */ + uint32_t base_cx; + uint32_t base_cy; + uint32_t cx; + uint32_t cy; + DXGI_FORMAT format; + bool using_shtex : 1; + bool using_scale : 1; + bool multisampled : 1; + + ID3D11Texture2D *scale_tex; + ID3D11ShaderResourceView *scale_resource; + + ID3D11VertexShader *vertex_shader; + ID3D11InputLayout *vertex_layout; + ID3D11PixelShader *pixel_shader; + + ID3D11SamplerState *sampler_state; + ID3D11BlendState *blend_state; + ID3D11DepthStencilState *zstencil_state; + ID3D11RasterizerState *raster_state; + + ID3D11Buffer *vertex_buffer; + + union { + /* shared texture */ + struct { + struct shtex_data *shtex_info; + ID3D11Texture2D *texture; + ID3D11RenderTargetView *render_target; + HANDLE handle; + }; + /* shared memory */ + struct { + ID3D11Texture2D *copy_surfaces[NUM_BUFFERS]; + ID3D11Texture2D *textures[NUM_BUFFERS]; + ID3D11RenderTargetView *render_targets[NUM_BUFFERS]; + bool texture_ready[NUM_BUFFERS]; + bool texture_mapped[NUM_BUFFERS]; + uint32_t pitch; + struct shmem_data *shmem_info; + int cur_tex; + int copy_wait; + }; + }; +}; + +struct d3d11_data data = {}; + +void d3d11_free(void) +{ + if (data.scale_tex) + data.scale_tex->Release(); + if (data.scale_resource) + data.scale_resource->Release(); + if (data.vertex_shader) + data.vertex_shader->Release(); + if (data.vertex_layout) + data.vertex_layout->Release(); + if (data.pixel_shader) + data.pixel_shader->Release(); + if (data.sampler_state) + data.sampler_state->Release(); + if (data.blend_state) + data.blend_state->Release(); + if (data.zstencil_state) + data.zstencil_state->Release(); + if (data.raster_state) + data.raster_state->Release(); + if (data.vertex_buffer) + data.vertex_buffer->Release(); + + capture_free(); + + if (data.using_shtex) { + if (data.texture) + data.texture->Release(); + if (data.render_target) + data.render_target->Release(); + } else { + for (size_t i = 0; i < NUM_BUFFERS; i++) { + if (data.copy_surfaces[i]) { + if (data.texture_mapped[i]) + data.context->Unmap( + data.copy_surfaces[i], + 0); + data.copy_surfaces[i]->Release(); + } + if (data.textures[i]) + data.textures[i]->Release(); + if (data.render_targets[i]) + data.render_targets[i]->Release(); + } + } + + memset(&data, 0, sizeof(data)); + + hlog("----------------- d3d11 capture freed ----------------"); +} + +static bool create_d3d11_stage_surface(ID3D11Texture2D **tex) +{ + HRESULT hr; + + D3D11_TEXTURE2D_DESC desc = {}; + desc.Width = data.cx; + desc.Height = data.cy; + desc.Format = data.format; + desc.MipLevels = 1; + desc.ArraySize = 1; + desc.SampleDesc.Count = 1; + desc.Usage = D3D11_USAGE_STAGING; + desc.CPUAccessFlags = D3D11_CPU_ACCESS_READ; + + hr = data.device->CreateTexture2D(&desc, nullptr, tex); + if (FAILED(hr)) { + hlog_hr("create_d3d11_stage_surface: failed to create texture", + hr); + return false; + } + + return true; +} + +static bool create_d3d11_tex(uint32_t cx, uint32_t cy, + ID3D11Texture2D **tex, + ID3D11ShaderResourceView **resource, + ID3D11RenderTargetView **render_target, + HANDLE *handle) +{ + UINT flags = 0; + UINT misc_flags = 0; + HRESULT hr; + + if (!!resource) + flags |= D3D11_BIND_SHADER_RESOURCE; + if (!!render_target) + flags |= D3D11_BIND_RENDER_TARGET; + if (!!handle) + misc_flags |= D3D11_RESOURCE_MISC_SHARED; + + D3D11_TEXTURE2D_DESC desc = {}; + desc.Width = cx; + desc.Height = cy; + desc.MipLevels = 1; + desc.ArraySize = 1; + desc.Format = data.format; + desc.BindFlags = flags; + desc.SampleDesc.Count = 1; + desc.Usage = D3D11_USAGE_DEFAULT; + desc.MiscFlags = misc_flags; + + hr = data.device->CreateTexture2D(&desc, nullptr, tex); + if (FAILED(hr)) { + hlog_hr("create_d3d11_tex: failed to create texture", hr); + return false; + } + + if (!!resource) { + D3D11_SHADER_RESOURCE_VIEW_DESC res_desc = {}; + res_desc.Format = data.format; + res_desc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D; + res_desc.Texture2D.MipLevels = 1; + + hr = data.device->CreateShaderResourceView(*tex, &res_desc, + resource); + if (FAILED(hr)) { + hlog_hr("create_d3d11_tex: failed to create resource " + "view", hr); + return false; + } + } + + if (!!render_target) { + hr = data.device->CreateRenderTargetView(*tex, nullptr, + render_target); + if (FAILED(hr)) { + hlog_hr("create_d3d11_tex: failed to create render " + "target view", hr); + return false; + } + } + + if (!!handle) { + IDXGIResource *dxgi_res; + hr = (*tex)->QueryInterface(__uuidof(IDXGIResource), + (void**)&dxgi_res); + if (FAILED(hr)) { + hlog_hr("create_d3d11_tex: failed to query " + "IDXGIResource interface from texture", hr); + return false; + } + + hr = dxgi_res->GetSharedHandle(handle); + dxgi_res->Release(); + if (FAILED(hr)) { + hlog_hr("create_d3d11_tex: failed to get shared handle", + hr); + return false; + } + } + + return true; +} + +static inline bool d3d11_init_format(IDXGISwapChain *swap, HWND &window) +{ + DXGI_SWAP_CHAIN_DESC desc; + HRESULT hr; + + hr = swap->GetDesc(&desc); + if (FAILED(hr)) { + hlog_hr("d3d11_init_format: swap->GetDesc failed", hr); + return false; + } + + data.format = fix_dxgi_format(desc.BufferDesc.Format); + data.multisampled = desc.SampleDesc.Count > 1; + window = desc.OutputWindow; + data.base_cx = desc.BufferDesc.Width; + data.base_cy = desc.BufferDesc.Height; + + if (data.using_scale) { + data.cx = global_hook_info->cx; + data.cy = global_hook_info->cy; + } else { + data.cx = desc.BufferDesc.Width; + data.cy = desc.BufferDesc.Height; + } + return true; +} + +static inline bool d3d11_init_vertex_shader(void) +{ + D3D11_INPUT_ELEMENT_DESC desc[2]; + uint8_t *vs_data; + size_t size; + HRESULT hr; + + vs_data = get_d3d1x_vertex_shader(&size); + + hr = data.device->CreateVertexShader(vs_data, size, nullptr, + &data.vertex_shader); + if (FAILED(hr)) { + hlog_hr("d3d11_init_vertex_shader: failed to create shader", + hr); + return false; + } + + desc[0].SemanticName = "SV_Position"; + desc[0].SemanticIndex = 0; + desc[0].Format = DXGI_FORMAT_R32G32B32A32_FLOAT; + desc[0].InputSlot = 0; + desc[0].AlignedByteOffset = 0; + desc[0].InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA; + desc[0].InstanceDataStepRate = 0; + + desc[1].SemanticName = "TEXCOORD"; + desc[1].SemanticIndex = 0; + desc[1].Format = DXGI_FORMAT_R32G32_FLOAT; + desc[1].InputSlot = 0; + desc[1].AlignedByteOffset = D3D11_APPEND_ALIGNED_ELEMENT; + desc[1].InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA; + desc[1].InstanceDataStepRate = 0; + + hr = data.device->CreateInputLayout(desc, 2, vs_data, size, + &data.vertex_layout); + if (FAILED(hr)) { + hlog_hr("d3d11_init_vertex_shader: failed to create layout", + hr); + return false; + } + + return true; +} + +static inline bool d3d11_init_pixel_shader(void) +{ + uint8_t *ps_data; + size_t size; + HRESULT hr; + + ps_data = get_d3d1x_pixel_shader(&size); + + hr = data.device->CreatePixelShader(ps_data, size, nullptr, + &data.pixel_shader); + if (FAILED(hr)) { + hlog_hr("d3d11_init_pixel_shader: failed to create shader", hr); + return false; + } + + return true; +} + +static inline bool d3d11_init_sampler_state(void) +{ + HRESULT hr; + + D3D11_SAMPLER_DESC desc = {}; + desc.Filter = D3D11_FILTER_MIN_MAG_MIP_LINEAR; + desc.AddressU = D3D11_TEXTURE_ADDRESS_CLAMP; + desc.AddressV = D3D11_TEXTURE_ADDRESS_CLAMP; + desc.AddressW = D3D11_TEXTURE_ADDRESS_CLAMP; + + hr = data.device->CreateSamplerState(&desc, &data.sampler_state); + if (FAILED(hr)) { + hlog_hr("d3d11_init_sampler_state: failed to create sampler " + "state", hr); + return false; + } + + return true; +} + +static inline bool d3d11_init_blend_state(void) +{ + D3D11_BLEND_DESC desc = {}; + HRESULT hr; + + for (size_t i = 0; i < 8; i++) + desc.RenderTarget[i].RenderTargetWriteMask = + D3D11_COLOR_WRITE_ENABLE_ALL; + + hr = data.device->CreateBlendState(&desc, &data.blend_state); + if (FAILED(hr)) { + hlog_hr("d3d11_init_blend_state: failed to create blend state", + hr); + return false; + } + + return true; +} + +static inline bool d3d11_init_zstencil_state(void) +{ + D3D11_DEPTH_STENCIL_DESC desc = {}; /* defaults all to off */ + HRESULT hr; + + hr = data.device->CreateDepthStencilState(&desc, &data.zstencil_state); + if (FAILED(hr)) { + hlog_hr("d3d11_init_zstencil_state: failed to create " + "zstencil state", hr); + return false; + } + + return true; +} + +static inline bool d3d11_init_raster_state(void) +{ + D3D11_RASTERIZER_DESC desc = {}; + HRESULT hr; + + desc.FillMode = D3D11_FILL_SOLID; + desc.CullMode = D3D11_CULL_NONE; + + hr = data.device->CreateRasterizerState(&desc, &data.raster_state); + if (FAILED(hr)) { + hlog_hr("d3d11_init_raster_state: failed to create raster " + "state", hr); + return false; + } + + return true; +} + +#define NUM_VERTS 4 + +static inline bool d3d11_init_vertex_buffer(void) +{ + HRESULT hr; + const vertex verts[NUM_VERTS] = { + {{-1.0f, 1.0f, 0.0f, 1.0f}, {0.0f, 0.0f}}, + {{-1.0f, -1.0f, 0.0f, 1.0f}, {0.0f, 1.0f}}, + {{ 1.0f, 1.0f, 0.0f, 1.0f}, {1.0f, 0.0f}}, + {{ 1.0f, -1.0f, 0.0f, 1.0f}, {1.0f, 1.0f}} + }; + + D3D11_BUFFER_DESC desc; + desc.ByteWidth = sizeof(vertex) * NUM_VERTS; + desc.Usage = D3D11_USAGE_DEFAULT; + desc.BindFlags = D3D11_BIND_VERTEX_BUFFER; + desc.CPUAccessFlags = 0; + desc.MiscFlags = 0; + + D3D11_SUBRESOURCE_DATA srd = {}; + srd.pSysMem = (const void*)verts; + + hr = data.device->CreateBuffer(&desc, &srd, &data.vertex_buffer); + if (FAILED(hr)) { + hlog_hr("d3d11_init_vertex_buffer: failed to create vertex " + "buffer", hr); + return false; + } + + return true; +} + +static bool d3d11_init_scaling(void) +{ + bool success; + + success = create_d3d11_tex(data.base_cx, data.base_cy, + &data.scale_tex, &data.scale_resource, nullptr, + nullptr); + if (!success) { + hlog("d3d11_init_scaling: failed to create scale texture"); + return false; + } + + if (!d3d11_init_vertex_shader()) { + return false; + } + if (!d3d11_init_pixel_shader()) { + return false; + } + if (!d3d11_init_sampler_state()) { + return false; + } + if (!d3d11_init_blend_state()) { + return false; + } + if (!d3d11_init_zstencil_state()) { + return false; + } + if (!d3d11_init_raster_state()) { + return false; + } + if (!d3d11_init_vertex_buffer()) { + return false; + } + + return true; +} + +static bool d3d11_shmem_init_buffers(size_t idx) +{ + bool success; + + success = create_d3d11_stage_surface(&data.copy_surfaces[idx]); + if (!success) { + hlog("d3d11_shmem_init_buffers: failed to create copy surface"); + return false; + } + + if (idx == 0) { + D3D11_MAPPED_SUBRESOURCE map = {}; + HRESULT hr; + + hr = data.context->Map(data.copy_surfaces[idx], 0, + D3D11_MAP_READ, 0, &map); + if (FAILED(hr)) { + hlog_hr("d3d11_shmem_init_buffers: failed to get " + "pitch", hr); + return false; + } + + data.pitch = map.RowPitch; + data.context->Unmap(data.copy_surfaces[idx], 0); + } + + success = create_d3d11_tex(data.cx, data.cy, &data.textures[idx], + nullptr, &data.render_targets[idx], nullptr); + if (!success) { + hlog("d3d11_shmem_init_buffers: failed to create texture"); + return false; + } + + return true; +} + +static bool d3d11_shmem_init(HWND window) +{ + data.using_shtex = false; + + for (size_t i = 0; i < NUM_BUFFERS; i++) { + if (!d3d11_shmem_init_buffers(i)) { + return false; + } + } + if (!capture_init_shmem(&data.shmem_info, window, + data.base_cx, data.base_cy, data.cx, data.cy, + data.pitch, data.format, false)) { + return false; + } + + hlog("d3d11 memory capture successful"); + return true; +} + +static bool d3d11_shtex_init(HWND window) +{ + ID3D11ShaderResourceView *resource = nullptr; + bool success; + + data.using_shtex = true; + + success = create_d3d11_tex(data.cx, data.cy, &data.texture, &resource, + &data.render_target, &data.handle); + if (resource) + resource->Release(); + + if (!success) { + hlog("d3d11_shtex_init: failed to create texture"); + return false; + } + if (!capture_init_shtex(&data.shtex_info, window, + data.base_cx, data.base_cy, data.cx, data.cy, + data.format, false, (uint32_t)data.handle)) { + return false; + } + + hlog("d3d11 shared texture capture successful"); + return true; +} + +static void d3d11_init(IDXGISwapChain *swap) +{ + bool success = true; + HWND window; + HRESULT hr; + + data.using_scale = global_hook_info->use_scale; + + hr = swap->GetDevice(__uuidof(ID3D11Device), (void**)&data.device); + if (FAILED(hr)) { + hlog_hr("d3d11_init: failed to get device from swap", hr); + return; + } + + data.device->Release(); + + data.device->GetImmediateContext(&data.context); + data.context->Release(); + + if (!d3d11_init_format(swap, window)) { + return; + } + if (data.using_scale && !d3d11_init_scaling()) { + hlog("d3d11_init: failed to initialize scaling"); + success = false; + } + if (success) { + if (global_hook_info->force_shmem) { + success = d3d11_shmem_init(window); + } else { + success = d3d11_shtex_init(window); + } + } + + if (!success) + d3d11_free(); +} + +#define MAX_RENDER_TARGETS D3D11_SIMULTANEOUS_RENDER_TARGET_COUNT +#define MAX_SO_TARGETS 4 +#define MAX_CLASS_INSTS 256 + +struct d3d11_state { + ID3D11GeometryShader *geom_shader; + ID3D11InputLayout *vertex_layout; + D3D11_PRIMITIVE_TOPOLOGY topology; + ID3D11Buffer *vertex_buffer; + UINT vb_stride; + UINT vb_offset; + ID3D11BlendState *blend_state; + float blend_factor[4]; + UINT sample_mask; + ID3D11DepthStencilState *zstencil_state; + UINT zstencil_ref; + ID3D11RenderTargetView *render_targets[MAX_RENDER_TARGETS]; + ID3D11DepthStencilView *zstencil_view; + ID3D11SamplerState *sampler_state; + ID3D11PixelShader *pixel_shader; + ID3D11ShaderResourceView *resource; + ID3D11RasterizerState *raster_state; + UINT num_viewports; + D3D11_VIEWPORT *viewports; + ID3D11Buffer *stream_output_targets[MAX_SO_TARGETS]; + ID3D11VertexShader *vertex_shader; + ID3D11ClassInstance *gs_class_instances[MAX_CLASS_INSTS]; + ID3D11ClassInstance *ps_class_instances[MAX_CLASS_INSTS]; + ID3D11ClassInstance *vs_class_instances[MAX_CLASS_INSTS]; + UINT gs_class_inst_count; + UINT ps_class_inst_count; + UINT vs_class_inst_count; +}; + +static inline void d3d11_save_state(struct d3d11_state *state) +{ + state->gs_class_inst_count = MAX_CLASS_INSTS; + state->ps_class_inst_count = MAX_CLASS_INSTS; + state->vs_class_inst_count = MAX_CLASS_INSTS; + + data.context->GSGetShader(&state->geom_shader, + state->gs_class_instances, + &state->gs_class_inst_count); + data.context->IAGetInputLayout(&state->vertex_layout); + data.context->IAGetPrimitiveTopology(&state->topology); + data.context->IAGetVertexBuffers(0, 1, &state->vertex_buffer, + &state->vb_stride, &state->vb_offset); + data.context->OMGetBlendState(&state->blend_state, state->blend_factor, + &state->sample_mask); + data.context->OMGetDepthStencilState(&state->zstencil_state, + &state->zstencil_ref); + data.context->OMGetRenderTargets(MAX_RENDER_TARGETS, + state->render_targets, &state->zstencil_view); + data.context->PSGetSamplers(0, 1, &state->sampler_state); + data.context->PSGetShader(&state->pixel_shader, + state->ps_class_instances, + &state->ps_class_inst_count); + data.context->PSGetShaderResources(0, 1, &state->resource); + data.context->RSGetState(&state->raster_state); + data.context->RSGetViewports(&state->num_viewports, nullptr); + if (state->num_viewports) { + state->viewports = (D3D11_VIEWPORT*)malloc( + sizeof(D3D11_VIEWPORT) * state->num_viewports); + data.context->RSGetViewports(&state->num_viewports, + state->viewports); + } + data.context->SOGetTargets(MAX_SO_TARGETS, + state->stream_output_targets); + data.context->VSGetShader(&state->vertex_shader, + state->vs_class_instances, + &state->vs_class_inst_count); +} + +static inline void safe_release(IUnknown *p) +{ + if (p) p->Release(); +} + +#define SO_APPEND ((UINT)-1) + +static inline void d3d11_restore_state(struct d3d11_state *state) +{ + UINT so_offsets[MAX_SO_TARGETS] = + {SO_APPEND, SO_APPEND, SO_APPEND, SO_APPEND}; + + data.context->GSSetShader(state->geom_shader, + state->gs_class_instances, + state->gs_class_inst_count); + data.context->IASetInputLayout(state->vertex_layout); + data.context->IASetPrimitiveTopology(state->topology); + data.context->IASetVertexBuffers(0, 1, &state->vertex_buffer, + &state->vb_stride, &state->vb_offset); + data.context->OMSetBlendState(state->blend_state, state->blend_factor, + state->sample_mask); + data.context->OMSetDepthStencilState(state->zstencil_state, + state->zstencil_ref); + data.context->OMSetRenderTargets(MAX_RENDER_TARGETS, + state->render_targets, + state->zstencil_view); + data.context->PSSetSamplers(0, 1, &state->sampler_state); + data.context->PSSetShader(state->pixel_shader, + state->ps_class_instances, + state->ps_class_inst_count); + data.context->PSSetShaderResources(0, 1, &state->resource); + data.context->RSSetState(state->raster_state); + data.context->RSSetViewports(state->num_viewports, state->viewports); + data.context->SOSetTargets(MAX_SO_TARGETS, + state->stream_output_targets, so_offsets); + data.context->VSSetShader(state->vertex_shader, + state->vs_class_instances, + state->vs_class_inst_count); + safe_release(state->geom_shader); + safe_release(state->vertex_layout); + safe_release(state->vertex_buffer); + safe_release(state->blend_state); + safe_release(state->zstencil_state); + for (size_t i = 0; i < MAX_RENDER_TARGETS; i++) + safe_release(state->render_targets[i]); + safe_release(state->zstencil_view); + safe_release(state->sampler_state); + safe_release(state->pixel_shader); + safe_release(state->resource); + safe_release(state->raster_state); + for (size_t i = 0; i < MAX_SO_TARGETS; i++) + safe_release(state->stream_output_targets[i]); + safe_release(state->vertex_shader); + for (size_t i = 0; i < state->gs_class_inst_count; i++) + state->gs_class_instances[i]->Release(); + for (size_t i = 0; i < state->ps_class_inst_count; i++) + state->ps_class_instances[i]->Release(); + for (size_t i = 0; i < state->vs_class_inst_count; i++) + state->vs_class_instances[i]->Release(); + free(state->viewports); + memset(state, 0, sizeof(*state)); +} + +static inline void d3d11_setup_pipeline(ID3D11RenderTargetView *target, + ID3D11ShaderResourceView *resource) +{ + const float factor[4] = {1.0f, 1.0f, 1.0f, 1.0f}; + D3D11_VIEWPORT viewport = {0}; + UINT stride = sizeof(vertex); + void *emptyptr = nullptr; + UINT zero = 0; + + viewport.Width = (float)data.cx; + viewport.Height = (float)data.cy; + viewport.MaxDepth = 1.0f; + + data.context->GSSetShader(nullptr, nullptr, 0); + data.context->IASetInputLayout(data.vertex_layout); + data.context->IASetPrimitiveTopology( + D3D11_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP); + data.context->IASetVertexBuffers(0, 1, &data.vertex_buffer, &stride, + &zero); + data.context->OMSetBlendState(data.blend_state, factor, 0xFFFFFFFF); + data.context->OMSetDepthStencilState(data.zstencil_state, 0); + data.context->OMSetRenderTargets(1, &target, nullptr); + data.context->PSSetSamplers(0, 1, &data.sampler_state); + data.context->PSSetShader(data.pixel_shader, nullptr, 0); + data.context->PSSetShaderResources(0, 1, &resource); + data.context->RSSetState(data.raster_state); + data.context->RSSetViewports(1, &viewport); + data.context->SOSetTargets(1, (ID3D11Buffer**)&emptyptr, &zero); + data.context->VSSetShader(data.vertex_shader, nullptr, 0); +} + +static inline void d3d11_scale_texture(ID3D11RenderTargetView *target, + ID3D11ShaderResourceView *resource) +{ + static struct d3d11_state old_state = {0}; + + d3d11_save_state(&old_state); + d3d11_setup_pipeline(target, resource); + data.context->Draw(4, 0); + d3d11_restore_state(&old_state); +} + +static inline void d3d11_copy_texture(ID3D11Resource *dst, ID3D11Resource *src) +{ + if (data.multisampled) { + data.context->ResolveSubresource(dst, 0, src, 0, data.format); + } else { + data.context->CopyResource(dst, src); + } +} + +static inline void d3d11_shtex_capture(ID3D11Resource *backbuffer) +{ + if (data.using_scale) { + d3d11_copy_texture(data.scale_tex, backbuffer); + d3d11_scale_texture(data.render_target, data.scale_resource); + } else { + d3d11_copy_texture(data.texture, backbuffer); + } +} + +static inline void d3d11_shmem_queue_copy() +{ + for (size_t i = 0; i < NUM_BUFFERS; i++) { + D3D11_MAPPED_SUBRESOURCE map; + HRESULT hr; + + if (data.texture_ready[i]) { + data.texture_ready[i] = false; + + hr = data.context->Map(data.copy_surfaces[i], 0, + D3D11_MAP_READ, 0, &map); + if (SUCCEEDED(hr)) { + data.texture_mapped[i] = true; + shmem_copy_data(i, map.pData); + } + break; + } + } +} + +static inline void d3d11_shmem_capture(ID3D11Resource *backbuffer) +{ + int next_tex; + + d3d11_shmem_queue_copy(); + + next_tex = (data.cur_tex == NUM_BUFFERS - 1) ? 0 : data.cur_tex + 1; + + if (data.using_scale) { + d3d11_copy_texture(data.scale_tex, backbuffer); + d3d11_scale_texture(data.render_targets[data.cur_tex], + data.scale_resource); + } else { + d3d11_copy_texture(data.textures[data.cur_tex], backbuffer); + } + + if (data.copy_wait < NUM_BUFFERS - 1) { + data.copy_wait++; + } else { + ID3D11Texture2D *src = data.textures[next_tex]; + ID3D11Texture2D *dst = data.copy_surfaces[next_tex]; + + if (shmem_texture_data_lock(next_tex)) { + data.context->Unmap(dst, 0); + data.texture_mapped[next_tex] = false; + shmem_texture_data_unlock(next_tex); + } + + d3d11_copy_texture(dst, src); + data.texture_ready[next_tex] = true; + } + + data.cur_tex = next_tex; +} + +void d3d11_capture(void *swap_ptr, void *backbuffer_ptr) +{ + IDXGIResource *dxgi_backbuffer = (IDXGIResource*)backbuffer_ptr; + IDXGISwapChain *swap = (IDXGISwapChain*)swap_ptr; + + HRESULT hr; + if (capture_should_stop()) { + d3d11_free(); + } + if (capture_should_init()) { + d3d11_init(swap); + } + if (capture_ready()) { + ID3D11Resource *backbuffer; + + hr = dxgi_backbuffer->QueryInterface(__uuidof(ID3D11Resource), + (void**)&backbuffer); + if (FAILED(hr)) { + hlog_hr("d3d11_shtex_capture: failed to get " + "backbuffer", hr); + return; + } + + if (data.using_shtex) + d3d11_shtex_capture(backbuffer); + else + d3d11_shmem_capture(backbuffer); + + backbuffer->Release(); + } +} diff --git a/plugins/win-capture/graphics-hook/d3d1x_shaders.hpp b/plugins/win-capture/graphics-hook/d3d1x_shaders.hpp new file mode 100644 index 000000000..3191c9750 --- /dev/null +++ b/plugins/win-capture/graphics-hook/d3d1x_shaders.hpp @@ -0,0 +1,33 @@ +#pragma once + +static const char vertex_shader_string[] = +"struct VertData \ +{ \ + float4 pos : SV_Position; \ + float2 texCoord : TexCoord0; \ +}; \ +VertData main(VertData input) \ +{ \ + VertData output; \ + output.pos = input.pos; \ + output.texCoord = input.texCoord; \ + return output; \ +}"; + +static const char pixel_shader_string[] = +"uniform Texture2D diffuseTexture; \ +SamplerState textureSampler \ +{ \ + AddressU = Clamp; \ + AddressV = Clamp; \ + Filter = Linear; \ +}; \ +struct VertData \ +{ \ + float4 pos : SV_Position; \ + float2 texCoord : TexCoord0; \ +}; \ +float4 main(VertData input) : SV_Target \ +{ \ + return diffuseTexture.Sample(textureSampler, input.texCoord); \ +}"; diff --git a/plugins/win-capture/graphics-hook/d3d9-capture.cpp b/plugins/win-capture/graphics-hook/d3d9-capture.cpp new file mode 100644 index 000000000..d37267d43 --- /dev/null +++ b/plugins/win-capture/graphics-hook/d3d9-capture.cpp @@ -0,0 +1,795 @@ +#define _CRT_SECURE_NO_WARNINGS +#include +#include +#include + +#include "graphics-hook.h" +#include "../funchook.h" +#include "d3d9-patches.hpp" + +typedef HRESULT (STDMETHODCALLTYPE *present_t)(IDirect3DDevice9*, + CONST RECT*, CONST RECT*, HWND, CONST RGNDATA*); +typedef HRESULT (STDMETHODCALLTYPE *present_ex_t)(IDirect3DDevice9*, + CONST RECT*, CONST RECT*, HWND, CONST RGNDATA*, DWORD); +typedef HRESULT (STDMETHODCALLTYPE *present_swap_t)(IDirect3DSwapChain9*, + CONST RECT*, CONST RECT*, HWND, CONST RGNDATA*, DWORD); +typedef HRESULT (STDMETHODCALLTYPE *reset_t)(IDirect3DDevice9*, + D3DPRESENT_PARAMETERS*); +typedef HRESULT (STDMETHODCALLTYPE *reset_ex_t)(IDirect3DDevice9*, + D3DPRESENT_PARAMETERS*, D3DDISPLAYMODEEX*); + +typedef HRESULT (WINAPI *createfactory1_t)(REFIID, void **); + +static struct func_hook present; +static struct func_hook present_ex; +static struct func_hook present_swap; +static struct func_hook reset; +static struct func_hook reset_ex; + +struct d3d9_data { + HMODULE d3d9; + IDirect3DDevice9 *device; /* do not release */ + uint32_t cx; + uint32_t cy; + D3DFORMAT d3d9_format; + DXGI_FORMAT dxgi_format; + bool using_shtex : 1; + bool using_scale : 1; + + union { + /* shared texture */ + struct { + IDirect3DSurface9 *d3d9_copytex; + ID3D11Device *d3d11_device; + ID3D11DeviceContext *d3d11_context; + ID3D11Resource *d3d11_tex; + struct shtex_data *shtex_info; + HANDLE handle; + int patch; + }; + /* shared memory */ + struct { + IDirect3DSurface9 *copy_surfaces[NUM_BUFFERS]; + IDirect3DSurface9 *render_targets[NUM_BUFFERS]; + IDirect3DQuery9 *queries[NUM_BUFFERS]; + struct shmem_data *shmem_info; + volatile bool issued_queries[NUM_BUFFERS]; + bool texture_mapped[NUM_BUFFERS]; + uint32_t pitch; + int cur_tex; + int copy_wait; + }; + }; +}; + +static struct d3d9_data data = {}; + +static void d3d9_free() +{ + capture_free(); + + if (data.using_shtex) { + if (data.d3d11_tex) + data.d3d11_tex->Release(); + if (data.d3d11_context) + data.d3d11_context->Release(); + if (data.d3d11_device) + data.d3d11_device->Release(); + if (data.d3d9_copytex) + data.d3d9_copytex->Release(); + } else { + for (size_t i = 0; i < NUM_BUFFERS; i++) { + if (data.copy_surfaces[i]) { + if (data.texture_mapped[i]) + data.copy_surfaces[i]->UnlockRect(); + data.copy_surfaces[i]->Release(); + } + if (data.render_targets[i]) + data.render_targets[i]->Release(); + if (data.queries[i]) + data.queries[i]->Release(); + } + } + + memset(&data, 0, sizeof(data)); + + hlog("----------------- d3d9 capture freed -----------------"); +} + +static DXGI_FORMAT d3d9_to_dxgi_format(D3DFORMAT format) +{ + switch (format) { + case D3DFMT_A2B10G10R10: return DXGI_FORMAT_R10G10B10A2_UNORM; + case D3DFMT_A8R8G8B8: return DXGI_FORMAT_B8G8R8A8_UNORM; + case D3DFMT_X8R8G8B8: return DXGI_FORMAT_B8G8R8X8_UNORM; + } + + return DXGI_FORMAT_UNKNOWN; +} + +const static D3D_FEATURE_LEVEL feature_levels[] = +{ + D3D_FEATURE_LEVEL_11_0, + D3D_FEATURE_LEVEL_10_1, + D3D_FEATURE_LEVEL_10_0, + D3D_FEATURE_LEVEL_9_3, +}; + +static inline bool shex_init_d3d11() +{ + PFN_D3D11_CREATE_DEVICE create_device; + createfactory1_t create_factory; + D3D_FEATURE_LEVEL level_used; + IDXGIFactory *factory; + IDXGIAdapter *adapter; + HMODULE d3d11; + HMODULE dxgi; + HRESULT hr; + + d3d11 = load_system_library("d3d11.dll"); + if (!d3d11) { + hlog("d3d9_init: Failed to load D3D11"); + return false; + } + + dxgi = load_system_library("dxgi.dll"); + if (!dxgi) { + hlog("d3d9_init: Failed to load DXGI"); + return false; + } + + create_factory = (createfactory1_t)GetProcAddress(dxgi, + "CreateDXGIFactory1"); + if (!create_factory) { + hlog("d3d9_init: Failed to get CreateDXGIFactory1 address"); + return false; + } + + create_device = (PFN_D3D11_CREATE_DEVICE)GetProcAddress(d3d11, + "D3D11CreateDevice"); + if (!create_device) { + hlog("d3d9_init: Failed to get D3D11CreateDevice address"); + return false; + } + + hr = create_factory(__uuidof(IDXGIFactory1), (void**)&factory); + if (FAILED(hr)) { + hlog_hr("d3d9_init: Failed to create factory object", hr); + return false; + } + + hr = factory->EnumAdapters(0, &adapter); + factory->Release(); + + if (FAILED(hr)) { + hlog_hr("d3d9_init: Failed to get adapter", hr); + return false; + } + + hr = create_device(adapter, D3D_DRIVER_TYPE_UNKNOWN, nullptr, + 0, feature_levels, + sizeof(feature_levels) / sizeof(D3D_FEATURE_LEVEL), + D3D11_SDK_VERSION, &data.d3d11_device, &level_used, + &data.d3d11_context); + adapter->Release(); + + if (FAILED(hr)) { + hlog_hr("d3d9_init: Failed to create D3D11 device", hr); + return false; + } + + return true; +} + +static inline bool d3d9_shtex_init_shtex() +{ + IDXGIResource *res; + HRESULT hr; + + D3D11_TEXTURE2D_DESC desc = {}; + desc.Width = data.cx; + desc.Height = data.cy; + desc.Format = data.dxgi_format; + desc.MipLevels = 1; + desc.ArraySize = 1; + desc.SampleDesc.Count = 1; + desc.Usage = D3D11_USAGE_DEFAULT; + desc.MiscFlags = D3D11_RESOURCE_MISC_SHARED; + desc.BindFlags = D3D11_BIND_RENDER_TARGET | + D3D11_BIND_SHADER_RESOURCE; + + hr = data.d3d11_device->CreateTexture2D(&desc, nullptr, + (ID3D11Texture2D**)&data.d3d11_tex); + if (FAILED(hr)) { + hlog_hr("d3d9_shtex_init_shtex: Failed to create D3D11 texture", + hr); + return false; + } + + hr = data.d3d11_tex->QueryInterface(__uuidof(IDXGIResource), + (void**)&res); + if (FAILED(hr)) { + hlog_hr("d3d9_shtex_init_shtex: Failed to query IDXGIResource", + hr); + return false; + } + + hr = res->GetSharedHandle(&data.handle); + res->Release(); + + if (FAILED(hr)) { + hlog_hr("d3d9_shtex_init_shtex: Failed to get shared handle", + hr); + return false; + } + + return true; +} + +static inline bool d3d9_shtex_init_copytex() +{ + uint8_t *patch_addr = get_d3d9_patch_addr(data.d3d9, data.patch); + uint8_t saved_data[MAX_PATCH_SIZE]; + size_t patch_size = 0; + IDirect3DTexture9 *tex; + DWORD protect_val; + HRESULT hr; + + if (patch_addr) { + patch_size = patch[data.patch].size; + VirtualProtect(patch_addr, patch_size, PAGE_EXECUTE_READWRITE, + &protect_val); + memcpy(saved_data, patch_addr, patch_size); + memcpy(patch_addr, patch[data.patch].data, patch_size); + } + + hr = data.device->CreateTexture(data.cx, data.cy, 1, + D3DUSAGE_RENDERTARGET, data.d3d9_format, + D3DPOOL_DEFAULT, &tex, &data.handle); + + if (patch_addr && patch_size) { + memcpy(patch_addr, saved_data, patch_size); + VirtualProtect(patch_addr, patch_size, protect_val, + &protect_val); + } + + if (FAILED(hr)) { + hlog_hr("d3d9_shtex_init_copytex: Failed to create shared texture", + hr); + return false; + } + + hr = tex->GetSurfaceLevel(0, &data.d3d9_copytex); + tex->Release(); + + if (FAILED(hr)) { + hlog_hr("d3d9_shtex_init_copytex: Failed to get surface level", hr); + return false; + } + + return true; +} + +static bool d3d9_shtex_init(uint32_t cx, uint32_t cy, HWND window) +{ + data.using_shtex = true; + + if (!shex_init_d3d11()) { + return false; + } + if (!d3d9_shtex_init_shtex()) { + return false; + } + if (!d3d9_shtex_init_copytex()) { + return false; + } + if (!capture_init_shtex(&data.shtex_info, window, cx, cy, + data.cx, data.cy, data.dxgi_format, false, + (uint32_t)data.handle)) { + return false; + } + + hlog("d3d9 shared texture capture successful"); + return true; +} + +static bool d3d9_shmem_init_buffers(size_t buffer) +{ + HRESULT hr; + + hr = data.device->CreateOffscreenPlainSurface(data.cx, data.cy, + data.d3d9_format, D3DPOOL_SYSTEMMEM, + &data.copy_surfaces[buffer], nullptr); + if (FAILED(hr)) { + hlog_hr("d3d9_shmem_init_buffers: Failed to create surface", + hr); + return false; + } + + if (buffer == 0) { + D3DLOCKED_RECT rect; + hr = data.copy_surfaces[buffer]->LockRect(&rect, nullptr, + D3DLOCK_READONLY); + if (FAILED(hr)) { + hlog_hr("d3d9_shmem_init_buffers: Failed to lock " + "buffer", hr); + return false; + } + + data.pitch = rect.Pitch; + data.copy_surfaces[buffer]->UnlockRect(); + } + + hr = data.device->CreateRenderTarget(data.cx, data.cy, + data.d3d9_format, D3DMULTISAMPLE_NONE, 0, false, + &data.render_targets[buffer], nullptr); + if (FAILED(hr)) { + hlog_hr("d3d9_shmem_init_buffers: Failed to create render " + "target", hr); + return false; + } + + hr = data.device->CreateQuery(D3DQUERYTYPE_EVENT, + &data.queries[buffer]); + if (FAILED(hr)) { + hlog_hr("d3d9_shmem_init_buffers: Failed to create query", hr); + return false; + } + + return true; +} + +static bool d3d9_shmem_init(uint32_t cx, uint32_t cy, HWND window) +{ + data.using_shtex = false; + + for (size_t i = 0; i < NUM_BUFFERS; i++) { + if (!d3d9_shmem_init_buffers(i)) { + return false; + } + } + if (!capture_init_shmem(&data.shmem_info, window, cx, cy, + data.cx, data.cy, data.pitch, data.dxgi_format, + false)) { + return false; + } + + hlog("d3d9 memory capture successful"); + return true; +} + +static bool d3d9_get_swap_desc(D3DPRESENT_PARAMETERS &pp) +{ + IDirect3DSwapChain9 *swap = nullptr; + HRESULT hr; + + hr = data.device->GetSwapChain(0, &swap); + if (FAILED(hr)) { + hlog_hr("d3d9_get_swap_desc: Failed to get swap chain", hr); + return false; + } + + hr = swap->GetPresentParameters(&pp); + swap->Release(); + + if (FAILED(hr)) { + hlog_hr("d3d9_get_swap_desc: Failed to get " + "presentation parameters", hr); + return false; + } + + return true; +} + +static bool d3d9_init_format_backbuffer(uint32_t &cx, uint32_t &cy, + HWND &window) +{ + IDirect3DSurface9 *back_buffer = nullptr; + D3DPRESENT_PARAMETERS pp; + D3DSURFACE_DESC desc; + HRESULT hr; + + if (!d3d9_get_swap_desc(pp)) { + return false; + } + + hr = data.device->GetRenderTarget(0, &back_buffer); + if (FAILED(hr)) { + return false; + } + + hr = back_buffer->GetDesc(&desc); + back_buffer->Release(); + + if (FAILED(hr)) { + hlog_hr("d3d9_init_format_backbuffer: Failed to get " + "backbuffer descriptor", hr); + return false; + } + + data.d3d9_format = desc.Format; + data.dxgi_format = d3d9_to_dxgi_format(desc.Format); + data.using_scale = global_hook_info->use_scale; + window = pp.hDeviceWindow; + cx = desc.Width; + cy = desc.Height; + + if (data.using_scale) { + data.cx = global_hook_info->cx; + data.cy = global_hook_info->cy; + } else { + data.cx = desc.Width; + data.cy = desc.Height; + } + + return true; +} + +static bool d3d9_init_format_swapchain(uint32_t cx, uint32_t cy, HWND window) +{ + D3DPRESENT_PARAMETERS pp; + + if (!d3d9_get_swap_desc(pp)) { + return false; + } + + data.dxgi_format = d3d9_to_dxgi_format(pp.BackBufferFormat); + data.d3d9_format = pp.BackBufferFormat; + data.using_scale = global_hook_info->use_scale; + window = pp.hDeviceWindow; + cx = pp.BackBufferWidth; + cy = pp.BackBufferHeight; + + if (data.using_scale) { + data.cx = global_hook_info->cx; + data.cy = global_hook_info->cy; + } else { + data.cx = pp.BackBufferWidth; + data.cy = pp.BackBufferHeight; + } + + return true; +} + +static void d3d9_init(IDirect3DDevice9 *device) +{ + IDirect3DDevice9Ex *d3d9ex = nullptr; + bool success; + uint32_t cx; + uint32_t cy; + HWND window; + HRESULT hr; + + data.d3d9 = get_system_module("d3d9.dll"); + data.device = device; + + hr = device->QueryInterface(__uuidof(IDirect3DDevice9Ex), + (void**)&d3d9ex); + if (SUCCEEDED(hr)) { + d3d9ex->Release(); + data.patch = -1; + } else { + data.patch = get_d3d9_patch(data.d3d9); + } + + if (!d3d9_init_format_backbuffer(cx, cy, window)) { + if (!d3d9_init_format_swapchain(cx, cy, window)) { + return; + } + } + + if (global_hook_info->force_shmem || (!d3d9ex && data.patch == -1)) { + success = d3d9_shmem_init(cx, cy, window); + } else { + success = d3d9_shtex_init(cx, cy, window); + } + + if (!success) + d3d9_free(); +} + +static inline HRESULT get_backbuffer(IDirect3DDevice9 *device, + IDirect3DSurface9 **surface) +{ + static bool use_backbuffer = false; + static bool checked_exceptions = false; + + if (!checked_exceptions) { + if (_strcmpi(get_process_name(), "hotd_ng.exe") == 0) + use_backbuffer = true; + checked_exceptions = true; + } + + if (use_backbuffer) { + return device->GetBackBuffer(0, 0, D3DBACKBUFFER_TYPE_MONO, + surface); + } else { + return device->GetRenderTarget(0, surface); + } +} + +static inline void d3d9_shtex_capture(IDirect3DSurface9 *backbuffer) +{ + D3DTEXTUREFILTERTYPE filter; + HRESULT hr; + + filter = data.using_scale ? D3DTEXF_LINEAR : D3DTEXF_NONE; + + hr = data.device->StretchRect(backbuffer, nullptr, data.d3d9_copytex, + nullptr, filter); + if (FAILED(hr)) + hlog_hr("d3d9_shtex_capture: StretchRect failed", hr); +} + +static inline void d3d9_shmem_capture_queue_copy() +{ + for (int i = 0; i < NUM_BUFFERS; i++) { + IDirect3DSurface9 *target = data.copy_surfaces[i]; + D3DLOCKED_RECT rect; + HRESULT hr; + + if (!data.issued_queries[i]) { + continue; + } + if (data.queries[i]->GetData(0, 0, 0) != S_OK) { + continue; + } + + data.issued_queries[i] = false; + + hr = target->LockRect(&rect, nullptr, D3DLOCK_READONLY); + if (SUCCEEDED(hr)) { + data.texture_mapped[i] = true; + shmem_copy_data(i, rect.pBits); + } + break; + } +} + +static inline void d3d9_shmem_capture(IDirect3DSurface9 *backbuffer) +{ + D3DTEXTUREFILTERTYPE filter; + IDirect3DSurface9 *copy; + int next_tex; + HRESULT hr; + + d3d9_shmem_capture_queue_copy(); + + next_tex = (data.cur_tex == NUM_BUFFERS - 1) ? 0 : data.cur_tex + 1; + filter = data.using_scale ? D3DTEXF_LINEAR : D3DTEXF_NONE; + copy = data.render_targets[data.cur_tex]; + + hr = data.device->StretchRect(backbuffer, nullptr, copy, nullptr, + filter); + + if (FAILED(hr)) { + hlog_hr("d3d9_shmem_capture: StretchRect failed", hr); + return; + } + + if (data.copy_wait < NUM_BUFFERS - 1) { + data.copy_wait++; + } else { + IDirect3DSurface9 *src = data.render_targets[next_tex]; + IDirect3DSurface9 *dst = data.copy_surfaces[next_tex]; + + if (shmem_texture_data_lock(next_tex)) { + dst->UnlockRect(); + data.texture_mapped[next_tex] = false; + shmem_texture_data_unlock(next_tex); + } + + hr = data.device->GetRenderTargetData(src, dst); + if (FAILED(hr)) { + hlog_hr("d3d9_shmem_capture: GetRenderTargetData " + "failed", hr); + } + + data.queries[next_tex]->Issue(D3DISSUE_END); + data.issued_queries[next_tex] = true; + } + + data.cur_tex = next_tex; +} + +static void d3d9_capture(IDirect3DDevice9 *device, + IDirect3DSurface9 *backbuffer) +{ + if (capture_should_stop()) { + d3d9_free(); + } + if (capture_should_init()) { + d3d9_init(device); + } + if (capture_ready()) { + if (data.using_shtex) + d3d9_shtex_capture(backbuffer); + else + d3d9_shmem_capture(backbuffer); + } +} + +/* this is used just in case Present calls PresentEx or vise versa. */ +static int present_recurse = 0; + +static inline void present_begin(IDirect3DDevice9 *device, + IDirect3DSurface9 *&backbuffer) +{ + HRESULT hr; + + if (!present_recurse) { + hr = get_backbuffer(device, &backbuffer); + if (FAILED(hr)) { + hlog_hr("d3d9_shmem_capture: Failed to get " + "backbuffer", hr); + } + + if (!global_hook_info->capture_overlay) { + d3d9_capture(device, backbuffer); + } + } + + present_recurse++; +} + +static inline void present_end(IDirect3DDevice9 *device, + IDirect3DSurface9 *backbuffer) +{ + present_recurse--; + + if (!present_recurse) { + if (global_hook_info->capture_overlay) { + if (!present_recurse) + d3d9_capture(device, backbuffer); + } + + if (backbuffer) + backbuffer->Release(); + } +} + +static HRESULT STDMETHODCALLTYPE hook_present(IDirect3DDevice9 *device, + CONST RECT *src_rect, CONST RECT *dst_rect, + HWND override_window, CONST RGNDATA *dirty_region) +{ + IDirect3DSurface9 *backbuffer = nullptr; + HRESULT hr; + + present_begin(device, backbuffer); + + unhook(&present); + present_t call = (present_t)present.call_addr; + hr = call(device, src_rect, dst_rect, override_window, dirty_region); + rehook(&present); + + present_end(device, backbuffer); + + return hr; +} + +static HRESULT STDMETHODCALLTYPE hook_present_ex(IDirect3DDevice9 *device, + CONST RECT *src_rect, CONST RECT *dst_rect, + HWND override_window, CONST RGNDATA *dirty_region, DWORD flags) +{ + IDirect3DSurface9 *backbuffer = nullptr; + HRESULT hr; + + present_begin(device, backbuffer); + + unhook(&present_ex); + present_ex_t call = (present_ex_t)present_ex.call_addr; + hr = call(device, src_rect, dst_rect, override_window, dirty_region, + flags); + rehook(&present_ex); + + present_end(device, backbuffer); + + return hr; +} + +static HRESULT STDMETHODCALLTYPE hook_present_swap(IDirect3DSwapChain9 *swap, + CONST RECT *src_rect, CONST RECT *dst_rect, + HWND override_window, CONST RGNDATA *dirty_region, DWORD flags) +{ + IDirect3DSurface9 *backbuffer = nullptr; + IDirect3DDevice9 *device = nullptr; + HRESULT hr; + + if (!present_recurse) { + hr = swap->GetDevice(&device); + if (SUCCEEDED(hr)) { + device->Release(); + } + } + + if (device) + present_begin(device, backbuffer); + + unhook(&present_swap); + present_swap_t call = (present_swap_t)present_swap.call_addr; + hr = call(swap, src_rect, dst_rect, override_window, dirty_region, + flags); + rehook(&present_swap); + + if (device) + present_end(device, backbuffer); + + return hr; +} + +static HRESULT STDMETHODCALLTYPE hook_reset(IDirect3DDevice9 *device, + D3DPRESENT_PARAMETERS *params) +{ + HRESULT hr; + + if (capture_active()) + d3d9_free(); + + unhook(&reset); + reset_t call = (reset_t)reset.call_addr; + hr = call(device, params); + rehook(&reset); + + return hr; +} + +static HRESULT STDMETHODCALLTYPE hook_reset_ex(IDirect3DDevice9 *device, + D3DPRESENT_PARAMETERS *params, D3DDISPLAYMODEEX *dmex) +{ + HRESULT hr; + + if (capture_active()) + d3d9_free(); + + unhook(&reset_ex); + reset_ex_t call = (reset_ex_t)reset_ex.call_addr; + hr = call(device, params, dmex); + rehook(&reset_ex); + + return hr; +} + +bool hook_d3d9(void) +{ + HMODULE d3d9_module = get_system_module("d3d9.dll"); + void *present_addr; + void *present_ex_addr; + void *present_swap_addr; + void *reset_addr; + void *reset_ex_addr; + + if (!d3d9_module) { + return false; + } + + present_addr = get_offset_addr(d3d9_module, + global_hook_info->offsets.d3d9.present); + present_ex_addr = get_offset_addr(d3d9_module, + global_hook_info->offsets.d3d9.present_ex); + present_swap_addr = get_offset_addr(d3d9_module, + global_hook_info->offsets.d3d9.present_swap); + reset_addr = get_offset_addr(d3d9_module, + global_hook_info->offsets.d3d9.reset); + reset_ex_addr = get_offset_addr(d3d9_module, + global_hook_info->offsets.d3d9.reset_ex); + + hook_init(&present, present_addr, hook_present, + "IDirect3DDevice9::Present"); + hook_init(&present_ex, present_ex_addr, hook_present_ex, + "IDirect3DDevice9Ex::PresentEx"); + hook_init(&present_swap, present_swap_addr, hook_present_swap, + "IDirect3DSwapChain9::Present"); + hook_init(&reset, reset_addr, hook_reset, + "IDirect3DDevice9::Reset"); + hook_init(&reset_ex, reset_ex_addr, hook_reset_ex, + "IDirect3DDevice9Ex::ResetEx"); + + rehook(&reset_ex); + rehook(&reset); + rehook(&present_swap); + rehook(&present_ex); + rehook(&present); + + hlog("Hooked D3D9"); + return true; +} diff --git a/plugins/win-capture/graphics-hook/d3d9-patches.hpp b/plugins/win-capture/graphics-hook/d3d9-patches.hpp new file mode 100644 index 000000000..dbfed85ef --- /dev/null +++ b/plugins/win-capture/graphics-hook/d3d9-patches.hpp @@ -0,0 +1,135 @@ +#pragma once + +#include + +static inline int safe_memcmp(const void *p1, const void *p2, size_t size) +{ + __try { + return memcmp(p1, p2, size); + } __except (GetExceptionCode() == EXCEPTION_ACCESS_VIOLATION) { + return -1; + } +} + +struct patch_info { + size_t size; + const BYTE *data; +}; + +#define NEW_PATCH(x) {sizeof(x), (x)} +#define MAX_PATCH_SIZE 2 + +static const BYTE force_jump[] = {0xEB}; +static const BYTE ignore_jump[] = {0x90, 0x90}; + +#ifdef _WIN64 + +#define NUM_VERS (10) +#define CMP_SIZE (13) + +static const uintptr_t patch_offset[NUM_VERS] = { + 0x54FE6, //win7 - 6.1.7600.16385 + 0x55095, //win7 - 6.1.7601.16562 + 0x550C5, //win7 - 6.1.7601.17514 + 0x8BDB5, //win8.1 - 6.3.9431.00000 + 0x8E635, //win8.1 - 6.3.9600.17415 + 0x90352, //win8.1 - 6.3.9600.17085 + 0x9038A, //win8.1 - 6.3.9600.17095 + 0x93AFA, //win8.1 - 6.3.9600.16384 + 0x93B8A, //win8.1 - 6.3.9600.16404 + 0x1841E5 //win8 - 6.2.9200.16384 +}; + +static const uint8_t patch_cmp[NUM_VERS][CMP_SIZE] = { +{0x48, 0x8b, 0x81, 0xb8, 0x3d, 0x00, 0x00, 0x39, 0x98, 0x68, 0x50, 0x00, 0x00}, +{0x48, 0x8b, 0x81, 0xb8, 0x3d, 0x00, 0x00, 0x39, 0x98, 0x68, 0x50, 0x00, 0x00}, +{0x48, 0x8b, 0x81, 0xb8, 0x3d, 0x00, 0x00, 0x39, 0x98, 0x68, 0x50, 0x00, 0x00}, +{0x48, 0x8b, 0x81, 0xb8, 0x3d, 0x00, 0x00, 0x39, 0xB0, 0x28, 0x51, 0x00, 0x00}, +{0x48, 0x8b, 0x81, 0xb8, 0x3d, 0x00, 0x00, 0x39, 0xA8, 0x28, 0x51, 0x00, 0x00}, +{0x8b, 0x81, 0xb8, 0x3d, 0x00, 0x00, 0x44, 0x39, 0xA0, 0x28, 0x51, 0x00, 0x00}, +{0x8b, 0x81, 0xb8, 0x3d, 0x00, 0x00, 0x44, 0x39, 0xA0, 0x28, 0x51, 0x00, 0x00}, +{0x8b, 0x81, 0xb8, 0x3d, 0x00, 0x00, 0x44, 0x39, 0xA0, 0x28, 0x51, 0x00, 0x00}, +{0x8b, 0x81, 0xb8, 0x3d, 0x00, 0x00, 0x44, 0x39, 0xA0, 0x28, 0x51, 0x00, 0x00}, +{0x49, 0x8b, 0x85, 0xb8, 0x3d, 0x00, 0x00, 0x39, 0x88, 0xc8, 0x50, 0x00, 0x00}, +}; + +static const struct patch_info patch[NUM_VERS] = { + NEW_PATCH(force_jump), + NEW_PATCH(force_jump), + NEW_PATCH(force_jump), + NEW_PATCH(ignore_jump), + NEW_PATCH(ignore_jump), + NEW_PATCH(ignore_jump), + NEW_PATCH(ignore_jump), + NEW_PATCH(ignore_jump), + NEW_PATCH(ignore_jump), + NEW_PATCH(ignore_jump), +}; + +#else + +#define NUM_VERS (10) +#define CMP_SIZE (12) + +static const uintptr_t patch_offset[NUM_VERS] = { + 0x79AA6, //win7 - 6.1.7601.16562 + 0x79C9E, //win7 - 6.1.7600.16385 + 0x79D96, //win7 - 6.1.7601.17514 + 0x7F9BD, //win8.1 - 6.3.9431.00000 + 0x8A3F4, //win8.1 - 6.3.9600.16404 + 0x8E9F7, //win8.1 - 6.3.9600.17095 + 0x8F00F, //win8.1 - 6.3.9600.17085 + 0x8FBB1, //win8.1 - 6.3.9600.16384 + 0x90264, //win8.1 - 6.3.9600.17415 + 0x166A08 //win8 - 6.2.9200.16384 +}; + +static const uint8_t patch_cmp[NUM_VERS][CMP_SIZE] = { +{0x8b, 0x89, 0xe8, 0x29, 0x00, 0x00, 0x39, 0xb9, 0x80, 0x4b, 0x00, 0x00}, +{0x8b, 0x89, 0xe8, 0x29, 0x00, 0x00, 0x39, 0xb9, 0x80, 0x4b, 0x00, 0x00}, +{0x8b, 0x89, 0xe8, 0x29, 0x00, 0x00, 0x39, 0xb9, 0x80, 0x4b, 0x00, 0x00}, +{0x8b, 0x80, 0xe8, 0x29, 0x00, 0x00, 0x39, 0xb0, 0x40, 0x4c, 0x00, 0x00}, +{0x80, 0xe8, 0x29, 0x00, 0x00, 0x83, 0xb8, 0x40, 0x4c, 0x00, 0x00, 0x00}, +{0x80, 0xe8, 0x29, 0x00, 0x00, 0x83, 0xb8, 0x40, 0x4c, 0x00, 0x00, 0x00}, +{0x80, 0xe8, 0x29, 0x00, 0x00, 0x83, 0xb8, 0x40, 0x4c, 0x00, 0x00, 0x00}, +{0x80, 0xe8, 0x29, 0x00, 0x00, 0x83, 0xb8, 0x40, 0x4c, 0x00, 0x00, 0x00}, +{0x87, 0xe8, 0x29, 0x00, 0x00, 0x83, 0xb8, 0x40, 0x4c, 0x00, 0x00, 0x00}, +{0x8b, 0x80, 0xe8, 0x29, 0x00, 0x00, 0x39, 0x90, 0xb0, 0x4b, 0x00, 0x00}, +}; + +static const struct patch_info patch[NUM_VERS] = { + NEW_PATCH(force_jump), + NEW_PATCH(force_jump), + NEW_PATCH(force_jump), + NEW_PATCH(force_jump), + NEW_PATCH(force_jump), + NEW_PATCH(force_jump), + NEW_PATCH(force_jump), + NEW_PATCH(force_jump), + NEW_PATCH(ignore_jump), + NEW_PATCH(force_jump), +}; + +#endif + +static inline int get_d3d9_patch(HMODULE d3d9) +{ + uint8_t *addr = (uint8_t*)d3d9; + for (int i = 0; i < NUM_VERS; i++) { + int ret = safe_memcmp(addr + patch_offset[i], patch_cmp[i], + CMP_SIZE); + if (ret == 0) + return i; + } + + return -1; +} + +static inline uint8_t *get_d3d9_patch_addr(HMODULE d3d9, int patch) +{ + if (patch == -1) + return nullptr; + + uint8_t *addr = (uint8_t*)d3d9; + return addr + patch_offset[patch] + CMP_SIZE; +} diff --git a/plugins/win-capture/graphics-hook/dxgi-capture.cpp b/plugins/win-capture/graphics-hook/dxgi-capture.cpp new file mode 100644 index 000000000..295b8cc24 --- /dev/null +++ b/plugins/win-capture/graphics-hook/dxgi-capture.cpp @@ -0,0 +1,242 @@ +#define _CRT_SECURE_NO_WARNINGS +#include +#include +#include +#include + +#include "d3d1x_shaders.hpp" +#include "graphics-hook.h" +#include "../funchook.h" + +typedef HRESULT (WINAPI *resize_buffers_t)(IDXGISwapChain*, UINT, UINT, UINT, + DXGI_FORMAT, UINT); +typedef HRESULT (WINAPI *present_t)(IDXGISwapChain*, UINT, UINT); + +static struct func_hook resize_buffers; +static struct func_hook present; + +struct dxgi_swap_data { + IDXGISwapChain *swap; + void (*capture)(void*, void*); + void (*free)(void); +}; + +static struct dxgi_swap_data data = {}; + +static bool setup_dxgi(IDXGISwapChain *swap) +{ + const char *process_name = get_process_name(); + bool ignore_d3d10 = false; + IUnknown *device; + HRESULT hr; + + /* Call of duty ghosts allows the context to be queried as a d3d10 + * context when it's actually a d3d11 context. Why this is I don't + * quite know. */ + if (_strcmpi(process_name, "iw6sp64_ship.exe") == 0 || + _strcmpi(process_name, "iw6mp64_ship.exe") == 0) { + ignore_d3d10 = true; + } + + if (!ignore_d3d10) { + hr = swap->GetDevice(__uuidof(ID3D10Device), (void**)&device); + if (SUCCEEDED(hr)) { + data.swap = swap; + data.capture = d3d10_capture; + data.free = d3d10_free; + device->Release(); + return true; + } + } + + hr = swap->GetDevice(__uuidof(ID3D11Device), (void**)&device); + if (SUCCEEDED(hr)) { + data.swap = swap; + data.capture = d3d11_capture; + data.free = d3d11_free; + device->Release(); + return true; + } + + return false; +} + +static HRESULT STDMETHODCALLTYPE hook_resize_buffers(IDXGISwapChain *swap, + UINT buffer_count, UINT width, UINT height, DXGI_FORMAT format, + UINT flags) +{ + HRESULT hr; + + if (!!data.free) + data.free(); + + data.swap = nullptr; + data.free = nullptr; + data.capture = nullptr; + + unhook(&resize_buffers); + resize_buffers_t call = (resize_buffers_t)resize_buffers.call_addr; + hr = call(swap, buffer_count, width, height, format, flags); + rehook(&resize_buffers); + + return hr; +} + +static inline IDXGIResource *get_dxgi_backbuffer(IDXGISwapChain *swap) +{ + IDXGIResource *res = nullptr; + HRESULT hr; + + hr = swap->GetBuffer(0, __uuidof(ID3D11Resource), (void**)&res); + if (FAILED(hr)) + hlog_hr("get_dxgi_backbuffer: GetBuffer failed", hr); + + return res; +} + +static HRESULT STDMETHODCALLTYPE hook_present(IDXGISwapChain *swap, + UINT sync_interval, UINT flags) +{ + IDXGIResource *backbuffer = nullptr; + bool test_draw = (flags & DXGI_PRESENT_TEST) != 0; + bool capture; + HRESULT hr; + + if (!data.swap && !capture_active()) { + setup_dxgi(swap); + } + + capture = !test_draw && swap == data.swap && !!data.capture; + if (capture) { + backbuffer = get_dxgi_backbuffer(swap); + capture = !!backbuffer; + + if (capture && !global_hook_info->capture_overlay) + data.capture(swap, backbuffer); + } + + unhook(&present); + present_t call = (present_t)present.call_addr; + hr = call(swap, sync_interval, flags); + rehook(&present); + + if (capture) { + if (global_hook_info->capture_overlay) + data.capture(swap, backbuffer); + + backbuffer->Release(); + } + + return hr; +} + +static pD3DCompile get_compiler(void) +{ + pD3DCompile compile = nullptr; + char d3dcompiler[40] = {}; + int ver = 49; + + while (ver > 30) { + sprintf_s(d3dcompiler, 40, "D3DCompiler_%02d.dll", ver); + + HMODULE module = LoadLibraryA(d3dcompiler); + if (module) { + compile = (pD3DCompile)GetProcAddress(module, + "D3DCompile"); + if (compile) { + break; + } + } + + ver--; + } + + return compile; +} + +static uint8_t vertex_shader_data[1024]; +static uint8_t pixel_shader_data[1024]; +static size_t vertex_shader_size = 0; +static size_t pixel_shader_size = 0; + +bool hook_dxgi(void) +{ + pD3DCompile compile; + ID3D10Blob *blob; + HMODULE dxgi_module = get_system_module("dxgi.dll"); + HRESULT hr; + void *present_addr; + void *resize_addr; + + if (!dxgi_module) { + return false; + } + + compile = get_compiler(); + if (!compile) { + hlog("hook_dxgi: failed to find d3d compiler library"); + return true; + } + + /* ---------------------- */ + + hr = compile(vertex_shader_string, sizeof(vertex_shader_string), + "vertex_shader_string", nullptr, nullptr, "main", + "vs_4_0", D3D10_SHADER_OPTIMIZATION_LEVEL1, 0, &blob, + nullptr); + if (FAILED(hr)) { + hlog_hr("hook_dxgi: failed to compile vertex shader", hr); + return true; + } + + vertex_shader_size = (size_t)blob->GetBufferSize(); + memcpy(vertex_shader_data, blob->GetBufferPointer(), + blob->GetBufferSize()); + blob->Release(); + + /* ---------------------- */ + + hr = compile(pixel_shader_string, sizeof(pixel_shader_string), + "pixel_shader_string", nullptr, nullptr, "main", + "ps_4_0", D3D10_SHADER_OPTIMIZATION_LEVEL1, 0, &blob, + nullptr); + if (FAILED(hr)) { + hlog_hr("hook_dxgi: failed to compile pixel shader", hr); + return true; + } + + pixel_shader_size = (size_t)blob->GetBufferSize(); + memcpy(pixel_shader_data, blob->GetBufferPointer(), + blob->GetBufferSize()); + blob->Release(); + + /* ---------------------- */ + + present_addr = get_offset_addr(dxgi_module, + global_hook_info->offsets.dxgi.present); + resize_addr = get_offset_addr(dxgi_module, + global_hook_info->offsets.dxgi.resize); + + hook_init(&present, present_addr, hook_present, + "IDXGISwapChain::Present"); + hook_init(&resize_buffers, resize_addr, hook_resize_buffers, + "IDXGISwapChain::ResizeBuffers"); + + rehook(&resize_buffers); + rehook(&present); + + hlog("Hooked DXGI"); + return true; +} + +uint8_t *get_d3d1x_vertex_shader(size_t *size) +{ + *size = vertex_shader_size; + return vertex_shader_data; +} + +uint8_t *get_d3d1x_pixel_shader(size_t *size) +{ + *size = pixel_shader_size; + return pixel_shader_data; +} diff --git a/plugins/win-capture/graphics-hook/dxgi-helpers.hpp b/plugins/win-capture/graphics-hook/dxgi-helpers.hpp new file mode 100644 index 000000000..65a61065e --- /dev/null +++ b/plugins/win-capture/graphics-hook/dxgi-helpers.hpp @@ -0,0 +1,14 @@ +#pragma once + +static inline DXGI_FORMAT fix_dxgi_format(DXGI_FORMAT format) +{ + switch (format) { + case DXGI_FORMAT_B8G8R8A8_UNORM_SRGB: + return DXGI_FORMAT_B8G8R8A8_UNORM; + case DXGI_FORMAT_R8G8B8A8_UNORM_SRGB: + return DXGI_FORMAT_R8G8B8A8_UNORM; + } + + return format; +} + diff --git a/plugins/win-capture/graphics-hook/gl-capture.c b/plugins/win-capture/graphics-hook/gl-capture.c new file mode 100644 index 000000000..36761e2b3 --- /dev/null +++ b/plugins/win-capture/graphics-hook/gl-capture.c @@ -0,0 +1,872 @@ +#define _CRT_SECURE_NO_WARNINGS +#pragma warning(disable : 4214) /* nonstandard extension, non-int bitfield */ +#pragma warning(disable : 4054) /* function pointer to data pointer */ +#include + +#define COBJMACROS +#include +#include + +#include "gl-decs.h" +#include "graphics-hook.h" +#include "../funchook.h" + +#define DUMMY_WINDOW_CLASS_NAME L"graphics_hook_gl_dummy_window" + +static const GUID GUID_IDXGIFactory1 = +{0x770aae78, 0xf26f, 0x4dba, {0xa8, 0x29, 0x25, 0x3c, 0x83, 0xd1, 0xb3, 0x87}}; +static const GUID GUID_IDXGIResource = +{0x035f3ab4, 0x482e, 0x4e50, {0xb4, 0x1f, 0x8a, 0x7f, 0x8b, 0xd8, 0x96, 0x0b}}; + +static struct func_hook swap_buffers; +static struct func_hook wgl_swap_layer_buffers; +static struct func_hook wgl_swap_buffers; +static struct func_hook wgl_delete_context; + +struct gl_data { + HDC hdc; + uint32_t base_cx; + uint32_t base_cy; + uint32_t cx; + uint32_t cy; + DXGI_FORMAT format; + GLuint fbo; + bool using_shtex : 1; + bool using_scale : 1; + + union { + /* shared texture */ + struct { + struct shtex_data *shtex_info; + ID3D11Device *d3d11_device; + ID3D11DeviceContext *d3d11_context; + ID3D11Texture2D *d3d11_tex; + IDXGISwapChain *dxgi_swap; + HANDLE gl_device; + HANDLE gl_dxobj; + HANDLE handle; + HWND hwnd; + GLuint texture; + }; + /* shared memory */ + struct { + struct shmem_data *shmem_info; + int cur_tex; + int copy_wait; + GLuint pbos[NUM_BUFFERS]; + GLuint textures[NUM_BUFFERS]; + bool texture_ready[NUM_BUFFERS]; + bool texture_mapped[NUM_BUFFERS]; + }; + }; +}; + +static HMODULE gl = NULL; +static bool nv_capture_available = false; +static struct gl_data data = {0}; + +static inline bool gl_error(const char *func, const char *str) +{ + GLenum error = glGetError(); + if (error != 0) { + hlog("%s: %s: %lu", func, str, error); + return true; + } + + return false; +} + +static void gl_free(void) +{ + capture_free(); + + if (data.using_shtex) { + if (data.gl_dxobj) + jimglDXUnregisterObjectNV(data.gl_device, + data.gl_dxobj); + if (data.gl_device) + jimglDXCloseDeviceNV(data.gl_device); + if (data.texture) + glDeleteTextures(1, &data.texture); + if (data.d3d11_tex) + ID3D11Resource_Release(data.d3d11_tex); + if (data.d3d11_context) + ID3D11DeviceContext_Release(data.d3d11_context); + if (data.d3d11_device) + ID3D11Device_Release(data.d3d11_device); + if (data.dxgi_swap) + IDXGISwapChain_Release(data.dxgi_swap); + if (data.hwnd) + DestroyWindow(data.hwnd); + } else { + for (size_t i = 0; i < NUM_BUFFERS; i++) { + if (data.pbos[i]) { + if (data.texture_mapped[i]) { + glBindBuffer(GL_PIXEL_PACK_BUFFER, + data.pbos[i]); + glUnmapBuffer(GL_PIXEL_PACK_BUFFER); + glBindBuffer(GL_PIXEL_PACK_BUFFER, 0); + } + + glDeleteBuffers(1, &data.pbos[i]); + } + + if (data.textures[i]) + glDeleteTextures(1, &data.textures[i]); + } + } + + if (data.fbo) + glDeleteFramebuffers(1, &data.fbo); + + gl_error("gl_free", "GL error occurred on free"); + + memset(&data, 0, sizeof(data)); + + hlog("------------------ gl capture freed ------------------"); +} + +static inline void *base_get_proc(const char *name) +{ + return (void*)GetProcAddress(gl, name); +} + +static inline void *wgl_get_proc(const char *name) +{ + return (void*)jimglGetProcAddress(name); +} + +static inline void *get_proc(const char *name) +{ + void *func = wgl_get_proc(name); + if (!func) + func = base_get_proc(name); + + return func; +} + +static void init_nv_functions(void) +{ + jimglDXSetResourceShareHandleNV = + get_proc("wglDXSetResourceShareHandleNV"); + jimglDXOpenDeviceNV = get_proc("wglDXOpenDeviceNV"); + jimglDXCloseDeviceNV = get_proc("wglDXCloseDeviceNV"); + jimglDXRegisterObjectNV = get_proc("wglDXRegisterObjectNV"); + jimglDXUnregisterObjectNV = get_proc("wglDXUnregisterObjectNV"); + jimglDXObjectAccessNV = get_proc("wglDXObjectAccessNV"); + jimglDXLockObjectsNV = get_proc("wglDXLockObjectsNV"); + jimglDXUnlockObjectsNV = get_proc("wglDXUnlockObjectsNV"); + + nv_capture_available = + !!jimglDXSetResourceShareHandleNV && + !!jimglDXOpenDeviceNV && + !!jimglDXCloseDeviceNV && + !!jimglDXRegisterObjectNV && + !!jimglDXUnregisterObjectNV && + !!jimglDXObjectAccessNV && + !!jimglDXLockObjectsNV && + !!jimglDXUnlockObjectsNV; + + if (nv_capture_available) + hlog("Shared-texture OpenGL capture available"); +} + +#define GET_PROC(cur_func, ptr, func) \ + do { \ + ptr = get_proc(#func); \ + if (!ptr) { \ + hlog("%s: failed to get function '%s'", #cur_func, \ + #func); \ + success = false; \ + } \ + } while (false) + +static bool init_gl_functions(void) +{ + bool success = true; + + jimglGetProcAddress = base_get_proc("wglGetProcAddress"); + if (!jimglGetProcAddress) { + hlog("init_gl_functions: failed to get wglGetProcAddress"); + return false; + } + + GET_PROC(init_gl_functions, jimglMakeCurrent, wglMakeCurrent); + GET_PROC(init_gl_functions, jimglGetCurrentDC, wglGetCurrentDC); + GET_PROC(init_gl_functions, jimglGetCurrentContext, + wglGetCurrentContext); + GET_PROC(init_gl_functions, glTexImage2D, glTexImage2D); + GET_PROC(init_gl_functions, glReadBuffer, glReadBuffer); + GET_PROC(init_gl_functions, glGetTexImage, glGetTexImage); + GET_PROC(init_gl_functions, glDrawBuffer, glDrawBuffer); + GET_PROC(init_gl_functions, glGetError, glGetError); + GET_PROC(init_gl_functions, glBufferData, glBufferData); + GET_PROC(init_gl_functions, glDeleteBuffers, glDeleteBuffers); + GET_PROC(init_gl_functions, glDeleteTextures, glDeleteTextures); + GET_PROC(init_gl_functions, glGenBuffers, glGenBuffers); + GET_PROC(init_gl_functions, glGenTextures, glGenTextures); + GET_PROC(init_gl_functions, glMapBuffer, glMapBuffer); + GET_PROC(init_gl_functions, glUnmapBuffer, glUnmapBuffer); + GET_PROC(init_gl_functions, glBindBuffer, glBindBuffer); + GET_PROC(init_gl_functions, glGetIntegerv, glGetIntegerv); + GET_PROC(init_gl_functions, glBindTexture, glBindTexture); + GET_PROC(init_gl_functions, glGenFramebuffers, glGenFramebuffers); + GET_PROC(init_gl_functions, glDeleteFramebuffers, glDeleteFramebuffers); + GET_PROC(init_gl_functions, glBindFramebuffer, glBindFramebuffer); + GET_PROC(init_gl_functions, glBlitFramebuffer, glBlitFramebuffer); + GET_PROC(init_gl_functions, glFramebufferTexture2D, + glFramebufferTexture2D); + + init_nv_functions(); + return success; +} + +static void get_window_size(HDC hdc, uint32_t *cx, uint32_t *cy) +{ + HWND hwnd = WindowFromDC(hdc); + RECT rc = {0}; + + GetClientRect(hwnd, &rc); + *cx = rc.right; + *cy = rc.bottom; +} + +static inline bool gl_shtex_init_window(void) +{ + data.hwnd = CreateWindowExW(0, DUMMY_WINDOW_CLASS_NAME, + L"Dummy GL window, ignore", + WS_POPUP | WS_CLIPCHILDREN | WS_CLIPSIBLINGS, + 0, 0, 2, 2, NULL, NULL, GetModuleHandle(NULL), NULL); + if (!data.hwnd) { + hlog("gl_shtex_init_window: failed to create window: %d", + GetLastError()); + return false; + } + + return true; +} + +typedef HRESULT (WINAPI *create_dxgi_factory1_t)(REFIID, void **); + +const static D3D_FEATURE_LEVEL feature_levels[] = +{ + D3D_FEATURE_LEVEL_11_0, + D3D_FEATURE_LEVEL_10_1, + D3D_FEATURE_LEVEL_10_0, + D3D_FEATURE_LEVEL_9_3, +}; + +static inline bool gl_shtex_init_d3d11(void) +{ + D3D_FEATURE_LEVEL level_used; + IDXGIFactory1 *factory; + IDXGIAdapter *adapter; + HRESULT hr; + + HMODULE d3d11 = load_system_library("d3d11.dll"); + if (!d3d11) { + hlog("gl_shtex_init_d3d11: failed to load D3D11.dll: %d", + GetLastError()); + return false; + } + + HMODULE dxgi = load_system_library("dxgi.dll"); + if (!dxgi) { + hlog("gl_shtex_init_d3d11: failed to load DXGI.dll: %d", + GetLastError()); + return false; + } + + DXGI_SWAP_CHAIN_DESC desc = {0}; + desc.BufferCount = 2; + desc.BufferDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM; + desc.BufferDesc.Width = 2; + desc.BufferDesc.Height = 2; + desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; + desc.SampleDesc.Count = 1; + desc.Windowed = true; + desc.OutputWindow = data.hwnd; + + create_dxgi_factory1_t create_factory = (void*)GetProcAddress(dxgi, + "CreateDXGIFactory1"); + if (!create_factory) { + hlog("gl_shtex_init_d3d11: failed to load CreateDXGIFactory1 " + "procedure: %d", GetLastError()); + return false; + } + + PFN_D3D11_CREATE_DEVICE_AND_SWAP_CHAIN create = (void*)GetProcAddress( + d3d11, "D3D11CreateDeviceAndSwapChain"); + if (!create) { + hlog("gl_shtex_init_d3d11: failed to load " + "D3D11CreateDeviceAndSwapChain procedure: %d", + GetLastError()); + return false; + } + + hr = create_factory(&GUID_IDXGIFactory1, (void**)&factory); + if (FAILED(hr)) { + hlog_hr("gl_shtex_init_d3d11: failed to create factory", hr); + return false; + } + + hr = IDXGIFactory1_EnumAdapters1(factory, 0, (IDXGIAdapter1**)&adapter); + IDXGIFactory1_Release(factory); + + if (FAILED(hr)) { + hlog_hr("gl_shtex_init_d3d11: failed to create adapter", hr); + return false; + } + + hr = create(adapter, D3D_DRIVER_TYPE_UNKNOWN, NULL, 0, feature_levels, + sizeof(feature_levels) / sizeof(D3D_FEATURE_LEVEL), + D3D11_SDK_VERSION, &desc, &data.dxgi_swap, + &data.d3d11_device, &level_used, &data.d3d11_context); + IDXGIAdapter_Release(adapter); + + if (FAILED(hr)) { + hlog_hr("gl_shtex_init_d3d11: failed to create device", hr); + return false; + } + + return true; +} + +static inline bool gl_shtex_init_d3d11_tex(void) +{ + IDXGIResource *dxgi_res; + HRESULT hr; + + D3D11_TEXTURE2D_DESC desc = {0}; + desc.Width = data.cx; + desc.Height = data.cy; + desc.MipLevels = 1; + desc.ArraySize = 1; + desc.Format = DXGI_FORMAT_B8G8R8A8_UNORM; + desc.SampleDesc.Count = 1; + desc.Usage = D3D11_USAGE_DEFAULT; + desc.MiscFlags = D3D11_RESOURCE_MISC_SHARED; + desc.BindFlags = D3D11_BIND_RENDER_TARGET | + D3D11_BIND_SHADER_RESOURCE; + + hr = ID3D11Device_CreateTexture2D(data.d3d11_device, &desc, NULL, + &data.d3d11_tex); + if (FAILED(hr)) { + hlog_hr("gl_shtex_init_d3d11_tex: failed to create texture", + hr); + return false; + } + + hr = ID3D11Device_QueryInterface(data.d3d11_tex, + &GUID_IDXGIResource, (void**)&dxgi_res); + if (FAILED(hr)) { + hlog_hr("gl_shtex_init_d3d11_tex: failed to get IDXGIResource", + hr); + return false; + } + + hr = IDXGIResource_GetSharedHandle(dxgi_res, &data.handle); + IDXGIResource_Release(dxgi_res); + + if (FAILED(hr)) { + hlog_hr("gl_shtex_init_d3d11_tex: failed to get shared handle", + hr); + return false; + } + + return true; +} + +static inline bool gl_shtex_init_gl_tex(void) +{ + data.gl_device = jimglDXOpenDeviceNV(data.d3d11_device); + if (!data.gl_device) { + hlog("gl_shtex_init_gl_tex: failed to open device"); + return false; + } + + glGenTextures(1, &data.texture); + if (gl_error("gl_shtex_init_gl_tex", "failed to generate texture")) { + return false; + } + + data.gl_dxobj = jimglDXRegisterObjectNV(data.gl_device, data.d3d11_tex, + data.texture, GL_TEXTURE_2D, + WGL_ACCESS_WRITE_DISCARD_NV); + if (!data.gl_dxobj) { + hlog("gl_shtex_init_gl_tex: failed to register object"); + return false; + } + + return true; +} + +static inline bool gl_init_fbo(void) +{ + glGenFramebuffers(1, &data.fbo); + return !gl_error("gl_init_fbo", "failed to initialize FBO"); +} + +static bool gl_shtex_init(HWND window) +{ + if (!gl_shtex_init_window()) { + return false; + } + if (!gl_shtex_init_d3d11()) { + return false; + } + if (!gl_shtex_init_d3d11_tex()) { + return false; + } + if (!gl_shtex_init_gl_tex()) { + return false; + } + if (!gl_init_fbo()) { + return false; + } + if (!capture_init_shtex(&data.shtex_info, window, + data.base_cx, data.base_cy, data.cx, data.cy, + data.format, true, (uint32_t)data.handle)) { + return false; + } + + hlog("gl shared texture capture successful"); + return true; +} + +static inline bool gl_shmem_init_data(size_t idx, size_t size) +{ + glBindBuffer(GL_PIXEL_PACK_BUFFER, data.pbos[idx]); + if (gl_error("gl_shmem_init_data", "failed to bind pbo")) { + return false; + } + + glBufferData(GL_PIXEL_PACK_BUFFER, size, 0, GL_STREAM_READ); + if (gl_error("gl_shmem_init_data", "failed to set pbo data")) { + return false; + } + + glBindTexture(GL_TEXTURE_2D, data.textures[idx]); + if (gl_error("gl_shmem_init_data", "failed to set bind texture")) { + return false; + } + + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, data.cx, data.cy, + 0, GL_BGRA, GL_UNSIGNED_BYTE, NULL); + if (gl_error("gl_shmem_init_data", "failed to set texture data")) { + return false; + } + + return true; +} + +static inline bool gl_shmem_init_buffers(void) +{ + uint32_t size = data.cx * data.cy * 4; + GLint last_pbo; + GLint last_tex; + + glGenBuffers(NUM_BUFFERS, data.pbos); + if (gl_error("gl_shmem_init_buffers", "failed to generate buffers")) { + return false; + } + + glGenTextures(NUM_BUFFERS, data.textures); + if (gl_error("gl_shmem_init_buffers", "failed to generate textures")) { + return false; + } + + glGetIntegerv(GL_PIXEL_PACK_BUFFER_BINDING, &last_pbo); + if (gl_error("gl_shmem_init_buffers", + "failed to save pixel pack buffer")) { + return false; + } + + glGetIntegerv(GL_TEXTURE_BINDING_2D, &last_tex); + if (gl_error("gl_shmem_init_buffers", "failed to save texture")) { + return false; + } + + for (size_t i = 0; i < NUM_BUFFERS; i++) { + if (!gl_shmem_init_data(i, size)) { + return false; + } + } + + glBindBuffer(GL_PIXEL_PACK_BUFFER, last_pbo); + glBindTexture(GL_TEXTURE_2D, last_tex); + return true; +} + +static bool gl_shmem_init(HWND window) +{ + if (!gl_shmem_init_buffers()) { + return false; + } + if (!gl_init_fbo()) { + return false; + } + if (!capture_init_shmem(&data.shmem_info, window, + data.base_cx, data.base_cy, data.cx, data.cy, + data.cx * 4, data.format, true)) { + return false; + } + + hlog("gl memory capture successful"); + return true; +} + +static void gl_init(HDC hdc) +{ + HWND window = WindowFromDC(hdc); + bool success = false; + RECT rc = {0}; + + GetClientRect(window, &rc); + + data.base_cx = rc.right; + data.base_cy = rc.bottom; + data.hdc = hdc; + data.format = DXGI_FORMAT_B8G8R8A8_UNORM; + data.using_scale = global_hook_info->use_scale; + data.using_shtex = nv_capture_available && + !global_hook_info->force_shmem; + + if (data.using_scale) { + data.cx = global_hook_info->cx; + data.cy = global_hook_info->cy; + } else { + data.cx = data.base_cx; + data.cy = data.base_cy; + } + + if (data.using_shtex) + success = gl_shtex_init(window); + else + success = gl_shmem_init(window); + + if (!success) + gl_free(); +} + +static void gl_copy_backbuffer(GLuint dst) +{ + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, data.fbo); + if (gl_error("gl_shtex_capture_blit", "failed to bind FBO")) { + return; + } + + glBindTexture(GL_TEXTURE_2D, dst); + if (gl_error("gl_shtex_capture_blit", "failed to bind texture")) { + return; + } + + glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, + GL_TEXTURE_2D, dst, 0); + if (gl_error("gl_shtex_capture_blit", "failed to set frame buffer")) { + return; + } + + glReadBuffer(GL_BACK); + if (gl_error("gl_shtex_capture_blit", "failed to set read buffer")) { + return; + } + + glDrawBuffer(GL_COLOR_ATTACHMENT0); + if (gl_error("gl_shtex_capture_blit", "failed to set draw buffer")) { + return; + } + + glBlitFramebuffer(0, 0, data.base_cx, data.base_cy, + 0, 0, data.cx, data.cy, GL_COLOR_BUFFER_BIT, GL_LINEAR); + gl_error("gl_shtex_capture_blit", "failed to blit"); +} + +static void gl_shtex_capture(void) +{ + GLint last_fbo; + GLint last_tex; + + jimglDXLockObjectsNV(data.gl_device, 1, &data.gl_dxobj); + + glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &last_fbo); + if (gl_error("gl_shtex_capture", "failed to get last fbo")) { + return; + } + + glGetIntegerv(GL_TEXTURE_BINDING_2D, &last_tex); + if (gl_error("gl_shtex_capture", "failed to get last texture")) { + return; + } + + gl_copy_backbuffer(data.texture); + + glBindTexture(GL_TEXTURE_2D, last_tex); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, last_fbo); + + jimglDXUnlockObjectsNV(data.gl_device, 1, &data.gl_dxobj); + + IDXGISwapChain_Present(data.dxgi_swap, 0, 0); +} + +static inline void gl_shmem_capture_queue_copy(void) +{ + for (int i = 0; i < NUM_BUFFERS; i++) { + if (data.texture_ready[i]) { + GLvoid *buffer; + + data.texture_ready[i] = false; + + glBindBuffer(GL_PIXEL_PACK_BUFFER, data.pbos[i]); + if (gl_error("gl_shmem_capture_queue_copy", + "failed to bind pbo")) { + return; + } + + buffer = glMapBuffer(GL_PIXEL_PACK_BUFFER, GL_READ_ONLY); + if (buffer) { + data.texture_mapped[i] = true; + shmem_copy_data(i, buffer); + } + break; + } + } +} + +static inline void gl_shmem_capture_stage(GLuint dst_pbo, GLuint src_tex) +{ + glBindTexture(GL_TEXTURE_2D, src_tex); + if (gl_error("gl_shmem_capture_stage", "failed to bind src_tex")) { + return; + } + + glBindBuffer(GL_PIXEL_PACK_BUFFER, dst_pbo); + if (gl_error("gl_shmem_capture_stage", "failed to bind dst_pbo")) { + return; + } + + glGetTexImage(GL_TEXTURE_2D, 0, GL_BGRA, GL_UNSIGNED_BYTE, 0); + if (gl_error("gl_shmem_capture_stage", "failed to read src_tex")) { + return; + } +} + +static void gl_shmem_capture(void) +{ + int next_tex; + GLint last_fbo; + GLint last_tex; + + glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &last_fbo); + if (gl_error("gl_shmem_capture", "failed to get last fbo")) { + return; + } + + glGetIntegerv(GL_TEXTURE_BINDING_2D, &last_tex); + if (gl_error("gl_shmem_capture", "failed to get last texture")) { + return; + } + + gl_shmem_capture_queue_copy(); + + next_tex = (data.cur_tex == NUM_BUFFERS - 1) ? 0 : data.cur_tex + 1; + + gl_copy_backbuffer(data.textures[next_tex]); + + if (data.copy_wait < NUM_BUFFERS - 1) { + data.copy_wait++; + } else { + GLuint src = data.textures[next_tex]; + GLuint dst = data.pbos[next_tex]; + + if (shmem_texture_data_lock(next_tex)) { + glUnmapBuffer(GL_PIXEL_PACK_BUFFER); + data.texture_mapped[next_tex] = false; + shmem_texture_data_unlock(next_tex); + } + + gl_shmem_capture_stage(dst, src); + data.texture_ready[next_tex] = true; + } + + glBindTexture(GL_TEXTURE_2D, last_tex); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, last_fbo); +} + +static void gl_capture(HDC hdc) +{ + static bool functions_initialized = false; + static bool critical_failure = false; + static bool reacquireing = false; + + if (critical_failure) { + return; + } + + if (!functions_initialized) { + functions_initialized = init_gl_functions(); + if (!functions_initialized) { + critical_failure = true; + return; + } + } + + if (capture_should_stop()) { + gl_free(); + } + if (capture_should_init()) { + gl_init(hdc); + } + if (capture_ready() && hdc == data.hdc) { + uint32_t new_cx; + uint32_t new_cy; + + /* reset capture if resized */ + get_window_size(hdc, &new_cx, &new_cy); + if (new_cx != data.base_cx || new_cy != data.base_cy) { + gl_free(); + return; + } + + if (data.using_shtex) + gl_shtex_capture(); + else + gl_shmem_capture(); + } +} + +static BOOL WINAPI hook_swap_buffers(HDC hdc) +{ + BOOL ret; + + if (!global_hook_info->capture_overlay) + gl_capture(hdc); + + unhook(&swap_buffers); + BOOL (WINAPI *call)(HDC) = swap_buffers.call_addr; + ret = call(hdc); + rehook(&swap_buffers); + + if (global_hook_info->capture_overlay) + gl_capture(hdc); + + return ret; +} + +static BOOL WINAPI hook_wgl_swap_buffers(HDC hdc) +{ + BOOL ret; + + if (!global_hook_info->capture_overlay) + gl_capture(hdc); + + unhook(&wgl_swap_buffers); + BOOL (WINAPI *call)(HDC) = wgl_swap_buffers.call_addr; + ret = call(hdc); + rehook(&wgl_swap_buffers); + + if (global_hook_info->capture_overlay) + gl_capture(hdc); + + return ret; +} + +static BOOL WINAPI hook_wgl_swap_layer_buffers(HDC hdc, UINT planes) +{ + BOOL ret; + + if (!global_hook_info->capture_overlay) + gl_capture(hdc); + + unhook(&wgl_swap_layer_buffers); + BOOL (WINAPI *call)(HDC, UINT) = wgl_swap_layer_buffers.call_addr; + ret = call(hdc, planes); + rehook(&wgl_swap_layer_buffers); + + if (global_hook_info->capture_overlay) + gl_capture(hdc); + + return ret; +} + +static BOOL WINAPI hook_wgl_delete_context(HGLRC hrc) +{ + BOOL ret; + + if (capture_active()) { + HDC last_hdc = jimglGetCurrentDC(); + HGLRC last_hrc = jimglGetCurrentContext(); + + jimglMakeCurrent(data.hdc, hrc); + gl_free(); + jimglMakeCurrent(last_hdc, last_hrc); + } + + unhook(&wgl_delete_context); + BOOL (WINAPI *call)(HGLRC) = wgl_delete_context.call_addr; + ret = call(hrc); + rehook(&wgl_delete_context); + + return ret; +} + +static bool gl_register_window(void) +{ + WNDCLASSW wc = {0}; + wc.style = CS_OWNDC; + wc.hInstance = GetModuleHandle(NULL); + wc.lpfnWndProc = DefWindowProc; + wc.lpszClassName = DUMMY_WINDOW_CLASS_NAME; + + if (!RegisterClassW(&wc)) { + hlog("gl_register_window: failed to register window class: %d", + GetLastError()); + return false; + } + + return true; +} + +bool hook_gl(void) +{ + void *wgl_dc_proc; + void *wgl_slb_proc; + void *wgl_sb_proc; + + gl = get_system_module("opengl32.dll"); + if (!gl) { + return false; + } + + if (!gl_register_window()) { + return true; + } + + wgl_dc_proc = base_get_proc("wglDeleteContext"); + wgl_slb_proc = base_get_proc("wglSwapLayerBuffers"); + wgl_sb_proc = base_get_proc("wglSwapBuffers"); + + hook_init(&swap_buffers, SwapBuffers, hook_swap_buffers, "SwapBuffers"); + if (wgl_dc_proc) { + hook_init(&wgl_delete_context, wgl_dc_proc, + hook_wgl_delete_context, + "wglDeleteContext"); + rehook(&wgl_delete_context); + } + if (wgl_slb_proc) { + hook_init(&wgl_swap_layer_buffers, wgl_slb_proc, + hook_wgl_swap_layer_buffers, + "wglSwapLayerBuffers"); + rehook(&wgl_swap_layer_buffers); + } + if (wgl_sb_proc) { + hook_init(&wgl_swap_buffers, wgl_sb_proc, + hook_wgl_swap_buffers, + "wglSwapBuffers"); + rehook(&wgl_swap_buffers); + } + + rehook(&swap_buffers); + + return true; +} diff --git a/plugins/win-capture/graphics-hook/gl-decs.h b/plugins/win-capture/graphics-hook/gl-decs.h new file mode 100644 index 000000000..a8d081cce --- /dev/null +++ b/plugins/win-capture/graphics-hook/gl-decs.h @@ -0,0 +1,152 @@ +#pragma once + +typedef unsigned int GLenum; +typedef unsigned int GLbitfield; +typedef unsigned int GLuint; +typedef int GLint; +typedef int GLsizei; +typedef unsigned char GLboolean; +typedef signed char GLbyte; +typedef short GLshort; +typedef unsigned char GLubyte; +typedef unsigned short GLushort; +typedef unsigned long GLulong; +typedef float GLfloat; +typedef float GLclampf; +typedef double GLdouble; +typedef double GLclampd; +typedef void GLvoid; +typedef ptrdiff_t GLintptrARB; +typedef ptrdiff_t GLsizeiptrARB; + +#define GL_FRONT 0x0404 +#define GL_BACK 0x0405 + +#define GL_UNSIGNED_BYTE 0x1401 + +#define GL_RGB 0x1907 +#define GL_RGBA 0x1908 + +#define GL_BGR 0x80E0 +#define GL_BGRA 0x80E1 + +#define GL_NEAREST 0x2600 +#define GL_LINEAR 0x2601 + +#define GL_READ_ONLY 0x88B8 +#define GL_WRITE_ONLY 0x88B9 +#define GL_READ_WRITE 0x88BA +#define GL_BUFFER_ACCESS 0x88BB +#define GL_BUFFER_MAPPED 0x88BC +#define GL_BUFFER_MAP_POINTER 0x88BD +#define GL_STREAM_DRAW 0x88E0 +#define GL_STREAM_READ 0x88E1 +#define GL_STREAM_COPY 0x88E2 +#define GL_STATIC_DRAW 0x88E4 +#define GL_STATIC_READ 0x88E5 +#define GL_STATIC_COPY 0x88E6 +#define GL_DYNAMIC_DRAW 0x88E8 +#define GL_DYNAMIC_READ 0x88E9 +#define GL_DYNAMIC_COPY 0x88EA +#define GL_PIXEL_PACK_BUFFER 0x88EB +#define GL_PIXEL_UNPACK_BUFFER 0x88EC +#define GL_PIXEL_PACK_BUFFER_BINDING 0x88ED +#define GL_PIXEL_UNPACK_BUFFER_BINDING 0x88EF + +#define GL_TEXTURE_2D 0x0DE1 +#define GL_TEXTURE_BINDING_2D 0x8069 +#define GL_DRAW_FRAMEBUFFER_BINDING 0x8CA6 + +#define WGL_ACCESS_READ_ONLY_NV 0x0000 +#define WGL_ACCESS_READ_WRITE_NV 0x0001 +#define WGL_ACCESS_WRITE_DISCARD_NV 0x0002 + +#define GL_READ_FRAMEBUFFER 0x8CA8 +#define GL_DRAW_FRAMEBUFFER 0x8CA9 +#define GL_COLOR_BUFFER_BIT 0x00004000 +#define GL_COLOR_ATTACHMENT0 0x8CE0 +#define GL_COLOR_ATTACHMENT1 0x8CE1 + +typedef void (WINAPI *GLTEXIMAGE2DPROC)(GLenum target, GLint level, + GLint internal_format, GLsizei width, GLsizei height, + GLint border, GLenum format, GLenum type, const GLvoid *data); +typedef void (WINAPI *GLGETTEXIMAGEPROC)(GLenum target, GLint level, + GLenum format, GLenum type, GLvoid *img); +typedef void (WINAPI *GLREADBUFFERPROC)(GLenum); +typedef void (WINAPI *GLDRAWBUFFERPROC)(GLenum mode); +typedef void (WINAPI *GLGETINTEGERVPROC)(GLenum pname, GLint *params); +typedef GLenum (WINAPI *GLGETERRORPROC)(); +typedef BOOL (WINAPI *WGLSWAPLAYERBUFFERSPROC)(HDC, UINT); +typedef BOOL (WINAPI *WGLSWAPBUFFERSPROC)(HDC); +typedef BOOL (WINAPI *WGLDELETECONTEXTPROC)(HGLRC); +typedef PROC (WINAPI *WGLGETPROCADDRESSPROC)(LPCSTR); +typedef BOOL (WINAPI *WGLMAKECURRENTPROC)(HDC, HGLRC); +typedef HDC (WINAPI *WGLGETCURRENTDCPROC)(); +typedef HGLRC (WINAPI *WGLGETCURRENTCONTEXTPROC)(); +typedef HGLRC (WINAPI *WGLCREATECONTEXTPROC)(HDC); +typedef void (WINAPI *GLBUFFERDATAARBPROC) (GLenum target, GLsizeiptrARB size, + const GLvoid* data, GLenum usage); +typedef void (WINAPI *GLDELETEBUFFERSARBPROC)(GLsizei n, const GLuint* buffers); +typedef void (WINAPI *GLDELETETEXTURESPROC)(GLsizei n, const GLuint* buffers); +typedef void (WINAPI *GLGENBUFFERSARBPROC)(GLsizei n, GLuint* buffers); +typedef void (WINAPI *GLGENTEXTURESPROC)(GLsizei n, GLuint* textures); +typedef GLvoid* (WINAPI *GLMAPBUFFERPROC)(GLenum target, GLenum access); +typedef GLboolean (WINAPI *GLUNMAPBUFFERPROC)(GLenum target); +typedef void (WINAPI *GLBINDBUFFERPROC)(GLenum target, GLuint buffer); +typedef void (WINAPI *GLBINDTEXTUREPROC)(GLenum target, GLuint texture); +typedef void (WINAPI *GLGENFRAMEBUFFERSPROC)(GLsizei n, GLuint* buffers); +typedef void (WINAPI *GLDELETEFRAMEBUFFERSPROC)(GLsizei n, + GLuint *framebuffers); +typedef void (WINAPI *GLBINDFRAMEBUFFERPROC)(GLenum target, GLuint framebuffer); +typedef void (WINAPI *GLBLITFRAMEBUFFERPROC)(GLint srcX0, GLint srcY0, + GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, + GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter); +typedef void (WINAPI *GLFRAMEBUFFERTEXTURE2DPROC)(GLenum target, + GLenum attachment, GLenum textarget, GLuint texture, + GLint level); +typedef BOOL (WINAPI *WGLSETRESOURCESHAREHANDLENVPROC)(void*, HANDLE); +typedef HANDLE (WINAPI *WGLDXOPENDEVICENVPROC)(void*); +typedef BOOL (WINAPI *WGLDXCLOSEDEVICENVPROC)(HANDLE); +typedef HANDLE (WINAPI *WGLDXREGISTEROBJECTNVPROC)(HANDLE, void *, GLuint, GLenum, GLenum); +typedef BOOL (WINAPI *WGLDXUNREGISTEROBJECTNVPROC)(HANDLE, HANDLE); +typedef BOOL (WINAPI *WGLDXOBJECTACCESSNVPROC)(HANDLE, GLenum); +typedef BOOL (WINAPI *WGLDXLOCKOBJECTSNVPROC)(HANDLE, GLint, HANDLE *); +typedef BOOL (WINAPI *WGLDXUNLOCKOBJECTSNVPROC)(HANDLE, GLint, HANDLE *); + +static GLTEXIMAGE2DPROC glTexImage2D = NULL; +static GLGETTEXIMAGEPROC glGetTexImage = NULL; +static GLREADBUFFERPROC glReadBuffer = NULL; +static GLDRAWBUFFERPROC glDrawBuffer = NULL; +static GLGETINTEGERVPROC glGetIntegerv = NULL; +static GLGETERRORPROC glGetError = NULL; +static WGLSWAPLAYERBUFFERSPROC jimglSwapLayerBuffers = NULL; +static WGLSWAPBUFFERSPROC jimglSwapBuffers = NULL; +static WGLDELETECONTEXTPROC jimglDeleteContext = NULL; +static WGLGETPROCADDRESSPROC jimglGetProcAddress = NULL; +static WGLMAKECURRENTPROC jimglMakeCurrent = NULL; +static WGLGETCURRENTDCPROC jimglGetCurrentDC = NULL; +static WGLGETCURRENTCONTEXTPROC jimglGetCurrentContext = NULL; +static WGLCREATECONTEXTPROC jimglCreateContext = NULL; +static GLBUFFERDATAARBPROC glBufferData = NULL; +static GLDELETEBUFFERSARBPROC glDeleteBuffers = NULL; +static GLDELETETEXTURESPROC glDeleteTextures = NULL; +static GLGENBUFFERSARBPROC glGenBuffers = NULL; +static GLGENTEXTURESPROC glGenTextures = NULL; +static GLMAPBUFFERPROC glMapBuffer = NULL; +static GLUNMAPBUFFERPROC glUnmapBuffer = NULL; +static GLBINDBUFFERPROC glBindBuffer = NULL; +static GLBINDTEXTUREPROC glBindTexture = NULL; +static GLGENFRAMEBUFFERSPROC glGenFramebuffers = NULL; +static GLDELETEFRAMEBUFFERSPROC glDeleteFramebuffers = NULL; +static GLBINDFRAMEBUFFERPROC glBindFramebuffer = NULL; +static GLBLITFRAMEBUFFERPROC glBlitFramebuffer = NULL; +static GLFRAMEBUFFERTEXTURE2DPROC glFramebufferTexture2D = NULL; + +static WGLSETRESOURCESHAREHANDLENVPROC jimglDXSetResourceShareHandleNV = NULL; +static WGLDXOPENDEVICENVPROC jimglDXOpenDeviceNV = NULL; +static WGLDXCLOSEDEVICENVPROC jimglDXCloseDeviceNV = NULL; +static WGLDXREGISTEROBJECTNVPROC jimglDXRegisterObjectNV = NULL; +static WGLDXUNREGISTEROBJECTNVPROC jimglDXUnregisterObjectNV = NULL; +static WGLDXOBJECTACCESSNVPROC jimglDXObjectAccessNV = NULL; +static WGLDXLOCKOBJECTSNVPROC jimglDXLockObjectsNV = NULL; +static WGLDXUNLOCKOBJECTSNVPROC jimglDXUnlockObjectsNV = NULL; diff --git a/plugins/win-capture/graphics-hook/graphics-hook.c b/plugins/win-capture/graphics-hook/graphics-hook.c new file mode 100644 index 000000000..dc0cad919 --- /dev/null +++ b/plugins/win-capture/graphics-hook/graphics-hook.c @@ -0,0 +1,904 @@ +#define _CRT_SECURE_NO_WARNINGS +#include +#include +#include +#include +#include "graphics-hook.h" +#include "../obfuscate.h" +#include "../funchook.h" + +#define DEBUG_OUTPUT + +#ifdef DEBUG_OUTPUT +#define DbgOut(x) OutputDebugStringA(x) +#else +#define DbgOut(x) +#endif + +struct thread_data { + CRITICAL_SECTION mutexes[NUM_BUFFERS]; + CRITICAL_SECTION data_mutex; + void *volatile cur_data; + uint8_t *shmem_textures[2]; + HANDLE copy_thread; + HANDLE copy_event; + HANDLE stop_event; + volatile int cur_tex; + unsigned int pitch; + unsigned int cy; + volatile bool locked_textures[NUM_BUFFERS]; +}; + +static ipc_pipe_client_t pipe = {0}; +static HANDLE signal_restart = NULL; +static HANDLE signal_stop = NULL; +static HANDLE signal_ready = NULL; +static HANDLE signal_exit = NULL; +static HANDLE tex_mutexes[2] = {NULL, NULL}; +static HANDLE filemap_hook_info = NULL; + +static volatile bool stop_loop = false; +static HANDLE capture_thread = NULL; +static char system_path[MAX_PATH] = {0}; +static char process_name[MAX_PATH] = {0}; +static char keepalive_name[64] = {0}; + +static unsigned int shmem_id_counter = 0; +static void *shmem_info = NULL; +static HANDLE shmem_file_handle = 0; + +static struct thread_data thread_data = {0}; + +volatile bool active = false; +struct hook_info *global_hook_info = NULL; + +static inline void wait_for_dll_main_finish(HANDLE thread_handle) +{ + if (thread_handle) { + WaitForSingleObject(thread_handle, 100); + CloseHandle(thread_handle); + } +} + +static inline bool init_pipe(void) +{ + char new_name[64]; + sprintf(new_name, "%s%d", PIPE_NAME, GetCurrentProcessId()); + + if (!ipc_pipe_client_open(&pipe, new_name)) { + DbgOut("Failed to open pipe\n"); + return false; + } + + return true; +} + +static HANDLE init_event(const char *name, DWORD pid) +{ + HANDLE handle = get_event_plus_id(name, pid); + if (!handle) + hlog("Failed to get event '%s': %lu", name, GetLastError()); + return handle; +} + +static HANDLE init_mutex(const char *name, DWORD pid) +{ + char new_name[64]; + HANDLE handle; + + sprintf(new_name, "%s%d", name, pid); + + handle = OpenMutexA(MUTEX_ALL_ACCESS, false, new_name); + if (!handle) + hlog("Failed to open mutex '%s': %lu", name, GetLastError()); + return handle; +} + +static inline bool init_signals(void) +{ + DWORD pid = GetCurrentProcessId(); + + signal_restart = init_event(EVENT_CAPTURE_RESTART, pid); + if (!signal_restart) { + return false; + } + + signal_stop = init_event(EVENT_CAPTURE_STOP, pid); + if (!signal_stop) { + return false; + } + + signal_ready = init_event(EVENT_HOOK_READY, pid); + if (!signal_ready) { + return false; + } + + signal_exit = init_event(EVENT_HOOK_EXIT, pid); + if (!signal_exit) { + return false; + } + + return true; +} + +static inline bool init_mutexes(void) +{ + DWORD pid = GetCurrentProcessId(); + + tex_mutexes[0] = init_mutex(MUTEX_TEXTURE1, pid); + if (!tex_mutexes[0]) { + return false; + } + + tex_mutexes[1] = init_mutex(MUTEX_TEXTURE2, pid); + if (!tex_mutexes[1]) { + return false; + } + + return true; +} + +static inline bool init_system_path(void) +{ + HRESULT hr = SHGetFolderPathA(NULL, CSIDL_SYSTEM, NULL, + SHGFP_TYPE_CURRENT, system_path); + if (hr != S_OK) { + hlog("Failed to get windows system path: %08lX", hr); + return false; + } + + return true; +} + +static inline void log_current_process(void) +{ + DWORD len = GetModuleBaseNameA(GetCurrentProcess(), NULL, process_name, + MAX_PATH); + if (len > 0) { + process_name[len] = 0; + hlog("Hooked to process: %s", process_name); + } + + hlog("(half life scientist) everything.. seems to be in order"); +} + +static inline bool init_hook_info(void) +{ + filemap_hook_info = get_hook_info(GetCurrentProcessId()); + if (!filemap_hook_info) { + hlog("Failed to create hook info file mapping: %lu", + GetLastError()); + return false; + } + + global_hook_info = MapViewOfFile(filemap_hook_info, FILE_MAP_ALL_ACCESS, + 0, 0, sizeof(struct hook_info)); + if (!global_hook_info) { + hlog("Failed to map the hook info file mapping: %lu", + GetLastError()); + return false; + } + + return true; +} + +static inline bool init_hook(HANDLE thread_handle) +{ + wait_for_dll_main_finish(thread_handle); + + sprintf(keepalive_name, "%s%d", EVENT_HOOK_KEEPALIVE, + GetCurrentProcessId()); + + if (!init_pipe()) { + return false; + } + if (!init_signals()) { + return false; + } + if (!init_mutexes()) { + return false; + } + if (!init_system_path()) { + return false; + } + if (!init_hook_info()) { + return false; + } + + log_current_process(); + + SetEvent(signal_restart); + return true; +} + +static inline void close_handle(HANDLE *handle) +{ + if (*handle) { + CloseHandle(*handle); + *handle = NULL; + } +} + +static void free_hook(void) +{ + if (filemap_hook_info) { + CloseHandle(filemap_hook_info); + filemap_hook_info = NULL; + } + if (global_hook_info) { + UnmapViewOfFile(global_hook_info); + global_hook_info = NULL; + } + + close_handle(&tex_mutexes[1]); + close_handle(&tex_mutexes[0]); + close_handle(&signal_exit); + close_handle(&signal_ready); + close_handle(&signal_stop); + close_handle(&signal_restart); + ipc_pipe_client_free(&pipe); +} + +static inline bool d3d8_hookable(void) +{ + return !!global_hook_info->offsets.d3d8.present && + !!global_hook_info->offsets.d3d8.reset; +} + +static inline bool ddraw_hookable(void) +{ + return !!global_hook_info->offsets.ddraw.surface_create && + !!global_hook_info->offsets.ddraw.surface_restore && + !!global_hook_info->offsets.ddraw.surface_release && + !!global_hook_info->offsets.ddraw.surface_unlock && + !!global_hook_info->offsets.ddraw.surface_blt && + !!global_hook_info->offsets.ddraw.surface_flip && + !!global_hook_info->offsets.ddraw.surface_set_palette && + !!global_hook_info->offsets.ddraw.palette_set_entries; +} + +static inline bool d3d9_hookable(void) +{ + return !!global_hook_info->offsets.d3d9.present && + !!global_hook_info->offsets.d3d9.present_ex && + !!global_hook_info->offsets.d3d9.present_swap && + !!global_hook_info->offsets.d3d9.reset && + !!global_hook_info->offsets.d3d9.reset_ex; +} + +static inline bool dxgi_hookable(void) +{ + return !!global_hook_info->offsets.dxgi.present && + !!global_hook_info->offsets.dxgi.resize; +} + +static inline bool attempt_hook(void) +{ + static bool ddraw_hooked = false; + static bool d3d8_hooked = false; + static bool d3d9_hooked = false; + static bool dxgi_hooked = false; + static bool gl_hooked = false; + + if (!d3d9_hooked) { + if (!d3d9_hookable()) { + d3d9_hooked = true; + } else { + d3d9_hooked = hook_d3d9(); + if (d3d9_hooked) { + return true; + } + } + } + + if (!dxgi_hooked) { + if (!dxgi_hookable()) { + dxgi_hooked = true; + } else { + dxgi_hooked = hook_dxgi(); + if (dxgi_hooked) { + return true; + } + } + } + + if (!gl_hooked) { + gl_hooked = hook_gl(); + if (gl_hooked) { + return true; + } + /*} else { + rehook_gl();*/ + } + + /*if (!d3d8_hooked) { + if (!d3d8_hookable()) { + d3d8_hooked = true; + } else { + d3d8_hooked = hook_d3d8(); + if (d3d8_hooked) { + return true; + } + } + } + + if (!ddraw_hooked) { + if (!ddraw_hookable()) { + ddraw_hooked = true; + } else { + ddraw_hooked = hook_ddraw(); + if (ddraw_hooked) { + return true; + } + } + }*/ + + return false; +} + +static inline void capture_loop(void) +{ + while (!attempt_hook()) + Sleep(40); + + for (size_t n = 0; !stop_loop; n++) { + /* this causes it to check every 4 seconds, but still with + * a small sleep interval in case the thread needs to stop */ + if (n % 100 == 0) attempt_hook(); + Sleep(40); + } +} + +static DWORD WINAPI main_capture_thread(HANDLE thread_handle) +{ + if (!init_hook(thread_handle)) { + DbgOut("Failed to init hook\n"); + free_hook(); + return 0; + } + + capture_loop(); + return 0; +} + +static inline void hlogv(const char *format, va_list args) +{ + char message[1024] = ""; + int num = _vsprintf_p(message, 1024, format, args); + if (num) { + if (!ipc_pipe_client_write(&pipe, message, num + 1)) { + ipc_pipe_client_free(&pipe); + } + DbgOut(message); + DbgOut("\n"); + } +} + +void hlog(const char *format, ...) +{ + va_list args; + + va_start(args, format); + hlogv(format, args); + va_end(args); +} + +void hlog_hr(const char *text, HRESULT hr) +{ + LPSTR buffer = NULL; + + FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, hr, MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), + (LPSTR)&buffer, 0, NULL); + + if (buffer) { + hlog("%s (0x%08lX): %s", text, hr, buffer); + LocalFree(buffer); + } else { + hlog("%s (0x%08lX)", text, hr); + } +} + +inline const char *get_process_name(void) +{ + return process_name; +} + +inline HMODULE get_system_module(const char *module) +{ + char base_path[MAX_PATH]; + + strcpy(base_path, system_path); + strcat(base_path, "\\"); + strcat(base_path, module); + return GetModuleHandleA(module); +} + +inline HMODULE load_system_library(const char *name) +{ + char base_path[MAX_PATH]; + HMODULE module; + + strcpy(base_path, system_path); + strcat(base_path, "\\"); + strcat(base_path, name); + + module = GetModuleHandleA(base_path); + if (module) + return module; + + return LoadLibraryA(base_path); +} + +static inline bool capture_alive(void) +{ + HANDLE event = OpenEventA(EVENT_ALL_ACCESS, false, keepalive_name); + if (event) { + CloseHandle(event); + return true; + } + + return false; +} + +inline bool capture_active(void) +{ + return active; +} + +static inline bool capture_stopped(void) +{ + return WaitForSingleObject(signal_stop, 0) == WAIT_OBJECT_0; +} + +static inline bool capture_restarted(void) +{ + return WaitForSingleObject(signal_restart, 0) == WAIT_OBJECT_0; +} + +inline bool capture_should_stop(void) +{ + bool stop_requested = false; + + if (capture_active()) + stop_requested = capture_stopped() || !capture_alive(); + + return stop_requested; +} + +inline bool capture_should_init(void) +{ + if (!capture_active() && capture_restarted()) { + if (capture_alive()) { + if (!ipc_pipe_client_valid(&pipe)) { + init_pipe(); + } + return true; + } + } + + return false; +} + +static inline uint64_t get_clockfreq(void) +{ + static bool have_clockfreq = false; + static LARGE_INTEGER clock_freq; + + if (!have_clockfreq) { + QueryPerformanceFrequency(&clock_freq); + have_clockfreq = true; + } + + return clock_freq.QuadPart; +} + +uint64_t os_gettime_ns(void) +{ + LARGE_INTEGER current_time; + double time_val; + + QueryPerformanceCounter(¤t_time); + time_val = (double)current_time.QuadPart; + time_val *= 1000000000.0; + time_val /= (double)get_clockfreq(); + + return (uint64_t)time_val; +} + +inline int try_lock_shmem_tex(int id) +{ + int next = id == 0 ? 1 : 0; + + if (WaitForSingleObject(tex_mutexes[id], 0) == WAIT_OBJECT_0) { + return id; + } else if (WaitForSingleObject(tex_mutexes[next], 0) == WAIT_OBJECT_0) { + return next; + } + + return -1; +} + +inline void unlock_shmem_tex(int id) +{ + if (id != -1) { + ReleaseMutex(tex_mutexes[id]); + } +} + +static inline bool frame_ready(uint64_t interval) +{ + static uint64_t last_time = 0; + uint64_t elapsed; + uint64_t t; + + if (!interval) { + return true; + } + + t = os_gettime_ns(); + elapsed = t - last_time; + + if (elapsed < interval) { + return false; + } + + last_time = (elapsed > interval * 2) ? t : last_time + interval; + return true; +} + +inline bool capture_ready(void) +{ + return capture_active() && + frame_ready(global_hook_info->frame_interval); +} + +static inline bool init_shared_info(size_t size) +{ + char name[64]; + sprintf_s(name, 64, "%s%u", SHMEM_TEXTURE, ++shmem_id_counter); + + shmem_file_handle = CreateFileMappingA(INVALID_HANDLE_VALUE, NULL, + PAGE_READWRITE, 0, (DWORD)size, name); + if (!shmem_file_handle) { + hlog("init_shared_info: Failed to create shared memory: %d", + GetLastError()); + return false; + } + + shmem_info = MapViewOfFile(shmem_file_handle, FILE_MAP_ALL_ACCESS, + 0, 0, size); + if (!shmem_info) { + hlog("init_shared_info: Failed to map shared memory: %d", + GetLastError()); + return false; + } + + return true; +} + +bool capture_init_shtex(struct shtex_data **data, HWND window, + uint32_t base_cx, uint32_t base_cy, uint32_t cx, uint32_t cy, + uint32_t format, bool flip, uint32_t handle) +{ + if (!init_shared_info(sizeof(struct shtex_data))) { + hlog("capture_init_shtex: Failed to initialize memory"); + return false; + } + + *data = shmem_info; + (*data)->tex_handle = handle; + + global_hook_info->window = (uint32_t)window; + global_hook_info->type = CAPTURE_TYPE_TEXTURE; + global_hook_info->format = format; + global_hook_info->flip = flip; + global_hook_info->map_id = shmem_id_counter; + global_hook_info->map_size = sizeof(struct shtex_data); + global_hook_info->cx = cx; + global_hook_info->cy = cy; + global_hook_info->base_cx = base_cx; + global_hook_info->base_cy = base_cy; + + if (!SetEvent(signal_ready)) { + hlog("capture_init_shtex: Failed to signal ready: %d", + GetLastError()); + return false; + } + + active = true; + return true; +} + +static DWORD CALLBACK copy_thread(LPVOID unused) +{ + uint32_t pitch = thread_data.pitch; + uint32_t cy = thread_data.cy; + HANDLE events[2] = {NULL, NULL}; + int shmem_id = 0; + + if (!duplicate_handle(&events[0], thread_data.copy_event)) { + hlog_hr("copy_thread: Failed to duplicate copy event: %d", + GetLastError()); + return 0; + } + + if (!duplicate_handle(&events[1], thread_data.stop_event)) { + hlog_hr("copy_thread: Failed to duplicate stop event: %d", + GetLastError()); + goto finish; + } + + for (;;) { + int copy_tex; + void *cur_data; + + DWORD ret = WaitForMultipleObjects(2, events, false, INFINITE); + if (ret != WAIT_OBJECT_0) { + break; + } + + EnterCriticalSection(&thread_data.data_mutex); + copy_tex = thread_data.cur_tex; + cur_data = thread_data.cur_data; + LeaveCriticalSection(&thread_data.data_mutex); + + if (copy_tex < NUM_BUFFERS && !!cur_data) { + EnterCriticalSection(&thread_data.mutexes[copy_tex]); + + int lock_id = try_lock_shmem_tex(shmem_id); + if (lock_id != -1) { + memcpy(thread_data.shmem_textures[lock_id], + cur_data, pitch * cy); + + unlock_shmem_tex(lock_id); + ((struct shmem_data*)shmem_info)->last_tex = + lock_id; + + shmem_id = lock_id == 0 ? 1 : 0; + } + + LeaveCriticalSection(&thread_data.mutexes[copy_tex]); + } + } + +finish: + for (size_t i = 0; i < 2; i++) { + if (events[i]) { + CloseHandle(events[i]); + } + } + + (void)unused; + return 0; +} + +inline void shmem_copy_data(size_t idx, void *volatile data) +{ + EnterCriticalSection(&thread_data.data_mutex); + thread_data.cur_tex = (int)idx; + thread_data.cur_data = data; + thread_data.locked_textures[idx] = true; + LeaveCriticalSection(&thread_data.data_mutex); + + SetEvent(thread_data.copy_event); +} + +inline bool shmem_texture_data_lock(int idx) +{ + bool locked; + + EnterCriticalSection(&thread_data.data_mutex); + locked = thread_data.locked_textures[idx]; + LeaveCriticalSection(&thread_data.data_mutex); + + if (locked) { + EnterCriticalSection(&thread_data.mutexes[idx]); + return true; + } + + return false; +} + +inline void shmem_texture_data_unlock(int idx) +{ + EnterCriticalSection(&thread_data.data_mutex); + thread_data.locked_textures[idx] = false; + LeaveCriticalSection(&thread_data.data_mutex); + + LeaveCriticalSection(&thread_data.mutexes[idx]); +} + +static inline bool init_shmem_thread(uint32_t pitch, uint32_t cy) +{ + struct shmem_data *data = shmem_info; + + thread_data.pitch = pitch; + thread_data.cy = cy; + thread_data.shmem_textures[0] = (uint8_t*)data + data->tex1_offset; + thread_data.shmem_textures[1] = (uint8_t*)data + data->tex2_offset; + + thread_data.copy_event = CreateEvent(NULL, false, false, NULL); + if (!thread_data.copy_event) { + hlog("init_shmem_thread: Failed to create copy event: %d", + GetLastError()); + return false; + } + + thread_data.stop_event = CreateEvent(NULL, true, false, NULL); + if (!thread_data.stop_event) { + hlog("init_shmem_thread: Failed to create stop event: %d", + GetLastError()); + return false; + } + + for (size_t i = 0; i < NUM_BUFFERS; i++) { + InitializeCriticalSection(&thread_data.mutexes[i]); + } + + InitializeCriticalSection(&thread_data.data_mutex); + + thread_data.copy_thread = CreateThread(NULL, 0, copy_thread, NULL, 0, + NULL); + if (!thread_data.copy_thread) { + hlog("init_shmem_thread: Failed to create thread: %d", + GetLastError()); + return false; + } + return true; +} + +#ifndef ALIGN +#define ALIGN(bytes, align) (((bytes) + ((align) - 1)) & ~((align) - 1)) +#endif + +bool capture_init_shmem(struct shmem_data **data, HWND window, + uint32_t base_cx, uint32_t base_cy, uint32_t cx, uint32_t cy, + uint32_t pitch, uint32_t format, bool flip) +{ + uint32_t tex_size = cy * pitch; + uint32_t aligned_header = ALIGN(sizeof(struct shmem_data), 32); + uint32_t aligned_tex = ALIGN(tex_size, 32); + uint32_t total_size = aligned_header + aligned_tex * 2; + uintptr_t align_pos; + + if (!init_shared_info(total_size)) { + hlog("capture_init_shmem: Failed to initialize memory"); + return false; + } + + *data = shmem_info; + + /* to ensure fast copy rate, align texture data to 256bit addresses */ + align_pos = (uintptr_t)shmem_info; + align_pos += aligned_header; + align_pos &= ~(32 - 1); + align_pos -= (uintptr_t)shmem_info; + + (*data)->last_tex = -1; + (*data)->tex1_offset = (uint32_t)align_pos; + (*data)->tex2_offset = (*data)->tex1_offset + aligned_tex; + + global_hook_info->window = (uint32_t)window; + global_hook_info->type = CAPTURE_TYPE_MEMORY; + global_hook_info->format = format; + global_hook_info->flip = flip; + global_hook_info->map_id = shmem_id_counter; + global_hook_info->map_size = total_size; + global_hook_info->pitch = pitch; + global_hook_info->cx = cx; + global_hook_info->cy = cy; + global_hook_info->base_cx = base_cx; + global_hook_info->base_cy = base_cy; + + if (!init_shmem_thread(pitch, cy)) { + return false; + } + + if (!SetEvent(signal_ready)) { + hlog("capture_init_shmem: Failed to signal ready: %d", + GetLastError()); + return false; + } + + active = true; + return true; +} + +static inline void thread_data_free(void) +{ + if (thread_data.copy_thread) { + DWORD ret; + + SetEvent(thread_data.stop_event); + ret = WaitForSingleObject(thread_data.copy_thread, 500); + if (ret != WAIT_OBJECT_0) + TerminateThread(thread_data.copy_thread, (DWORD)-1); + + CloseHandle(thread_data.copy_thread); + } + if (thread_data.stop_event) + CloseHandle(thread_data.stop_event); + if (thread_data.copy_event) + CloseHandle(thread_data.copy_event); + for (size_t i = 0; i < NUM_BUFFERS; i++) + DeleteCriticalSection(&thread_data.mutexes[i]); + + DeleteCriticalSection(&thread_data.data_mutex); + + memset(&thread_data, 0, sizeof(thread_data)); +} + +void capture_free(void) +{ + thread_data_free(); + + if (shmem_info) { + UnmapViewOfFile(shmem_info); + shmem_info = NULL; + } + + close_handle(&shmem_file_handle); + + SetEvent(signal_restart); + active = false; +} + +BOOL WINAPI DllMain(HINSTANCE hinst, DWORD reason, LPVOID unused1) +{ + if (reason == DLL_PROCESS_ATTACH) { + wchar_t name[MAX_PATH]; + + HANDLE cur_thread = OpenThread(THREAD_ALL_ACCESS, false, + GetCurrentThreadId()); + + /* this prevents the library from being automatically unloaded + * by the next FreeLibrary call */ + GetModuleFileNameW(hinst, name, MAX_PATH); + LoadLibraryW(name); + + capture_thread = CreateThread(NULL, 0, + (LPTHREAD_START_ROUTINE)main_capture_thread, + (LPVOID)cur_thread, 0, 0); + if (!capture_thread) { + CloseHandle(cur_thread); + return false; + } + + } else if (reason == DLL_PROCESS_DETACH) { + if (capture_thread) { + stop_loop = true; + WaitForSingleObject(capture_thread, 300); + CloseHandle(capture_thread); + } + + free_hook(); + } + + (void)unused1; + return true; +} + +__declspec(dllexport) LRESULT CALLBACK dummy_debug_proc(int code, + WPARAM wparam, LPARAM lparam) +{ + static bool hooking = true; + MSG *msg = (MSG*)lparam; + + if (hooking && msg->message == (WM_USER + 432)) { + HMODULE user32 = GetModuleHandleW(L"USER32"); + BOOL (WINAPI *unhook_windows_hook_ex)(HHOOK) = NULL; + + unhook_windows_hook_ex = get_obfuscated_func(user32, + "VojeleY`bdgxvM`hhDz", + 0x7F55F80C9EE3A213ULL); + + if (unhook_windows_hook_ex) + unhook_windows_hook_ex((HHOOK)msg->lParam); + hooking = false; + } + + return CallNextHookEx(0, code, wparam, lparam); +} diff --git a/plugins/win-capture/graphics-hook/graphics-hook.h b/plugins/win-capture/graphics-hook/graphics-hook.h new file mode 100644 index 000000000..bb82dfddf --- /dev/null +++ b/plugins/win-capture/graphics-hook/graphics-hook.h @@ -0,0 +1,82 @@ +#pragma once + +/* conversion from data/function pointer */ +#pragma warning(disable: 4152) + +#include "../graphics-hook-info.h" + +#ifdef __cplusplus +extern "C" { +#else +#ifndef inline +#define inline __inline +#endif +#endif + +#define NUM_BUFFERS 3 + +extern void hlog(const char *format, ...); +extern void hlog_hr(const char *text, HRESULT hr); +extern inline const char *get_process_name(void); +extern inline HMODULE get_system_module(const char *module); +extern inline HMODULE load_system_library(const char *module); +extern uint64_t os_gettime_ns(void); + +extern inline bool capture_active(void); +extern inline bool capture_ready(void); +extern inline bool capture_should_stop(void); +extern inline bool capture_should_init(void); + +extern inline void shmem_copy_data(size_t idx, void *volatile data); +extern inline bool shmem_texture_data_lock(int idx); +extern inline void shmem_texture_data_unlock(int idx); + +extern bool hook_ddraw(void); +extern bool hook_d3d8(void); +extern bool hook_d3d9(void); +extern bool hook_dxgi(void); +extern bool hook_gl(void); + +extern void d3d10_capture(void *swap, void *backbuffer); +extern void d3d10_free(void); +extern void d3d11_capture(void *swap, void *backbuffer); +extern void d3d11_free(void); + +extern uint8_t *get_d3d1x_vertex_shader(size_t *size); +extern uint8_t *get_d3d1x_pixel_shader(size_t *size); + +extern bool rehook_gl(void); + +extern bool capture_init_shtex(struct shtex_data **data, HWND window, + uint32_t base_cx, uint32_t base_cy, uint32_t cx, uint32_t cy, + uint32_t format, bool flip, uint32_t handle); +extern bool capture_init_shmem(struct shmem_data **data, HWND window, + uint32_t base_cx, uint32_t base_cy, uint32_t cx, uint32_t cy, + uint32_t pitch, uint32_t format, bool flip); +extern void capture_free(void); + +extern struct hook_info *global_hook_info; + +struct vertex { + struct { + float x, y, z, w; + } pos; + struct { + float u, v; + } tex; +}; + +static inline bool duplicate_handle(HANDLE *dst, HANDLE src) +{ + return !!DuplicateHandle(GetCurrentProcess(), src, GetCurrentProcess(), + dst, 0, false, DUPLICATE_SAME_ACCESS); +} + +static inline void *get_offset_addr(HMODULE module, uint32_t offset) +{ + return (void*)((uintptr_t)module + (uintptr_t)offset); +} + +#ifdef __cplusplus +} +#endif