Directx12 chapter4

官方的初始化需要的组件

Initialize

初始化涉及到首次设置全局变量和类,initialize 函数必须准备管道和资产。

  • 初始化管道。
    • 启用调试层。
    • 创建设备。
    • 创建命令队列。
    • 创建交换链。
    • 创建渲染器目标视图 (RTV) 描述符堆。

备注

可将描述符堆视为描述符的数组。 其中的每个描述符完全描述 GPU 的对象。

    • 创建帧资源(每个帧的渲染器目标视图)。
    • 创建命令分配器。

备注

命令分配器管理命令列表和捆绑的基础存储。

  • 初始化资产。
    • 创建空的根签名。

备注

图形根签名定义哪些资源要绑定到图形管道。

    • 编译着色器。
    • 创建顶点输入布局。
    • 创建管道状态对象说明,然后创建对象。

备注

管道状态对象保留所有当前设置的着色器以及某些固定函数状态对象(例如输入汇编器、细分器、光栅器和输出合并器)的状态。

    • 创建命令列表。
    • 关闭命令列表。
    • 创建并加载顶点缓冲区。
    • 创建顶点缓冲区视图。
    • 创建围栏。

备注

围栏用于将 CPU 与 GPU 同步 (请参阅 多引擎同步) 。

    • 创建事件处理程序。
    • 等待 GPU 完成。

备注

检查围栏!

请参阅类 D3D12HelloTriangle、OnInit、LoadPipeline 和 LoadAssets。

实际编写

创建窗口

就和vulkan一样首先创建窗口

LRESULT WindowProcedure(HWND hwnd, UINT messageType, WPARAM wParam, LPARAM lParam)
{if (messageType == WM_DESTROY){PostQuitMessage(0);return 0;}return DefWindowProc(hwnd, messageType, wParam, lParam);
}void HelloTriangle::LoadPipeline()
{WNDCLASSEX winClassMessage{};winClassMessage.cbSize = sizeof(WNDCLASSEX);winClassMessage.style = CS_HREDRAW | CS_VREDRAW;winClassMessage.lpfnWndProc = WindowProcedure;winClassMessage.hInstance = GetModuleHandle(0);winClassMessage.lpszClassName = L"MainWindow";RegisterClassEx(&winClassMessage);RECT wrc{ 0,0,m_Width, m_Height };m_Window = CreateWindow(winClassMessage.lpszClassName,L"TriangleWindow", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT,wrc.right - wrc.left, wrc.bottom - wrc.top, nullptr, nullptr,winClassMessage.hInstance, nullptr);}

打印HRESULT的结果

定义了一个ThrowIfFailed去打印HRESULT的失败的结果

inline std::string HrToString(HRESULT hr)
{char s_str[64];sprintf_s(s_str, "HRESULT of 0x%08X", static_cast<UINT>(hr));return std::string(s_str);
}class HrException : public std::runtime_error
{
public:HrException(HRESULT hr) :std::runtime_error(HrToString(hr)), m_Hr(hr){}
private:const HRESULT m_Hr;
};void inline ThrowIfFailed(HRESULT hr)
{if (FAILED(hr)){throw HrException(hr);}
}

创建验证层

COM 对象通过接口来提供服务,当获取一个 COM 接口指针时,需要正确地传递接口的标识符(IID - Interface Identifier)以及用于接收指针的变量地址,并且要考虑接口对象的引用计数等问题。IID_PPV_ARGS是一个宏,它的主要作用是在获取 COM 接口时,正确地传递接口的全局唯一标识符(IID)和用于接收接口指针的指针(PPV - Pointer to Pointer of a Variable)。

#if defined(_DEBUG)
{ComPtr<ID3D12Debug> debugController;if (SUCCEEDED(D3D12GetDebugInterface(IID_PPV_ARGS(&debugController)))){debugController->EnableDebugLayer();}
}
#endif

创建factory

ComPtr<IDXGIFactory4> factory;
ThrowIfFailed(CreateDXGIFactory1(IID_PPV_ARGS(&factory)));

创建设备

这里的nullptr是使用默认的显示设备

HRESULT hardwareResult = D3D12CreateDevice(nullptr,D3D_FEATURE_LEVEL_11_0,IID_PPV_ARGS(&m_Device)
);

如果返回值是filed则使用warp设备

if (FAILED(hardwareResult))
{ComPtr<IDXGIAdapter> pWrapAdater;ThrowIfFailed(factory->EnumWarpAdapter(IID_PPV_ARGS(&pWrapAdater)));ThrowIfFailed(D3D12CreateDevice(pWrapAdater.Get(),D3D_FEATURE_LEVEL_11_0,IID_PPV_ARGS(&m_Device)));
}

创建命令队列

D3D12_COMMAND_QUEUE_DESC queueDesc = {};
queueDesc.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE;
queueDesc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT;ThrowIfFailed(m_Device->CreateCommandQueue(&queueDesc, IID_PPV_ARGS(&m_CommandQueue)));

创建命令分配器和命名列表

ThrowIfFailed(m_Device->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(m_CommandAllocator.GetAddressOf())));
ThrowIfFailed(m_Device->CreateCommandList(0,D3D12_COMMAND_LIST_TYPE_DIRECT,m_CommandAllocator.Get(),nullptr,IID_PPV_ARGS(m_CommandList.GetAddressOf())));
m_CommandList->Close();

创建交换链

DXGI_SWAP_CHAIN_DESC swapChainDesc{};
swapChainDesc.BufferDesc.Width = m_Width;
swapChainDesc.BufferDesc.Height = m_Height;
swapChainDesc.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
swapChainDesc.BufferDesc.RefreshRate.Numerator = 60;   //60 / 1
swapChainDesc.BufferDesc.RefreshRate.Denominator = 1;
swapChainDesc.BufferDesc.ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED;
swapChainDesc.BufferDesc.Scaling = DXGI_MODE_SCALING_UNSPECIFIED;swapChainDesc.SampleDesc.Count = 1;
swapChainDesc.SampleDesc.Quality = 0;
swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
swapChainDesc.BufferCount = 2;
swapChainDesc.OutputWindow = m_Window;
swapChainDesc.Windowed = true;
swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD;
swapChainDesc.Flags = DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH;//attention swap Chain need command queue to reflash
ThrowIfFailed(factory->CreateSwapChain(m_CommandQueue.Get(),&swapChainDesc,&m_SwapChain));

