Permalink
Cannot retrieve contributors at this time
368 lines (275 sloc)
9.28 KB
| /* | |
| * This file is part of the CitizenFX project - http://citizen.re/ | |
| * | |
| * See LICENSE and MENTIONS in the root of the source tree for information | |
| * regarding licensing. | |
| */ | |
| #include "StdInc.h" | |
| #include <DrawCommands.h> | |
| #include <grcTexture.h> | |
| #include <CfxRGBA.h> | |
| #include <wrl.h> | |
| #include <wincodec.h> | |
| #include <boost/bimap.hpp> | |
| #include <mmsystem.h> | |
| #include <mutex> | |
| #include <GfxUtil.h> | |
| #include <Error.h> | |
| using namespace Microsoft::WRL; | |
| #pragma comment(lib, "winmm.lib") | |
| #pragma comment(lib, "windowscodecs.lib") | |
| // API for compatibility with the ViSH 'texture' API. | |
| // FIXME: copied from nui:core - might need to be shared? | |
| static rage::grcTexture* InitializeTextureFromFile(const std::wstring& filename) | |
| { | |
| ComPtr<IWICBitmapDecoder> decoder; | |
| ComPtr<IWICImagingFactory> imagingFactory; | |
| CoInitializeEx(nullptr, COINIT_MULTITHREADED); | |
| HRESULT hr = CoCreateInstance(CLSID_WICImagingFactory1, nullptr, CLSCTX_INPROC_SERVER, __uuidof(IWICImagingFactory), (void**)imagingFactory.GetAddressOf()); | |
| if (FAILED(hr)) | |
| { | |
| return nullptr; | |
| } | |
| hr = imagingFactory->CreateDecoderFromFilename(filename.c_str(), nullptr, GENERIC_READ, WICDecodeMetadataCacheOnDemand, decoder.GetAddressOf()); | |
| if (SUCCEEDED(hr)) | |
| { | |
| ComPtr<IWICBitmapFrameDecode> frame; | |
| hr = decoder->GetFrame(0, frame.GetAddressOf()); | |
| if (SUCCEEDED(hr)) | |
| { | |
| ComPtr<IWICBitmapSource> source; | |
| ComPtr<IWICBitmapSource> convertedSource; | |
| UINT width = 0, height = 0; | |
| frame->GetSize(&width, &height); | |
| // try to convert to a pixel format we like | |
| frame.As(&source); | |
| hr = WICConvertBitmapSource(GUID_WICPixelFormat32bppRGBA, source.Get(), convertedSource.GetAddressOf()); | |
| if (SUCCEEDED(hr)) | |
| { | |
| source = convertedSource; | |
| } | |
| // create a pixel data buffer | |
| uint32_t* pixelData = new uint32_t[width * height]; | |
| hr = source->CopyPixels(nullptr, width * 4, width * height * 4, reinterpret_cast<BYTE*>(pixelData)); | |
| if (SUCCEEDED(hr)) | |
| { | |
| rage::grcTextureReference reference; | |
| memset(&reference, 0, sizeof(reference)); | |
| reference.width = width; | |
| reference.height = height; | |
| reference.depth = 1; | |
| reference.stride = width * 4; | |
| #ifdef GTA_NY | |
| reference.format = 1; // RGBA | |
| #elif defined(GTA_FIVE) | |
| reference.format = 11; // should correspond to DXGI_FORMAT_B8G8R8A8_UNORM | |
| #endif | |
| // swap the pixels before writing | |
| std::vector<uint8_t> inDataConverted; | |
| inDataConverted.resize(width * height * 4); | |
| ConvertImageDataRGBA_BGRA(0, 0, width, height, width * 4, pixelData, width * 4, &inDataConverted[0]); | |
| delete[] pixelData; | |
| reference.pixelData = (uint8_t*)&inDataConverted[0]; | |
| return rage::grcTextureFactory::getInstance()->createImage(&reference, nullptr); | |
| } | |
| } | |
| } | |
| return nullptr; | |
| } | |
| struct VishTexture | |
| { | |
| struct Instance | |
| { | |
| int level; | |
| uint32_t expire; | |
| float size[2]; | |
| float center[2]; | |
| float pos[2]; | |
| float rotation; | |
| CRGBA color; | |
| float aspectValue; | |
| }; | |
| rage::grcTexture* texture; | |
| std::map<int, Instance> instances; | |
| VishTexture() | |
| : texture(nullptr) | |
| { | |
| } | |
| VishTexture(rage::grcTexture* texture) | |
| : texture(texture) | |
| { | |
| } | |
| }; | |
| static std::map<int, VishTexture> g_vishTextures; | |
| static int g_vishTextureId; | |
| // to bind textures to existing IDs | |
| static boost::bimap<int, std::string> g_vishTextureIds; | |
| static std::mutex g_textureLock; | |
| DLL_EXPORT int createTexture(const char* fileName) | |
| { | |
| // lock | |
| std::unique_lock<std::mutex> lock(g_textureLock); | |
| // look up if a texture with this 'name' already exists, if so, return it | |
| std::string fileNameStr = fileName; | |
| auto it = g_vishTextureIds.right.find(fileNameStr); | |
| if (it != g_vishTextureIds.right.end()) | |
| { | |
| return it->second; | |
| } | |
| // convert the filename from UTF-8... | |
| std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>, wchar_t> converter; | |
| std::wstring passedFileName = converter.from_bytes(fileNameStr); | |
| std::wstring retFileName = passedFileName; | |
| // then, try finding the requested file in various locations | |
| bool found = false; | |
| // absolute path? | |
| if (passedFileName[1] == L':' || passedFileName[0] == '\\') | |
| { | |
| found = true; | |
| } | |
| // plugins directory path? | |
| if (!found) | |
| { | |
| retFileName = MakeRelativeCitPath(va(L"plugins\\%s", passedFileName.c_str())); | |
| found = (GetFileAttributes(retFileName.c_str()) != INVALID_FILE_ATTRIBUTES); | |
| } | |
| // root Citizen directory? | |
| if (!found) | |
| { | |
| retFileName = MakeRelativeCitPath(passedFileName); | |
| found = (GetFileAttributes(retFileName.c_str()) != INVALID_FILE_ATTRIBUTES); | |
| } | |
| // root game directory? | |
| if (!found) | |
| { | |
| retFileName = MakeRelativeGamePath(passedFileName); | |
| found = (GetFileAttributes(retFileName.c_str()) != INVALID_FILE_ATTRIBUTES); | |
| } | |
| // nowhere at all? | |
| if (!found) | |
| { | |
| return -1; | |
| } | |
| // create the texture | |
| auto texture = InitializeTextureFromFile(retFileName); | |
| if (!texture) | |
| { | |
| GlobalError("Failed to initialize ViSH compatibility texture %s.", converter.to_bytes(retFileName).c_str()); | |
| return -1; | |
| } | |
| int thisId = g_vishTextureId++; | |
| g_vishTextures[thisId] = VishTexture{ texture }; | |
| g_vishTextureIds.insert({ thisId, fileName }); | |
| return thisId; | |
| } | |
| DLL_EXPORT void drawTexture(int id, int index, int level, int time, | |
| float sizeX, float sizeY, float centerX, float centerY, | |
| float posX, float posY, float rotation, float screenHeightScaleFactor, | |
| float r, float g, float b, float a) | |
| { | |
| // lock | |
| std::unique_lock<std::mutex> lock(g_textureLock); | |
| // check if existent | |
| auto it = g_vishTextures.find(id); | |
| if (it == g_vishTextures.end()) | |
| { | |
| return; | |
| } | |
| // store ref | |
| auto& texture = it->second; | |
| VishTexture::Instance instance; | |
| instance.level = level; | |
| instance.expire = timeGetTime() + time; | |
| instance.size[0] = sizeX; | |
| instance.size[1] = sizeY; | |
| instance.center[0] = centerX; | |
| instance.center[1] = centerY; | |
| instance.pos[0] = posX; | |
| instance.pos[1] = posY; | |
| instance.rotation = rotation; | |
| instance.aspectValue = screenHeightScaleFactor; | |
| instance.color = CRGBA::FromFloat(r, g, b, a); | |
| texture.instances[index] = instance; | |
| } | |
| static InitFunction initFunction([] () | |
| { | |
| OnPostFrontendRender.Connect([] () | |
| { | |
| uintptr_t a1; | |
| uintptr_t a2; | |
| EnqueueGenericDrawCommand([] (uintptr_t, uintptr_t) | |
| { | |
| uint32_t renderTime = timeGetTime(); | |
| std::vector<std::pair<rage::grcTexture*, VishTexture::Instance>> drawList; | |
| // get a draw list during the lock | |
| { | |
| std::unique_lock<std::mutex> lock(g_textureLock); | |
| for (auto& texture : g_vishTextures) | |
| { | |
| for (auto& instance : texture.second.instances) | |
| { | |
| if (renderTime <= instance.second.expire) | |
| { | |
| drawList.push_back({ texture.second.texture, instance.second }); | |
| } | |
| } | |
| } | |
| } | |
| // sort the draw list by 'global level' | |
| std::sort(drawList.begin(), drawList.end(), [] (const auto& left, const auto& right) | |
| { | |
| return left.second.level < right.second.level; | |
| }); | |
| // render based on the draw list | |
| auto oldBlendState = GetBlendState(); | |
| SetBlendState(GetStockStateIdentifier(BlendStateDefault)); | |
| auto oldRasterizerState = GetRasterizerState(); | |
| SetRasterizerState(GetStockStateIdentifier(StateType::RasterizerStateNoCulling)); | |
| auto oldDepthStencilState = GetDepthStencilState(); | |
| SetDepthStencilState(GetStockStateIdentifier(StateType::DepthStencilStateNoDepth)); | |
| // get screen resolution | |
| int width, height; | |
| GetGameResolution(width, height); | |
| for (auto& entry : drawList) | |
| { | |
| SetTextureGtaIm(entry.first); | |
| PushDrawBlitImShader(); | |
| uint32_t color = entry.second.color.AsARGB(); | |
| // this swaps ABGR (as CRGBA is ABGR in little-endian) to ARGB by rotating left | |
| color = (color & 0xFF00FF00) | _rotl(color & 0x00FF00FF, 16); | |
| // calculate verts | |
| float xG = entry.second.pos[0]; | |
| float yG = entry.second.pos[1]; | |
| float s = sin(entry.second.rotation * (3.14159265358979323846 / 180.0)); | |
| float c = cos(entry.second.rotation * (3.14159265358979323846 / 180.0)); | |
| auto rotatePoint = [&] (float x, float y) -> POINTF | |
| { | |
| x -= entry.second.center[0]; | |
| y -= entry.second.center[1]; | |
| float x2 = (x * c) + (y * s); | |
| float y2 = (-x * s) + (y * c); | |
| return POINTF{ x2, y2 }; | |
| }; | |
| auto scaleAndRotatePoint = [&] (float x, float y) | |
| { | |
| auto p = rotatePoint(x, y); | |
| return POINTF{ ((p.x * entry.second.size[0]) + xG) * width, ((p.y * entry.second.size[1] * entry.second.aspectValue) + yG) * height }; | |
| }; | |
| POINTF p1 = scaleAndRotatePoint(0.0f, 0.0f); | |
| POINTF p2 = scaleAndRotatePoint(1.0f, 0.0f); | |
| POINTF p3 = scaleAndRotatePoint(0.0f, 1.0f); | |
| POINTF p4 = scaleAndRotatePoint(1.0f, 1.0f); | |
| rage::grcBegin(4, 4); | |
| rage::grcVertex(p1.x, p1.y, 0.0f, 0.0f, 0.0f, -1.0f, color, 0.0f, 0.0f); | |
| rage::grcVertex(p2.x, p2.y, 0.0f, 0.0f, 0.0f, -1.0f, color, 1.0f, 0.0f); | |
| rage::grcVertex(p3.x, p3.y, 0.0f, 0.0f, 0.0f, -1.0f, color, 0.0f, 1.0f); | |
| rage::grcVertex(p4.x, p4.y, 0.0f, 0.0f, 0.0f, -1.0f, color, 1.0f, 1.0f); | |
| rage::grcEnd(); | |
| PopDrawBlitImShader(); | |
| } | |
| SetDepthStencilState(oldDepthStencilState); | |
| SetRasterizerState(oldRasterizerState); | |
| SetBlendState(oldBlendState); | |
| }, &a1, &a2); | |
| }); | |
| }); |