OpenGL 使用离屏渲染技术进行截图

news2024/9/30 14:41:11

文章目录

  • 背景
    • 第三方库
    • 注意
    • 参考资料
  • 一、离屏渲染
    • (一)帧缓冲与帧缓冲对象(FBO)
    • (二)附件(Attachment)
  • 二、具体代码
    • (一)主线程创建OpenGL窗口
    • (二)渲染子线程
    • (三)窗口尺寸改变的回调函数
    • (四)创建FBO
    • (五)主线程主动调用截图函数
  • 三、注意


背景

我有一个Duilib项目,目前已经实现了将OpenGL窗口嵌入到该项目中,并且能够加载obj文件以显示。这是我的效果:
在这里插入图片描述

第三方库

  1. glfw-3.3.8(创建OpenGL窗口)
  2. glad-0.1.33(使用OpenGL函数)
  3. glm-0.9.9.8(数学库)
  4. tinyobjloader-2.0.0rc13(读取Obj文件)

注意

  1. 因为Duilib项目是主线程,OpenGL窗口是嵌入进去,但是它也要自己的线程,所以只能作为子线程去渲染。
  2. 对于我的项目,我仍然需要去控制OpenGL窗口的显示和隐藏。一个具体的需求是,我需要在OpenGL窗口隐藏的场景下,对窗口渲染内容进行截图。所以就引出了离屏渲染技术。

参考资料

LearnOpenGL:非常好用的OpenGL学习资料,如果能认真过一遍,相信就能完成大部分的OpenGL需求了。
ChatGPT:在有基本概念之后,使用AI能大幅提高编程速度。


一、离屏渲染

先通过 LearnOpenGL-帧缓冲 了解一些基本。

(一)帧缓冲与帧缓冲对象(FBO)

简而言之,帧缓冲是一种技术,你可以等价为双缓冲算法。
帧缓冲对象是一个OpenGL对象,与什么VAO, VBO本质上没什么差别,就是一个名称标识。
你可以把FBO当成是MFC中CDC对象,绑定之后就可以在上面绘制各种内容。至于绘制完成之后,你想要拿去做什么,显示在屏幕或者截图,那是你自己的操作。

(二)附件(Attachment)

正如CDC一样,FBO也只是一个标识,如果需要绘制内容,仍然需要绑定画布(Bitmap)才能绘制内容。只不过在FBO中,画布被区分了不同的类型,分别用于绘制不同的内容,比如颜色附件和深度附件。它们通过glTexImage2D()来制定纹理的类型。

二、具体代码

总体的思路是:

  1. 当窗口尺寸改变时,主动创建FBO相关内容。
  2. 主线程要进行截图时,将m_bStartCapture = true; ,通知子线程将内容绘制在FBO上;
  3. 等待绘制完成,根据需要将像素上下反转(因为OpenGL是左下角是原点,而Bitmap图像是左上角为原点)。

(一)主线程创建OpenGL窗口

bool MyOpenGLWnd::CreateOpenGLWnd(HWND hWnd)
{
	// 1. Init GLFW library
	glfwInit();
	glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
	glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
	glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);

	// 2. Create GLFW window
	m_pGlfwWnd = glfwCreateWindow(400, 400, "OpenGL", NULL, NULL);
	if (m_pGlfwWnd == NULL)
	{
		m_pLogger->error("Failed to create GLFW window");
		glfwTerminate();
		return false;
	}

	// 3. Retrieve the handle of the glfw window and embed it into the parent window
	HWND hwndGLFW = glfwGetWin32Window(m_pGlfwWnd);
	SetWindowLongW(hwndGLFW, GWL_STYLE, WS_VISIBLE);
	MoveWindow(hwndGLFW, 0, 0, 0, 0, TRUE);
	SetParent(hwndGLFW, hWnd);

	// 4. Set the GLFW window context as the main context
	glfwMakeContextCurrent(m_pGlfwWnd);

	// 5. Register the GLAD function address (context must be set for it to take effect)
	if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
	{
		m_pLogger->error("Failed to initialize GLAD");
		glfwTerminate();
		return false;
	}

	// 6. Enable depth test
	glEnable(GL_DEPTH_TEST);

	// 7. Create shader object
	m_pShader = new Shader;
	std::string error;
	if (!m_pShader->Load("./shader/Shader.vs", "./shader/Shader.fs", error))
	{
		m_pLogger->error(error);
		glfwTerminate();
		return false;
	}

	// 8. Create camera object
	m_pCamera = new Camera(glm::vec3(0.0f, 0.0f, 3.0f));

	// 9. Reset the context because the render procedure will be run in the child thread
	glfwMakeContextCurrent(NULL);

	// 10. Create render thread
	createRenderThread();

	return true;
}