创建描述符堆

D3D12中GPU资源并不直接绑定到渲染流水线上,而是通过名为描述符(Descriptor)的中间对象来绑定。描述符是一个轻量级的数据,用来描述资源的信息,在之前版本API中,类似的概念叫视图(view),在D3D12中这个名词依然保存,和描述符是同义词。

我们可以在不同的阶段,为资源创建不同的描述符,这样资源就可以有不同的用处。比如对于一个纹理资源,我们可以先使用它的渲染目标视图(render target view,RTV),把它作为着色器渲染的目标。然后我们使用纹理的着色器资源视图(shader resource view, SRV),把它作为着色器资源用于后续的渲染,典型的一个例子就是shadowmap。

常用的描述符有:

  • CBV(constant buffer view):常量缓冲区视图
  • SRV(shader resource view):着色器资源视图
  • UAV(unorder access view):无序访问视图
  • Sampler:采样器,用于采样纹理资源
  • RTV(render target view):渲染目标视图
  • DSV(depth/stencil view):深度/模板视图

描述符堆(Descriptor Heap)

描述符堆实际上就是存放描述符的数组,本质上是存放特定类型描述符的一块内存。

描述符堆的类型

描述符堆有以下类型:

  • D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV:可以存放CBV, SRV和UAV
  • D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER: 存放sampler
  • D3D12_DESCRIPTOR_HEAP_TYPE_RTV:存放RTV
  • D3D12_DESCRIPTOR_HEAP_TYPE_DSV:存放DSV

其中CBV_SRV_UAV和SAMPLER都是可以被shader使用的数据,但却被分成两类,原因是在大多数硬件实现中,CBV_SRV_UAV和SAMPLER是分开管理的。

		D3D12_DESCRIPTOR_HEAP_DESC descriptorHeapDesc{};descriptorHeapDesc.NumDescriptors = swapChainBufferCount;descriptorHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_RTV;descriptorHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE; //shader can't usedescriptorHeapDesc.NodeMask = 0;ThrowIfFailed(m_Device->CreateDescriptorHeap(&descriptorHeapDesc, IID_PPV_ARGS(m_DescriptorHeap.GetAddressOf())));

很清楚可以看到,描述符就和资源一样(资源代言人)

创建渲染目标视图

参考链接

这里我理解,缓冲区就是存放了资源,我们需要在缓冲区和描述符之间创建联系,渲染目标视图就是描述符,我们首先需要得到对应的缓冲区资源

比如,为了将后台缓冲区绑定到流水线的输出合并阶段,我们需要为该后台缓冲区创建一个渲染目标视图。

从功能角度讲,交换链就像是一个缓冲区的管理者和调度者

步骤一 从交换链得到目标缓冲区
HRESULT IDXGISwapChain::GetBuffer(
UINT Buffer,//希望获得的特定后台缓冲区的索引。是索引!!!后台缓冲区不只有一个,所以要用索引指明。
REFIID riid,//希望获得的ID3D12Resource接口的COMID
void **ppSurface//返回一个指向ID3D12Resourse接口的指针。这便是希望获得的后台缓冲区
)
【注意,这个函数会增加相关后台缓冲区的COM引用计数,所以在每次使用后一定要将其释放。
通过ComPtr便可以自动做到这一点。】
步骤二 为获取的后台缓冲区创建 渲染目标视图。
void ID3D12Device::CreateRenderTargetView(
ID3D12Resource *pResource,//用作渲染目标的资源
const D3D12_RENDER_TARGET_VIEW_DESC *pDesc,//
D3D12_CPU_DESCRIPTOR_HANDLE DestDescriptor,
)
使用
static const int swapChainBufferCount = 2;ComPtr<ID3D12Resource> m_SwapChainBuffer[swapChainBufferCount];

根据交换链中的缓冲区的数量创建了缓冲区的资源数量,接下来我们需要将缓冲区绑定到描述符中,其实描述符的堆类型有两种,一种是gpu不可见,一种是gpu可见

我们先绑定cpu可见的gpu不可见的描述符

首先我们需要使用GetBuffer去绑定ID3D12Resourse,也就是我们的资源,我们通过

for (int i = 0; i < swapChainBufferCount; i++)
{ThrowIfFailed(m_SwapChain->GetBuffer(i, IID_PPV_ARGS(m_SwapChainBuffer[i].GetAddressOf())));
}

接下来就是绑定到我们的描述符上,在绑定之前,我们需要知道它其实是首先得到描述符堆中的首描述符然后不断的向下位移得到描述符,也就是说我们需要获得堆描述符的首地址,然后还需要知道最终能够位移多少个描述符防止过载,同时还可以拿到别的类型的描述符的数量

这里的描述符就是rtv的类型,我们只需要在设备上进行查询即可

