安卓硬件加速hwui

安卓硬件加速
本文基于安卓11。

从 Android 3.0 (API 级别 11) 开始,Android 2D 渲染管道支持硬件加速,这意味着在 View 的画布上执行的所有绘图操作都使用 GPU。由于启用硬件加速所需的资源增加,你的应用程序将消耗更多内存。

软件绘制:

  1. Invalidate the hierarchy
  2. Draw the hierarchy

软件绘制在每次draw时都需要执行大量操作,比如一个Button位于另一个View上,当Button执行invalidate(),系统也重新绘制View尽管它什么都没有改变。

和硬件加速绘制:

  1. Invalidate the hierarchy
  2. Record and update display lists
  3. Draw the display lists

和软件绘制不同,硬件绘制不是立即执行绘制操作,而是UI线程把繁杂的绘制操作记录保存在display list当中,renderThread执行其中的绘制命令,对比软件绘制,硬件绘制只需要记录和更新dirty的View,也就是执行了invalidate()的View,其他的View可以重用display list中的记录。

其具体实现在hwui模块。
hwui UML:
hwui UML

1. RenderProxy

RenderProxy作为hwui提供给应用的功能接口,应用层通过ThreadedRenderer调用RenderProxy,RenderProxy内部持有RenderThread、CanvasContext、DrawFrameTask对象,CanvasContext拥有实际操作画面的能力,DrawFrameTask是对CanvasContext能力的封装。

ThreadedRenderer继承自HardwareRenderer,HardwareRenderer持有mNativeProxy变量,作为native层hwlib模块RenderProxy的引用。

RenderProxy提供了setSurface(), syncAndDrawFrame(), 等API供应用使用。

2. RenderThread

//ThreadedRenderer.java
void draw(View view, AttachInfo attachInfo, DrawCallbacks callbacks) {final Choreographer choreographer = attachInfo.mViewRootImpl.mChoreographer;choreographer.mFrameInfo.markDrawStart();updateRootDisplayList(view, callbacks);// register animating rendernodes which started animating prior to renderer// creation, which is typical for animators started prior to first drawif (attachInfo.mPendingAnimatingRenderNodes != null) {final int count = attachInfo.mPendingAnimatingRenderNodes.size();for (int i = 0; i < count; i++) {registerAnimatingRenderNode(attachInfo.mPendingAnimatingRenderNodes.get(i));}attachInfo.mPendingAnimatingRenderNodes.clear();// We don't need this anymore as subsequent calls to// ViewRootImpl#attachRenderNodeAnimator will go directly to us.attachInfo.mPendingAnimatingRenderNodes = null;}int syncResult = syncAndDrawFrame(choreographer.mFrameInfo);if ((syncResult & SYNC_LOST_SURFACE_REWARD_IF_FOUND) != 0) {Log.w("OpenGLRenderer", "Surface lost, forcing relayout");// We lost our surface. For a relayout next frame which should give us a new// surface from WindowManager, which hopefully will work.attachInfo.mViewRootImpl.mForceNextWindowRelayout = true;attachInfo.mViewRootImpl.requestLayout();}if ((syncResult & SYNC_REDRAW_REQUESTED) != 0) {attachInfo.mViewRootImpl.invalidate();}
}

对于硬件加速的设备,绘制时启动新线程RenderThread负责绘制工作,RenderThread继承Thread类,但不是指Java层的ThreadedRenderer类,而是native层hwui的RenderThread,可以理解为Java层的ThreadedRenderer作为RenderThread的一个接口。

ThreadedRenderer的draw方法主要有两个步骤。

  1. 更新DisplayList,updateRootDisplayList

​ 更新DisplayList,分为LAYER_TYPE_SOFTWARE、LAYER_TYPE_HARDWARE两种情况:

  • LAYER_TYPE_SOFTWARE:drawBitmap,每个View缓存了Bitmap对象mDrawingCache。
  • LAYER_TYPE_HARDWARE: 更新DisplayList。
  1. 同步并提交绘制请求,syncAndDrawFrame:Syncs the RenderNode tree to the render thread and requests a frame to be drawn.

syncAndDrawFrame通过上述引用调用RenderProxy的syncAndDrawFrame方法,RenderProxy在RenderThread添加一个新的任务,执行DrawFrameTask的run()方法。

3. ReliableSurface