(二)渲染子线程

void MyOpenGLWnd::createRenderThread()
{
	m_pRenderThread = new std::thread([this]() {
		glfwMakeContextCurrent(m_pGlfwWnd);

		// 2. Register the callback function we need
		glfwSetFramebufferSizeCallback(m_pGlfwWnd, [](GLFWwindow* window, int width, int height) {
			auto pThisClass = static_cast<MyOpenGLWnd*>(glfwGetWindowUserPointer(window));
			if (pThisClass) {
				pThisClass->frameSizeCallback(window, width, height);
			}
		});

		glfwSetMouseButtonCallback(m_pGlfwWnd, [](GLFWwindow* window, int button, int action, int mods) {
			auto pThisClass = static_cast<MyOpenGLWnd*>(glfwGetWindowUserPointer(window));
			if (pThisClass) {
				pThisClass->mouseBtnCallback(window, button, action, mods);
			}
		});

		glfwSetCursorPosCallback(m_pGlfwWnd, [](GLFWwindow* window, double xposIn, double yposIn) {
			auto pThisClass = static_cast<MyOpenGLWnd*>(glfwGetWindowUserPointer(window));
			if (pThisClass) {
				pThisClass->mousePosCallback(window, xposIn, yposIn);
			}
		});

		glfwSetScrollCallback(m_pGlfwWnd, [](GLFWwindow* window, double xoffset, double yoffset) {
			auto pThisClass = static_cast<MyOpenGLWnd*>(glfwGetWindowUserPointer(window));
			if (pThisClass) {
				pThisClass->scrollCallback(window, xoffset, yoffset);
			}
		});

		glfwSetKeyCallback(m_pGlfwWnd, [](GLFWwindow* window, int key, int scancode, int action, int mods) {
			auto pThisClass = static_cast<MyOpenGLWnd*>(glfwGetWindowUserPointer(window));
			if (pThisClass) {
				pThisClass->keyCallback(window, key, scancode, action, mods);
			}
		});
		glfwSetWindowUserPointer(m_pGlfwWnd, this);

		// 3. Render loop
		while (!glfwWindowShouldClose(m_pGlfwWnd))
		{
			// If window is not visible, just continue.
			if (!glfwGetWindowAttrib(m_pGlfwWnd, GLFW_VISIBLE) && !m_bForceRendering)
			{
				Sleep(10);
				continue;
			}

			// 4. Check the whether the mesh needs to update
			checkVerticeUpdate();

			// 5. Check window size
			checkFrameSize();

			// 6. Check mouse position
			checkMousePos();

			// 7. Check mouse wheel              
			checkScrollOffset();

			// 8. Chek the keydown
			checkKeyDown();

			// 9. Update the timing
			float currentFrameTime = static_cast<float>(glfwGetTime());
			m_deltaTime = currentFrameTime - m_lastFrameTime;
			m_lastFrameTime = currentFrameTime;

			bool bCapture = m_bStartCapture;
			if (bCapture)
			{
				glBindFramebuffer(GL_FRAMEBUFFER, m_frameBufferObj._name);
			}
			else
			{
				glBindFramebuffer(GL_FRAMEBUFFER, 0);
			}

			// 10. Refresh color buffer and depth
			glClearColor(m_clearColor.r, m_clearColor.g, m_clearColor.b, m_clearColor.a);
			glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

			// 11. Use shader
			m_pShader->use();

#pragma region 12. Set vertices shader
			// 12.1 pass projection matrix to shader (note that in this case it could change every frame)
			const auto& frameSize = m_frameSize.load();
			glm::mat4 projection{ 1.0f };
			if (frameSize.y > 0)
			{
				ReadLock(m_cameraMutex);
				projection = glm::perspective(glm::radians(m_pCamera->Zoom), (float)m_frameSize.load().x / (float)m_frameSize.load().y, 0.1f, 100.0f);
			}
			m_pShader->setMat4("projection", projection);

			// 12.2 camera/view transformation
			{
				ReadLock(m_cameraMutex);
				glm::mat4 view = m_pCamera->GetViewMatrix();
				m_pShader->setMat4("view", view);
			}

			// 12.3 model transformation
			glm::mat4 model = glm::mat4(1.0f);
			if (m_bAutorotate) {
				updateRotationAngles(m_deltaTime);
			}
			model = glm::rotate(model, glm::radians(m_modelRotAngle.x), glm::vec3(1.0f, 0.0f, 0.0f));
			model = glm::rotate(model, glm::radians(m_modelRotAngle.y), glm::vec3(0.0f, 1.0f, 0.0f));
			m_pShader->setMat4("model", model);
#pragma endregion

#pragma region 13. Set fragment shader
			{
				ReadLock(m_cameraMutex);
				m_pShader->setVec3("viewPos", m_pCamera->Position);
			}

			// light properties
			glm::vec3 lightColor{ 1.0, 1.0, 1.0 };

			glm::vec3 diffuseColor = lightColor * glm::vec3(0.5f); // decrease the influence
			glm::vec3 ambientColor = diffuseColor * glm::vec3(0.2f); // low influence
			m_pShader->setVec3("light.ambient", ambientColor);
			m_pShader->setVec3("light.diffuse", diffuseColor);
			m_pShader->setVec3("light.specular", 1.0f, 1.0f, 1.0f);

			m_pShader->setVec3("light.direction", glm::normalize(glm::vec3(-0.5f, -0.5f, -0.5f)));
#pragma endregion

			// 14. Draw elements
			for (const Mesh& mesh : m_vMeshs)
			{
				m_pShader->setVec3("material.ambient", mesh.ambient);
				m_pShader->setVec3("material.diffuse", mesh.diffuse);
				m_pShader->setVec3("material.specular", mesh.specular); // specular lighting doesn't have full effect on this object's material
				m_pShader->setFloat("material.shininess", mesh.shininess);

				glBindVertexArray(mesh.VAO); // seeing as we only have a single VAO there's no need to bind it every time, but we'll do so to keep things a bit more organized
				glDrawElements(GL_TRIANGLES, mesh.indices.size(), GL_UNSIGNED_INT, 0);
				glBindVertexArray(0);
			}

			for (const auto& mesh : m_vPlaneMeshs)
			{
				m_pShader->setVec3("material.ambient", mesh.ambient);
				m_pShader->setVec3("material.diffuse", mesh.diffuse);
				m_pShader->setVec3("material.specular", mesh.specular); // specular lighting doesn't have full effect on this object's material
				m_pShader->setFloat("material.shininess", mesh.shininess);

				glBindVertexArray(mesh.VAO); // seeing as we only have a single VAO there's no need to bind it every time, but we'll do so to keep things a bit more organized
				glDrawElements(GL_TRIANGLES, mesh.indices.size(), GL_UNSIGNED_INT, 0);
				glBindVertexArray(0);
			}

			if (bCapture && m_bForceRendering)
			{
				auto frameSize = m_frameSize.load(std::memory_order_relaxed);
				size_t w = frameSize.x;
				size_t h = frameSize.y;
				size_t c = m_capImageChannel;
				size_t pixelSize = w * h * c; 
				
				m_vCaptureDatas.clear();
				m_vCaptureDatas.resize(pixelSize);
				glReadPixels(0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, m_vCaptureDatas.data());

				glBindFramebuffer(GL_FRAMEBUFFER, 0);

				m_bStartCapture = false;
				m_bForceRendering = false;
			}

			// 15. Update buffer
			glfwSwapBuffers(m_pGlfwWnd);
			glfwPollEvents();
		}

		// 16. release data
		for (auto& mesh : m_vMeshs)
		{
			glDeleteVertexArrays(1, &mesh.VAO);
			glDeleteBuffers(1, &mesh.VBO_1);
			glDeleteBuffers(1, &mesh.VBO_2);
			glDeleteBuffers(1, &mesh.EBO);
		}

		for (auto& mesh : m_vPlaneMeshs)
		{
			glDeleteVertexArrays(1, &mesh.VAO);
			glDeleteBuffers(1, &mesh.VBO_1);
			glDeleteBuffers(1, &mesh.VBO_2);
			glDeleteBuffers(1, &mesh.EBO);
		}

		m_frameBufferObj.Clear();

		glfwDestroyWindow(m_pGlfwWnd);
	});
}

