Magpie/Runtime/CursorRenderer.cpp
2021-09-25 21:21:12 +08:00

500 lines
15 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include "pch.h"
#include "CursorRenderer.h"
#include "App.h"
#include "Utils.h"
#include "shaders/MonochromeCursorPS.h"
#include <VertexTypes.h>
extern std::shared_ptr<spdlog::logger> logger;
bool CursorRenderer::Initialize(ComPtr<ID3D11Texture2D> renderTarget, SIZE outputSize) {
App& app = App::GetInstance();
Renderer& renderer = app.GetRenderer();
_d3dDC = renderer.GetD3DDC();
_d3dDevice = renderer.GetD3DDevice();
HRESULT hr = renderer.GetRenderTargetView(renderTarget.Get(), &_rtv);
if (FAILED(hr)) {
SPDLOG_LOGGER_ERROR(logger, MakeComErrorMsg("GetRenderTargetView 失败", hr));
return false;
}
hr = renderer.GetShaderResourceView(renderTarget.Get(), &_renderTargetSrv);
if (FAILED(hr)) {
SPDLOG_LOGGER_ERROR(logger, MakeComErrorMsg("GetShaderResourceView 失败", hr));
return false;
}
hr = renderer.GetD3DDevice()->CreatePixelShader(
MonochromeCursorPSShaderByteCode, sizeof(MonochromeCursorPSShaderByteCode), nullptr, &_monoCursorPS);
if (FAILED(hr)) {
SPDLOG_LOGGER_ERROR(logger, MakeComErrorMsg("创建 MonochromeCursorPS 失败", hr));
return false;
}
D3D11_BUFFER_DESC bd = {};
bd.Usage = D3D11_USAGE_DYNAMIC;
bd.ByteWidth = sizeof(VertexPositionTexture) * 4;
bd.BindFlags = D3D11_BIND_VERTEX_BUFFER;
bd.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
hr = renderer.GetD3DDevice()->CreateBuffer(&bd, nullptr, &_vtxBuffer);
if (FAILED(hr)) {
SPDLOG_LOGGER_ERROR(logger, MakeComErrorMsg("创建顶点缓冲区失败", hr));
return false;
}
if (!renderer.GetSampler(Renderer::FilterType::LINEAR, &_linearSam)
|| !renderer.GetSampler(Renderer::FilterType::POINT, &_pointSam)
) {
SPDLOG_LOGGER_ERROR(logger, "GetSampler 失败");
return false;
}
_monoCursorSize = { GetSystemMetrics(SM_CXCURSOR), GetSystemMetrics(SM_CYCURSOR) };
D3D11_TEXTURE2D_DESC desc{};
desc.Width = _monoCursorSize.cx;
desc.Height = _monoCursorSize.cy;
desc.MipLevels = 1;
desc.ArraySize = 1;
desc.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
desc.SampleDesc.Count = 1;
desc.SampleDesc.Quality = 0;
desc.BindFlags = D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_RENDER_TARGET;
desc.Usage = D3D11_USAGE_DEFAULT;
hr = _d3dDevice->CreateTexture2D(&desc, nullptr, &_monoTmpTexture);
if (FAILED(hr)) {
SPDLOG_LOGGER_ERROR(logger, MakeComErrorMsg("创建 Texture2D 失败", hr));
return false;
}
hr = renderer.GetRenderTargetView(_monoTmpTexture.Get(), &_monoTmpRtv);
if (FAILED(hr)) {
SPDLOG_LOGGER_ERROR(logger, MakeComErrorMsg("GetRenderTargetView 失败", hr));
return false;
}
hr = renderer.GetShaderResourceView(_monoTmpTexture.Get(), &_monoTmpSrv);
if (FAILED(hr)) {
SPDLOG_LOGGER_ERROR(logger, MakeComErrorMsg("GetShaderResourceView 失败", hr));
return false;
}
D3D11_TEXTURE2D_DESC rtDesc;
renderTarget->GetDesc(&rtDesc);
_renderTargetSize = { (long)rtDesc.Width, (long)rtDesc.Height };
_destRect.left = (rtDesc.Width - outputSize.cx) / 2;
_destRect.right = _destRect.left + outputSize.cx;
_destRect.top = (rtDesc.Height - outputSize.cy) / 2;
_destRect.bottom = _destRect.top + outputSize.cy;
const RECT& srcClient = app.GetSrcClientRect();
SIZE srcSize = { srcClient.right - srcClient.left, srcClient.bottom - srcClient.top };
_scaleX = float(_destRect.right - _destRect.left) / srcSize.cx;
_scaleY = float(_destRect.bottom - _destRect.top) / srcSize.cy;
SPDLOG_LOGGER_INFO(logger, fmt::format("scaleX{}scaleY{}", _scaleX, _scaleY));
// 限制鼠标在窗口内
if (!ClipCursor(&App::GetInstance().GetSrcClientRect())) {
SPDLOG_LOGGER_ERROR(logger, MakeWin32ErrorMsg("ClipCursor 失败"));
}
if (App::GetInstance().IsAdjustCursorSpeed()) {
// 设置鼠标移动速度
if (SystemParametersInfo(SPI_GETMOUSESPEED, 0, &_cursorSpeed, 0)) {
long newSpeed = std::clamp(lroundf(_cursorSpeed / (_scaleX + _scaleY) * 2), 1L, 20L);
if (!SystemParametersInfo(SPI_SETMOUSESPEED, 0, (PVOID)(intptr_t)newSpeed, 0)) {
SPDLOG_LOGGER_ERROR(logger, MakeWin32ErrorMsg("设置光标移速失败"));
}
} else {
SPDLOG_LOGGER_ERROR(logger, MakeWin32ErrorMsg("获取光标移速失败"));
}
SPDLOG_LOGGER_INFO(logger, "已调整光标移速");
}
if (!MagShowSystemCursor(FALSE)) {
SPDLOG_LOGGER_ERROR(logger, MakeWin32ErrorMsg("MagShowSystemCursor 失败"));
}
SPDLOG_LOGGER_INFO(logger, "CursorRenderer 初始化完成");
return true;
}
CursorRenderer::~CursorRenderer() {
ClipCursor(nullptr);
if (App::GetInstance().IsAdjustCursorSpeed()) {
SystemParametersInfo(SPI_SETMOUSESPEED, 0, (PVOID)(intptr_t)_cursorSpeed, 0);
}
MagShowSystemCursor(TRUE);
SPDLOG_LOGGER_INFO(logger, "CursorRenderer 已析构");
}
bool GetHBmpBits32(HBITMAP hBmp, int& width, int& height, std::vector<BYTE>& pixels) {
BITMAP bmp{};
if (!GetObject(hBmp, sizeof(bmp), &bmp)) {
SPDLOG_LOGGER_ERROR(logger, MakeWin32ErrorMsg("GetObject 失败"));
return false;
}
width = bmp.bmWidth;
height = bmp.bmHeight;
BITMAPINFO bi{};
bi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
bi.bmiHeader.biWidth = width;
bi.bmiHeader.biHeight = -height;
bi.bmiHeader.biPlanes = 1;
bi.bmiHeader.biCompression = BI_RGB;
bi.bmiHeader.biBitCount = 32;
bi.bmiHeader.biSizeImage = width * height * 4;
pixels.resize(bi.bmiHeader.biSizeImage);
HDC hdc = GetDC(NULL);
if (GetDIBits(hdc, hBmp, 0, height, &pixels[0], &bi, DIB_RGB_COLORS) != height) {
SPDLOG_LOGGER_ERROR(logger, MakeWin32ErrorMsg("GetDIBits 失败"));
ReleaseDC(NULL, hdc);
return false;
}
ReleaseDC(NULL, hdc);
return true;
}
bool CursorRenderer::_ResolveCursor(HCURSOR hCursor, _CursorInfo& result) const {
assert(hCursor != NULL);
ICONINFO ii{};
if (!GetIconInfo(hCursor, &ii)) {
SPDLOG_LOGGER_ERROR(logger, MakeWin32ErrorMsg("GetIconInfo 失败"));
return false;
}
result.xHotSpot = ii.xHotspot;
result.yHotSpot = ii.yHotspot;
auto clear = [&ii]() {
if (ii.hbmColor) {
DeleteBitmap(ii.hbmColor);
}
DeleteBitmap(ii.hbmMask);
};
ComPtr<ID3D11Texture2D> texture;
if(ii.hbmColor == NULL) {
// 单色光标
BITMAP bmp{};
if (!GetObject(ii.hbmMask, sizeof(bmp), &bmp)) {
SPDLOG_LOGGER_ERROR(logger, MakeWin32ErrorMsg("GetObject 失败"));
clear();
return false;
}
result.width = bmp.bmWidth;
result.height = bmp.bmHeight;
BITMAPINFO bi{};
bi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
bi.bmiHeader.biWidth = result.width;
bi.bmiHeader.biHeight = -(LONG)result.height;
bi.bmiHeader.biPlanes = 1;
bi.bmiHeader.biCompression = BI_RGB;
bi.bmiHeader.biBitCount = 32;
bi.bmiHeader.biSizeImage = result.width * result.height * 4;
std::vector<BYTE> pixels(bi.bmiHeader.biSizeImage);
HDC hdc = GetDC(NULL);
if (GetDIBits(hdc, ii.hbmMask, 0, result.height, &pixels[0], &bi, DIB_RGB_COLORS) != result.height) {
SPDLOG_LOGGER_ERROR(logger, MakeWin32ErrorMsg("GetDIBits 失败"));
ReleaseDC(NULL, hdc);
clear();
return false;
}
ReleaseDC(NULL, hdc);
// 红色通道是 AND 掩码,绿色通道是 XOR 掩码
const int halfSize = bi.bmiHeader.biSizeImage / 8;
BYTE* upPtr = &pixels[1];
BYTE* downPtr = &pixels[static_cast<size_t>(halfSize) * 4];
for (int i = 0; i < halfSize; ++i) {
*upPtr = *downPtr;
// 判断光标是否存在反色部分
if (!result.hasInv && *(upPtr - 1) == 255 && *upPtr == 255) {
result.hasInv = true;
}
upPtr += 4;
downPtr += 4;
}
if (result.hasInv) {
SPDLOG_LOGGER_INFO(logger, "此光标含反色部分");
result.height /= 2;
if (result.width != _monoCursorSize.cx || result.height != _monoCursorSize.cy) {
SPDLOG_LOGGER_ERROR(logger, "单色光标的尺寸不合法");
return false;
}
D3D11_TEXTURE2D_DESC desc{};
desc.Width = result.width;
desc.Height = result.height;
desc.MipLevels = 1;
desc.ArraySize = 1;
desc.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
desc.SampleDesc.Count = 1;
desc.SampleDesc.Quality = 0;
desc.BindFlags = D3D11_BIND_SHADER_RESOURCE;
desc.Usage = D3D11_USAGE_DEFAULT;
D3D11_SUBRESOURCE_DATA initData{};
initData.pSysMem = &pixels[0];
initData.SysMemPitch = result.width * 4;
HRESULT hr = _d3dDevice->CreateTexture2D(&desc, &initData, &texture);
if (FAILED(hr)) {
SPDLOG_LOGGER_ERROR(logger, MakeComErrorMsg("创建 Texture2D 失败", hr));
return false;
}
}
}
clear();
if(!result.hasInv) {
// 光标无反色部分,使用 WIC 将光标转换为带 Alpha 通道的图像
ComPtr<IWICImagingFactory2> wicFactory = App::GetInstance().GetWICImageFactory();
if (!wicFactory) {
SPDLOG_LOGGER_ERROR(logger, "获取 WICImageFactory 失败");
return false;
}
ComPtr<IWICBitmap> wicBitmap;
HRESULT hr = wicFactory->CreateBitmapFromHICON(hCursor, &wicBitmap);
if (FAILED(hr)) {
SPDLOG_LOGGER_ERROR(logger, MakeComErrorMsg("CreateBitmapFromHICON 失败", hr));
return false;
}
hr = wicBitmap->GetSize(&result.width, &result.height);
if (FAILED(hr)) {
SPDLOG_LOGGER_ERROR(logger, MakeComErrorMsg("GetSize 失败", hr));
return false;
}
std::vector<BYTE> pixels(result.width * result.height * 4);
hr = wicBitmap->CopyPixels(nullptr, result.width * 4, (UINT)pixels.size(), pixels.data());
if (FAILED(hr)) {
SPDLOG_LOGGER_ERROR(logger, MakeComErrorMsg("CopyPixels 失败", hr));
return false;
}
D3D11_TEXTURE2D_DESC desc{};
desc.Width = result.width;
desc.Height = result.height;
desc.MipLevels = 1;
desc.ArraySize = 1;
desc.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
desc.SampleDesc.Count = 1;
desc.SampleDesc.Quality = 0;
desc.BindFlags = D3D11_BIND_SHADER_RESOURCE;
desc.Usage = D3D11_USAGE_IMMUTABLE;
D3D11_SUBRESOURCE_DATA initData{};
initData.pSysMem = &pixels[0];
initData.SysMemPitch = result.width * 4;
hr = _d3dDevice->CreateTexture2D(&desc, &initData, &texture);
if (FAILED(hr)) {
SPDLOG_LOGGER_ERROR(logger, MakeComErrorMsg("创建 Texture2D 失败", hr));
return false;
}
}
HRESULT hr = _d3dDevice->CreateShaderResourceView(texture.Get(), nullptr, &result.texture);
if (FAILED(hr)) {
SPDLOG_LOGGER_ERROR(logger, MakeComErrorMsg("创建 ShaderResourceView 失败", hr));
return false;
}
return true;
}
void CursorRenderer::Draw() {
CURSORINFO ci{};
ci.cbSize = sizeof(ci);
if (!GetCursorInfo(&ci)) {
SPDLOG_LOGGER_ERROR(logger, MakeWin32ErrorMsg("GetCursorInfo 失败"));
return;
}
if (!ci.hCursor || ci.flags != CURSOR_SHOWING) {
return;
}
_CursorInfo* info = nullptr;
auto it = _cursorMap.find(ci.hCursor);
if (it != _cursorMap.end()) {
info = &it->second;
} else {
// 未在映射中找到,创建新映射
_CursorInfo t;
if (_ResolveCursor(ci.hCursor, t)) {
info = &_cursorMap[ci.hCursor];
*info = t;
SPDLOG_LOGGER_INFO(logger, fmt::format("已解析光标:{}", (void*)ci.hCursor));
} else {
SPDLOG_LOGGER_ERROR(logger, "解析光标失败");
return;
}
}
// 映射坐标
const RECT& srcClient = App::GetInstance().GetSrcClientRect();
POINT targetScreenPos = {
lroundf((ci.ptScreenPos.x - srcClient.left) * _scaleX) - info->xHotSpot,
lroundf((ci.ptScreenPos.y - srcClient.top) * _scaleY) - info->yHotSpot
};
RECT cursorRect{
targetScreenPos.x + _destRect.left,
targetScreenPos.y + _destRect.top,
targetScreenPos.x + (LONG)info->width + _destRect.left,
targetScreenPos.y + (LONG)info->height + _destRect.top
};
if (cursorRect.right <= _destRect.left || cursorRect.bottom <= _destRect.top
|| cursorRect.left >= _destRect.right || cursorRect.top >= _destRect.bottom
) {
// 光标在窗口外
return;
}
float left = targetScreenPos.x / FLOAT(_destRect.right - _destRect.left) * 2 - 1.0f;
float top = 1.0f - targetScreenPos.y / FLOAT(_destRect.bottom - _destRect.top) * 2;
float right = left + info->width / FLOAT(_destRect.right - _destRect.left) * 2;
float bottom = top - info->height / FLOAT(_destRect.bottom - _destRect.top) * 2;
Renderer& renderer = App::GetInstance().GetRenderer();
renderer.SetSimpleVS(_vtxBuffer.Get());
if (!info->hasInv) {
_d3dDC->OMSetRenderTargets(1, &_rtv, nullptr);
D3D11_VIEWPORT vp{
(FLOAT)_destRect.left,
(FLOAT)_destRect.top,
FLOAT(_destRect.right - _destRect.left),
FLOAT(_destRect.bottom - _destRect.top),
0.0f,
1.0f
};
_d3dDC->RSSetViewports(1, &vp);
D3D11_MAPPED_SUBRESOURCE ms;
HRESULT hr = _d3dDC->Map(_vtxBuffer.Get(), 0, D3D11_MAP_WRITE_DISCARD, 0, &ms);
if (FAILED(hr)) {
SPDLOG_LOGGER_ERROR(logger, MakeComErrorMsg("Map 失败", hr));
return;
}
VertexPositionTexture* data = (VertexPositionTexture*)ms.pData;
data[0] = { XMFLOAT3(left, top, 0.5f), XMFLOAT2(0.0f, 0.0f) };
data[1] = { XMFLOAT3(right, top, 0.5f), XMFLOAT2(1.0f, 0.0f) };
data[2] = { XMFLOAT3(left, bottom, 0.5f), XMFLOAT2(0.0f, 1.0f) };
data[3] = { XMFLOAT3(right, bottom, 0.5f), XMFLOAT2(1.0f, 1.0f) };
_d3dDC->Unmap(_vtxBuffer.Get(), 0);
if (!renderer.SetCopyPS(_pointSam, info->texture.Get())) {
SPDLOG_LOGGER_ERROR(logger, "SetCopyPS 失败");
return;
}
if (!renderer.SetAlphaBlend(true)) {
SPDLOG_LOGGER_ERROR(logger, "SetAlphaBlend 失败");
return;
}
_d3dDC->Draw(4, 0);
if (!renderer.SetAlphaBlend(false)) {
SPDLOG_LOGGER_ERROR(logger, "SetAlphaBlend 失败");
return;
}
} else {
// 绘制带有反色部分的光标,首先将光标覆盖的纹理复制到 _monoTmpTexture 中
// 不知为何 CopySubresourceRegion 会大幅增加 GPU 占用
_d3dDC->OMSetRenderTargets(1, &_monoTmpRtv, nullptr);
D3D11_VIEWPORT vp{};
vp.Width = (FLOAT)info->width;
vp.Height = (FLOAT)info->height;
vp.MaxDepth = 1.0f;
_d3dDC->RSSetViewports(1, &vp);
D3D11_MAPPED_SUBRESOURCE ms;
HRESULT hr = _d3dDC->Map(_vtxBuffer.Get(), 0, D3D11_MAP_WRITE_DISCARD, 0, &ms);
if (FAILED(hr)) {
SPDLOG_LOGGER_ERROR(logger, MakeComErrorMsg("Map 失败", hr));
return;
}
VertexPositionTexture* data = (VertexPositionTexture*)ms.pData;
float leftPos = (float)cursorRect.left / _renderTargetSize.cx;
float rightPos = (float)cursorRect.right / _renderTargetSize.cx;
float topPos = (float)cursorRect.top / _renderTargetSize.cy;
float bottomPos = (float)cursorRect.bottom / _renderTargetSize.cy;
data[0] = { XMFLOAT3(-1, 1, 0.5f), XMFLOAT2(leftPos, topPos) };
data[1] = { XMFLOAT3(1, 1, 0.5f), XMFLOAT2(rightPos, topPos) };
data[2] = { XMFLOAT3(-1, -1, 0.5f), XMFLOAT2(leftPos, bottomPos) };
data[3] = { XMFLOAT3(1, -1, 0.5f), XMFLOAT2(rightPos, bottomPos) };
_d3dDC->Unmap(_vtxBuffer.Get(), 0);
if (!renderer.SetCopyPS(_pointSam, _renderTargetSrv)) {
SPDLOG_LOGGER_ERROR(logger, "SetCopyPS 失败");
return;
}
_d3dDC->Draw(4, 0);
// 绘制光标
hr = _d3dDC->Map(_vtxBuffer.Get(), 0, D3D11_MAP_WRITE_DISCARD, 0, &ms);
if (FAILED(hr)) {
SPDLOG_LOGGER_ERROR(logger, MakeComErrorMsg("Map 失败", hr));
return;
}
data = (VertexPositionTexture*)ms.pData;
data[0] = { XMFLOAT3(left, top, 0.5f), XMFLOAT2(0.0f, 0.0f) };
data[1] = { XMFLOAT3(right, top, 0.5f), XMFLOAT2(1.0f, 0.0f) };
data[2] = { XMFLOAT3(left, bottom, 0.5f), XMFLOAT2(0.0f, 1.0f) };
data[3] = { XMFLOAT3(right, bottom, 0.5f), XMFLOAT2(1.0f, 1.0f) };
_d3dDC->Unmap(_vtxBuffer.Get(), 0);
_d3dDC->PSSetShader(_monoCursorPS.Get(), nullptr, 0);
_d3dDC->PSSetConstantBuffers(0, 0, nullptr);
_d3dDC->OMSetRenderTargets(0, nullptr, nullptr);
ID3D11ShaderResourceView* srv[2] = { _monoTmpSrv, info->texture.Get() };
_d3dDC->PSSetShaderResources(0, 2, srv);
_d3dDC->PSSetSamplers(0, 1, &_pointSam);
_d3dDC->OMSetRenderTargets(1, &_rtv, nullptr);
vp.TopLeftX = (FLOAT)_destRect.left;
vp.TopLeftY = (FLOAT)_destRect.top;
vp.Width = FLOAT(_destRect.right - _destRect.left);
vp.Height = FLOAT(_destRect.bottom - _destRect.top);
_d3dDC->RSSetViewports(1, &vp);
_d3dDC->Draw(4, 0);
}
}