m_RTVDescriptorMaxCount = m_Device->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_RTV);
m_CBVSRVUAVDescriptorMaxCount = m_Device->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV);
m_DSVDescriptorMaxCount = m_Device->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_DSV);CD3DX12_CPU_DESCRIPTOR_HANDLE descriptorPtr(m_DescriptorHeap->GetCPUDescriptorHandleForHeapStart());
for (UINT i = 0; i < (UINT)swapChainBufferCount; i++)
{ThrowIfFailed(m_SwapChain->GetBuffer(i, IID_PPV_ARGS(m_SwapChainBuffer[i].GetAddressOf())));m_Device->CreateRenderTargetView(m_SwapChainBuffer[i].Get(), nullptr, descriptorPtr);descriptorPtr.Offset(1, m_RTVDescriptorMaxCount);
}
  • CreateRenderTargetView函数中,第二个参数用于指定渲染目标视图(RTV)的描述信息,类型通常是D3D12_RTV_DESC结构体。这个结构体包含了诸如格式、维度等关于视图的详细信息。
  • 当你传入nullptr作为这个参数时,DirectX 12 会使用默认的设置来创建渲染目标视图。这些默认设置是基于你提供的第一个参数(资源指针,这里是m_SwapChainBuffer[i].Get())的属性来确定的。对于交换链缓冲区(m_SwapChainBuffer[i]),它本身有自己的格式和维度等属性。DirectX 12 能够自动根据交换链缓冲区的资源类型和格式来确定合适的渲染目标视图设置。
  • 例如,如果交换链缓冲区是一个标准的 2D 纹理资源,DirectX 会自动按照 2D 纹理的默认规则来创建 RTV。这包括使用交换链缓冲区本身的纹理格式(如DXGI_FORMAT_R8G8B8A8_UNORM等常见格式)作为 RTV 的格式,并且视图维度会被设置为与纹理资源对应的 2D 维度(D3D12_RTV_DIMENSION_TEXTURE2D)。

创建fence

为了实现cpu和gpu的同步,我们还需要创建fence(围栏)

		//create fenceThrowIfFailed(m_Device->CreateFence(0,D3D12_FENCE_FLAG_NONE,IID_PPV_ARGS(m_FenceOne.GetAddressOf())));

检测对4X MSAA质量级别的支持

也就是检测一下是否对4X MSAA的支持

利用Device的CheckFeatureSupport()来查看是否支持

D3D12_FEATURE_DATA_MULTISAMPLE_QUALITY_LEVELS msQualityLevels;
msQualityLevels.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
msQualityLevels.SampleCount = 4;
msQualityLevels.Flags = D3D12_MULTISAMPLE_QUALITY_LEVELS_FLAG_NONE;
msQualityLevels.NumQualityLevels = 0;ThrowIfFailed(m_Device->CheckFeatureSupport(D3D12_FEATURE_MULTISAMPLE_QUALITY_LEVELS,&msQualityLevels,sizeof(msQualityLevels)));UINT QualityLevels = msQualityLevels.NumQualityLevels;
std::cout << QualityLevels << std::endl; //1

由于这里一般都是支持的所以很多都不会写这段代码了。

创建深度/模板缓冲区及其视图

之前的buffer是资源,纹理也是一种资源,纹理是一种GPU资源,所以需要填写dec

//create depth material
D3D12_RESOURCE_DESC DepthBufferDesc{};
DepthBufferDesc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D;
DepthBufferDesc.Alignment = 0;
DepthBufferDesc.Width = m_Width;
DepthBufferDesc.Height = m_Height;
DepthBufferDesc.DepthOrArraySize = 1;
DepthBufferDesc.MipLevels = 1;
DepthBufferDesc.Format = DXGI_FORMAT_D16_UNORM;
DepthBufferDesc.SampleDesc.Count = multiSampleState ? 4 : 1;
DepthBufferDesc.SampleDesc.Quality = multiSampleState ? (QualityLevels - 1) : 0;
DepthBufferDesc.Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN;
DepthBufferDesc.Flags = D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL;

创建的深度和模板缓冲区需要设定清空的缓冲值是多少

D3D12_CLEAR_VALUE optClear;
optClear.DepthStencil.Depth = 1.0f;
optClear.DepthStencil.Stencil = 0;

针对资源通过CreateCommittedResource创建资源。

ThrowIfFailed(m_Device->CreateCommittedResource(&CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_DEFAULT),D3D12_HEAP_FLAG_NONE,&DepthBufferDesc,D3D12_RESOURCE_STATE_COMMON,&optClear,IID_PPV_ARGS(&m_DepthStencilResource)
));

有了资源,就需要绑定描述符,同时还需要将resource的state状态改为write,写状态

m_Device->CreateDepthStencilView(m_DepthStencilResource.Get(), nullptr, descriptorPtr
);
descriptorPtr.Offset(1, m_RTVDescriptorMaxCount);
m_CommandList->ResourceBarrier(1,&CD3DX12_RESOURCE_BARRIER::Transition(m_DepthStencilResource.Get(), D3D12_RESOURCE_STATE_COMMON,D3D12_RESOURCE_STATE_DEPTH_WRITE));

设置视口

D3D12_VIEWPORT vp;
vp.TopLeftX = 0.0f;
vp.TopLeftY = 0.0f;
vp.Width = m_Width;
vp.Height = m_Height;
vp.MinDepth = 0.0f;
vp.MaxDepth = 1.0f;m_CommandList->RSSetViewports(1, &vp);

设置裁剪视口

其实也就是裁剪测试中,允许程序员设定的一个视口,这个视口外的不会渲染

D3D12_RECT m_ScissorRect;
m_ScissorRect.left = 0;
m_ScissorRect.right = m_Width;
m_ScissorRect.bottom = m_Height;
m_ScissorRect.top = 0;

计时与动画

这里提到了一个方法去计算时间的间隔,也就是后期用来性能测试的

首先在程序中是计数的,但是多少秒每个数不知道,所以需要计算

void calculateTime()
{_int64 CurrentTime;QueryPerformanceCounter((LARGE_INTEGER*)&CurrentTime);_int64 countPerSec;QueryPerformanceFrequency((LARGE_INTEGER*)&countPerSec);double secPerCount = 1.0 / countPerSec;std::cout << secPerCount << std::endl;
}

通过QueryPerformanceCounter可以拿到当前的计数, QueryPerformanceFrequency是多少个计数每秒, 1.0 / countPerSec, 得到多少秒每个计数,然后有了这个就可以通过计算相差的计数个数进行计算间距的时间

InitDirect3DApp代码解读,和渲染流程

这个书中的意思就是基础的框架,继承自D3DApp,这个后面在分析