(三)窗口尺寸改变的回调函数

/*
	因为窗口尺寸改变是在主线程,渲染是在子线程,所以只能子线程时刻检查
*/
void MyOpenGLWnd::checkFrameSize()
{
	auto frameSize = m_frameSize.load(std::memory_order_relaxed);
	if (frameSize.bUpdate)
	{
		glViewport(0, 0, frameSize.x, frameSize.y);
		m_frameSize.store(FrameSize(false, frameSize.x, frameSize.y), std::memory_order_relaxed);

		createFBO();
	}
}

(四)创建FBO

/*
* @brief	Create FBO for off-line rendering
* @note
*
* @author 	Canliang Wu
* @day		2024/09/26
*/
void MyOpenGLWnd::createFBO()
{
	auto frameSize = m_frameSize.load(std::memory_order_relaxed);
	int width = frameSize.x;
	int height = frameSize.y;

	// Generate fbo
	if (m_frameBufferObj._name == 0)
	{
		glGenFramebuffers(1, &m_frameBufferObj._name);
	}
	glBindFramebuffer(GL_FRAMEBUFFER, m_frameBufferObj._name);

	// Create textures for fbo
	if (!m_frameBufferObj._vTextures.empty())
	{
		glDeleteTextures(m_frameBufferObj._vTextures.size(), m_frameBufferObj._vTextures.data());
		m_frameBufferObj._vTextures.clear();
	}
	m_frameBufferObj._vTextures.resize(2);
	glGenTextures(m_frameBufferObj._vTextures.size(), m_frameBufferObj._vTextures.data());

	// Set color texture attributes
	glBindTexture(GL_TEXTURE_2D, m_frameBufferObj._vTextures[0]);
	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

	// Add color texture
	glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, m_frameBufferObj._vTextures[0], 0);

	// Set depth texture attributes (If there is a lack of depth texture, the screenshot will become fragmented)
	glBindTexture(GL_TEXTURE_2D, m_frameBufferObj._vTextures[1]);
	glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, width, height, 0, GL_DEPTH_COMPONENT, GL_FLOAT, NULL);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);

	// Add depth texture
	glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, m_frameBufferObj._vTextures[1], 0);

	// Check the fbo is complete
	if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
		std::cerr << "Framebuffer is not complete!" << std::endl;
	}

	glBindTexture(GL_TEXTURE_2D, 0);		// unbind Texture
	glBindFramebuffer(GL_FRAMEBUFFER, 0);	// unbind Framebuffer
}

