/// <summary> /// Finishes the draw call. /// This draws geometry on the bound buffers based on the current GPU state. /// </summary> /// <param name="state">Current GPU state</param> /// <param name="argument">Method call argument</param> private void DrawEnd(GpuState state, int argument) { var indexBuffer = state.Get <IndexBufferState>(MethodOffset.IndexBufferState); DrawEnd(state, indexBuffer.First, indexBuffer.Count); }
/// <summary> /// Finishes draw call. /// This draws geometry on the bound buffers based on the current GPU state. /// </summary> /// <param name="state">Current GPU state</param> /// <param name="argument">Method call argument</param> private void DrawEnd(GpuState state, int argument) { ConditionalRenderEnabled renderEnable = GetRenderEnable(state); if (renderEnable == ConditionalRenderEnabled.False || _instancedDrawPending) { if (renderEnable == ConditionalRenderEnabled.False) { PerformDeferredDraws(); } _drawIndexed = false; if (renderEnable == ConditionalRenderEnabled.Host) { _context.Renderer.Pipeline.EndHostConditionalRendering(); } return; } UpdateState(state); bool instanced = _vsUsesInstanceId || _isAnyVbInstanced; if (instanced) { _instancedDrawPending = true; _instancedIndexed = _drawIndexed; _instancedFirstIndex = _firstIndex; _instancedFirstVertex = state.Get <int>(MethodOffset.FirstVertex); _instancedFirstInstance = state.Get <int>(MethodOffset.FirstInstance); _instancedIndexCount = _indexCount; var drawState = state.Get <VertexBufferDrawState>(MethodOffset.VertexBufferDrawState); _instancedDrawStateFirst = drawState.First; _instancedDrawStateCount = drawState.Count; _drawIndexed = false; if (renderEnable == ConditionalRenderEnabled.Host) { _context.Renderer.Pipeline.EndHostConditionalRendering(); } return; } int firstInstance = state.Get <int>(MethodOffset.FirstInstance); if (_drawIndexed) { _drawIndexed = false; int firstVertex = state.Get <int>(MethodOffset.FirstVertex); _context.Renderer.Pipeline.DrawIndexed( _indexCount, 1, _firstIndex, firstVertex, firstInstance); } else { var drawState = state.Get <VertexBufferDrawState>(MethodOffset.VertexBufferDrawState); _context.Renderer.Pipeline.Draw( drawState.Count, 1, drawState.First, firstInstance); } if (renderEnable == ConditionalRenderEnabled.Host) { _context.Renderer.Pipeline.EndHostConditionalRendering(); } }
/// <summary> /// Clears the current color and depth-stencil buffers. /// Which buffers should be cleared is also specified on the argument. /// </summary> /// <param name="state">Current GPU state</param> /// <param name="argument">Method call argument</param> private void Clear(GpuState state, int argument) { ConditionalRenderEnabled renderEnable = GetRenderEnable(state); if (renderEnable == ConditionalRenderEnabled.False) { return; } // Scissor and rasterizer discard also affect clears. if (state.QueryModified(MethodOffset.ScissorState)) { UpdateScissorState(state); } if (state.QueryModified(MethodOffset.RasterizeEnable)) { UpdateRasterizerState(state); } int index = (argument >> 6) & 0xf; UpdateRenderTargetState(state, useControl: false, singleUse: index); state.Channel.TextureManager.UpdateRenderTargets(); bool clearDepth = (argument & 1) != 0; bool clearStencil = (argument & 2) != 0; uint componentMask = (uint)((argument >> 2) & 0xf); if (componentMask != 0) { var clearColor = state.Get <ClearColors>(MethodOffset.ClearColors); ColorF color = new ColorF( clearColor.Red, clearColor.Green, clearColor.Blue, clearColor.Alpha); _context.Renderer.Pipeline.ClearRenderTargetColor(index, componentMask, color); } if (clearDepth || clearStencil) { float depthValue = state.Get <float>(MethodOffset.ClearDepthValue); int stencilValue = state.Get <int> (MethodOffset.ClearStencilValue); int stencilMask = 0; if (clearStencil) { stencilMask = state.Get <StencilTestState>(MethodOffset.StencilTestState).FrontMask; } _context.Renderer.Pipeline.ClearRenderTargetDepthStencil( depthValue, clearDepth, stencilValue, stencilMask); } UpdateRenderTargetState(state, useControl: true); if (renderEnable == ConditionalRenderEnabled.Host) { _context.Renderer.Pipeline.EndHostConditionalRendering(); } }
/// <summary> /// Updates Rasterizer primitive discard state based on guest gpu state. /// </summary> /// <param name="state">Current GPU state</param> private void UpdateRasterizerState(GpuState state) { Boolean32 enable = state.Get <Boolean32>(MethodOffset.RasterizeEnable); _context.Renderer.Pipeline.SetRasterizerDiscard(!enable); }
/// <summary> /// Performs a buffer to buffer, or buffer to texture copy. /// </summary> /// <param name="state">Current GPU state</param> /// <param name="argument">Method call argument</param> private void CopyBuffer(GpuState state, int argument) { var cbp = state.Get <CopyBufferParams>(MethodOffset.CopyBufferParams); var swizzle = state.Get <CopyBufferSwizzle>(MethodOffset.CopyBufferSwizzle); bool srcLinear = (argument & (1 << 7)) != 0; bool dstLinear = (argument & (1 << 8)) != 0; bool copy2D = (argument & (1 << 9)) != 0; int size = cbp.XCount; if (size == 0) { return; } if (copy2D) { // Buffer to texture copy. int srcBpp = swizzle.UnpackSrcComponentsCount() * swizzle.UnpackComponentSize(); int dstBpp = swizzle.UnpackDstComponentsCount() * swizzle.UnpackComponentSize(); var dst = state.Get <CopyBufferTexture>(MethodOffset.CopyBufferDstTexture); var src = state.Get <CopyBufferTexture>(MethodOffset.CopyBufferSrcTexture); var srcCalculator = new OffsetCalculator( src.Width, src.Height, cbp.SrcStride, srcLinear, src.MemoryLayout.UnpackGobBlocksInY(), srcBpp); var dstCalculator = new OffsetCalculator( dst.Width, dst.Height, cbp.DstStride, dstLinear, dst.MemoryLayout.UnpackGobBlocksInY(), dstBpp); ulong srcBaseAddress = _context.MemoryManager.Translate(cbp.SrcAddress.Pack()); ulong dstBaseAddress = _context.MemoryManager.Translate(cbp.DstAddress.Pack()); for (int y = 0; y < cbp.YCount; y++) { for (int x = 0; x < cbp.XCount; x++) { int srcOffset = srcCalculator.GetOffset(src.RegionX + x, src.RegionY + y); int dstOffset = dstCalculator.GetOffset(dst.RegionX + x, dst.RegionY + y); ulong srcAddress = srcBaseAddress + (ulong)srcOffset; ulong dstAddress = dstBaseAddress + (ulong)dstOffset; ReadOnlySpan <byte> pixel = _context.PhysicalMemory.GetSpan(srcAddress, srcBpp); _context.PhysicalMemory.Write(dstAddress, pixel); } } } else { // Buffer to buffer copy. BufferManager.CopyBuffer(cbp.SrcAddress, cbp.DstAddress, (uint)size); } }
/// <summary> /// Updates host point size based on guest GPU state. /// </summary> /// <param name="state">Current GPU state</param> private void UpdatePointSizeState(GpuState state) { float size = state.Get <float>(MethodOffset.PointSize); _context.Renderer.Pipeline.SetPointSize(size); }
/// <summary> /// Performs a buffer to buffer, or buffer to texture copy. /// </summary> /// <param name="state">Current GPU state</param> /// <param name="argument">Method call argument</param> private void CopyBuffer(GpuState state, int argument) { var cbp = state.Get <CopyBufferParams>(MethodOffset.CopyBufferParams); var swizzle = state.Get <CopyBufferSwizzle>(MethodOffset.CopyBufferSwizzle); CopyFlags copyFlags = (CopyFlags)argument; bool srcLinear = copyFlags.HasFlag(CopyFlags.SrcLinear); bool dstLinear = copyFlags.HasFlag(CopyFlags.DstLinear); bool copy2D = copyFlags.HasFlag(CopyFlags.MultiLineEnable); bool remap = copyFlags.HasFlag(CopyFlags.RemapEnable); int size = cbp.XCount; if (size == 0) { return; } FlushUboUpdate(); if (copy2D) { // Buffer to texture copy. int srcBpp = remap ? swizzle.UnpackSrcComponentsCount() * swizzle.UnpackComponentSize() : 1; int dstBpp = remap ? swizzle.UnpackDstComponentsCount() * swizzle.UnpackComponentSize() : 1; var dst = state.Get <CopyBufferTexture>(MethodOffset.CopyBufferDstTexture); var src = state.Get <CopyBufferTexture>(MethodOffset.CopyBufferSrcTexture); var srcCalculator = new OffsetCalculator( src.Width, src.Height, cbp.SrcStride, srcLinear, src.MemoryLayout.UnpackGobBlocksInY(), src.MemoryLayout.UnpackGobBlocksInZ(), srcBpp); var dstCalculator = new OffsetCalculator( dst.Width, dst.Height, cbp.DstStride, dstLinear, dst.MemoryLayout.UnpackGobBlocksInY(), dst.MemoryLayout.UnpackGobBlocksInZ(), dstBpp); ulong srcBaseAddress = _context.MemoryManager.Translate(cbp.SrcAddress.Pack()); ulong dstBaseAddress = _context.MemoryManager.Translate(cbp.DstAddress.Pack()); (int srcBaseOffset, int srcSize) = srcCalculator.GetRectangleRange(src.RegionX, src.RegionY, cbp.XCount, cbp.YCount); (int dstBaseOffset, int dstSize) = dstCalculator.GetRectangleRange(dst.RegionX, dst.RegionY, cbp.XCount, cbp.YCount); ReadOnlySpan <byte> srcSpan = _context.PhysicalMemory.GetSpan(srcBaseAddress + (ulong)srcBaseOffset, srcSize, true); Span <byte> dstSpan = _context.PhysicalMemory.GetSpan(dstBaseAddress + (ulong)dstBaseOffset, dstSize).ToArray(); bool completeSource = IsTextureCopyComplete(cbp, src, srcLinear, srcBpp, cbp.SrcStride); bool completeDest = IsTextureCopyComplete(cbp, dst, dstLinear, dstBpp, cbp.DstStride); if (completeSource && completeDest) { Image.Texture target = TextureManager.FindTexture(dst, cbp, swizzle, dstLinear); if (target != null) { ReadOnlySpan <byte> data; if (srcLinear) { data = LayoutConverter.ConvertLinearStridedToLinear( target.Info.Width, target.Info.Height, 1, 1, cbp.SrcStride, target.Info.FormatInfo.BytesPerPixel, srcSpan); } else { data = LayoutConverter.ConvertBlockLinearToLinear( src.Width, src.Height, 1, target.Info.Levels, 1, 1, 1, srcBpp, src.MemoryLayout.UnpackGobBlocksInY(), src.MemoryLayout.UnpackGobBlocksInZ(), 1, new SizeInfo((int)target.Size), srcSpan); } target.SetData(data); target.SignalModified(); return; } else if (srcCalculator.LayoutMatches(dstCalculator)) { srcSpan.CopyTo(dstSpan); // No layout conversion has to be performed, just copy the data entirely. _context.PhysicalMemory.Write(dstBaseAddress + (ulong)dstBaseOffset, dstSpan); return; } } unsafe bool Convert <T>(Span <byte> dstSpan, ReadOnlySpan <byte> srcSpan) where T : unmanaged { fixed(byte *dstPtr = dstSpan, srcPtr = srcSpan) { byte *dstBase = dstPtr - dstBaseOffset; // Layout offset is relative to the base, so we need to subtract the span's offset. byte *srcBase = srcPtr - srcBaseOffset; for (int y = 0; y < cbp.YCount; y++) { srcCalculator.SetY(src.RegionY + y); dstCalculator.SetY(dst.RegionY + y); for (int x = 0; x < cbp.XCount; x++) { int srcOffset = srcCalculator.GetOffset(src.RegionX + x); int dstOffset = dstCalculator.GetOffset(dst.RegionX + x); *(T *)(dstBase + dstOffset) = *(T *)(srcBase + srcOffset); } } } return(true); } bool _ = srcBpp switch { 1 => Convert <byte>(dstSpan, srcSpan), 2 => Convert <ushort>(dstSpan, srcSpan), 4 => Convert <uint>(dstSpan, srcSpan), 8 => Convert <ulong>(dstSpan, srcSpan), 12 => Convert <Bpp12Pixel>(dstSpan, srcSpan), 16 => Convert <Vector128 <byte> >(dstSpan, srcSpan), _ => throw new NotSupportedException($"Unable to copy ${srcBpp} bpp pixel format.") }; _context.PhysicalMemory.Write(dstBaseAddress + (ulong)dstBaseOffset, dstSpan); } else { if (remap && swizzle.UnpackDstX() == BufferSwizzleComponent.ConstA && swizzle.UnpackDstY() == BufferSwizzleComponent.ConstA && swizzle.UnpackDstZ() == BufferSwizzleComponent.ConstA && swizzle.UnpackDstW() == BufferSwizzleComponent.ConstA && swizzle.UnpackSrcComponentsCount() == 1 && swizzle.UnpackDstComponentsCount() == 1 && swizzle.UnpackComponentSize() == 4) { // Fast path for clears when remap is enabled. BufferManager.ClearBuffer(cbp.DstAddress, (uint)size * 4, state.Get <uint>(MethodOffset.CopyBufferConstA)); } else { // TODO: Implement remap functionality. // Buffer to buffer copy. BufferManager.CopyBuffer(cbp.SrcAddress, cbp.DstAddress, (uint)size); } } }
/// <summary> /// Performs a texture to texture copy. /// </summary> /// <param name="state">Current GPU state</param> /// <param name="argument">Method call argument</param> private void CopyTexture(GpuState state, int argument) { var dstCopyTexture = state.Get <CopyTexture>(MethodOffset.CopyDstTexture); var srcCopyTexture = state.Get <CopyTexture>(MethodOffset.CopySrcTexture); Texture srcTexture = TextureManager.FindOrCreateTexture(srcCopyTexture); if (srcTexture == null) { return; } // When the source texture that was found has a depth format, // we must enforce the target texture also has a depth format, // as copies between depth and color formats are not allowed. if (srcTexture.Format == Format.D32Float) { dstCopyTexture.Format = RtFormat.D32Float; } Texture dstTexture = TextureManager.FindOrCreateTexture(dstCopyTexture); if (dstTexture == null) { return; } var control = state.Get <CopyTextureControl>(MethodOffset.CopyTextureControl); var region = state.Get <CopyRegion>(MethodOffset.CopyRegion); int srcX1 = (int)(region.SrcXF >> 32); int srcY1 = (int)(region.SrcYF >> 32); int srcX2 = (int)((region.SrcXF + region.SrcWidthRF * region.DstWidth) >> 32); int srcY2 = (int)((region.SrcYF + region.SrcHeightRF * region.DstHeight) >> 32); int dstX1 = region.DstX; int dstY1 = region.DstY; int dstX2 = region.DstX + region.DstWidth; int dstY2 = region.DstY + region.DstHeight; Extents2D srcRegion = new Extents2D( srcX1 / srcTexture.Info.SamplesInX, srcY1 / srcTexture.Info.SamplesInY, srcX2 / srcTexture.Info.SamplesInX, srcY2 / srcTexture.Info.SamplesInY); Extents2D dstRegion = new Extents2D( dstX1 / dstTexture.Info.SamplesInX, dstY1 / dstTexture.Info.SamplesInY, dstX2 / dstTexture.Info.SamplesInX, dstY2 / dstTexture.Info.SamplesInY); bool linearFilter = control.UnpackLinearFilter(); srcTexture.HostTexture.CopyTo(dstTexture.HostTexture, srcRegion, dstRegion, linearFilter); // For an out of bounds copy, we must ensure that the copy wraps to the next line, // so for a copy from a 64x64 texture, in the region [32, 96[, there are 32 pixels that are // outside the bounds of the texture. We fill the destination with the first 32 pixels // of the next line on the source texture. // This can be emulated with 2 copies (the first copy handles the region inside the bounds, // the second handles the region outside of the bounds). // We must also extend the source texture by one line to ensure we can wrap on the last line. // This is required by the (guest) OpenGL driver. if (srcRegion.X2 > srcTexture.Info.Width) { srcCopyTexture.Height++; srcTexture = TextureManager.FindOrCreateTexture(srcCopyTexture); srcRegion = new Extents2D( srcRegion.X1 - srcTexture.Info.Width, srcRegion.Y1 + 1, srcRegion.X2 - srcTexture.Info.Width, srcRegion.Y2 + 1); srcTexture.HostTexture.CopyTo(dstTexture.HostTexture, srcRegion, dstRegion, linearFilter); } dstTexture.SignalModified(); }
/// <summary> /// Updates render targets (color and depth-stencil buffers) based on current render target state. /// </summary> /// <param name="state">Current GPU state</param> /// <param name="useControl">Use draw buffers information from render target control register</param> /// <param name="singleUse">If this is not -1, it indicates that only the given indexed target will be used.</param> private void UpdateRenderTargetState(GpuState state, bool useControl, int singleUse = -1) { var memoryManager = state.Channel.MemoryManager; var rtControl = state.Get <RtControl>(MethodOffset.RtControl); int count = useControl ? rtControl.UnpackCount() : Constants.TotalRenderTargets; var msaaMode = state.Get <TextureMsaaMode>(MethodOffset.RtMsaaMode); int samplesInX = msaaMode.SamplesInX(); int samplesInY = msaaMode.SamplesInY(); var scissor = state.Get <ScreenScissorState>(MethodOffset.ScreenScissorState); Size sizeHint = new Size(scissor.X + scissor.Width, scissor.Y + scissor.Height, 1); bool changedScale = false; for (int index = 0; index < Constants.TotalRenderTargets; index++) { int rtIndex = useControl ? rtControl.UnpackPermutationIndex(index) : index; var colorState = state.Get <RtColorState>(MethodOffset.RtColorState, rtIndex); if (index >= count || !IsRtEnabled(colorState)) { changedScale |= state.Channel.TextureManager.SetRenderTargetColor(index, null); continue; } Texture color = memoryManager.Physical.TextureCache.FindOrCreateTexture( memoryManager, colorState, samplesInX, samplesInY, sizeHint); changedScale |= state.Channel.TextureManager.SetRenderTargetColor(index, color); } bool dsEnable = state.Get <Boolean32>(MethodOffset.RtDepthStencilEnable); Texture depthStencil = null; if (dsEnable) { var dsState = state.Get <RtDepthStencilState>(MethodOffset.RtDepthStencilState); var dsSize = state.Get <Size3D>(MethodOffset.RtDepthStencilSize); depthStencil = memoryManager.Physical.TextureCache.FindOrCreateTexture( memoryManager, dsState, dsSize, samplesInX, samplesInY, sizeHint); } changedScale |= state.Channel.TextureManager.SetRenderTargetDepthStencil(depthStencil); if (changedScale) { state.Channel.TextureManager.UpdateRenderTargetScale(singleUse); _context.Renderer.Pipeline.SetRenderTargetScale(state.Channel.TextureManager.RenderTargetScale); UpdateViewportTransform(state); UpdateScissorState(state); } }
/// <summary> /// Performs a buffer to buffer, or buffer to texture copy. /// </summary> /// <param name="state">Current GPU state</param> /// <param name="argument">Method call argument</param> private void CopyBuffer(GpuState state, int argument) { var cbp = state.Get <CopyBufferParams>(MethodOffset.CopyBufferParams); var swizzle = state.Get <CopyBufferSwizzle>(MethodOffset.CopyBufferSwizzle); bool srcLinear = (argument & (1 << 7)) != 0; bool dstLinear = (argument & (1 << 8)) != 0; bool copy2D = (argument & (1 << 9)) != 0; int size = cbp.XCount; if (size == 0) { return; } if (copy2D) { // Buffer to texture copy. int srcBpp = swizzle.UnpackSrcComponentsCount() * swizzle.UnpackComponentSize(); int dstBpp = swizzle.UnpackDstComponentsCount() * swizzle.UnpackComponentSize(); var dst = state.Get <CopyBufferTexture>(MethodOffset.CopyBufferDstTexture); var src = state.Get <CopyBufferTexture>(MethodOffset.CopyBufferSrcTexture); var srcCalculator = new OffsetCalculator( src.Width, src.Height, cbp.SrcStride, srcLinear, src.MemoryLayout.UnpackGobBlocksInY(), src.MemoryLayout.UnpackGobBlocksInZ(), srcBpp); var dstCalculator = new OffsetCalculator( dst.Width, dst.Height, cbp.DstStride, dstLinear, dst.MemoryLayout.UnpackGobBlocksInY(), dst.MemoryLayout.UnpackGobBlocksInZ(), dstBpp); ulong srcBaseAddress = _context.MemoryManager.Translate(cbp.SrcAddress.Pack()); ulong dstBaseAddress = _context.MemoryManager.Translate(cbp.DstAddress.Pack()); (int srcBaseOffset, int srcSize) = srcCalculator.GetRectangleRange(src.RegionX, src.RegionY, cbp.XCount, cbp.YCount); (int dstBaseOffset, int dstSize) = dstCalculator.GetRectangleRange(dst.RegionX, dst.RegionY, cbp.XCount, cbp.YCount); ReadOnlySpan <byte> srcSpan = _context.PhysicalMemory.GetSpan(srcBaseAddress + (ulong)srcBaseOffset, srcSize); Span <byte> dstSpan = _context.PhysicalMemory.GetSpan(dstBaseAddress + (ulong)dstBaseOffset, dstSize).ToArray(); bool completeSource = src.RegionX == 0 && src.RegionY == 0 && src.Width == cbp.XCount && src.Height == cbp.YCount; bool completeDest = dst.RegionX == 0 && dst.RegionY == 0 && dst.Width == cbp.XCount && dst.Height == cbp.YCount; if (completeSource && completeDest && srcCalculator.LayoutMatches(dstCalculator)) { srcSpan.CopyTo(dstSpan); // No layout conversion has to be performed, just copy the data entirely. } else { unsafe bool Convert <T>(Span <byte> dstSpan, ReadOnlySpan <byte> srcSpan) where T : unmanaged { fixed(byte *dstPtr = dstSpan, srcPtr = srcSpan) { byte *dstBase = dstPtr - dstBaseOffset; // Layout offset is relative to the base, so we need to subtract the span's offset. byte *srcBase = srcPtr - srcBaseOffset; for (int y = 0; y < cbp.YCount; y++) { srcCalculator.SetY(src.RegionY + y); dstCalculator.SetY(dst.RegionY + y); for (int x = 0; x < cbp.XCount; x++) { int srcOffset = srcCalculator.GetOffset(src.RegionX + x); int dstOffset = dstCalculator.GetOffset(dst.RegionX + x); *(T *)(dstBase + dstOffset) = *(T *)(srcBase + srcOffset); } } } return(true); } bool _ = srcBpp switch { 1 => Convert <byte>(dstSpan, srcSpan), 2 => Convert <ushort>(dstSpan, srcSpan), 4 => Convert <uint>(dstSpan, srcSpan), 8 => Convert <ulong>(dstSpan, srcSpan), 12 => Convert <Bpp12Pixel>(dstSpan, srcSpan), 16 => Convert <Vector128 <byte> >(dstSpan, srcSpan), _ => throw new NotSupportedException($"Unable to copy ${srcBpp} bpp pixel format.") }; } _context.PhysicalMemory.Write(dstBaseAddress + (ulong)dstBaseOffset, dstSpan); } else { // Buffer to buffer copy. BufferManager.CopyBuffer(cbp.SrcAddress, cbp.DstAddress, (uint)size); } }
/// <summary> /// Updates host viewport transform and clipping state based on current GPU state. /// </summary> /// <param name="state">Current GPU state</param> private void UpdateViewportTransform(GpuState state) { DepthMode depthMode = state.Get <DepthMode>(MethodOffset.DepthMode); _context.Renderer.Pipeline.SetDepthMode(depthMode); YControl yControl = state.Get <YControl>(MethodOffset.YControl); bool flipY = yControl.HasFlag(YControl.NegateY); Origin origin = yControl.HasFlag(YControl.TriangleRastFlip) ? Origin.LowerLeft : Origin.UpperLeft; _context.Renderer.Pipeline.SetOrigin(origin); // The triangle rast flip flag only affects rasterization, the viewport is not flipped. // Setting the origin mode to upper left on the host, however, not only affects rasterization, // but also flips the viewport. // We negate the effects of flipping the viewport by flipping it again using the viewport swizzle. if (origin == Origin.UpperLeft) { flipY = !flipY; } Span <Viewport> viewports = stackalloc Viewport[Constants.TotalViewports]; for (int index = 0; index < Constants.TotalViewports; index++) { var transform = state.Get <ViewportTransform>(MethodOffset.ViewportTransform, index); var extents = state.Get <ViewportExtents> (MethodOffset.ViewportExtents, index); float x = transform.TranslateX - MathF.Abs(transform.ScaleX); float y = transform.TranslateY - MathF.Abs(transform.ScaleY); float width = MathF.Abs(transform.ScaleX) * 2; float height = MathF.Abs(transform.ScaleY) * 2; float scale = TextureManager.RenderTargetScale; if (scale != 1f) { x *= scale; y *= scale; width *= scale; height *= scale; } RectangleF region = new RectangleF(x, y, width, height); ViewportSwizzle swizzleX = transform.UnpackSwizzleX(); ViewportSwizzle swizzleY = transform.UnpackSwizzleY(); ViewportSwizzle swizzleZ = transform.UnpackSwizzleZ(); ViewportSwizzle swizzleW = transform.UnpackSwizzleW(); if (transform.ScaleX < 0) { swizzleX ^= ViewportSwizzle.NegativeFlag; } if (flipY) { swizzleY ^= ViewportSwizzle.NegativeFlag; } if (transform.ScaleY < 0) { swizzleY ^= ViewportSwizzle.NegativeFlag; } if (transform.ScaleZ < 0) { swizzleZ ^= ViewportSwizzle.NegativeFlag; } viewports[index] = new Viewport( region, swizzleX, swizzleY, swizzleZ, swizzleW, extents.DepthNear, extents.DepthFar); } _context.Renderer.Pipeline.SetViewports(0, viewports); }
/// <summary> /// Dispatches compute work. /// </summary> /// <param name="state">Current GPU state</param> /// <param name="argument">Method call argument</param> public void Dispatch(GpuState state, int argument) { uint dispatchParamsAddress = (uint)state.Get <int>(MethodOffset.DispatchParamsAddress); var dispatchParams = _context.MemoryAccessor.Read <ComputeParams>((ulong)dispatchParamsAddress << 8); GpuVa shaderBaseAddress = state.Get <GpuVa>(MethodOffset.ShaderBaseAddress); ulong shaderGpuVa = shaderBaseAddress.Pack() + (uint)dispatchParams.ShaderOffset; // Note: A size of 0 is also invalid, the size must be at least 1. int sharedMemorySize = Math.Clamp(dispatchParams.SharedMemorySize & 0xffff, 1, _context.Capabilities.MaximumComputeSharedMemorySize); ComputeShader cs = ShaderCache.GetComputeShader( shaderGpuVa, sharedMemorySize, dispatchParams.UnpackBlockSizeX(), dispatchParams.UnpackBlockSizeY(), dispatchParams.UnpackBlockSizeZ()); _context.Renderer.Pipeline.SetProgram(cs.HostProgram); var samplerPool = state.Get <PoolState>(MethodOffset.SamplerPoolState); TextureManager.SetComputeSamplerPool(samplerPool.Address.Pack(), samplerPool.MaximumId, dispatchParams.SamplerIndex); var texturePool = state.Get <PoolState>(MethodOffset.TexturePoolState); TextureManager.SetComputeTexturePool(texturePool.Address.Pack(), texturePool.MaximumId); TextureManager.SetComputeTextureBufferIndex(state.Get <int>(MethodOffset.TextureBufferIndex)); ShaderProgramInfo info = cs.Shader.Program.Info; uint sbEnableMask = 0; uint ubEnableMask = dispatchParams.UnpackUniformBuffersEnableMask(); for (int index = 0; index < dispatchParams.UniformBuffers.Length; index++) { if ((ubEnableMask & (1 << index)) == 0) { continue; } ulong gpuVa = dispatchParams.UniformBuffers[index].PackAddress(); ulong size = dispatchParams.UniformBuffers[index].UnpackSize(); BufferManager.SetComputeUniformBuffer(index, gpuVa, size); } for (int index = 0; index < info.SBuffers.Count; index++) { BufferDescriptor sb = info.SBuffers[index]; sbEnableMask |= 1u << sb.Slot; ulong sbDescAddress = BufferManager.GetComputeUniformBufferAddress(0); int sbDescOffset = 0x310 + sb.Slot * 0x10; sbDescAddress += (ulong)sbDescOffset; ReadOnlySpan <byte> sbDescriptorData = _context.PhysicalMemory.GetSpan(sbDescAddress, 0x10); SbDescriptor sbDescriptor = MemoryMarshal.Cast <byte, SbDescriptor>(sbDescriptorData)[0]; BufferManager.SetComputeStorageBuffer(sb.Slot, sbDescriptor.PackAddress(), (uint)sbDescriptor.Size); } ubEnableMask = 0; for (int index = 0; index < info.CBuffers.Count; index++) { ubEnableMask |= 1u << info.CBuffers[index].Slot; } BufferManager.SetComputeStorageBufferEnableMask(sbEnableMask); BufferManager.SetComputeUniformBufferEnableMask(ubEnableMask); var textureBindings = new TextureBindingInfo[info.Textures.Count]; for (int index = 0; index < info.Textures.Count; index++) { var descriptor = info.Textures[index]; Target target = GetTarget(descriptor.Type); if (descriptor.IsBindless) { textureBindings[index] = new TextureBindingInfo(target, descriptor.CbufOffset, descriptor.CbufSlot); } else { textureBindings[index] = new TextureBindingInfo(target, descriptor.HandleIndex); } } TextureManager.SetComputeTextures(textureBindings); var imageBindings = new TextureBindingInfo[info.Images.Count]; for (int index = 0; index < info.Images.Count; index++) { var descriptor = info.Images[index]; Target target = GetTarget(descriptor.Type); imageBindings[index] = new TextureBindingInfo(target, descriptor.HandleIndex); } TextureManager.SetComputeImages(imageBindings); BufferManager.CommitComputeBindings(); TextureManager.CommitComputeBindings(); _context.Renderer.Pipeline.DispatchCompute( dispatchParams.UnpackGridSizeX(), dispatchParams.UnpackGridSizeY(), dispatchParams.UnpackGridSizeZ()); UpdateShaderState(state); }
/// <summary> /// Performs a texture to texture copy. /// </summary> /// <param name="state">Current GPU state</param> /// <param name="argument">Method call argument</param> private void CopyTexture(GpuState state, int argument) { var dstCopyTexture = state.Get <CopyTexture>(MethodOffset.CopyDstTexture); var srcCopyTexture = state.Get <CopyTexture>(MethodOffset.CopySrcTexture); var region = state.Get <CopyRegion>(MethodOffset.CopyRegion); var control = state.Get <CopyTextureControl>(MethodOffset.CopyTextureControl); int srcX1 = (int)(region.SrcXF >> 32); int srcY1 = (int)(region.SrcYF >> 32); int srcX2 = (int)((region.SrcXF + region.SrcWidthRF * region.DstWidth) >> 32); int srcY2 = (int)((region.SrcYF + region.SrcHeightRF * region.DstHeight) >> 32); int dstX1 = region.DstX; int dstY1 = region.DstY; int dstX2 = region.DstX + region.DstWidth; int dstY2 = region.DstY + region.DstHeight; // The source and destination textures should at least be as big as the region being requested. // The hints will only resize within alignment constraints, so out of bound copies won't resize in most cases. var srcHint = new Size(srcX2, srcY2, 1); var dstHint = new Size(dstX2, dstY2, 1); var srcCopyTextureFormat = srcCopyTexture.Format.Convert(); Texture srcTexture = TextureManager.FindOrCreateTexture(srcCopyTexture, srcCopyTextureFormat, true, srcHint); if (srcTexture == null) { return; } // When the source texture that was found has a depth format, // we must enforce the target texture also has a depth format, // as copies between depth and color formats are not allowed. FormatInfo dstCopyTextureFormat; if (srcTexture.Format.IsDepthOrStencil()) { dstCopyTextureFormat = srcCopyTextureFormat; } else { dstCopyTextureFormat = dstCopyTexture.Format.Convert(); } Texture dstTexture = TextureManager.FindOrCreateTexture(dstCopyTexture, dstCopyTextureFormat, srcTexture.ScaleMode == TextureScaleMode.Scaled, dstHint); if (dstTexture == null) { return; } if (srcTexture.ScaleFactor != dstTexture.ScaleFactor) { srcTexture.PropagateScale(dstTexture); } float scale = srcTexture.ScaleFactor; // src and dest scales are identical now. Extents2D srcRegion = new Extents2D( (int)Math.Ceiling(scale * (srcX1 / srcTexture.Info.SamplesInX)), (int)Math.Ceiling(scale * (srcY1 / srcTexture.Info.SamplesInY)), (int)Math.Ceiling(scale * (srcX2 / srcTexture.Info.SamplesInX)), (int)Math.Ceiling(scale * (srcY2 / srcTexture.Info.SamplesInY))); Extents2D dstRegion = new Extents2D( (int)Math.Ceiling(scale * (dstX1 / dstTexture.Info.SamplesInX)), (int)Math.Ceiling(scale * (dstY1 / dstTexture.Info.SamplesInY)), (int)Math.Ceiling(scale * (dstX2 / dstTexture.Info.SamplesInX)), (int)Math.Ceiling(scale * (dstY2 / dstTexture.Info.SamplesInY))); bool linearFilter = control.UnpackLinearFilter(); srcTexture.HostTexture.CopyTo(dstTexture.HostTexture, srcRegion, dstRegion, linearFilter); // For an out of bounds copy, we must ensure that the copy wraps to the next line, // so for a copy from a 64x64 texture, in the region [32, 96[, there are 32 pixels that are // outside the bounds of the texture. We fill the destination with the first 32 pixels // of the next line on the source texture. // This can be emulated with 2 copies (the first copy handles the region inside the bounds, // the second handles the region outside of the bounds). // We must also extend the source texture by one line to ensure we can wrap on the last line. // This is required by the (guest) OpenGL driver. if (srcX2 / srcTexture.Info.SamplesInX > srcTexture.Info.Width) { srcCopyTexture.Height++; srcTexture = TextureManager.FindOrCreateTexture(srcCopyTexture, srcCopyTextureFormat, srcTexture.ScaleMode == TextureScaleMode.Scaled, srcHint); if (srcTexture.ScaleFactor != dstTexture.ScaleFactor) { srcTexture.PropagateScale(dstTexture); } srcRegion = new Extents2D( (int)Math.Ceiling(scale * ((srcX1 / srcTexture.Info.SamplesInX) - srcTexture.Info.Width)), (int)Math.Ceiling(scale * ((srcY1 / srcTexture.Info.SamplesInY) + 1)), (int)Math.Ceiling(scale * ((srcX2 / srcTexture.Info.SamplesInX) - srcTexture.Info.Width)), (int)Math.Ceiling(scale * ((srcY2 / srcTexture.Info.SamplesInY) + 1))); srcTexture.HostTexture.CopyTo(dstTexture.HostTexture, srcRegion, dstRegion, linearFilter); } dstTexture.SignalModified(); }
/// <summary> /// Performs a texture to texture copy. /// </summary> /// <param name="state">Current GPU state</param> /// <param name="argument">Method call argument</param> private void CopyTexture(GpuState state, int argument) { var dstCopyTexture = state.Get <CopyTexture>(MethodOffset.CopyDstTexture); var srcCopyTexture = state.Get <CopyTexture>(MethodOffset.CopySrcTexture); var region = state.Get <CopyRegion>(MethodOffset.CopyRegion); var control = state.Get <CopyTextureControl>(MethodOffset.CopyTextureControl); int srcX1 = (int)(region.SrcXF >> 32); int srcY1 = (int)(region.SrcYF >> 32); int srcX2 = (int)((region.SrcXF + region.SrcWidthRF * region.DstWidth) >> 32); int srcY2 = (int)((region.SrcYF + region.SrcHeightRF * region.DstHeight) >> 32); int dstX1 = region.DstX; int dstY1 = region.DstY; int dstX2 = region.DstX + region.DstWidth; int dstY2 = region.DstY + region.DstHeight; // The source and destination textures should at least be as big as the region being requested. // The hints will only resize within alignment constraints, so out of bound copies won't resize in most cases. var srcHint = new Size(srcX2, srcY2, 1); var dstHint = new Size(dstX2, dstY2, 1); var srcCopyTextureFormat = srcCopyTexture.Format.Convert(); int srcWidthAligned = srcCopyTexture.Stride / srcCopyTextureFormat.BytesPerPixel; ulong offset = 0; // For an out of bounds copy, we must ensure that the copy wraps to the next line, // so for a copy from a 64x64 texture, in the region [32, 96[, there are 32 pixels that are // outside the bounds of the texture. We fill the destination with the first 32 pixels // of the next line on the source texture. // This can be done by simply adding an offset to the texture address, so that the initial // gap is skipped and the copy is inside bounds again. // This is required by the proprietary guest OpenGL driver. if (srcCopyTexture.LinearLayout && srcCopyTexture.Width == srcX2 && srcX2 > srcWidthAligned && srcX1 > 0) { offset = (ulong)(srcX1 * srcCopyTextureFormat.BytesPerPixel); srcCopyTexture.Width -= srcX1; srcX2 -= srcX1; srcX1 = 0; } Texture srcTexture = TextureManager.FindOrCreateTexture(srcCopyTexture, offset, srcCopyTextureFormat, true, srcHint); if (srcTexture == null) { return; } // When the source texture that was found has a depth format, // we must enforce the target texture also has a depth format, // as copies between depth and color formats are not allowed. FormatInfo dstCopyTextureFormat; if (srcTexture.Format.IsDepthOrStencil()) { dstCopyTextureFormat = srcTexture.Info.FormatInfo; } else { dstCopyTextureFormat = dstCopyTexture.Format.Convert(); } Texture dstTexture = TextureManager.FindOrCreateTexture(dstCopyTexture, 0, dstCopyTextureFormat, srcTexture.ScaleMode == TextureScaleMode.Scaled, dstHint); if (dstTexture == null) { return; } float scale = srcTexture.ScaleFactor; float dstScale = dstTexture.ScaleFactor; Extents2D srcRegion = new Extents2D( (int)Math.Ceiling(scale * (srcX1 / srcTexture.Info.SamplesInX)), (int)Math.Ceiling(scale * (srcY1 / srcTexture.Info.SamplesInY)), (int)Math.Ceiling(scale * (srcX2 / srcTexture.Info.SamplesInX)), (int)Math.Ceiling(scale * (srcY2 / srcTexture.Info.SamplesInY))); Extents2D dstRegion = new Extents2D( (int)Math.Ceiling(dstScale * (dstX1 / dstTexture.Info.SamplesInX)), (int)Math.Ceiling(dstScale * (dstY1 / dstTexture.Info.SamplesInY)), (int)Math.Ceiling(dstScale * (dstX2 / dstTexture.Info.SamplesInX)), (int)Math.Ceiling(dstScale * (dstY2 / dstTexture.Info.SamplesInY))); bool linearFilter = control.UnpackLinearFilter(); srcTexture.HostTexture.CopyTo(dstTexture.HostTexture, srcRegion, dstRegion, linearFilter); dstTexture.SignalModified(); }
/// <summary> /// Finishes the draw call. /// This draws geometry on the bound buffers based on the current GPU state. /// </summary> /// <param name="state">Current GPU state</param> /// <param name="firstIndex">Index of the first index buffer element used on the draw</param> /// <param name="indexCount">Number of index buffer elements used on the draw</param> private void DrawEnd(GpuState state, int firstIndex, int indexCount) { ConditionalRenderEnabled renderEnable = GetRenderEnable(state); if (renderEnable == ConditionalRenderEnabled.False || _instancedDrawPending) { if (renderEnable == ConditionalRenderEnabled.False) { PerformDeferredDraws(); } _drawIndexed = false; if (renderEnable == ConditionalRenderEnabled.Host) { _context.Renderer.Pipeline.EndHostConditionalRendering(); } return; } UpdateState(state, firstIndex, indexCount); bool instanced = _vsUsesInstanceId || _isAnyVbInstanced; if (instanced) { _instancedDrawPending = true; _instancedIndexed = _drawIndexed; _instancedFirstIndex = firstIndex; _instancedFirstVertex = state.Get <int>(MethodOffset.FirstVertex); _instancedFirstInstance = state.Get <int>(MethodOffset.FirstInstance); _instancedIndexCount = indexCount; var drawState = state.Get <VertexBufferDrawState>(MethodOffset.VertexBufferDrawState); _instancedDrawStateFirst = drawState.First; _instancedDrawStateCount = drawState.Count; _drawIndexed = false; if (renderEnable == ConditionalRenderEnabled.Host) { _context.Renderer.Pipeline.EndHostConditionalRendering(); } return; } int firstInstance = state.Get <int>(MethodOffset.FirstInstance); int inlineIndexCount = _ibStreamer.GetAndResetInlineIndexCount(); if (inlineIndexCount != 0) { int firstVertex = state.Get <int>(MethodOffset.FirstVertex); BufferRange br = new BufferRange(_ibStreamer.GetInlineIndexBuffer(), 0, inlineIndexCount * 4); state.Channel.BufferManager.SetIndexBuffer(br, IndexType.UInt); _context.Renderer.Pipeline.DrawIndexed( inlineIndexCount, 1, firstIndex, firstVertex, firstInstance); } else if (_drawIndexed) { int firstVertex = state.Get <int>(MethodOffset.FirstVertex); _context.Renderer.Pipeline.DrawIndexed( indexCount, 1, firstIndex, firstVertex, firstInstance); } else { var drawState = state.Get <VertexBufferDrawState>(MethodOffset.VertexBufferDrawState); _context.Renderer.Pipeline.Draw( drawState.Count, 1, drawState.First, firstInstance); } _drawIndexed = false; if (renderEnable == ConditionalRenderEnabled.Host) { _context.Renderer.Pipeline.EndHostConditionalRendering(); } }
/// <summary> /// Updates host depth clamp state based on current GPU state. /// </summary> /// <param name="state">Current GPU state</param> private void UpdateDepthClampState(GpuState state) { ViewVolumeClipControl clip = state.Get <ViewVolumeClipControl>(MethodOffset.ViewVolumeClipControl); _context.Renderer.Pipeline.SetDepthClamp((clip & ViewVolumeClipControl.DepthClampDisabled) == 0); }
/// <summary> /// Dispatches compute work. /// </summary> /// <param name="state">Current GPU state</param> /// <param name="argument">Method call argument</param> public void Dispatch(GpuState state, int argument) { uint qmdAddress = (uint)state.Get <int>(MethodOffset.DispatchParamsAddress); var qmd = _context.MemoryManager.Read <ComputeQmd>((ulong)qmdAddress << 8); GpuVa shaderBaseAddress = state.Get <GpuVa>(MethodOffset.ShaderBaseAddress); ulong shaderGpuVa = shaderBaseAddress.Pack() + (uint)qmd.ProgramOffset; int localMemorySize = qmd.ShaderLocalMemoryLowSize + qmd.ShaderLocalMemoryHighSize; int sharedMemorySize = Math.Min(qmd.SharedMemorySize, _context.Capabilities.MaximumComputeSharedMemorySize); for (int index = 0; index < Constants.TotalCpUniformBuffers; index++) { if (!qmd.ConstantBufferValid(index)) { continue; } ulong gpuVa = (uint)qmd.ConstantBufferAddrLower(index) | (ulong)qmd.ConstantBufferAddrUpper(index) << 32; ulong size = (ulong)qmd.ConstantBufferSize(index); BufferManager.SetComputeUniformBuffer(index, gpuVa, size); } ShaderBundle cs = ShaderCache.GetComputeShader( state, shaderGpuVa, qmd.CtaThreadDimension0, qmd.CtaThreadDimension1, qmd.CtaThreadDimension2, localMemorySize, sharedMemorySize); _context.Renderer.Pipeline.SetProgram(cs.HostProgram); var samplerPool = state.Get <PoolState>(MethodOffset.SamplerPoolState); var texturePool = state.Get <PoolState>(MethodOffset.TexturePoolState); TextureManager.SetComputeSamplerPool(samplerPool.Address.Pack(), samplerPool.MaximumId, qmd.SamplerIndex); TextureManager.SetComputeTexturePool(texturePool.Address.Pack(), texturePool.MaximumId); TextureManager.SetComputeTextureBufferIndex(state.Get <int>(MethodOffset.TextureBufferIndex)); ShaderProgramInfo info = cs.Shaders[0].Info; for (int index = 0; index < info.CBuffers.Count; index++) { BufferDescriptor cb = info.CBuffers[index]; // NVN uses the "hardware" constant buffer for anything that is less than 8, // and those are already bound above. // Anything greater than or equal to 8 uses the emulated constant buffers. // They are emulated using global memory loads. if (cb.Slot < 8) { continue; } ulong cbDescAddress = BufferManager.GetComputeUniformBufferAddress(0); int cbDescOffset = 0x260 + (cb.Slot - 8) * 0x10; cbDescAddress += (ulong)cbDescOffset; SbDescriptor cbDescriptor = _context.PhysicalMemory.Read <SbDescriptor>(cbDescAddress); BufferManager.SetComputeUniformBuffer(cb.Slot, cbDescriptor.PackAddress(), (uint)cbDescriptor.Size); } for (int index = 0; index < info.SBuffers.Count; index++) { BufferDescriptor sb = info.SBuffers[index]; ulong sbDescAddress = BufferManager.GetComputeUniformBufferAddress(0); int sbDescOffset = 0x310 + sb.Slot * 0x10; sbDescAddress += (ulong)sbDescOffset; SbDescriptor sbDescriptor = _context.PhysicalMemory.Read <SbDescriptor>(sbDescAddress); BufferManager.SetComputeStorageBuffer(sb.Slot, sbDescriptor.PackAddress(), (uint)sbDescriptor.Size, sb.Flags); } BufferManager.SetComputeStorageBufferBindings(info.SBuffers); BufferManager.SetComputeUniformBufferBindings(info.CBuffers); var textureBindings = new TextureBindingInfo[info.Textures.Count]; for (int index = 0; index < info.Textures.Count; index++) { var descriptor = info.Textures[index]; Target target = ShaderTexture.GetTarget(descriptor.Type); textureBindings[index] = new TextureBindingInfo( target, descriptor.Binding, descriptor.CbufSlot, descriptor.HandleIndex, descriptor.Flags); } TextureManager.SetComputeTextures(textureBindings); var imageBindings = new TextureBindingInfo[info.Images.Count]; for (int index = 0; index < info.Images.Count; index++) { var descriptor = info.Images[index]; Target target = ShaderTexture.GetTarget(descriptor.Type); Format format = ShaderTexture.GetFormat(descriptor.Format); imageBindings[index] = new TextureBindingInfo( target, format, descriptor.Binding, descriptor.CbufSlot, descriptor.HandleIndex, descriptor.Flags); } TextureManager.SetComputeImages(imageBindings); TextureManager.CommitComputeBindings(); BufferManager.CommitComputeBindings(); _context.Renderer.Pipeline.DispatchCompute( qmd.CtaRasterWidth, qmd.CtaRasterHeight, qmd.CtaRasterDepth); _forceShaderUpdate = true; }
/// <summary> /// Updates host viewport transform and clipping state based on current GPU state. /// </summary> /// <param name="state">Current GPU state</param> private void UpdateViewportTransform(GpuState state) { var yControl = state.Get <YControl> (MethodOffset.YControl); var face = state.Get <FaceState>(MethodOffset.FaceState); UpdateFrontFace(yControl, face.FrontFace); bool flipY = yControl.HasFlag(YControl.NegateY); Span <Viewport> viewports = stackalloc Viewport[Constants.TotalViewports]; for (int index = 0; index < Constants.TotalViewports; index++) { var transform = state.Get <ViewportTransform>(MethodOffset.ViewportTransform, index); var extents = state.Get <ViewportExtents> (MethodOffset.ViewportExtents, index); float scaleX = MathF.Abs(transform.ScaleX); float scaleY = transform.ScaleY; if (flipY) { scaleY = -scaleY; } if (!_context.Capabilities.SupportsViewportSwizzle && transform.UnpackSwizzleY() == ViewportSwizzle.NegativeY) { scaleY = -scaleY; } if (index == 0) { // Try to guess the depth mode being used on the high level API // based on current transform. // It is setup like so by said APIs: // If depth mode is ZeroToOne: // TranslateZ = Near // ScaleZ = Far - Near // If depth mode is MinusOneToOne: // TranslateZ = (Near + Far) / 2 // ScaleZ = (Far - Near) / 2 // DepthNear/Far are sorted such as that Near is always less than Far. DepthMode depthMode = extents.DepthNear != transform.TranslateZ && extents.DepthFar != transform.TranslateZ ? DepthMode.MinusOneToOne : DepthMode.ZeroToOne; _context.Renderer.Pipeline.SetDepthMode(depthMode); } float x = transform.TranslateX - scaleX; float y = transform.TranslateY - scaleY; float width = scaleX * 2; float height = scaleY * 2; float scale = state.Channel.TextureManager.RenderTargetScale; if (scale != 1f) { x *= scale; y *= scale; width *= scale; height *= scale; } RectangleF region = new RectangleF(x, y, width, height); ViewportSwizzle swizzleX = transform.UnpackSwizzleX(); ViewportSwizzle swizzleY = transform.UnpackSwizzleY(); ViewportSwizzle swizzleZ = transform.UnpackSwizzleZ(); ViewportSwizzle swizzleW = transform.UnpackSwizzleW(); float depthNear = extents.DepthNear; float depthFar = extents.DepthFar; if (transform.ScaleZ < 0) { float temp = depthNear; depthNear = depthFar; depthFar = temp; } viewports[index] = new Viewport(region, swizzleX, swizzleY, swizzleZ, swizzleW, depthNear, depthFar); } _context.Renderer.Pipeline.SetViewports(0, viewports); }
/// <summary> /// Updates host shaders based on the guest GPU state. /// </summary> /// <param name="state">Current GPU state</param> private void UpdateShaderState(GpuState state) { ShaderAddresses addresses = new ShaderAddresses(); Span <ShaderAddresses> addressesSpan = MemoryMarshal.CreateSpan(ref addresses, 1); Span <ulong> addressesArray = MemoryMarshal.Cast <ShaderAddresses, ulong>(addressesSpan); ulong baseAddress = state.Get <GpuVa>(MethodOffset.ShaderBaseAddress).Pack(); for (int index = 0; index < 6; index++) { var shader = state.Get <ShaderState>(MethodOffset.ShaderState, index); if (!shader.UnpackEnable() && index != 1) { continue; } addressesArray[index] = baseAddress + shader.Offset; } GraphicsShader gs = ShaderCache.GetGraphicsShader(state, addresses); _vsUsesInstanceId = gs.Shaders[0]?.Program.Info.UsesInstanceId ?? false; for (int stage = 0; stage < Constants.ShaderStages; stage++) { ShaderProgramInfo info = gs.Shaders[stage]?.Program.Info; _currentProgramInfo[stage] = info; if (info == null) { continue; } var textureBindings = new TextureBindingInfo[info.Textures.Count]; for (int index = 0; index < info.Textures.Count; index++) { var descriptor = info.Textures[index]; Target target = GetTarget(descriptor.Type); if (descriptor.IsBindless) { textureBindings[index] = new TextureBindingInfo(target, descriptor.CbufSlot, descriptor.CbufOffset); } else { textureBindings[index] = new TextureBindingInfo(target, descriptor.HandleIndex); } } TextureManager.SetGraphicsTextures(stage, textureBindings); var imageBindings = new TextureBindingInfo[info.Images.Count]; for (int index = 0; index < info.Images.Count; index++) { var descriptor = info.Images[index]; Target target = GetTarget(descriptor.Type); imageBindings[index] = new TextureBindingInfo(target, descriptor.HandleIndex); } TextureManager.SetGraphicsImages(stage, imageBindings); uint sbEnableMask = 0; uint ubEnableMask = 0; for (int index = 0; index < info.SBuffers.Count; index++) { sbEnableMask |= 1u << info.SBuffers[index].Slot; } for (int index = 0; index < info.CBuffers.Count; index++) { ubEnableMask |= 1u << info.CBuffers[index].Slot; } BufferManager.SetGraphicsStorageBufferEnableMask(stage, sbEnableMask); BufferManager.SetGraphicsUniformBufferEnableMask(stage, ubEnableMask); } _context.Renderer.Pipeline.SetProgram(gs.HostProgram); }
/// <summary> /// Updates host logical operation state, based on guest state. /// </summary> /// <param name="state">Current GPU state</param> public void UpdateLogicOpState(GpuState state) { LogicalOpState logicOpState = state.Get <LogicalOpState>(MethodOffset.LogicOpState); _context.Renderer.Pipeline.SetLogicOpState(logicOpState.Enable, logicOpState.LogicalOp); }
/// <summary> /// Updates host state based on the current guest GPU state. /// </summary> /// <param name="state">Guest GPU state</param> private void UpdateState(GpuState state) { bool tfEnable = state.Get <Boolean32>(MethodOffset.TfEnable); if (!tfEnable && _prevTfEnable) { _context.Renderer.Pipeline.EndTransformFeedback(); _prevTfEnable = false; } // Shaders must be the first one to be updated if modified, because // some of the other state depends on information from the currently // bound shaders. if (state.QueryModified(MethodOffset.ShaderBaseAddress, MethodOffset.ShaderState) || _forceShaderUpdate) { _forceShaderUpdate = false; UpdateShaderState(state); } if (state.QueryModified(MethodOffset.TfBufferState)) { UpdateTfBufferState(state); } if (state.QueryModified(MethodOffset.ClipDistanceEnable)) { UpdateUserClipState(state); } if (state.QueryModified(MethodOffset.RasterizeEnable)) { UpdateRasterizerState(state); } if (state.QueryModified(MethodOffset.RtColorState, MethodOffset.RtDepthStencilState, MethodOffset.RtControl, MethodOffset.RtDepthStencilSize, MethodOffset.RtDepthStencilEnable)) { UpdateRenderTargetState(state, useControl: true); } if (state.QueryModified(MethodOffset.ScissorState)) { UpdateScissorState(state); } if (state.QueryModified(MethodOffset.ViewVolumeClipControl)) { UpdateDepthClampState(state); } if (state.QueryModified(MethodOffset.DepthTestEnable, MethodOffset.DepthWriteEnable, MethodOffset.DepthTestFunc)) { UpdateDepthTestState(state); } if (state.QueryModified(MethodOffset.DepthMode, MethodOffset.ViewportTransform, MethodOffset.ViewportExtents)) { UpdateViewportTransform(state); } if (state.QueryModified(MethodOffset.DepthBiasState, MethodOffset.DepthBiasFactor, MethodOffset.DepthBiasUnits, MethodOffset.DepthBiasClamp)) { UpdateDepthBiasState(state); } if (state.QueryModified(MethodOffset.StencilBackMasks, MethodOffset.StencilTestState, MethodOffset.StencilBackTestState)) { UpdateStencilTestState(state); } // Pools. if (state.QueryModified(MethodOffset.SamplerPoolState, MethodOffset.SamplerIndex)) { UpdateSamplerPoolState(state); } if (state.QueryModified(MethodOffset.TexturePoolState)) { UpdateTexturePoolState(state); } // Input assembler state. if (state.QueryModified(MethodOffset.VertexAttribState)) { UpdateVertexAttribState(state); } if (state.QueryModified(MethodOffset.PointSize, MethodOffset.VertexProgramPointSize, MethodOffset.PointSpriteEnable, MethodOffset.PointCoordReplace)) { UpdatePointState(state); } if (state.QueryModified(MethodOffset.PrimitiveRestartState)) { UpdatePrimitiveRestartState(state); } if (state.QueryModified(MethodOffset.IndexBufferState)) { UpdateIndexBufferState(state); } if (state.QueryModified(MethodOffset.VertexBufferDrawState, MethodOffset.VertexBufferInstanced, MethodOffset.VertexBufferState, MethodOffset.VertexBufferEndAddress)) { UpdateVertexBufferState(state); } if (state.QueryModified(MethodOffset.FaceState)) { UpdateFaceState(state); } if (state.QueryModified(MethodOffset.RtColorMaskShared, MethodOffset.RtColorMask)) { UpdateRtColorMask(state); } if (state.QueryModified(MethodOffset.BlendIndependent, MethodOffset.BlendConstant, MethodOffset.BlendStateCommon, MethodOffset.BlendEnableCommon, MethodOffset.BlendEnable, MethodOffset.BlendState)) { UpdateBlendState(state); } if (state.QueryModified(MethodOffset.LogicOpState)) { UpdateLogicOpState(state); } CommitBindings(); if (tfEnable && !_prevTfEnable) { _context.Renderer.Pipeline.BeginTransformFeedback(PrimitiveType.Convert()); _prevTfEnable = true; } }
/// <summary> /// Updates host shaders based on the guest GPU state. /// </summary> /// <param name="state">Current GPU state</param> private void UpdateShaderState(GpuState state) { ShaderAddresses addresses = new ShaderAddresses(); Span <ShaderAddresses> addressesSpan = MemoryMarshal.CreateSpan(ref addresses, 1); Span <ulong> addressesArray = MemoryMarshal.Cast <ShaderAddresses, ulong>(addressesSpan); ulong baseAddress = state.Get <GpuVa>(MethodOffset.ShaderBaseAddress).Pack(); for (int index = 0; index < 6; index++) { var shader = state.Get <ShaderState>(MethodOffset.ShaderState, index); if (!shader.UnpackEnable() && index != 1) { continue; } addressesArray[index] = baseAddress + shader.Offset; } ShaderBundle gs = state.Channel.MemoryManager.Physical.ShaderCache.GetGraphicsShader(state, addresses); byte oldVsClipDistancesWritten = _vsClipDistancesWritten; _vsUsesInstanceId = gs.Shaders[0]?.Info.UsesInstanceId ?? false; _vsClipDistancesWritten = gs.Shaders[0]?.Info.ClipDistancesWritten ?? 0; if (oldVsClipDistancesWritten != _vsClipDistancesWritten) { UpdateUserClipState(state); } int storageBufferBindingsCount = 0; int uniformBufferBindingsCount = 0; for (int stage = 0; stage < Constants.ShaderStages; stage++) { ShaderProgramInfo info = gs.Shaders[stage]?.Info; _currentProgramInfo[stage] = info; if (info == null) { state.Channel.TextureManager.SetGraphicsTextures(stage, Array.Empty <TextureBindingInfo>()); state.Channel.TextureManager.SetGraphicsImages(stage, Array.Empty <TextureBindingInfo>()); state.Channel.BufferManager.SetGraphicsStorageBufferBindings(stage, null); state.Channel.BufferManager.SetGraphicsUniformBufferBindings(stage, null); continue; } var textureBindings = new TextureBindingInfo[info.Textures.Count]; for (int index = 0; index < info.Textures.Count; index++) { var descriptor = info.Textures[index]; Target target = ShaderTexture.GetTarget(descriptor.Type); textureBindings[index] = new TextureBindingInfo( target, descriptor.Binding, descriptor.CbufSlot, descriptor.HandleIndex, descriptor.Flags); } state.Channel.TextureManager.SetGraphicsTextures(stage, textureBindings); var imageBindings = new TextureBindingInfo[info.Images.Count]; for (int index = 0; index < info.Images.Count; index++) { var descriptor = info.Images[index]; Target target = ShaderTexture.GetTarget(descriptor.Type); Format format = ShaderTexture.GetFormat(descriptor.Format); imageBindings[index] = new TextureBindingInfo( target, format, descriptor.Binding, descriptor.CbufSlot, descriptor.HandleIndex, descriptor.Flags); } state.Channel.TextureManager.SetGraphicsImages(stage, imageBindings); state.Channel.BufferManager.SetGraphicsStorageBufferBindings(stage, info.SBuffers); state.Channel.BufferManager.SetGraphicsUniformBufferBindings(stage, info.CBuffers); if (info.SBuffers.Count != 0) { storageBufferBindingsCount = Math.Max(storageBufferBindingsCount, info.SBuffers.Max(x => x.Binding) + 1); } if (info.CBuffers.Count != 0) { uniformBufferBindingsCount = Math.Max(uniformBufferBindingsCount, info.CBuffers.Max(x => x.Binding) + 1); } } state.Channel.BufferManager.SetGraphicsStorageBufferBindingsCount(storageBufferBindingsCount); state.Channel.BufferManager.SetGraphicsUniformBufferBindingsCount(uniformBufferBindingsCount); _context.Renderer.Pipeline.SetProgram(gs.HostProgram); }
/// <summary> /// Updates render targets (color and depth-stencil buffers) based on current render target state. /// </summary> /// <param name="state">Current GPU state</param> /// <param name="useControl">Use draw buffers information from render target control register</param> /// <param name="singleUse">If this is not -1, it indicates that only the given indexed target will be used.</param> private void UpdateRenderTargetState(GpuState state, bool useControl, int singleUse = -1) { var rtControl = state.Get <RtControl>(MethodOffset.RtControl); int count = useControl ? rtControl.UnpackCount() : Constants.TotalRenderTargets; var msaaMode = state.Get <TextureMsaaMode>(MethodOffset.RtMsaaMode); int samplesInX = msaaMode.SamplesInX(); int samplesInY = msaaMode.SamplesInY(); bool changedScale = false; for (int index = 0; index < Constants.TotalRenderTargets; index++) { int rtIndex = useControl ? rtControl.UnpackPermutationIndex(index) : index; var colorState = state.Get <RtColorState>(MethodOffset.RtColorState, rtIndex); if (index >= count || !IsRtEnabled(colorState)) { changedScale |= TextureManager.SetRenderTargetColor(index, null); continue; } Texture color = TextureManager.FindOrCreateTexture(colorState, samplesInX, samplesInY); changedScale |= TextureManager.SetRenderTargetColor(index, color); if (color != null) { color.SignalModified(); } } bool dsEnable = state.Get <Boolean32>(MethodOffset.RtDepthStencilEnable); Texture depthStencil = null; if (dsEnable) { var dsState = state.Get <RtDepthStencilState>(MethodOffset.RtDepthStencilState); var dsSize = state.Get <Size3D> (MethodOffset.RtDepthStencilSize); depthStencil = TextureManager.FindOrCreateTexture(dsState, dsSize, samplesInX, samplesInY); } changedScale |= TextureManager.SetRenderTargetDepthStencil(depthStencil); if (changedScale) { TextureManager.UpdateRenderTargetScale(singleUse); _context.Renderer.Pipeline.SetRenderTargetScale(TextureManager.RenderTargetScale); UpdateViewportTransform(state); UpdateScissorState(state); } if (depthStencil != null) { depthStencil.SignalModified(); } }
/// <summary> /// Performs a texture to texture copy. /// </summary> /// <param name="state">Current GPU state</param> /// <param name="argument">Method call argument</param> private void CopyTexture(GpuState state, int argument) { var dstCopyTexture = state.Get <CopyTexture>(MethodOffset.CopyDstTexture); var srcCopyTexture = state.Get <CopyTexture>(MethodOffset.CopySrcTexture); Texture srcTexture = TextureManager.FindOrCreateTexture(srcCopyTexture); if (srcTexture == null) { return; } // When the source texture that was found has a depth format, // we must enforce the target texture also has a depth format, // as copies between depth and color formats are not allowed. dstCopyTexture.Format = srcTexture.Format switch { Format.S8Uint => RtFormat.S8Uint, Format.D16Unorm => RtFormat.D16Unorm, Format.D24X8Unorm => RtFormat.D24Unorm, Format.D32Float => RtFormat.D32Float, Format.D24UnormS8Uint => RtFormat.D24UnormS8Uint, Format.D32FloatS8Uint => RtFormat.D32FloatS8Uint, _ => dstCopyTexture.Format }; Texture dstTexture = TextureManager.FindOrCreateTexture(dstCopyTexture, srcTexture.ScaleMode == Image.TextureScaleMode.Scaled); if (dstTexture == null) { return; } if (srcTexture.ScaleFactor != dstTexture.ScaleFactor) { srcTexture.PropagateScale(dstTexture); } var control = state.Get <CopyTextureControl>(MethodOffset.CopyTextureControl); var region = state.Get <CopyRegion>(MethodOffset.CopyRegion); int srcX1 = (int)(region.SrcXF >> 32); int srcY1 = (int)(region.SrcYF >> 32); int srcX2 = (int)((region.SrcXF + region.SrcWidthRF * region.DstWidth) >> 32); int srcY2 = (int)((region.SrcYF + region.SrcHeightRF * region.DstHeight) >> 32); int dstX1 = region.DstX; int dstY1 = region.DstY; int dstX2 = region.DstX + region.DstWidth; int dstY2 = region.DstY + region.DstHeight; float scale = srcTexture.ScaleFactor; // src and dest scales are identical now. Extents2D srcRegion = new Extents2D( (int)Math.Ceiling(scale * (srcX1 / srcTexture.Info.SamplesInX)), (int)Math.Ceiling(scale * (srcY1 / srcTexture.Info.SamplesInY)), (int)Math.Ceiling(scale * (srcX2 / srcTexture.Info.SamplesInX)), (int)Math.Ceiling(scale * (srcY2 / srcTexture.Info.SamplesInY))); Extents2D dstRegion = new Extents2D( (int)Math.Ceiling(scale * (dstX1 / dstTexture.Info.SamplesInX)), (int)Math.Ceiling(scale * (dstY1 / dstTexture.Info.SamplesInY)), (int)Math.Ceiling(scale * (dstX2 / dstTexture.Info.SamplesInX)), (int)Math.Ceiling(scale * (dstY2 / dstTexture.Info.SamplesInY))); bool linearFilter = control.UnpackLinearFilter(); srcTexture.HostTexture.CopyTo(dstTexture.HostTexture, srcRegion, dstRegion, linearFilter); // For an out of bounds copy, we must ensure that the copy wraps to the next line, // so for a copy from a 64x64 texture, in the region [32, 96[, there are 32 pixels that are // outside the bounds of the texture. We fill the destination with the first 32 pixels // of the next line on the source texture. // This can be emulated with 2 copies (the first copy handles the region inside the bounds, // the second handles the region outside of the bounds). // We must also extend the source texture by one line to ensure we can wrap on the last line. // This is required by the (guest) OpenGL driver. if (srcX2 / srcTexture.Info.SamplesInX > srcTexture.Info.Width) { srcCopyTexture.Height++; srcTexture = TextureManager.FindOrCreateTexture(srcCopyTexture, srcTexture.ScaleMode == Image.TextureScaleMode.Scaled); if (srcTexture.ScaleFactor != dstTexture.ScaleFactor) { srcTexture.PropagateScale(dstTexture); } srcRegion = new Extents2D( (int)Math.Ceiling(scale * ((srcX1 / srcTexture.Info.SamplesInX) - srcTexture.Info.Width)), (int)Math.Ceiling(scale * ((srcY1 / srcTexture.Info.SamplesInY) + 1)), (int)Math.Ceiling(scale * ((srcX2 / srcTexture.Info.SamplesInX) - srcTexture.Info.Width)), (int)Math.Ceiling(scale * ((srcY2 / srcTexture.Info.SamplesInY) + 1))); srcTexture.HostTexture.CopyTo(dstTexture.HostTexture, srcRegion, dstRegion, linearFilter); } dstTexture.SignalModified(); } }