/* * Copyright (C) 2005-2018 Team Kodi * This file is part of Kodi - https://kodi.tv * * SPDX-License-Identifier: GPL-2.0-or-later * See LICENSES/README.md for more information. */ #include "GUIFontTTFDX.h" #include "GUIFontManager.h" #include "GUIShaderDX.h" #include "TextureDX.h" #include "rendering/dx/DeviceResources.h" #include "rendering/dx/RenderContext.h" #include "utils/log.h" // stuff for freetype #include using namespace Microsoft::WRL; #ifdef TARGET_WINDOWS_STORE #define generic GenericFromFreeTypeLibrary #endif #include FT_FREETYPE_H #include FT_GLYPH_H namespace { constexpr size_t ELEMENT_ARRAY_MAX_CHAR_INDEX = 2000; } /* namespace */ CGUIFontTTF* CGUIFontTTF::CreateGUIFontTTF(const std::string& fontIdent) { return new CGUIFontTTFDX(fontIdent); } CGUIFontTTFDX::CGUIFontTTFDX(const std::string& fontIdent) : CGUIFontTTF(fontIdent) { DX::Windowing()->Register(this); } CGUIFontTTFDX::~CGUIFontTTFDX(void) { DX::Windowing()->Unregister(this); m_vertexBuffer = nullptr; m_staticIndexBuffer = nullptr; if (!m_buffers.empty()) { std::for_each(m_buffers.begin(), m_buffers.end(), [](CD3DBuffer* buf) { if (buf) delete buf; }); } m_buffers.clear(); m_staticIndexBufferCreated = false; m_vertexWidth = 0; } bool CGUIFontTTFDX::FirstBegin() { if (!DX::DeviceResources::Get()->GetD3DContext()) return false; CGUIShaderDX* pGUIShader = DX::Windowing()->GetGUIShader(); pGUIShader->Begin(SHADER_METHOD_RENDER_FONT); return true; } void CGUIFontTTFDX::LastEnd() { CWinSystemBase* const winSystem = CServiceBroker::GetWinSystem(); ComPtr pContext = DX::DeviceResources::Get()->GetD3DContext(); if (!pContext || !winSystem) return; typedef CGUIFontTTF::CTranslatedVertices trans; bool transIsEmpty = std::all_of(m_vertexTrans.begin(), m_vertexTrans.end(), [](trans& _) { return _.m_vertexBuffer->size <= 0; }); // no chars to render if (m_vertex.empty() && transIsEmpty) return; CreateStaticIndexBuffer(); unsigned int offset = 0; unsigned int stride = sizeof(SVertex); CGUIShaderDX* pGUIShader = DX::Windowing()->GetGUIShader(); // Set font texture as shader resource pGUIShader->SetShaderViews(1, m_speedupTexture->GetAddressOfSRV()); // Enable alpha blend DX::Windowing()->SetAlphaBlendEnable(true); // Set our static index buffer pContext->IASetIndexBuffer(m_staticIndexBuffer.Get(), DXGI_FORMAT_R16_UINT, 0); // Set the type of primitive that should be rendered from this vertex buffer, in this case triangles. pContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST); if (!m_vertex.empty()) { // Deal with vertices that had to use software clipping if (!UpdateDynamicVertexBuffer(&m_vertex[0], m_vertex.size())) return; // Set the dynamic vertex buffer to active in the input assembler pContext->IASetVertexBuffers(0, 1, m_vertexBuffer.GetAddressOf(), &stride, &offset); // Do the actual drawing operation, split into groups of characters no // larger than the pre-determined size of the element array size_t size = m_vertex.size() / 4; for (size_t character = 0; size > character; character += ELEMENT_ARRAY_MAX_CHAR_INDEX) { size_t count = size - character; count = std::min(count, ELEMENT_ARRAY_MAX_CHAR_INDEX); // 6 indices and 4 vertices per character pGUIShader->DrawIndexed(count * 6, 0, character * 4); } } if (!transIsEmpty) { // Deal with the vertices that can be hardware clipped and therefore translated // Store current GPU transform XMMATRIX view = pGUIShader->GetView(); // Store current scissor CGraphicContext& context = winSystem->GetGfxContext(); CRect scissor = context.StereoCorrection(context.GetScissors()); for (size_t i = 0; i < m_vertexTrans.size(); i++) { // ignore empty buffers if (m_vertexTrans[i].m_vertexBuffer->size == 0) continue; // Apply the clip rectangle CRect clip = DX::Windowing()->ClipRectToScissorRect(m_vertexTrans[i].m_clip); // Intersect with current scissors clip.Intersect(scissor); // skip empty clip, a little improvement to not render invisible text if (clip.IsEmpty()) continue; DX::Windowing()->SetScissors(clip); // Apply the translation to the model view matrix XMMATRIX translation = XMMatrixTranslation(m_vertexTrans[i].m_translateX, m_vertexTrans[i].m_translateY, m_vertexTrans[i].m_translateZ); pGUIShader->SetView(XMMatrixMultiply(translation, view)); CD3DBuffer* vbuffer = reinterpret_cast(m_vertexTrans[i].m_vertexBuffer->bufferHandle); // Set the static vertex buffer to active in the input assembler ID3D11Buffer* buffers[1] = {vbuffer->Get()}; pContext->IASetVertexBuffers(0, 1, buffers, &stride, &offset); // Do the actual drawing operation, split into groups of characters no // larger than the pre-determined size of the element array for (size_t character = 0; m_vertexTrans[i].m_vertexBuffer->size > character; character += ELEMENT_ARRAY_MAX_CHAR_INDEX) { size_t count = m_vertexTrans[i].m_vertexBuffer->size - character; count = std::min(count, ELEMENT_ARRAY_MAX_CHAR_INDEX); // 6 indices and 4 vertices per character pGUIShader->DrawIndexed(count * 6, 0, character * 4); } } // restore scissor DX::Windowing()->SetScissors(scissor); // Restore the original transform pGUIShader->SetView(view); } pGUIShader->RestoreBuffers(); } CVertexBuffer CGUIFontTTFDX::CreateVertexBuffer(const std::vector& vertices) const { CD3DBuffer* buffer = nullptr; // do not create empty buffers, leave buffer as nullptr, it will be ignored on drawing stage if (!vertices.empty()) { buffer = new CD3DBuffer(); if (!buffer->Create(D3D11_BIND_VERTEX_BUFFER, vertices.size(), sizeof(SVertex), DXGI_FORMAT_UNKNOWN, D3D11_USAGE_IMMUTABLE, &vertices[0])) CLog::LogF(LOGERROR, "Failed to create vertex buffer."); else AddReference((CGUIFontTTFDX*)this, buffer); } return CVertexBuffer(reinterpret_cast(buffer), vertices.size() / 4, this); } void CGUIFontTTFDX::AddReference(CGUIFontTTFDX* font, CD3DBuffer* pBuffer) { font->m_buffers.emplace_back(pBuffer); } void CGUIFontTTFDX::DestroyVertexBuffer(CVertexBuffer& buffer) const { if (nullptr != buffer.bufferHandle) { CD3DBuffer* vbuffer = reinterpret_cast(buffer.bufferHandle); ClearReference((CGUIFontTTFDX*)this, vbuffer); if (vbuffer) delete vbuffer; buffer.bufferHandle = 0; } } void CGUIFontTTFDX::ClearReference(CGUIFontTTFDX* font, CD3DBuffer* pBuffer) { std::list::iterator it = std::find(font->m_buffers.begin(), font->m_buffers.end(), pBuffer); if (it != font->m_buffers.end()) font->m_buffers.erase(it); } std::unique_ptr CGUIFontTTFDX::ReallocTexture(unsigned int& newHeight) { assert(newHeight != 0); assert(m_textureWidth != 0); if (m_textureHeight == 0) { m_texture.reset(); m_speedupTexture.reset(); } m_staticCache.Flush(); m_dynamicCache.Flush(); std::unique_ptr pNewTexture = std::make_unique(m_textureWidth, newHeight, XB_FMT_A8); std::unique_ptr newSpeedupTexture = std::make_unique(); if (!newSpeedupTexture->Create(m_textureWidth, newHeight, 1, D3D11_USAGE_DEFAULT, DXGI_FORMAT_R8_UNORM)) { return nullptr; } // There might be data to copy from the previous texture if (newSpeedupTexture && m_speedupTexture) { CD3D11_BOX rect(0, 0, 0, m_textureWidth, m_textureHeight, 1); ComPtr pContext = DX::DeviceResources::Get()->GetImmediateContext(); pContext->CopySubresourceRegion(newSpeedupTexture->Get(), 0, 0, 0, 0, m_speedupTexture->Get(), 0, &rect); } m_texture.reset(); m_textureHeight = newHeight; m_textureScaleY = 1.0f / m_textureHeight; m_speedupTexture = std::move(newSpeedupTexture); return pNewTexture; } bool CGUIFontTTFDX::CopyCharToTexture( FT_BitmapGlyph bitGlyph, unsigned int x1, unsigned int y1, unsigned int x2, unsigned int y2) { FT_Bitmap bitmap = bitGlyph->bitmap; ComPtr pContext = DX::DeviceResources::Get()->GetImmediateContext(); if (m_speedupTexture && m_speedupTexture->Get() && pContext && bitmap.buffer) { CD3D11_BOX dstBox(x1, y1, 0, x2, y2, 1); pContext->UpdateSubresource(m_speedupTexture->Get(), 0, &dstBox, bitmap.buffer, bitmap.pitch, 0); return true; } return false; } void CGUIFontTTFDX::DeleteHardwareTexture() { } bool CGUIFontTTFDX::UpdateDynamicVertexBuffer(const SVertex* pSysMem, unsigned int vertex_count) { ComPtr pDevice = DX::DeviceResources::Get()->GetD3DDevice(); ComPtr pContext = DX::DeviceResources::Get()->GetD3DContext(); if (!pDevice || !pContext) return false; unsigned width = sizeof(SVertex) * vertex_count; if (width > m_vertexWidth) // create or re-create { CD3D11_BUFFER_DESC bufferDesc(width, D3D11_BIND_VERTEX_BUFFER, D3D11_USAGE_DYNAMIC, D3D11_CPU_ACCESS_WRITE); D3D11_SUBRESOURCE_DATA initData = {}; initData.pSysMem = pSysMem; if (FAILED( pDevice->CreateBuffer(&bufferDesc, &initData, m_vertexBuffer.ReleaseAndGetAddressOf()))) { CLog::LogF(LOGERROR, "Failed to create the vertex buffer."); return false; } m_vertexWidth = width; } else { D3D11_MAPPED_SUBRESOURCE resource; if (FAILED(pContext->Map(m_vertexBuffer.Get(), 0, D3D11_MAP_WRITE_DISCARD, 0, &resource))) { CLog::LogF(LOGERROR, "Failed to update the vertex buffer."); return false; } memcpy(resource.pData, pSysMem, width); pContext->Unmap(m_vertexBuffer.Get(), 0); } return true; } void CGUIFontTTFDX::CreateStaticIndexBuffer(void) { if (m_staticIndexBufferCreated) return; ComPtr pDevice = DX::DeviceResources::Get()->GetD3DDevice(); if (!pDevice) return; uint16_t index[ELEMENT_ARRAY_MAX_CHAR_INDEX][6]; for (size_t i = 0; i < ELEMENT_ARRAY_MAX_CHAR_INDEX; i++) { index[i][0] = 4 * i; index[i][1] = 4 * i + 1; index[i][2] = 4 * i + 2; index[i][3] = 4 * i + 2; index[i][4] = 4 * i + 3; index[i][5] = 4 * i + 0; } CD3D11_BUFFER_DESC desc(sizeof(index), D3D11_BIND_INDEX_BUFFER, D3D11_USAGE_IMMUTABLE); D3D11_SUBRESOURCE_DATA initData = {}; initData.pSysMem = index; if (SUCCEEDED( pDevice->CreateBuffer(&desc, &initData, m_staticIndexBuffer.ReleaseAndGetAddressOf()))) m_staticIndexBufferCreated = true; } bool CGUIFontTTFDX::m_staticIndexBufferCreated = false; ComPtr CGUIFontTTFDX::m_staticIndexBuffer = nullptr; void CGUIFontTTFDX::OnDestroyDevice(bool fatal) { m_staticIndexBufferCreated = false; m_vertexWidth = 0; m_staticIndexBuffer = nullptr; m_vertexBuffer = nullptr; } void CGUIFontTTFDX::OnCreateDevice(void) { }