Skip to content

Commit 83e3df6

Browse files
committed
Add CpuTextureManager class
1 parent ef67e3d commit 83e3df6

File tree

5 files changed

+274
-0
lines changed

5 files changed

+274
-0
lines changed

src/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,8 @@ qt_add_qml_module(scratchcpp-render
7070
textbubbleshape.h
7171
textbubblepainter.cpp
7272
textbubblepainter.h
73+
cputexturemanager.cpp
74+
cputexturemanager.h
7375
blocks/penextension.cpp
7476
blocks/penextension.h
7577
blocks/penblocks.cpp

src/cputexturemanager.cpp

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
// SPDX-License-Identifier: LGPL-3.0-or-later
2+
3+
#include "cputexturemanager.h"
4+
#include "texture.h"
5+
6+
using namespace scratchcpprender;
7+
8+
CpuTextureManager::CpuTextureManager()
9+
{
10+
}
11+
12+
CpuTextureManager::~CpuTextureManager()
13+
{
14+
for (const auto &[handle, data] : m_textureData)
15+
delete[] data;
16+
}
17+
18+
GLubyte *CpuTextureManager::getTextureData(const Texture &texture)
19+
{
20+
if (!texture.isValid())
21+
return nullptr;
22+
23+
const GLuint handle = texture.handle();
24+
auto it = m_textureData.find(handle);
25+
26+
if (it == m_textureData.cend()) {
27+
if (addTexture(texture))
28+
return m_textureData[handle];
29+
else
30+
return nullptr;
31+
} else
32+
return it->second;
33+
}
34+
35+
const std::vector<QPoint> &CpuTextureManager::getTextureConvexHullPoints(const Texture &texture)
36+
{
37+
static const std::vector<QPoint> empty;
38+
39+
if (!texture.isValid())
40+
return empty;
41+
42+
const GLuint handle = texture.handle();
43+
auto it = m_convexHullPoints.find(handle);
44+
45+
if (it == m_convexHullPoints.cend()) {
46+
if (addTexture(texture))
47+
return m_convexHullPoints[handle];
48+
else
49+
return empty;
50+
} else
51+
return it->second;
52+
}
53+
54+
bool CpuTextureManager::addTexture(const Texture &texture)
55+
{
56+
if (!texture.isValid())
57+
return false;
58+
59+
const GLuint handle = texture.handle();
60+
const int width = texture.width();
61+
const int height = texture.height();
62+
63+
QOpenGLFunctions glF;
64+
glF.initializeOpenGLFunctions();
65+
66+
// Create a FBO for the texture
67+
unsigned int fbo;
68+
glF.glGenFramebuffers(1, &fbo);
69+
glF.glBindFramebuffer(GL_FRAMEBUFFER, fbo);
70+
glF.glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, handle, 0);
71+
72+
if (glF.glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
73+
qWarning() << "error: framebuffer incomplete (CpuTextureManager)";
74+
glF.glDeleteFramebuffers(1, &fbo);
75+
return false;
76+
}
77+
78+
// Read pixels
79+
GLubyte *pixels = new GLubyte[width * height * 4]; // 4 channels (RGBA)
80+
glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
81+
82+
// Flip vertically
83+
int rowSize = width * 4;
84+
GLubyte *tempRow = new GLubyte[rowSize];
85+
86+
for (size_t i = 0; i < height / 2; ++i) {
87+
size_t topRowIndex = i * rowSize;
88+
size_t bottomRowIndex = (height - 1 - i) * rowSize;
89+
90+
// Swap rows
91+
memcpy(tempRow, &pixels[topRowIndex], rowSize);
92+
memcpy(&pixels[topRowIndex], &pixels[bottomRowIndex], rowSize);
93+
memcpy(&pixels[bottomRowIndex], tempRow, rowSize);
94+
}
95+
96+
delete[] tempRow;
97+
98+
m_textureData[handle] = pixels;
99+
m_convexHullPoints[handle] = {};
100+
std::vector<QPoint> &hullPoints = m_convexHullPoints[handle];
101+
102+
// Get convex hull points
103+
for (int y = 0; y < height; y++) {
104+
for (int x = 0; x < width; x++) {
105+
int index = (y * width + x) * 4; // 4 channels (RGBA)
106+
107+
// Check alpha channel
108+
if (pixels[index + 3] > 0)
109+
hullPoints.push_back(QPoint(x, y));
110+
}
111+
}
112+
113+
// Cleanup
114+
glF.glBindFramebuffer(GL_FRAMEBUFFER, 0);
115+
glF.glDeleteFramebuffers(1, &fbo);
116+
117+
return true;
118+
}