(五)主线程主动调用截图函数

/*
* @brief	Capture glfw image no matter whether the window is displayed or hidden
* @param	vImageDatas - image source data
* @param	bResize - when the glfw is hidden, need to set the viewport directly
* @param	w - viewport width
* @param	h - viewport height
* @note
*
* @author 	Canliang Wu
* @day		2024/09/26
*/
void MyOpenGLWnd::CaptureScreen(std::vector<unsigned char>& vImageDatas, bool bResize, int w, int h)
{
	// 1. Resize the viewport when the glfw window is hiding
	if (bResize)
	{
		m_frameSize.store(FrameSize(true, w, h), std::memory_order_relaxed);
	}

	// 2. Start capture 
	m_bStartCapture = true;
	m_bForceRendering = true;
	while (m_bStartCapture)
	{
		Sleep(10);
	}

	// 3. Reverse the image
	auto frameSize = m_frameSize.load(std::memory_order_relaxed);

	vImageDatas.resize(m_vCaptureDatas.size());
	for (int y = 0; y < frameSize.y; ++y) {
		for (int x = 0; x < frameSize.x; ++x) {
			// 原始像素位置
			int originalIndex = (frameSize.y - 1 - y) * frameSize.x * m_capImageChannel + x * m_capImageChannel;
			// 翻转后的像素位置
			int flippedIndex = y * frameSize.x * m_capImageChannel + x * m_capImageChannel;

			// 复制 RGB 值
			vImageDatas[flippedIndex] = m_vCaptureDatas[originalIndex];         // R
			vImageDatas[flippedIndex + 1] = m_vCaptureDatas[originalIndex + 1]; // G
			vImageDatas[flippedIndex + 2] = m_vCaptureDatas[originalIndex + 2]; // B
			vImageDatas[flippedIndex + 3] = m_vCaptureDatas[originalIndex + 3]; // A
		}
	}
}