对以下四个方法进行复写,其中Update和Draw是一定要复写的内容,其它的方法复写是选择性的

  • HINSTANCE是一个 Windows 编程中的数据类型。它代表一个实例句柄(Handle to an Instance)。在 Windows 操作系统中,当一个程序被加载到内存中运行时,系统会为这个程序的每个实例分配一个唯一的句柄,这个句柄就是HINSTANCE类型。
  • 它主要用于标识应用程序或动态链接库(DLL)在内存中的实例。例如,当你启动多个副本(实例) of a Windows 应用程序时,每个实例都有自己的HINSTANCE值。
class InitDirect3DApp : public D3DApp
{
public:InitDirect3DApp(HINSTANCE hInstance);~InitDirect3DApp();virtual bool Initialize()override;private:virtual void OnResize()override;virtual void Update(const GameTimer& gt)override;virtual void Draw(const GameTimer& gt)override;};

这里回在父类中去创建这个D3DApp的类,同时保证只有一个实例被创建

D3DApp::D3DApp(HINSTANCE hInstance)
:	mhAppInst(hInstance)
{// Only one D3DApp can be constructed.assert(mApp == nullptr);mApp = this;
}

初始化

bool InitDirect3DApp::Initialize()
{if(!D3DApp::Initialize())return false;return true;
}
bool D3DApp::Initialize()
{if(!InitMainWindow())return false;if(!InitDirect3D())return false;// Do the initial resize code.OnResize();return true;
}

初始化window

涉及到的就是初始化窗口的类,注册这个窗口,创建窗口

bool D3DApp::InitMainWindow()
{WNDCLASS wndClassEx{};wndClassEx.style = CS_HREDRAW | CS_VREDRAW;wndClassEx.lpfnWndProc = MainWndProc;wndClassEx.cbClsExtra = 0;wndClassEx.cbWndExtra = 0;wndClassEx.hInstance - m_AppInstance;wndClassEx.hIcon = LoadIcon(0, IDI_APPLICATION);wndClassEx.hCursor = LoadCursor(0, IDC_ARROW);wndClassEx.hbrBackground = (HBRUSH)GetStockObject(NULL_BRUSH);wndClassEx.lpszMenuName = 0;wndClassEx.lpszClassName = L"MainWindow";if (!RegisterClass(&wndClassEx)){MessageBox(0, L"RegisterClass Failed", 0, 0);return false;}RECT R = { 0, 0, m_ClientWidth, m_ClientHeight };AdjustWindowRect(&R, WS_OVERLAPPEDWINDOW, false); //adjust the window rectint width = R.right - R.left;int height = R.bottom - R.top;m_Window = CreateWindow(wndClassEx.lpszClassName, L"FirstProgram", WS_OVERLAPPEDWINDOW,CW_USEDEFAULT, CW_USEDEFAULT, width, height, 0, 0, m_AppInstance, 0);if (!m_Window){MessageBox(0, L"CreateWindow Failed", 0, 0);return false;}ShowWindow(m_Window, SW_SHOW);UpdateWindow(m_Window);return true;
}

初始化direcxt3D

然后就是初始化direcxt3D

bool D3DApp::InitDirectx3D()
{#if defined(DEBUG) || defined(_DEBUG){ComPtr<ID3D12Debug> debug;ThrowIfFailed(D3D12GetDebugInterface(IID_PPV_ARGS(debug.GetAddressOf())));debug->EnableDebugLayer();}#endifThrowIfFailed(CreateDXGIFactory1(IID_PPV_ARGS(m_Factory.GetAddressOf())));HRESULT isokDevice = D3D12CreateDevice(nullptr, D3D_FEATURE_LEVEL_11_0, IID_PPV_ARGS(m_Device.GetAddressOf()));if (FAILED(isokDevice)){ComPtr<IDXGIAdapter> softAdapter;ThrowIfFailed(m_Factory->EnumWarpAdapter(IID_PPV_ARGS(softAdapter.GetAddressOf())));ThrowIfFailed(D3D12CreateDevice(softAdapter.Get(), D3D_FEATURE_LEVEL_11_0, IID_PPV_ARGS(m_Device.GetAddressOf())));}ThrowIfFailed(m_Device->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(m_Fence.GetAddressOf())));m_CBVSRVUAVSize = m_Device->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV);m_RTVDescriptorSize = m_Device->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_RTV);m_DSVDescriptorSize = m_Device->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_DSV);D3D12_FEATURE_DATA_MULTISAMPLE_QUALITY_LEVELS msaaSupport{};msaaSupport.Format = m_BackBufferFormat;msaaSupport.SampleCount = 4;msaaSupport.Flags = D3D12_MULTISAMPLE_QUALITY_LEVELS_FLAG_NONE;msaaSupport.NumQualityLevels = 0;ThrowIfFailed(m_Device->CheckFeatureSupport(D3D12_FEATURE_MULTISAMPLE_QUALITY_LEVELS, &msaaSupport,sizeof(msaaSupport)));m4xMsaaQuality = msaaSupport.NumQualityLevels;assert(m4xMsaaQuality > 0 && "Unexpected MSAA quality level.");#if defined(DEBUG) || defined(_DEBUG){LogAdapter();}#endifCreateCommandObjects();createSwapChain();createRtvAndDsvDescriptorHeaps();return true;
}
}void D3DApp::CreateCommandObjects()
{D3D12_COMMAND_QUEUE_DESC commandQueueDesc{};commandQueueDesc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT;commandQueueDesc.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE;ThrowIfFailed(m_Device->CreateCommandQueue(&commandQueueDesc, IID_PPV_ARGS(m_CommandQueue.GetAddressOf())));ThrowIfFailed(m_Device->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(m_CommandAllocator.GetAddressOf())));ThrowIfFailed(m_Device->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT,m_CommandAllocator.Get(), nullptr, IID_PPV_ARGS(m_CommandList.GetAddressOf())));m_CommandList->Close();
}void D3DApp::createSwapChain()
{DXGI_SWAP_CHAIN_DESC swapChainDesc{};swapChainDesc.BufferDesc.Width = m_ClientWidth;swapChainDesc.BufferDesc.Height = m_ClientHeight;swapChainDesc.BufferDesc.RefreshRate.Denominator = 1;swapChainDesc.BufferDesc.RefreshRate.Numerator = 60;swapChainDesc.BufferDesc.Format = m_BackBufferFormat;swapChainDesc.BufferDesc.ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED;swapChainDesc.BufferDesc.Scaling = DXGI_MODE_SCALING_UNSPECIFIED;swapChainDesc.SampleDesc.Count = m4xMsaaQuality > 0 ? 4 : 1;swapChainDesc.SampleDesc.Quality = m4xMsaaQuality > 0 ? (m4xMsaaQuality - 1) : 0;swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;swapChainDesc.BufferCount = SwapChainBufferCount;swapChainDesc.OutputWindow = m_Window;swapChainDesc.Windowed = true;swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD;swapChainDesc.Flags = DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH;ThrowIfFailed(m_Factory->CreateSwapChain(m_CommandQueue.Get(), &swapChainDesc, m_SwapChain.GetAddressOf()));
}void D3DApp::createRtvAndDsvDescriptorHeaps()
{D3D12_DESCRIPTOR_HEAP_DESC descriptorDesc{};descriptorDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_RTV;descriptorDesc.NumDescriptors = SwapChainBufferCount;descriptorDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE;descriptorDesc.NodeMask = 0;ThrowIfFailed(m_Device->CreateDescriptorHeap(&descriptorDesc, IID_PPV_ARGS(m_RtvDescriptorHeap.GetAddressOf())));D3D12_DESCRIPTOR_HEAP_DESC DsvDescriptorDesc{};DsvDescriptorDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_DSV;DsvDescriptorDesc.NumDescriptors = 1;DsvDescriptorDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE;DsvDescriptorDesc.NodeMask = 0;ThrowIfFailed(m_Device->CreateDescriptorHeap(&DsvDescriptorDesc, IID_PPV_ARGS(m_DsvDescriptorHeap.GetAddressOf())));
}