src/cputexturemanager.h

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
// SPDX-License-Identifier: LGPL-3.0-or-later
2+
3+
#pragma once
4+
5+
#include <QPoint>
6+
#include <QtOpenGL>
7+
#include <unordered_map>
8+
9+
namespace scratchcpprender
10+
{
11+
12+
class Texture;
13+
14+
class CpuTextureManager
15+
{
16+
public:
17+
CpuTextureManager();
18+
~CpuTextureManager();
19+
20+
GLubyte *getTextureData(const Texture &texture);
21+
const std::vector<QPoint> &getTextureConvexHullPoints(const Texture &texture);
22+
23+
private:
24+
bool addTexture(const Texture &texture);
25+
26+
std::unordered_map<GLuint, GLubyte *> m_textureData;
27+
std::unordered_map<GLuint, std::vector<QPoint>> m_convexHullPoints;
28+
};
29+
30+
} // namespace scratchcpprender

test/texture/CMakeLists.txt

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
# texture_test
12
add_executable(
23
texture_test
34
texture_test.cpp
@@ -12,3 +13,19 @@ target_link_libraries(
1213

1314
add_test(texture_test)
1415
gtest_discover_tests(texture_test)
16+
17+
# cputexturemanager_test
18+
add_executable(
19+
cputexturemanager_test
20+
cputexturemanager_test.cpp
21+
)
22+
23+
target_link_libraries(
24+
cputexturemanager_test
25+
GTest::gtest_main
26+
scratchcpp-render
27+
${QT_LIBS}
28+
)
29+
30+
add_test(cputexturemanager_test)
31+
gtest_discover_tests(cputexturemanager_test)
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
#include <cputexturemanager.h>
2+
#include <texture.h>
3+
#include <qnanopainter.h>
4+
5+
#include "../common.h"
6+
7+
using namespace scratchcpprender;
8+
9+
class CpuTextureManagerTest : public testing::Test
10+
{
11+
public:
12+
void createContextAndSurface(QOpenGLContext *context, QOffscreenSurface *surface)
13+
{
14+
QSurfaceFormat surfaceFormat;
15+
surfaceFormat.setMajorVersion(4);
16+
surfaceFormat.setMinorVersion(3);
17+
18+
context->setFormat(surfaceFormat);
19+
context->create();
20+
ASSERT_TRUE(context->isValid());
21+
22+
surface->setFormat(surfaceFormat);
23+
surface->create();
24+
ASSERT_TRUE(surface->isValid());
25+
26+
context->makeCurrent(surface);
27+
ASSERT_EQ(QOpenGLContext::currentContext(), context);
28+
}
29+
};
30+
31+
class ImagePainter
32+
{
33+
public:
34+
ImagePainter(QNanoPainter *painter, const QString &fileName)
35+
{
36+
QOpenGLFramebufferObjectFormat format;
37+
format.setAttachment(QOpenGLFramebufferObject::CombinedDepthStencil);
38+
39+
// Begin painting
40+
m_fbo = std::make_unique<QOpenGLFramebufferObject>(4, 6, format);
41+
m_fbo->bind();
42+
painter->beginFrame(m_fbo->width(), m_fbo->height());
43+
44+
// Paint
45+
QNanoImage image = QNanoImage::fromCache(painter, fileName);
46+
painter->drawImage(image, 0, 0);
47+
painter->endFrame();
48+
}
49+
50+
~ImagePainter() { m_fbo->release(); }
51+
52+
QOpenGLFramebufferObject *fbo() const { return m_fbo.get(); };
53+
54+
private:
55+
std::unique_ptr<QOpenGLFramebufferObject> m_fbo;
56+
};
57+
58+
TEST_F(CpuTextureManagerTest, TextureDataAndHullPoints)
59+
{
60+
static const GLubyte refData1[] = {
61+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 0, 255, 255, 255, 128, 128, 255, 0, 0, 0, 0, 0, 0, 128, 255, 0, 0, 0, 0, 87, 149, 87, 149,
62+
0, 0, 0, 0, 128, 0, 128, 255, 128, 128, 255, 255, 128, 128, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
63+
};
64+
65+
static const GLubyte refData2[] = {
66+
0, 0, 57, 255, 10, 0, 50, 255, 43, 0, 35, 255, 60, 0, 28, 255, 0, 0, 55, 255, 39, 15, 73, 255, 137, 85, 133, 255, 207, 142, 182, 255,
67+
10, 0, 50, 255, 23, 4, 50, 255, 4, 0, 7, 255, 204, 204, 196, 255, 11, 0, 35, 255, 59, 46, 76, 255, 135, 146, 140, 255, 99, 123, 99, 255,
68+
4, 0, 12, 255, 1, 0, 7, 255, 0, 1, 0, 255, 0, 3, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255
69+
};
70+
71+
static const std::vector<QPoint> refHullPoints1 = { { 1, 1 }, { 2, 1 }, { 3, 1 }, { 1, 2 }, { 3, 2 }, { 1, 3 }, { 2, 3 }, { 3, 3 } };
72+
73+
static const std::vector<QPoint> refHullPoints2 = {
74+
{ 0, 0 }, { 1, 0 }, { 2, 0 }, { 3, 0 }, { 0, 1 }, { 1, 1 }, { 2, 1 }, { 3, 1 }, { 0, 2 }, { 1, 2 }, { 2, 2 }, { 3, 2 },
75+
{ 0, 3 }, { 1, 3 }, { 2, 3 }, { 3, 3 }, { 0, 4 }, { 1, 4 }, { 2, 4 }, { 3, 4 }, { 0, 5 }, { 1, 5 }, { 2, 5 }, { 3, 5 }
76+
};
77+
78+
// Create OpenGL context
79+
QOpenGLContext context;
80+
QOffscreenSurface surface;
81+
createContextAndSurface(&context, &surface);
82+
83+
// Paint images
84+
QNanoPainter painter;
85+
ImagePainter imgPainter1(&painter, "image.png");
86+
ImagePainter imgPainter2(&painter, "image.jpg");
87+
88+
// Read texture data
89+
CpuTextureManager manager;
90+
91+
for (int i = 0; i < 2; i++) {
92+
Texture texture1(imgPainter1.fbo()->texture(), imgPainter1.fbo()->size());
93+
GLubyte *data = manager.getTextureData(texture1);
94+
ASSERT_EQ(memcmp(data, refData1, 96), 0);
95+
const auto &hullPoints1 = manager.getTextureConvexHullPoints(texture1);
96+
ASSERT_EQ(hullPoints1, refHullPoints1);
97+
98+
Texture texture2(imgPainter2.fbo()->texture(), imgPainter2.fbo()->size());
99+
data = manager.getTextureData(texture2);
100+
ASSERT_EQ(memcmp(data, refData2, 96), 0);
101+
const auto &hullPoints2 = manager.getTextureConvexHullPoints(texture2);
102+
ASSERT_EQ(hullPoints2, refHullPoints2);
103+
}
104+
105+
// Cleanup
106+
context.doneCurrent();
107+
}

0 commit comments

Comments
 (0)