三、注意

这里必须添加深度附件,不然就会出现这样的结果:
在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

python画图|自制渐变柱状图

在前述学习过程中&#xff0c;我们已经通过官网学习了如何绘制渐变的柱状图及其背景。 掌握一门技能的最佳检验方式就是通过实战&#xff0c;因此&#xff0c;本文尝试做一些渐变设计。 前述学习记录可查看链接&#xff1a; Python画图|渐变背景-CSDN博客 【1】柱状图渐变 …

CORE 中间件、wwwroot

ASP.NET Core中间件组件是被组装到应用程序管道中以处理HTTP请求和响应的软件组件&#xff08;从技术上来说&#xff0c;组件只是C&#xff03;类&#xff09;。 ASP.NET Core应用程序中的每个中间件组件都执行以下任务。 选择是否将 HTTP 请求传递给管道中的下一个组件。这可…

《C++》解密--单链表

目录 一、概念与结构 二、实现单链表 三、链表的分类 四、单链表算法题 一、概念与结构 1、节点 结点的组成主要有&#xff1a;当前结点要保存的数据和保存下一个节点的地址&#xff08;指针变量&#xff09; 图中指针变量plist保存的是第一个结点的地址&#xff0c;我们称p…

红日靶机(二)笔记

红日靶机二 环境搭建 只需要把虚拟机的 host-only&#xff08;仅主机&#xff09;网卡改为 10.10.10.0 网段&#xff0c;如下配置 把 NAT 网卡&#xff0c;改为 192.168.96.0 网段&#xff0c;如下 首先恢复到 v1.3 快照 让后点击放弃&#xff0c;放弃后再开机&#xff0c;用…

论文写作工具推荐小渡ai,MedSci,Open Access Library

1、知网作为写过论文或者即将要写论文的人&#xff0c;这个网站真的真的真的是你用的最多最多的网站。但是你一定不用自己充会员&#xff0c;因为你的学校肯定给你买了这个资料库&#xff0c;从学校图书馆的网页进去就行&#xff0c;或者你校外访问&#xff0c;就算是没有账号不…

【自动驾驶】控制算法(十一)深度解析车辆纵向控制 | 纵向双 PID 控制算法

写在前面&#xff1a; &#x1f31f; 欢迎光临 清流君 的博客小天地&#xff0c;这里是我分享技术与心得的温馨角落。&#x1f4dd; 个人主页&#xff1a;清流君_CSDN博客&#xff0c;期待与您一同探索 移动机器人 领域的无限可能。 &#x1f50d; 本文系 清流君 原创之作&…

心觉:如何重塑高效学习的潜意识(5)终结篇

Hi&#xff0c;我是心觉&#xff0c;与你一起玩转潜意识、脑波音乐和吸引力法则&#xff0c;轻松掌控自己的人生&#xff01; 挑战每日一省写作184/1000天 上篇文章讲了如何发挥边学边用的威力及其底层逻辑 到此为止&#xff0c;我们已经系统地把“系统化学习”和“边学边用…

scrapy 爬取微博(五)【最新超详细解析】: 爬取微博文章

1 读取配置参数 爬取微博文章首先需要读取settings.py中的设置的配置变量&#xff0c;然后编写爬虫&#xff0c;读取的配置变量主要有爬取的关键词、时间范围、爬取区域等。 class WeiboSearchSpider(scrapy.Spider):name weibo_searchallowed_domains [weibo.com]settings…

【北京迅为】《STM32MP157开发板嵌入式开发指南》- 第十五章 Linux 文件系统概念

iTOP-STM32MP157开发板采用ST推出的双核cortex-A7单核cortex-M4异构处理器&#xff0c;既可用Linux、又可以用于STM32单片机开发。开发板采用核心板底板结构&#xff0c;主频650M、1G内存、8G存储&#xff0c;核心板采用工业级板对板连接器&#xff0c;高可靠&#xff0c;牢固耐…

Navicat 工具 下载安装

准备工作 下载 下载链接&#xff1a;http://localhost:8080 演示环境 操作系统&#xff1a;windows10 产品&#xff1a;Navicat 版本&#xff1a; 15.0.25 注意&#xff1a;如果需要其他版本可以自行下载。 安装步骤 1、解压&#xff08;如果解压中出现提示威胁要允许&#…

