├── LICENSE ├── com ├── com_utils.go └── iunknown_vtbl.go ├── d3d11 ├── d3d11.go ├── d3d11_types.go ├── d3d11_vtbl.go └── d3d11debug.go ├── dxgi ├── dxgi.go ├── dxgi_types.go ├── dxgi_types_string.go └── dxgi_vtbl.go ├── examples ├── framelimiter │ └── framelimiter.go ├── mjpegstream │ ├── jpeg_native.go │ ├── jpeg_turbo.go │ └── main.go └── recording │ ├── main.go │ └── transcode.go ├── go.mod ├── go.sum ├── hresult.go ├── hresult_string.go ├── outputduplication ├── output_duplication.go └── swizzle │ ├── LICENSE │ ├── swizzle_amd64.go │ ├── swizzle_amd64.s │ ├── swizzle_common.go │ ├── swizzle_other.go │ └── swizzle_test.go ├── readme.md └── win ├── syscall_windows.go └── zsyscall_windows.go /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 kirides 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | # 24 | # golang.org/x/exp/shiny/driver/internal/swizzle 25 | # 26 | 27 | Copyright (c) 2009 The Go Authors. All rights reserved. 28 | 29 | Redistribution and use in source and binary forms, with or without 30 | modification, are permitted provided that the following conditions are 31 | met: 32 | 33 | * Redistributions of source code must retain the above copyright 34 | notice, this list of conditions and the following disclaimer. 35 | * Redistributions in binary form must reproduce the above 36 | copyright notice, this list of conditions and the following disclaimer 37 | in the documentation and/or other materials provided with the 38 | distribution. 39 | * Neither the name of Google Inc. nor the names of its 40 | contributors may be used to endorse or promote products derived from 41 | this software without specific prior written permission. 42 | 43 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 44 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 45 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 46 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 47 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 48 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 49 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 50 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 51 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 52 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 53 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /com/com_utils.go: -------------------------------------------------------------------------------- 1 | package com 2 | 3 | import ( 4 | "reflect" 5 | "syscall" 6 | "unsafe" 7 | 8 | "golang.org/x/sys/windows" 9 | ) 10 | 11 | func ReflectQueryInterface(self interface{}, method uintptr, interfaceID *windows.GUID, obj interface{}) int32 { 12 | selfValue := reflect.ValueOf(self).Elem() 13 | objValue := reflect.ValueOf(obj).Elem() 14 | 15 | hr, _, _ := syscall.SyscallN( 16 | method, 17 | selfValue.UnsafeAddr(), 18 | uintptr(unsafe.Pointer(interfaceID)), 19 | objValue.Addr().Pointer()) 20 | 21 | return int32(hr) 22 | } 23 | -------------------------------------------------------------------------------- /com/iunknown_vtbl.go: -------------------------------------------------------------------------------- 1 | package com 2 | 3 | import "structs" 4 | 5 | type IUnknownVtbl struct { 6 | _ structs.HostLayout 7 | // every COM object starts with these three 8 | QueryInterface uintptr 9 | AddRef uintptr 10 | Release uintptr 11 | // _QueryInterface2 uintptr 12 | } 13 | -------------------------------------------------------------------------------- /d3d11/d3d11.go: -------------------------------------------------------------------------------- 1 | package d3d11 2 | 3 | import ( 4 | "fmt" 5 | "structs" 6 | "syscall" 7 | "unsafe" 8 | 9 | "golang.org/x/sys/windows" 10 | 11 | "github.com/kirides/go-d3d" 12 | "github.com/kirides/go-d3d/com" 13 | "github.com/kirides/go-d3d/dxgi" 14 | ) 15 | 16 | var ( 17 | modD3D11 = windows.NewLazySystemDLL("d3d11.dll") 18 | procD3D11CreateDevice = modD3D11.NewProc("D3D11CreateDevice") 19 | 20 | // iid_ID3D11Device1, _ = windows.GUIDFromString("{a04bfb29-08ef-43d6-a49c-a9bdbdcbe686}") 21 | IID_ID3D11Texture2D, _ = windows.GUIDFromString("{6f15aaf2-d208-4e89-9ab4-489535d34f9c}") 22 | IID_ID3D11Debug, _ = windows.GUIDFromString("{79cf2233-7536-4948-9d36-1e4692dc5760}") 23 | IID_ID3D11InfoQueue, _ = windows.GUIDFromString("{6543dbb6-1b48-42f5-ab82-e97ec74326f6}") 24 | ) 25 | 26 | const ( 27 | D3D11_USAGE_DEFAULT = 0 28 | D3D11_USAGE_STAGING = 3 29 | 30 | D3D11_CPU_ACCESS_READ = 0x20000 31 | 32 | D3D11_RLDO_SUMMARY = 0x1 33 | D3D11_RLDO_DETAIL = 0x2 34 | D3D11_RLDO_IGNORE_INTERNAL = 0x4 35 | 36 | D3D11_CREATE_DEVICE_DEBUG = 0x2 37 | D3D11_CREATE_DEVICE_BGRA_SUPPORT = 0x20 38 | 39 | D3D11_SDK_VERSION = 7 40 | ) 41 | 42 | func _D3D11CreateDevice(ppDevice **ID3D11Device, ppDeviceContext **ID3D11DeviceContext) error { 43 | var factory1 *dxgi.IDXGIFactory1 44 | if err := dxgi.CreateDXGIFactory1(&factory1); err != nil { 45 | return fmt.Errorf("CreateDXGIFactory1: %w", err) 46 | } 47 | defer factory1.Release() 48 | 49 | var adapter1 *dxgi.IDXGIAdapter1 50 | if hr := factory1.EnumAdapters1(0, &adapter1); d3d.HRESULT(hr).Failed() { 51 | return fmt.Errorf("failed to enumerate desktop adapter. %w", d3d.HRESULT(hr)) 52 | } 53 | defer adapter1.Release() 54 | 55 | fflags := [...]uint32{ 56 | // 0xc100, // D3D_FEATURE_LEVEL_12_1 57 | // 0xc000, // D3D_FEATURE_LEVEL_12_0 58 | 0xb100, // D3D_FEATURE_LEVEL_11_1 59 | 0xb000, // D3D_FEATURE_LEVEL_11_0 60 | // 0xa100, // D3D_FEATURE_LEVEL_10_1 61 | // 0xa000, // D3D_FEATURE_LEVEL_10_0 62 | // 0x9300, // D3D_FEATURE_LEVEL_9_3 63 | // 0x9200, // D3D_FEATURE_LEVEL_9_2 64 | // 0x9100, // D3D_FEATURE_LEVEL_9_1 65 | // 0x1000, // D3D_FEATURE_LEVEL_1_0_CORE <-- unsupported! 66 | } 67 | featureLevel := 0x9100 68 | flags := 69 | // D3D11_CREATE_DEVICE_DEBUG | 70 | 0 71 | 72 | ret, _, _ := syscall.SyscallN( 73 | procD3D11CreateDevice.Addr(), 74 | uintptr(unsafe.Pointer(adapter1)), // pAdapter 75 | uintptr(0), // driverType: 1 = Hardware 76 | uintptr(0), // software 77 | uintptr(flags), // flags 78 | uintptr(unsafe.Pointer(&fflags[0])), // supported feature levels 79 | uintptr(len(fflags)), // number of levels 80 | uintptr(D3D11_SDK_VERSION), 81 | uintptr(unsafe.Pointer(ppDevice)), // *D3D11Device 82 | uintptr(unsafe.Pointer(&featureLevel)), // feature level 83 | uintptr(unsafe.Pointer(ppDeviceContext)), // *D3D11DeviceContext 84 | ) 85 | 86 | if ret != 0 { 87 | return d3d.HRESULT(ret) 88 | } 89 | return nil 90 | } 91 | 92 | func NewD3D11Device() (*ID3D11Device, *ID3D11DeviceContext, error) { 93 | var device *ID3D11Device 94 | var deviceCtx *ID3D11DeviceContext 95 | 96 | err := _D3D11CreateDevice(&device, &deviceCtx) 97 | 98 | if err != nil || device == nil || deviceCtx == nil { 99 | return nil, nil, err 100 | } 101 | 102 | return device, deviceCtx, nil 103 | } 104 | 105 | type ID3D11Texture2D struct { 106 | _ structs.HostLayout 107 | vtbl *ID3D11Texture2DVtbl 108 | } 109 | 110 | func (obj *ID3D11Texture2D) GetDesc(desc *D3D11_TEXTURE2D_DESC) int32 { 111 | ret, _, _ := syscall.SyscallN( 112 | obj.vtbl.GetDesc, 113 | uintptr(unsafe.Pointer(obj)), 114 | uintptr(unsafe.Pointer(desc)), 115 | ) 116 | return int32(ret) 117 | } 118 | func (obj *ID3D11Texture2D) Release() int32 { 119 | ret, _, _ := syscall.SyscallN( 120 | obj.vtbl.Release, 121 | uintptr(unsafe.Pointer(obj)), 122 | ) 123 | return int32(ret) 124 | } 125 | func (obj *ID3D11Texture2D) QueryInterface(iid windows.GUID, pp interface{}) int32 { 126 | return com.ReflectQueryInterface(obj, obj.vtbl.QueryInterface, &iid, pp) 127 | } 128 | 129 | type ID3D11Device struct { 130 | _ structs.HostLayout 131 | vtbl *ID3D11DeviceVtbl 132 | } 133 | 134 | func (obj *ID3D11Device) QueryInterface(iid windows.GUID, pp interface{}) int32 { 135 | return com.ReflectQueryInterface(obj, obj.vtbl.QueryInterface, &iid, pp) 136 | } 137 | 138 | func (obj *ID3D11Device) CreateTexture2D(desc *D3D11_TEXTURE2D_DESC, ppTexture2D **ID3D11Texture2D) int32 { 139 | ret, _, _ := syscall.SyscallN( 140 | obj.vtbl.CreateTexture2D, 141 | uintptr(unsafe.Pointer(obj)), 142 | uintptr(unsafe.Pointer(desc)), 143 | uintptr(0), 144 | uintptr(unsafe.Pointer(ppTexture2D)), 145 | ) 146 | return int32(ret) 147 | } 148 | 149 | func (obj *ID3D11Device) Release() int32 { 150 | ret, _, _ := syscall.SyscallN( 151 | obj.vtbl.Release, 152 | uintptr(unsafe.Pointer(obj)), 153 | ) 154 | return int32(ret) 155 | } 156 | 157 | type ID3D11Device1 struct { 158 | _ structs.HostLayout 159 | vtbl *ID3D11DeviceVtbl 160 | } 161 | 162 | func (obj *ID3D11Device1) Release() int32 { 163 | ret, _, _ := syscall.SyscallN( 164 | obj.vtbl.Release, 165 | uintptr(unsafe.Pointer(obj)), 166 | ) 167 | return int32(ret) 168 | } 169 | 170 | func (obj *ID3D11Device1) CreateTexture2D(desc *D3D11_TEXTURE2D_DESC, ppTexture2D **ID3D11Texture2D) int32 { 171 | ret, _, _ := syscall.SyscallN( 172 | obj.vtbl.CreateTexture2D, 173 | uintptr(unsafe.Pointer(obj)), 174 | uintptr(unsafe.Pointer(desc)), 175 | uintptr(0), 176 | uintptr(unsafe.Pointer(ppTexture2D)), 177 | ) 178 | return int32(ret) 179 | } 180 | 181 | type ID3D11DeviceContext struct { 182 | _ structs.HostLayout 183 | vtbl *ID3D11DeviceContextVtbl 184 | } 185 | 186 | func (obj *ID3D11DeviceContext) CopyResourceDXGI(dst, src *dxgi.IDXGIResource) int32 { 187 | ret, _, _ := syscall.SyscallN( 188 | obj.vtbl.CopyResource, 189 | uintptr(unsafe.Pointer(obj)), 190 | uintptr(unsafe.Pointer(dst)), 191 | uintptr(unsafe.Pointer(src)), 192 | ) 193 | return int32(ret) 194 | } 195 | func (obj *ID3D11DeviceContext) CopyResource2D(dst, src *ID3D11Texture2D) int32 { 196 | ret, _, _ := syscall.SyscallN( 197 | obj.vtbl.CopyResource, 198 | uintptr(unsafe.Pointer(obj)), 199 | uintptr(unsafe.Pointer(dst)), 200 | uintptr(unsafe.Pointer(src)), 201 | ) 202 | return int32(ret) 203 | } 204 | func (obj *ID3D11DeviceContext) CopySubresourceRegion2D(dst *ID3D11Texture2D, dstSubResource, dstX, dstY, dstZ uint32, src *ID3D11Texture2D, srcSubResource uint32, pSrcBox *D3D11_BOX) int32 { 205 | ret, _, _ := syscall.SyscallN( 206 | obj.vtbl.CopySubresourceRegion, 207 | uintptr(unsafe.Pointer(obj)), 208 | uintptr(unsafe.Pointer(dst)), 209 | uintptr(dstSubResource), 210 | uintptr(dstX), 211 | uintptr(dstY), 212 | uintptr(dstZ), 213 | uintptr(unsafe.Pointer(src)), 214 | uintptr(srcSubResource), 215 | uintptr(unsafe.Pointer(pSrcBox)), 216 | ) 217 | return int32(ret) 218 | } 219 | 220 | func (obj *ID3D11DeviceContext) CopySubresourceRegion(dst *ID3D11Resource, dstSubResource, dstX, dstY, dstZ uint32, src *ID3D11Resource, srcSubResource uint32, pSrcBox *D3D11_BOX) int32 { 221 | ret, _, _ := syscall.SyscallN( 222 | obj.vtbl.CopySubresourceRegion, 223 | uintptr(unsafe.Pointer(obj)), 224 | uintptr(unsafe.Pointer(dst)), 225 | uintptr(dstSubResource), 226 | uintptr(dstX), 227 | uintptr(dstY), 228 | uintptr(dstZ), 229 | uintptr(unsafe.Pointer(src)), 230 | uintptr(srcSubResource), 231 | uintptr(unsafe.Pointer(pSrcBox)), 232 | ) 233 | return int32(ret) 234 | } 235 | func (obj *ID3D11DeviceContext) Release() int32 { 236 | ret, _, _ := syscall.SyscallN( 237 | obj.vtbl.Release, 238 | uintptr(unsafe.Pointer(obj)), 239 | ) 240 | return int32(ret) 241 | } 242 | 243 | type ID3D11Resource struct { 244 | _ structs.HostLayout 245 | vtbl *ID3D11ResourceVtbl 246 | } 247 | 248 | func (obj *ID3D11Resource) Release() int32 { 249 | ret, _, _ := syscall.SyscallN( 250 | obj.vtbl.Release, 251 | uintptr(unsafe.Pointer(obj)), 252 | ) 253 | return int32(ret) 254 | } 255 | -------------------------------------------------------------------------------- /d3d11/d3d11_types.go: -------------------------------------------------------------------------------- 1 | package d3d11 2 | 3 | import ( 4 | "structs" 5 | 6 | "github.com/kirides/go-d3d/dxgi" 7 | ) 8 | 9 | type D3D11_BOX struct { 10 | _ structs.HostLayout 11 | 12 | Left, Top, Front, Right, Bottom, Back uint32 13 | } 14 | 15 | type D3D11_TEXTURE2D_DESC struct { 16 | _ structs.HostLayout 17 | 18 | Width uint32 19 | Height uint32 20 | MipLevels uint32 21 | ArraySize uint32 22 | Format uint32 23 | SampleDesc dxgi.DXGI_SAMPLE_DESC 24 | Usage uint32 25 | BindFlags uint32 26 | CPUAccessFlags uint32 27 | MiscFlags uint32 28 | } 29 | -------------------------------------------------------------------------------- /d3d11/d3d11_vtbl.go: -------------------------------------------------------------------------------- 1 | package d3d11 2 | 3 | import ( 4 | "structs" 5 | 6 | "github.com/kirides/go-d3d/com" 7 | ) 8 | 9 | type ID3D11DeviceChildVtbl struct { 10 | _ structs.HostLayout 11 | 12 | com.IUnknownVtbl 13 | 14 | GetDevice uintptr 15 | GetPrivateData uintptr 16 | SetPrivateData uintptr 17 | SetPrivateDataInterface uintptr 18 | } 19 | 20 | type ID3D11DeviceContextVtbl struct { 21 | _ structs.HostLayout 22 | ID3D11DeviceChildVtbl 23 | 24 | VSSetConstantBuffers uintptr 25 | PSSetShaderResources uintptr 26 | PSSetShader uintptr 27 | PSSetSamplers uintptr 28 | VSSetShader uintptr 29 | DrawIndexed uintptr 30 | Draw uintptr 31 | Map uintptr 32 | Unmap uintptr 33 | PSSetConstantBuffers uintptr 34 | IASetInputLayout uintptr 35 | IASetVertexBuffers uintptr 36 | IASetIndexBuffer uintptr 37 | DrawIndexedInstanced uintptr 38 | DrawInstanced uintptr 39 | GSSetConstantBuffers uintptr 40 | GSSetShader uintptr 41 | IASetPrimitiveTopology uintptr 42 | VSSetShaderResources uintptr 43 | VSSetSamplers uintptr 44 | Begin uintptr 45 | End uintptr 46 | GetData uintptr 47 | SetPredication uintptr 48 | GSSetShaderResources uintptr 49 | GSSetSamplers uintptr 50 | OMSetRenderTargets uintptr 51 | OMSetRenderTargetsAndUnorderedAccessViews uintptr 52 | OMSetBlendState uintptr 53 | OMSetDepthStencilState uintptr 54 | SOSetTargets uintptr 55 | DrawAuto uintptr 56 | DrawIndexedInstancedIndirect uintptr 57 | DrawInstancedIndirect uintptr 58 | Dispatch uintptr 59 | DispatchIndirect uintptr 60 | RSSetState uintptr 61 | RSSetViewports uintptr 62 | RSSetScissorRects uintptr 63 | CopySubresourceRegion uintptr 64 | CopyResource uintptr 65 | 66 | /// ..... 67 | } 68 | 69 | type ID3D11DeviceVtbl struct { 70 | _ structs.HostLayout 71 | com.IUnknownVtbl 72 | 73 | CreateBuffer uintptr 74 | CreateTexture1D uintptr 75 | CreateTexture2D uintptr 76 | CreateTexture3D uintptr 77 | CreateShaderResourceView uintptr 78 | CreateUnorderedAccessView uintptr 79 | CreateRenderTargetView uintptr 80 | CreateDepthStencilView uintptr 81 | CreateInputLayout uintptr 82 | CreateVertexShader uintptr 83 | CreateGeometryShader uintptr 84 | CreateGeometryShaderWithStreamOutput uintptr 85 | CreatePixelShader uintptr 86 | CreateHullShader uintptr 87 | CreateDomainShader uintptr 88 | CreateComputeShader uintptr 89 | CreateClassLinkage uintptr 90 | CreateBlendState uintptr 91 | CreateDepthStencilState uintptr 92 | CreateRasterizerState uintptr 93 | CreateSamplerState uintptr 94 | CreateQuery uintptr 95 | CreatePredicate uintptr 96 | CreateCounter uintptr 97 | CreateDeferredContext uintptr 98 | OpenSharedResource uintptr 99 | CheckFormatSupport uintptr 100 | CheckMultisampleQualityLevels uintptr 101 | CheckCounterInfo uintptr 102 | CheckCounter uintptr 103 | CheckFeatureSupport uintptr 104 | GetPrivateData uintptr 105 | SetPrivateData uintptr 106 | SetPrivateDataInterface uintptr 107 | GetFeatureLevel uintptr 108 | GetCreationFlags uintptr 109 | GetDeviceRemovedReason uintptr 110 | GetImmediateContext uintptr 111 | SetExceptionMode uintptr 112 | GetExceptionMode uintptr 113 | } 114 | 115 | type ID3D11DebugVtbl struct { 116 | _ structs.HostLayout 117 | com.IUnknownVtbl 118 | 119 | SetFeatureMask uintptr 120 | GetFeatureMask uintptr 121 | SetPresentPerRenderOpDelay uintptr 122 | GetPresentPerRenderOpDelay uintptr 123 | SetSwapChain uintptr 124 | GetSwapChain uintptr 125 | ValidateContext uintptr 126 | ReportLiveDeviceObjects uintptr 127 | ValidateContextForDispatch uintptr 128 | } 129 | 130 | type ID3D11InfoQueueVtbl struct { 131 | _ structs.HostLayout 132 | com.IUnknownVtbl 133 | 134 | AddApplicationMessage uintptr 135 | AddMessage uintptr 136 | AddRetrievalFilterEntries uintptr 137 | AddStorageFilterEntries uintptr 138 | ClearRetrievalFilter uintptr 139 | ClearStorageFilter uintptr 140 | ClearStoredMessages uintptr 141 | GetBreakOnCategory uintptr 142 | GetBreakOnID uintptr 143 | GetBreakOnSeverity uintptr 144 | GetMessage uintptr 145 | GetMessageCountLimit uintptr 146 | GetMuteDebugOutput uintptr 147 | GetNumMessagesAllowedByStorageFilter uintptr 148 | GetNumMessagesDeniedByStorageFilter uintptr 149 | GetNumMessagesDiscardedByMessageCountLimit uintptr 150 | GetNumStoredMessages uintptr 151 | GetNumStoredMessagesAllowedByRetrievalFilter uintptr 152 | GetRetrievalFilter uintptr 153 | GetRetrievalFilterStackSize uintptr 154 | GetStorageFilter uintptr 155 | GetStorageFilterStackSize uintptr 156 | PopRetrievalFilter uintptr 157 | PopStorageFilter uintptr 158 | PushCopyOfRetrievalFilter uintptr 159 | PushCopyOfStorageFilter uintptr 160 | PushEmptyRetrievalFilter uintptr 161 | PushEmptyStorageFilter uintptr 162 | PushRetrievalFilter uintptr 163 | PushStorageFilter uintptr 164 | SetBreakOnCategory uintptr 165 | SetBreakOnID uintptr 166 | SetBreakOnSeverity uintptr 167 | SetMessageCountLimit uintptr 168 | SetMuteDebugOutput uintptr 169 | } 170 | type ID3D11ResourceVtbl struct { 171 | _ structs.HostLayout 172 | ID3D11DeviceChildVtbl 173 | 174 | GetType uintptr 175 | SetEvictionPriority uintptr 176 | GetEvictionPriority uintptr 177 | } 178 | 179 | type ID3D11Texture2DVtbl struct { 180 | _ structs.HostLayout 181 | ID3D11ResourceVtbl 182 | 183 | GetDesc uintptr 184 | } 185 | -------------------------------------------------------------------------------- /d3d11/d3d11debug.go: -------------------------------------------------------------------------------- 1 | package d3d11 2 | 3 | import ( 4 | "structs" 5 | "syscall" 6 | "unsafe" 7 | 8 | "github.com/kirides/go-d3d/com" 9 | "golang.org/x/sys/windows" 10 | ) 11 | 12 | type ID3D11Debug struct { 13 | _ structs.HostLayout 14 | vtbl *ID3D11DebugVtbl 15 | } 16 | 17 | func (obj *ID3D11Debug) QueryInterface(iid windows.GUID, pp interface{}) int32 { 18 | return com.ReflectQueryInterface(obj, obj.vtbl.QueryInterface, &iid, pp) 19 | } 20 | func (obj *ID3D11Debug) ReportLiveDeviceObjects(flags uint32) int32 { 21 | ret, _, _ := syscall.Syscall( 22 | obj.vtbl.ReportLiveDeviceObjects, 23 | 2, 24 | uintptr(unsafe.Pointer(obj)), 25 | uintptr(flags), 26 | 0, 27 | ) 28 | return int32(ret) 29 | } 30 | func (obj *ID3D11Debug) Release() int32 { 31 | ret, _, _ := syscall.Syscall( 32 | obj.vtbl.Release, 33 | 1, 34 | uintptr(unsafe.Pointer(obj)), 35 | 0, 36 | 0, 37 | ) 38 | return int32(ret) 39 | } 40 | 41 | type ID3D11InfoQueue struct { 42 | _ structs.HostLayout 43 | vtbl *ID3D11InfoQueueVtbl 44 | } 45 | 46 | func (obj *ID3D11InfoQueue) Release() int32 { 47 | ret, _, _ := syscall.Syscall( 48 | obj.vtbl.Release, 49 | 1, 50 | uintptr(unsafe.Pointer(obj)), 51 | 0, 52 | 0, 53 | ) 54 | return int32(ret) 55 | } 56 | -------------------------------------------------------------------------------- /dxgi/dxgi.go: -------------------------------------------------------------------------------- 1 | package dxgi 2 | 3 | import ( 4 | "structs" 5 | "syscall" 6 | "unsafe" 7 | 8 | "github.com/kirides/go-d3d" 9 | "github.com/kirides/go-d3d/com" 10 | "golang.org/x/sys/windows" 11 | ) 12 | 13 | var ( 14 | modDXGI = windows.NewLazySystemDLL("dxgi.dll") 15 | procCreateDXGIFactory1 = modDXGI.NewProc("CreateDXGIFactory1") 16 | 17 | // iid_IDXGIDevice, _ = windows.GUIDFromString("{54ec77fa-1377-44e6-8c32-88fd5f44c84c}") 18 | IID_IDXGIDevice1, _ = windows.GUIDFromString("{77db970f-6276-48ba-ba28-070143b4392c}") 19 | // IID_IDXGIAdapter, _ = windows.GUIDFromString("{2411E7E1-12AC-4CCF-BD14-9798E8534DC0}") 20 | IID_IDXGIAdapter1, _ = windows.GUIDFromString("{29038f61-3839-4626-91fd-086879011a05}") 21 | // IID_IDXGIOutput, _ = windows.GUIDFromString("{ae02eedb-c735-4690-8d52-5a8dc20213aa}") 22 | IID_IDXGIOutput1, _ = windows.GUIDFromString("{00cddea8-939b-4b83-a340-a685226666cc}") 23 | IID_IDXGIOutput5, _ = windows.GUIDFromString("{80A07424-AB52-42EB-833C-0C42FD282D98}") 24 | IID_IDXGIFactory1, _ = windows.GUIDFromString("{770aae78-f26f-4dba-a829-253c83d1b387}") 25 | // IID_IDXGIResource, _ = windows.GUIDFromString("{035f3ab4-482e-4e50-b41f-8a7f8bd8960b}") 26 | IID_IDXGISurface, _ = windows.GUIDFromString("{cafcb56c-6ac3-4889-bf47-9e23bbd260ec}") 27 | ) 28 | 29 | const ( 30 | DXGI_MAP_READ = 1 << 0 31 | DXGI_MAP_WRITE = 1 << 1 32 | DXGI_MAP_DISCARD = 1 << 2 33 | ) 34 | 35 | type IDXGIFactory1 struct { 36 | _ structs.HostLayout 37 | vtbl *IDXGIFactory1Vtbl 38 | } 39 | 40 | func (obj *IDXGIFactory1) Release() int32 { 41 | ret, _, _ := syscall.SyscallN( 42 | obj.vtbl.Release, 43 | uintptr(unsafe.Pointer(obj)), 44 | ) 45 | return int32(ret) 46 | } 47 | 48 | func (obj *IDXGIFactory1) EnumAdapters1(adapter uint32, pp **IDXGIAdapter1) int32 { 49 | ret, _, _ := syscall.SyscallN( 50 | obj.vtbl.EnumAdapters1, 51 | uintptr(unsafe.Pointer(obj)), 52 | uintptr(adapter), 53 | uintptr(unsafe.Pointer(pp)), 54 | ) 55 | return int32(ret) 56 | } 57 | 58 | func CreateDXGIFactory1(ppFactory **IDXGIFactory1) error { 59 | ret, _, _ := syscall.SyscallN( 60 | procCreateDXGIFactory1.Addr(), 61 | uintptr(unsafe.Pointer(&IID_IDXGIFactory1)), 62 | uintptr(unsafe.Pointer(ppFactory)), 63 | ) 64 | if ret != 0 { 65 | return d3d.HRESULT(ret) 66 | } 67 | 68 | return nil 69 | } 70 | 71 | type IDXGIAdapter1 struct { 72 | _ structs.HostLayout 73 | vtbl *IDXGIAdapter1Vtbl 74 | } 75 | 76 | func (obj *IDXGIAdapter1) Release() int32 { 77 | ret, _, _ := syscall.SyscallN( 78 | obj.vtbl.Release, 79 | uintptr(unsafe.Pointer(obj)), 80 | ) 81 | return int32(ret) 82 | } 83 | 84 | func (obj *IDXGIAdapter1) EnumOutputs(output uint32, pp **IDXGIOutput) uint32 { 85 | ret, _, _ := syscall.SyscallN( 86 | obj.vtbl.EnumOutputs, 87 | uintptr(unsafe.Pointer(obj)), 88 | uintptr(output), 89 | uintptr(unsafe.Pointer(pp)), 90 | ) 91 | return uint32(ret) 92 | } 93 | 94 | type IDXGIAdapter struct { 95 | _ structs.HostLayout 96 | vtbl *IDXGIAdapterVtbl 97 | } 98 | 99 | func (obj *IDXGIAdapter) EnumOutputs(output uint32, pp **IDXGIOutput) uint32 { 100 | ret, _, _ := syscall.SyscallN( 101 | obj.vtbl.EnumOutputs, 102 | uintptr(unsafe.Pointer(obj)), 103 | uintptr(output), 104 | uintptr(unsafe.Pointer(pp)), 105 | ) 106 | return uint32(ret) 107 | } 108 | 109 | func (obj *IDXGIAdapter) Release() int32 { 110 | ret, _, _ := syscall.SyscallN( 111 | obj.vtbl.Release, 112 | uintptr(unsafe.Pointer(obj)), 113 | ) 114 | return int32(ret) 115 | } 116 | 117 | type IDXGIDevice struct { 118 | _ structs.HostLayout 119 | vtbl *IDXGIDeviceVtbl 120 | } 121 | 122 | func (obj *IDXGIDevice) GetGPUThreadPriority(priority *int) int32 { 123 | ret, _, _ := syscall.SyscallN( 124 | obj.vtbl.GetGPUThreadPriority, 125 | uintptr(unsafe.Pointer(obj)), 126 | uintptr(unsafe.Pointer(priority)), 127 | ) 128 | return int32(ret) 129 | } 130 | func (obj *IDXGIDevice) QueryInterface(iid windows.GUID, pp interface{}) int32 { 131 | return com.ReflectQueryInterface(obj, obj.vtbl.QueryInterface, &iid, pp) 132 | } 133 | func (obj *IDXGIDevice) GetParent(iid windows.GUID, pp *unsafe.Pointer) int32 { 134 | ret, _, _ := syscall.SyscallN( 135 | obj.vtbl.GetParent, 136 | uintptr(unsafe.Pointer(obj)), 137 | uintptr(unsafe.Pointer(&iid)), 138 | uintptr(unsafe.Pointer(pp)), 139 | ) 140 | return int32(ret) 141 | } 142 | func (obj *IDXGIDevice) GetAdapter(pAdapter **IDXGIAdapter) int32 { 143 | ret, _, _ := syscall.SyscallN( 144 | obj.vtbl.GetAdapter, 145 | uintptr(unsafe.Pointer(obj)), 146 | uintptr(unsafe.Pointer(pAdapter)), 147 | ) 148 | return int32(ret) 149 | } 150 | func (obj *IDXGIDevice) Release() int32 { 151 | ret, _, _ := syscall.SyscallN( 152 | obj.vtbl.Release, 153 | uintptr(unsafe.Pointer(obj)), 154 | ) 155 | return int32(ret) 156 | } 157 | 158 | type IDXGIDevice1 struct { 159 | _ structs.HostLayout 160 | vtbl *IDXGIDevice1Vtbl 161 | } 162 | 163 | func (obj *IDXGIDevice1) QueryInterface(iid windows.GUID, pp interface{}) int32 { 164 | return com.ReflectQueryInterface(obj, obj.vtbl.QueryInterface, &iid, pp) 165 | } 166 | 167 | func (obj *IDXGIDevice1) GetParent(iid windows.GUID, pp *unsafe.Pointer) int32 { 168 | ret, _, _ := syscall.SyscallN( 169 | obj.vtbl.GetParent, 170 | uintptr(unsafe.Pointer(obj)), 171 | uintptr(unsafe.Pointer(&iid)), 172 | uintptr(unsafe.Pointer(pp)), 173 | ) 174 | 175 | return int32(ret) 176 | } 177 | func (obj *IDXGIDevice1) GetAdapter(pAdapter *IDXGIAdapter) int32 { 178 | ret, _, _ := syscall.SyscallN( 179 | obj.vtbl.GetAdapter, 180 | uintptr(unsafe.Pointer(obj)), 181 | uintptr(unsafe.Pointer(&pAdapter)), 182 | ) 183 | 184 | return int32(ret) 185 | } 186 | func (obj *IDXGIDevice1) Release() int32 { 187 | ret, _, _ := syscall.SyscallN( 188 | obj.vtbl.Release, 189 | uintptr(unsafe.Pointer(obj)), 190 | ) 191 | return int32(ret) 192 | } 193 | 194 | type IDXGIOutput struct { 195 | _ structs.HostLayout 196 | vtbl *IDXGIOutputVtbl 197 | } 198 | 199 | func (obj *IDXGIOutput) QueryInterface(iid windows.GUID, pp interface{}) int32 { 200 | return com.ReflectQueryInterface(obj, obj.vtbl.QueryInterface, &iid, pp) 201 | } 202 | 203 | func (obj *IDXGIOutput) GetParent(iid windows.GUID, pp *unsafe.Pointer) int32 { 204 | ret, _, _ := syscall.SyscallN( 205 | obj.vtbl.GetParent, 206 | uintptr(unsafe.Pointer(obj)), 207 | uintptr(unsafe.Pointer(&iid)), 208 | uintptr(unsafe.Pointer(pp)), 209 | ) 210 | return int32(ret) 211 | } 212 | 213 | func (obj *IDXGIOutput) Release() int32 { 214 | ret, _, _ := syscall.SyscallN( 215 | obj.vtbl.Release, 216 | uintptr(unsafe.Pointer(obj)), 217 | ) 218 | return int32(ret) 219 | } 220 | 221 | type IDXGIOutput1 struct { 222 | _ structs.HostLayout 223 | vtbl *IDXGIOutput1Vtbl 224 | } 225 | 226 | func (obj *IDXGIOutput1) DuplicateOutput(device1 *IDXGIDevice1, ppOutputDuplication **IDXGIOutputDuplication) int32 { 227 | ret, _, _ := syscall.SyscallN( 228 | obj.vtbl.DuplicateOutput, 229 | uintptr(unsafe.Pointer(obj)), 230 | uintptr(unsafe.Pointer(device1)), 231 | uintptr(unsafe.Pointer(ppOutputDuplication)), 232 | ) 233 | return int32(ret) 234 | } 235 | 236 | func (obj *IDXGIOutput1) GetParent(iid windows.GUID, pp *unsafe.Pointer) int32 { 237 | ret, _, _ := syscall.SyscallN( 238 | obj.vtbl.GetParent, 239 | uintptr(unsafe.Pointer(obj)), 240 | uintptr(unsafe.Pointer(&iid)), 241 | uintptr(unsafe.Pointer(pp)), 242 | ) 243 | return int32(ret) 244 | } 245 | 246 | func (obj *IDXGIOutput1) Release() int32 { 247 | ret, _, _ := syscall.SyscallN( 248 | obj.vtbl.Release, 249 | uintptr(unsafe.Pointer(obj)), 250 | ) 251 | return int32(ret) 252 | } 253 | 254 | type IDXGIOutput5 struct { 255 | _ structs.HostLayout 256 | vtbl *IDXGIOutput5Vtbl 257 | } 258 | 259 | type DXGI_FORMAT uint32 260 | 261 | func (obj *IDXGIOutput5) GetDesc(desc *DXGI_OUTPUT_DESC) int32 { 262 | ret, _, _ := syscall.SyscallN( 263 | obj.vtbl.GetDesc, 264 | uintptr(unsafe.Pointer(obj)), 265 | uintptr(unsafe.Pointer(desc)), 266 | ) 267 | return int32(ret) 268 | } 269 | 270 | func (obj *IDXGIOutput5) DuplicateOutput1(device1 *IDXGIDevice1, flags uint32, pSupportedFormats []DXGI_FORMAT, ppOutputDuplication **IDXGIOutputDuplication) int32 { 271 | pFormats := &pSupportedFormats[0] 272 | ret, _, _ := syscall.SyscallN( 273 | obj.vtbl.DuplicateOutput1, 274 | uintptr(unsafe.Pointer(obj)), 275 | uintptr(unsafe.Pointer(device1)), 276 | uintptr(flags), 277 | uintptr(len(pSupportedFormats)), 278 | uintptr(unsafe.Pointer(pFormats)), 279 | uintptr(unsafe.Pointer(ppOutputDuplication)), 280 | ) 281 | return int32(ret) 282 | } 283 | 284 | func (obj *IDXGIOutput5) GetParent(iid windows.GUID, pp *unsafe.Pointer) int32 { 285 | ret, _, _ := syscall.SyscallN( 286 | obj.vtbl.GetParent, 287 | uintptr(unsafe.Pointer(obj)), 288 | uintptr(unsafe.Pointer(&iid)), 289 | uintptr(unsafe.Pointer(pp)), 290 | ) 291 | return int32(ret) 292 | } 293 | 294 | func (obj *IDXGIOutput5) Release() int32 { 295 | ret, _, _ := syscall.SyscallN( 296 | obj.vtbl.Release, 297 | uintptr(unsafe.Pointer(obj)), 298 | ) 299 | return int32(ret) 300 | } 301 | 302 | type IDXGIResource struct { 303 | _ structs.HostLayout 304 | vtbl *IDXGIResourceVtbl 305 | } 306 | 307 | func (obj *IDXGIResource) QueryInterface(iid windows.GUID, pp interface{}) int32 { 308 | return com.ReflectQueryInterface(obj, obj.vtbl.QueryInterface, &iid, pp) 309 | } 310 | func (obj *IDXGIResource) Release() int32 { 311 | ret, _, _ := syscall.SyscallN( 312 | obj.vtbl.Release, 313 | uintptr(unsafe.Pointer(obj)), 314 | ) 315 | return int32(ret) 316 | } 317 | 318 | type IDXGISurface struct { 319 | _ structs.HostLayout 320 | vtbl *IDXGISurfaceVtbl 321 | } 322 | 323 | func (obj *IDXGISurface) QueryInterface(iid windows.GUID, pp interface{}) int32 { 324 | return com.ReflectQueryInterface(obj, obj.vtbl.QueryInterface, &iid, pp) 325 | } 326 | func (obj *IDXGISurface) Map(pLockedRect *DXGI_MAPPED_RECT, mapFlags uint32) int32 { 327 | ret, _, _ := syscall.SyscallN( 328 | obj.vtbl.Map, 329 | uintptr(unsafe.Pointer(obj)), 330 | uintptr(unsafe.Pointer(pLockedRect)), 331 | uintptr(mapFlags), 332 | ) 333 | return int32(ret) 334 | } 335 | func (obj *IDXGISurface) Unmap() int32 { 336 | ret, _, _ := syscall.SyscallN( 337 | obj.vtbl.Unmap, 338 | uintptr(unsafe.Pointer(obj)), 339 | ) 340 | return int32(ret) 341 | } 342 | func (obj *IDXGISurface) Release() int32 { 343 | ret, _, _ := syscall.SyscallN( 344 | obj.vtbl.Release, 345 | uintptr(unsafe.Pointer(obj)), 346 | ) 347 | return int32(ret) 348 | } 349 | 350 | type IDXGIOutputDuplication struct { 351 | _ structs.HostLayout 352 | vtbl *IDXGIOutputDuplicationVtbl 353 | } 354 | 355 | func (obj *IDXGIOutputDuplication) GetFrameMoveRects(buffer []DXGI_OUTDUPL_MOVE_RECT, rectsRequired *uint32) int32 { 356 | var buf *DXGI_OUTDUPL_MOVE_RECT 357 | if len(buffer) > 0 { 358 | buf = &buffer[0] 359 | } 360 | size := uint32(len(buffer) * 24) 361 | ret, _, _ := syscall.SyscallN( 362 | obj.vtbl.GetFrameMoveRects, 363 | uintptr(unsafe.Pointer(obj)), 364 | uintptr(size), 365 | uintptr(unsafe.Pointer(buf)), 366 | uintptr(unsafe.Pointer(rectsRequired)), 367 | ) 368 | *rectsRequired = *rectsRequired / 24 369 | return int32(ret) 370 | } 371 | func (obj *IDXGIOutputDuplication) GetFrameDirtyRects(buffer []RECT, rectsRequired *uint32) int32 { 372 | var buf *RECT 373 | if len(buffer) > 0 { 374 | buf = &buffer[0] 375 | } 376 | size := uint32(len(buffer) * 16) 377 | ret, _, _ := syscall.SyscallN( 378 | obj.vtbl.GetFrameDirtyRects, 379 | uintptr(unsafe.Pointer(obj)), 380 | uintptr(size), 381 | uintptr(unsafe.Pointer(buf)), 382 | uintptr(unsafe.Pointer(rectsRequired)), 383 | ) 384 | *rectsRequired = *rectsRequired / 16 385 | return int32(ret) 386 | } 387 | 388 | func (obj *IDXGIOutputDuplication) GetFramePointerShape(pointerShapeBufferSize uint32, 389 | pPointerShapeBuffer []byte, 390 | pPointerShapeBufferSizeRequired *uint32, 391 | pPointerShapeInfo *DXGI_OUTDUPL_POINTER_SHAPE_INFO) int32 { 392 | 393 | var buf *byte 394 | if len(pPointerShapeBuffer) > 0 { 395 | buf = &pPointerShapeBuffer[0] 396 | } 397 | 398 | ret, _, _ := syscall.SyscallN( 399 | obj.vtbl.GetFramePointerShape, 400 | uintptr(unsafe.Pointer(obj)), 401 | uintptr(pointerShapeBufferSize), 402 | uintptr(unsafe.Pointer(buf)), 403 | uintptr(unsafe.Pointer(pPointerShapeBufferSizeRequired)), 404 | uintptr(unsafe.Pointer(pPointerShapeInfo)), 405 | ) 406 | 407 | return int32(ret) 408 | } 409 | func (obj *IDXGIOutputDuplication) GetDesc(desc *DXGI_OUTDUPL_DESC) int32 { 410 | ret, _, _ := syscall.SyscallN( 411 | obj.vtbl.GetDesc, 412 | uintptr(unsafe.Pointer(obj)), 413 | uintptr(unsafe.Pointer(desc)), 414 | ) 415 | return int32(ret) 416 | } 417 | 418 | func (obj *IDXGIOutputDuplication) MapDesktopSurface(pLockedRect *DXGI_MAPPED_RECT) int32 { 419 | ret, _, _ := syscall.SyscallN( 420 | obj.vtbl.MapDesktopSurface, 421 | uintptr(unsafe.Pointer(obj)), 422 | uintptr(unsafe.Pointer(pLockedRect)), 423 | ) 424 | return int32(ret) 425 | } 426 | func (obj *IDXGIOutputDuplication) UnMapDesktopSurface() int32 { 427 | ret, _, _ := syscall.SyscallN( 428 | obj.vtbl.UnMapDesktopSurface, 429 | uintptr(unsafe.Pointer(obj)), 430 | ) 431 | return int32(ret) 432 | } 433 | func (obj *IDXGIOutputDuplication) AddRef() uint32 { 434 | ret, _, _ := syscall.SyscallN( 435 | obj.vtbl.AddRef, 436 | uintptr(unsafe.Pointer(obj)), 437 | ) 438 | return uint32(ret) 439 | } 440 | 441 | func (obj *IDXGIOutputDuplication) Release() uint32 { 442 | ret, _, _ := syscall.SyscallN( 443 | obj.vtbl.Release, 444 | uintptr(unsafe.Pointer(obj)), 445 | ) 446 | return uint32(ret) 447 | } 448 | 449 | func (obj *IDXGIOutputDuplication) AcquireNextFrame(timeoutMs uint32, pFrameInfo *DXGI_OUTDUPL_FRAME_INFO, ppDesktopResource **IDXGIResource) uint32 { 450 | ret, _, _ := syscall.SyscallN( 451 | obj.vtbl.AcquireNextFrame, // function address 452 | uintptr(unsafe.Pointer(obj)), // always pass the COM object address first 453 | uintptr(timeoutMs), // then all function parameters follow 454 | uintptr(unsafe.Pointer(pFrameInfo)), 455 | uintptr(unsafe.Pointer(ppDesktopResource)), 456 | ) 457 | return uint32(ret) 458 | } 459 | 460 | func (obj *IDXGIOutputDuplication) ReleaseFrame() uint32 { 461 | ret, _, _ := syscall.SyscallN( 462 | obj.vtbl.ReleaseFrame, 463 | uintptr(unsafe.Pointer(obj)), 464 | ) 465 | return uint32(ret) 466 | } 467 | -------------------------------------------------------------------------------- /dxgi/dxgi_types.go: -------------------------------------------------------------------------------- 1 | package dxgi 2 | 3 | import "structs" 4 | 5 | //go:generate stringer -type=_DXGI_OUTDUPL_POINTER_SHAPE_TYPE -output=dxgi_types_string.go 6 | 7 | type DXGI_RATIONAL struct { 8 | _ structs.HostLayout 9 | 10 | Numerator uint32 11 | Denominator uint32 12 | } 13 | 14 | type DXGI_MODE_ROTATION uint32 15 | 16 | type DXGI_OUTPUT_DESC struct { 17 | _ structs.HostLayout 18 | 19 | DeviceName [32]uint16 20 | DesktopCoordinates RECT 21 | AttachedToDesktop uint32 // BOOL 22 | Rotation DXGI_MODE_ROTATION 23 | Monitor uintptr 24 | } 25 | 26 | type DXGI_MODE_DESC struct { 27 | _ structs.HostLayout 28 | 29 | Width uint32 30 | Height uint32 31 | Rational DXGI_RATIONAL 32 | Format uint32 // DXGI_FORMAT 33 | ScanlineOrdering uint32 // DXGI_MODE_SCANLINE_ORDER 34 | Scaling uint32 // DXGI_MODE_SCALING 35 | } 36 | 37 | type DXGI_OUTDUPL_DESC struct { 38 | _ structs.HostLayout 39 | 40 | ModeDesc DXGI_MODE_DESC 41 | Rotation uint32 // DXGI_MODE_ROTATION 42 | DesktopImageInSystemMemory uint32 // BOOL 43 | } 44 | 45 | type DXGI_SAMPLE_DESC struct { 46 | _ structs.HostLayout 47 | 48 | Count uint32 49 | Quality uint32 50 | } 51 | 52 | type POINT struct { 53 | _ structs.HostLayout 54 | 55 | X int32 56 | Y int32 57 | } 58 | type RECT struct { 59 | _ structs.HostLayout 60 | 61 | Left, Top, Right, Bottom int32 62 | } 63 | 64 | type DXGI_OUTDUPL_MOVE_RECT struct { 65 | _ structs.HostLayout 66 | 67 | Src POINT 68 | Dest RECT 69 | } 70 | type DXGI_OUTDUPL_POINTER_POSITION struct { 71 | _ structs.HostLayout 72 | 73 | Position POINT 74 | Visible uint32 75 | } 76 | type DXGI_OUTDUPL_FRAME_INFO struct { 77 | _ structs.HostLayout 78 | 79 | LastPresentTime int64 80 | LastMouseUpdateTime int64 81 | AccumulatedFrames uint32 82 | RectsCoalesced uint32 83 | ProtectedContentMaskedOut uint32 84 | PointerPosition DXGI_OUTDUPL_POINTER_POSITION 85 | TotalMetadataBufferSize uint32 86 | PointerShapeBufferSize uint32 87 | } 88 | type DXGI_MAPPED_RECT struct { 89 | _ structs.HostLayout 90 | 91 | Pitch int32 92 | PBits uintptr 93 | } 94 | 95 | const ( 96 | DXGI_FORMAT_R8G8B8A8_UNORM DXGI_FORMAT = 28 97 | DXGI_FORMAT_B8G8R8A8_UNORM DXGI_FORMAT = 87 98 | ) 99 | 100 | type DXGI_OUTDUPL_POINTER_SHAPE_TYPE uint32 101 | 102 | const ( 103 | DXGI_OUTDUPL_POINTER_SHAPE_TYPE_MONOCHROME DXGI_OUTDUPL_POINTER_SHAPE_TYPE = 1 104 | DXGI_OUTDUPL_POINTER_SHAPE_TYPE_COLOR DXGI_OUTDUPL_POINTER_SHAPE_TYPE = 2 105 | DXGI_OUTDUPL_POINTER_SHAPE_TYPE_MASKED_COLOR DXGI_OUTDUPL_POINTER_SHAPE_TYPE = 4 106 | ) 107 | 108 | type DXGI_OUTDUPL_POINTER_SHAPE_INFO struct { 109 | _ structs.HostLayout 110 | 111 | Type DXGI_OUTDUPL_POINTER_SHAPE_TYPE 112 | Width uint32 113 | Height uint32 114 | Pitch uint32 115 | HotSpot POINT 116 | } 117 | -------------------------------------------------------------------------------- /dxgi/dxgi_types_string.go: -------------------------------------------------------------------------------- 1 | // Code generated by "stringer -type=_DXGI_OUTDUPL_POINTER_SHAPE_TYPE -output=dxgi_types_string.go"; DO NOT EDIT. 2 | 3 | package dxgi 4 | 5 | import "strconv" 6 | 7 | func _() { 8 | // An "invalid array index" compiler error signifies that the constant values have changed. 9 | // Re-run the stringer command to generate them again. 10 | var x [1]struct{} 11 | _ = x[DXGI_OUTDUPL_POINTER_SHAPE_TYPE_MONOCHROME-1] 12 | _ = x[DXGI_OUTDUPL_POINTER_SHAPE_TYPE_COLOR-2] 13 | _ = x[DXGI_OUTDUPL_POINTER_SHAPE_TYPE_MASKED_COLOR-4] 14 | } 15 | 16 | const ( 17 | __DXGI_OUTDUPL_POINTER_SHAPE_TYPE_name_0 = "DXGI_OUTDUPL_POINTER_SHAPE_TYPE_MONOCHROMEDXGI_OUTDUPL_POINTER_SHAPE_TYPE_COLOR" 18 | __DXGI_OUTDUPL_POINTER_SHAPE_TYPE_name_1 = "DXGI_OUTDUPL_POINTER_SHAPE_TYPE_MASKED_COLOR" 19 | ) 20 | 21 | var ( 22 | __DXGI_OUTDUPL_POINTER_SHAPE_TYPE_index_0 = [...]uint8{0, 42, 79} 23 | ) 24 | 25 | func (i DXGI_OUTDUPL_POINTER_SHAPE_TYPE) String() string { 26 | switch { 27 | case 1 <= i && i <= 2: 28 | i -= 1 29 | return __DXGI_OUTDUPL_POINTER_SHAPE_TYPE_name_0[__DXGI_OUTDUPL_POINTER_SHAPE_TYPE_index_0[i]:__DXGI_OUTDUPL_POINTER_SHAPE_TYPE_index_0[i+1]] 30 | case i == 4: 31 | return __DXGI_OUTDUPL_POINTER_SHAPE_TYPE_name_1 32 | default: 33 | return "_DXGI_OUTDUPL_POINTER_SHAPE_TYPE(" + strconv.FormatInt(int64(i), 10) + ")" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /dxgi/dxgi_vtbl.go: -------------------------------------------------------------------------------- 1 | package dxgi 2 | 3 | import ( 4 | "structs" 5 | 6 | "github.com/kirides/go-d3d/com" 7 | ) 8 | 9 | type IDXGIObjectVtbl struct { 10 | _ structs.HostLayout 11 | com.IUnknownVtbl 12 | 13 | SetPrivateData uintptr 14 | SetPrivateDataInterface uintptr 15 | GetPrivateData uintptr 16 | GetParent uintptr 17 | } 18 | 19 | type IDXGIAdapterVtbl struct { 20 | _ structs.HostLayout 21 | IDXGIObjectVtbl 22 | 23 | EnumOutputs uintptr 24 | GetDesc uintptr 25 | CheckInterfaceSupport uintptr 26 | } 27 | type IDXGIAdapter1Vtbl struct { 28 | _ structs.HostLayout 29 | IDXGIAdapterVtbl 30 | 31 | GetDesc1 uintptr 32 | } 33 | 34 | type IDXGIDeviceVtbl struct { 35 | _ structs.HostLayout 36 | IDXGIObjectVtbl 37 | 38 | CreateSurface uintptr 39 | GetAdapter uintptr 40 | GetGPUThreadPriority uintptr 41 | QueryResourceResidency uintptr 42 | SetGPUThreadPriority uintptr 43 | } 44 | 45 | type IDXGIDevice1Vtbl struct { 46 | _ structs.HostLayout 47 | IDXGIDeviceVtbl 48 | 49 | GetMaximumFrameLatency uintptr 50 | SetMaximumFrameLatency uintptr 51 | } 52 | 53 | type IDXGIDeviceSubObjectVtbl struct { 54 | _ structs.HostLayout 55 | IDXGIObjectVtbl 56 | 57 | GetDevice uintptr 58 | } 59 | 60 | type IDXGISurfaceVtbl struct { 61 | _ structs.HostLayout 62 | IDXGIDeviceSubObjectVtbl 63 | 64 | GetDesc uintptr 65 | Map uintptr 66 | Unmap uintptr 67 | } 68 | 69 | type IDXGIResourceVtbl struct { 70 | _ structs.HostLayout 71 | IDXGIDeviceSubObjectVtbl 72 | 73 | GetSharedHandle uintptr 74 | GetUsage uintptr 75 | SetEvictionPriority uintptr 76 | GetEvictionPriority uintptr 77 | } 78 | 79 | type IDXGIOutputVtbl struct { 80 | _ structs.HostLayout 81 | IDXGIObjectVtbl 82 | 83 | GetDesc uintptr 84 | GetDisplayModeList uintptr 85 | FindClosestMatchingMode uintptr 86 | WaitForVBlank uintptr 87 | TakeOwnership uintptr 88 | ReleaseOwnership uintptr 89 | GetGammaControlCapabilities uintptr 90 | SetGammaControl uintptr 91 | GetGammaControl uintptr 92 | SetDisplaySurface uintptr 93 | GetDisplaySurfaceData uintptr 94 | GetFrameStatistics uintptr 95 | } 96 | 97 | type IDXGIOutput1Vtbl struct { 98 | _ structs.HostLayout 99 | IDXGIOutputVtbl 100 | 101 | GetDisplayModeList1 uintptr 102 | FindClosestMatchingMode1 uintptr 103 | GetDisplaySurfaceData1 uintptr 104 | DuplicateOutput uintptr 105 | } 106 | 107 | type IDXGIOutput2Vtbl struct { 108 | _ structs.HostLayout 109 | IDXGIOutput1Vtbl 110 | 111 | SupportsOverlays uintptr 112 | } 113 | 114 | type IDXGIOutput3Vtbl struct { 115 | _ structs.HostLayout 116 | IDXGIOutput2Vtbl 117 | 118 | CheckOverlaySupport uintptr 119 | } 120 | 121 | type IDXGIOutput4Vtbl struct { 122 | _ structs.HostLayout 123 | IDXGIOutput3Vtbl 124 | 125 | CheckOverlayColorSpaceSupport uintptr 126 | } 127 | type IDXGIOutput5Vtbl struct { 128 | _ structs.HostLayout 129 | IDXGIOutput4Vtbl 130 | 131 | DuplicateOutput1 uintptr 132 | } 133 | 134 | type IDXGIOutputDuplicationVtbl struct { 135 | _ structs.HostLayout 136 | IDXGIObjectVtbl 137 | 138 | GetDesc uintptr 139 | AcquireNextFrame uintptr 140 | GetFrameDirtyRects uintptr 141 | GetFrameMoveRects uintptr 142 | GetFramePointerShape uintptr 143 | MapDesktopSurface uintptr 144 | UnMapDesktopSurface uintptr 145 | ReleaseFrame uintptr 146 | } 147 | type IDXGIFactoryVtbl struct { 148 | _ structs.HostLayout 149 | IDXGIObjectVtbl 150 | 151 | EnumAdapters uintptr 152 | MakeWindowAssociation uintptr 153 | GetWindowAssociation uintptr 154 | CreateSwapChain uintptr 155 | CreateSoftwareAdapter uintptr 156 | } 157 | type IDXGIFactory1Vtbl struct { 158 | _ structs.HostLayout 159 | IDXGIFactoryVtbl 160 | 161 | EnumAdapters1 uintptr 162 | IsCurrent uintptr 163 | } 164 | -------------------------------------------------------------------------------- /examples/framelimiter/framelimiter.go: -------------------------------------------------------------------------------- 1 | package framelimiter 2 | 3 | import "time" 4 | 5 | // finer granularity for sleeping 6 | type frameLimiter struct { 7 | DesiredFps int 8 | frameTimeNs int64 9 | 10 | LastFrameTime time.Time 11 | LastSleepDuration time.Duration 12 | 13 | DidSleep bool 14 | DidSpin bool 15 | } 16 | 17 | func New(desiredFps int) *frameLimiter { 18 | return &frameLimiter{ 19 | DesiredFps: desiredFps, 20 | frameTimeNs: (time.Second / time.Duration(desiredFps)).Nanoseconds(), 21 | LastFrameTime: time.Now(), 22 | } 23 | } 24 | 25 | func (l *frameLimiter) Wait() { 26 | l.DidSleep = false 27 | l.DidSpin = false 28 | 29 | now := time.Now() 30 | spinWaitUntil := now 31 | 32 | sleepTime := l.frameTimeNs - now.Sub(l.LastFrameTime).Nanoseconds() 33 | 34 | if sleepTime > int64(1*time.Millisecond) { 35 | if sleepTime < int64(30*time.Millisecond) { 36 | l.LastSleepDuration = time.Duration(sleepTime / 8) 37 | } else { 38 | l.LastSleepDuration = time.Duration(sleepTime / 4 * 3) 39 | } 40 | time.Sleep(time.Duration(l.LastSleepDuration)) 41 | l.DidSleep = true 42 | 43 | newNow := time.Now() 44 | spinWaitUntil = newNow.Add(time.Duration(sleepTime) - newNow.Sub(now)) 45 | now = newNow 46 | 47 | for spinWaitUntil.After(now) { 48 | now = time.Now() 49 | // SPIN WAIT 50 | l.DidSpin = true 51 | } 52 | } else { 53 | l.LastSleepDuration = 0 54 | spinWaitUntil = now.Add(time.Duration(sleepTime)) 55 | for spinWaitUntil.After(now) { 56 | now = time.Now() 57 | // SPIN WAIT 58 | l.DidSpin = true 59 | } 60 | } 61 | l.LastFrameTime = time.Now() 62 | } 63 | -------------------------------------------------------------------------------- /examples/mjpegstream/jpeg_native.go: -------------------------------------------------------------------------------- 1 | //go:build !turbo 2 | 3 | package main 4 | 5 | import ( 6 | "image" 7 | "io" 8 | 9 | "image/jpeg" 10 | ) 11 | 12 | func jpegQuality(q int) *jpeg.Options { 13 | return &jpeg.Options{Quality: q} 14 | } 15 | 16 | func encodeJpeg(w io.Writer, src image.Image, opts *jpeg.Options) { 17 | jpeg.Encode(w, src, opts) 18 | } 19 | -------------------------------------------------------------------------------- /examples/mjpegstream/jpeg_turbo.go: -------------------------------------------------------------------------------- 1 | //go:build turbo 2 | 3 | package main 4 | 5 | import ( 6 | "image" 7 | "io" 8 | 9 | "github.com/viam-labs/go-libjpeg/jpeg" 10 | ) 11 | 12 | func jpegQuality(q int) *jpeg.EncoderOptions { 13 | return &jpeg.EncoderOptions{Quality: q} 14 | } 15 | 16 | func encodeJpeg(w io.Writer, src image.Image, opts *jpeg.EncoderOptions) { 17 | jpeg.Encode(w, src, opts) 18 | } 19 | -------------------------------------------------------------------------------- /examples/mjpegstream/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "errors" 7 | "fmt" 8 | "image" 9 | "net/http" 10 | "runtime" 11 | 12 | "os" 13 | "os/signal" 14 | "strconv" 15 | "syscall" 16 | "time" 17 | 18 | "github.com/kirides/go-d3d/d3d11" 19 | "github.com/kirides/go-d3d/examples/framelimiter" 20 | "github.com/kirides/go-d3d/outputduplication" 21 | "github.com/kirides/go-d3d/win" 22 | 23 | "github.com/kbinani/screenshot" 24 | "github.com/mattn/go-mjpeg" 25 | ) 26 | 27 | func main() { 28 | n := screenshot.NumActiveDisplays() 29 | ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM) 30 | defer cancel() 31 | http.HandleFunc("/watch", func(w http.ResponseWriter, r *http.Request) { 32 | screen := r.URL.Query().Get("screen") 33 | if screen == "" { 34 | screen = "0" 35 | } 36 | screenNo, err := strconv.Atoi(screen) 37 | if err != nil { 38 | w.WriteHeader(500) 39 | return 40 | } 41 | if screenNo >= n || screenNo < 0 { 42 | screenNo = 0 43 | } 44 | 45 | w.Header().Set("Content-Type", "text/html") 46 | w.Write([]byte(` 47 | 48 | 49 | 50 | Screen ` + strconv.Itoa(screenNo) + ` 51 | 52 | 53 | 54 | `)) 55 | }) 56 | 57 | framerate := 25 58 | for i := 0; i < screenshot.NumActiveDisplays(); i++ { 59 | fmt.Fprintf(os.Stderr, "Registering stream %d\n", i) 60 | stream := mjpeg.NewStream() 61 | defer stream.Close() 62 | go streamDisplayDXGI(ctx, i, framerate, stream) 63 | http.HandleFunc(fmt.Sprintf("/mjpeg%d", i), stream.ServeHTTP) 64 | } 65 | go func() { 66 | http.ListenAndServe("127.0.0.1:8023", nil) 67 | 68 | }() 69 | <-ctx.Done() 70 | <-time.After(time.Second) 71 | } 72 | 73 | // Capture using IDXGIOutputDuplication 74 | // 75 | // https://docs.microsoft.com/en-us/windows/win32/api/dxgi1_2/nn-dxgi1_2-idxgioutputduplication 76 | func streamDisplayDXGI(ctx context.Context, n int, framerate int, out *mjpeg.Stream) { 77 | max := screenshot.NumActiveDisplays() 78 | if n >= max { 79 | fmt.Printf("Not enough displays\n") 80 | return 81 | } 82 | 83 | // Keep this thread, so windows/d3d11/dxgi can use their threadlocal caches, if any 84 | runtime.LockOSThread() 85 | defer runtime.UnlockOSThread() 86 | 87 | // Make thread PerMonitorV2 Dpi aware if supported on OS 88 | // allows to let windows handle BGRA -> RGBA conversion and possibly more things 89 | if win.IsValidDpiAwarenessContext(win.DpiAwarenessContextPerMonitorAwareV2) { 90 | _, err := win.SetThreadDpiAwarenessContext(win.DpiAwarenessContextPerMonitorAwareV2) 91 | if err != nil { 92 | fmt.Printf("Could not set thread DPI awareness to PerMonitorAwareV2. %v\n", err) 93 | } else { 94 | fmt.Printf("Enabled PerMonitorAwareV2 DPI awareness.\n") 95 | } 96 | } 97 | 98 | // Setup D3D11 stuff 99 | device, deviceCtx, err := d3d11.NewD3D11Device() 100 | if err != nil { 101 | fmt.Printf("Could not create D3D11 Device. %v\n", err) 102 | return 103 | } 104 | defer device.Release() 105 | defer deviceCtx.Release() 106 | 107 | var ddup *outputduplication.OutputDuplicator 108 | defer func() { 109 | if ddup != nil { 110 | ddup.Release() 111 | ddup = nil 112 | } 113 | }() 114 | 115 | buf := &bufferFlusher{Buffer: bytes.Buffer{}} 116 | opts := jpegQuality(50) 117 | limiter := framelimiter.New(framerate) 118 | 119 | lastBounds := image.Rectangle{} 120 | var imgBuf *image.RGBA 121 | var lastFrameWithoutCursor *image.RGBA 122 | 123 | drawCursor := true 124 | for { 125 | select { 126 | case <-ctx.Done(): 127 | return 128 | default: 129 | limiter.Wait() 130 | } 131 | // create output duplication if doesn't exist yet (maybe due to resolution change) 132 | if ddup == nil { 133 | ddup, err = outputduplication.NewIDXGIOutputDuplication(device, deviceCtx, uint(n)) 134 | if err != nil { 135 | fmt.Printf("err: %v\n", err) 136 | continue 137 | } 138 | ddup.UpdatePointerInfo = drawCursor 139 | bounds, err := ddup.GetBounds() 140 | if err != nil { 141 | return 142 | } 143 | if bounds != lastBounds { 144 | lastBounds = bounds 145 | imgBuf = image.NewRGBA(lastBounds) 146 | lastFrameWithoutCursor = image.NewRGBA(lastBounds) 147 | } 148 | } 149 | 150 | // Grab an image.RGBA from the current output presenter 151 | currentImage := imgBuf 152 | err = ddup.GetImage(currentImage, 999) 153 | if err != nil { 154 | if errors.Is(err, outputduplication.ErrNoImageYet) { 155 | // don't update 156 | copy(imgBuf.Pix, lastFrameWithoutCursor.Pix) 157 | } else { 158 | fmt.Printf("Err ddup.GetImage: %v\n", err) 159 | // Retry with new ddup, can occur when changing resolution 160 | ddup.Release() 161 | ddup = nil 162 | continue 163 | } 164 | } else { 165 | copy(lastFrameWithoutCursor.Pix, imgBuf.Pix) 166 | } 167 | if drawCursor { 168 | ddup.DrawCursor(currentImage) 169 | } 170 | 171 | buf.Reset() 172 | encodeJpeg(buf, imgBuf, opts) 173 | out.Update(buf.Bytes()) 174 | } 175 | } 176 | 177 | // Workaround for jpeg.Encode(), which requires a Flush() 178 | // method to not call `bufio.NewWriter` 179 | type bufferFlusher struct { 180 | bytes.Buffer 181 | } 182 | 183 | func (*bufferFlusher) Flush() error { return nil } 184 | -------------------------------------------------------------------------------- /examples/recording/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "net/http" 7 | 8 | "os" 9 | "os/signal" 10 | "syscall" 11 | "time" 12 | 13 | "github.com/kbinani/screenshot" 14 | "github.com/mattn/go-mjpeg" 15 | ) 16 | 17 | func main() { 18 | ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM) 19 | defer cancel() 20 | 21 | framerate := 25 22 | for i := 0; i < screenshot.NumActiveDisplays(); i++ { 23 | fmt.Fprintf(os.Stderr, "Registering stream %d\n", i) 24 | stream := mjpeg.NewStream() 25 | defer stream.Close() 26 | go captureScreenTranscode(ctx, i, framerate) 27 | http.HandleFunc(fmt.Sprintf("/mjpeg%d", i), stream.ServeHTTP) 28 | } 29 | <-ctx.Done() 30 | <-time.After(time.Second) 31 | } 32 | -------------------------------------------------------------------------------- /examples/recording/transcode.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "fmt" 7 | "image" 8 | "io" 9 | "os/exec" 10 | "runtime" 11 | "time" 12 | 13 | "github.com/kbinani/screenshot" 14 | "github.com/kirides/go-d3d/d3d11" 15 | "github.com/kirides/go-d3d/examples/framelimiter" 16 | "github.com/kirides/go-d3d/outputduplication" 17 | "github.com/kirides/go-d3d/win" 18 | ) 19 | 20 | func captureScreenTranscode(ctx context.Context, n int, framerate int) { 21 | max := screenshot.NumActiveDisplays() 22 | if n >= max { 23 | fmt.Printf("Not enough displays\n") 24 | return 25 | } 26 | 27 | // Keep this thread, so windows/d3d11/dxgi can use their threadlocal caches, if any 28 | runtime.LockOSThread() 29 | 30 | // Make thread PerMonitorV2 Dpi aware if supported on OS 31 | // allows to let windows handle BGRA -> RGBA conversion and possibly more things 32 | if win.IsValidDpiAwarenessContext(win.DpiAwarenessContextPerMonitorAwareV2) { 33 | _, err := win.SetThreadDpiAwarenessContext(win.DpiAwarenessContextPerMonitorAwareV2) 34 | if err != nil { 35 | fmt.Printf("Could not set thread DPI awareness to PerMonitorAwareV2. %v\n", err) 36 | } else { 37 | fmt.Printf("Enabled PerMonitorAwareV2 DPI awareness.\n") 38 | } 39 | } 40 | 41 | // Setup D3D11 stuff 42 | device, deviceCtx, err := d3d11.NewD3D11Device() 43 | if err != nil { 44 | fmt.Printf("Could not create D3D11 Device. %v\n", err) 45 | return 46 | } 47 | defer device.Release() 48 | defer deviceCtx.Release() 49 | 50 | ddup, err := outputduplication.NewIDXGIOutputDuplication(device, deviceCtx, uint(n)) 51 | if err != nil { 52 | fmt.Printf("Err NewIDXGIOutputDuplication: %v\n", err) 53 | return 54 | } 55 | defer ddup.Release() 56 | 57 | screenBounds, err := ddup.GetBounds() 58 | if err != nil { 59 | fmt.Printf("Unable to obtain output bounds: %v\n", err) 60 | return 61 | } 62 | transcoder := newVideotranscoder(fmt.Sprintf("screen_%d.mp4", n), screenBounds.Dx(), screenBounds.Dy(), float32(framerate)) 63 | 64 | limiter := framelimiter.New(framerate) 65 | 66 | // Create image that can contain the wanted output (desktop) 67 | imgBuf := image.NewRGBA(screenBounds) 68 | 69 | defer transcoder.Close() 70 | t1 := time.Now() 71 | numFrames := 0 72 | for { 73 | if time.Since(t1).Seconds() >= 1 { 74 | fmt.Printf("%d: written %d frames in 1s\n", n, numFrames) 75 | t1 = time.Now() 76 | numFrames = 0 77 | } 78 | select { 79 | case <-ctx.Done(): 80 | return 81 | default: 82 | limiter.Wait() 83 | } 84 | // Grab an image.RGBA from the current output presenter 85 | err = ddup.GetImage(imgBuf, 0) 86 | if err != nil && !errors.Is(err, outputduplication.ErrNoImageYet) { 87 | fmt.Printf("Err ddup.GetImage: %v\n", err) 88 | return 89 | } 90 | 91 | numFrames++ 92 | 93 | n, err := transcoder.Write(imgBuf.Pix) 94 | if err != nil || n != len(imgBuf.Pix) { 95 | fmt.Printf("Failed to write image: %v\n", err) 96 | return 97 | } 98 | } 99 | } 100 | 101 | type videotranscoder struct { 102 | cmd *exec.Cmd 103 | 104 | in io.WriteCloser 105 | } 106 | 107 | func newVideotranscoder(filePath string, width, height int, framerate float32) *videotranscoder { 108 | cmd := exec.Command("ffmpeg", 109 | "-y", 110 | "-vsync", "0", 111 | "-f", "rawvideo", 112 | "-video_size", fmt.Sprintf("%dx%d", width, height), 113 | "-pixel_format", "rgba", 114 | "-framerate", fmt.Sprintf("%f", framerate), 115 | "-i", "-", 116 | // "-vf", "scale=-1:1080", 117 | "-c:v", "libx264", "-preset", "ultrafast", 118 | "-crf", "26", 119 | "-tune", "zerolatency", 120 | filePath, 121 | ) 122 | 123 | wc, err := cmd.StdinPipe() 124 | if err != nil { 125 | panic(err) 126 | } 127 | if err := cmd.Start(); err != nil { 128 | panic(err) 129 | } 130 | return &videotranscoder{ 131 | cmd: cmd, 132 | in: wc, 133 | } 134 | } 135 | func (v *videotranscoder) Write(buf []byte) (int, error) { 136 | return v.in.Write(buf) 137 | } 138 | func (v *videotranscoder) Close() error { 139 | // v.out.Close() 140 | v.in.Close() 141 | return nil 142 | } 143 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/kirides/go-d3d 2 | 3 | go 1.24 4 | 5 | require ( 6 | github.com/kbinani/screenshot v0.0.0-20250118074034-a3924b7bbc8c 7 | github.com/mattn/go-mjpeg v0.0.3 8 | github.com/viam-labs/go-libjpeg v0.3.1 9 | golang.org/x/sys v0.33.0 10 | ) 11 | 12 | require ( 13 | github.com/gen2brain/shm v0.1.1 // indirect 14 | github.com/godbus/dbus/v5 v5.1.0 // indirect 15 | github.com/jezek/xgb v1.1.1 // indirect 16 | github.com/lxn/win v0.0.0-20210218163916-a377121e959e // indirect 17 | ) 18 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/gen2brain/shm v0.1.1 h1:1cTVA5qcsUFixnDHl14TmRoxgfWEEZlTezpUj1vm5uQ= 2 | github.com/gen2brain/shm v0.1.1/go.mod h1:UgIcVtvmOu+aCJpqJX7GOtiN7X2ct+TKLg4RTxwPIUA= 3 | github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= 4 | github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= 5 | github.com/jezek/xgb v1.1.1 h1:bE/r8ZZtSv7l9gk6nU0mYx51aXrvnyb44892TwSaqS4= 6 | github.com/jezek/xgb v1.1.1/go.mod h1:nrhwO0FX/enq75I7Y7G8iN1ubpSGZEiA3v9e9GyRFlk= 7 | github.com/kbinani/screenshot v0.0.0-20250118074034-a3924b7bbc8c h1:1IlzDla/ZATV/FsRn1ETf7ir91PHS2mrd4VMunEtd9k= 8 | github.com/kbinani/screenshot v0.0.0-20250118074034-a3924b7bbc8c/go.mod h1:Pmpz2BLf55auQZ67u3rvyI2vAQvNetkK/4zYUmpauZQ= 9 | github.com/lxn/win v0.0.0-20210218163916-a377121e959e h1:H+t6A/QJMbhCSEH5rAuRxh+CtW96g0Or0Fxa9IKr4uc= 10 | github.com/lxn/win v0.0.0-20210218163916-a377121e959e/go.mod h1:KxxjdtRkfNoYDCUP5ryK7XJJNTnpC8atvtmTheChOtk= 11 | github.com/mattn/go-mjpeg v0.0.3 h1:0G/+KddrbI5Hnq83B11O1O4vP7Q6L9MsBu6aW71jhUM= 12 | github.com/mattn/go-mjpeg v0.0.3/go.mod h1:65z7Cj+u5y5K3B8Sy5NtrJFTWAhguGHs9FEkADdx6kE= 13 | github.com/viam-labs/go-libjpeg v0.3.1 h1:J/byavXHFqRI1PFPrnPbP+wFCr1y+Cn1CwKXrORCPD0= 14 | github.com/viam-labs/go-libjpeg v0.3.1/go.mod h1:b0ISpf9lJv9MO1h1gXAmSA/osG19cKGYjfYc6aeEjqs= 15 | golang.org/x/sys v0.0.0-20201018230417-eeed37f84f13/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 16 | golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= 17 | golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= 18 | -------------------------------------------------------------------------------- /hresult.go: -------------------------------------------------------------------------------- 1 | package d3d 2 | 3 | /* 4 | Contains common D3D11 / DXGI errorcodes and allows to use them as Go errors 5 | */ 6 | 7 | //go:generate stringer -type=HRESULT -output=hresult_string.go 8 | 9 | import ( 10 | "strconv" 11 | "strings" 12 | ) 13 | 14 | type HRESULT uint32 15 | 16 | func (hr HRESULT) Failed() bool { 17 | return int32(hr) < 0 18 | } 19 | 20 | const ( 21 | S_OK HRESULT = 0x0 22 | E_INVALIDARG HRESULT = 0x80070057 23 | DXGI_STATUS_OCCLUDED HRESULT = 0x087A0001 24 | DXGI_STATUS_CLIPPED HRESULT = 0x087A0002 25 | DXGI_STATUS_NO_REDIRECTION HRESULT = 0x087A0004 26 | DXGI_STATUS_NO_DESKTOP_ACCESS HRESULT = 0x087A0005 27 | DXGI_STATUS_GRAPHICS_VIDPN_SOURCE_IN_USE HRESULT = 0x087A0006 28 | DXGI_STATUS_MODE_CHANGED HRESULT = 0x087A0007 29 | DXGI_STATUS_MODE_CHANGE_IN_PROGRESS HRESULT = 0x087A0008 30 | DXGI_ERROR_INVALID_CALL HRESULT = 0x887A0001 31 | DXGI_ERROR_NOT_FOUND HRESULT = 0x887A0002 32 | DXGI_ERROR_MORE_DATA HRESULT = 0x887A0003 33 | DXGI_ERROR_UNSUPPORTED HRESULT = 0x887A0004 34 | DXGI_ERROR_DEVICE_REMOVED HRESULT = 0x887A0005 35 | DXGI_ERROR_DEVICE_HUNG HRESULT = 0x887A0006 36 | DXGI_ERROR_DEVICE_RESET HRESULT = 0x887A0007 37 | DXGI_ERROR_WAS_STILL_DRAWING HRESULT = 0x887A000A 38 | DXGI_ERROR_FRAME_STATISTICS_DISJOINT HRESULT = 0x887A000B 39 | DXGI_ERROR_GRAPHICS_VIDPN_SOURCE_IN_USE HRESULT = 0x887A000C 40 | DXGI_ERROR_DRIVER_INTERNAL_ERROR HRESULT = 0x887A0020 41 | DXGI_ERROR_NONEXCLUSIVE HRESULT = 0x887A0021 42 | DXGI_ERROR_NOT_CURRENTLY_AVAILABLE HRESULT = 0x887A0022 43 | DXGI_ERROR_REMOTE_CLIENT_DISCONNECTED HRESULT = 0x887A0023 44 | DXGI_ERROR_REMOTE_OUTOFMEMORY HRESULT = 0x887A0024 45 | DXGI_ERROR_ACCESS_LOST HRESULT = 0x887A0026 46 | DXGI_ERROR_WAIT_TIMEOUT HRESULT = 0x887A0027 47 | DXGI_ERROR_SESSION_DISCONNECTED HRESULT = 0x887A0028 48 | DXGI_ERROR_RESTRICT_TO_OUTPUT_STALE HRESULT = 0x887A0029 49 | DXGI_ERROR_CANNOT_PROTECT_CONTENT HRESULT = 0x887A002A 50 | DXGI_ERROR_ACCESS_DENIED HRESULT = 0x887A002B 51 | DXGI_ERROR_NAME_ALREADY_EXISTS HRESULT = 0x887A002C 52 | DXGI_ERROR_SDK_COMPONENT_MISSING HRESULT = 0x887A002D 53 | DXGI_ERROR_NOT_CURRENT HRESULT = 0x887A002E 54 | DXGI_ERROR_HW_PROTECTION_OUTOFMEMORY HRESULT = 0x887A0030 55 | DXGI_ERROR_DYNAMIC_CODE_POLICY_VIOLATION HRESULT = 0x887A0031 56 | DXGI_ERROR_NON_COMPOSITED_UI HRESULT = 0x887A0032 57 | DXGI_STATUS_UNOCCLUDED HRESULT = 0x087A0009 58 | DXGI_STATUS_DDA_WAS_STILL_DRAWING HRESULT = 0x087A000A 59 | DXGI_ERROR_MODE_CHANGE_IN_PROGRESS HRESULT = 0x887A0025 60 | DXGI_STATUS_PRESENT_REQUIRED HRESULT = 0x087A002F 61 | DXGI_ERROR_CACHE_CORRUPT HRESULT = 0x887A0033 62 | DXGI_ERROR_CACHE_FULL HRESULT = 0x887A0034 63 | DXGI_ERROR_CACHE_HASH_COLLISION HRESULT = 0x887A0035 64 | DXGI_ERROR_ALREADY_EXISTS HRESULT = 0x887A0036 65 | DXGI_DDI_ERR_WASSTILLDRAWING HRESULT = 0x887B0001 66 | DXGI_DDI_ERR_UNSUPPORTED HRESULT = 0x887B0002 67 | DXGI_DDI_ERR_NONEXCLUSIVE HRESULT = 0x887B0003 68 | ) 69 | 70 | func (e HRESULT) Error() string { 71 | str := e.String() 72 | 73 | if strings.HasSuffix(str, ")") { 74 | // Workaround: return just the hex code as error if no name found 75 | return "0x" + strconv.FormatUint(uint64(e), 16) 76 | } 77 | // return name and hex value 78 | return str + " (0x" + strconv.FormatUint(uint64(e), 16) + ")" 79 | } 80 | -------------------------------------------------------------------------------- /hresult_string.go: -------------------------------------------------------------------------------- 1 | // Code generated by "stringer -type=HRESULT -output=hresult_string.go"; DO NOT EDIT. 2 | 3 | package d3d 4 | 5 | import "strconv" 6 | 7 | func _() { 8 | // An "invalid array index" compiler error signifies that the constant values have changed. 9 | // Re-run the stringer command to generate them again. 10 | var x [1]struct{} 11 | _ = x[S_OK-0] 12 | _ = x[E_INVALIDARG-2147942487] 13 | _ = x[DXGI_STATUS_OCCLUDED-142213121] 14 | _ = x[DXGI_STATUS_CLIPPED-142213122] 15 | _ = x[DXGI_STATUS_NO_REDIRECTION-142213124] 16 | _ = x[DXGI_STATUS_NO_DESKTOP_ACCESS-142213125] 17 | _ = x[DXGI_STATUS_GRAPHICS_VIDPN_SOURCE_IN_USE-142213126] 18 | _ = x[DXGI_STATUS_MODE_CHANGED-142213127] 19 | _ = x[DXGI_STATUS_MODE_CHANGE_IN_PROGRESS-142213128] 20 | _ = x[DXGI_ERROR_INVALID_CALL-2289696769] 21 | _ = x[DXGI_ERROR_NOT_FOUND-2289696770] 22 | _ = x[DXGI_ERROR_MORE_DATA-2289696771] 23 | _ = x[DXGI_ERROR_UNSUPPORTED-2289696772] 24 | _ = x[DXGI_ERROR_DEVICE_REMOVED-2289696773] 25 | _ = x[DXGI_ERROR_DEVICE_HUNG-2289696774] 26 | _ = x[DXGI_ERROR_DEVICE_RESET-2289696775] 27 | _ = x[DXGI_ERROR_WAS_STILL_DRAWING-2289696778] 28 | _ = x[DXGI_ERROR_FRAME_STATISTICS_DISJOINT-2289696779] 29 | _ = x[DXGI_ERROR_GRAPHICS_VIDPN_SOURCE_IN_USE-2289696780] 30 | _ = x[DXGI_ERROR_DRIVER_INTERNAL_ERROR-2289696800] 31 | _ = x[DXGI_ERROR_NONEXCLUSIVE-2289696801] 32 | _ = x[DXGI_ERROR_NOT_CURRENTLY_AVAILABLE-2289696802] 33 | _ = x[DXGI_ERROR_REMOTE_CLIENT_DISCONNECTED-2289696803] 34 | _ = x[DXGI_ERROR_REMOTE_OUTOFMEMORY-2289696804] 35 | _ = x[DXGI_ERROR_ACCESS_LOST-2289696806] 36 | _ = x[DXGI_ERROR_WAIT_TIMEOUT-2289696807] 37 | _ = x[DXGI_ERROR_SESSION_DISCONNECTED-2289696808] 38 | _ = x[DXGI_ERROR_RESTRICT_TO_OUTPUT_STALE-2289696809] 39 | _ = x[DXGI_ERROR_CANNOT_PROTECT_CONTENT-2289696810] 40 | _ = x[DXGI_ERROR_ACCESS_DENIED-2289696811] 41 | _ = x[DXGI_ERROR_NAME_ALREADY_EXISTS-2289696812] 42 | _ = x[DXGI_ERROR_SDK_COMPONENT_MISSING-2289696813] 43 | _ = x[DXGI_ERROR_NOT_CURRENT-2289696814] 44 | _ = x[DXGI_ERROR_HW_PROTECTION_OUTOFMEMORY-2289696816] 45 | _ = x[DXGI_ERROR_DYNAMIC_CODE_POLICY_VIOLATION-2289696817] 46 | _ = x[DXGI_ERROR_NON_COMPOSITED_UI-2289696818] 47 | _ = x[DXGI_STATUS_UNOCCLUDED-142213129] 48 | _ = x[DXGI_STATUS_DDA_WAS_STILL_DRAWING-142213130] 49 | _ = x[DXGI_ERROR_MODE_CHANGE_IN_PROGRESS-2289696805] 50 | _ = x[DXGI_STATUS_PRESENT_REQUIRED-142213167] 51 | _ = x[DXGI_ERROR_CACHE_CORRUPT-2289696819] 52 | _ = x[DXGI_ERROR_CACHE_FULL-2289696820] 53 | _ = x[DXGI_ERROR_CACHE_HASH_COLLISION-2289696821] 54 | _ = x[DXGI_ERROR_ALREADY_EXISTS-2289696822] 55 | _ = x[DXGI_DDI_ERR_WASSTILLDRAWING-2289762305] 56 | _ = x[DXGI_DDI_ERR_UNSUPPORTED-2289762306] 57 | _ = x[DXGI_DDI_ERR_NONEXCLUSIVE-2289762307] 58 | } 59 | 60 | const ( 61 | _HRESULT_name_0 = "S_OK" 62 | _HRESULT_name_1 = "DXGI_STATUS_OCCLUDEDDXGI_STATUS_CLIPPED" 63 | _HRESULT_name_2 = "DXGI_STATUS_NO_REDIRECTIONDXGI_STATUS_NO_DESKTOP_ACCESSDXGI_STATUS_GRAPHICS_VIDPN_SOURCE_IN_USEDXGI_STATUS_MODE_CHANGEDDXGI_STATUS_MODE_CHANGE_IN_PROGRESSDXGI_STATUS_UNOCCLUDEDDXGI_STATUS_DDA_WAS_STILL_DRAWING" 64 | _HRESULT_name_3 = "DXGI_STATUS_PRESENT_REQUIRED" 65 | _HRESULT_name_4 = "E_INVALIDARG" 66 | _HRESULT_name_5 = "DXGI_ERROR_INVALID_CALLDXGI_ERROR_NOT_FOUNDDXGI_ERROR_MORE_DATADXGI_ERROR_UNSUPPORTEDDXGI_ERROR_DEVICE_REMOVEDDXGI_ERROR_DEVICE_HUNGDXGI_ERROR_DEVICE_RESET" 67 | _HRESULT_name_6 = "DXGI_ERROR_WAS_STILL_DRAWINGDXGI_ERROR_FRAME_STATISTICS_DISJOINTDXGI_ERROR_GRAPHICS_VIDPN_SOURCE_IN_USE" 68 | _HRESULT_name_7 = "DXGI_ERROR_DRIVER_INTERNAL_ERRORDXGI_ERROR_NONEXCLUSIVEDXGI_ERROR_NOT_CURRENTLY_AVAILABLEDXGI_ERROR_REMOTE_CLIENT_DISCONNECTEDDXGI_ERROR_REMOTE_OUTOFMEMORYDXGI_ERROR_MODE_CHANGE_IN_PROGRESSDXGI_ERROR_ACCESS_LOSTDXGI_ERROR_WAIT_TIMEOUTDXGI_ERROR_SESSION_DISCONNECTEDDXGI_ERROR_RESTRICT_TO_OUTPUT_STALEDXGI_ERROR_CANNOT_PROTECT_CONTENTDXGI_ERROR_ACCESS_DENIEDDXGI_ERROR_NAME_ALREADY_EXISTSDXGI_ERROR_SDK_COMPONENT_MISSINGDXGI_ERROR_NOT_CURRENT" 69 | _HRESULT_name_8 = "DXGI_ERROR_HW_PROTECTION_OUTOFMEMORYDXGI_ERROR_DYNAMIC_CODE_POLICY_VIOLATIONDXGI_ERROR_NON_COMPOSITED_UIDXGI_ERROR_CACHE_CORRUPTDXGI_ERROR_CACHE_FULLDXGI_ERROR_CACHE_HASH_COLLISIONDXGI_ERROR_ALREADY_EXISTS" 70 | _HRESULT_name_9 = "DXGI_DDI_ERR_WASSTILLDRAWINGDXGI_DDI_ERR_UNSUPPORTEDDXGI_DDI_ERR_NONEXCLUSIVE" 71 | ) 72 | 73 | var ( 74 | _HRESULT_index_1 = [...]uint8{0, 20, 39} 75 | _HRESULT_index_2 = [...]uint8{0, 26, 55, 95, 119, 154, 176, 209} 76 | _HRESULT_index_5 = [...]uint8{0, 23, 43, 63, 85, 110, 132, 155} 77 | _HRESULT_index_6 = [...]uint8{0, 28, 64, 103} 78 | _HRESULT_index_7 = [...]uint16{0, 32, 55, 89, 126, 155, 189, 211, 234, 265, 300, 333, 357, 387, 419, 441} 79 | _HRESULT_index_8 = [...]uint8{0, 36, 76, 104, 128, 149, 180, 205} 80 | _HRESULT_index_9 = [...]uint8{0, 28, 52, 77} 81 | ) 82 | 83 | func (i HRESULT) String() string { 84 | switch { 85 | case i == 0: 86 | return _HRESULT_name_0 87 | case 142213121 <= i && i <= 142213122: 88 | i -= 142213121 89 | return _HRESULT_name_1[_HRESULT_index_1[i]:_HRESULT_index_1[i+1]] 90 | case 142213124 <= i && i <= 142213130: 91 | i -= 142213124 92 | return _HRESULT_name_2[_HRESULT_index_2[i]:_HRESULT_index_2[i+1]] 93 | case i == 142213167: 94 | return _HRESULT_name_3 95 | case i == 2147942487: 96 | return _HRESULT_name_4 97 | case 2289696769 <= i && i <= 2289696775: 98 | i -= 2289696769 99 | return _HRESULT_name_5[_HRESULT_index_5[i]:_HRESULT_index_5[i+1]] 100 | case 2289696778 <= i && i <= 2289696780: 101 | i -= 2289696778 102 | return _HRESULT_name_6[_HRESULT_index_6[i]:_HRESULT_index_6[i+1]] 103 | case 2289696800 <= i && i <= 2289696814: 104 | i -= 2289696800 105 | return _HRESULT_name_7[_HRESULT_index_7[i]:_HRESULT_index_7[i+1]] 106 | case 2289696816 <= i && i <= 2289696822: 107 | i -= 2289696816 108 | return _HRESULT_name_8[_HRESULT_index_8[i]:_HRESULT_index_8[i+1]] 109 | case 2289762305 <= i && i <= 2289762307: 110 | i -= 2289762305 111 | return _HRESULT_name_9[_HRESULT_index_9[i]:_HRESULT_index_9[i+1]] 112 | default: 113 | return "HRESULT(" + strconv.FormatInt(int64(i), 10) + ")" 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /outputduplication/output_duplication.go: -------------------------------------------------------------------------------- 1 | package outputduplication 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "image" 7 | 8 | "unsafe" 9 | 10 | "github.com/kirides/go-d3d" 11 | "github.com/kirides/go-d3d/d3d11" 12 | "github.com/kirides/go-d3d/dxgi" 13 | "github.com/kirides/go-d3d/outputduplication/swizzle" 14 | ) 15 | 16 | type PointerInfo struct { 17 | pos dxgi.POINT 18 | 19 | size dxgi.POINT 20 | shapeInBuffer []byte 21 | shapeOutBuffer *image.RGBA 22 | visible bool 23 | } 24 | 25 | type OutputDuplicator struct { 26 | device *d3d11.ID3D11Device 27 | deviceCtx *d3d11.ID3D11DeviceContext 28 | outputDuplication *dxgi.IDXGIOutputDuplication 29 | dxgiOutput *dxgi.IDXGIOutput5 30 | 31 | stagedTex *d3d11.ID3D11Texture2D 32 | surface *dxgi.IDXGISurface 33 | mappedRect dxgi.DXGI_MAPPED_RECT 34 | size dxgi.POINT 35 | 36 | pointerInfo PointerInfo 37 | // Always draw pointer onto the final image when calling GetImage 38 | DrawPointer bool 39 | // Update pointer information when it changes, used with DrawCursor(image) 40 | UpdatePointerInfo bool 41 | 42 | // TODO: handle DPI? Do we need it? 43 | dirtyRects []dxgi.RECT 44 | movedRects []dxgi.DXGI_OUTDUPL_MOVE_RECT 45 | acquiredFrame bool 46 | needsSwizzle bool // in case we use DuplicateOutput1, swizzle is not neccessery 47 | } 48 | 49 | func (dup *OutputDuplicator) initializeStage(texture *d3d11.ID3D11Texture2D) int32 { 50 | 51 | /* 52 | TODO: Only do this on changes! 53 | */ 54 | var hr int32 55 | desc := d3d11.D3D11_TEXTURE2D_DESC{} 56 | hr = texture.GetDesc(&desc) 57 | if d3d.HRESULT(hr).Failed() { 58 | return hr 59 | } 60 | 61 | desc.Usage = d3d11.D3D11_USAGE_STAGING 62 | desc.CPUAccessFlags = d3d11.D3D11_CPU_ACCESS_READ 63 | desc.BindFlags = 0 64 | desc.MipLevels = 1 65 | desc.ArraySize = 1 66 | desc.MiscFlags = 0 67 | desc.SampleDesc.Count = 1 68 | 69 | hr = dup.device.CreateTexture2D(&desc, &dup.stagedTex) 70 | if d3d.HRESULT(hr).Failed() { 71 | return hr 72 | } 73 | 74 | hr = dup.stagedTex.QueryInterface(dxgi.IID_IDXGISurface, &dup.surface) 75 | if d3d.HRESULT(hr).Failed() { 76 | return hr 77 | } 78 | dup.size = dxgi.POINT{X: int32(desc.Width), Y: int32(desc.Height)} 79 | 80 | return 0 81 | } 82 | 83 | func (dup *OutputDuplicator) Release() { 84 | dup.ReleaseFrame() 85 | if dup.stagedTex != nil { 86 | dup.stagedTex.Release() 87 | dup.stagedTex = nil 88 | } 89 | if dup.surface != nil { 90 | dup.surface.Release() 91 | dup.surface = nil 92 | } 93 | if dup.outputDuplication != nil { 94 | dup.outputDuplication.Release() 95 | dup.outputDuplication = nil 96 | } 97 | if dup.dxgiOutput != nil { 98 | dup.dxgiOutput.Release() 99 | dup.dxgiOutput = nil 100 | } 101 | } 102 | 103 | var ErrNoImageYet = errors.New("no image yet") 104 | 105 | type unmapFn func() int32 106 | 107 | func (dup *OutputDuplicator) ReleaseFrame() { 108 | if dup.acquiredFrame { 109 | dup.outputDuplication.ReleaseFrame() 110 | dup.acquiredFrame = false 111 | } 112 | } 113 | 114 | // returns DXGI_FORMAT_B8G8R8A8_UNORM data 115 | func (dup *OutputDuplicator) Snapshot(timeoutMs uint) (unmapFn, *dxgi.DXGI_MAPPED_RECT, *dxgi.POINT, error) { 116 | var hr int32 117 | desc := dxgi.DXGI_OUTDUPL_DESC{} 118 | hr = dup.outputDuplication.GetDesc(&desc) 119 | if hr := d3d.HRESULT(hr); hr.Failed() { 120 | return nil, nil, nil, fmt.Errorf("failed to get the description. %w", hr) 121 | } 122 | 123 | if desc.DesktopImageInSystemMemory != 0 { 124 | // TODO: Figure out WHEN exactly this can occur, and if we can make use of it 125 | dup.size = dxgi.POINT{X: int32(desc.ModeDesc.Width), Y: int32(desc.ModeDesc.Height)} 126 | hr = dup.outputDuplication.MapDesktopSurface(&dup.mappedRect) 127 | if hr := d3d.HRESULT(hr); !hr.Failed() { 128 | return dup.outputDuplication.UnMapDesktopSurface, &dup.mappedRect, &dup.size, nil 129 | } 130 | } 131 | 132 | var desktop *dxgi.IDXGIResource 133 | var frameInfo dxgi.DXGI_OUTDUPL_FRAME_INFO 134 | 135 | // Release a possible previous frame 136 | // TODO: Properly use ReleaseFrame... 137 | 138 | dup.ReleaseFrame() 139 | hrF := dup.outputDuplication.AcquireNextFrame(uint32(timeoutMs), &frameInfo, &desktop) 140 | dup.acquiredFrame = true 141 | if hr := d3d.HRESULT(hrF); hr.Failed() { 142 | if hr == d3d.DXGI_ERROR_WAIT_TIMEOUT { 143 | return nil, nil, nil, ErrNoImageYet 144 | } 145 | return nil, nil, nil, fmt.Errorf("failed to AcquireNextFrame. %w", d3d.HRESULT(hrF)) 146 | } 147 | // If we do not release the frame ASAP, we only get FPS / 2 frames :/ 148 | // Something wrong here? 149 | // defer dup.ReleaseFrame() 150 | defer desktop.Release() 151 | 152 | if dup.UpdatePointerInfo { 153 | if err := dup.updatePointer(&frameInfo); err != nil { 154 | return nil, nil, nil, err 155 | } 156 | } 157 | 158 | if frameInfo.AccumulatedFrames == 0 { 159 | return nil, nil, nil, ErrNoImageYet 160 | } 161 | var desktop2d *d3d11.ID3D11Texture2D 162 | hr = desktop.QueryInterface(d3d11.IID_ID3D11Texture2D, &desktop2d) 163 | if hr := d3d.HRESULT(hr); hr.Failed() { 164 | return nil, nil, nil, fmt.Errorf("failed to QueryInterface(iid_ID3D11Texture2D, ...). %w", hr) 165 | } 166 | defer desktop2d.Release() 167 | 168 | if dup.stagedTex == nil { 169 | hr = dup.initializeStage(desktop2d) 170 | if hr := d3d.HRESULT(hr); hr.Failed() { 171 | return nil, nil, nil, fmt.Errorf("failed to InitializeStage. %w", hr) 172 | } 173 | } 174 | 175 | // NOTE: we could use a single, large []byte buffer and use it as storage for moved rects & dirty rects 176 | if frameInfo.TotalMetadataBufferSize > 0 { 177 | // Handling moved / dirty rects, to reduce GPU<->CPU memory copying 178 | moveRectsRequired := uint32(1) 179 | for { 180 | if len(dup.movedRects) < int(moveRectsRequired) { 181 | dup.movedRects = make([]dxgi.DXGI_OUTDUPL_MOVE_RECT, moveRectsRequired) 182 | } 183 | hr = dup.outputDuplication.GetFrameMoveRects(dup.movedRects, &moveRectsRequired) 184 | if hr := d3d.HRESULT(hr); hr.Failed() { 185 | if hr == d3d.DXGI_ERROR_MORE_DATA { 186 | continue 187 | } 188 | return nil, nil, nil, fmt.Errorf("failed to GetFrameMoveRects. %w", d3d.HRESULT(hr)) 189 | } 190 | dup.movedRects = dup.movedRects[:moveRectsRequired] 191 | break 192 | } 193 | 194 | dirtyRectsRequired := uint32(1) 195 | for { 196 | if len(dup.dirtyRects) < int(dirtyRectsRequired) { 197 | dup.dirtyRects = make([]dxgi.RECT, dirtyRectsRequired) 198 | } 199 | hr = dup.outputDuplication.GetFrameDirtyRects(dup.dirtyRects, &dirtyRectsRequired) 200 | if hr := d3d.HRESULT(hr); hr.Failed() { 201 | if hr == d3d.DXGI_ERROR_MORE_DATA { 202 | continue 203 | } 204 | return nil, nil, nil, fmt.Errorf("failed to GetFrameDirtyRects. %w", d3d.HRESULT(hr)) 205 | } 206 | dup.dirtyRects = dup.dirtyRects[:dirtyRectsRequired] 207 | break 208 | } 209 | 210 | box := d3d11.D3D11_BOX{ 211 | Front: 0, 212 | Back: 1, 213 | } 214 | if len(dup.movedRects) == 0 { 215 | for i := 0; i < len(dup.dirtyRects); i++ { 216 | box.Left = uint32(dup.dirtyRects[i].Left) 217 | box.Top = uint32(dup.dirtyRects[i].Top) 218 | box.Right = uint32(dup.dirtyRects[i].Right) 219 | box.Bottom = uint32(dup.dirtyRects[i].Bottom) 220 | 221 | dup.deviceCtx.CopySubresourceRegion2D(dup.stagedTex, 0, box.Left, box.Top, 0, desktop2d, 0, &box) 222 | } 223 | } else { 224 | // TODO: handle moved rects, then dirty rects 225 | // for now, just update the whole image instead 226 | dup.deviceCtx.CopyResource2D(dup.stagedTex, desktop2d) 227 | } 228 | } else { 229 | // no frame metadata, copy whole image 230 | dup.deviceCtx.CopyResource2D(dup.stagedTex, desktop2d) 231 | if !dup.needsSwizzle { 232 | dup.needsSwizzle = true 233 | } 234 | print("no frame metadata\n") 235 | } 236 | 237 | hr = dup.surface.Map(&dup.mappedRect, dxgi.DXGI_MAP_READ) 238 | if hr := d3d.HRESULT(hr); hr.Failed() { 239 | return nil, nil, nil, fmt.Errorf("failed to surface_.Map(...). %v", hr) 240 | } 241 | return dup.surface.Unmap, &dup.mappedRect, &dup.size, nil 242 | } 243 | 244 | func (dup *OutputDuplicator) DrawCursor(img *image.RGBA) error { 245 | return dup.drawPointer(img) 246 | } 247 | 248 | func (dup *OutputDuplicator) GetImage(img *image.RGBA, timeoutMs uint) error { 249 | unmap, mappedRect, size, err := dup.Snapshot(timeoutMs) 250 | if err != nil { 251 | return err 252 | } 253 | defer unmap() 254 | 255 | // docs are unclear, but pitch is the total width of each row 256 | dataSize := int(mappedRect.Pitch) * int(size.Y) 257 | data := unsafe.Slice((*byte)(unsafe.Pointer(mappedRect.PBits)), dataSize) 258 | 259 | contentWidth := int(size.X) * 4 260 | dataWidth := int(mappedRect.Pitch) 261 | 262 | var imgStart, dataStart, dataEnd int 263 | // copy source bytes into image.RGBA.Pix, skipping padding 264 | for i := 0; i < int(size.Y); i++ { 265 | dataEnd = dataStart + contentWidth 266 | copy(img.Pix[imgStart:], data[dataStart:dataEnd]) 267 | imgStart += contentWidth 268 | dataStart += dataWidth 269 | } 270 | 271 | if dup.needsSwizzle { 272 | swizzle.BGRA(img.Pix) 273 | } 274 | 275 | if dup.DrawPointer { 276 | dup.drawPointer(img) 277 | } 278 | 279 | return nil 280 | } 281 | 282 | func (dup *OutputDuplicator) updatePointer(info *dxgi.DXGI_OUTDUPL_FRAME_INFO) error { 283 | if info.LastMouseUpdateTime == 0 { 284 | return nil 285 | } 286 | dup.pointerInfo.visible = info.PointerPosition.Visible != 0 287 | dup.pointerInfo.pos = info.PointerPosition.Position 288 | 289 | if info.PointerShapeBufferSize != 0 { 290 | // new shape 291 | if len(dup.pointerInfo.shapeInBuffer) < int(info.PointerShapeBufferSize) { 292 | dup.pointerInfo.shapeInBuffer = make([]byte, info.PointerShapeBufferSize) 293 | } 294 | var requiredSize uint32 295 | var pointerInfo dxgi.DXGI_OUTDUPL_POINTER_SHAPE_INFO 296 | 297 | hr := dup.outputDuplication.GetFramePointerShape(info.PointerShapeBufferSize, 298 | dup.pointerInfo.shapeInBuffer, 299 | &requiredSize, 300 | &pointerInfo, 301 | ) 302 | if hr != 0 { 303 | return fmt.Errorf("unable to obtain frame pointer shape") 304 | } 305 | neededSize := int(pointerInfo.Width) * int(pointerInfo.Height/2) * 4 306 | dup.pointerInfo.shapeOutBuffer = image.NewRGBA(image.Rect(0, 0, int(pointerInfo.Width), int(pointerInfo.Height))) 307 | if len(dup.pointerInfo.shapeOutBuffer.Pix) < int(neededSize) { 308 | dup.pointerInfo.shapeOutBuffer.Pix = make([]byte, neededSize) 309 | } 310 | 311 | switch pointerInfo.Type { 312 | case dxgi.DXGI_OUTDUPL_POINTER_SHAPE_TYPE_MONOCHROME: 313 | width := int(pointerInfo.Width) 314 | height := int(pointerInfo.Height) / 2 // Corrected height! 315 | 316 | dup.pointerInfo.size = dxgi.POINT{X: int32(width), Y: int32(height)} 317 | 318 | xor_offset := pointerInfo.Pitch * uint32(height) 319 | andMap := dup.pointerInfo.shapeInBuffer 320 | xorMap := dup.pointerInfo.shapeInBuffer[xor_offset:] 321 | out_pixels := dup.pointerInfo.shapeOutBuffer.Pix 322 | widthBytes := (width + 7) / 8 323 | 324 | for j := 0; j < height; j++ { 325 | for i := 0; i < width; i++ { 326 | byteIndex := j*widthBytes + i/8 327 | bitMask := byte(0x80 >> (i % 8)) 328 | 329 | andBit := (andMap[byteIndex] & bitMask) != 0 330 | xorBit := (xorMap[byteIndex] & bitMask) != 0 331 | 332 | outIndex := (j*width + i) * 4 333 | // var r, g, b, a byte 334 | 335 | switch { 336 | case !andBit && !xorBit: // Transparent 337 | // r, g, b, a = 0, 0, 0, 0 338 | *(*uint32)(unsafe.Pointer(&out_pixels[outIndex])) = 0x00000000 339 | case !andBit && xorBit: // Inverted (white) 340 | // r, g, b, a = 255, 255, 255, 255 341 | *(*uint32)(unsafe.Pointer(&out_pixels[outIndex])) = 0xFFFFFFFF 342 | case andBit && !xorBit: // Black 343 | // r, g, b, a = 0, 0, 0, 255 // causes a black plane to be rendered alongside the cursors image 344 | // out_pixels[outIndex+0] = 0 // r 345 | // out_pixels[outIndex+1] = 0 // g 346 | // out_pixels[outIndex+2] = 0 // b 347 | // out_pixels[outIndex+3] = 255 // a 348 | *(*uint32)(unsafe.Pointer(&out_pixels[outIndex])) = 0x00000000 349 | case andBit && xorBit: // Inverted (black) 350 | // r, g, b, a = 255, 255, 255, 255 351 | *(*uint32)(unsafe.Pointer(&out_pixels[outIndex])) = 0xFFFFFFFF 352 | } 353 | } 354 | } 355 | 356 | case dxgi.DXGI_OUTDUPL_POINTER_SHAPE_TYPE_COLOR: 357 | dup.pointerInfo.size = dxgi.POINT{X: int32(pointerInfo.Width), Y: int32(pointerInfo.Height)} 358 | 359 | out, in := dup.pointerInfo.shapeOutBuffer.Pix, dup.pointerInfo.shapeInBuffer 360 | for j := 0; j < int(pointerInfo.Height); j++ { 361 | tout := out[j*int(pointerInfo.Pitch):] 362 | tin := in[j*int(pointerInfo.Pitch):] 363 | copy(tout, tin[:pointerInfo.Pitch]) 364 | } 365 | case dxgi.DXGI_OUTDUPL_POINTER_SHAPE_TYPE_MASKED_COLOR: 366 | dup.pointerInfo.size = dxgi.POINT{X: int32(pointerInfo.Width), Y: int32(pointerInfo.Height)} 367 | 368 | // TODO: Properly add mask 369 | out, in := dup.pointerInfo.shapeOutBuffer.Pix, dup.pointerInfo.shapeInBuffer 370 | for j := 0; j < int(pointerInfo.Height); j++ { 371 | tout := out[j*int(pointerInfo.Pitch):] 372 | tin := in[j*int(pointerInfo.Pitch):] 373 | copy(tout, tin[:pointerInfo.Pitch]) 374 | } 375 | default: 376 | dup.pointerInfo.size = dxgi.POINT{X: 0, Y: 0} 377 | return fmt.Errorf("unsupported type %v", pointerInfo.Type) 378 | } 379 | } 380 | return nil 381 | } 382 | 383 | func (dup *OutputDuplicator) drawPointer(img *image.RGBA) error { 384 | for j := 0; j < int(dup.pointerInfo.size.Y); j++ { 385 | for i := 0; i < int(dup.pointerInfo.size.X); i++ { 386 | col := dup.pointerInfo.shapeOutBuffer.At(i, j) 387 | _, _, _, a := col.RGBA() 388 | if a == 0 { 389 | // just dont draw invisible pixel? 390 | // TODO: correctly apply mask 391 | continue 392 | } 393 | 394 | img.Set(int(dup.pointerInfo.pos.X)+i, int(dup.pointerInfo.pos.Y)+j, col) 395 | } 396 | } 397 | return nil 398 | } 399 | 400 | func (ddup *OutputDuplicator) GetBounds() (image.Rectangle, error) { 401 | desc := dxgi.DXGI_OUTPUT_DESC{} 402 | hr := ddup.dxgiOutput.GetDesc(&desc) 403 | if hr := d3d.HRESULT(hr); hr.Failed() { 404 | return image.Rectangle{}, fmt.Errorf("failed at dxgiOutput.GetDesc. %w", hr) 405 | } 406 | 407 | return image.Rect(int(desc.DesktopCoordinates.Left), int(desc.DesktopCoordinates.Top), int(desc.DesktopCoordinates.Right), int(desc.DesktopCoordinates.Bottom)), nil 408 | } 409 | 410 | // NewIDXGIOutputDuplication creates a new OutputDuplicator 411 | func NewIDXGIOutputDuplication(device *d3d11.ID3D11Device, deviceCtx *d3d11.ID3D11DeviceContext, output uint) (*OutputDuplicator, error) { 412 | // DEBUG 413 | 414 | var d3dDebug *d3d11.ID3D11Debug 415 | hr := device.QueryInterface(d3d11.IID_ID3D11Debug, &d3dDebug) 416 | if hr := d3d.HRESULT(hr); !hr.Failed() { 417 | defer d3dDebug.Release() 418 | 419 | var d3dInfoQueue *d3d11.ID3D11InfoQueue 420 | hr := d3dDebug.QueryInterface(d3d11.IID_ID3D11InfoQueue, &d3dInfoQueue) 421 | if hr := d3d.HRESULT(hr); hr.Failed() { 422 | return nil, fmt.Errorf("failed at device.QueryInterface. %w", hr) 423 | } 424 | defer d3dInfoQueue.Release() 425 | // defer d3dDebug.ReportLiveDeviceObjects(D3D11_RLDO_SUMMARY | D3D11_RLDO_DETAIL) 426 | } 427 | 428 | var dxgiDevice1 *dxgi.IDXGIDevice1 429 | hr = device.QueryInterface(dxgi.IID_IDXGIDevice1, &dxgiDevice1) 430 | if hr := d3d.HRESULT(hr); hr.Failed() { 431 | return nil, fmt.Errorf("failed at device.QueryInterface. %w", hr) 432 | } 433 | defer dxgiDevice1.Release() 434 | 435 | var pdxgiAdapter unsafe.Pointer 436 | hr = dxgiDevice1.GetParent(dxgi.IID_IDXGIAdapter1, &pdxgiAdapter) 437 | if hr := d3d.HRESULT(hr); hr.Failed() { 438 | return nil, fmt.Errorf("failed at dxgiDevice1.GetAdapter. %w", hr) 439 | } 440 | dxgiAdapter := (*dxgi.IDXGIAdapter1)(pdxgiAdapter) 441 | defer dxgiAdapter.Release() 442 | 443 | var dxgiOutput *dxgi.IDXGIOutput 444 | // const DXGI_ERROR_NOT_FOUND = 0x887A0002 445 | hr = int32(dxgiAdapter.EnumOutputs(uint32(output), &dxgiOutput)) 446 | if hr := d3d.HRESULT(hr); hr.Failed() { 447 | return nil, fmt.Errorf("failed at dxgiAdapter.EnumOutputs. %w", hr) 448 | } 449 | defer dxgiOutput.Release() 450 | 451 | var dxgiOutput5 *dxgi.IDXGIOutput5 452 | hr = dxgiOutput.QueryInterface(dxgi.IID_IDXGIOutput5, &dxgiOutput5) 453 | if hr := d3d.HRESULT(hr); hr.Failed() { 454 | return nil, fmt.Errorf("failed at dxgiOutput.QueryInterface. %w", hr) 455 | } 456 | 457 | var dup *dxgi.IDXGIOutputDuplication 458 | hr = dxgiOutput5.DuplicateOutput1(dxgiDevice1, 0, []dxgi.DXGI_FORMAT{ 459 | dxgi.DXGI_FORMAT_R8G8B8A8_UNORM, 460 | // using the former, we don't have to swizzle ourselves 461 | // DXGI_FORMAT_B8G8R8A8_UNORM, 462 | }, &dup) 463 | needsSwizzle := false 464 | if hr := d3d.HRESULT(hr); hr.Failed() { 465 | needsSwizzle = true 466 | // fancy stuff not supported :/ 467 | // fmt.Printf("Info: failed to use dxgiOutput5.DuplicateOutput1, falling back to dxgiOutput1.DuplicateOutput. Missing manifest with DPI awareness set to \"PerMonitorV2\"? %v\n", _DXGI_ERROR(hr)) 468 | var dxgiOutput1 *dxgi.IDXGIOutput1 469 | hr := dxgiOutput.QueryInterface(dxgi.IID_IDXGIOutput1, &dxgiOutput1) 470 | if hr := d3d.HRESULT(hr); hr.Failed() { 471 | dxgiOutput5.Release() 472 | return nil, fmt.Errorf("failed at dxgiOutput.QueryInterface. %w", hr) 473 | } 474 | defer dxgiOutput1.Release() 475 | hr = dxgiOutput1.DuplicateOutput(dxgiDevice1, &dup) 476 | if hr := d3d.HRESULT(hr); hr.Failed() { 477 | dxgiOutput5.Release() 478 | return nil, fmt.Errorf("failed at dxgiOutput1.DuplicateOutput. %w", hr) 479 | } 480 | } 481 | 482 | return &OutputDuplicator{device: device, deviceCtx: deviceCtx, outputDuplication: dup, needsSwizzle: needsSwizzle, dxgiOutput: dxgiOutput5}, nil 483 | } 484 | -------------------------------------------------------------------------------- /outputduplication/swizzle/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2009 The Go Authors. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above 10 | copyright notice, this list of conditions and the following disclaimer 11 | in the documentation and/or other materials provided with the 12 | distribution. 13 | * Neither the name of Google Inc. nor the names of its 14 | contributors may be used to endorse or promote products derived from 15 | this software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /outputduplication/swizzle/swizzle_amd64.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package swizzle 6 | 7 | // haveSSSE3 returns whether the CPU supports SSSE3 instructions (i.e. PSHUFB). 8 | // 9 | // Note that this is SSSE3, not SSE3. 10 | func haveSSSE3() bool 11 | 12 | var useBGRA16 = haveSSSE3() 13 | 14 | const useBGRA4 = true 15 | 16 | func bgra16(p []byte) 17 | func bgra4(p []byte) 18 | -------------------------------------------------------------------------------- /outputduplication/swizzle/swizzle_amd64.s: -------------------------------------------------------------------------------- 1 | // Copyright 2015 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | #include "textflag.h" 6 | 7 | // func haveSSSE3() bool 8 | TEXT ·haveSSSE3(SB),NOSPLIT,$0 9 | MOVQ $1, AX 10 | CPUID 11 | SHRQ $9, CX 12 | ANDQ $1, CX 13 | MOVB CX, ret+0(FP) 14 | RET 15 | 16 | // func bgra16(p []byte) 17 | TEXT ·bgra16(SB),NOSPLIT,$0-24 18 | MOVQ p+0(FP), SI 19 | MOVQ len+8(FP), DI 20 | 21 | // Sanity check that len is a multiple of 16. 22 | MOVQ DI, AX 23 | ANDQ $15, AX 24 | JNZ done 25 | 26 | // Make the shuffle control mask (16-byte register X0) look like this, 27 | // where the low order byte comes first: 28 | // 29 | // 02 01 00 03 06 05 04 07 0a 09 08 0b 0e 0d 0c 0f 30 | // 31 | // Load the bottom 8 bytes into X0, the top into X1, then interleave them 32 | // into X0. 33 | MOVQ $0x0704050603000102, AX 34 | MOVQ AX, X0 35 | MOVQ $0x0f0c0d0e0b08090a, AX 36 | MOVQ AX, X1 37 | PUNPCKLQDQ X1, X0 38 | 39 | ADDQ SI, DI 40 | loop: 41 | CMPQ SI, DI 42 | JEQ done 43 | 44 | MOVOU (SI), X1 45 | PSHUFB X0, X1 46 | MOVOU X1, (SI) 47 | 48 | ADDQ $16, SI 49 | JMP loop 50 | done: 51 | RET 52 | 53 | // func bgra4(p []byte) 54 | TEXT ·bgra4(SB),NOSPLIT,$0-24 55 | MOVQ p+0(FP), SI 56 | MOVQ len+8(FP), DI 57 | 58 | // Sanity check that len is a multiple of 4. 59 | MOVQ DI, AX 60 | ANDQ $3, AX 61 | JNZ done 62 | 63 | ADDQ SI, DI 64 | loop: 65 | CMPQ SI, DI 66 | JEQ done 67 | 68 | MOVB 0(SI), AX 69 | MOVB 2(SI), BX 70 | MOVB BX, 0(SI) 71 | MOVB AX, 2(SI) 72 | 73 | ADDQ $4, SI 74 | JMP loop 75 | done: 76 | RET 77 | -------------------------------------------------------------------------------- /outputduplication/swizzle/swizzle_common.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // Package swizzle provides functions for converting between RGBA pixel 6 | // formats. 7 | package swizzle // import "golang.org/x/exp/shiny/driver/internal/swizzle" 8 | 9 | // BGRA converts a pixel buffer between Go's RGBA and other systems' BGRA byte 10 | // orders. 11 | // 12 | // It panics if the input slice length is not a multiple of 4. 13 | func BGRA(p []byte) { 14 | if len(p)%4 != 0 { 15 | panic("input slice length is not a multiple of 4") 16 | } 17 | 18 | // Use asm code for 16- or 4-byte chunks, if supported. 19 | if useBGRA16 { 20 | n := len(p) &^ (16 - 1) 21 | bgra16(p[:n]) 22 | p = p[n:] 23 | } else if useBGRA4 { 24 | bgra4(p) 25 | return 26 | } 27 | 28 | for i := 0; i < len(p); i += 4 { 29 | p[i+0], p[i+2] = p[i+2], p[i+0] 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /outputduplication/swizzle/swizzle_other.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // +build !amd64 6 | 7 | package swizzle 8 | 9 | const ( 10 | useBGRA16 = false 11 | useBGRA4 = false 12 | ) 13 | 14 | func bgra16(p []byte) { panic("unreachable") } 15 | func bgra4(p []byte) { panic("unreachable") } 16 | -------------------------------------------------------------------------------- /outputduplication/swizzle/swizzle_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package swizzle 6 | 7 | import ( 8 | "bytes" 9 | "math/rand" 10 | "testing" 11 | ) 12 | 13 | func TestBGRAShortInput(t *testing.T) { 14 | const s = "012.456.89A.CDE.GHI.KLM.O" 15 | testCases := []string{ 16 | 0: "012.456.89A.CDE.GHI.KLM.O", 17 | 1: "210.456.89A.CDE.GHI.KLM.O", 18 | 2: "210.654.89A.CDE.GHI.KLM.O", 19 | 3: "210.654.A98.CDE.GHI.KLM.O", 20 | 4: "210.654.A98.EDC.GHI.KLM.O", 21 | 5: "210.654.A98.EDC.IHG.KLM.O", 22 | 6: "210.654.A98.EDC.IHG.MLK.O", 23 | } 24 | for i, want := range testCases { 25 | b := []byte(s) 26 | BGRA(b[:4*i]) 27 | got := string(b) 28 | if got != want { 29 | t.Errorf("i=%d: got %q, want %q", i, got, want) 30 | } 31 | changed := got != s 32 | wantChanged := i != 0 33 | if changed != wantChanged { 34 | t.Errorf("i=%d: changed=%t, want %t", i, changed, wantChanged) 35 | } 36 | } 37 | } 38 | 39 | func TestBGRARandomInput(t *testing.T) { 40 | r := rand.New(rand.NewSource(1)) 41 | fastBuf := make([]byte, 1024) 42 | slowBuf := make([]byte, 1024) 43 | for i := range fastBuf { 44 | fastBuf[i] = uint8(r.Intn(256)) 45 | } 46 | copy(slowBuf, fastBuf) 47 | 48 | for i := 0; i < 100000; i++ { 49 | o := r.Intn(len(fastBuf)) 50 | n := r.Intn(len(fastBuf)-o) &^ 0x03 51 | BGRA(fastBuf[o : o+n]) 52 | pureGoBGRA(slowBuf[o : o+n]) 53 | if bytes.Equal(fastBuf, slowBuf) { 54 | continue 55 | } 56 | for j := range fastBuf { 57 | x := fastBuf[j] 58 | y := slowBuf[j] 59 | if x != y { 60 | t.Fatalf("iter %d: swizzling [%d:%d+%d]: bytes differ at offset %d (aka %d+%d): %#02x vs %#02x", 61 | i, o, o, n, j, o, j-o, x, y) 62 | } 63 | } 64 | } 65 | } 66 | 67 | func pureGoBGRA(p []byte) { 68 | if len(p)%4 != 0 { 69 | return 70 | } 71 | for i := 0; i < len(p); i += 4 { 72 | p[i+0], p[i+2] = p[i+2], p[i+0] 73 | } 74 | } 75 | 76 | func benchmarkBGRA(b *testing.B, f func([]byte)) { 77 | const w, h = 1920, 1080 // 1080p RGBA. 78 | buf := make([]byte, 4*w*h) 79 | b.ResetTimer() 80 | for i := 0; i < b.N; i++ { 81 | f(buf) 82 | } 83 | } 84 | 85 | func BenchmarkBGRA(b *testing.B) { benchmarkBGRA(b, BGRA) } 86 | func BenchmarkPureGoBGRA(b *testing.B) { benchmarkBGRA(b, pureGoBGRA) } 87 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | This code allows to use D3D11 IDXGIOutputDuplication in Go 2 | ## Examples 3 | 4 | - Encoding an mjpeg stream [examples/mjpegstream](./examples/mjpegstream) 5 | - Recording an h264 video using ffmpeg for transcoding [examples/recording](./examples/recording) 6 | 7 | ## Libaries used 8 | 9 | - `golang.org/x/exp/shiny/driver/internal/swizzle` for faster BGRA -> RGBA conversion (see [shiny LICENSE](./outputduplication/swizzle/LICENSE)) 10 | 11 | ## app.manifest 12 | 13 | To make use of `IDXGIOutput5::DuplicateOutput1`, an application has to provide support for PerMonitorV2 DPI-Awareness (Windows 10 1703+) This is usually done by providing an `my-executable.exe.manifest` file either next to the executable, or as an embedded resource. 14 | 15 | In the examples there are calls to `IsValidDpiAwarenessContext` and `SetThreadDpiAwarenessContext` which circumvent the requirement. 16 | -------------------------------------------------------------------------------- /win/syscall_windows.go: -------------------------------------------------------------------------------- 1 | package win 2 | 3 | //go:generate mkwinsyscall -output zsyscall_windows.go syscall_windows.go 4 | 5 | type ( 6 | BOOL uint32 7 | BOOLEAN byte 8 | BYTE byte 9 | DWORD uint32 10 | DWORD64 uint64 11 | HANDLE uintptr 12 | HLOCAL uintptr 13 | LARGE_INTEGER int64 14 | LONG int32 15 | LPVOID uintptr 16 | SIZE_T uintptr 17 | UINT uint32 18 | ULONG_PTR uintptr 19 | ULONGLONG uint64 20 | WORD uint16 21 | 22 | HWND uintptr 23 | ) 24 | 25 | type BITMAPINFOHEADER struct { 26 | BiSize uint32 27 | BiWidth int32 28 | BiHeight int32 29 | BiPlanes uint16 30 | BiBitCount uint16 31 | BiCompression uint32 32 | BiSizeImage uint32 33 | BiXPelsPerMeter int32 34 | BiYPelsPerMeter int32 35 | BiClrUsed uint32 36 | BiClrImportant uint32 37 | } 38 | type RGBQUAD struct { 39 | RgbBlue byte 40 | RgbGreen byte 41 | RgbRed byte 42 | RgbReserved byte 43 | } 44 | 45 | type BITMAPINFO struct { 46 | BmiHeader BITMAPINFOHEADER 47 | BmiColors *RGBQUAD 48 | } 49 | 50 | const ( 51 | OBJ_BITMAP = 7 52 | ) 53 | 54 | const ( 55 | DpiAwarenessContextUndefined = 0 56 | DpiAwarenessContextUnaware = -1 57 | DpiAwarenessContextSystemAware = -2 58 | DpiAwarenessContextPerMonitorAware = -3 59 | DpiAwarenessContextPerMonitorAwareV2 = -4 60 | DpiAwarenessContextUnawareGdiScaled = -5 61 | ) 62 | 63 | //sys SetThreadDpiAwarenessContext(value int32) (n int, err error) = User32.SetThreadDpiAwarenessContext 64 | //sys IsValidDpiAwarenessContext(value int32) (n bool) = User32.IsValidDpiAwarenessContext 65 | -------------------------------------------------------------------------------- /win/zsyscall_windows.go: -------------------------------------------------------------------------------- 1 | // Code generated by 'go generate'; DO NOT EDIT. 2 | 3 | package win 4 | 5 | import ( 6 | "syscall" 7 | "unsafe" 8 | 9 | "golang.org/x/sys/windows" 10 | ) 11 | 12 | var _ unsafe.Pointer 13 | 14 | // Do the interface allocations only once for common 15 | // Errno values. 16 | const ( 17 | errnoERROR_IO_PENDING = 997 18 | ) 19 | 20 | var ( 21 | errERROR_IO_PENDING error = syscall.Errno(errnoERROR_IO_PENDING) 22 | errERROR_EINVAL error = syscall.EINVAL 23 | ) 24 | 25 | // errnoErr returns common boxed Errno values, to prevent 26 | // allocations at runtime. 27 | func errnoErr(e syscall.Errno) error { 28 | switch e { 29 | case 0: 30 | return errERROR_EINVAL 31 | case errnoERROR_IO_PENDING: 32 | return errERROR_IO_PENDING 33 | } 34 | // TODO: add more here, after collecting data on the common 35 | // error values see on Windows. (perhaps when running 36 | // all.bat?) 37 | return e 38 | } 39 | 40 | var ( 41 | modUser32 = windows.NewLazySystemDLL("User32.dll") 42 | 43 | procIsValidDpiAwarenessContext = modUser32.NewProc("IsValidDpiAwarenessContext") 44 | procSetThreadDpiAwarenessContext = modUser32.NewProc("SetThreadDpiAwarenessContext") 45 | ) 46 | 47 | func IsValidDpiAwarenessContext(value int32) (n bool) { 48 | r0, _, _ := syscall.Syscall(procIsValidDpiAwarenessContext.Addr(), 1, uintptr(value), 0, 0) 49 | n = r0 != 0 50 | return 51 | } 52 | 53 | func SetThreadDpiAwarenessContext(value int32) (n int, err error) { 54 | r0, _, e1 := syscall.Syscall(procSetThreadDpiAwarenessContext.Addr(), 1, uintptr(value), 0, 0) 55 | n = int(r0) 56 | if n == 0 { 57 | err = errnoErr(e1) 58 | } 59 | return 60 | } 61 | --------------------------------------------------------------------------------