这里第四章中是针对显卡信息,和显卡绑定的显示输出,以及显示输出对于特定format的显示模式进行输出

void D3DApp::LogAdapter()
{IDXGIAdapter* adapter;UINT i = 0;std::vector<IDXGIAdapter*> adapterList;while (m_Factory->EnumAdapters(i, &adapter) != DXGI_ERROR_NOT_FOUND){DXGI_ADAPTER_DESC adapterDesc{};adapter->GetDesc(&adapterDesc);std::wstring text = L"***Adapter: ";text += adapterDesc.Description;text += L"\n";OutputDebugString(text.c_str());adapterList.push_back(adapter);++i;}for (size_t i = 0; i < adapterList.size(); i++){LogAdapterOutputs(adapterList[i]);adapterList[i]->Release();}
}void D3DApp::LogAdapterOutputs(IDXGIAdapter* adapter)
{UINT i = 0;IDXGIOutput* output;while (adapter->EnumOutputs(i, &output) != DXGI_ERROR_NOT_FOUND){DXGI_OUTPUT_DESC outputDesc{};output->GetDesc(&outputDesc);std::wstring text = L"***output: ";text += outputDesc.DeviceName;text += L"\n";OutputDebugString(text.c_str());LogOutputDisplayModes(output, m_BackBufferFormat);output->Release();}
}void D3DApp::LogOutputDisplayModes(IDXGIOutput* output, DXGI_FORMAT format)
{UINT count = 0;UINT flag = 0;output->GetDisplayModeList(format, flag, &count, nullptr);std::vector<DXGI_MODE_DESC> modeList(count);output->GetDisplayModeList(format, flag, &count, modeList.data());for (auto& x : modeList){UINT n = x.RefreshRate.Numerator;UINT d = x.RefreshRate.Denominator;std::wstring text =L"Width = " + std::to_wstring(x.Width) + L" " +L"Height = " + std::to_wstring(x.Height) + L" " +L"Refresh = " + std::to_wstring(n) + L"/" + std::to_wstring(d) +L"\n";OutputDebugString(text.c_str());}
}

resize

resize实际上就是窗口初始化或者每次改变窗口的时候图像的对应操作。对于resize首先就是需要resize 输出的buffer,resizedsv, resetSwapchain,然后就是创建rtv, dsv

最后是执行命令和存储视图

首先我们在resize之前保证,我们之前的命令执行完毕,这里就是设置这个同步机制,保证之前的命令结束

