You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
362 lines
11 KiB
362 lines
11 KiB
/*
|
|
* Copyright (c) 2019-2021, NVIDIA CORPORATION. All rights reserved.
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*
|
|
* SPDX-FileCopyrightText: Copyright (c) 2019-2021 NVIDIA CORPORATION
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
//--------------------------------------------------------------------
|
|
|
|
#include "nvpsystem.hpp"
|
|
|
|
#define GLFW_INCLUDE_NONE
|
|
#include <GLFW/glfw3.h>
|
|
#define GLFW_EXPOSE_NATIVE_X11
|
|
#include <GLFW/glfw3native.h>
|
|
|
|
#include <vector>
|
|
#include <algorithm>
|
|
#include <unistd.h>
|
|
#include <stdio.h>
|
|
#include <limits.h>
|
|
#include <string>
|
|
#include <assert.h>
|
|
#include <memory>
|
|
#include <sys/shm.h>
|
|
#include <X11/extensions/XShm.h>
|
|
|
|
// Samples include their own definitions of stb_image. Use STB_IMAGE_WRITE_STATIC to avoid issues with multiple
|
|
// definitions in the nvpro_core static lib at the cost of having the code exist multiple times.
|
|
#define STB_IMAGE_WRITE_IMPLEMENTATION
|
|
#define STB_IMAGE_WRITE_STATIC
|
|
#include <stb_image_write.h>
|
|
|
|
#include "linux_file_dialog.h"
|
|
|
|
union Pixel
|
|
{
|
|
uint32_t data;
|
|
struct
|
|
{
|
|
uint8_t r, g, b, a;
|
|
} channels;
|
|
};
|
|
|
|
// Object to allocate and hold a shared memory XImage and a shared memory segment
|
|
// This allows reading/writing an XImage in one IPC call
|
|
// Check XShmQueryExtension() before using
|
|
class XShmImage
|
|
{
|
|
public:
|
|
XShmImage(Display* display, int width, int height)
|
|
: m_display{display}
|
|
{
|
|
// Allocate a shared XImage
|
|
int screen = XDefaultScreen(m_display);
|
|
Visual* visual = XDefaultVisual(m_display, screen);
|
|
int depth = DefaultDepth(m_display, screen);
|
|
m_image = XShmCreateImage(m_display, visual, depth, ZPixmap, nullptr, &m_shmSegmentInfo, width, height);
|
|
if(!m_image)
|
|
{
|
|
LOGE("Error: XShmCreateImage() failed\n");
|
|
return;
|
|
}
|
|
|
|
// Create the shared memory, used by XShmGetImage()
|
|
int permissions = 0600;
|
|
m_shmID = shmget(IPC_PRIVATE, height * m_image->bytes_per_line, IPC_CREAT | permissions);
|
|
if(m_shmID == -1)
|
|
{
|
|
LOGE("Error: shmget() failed\n");
|
|
return;
|
|
}
|
|
|
|
// Map the shared memory segment into the address space of this process
|
|
m_shmAddr = shmat(m_shmID, 0, 0);
|
|
if(reinterpret_cast<intptr_t>(m_shmAddr) == -1)
|
|
{
|
|
LOGE("Error: shmat() failed\n");
|
|
return;
|
|
}
|
|
|
|
// Use the allocated shared memory for the XImage
|
|
m_shmSegmentInfo.shmid = m_shmID;
|
|
m_shmSegmentInfo.shmaddr = reinterpret_cast<char*>(m_shmAddr);
|
|
m_shmSegmentInfo.readOnly = false;
|
|
m_image->data = m_shmSegmentInfo.shmaddr;
|
|
|
|
// Get the X server to attach the shared memory segment on its side and sync
|
|
if(!XShmAttach(m_display, &m_shmSegmentInfo))
|
|
LOGE("Error: XShmAttach() failed\n");
|
|
if(!XSync(m_display, false))
|
|
LOGE("Error: XSync() failed\n");
|
|
return;
|
|
}
|
|
~XShmImage()
|
|
{
|
|
if(m_image)
|
|
{
|
|
if(!XShmDetach(m_display, &m_shmSegmentInfo))
|
|
LOGE("Error: XShmDetach() failed\n");
|
|
if(!XDestroyImage(m_image))
|
|
LOGE("Error: XDestroyImage() failed\n");
|
|
}
|
|
if(reinterpret_cast<intptr_t>(m_shmAddr) != -1 && shmdt(m_shmAddr) == -1)
|
|
LOGE("Error: shmdt() failed\n");
|
|
if(m_shmID != -1 && shmctl(m_shmID, IPC_RMID, nullptr) == -1)
|
|
LOGE("Error: shmctl(IPC_RMID) failed\n");
|
|
}
|
|
|
|
// Get the X server to copy the window contents into the shared memory
|
|
bool read(Window window) { return XShmGetImage(m_display, window, m_image, 0, 0, AllPlanes); }
|
|
|
|
// In lieu of exceptions, call this after constructing to see if the constructor failed
|
|
bool valid() const { return m_shmID != -1 && reinterpret_cast<intptr_t>(m_shmAddr) != -1; }
|
|
|
|
// Returns the XImage object to be accessed after calling read()
|
|
XImage* image() const { return m_image; }
|
|
|
|
private:
|
|
int m_shmID{-1};
|
|
void* m_shmAddr{reinterpret_cast<void*>(-1)};
|
|
XShmSegmentInfo m_shmSegmentInfo{};
|
|
Display* m_display{};
|
|
XImage* m_image{};
|
|
};
|
|
|
|
void NVPSystem::windowScreenshot(struct GLFWwindow* glfwin, const char* filename)
|
|
{
|
|
const int bytesPerPixel = sizeof(Pixel);
|
|
int width{};
|
|
int height{};
|
|
std::unique_ptr<XShmImage> shmImage;
|
|
std::vector<Pixel> imageData;
|
|
XImage* fallbackXImage{};
|
|
|
|
Display* display = glfwGetX11Display();
|
|
Window window = glfwGetX11Window(glfwin);
|
|
glfwGetWindowSize(glfwin, &width, &height);
|
|
|
|
if(XShmQueryExtension(display))
|
|
{
|
|
// Use the shared memory extension if it is supported to avoid expensive XGetPixel calls
|
|
// Shared memory allows X11 to copy all the image data at once
|
|
shmImage = std::make_unique<XShmImage>(display, width, height);
|
|
if(shmImage->valid() && bytesPerPixel * 8 == shmImage->image()->bits_per_pixel)
|
|
{
|
|
if(shmImage->read(window))
|
|
{
|
|
XImage* ximg = shmImage->image();
|
|
imageData.reserve(width * height);
|
|
for(int y = 0; y < ximg->height; ++y)
|
|
{
|
|
Pixel* ximgData = reinterpret_cast<Pixel*>(ximg->data + ximg->bytes_per_line * y);
|
|
imageData.insert(imageData.end(), ximgData, ximgData + ximg->width);
|
|
}
|
|
|
|
// bgr to rgb
|
|
for(Pixel& pixel : imageData)
|
|
{
|
|
std::swap(pixel.channels.r, pixel.channels.b);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
LOGE("Error: Failed to get window contents for screenshot. Falling back to XGetPixel()\n");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
LOGE("Error: Failed to create XShm Image for screenshot. Falling back to XGetPixel()\n");
|
|
}
|
|
}
|
|
|
|
if(imageData.empty())
|
|
{
|
|
fallbackXImage = XGetImage(display, window, 0, 0, width, height, AllPlanes, ZPixmap);
|
|
if(!fallbackXImage)
|
|
{
|
|
LOGE("Error: XGetImage() failed to get window contents for screenshot\n");
|
|
return;
|
|
}
|
|
if(bytesPerPixel * 8 != fallbackXImage->bits_per_pixel)
|
|
{
|
|
LOGE("Error: XGetImage() returned an image with %i bits per pixel but only %i is supported\n",
|
|
fallbackXImage->bits_per_pixel, bytesPerPixel * 8);
|
|
return;
|
|
}
|
|
imageData.reserve(width * height);
|
|
for(int y = 0; y < height; ++y)
|
|
{
|
|
for(int x = 0; x < width; ++x)
|
|
{
|
|
Pixel pixel;
|
|
pixel.data = static_cast<uint32_t>(XGetPixel(fallbackXImage, x, y));
|
|
std::swap(pixel.channels.r, pixel.channels.b); // bgr to rgb
|
|
pixel.channels.a = 0xff; // set full alpha
|
|
imageData.push_back(pixel);
|
|
}
|
|
}
|
|
}
|
|
|
|
if(!stbi_write_png(filename, width, height, 4, imageData.data(), width * bytesPerPixel))
|
|
{
|
|
LOGE("Error: Writing %s failed\n", filename);
|
|
}
|
|
|
|
if(fallbackXImage && !XDestroyImage(fallbackXImage))
|
|
LOGE("Error: XDestroyImage() failed\n");
|
|
}
|
|
|
|
void NVPSystem::windowClear(struct GLFWwindow* glfwin, uint32_t r, uint32_t g, uint32_t b)
|
|
{
|
|
Window hwnd = glfwGetX11Window(glfwin);
|
|
assert(0 && "not yet implemented");
|
|
}
|
|
|
|
static void fixSingleFilter(std::string* pFilter);
|
|
|
|
static std::vector<std::string> toFilterArgs(const char* exts)
|
|
{
|
|
// Convert exts list to filter format recognized by portable-file-dialogs
|
|
// Try to match implemented nvpsystem on Windows behavior:
|
|
// Alternate between human-readable descriptions and filter strings.
|
|
// | separates strings
|
|
// ; separates filters e.g. .png|.gif
|
|
// Case-insensitive e.g. .png = .PNG = .pNg
|
|
|
|
// Split strings by |
|
|
std::vector<std::string> filterArgs(1);
|
|
for(const char* pC = exts; pC != nullptr && *pC != '\0'; ++pC)
|
|
{
|
|
char c = *pC;
|
|
if(c == '|')
|
|
filterArgs.emplace_back();
|
|
else
|
|
filterArgs.back().push_back(c);
|
|
}
|
|
|
|
// Default arguments
|
|
if(filterArgs.size() < 2)
|
|
{
|
|
filterArgs = {"All files", "*"};
|
|
}
|
|
|
|
// Split filters by ; and fix those filters.
|
|
for(size_t i = 1; i < filterArgs.size(); i += 2)
|
|
{
|
|
std::string& arg = filterArgs[i];
|
|
std::string newArg;
|
|
std::string singleFilter;
|
|
for(char c : arg)
|
|
{
|
|
if(c == ';')
|
|
{
|
|
fixSingleFilter(&singleFilter);
|
|
newArg += std::move(singleFilter);
|
|
singleFilter.clear();
|
|
newArg += ' '; // portable-file-dialogs uses spaces to separate... win32 wants no spaces in filters at all so presumably this is fine.
|
|
}
|
|
else
|
|
{
|
|
singleFilter.push_back(c);
|
|
}
|
|
}
|
|
fixSingleFilter(&singleFilter);
|
|
newArg += std::move(singleFilter);
|
|
arg = std::move(newArg);
|
|
}
|
|
|
|
return filterArgs;
|
|
}
|
|
|
|
static void fixSingleFilter(std::string* pFilter)
|
|
{
|
|
// Make case insensitive.
|
|
std::string newFilter;
|
|
for(char c : *pFilter)
|
|
{
|
|
if(('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z'))
|
|
{
|
|
// replace c with [cC] to make case-insensitive. TODO: Unicode support, not sure how to implement for multibyte utf-8 characters.
|
|
newFilter.push_back('[');
|
|
newFilter.push_back(c);
|
|
newFilter.push_back(char(c ^ 32));
|
|
newFilter.push_back(']');
|
|
}
|
|
else
|
|
{
|
|
newFilter.push_back(c);
|
|
}
|
|
}
|
|
*pFilter = std::move(newFilter);
|
|
}
|
|
|
|
std::string NVPSystem::windowOpenFileDialog(struct GLFWwindow* glfwin, const char* title, const char* exts)
|
|
{
|
|
// Not sure yet how to use this; maybe make as a child window somehow?
|
|
[[maybe_unused]] Window hwnd = glfwGetX11Window(glfwin);
|
|
|
|
std::vector<std::string> filterArgs = toFilterArgs(exts);
|
|
std::vector<std::string> resultVector = open_file(title, ".", filterArgs).result();
|
|
assert(resultVector.size() <= 1);
|
|
return resultVector.empty() ? "" : std::move(resultVector[0]);
|
|
}
|
|
|
|
std::string NVPSystem::windowSaveFileDialog(struct GLFWwindow* glfwin, const char* title, const char* exts)
|
|
{
|
|
// Not sure yet how to use this; maybe make as a child window somehow?
|
|
[[maybe_unused]] Window hwnd = glfwGetX11Window(glfwin);
|
|
|
|
std::vector<std::string> filterArgs = toFilterArgs(exts);
|
|
return save_file(title, ".", filterArgs).result();
|
|
}
|
|
|
|
void NVPSystem::sleep(double seconds)
|
|
{
|
|
::sleep(seconds);
|
|
}
|
|
|
|
void NVPSystem::platformInit()
|
|
{
|
|
}
|
|
|
|
void NVPSystem::platformDeinit()
|
|
{
|
|
}
|
|
|
|
static bool s_exePathInit = false;
|
|
|
|
std::string NVPSystem::exePath()
|
|
{
|
|
static std::string s_exePath;
|
|
|
|
if(!s_exePathInit)
|
|
{
|
|
char modulePath[PATH_MAX];
|
|
ssize_t modulePathLength = readlink( "/proc/self/exe", modulePath, PATH_MAX );
|
|
|
|
s_exePath = std::string(modulePath, modulePathLength > 0 ? modulePathLength : 0);
|
|
|
|
size_t last = s_exePath.rfind('/');
|
|
if(last != std::string::npos)
|
|
{
|
|
s_exePath = s_exePath.substr(0, last) + std::string("/");
|
|
}
|
|
|
|
s_exePathInit = true;
|
|
}
|
|
|
|
return s_exePath;
|
|
}
|
|
|