基于CNN+Transformer混合模型实现交通流量时序预测(PyTorch版)

前言 系列专栏:【深度学习&#xff1a;算法项目实战】✨︎ 涉及医疗健康、财经金融、商业零售、食品饮料、运动健身、交通运输、环境科学、社交媒体以及文本和图像处理等诸多领域&#xff0c;讨论了各种复杂的深度神经网络思想&#xff0c;如卷积神经网络、循环神经网络、生成对…

PHP程序如何实现限制一台电脑登录?

PHP程序如何实现限制一台电脑登录&#xff1f; 可以使用以下几种方法&#xff1a; 1. IP地址限制&#xff1a;在PHP中&#xff0c;可以通过获取客户端的IP地址&#xff0c;然后与允许登录的IP地址列表进行比对。如果客户端的IP地址不在列表中&#xff0c;就禁止登录。 “php $…

洛谷P1789MC生存插火把

洛谷P1789MC生存插火把 这道题有一个小坑&#xff0c;就是火把照亮的地方可能不在数组里&#xff0c;注意要把那一块地方去掉&#xff0c;这道题的出题者把范围都告诉我们了&#xff0c;大大滴降低了这道题的难度 下面是我的代码 #include <stdio.h>int n, m,k ;//一…

HarmonyOS Next系列之水波纹动画特效实现(十三)

系列文章目录 HarmonyOS Next 系列之省市区弹窗选择器实现&#xff08;一&#xff09; HarmonyOS Next 系列之验证码输入组件实现&#xff08;二&#xff09; HarmonyOS Next 系列之底部标签栏TabBar实现&#xff08;三&#xff09; HarmonyOS Next 系列之HTTP请求封装和Token…

基于ssm+vue的在线家用电器销售系统

摘要 本文介绍了一个基于SSM&#xff08;SpringSpring MVCMyBatis&#xff09;框架与Vue.js技术的在线家用电器销售系统。该系统旨在为用户提供便捷的家用电器购买体验&#xff0c;同时为商家提供一个高效的销售管理平台。系统前端采用Vue.js框架开发&#xff0c;实现了响应式布…

【制作自解压程序】使用7Z制作自解压程序

文章目录 1.前言2.准备压缩包3.准备7zSD.sfx文件4.准备config.txt5.合并文件6.完成 1.前言 自解压程序是利用压缩包制作一个类似于下载程序样式的文件&#xff0c;可以让用户直接点击使用&#xff0c;而不是解压以后去文件中找哪个是启动程序。 2.准备压缩包 首先&#xff0…

【U8+】安装用友U8+16.5后,应用服务管理中缺少加密服务。

【问题描述】 安装用友U8+后,应用服务管理中,没有加密服务。 导致软件无法登录到加密服务器。 【解决方法】 此问题多为CPU所影响: 1、深信服和霆智虚拟机需要开启HOST CPU选项。不开启此选项无法发挥CPU的全部功能,对U8和SQL Server的性能影响很大,所以在U8V16.5中要求开…

SOMEIP_ETS_142: SD_Request_non_existing_Major_Version

测试目的&#xff1a; 验证DUT能够拒绝一个请求不存在的主版本号的SubscribeEventgroup消息&#xff0c;并以SubscribeEventgroupNAck作为响应。 描述 本测试用例旨在确保DUT遵循SOME/IP协议&#xff0c;当接收到一个请求不存在主版本号的SubscribeEventgroup消息时&#xf…

828华为云征文|部署个人文档管理系统 Docspell

828华为云征文&#xff5c;部署个人文档管理系统 Docspell 一、Flexus云服务器X实例介绍二、Flexus云服务器X实例配置2.1 重置密码2.2 服务器连接2.3 安全组配置2.4 Docker 环境搭建 三、Flexus云服务器X实例部署 Docspell3.1 Docspell 介绍3.2 Docspell 部署3.3 Docspell 使用…

PostgreSQL的学习心得和知识总结(一百五十二)|transaction_timeout:达到事务超时时终止会话

目录结构 注&#xff1a;提前言明 本文借鉴了以下博主、书籍或网站的内容&#xff0c;其列表如下&#xff1a; 1、参考书籍&#xff1a;《PostgreSQL数据库内核分析》 2、参考书籍&#xff1a;《数据库事务处理的艺术&#xff1a;事务管理与并发控制》 3、PostgreSQL数据库仓库…