void D3DApp::FlushCommandQueue()
{m_CurrentFence++;ThrowIfFailed(m_CommandQueue->Signal(m_Fence.Get(), m_CurrentFence));if (m_Fence->GetCompletedValue() < m_CurrentFence){HANDLE eventHandle = CreateEventEx(nullptr, false, false, EVENT_ALL_ACCESS);ThrowIfFailed(m_Fence->SetEventOnCompletion(m_CurrentFence, eventHandle));WaitForSingleObject(eventHandle, INFINITE);CloseHandle(eventHandle);}
}
void D3DApp::OnResize()
{assert(m_Device);assert(m_SwapChain);assert(m_CommandAllocator);//flush everything before have finishedFlushCommandQueue();ThrowIfFailed(m_CommandList->Reset(m_CommandAllocator.Get(), nullptr));for (int i = 0; i < SwapChainBufferCount; i++){m_SwapchainBuffers[i].Reset();}m_DepthStencileBuffer.Reset();ThrowIfFailed(m_SwapChain->ResizeBuffers(SwapChainBufferCount,m_ClientWidth, m_ClientHeight,m_BackBufferFormat, DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH));CD3DX12_CPU_DESCRIPTOR_HANDLE rtvHandle(m_RtvDescriptorHeap->GetCPUDescriptorHandleForHeapStart());for (int i = 0; i < SwapChainBufferCount; i++){ThrowIfFailed(m_SwapChain->GetBuffer(i, IID_PPV_ARGS(m_SwapchainBuffers[i].GetAddressOf())));m_Device->CreateRenderTargetView(m_SwapchainBuffers[i].Get(), nullptr, rtvHandle);rtvHandle.Offset(1, m_RTVDescriptorSize);}D3D12_RESOURCE_DESC depthResouceDesc{};depthResouceDesc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D;depthResouceDesc.Alignment = 0;depthResouceDesc.Width = m_ClientWidth;depthResouceDesc.Height = m_ClientHeight;depthResouceDesc.DepthOrArraySize = 1;depthResouceDesc.MipLevels = 1;depthResouceDesc.Format = DXGI_FORMAT_R24G8_TYPELESS;depthResouceDesc.SampleDesc.Count = m4xMsaaQuality > 0 ? 4 : 1;depthResouceDesc.SampleDesc.Quality = m4xMsaaQuality > 0 ? (m4xMsaaQuality - 1) : 0;depthResouceDesc.Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN;depthResouceDesc.Flags = D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL;D3D12_CLEAR_VALUE optClear;optClear.DepthStencil.Depth = 1.0f;optClear.DepthStencil.Stencil = 0;optClear.Format = m_DepthStencilFormat;ThrowIfFailed(m_Device->CreateCommittedResource(&CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_DEFAULT),D3D12_HEAP_FLAG_NONE, &depthResouceDesc, D3D12_RESOURCE_STATE_COMMON, &optClear, IID_PPV_ARGS(m_DepthStencileBuffer.GetAddressOf())));D3D12_DEPTH_STENCIL_VIEW_DESC depthStencilDesc{};depthStencilDesc.Flags = D3D12_DSV_FLAG_NONE;depthStencilDesc.ViewDimension = D3D12_DSV_DIMENSION_TEXTURE2D;depthStencilDesc.Format = m_DepthStencilFormat;depthStencilDesc.Texture2D.MipSlice = 0;m_Device->CreateDepthStencilView(m_DepthStencileBuffer.Get(), &depthStencilDesc, DepthStencilView());m_CommandList->ResourceBarrier(1, &CD3DX12_RESOURCE_BARRIER::Transition(m_DepthStencileBuffer.Get(),D3D12_RESOURCE_STATE_COMMON, D3D12_RESOURCE_STATE_DEPTH_WRITE));// Execute the resize commands.ThrowIfFailed(m_CommandList->Close());ID3D12CommandList* cmdsLists[] = { m_CommandList.Get() };m_CommandQueue->ExecuteCommandLists(_countof(cmdsLists), cmdsLists);// Wait until resize is complete.FlushCommandQueue();m_ScreenViewport.TopLeftX = 0;m_ScreenViewport.TopLeftY = 0;m_ScreenViewport.Width = static_cast<float>(m_ClientWidth);m_ScreenViewport.Height = static_cast<float>(m_ClientHeight);m_ScreenViewport.MinDepth = 0.0f;m_ScreenViewport.MaxDepth = 1.0f;m_ScissorRect = { 0, 0, m_ClientWidth, m_ClientHeight };
}

实际的使用类,书中例子

//***************************************************************************************
// Init Direct3D.cpp by Frank Luna (C) 2015 All Rights Reserved.
//
// Demonstrates the sample framework by initializing Direct3D, clearing 
// the screen, and displaying frame stats.
//
//***************************************************************************************#include "../../Common/d3dApp.h"
#include <DirectXColors.h>using namespace DirectX;class InitDirect3DApp : public D3DApp
{
public:InitDirect3DApp(HINSTANCE hInstance);~InitDirect3DApp();virtual bool Initialize()override;private:virtual void OnResize()override;virtual void Update(const GameTimer& gt)override;virtual void Draw(const GameTimer& gt)override;};int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE prevInstance,PSTR cmdLine, int showCmd)
{// Enable run-time memory check for debug builds.
#if defined(DEBUG) | defined(_DEBUG)_CrtSetDbgFlag( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF );
#endiftry{InitDirect3DApp theApp(hInstance);if(!theApp.Initialize())return 0;return theApp.Run();}catch(DxException& e){MessageBox(nullptr, e.ToString().c_str(), L"HR Failed", MB_OK);return 0;}
}InitDirect3DApp::InitDirect3DApp(HINSTANCE hInstance)
: D3DApp(hInstance) 
{
}InitDirect3DApp::~InitDirect3DApp()
{
}bool InitDirect3DApp::Initialize()
{if(!D3DApp::Initialize())return false;return true;
}void InitDirect3DApp::OnResize()
{D3DApp::OnResize();
}void InitDirect3DApp::Update(const GameTimer& gt)
{}void InitDirect3DApp::Draw(const GameTimer& gt)
{// Reuse the memory associated with command recording.// We can only reset when the associated command lists have finished execution on the GPU.ThrowIfFailed(mDirectCmdListAlloc->Reset());// A command list can be reset after it has been added to the command queue via ExecuteCommandList.// Reusing the command list reuses memory.ThrowIfFailed(mCommandList->Reset(mDirectCmdListAlloc.Get(), nullptr));// Indicate a state transition on the resource usage.mCommandList->ResourceBarrier(1, &CD3DX12_RESOURCE_BARRIER::Transition(CurrentBackBuffer(),D3D12_RESOURCE_STATE_PRESENT, D3D12_RESOURCE_STATE_RENDER_TARGET));// Set the viewport and scissor rect.  This needs to be reset whenever the command list is reset.mCommandList->RSSetViewports(1, &mScreenViewport);mCommandList->RSSetScissorRects(1, &mScissorRect);// Clear the back buffer and depth buffer.mCommandList->ClearRenderTargetView(CurrentBackBufferView(), Colors::LightSteelBlue, 0, nullptr);mCommandList->ClearDepthStencilView(DepthStencilView(), D3D12_CLEAR_FLAG_DEPTH | D3D12_CLEAR_FLAG_STENCIL, 1.0f, 0, 0, nullptr);// Specify the buffers we are going to render to.mCommandList->OMSetRenderTargets(1, &CurrentBackBufferView(), true, &DepthStencilView());// Indicate a state transition on the resource usage.mCommandList->ResourceBarrier(1, &CD3DX12_RESOURCE_BARRIER::Transition(CurrentBackBuffer(),D3D12_RESOURCE_STATE_RENDER_TARGET, D3D12_RESOURCE_STATE_PRESENT));// Done recording commands.ThrowIfFailed(mCommandList->Close());// Add the command list to the queue for execution.ID3D12CommandList* cmdsLists[] = { mCommandList.Get() };mCommandQueue->ExecuteCommandLists(_countof(cmdsLists), cmdsLists);// swap the back and front buffersThrowIfFailed(mSwapChain->Present(0, 0));mCurrBackBuffer = (mCurrBackBuffer + 1) % SwapChainBufferCount;// Wait until frame commands are complete.  This waiting is inefficient and is// done for simplicity.  Later we will show how to organize our rendering code// so we do not have to wait per frame.FlushCommandQueue();
}