Surface初始化完成后,就可以把它传递给hwui模块的RenderProxy、CanvasContext、IRenderPipeline等对象使用。

//ViewRootImpl.java
private void performTraversals() {bool surfaceCreated = !hadSurface && mSurface.isValid();bool surfaceDestroyed = hadSurface && !mSurface.isValid();bool surfaceReplaced = (surfaceGenerationId != mSurface.getGenerationId())&& mSurface.isValid();if (surfaceCreated) {if (mAttachInfo.mThreadedRenderer != null) {hwInitialized = mAttachInfo.mThreadedRenderer.initialize(mSurface);if (hwInitialized && (host.mPrivateFlags& View.PFLAG_REQUEST_TRANSPARENT_REGIONS) == 0) {// Don't pre-allocate if transparent regions// are requested as they may not be neededmAttachInfo.mThreadedRenderer.allocateBuffers();}}} else if (surfaceDestroyed) {if (mAttachInfo.mThreadedRenderer != null &&mAttachInfo.mThreadedRenderer.isEnabled()) {mAttachInfo.mThreadedRenderer.destroy();}} else if ((surfaceReplaced|| surfaceSizeChanged || windowRelayoutWasForced || colorModeChanged) {mAttachInfo.mThreadedRenderer.updateSurface(mSurface);}
}

ViewRootImpl判断surface状态是否是创建(surfaceCreated)、销毁(surfaceDestroyed)或者更新(surfaceReplaced|Changed),创建销毁和更新都是执行的同一个方法,销毁的时候setSurface(null),创建和更新setSurface(mSurface)。

mThreadedRenderer将mSurface通过RenderProxy传递给CanvasContext,更新其mNativeSurface变量std::unique_ptr<ReliableSurface> mNativeSurface;

ReliableSurface持有类变量ANativeWindow* mWindow;,是ANativeWindow的装饰者模式,ANativeWindow提供了扩展接口,使ReliableSurface可以在不改变现有对象结构的情况下,动态地向Surface对象添加功能,在其init()方法中通过添加拦截器,通过ANativeWindow扩展接口,将ReliableSurface的方法动态插入到Surface的接口中,通过拦截和管理ANativeWindow的操作,增强了对图形缓冲区的控制,从而提升系统的稳定性和渲染效果,例如检查缓冲区的状态是否合法、在操作失败时尝试恢复或提供警告、优化缓冲区的分配和释放逻辑等。

//ReliableSurface.cpp
void ReliableSurface::init() {int result = ANativeWindow_setCancelBufferInterceptor(mWindow, hook_cancelBuffer, this);LOG_ALWAYS_FATAL_IF(result != NO_ERROR, "Failed to set cancelBuffer interceptor: error = %d",result);result = ANativeWindow_setDequeueBufferInterceptor(mWindow, hook_dequeueBuffer, this);LOG_ALWAYS_FATAL_IF(result != NO_ERROR, "Failed to set dequeueBuffer interceptor: error = %d",result);result = ANativeWindow_setQueueBufferInterceptor(mWindow, hook_queueBuffer, this);LOG_ALWAYS_FATAL_IF(result != NO_ERROR, "Failed to set queueBuffer interceptor: error = %d",result);result = ANativeWindow_setPerformInterceptor(mWindow, hook_perform, this);LOG_ALWAYS_FATAL_IF(result != NO_ERROR, "Failed to set perform interceptor: error = %d",result);result = ANativeWindow_setQueryInterceptor(mWindow, hook_query, this);LOG_ALWAYS_FATAL_IF(result != NO_ERROR, "Failed to set query interceptor: error = %d",result);
}

ANativeWindow提供了ANativeWindow_setCancelBufferInterceptor、ANativeWindow_setDequeueBufferInterceptor、ANativeWindow_setQueueBufferInterceptor等扩展接口,ReliableSurface分别用自己的hook_cancelBuffer、hook_dequeueBuffer、hook_queueBuffer等方法替代native层Surface的实现。

//ANativeWindow.cpp
int ANativeWindow_setDequeueBufferInterceptor(ANativeWindow* window,ANativeWindow_dequeueBufferInterceptor interceptor,void* data) {return window->perform(window, NATIVE_WINDOW_SET_DEQUEUE_INTERCEPTOR, interceptor, data);
}

ANativeWindow提供的扩展接口。

//window.h
int     (*perform)(struct ANativeWindow* window,int operation, ... );

Surface作为ANativeWindow的接口实现,实现了perform方法。

//Surface.cpp
int Surface::perform(int operation, va_list args)
{int res = NO_ERROR;switch (operation) {case NATIVE_WINDOW_SET_DEQUEUE_INTERCEPTOR:res = dispatchAddDequeueInterceptor(args);break;}return res;
}
int Surface::dispatchAddDequeueInterceptor(va_list args) {ANativeWindow_dequeueBufferInterceptor interceptor =va_arg(args, ANativeWindow_dequeueBufferInterceptor);void* data = va_arg(args, void*);std::lock_guard<std::shared_mutex> lock(mInterceptorMutex);mDequeueInterceptor = interceptor;mDequeueInterceptorData = data;return NO_ERROR;
}

将ReliableSurface的hook_dequeueBuffer实现赋值给了Surface的mDequeueInterceptor变量,Surface在hook_dequeueBuffer时检查拦截器是否为空,如果不为空的话调用拦截器的操作。

//Surface.cpp
int Surface::hook_dequeueBuffer(ANativeWindow* window,ANativeWindowBuffer** buffer, int* fenceFd) {Surface* c = getSelf(window);{std::shared_lock<std::shared_mutex> lock(c->mInterceptorMutex);if (c->mDequeueInterceptor != nullptr) {auto interceptor = c->mDequeueInterceptor;auto data = c->mDequeueInterceptorData;return interceptor(window, Surface::dequeueBufferInternal, data, buffer, fenceFd);}}return c->dequeueBuffer(buffer, fenceFd);
}

Surface的hook_dequeueBuffer在其构造函数中被绑定到ANativeWindow的dequeueBuffer函数指针上,从此dequeueBuffer都会调用ReliableSurface动态插入的hook_dequeueBuffer方法。

4. IRenderPipeline

前面说到应用层ViewRootImple实例化Surface对象通过RenderProxy接口传递给hwui模块,CanvasContext、IRenderPipeline对象需要Surface对象开始图形绘制工作,安卓支持两种渲染管线,OpenGL和Vulkan,这里是OpenGL的实现SkiaOpenGLPipeline,SkiaOpenGLPipeline通过使用跨平台的接口EGL管理OpenGL ES的上下文,可以看作是OpenGL ES提供给应用的接口。

setSurface(mSurface)最终SkiaOpenGLPipeline通过EglManager调用eglCreateWindowSurface,将窗口对象mSurface作为参数,EGL 创建一个新的 EGLSurface 对象,并将其连接到窗口对象的 BufferQueue 的生产方接口,此后,渲染到该 EGLSurface 会导致一个缓冲区离开队列、进行渲染,然后排队等待消费方使用。

setSurface(null)!mSurface.isValid()时调用,判断当前是否需要保留或者丢弃buffer,最终通过eglSurfaceAttrib改变EGL的buffer行为。

eglCreateWindowSurface只是创建了一个EGLSurface,还需要等到应用请求提交当前帧eglSwapBuffersWithDamageKHR发出绘制命令才能看到绘制的画面。

4.1 EGLSurface

关注一下EGLSurface是怎么创建的,它和Surface的关系是什么。

//SkiaOpenGLPipeline.cpp
bool SkiaOpenGLPipeline::setSurface(ANativeWindow* surface, SwapBehavior swapBehavior) {if (surface) {mRenderThread.requireGlContext();auto newSurface = mEglManager.createSurface(surface, mColorMode, mSurfaceColorSpace);if (!newSurface) {return false;}mEglSurface = newSurface.unwrap();}
}

传递ANativeWindow* surface给EglManager。

Result<EGLSurface, EGLint> EglManager::createSurface(EGLNativeWindowType window,ColorMode colorMode,sk_sp<SkColorSpace> colorSpace) {EGLSurface surface = eglCreateWindowSurface(mEglDisplay, wideColorGamut ? mEglConfigWideGamut : mEglConfig, window, attribs);return surface;
}

注意看这里surface对象被从ANativeWindow类型转换成了EGLNativeWindowType类型,EGLNativeWindowType被定义在EGL模块。

//EGL/eglplatform.h
#elif defined(__ANDROID__) || defined(ANDROID)
struct ANativeWindow;
struct egl_native_pixmap_t;typedef void*                           EGLNativeDisplayType;
typedef struct egl_native_pixmap_t*     EGLNativePixmapType;
typedef struct ANativeWindow*           EGLNativeWindowType;
#elif defined(USE_OZONE)

EGL的eglplatform.h头文件定义了在Android平台,EGLNativeWindowType就是ANativeWindow*类型,安卓native层的Surface对象作为ANativeWindow的实现,被作为参数传递给eglCreateWindowSurface方法创建了EGLSurface对象,后续eglSwapBuffersWithDamageKHR交换缓冲区也是这个对象。

5. DrawFrameTask

//DrawFrameTask.cpp
void DrawFrameTask::run() {ATRACE_NAME("DrawFrame");bool canUnblockUiThread;bool canDrawThisFrame;{TreeInfo info(TreeInfo::MODE_FULL, *mContext);canUnblockUiThread = syncFrameState(info);canDrawThisFrame = info.out.canDrawThisFrame;if (mFrameCompleteCallback) {mContext->addFrameCompleteListener(std::move(mFrameCompleteCallback));mFrameCompleteCallback = nullptr;}}// Grab a copy of everything we needCanvasContext* context = mContext;std::function<void(int64_t)> callback = std::move(mFrameCallback);mFrameCallback = nullptr;// From this point on anything in "this" is *UNSAFE TO ACCESS*if (canUnblockUiThread) {unblockUiThread();}// Even if we aren't drawing this vsync pulse the next frame number will still be accurateif (CC_UNLIKELY(callback)) {context->enqueueFrameWork([callback, frameNr = context->getFrameNumber()]() { callback(frameNr); });}if (CC_LIKELY(canDrawThisFrame)) {context->draw();} else {// wait on fences so tasks don't overlap next framecontext->waitOnFences();}if (!canUnblockUiThread) {unblockUiThread();}
}

UI线程(主线程)在RenderThread添加一个新的任务,执行DrawFrameTask的run()方法,UI线程阻塞等待RenderThread从UI线程同步完绘制所需要的信息之后,包括各个RenderNode的DisplayList、RenderProperties等属性,同步完判读是否能unblockUiThread发出信号,UI线程才能退出继续执行其他任务,重点关注context->draw();方法。

void CanvasContext::draw() {Frame frame = mRenderPipeline->getFrame();	// dequeueBuffersetPresentTime();SkRect windowDirty = computeDirtyRect(frame, &dirty);bool drew = mRenderPipeline->draw(frame, windowDirty, dirty, mLightGeometry, &mLayerUpdateQueue,mContentDrawBounds, mOpaque, mLightInfo, mRenderNodes,&(profiler()));int64_t frameCompleteNr = getFrameNumber();waitOnFences();bool requireSwap = false;int error = OK;// queueBufferbool didSwap =mRenderPipeline->swapBuffers(frame, drew, windowDirty, mCurrentFrameInfo, &requireSwap);
}

CanvasContext::draw执行一系列渲染操作,将绘制结果呈现到显示设备上。

  1. 获取帧。mRenderPipeline->getFrame(),作为图形队列中的生产者,getFrame通过gui模块的Surface对象dequeueBuffer申请GraphicBuffer,Surface对象由上文的setSurface方法传递过来。

  2. 计算脏区域(需要更新的区域)。computeDirtyRect(frame, &dirty)

  3. 绘制当前帧。mRenderPipeline->draw,向申请的GraphicBuffer中填充数据。

  4. 等待所有任务完成。waitOnFences

  5. 交换缓冲区并提交渲染结果。mRenderPipeline->swapBuffers,填充完成后通过gui模块的Surface对象queueBuffer将GraphicBuffer加入队列中。

5.1 draw

mRenderPipeline->draw

void SkiaPipeline::renderFrame(const LayerUpdateQueue& layers, const SkRect& clip,const std::vector<sp<RenderNode>>& nodes, bool opaque,const Rect& contentDrawBounds, sk_sp<SkSurface> surface,const SkMatrix& preTransform) {// Initialize the canvas for the current frame, that might be a recording canvas if SKP// capture is enabled.SkCanvas* canvas = tryCapture(surface.get(), nodes[0].get(), layers);// draw all layers up frontrenderLayersImpl(layers, opaque);renderFrameImpl(clip, nodes, opaque, contentDrawBounds, canvas, preTransform);endCapture(surface.get());if (CC_UNLIKELY(Properties::debugOverdraw)) {renderOverdraw(clip, nodes, contentDrawBounds, surface, preTransform);}ATRACE_NAME("flush commands");surface->getCanvas()->flush();}
  1. tryCapture:Returns the canvas that records the drawing commands.
  2. renderFrameImpl:执行绘制命令。
  3. endCapture:Signal that the caller is done recording.
  4. surface->getCanvas()->flush();刷新fBytes缓存。

renderFrameImpl执行DisplayList记录的绘制操作,实际调用SkCanvas的绘制命令,例如canvas->drawRect(bounds, layerPaint),RecordingCanvas继承自SkCanvas,调用其onDrawRect方法:

void RecordingCanvas::onDrawRect(const SkRect& rect, const SkPaint& paint) {fDL->drawRect(rect, paint);
}

fDL是DisplayListData* fDL;对象

void DisplayListData::drawRect(const SkRect& rect, const SkPaint& paint) {this->push<DrawRect>(0, rect, paint);
}
template <typename T, typename... Args>
void* DisplayListData::push(size_t pod, Args&&... args) {size_t skip = SkAlignPtr(sizeof(T) + pod);SkASSERT(skip < (1 << 24));if (fUsed + skip > fReserved) {static_assert(SkIsPow2(SKLITEDL_PAGE), "This math needs updating for non-pow2.");// Next greater multiple of SKLITEDL_PAGE.fReserved = (fUsed + skip + SKLITEDL_PAGE) & ~(SKLITEDL_PAGE - 1);fBytes.realloc(fReserved);}SkASSERT(fUsed + skip <= fReserved);auto op = (T*)(fBytes.get() + fUsed);fUsed += skip;new (op) T{std::forward<Args>(args)...};op->type = (uint32_t)T::kType;op->skip = skip;return op + 1;
}

fBytes是SkAutoTMalloc<uint8_t> fBytes;,保存了所有绘制操作的内存空间,DisplayListData::push向其添加绘制操作,然后调用displayList->draw(canvas)读取保存的数据开始真正的绘制操作:

void DisplayListData::draw(SkCanvas* canvas) const {SkAutoCanvasRestore acr(canvas, false);this->map(draw_fns, canvas, canvas->getTotalMatrix());
}

draw_fn定义在"DisplayListOps.in"。

#define X(T)                                                    \[](const void* op, SkCanvas* c, const SkMatrix& original) { \((const T*)op)->draw(c, original);                      \},
static const draw_fn draw_fns[] = {
#include "DisplayListOps.in"
};
#undef X

DisplayListOps.in定义了所有的绘制方法,X(T)宏生成一个 lambda 表达式,将 const void* 类型的对象转换为 T 类型,并调用该类型的 draw 方法来执行绘制操作。

X(Flush)
X(Save)
X(Restore)...
X(Scale)
X(Translate)
X(ClipPath)
X(ClipRect)
X(ClipRRect)...
X(DrawPaint)
X(DrawBehind)
X(DrawPath)
X(DrawRect)...

例如DrawRect:

struct Op {uint32_t type : 8;uint32_t skip : 24;
};
struct DrawRect final : Op {static const auto kType = Type::DrawRect;DrawRect(const SkRect& rect, const SkPaint& paint) : rect(rect), paint(paint) {}SkRect rect;SkPaint paint;void draw(SkCanvas* c, const SkMatrix&) const { c->drawRect(rect, paint); }
};

DisplayListData::map是一个模板方法,遍历查找fBytes中是否存在Type::DrawRect,如果存在调用drawRect(rect, paint)

template <typename Fn, typename... Args>
inline void DisplayListData::map(const Fn fns[], Args... args) const {auto end = fBytes.get() + fUsed;for (const uint8_t* ptr = fBytes.get(); ptr < end;) {auto op = (const Op*)ptr;auto type = op->type;auto skip = op->skip;if (auto fn = fns[type]) {  // We replace no-op functions with nullptrsfn(op, args...);        // to avoid the overhead of a pointless call.}ptr += skip;}
}

5.2 swapBuffers

最终SkiaOpenGLPipeline通过EglManager调用eglSwapBuffersWithDamageKHR交换指定的脏区域的缓冲区内容提交当前帧,EGL 的工作机制是双缓冲模式,一个 Back Frame Buffer 和一个 Front Frame Buffer,正常绘制操作的目标都是 Back Frame Buffer,渲染完毕之后,调用eglSwapBuffersWithDamageKHR这个 API,会将绘制完毕的 Back Frame Buffer 与当前的 Front Frame Buffer 进行交换,buffer被EGL渲染完成。

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

第R4周:LSTM-火灾温度预测

&#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客&#x1f356; 原作者&#xff1a;K同学啊 文章目录 一、代码流程1、导入包&#xff0c;设置GPU2、导入数据3、数据集可视化4、数据集预处理5、设置X&#xff0c;y6、划分数据集7、构建模型8、定义训练函…

Spring 设计模式:经典设计模式

Spring 设计模式&#xff1a;经典设计模式 引言 Spring 框架广泛使用了经典设计模式。 这些模式在 Spring 内部发挥着重要作用。 通过理解这些设计模式在 Spring 中的应用&#xff0c;开发者可以更深入地掌握 Spring 框架的设计哲学和实现细节。 经典设计模式 控制反转&am…

现代企业架构白皮书(可以在线阅读完整PDF文件)

数据架构元模型综述 数据架构的内容元模型包括“结构”、“端口”两个部分&#xff0c;如下图所示&#xff1a; 结构部分用来对数据模型、数据处理建模&#xff0c;其中包括数据对象、数据组件 端口部分用来对数据模型的边界建模&#xff0c;其中包括数据服务 数据架构元模型…

krpano 实现文字热点中的三角形和竖杆

krpano 实现文字热点中的三角形和竖杆 实现文字热点中的三角形和竖杆 一个后端写前端真的是脑阔疼 一个后端写前端真的是脑阔疼 一个后端写前端真的是脑阔疼 实现文字热点中的三角形和竖杆 上图看效果 v&#xff1a;2549789059

Win10本地部署大语言模型ChatGLM2-6B

鸣谢《ChatGLM2-6B&#xff5c;开源本地化语言模型》作者PhiltreX 作者显卡为英伟达4060 安装程序 打开CMD命令行&#xff0c;在D盘新建目录openai.wiki if not exist D:\openai.wiki mkdir D:\openai.wiki 强制切换工作路径为D盘的openai.wiki文件夹。 cd /d D:\openai.wik…

互联网架构变迁:从 TCP/IP “呼叫” 到 NDN “内容分发” 的逐浪之旅

本文将给出关于互联网架构演进的一个不同视角。回顾一下互联网的核心理论基础产生的背景&#xff1a; 左边是典型的集中控制通信网络&#xff0c;很容易被摧毁&#xff0c;而右边的网络则没有单点问题&#xff0c;换句话说它很难被全部摧毁&#xff0c;与此同时&#xff0c;分…

priority_queue优先队列

目录 1. 最短路径算法&#xff08;Dijkstra算法&#xff09; 应用场景&#xff1a; 优先队列的作用&#xff1a; 2. 最小生成树算法&#xff08;Prim算法&#xff09; 应用场景&#xff1a; 优先队列的作用&#xff1a; 3. 哈夫曼编码&#xff08;Huffman Coding&#x…

vs2022编译webrtc步骤

1、主要步骤说明 概述&#xff1a;基础环境必须有&#xff0c;比如git&#xff0c;Powershell这些&#xff0c;就不写到下面了。 1.1 安装vs2022 1、选择使用C的桌面开发 2、 Windows 10 SDK安装10.0.20348.0 3、勾选MFC及ATL这两项 4、 安装完VS2022后&#xff0c;必须安…

如何评价deepseek-V3 VS OpenAI o1 自然语言处理成Sql的能力

DeepSeek-V3 介绍 在目前大模型主流榜单中&#xff0c;DeepSeek-V3 在开源模型中位列榜首&#xff0c;与世界上最先进的闭源模型不分伯仲。 准备工作&#xff1a; 笔者只演示实例o1 VS DeepSeek-V3两个模型&#xff0c;大家可以自行验证结果或者实验更多场景&#xff0c;同时…

9.4 visualStudio 2022 配置 cuda 和 torch (c++)

一、配置torch 1.Libtorch下载 该内容看了【Libtorch 一】libtorchwin10环境配置_vsixtorch-CSDN博客的博客&#xff0c;作为笔记用。我自己搭建后可以正常运行。 下载地址为windows系统下各种LibTorch下载地址_libtorch 百度云-CSDN博客 下载解压后的目录为&#xff1a; 2.vs…

Mysql--基础篇--多表查询(JOIN,笛卡尔积)

在MySQL中&#xff0c;多表查询&#xff08;也称为联表查询或JOIN操作&#xff09;是数据库操作中非常常见的需求。通过多表查询&#xff0c;你可以从多个表中获取相关数据&#xff0c;并根据一定的条件将它们组合在一起。MySQL支持多种类型的JOIN操作&#xff0c;每种JOIN都有…

gesp(C++四级)(11)洛谷:B4005:[GESP202406 四级] 黑白方块

gesp(C四级)&#xff08;11&#xff09;洛谷&#xff1a;B4005&#xff1a;[GESP202406 四级] 黑白方块 题目描述 小杨有一个 n n n 行 m m m 列的网格图&#xff0c;其中每个格子要么是白色&#xff0c;要么是黑色。对于网格图中的一个子矩形&#xff0c;小杨认为它是平衡的…

易于上手难于精通---关于游戏性的一点思考

1、小鸟、狙击、一闪&#xff0c;都是通过精准时机来逼迫玩家练习&#xff0c; 而弹道、出招时机等玩意&#xff0c;不是那么容易掌握的&#xff0c;需要反复的观察、反应与行动&#xff0c; 这也正是游戏性的体现&#xff0c; 玩家能感觉到一些朦胧的东西&#xff0c;但又不…

微信小程序——创建滑动颜色条

在微信小程序中&#xff0c;你可以使用 slider 组件来创建一个颜色滑动条。以下是一个简单的示例&#xff0c;展示了如何实现一个颜色滑动条&#xff0c;该滑动条会根据滑动位置改变背景颜色。 步骤一&#xff1a;创建小程序项目 首先&#xff0c;使用微信开发者工具创建一个新…

JVM实战—12.OOM的定位和解决

大纲 1.如何对系统的OOM异常进行监控和报警 2.如何在JVM内存溢出时自动dump内存快照 3.Metaspace区域内存溢出时应如何解决(OutOfMemoryError: Metaspace) 4.JVM栈内存溢出时应如何解决(StackOverflowError) 5.JVM堆内存溢出时应该如何解决(OutOfMemoryError: Java heap s…

Unity自定义编辑器:基于枚举类型动态显示属性

1.参考链接 2.应用 target并设置多选编辑 添加[CanEditMultipleObjects] using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEditor;[CustomEditor(typeof(LightsState))] [CanEditMultipleObjects] public class TestInspector :…

Qt重写webrtc的demo peerconnection

整个demo为&#xff1a; 可以选择多个编码方式&#xff1a; cmake_minimum_required(VERSION 3.5)project(untitled LANGUAGES CXX) set(CMAKE_CXX_STANDARD 20) set(CMAKE_INCLUDE_CURRENT_DIR ON)set(CMAKE_AUTOUIC ON) set(CMAKE_AUTOMOC ON) set(CMAKE_AUTORCC ON)set(CMA…

IOS HTTPS代理抓包工具使用教程

打开抓包软件 在设备列表中选择要抓包的 设备&#xff0c;然后选择功能区域中的 HTTPS代理抓包。根据弹出的提示按照配置文件和设置手机代理。如果是本机则会自动配置&#xff0c;只需要按照提醒操作即可。 iOS 抓包准备 通过 USB 将 iOS 设备连接到电脑&#xff0c;设备需解…

《机器学习》——支持向量机(SVM)

文章目录 什么是支持向量机&#xff1f;基本原理数学模型 支持向量机模型模型参数属性信息 支持向量机实例&#xff08;1&#xff09;实例步骤读取数据可视化原始数据使用支持向量机训练可视化支持向量机结果完整代码 支持向量机实例&#xff08;2&#xff09;实例步骤导入数据…

高级软件工程-复习

高级软件工程复习 坐标国科大&#xff0c;下面是老师说的考试重点。 Ruby编程语言的一些特征需要了解要能读得懂Ruby程序Git的基本命令操作知道Rails的MVC工作机理需要清楚&#xff0c;Model, Controller, View各司什么职责明白BDD的User Story需要会写&#xff0c;SMART要求能…