| 1 | //! Submission through Direct3D 11 from feature level 10_0, or WARP where no device has it: |
| 2 | //! the Direct3D Windows 7 has. Frames draw into an sRGB texture, copied to a DXGI 1.1 swap |
| 3 | //! chain that blits into the window's redirection surface, as Windows 7 has no flip model, |
| 4 | //! or where the window has none, to a flip-model one that DirectComposition shows. |
| 5 | use super::*; |
| 6 | use std::{ffi::c_void, ptr}; |
| 7 | use windows::{ |
| 8 | Win32::{ |
| 9 | Foundation::{HMODULE, HWND, RECT}, |
| 10 | Graphics::{ |
| 11 | Direct3D::*, |
| 12 | Direct3D11::*, |
| 13 | DirectComposition::{IDCompositionDevice, IDCompositionTarget, IDCompositionVisual}, |
| 14 | Dxgi::{Common::*, *}, |
| 15 | }, |
| 16 | System::LibraryLoader::{GetProcAddress, LoadLibraryW}, |
| 17 | UI::WindowsAndMessaging::{ |
| 18 | GWL_EXSTYLE, GetClientRect, GetWindowLongPtrW, WS_EX_NOREDIRECTIONBITMAP, |
| 19 | }, |
| 20 | }, |
| 21 | core::{Error, GUID, HRESULT, Interface, PCSTR, Result, s, w}, |
| 22 | }; |
| 23 | |
| 24 | // Shader model 4.0 bytecode from `dxbc/compile.ps1`: the build host has no HLSL compiler, and |
| 25 | // Windows 7 has a runtime one only with an optional update. |
| 26 | const DRAW_VERTEX: &[u8] = include_bytes!("dxbc/draw.vertex.dxbc"); |
| 27 | const DRAW_FRAGMENT: &[u8] = include_bytes!("dxbc/draw.fragment.dxbc"); |
| 28 | const TRANSLUCENT_VERTEX: &[u8] = include_bytes!("dxbc/translucent.vertex.dxbc"); |
| 29 | const TRANSLUCENT_FRAGMENT: &[u8] = include_bytes!("dxbc/translucent.fragment.dxbc"); |
| 30 | |
| 31 | const FORMAT: DXGI_FORMAT = DXGI_FORMAT_R8G8B8A8_UNORM_SRGB; |
| 32 | |
| 33 | /// What a successful creating call leaves in its out-parameter. |
| 34 | fn made<T>(create: impl FnOnce(&mut Option<T>) -> Result<()>) -> Result<T> { |
| 35 | let mut made = None; |
| 36 | create(&mut made)?; |
| 37 | Ok(made.expect("A successful call returns what it made")) |
| 38 | } |
| 39 | |
| 40 | /// An sRGB RGBA texture, `pixels` its rows if given. |
| 41 | fn texture( |
| 42 | device: &ID3D11Device, |
| 43 | size: [u32; 2], |
| 44 | bind: D3D11_BIND_FLAG, |
| 45 | pixels: Option<&[u8]>, |
| 46 | ) -> Result<ID3D11Texture2D> { |
| 47 | let description = D3D11_TEXTURE2D_DESC { |
| 48 | Width: size[0], |
| 49 | Height: size[1], |
| 50 | MipLevels: 1, |
| 51 | ArraySize: 1, |
| 52 | Format: FORMAT, |
| 53 | SampleDesc: DXGI_SAMPLE_DESC { |
| 54 | Count: 1, |
| 55 | Quality: 0, |
| 56 | }, |
| 57 | Usage: D3D11_USAGE_DEFAULT, |
| 58 | BindFlags: bind.0 as u32, |
| 59 | ..Default::default() |
| 60 | }; |
| 61 | let data = pixels.map(|pixels| D3D11_SUBRESOURCE_DATA { |
| 62 | pSysMem: pixels.as_ptr().cast(), |
| 63 | SysMemPitch: size[0] * 4, |
| 64 | SysMemSlicePitch: 0, |
| 65 | }); |
| 66 | made(|out| unsafe { |
| 67 | device.CreateTexture2D(&description, data.as_ref().map(ptr::from_ref), Some(out)) |
| 68 | }) |
| 69 | } |
| 70 | |
| 71 | fn description(texture: &ID3D11Texture2D) -> D3D11_TEXTURE2D_DESC { |
| 72 | let mut description = D3D11_TEXTURE2D_DESC::default(); |
| 73 | unsafe { texture.GetDesc(&mut description) }; |
| 74 | description |
| 75 | } |
| 76 | |
| 77 | fn resource(device: &ID3D11Device, texture: &ID3D11Texture2D) -> Result<ID3D11ShaderResourceView> { |
| 78 | made(|out| unsafe { device.CreateShaderResourceView(texture, None, Some(out)) }) |
| 79 | } |
| 80 | |
| 81 | /// A texture as the fragment shader samples it, or none where Direct3D made none, as when |
| 82 | /// the device is lost. |
| 83 | pub(super) struct Image(Option<ID3D11ShaderResourceView>); |
| 84 | |
| 85 | /// An offscreen frame, which `Renderer::present` shows in the window. |
| 86 | pub struct Target { |
| 87 | texture: ID3D11Texture2D, |
| 88 | view: ID3D11RenderTargetView, |
| 89 | resource: ID3D11ShaderResourceView, |
| 90 | } |
| 91 | |
| 92 | impl Target { |
| 93 | fn new(device: &ID3D11Device, size: [u32; 2]) -> Result<Self> { |
| 94 | let bind = D3D11_BIND_RENDER_TARGET | D3D11_BIND_SHADER_RESOURCE; |
| 95 | let texture = texture(device, size, bind, None)?; |
| 96 | let view = made(|out| unsafe { device.CreateRenderTargetView(&texture, None, Some(out)) })?; |
| 97 | let resource = resource(device, &texture)?; |
| 98 | Ok(Self { |
| 99 | texture, |
| 100 | view, |
| 101 | resource, |
| 102 | }) |
| 103 | } |
| 104 | |
| 105 | pub fn size(&self) -> [u32; 2] { |
| 106 | let description = description(&self.texture); |
| 107 | [description.Width, description.Height] |
| 108 | } |
| 109 | } |
| 110 | |
| 111 | pub(super) struct Gpu { |
| 112 | device: ID3D11Device, |
| 113 | context: ID3D11DeviceContext, |
| 114 | swap_chain: IDXGISwapChain, |
| 115 | /// What shows the swap chain in a window without a redirection surface. |
| 116 | _composition: Option<Composition>, |
| 117 | layout: ID3D11InputLayout, |
| 118 | vertex: ID3D11VertexShader, |
| 119 | fragment: ID3D11PixelShader, |
| 120 | translucent: (ID3D11VertexShader, ID3D11PixelShader), |
| 121 | /// One for each of `Blend::STATES`. |
| 122 | blends: [ID3D11BlendState; 4], |
| 123 | rasterizer: ID3D11RasterizerState, |
| 124 | nearest: ID3D11SamplerState, |
| 125 | linear: ID3D11SamplerState, |
| 126 | buffer: ID3D11Buffer, |
| 127 | atlas: (ID3D11Texture2D, ID3D11ShaderResourceView), |
| 128 | /// Offscreen pictures for groups, the target's size. |
| 129 | groups: Vec<Target>, |
| 130 | } |
| 131 | |
| 132 | impl Gpu { |
| 133 | /// A device and a swap chain on `window`, an `HWND`, and the adapter's description. |
| 134 | pub(super) fn new(window: *mut c_void) -> Result<(Self, String)> { |
| 135 | let levels = [ |
| 136 | D3D_FEATURE_LEVEL_11_0, |
| 137 | D3D_FEATURE_LEVEL_10_1, |
| 138 | D3D_FEATURE_LEVEL_10_0, |
| 139 | ]; |
| 140 | let create = |driver| { |
| 141 | let (mut device, mut context) = (None, None); |
| 142 | unsafe { |
| 143 | D3D11CreateDevice( |
| 144 | None, |
| 145 | driver, |
| 146 | HMODULE::default(), |
| 147 | D3D11_CREATE_DEVICE_FLAG(0), |
| 148 | Some(&levels), |
| 149 | D3D11_SDK_VERSION, |
| 150 | Some(&mut device), |
| 151 | None, |
| 152 | Some(&mut context), |
| 153 | ) |
| 154 | } |
| 155 | .map(|()| (device.expect("A device"), context.expect("A context"))) |
| 156 | }; |
| 157 | let (device, context) = |
| 158 | create(D3D_DRIVER_TYPE_HARDWARE).or_else(|_| create(D3D_DRIVER_TYPE_WARP))?; |
| 159 | let adapter = unsafe { device.cast::<IDXGIDevice>()?.GetAdapter()? }; |
| 160 | let name = unsafe { adapter.GetDesc()? }.Description; |
| 161 | let name = String::from_utf16_lossy(name.split(|&c| c == 0).next().unwrap_or(&[])); |
| 162 | let level = unsafe { device.GetFeatureLevel() }.0; |
| 163 | let [major, minor] = [level >> 12, (level >> 8) & 0xf]; |
| 164 | let description = format!("{name}, feature level {major}_{minor}"); |
| 165 | let hwnd = HWND(window); |
| 166 | let factory: IDXGIFactory = unsafe { adapter.GetParent()? }; |
| 167 | // Windows 10's window has no redirection surface, made for wgpu's DirectComposition. |
| 168 | let style = unsafe { GetWindowLongPtrW(hwnd, GWL_EXSTYLE) } as u32; |
| 169 | let (swap_chain, composition) = if style & WS_EX_NOREDIRECTIONBITMAP.0 != 0 { |
| 170 | let (swap_chain, composition) = compose(&device, &factory, hwnd)?; |
| 171 | (swap_chain, Some(composition)) |
| 172 | } else { |
| 173 | let description = DXGI_SWAP_CHAIN_DESC { |
| 174 | // Windows 7's desktop composes a blit swap chain's alpha only from BGRA. |
| 175 | BufferDesc: DXGI_MODE_DESC { |
| 176 | Format: DXGI_FORMAT_B8G8R8A8_UNORM, |
| 177 | ..Default::default() |
| 178 | }, |
| 179 | SampleDesc: DXGI_SAMPLE_DESC { |
| 180 | Count: 1, |
| 181 | Quality: 0, |
| 182 | }, |
| 183 | BufferUsage: DXGI_USAGE_RENDER_TARGET_OUTPUT, |
| 184 | BufferCount: 1, |
| 185 | OutputWindow: hwnd, |
| 186 | Windowed: true.into(), |
| 187 | SwapEffect: DXGI_SWAP_EFFECT_DISCARD, |
| 188 | Flags: 0, |
| 189 | }; |
| 190 | let swap_chain = |
| 191 | made(|out| unsafe { factory.CreateSwapChain(&device, &description, out).ok() })?; |
| 192 | (swap_chain, None) |
| 193 | }; |
| 194 | // DXGI would otherwise watch the window's messages and take Alt+Enter to full screen. |
| 195 | unsafe { |
| 196 | factory |
| 197 | .MakeWindowAssociation(hwnd, DXGI_MWA_NO_WINDOW_CHANGES | DXGI_MWA_NO_ALT_ENTER)?; |
| 198 | } |
| 199 | |
| 200 | let elements: [_; 9] = std::array::from_fn(|index| { |
| 201 | let (_, floats, offset) = ATTRIBUTES[index]; |
| 202 | D3D11_INPUT_ELEMENT_DESC { |
| 203 | SemanticName: PCSTR(c"TEXCOORD".as_ptr().cast()), |
| 204 | SemanticIndex: index as u32, |
| 205 | Format: [ |
| 206 | DXGI_FORMAT_R32_FLOAT, |
| 207 | DXGI_FORMAT_R32G32_FLOAT, |
| 208 | DXGI_FORMAT_R32G32B32_FLOAT, |
| 209 | DXGI_FORMAT_R32G32B32A32_FLOAT, |
| 210 | ][floats - 1], |
| 211 | InputSlot: 0, |
| 212 | AlignedByteOffset: offset as u32, |
| 213 | InputSlotClass: D3D11_INPUT_PER_VERTEX_DATA, |
| 214 | InstanceDataStepRate: 0, |
| 215 | } |
| 216 | }); |
| 217 | let layout = |
| 218 | made(|out| unsafe { device.CreateInputLayout(&elements, DRAW_VERTEX, Some(out)) })?; |
| 219 | let vertex_shader = |
| 220 | |code| made(|out| unsafe { device.CreateVertexShader(code, None, Some(out)) }); |
| 221 | let pixel_shader = |
| 222 | |code| made(|out| unsafe { device.CreatePixelShader(code, None, Some(out)) }); |
| 223 | |
| 224 | let blend = |factors: [Factor; 4]| { |
| 225 | let [src, dst, src_alpha, dst_alpha] = factors.map(|factor| match factor { |
| 226 | Factor::Zero => D3D11_BLEND_ZERO, |
| 227 | Factor::One => D3D11_BLEND_ONE, |
| 228 | Factor::SourceAlpha => D3D11_BLEND_SRC_ALPHA, |
| 229 | Factor::OneMinusSourceAlpha => D3D11_BLEND_INV_SRC_ALPHA, |
| 230 | Factor::Destination => D3D11_BLEND_DEST_COLOR, |
| 231 | }); |
| 232 | let mut description = D3D11_BLEND_DESC::default(); |
| 233 | description.RenderTarget[0] = D3D11_RENDER_TARGET_BLEND_DESC { |
| 234 | BlendEnable: true.into(), |
| 235 | SrcBlend: src, |
| 236 | DestBlend: dst, |
| 237 | BlendOp: D3D11_BLEND_OP_ADD, |
| 238 | SrcBlendAlpha: src_alpha, |
| 239 | DestBlendAlpha: dst_alpha, |
| 240 | BlendOpAlpha: D3D11_BLEND_OP_ADD, |
| 241 | RenderTargetWriteMask: D3D11_COLOR_WRITE_ENABLE_ALL.0 as u8, |
| 242 | }; |
| 243 | made(|out| unsafe { device.CreateBlendState(&description, Some(out)) }) |
| 244 | }; |
| 245 | let sampler = |filter| { |
| 246 | let description = D3D11_SAMPLER_DESC { |
| 247 | Filter: filter, |
| 248 | AddressU: D3D11_TEXTURE_ADDRESS_CLAMP, |
| 249 | AddressV: D3D11_TEXTURE_ADDRESS_CLAMP, |
| 250 | AddressW: D3D11_TEXTURE_ADDRESS_CLAMP, |
| 251 | ComparisonFunc: D3D11_COMPARISON_NEVER, |
| 252 | MaxLOD: f32::MAX, |
| 253 | ..Default::default() |
| 254 | }; |
| 255 | made(|out| unsafe { device.CreateSamplerState(&description, Some(out)) }) |
| 256 | }; |
| 257 | let rasterizer = D3D11_RASTERIZER_DESC { |
| 258 | FillMode: D3D11_FILL_SOLID, |
| 259 | CullMode: D3D11_CULL_NONE, |
| 260 | DepthClipEnable: true.into(), |
| 261 | ScissorEnable: true.into(), |
| 262 | ..Default::default() |
| 263 | }; |
| 264 | let buffer = D3D11_BUFFER_DESC { |
| 265 | ByteWidth: VERTEX_BUFFER_BYTES as u32, |
| 266 | Usage: D3D11_USAGE_DYNAMIC, |
| 267 | BindFlags: D3D11_BIND_VERTEX_BUFFER.0 as u32, |
| 268 | CPUAccessFlags: D3D11_CPU_ACCESS_WRITE.0 as u32, |
| 269 | ..Default::default() |
| 270 | }; |
| 271 | let [over, picture, erase, multiply] = Blend::STATES; |
| 272 | let gpu = Self { |
| 273 | layout, |
| 274 | vertex: vertex_shader(DRAW_VERTEX)?, |
| 275 | fragment: pixel_shader(DRAW_FRAGMENT)?, |
| 276 | translucent: ( |
| 277 | vertex_shader(TRANSLUCENT_VERTEX)?, |
| 278 | pixel_shader(TRANSLUCENT_FRAGMENT)?, |
| 279 | ), |
| 280 | blends: [ |
| 281 | blend(over)?, |
| 282 | blend(picture)?, |
| 283 | blend(erase)?, |
| 284 | blend(multiply)?, |
| 285 | ], |
| 286 | rasterizer: made(|out| unsafe { |
| 287 | device.CreateRasterizerState(&rasterizer, Some(out)) |
| 288 | })?, |
| 289 | nearest: sampler(D3D11_FILTER_MIN_MAG_MIP_POINT)?, |
| 290 | linear: sampler(D3D11_FILTER_MIN_MAG_MIP_LINEAR)?, |
| 291 | buffer: made(|out| unsafe { device.CreateBuffer(&buffer, None, Some(out)) })?, |
| 292 | atlas: atlas(&device, ATLAS_SIZE)?, |
| 293 | groups: Vec::new(), |
| 294 | device, |
| 295 | context, |
| 296 | swap_chain, |
| 297 | _composition: composition, |
| 298 | }; |
| 299 | Ok((gpu, description)) |
| 300 | } |
| 301 | |
| 302 | pub(super) fn target(&self, size: [u32; 2]) -> Result<Target> { |
| 303 | Target::new(&self.device, size) |
| 304 | } |
| 305 | |
| 306 | /// Copies `target` to the window, premultiplying each pixel again in sRGB, as the |
| 307 | /// desktop compositing the window over its backdrop needs, which leaves opaque ones be. |
| 308 | pub(super) fn present(&self, target: &Target) -> Result<()> { |
| 309 | let context = &self.context; |
| 310 | let size = target.size(); |
| 311 | let [width, height] = size; |
| 312 | unsafe { |
| 313 | let buffers = self.swap_chain.GetDesc()?.BufferDesc; |
| 314 | if [buffers.Width, buffers.Height] != size { |
| 315 | let flags = DXGI_SWAP_CHAIN_FLAG(0); |
| 316 | (self.swap_chain).ResizeBuffers(0, width, height, DXGI_FORMAT_UNKNOWN, flags)?; |
| 317 | } |
| 318 | let buffer: ID3D11Texture2D = self.swap_chain.GetBuffer(0)?; |
| 319 | let view = made(|out| self.device.CreateRenderTargetView(&buffer, None, Some(out)))?; |
| 320 | let (vertex, fragment) = &self.translucent; |
| 321 | self.begin(&view, size); |
| 322 | context.OMSetBlendState(None, None, u32::MAX); |
| 323 | context.VSSetShader(vertex, None); |
| 324 | context.PSSetShader(fragment, None); |
| 325 | context.PSSetShaderResources(0, Some(&[Some(target.resource.clone())])); |
| 326 | context.Draw(3, 0); |
| 327 | context.PSSetShaderResources(0, Some(&[None])); |
| 328 | context.OMSetRenderTargets(None, None); |
| 329 | self.swap_chain.Present(1, DXGI_PRESENT(0)).ok() |
| 330 | } |
| 331 | } |
| 332 | |
| 333 | /// sRGB-encoded RGBA rows of `target`, top first. |
| 334 | pub(super) fn read_pixels(&self, target: &Target) -> Result<Vec<u8>> { |
| 335 | let mut description = description(&target.texture); |
| 336 | let [width, height] = [description.Width, description.Height]; |
| 337 | description.Usage = D3D11_USAGE_STAGING; |
| 338 | description.BindFlags = 0; |
| 339 | description.CPUAccessFlags = D3D11_CPU_ACCESS_READ.0 as u32; |
| 340 | let row = width as usize * 4; |
| 341 | let mut pixels = Vec::with_capacity(row * height as usize); |
| 342 | unsafe { |
| 343 | let staging = made(|out| self.device.CreateTexture2D(&description, None, Some(out)))?; |
| 344 | self.context.CopyResource(&staging, &target.texture); |
| 345 | let mut mapped = D3D11_MAPPED_SUBRESOURCE::default(); |
| 346 | self.context |
| 347 | .Map(&staging, 0, D3D11_MAP_READ, 0, Some(&mut mapped))?; |
| 348 | for y in 0..height as usize { |
| 349 | let start = mapped.pData.cast::<u8>().add(y * mapped.RowPitch as usize); |
| 350 | pixels.extend_from_slice(std::slice::from_raw_parts(start, row)); |
| 351 | } |
| 352 | self.context.Unmap(&staging, 0); |
| 353 | } |
| 354 | Ok(pixels) |
| 355 | } |
| 356 | |
| 357 | /// Blends in linear light, its targets being sRGB. |
| 358 | pub(super) fn blends_linear(&self) -> bool { |
| 359 | true |
| 360 | } |
| 361 | |
| 362 | /// Offscreen pictures' rows run top first. |
| 363 | pub(super) fn flipped(&self) -> bool { |
| 364 | false |
| 365 | } |
| 366 | |
| 367 | pub(super) fn max_texture_dimension(&self) -> u32 { |
| 368 | if unsafe { self.device.GetFeatureLevel() }.0 >= D3D_FEATURE_LEVEL_11_0.0 { |
| 369 | D3D11_REQ_TEXTURE2D_U_OR_V_DIMENSION |
| 370 | } else { |
| 371 | // Feature level 10's. |
| 372 | 8192 |
| 373 | } |
| 374 | } |
| 375 | |
| 376 | pub(super) fn atlas_side(&self) -> u32 { |
| 377 | description(&self.atlas.0).Width |
| 378 | } |
| 379 | |
| 380 | pub(super) fn new_atlas(&mut self, side: u32) { |
| 381 | match atlas(&self.device, side) { |
| 382 | Ok(atlas) => self.atlas = atlas, |
| 383 | Err(error) => eprintln!("No glyph atlas {side} pixels square: {error}"), |
| 384 | } |
| 385 | } |
| 386 | |
| 387 | pub(super) fn write_atlas(&self, origin: [u32; 2], size: [u32; 2], rgba: &[u8]) { |
| 388 | let area = D3D11_BOX { |
| 389 | left: origin[0], |
| 390 | top: origin[1], |
| 391 | front: 0, |
| 392 | right: origin[0] + size[0], |
| 393 | bottom: origin[1] + size[1], |
| 394 | back: 1, |
| 395 | }; |
| 396 | unsafe { |
| 397 | self.context.UpdateSubresource( |
| 398 | &self.atlas.0, |
| 399 | 0, |
| 400 | Some(&area), |
| 401 | rgba.as_ptr().cast(), |
| 402 | size[0] * 4, |
| 403 | 0, |
| 404 | ); |
| 405 | } |
| 406 | } |
| 407 | |
| 408 | pub(super) fn upload_image(&self, image: &RasterImage) -> Image { |
| 409 | let texture = texture( |
| 410 | &self.device, |
| 411 | image.size, |
| 412 | D3D11_BIND_SHADER_RESOURCE, |
| 413 | Some(image.pixels()), |
| 414 | ); |
| 415 | let view = texture.and_then(|texture| resource(&self.device, &texture)); |
| 416 | Image( |
| 417 | view.inspect_err(|error| eprintln!("No image texture: {error}")) |
| 418 | .ok(), |
| 419 | ) |
| 420 | } |
| 421 | |
| 422 | /// Binds `view`, `size` pixels, as the render target, with the pipeline's state. |
| 423 | fn begin(&self, view: &ID3D11RenderTargetView, size: [u32; 2]) { |
| 424 | let context = &self.context; |
| 425 | unsafe { |
| 426 | context.PSSetShaderResources(0, Some(&[None])); |
| 427 | context.OMSetRenderTargets(Some(&[Some(view.clone())]), None); |
| 428 | context.IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST); |
| 429 | context.RSSetState(&self.rasterizer); |
| 430 | context.RSSetViewports(Some(&[D3D11_VIEWPORT { |
| 431 | TopLeftX: 0.0, |
| 432 | TopLeftY: 0.0, |
| 433 | Width: size[0] as f32, |
| 434 | Height: size[1] as f32, |
| 435 | MinDepth: 0.0, |
| 436 | MaxDepth: 1.0, |
| 437 | }])); |
| 438 | context.RSSetScissorRects(Some(&[RECT { |
| 439 | left: 0, |
| 440 | top: 0, |
| 441 | right: size[0] as i32, |
| 442 | bottom: size[1] as i32, |
| 443 | }])); |
| 444 | } |
| 445 | } |
| 446 | |
| 447 | /// Clears `target`, `size` device pixels, to linear `clear` and draws the prepared |
| 448 | /// batches, each group's offscreen first. |
| 449 | pub(super) fn submit( |
| 450 | &mut self, |
| 451 | frame: &Frame<'_>, |
| 452 | target: &Target, |
| 453 | size: [u32; 2], |
| 454 | clear: [f32; 4], |
| 455 | ) { |
| 456 | if self |
| 457 | .groups |
| 458 | .first() |
| 459 | .is_some_and(|picture| picture.size() != size) |
| 460 | { |
| 461 | self.groups.clear(); |
| 462 | } |
| 463 | while self.groups.len() < frame.groups.len() { |
| 464 | match Target::new(&self.device, size) { |
| 465 | Ok(picture) => self.groups.push(picture), |
| 466 | Err(error) => { |
| 467 | eprintln!("No offscreen picture: {error}"); |
| 468 | return; |
| 469 | } |
| 470 | } |
| 471 | } |
| 472 | let gpu = &*self; |
| 473 | let context = &gpu.context; |
| 474 | let bytes: &[u8] = bytemuck::cast_slice(frame.vertices); |
| 475 | let bytes = &bytes[..bytes.len().min(VERTEX_BUFFER_BYTES as usize)]; |
| 476 | unsafe { |
| 477 | let mut mapped = D3D11_MAPPED_SUBRESOURCE::default(); |
| 478 | if let Err(error) = context.Map( |
| 479 | &gpu.buffer, |
| 480 | 0, |
| 481 | D3D11_MAP_WRITE_DISCARD, |
| 482 | 0, |
| 483 | Some(&mut mapped), |
| 484 | ) { |
| 485 | eprintln!("No vertex buffer: {error}"); |
| 486 | return; |
| 487 | } |
| 488 | ptr::copy_nonoverlapping(bytes.as_ptr(), mapped.pData.cast(), bytes.len()); |
| 489 | context.Unmap(&gpu.buffer, 0); |
| 490 | context.IASetInputLayout(&gpu.layout); |
| 491 | let buffer = Some(gpu.buffer.clone()); |
| 492 | let stride = size_of::<Vertex>() as u32; |
| 493 | context.IASetVertexBuffers(0, 1, Some(&buffer), Some(&stride), Some(&0)); |
| 494 | } |
| 495 | let paint = |target: &Target, clear: [f32; 4], batches: &[Batch]| unsafe { |
| 496 | gpu.begin(&target.view, size); |
| 497 | context.VSSetShader(&gpu.vertex, None); |
| 498 | context.PSSetShader(&gpu.fragment, None); |
| 499 | context.ClearRenderTargetView(&target.view, &clear); |
| 500 | for batch in batches { |
| 501 | let [x, y, w, h] = batch.scissor.map(|value| value as i32); |
| 502 | let rect = RECT { |
| 503 | left: x, |
| 504 | top: y, |
| 505 | right: x + w, |
| 506 | bottom: y + h, |
| 507 | }; |
| 508 | context.RSSetScissorRects(Some(&[rect])); |
| 509 | let (resource, sampler) = match batch.blend { |
| 510 | Blend::Image(id) => (frame.image::<Image>(id).0.as_ref(), &gpu.linear), |
| 511 | Blend::Group(index) => (Some(&gpu.groups[index].resource), &gpu.linear), |
| 512 | Blend::Over | Blend::Erase | Blend::Multiply => { |
| 513 | (Some(&gpu.atlas.1), &gpu.nearest) |
| 514 | } |
| 515 | }; |
| 516 | context.OMSetBlendState(&gpu.blends[batch.blend.state()], None, u32::MAX); |
| 517 | context.PSSetSamplers(0, Some(&[Some(sampler.clone())])); |
| 518 | context.PSSetShaderResources(0, Some(&[resource.cloned()])); |
| 519 | context.Draw(batch.vertices.len() as u32, batch.vertices.start); |
| 520 | } |
| 521 | }; |
| 522 | for (group, picture) in frame.groups.iter().zip(&gpu.groups) { |
| 523 | paint(picture, [0.0; 4], &group.batches); |
| 524 | } |
| 525 | paint(target, clear, frame.batches); |
| 526 | unsafe { |
| 527 | context.PSSetShaderResources(0, Some(&[None])); |
| 528 | context.OMSetRenderTargets(None, None); |
| 529 | } |
| 530 | } |
| 531 | } |
| 532 | |
| 533 | type Composition = ( |
| 534 | IDCompositionDevice, |
| 535 | IDCompositionTarget, |
| 536 | IDCompositionVisual, |
| 537 | ); |
| 538 | |
| 539 | /// A flip-model swap chain with premultiplied alpha, which DirectComposition shows over |
| 540 | /// all of `hwnd`. |
| 541 | fn compose( |
| 542 | device: &ID3D11Device, |
| 543 | factory: &IDXGIFactory, |
| 544 | hwnd: HWND, |
| 545 | ) -> Result<(IDXGISwapChain, Composition)> { |
| 546 | let mut client = RECT::default(); |
| 547 | unsafe { GetClientRect(hwnd, &mut client)? }; |
| 548 | let description = DXGI_SWAP_CHAIN_DESC1 { |
| 549 | Width: client.right.max(1) as u32, |
| 550 | Height: client.bottom.max(1) as u32, |
| 551 | Format: DXGI_FORMAT_B8G8R8A8_UNORM, |
| 552 | SampleDesc: DXGI_SAMPLE_DESC { |
| 553 | Count: 1, |
| 554 | Quality: 0, |
| 555 | }, |
| 556 | BufferUsage: DXGI_USAGE_RENDER_TARGET_OUTPUT, |
| 557 | BufferCount: 2, |
| 558 | Scaling: DXGI_SCALING_STRETCH, |
| 559 | SwapEffect: DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL, |
| 560 | AlphaMode: DXGI_ALPHA_MODE_PREMULTIPLIED, |
| 561 | ..Default::default() |
| 562 | }; |
| 563 | // Windows 7 has no dcomp.dll, so it is looked up rather than linked. |
| 564 | type Create = unsafe extern "system" fn(*mut c_void, *const GUID, *mut *mut c_void) -> HRESULT; |
| 565 | unsafe { |
| 566 | let factory: IDXGIFactory2 = factory.cast()?; |
| 567 | let swap_chain = factory.CreateSwapChainForComposition(device, &description, None)?; |
| 568 | let library = LoadLibraryW(w!("dcomp.dll"))?; |
| 569 | let create = GetProcAddress(library, s!("DCompositionCreateDevice")) |
| 570 | .ok_or_else(Error::from_thread)?; |
| 571 | let create = std::mem::transmute::<unsafe extern "system" fn() -> isize, Create>(create); |
| 572 | let mut composition = ptr::null_mut(); |
| 573 | let dxgi: IDXGIDevice = device.cast()?; |
| 574 | create(dxgi.as_raw(), &IDCompositionDevice::IID, &mut composition).ok()?; |
| 575 | let composition = IDCompositionDevice::from_raw(composition); |
| 576 | let target = composition.CreateTargetForHwnd(hwnd, true)?; |
| 577 | let visual = composition.CreateVisual()?; |
| 578 | visual.SetContent(&swap_chain)?; |
| 579 | target.SetRoot(&visual)?; |
| 580 | composition.Commit()?; |
| 581 | Ok((swap_chain.cast()?, (composition, target, visual))) |
| 582 | } |
| 583 | } |
| 584 | |
| 585 | /// An empty atlas `side` texels square. |
| 586 | fn atlas(device: &ID3D11Device, side: u32) -> Result<(ID3D11Texture2D, ID3D11ShaderResourceView)> { |
| 587 | let texture = texture(device, [side; 2], D3D11_BIND_SHADER_RESOURCE, None)?; |
| 588 | let view = resource(device, &texture)?; |
| 589 | Ok((texture, view)) |
| 590 | } |