我的使用

#include "D3DApp.h"class InitDirect3DApp : public D3DApp
{InitDirect3DApp(HINSTANCE hInstance):D3DApp(hInstance){ }~InitDirect3DApp();virtual bool Initialize() override;private:virtual void OnResize() override;virtual void Update() override;virtual void Draw() override;
};InitDirect3DApp::~InitDirect3DApp()
{
}bool InitDirect3DApp::Initialize()
{if (!D3DApp::Initialize())return false;return true;
}void InitDirect3DApp::OnResize()
{D3DApp::OnResize();
}void InitDirect3DApp::Update()
{
}void InitDirect3DApp::Draw()
{ThrowIfFailed(m_CommandAllocator->Reset());ThrowIfFailed(m_CommandList->Reset(m_CommandAllocator.Get(), nullptr));m_CommandList->ResourceBarrier(1, &CD3DX12_RESOURCE_BARRIER::Transition(CurrentBackBuffer(),D3D12_RESOURCE_STATE_PRESENT, D3D12_RESOURCE_STATE_RENDER_TARGET));m_CommandList->RSSetViewports(1, &m_ScreenViewport);m_CommandList->RSSetScissorRects(1, &m_ScissorRect);m_CommandList->OMSetRenderTargets(1, &CurrentBackBufferView(), true, &DepthStencilView());m_CommandList->ResourceBarrier(1, &CD3DX12_RESOURCE_BARRIER::Transition(CurrentBackBuffer(),D3D12_RESOURCE_STATE_RENDER_TARGET, D3D12_RESOURCE_STATE_PRESENT));ThrowIfFailed(m_CommandList->Close());ID3D12CommandList* cmdsLists[] = { m_CommandList.Get() };m_CommandQueue->ExecuteCommandLists(_countof(cmdsLists), cmdsLists);ThrowIfFailed(m_SwapChain->Present(0, 0));m_CurrentBackBuffer = (m_CurrentBackBuffer + 1) % SwapChainBufferCount;FlushCommandQueue();
}

显示

这里写了一个main的方法去使用

int WINAPI main(HINSTANCE hInstance, HINSTANCE prevInstance, PSTR cmdLine, int showCmd)
{
#if defined(DEBUG) | defined(_DEBUG)_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
#endiftry{InitDirect3DApp theApp(hInstance);if (!theApp.Initialize())return 0;return theApp.Run();}catch (std::exception& e){std::cerr << e.what() << std::endl;}
}

说实话,我现在还是一头雾水,先开第五章了

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/503314.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

STM32 软件I2C读写

单片机学习&#xff01; 目录 前言 一、软件I2C读写代码框架 二、I2C初始化 三、六个时序基本单元 3.1 引脚操作的封装和改名 3.2 起始条件执行逻辑 3.3 终止条件执行逻辑 3.4 发送一个字节 3.5 接收一个字节 3.5 发送应答&接收应答 3.5.1 发送应答 3.5.2 接…

计算机网络--UDP和TCP课后习题

【5-05】 试举例说明有些应用程序愿意采用不可靠的UDP, 而不愿意采用可靠的TCP。 解答&#xff1a; 这可能有以下几种情况。 首先&#xff0c;在互联网上传输实时数据的分组时&#xff0c;有可能会出现差错甚至丢失。如果利用 TCP 协议对这些出错或丢失的分组进行重传&…

【C++】B2099 矩阵交换行

博客主页&#xff1a; [小ᶻ☡꙳ᵃⁱᵍᶜ꙳] 本文专栏: C 文章目录 &#x1f4af;前言&#x1f4af;题目描述题目描述输入格式输出格式输入输出样例输入 #1输出 #1 &#x1f4af;题目分析&#x1f4af;不同解法分析我的做法实现步骤&#xff1a;优点&#xff1a;不足&#…

[微服务]redis主从集群搭建与优化

搭建主从集群 单节点Redis的并发能力是有上限的&#xff0c;要进一步提高Redis的并发能力&#xff0c;就需要搭建主从集群&#xff0c;实现读写分离。 1. 主从集群结构 下图就是一个简单的Redis主从集群结构&#xff1a; 如图所示&#xff0c;集群中有一个master节点、两个s…

使用WebSocket 获取实时数据

回车发送数据&#xff0c;模拟服务器发送数据 效果图&#xff1a; 源码&#xff1a; <template><div><h1>WebSocket 实时数据</h1><input type"text" v-model"ipt" keyup.enter"sendMessage(ipt)"><div v-if…

Element-UI:如何实现表格组件el-table多选场景下根据数据对某一行进行禁止被选中?

如何实现表格组件el-table多选场景下根据数据对某一行进行禁止被选中&#xff1f; 在使用 Element UI 的 Table 组件时&#xff0c;如果你想要禁用某一行的选中&#xff08;特别是在多选模式下&#xff09;&#xff0c;可以通过自定义行的 selectable 属性来实现。selectable …

