[core] Switch to the new OpenGL abstraction
This patch will make Mapbox GL Core never use OpenGL directly. We should consider locking into OpenGL ES 2.0 to simplify the code path and remove #ifdefs.
This commit is contained in:
@@ -81,6 +81,7 @@
|
||||
"platform/android/src/style/value.cpp",
|
||||
"platform/android/src/text/collator.cpp",
|
||||
"platform/android/src/text/local_glyph_rasterizer.cpp",
|
||||
"platform/android/src/gl_functions.cpp",
|
||||
"platform/android/src/thread.cpp",
|
||||
"platform/android/src/timer.cpp",
|
||||
"platform/android/src/unaccent.cpp",
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
"//": "This file can be edited manually and is the canonical source.",
|
||||
"sources": [
|
||||
"platform/darwin/src/collator.mm",
|
||||
"platform/darwin/src/gl_functions.cpp",
|
||||
"platform/darwin/src/headless_backend_eagl.mm",
|
||||
"platform/darwin/src/image.mm",
|
||||
"platform/darwin/src/local_glyph_rasterizer.mm",
|
||||
|
||||
@@ -41,6 +41,9 @@ macro(mbgl_platform_core)
|
||||
endif()
|
||||
|
||||
target_sources(mbgl-core
|
||||
# GL
|
||||
PRIVATE platform/linux/src/gl_functions.cpp
|
||||
|
||||
# Misc
|
||||
PRIVATE platform/default/src/mbgl/util/logging_stderr.cpp
|
||||
PRIVATE platform/default/src/mbgl/util/string_stdlib.cpp
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
"//": "This file can be edited manually and is the canonical source.",
|
||||
"sources": [
|
||||
"platform/darwin/src/collator.mm",
|
||||
"platform/darwin/src/gl_functions.cpp",
|
||||
"platform/darwin/src/image.mm",
|
||||
"platform/darwin/src/local_glyph_rasterizer.mm",
|
||||
"platform/darwin/src/logging_nslog.mm",
|
||||
|
||||
@@ -6,9 +6,9 @@ macro(mbgl_platform_core)
|
||||
)
|
||||
|
||||
target_include_directories(mbgl-core
|
||||
PUBLIC platform/default/include
|
||||
PRIVATE platform/qt
|
||||
PRIVATE platform/qt/include
|
||||
PUBLIC platform/default/include
|
||||
)
|
||||
|
||||
target_link_libraries(mbgl-core PRIVATE
|
||||
|
||||
@@ -38,6 +38,7 @@ set(MBGL_QT_CORE_FILES
|
||||
PRIVATE platform/qt/src/timer.cpp
|
||||
PRIVATE platform/qt/src/timer_impl.hpp
|
||||
PRIVATE platform/qt/src/utf.cpp
|
||||
PRIVATE platform/qt/src/gl_functions.cpp
|
||||
|
||||
PRIVATE platform/default/src/mbgl/text/collator.cpp
|
||||
PRIVATE platform/default/src/mbgl/text/unaccent.cpp
|
||||
|
||||
@@ -23,7 +23,6 @@
|
||||
"src/mbgl/gl/debugging.cpp",
|
||||
"src/mbgl/gl/debugging_extension.cpp",
|
||||
"src/mbgl/gl/depth_mode.cpp",
|
||||
"src/mbgl/gl/gl.cpp",
|
||||
"src/mbgl/gl/object.cpp",
|
||||
"src/mbgl/gl/stencil_mode.cpp",
|
||||
"src/mbgl/gl/uniform.cpp",
|
||||
@@ -50,6 +49,7 @@
|
||||
"src/mbgl/map/transform.cpp",
|
||||
"src/mbgl/map/transform_state.cpp",
|
||||
"src/mbgl/math/log2.cpp",
|
||||
"src/mbgl/platform/gl_functions.cpp",
|
||||
"src/mbgl/programs/background_program.cpp",
|
||||
"src/mbgl/programs/binary_program.cpp",
|
||||
"src/mbgl/programs/circle_program.cpp",
|
||||
@@ -336,6 +336,7 @@
|
||||
"mbgl/math/log2.hpp": "include/mbgl/math/log2.hpp",
|
||||
"mbgl/math/minmax.hpp": "include/mbgl/math/minmax.hpp",
|
||||
"mbgl/math/wrap.hpp": "include/mbgl/math/wrap.hpp",
|
||||
"mbgl/platform/gl_functions.hpp": "include/mbgl/platform/gl_functions.hpp",
|
||||
"mbgl/renderer/backend_scope.hpp": "include/mbgl/renderer/backend_scope.hpp",
|
||||
"mbgl/renderer/mode.hpp": "include/mbgl/renderer/mode.hpp",
|
||||
"mbgl/renderer/query.hpp": "include/mbgl/renderer/query.hpp",
|
||||
@@ -502,12 +503,12 @@
|
||||
"mbgl/gl/cull_face_mode.hpp": "src/mbgl/gl/cull_face_mode.hpp",
|
||||
"mbgl/gl/debugging.hpp": "src/mbgl/gl/debugging.hpp",
|
||||
"mbgl/gl/debugging_extension.hpp": "src/mbgl/gl/debugging_extension.hpp",
|
||||
"mbgl/gl/defines.hpp": "src/mbgl/gl/defines.hpp",
|
||||
"mbgl/gl/depth_mode.hpp": "src/mbgl/gl/depth_mode.hpp",
|
||||
"mbgl/gl/draw_mode.hpp": "src/mbgl/gl/draw_mode.hpp",
|
||||
"mbgl/gl/extension.hpp": "src/mbgl/gl/extension.hpp",
|
||||
"mbgl/gl/features.hpp": "src/mbgl/gl/features.hpp",
|
||||
"mbgl/gl/framebuffer.hpp": "src/mbgl/gl/framebuffer.hpp",
|
||||
"mbgl/gl/gl.hpp": "src/mbgl/gl/gl.hpp",
|
||||
"mbgl/gl/index_buffer.hpp": "src/mbgl/gl/index_buffer.hpp",
|
||||
"mbgl/gl/object.hpp": "src/mbgl/gl/object.hpp",
|
||||
"mbgl/gl/primitives.hpp": "src/mbgl/gl/primitives.hpp",
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
#include <mbgl/gl/attribute.hpp>
|
||||
#include <mbgl/gl/context.hpp>
|
||||
#include <mbgl/gl/gl.hpp>
|
||||
#include <mbgl/gl/defines.hpp>
|
||||
|
||||
namespace mbgl {
|
||||
namespace gl {
|
||||
|
||||
using namespace platform;
|
||||
|
||||
void bindAttributeLocation(Context& context, ProgramID id, AttributeLocation location, const char* name) {
|
||||
// We're using sequentially numberered attribute locations starting with 0. Therefore we can use
|
||||
// the location as a proxy for the number of attributes.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#include <mbgl/gl/color_mode.hpp>
|
||||
#include <mbgl/gl/gl.hpp>
|
||||
#include <mbgl/gl/defines.hpp>
|
||||
#include <mbgl/util/traits.hpp>
|
||||
|
||||
namespace mbgl {
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
#include <mbgl/gl/context.hpp>
|
||||
#include <mbgl/gl/gl.hpp>
|
||||
#include <mbgl/gl/debugging_extension.hpp>
|
||||
#include <mbgl/gl/vertex_array_extension.hpp>
|
||||
#include <mbgl/gl/program_binary_extension.hpp>
|
||||
@@ -12,6 +11,8 @@
|
||||
namespace mbgl {
|
||||
namespace gl {
|
||||
|
||||
using namespace platform;
|
||||
|
||||
static_assert(underlying_type(ShaderType::Vertex) == GL_VERTEX_SHADER, "OpenGL type mismatch");
|
||||
static_assert(underlying_type(ShaderType::Fragment) == GL_FRAGMENT_SHADER, "OpenGL type mismatch");
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
#include <mbgl/gl/depth_mode.hpp>
|
||||
#include <mbgl/gl/stencil_mode.hpp>
|
||||
#include <mbgl/gl/color_mode.hpp>
|
||||
#include <mbgl/platform/gl_functions.hpp>
|
||||
#include <mbgl/util/noncopyable.hpp>
|
||||
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#include <mbgl/gl/cull_face_mode.hpp>
|
||||
#include <mbgl/gl/gl.hpp>
|
||||
#include <mbgl/gl/defines.hpp>
|
||||
#include <mbgl/util/traits.hpp>
|
||||
|
||||
namespace mbgl {
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
namespace mbgl {
|
||||
namespace gl {
|
||||
|
||||
using namespace platform;
|
||||
|
||||
#ifndef NDEBUG
|
||||
|
||||
DebugGroup::DebugGroup(const Context& context_, const std::string& name) : context(context_) {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <mbgl/gl/extension.hpp>
|
||||
#include <mbgl/gl/gl.hpp>
|
||||
#include <mbgl/platform/gl_functions.hpp>
|
||||
|
||||
#define GL_DEBUG_OUTPUT_SYNCHRONOUS 0x8242
|
||||
#define GL_DEBUG_NEXT_LOGGED_MESSAGE_LENGTH 0x8243
|
||||
@@ -51,6 +51,8 @@ namespace mbgl {
|
||||
namespace gl {
|
||||
namespace extension {
|
||||
|
||||
using namespace platform;
|
||||
|
||||
class Debugging {
|
||||
public:
|
||||
using Callback = void (*)(GLenum source,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#include <mbgl/gl/depth_mode.hpp>
|
||||
#include <mbgl/gl/gl.hpp>
|
||||
#include <mbgl/gl/defines.hpp>
|
||||
#include <mbgl/util/traits.hpp>
|
||||
|
||||
namespace mbgl {
|
||||
|
||||
@@ -1,71 +0,0 @@
|
||||
#include <mbgl/gl/gl.hpp>
|
||||
#include <mbgl/util/string.hpp>
|
||||
#include <mbgl/util/util.hpp>
|
||||
|
||||
namespace mbgl {
|
||||
namespace gl {
|
||||
|
||||
namespace {
|
||||
|
||||
MBGL_CONSTEXPR const char* stringFromError(GLenum err) {
|
||||
switch (err) {
|
||||
case GL_INVALID_ENUM:
|
||||
return "GL_INVALID_ENUM";
|
||||
|
||||
case GL_INVALID_VALUE:
|
||||
return "GL_INVALID_VALUE";
|
||||
|
||||
case GL_INVALID_OPERATION:
|
||||
return "GL_INVALID_OPERATION";
|
||||
|
||||
case GL_INVALID_FRAMEBUFFER_OPERATION:
|
||||
return "GL_INVALID_FRAMEBUFFER_OPERATION";
|
||||
|
||||
case GL_OUT_OF_MEMORY:
|
||||
return "GL_OUT_OF_MEMORY";
|
||||
|
||||
#ifdef GL_TABLE_TOO_LARGE
|
||||
case GL_TABLE_TOO_LARGE:
|
||||
return "GL_TABLE_TOO_LARGE";
|
||||
#endif
|
||||
|
||||
#ifdef GL_STACK_OVERFLOW
|
||||
case GL_STACK_OVERFLOW:
|
||||
return "GL_STACK_OVERFLOW";
|
||||
#endif
|
||||
|
||||
#ifdef GL_STACK_UNDERFLOW
|
||||
case GL_STACK_UNDERFLOW:
|
||||
return "GL_STACK_UNDERFLOW";
|
||||
#endif
|
||||
|
||||
#ifdef GL_CONTEXT_LOST
|
||||
case GL_CONTEXT_LOST:
|
||||
return "GL_CONTEXT_LOST";
|
||||
#endif
|
||||
|
||||
default:
|
||||
return "GL_UNKNOWN";
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void checkError(const char* cmd, const char* file, int line) {
|
||||
// fprintf(stderr, "cmd: %s\n", cmd);
|
||||
GLenum err = GL_NO_ERROR;
|
||||
if ((err = glGetError()) != GL_NO_ERROR) {
|
||||
std::string message = std::string(cmd) + ": Error " + stringFromError(err);
|
||||
|
||||
// Check for further errors
|
||||
while ((err = glGetError()) != GL_NO_ERROR) {
|
||||
message += ", ";
|
||||
message += stringFromError(err);
|
||||
}
|
||||
|
||||
throw Error(message + " at " + file + ":" + util::toString(line));
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace gl
|
||||
} // namespace mbgl
|
||||
@@ -1,24 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <mbgl/gl/gl_impl.hpp>
|
||||
|
||||
#include <stdexcept>
|
||||
#include <limits>
|
||||
|
||||
namespace mbgl {
|
||||
namespace gl {
|
||||
|
||||
struct Error : std::runtime_error {
|
||||
using std::runtime_error::runtime_error;
|
||||
};
|
||||
|
||||
void checkError(const char *cmd, const char *file, int line);
|
||||
|
||||
#ifndef NDEBUG
|
||||
#define MBGL_CHECK_ERROR(cmd) ([&]() { struct __MBGL_C_E { ~__MBGL_C_E() noexcept(false) { ::mbgl::gl::checkError(#cmd, __FILE__, __LINE__); } } __MBGL_C_E; return cmd; }())
|
||||
#else
|
||||
#define MBGL_CHECK_ERROR(cmd) (cmd)
|
||||
#endif
|
||||
|
||||
} // namespace gl
|
||||
} // namespace mbgl
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
#include <mbgl/gl/features.hpp>
|
||||
#include <mbgl/gl/extension.hpp>
|
||||
#include <mbgl/gl/gl.hpp>
|
||||
#include <mbgl/gl/defines.hpp>
|
||||
|
||||
#if MBGL_HAS_BINARY_PROGRAMS
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#include <mbgl/gl/stencil_mode.hpp>
|
||||
#include <mbgl/gl/gl.hpp>
|
||||
#include <mbgl/gl/defines.hpp>
|
||||
#include <mbgl/util/traits.hpp>
|
||||
|
||||
namespace mbgl {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include <mbgl/gl/uniform.hpp>
|
||||
#include <mbgl/gl/gl.hpp>
|
||||
#include <mbgl/gl/defines.hpp>
|
||||
#include <mbgl/platform/gl_functions.hpp>
|
||||
#include <mbgl/util/color.hpp>
|
||||
#include <mbgl/util/size.hpp>
|
||||
#include <mbgl/util/convert.hpp>
|
||||
@@ -9,6 +10,8 @@
|
||||
namespace mbgl {
|
||||
namespace gl {
|
||||
|
||||
using namespace platform;
|
||||
|
||||
UniformLocation uniformLocation(ProgramID id, const char* name) {
|
||||
return MBGL_CHECK_ERROR(glGetUniformLocation(id, name));
|
||||
}
|
||||
@@ -101,8 +104,7 @@ ActiveUniforms activeUniforms(ProgramID id) {
|
||||
GLint size;
|
||||
GLenum type;
|
||||
for (GLint index = 0; index < count; index++) {
|
||||
MBGL_CHECK_ERROR(
|
||||
glGetActiveUniform(id, index, maxLength, &length, &size, &type, name.get()));
|
||||
MBGL_CHECK_ERROR(glGetActiveUniform(id, index, maxLength, &length, &size, &type, name.get()));
|
||||
active.emplace(
|
||||
std::string{ name.get(), static_cast<size_t>(length) },
|
||||
ActiveUniform{ static_cast<size_t>(size), static_cast<UniformDataType>(type) });
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
#include <mbgl/gl/value.hpp>
|
||||
#include <mbgl/gl/gl.hpp>
|
||||
#include <mbgl/gl/context.hpp>
|
||||
#include <mbgl/gl/vertex_array_extension.hpp>
|
||||
|
||||
@@ -7,14 +6,12 @@ namespace mbgl {
|
||||
namespace gl {
|
||||
namespace value {
|
||||
|
||||
using namespace platform;
|
||||
|
||||
const constexpr ClearDepth::Type ClearDepth::Default;
|
||||
|
||||
void ClearDepth::Set(const Type& value) {
|
||||
#if MBGL_USE_GLES2
|
||||
MBGL_CHECK_ERROR(glClearDepthf(value));
|
||||
#else
|
||||
MBGL_CHECK_ERROR(glClearDepth(value));
|
||||
#endif
|
||||
}
|
||||
|
||||
ClearDepth::Type ClearDepth::Get() {
|
||||
@@ -130,11 +127,7 @@ StencilOp::Type StencilOp::Get() {
|
||||
const constexpr DepthRange::Type DepthRange::Default;
|
||||
|
||||
void DepthRange::Set(const Type& value) {
|
||||
#if MBGL_USE_GLES2
|
||||
MBGL_CHECK_ERROR(glDepthRangef(value.min, value.max));
|
||||
#else
|
||||
MBGL_CHECK_ERROR(glDepthRange(value.min, value.max));
|
||||
#endif
|
||||
}
|
||||
|
||||
DepthRange::Type DepthRange::Get() {
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include <mbgl/gl/color_mode.hpp>
|
||||
#include <mbgl/gl/cull_face_mode.hpp>
|
||||
#include <mbgl/gl/attribute.hpp>
|
||||
#include <mbgl/platform/gl_functions.hpp>
|
||||
#include <mbgl/util/color.hpp>
|
||||
#include <mbgl/util/size.hpp>
|
||||
#include <mbgl/util/range.hpp>
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
#include <mbgl/gl/vertex_array.hpp>
|
||||
#include <mbgl/gl/context.hpp>
|
||||
#include <mbgl/gl/gl.hpp>
|
||||
|
||||
namespace mbgl {
|
||||
namespace gl {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <mbgl/gl/extension.hpp>
|
||||
#include <mbgl/gl/gl.hpp>
|
||||
#include <mbgl/gl/defines.hpp>
|
||||
|
||||
namespace mbgl {
|
||||
namespace gl {
|
||||
@@ -25,11 +25,11 @@ public:
|
||||
{ "GL_APPLE_vertex_array_object", "glGenVertexArraysAPPLE" } })) {
|
||||
}
|
||||
|
||||
const ExtensionFunction<void(GLuint array)> bindVertexArray;
|
||||
const ExtensionFunction<void(platform::GLuint array)> bindVertexArray;
|
||||
|
||||
const ExtensionFunction<void(GLsizei n, const GLuint* arrays)> deleteVertexArrays;
|
||||
const ExtensionFunction<void(platform::GLsizei n, const platform::GLuint* arrays)> deleteVertexArrays;
|
||||
|
||||
const ExtensionFunction<void(GLsizei n, GLuint* arrays)> genVertexArrays;
|
||||
const ExtensionFunction<void(platform::GLsizei n, platform::GLuint* arrays)> genVertexArrays;
|
||||
};
|
||||
|
||||
} // namespace extension
|
||||
|
||||
@@ -3,9 +3,9 @@
|
||||
#include <mbgl/renderer/backend_scope.hpp>
|
||||
#include <mbgl/renderer/renderer_backend.hpp>
|
||||
#include <mbgl/renderer/bucket.hpp>
|
||||
#include <mbgl/platform/gl_functions.hpp>
|
||||
#include <mbgl/style/layers/custom_layer_impl.hpp>
|
||||
#include <mbgl/map/transform_state.hpp>
|
||||
#include <mbgl/gl/gl.hpp>
|
||||
#include <mbgl/util/mat4.hpp>
|
||||
|
||||
namespace mbgl {
|
||||
@@ -15,7 +15,7 @@ using namespace style;
|
||||
RenderCustomLayer::RenderCustomLayer(Immutable<style::CustomLayer::Impl> _impl)
|
||||
: RenderLayer(std::move(_impl)), host(impl().host) {
|
||||
assert(BackendScope::exists());
|
||||
host->initialize();
|
||||
MBGL_CHECK_ERROR(host->initialize());
|
||||
}
|
||||
|
||||
RenderCustomLayer::~RenderCustomLayer() {
|
||||
@@ -23,7 +23,7 @@ RenderCustomLayer::~RenderCustomLayer() {
|
||||
if (contextDestroyed) {
|
||||
host->contextLost();
|
||||
} else {
|
||||
host->deinitialize();
|
||||
MBGL_CHECK_ERROR(host->deinitialize());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
#include <mbgl/test/util.hpp>
|
||||
|
||||
#include <mbgl/gl/gl.hpp>
|
||||
#include <mbgl/map/map.hpp>
|
||||
#include <mbgl/util/shared_thread_pool.hpp>
|
||||
#include <mbgl/storage/default_file_source.hpp>
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
#include <mbgl/test/util.hpp>
|
||||
|
||||
#include <mbgl/gl/gl.hpp>
|
||||
#include <mbgl/platform/gl_functions.hpp>
|
||||
#include <mbgl/map/map.hpp>
|
||||
#include <mbgl/util/default_thread_pool.hpp>
|
||||
#include <mbgl/storage/default_file_source.hpp>
|
||||
#include <mbgl/gl/defines.hpp>
|
||||
#include <mbgl/gl/headless_frontend.hpp>
|
||||
#include <mbgl/style/style.hpp>
|
||||
#include <mbgl/style/layers/custom_layer.hpp>
|
||||
@@ -14,6 +15,7 @@
|
||||
|
||||
using namespace mbgl;
|
||||
using namespace mbgl::style;
|
||||
using namespace mbgl::platform;
|
||||
|
||||
// Note that custom layers need to draw geometry with a z value of 1 to take advantage of
|
||||
// depth-based fragment culling.
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
#include <mbgl/test/util.hpp>
|
||||
|
||||
#include <mbgl/gl/gl.hpp>
|
||||
#include <mbgl/platform/gl_functions.hpp>
|
||||
#include <mbgl/gl/context.hpp>
|
||||
#include <mbgl/map/map.hpp>
|
||||
#include <mbgl/util/default_thread_pool.hpp>
|
||||
#include <mbgl/storage/default_file_source.hpp>
|
||||
#include <mbgl/gl/defines.hpp>
|
||||
#include <mbgl/gl/headless_frontend.hpp>
|
||||
#include <mbgl/style/style.hpp>
|
||||
#include <mbgl/style/layers/custom_layer.hpp>
|
||||
@@ -16,6 +17,7 @@
|
||||
|
||||
using namespace mbgl;
|
||||
using namespace mbgl::style;
|
||||
using namespace mbgl::platform;
|
||||
|
||||
static const GLchar* vertexShaderSource = R"MBGL_SHADER(
|
||||
#ifdef GL_ES
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
#include <mbgl/test/util.hpp>
|
||||
|
||||
#include <mbgl/gl/gl.hpp>
|
||||
#include <mbgl/platform/gl_functions.hpp>
|
||||
#include <mbgl/gl/context.hpp>
|
||||
#include <mbgl/gl/defines.hpp>
|
||||
#include <mbgl/gl/headless_backend.hpp>
|
||||
#include <mbgl/renderer/backend_scope.hpp>
|
||||
|
||||
#include <mbgl/util/offscreen_texture.hpp>
|
||||
|
||||
using namespace mbgl;
|
||||
using namespace mbgl::platform;
|
||||
|
||||
TEST(OffscreenTexture, EmptyRed) {
|
||||
HeadlessBackend backend({ 512, 256 });
|
||||
|
||||
Reference in New Issue
Block a user