移动端自动化测试Appium-java

一、Appium的简介 移动端的自动化测试框架 模拟人的操作进行功能自动化常用于功能测试、兼容性测试 跨平台的自动化测试 二、Appium的原理 核心是web服务器&#xff0c;接受客户端的连接&#xff0c;接收客户端的命令&#xff0c;在手机设备上执行命令&#xff0c;收集命令…

Geoserver修行记-后端调用WMS/WMTS服务无找不到图层Could not find layer

项目场景 调用geoserver地图服务WMS,找不到图层 我在进行地图服务调用的时候&#xff0c;总是提示我找不多图层 Could not find layer&#xff0c;重点是这个图层我明明是定义了&#xff0c;发布了&#xff0c;且还能够正常查看图层的wms的样式&#xff0c;但是在调用后端调用…

深入探讨 Android 中的 AlarmManager:定时任务调度及优化实践

引言 在 Android 开发中&#xff0c;AlarmManager 是一个非常重要的系统服务&#xff0c;用于设置定时任务或者周期性任务。无论是设置一个闹钟&#xff0c;还是定时进行数据同步&#xff0c;AlarmManager 都是不可或缺的工具之一。然而&#xff0c;随着 Android 系统的不断演…

玉米识别数据集,4880张图,正确识别率可达98.6%,支持yolo,coco json,pasical voc xml格式的标注,可识别玉米

玉米识别数据集&#xff0c;4880张图&#xff0c;正确识别率可达98.6%&#xff0c;支持yolo&#xff0c;coco json,pasical voc xml格式的标注&#xff0c;可识别玉米 数据集下载地址&#xff1a; yolo v11:https://download.csdn.net/download/pbymw8iwm/90230969 yolo v9:…

【UI自动化测试】selenium八种定位方式

&#x1f3e1;个人主页&#xff1a;謬熙&#xff0c;欢迎各位大佬到访❤️❤️❤️~ &#x1f472;个人简介&#xff1a;本人编程小白&#xff0c;正在学习互联网求职知识…… 如果您觉得本文对您有帮助的话&#xff0c;记得点赞&#x1f44d;、收藏⭐️、评论&#x1f4ac;&am…

【前端系列01】优化axios响应拦截器

文章目录 一、前言&#x1f680;&#x1f680;&#x1f680;二、axios响应拦截器&#xff1a;☀️☀️☀️2.1 为什么前端需要响应拦截器element ui的消息组件 一、前言&#x1f680;&#x1f680;&#x1f680; ☀️ 回报不在行动之后&#xff0c;回报在行动之中。 这个系列可…

【C语言程序设计——选择结构程序设计】求阶跃函数的值(头歌实践教学平台习题)【合集】

目录&#x1f60b; 任务描述 相关知识 1. 选择结构基本概念 2. 主要语句类型​&#xff08;if、if-else、switch&#xff09; 3. 跃迁函数中变量的取值范围 4. 计算阶跃函数的值 编程要求 测试说明 通关代码 测试结果 任务描述 本关任务&#xff1a;输入x的值&#x…

利用 NineData 实现 PostgreSQL 到 Kafka 的高效数据同步

记录一次 PostgreSQL 到 Kafka 的数据迁移实践。前段时间&#xff0c;NineData 的某个客户在一个项目中需要将 PostgreSQL 的数据实时同步到 Kafka。需求明确且普遍&#xff1a; PostgreSQL 中的交易数据&#xff0c;需要实时推送到 Kafka&#xff0c;供下游多个系统消费&#…

【C++面向对象——类的多态性与虚函数】编写教学游戏:认识动物(头歌实践教学平台习题)【合集】

目录&#x1f60b; 任务描述 详细说明&#xff08;类的设计&#xff09; 基类&#xff1a; Animal 派生类: 应用程序说明&#xff1a; 相关知识 1. 虚函数与多态 一、多态的概念与意义 二、虚函数实现多态的原理 三、虚函数的语法细节 2. 纯虚函数与抽象类 一、纯虚…

我的nvim的init.lua配置

nvim的配置文件路径在&#xff5e;/.config/nvim路径下&#xff1a; 一、目录如下&#xff1a; coc-settings.json文件是配置代码片段路径的文件init.lua配置文件的启动脚本lua/config.lua 全局配置文件lua/keymaps.lua 快捷键映射键文件lua/plugins.lua 插件的安装和配置文件…

微服务-Eureka

Eureka的作用 使用RestTemplate完成远程调用需要被调用者的ip和端口&#xff0c;从而能够发起http请求&#xff0c;但是如果有很多个实例也更加不能有效的处理&#xff0c;而且我们又该如何知道这些实例是否健康呢。所以就有了很多的注册中心比如Eureka、Nacos等等。 服务注…

微服务保护—Sentinel快速入门+微服务整合 示例: 黑马商城

1.微服务保护 微服务保护是确保微服务架构可靠、稳定和安全的策略与技术。 在可靠性上&#xff0c;限流是控制进入微服务的请求数量&#xff0c;防止流量过大导致服务崩溃。比如电商促销时对商品详情服务进行流量限制。熔断是当被调用的微服务故障过多或响应过慢时&#xff0c;…

Maven 详细配置:Maven settings 配置文件的详细说明

Maven settings 配置文件是 Maven 环境的重要组成部分&#xff0c;它用于定义用户特定的配置信息和全局设置&#xff0c;例如本地仓库路径、远程仓库镜像、代理服务器以及认证信息等。settings 文件分为全局配置文件&#xff08;settings.xml&#xff09;和用户配置文件&#x…

【Uniapp-Vue3】image媒体组件属性

如果我们想要在页面上展示图片就需要使用到image标签。 这部分最重要的是图片的裁剪&#xff0c;图片的裁剪和缩放属性&#xff1a; mode 图片裁剪、缩放的模式 默认值是scaleToFill 我将用两张图片对属性进行演示&#xff0c;一张是pic1.jpg&#xff08;宽更长&#xf…